From d0f776b44cf5bab2b4be0b727e16297df65c323e Mon Sep 17 00:00:00 2001 From: JustScott Date: Wed, 10 Jan 2024 22:32:01 -0600 Subject: [PATCH 001/101] Add a new Calendar App A basic calendar app that shows all the days dates in the current month along with the correlating week days, highlights the current day, and allows swiping left and right to increase and decrease the current month by one. Co-authored-by: thnikk Co-authored-by: Boteium --- src/CMakeLists.txt | 1 + src/displayapp/DisplayApp.cpp | 1 + src/displayapp/apps/Apps.h.in | 1 + src/displayapp/apps/CMakeLists.txt | 1 + src/displayapp/fonts/fonts.json | 2 +- src/displayapp/screens/Calendar.cpp | 88 +++++++++++++++++++++++++++++ src/displayapp/screens/Calendar.h | 42 ++++++++++++++ src/displayapp/screens/Symbols.h | 1 + 8 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 src/displayapp/screens/Calendar.cpp create mode 100644 src/displayapp/screens/Calendar.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fd8ece62..21413bdb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -377,6 +377,7 @@ list(APPEND SOURCE_FILES displayapp/screens/FirmwareUpdate.cpp displayapp/screens/Music.cpp displayapp/screens/Navigation.cpp + displayapp/screens/Calendar.cpp displayapp/screens/Metronome.cpp displayapp/screens/Motion.cpp displayapp/screens/Weather.cpp diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 9c7d87b2..adfa6171 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -19,6 +19,7 @@ #include "displayapp/screens/Metronome.h" #include "displayapp/screens/Music.h" #include "displayapp/screens/Navigation.h" +#include "displayapp/screens/Calendar.h" #include "displayapp/screens/Notifications.h" #include "displayapp/screens/SystemInfo.h" #include "displayapp/screens/Tile.h" diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index 2104a267..9fa4843a 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -23,6 +23,7 @@ namespace Pinetime { Twos, HeartRate, Navigation, + Calendar, StopWatch, Metronome, Motion, diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index d7858760..99b0a936 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -14,6 +14,7 @@ else () set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Metronome") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Navigation") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Weather") + set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Calendar") #set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Motion") set(USERAPP_TYPES "${DEFAULT_USER_APP_TYPES}" CACHE STRING "List of user apps to build into the firmware") endif () diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json index 41c383c0..b88af94f 100644 --- a/src/displayapp/fonts/fonts.json +++ b/src/displayapp/fonts/fonts.json @@ -7,7 +7,7 @@ }, { "file": "FontAwesome5-Solid+Brands+Regular.woff", - "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743" + "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf073" } ], "bpp": 1, diff --git a/src/displayapp/screens/Calendar.cpp b/src/displayapp/screens/Calendar.cpp new file mode 100644 index 00000000..e466ac4a --- /dev/null +++ b/src/displayapp/screens/Calendar.cpp @@ -0,0 +1,88 @@ +#include "displayapp/screens/Calendar.h" +#include "components/datetime/DateTimeController.h" + +using namespace Pinetime::Applications::Screens; + +Calendar::Calendar(Controllers::DateTime& dateTimeController) : dateTimeController {dateTimeController} { + + // Create calendar object + calendar = lv_calendar_create(lv_scr_act(), NULL); + // Set size + lv_obj_set_size(calendar, LV_HOR_RES, LV_VER_RES); + // Set alignment + lv_obj_align(calendar, NULL, LV_ALIGN_IN_BOTTOM_MID, 0, -5); + // Disable clicks + lv_obj_set_click(calendar, false); + + // Set background of today's date + /* + lv_obj_set_style_local_bg_opa(calendar, LV_CALENDAR_PART_DATE, LV_STATE_FOCUSED, LV_OPA_COVER); + lv_obj_set_style_local_bg_color(calendar, LV_CALENDAR_PART_DATE, LV_STATE_FOCUSED, LV_COLOR_WHITE); + lv_obj_set_style_local_radius(calendar, LV_CALENDAR_PART_DATE, LV_STATE_FOCUSED, 3); + */ + + // Set style of today's date + lv_obj_set_style_local_text_color(calendar, LV_CALENDAR_PART_DATE, LV_STATE_FOCUSED, LV_COLOR_RED); + + // Set style of inactive month's days + lv_obj_set_style_local_text_color(calendar, LV_CALENDAR_PART_DATE, LV_STATE_DISABLED, lv_color_hex(0x505050)); + + // Get today's date + today.year = static_cast(dateTimeController.Year()); + today.month = static_cast(dateTimeController.Month()); + today.day = static_cast(dateTimeController.Day()); + + // Set today's date + lv_calendar_set_today_date(calendar, &today); + lv_calendar_set_showed_date(calendar, &today); + + // Use today's date as a reference for which month to show if moved + current = today; + +} + +bool Calendar::OnTouchEvent(Pinetime::Applications::TouchEvents event) { + switch (event) { + case TouchEvents::SwipeLeft: { + if (current.month == 12) { + current.month = 1; + current.year++; + } + else{ + current.month++; + } + lv_calendar_set_showed_date(calendar, ¤t); + return true; + } + case TouchEvents::SwipeRight: { + if (current.month == 1) { + current.month = 12; + current.year--; + } + else{ + current.month--; + } + lv_calendar_set_showed_date(calendar, ¤t); + return true; + } + /* + case TouchEvents::SwipeUp: { + current.year++; + lv_calendar_set_showed_date(calendar, ¤t); + return true; + } + case TouchEvents::SwipeDown: { + current.year--; + lv_calendar_set_showed_date(calendar, ¤t); + return true; + } + */ + default: { + return false; + } + } +} + +Calendar::~Calendar() { + lv_obj_clean(lv_scr_act()); +} diff --git a/src/displayapp/screens/Calendar.h b/src/displayapp/screens/Calendar.h new file mode 100644 index 00000000..ab5facfb --- /dev/null +++ b/src/displayapp/screens/Calendar.h @@ -0,0 +1,42 @@ +#pragma once + +#include "displayapp/apps/Apps.h" +#include "displayapp/Controllers.h" +#include "displayapp/screens/Screen.h" +#include "components/datetime/DateTimeController.h" +#include + +#include "Symbols.h" + +namespace Pinetime { + namespace Controllers { + class Settings; + } + + namespace Applications { + namespace Screens { + class Calendar : public Screen { + public: + Calendar(Controllers::DateTime& dateTimeController); + ~Calendar() override; + private: + bool OnTouchEvent(TouchEvents event); + Controllers::DateTime& dateTimeController; + lv_obj_t* label_time; + lv_obj_t * calendar; + lv_calendar_date_t today; + lv_calendar_date_t current; + }; + } + + template <> + struct AppTraits { + static constexpr Apps app = Apps::Calendar; + static constexpr const char* icon = Screens::Symbols::calendar; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::Calendar(controllers.dateTimeController); + }; + }; + } +} diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index bd958b28..e48b4336 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -39,6 +39,7 @@ namespace Pinetime { static constexpr const char* eye = "\xEF\x81\xAE"; static constexpr const char* home = "\xEF\x80\x95"; static constexpr const char* sleep = "\xEE\xBD\x84"; + static constexpr const char* calendar = "\xEF\x81\xB3"; // fontawesome_weathericons.c // static constexpr const char* sun = "\xEF\x86\x85"; From 780e68f456678e6a31c7cb47b621dd3ddec569be Mon Sep 17 00:00:00 2001 From: JustScott Date: Thu, 11 Jan 2024 10:58:05 -0600 Subject: [PATCH 002/101] Removed unused variables, some of the commented out code, and added a license copyright notice to the source and header calendar files. --- src/displayapp/screens/Calendar.cpp | 41 ++++++++++++++++++----------- src/displayapp/screens/Calendar.h | 22 +++++++++++++--- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/src/displayapp/screens/Calendar.cpp b/src/displayapp/screens/Calendar.cpp index e466ac4a..837970e4 100644 --- a/src/displayapp/screens/Calendar.cpp +++ b/src/displayapp/screens/Calendar.cpp @@ -1,3 +1,21 @@ +/* Copyright (C) 2024 thnikk, Boteium, JustScott + + This file is part of InfiniTime. + + InfiniTime is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InfiniTime is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + #include "displayapp/screens/Calendar.h" #include "components/datetime/DateTimeController.h" @@ -14,13 +32,6 @@ Calendar::Calendar(Controllers::DateTime& dateTimeController) : dateTimeControll // Disable clicks lv_obj_set_click(calendar, false); - // Set background of today's date - /* - lv_obj_set_style_local_bg_opa(calendar, LV_CALENDAR_PART_DATE, LV_STATE_FOCUSED, LV_OPA_COVER); - lv_obj_set_style_local_bg_color(calendar, LV_CALENDAR_PART_DATE, LV_STATE_FOCUSED, LV_COLOR_WHITE); - lv_obj_set_style_local_radius(calendar, LV_CALENDAR_PART_DATE, LV_STATE_FOCUSED, 3); - */ - // Set style of today's date lv_obj_set_style_local_text_color(calendar, LV_CALENDAR_PART_DATE, LV_STATE_FOCUSED, LV_COLOR_RED); @@ -28,17 +39,13 @@ Calendar::Calendar(Controllers::DateTime& dateTimeController) : dateTimeControll lv_obj_set_style_local_text_color(calendar, LV_CALENDAR_PART_DATE, LV_STATE_DISABLED, lv_color_hex(0x505050)); // Get today's date - today.year = static_cast(dateTimeController.Year()); - today.month = static_cast(dateTimeController.Month()); - today.day = static_cast(dateTimeController.Day()); + current.year = static_cast(dateTimeController.Year()); + current.month = static_cast(dateTimeController.Month()); + current.day = static_cast(dateTimeController.Day()); // Set today's date - lv_calendar_set_today_date(calendar, &today); - lv_calendar_set_showed_date(calendar, &today); - - // Use today's date as a reference for which month to show if moved - current = today; - + lv_calendar_set_today_date(calendar, ¤t); + lv_calendar_set_showed_date(calendar, ¤t); } bool Calendar::OnTouchEvent(Pinetime::Applications::TouchEvents event) { @@ -51,6 +58,7 @@ bool Calendar::OnTouchEvent(Pinetime::Applications::TouchEvents event) { else{ current.month++; } + lv_calendar_set_showed_date(calendar, ¤t); return true; } @@ -62,6 +70,7 @@ bool Calendar::OnTouchEvent(Pinetime::Applications::TouchEvents event) { else{ current.month--; } + lv_calendar_set_showed_date(calendar, ¤t); return true; } diff --git a/src/displayapp/screens/Calendar.h b/src/displayapp/screens/Calendar.h index ab5facfb..b20edd73 100644 --- a/src/displayapp/screens/Calendar.h +++ b/src/displayapp/screens/Calendar.h @@ -1,3 +1,21 @@ +/* Copyright (C) 2024 thnikk, Boteium, JustScott + + This file is part of InfiniTime. + + InfiniTime is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InfiniTime is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + #pragma once #include "displayapp/apps/Apps.h" @@ -22,9 +40,7 @@ namespace Pinetime { private: bool OnTouchEvent(TouchEvents event); Controllers::DateTime& dateTimeController; - lv_obj_t* label_time; - lv_obj_t * calendar; - lv_calendar_date_t today; + lv_obj_t* calendar; lv_calendar_date_t current; }; } From 0535e93848fce686edc041aa225bc1b2538eb8bb Mon Sep 17 00:00:00 2001 From: JustScott Date: Sun, 14 Jan 2024 10:15:13 -0600 Subject: [PATCH 003/101] Reformatted the code to comply with clang-format. --- src/displayapp/screens/Calendar.cpp | 116 ++++++++++++++-------------- src/displayapp/screens/Calendar.h | 3 +- 2 files changed, 59 insertions(+), 60 deletions(-) diff --git a/src/displayapp/screens/Calendar.cpp b/src/displayapp/screens/Calendar.cpp index 837970e4..fea69496 100644 --- a/src/displayapp/screens/Calendar.cpp +++ b/src/displayapp/screens/Calendar.cpp @@ -23,75 +23,73 @@ using namespace Pinetime::Applications::Screens; Calendar::Calendar(Controllers::DateTime& dateTimeController) : dateTimeController {dateTimeController} { - // Create calendar object - calendar = lv_calendar_create(lv_scr_act(), NULL); - // Set size - lv_obj_set_size(calendar, LV_HOR_RES, LV_VER_RES); - // Set alignment - lv_obj_align(calendar, NULL, LV_ALIGN_IN_BOTTOM_MID, 0, -5); - // Disable clicks - lv_obj_set_click(calendar, false); + // Create calendar object + calendar = lv_calendar_create(lv_scr_act(), NULL); + // Set size + lv_obj_set_size(calendar, LV_HOR_RES, LV_VER_RES); + // Set alignment + lv_obj_align(calendar, NULL, LV_ALIGN_IN_BOTTOM_MID, 0, -5); + // Disable clicks + lv_obj_set_click(calendar, false); - // Set style of today's date - lv_obj_set_style_local_text_color(calendar, LV_CALENDAR_PART_DATE, LV_STATE_FOCUSED, LV_COLOR_RED); + // Set style of today's date + lv_obj_set_style_local_text_color(calendar, LV_CALENDAR_PART_DATE, LV_STATE_FOCUSED, LV_COLOR_RED); - // Set style of inactive month's days - lv_obj_set_style_local_text_color(calendar, LV_CALENDAR_PART_DATE, LV_STATE_DISABLED, lv_color_hex(0x505050)); + // Set style of inactive month's days + lv_obj_set_style_local_text_color(calendar, LV_CALENDAR_PART_DATE, LV_STATE_DISABLED, lv_color_hex(0x505050)); - // Get today's date - current.year = static_cast(dateTimeController.Year()); - current.month = static_cast(dateTimeController.Month()); - current.day = static_cast(dateTimeController.Day()); + // Get today's date + current.year = static_cast(dateTimeController.Year()); + current.month = static_cast(dateTimeController.Month()); + current.day = static_cast(dateTimeController.Day()); - // Set today's date - lv_calendar_set_today_date(calendar, ¤t); - lv_calendar_set_showed_date(calendar, ¤t); + // Set today's date + lv_calendar_set_today_date(calendar, ¤t); + lv_calendar_set_showed_date(calendar, ¤t); } bool Calendar::OnTouchEvent(Pinetime::Applications::TouchEvents event) { - switch (event) { - case TouchEvents::SwipeLeft: { - if (current.month == 12) { - current.month = 1; - current.year++; - } - else{ - current.month++; - } + switch (event) { + case TouchEvents::SwipeLeft: { + if (current.month == 12) { + current.month = 1; + current.year++; + } else { + current.month++; + } - lv_calendar_set_showed_date(calendar, ¤t); - return true; - } - case TouchEvents::SwipeRight: { - if (current.month == 1) { - current.month = 12; - current.year--; - } - else{ - current.month--; - } - - lv_calendar_set_showed_date(calendar, ¤t); - return true; - } - /* - case TouchEvents::SwipeUp: { - current.year++; - lv_calendar_set_showed_date(calendar, ¤t); - return true; - } - case TouchEvents::SwipeDown: { - current.year--; - lv_calendar_set_showed_date(calendar, ¤t); - return true; - } - */ - default: { - return false; - } + lv_calendar_set_showed_date(calendar, ¤t); + return true; } + case TouchEvents::SwipeRight: { + if (current.month == 1) { + current.month = 12; + current.year--; + } else { + current.month--; + } + + lv_calendar_set_showed_date(calendar, ¤t); + return true; + } + /* + case TouchEvents::SwipeUp: { + current.year++; + lv_calendar_set_showed_date(calendar, ¤t); + return true; + } + case TouchEvents::SwipeDown: { + current.year--; + lv_calendar_set_showed_date(calendar, ¤t); + return true; + } + */ + default: { + return false; + } + } } Calendar::~Calendar() { - lv_obj_clean(lv_scr_act()); + lv_obj_clean(lv_scr_act()); } diff --git a/src/displayapp/screens/Calendar.h b/src/displayapp/screens/Calendar.h index b20edd73..bbdc8322 100644 --- a/src/displayapp/screens/Calendar.h +++ b/src/displayapp/screens/Calendar.h @@ -23,7 +23,7 @@ #include "displayapp/screens/Screen.h" #include "components/datetime/DateTimeController.h" #include - + #include "Symbols.h" namespace Pinetime { @@ -37,6 +37,7 @@ namespace Pinetime { public: Calendar(Controllers::DateTime& dateTimeController); ~Calendar() override; + private: bool OnTouchEvent(TouchEvents event); Controllers::DateTime& dateTimeController; From 577dfd5b8703969b1c42f14ac876ff2309c06cac Mon Sep 17 00:00:00 2001 From: JustScott Date: Mon, 22 Jan 2024 18:44:47 -0600 Subject: [PATCH 004/101] Updated the calendar to use colors from the InfiniTimeTheme.h's Colors namespace --- src/displayapp/InfiniTimeTheme.h | 1 + src/displayapp/screens/Calendar.cpp | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/displayapp/InfiniTimeTheme.h b/src/displayapp/InfiniTimeTheme.h index 0690b099..57680e87 100644 --- a/src/displayapp/InfiniTimeTheme.h +++ b/src/displayapp/InfiniTimeTheme.h @@ -8,6 +8,7 @@ namespace Colors { static constexpr lv_color_t green = LV_COLOR_MAKE(0x0, 0xb0, 0x0); static constexpr lv_color_t blue = LV_COLOR_MAKE(0x0, 0x50, 0xff); static constexpr lv_color_t lightGray = LV_COLOR_MAKE(0xb0, 0xb0, 0xb0); + static constexpr lv_color_t gray = LV_COLOR_MAKE(0x50, 0x50, 0x50); static constexpr lv_color_t bg = LV_COLOR_MAKE(0x5d, 0x69, 0x7e); static constexpr lv_color_t bgAlt = LV_COLOR_MAKE(0x38, 0x38, 0x38); diff --git a/src/displayapp/screens/Calendar.cpp b/src/displayapp/screens/Calendar.cpp index fea69496..43643f6c 100644 --- a/src/displayapp/screens/Calendar.cpp +++ b/src/displayapp/screens/Calendar.cpp @@ -18,6 +18,7 @@ #include "displayapp/screens/Calendar.h" #include "components/datetime/DateTimeController.h" +#include "displayapp/InfiniTimeTheme.h" using namespace Pinetime::Applications::Screens; @@ -33,10 +34,10 @@ Calendar::Calendar(Controllers::DateTime& dateTimeController) : dateTimeControll lv_obj_set_click(calendar, false); // Set style of today's date - lv_obj_set_style_local_text_color(calendar, LV_CALENDAR_PART_DATE, LV_STATE_FOCUSED, LV_COLOR_RED); + lv_obj_set_style_local_text_color(calendar, LV_CALENDAR_PART_DATE, LV_STATE_FOCUSED, Colors::deepOrange); // Set style of inactive month's days - lv_obj_set_style_local_text_color(calendar, LV_CALENDAR_PART_DATE, LV_STATE_DISABLED, lv_color_hex(0x505050)); + lv_obj_set_style_local_text_color(calendar, LV_CALENDAR_PART_DATE, LV_STATE_DISABLED, Colors::gray); // Get today's date current.year = static_cast(dateTimeController.Year()); From 1162fdbf149b78beb6d68f85c00c65ecd50587dd Mon Sep 17 00:00:00 2001 From: ecarlett Date: Tue, 28 May 2024 10:39:51 +0200 Subject: [PATCH 005/101] initial commit, with already alarm added. TODO : add possibility to disable alarm status, and make it work on 12hrs format --- node_modules/.package-lock.json | 113 + node_modules/lv_font_conv/CHANGELOG.md | 164 + node_modules/lv_font_conv/LICENSE | 22 + node_modules/lv_font_conv/README.md | 150 + node_modules/lv_font_conv/lib/app_error.js | 9 + node_modules/lv_font_conv/lib/cli.js | 318 + .../lv_font_conv/lib/collect_font_data.js | 173 + node_modules/lv_font_conv/lib/convert.js | 30 + .../lib/font/cmap_build_subtables.js | 105 + .../lv_font_conv/lib/font/compress.js | 107 + node_modules/lv_font_conv/lib/font/font.js | 131 + .../lv_font_conv/lib/font/table_cmap.js | 201 + .../lv_font_conv/lib/font/table_glyf.js | 147 + .../lv_font_conv/lib/font/table_head.js | 99 + .../lv_font_conv/lib/font/table_kern.js | 256 + .../lv_font_conv/lib/font/table_loca.js | 42 + .../lv_font_conv/lib/freetype/index.js | 317 + .../lv_font_conv/lib/freetype/render.c | 83 + node_modules/lv_font_conv/lib/ranger.js | 51 + node_modules/lv_font_conv/lib/utils.js | 131 + node_modules/lv_font_conv/lib/writers/bin.js | 17 + node_modules/lv_font_conv/lib/writers/dump.js | 68 + .../lv_font_conv/lib/writers/lvgl/index.js | 17 + .../lv_font_conv/lib/writers/lvgl/lv_font.js | 98 + .../lib/writers/lvgl/lv_table_cmap.js | 125 + .../lib/writers/lvgl/lv_table_glyf.js | 121 + .../lib/writers/lvgl/lv_table_head.js | 99 + .../lib/writers/lvgl/lv_table_kern.js | 121 + node_modules/lv_font_conv/lv_font_conv.js | 17 + .../node_modules/argparse/CHANGELOG.md | 216 + .../node_modules/argparse/LICENSE | 254 + .../node_modules/argparse/README.md | 84 + .../node_modules/argparse/argparse.js | 3707 ++++ .../node_modules/argparse/lib/sub.js | 67 + .../node_modules/argparse/lib/textwrap.js | 440 + .../node_modules/argparse/package.json | 67 + .../bit-buffer/.github/workflows/ci.yml | 36 + .../node_modules/bit-buffer/LICENSE | 21 + .../node_modules/bit-buffer/README.md | 148 + .../node_modules/bit-buffer/bit-buffer.d.ts | 115 + .../node_modules/bit-buffer/bit-buffer.js | 502 + .../node_modules/bit-buffer/package.json | 71 + .../node_modules/bit-buffer/test.js | 628 + .../lv_font_conv/node_modules/debug/LICENSE | 19 + .../lv_font_conv/node_modules/debug/README.md | 455 + .../node_modules/debug/package.json | 111 + .../node_modules/debug/src/browser.js | 269 + .../node_modules/debug/src/common.js | 261 + .../node_modules/debug/src/index.js | 10 + .../node_modules/debug/src/node.js | 263 + .../node_modules/make-error/LICENSE | 5 + .../node_modules/make-error/README.md | 112 + .../make-error/dist/make-error.js | 1 + .../node_modules/make-error/index.d.ts | 47 + .../node_modules/make-error/index.js | 151 + .../node_modules/make-error/package.json | 95 + .../node_modules/mkdirp/CHANGELOG.md | 15 + .../lv_font_conv/node_modules/mkdirp/LICENSE | 21 + .../node_modules/mkdirp/bin/cmd.js | 68 + .../lv_font_conv/node_modules/mkdirp/index.js | 31 + .../node_modules/mkdirp/lib/find-made.js | 29 + .../node_modules/mkdirp/lib/mkdirp-manual.js | 64 + .../node_modules/mkdirp/lib/mkdirp-native.js | 39 + .../node_modules/mkdirp/lib/opts-arg.js | 23 + .../node_modules/mkdirp/lib/path-arg.js | 29 + .../node_modules/mkdirp/lib/use-native.js | 10 + .../node_modules/mkdirp/package.json | 78 + .../node_modules/mkdirp/readme.markdown | 266 + .../lv_font_conv/node_modules/ms/index.js | 162 + .../lv_font_conv/node_modules/ms/license.md | 21 + .../lv_font_conv/node_modules/ms/package.json | 72 + .../lv_font_conv/node_modules/ms/readme.md | 60 + .../node_modules/opentype.js/LICENSE | 20 + .../node_modules/opentype.js/README.md | 313 + .../node_modules/opentype.js/RELEASES.md | 267 + .../node_modules/opentype.js/bin/ot | 84 + .../node_modules/opentype.js/bin/server.js | 64 + .../node_modules/opentype.js/bin/test-render | 96 + .../node_modules/opentype.js/dist/opentype.js | 14254 ++++++++++++++++ .../node_modules/opentype.js/package.json | 102 + .../lv_font_conv/node_modules/pngjs/LICENSE | 20 + .../lv_font_conv/node_modules/pngjs/README.md | 433 + .../node_modules/pngjs/lib/bitmapper.js | 267 + .../node_modules/pngjs/lib/bitpacker.js | 158 + .../node_modules/pngjs/lib/chunkstream.js | 189 + .../node_modules/pngjs/lib/constants.js | 32 + .../node_modules/pngjs/lib/crc.js | 40 + .../node_modules/pngjs/lib/filter-pack.js | 171 + .../pngjs/lib/filter-parse-async.js | 24 + .../pngjs/lib/filter-parse-sync.js | 21 + .../node_modules/pngjs/lib/filter-parse.js | 177 + .../pngjs/lib/format-normaliser.js | 93 + .../node_modules/pngjs/lib/interlace.js | 95 + .../node_modules/pngjs/lib/packer-async.js | 50 + .../node_modules/pngjs/lib/packer-sync.js | 56 + .../node_modules/pngjs/lib/packer.js | 129 + .../node_modules/pngjs/lib/paeth-predictor.js | 16 + .../node_modules/pngjs/lib/parser-async.js | 169 + .../node_modules/pngjs/lib/parser-sync.js | 112 + .../node_modules/pngjs/lib/parser.js | 290 + .../node_modules/pngjs/lib/png-sync.js | 12 + .../node_modules/pngjs/lib/png.js | 194 + .../node_modules/pngjs/lib/sync-inflate.js | 168 + .../node_modules/pngjs/lib/sync-reader.js | 45 + .../node_modules/pngjs/package.json | 129 + .../LICENSE-MIT.txt | 20 + .../string.prototype.codepointat/README.md | 47 + .../codepointat.js | 54 + .../string.prototype.codepointat/package.json | 62 + .../node_modules/tiny-inflate/LICENSE | 21 + .../node_modules/tiny-inflate/index.js | 375 + .../node_modules/tiny-inflate/package.json | 60 + .../node_modules/tiny-inflate/readme.md | 31 + .../node_modules/tiny-inflate/test/index.js | 75 + .../node_modules/tiny-inflate/test/lorem.txt | 199 + node_modules/lv_font_conv/package.json | 61 + package-lock.json | 183 + package.json | 5 + src/CMakeLists.txt | 1 + src/displayapp/fonts/fonts.json | 2 +- src/displayapp/screens/AlarmIcon.cpp | 11 + src/displayapp/screens/AlarmIcon.h | 14 + src/displayapp/screens/Symbols.h | 1 + src/displayapp/screens/WatchFaceInfineat.cpp | 34 +- src/displayapp/screens/WatchFaceInfineat.h | 6 + 125 files changed, 32022 insertions(+), 5 deletions(-) create mode 100644 node_modules/.package-lock.json create mode 100644 node_modules/lv_font_conv/CHANGELOG.md create mode 100644 node_modules/lv_font_conv/LICENSE create mode 100644 node_modules/lv_font_conv/README.md create mode 100644 node_modules/lv_font_conv/lib/app_error.js create mode 100644 node_modules/lv_font_conv/lib/cli.js create mode 100644 node_modules/lv_font_conv/lib/collect_font_data.js create mode 100644 node_modules/lv_font_conv/lib/convert.js create mode 100644 node_modules/lv_font_conv/lib/font/cmap_build_subtables.js create mode 100644 node_modules/lv_font_conv/lib/font/compress.js create mode 100644 node_modules/lv_font_conv/lib/font/font.js create mode 100644 node_modules/lv_font_conv/lib/font/table_cmap.js create mode 100644 node_modules/lv_font_conv/lib/font/table_glyf.js create mode 100644 node_modules/lv_font_conv/lib/font/table_head.js create mode 100644 node_modules/lv_font_conv/lib/font/table_kern.js create mode 100644 node_modules/lv_font_conv/lib/font/table_loca.js create mode 100644 node_modules/lv_font_conv/lib/freetype/index.js create mode 100644 node_modules/lv_font_conv/lib/freetype/render.c create mode 100644 node_modules/lv_font_conv/lib/ranger.js create mode 100644 node_modules/lv_font_conv/lib/utils.js create mode 100644 node_modules/lv_font_conv/lib/writers/bin.js create mode 100644 node_modules/lv_font_conv/lib/writers/dump.js create mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/index.js create mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/lv_font.js create mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/lv_table_cmap.js create mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/lv_table_glyf.js create mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/lv_table_head.js create mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/lv_table_kern.js create mode 100755 node_modules/lv_font_conv/lv_font_conv.js create mode 100644 node_modules/lv_font_conv/node_modules/argparse/CHANGELOG.md create mode 100644 node_modules/lv_font_conv/node_modules/argparse/LICENSE create mode 100644 node_modules/lv_font_conv/node_modules/argparse/README.md create mode 100644 node_modules/lv_font_conv/node_modules/argparse/argparse.js create mode 100644 node_modules/lv_font_conv/node_modules/argparse/lib/sub.js create mode 100644 node_modules/lv_font_conv/node_modules/argparse/lib/textwrap.js create mode 100644 node_modules/lv_font_conv/node_modules/argparse/package.json create mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/.github/workflows/ci.yml create mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/LICENSE create mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/README.md create mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.d.ts create mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.js create mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/package.json create mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/test.js create mode 100644 node_modules/lv_font_conv/node_modules/debug/LICENSE create mode 100644 node_modules/lv_font_conv/node_modules/debug/README.md create mode 100644 node_modules/lv_font_conv/node_modules/debug/package.json create mode 100644 node_modules/lv_font_conv/node_modules/debug/src/browser.js create mode 100644 node_modules/lv_font_conv/node_modules/debug/src/common.js create mode 100644 node_modules/lv_font_conv/node_modules/debug/src/index.js create mode 100644 node_modules/lv_font_conv/node_modules/debug/src/node.js create mode 100644 node_modules/lv_font_conv/node_modules/make-error/LICENSE create mode 100644 node_modules/lv_font_conv/node_modules/make-error/README.md create mode 100644 node_modules/lv_font_conv/node_modules/make-error/dist/make-error.js create mode 100644 node_modules/lv_font_conv/node_modules/make-error/index.d.ts create mode 100644 node_modules/lv_font_conv/node_modules/make-error/index.js create mode 100644 node_modules/lv_font_conv/node_modules/make-error/package.json create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/CHANGELOG.md create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/LICENSE create mode 100755 node_modules/lv_font_conv/node_modules/mkdirp/bin/cmd.js create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/index.js create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/find-made.js create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-manual.js create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-native.js create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/opts-arg.js create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/path-arg.js create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/use-native.js create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/package.json create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/readme.markdown create mode 100644 node_modules/lv_font_conv/node_modules/ms/index.js create mode 100644 node_modules/lv_font_conv/node_modules/ms/license.md create mode 100644 node_modules/lv_font_conv/node_modules/ms/package.json create mode 100644 node_modules/lv_font_conv/node_modules/ms/readme.md create mode 100644 node_modules/lv_font_conv/node_modules/opentype.js/LICENSE create mode 100644 node_modules/lv_font_conv/node_modules/opentype.js/README.md create mode 100644 node_modules/lv_font_conv/node_modules/opentype.js/RELEASES.md create mode 100755 node_modules/lv_font_conv/node_modules/opentype.js/bin/ot create mode 100755 node_modules/lv_font_conv/node_modules/opentype.js/bin/server.js create mode 100755 node_modules/lv_font_conv/node_modules/opentype.js/bin/test-render create mode 100644 node_modules/lv_font_conv/node_modules/opentype.js/dist/opentype.js create mode 100644 node_modules/lv_font_conv/node_modules/opentype.js/package.json create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/LICENSE create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/README.md create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/bitmapper.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/bitpacker.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/chunkstream.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/constants.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/crc.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/filter-pack.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-async.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-sync.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/format-normaliser.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/interlace.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/packer-async.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/packer-sync.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/packer.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/paeth-predictor.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/parser-async.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/parser-sync.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/parser.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/png-sync.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/png.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/sync-inflate.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/sync-reader.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/package.json create mode 100644 node_modules/lv_font_conv/node_modules/string.prototype.codepointat/LICENSE-MIT.txt create mode 100644 node_modules/lv_font_conv/node_modules/string.prototype.codepointat/README.md create mode 100644 node_modules/lv_font_conv/node_modules/string.prototype.codepointat/codepointat.js create mode 100644 node_modules/lv_font_conv/node_modules/string.prototype.codepointat/package.json create mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/LICENSE create mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/index.js create mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/package.json create mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/readme.md create mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/test/index.js create mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/test/lorem.txt create mode 100644 node_modules/lv_font_conv/package.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/displayapp/screens/AlarmIcon.cpp create mode 100644 src/displayapp/screens/AlarmIcon.h diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json new file mode 100644 index 00000000..a519c455 --- /dev/null +++ b/node_modules/.package-lock.json @@ -0,0 +1,113 @@ +{ + "name": "InfiniTime", + "lockfileVersion": 2, + "requires": true, + "packages": { + "node_modules/lv_font_conv": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/lv_font_conv/-/lv_font_conv-1.5.2.tgz", + "integrity": "sha512-0UapRSTkVP/pnB8Z4r2HDHx5p2dJx/xUG1+14u/WXo59mwuC7BahR+Bnx/66jKoDrG1wFQwn9ZzoyMxRHOD9bg==", + "bundleDependencies": [ + "argparse", + "bit-buffer", + "debug", + "make-error", + "mkdirp", + "opentype.js", + "pngjs" + ], + "dependencies": { + "argparse": "^2.0.0", + "bit-buffer": "^0.2.5", + "debug": "^4.1.1", + "make-error": "^1.3.5", + "mkdirp": "^1.0.4", + "opentype.js": "^1.1.0", + "pngjs": "^6.0.0" + }, + "bin": { + "lv_font_conv": "lv_font_conv.js" + } + }, + "node_modules/lv_font_conv/node_modules/argparse": { + "version": "2.0.1", + "inBundle": true, + "license": "Python-2.0" + }, + "node_modules/lv_font_conv/node_modules/bit-buffer": { + "version": "0.2.5", + "inBundle": true, + "license": "MIT" + }, + "node_modules/lv_font_conv/node_modules/debug": { + "version": "4.3.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/lv_font_conv/node_modules/make-error": { + "version": "1.3.6", + "inBundle": true, + "license": "ISC" + }, + "node_modules/lv_font_conv/node_modules/mkdirp": { + "version": "1.0.4", + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lv_font_conv/node_modules/ms": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/lv_font_conv/node_modules/opentype.js": { + "version": "1.3.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "string.prototype.codepointat": "^0.2.1", + "tiny-inflate": "^1.0.3" + }, + "bin": { + "ot": "bin/ot" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/lv_font_conv/node_modules/pngjs": { + "version": "6.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/lv_font_conv/node_modules/string.prototype.codepointat": { + "version": "0.2.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/lv_font_conv/node_modules/tiny-inflate": { + "version": "1.0.3", + "inBundle": true, + "license": "MIT" + } + } +} diff --git a/node_modules/lv_font_conv/CHANGELOG.md b/node_modules/lv_font_conv/CHANGELOG.md new file mode 100644 index 00000000..94512228 --- /dev/null +++ b/node_modules/lv_font_conv/CHANGELOG.md @@ -0,0 +1,164 @@ +1.5.2 / 2021-07-18 +------------------ + +- Fixed lvgl version check for v8+, #64. + + +1.5.1 / 2021-04-06 +------------------ + +- Fixed fail of CMAP generation for edge cases, #62. +- Dev deps bump. + + +1.5.0 / 2021-03-08 +------------------ + +- More `const` in generated font (for v8+), #59. + + +1.4.1 / 2021-01-26 +------------------ + +- Fix charcodes padding in comments, #54. + + +1.4.0 / 2021-01-03 +------------------ + +- Added OTF fonts support. +- Added `--use-color-info` for limited multi-tone glyphs support. + + +1.3.1 / 2020-12-28 +------------------ + +- Unify `lvgl.h` include. +- Updated repo refs (littlevgl => lvgl). +- Deps bump. +- Moved CI to github actions. + + +1.3.0 / 2020-10-25 +------------------ + +- Drop `lodash` use. +- Deps bump. + + +1.2.1 / 2020-10-24 +------------------ + +- Reduced npm package size (drop unneeded files before publish). + + +1.2.0 / 2020-10-24 +------------------ + +- Bump FreeType to 2.10.4. +- Bundle dependencies to npm package. + + +1.1.3 / 2020-09-22 +------------------ + +- lvgl: added `LV_FONT_FMT_TXT_LARGE` check or very large fonts. + + +1.1.2 / 2020-08-23 +------------------ + +- Fix: skip `glyph.advanceWidth` for monospace fonts, #43. +- Spec fix: version size should be 4 bytes, #44. +- Spec fix: bbox x/y bits => unsigned, #45. +- Bump argparse. +- Cleanup help formatter. + + +1.1.1 / 2020-08-01 +------------------ + +- `--version` should show number from `package.json`. + + +1.1.0 / 2020-07-27 +------------------ + +- Added `post.underlinePosition` & `post.underlineThickness` info to font header. + + +1.0.0 / 2020-06-26 +------------------ + +- Maintenance release. +- Set package version 1.x, to label package as stable. +- Deps bump. + + +0.4.3 / 2020-03-05 +------------------ + +- Enabled `--bpp 8` mode. + + +0.4.2 / 2020-01-05 +------------------ + +- Added `--lv_include` option to set alternate `lvgl.h` path. +- Added guards to hide `.subpx` property for lvgl 6.0 (supported from 6.1 only), #32. +- Dev deps bump + + +0.4.1 / 2019-12-09 +------------------ + +- Allow memory growth for FreeType build, #29. +- Dev deps bump. +- Web build update. + + +0.4.0 / 2019-11-29 +------------------ + +- Note, this release is for lvgl 6.1 and has potentially breaking changes + (see below). If you have compatibility issues with lvgl 6.0 - use previous + versions or update your code. +- Spec change: added subpixels info field to font header (header size increased). +- Updated `bin` & `lvgl` writers to match new spec. +- lvgl: fixed data type for kerning values (needs appropriate update + in LittlevGL 6.1+). +- Fix errors display (disable emscripten error catcher). + + +0.3.1 / 2019-10-24 +------------------ + +- Fixed "out of range" error for big `--size`. + + +0.3.0 / 2019-10-12 +------------------ + +- Added beta options `--lcd` & `--lcd-v` for subpixel rendering (still need + header info update). +- Added FreeType data properties to dump info. +- Fixed glyph width (missed fractional part after switch to FreeType). +- Fixed missed sigh for negative X/Y bitmap offsets. +- Deps bump. + + +0.2.0 / 2019-09-26 +------------------ + +- Use FreeType renderer. Should solve all regressions, reported in 0.1.0. +- Enforced light autohinting (horizontal lines only). +- Use special hinter for monochrome output (improve quality). +- API changed to async. +- Fix: added missed `.bitmap_format` field to lvgl writer. +- Fix: changed struct fields init order to match declaration, #25. + + +0.1.0 / 2019-09-03 +------------------ + +- First release. diff --git a/node_modules/lv_font_conv/LICENSE b/node_modules/lv_font_conv/LICENSE new file mode 100644 index 00000000..4dc31bfa --- /dev/null +++ b/node_modules/lv_font_conv/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2018 authors + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/lv_font_conv/README.md b/node_modules/lv_font_conv/README.md new file mode 100644 index 00000000..6716ebd9 --- /dev/null +++ b/node_modules/lv_font_conv/README.md @@ -0,0 +1,150 @@ +lv_font_conv - font convertor to compact bitmap format +====================================================== + +[![CI](https://github.com/lvgl/lv_font_conv/workflows/CI/badge.svg?branch=master)](https://github.com/lvgl/lv_font_conv/actions) +[![NPM version](https://img.shields.io/npm/v/lv_font_conv.svg?style=flat)](https://www.npmjs.org/package/lv_font_conv) + +Converts TTF/WOFF/OTF fonts to __[compact format](https://github.com/lvgl/lv_font_conv/blob/master/doc/font_spec.md)__, suitable for small embedded systems. Main features are: + +- Allows bitonal and anti-aliased glyphs (1-4 bits per pixel). +- Preserves kerning info. +- Compression. +- Users can select required glyphs only (subsetting). +- Multiple font sources can be merged. +- Simple CLI interface, easy to integrate into external build systems. + + +## Install the script + +[node.js](https://nodejs.org/en/download/) v10+ required. + +Global install of the last version, execute as "lv_font_conv" + +```sh +# install release from npm registry +npm i lv_font_conv -g +# install from github's repo, master branch +npm i lvgl/lv_font_conv -g +``` + +**run via [npx](https://www.npmjs.com/package/npx) without install** + +```sh +# run from npm registry +npx lv_font_conv -h +# run from github master +npx github:lvgl/lv_font_conv -h +``` + +Note, runing via `npx` may take some time until modules installed, be patient. + + +## CLI params + +Common: + +- `--bpp` - bits per pixel (antialiasing). +- `--size` - output font size (pixels). +- `-o`, `--output` - output path (file or directory, depends on format). +- `--format` - output format. + - `--format dump` - dump glyph images and font info, useful for debug. + - `--format bin` - dump font in binary form (as described in [spec](https://github.com/lvgl/lv_font_conv/blob/master/doc/font_spec.md)). + - `--format lvgl` - dump font in [LittlevGL](https://github.com/lvgl/lvgl) format. +- `--force-fast-kern-format` - always use more fast kering storage format, + at cost of some size. If size difference appears, it will be displayed. +- `--lcd` - generate bitmaps with 3x horizontal resolution, for subpixel + smoothing. +- `--lcd-v` - generate bitmaps with 3x vertical resolution, for subpixel + smoothing. +- `--use-color-info` - try to use glyph color info from font to create + grayscale icons. Since gray tones are emulated via transparency, result + will be good on contrast background only. +- `--lv-include` - only with `--format lvgl`, set alternate path for `lvgl.h`. + +Per font: + +- `--font` - path to font file (ttf/woff/woff2/otf). May be used multiple time for + merge. +- `-r`, `--range` - single glyph or range + optional mapping, belongs to + previously declared `--font`. Can be used multiple times. Examples: + - `-r 0x1F450` - single value, dec or hex format. + - `-r 0x1F450-0x1F470` - range. + - `-r '0x1F450=>0xF005'` - single glyph with mapping. + - `-r '0x1F450-0x1F470=>0xF005'` - range with mapping. + - `-r 0x1F450 -r 0x1F451-0x1F470` - 2 ranges. + - `-r 0x1F450,0x1F451-0x1F470` - the same as above, but defined with single `-r`. +- `--symbols` - list of characters to copy (instead of numeric format in `-r`). + - `--symbols 0123456789.,` - extract chars to display numbers. +- `--autohint-off` - do not force autohinting ("light" is on by default). +- `--autohint-strong` - use more strong autohinting (will break kerning). + +Additional debug options: + +- `--no-compress` - disable built-in RLE compression. +- `--no-prefilter` - disable bitmap lines filter (XOR), used to improve + compression ratio. +- `--no-kerning` - drop kerning info to reduce size (not recommended). +- `--full-info` - don't shorten 'font_info.json' (include pixels data). + + +## Examples + +Merge english from Roboto Regular and icons from Font Awesome, and show debug +info: + +`env DEBUG=* lv_font_conv --font Roboto-Regular.ttf -r 0x20-0x7F --font FontAwesome.ttf -r 0xFE00=>0x81 --size 16 --format bin --bpp 3 --no-compress -o output.font` + +Merge english & russian from Roboto Regular, and show debug info: + +`env DEBUG=* lv_font_conv --font Roboto-Regular.ttf -r 0x20-0x7F -r 0x401,0x410-0x44F,0x451 --size 16 --format bin --bpp 3 --no-compress -o output.font` + +Dump all Roboto glyphs to inspect icons and font details: + +`lv_font_conv --font Roboto-Regular.ttf -r 0x20-0x7F --size 16 --format dump --bpp 3 -o ./dump` + +**Note**. Option `--no-compress` exists temporary, to avoid confusion until LVGL +adds compression support. + + +## Technical notes + +### Supported output formats + +1. **bin** - universal binary format, as described in https://github.com/lvgl/lv_font_conv/tree/master/doc. +2. **lvgl** - format for LittlevGL, C file. Has minor limitations and a bit + bigger size, because C does not allow to effectively define relative offsets + in data blocks. +3. **dump** - create folder with each glyph in separate image, and other font + data as `json`. Useful for debug. + +### Merged font metrics + +When multiple fonts merged into one, sources can have different metrics. Result +will follow principles below: + +1. No scaling. Glyphs will have exactly the same size, as intended by font authors. +2. The same baseline. +3. `OS/2` metrics (`sTypoAscender`, `sTypoDescender`, `sTypoLineGap`) will be + used from the first font in list. +4. `hhea` metrics (`ascender`, `descender`), defined as max/min point of all + font glyphs, are recalculated, according to new glyphs set. + + +## Development + +Current package includes WebAssembly build of FreeType with some helper +functions. Everything is wrapped into Docker and requires zero knowledge about +additional tools install. See `package.json` for additional commands. You may +need those if decide to upgrade FreeType or update helpers. + +This builds image with emscripten & freetype, usually should be done only once: + +``` +npm run build:dockerimage +``` + +This compiles helpers and creates WebAssembly files: + +``` +npm run build:freetype +``` diff --git a/node_modules/lv_font_conv/lib/app_error.js b/node_modules/lv_font_conv/lib/app_error.js new file mode 100644 index 00000000..98e9052e --- /dev/null +++ b/node_modules/lv_font_conv/lib/app_error.js @@ -0,0 +1,9 @@ +// Custom Error type to simplify error messaging +// +'use strict'; + + +//const ExtendableError = require('es6-error'); +//module.exports = class AppError extends ExtendableError {}; + +module.exports = require('make-error')('AppError'); diff --git a/node_modules/lv_font_conv/lib/cli.js b/node_modules/lv_font_conv/lib/cli.js new file mode 100644 index 00000000..a73a9b22 --- /dev/null +++ b/node_modules/lv_font_conv/lib/cli.js @@ -0,0 +1,318 @@ +// Parse input arguments and execute convertor + +'use strict'; + + +const argparse = require('argparse'); +const fs = require('fs'); +const mkdirp = require('mkdirp'); +const path = require('path'); +const convert = require('./convert'); + + +class ActionFontAdd extends argparse.Action { + call(parser, namespace, value/*, option_string*/) { + let items = (namespace[this.dest] || []).slice(); + items.push({ source_path: value, ranges: [] }); + namespace[this.dest] = items; + } +} + + +// add range or symbols to font; +// need to merge them into one array here so overrides work correctly +class ActionFontRangeAdd extends argparse.Action { + call(parser, namespace, value, option_string) { + let fonts = namespace.font || []; + + if (fonts.length === 0) { + parser.error(`argument ${option_string}: Only allowed after --font`); + } + + let lastFont = fonts[fonts.length - 1]; + + // { symbols: 'ABC' }, or { range: [ 65, 67, 65 ] } + lastFont.ranges.push({ [this.dest]: value }); + } +} + + +// add hinting option to font; +class ActionFontStoreTrue extends argparse.Action { + constructor(options) { + options = options || {}; + options.const = true; + options.default = options.default !== null ? options.default : false; + options.nargs = 0; + super(options); + } + + call(parser, namespace, value, option_string) { + let fonts = namespace.font || []; + + if (fonts.length === 0) { + parser.error(`argument ${option_string}: Only allowed after --font`); + } + + let lastFont = fonts[fonts.length - 1]; + + lastFont[this.dest] = this.const; + } +} + + +// Formatter with support of `\n` in Help texts. +class RawTextHelpFormatter2 extends argparse.RawDescriptionHelpFormatter { + // executes parent _split_lines for each line of the help, then flattens the result + _split_lines(text, width) { + return [].concat(...text.split('\n').map(line => super._split_lines(line, width))); + } +} + + +// parse decimal or hex code in unicode range +function unicode_point(str) { + let m = /^(?:(?:0x([0-9a-f]+))|([0-9]+))$/i.exec(str.trim()); + + if (!m) throw new TypeError(`${str} is not a number`); + + let [ , hex, dec ] = m; + + let value = hex ? parseInt(hex, 16) : parseInt(dec, 10); + + if (value > 0x10FFFF) throw new TypeError(`${str} is out of unicode range`); + + return value; +} + + +// parse range +function range(str) { + let result = []; + + for (let s of str.split(',')) { + let m = /^(.+?)(?:-(.+?))?(?:=>(.+?))?$/i.exec(s); + + let [ , start, end, mapped_start ] = m; + + if (!end) end = start; + if (!mapped_start) mapped_start = start; + + start = unicode_point(start); + end = unicode_point(end); + + if (start > end) throw new TypeError(`Invalid range: ${s}`); + + mapped_start = unicode_point(mapped_start); + + result.push(start, end, mapped_start); + } + + return result; +} + + +// exclude negative numbers and non-numbers +function positive_int(str) { + if (!/^\d+$/.test(str)) throw new TypeError(`${str} is not a valid number`); + + let n = parseInt(str, 10); + + if (n <= 0) throw new TypeError(`${str} is not a valid number`); + + return n; +} + + +module.exports.run = async function (argv, debug = false) { + + // + // Configure CLI + // + + let parser = new argparse.ArgumentParser({ + add_help: true, + formatter_class: RawTextHelpFormatter2 + }); + + if (debug) { + parser.exit = function (status, message) { + throw new Error(message); + }; + } + + parser.add_argument('-v', '--version', { + action: 'version', + version: require('../package.json').version + }); + + parser.add_argument('--size', { + metavar: 'PIXELS', + type: positive_int, + required: true, + help: 'Output font size, pixels.' + }); + + parser.add_argument('-o', '--output', { + metavar: '', + help: 'Output path.' + }); + + parser.add_argument('--bpp', { + choices: [ 1, 2, 3, 4, 8 ], + type: positive_int, + required: true, + help: 'Bits per pixel, for antialiasing.' + }); + + let lcd_group = parser.add_mutually_exclusive_group(); + + lcd_group.add_argument('--lcd', { + action: 'store_true', + default: false, + help: 'Enable subpixel rendering (horizontal pixel layout).' + }); + + lcd_group.add_argument('--lcd-v', { + action: 'store_true', + default: false, + help: 'Enable subpixel rendering (vertical pixel layout).' + }); + + parser.add_argument('--use-color-info', { + dest: 'use_color_info', + action: 'store_true', + default: false, + help: 'Try to use glyph color info from font to create grayscale icons. ' + + 'Since gray tones are emulated via transparency, result will be good on contrast background only.' + }); + + parser.add_argument('--format', { + choices: convert.formats, + required: true, + help: 'Output format.' + }); + + parser.add_argument('--font', { + metavar: '', + action: ActionFontAdd, + required: true, + help: 'Source font path. Can be used multiple times to merge glyphs from different fonts.' + }); + + parser.add_argument('-r', '--range', { + type: range, + action: ActionFontRangeAdd, + help: ` +Range of glyphs to copy. Can be used multiple times, belongs to previously declared "--font". Examples: + -r 0x1F450 + -r 0x20-0x7F + -r 32-127 + -r 32-127,0x1F450 + -r '0x1F450=>0xF005' + -r '0x1F450-0x1F470=>0xF005' +` + }); + + parser.add_argument('--symbols', { + action: ActionFontRangeAdd, + help: ` +List of characters to copy, belongs to previously declared "--font". Examples: + --symbols ,.0123456789 + --symbols abcdefghigklmnopqrstuvwxyz +` + }); + + parser.add_argument('--autohint-off', { + type: range, + action: ActionFontStoreTrue, + help: 'Disable autohinting for previously declared "--font"' + }); + + parser.add_argument('--autohint-strong', { + type: range, + action: ActionFontStoreTrue, + help: 'Use more strong autohinting for previously declared "--font" (will break kerning)' + }); + + parser.add_argument('--force-fast-kern-format', { + dest: 'fast_kerning', + action: 'store_true', + default: false, + help: 'Always use kern classes instead of pairs (might be larger but faster).' + }); + + parser.add_argument('--no-compress', { + dest: 'no_compress', + action: 'store_true', + default: false, + help: 'Disable built-in RLE compression.' + }); + + parser.add_argument('--no-prefilter', { + dest: 'no_prefilter', + action: 'store_true', + default: false, + help: 'Disable bitmap lines filter (XOR), used to improve compression ratio.' + }); + + parser.add_argument('--no-kerning', { + dest: 'no_kerning', + action: 'store_true', + default: false, + help: 'Drop kerning info to reduce size (not recommended).' + }); + + parser.add_argument('--lv-include', { + metavar: '', + help: 'Set alternate "lvgl.h" path (for --format lvgl).' + }); + + parser.add_argument('--full-info', { + dest: 'full_info', + action: 'store_true', + default: false, + help: 'Don\'t shorten "font_info.json" (include pixels data).' + }); + + // + // Process CLI options + // + + let args = parser.parse_args(argv.length ? argv : [ '-h' ]); + + for (let font of args.font) { + if (font.ranges.length === 0) { + parser.error(`You need to specify either "--range" or "--symbols" for font "${font.source_path}"`); + } + + try { + font.source_bin = fs.readFileSync(font.source_path); + } catch (err) { + parser.error(`Cannot read file "${font.source_path}": ${err.message}`); + } + } + + // + // Convert + // + + let files = await convert(args); + + // + // Store files + // + + for (let [ filename, data ] of Object.entries(files)) { + let dir = path.dirname(filename); + + mkdirp.sync(dir); + + fs.writeFileSync(filename, data); + } + +}; + + +// export for tests +module.exports._range = range; diff --git a/node_modules/lv_font_conv/lib/collect_font_data.js b/node_modules/lv_font_conv/lib/collect_font_data.js new file mode 100644 index 00000000..227c5835 --- /dev/null +++ b/node_modules/lv_font_conv/lib/collect_font_data.js @@ -0,0 +1,173 @@ +// Read fonts + +'use strict'; + + +const opentype = require('opentype.js'); +const ft_render = require('./freetype'); +const AppError = require('./app_error'); +const Ranger = require('./ranger'); + + +module.exports = async function collect_font_data(args) { + await ft_render.init(); + + // Duplicate font options as k/v for quick access + let fonts_options = {}; + args.font.forEach(f => { fonts_options[f.source_path] = f; }); + + // read fonts + let fonts_opentype = {}; + let fonts_freetype = {}; + + for (let { source_path, source_bin } of args.font) { + // don't load font again if it's specified multiple times in args + if (fonts_opentype[source_path]) continue; + + try { + let b = source_bin; + + if (Buffer.isBuffer(b)) { + // node.js Buffer -> ArrayBuffer + b = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength); + } + + fonts_opentype[source_path] = opentype.parse(b); + } catch (err) { + throw new AppError(`Cannot load font "${source_path}": ${err.message}`); + } + + fonts_freetype[source_path] = ft_render.fontface_create(source_bin, args.size); + } + + // merge all ranges + let ranger = new Ranger(); + + for (let { source_path, ranges } of args.font) { + let font = fonts_freetype[source_path]; + + for (let item of ranges) { + /* eslint-disable max-depth */ + if (item.range) { + for (let i = 0; i < item.range.length; i += 3) { + let range = item.range.slice(i, i + 3); + let chars = ranger.add_range(source_path, ...range); + let is_empty = true; + + for (let code of chars) { + if (ft_render.glyph_exists(font, code)) { + is_empty = false; + break; + } + } + + if (is_empty) { + let a = '0x' + range[0].toString(16); + let b = '0x' + range[1].toString(16); + throw new AppError(`Font "${source_path}" doesn't have any characters included in range ${a}-${b}`); + } + } + } + + if (item.symbols) { + let chars = ranger.add_symbols(source_path, item.symbols); + let is_empty = true; + + for (let code of chars) { + if (ft_render.glyph_exists(font, code)) { + is_empty = false; + break; + } + } + + if (is_empty) { + throw new AppError(`Font "${source_path}" doesn't have any characters included in "${item.symbols}"`); + } + } + } + } + + let mapping = ranger.get(); + let glyphs = []; + let all_dst_charcodes = Object.keys(mapping).sort((a, b) => a - b).map(Number); + + for (let dst_code of all_dst_charcodes) { + let src_code = mapping[dst_code].code; + let src_font = mapping[dst_code].font; + + if (!ft_render.glyph_exists(fonts_freetype[src_font], src_code)) continue; + + let ft_result = ft_render.glyph_render( + fonts_freetype[src_font], + src_code, + { + autohint_off: fonts_options[src_font].autohint_off, + autohint_strong: fonts_options[src_font].autohint_strong, + lcd: args.lcd, + lcd_v: args.lcd_v, + mono: !args.lcd && !args.lcd_v && args.bpp === 1, + use_color_info: args.use_color_info + } + ); + + glyphs.push({ + code: dst_code, + advanceWidth: ft_result.advance_x, + bbox: { + x: ft_result.x, + y: ft_result.y - ft_result.height, + width: ft_result.width, + height: ft_result.height + }, + kerning: {}, + freetype: ft_result.freetype, + pixels: ft_result.pixels + }); + } + + if (!args.no_kerning) { + let existing_dst_charcodes = glyphs.map(g => g.code); + + for (let { code, kerning } of glyphs) { + let src_code = mapping[code].code; + let src_font = mapping[code].font; + let font = fonts_opentype[src_font]; + let glyph = font.charToGlyph(String.fromCodePoint(src_code)); + + for (let dst_code2 of existing_dst_charcodes) { + // can't merge kerning values from 2 different fonts + if (mapping[dst_code2].font !== src_font) continue; + + let src_code2 = mapping[dst_code2].code; + let glyph2 = font.charToGlyph(String.fromCodePoint(src_code2)); + let krn_value = font.getKerningValue(glyph, glyph2); + + if (krn_value) kerning[dst_code2] = krn_value * args.size / font.unitsPerEm; + + //let krn_value = ft_render.get_kerning(font, src_code, src_code2).x; + //if (krn_value) kerning[dst_code2] = krn_value; + } + } + } + + let first_font = fonts_freetype[args.font[0].source_path]; + let first_font_scale = args.size / first_font.units_per_em; + let os2_metrics = ft_render.fontface_os2_table(first_font); + let post_table = fonts_opentype[args.font[0].source_path].tables.post; + + for (let font of Object.values(fonts_freetype)) ft_render.fontface_destroy(font); + + ft_render.destroy(); + + return { + ascent: Math.max(...glyphs.map(g => g.bbox.y + g.bbox.height)), + descent: Math.min(...glyphs.map(g => g.bbox.y)), + typoAscent: Math.round(os2_metrics.typoAscent * first_font_scale), + typoDescent: Math.round(os2_metrics.typoDescent * first_font_scale), + typoLineGap: Math.round(os2_metrics.typoLineGap * first_font_scale), + size: args.size, + glyphs, + underlinePosition: Math.round(post_table.underlinePosition * first_font_scale), + underlineThickness: Math.round(post_table.underlineThickness * first_font_scale) + }; +}; diff --git a/node_modules/lv_font_conv/lib/convert.js b/node_modules/lv_font_conv/lib/convert.js new file mode 100644 index 00000000..0cf088a0 --- /dev/null +++ b/node_modules/lv_font_conv/lib/convert.js @@ -0,0 +1,30 @@ +// Internal API to convert input data into output font data +// Used by both CLI and Web wrappers. +'use strict'; + +const collect_font_data = require('./collect_font_data'); + +let writers = { + dump: require('./writers/dump'), + bin: require('./writers/bin'), + lvgl: require('./writers/lvgl') +}; + + +// +// Input: +// - args like from CLI (optionally extended with binary content of files) +// +// Output: +// - { name1: bin_data1, name2: bin_data2, ... } +// +// returns hash with files to write +// +module.exports = async function convert(args) { + let font_data = await collect_font_data(args); + let files = writers[args.format](args, font_data); + + return files; +}; + +module.exports.formats = Object.keys(writers); diff --git a/node_modules/lv_font_conv/lib/font/cmap_build_subtables.js b/node_modules/lv_font_conv/lib/font/cmap_build_subtables.js new file mode 100644 index 00000000..c80a219e --- /dev/null +++ b/node_modules/lv_font_conv/lib/font/cmap_build_subtables.js @@ -0,0 +1,105 @@ +// Find an optimal configuration of cmap tables representing set of codepoints, +// using simple breadth-first algorithm +// +// Assume that: +// - codepoints have one-to-one correspondence to glyph ids +// - glyph ids are always bigger for bigger codepoints +// - glyph ids are always consecutive (1..N without gaps) +// +// This way we can omit glyph ids from all calculations entirely: if codepoints +// fit in format0, then glyph ids also will. +// +// format6 is not considered, because if glyph ids can be delta-coded, +// multiple format0 tables are guaranteed to be smaller than a single format6. +// +// sparse format is not used because as long as glyph ids are consecutive, +// sparse_tiny will always be preferred. +// + +'use strict'; + + +function estimate_format0_tiny_size(/*start_code, end_code*/) { + return 16; +} + +function estimate_format0_size(start_code, end_code) { + return 16 + (end_code - start_code + 1); +} + +//function estimate_sparse_size(count) { +// return 16 + count * 4; +//} + +function estimate_sparse_tiny_size(count) { + return 16 + count * 2; +} + +module.exports = function cmap_split(all_codepoints) { + all_codepoints = all_codepoints.sort((a, b) => a - b); + + let min_paths = []; + + for (let i = 0; i < all_codepoints.length; i++) { + let min = { dist: Infinity }; + + for (let j = 0; j <= i; j++) { + let prev_dist = (j - 1 >= 0) ? min_paths[j - 1].dist : 0; + let s; + + if (all_codepoints[i] - all_codepoints[j] < 256) { + s = estimate_format0_size(all_codepoints[j], all_codepoints[i]); + + /* eslint-disable max-depth */ + if (prev_dist + s < min.dist) { + min = { + dist: prev_dist + s, + start: j, + end: i, + format: 'format0' + }; + } + } + + if (all_codepoints[i] - all_codepoints[j] < 256 && all_codepoints[i] - i === all_codepoints[j] - j) { + s = estimate_format0_tiny_size(all_codepoints[j], all_codepoints[i]); + + /* eslint-disable max-depth */ + if (prev_dist + s < min.dist) { + min = { + dist: prev_dist + s, + start: j, + end: i, + format: 'format0_tiny' + }; + } + } + + // tiny sparse will always be preferred over full sparse because glyph ids are consecutive + if (all_codepoints[i] - all_codepoints[j] < 65536) { + s = estimate_sparse_tiny_size(i - j + 1); + + if (prev_dist + s < min.dist) { + min = { + dist: prev_dist + s, + start: j, + end: i, + format: 'sparse_tiny' + }; + } + } + } + + min_paths[i] = min; + } + + let result = []; + + for (let i = all_codepoints.length - 1; i >= 0;) { + let path = min_paths[i]; + result.unshift([ path.format, all_codepoints.slice(path.start, path.end + 1) ]); + i = path.start - 1; + } + + return result; +}; diff --git a/node_modules/lv_font_conv/lib/font/compress.js b/node_modules/lv_font_conv/lib/font/compress.js new file mode 100644 index 00000000..aa30a3a1 --- /dev/null +++ b/node_modules/lv_font_conv/lib/font/compress.js @@ -0,0 +1,107 @@ +'use strict'; + +//const debug = require('debug')('compress'); + +function count_same(arr, offset) { + let same = 1; + let val = arr[offset]; + + for (let i = offset + 1; i < arr.length; i++) { + if (arr[i] !== val) break; + same++; + } + + return same; +} + +// +// Compress pixels with RLE-like algorythm (modified I3BN) +// +// 1. Require minimal repeat count (1) to enter I3BN mode +// 2. Increased 1-bit-replaced repeat limit (2 => 10) +// 3. Length of direct repetition counter reduced (8 => 6 bits). +// +// pixels - flat array of pixels (one per entry) +// options.bpp - bits per pixels +// +module.exports = function compress(bitStream, pixels, options) { + const opts = Object.assign({}, { repeat: 1 }, options); + + // Minimal repetitions count to enable RLE mode. + const RLE_SKIP_COUNT = 1; + // Number of repeats, when `1` used to replace data + // If more - write as number + const RLE_BIT_COLLAPSED_COUNT = 10; + + const RLE_COUNTER_BITS = 6; // (2^bits - 1) - max value + const RLE_COUNTER_MAX = (1 << RLE_COUNTER_BITS) - 1; + // Force flush if counter dencity exceeded. + const RLE_MAX_REPEATS = RLE_COUNTER_MAX + RLE_BIT_COLLAPSED_COUNT + 1; + + //let bits_start_offset = bitStream.index; + + let offset = 0; + + while (offset < pixels.length) { + const p = pixels[offset]; + + let same = count_same(pixels, offset); + + // Clamp value because RLE counter density is limited + if (same > RLE_MAX_REPEATS + RLE_SKIP_COUNT) { + same = RLE_MAX_REPEATS + RLE_SKIP_COUNT; + } + + //debug(`offset: ${offset}, count: ${same}, pixel: ${p}`); + + offset += same; + + // If not enough for RLE - write as is. + if (same <= RLE_SKIP_COUNT) { + for (let i = 0; i < same; i++) { + bitStream.writeBits(p, opts.bpp); + //debug(`==> ${opts.bpp} bits`); + } + continue; + } + + // First, write "skipped" head as is. + for (let i = 0; i < RLE_SKIP_COUNT; i++) { + bitStream.writeBits(p, opts.bpp); + //debug(`==> ${opts.bpp} bits`); + } + + same -= RLE_SKIP_COUNT; + + // Not reached state to use counter => dump bit-extended + if (same <= RLE_BIT_COLLAPSED_COUNT) { + bitStream.writeBits(p, opts.bpp); + //debug(`==> ${opts.bpp} bits (val)`); + for (let i = 0; i < same; i++) { + /*eslint-disable max-depth*/ + if (i < same - 1) { + bitStream.writeBits(1, 1); + //debug('==> 1 bit (rle repeat)'); + } else { + bitStream.writeBits(0, 1); + //debug('==> 1 bit (rle repeat last)'); + } + } + continue; + } + + same -= RLE_BIT_COLLAPSED_COUNT + 1; + + bitStream.writeBits(p, opts.bpp); + //debug(`==> ${opts.bpp} bits (val)`); + + for (let i = 0; i < RLE_BIT_COLLAPSED_COUNT + 1; i++) { + bitStream.writeBits(1, 1); + //debug('==> 1 bit (rle repeat)'); + } + bitStream.writeBits(same, RLE_COUNTER_BITS); + //debug(`==> 4 bits (rle repeat count ${same})`); + } + + //debug(`output bits: ${bitStream.index - bits_start_offset}`); +}; diff --git a/node_modules/lv_font_conv/lib/font/font.js b/node_modules/lv_font_conv/lib/font/font.js new file mode 100644 index 00000000..e5743629 --- /dev/null +++ b/node_modules/lv_font_conv/lib/font/font.js @@ -0,0 +1,131 @@ +// Font class to generate tables +'use strict'; + +const u = require('../utils'); +const debug = require('debug')('font'); +const Head = require('./table_head'); +const Cmap = require('./table_cmap'); +const Glyf = require('./table_glyf'); +const Loca = require('./table_loca'); +const Kern = require('./table_kern'); + +class Font { + constructor(fontData, options) { + this.src = fontData; + + this.opts = options; + + // Map chars to IDs (zero is reserved) + this.glyph_id = { 0: 0 }; + + this.last_id = 1; + this.createIDs(); + debug(`last_id: ${this.last_id}`); + + this.init_tables(); + + this.minY = Math.min(...this.src.glyphs.map(g => g.bbox.y)); + debug(`minY: ${this.minY}`); + this.maxY = Math.max(...this.src.glyphs.map(g => g.bbox.y + g.bbox.height)); + debug(`maxY: ${this.maxY}`); + + // 0 => 1 byte, 1 => 2 bytes + this.glyphIdFormat = Math.max(...Object.values(this.glyph_id)) > 255 ? 1 : 0; + debug(`glyphIdFormat: ${this.glyphIdFormat}`); + + // 1.0 by default, will be stored in font as FP12.4 + this.kerningScale = 1.0; + let kerningMax = Math.max(...this.src.glyphs.map(g => Object.values(g.kerning).map(Math.abs)).flat()); + if (kerningMax >= 7.5) this.kerningScale = Math.ceil(kerningMax / 7.5 * 16) / 16; + debug(`kerningScale: ${this.kerningScale}`); + + // 0 => int, 1 => FP4 + this.advanceWidthFormat = this.hasKerning() ? 1 : 0; + debug(`advanceWidthFormat: ${this.advanceWidthFormat}`); + + this.xy_bits = Math.max(...this.src.glyphs.map(g => Math.max( + u.signed_bits(g.bbox.x), u.signed_bits(g.bbox.y) + ))); + debug(`xy_bits: ${this.xy_bits}`); + + this.wh_bits = Math.max(...this.src.glyphs.map(g => Math.max( + u.unsigned_bits(g.bbox.width), u.unsigned_bits(g.bbox.height) + ))); + debug(`wh_bits: ${this.wh_bits}`); + + this.advanceWidthBits = Math.max(...this.src.glyphs.map( + g => u.signed_bits(this.widthToInt(g.advanceWidth)) + )); + debug(`advanceWidthBits: ${this.advanceWidthBits}`); + + let glyphs = this.src.glyphs; + + this.monospaced = glyphs.every((v, i, arr) => v.advanceWidth === arr[0].advanceWidth); + debug(`monospaced: ${this.monospaced}`); + + // This should stay in the end, because depends on previous variables + // 0 => 2 bytes, 1 => 4 bytes + this.indexToLocFormat = this.glyf.getSize() > 65535 ? 1 : 0; + debug(`indexToLocFormat: ${this.indexToLocFormat}`); + + this.subpixels_mode = options.lcd ? 1 : (options.lcd_v ? 2 : 0); + debug(`subpixels_mode: ${this.subpixels_mode}`); + } + + init_tables() { + this.head = new Head(this); + this.glyf = new Glyf(this); + this.cmap = new Cmap(this); + this.loca = new Loca(this); + this.kern = new Kern(this); + } + + createIDs() { + // Simplified, don't check dupes + this.last_id = 1; + + for (let i = 0; i < this.src.glyphs.length; i++) { + // reserve zero for special cases + this.glyph_id[this.src.glyphs[i].code] = this.last_id; + this.last_id++; + } + } + + hasKerning() { + if (this.opts.no_kerning) return false; + + for (let glyph of this.src.glyphs) { + if (glyph.kerning && Object.keys(glyph.kerning).length) return true; + } + return false; + } + + // Returns integer width, depending on format + widthToInt(val) { + if (this.advanceWidthFormat === 0) return Math.round(val); + + return Math.round(val * 16); + } + + // Convert kerning to FP4.4, useable for writer. Apply `kerningScale`. + kernToFP(val) { + return Math.round(val / this.kerningScale * 16); + } + + toBin() { + const result = Buffer.concat([ + this.head.toBin(), + this.cmap.toBin(), + this.loca.toBin(), + this.glyf.toBin(), + this.kern.toBin() + ]); + + debug(`font size: ${result.length}`); + + return result; + } +} + + +module.exports = Font; diff --git a/node_modules/lv_font_conv/lib/font/table_cmap.js b/node_modules/lv_font_conv/lib/font/table_cmap.js new file mode 100644 index 00000000..8e94bde1 --- /dev/null +++ b/node_modules/lv_font_conv/lib/font/table_cmap.js @@ -0,0 +1,201 @@ +'use strict'; + + +const build_subtables = require('./cmap_build_subtables'); +const u = require('../utils'); +const debug = require('debug')('font.table.cmap'); + + +const O_SIZE = 0; +const O_LABEL = O_SIZE + 4; +const O_COUNT = O_LABEL + 4; + +const HEAD_LENGTH = O_COUNT + 4; + +const SUB_FORMAT_0 = 0; +const SUB_FORMAT_0_TINY = 2; +const SUB_FORMAT_SPARSE = 1; +const SUB_FORMAT_SPARSE_TINY = 3; + + +class Cmap { + constructor(font) { + this.font = font; + this.label = 'cmap'; + + this.sub_heads = []; + this.sub_data = []; + + this.compiled = false; + } + + compile() { + if (this.compiled) return; + this.compiled = true; + + const f = this.font; + + let subtables_plan = build_subtables(f.src.glyphs.map(g => g.code)); + + const count_format0 = subtables_plan.filter(s => s[0] === 'format0').length; + const count_sparse = subtables_plan.length - count_format0; + debug(`${subtables_plan.length} subtable(s): ${count_format0} "format 0", ${count_sparse} "sparse"`); + + for (let [ format, codepoints ] of subtables_plan) { + let g = this.glyphByCode(codepoints[0]); + let start_glyph_id = f.glyph_id[g.code]; + let min_code = codepoints[0]; + let max_code = codepoints[codepoints.length - 1]; + let entries_count = max_code - min_code + 1; + let format_code = 0; + + if (format === 'format0_tiny') { + format_code = SUB_FORMAT_0_TINY; + this.sub_data.push(Buffer.alloc(0)); + } else if (format === 'format0') { + format_code = SUB_FORMAT_0; + this.sub_data.push(this.create_format0_data(min_code, max_code, start_glyph_id)); + } else if (format === 'sparse_tiny') { + entries_count = codepoints.length; + format_code = SUB_FORMAT_SPARSE_TINY; + this.sub_data.push(this.create_sparse_tiny_data(codepoints, start_glyph_id)); + } else { // assume format === 'sparse' + entries_count = codepoints.length; + format_code = SUB_FORMAT_SPARSE; + this.sub_data.push(this.create_sparse_data(codepoints, start_glyph_id)); + } + + this.sub_heads.push(this.createSubHeader( + min_code, + max_code - min_code + 1, + start_glyph_id, + entries_count, + format_code + )); + } + + this.subHeaderUpdateAllOffsets(); + } + + createSubHeader(rangeStart, rangeLen, glyphIdOffset, total, type) { + const buf = Buffer.alloc(16); + + // buf.writeUInt32LE(offset, 0); offset unknown at this moment + buf.writeUInt32LE(rangeStart, 4); + buf.writeUInt16LE(rangeLen, 8); + buf.writeUInt16LE(glyphIdOffset, 10); + buf.writeUInt16LE(total, 12); + buf.writeUInt8(type, 14); + + return buf; + } + + subHeaderUpdateOffset(header, val) { + header.writeUInt32LE(val, 0); + } + + subHeaderUpdateAllOffsets() { + for (let i = 0; i < this.sub_heads.length; i++) { + const offset = HEAD_LENGTH + + u.sum(this.sub_heads.map(h => h.length)) + + u.sum(this.sub_data.slice(0, i).map(d => d.length)); + + this.subHeaderUpdateOffset(this.sub_heads[i], offset); + } + } + + glyphByCode(code) { + for (let g of this.font.src.glyphs) { + if (g.code === code) return g; + } + + return null; + } + + + collect_format0_data(min_code, max_code, start_glyph_id) { + let data = []; + + for (let i = min_code; i <= max_code; i++) { + const g = this.glyphByCode(i); + + if (!g) { + data.push(0); + continue; + } + + const id_delta = this.font.glyph_id[g.code] - start_glyph_id; + + if (id_delta < 0 || id_delta > 255) throw new Error('Glyph ID delta out of Format 0 range'); + + data.push(id_delta); + } + + return data; + } + + create_format0_data(min_code, max_code, start_glyph_id) { + const data = this.collect_format0_data(min_code, max_code, start_glyph_id); + + return u.balign4(Buffer.from(data)); + } + + collect_sparse_data(codepoints, start_glyph_id) { + let codepoints_list = []; + let ids_list = []; + + for (let code of codepoints) { + let g = this.glyphByCode(code); + let id = this.font.glyph_id[g.code]; + + let code_delta = code - codepoints[0]; + let id_delta = id - start_glyph_id; + + if (code_delta < 0 || code_delta > 65535) throw new Error('Codepoint delta out of range'); + if (id_delta < 0 || id_delta > 65535) throw new Error('Glyph ID delta out of range'); + + codepoints_list.push(code_delta); + ids_list.push(id_delta); + } + + return { + codes: codepoints_list, + ids: ids_list + }; + } + + create_sparse_data(codepoints, start_glyph_id) { + const data = this.collect_sparse_data(codepoints, start_glyph_id); + + return u.balign4(Buffer.concat([ + u.bFromA16(data.codes), + u.bFromA16(data.ids) + ])); + } + + create_sparse_tiny_data(codepoints, start_glyph_id) { + const data = this.collect_sparse_data(codepoints, start_glyph_id); + + return u.balign4(u.bFromA16(data.codes)); + } + + toBin() { + if (!this.compiled) this.compile(); + + const buf = Buffer.concat([ + Buffer.alloc(HEAD_LENGTH), + Buffer.concat(this.sub_heads), + Buffer.concat(this.sub_data) + ]); + debug(`table size = ${buf.length}`); + + buf.writeUInt32LE(buf.length, O_SIZE); + buf.write(this.label, O_LABEL); + buf.writeUInt32LE(this.sub_heads.length, O_COUNT); + + return buf; + } +} + + +module.exports = Cmap; diff --git a/node_modules/lv_font_conv/lib/font/table_glyf.js b/node_modules/lv_font_conv/lib/font/table_glyf.js new file mode 100644 index 00000000..e7a62999 --- /dev/null +++ b/node_modules/lv_font_conv/lib/font/table_glyf.js @@ -0,0 +1,147 @@ +'use strict'; + +const u = require('../utils'); +const { BitStream } = require('bit-buffer'); +const debug = require('debug')('font.table.glyf'); +const compress = require('./compress'); + + +const O_SIZE = 0; +const O_LABEL = O_SIZE + 4; + +const HEAD_LENGTH = O_LABEL + 4; + + +class Glyf { + constructor(font) { + this.font = font; + this.label = 'glyf'; + + this.compiled = false; + + this.binData = []; + } + + // convert 8-bit opacity to bpp-bit + pixelsToBpp(pixels) { + const bpp = this.font.opts.bpp; + return pixels.map(line => line.map(p => (p >>> (8 - bpp)))); + } + + // Returns "binary stream" (Buffer) of compiled glyph data + compileGlyph(glyph) { + // Allocate memory, enough for eny storage formats + const buf = Buffer.alloc(100 + glyph.bbox.width * glyph.bbox.height * 4); + const bs = new BitStream(buf); + bs.bigEndian = true; + const f = this.font; + + // Store Width + if (!f.monospaced) { + let w = f.widthToInt(glyph.advanceWidth); + bs.writeBits(w, f.advanceWidthBits); + } + + // Store X, Y + bs.writeBits(glyph.bbox.x, f.xy_bits); + bs.writeBits(glyph.bbox.y, f.xy_bits); + bs.writeBits(glyph.bbox.width, f.wh_bits); + bs.writeBits(glyph.bbox.height, f.wh_bits); + + const pixels = this.pixelsToBpp(glyph.pixels); + + this.storePixels(bs, pixels); + + // Shrink size + const result = Buffer.alloc(bs.byteIndex); + buf.copy(result, 0, 0, bs.byteIndex); + + return result; + } + + storePixels(bitStream, pixels) { + if (this.getCompressionCode() === 0) this.storePixelsRaw(bitStream, pixels); + else this.storePixelsCompressed(bitStream, pixels); + } + + storePixelsRaw(bitStream, pixels) { + const bpp = this.font.opts.bpp; + + for (let y = 0; y < pixels.length; y++) { + const line = pixels[y]; + for (let x = 0; x < line.length; x++) { + bitStream.writeBits(line[x], bpp); + } + } + } + + storePixelsCompressed(bitStream, pixels) { + let p; + + if (this.font.opts.no_prefilter) p = pixels.flat(); + else p = u.prefilter(pixels).flat(); + + compress(bitStream, p, this.font.opts); + } + + // Create internal struct with binary data for each glyph + // Needed to calculate offsets & build final result + compile() { + this.compiled = true; + + this.binData = [ + Buffer.alloc(0) // Reserve id 0 + ]; + + const f = this.font; + + f.src.glyphs.forEach(g => { + const id = f.glyph_id[g.code]; + + this.binData[id] = this.compileGlyph(g); + }); + } + + toBin() { + if (!this.compiled) this.compile(); + + const buf = u.balign4(Buffer.concat([ + Buffer.alloc(HEAD_LENGTH), + Buffer.concat(this.binData) + ])); + + buf.writeUInt32LE(buf.length, O_SIZE); + buf.write(this.label, O_LABEL); + + debug(`table size = ${buf.length}`); + + return buf; + } + + getSize() { + if (!this.compiled) this.compile(); + + return u.align4(HEAD_LENGTH + u.sum(this.binData.map(b => b.length))); + } + + getOffset(id) { + if (!this.compiled) this.compile(); + + let offset = HEAD_LENGTH; + + for (let i = 0; i < id; i++) offset += this.binData[i].length; + + return offset; + } + + getCompressionCode() { + if (this.font.opts.no_compress) return 0; + if (this.font.opts.bpp === 1) return 0; + + if (this.font.opts.no_prefilter) return 2; + return 1; + } +} + + +module.exports = Glyf; diff --git a/node_modules/lv_font_conv/lib/font/table_head.js b/node_modules/lv_font_conv/lib/font/table_head.js new file mode 100644 index 00000000..4cb9f676 --- /dev/null +++ b/node_modules/lv_font_conv/lib/font/table_head.js @@ -0,0 +1,99 @@ +'use strict'; + + +const u = require('../utils'); +const debug = require('debug')('font.table.head'); + +const O_SIZE = 0; +const O_LABEL = O_SIZE + 4; +const O_VERSION = O_LABEL + 4; +const O_TABLES = O_VERSION + 4; +const O_FONT_SIZE = O_TABLES + 2; +const O_ASCENT = O_FONT_SIZE + 2; +const O_DESCENT = O_ASCENT + 2; +const O_TYPO_ASCENT = O_DESCENT + 2; +const O_TYPO_DESCENT = O_TYPO_ASCENT + 2; +const O_TYPO_LINE_GAP = O_TYPO_DESCENT + 2; +const O_MIN_Y = O_TYPO_LINE_GAP + 2; +const O_MAX_Y = O_MIN_Y + 2; +const O_DEF_ADVANCE_WIDTH = O_MAX_Y + 2; +const O_KERNING_SCALE = O_DEF_ADVANCE_WIDTH + 2; +const O_INDEX_TO_LOC_FORMAT = O_KERNING_SCALE + 2; +const O_GLYPH_ID_FORMAT = O_INDEX_TO_LOC_FORMAT + 1; +const O_ADVANCE_WIDTH_FORMAT = O_GLYPH_ID_FORMAT + 1; +const O_BITS_PER_PIXEL = O_ADVANCE_WIDTH_FORMAT + 1; +const O_XY_BITS = O_BITS_PER_PIXEL + 1; +const O_WH_BITS = O_XY_BITS + 1; +const O_ADVANCE_WIDTH_BITS = O_WH_BITS + 1; +const O_COMPRESSION_ID = O_ADVANCE_WIDTH_BITS + 1; +const O_SUBPIXELS_MODE = O_COMPRESSION_ID + 1; +const O_TMP_RESERVED1 = O_SUBPIXELS_MODE + 1; +const O_UNDERLINE_POSITION = O_TMP_RESERVED1 + 1; +const O_UNDERLINE_THICKNESS = O_UNDERLINE_POSITION + 2; +const HEAD_LENGTH = u.align4(O_UNDERLINE_THICKNESS + 2); + + +class Head { + constructor(font) { + this.font = font; + this.label = 'head'; + this.version = 1; + } + + toBin() { + const buf = Buffer.alloc(HEAD_LENGTH); + debug(`table size = ${buf.length}`); + + buf.writeUInt32LE(HEAD_LENGTH, O_SIZE); + buf.write(this.label, O_LABEL); + buf.writeUInt32LE(this.version, O_VERSION); + + const f = this.font; + + const tables_count = f.hasKerning() ? 4 : 3; + + buf.writeUInt16LE(tables_count, O_TABLES); + + buf.writeUInt16LE(f.src.size, O_FONT_SIZE); + buf.writeUInt16LE(f.src.ascent, O_ASCENT); + buf.writeInt16LE(f.src.descent, O_DESCENT); + + buf.writeUInt16LE(f.src.typoAscent, O_TYPO_ASCENT); + buf.writeInt16LE(f.src.typoDescent, O_TYPO_DESCENT); + buf.writeUInt16LE(f.src.typoLineGap, O_TYPO_LINE_GAP); + + buf.writeInt16LE(f.minY, O_MIN_Y); + buf.writeInt16LE(f.maxY, O_MAX_Y); + + if (f.monospaced) { + buf.writeUInt16LE(f.widthToInt(f.src.glyphs[0].advanceWidth), O_DEF_ADVANCE_WIDTH); + } else { + buf.writeUInt16LE(0, O_DEF_ADVANCE_WIDTH); + } + + buf.writeUInt16LE(Math.round(f.kerningScale * 16), O_KERNING_SCALE); // FP12.4 + + buf.writeUInt8(f.indexToLocFormat, O_INDEX_TO_LOC_FORMAT); + buf.writeUInt8(f.glyphIdFormat, O_GLYPH_ID_FORMAT); + buf.writeUInt8(f.advanceWidthFormat, O_ADVANCE_WIDTH_FORMAT); + + buf.writeUInt8(f.opts.bpp, O_BITS_PER_PIXEL); + buf.writeUInt8(f.xy_bits, O_XY_BITS); + buf.writeUInt8(f.wh_bits, O_WH_BITS); + + if (f.monospaced) buf.writeUInt8(0, O_ADVANCE_WIDTH_BITS); + else buf.writeUInt8(f.advanceWidthBits, O_ADVANCE_WIDTH_BITS); + + buf.writeUInt8(f.glyf.getCompressionCode(), O_COMPRESSION_ID); + + buf.writeUInt8(f.subpixels_mode, O_SUBPIXELS_MODE); + + buf.writeInt16LE(f.src.underlinePosition, O_UNDERLINE_POSITION); + buf.writeUInt16LE(f.src.underlineThickness, O_UNDERLINE_POSITION); + + return buf; + } +} + + +module.exports = Head; diff --git a/node_modules/lv_font_conv/lib/font/table_kern.js b/node_modules/lv_font_conv/lib/font/table_kern.js new file mode 100644 index 00000000..e879b3c7 --- /dev/null +++ b/node_modules/lv_font_conv/lib/font/table_kern.js @@ -0,0 +1,256 @@ +'use strict'; + +const u = require('../utils'); +const debug = require('debug')('font.table.kern'); + + +const O_SIZE = 0; +const O_LABEL = O_SIZE + 4; +const O_FORMAT = O_LABEL + 4; + +const HEAD_LENGTH = u.align4(O_FORMAT + 1); + + +class Kern { + constructor(font) { + this.font = font; + this.label = 'kern'; + this.format3_forced = false; + } + + collect_format0_data() { + const f = this.font; + const glyphs = u.sort_by(this.font.src.glyphs, g => f.glyph_id[g.code]); + const kernSorted = []; + + for (let g of glyphs) { + if (!g.kerning || !Object.keys(g.kerning).length) continue; + + const glyph_id = f.glyph_id[g.code]; + const paired = u.sort_by(Object.keys(g.kerning), code => f.glyph_id[code]); + + for (let code of paired) { + const glyph_id2 = f.glyph_id[code]; + kernSorted.push([ glyph_id, glyph_id2, g.kerning[code] ]); + } + } + + return kernSorted; + } + + create_format0_data() { + const f = this.font; + const glyphs = this.font.src.glyphs; + const kernSorted = this.collect_format0_data(); + + const count = kernSorted.length; + + const kerned_glyphs = glyphs.filter(g => Object.keys(g.kerning).length).length; + const kerning_list_max = Math.max(...glyphs.map(g => Object.keys(g.kerning).length)); + debug(`${kerned_glyphs} kerned glyphs of ${glyphs.length}, ${kerning_list_max} max list, ${count} total pairs`); + + const subheader = Buffer.alloc(4); + + subheader.writeUInt32LE(count, 0); + + const pairs_buf = Buffer.alloc((f.glyphIdFormat ? 4 : 2) * count); + + // Write kerning pairs + for (let i = 0; i < count; i++) { + if (f.glyphIdFormat === 0) { + pairs_buf.writeUInt8(kernSorted[i][0], 2 * i); + pairs_buf.writeUInt8(kernSorted[i][1], 2 * i + 1); + } else { + pairs_buf.writeUInt16LE(kernSorted[i][0], 4 * i); + pairs_buf.writeUInt16LE(kernSorted[i][1], 4 * i + 2); + } + } + + const values_buf = Buffer.alloc(count); + + // Write kerning values + for (let i = 0; i < count; i++) { + values_buf.writeInt8(f.kernToFP(kernSorted[i][2]), i); // FP4.4 + } + + let buf = Buffer.concat([ + subheader, + pairs_buf, + values_buf + ]); + + let buf_aligned = u.balign4(buf); + + debug(`table format0 size = ${buf_aligned.length}`); + return buf_aligned; + } + + collect_format3_data() { + const f = this.font; + const glyphs = u.sort_by(this.font.src.glyphs, g => f.glyph_id[g.code]); + + // extract kerning pairs for each character; + // left kernings are kerning values based on left char (already there), + // right kernings are kerning values based on right char (extracted from left) + const left_kernings = {}; + const right_kernings = {}; + + for (let g of glyphs) { + if (!g.kerning || !Object.keys(g.kerning).length) continue; + + const paired = Object.keys(g.kerning); + + left_kernings[g.code] = g.kerning; + + for (let code of paired) { + right_kernings[code] = right_kernings[code] || {}; + right_kernings[code][g.code] = g.kerning[code]; + } + } + + // input: + // - kernings, char => { hash: String, [char1]: Number, [char2]: Number, ... } + // + // returns: + // - array of [ char1, char2, ... ] + // + function build_classes(kernings) { + const classes = []; + + for (let code of Object.keys(kernings)) { + // for each kerning table calculate unique value representing it; + // keys needs to be sorted for this (but we're using numeric keys, so + // sorting happens automatically and can't be changed) + const hash = JSON.stringify(kernings[code]); + + classes[hash] = classes[hash] || []; + classes[hash].push(Number(code)); + } + + return Object.values(classes); + } + + const left_classes = build_classes(left_kernings); + debug(`unique left classes: ${left_classes.length}`); + + const right_classes = build_classes(right_kernings); + debug(`unique right classes: ${right_classes.length}`); + + if (left_classes.length >= 255 || right_classes.length >= 255) { + debug('too many classes for format3 subtable'); + return null; + } + + function kern_class_mapping(classes) { + const arr = Array(f.last_id).fill(0); + + classes.forEach((members, idx) => { + for (let code of members) { + arr[f.glyph_id[code]] = idx + 1; + } + }); + + return arr; + } + + function kern_class_values() { + const arr = []; + + for (let left_class of left_classes) { + for (let right_class of right_classes) { + let code1 = left_class[0]; + let code2 = right_class[0]; + arr.push(left_kernings[code1][code2] || 0); + } + } + + return arr; + } + + return { + left_classes: left_classes.length, + right_classes: right_classes.length, + left_mapping: kern_class_mapping(left_classes), + right_mapping: kern_class_mapping(right_classes), + values: kern_class_values() + }; + } + + create_format3_data() { + const f = this.font; + const { + left_classes, + right_classes, + left_mapping, + right_mapping, + values + } = this.collect_format3_data(); + + const subheader = Buffer.alloc(4); + subheader.writeUInt16LE(f.last_id); + subheader.writeUInt8(left_classes, 2); + subheader.writeUInt8(right_classes, 3); + + let buf = Buffer.concat([ + subheader, + Buffer.from(left_mapping), + Buffer.from(right_mapping), + Buffer.from(values.map(v => f.kernToFP(v))) + ]); + + let buf_aligned = u.balign4(buf); + + debug(`table format3 size = ${buf_aligned.length}`); + return buf_aligned; + } + + should_use_format3() { + if (!this.font.hasKerning()) return false; + + const format0_data = this.create_format0_data(); + const format3_data = this.create_format3_data(); + + if (format3_data && format3_data.length <= format0_data.length) return true; + + if (this.font.opts.fast_kerning && format3_data) { + this.format3_forced = true; + return true; + } + + return false; + } + + toBin() { + if (!this.font.hasKerning()) return Buffer.alloc(0); + + const format0_data = this.create_format0_data(); + const format3_data = this.create_format3_data(); + + let header = Buffer.alloc(HEAD_LENGTH); + + let data = format0_data; + header.writeUInt8(0, O_FORMAT); + + /* eslint-disable no-console */ + + if (this.should_use_format3()) { + data = format3_data; + header.writeUInt8(3, O_FORMAT); + + if (this.format3_forced) { + let diff = format3_data.length - format0_data.length; + console.log(`Forced faster kerning format (via classes). Size increase is ${diff} bytes.`); + } + } else if (this.font.opts.fast_kerning) { + console.log('Forced faster kerning format (via classes), but data exceeds it\'s limits. Continue use pairs.'); + } + + header.writeUInt32LE(header.length + data.length, O_SIZE); + header.write(this.label, O_LABEL); + + return Buffer.concat([ header, data ]); + } +} + + +module.exports = Kern; diff --git a/node_modules/lv_font_conv/lib/font/table_loca.js b/node_modules/lv_font_conv/lib/font/table_loca.js new file mode 100644 index 00000000..4aa50d22 --- /dev/null +++ b/node_modules/lv_font_conv/lib/font/table_loca.js @@ -0,0 +1,42 @@ +'use strict'; + + +const u = require('../utils'); +const debug = require('debug')('font.table.loca'); + + +const O_SIZE = 0; +const O_LABEL = O_SIZE + 4; +const O_COUNT = O_LABEL + 4; + +const HEAD_LENGTH = O_COUNT + 4; + + +class Loca { + constructor(font) { + this.font = font; + this.label = 'loca'; + } + + toBin() { + const f = this.font; + + const offsets = [ ...Array(f.last_id).keys() ].map(i => f.glyf.getOffset(i)); + + const buf = u.balign4(Buffer.concat([ + Buffer.alloc(HEAD_LENGTH), + f.indexToLocFormat ? u.bFromA32(offsets) : u.bFromA16(offsets) + ])); + + buf.writeUInt32LE(buf.length, O_SIZE); + buf.write(this.label, O_LABEL); + buf.writeUInt32LE(f.last_id, O_COUNT); + + debug(`table size = ${buf.length}`); + + return buf; + } +} + + +module.exports = Loca; diff --git a/node_modules/lv_font_conv/lib/freetype/index.js b/node_modules/lv_font_conv/lib/freetype/index.js new file mode 100644 index 00000000..d05f68c3 --- /dev/null +++ b/node_modules/lv_font_conv/lib/freetype/index.js @@ -0,0 +1,317 @@ +'use strict'; + + +const ft_render_fabric = require('./build/ft_render'); + +let m = null; // compiled module instance +let library = 0; // pointer to library struct in wasm memory + + +// workaround because of bug in emscripten: +// https://github.com/emscripten-core/emscripten/issues/5820 +const runtime_initialized = new Promise(resolve => { + ft_render_fabric().then(module_instance => { + m = module_instance; + resolve(); + }); +}); + +function from_16_16(fixed_point) { + return fixed_point / (1 << 16); +} + +function from_26_6(fixed_point) { + return fixed_point / (1 << 6); +} + +function int8_to_uint8(value) { + return value >= 0 ? value : value + 0x100; +} + +let FT_New_Memory_Face, + FT_Set_Char_Size, + FT_Set_Pixel_Sizes, + FT_Get_Char_Index, + FT_Load_Glyph, + FT_Get_Sfnt_Table, + FT_Get_Kerning, + FT_Done_Face; + +module.exports.init = async function () { + await runtime_initialized; + m._init_constants(); + + FT_New_Memory_Face = module.exports.FT_New_Memory_Face = + m.cwrap('FT_New_Memory_Face', 'number', [ 'number', 'number', 'number', 'number', 'number' ]); + + FT_Set_Char_Size = module.exports.FT_Set_Char_Size = + m.cwrap('FT_Set_Char_Size', 'number', [ 'number', 'number', 'number', 'number', 'number' ]); + + FT_Set_Pixel_Sizes = module.exports.FT_Set_Pixel_Sizes = + m.cwrap('FT_Set_Pixel_Sizes', 'number', [ 'number', 'number', 'number' ]); + + FT_Get_Char_Index = module.exports.FT_Get_Char_Index = + m.cwrap('FT_Get_Char_Index', 'number', [ 'number', 'number' ]); + + FT_Load_Glyph = module.exports.FT_Load_Glyph = + m.cwrap('FT_Load_Glyph', 'number', [ 'number', 'number', 'number' ]); + + FT_Get_Sfnt_Table = module.exports.FT_Get_Sfnt_Table = + m.cwrap('FT_Get_Sfnt_Table', 'number', [ 'number', 'number' ]); + + FT_Get_Kerning = module.exports.FT_Get_Kerning = + m.cwrap('FT_Get_Kerning', 'number', [ 'number', 'number', 'number', 'number', 'number' ]); + + FT_Done_Face = module.exports.FT_Done_Face = + m.cwrap('FT_Done_Face', 'number', [ 'number' ]); + + if (!library) { + let ptr = m._malloc(4); + + try { + let error = m.ccall('FT_Init_FreeType', 'number', [ 'number' ], [ ptr ]); + + if (error) throw new Error(`error in FT_Init_FreeType: ${error}`); + + library = m.getValue(ptr, 'i32'); + } finally { + m._free(ptr); + } + } +}; + + +module.exports.fontface_create = function (source, size) { + let error; + let face = { + ptr: 0, + font: m._malloc(source.length) + }; + + m.writeArrayToMemory(source, face.font); + + let ptr = m._malloc(4); + + try { + error = FT_New_Memory_Face(library, face.font, source.length, 0, ptr); + + if (error) throw new Error(`error in FT_New_Memory_Face: ${error}`); + + face.ptr = m.getValue(ptr, 'i32'); + } finally { + m._free(ptr); + } + + error = FT_Set_Char_Size(face.ptr, 0, size * 64, 300, 300); + + if (error) throw new Error(`error in FT_Set_Char_Size: ${error}`); + + error = FT_Set_Pixel_Sizes(face.ptr, 0, size); + + if (error) throw new Error(`error in FT_Set_Pixel_Sizes: ${error}`); + + let units_per_em = m.getValue(face.ptr + m.OFFSET_FACE_UNITS_PER_EM, 'i16'); + let ascender = m.getValue(face.ptr + m.OFFSET_FACE_ASCENDER, 'i16'); + let descender = m.getValue(face.ptr + m.OFFSET_FACE_DESCENDER, 'i16'); + let height = m.getValue(face.ptr + m.OFFSET_FACE_HEIGHT, 'i16'); + + return Object.assign(face, { + units_per_em, + ascender, + descender, + height + }); +}; + + +module.exports.fontface_os2_table = function (face) { + let sfnt_ptr = FT_Get_Sfnt_Table(face.ptr, m.FT_SFNT_OS2); + + if (!sfnt_ptr) throw new Error('os/2 table not found for this font'); + + let typoAscent = m.getValue(sfnt_ptr + m.OFFSET_TT_OS2_ASCENDER, 'i16'); + let typoDescent = m.getValue(sfnt_ptr + m.OFFSET_TT_OS2_DESCENDER, 'i16'); + let typoLineGap = m.getValue(sfnt_ptr + m.OFFSET_TT_OS2_LINEGAP, 'i16'); + + return { + typoAscent, + typoDescent, + typoLineGap + }; +}; + + +module.exports.get_kerning = function (face, code1, code2) { + let glyph1 = FT_Get_Char_Index(face.ptr, code1); + let glyph2 = FT_Get_Char_Index(face.ptr, code2); + let ptr = m._malloc(4 * 2); + + try { + let error = FT_Get_Kerning(face.ptr, glyph1, glyph2, m.FT_KERNING_DEFAULT, ptr); + + if (error) throw new Error(`error in FT_Get_Kerning: ${error}`); + } finally { + m._free(ptr); + } + + return { + x: from_26_6(m.getValue(ptr, 'i32')), + y: from_26_6(m.getValue(ptr + 4, 'i32')) + }; +}; + + +module.exports.glyph_exists = function (face, code) { + let glyph_index = FT_Get_Char_Index(face.ptr, code); + + return glyph_index !== 0; +}; + + +module.exports.glyph_render = function (face, code, opts = {}) { + let glyph_index = FT_Get_Char_Index(face.ptr, code); + + if (glyph_index === 0) throw new Error(`glyph does not exist for codepoint ${code}`); + + let load_flags = m.FT_LOAD_RENDER; + + if (opts.mono) { + load_flags |= m.FT_LOAD_TARGET_MONO; + + } else if (opts.lcd) { + load_flags |= m.FT_LOAD_TARGET_LCD; + + } else if (opts.lcd_v) { + load_flags |= m.FT_LOAD_TARGET_LCD_V; + + } else { + /* eslint-disable no-lonely-if */ + + // Use "light" by default, it changes horizontal lines only. + // "normal" is more strong (with vertical lines), but will break kerning, if + // no additional care taken. More advanced rendering requires upper level + // layout support (via Harfbuzz, for example). + if (!opts.autohint_strong) load_flags |= m.FT_LOAD_TARGET_LIGHT; + else load_flags |= m.FT_LOAD_TARGET_NORMAL; + } + + if (opts.autohint_off) load_flags |= m.FT_LOAD_NO_AUTOHINT; + else load_flags |= m.FT_LOAD_FORCE_AUTOHINT; + + if (opts.use_color_info) load_flags |= m.FT_LOAD_COLOR; + + let error = FT_Load_Glyph(face.ptr, glyph_index, load_flags); + + if (error) throw new Error(`error in FT_Load_Glyph: ${error}`); + + let glyph = m.getValue(face.ptr + m.OFFSET_FACE_GLYPH, 'i32'); + + let glyph_data = { + glyph_index: m.getValue(glyph + m.OFFSET_GLYPH_INDEX, 'i32'), + metrics: { + width: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_WIDTH, 'i32')), + height: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_HEIGHT, 'i32')), + horiBearingX: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_HORI_BEARING_X, 'i32')), + horiBearingY: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_HORI_BEARING_Y, 'i32')), + horiAdvance: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_HORI_ADVANCE, 'i32')), + vertBearingX: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_VERT_BEARING_X, 'i32')), + vertBearingY: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_VERT_BEARING_Y, 'i32')), + vertAdvance: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_VERT_ADVANCE, 'i32')) + }, + linearHoriAdvance: from_16_16(m.getValue(glyph + m.OFFSET_GLYPH_LINEAR_HORI_ADVANCE, 'i32')), + linearVertAdvance: from_16_16(m.getValue(glyph + m.OFFSET_GLYPH_LINEAR_VERT_ADVANCE, 'i32')), + advance: { + x: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_ADVANCE_X, 'i32')), + y: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_ADVANCE_Y, 'i32')) + }, + bitmap: { + width: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_WIDTH, 'i32'), + rows: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_ROWS, 'i32'), + pitch: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_PITCH, 'i32'), + num_grays: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_NUM_GRAYS, 'i16'), + pixel_mode: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_PIXEL_MODE, 'i8'), + palette_mode: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_PALETTE_MODE, 'i8') + }, + bitmap_left: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_LEFT, 'i32'), + bitmap_top: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_TOP, 'i32'), + lsb_delta: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_LSB_DELTA, 'i32')), + rsb_delta: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_RSB_DELTA, 'i32')) + }; + + let g_w = glyph_data.bitmap.width; + let g_h = glyph_data.bitmap.rows; + let g_x = glyph_data.bitmap_left; + let g_y = glyph_data.bitmap_top; + + let buffer = m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_BUFFER, 'i32'); + let pitch = Math.abs(glyph_data.bitmap.pitch); + + let advance_x = glyph_data.linearHoriAdvance; + let advance_y = glyph_data.linearVertAdvance; + + let pixel_mode = glyph_data.bitmap.pixel_mode; + + let output = []; + + for (let y = 0; y < g_h; y++) { + let row_start = buffer + y * pitch; + let line = []; + + for (let x = 0; x < g_w; x++) { + if (pixel_mode === m.FT_PIXEL_MODE_MONO) { + let value = m.getValue(row_start + ~~(x / 8), 'i8'); + line.push(value & (1 << (7 - (x % 8))) ? 255 : 0); + } else if (pixel_mode === m.FT_PIXEL_MODE_BGRA) { + let blue = int8_to_uint8(m.getValue(row_start + (x * 4) + 0, 'i8')); + let green = int8_to_uint8(m.getValue(row_start + (x * 4) + 1, 'i8')); + let red = int8_to_uint8(m.getValue(row_start + (x * 4) + 2, 'i8')); + let alpha = int8_to_uint8(m.getValue(row_start + (x * 4) + 3, 'i8')); + // convert RGBA to grayscale + let grayscale = Math.round(0.299 * red + 0.587 * green + 0.114 * blue); + if (grayscale > 255) grayscale = 255; + // meld grayscale into alpha channel + alpha = ((255 - grayscale) * alpha) / 255; + line.push(alpha); + } else { + let value = m.getValue(row_start + x, 'i8'); + line.push(int8_to_uint8(value)); + } + } + + output.push(line); + } + + return { + x: g_x, + y: g_y, + width: g_w, + height: g_h, + advance_x, + advance_y, + pixels: output, + freetype: glyph_data + }; +}; + + +module.exports.fontface_destroy = function (face) { + let error = FT_Done_Face(face.ptr); + + if (error) throw new Error(`error in FT_Done_Face: ${error}`); + + m._free(face.font); + face.ptr = 0; + face.font = 0; +}; + + +module.exports.destroy = function () { + let error = m.ccall('FT_Done_FreeType', 'number', [ 'number' ], [ library ]); + + if (error) throw new Error(`error in FT_Done_FreeType: ${error}`); + + library = 0; + + // don't unload wasm - slows down tests too much + //m = null; +}; diff --git a/node_modules/lv_font_conv/lib/freetype/render.c b/node_modules/lv_font_conv/lib/freetype/render.c new file mode 100644 index 00000000..901d4974 --- /dev/null +++ b/node_modules/lv_font_conv/lib/freetype/render.c @@ -0,0 +1,83 @@ +#include +#include +#include FT_FREETYPE_H +#include FT_TRUETYPE_TABLES_H + +static void set_js_variable(char* name, int value) { + char buffer[strlen(name) + 32]; + sprintf(buffer, "Module.%s = %d;", name, value); + emscripten_run_script(buffer); +} + +// Expose constants, used in calls from js +void init_constants() +{ + set_js_variable("FT_LOAD_DEFAULT", FT_LOAD_DEFAULT); + set_js_variable("FT_LOAD_NO_HINTING", FT_LOAD_NO_HINTING); + set_js_variable("FT_LOAD_RENDER", FT_LOAD_RENDER); + set_js_variable("FT_LOAD_FORCE_AUTOHINT", FT_LOAD_FORCE_AUTOHINT); + set_js_variable("FT_LOAD_PEDANTIC", FT_LOAD_PEDANTIC); + set_js_variable("FT_LOAD_MONOCHROME", FT_LOAD_MONOCHROME); + set_js_variable("FT_LOAD_NO_AUTOHINT", FT_LOAD_NO_AUTOHINT); + set_js_variable("FT_LOAD_COLOR", FT_LOAD_COLOR); + + set_js_variable("FT_LOAD_TARGET_NORMAL", FT_LOAD_TARGET_NORMAL); + set_js_variable("FT_LOAD_TARGET_LIGHT", FT_LOAD_TARGET_LIGHT); + set_js_variable("FT_LOAD_TARGET_MONO", FT_LOAD_TARGET_MONO); + set_js_variable("FT_LOAD_TARGET_LCD", FT_LOAD_TARGET_LCD); + set_js_variable("FT_LOAD_TARGET_LCD_V", FT_LOAD_TARGET_LCD_V); + + set_js_variable("FT_RENDER_MODE_NORMAL", FT_RENDER_MODE_NORMAL); + set_js_variable("FT_RENDER_MODE_MONO", FT_RENDER_MODE_MONO); + set_js_variable("FT_RENDER_MODE_LCD", FT_RENDER_MODE_LCD); + set_js_variable("FT_RENDER_MODE_LCD_V", FT_RENDER_MODE_LCD_V); + + set_js_variable("FT_KERNING_DEFAULT", FT_KERNING_DEFAULT); + set_js_variable("FT_KERNING_UNFITTED", FT_KERNING_UNFITTED); + set_js_variable("FT_KERNING_UNSCALED", FT_KERNING_UNSCALED); + + set_js_variable("FT_SFNT_OS2", FT_SFNT_OS2); + + set_js_variable("FT_FACE_FLAG_COLOR", FT_FACE_FLAG_COLOR); + + set_js_variable("FT_PIXEL_MODE_MONO", FT_PIXEL_MODE_MONO); + set_js_variable("FT_PIXEL_MODE_BGRA", FT_PIXEL_MODE_BGRA); + + set_js_variable("OFFSET_FACE_GLYPH", offsetof(FT_FaceRec, glyph)); + set_js_variable("OFFSET_FACE_UNITS_PER_EM", offsetof(FT_FaceRec, units_per_EM)); + set_js_variable("OFFSET_FACE_ASCENDER", offsetof(FT_FaceRec, ascender)); + set_js_variable("OFFSET_FACE_DESCENDER", offsetof(FT_FaceRec, descender)); + set_js_variable("OFFSET_FACE_HEIGHT", offsetof(FT_FaceRec, height)); + set_js_variable("OFFSET_FACE_FACE_FLAGS", offsetof(FT_FaceRec, face_flags)); + + set_js_variable("OFFSET_GLYPH_BITMAP_WIDTH", offsetof(FT_GlyphSlotRec, bitmap.width)); + set_js_variable("OFFSET_GLYPH_BITMAP_ROWS", offsetof(FT_GlyphSlotRec, bitmap.rows)); + set_js_variable("OFFSET_GLYPH_BITMAP_PITCH", offsetof(FT_GlyphSlotRec, bitmap.pitch)); + set_js_variable("OFFSET_GLYPH_BITMAP_BUFFER", offsetof(FT_GlyphSlotRec, bitmap.buffer)); + set_js_variable("OFFSET_GLYPH_BITMAP_NUM_GRAYS", offsetof(FT_GlyphSlotRec, bitmap.num_grays)); + set_js_variable("OFFSET_GLYPH_BITMAP_PIXEL_MODE", offsetof(FT_GlyphSlotRec, bitmap.pixel_mode)); + set_js_variable("OFFSET_GLYPH_BITMAP_PALETTE_MODE", offsetof(FT_GlyphSlotRec, bitmap.palette_mode)); + + set_js_variable("OFFSET_GLYPH_METRICS_WIDTH", offsetof(FT_GlyphSlotRec, metrics.width)); + set_js_variable("OFFSET_GLYPH_METRICS_HEIGHT", offsetof(FT_GlyphSlotRec, metrics.height)); + set_js_variable("OFFSET_GLYPH_METRICS_HORI_BEARING_X", offsetof(FT_GlyphSlotRec, metrics.horiBearingX)); + set_js_variable("OFFSET_GLYPH_METRICS_HORI_BEARING_Y", offsetof(FT_GlyphSlotRec, metrics.horiBearingY)); + set_js_variable("OFFSET_GLYPH_METRICS_HORI_ADVANCE", offsetof(FT_GlyphSlotRec, metrics.horiAdvance)); + set_js_variable("OFFSET_GLYPH_METRICS_VERT_BEARING_X", offsetof(FT_GlyphSlotRec, metrics.vertBearingX)); + set_js_variable("OFFSET_GLYPH_METRICS_VERT_BEARING_Y", offsetof(FT_GlyphSlotRec, metrics.vertBearingY)); + set_js_variable("OFFSET_GLYPH_METRICS_VERT_ADVANCE", offsetof(FT_GlyphSlotRec, metrics.vertAdvance)); + + set_js_variable("OFFSET_GLYPH_BITMAP_LEFT", offsetof(FT_GlyphSlotRec, bitmap_left)); + set_js_variable("OFFSET_GLYPH_BITMAP_TOP", offsetof(FT_GlyphSlotRec, bitmap_top)); + set_js_variable("OFFSET_GLYPH_INDEX", offsetof(FT_GlyphSlotRec, glyph_index)); + set_js_variable("OFFSET_GLYPH_LINEAR_HORI_ADVANCE", offsetof(FT_GlyphSlotRec, linearHoriAdvance)); + set_js_variable("OFFSET_GLYPH_LINEAR_VERT_ADVANCE", offsetof(FT_GlyphSlotRec, linearVertAdvance)); + set_js_variable("OFFSET_GLYPH_ADVANCE_X", offsetof(FT_GlyphSlotRec, advance.x)); + set_js_variable("OFFSET_GLYPH_ADVANCE_Y", offsetof(FT_GlyphSlotRec, advance.y)); + set_js_variable("OFFSET_GLYPH_LSB_DELTA", offsetof(FT_GlyphSlotRec, lsb_delta)); + set_js_variable("OFFSET_GLYPH_RSB_DELTA", offsetof(FT_GlyphSlotRec, rsb_delta)); + + set_js_variable("OFFSET_TT_OS2_ASCENDER", offsetof(TT_OS2, sTypoAscender)); + set_js_variable("OFFSET_TT_OS2_DESCENDER", offsetof(TT_OS2, sTypoDescender)); + set_js_variable("OFFSET_TT_OS2_LINEGAP", offsetof(TT_OS2, sTypoLineGap)); +} diff --git a/node_modules/lv_font_conv/lib/ranger.js b/node_modules/lv_font_conv/lib/ranger.js new file mode 100644 index 00000000..34372d75 --- /dev/null +++ b/node_modules/lv_font_conv/lib/ranger.js @@ -0,0 +1,51 @@ +// Merge ranges into single object + +'use strict'; + + +class Ranger { + constructor() { + this.data = {}; + } + + // input: + // -r 0x1F450 - single value, dec or hex format + // -r 0x1F450-0x1F470 - range + // -r 0x1F450=>0xF005 - single glyph with mapping + // -r 0x1F450-0x1F470=>0xF005 - range with mapping + add_range(font, start, end, mapped_start) { + let offset = mapped_start - start; + let output = []; + + for (let i = start; i <= end; i++) { + this._set_char(font, i, i + offset); + output.push(i); + } + + return output; + } + + // input: characters to copy, e.g. '1234567890abcdef' + add_symbols(font, str) { + let output = []; + + for (let chr of str) { + let code = chr.codePointAt(0); + this._set_char(font, code, code); + output.push(code); + } + + return output; + } + + _set_char(font, code, mapped_to) { + this.data[mapped_to] = { font, code }; + } + + get() { + return this.data; + } +} + + +module.exports = Ranger; diff --git a/node_modules/lv_font_conv/lib/utils.js b/node_modules/lv_font_conv/lib/utils.js new file mode 100644 index 00000000..0ca79322 --- /dev/null +++ b/node_modules/lv_font_conv/lib/utils.js @@ -0,0 +1,131 @@ +'use strict'; + + +function set_byte_depth(depth) { + return function (byte) { + // calculate significant bits, e.g. for depth=2 it's 0, 1, 2 or 3 + let value = ~~(byte / (256 >> depth)); + + // spread those bits around 0..255 range, e.g. for depth=2 it's 0, 85, 170 or 255 + let scale = (2 << (depth - 1)) - 1; + + return (value * 0xFFFF / scale) >> 8; + }; +} + + +module.exports.set_depth = function set_depth(glyph, depth) { + let pixels = []; + let fn = set_byte_depth(depth); + + for (let y = 0; y < glyph.bbox.height; y++) { + pixels.push(glyph.pixels[y].map(fn)); + } + + return Object.assign({}, glyph, { pixels }); +}; + + +function count_bits(val) { + let count = 0; + val = ~~val; + + while (val) { + count++; + val >>= 1; + } + + return count; +} + +// Minimal number of bits to store unsigned value +module.exports.unsigned_bits = count_bits; + +// Minimal number of bits to store signed value +module.exports.signed_bits = function signed_bits(val) { + if (val >= 0) return count_bits(val) + 1; + + return count_bits(Math.abs(val) - 1) + 1; +}; + +// Align value to 4x - useful to create word-aligned arrays +function align4(size) { + if (size % 4 === 0) return size; + return size + 4 - (size % 4); +} +module.exports.align4 = align4; + +// Align buffer length to 4x (returns copy with zero-filled tail) +module.exports.balign4 = function balign4(buf) { + let buf_aligned = Buffer.alloc(align4(buf.length)); + buf.copy(buf_aligned); + return buf_aligned; +}; + +// Pre-filter image to improve compression ratio +// In this case - XOR lines, because it's very effective +// in decompressor and does not depend on bpp. +module.exports.prefilter = function prefilter(pixels) { + return pixels.map((line, l_idx, arr) => { + if (l_idx === 0) return line.slice(); + + return line.map((p, idx) => p ^ arr[l_idx - 1][idx]); + }); +}; + + +// Convert array with uint16 data to buffer +module.exports.bFromA16 = function bFromA16(arr) { + const buf = Buffer.alloc(arr.length * 2); + + for (let i = 0; i < arr.length; i++) buf.writeUInt16LE(arr[i], i * 2); + + return buf; +}; + +// Convert array with uint32 data to buffer +module.exports.bFromA32 = function bFromA32(arr) { + const buf = Buffer.alloc(arr.length * 4); + + for (let i = 0; i < arr.length; i++) buf.writeUInt32LE(arr[i], i * 4); + + return buf; +}; + + +function chunk(arr, size) { + const result = []; + for (let i = 0; i < arr.length; i += size) { + result.push(arr.slice(i, i + size)); + } + return result; +} + +// Dump long array to multiline format with X columns and Y indent +module.exports.long_dump = function long_dump(arr, options = {}) { + const defaults = { + col: 8, + indent: 4, + hex: false + }; + + let opts = Object.assign({}, defaults, options); + let indent = ' '.repeat(opts.indent); + + return chunk(Array.from(arr), opts.col) + .map(l => l.map(v => (opts.hex ? `0x${v.toString(16)}` : v.toString()))) + .map(l => `${indent}${l.join(', ')}`) + .join(',\n'); +}; + +// stable sort by pick() result +module.exports.sort_by = function sort_by(arr, pick) { + return arr + .map((el, idx) => ({ el, idx })) + .sort((a, b) => (pick(a.el) - pick(b.el)) || (a.idx - b.idx)) + .map(({ el }) => el); +}; + +module.exports.sum = function sum(arr) { + return arr.reduce((a, v) => a + v, 0); +}; diff --git a/node_modules/lv_font_conv/lib/writers/bin.js b/node_modules/lv_font_conv/lib/writers/bin.js new file mode 100644 index 00000000..bb482080 --- /dev/null +++ b/node_modules/lv_font_conv/lib/writers/bin.js @@ -0,0 +1,17 @@ +// Write font in binary format +'use strict'; + + +const AppError = require('../app_error'); +const Font = require('../font/font'); + + +module.exports = function write_images(args, fontData) { + if (!args.output) throw new AppError('Output is required for "bin" writer'); + + const font = new Font(fontData, args); + + return { + [args.output]: font.toBin() + }; +}; diff --git a/node_modules/lv_font_conv/lib/writers/dump.js b/node_modules/lv_font_conv/lib/writers/dump.js new file mode 100644 index 00000000..150d3b99 --- /dev/null +++ b/node_modules/lv_font_conv/lib/writers/dump.js @@ -0,0 +1,68 @@ +// Write font data into png images + +'use strict'; + + +const path = require('path'); +const { PNG } = require('pngjs'); +const AppError = require('../app_error'); +const utils = require('../utils'); + +const normal_color = [ 255, 255, 255 ]; +const outside_color = [ 255, 127, 184 ]; + + +module.exports = function write_images(args, font) { + if (!args.output) throw new AppError('Output is required for "dump" writer'); + + let files = {}; + + let glyphs = font.glyphs.map(glyph => utils.set_depth(glyph, args.bpp)); + + for (let glyph of glyphs) { + let { code, advanceWidth, bbox, pixels } = glyph; + + advanceWidth = Math.round(advanceWidth); + + let minX = bbox.x; + let maxX = Math.max(bbox.x + bbox.width - 1, bbox.x); + let minY = Math.min(bbox.y, font.typoDescent); + let maxY = Math.max(bbox.y + bbox.height - 1, font.typoAscent); + + let png = new PNG({ width: maxX - minX + 1, height: maxY - minY + 1 }); + + /* eslint-disable max-depth */ + for (let pos = 0, y = maxY; y >= minY; y--) { + for (let x = minX; x <= maxX; x++) { + let value = 0; + + if (x >= bbox.x && x < bbox.x + bbox.width && y >= bbox.y && y < bbox.y + bbox.height) { + value = pixels[bbox.height - (y - bbox.y) - 1][x - bbox.x]; + } + + let r, g, b; + + if (x < 0 || x >= advanceWidth || y < font.typoDescent || y > font.typoAscent) { + [ r, g, b ] = outside_color; + } else { + [ r, g, b ] = normal_color; + } + + png.data[pos++] = (255 - value) * r / 255; + png.data[pos++] = (255 - value) * g / 255; + png.data[pos++] = (255 - value) * b / 255; + png.data[pos++] = 255; + } + } + + + files[path.join(args.output, `${code.toString(16)}.png`)] = PNG.sync.write(png); + } + + files[path.join(args.output, 'font_info.json')] = JSON.stringify( + font, + (k, v) => (k === 'pixels' && !args.full_info ? undefined : v), + 2); + + return files; +}; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/index.js b/node_modules/lv_font_conv/lib/writers/lvgl/index.js new file mode 100644 index 00000000..b592104f --- /dev/null +++ b/node_modules/lv_font_conv/lib/writers/lvgl/index.js @@ -0,0 +1,17 @@ +// Write font in lvgl format +'use strict'; + + +const AppError = require('../../app_error'); +const Font = require('./lv_font'); + + +module.exports = function write_images(args, fontData) { + if (!args.output) throw new AppError('Output is required for "lvgl" writer'); + + const font = new Font(fontData, args); + + return { + [args.output]: font.toLVGL() + }; +}; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/lv_font.js b/node_modules/lv_font_conv/lib/writers/lvgl/lv_font.js new file mode 100644 index 00000000..e3ad9c72 --- /dev/null +++ b/node_modules/lv_font_conv/lib/writers/lvgl/lv_font.js @@ -0,0 +1,98 @@ +'use strict'; + + +const path = require('path'); + +const Font = require('../../font/font'); +const Head = require('./lv_table_head'); +const Cmap = require('./lv_table_cmap'); +const Glyf = require('./lv_table_glyf'); +const Kern = require('./lv_table_kern'); +const AppError = require('../../app_error'); + + +class LvFont extends Font { + constructor(fontData, options) { + super(fontData, options); + + const ext = path.extname(options.output); + this.font_name = path.basename(options.output, ext); + + if (options.bpp === 3 & options.no_compress) { + throw new AppError('LittlevGL supports "--bpp 3" with compression only'); + } + } + + init_tables() { + this.head = new Head(this); + this.glyf = new Glyf(this); + this.cmap = new Cmap(this); + this.kern = new Kern(this); + } + + large_format_guard() { + let guard_required = false; + let glyphs_bin_size = 0; + + this.glyf.lv_data.forEach(d => { + glyphs_bin_size += d.bin.length; + + if (d.glyph.bbox.width > 255 || + d.glyph.bbox.height > 255 || + Math.abs(d.glyph.bbox.x) > 127 || + Math.abs(d.glyph.bbox.y) > 127 || + Math.round(d.glyph.advanceWidth * 16) > 4096) { + guard_required = true; + } + }); + + if (glyphs_bin_size > 1024 * 1024) guard_required = true; + + if (!guard_required) return ''; + + return ` +#if (LV_FONT_FMT_TXT_LARGE == 0) +# error "Too large font or glyphs in ${this.font_name.toUpperCase()}. Enable LV_FONT_FMT_TXT_LARGE in lv_conf.h") +#endif +`.trimLeft(); + } + + toLVGL() { + let guard_name = this.font_name.toUpperCase(); + + return `/******************************************************************************* + * Size: ${this.src.size} px + * Bpp: ${this.opts.bpp} + * Opts: ${process.argv.slice(2).join(' ')} + ******************************************************************************/ + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "${this.opts.lv_include || 'lvgl/lvgl.h'}" +#endif + +#ifndef ${guard_name} +#define ${guard_name} 1 +#endif + +#if ${guard_name} + +${this.glyf.toLVGL()} + +${this.cmap.toLVGL()} + +${this.kern.toLVGL()} + +${this.head.toLVGL()} + +${this.large_format_guard()} + +#endif /*#if ${guard_name}*/ + +`; + } +} + + +module.exports = LvFont; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_cmap.js b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_cmap.js new file mode 100644 index 00000000..56c1e6aa --- /dev/null +++ b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_cmap.js @@ -0,0 +1,125 @@ +'use strict'; + + +const u = require('../../utils'); +const build_subtables = require('../../font/cmap_build_subtables'); +const Cmap = require('../../font/table_cmap'); + + +class LvCmap extends Cmap { + constructor(font) { + super(font); + + this.lv_compiled = false; + this.lv_subtables = []; + } + + lv_format2enum(name) { + switch (name) { + case 'format0_tiny': return 'LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY'; + case 'format0': return 'LV_FONT_FMT_TXT_CMAP_FORMAT0_FULL'; + case 'sparse_tiny': return 'LV_FONT_FMT_TXT_CMAP_SPARSE_TINY'; + case 'sparse': return 'LV_FONT_FMT_TXT_CMAP_SPARSE_FULL'; + default: throw new Error('Unknown subtable format'); + } + } + + lv_compile() { + if (this.lv_compiled) return; + this.lv_compiled = true; + + const f = this.font; + + let subtables_plan = build_subtables(f.src.glyphs.map(g => g.code)); + let idx = 0; + + for (let [ format, codepoints ] of subtables_plan) { + let g = this.glyphByCode(codepoints[0]); + let start_glyph_id = f.glyph_id[g.code]; + let min_code = codepoints[0]; + let max_code = codepoints[codepoints.length - 1]; + + let has_charcodes = false; + let has_ids = false; + let defs = ''; + let entries_count = 0; + + if (format === 'format0_tiny') { + // use default empty values + } else if (format === 'format0') { + has_ids = true; + let d = this.collect_format0_data(min_code, max_code, start_glyph_id); + entries_count = d.length; + + defs = ` +static const uint8_t glyph_id_ofs_list_${idx}[] = { +${u.long_dump(d)} +}; +`.trim(); + + } else if (format === 'sparse_tiny') { + has_charcodes = true; + let d = this.collect_sparse_data(codepoints, start_glyph_id); + entries_count = d.codes.length; + + defs = ` +static const uint16_t unicode_list_${idx}[] = { +${u.long_dump(d.codes, { hex: true })} +}; +`.trim(); + + } else { // assume format === 'sparse' + has_charcodes = true; + has_ids = true; + let d = this.collect_sparse_data(codepoints, start_glyph_id); + entries_count = d.codes.length; + + defs = ` +static const uint16_t unicode_list_${idx}[] = { +${u.long_dump(d.codes, { hex: true })} +}; +static const uint16_t glyph_id_ofs_list_${idx}[] = { +${u.long_dump(d.ids)} +}; +`.trim(); + } + + const u_list = has_charcodes ? `unicode_list_${idx}` : 'NULL'; + const id_list = has_ids ? `glyph_id_ofs_list_${idx}` : 'NULL'; + + /* eslint-disable max-len */ + const head = ` { + .range_start = ${min_code}, .range_length = ${max_code - min_code + 1}, .glyph_id_start = ${start_glyph_id}, + .unicode_list = ${u_list}, .glyph_id_ofs_list = ${id_list}, .list_length = ${entries_count}, .type = ${this.lv_format2enum(format)} + }`; + + this.lv_subtables.push({ + defs, + head + }); + + idx++; + } + } + + toLVGL() { + this.lv_compile(); + + return ` +/*--------------------- + * CHARACTER MAPPING + *--------------------*/ + +${this.lv_subtables.map(d => d.defs).filter(Boolean).join('\n\n')} + +/*Collect the unicode lists and glyph_id offsets*/ +static const lv_font_fmt_txt_cmap_t cmaps[] = +{ +${this.lv_subtables.map(d => d.head).join(',\n')} +}; + `.trim(); + } +} + + +module.exports = LvCmap; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_glyf.js b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_glyf.js new file mode 100644 index 00000000..3f14851f --- /dev/null +++ b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_glyf.js @@ -0,0 +1,121 @@ +'use strict'; + + +const { BitStream } = require('bit-buffer'); +const u = require('../../utils'); +const Glyf = require('../../font/table_glyf'); + + +class LvGlyf extends Glyf { + constructor(font) { + super(font); + + this.lv_data = []; + this.lv_compiled = false; + } + + lv_bitmap(glyph) { + const buf = Buffer.alloc(100 + glyph.bbox.width * glyph.bbox.height * 4); + const bs = new BitStream(buf); + bs.bigEndian = true; + + const pixels = this.font.glyf.pixelsToBpp(glyph.pixels); + + this.font.glyf.storePixels(bs, pixels); + + const glyph_bitmap = Buffer.alloc(bs.byteIndex); + buf.copy(glyph_bitmap, 0, 0, bs.byteIndex); + + return glyph_bitmap; + } + + lv_compile() { + if (this.lv_compiled) return; + + this.lv_compiled = true; + + const f = this.font; + this.lv_data = []; + let offset = 0; + + f.src.glyphs.forEach(g => { + const id = f.glyph_id[g.code]; + const bin = this.lv_bitmap(g); + this.lv_data[id] = { + bin, + offset, + glyph: g + }; + offset += bin.length; + }); + } + + to_lv_bitmaps() { + this.lv_compile(); + + let result = []; + this.lv_data.forEach((d, idx) => { + if (idx === 0) return; + const code_hex = d.glyph.code.toString(16).toUpperCase(); + const code_str = JSON.stringify(String.fromCodePoint(d.glyph.code)); + + let txt = ` /* U+${code_hex.padStart(4, '0')} ${code_str} */ +${u.long_dump(d.bin, { hex: true })}`; + + if (idx < this.lv_data.length - 1) { + // skip comma for zero data + txt += d.bin.length ? ',\n\n' : '\n'; + } + + result.push(txt); + }); + + return result.join(''); + } + + to_lv_glyph_dsc() { + this.lv_compile(); + + /* eslint-disable max-len */ + + let result = [ ' {.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */' ]; + + this.lv_data.forEach(d => { + const idx = d.offset, + adv_w = Math.round(d.glyph.advanceWidth * 16), + h = d.glyph.bbox.height, + w = d.glyph.bbox.width, + x = d.glyph.bbox.x, + y = d.glyph.bbox.y; + result.push(` {.bitmap_index = ${idx}, .adv_w = ${adv_w}, .box_w = ${w}, .box_h = ${h}, .ofs_x = ${x}, .ofs_y = ${y}}`); + }); + + return result.join(',\n'); + } + + + toLVGL() { + return ` +/*----------------- + * BITMAPS + *----------------*/ + +/*Store the image of the glyphs*/ +static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = { +${this.to_lv_bitmaps()} +}; + + +/*--------------------- + * GLYPH DESCRIPTION + *--------------------*/ + +static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = { +${this.to_lv_glyph_dsc()} +}; +`.trim(); + } +} + + +module.exports = LvGlyf; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_head.js b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_head.js new file mode 100644 index 00000000..f5c0173e --- /dev/null +++ b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_head.js @@ -0,0 +1,99 @@ +'use strict'; + + +const Head = require('../../font/table_head'); + + +class LvHead extends Head { + constructor(font) { + super(font); + } + + kern_ref() { + const f = this.font; + + if (!f.hasKerning()) { + return { + scale: '0', + dsc: 'NULL', + classes: '0' + }; + } + + if (!f.kern.should_use_format3()) { + return { + scale: `${Math.round(f.kerningScale * 16)}`, + dsc: '&kern_pairs', + classes: '0' + }; + } + + return { + scale: `${Math.round(f.kerningScale * 16)}`, + dsc: '&kern_classes', + classes: '1' + }; + } + + toLVGL() { + const f = this.font; + const kern = this.kern_ref(); + const subpixels = (f.subpixels_mode === 0) ? 'LV_FONT_SUBPX_NONE' : + (f.subpixels_mode === 1) ? 'LV_FONT_SUBPX_HOR' : 'LV_FONT_SUBPX_VER'; + + return ` +/*-------------------- + * ALL CUSTOM DATA + *--------------------*/ + +#if LV_VERSION_CHECK(8, 0, 0) +/*Store all the custom data of the font*/ +static lv_font_fmt_txt_glyph_cache_t cache; +static const lv_font_fmt_txt_dsc_t font_dsc = { +#else +static lv_font_fmt_txt_dsc_t font_dsc = { +#endif + .glyph_bitmap = glyph_bitmap, + .glyph_dsc = glyph_dsc, + .cmaps = cmaps, + .kern_dsc = ${kern.dsc}, + .kern_scale = ${kern.scale}, + .cmap_num = ${f.cmap.toBin().readUInt32LE(8)}, + .bpp = ${f.opts.bpp}, + .kern_classes = ${kern.classes}, + .bitmap_format = ${f.glyf.getCompressionCode()}, +#if LV_VERSION_CHECK(8, 0, 0) + .cache = &cache +#endif +}; + + +/*----------------- + * PUBLIC FONT + *----------------*/ + +/*Initialize a public general font descriptor*/ +#if LV_VERSION_CHECK(8, 0, 0) +const lv_font_t ${f.font_name} = { +#else +lv_font_t ${f.font_name} = { +#endif + .get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/ + .get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/ + .line_height = ${f.src.ascent - f.src.descent}, /*The maximum line height required by the font*/ + .base_line = ${-f.src.descent}, /*Baseline measured from the bottom of the line*/ +#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0) + .subpx = ${subpixels}, +#endif +#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8 + .underline_position = ${f.src.underlinePosition}, + .underline_thickness = ${f.src.underlineThickness}, +#endif + .dsc = &font_dsc /*The custom font data. Will be accessed by \`get_glyph_bitmap/dsc\` */ +}; +`.trim(); + } +} + + +module.exports = LvHead; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_kern.js b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_kern.js new file mode 100644 index 00000000..e50ba427 --- /dev/null +++ b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_kern.js @@ -0,0 +1,121 @@ +'use strict'; + + +const u = require('../../utils'); +const Kern = require('../../font/table_kern'); + + +class LvKern extends Kern { + constructor(font) { + super(font); + } + + to_lv_format0() { + const f = this.font; + let kern_pairs = this.collect_format0_data(); + + return ` +/*----------------- + * KERNING + *----------------*/ + + +/*Pair left and right glyphs for kerning*/ +static const ${f.glyphIdFormat ? 'uint16_t' : 'uint8_t'} kern_pair_glyph_ids[] = +{ +${kern_pairs.map(pair => ` ${pair[0]}, ${pair[1]}`).join(',\n')} +}; + +/* Kerning between the respective left and right glyphs + * 4.4 format which needs to scaled with \`kern_scale\`*/ +static const int8_t kern_pair_values[] = +{ +${u.long_dump(kern_pairs.map(pair => f.kernToFP(pair[2])))} +}; + +/*Collect the kern pair's data in one place*/ +static const lv_font_fmt_txt_kern_pair_t kern_pairs = +{ + .glyph_ids = kern_pair_glyph_ids, + .values = kern_pair_values, + .pair_cnt = ${kern_pairs.length}, + .glyph_ids_size = ${f.glyphIdFormat} +}; + + +`.trim(); + } + + to_lv_format3() { + const f = this.font; + const { + left_classes, + right_classes, + left_mapping, + right_mapping, + values + } = this.collect_format3_data(); + + return ` +/*----------------- + * KERNING + *----------------*/ + + +/*Map glyph_ids to kern left classes*/ +static const uint8_t kern_left_class_mapping[] = +{ +${u.long_dump(left_mapping)} +}; + +/*Map glyph_ids to kern right classes*/ +static const uint8_t kern_right_class_mapping[] = +{ +${u.long_dump(right_mapping)} +}; + +/*Kern values between classes*/ +static const int8_t kern_class_values[] = +{ +${u.long_dump(values.map(v => f.kernToFP(v)))} +}; + + +/*Collect the kern class' data in one place*/ +static const lv_font_fmt_txt_kern_classes_t kern_classes = +{ + .class_pair_values = kern_class_values, + .left_class_mapping = kern_left_class_mapping, + .right_class_mapping = kern_right_class_mapping, + .left_class_cnt = ${left_classes}, + .right_class_cnt = ${right_classes}, +}; + + +`.trim(); + } + + toLVGL() { + const f = this.font; + + if (!f.hasKerning()) return ''; + + /* eslint-disable no-console */ + + if (f.kern.should_use_format3()) { + if (f.kern.format3_forced) { + let diff = this.create_format3_data().length - this.create_format0_data().length; + console.log(`Forced faster kerning format (via classes). Size increase is ${diff} bytes.`); + } + return this.to_lv_format3(); + } + + if (this.font.opts.fast_kerning) { + console.log('Forced faster kerning format (via classes), but data exceeds it\'s limits. Continue use pairs.'); + } + return this.to_lv_format0(); + } +} + + +module.exports = LvKern; diff --git a/node_modules/lv_font_conv/lv_font_conv.js b/node_modules/lv_font_conv/lv_font_conv.js new file mode 100755 index 00000000..f76cbde4 --- /dev/null +++ b/node_modules/lv_font_conv/lv_font_conv.js @@ -0,0 +1,17 @@ +#!/usr/bin/env node + +'use strict'; + +const AppError = require('./lib/app_error'); + +require('./lib/cli').run(process.argv.slice(2)).catch(err => { + /*eslint-disable no-console*/ + if (err instanceof AppError) { + // Try to beautify normal errors + console.error(err.message.trim()); + } else { + // Print crashes + console.error(err.stack); + } + process.exit(1); +}); diff --git a/node_modules/lv_font_conv/node_modules/argparse/CHANGELOG.md b/node_modules/lv_font_conv/node_modules/argparse/CHANGELOG.md new file mode 100644 index 00000000..dc39ed69 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/argparse/CHANGELOG.md @@ -0,0 +1,216 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +## [2.0.1] - 2020-08-29 +### Fixed +- Fix issue with `process.argv` when used with interpreters (`coffee`, `ts-node`, etc.), #150. + + +## [2.0.0] - 2020-08-14 +### Changed +- Full rewrite. Now port from python 3.9.0 & more precise following. + See [doc](./doc) for difference and migration info. +- node.js 10+ required +- Removed most of local docs in favour of original ones. + + +## [1.0.10] - 2018-02-15 +### Fixed +- Use .concat instead of + for arrays, #122. + + +## [1.0.9] - 2016-09-29 +### Changed +- Rerelease after 1.0.8 - deps cleanup. + + +## [1.0.8] - 2016-09-29 +### Changed +- Maintenance (deps bump, fix node 6.5+ tests, coverage report). + + +## [1.0.7] - 2016-03-17 +### Changed +- Teach `addArgument` to accept string arg names. #97, @tomxtobin. + + +## [1.0.6] - 2016-02-06 +### Changed +- Maintenance: moved to eslint & updated CS. + + +## [1.0.5] - 2016-02-05 +### Changed +- Removed lodash dependency to significantly reduce install size. + Thanks to @mourner. + + +## [1.0.4] - 2016-01-17 +### Changed +- Maintenance: lodash update to 4.0.0. + + +## [1.0.3] - 2015-10-27 +### Fixed +- Fix parse `=` in args: `--examplepath="C:\myfolder\env=x64"`. #84, @CatWithApple. + + +## [1.0.2] - 2015-03-22 +### Changed +- Relaxed lodash version dependency. + + +## [1.0.1] - 2015-02-20 +### Changed +- Changed dependencies to be compatible with ancient nodejs. + + +## [1.0.0] - 2015-02-19 +### Changed +- Maintenance release. +- Replaced `underscore` with `lodash`. +- Bumped version to 1.0.0 to better reflect semver meaning. +- HISTORY.md -> CHANGELOG.md + + +## [0.1.16] - 2013-12-01 +### Changed +- Maintenance release. Updated dependencies and docs. + + +## [0.1.15] - 2013-05-13 +### Fixed +- Fixed #55, @trebor89 + + +## [0.1.14] - 2013-05-12 +### Fixed +- Fixed #62, @maxtaco + + +## [0.1.13] - 2013-04-08 +### Changed +- Added `.npmignore` to reduce package size + + +## [0.1.12] - 2013-02-10 +### Fixed +- Fixed conflictHandler (#46), @hpaulj + + +## [0.1.11] - 2013-02-07 +### Added +- Added 70+ tests (ported from python), @hpaulj +- Added conflictHandler, @applepicke +- Added fromfilePrefixChar, @hpaulj + +### Fixed +- Multiple bugfixes, @hpaulj + + +## [0.1.10] - 2012-12-30 +### Added +- Added [mutual exclusion](http://docs.python.org/dev/library/argparse.html#mutual-exclusion) + support, thanks to @hpaulj + +### Fixed +- Fixed options check for `storeConst` & `appendConst` actions, thanks to @hpaulj + + +## [0.1.9] - 2012-12-27 +### Fixed +- Fixed option dest interferens with other options (issue #23), thanks to @hpaulj +- Fixed default value behavior with `*` positionals, thanks to @hpaulj +- Improve `getDefault()` behavior, thanks to @hpaulj +- Improve negative argument parsing, thanks to @hpaulj + + +## [0.1.8] - 2012-12-01 +### Fixed +- Fixed parser parents (issue #19), thanks to @hpaulj +- Fixed negative argument parse (issue #20), thanks to @hpaulj + + +## [0.1.7] - 2012-10-14 +### Fixed +- Fixed 'choices' argument parse (issue #16) +- Fixed stderr output (issue #15) + + +## [0.1.6] - 2012-09-09 +### Fixed +- Fixed check for conflict of options (thanks to @tomxtobin) + + +## [0.1.5] - 2012-09-03 +### Fixed +- Fix parser #setDefaults method (thanks to @tomxtobin) + + +## [0.1.4] - 2012-07-30 +### Fixed +- Fixed pseudo-argument support (thanks to @CGamesPlay) +- Fixed addHelp default (should be true), if not set (thanks to @benblank) + + +## [0.1.3] - 2012-06-27 +### Fixed +- Fixed formatter api name: Formatter -> HelpFormatter + + +## [0.1.2] - 2012-05-29 +### Fixed +- Removed excess whitespace in help +- Fixed error reporting, when parcer with subcommands + called with empty arguments + +### Added +- Added basic tests + + +## [0.1.1] - 2012-05-23 +### Fixed +- Fixed line wrapping in help formatter +- Added better error reporting on invalid arguments + + +## [0.1.0] - 2012-05-16 +### Added +- First release. + + +[2.0.1]: https://github.com/nodeca/argparse/compare/2.0.0...2.0.1 +[2.0.0]: https://github.com/nodeca/argparse/compare/1.0.10...2.0.0 +[1.0.10]: https://github.com/nodeca/argparse/compare/1.0.9...1.0.10 +[1.0.9]: https://github.com/nodeca/argparse/compare/1.0.8...1.0.9 +[1.0.8]: https://github.com/nodeca/argparse/compare/1.0.7...1.0.8 +[1.0.7]: https://github.com/nodeca/argparse/compare/1.0.6...1.0.7 +[1.0.6]: https://github.com/nodeca/argparse/compare/1.0.5...1.0.6 +[1.0.5]: https://github.com/nodeca/argparse/compare/1.0.4...1.0.5 +[1.0.4]: https://github.com/nodeca/argparse/compare/1.0.3...1.0.4 +[1.0.3]: https://github.com/nodeca/argparse/compare/1.0.2...1.0.3 +[1.0.2]: https://github.com/nodeca/argparse/compare/1.0.1...1.0.2 +[1.0.1]: https://github.com/nodeca/argparse/compare/1.0.0...1.0.1 +[1.0.0]: https://github.com/nodeca/argparse/compare/0.1.16...1.0.0 +[0.1.16]: https://github.com/nodeca/argparse/compare/0.1.15...0.1.16 +[0.1.15]: https://github.com/nodeca/argparse/compare/0.1.14...0.1.15 +[0.1.14]: https://github.com/nodeca/argparse/compare/0.1.13...0.1.14 +[0.1.13]: https://github.com/nodeca/argparse/compare/0.1.12...0.1.13 +[0.1.12]: https://github.com/nodeca/argparse/compare/0.1.11...0.1.12 +[0.1.11]: https://github.com/nodeca/argparse/compare/0.1.10...0.1.11 +[0.1.10]: https://github.com/nodeca/argparse/compare/0.1.9...0.1.10 +[0.1.9]: https://github.com/nodeca/argparse/compare/0.1.8...0.1.9 +[0.1.8]: https://github.com/nodeca/argparse/compare/0.1.7...0.1.8 +[0.1.7]: https://github.com/nodeca/argparse/compare/0.1.6...0.1.7 +[0.1.6]: https://github.com/nodeca/argparse/compare/0.1.5...0.1.6 +[0.1.5]: https://github.com/nodeca/argparse/compare/0.1.4...0.1.5 +[0.1.4]: https://github.com/nodeca/argparse/compare/0.1.3...0.1.4 +[0.1.3]: https://github.com/nodeca/argparse/compare/0.1.2...0.1.3 +[0.1.2]: https://github.com/nodeca/argparse/compare/0.1.1...0.1.2 +[0.1.1]: https://github.com/nodeca/argparse/compare/0.1.0...0.1.1 +[0.1.0]: https://github.com/nodeca/argparse/releases/tag/0.1.0 diff --git a/node_modules/lv_font_conv/node_modules/argparse/LICENSE b/node_modules/lv_font_conv/node_modules/argparse/LICENSE new file mode 100644 index 00000000..66a3ac80 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/argparse/LICENSE @@ -0,0 +1,254 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. + +All Python releases are Open Source (see http://www.opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the Internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the Internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/argparse/README.md b/node_modules/lv_font_conv/node_modules/argparse/README.md new file mode 100644 index 00000000..550b5c9b --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/argparse/README.md @@ -0,0 +1,84 @@ +argparse +======== + +[![Build Status](https://secure.travis-ci.org/nodeca/argparse.svg?branch=master)](http://travis-ci.org/nodeca/argparse) +[![NPM version](https://img.shields.io/npm/v/argparse.svg)](https://www.npmjs.org/package/argparse) + +CLI arguments parser for node.js, with [sub-commands](https://docs.python.org/3.9/library/argparse.html#sub-commands) support. Port of python's [argparse](http://docs.python.org/dev/library/argparse.html) (version [3.9.0](https://github.com/python/cpython/blob/v3.9.0rc1/Lib/argparse.py)). + +**Difference with original.** + +- JS has no keyword arguments support. + - Pass options instead: `new ArgumentParser({ description: 'example', add_help: true })`. +- JS has no python's types `int`, `float`, ... + - Use string-typed names: `.add_argument('-b', { type: 'int', help: 'help' })`. +- `%r` format specifier uses `require('util').inspect()`. + +More details in [doc](./doc). + + +Example +------- + +`test.js` file: + +```javascript +#!/usr/bin/env node +'use strict'; + +const { ArgumentParser } = require('argparse'); +const { version } = require('./package.json'); + +const parser = new ArgumentParser({ + description: 'Argparse example' +}); + +parser.add_argument('-v', '--version', { action: 'version', version }); +parser.add_argument('-f', '--foo', { help: 'foo bar' }); +parser.add_argument('-b', '--bar', { help: 'bar foo' }); +parser.add_argument('--baz', { help: 'baz bar' }); + +console.dir(parser.parse_args()); +``` + +Display help: + +``` +$ ./test.js -h +usage: test.js [-h] [-v] [-f FOO] [-b BAR] [--baz BAZ] + +Argparse example + +optional arguments: + -h, --help show this help message and exit + -v, --version show program's version number and exit + -f FOO, --foo FOO foo bar + -b BAR, --bar BAR bar foo + --baz BAZ baz bar +``` + +Parse arguments: + +``` +$ ./test.js -f=3 --bar=4 --baz 5 +{ foo: '3', bar: '4', baz: '5' } +``` + + +API docs +-------- + +Since this is a port with minimal divergence, there's no separate documentation. +Use original one instead, with notes about difference. + +1. [Original doc](https://docs.python.org/3.9/library/argparse.html). +2. [Original tutorial](https://docs.python.org/3.9/howto/argparse.html). +3. [Difference with python](./doc). + + +argparse for enterprise +----------------------- + +Available as part of the Tidelift Subscription + +The maintainers of argparse and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-argparse?utm_source=npm-argparse&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/node_modules/lv_font_conv/node_modules/argparse/argparse.js b/node_modules/lv_font_conv/node_modules/argparse/argparse.js new file mode 100644 index 00000000..2b8c8c63 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/argparse/argparse.js @@ -0,0 +1,3707 @@ +// Port of python's argparse module, version 3.9.0: +// https://github.com/python/cpython/blob/v3.9.0rc1/Lib/argparse.py + +'use strict' + +// Copyright (C) 2010-2020 Python Software Foundation. +// Copyright (C) 2020 argparse.js authors + +/* + * Command-line parsing library + * + * This module is an optparse-inspired command-line parsing library that: + * + * - handles both optional and positional arguments + * - produces highly informative usage messages + * - supports parsers that dispatch to sub-parsers + * + * The following is a simple usage example that sums integers from the + * command-line and writes the result to a file:: + * + * parser = argparse.ArgumentParser( + * description='sum the integers at the command line') + * parser.add_argument( + * 'integers', metavar='int', nargs='+', type=int, + * help='an integer to be summed') + * parser.add_argument( + * '--log', default=sys.stdout, type=argparse.FileType('w'), + * help='the file where the sum should be written') + * args = parser.parse_args() + * args.log.write('%s' % sum(args.integers)) + * args.log.close() + * + * The module contains the following public classes: + * + * - ArgumentParser -- The main entry point for command-line parsing. As the + * example above shows, the add_argument() method is used to populate + * the parser with actions for optional and positional arguments. Then + * the parse_args() method is invoked to convert the args at the + * command-line into an object with attributes. + * + * - ArgumentError -- The exception raised by ArgumentParser objects when + * there are errors with the parser's actions. Errors raised while + * parsing the command-line are caught by ArgumentParser and emitted + * as command-line messages. + * + * - FileType -- A factory for defining types of files to be created. As the + * example above shows, instances of FileType are typically passed as + * the type= argument of add_argument() calls. + * + * - Action -- The base class for parser actions. Typically actions are + * selected by passing strings like 'store_true' or 'append_const' to + * the action= argument of add_argument(). However, for greater + * customization of ArgumentParser actions, subclasses of Action may + * be defined and passed as the action= argument. + * + * - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter, + * ArgumentDefaultsHelpFormatter -- Formatter classes which + * may be passed as the formatter_class= argument to the + * ArgumentParser constructor. HelpFormatter is the default, + * RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser + * not to change the formatting for help text, and + * ArgumentDefaultsHelpFormatter adds information about argument defaults + * to the help. + * + * All other classes in this module are considered implementation details. + * (Also note that HelpFormatter and RawDescriptionHelpFormatter are only + * considered public as object names -- the API of the formatter objects is + * still considered an implementation detail.) + */ + +const SUPPRESS = '==SUPPRESS==' + +const OPTIONAL = '?' +const ZERO_OR_MORE = '*' +const ONE_OR_MORE = '+' +const PARSER = 'A...' +const REMAINDER = '...' +const _UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args' + + +// ================================== +// Utility functions used for porting +// ================================== +const assert = require('assert') +const util = require('util') +const fs = require('fs') +const sub = require('./lib/sub') +const path = require('path') +const repr = util.inspect + +function get_argv() { + // omit first argument (which is assumed to be interpreter - `node`, `coffee`, `ts-node`, etc.) + return process.argv.slice(1) +} + +function get_terminal_size() { + return { + columns: +process.env.COLUMNS || process.stdout.columns || 80 + } +} + +function hasattr(object, name) { + return Object.prototype.hasOwnProperty.call(object, name) +} + +function getattr(object, name, value) { + return hasattr(object, name) ? object[name] : value +} + +function setattr(object, name, value) { + object[name] = value +} + +function setdefault(object, name, value) { + if (!hasattr(object, name)) object[name] = value + return object[name] +} + +function delattr(object, name) { + delete object[name] +} + +function range(from, to, step=1) { + // range(10) is equivalent to range(0, 10) + if (arguments.length === 1) [ to, from ] = [ from, 0 ] + if (typeof from !== 'number' || typeof to !== 'number' || typeof step !== 'number') { + throw new TypeError('argument cannot be interpreted as an integer') + } + if (step === 0) throw new TypeError('range() arg 3 must not be zero') + + let result = [] + if (step > 0) { + for (let i = from; i < to; i += step) result.push(i) + } else { + for (let i = from; i > to; i += step) result.push(i) + } + return result +} + +function splitlines(str, keepends = false) { + let result + if (!keepends) { + result = str.split(/\r\n|[\n\r\v\f\x1c\x1d\x1e\x85\u2028\u2029]/) + } else { + result = [] + let parts = str.split(/(\r\n|[\n\r\v\f\x1c\x1d\x1e\x85\u2028\u2029])/) + for (let i = 0; i < parts.length; i += 2) { + result.push(parts[i] + (i + 1 < parts.length ? parts[i + 1] : '')) + } + } + if (!result[result.length - 1]) result.pop() + return result +} + +function _string_lstrip(string, prefix_chars) { + let idx = 0 + while (idx < string.length && prefix_chars.includes(string[idx])) idx++ + return idx ? string.slice(idx) : string +} + +function _string_split(string, sep, maxsplit) { + let result = string.split(sep) + if (result.length > maxsplit) { + result = result.slice(0, maxsplit).concat([ result.slice(maxsplit).join(sep) ]) + } + return result +} + +function _array_equal(array1, array2) { + if (array1.length !== array2.length) return false + for (let i = 0; i < array1.length; i++) { + if (array1[i] !== array2[i]) return false + } + return true +} + +function _array_remove(array, item) { + let idx = array.indexOf(item) + if (idx === -1) throw new TypeError(sub('%r not in list', item)) + array.splice(idx, 1) +} + +// normalize choices to array; +// this isn't required in python because `in` and `map` operators work with anything, +// but in js dealing with multiple types here is too clunky +function _choices_to_array(choices) { + if (choices === undefined) { + return [] + } else if (Array.isArray(choices)) { + return choices + } else if (choices !== null && typeof choices[Symbol.iterator] === 'function') { + return Array.from(choices) + } else if (typeof choices === 'object' && choices !== null) { + return Object.keys(choices) + } else { + throw new Error(sub('invalid choices value: %r', choices)) + } +} + +// decorator that allows a class to be called without new +function _callable(cls) { + let result = { // object is needed for inferred class name + [cls.name]: function (...args) { + let this_class = new.target === result || !new.target + return Reflect.construct(cls, args, this_class ? cls : new.target) + } + } + result[cls.name].prototype = cls.prototype + // fix default tag for toString, e.g. [object Action] instead of [object Object] + cls.prototype[Symbol.toStringTag] = cls.name + return result[cls.name] +} + +function _alias(object, from, to) { + try { + let name = object.constructor.name + Object.defineProperty(object, from, { + value: util.deprecate(object[to], sub('%s.%s() is renamed to %s.%s()', + name, from, name, to)), + enumerable: false + }) + } catch {} +} + +// decorator that allows snake_case class methods to be called with camelCase and vice versa +function _camelcase_alias(_class) { + for (let name of Object.getOwnPropertyNames(_class.prototype)) { + let camelcase = name.replace(/\w_[a-z]/g, s => s[0] + s[2].toUpperCase()) + if (camelcase !== name) _alias(_class.prototype, camelcase, name) + } + return _class +} + +function _to_legacy_name(key) { + key = key.replace(/\w_[a-z]/g, s => s[0] + s[2].toUpperCase()) + if (key === 'default') key = 'defaultValue' + if (key === 'const') key = 'constant' + return key +} + +function _to_new_name(key) { + if (key === 'defaultValue') key = 'default' + if (key === 'constant') key = 'const' + key = key.replace(/[A-Z]/g, c => '_' + c.toLowerCase()) + return key +} + +// parse options +let no_default = Symbol('no_default_value') +function _parse_opts(args, descriptor) { + function get_name() { + let stack = new Error().stack.split('\n') + .map(x => x.match(/^ at (.*) \(.*\)$/)) + .filter(Boolean) + .map(m => m[1]) + .map(fn => fn.match(/[^ .]*$/)[0]) + + if (stack.length && stack[0] === get_name.name) stack.shift() + if (stack.length && stack[0] === _parse_opts.name) stack.shift() + return stack.length ? stack[0] : '' + } + + args = Array.from(args) + let kwargs = {} + let result = [] + let last_opt = args.length && args[args.length - 1] + + if (typeof last_opt === 'object' && last_opt !== null && !Array.isArray(last_opt) && + (!last_opt.constructor || last_opt.constructor.name === 'Object')) { + kwargs = Object.assign({}, args.pop()) + } + + // LEGACY (v1 compatibility): camelcase + let renames = [] + for (let key of Object.keys(descriptor)) { + let old_name = _to_legacy_name(key) + if (old_name !== key && (old_name in kwargs)) { + if (key in kwargs) { + // default and defaultValue specified at the same time, happens often in old tests + //throw new TypeError(sub('%s() got multiple values for argument %r', get_name(), key)) + } else { + kwargs[key] = kwargs[old_name] + } + renames.push([ old_name, key ]) + delete kwargs[old_name] + } + } + if (renames.length) { + let name = get_name() + deprecate('camelcase_' + name, sub('%s(): following options are renamed: %s', + name, renames.map(([ a, b ]) => sub('%r -> %r', a, b)))) + } + // end + + let missing_positionals = [] + let positional_count = args.length + + for (let [ key, def ] of Object.entries(descriptor)) { + if (key[0] === '*') { + if (key.length > 0 && key[1] === '*') { + // LEGACY (v1 compatibility): camelcase + let renames = [] + for (let key of Object.keys(kwargs)) { + let new_name = _to_new_name(key) + if (new_name !== key && (key in kwargs)) { + if (new_name in kwargs) { + // default and defaultValue specified at the same time, happens often in old tests + //throw new TypeError(sub('%s() got multiple values for argument %r', get_name(), new_name)) + } else { + kwargs[new_name] = kwargs[key] + } + renames.push([ key, new_name ]) + delete kwargs[key] + } + } + if (renames.length) { + let name = get_name() + deprecate('camelcase_' + name, sub('%s(): following options are renamed: %s', + name, renames.map(([ a, b ]) => sub('%r -> %r', a, b)))) + } + // end + result.push(kwargs) + kwargs = {} + } else { + result.push(args) + args = [] + } + } else if (key in kwargs && args.length > 0) { + throw new TypeError(sub('%s() got multiple values for argument %r', get_name(), key)) + } else if (key in kwargs) { + result.push(kwargs[key]) + delete kwargs[key] + } else if (args.length > 0) { + result.push(args.shift()) + } else if (def !== no_default) { + result.push(def) + } else { + missing_positionals.push(key) + } + } + + if (Object.keys(kwargs).length) { + throw new TypeError(sub('%s() got an unexpected keyword argument %r', + get_name(), Object.keys(kwargs)[0])) + } + + if (args.length) { + let from = Object.entries(descriptor).filter(([ k, v ]) => k[0] !== '*' && v !== no_default).length + let to = Object.entries(descriptor).filter(([ k ]) => k[0] !== '*').length + throw new TypeError(sub('%s() takes %s positional argument%s but %s %s given', + get_name(), + from === to ? sub('from %s to %s', from, to) : to, + from === to && to === 1 ? '' : 's', + positional_count, + positional_count === 1 ? 'was' : 'were')) + } + + if (missing_positionals.length) { + let strs = missing_positionals.map(repr) + if (strs.length > 1) strs[strs.length - 1] = 'and ' + strs[strs.length - 1] + let str_joined = strs.join(strs.length === 2 ? '' : ', ') + throw new TypeError(sub('%s() missing %i required positional argument%s: %s', + get_name(), strs.length, strs.length === 1 ? '' : 's', str_joined)) + } + + return result +} + +let _deprecations = {} +function deprecate(id, string) { + _deprecations[id] = _deprecations[id] || util.deprecate(() => {}, string) + _deprecations[id]() +} + + +// ============================= +// Utility functions and classes +// ============================= +function _AttributeHolder(cls = Object) { + /* + * Abstract base class that provides __repr__. + * + * The __repr__ method returns a string in the format:: + * ClassName(attr=name, attr=name, ...) + * The attributes are determined either by a class-level attribute, + * '_kwarg_names', or by inspecting the instance __dict__. + */ + + return class _AttributeHolder extends cls { + [util.inspect.custom]() { + let type_name = this.constructor.name + let arg_strings = [] + let star_args = {} + for (let arg of this._get_args()) { + arg_strings.push(repr(arg)) + } + for (let [ name, value ] of this._get_kwargs()) { + if (/^[a-z_][a-z0-9_$]*$/i.test(name)) { + arg_strings.push(sub('%s=%r', name, value)) + } else { + star_args[name] = value + } + } + if (Object.keys(star_args).length) { + arg_strings.push(sub('**%s', repr(star_args))) + } + return sub('%s(%s)', type_name, arg_strings.join(', ')) + } + + toString() { + return this[util.inspect.custom]() + } + + _get_kwargs() { + return Object.entries(this) + } + + _get_args() { + return [] + } + } +} + + +function _copy_items(items) { + if (items === undefined) { + return [] + } + return items.slice(0) +} + + +// =============== +// Formatting Help +// =============== +const HelpFormatter = _camelcase_alias(_callable(class HelpFormatter { + /* + * Formatter for generating usage messages and argument help strings. + * + * Only the name of this class is considered a public API. All the methods + * provided by the class are considered an implementation detail. + */ + + constructor() { + let [ + prog, + indent_increment, + max_help_position, + width + ] = _parse_opts(arguments, { + prog: no_default, + indent_increment: 2, + max_help_position: 24, + width: undefined + }) + + // default setting for width + if (width === undefined) { + width = get_terminal_size().columns + width -= 2 + } + + this._prog = prog + this._indent_increment = indent_increment + this._max_help_position = Math.min(max_help_position, + Math.max(width - 20, indent_increment * 2)) + this._width = width + + this._current_indent = 0 + this._level = 0 + this._action_max_length = 0 + + this._root_section = this._Section(this, undefined) + this._current_section = this._root_section + + this._whitespace_matcher = /[ \t\n\r\f\v]+/g // equivalent to python /\s+/ with ASCII flag + this._long_break_matcher = /\n\n\n+/g + } + + // =============================== + // Section and indentation methods + // =============================== + _indent() { + this._current_indent += this._indent_increment + this._level += 1 + } + + _dedent() { + this._current_indent -= this._indent_increment + assert(this._current_indent >= 0, 'Indent decreased below 0.') + this._level -= 1 + } + + _add_item(func, args) { + this._current_section.items.push([ func, args ]) + } + + // ======================== + // Message building methods + // ======================== + start_section(heading) { + this._indent() + let section = this._Section(this, this._current_section, heading) + this._add_item(section.format_help.bind(section), []) + this._current_section = section + } + + end_section() { + this._current_section = this._current_section.parent + this._dedent() + } + + add_text(text) { + if (text !== SUPPRESS && text !== undefined) { + this._add_item(this._format_text.bind(this), [text]) + } + } + + add_usage(usage, actions, groups, prefix = undefined) { + if (usage !== SUPPRESS) { + let args = [ usage, actions, groups, prefix ] + this._add_item(this._format_usage.bind(this), args) + } + } + + add_argument(action) { + if (action.help !== SUPPRESS) { + + // find all invocations + let invocations = [this._format_action_invocation(action)] + for (let subaction of this._iter_indented_subactions(action)) { + invocations.push(this._format_action_invocation(subaction)) + } + + // update the maximum item length + let invocation_length = Math.max(...invocations.map(invocation => invocation.length)) + let action_length = invocation_length + this._current_indent + this._action_max_length = Math.max(this._action_max_length, + action_length) + + // add the item to the list + this._add_item(this._format_action.bind(this), [action]) + } + } + + add_arguments(actions) { + for (let action of actions) { + this.add_argument(action) + } + } + + // ======================= + // Help-formatting methods + // ======================= + format_help() { + let help = this._root_section.format_help() + if (help) { + help = help.replace(this._long_break_matcher, '\n\n') + help = help.replace(/^\n+|\n+$/g, '') + '\n' + } + return help + } + + _join_parts(part_strings) { + return part_strings.filter(part => part && part !== SUPPRESS).join('') + } + + _format_usage(usage, actions, groups, prefix) { + if (prefix === undefined) { + prefix = 'usage: ' + } + + // if usage is specified, use that + if (usage !== undefined) { + usage = sub(usage, { prog: this._prog }) + + // if no optionals or positionals are available, usage is just prog + } else if (usage === undefined && !actions.length) { + usage = sub('%(prog)s', { prog: this._prog }) + + // if optionals and positionals are available, calculate usage + } else if (usage === undefined) { + let prog = sub('%(prog)s', { prog: this._prog }) + + // split optionals from positionals + let optionals = [] + let positionals = [] + for (let action of actions) { + if (action.option_strings.length) { + optionals.push(action) + } else { + positionals.push(action) + } + } + + // build full usage string + let action_usage = this._format_actions_usage([].concat(optionals).concat(positionals), groups) + usage = [ prog, action_usage ].map(String).join(' ') + + // wrap the usage parts if it's too long + let text_width = this._width - this._current_indent + if (prefix.length + usage.length > text_width) { + + // break usage into wrappable parts + let part_regexp = /\(.*?\)+(?=\s|$)|\[.*?\]+(?=\s|$)|\S+/g + let opt_usage = this._format_actions_usage(optionals, groups) + let pos_usage = this._format_actions_usage(positionals, groups) + let opt_parts = opt_usage.match(part_regexp) || [] + let pos_parts = pos_usage.match(part_regexp) || [] + assert(opt_parts.join(' ') === opt_usage) + assert(pos_parts.join(' ') === pos_usage) + + // helper for wrapping lines + let get_lines = (parts, indent, prefix = undefined) => { + let lines = [] + let line = [] + let line_len + if (prefix !== undefined) { + line_len = prefix.length - 1 + } else { + line_len = indent.length - 1 + } + for (let part of parts) { + if (line_len + 1 + part.length > text_width && line) { + lines.push(indent + line.join(' ')) + line = [] + line_len = indent.length - 1 + } + line.push(part) + line_len += part.length + 1 + } + if (line.length) { + lines.push(indent + line.join(' ')) + } + if (prefix !== undefined) { + lines[0] = lines[0].slice(indent.length) + } + return lines + } + + let lines + + // if prog is short, follow it with optionals or positionals + if (prefix.length + prog.length <= 0.75 * text_width) { + let indent = ' '.repeat(prefix.length + prog.length + 1) + if (opt_parts.length) { + lines = get_lines([prog].concat(opt_parts), indent, prefix) + lines = lines.concat(get_lines(pos_parts, indent)) + } else if (pos_parts.length) { + lines = get_lines([prog].concat(pos_parts), indent, prefix) + } else { + lines = [prog] + } + + // if prog is long, put it on its own line + } else { + let indent = ' '.repeat(prefix.length) + let parts = [].concat(opt_parts).concat(pos_parts) + lines = get_lines(parts, indent) + if (lines.length > 1) { + lines = [] + lines = lines.concat(get_lines(opt_parts, indent)) + lines = lines.concat(get_lines(pos_parts, indent)) + } + lines = [prog].concat(lines) + } + + // join lines into usage + usage = lines.join('\n') + } + } + + // prefix with 'usage:' + return sub('%s%s\n\n', prefix, usage) + } + + _format_actions_usage(actions, groups) { + // find group indices and identify actions in groups + let group_actions = new Set() + let inserts = {} + for (let group of groups) { + let start = actions.indexOf(group._group_actions[0]) + if (start === -1) { + continue + } else { + let end = start + group._group_actions.length + if (_array_equal(actions.slice(start, end), group._group_actions)) { + for (let action of group._group_actions) { + group_actions.add(action) + } + if (!group.required) { + if (start in inserts) { + inserts[start] += ' [' + } else { + inserts[start] = '[' + } + if (end in inserts) { + inserts[end] += ']' + } else { + inserts[end] = ']' + } + } else { + if (start in inserts) { + inserts[start] += ' (' + } else { + inserts[start] = '(' + } + if (end in inserts) { + inserts[end] += ')' + } else { + inserts[end] = ')' + } + } + for (let i of range(start + 1, end)) { + inserts[i] = '|' + } + } + } + } + + // collect all actions format strings + let parts = [] + for (let [ i, action ] of Object.entries(actions)) { + + // suppressed arguments are marked with None + // remove | separators for suppressed arguments + if (action.help === SUPPRESS) { + parts.push(undefined) + if (inserts[+i] === '|') { + delete inserts[+i] + } else if (inserts[+i + 1] === '|') { + delete inserts[+i + 1] + } + + // produce all arg strings + } else if (!action.option_strings.length) { + let default_value = this._get_default_metavar_for_positional(action) + let part = this._format_args(action, default_value) + + // if it's in a group, strip the outer [] + if (group_actions.has(action)) { + if (part[0] === '[' && part[part.length - 1] === ']') { + part = part.slice(1, -1) + } + } + + // add the action string to the list + parts.push(part) + + // produce the first way to invoke the option in brackets + } else { + let option_string = action.option_strings[0] + let part + + // if the Optional doesn't take a value, format is: + // -s or --long + if (action.nargs === 0) { + part = action.format_usage() + + // if the Optional takes a value, format is: + // -s ARGS or --long ARGS + } else { + let default_value = this._get_default_metavar_for_optional(action) + let args_string = this._format_args(action, default_value) + part = sub('%s %s', option_string, args_string) + } + + // make it look optional if it's not required or in a group + if (!action.required && !group_actions.has(action)) { + part = sub('[%s]', part) + } + + // add the action string to the list + parts.push(part) + } + } + + // insert things at the necessary indices + for (let i of Object.keys(inserts).map(Number).sort((a, b) => b - a)) { + parts.splice(+i, 0, inserts[+i]) + } + + // join all the action items with spaces + let text = parts.filter(Boolean).join(' ') + + // clean up separators for mutually exclusive groups + text = text.replace(/([\[(]) /g, '$1') + text = text.replace(/ ([\])])/g, '$1') + text = text.replace(/[\[(] *[\])]/g, '') + text = text.replace(/\(([^|]*)\)/g, '$1', text) + text = text.trim() + + // return the text + return text + } + + _format_text(text) { + if (text.includes('%(prog)')) { + text = sub(text, { prog: this._prog }) + } + let text_width = Math.max(this._width - this._current_indent, 11) + let indent = ' '.repeat(this._current_indent) + return this._fill_text(text, text_width, indent) + '\n\n' + } + + _format_action(action) { + // determine the required width and the entry label + let help_position = Math.min(this._action_max_length + 2, + this._max_help_position) + let help_width = Math.max(this._width - help_position, 11) + let action_width = help_position - this._current_indent - 2 + let action_header = this._format_action_invocation(action) + let indent_first + + // no help; start on same line and add a final newline + if (!action.help) { + let tup = [ this._current_indent, '', action_header ] + action_header = sub('%*s%s\n', ...tup) + + // short action name; start on the same line and pad two spaces + } else if (action_header.length <= action_width) { + let tup = [ this._current_indent, '', action_width, action_header ] + action_header = sub('%*s%-*s ', ...tup) + indent_first = 0 + + // long action name; start on the next line + } else { + let tup = [ this._current_indent, '', action_header ] + action_header = sub('%*s%s\n', ...tup) + indent_first = help_position + } + + // collect the pieces of the action help + let parts = [action_header] + + // if there was help for the action, add lines of help text + if (action.help) { + let help_text = this._expand_help(action) + let help_lines = this._split_lines(help_text, help_width) + parts.push(sub('%*s%s\n', indent_first, '', help_lines[0])) + for (let line of help_lines.slice(1)) { + parts.push(sub('%*s%s\n', help_position, '', line)) + } + + // or add a newline if the description doesn't end with one + } else if (!action_header.endsWith('\n')) { + parts.push('\n') + } + + // if there are any sub-actions, add their help as well + for (let subaction of this._iter_indented_subactions(action)) { + parts.push(this._format_action(subaction)) + } + + // return a single string + return this._join_parts(parts) + } + + _format_action_invocation(action) { + if (!action.option_strings.length) { + let default_value = this._get_default_metavar_for_positional(action) + let metavar = this._metavar_formatter(action, default_value)(1)[0] + return metavar + + } else { + let parts = [] + + // if the Optional doesn't take a value, format is: + // -s, --long + if (action.nargs === 0) { + parts = parts.concat(action.option_strings) + + // if the Optional takes a value, format is: + // -s ARGS, --long ARGS + } else { + let default_value = this._get_default_metavar_for_optional(action) + let args_string = this._format_args(action, default_value) + for (let option_string of action.option_strings) { + parts.push(sub('%s %s', option_string, args_string)) + } + } + + return parts.join(', ') + } + } + + _metavar_formatter(action, default_metavar) { + let result + if (action.metavar !== undefined) { + result = action.metavar + } else if (action.choices !== undefined) { + let choice_strs = _choices_to_array(action.choices).map(String) + result = sub('{%s}', choice_strs.join(',')) + } else { + result = default_metavar + } + + function format(tuple_size) { + if (Array.isArray(result)) { + return result + } else { + return Array(tuple_size).fill(result) + } + } + return format + } + + _format_args(action, default_metavar) { + let get_metavar = this._metavar_formatter(action, default_metavar) + let result + if (action.nargs === undefined) { + result = sub('%s', ...get_metavar(1)) + } else if (action.nargs === OPTIONAL) { + result = sub('[%s]', ...get_metavar(1)) + } else if (action.nargs === ZERO_OR_MORE) { + let metavar = get_metavar(1) + if (metavar.length === 2) { + result = sub('[%s [%s ...]]', ...metavar) + } else { + result = sub('[%s ...]', ...metavar) + } + } else if (action.nargs === ONE_OR_MORE) { + result = sub('%s [%s ...]', ...get_metavar(2)) + } else if (action.nargs === REMAINDER) { + result = '...' + } else if (action.nargs === PARSER) { + result = sub('%s ...', ...get_metavar(1)) + } else if (action.nargs === SUPPRESS) { + result = '' + } else { + let formats + try { + formats = range(action.nargs).map(() => '%s') + } catch (err) { + throw new TypeError('invalid nargs value') + } + result = sub(formats.join(' '), ...get_metavar(action.nargs)) + } + return result + } + + _expand_help(action) { + let params = Object.assign({ prog: this._prog }, action) + for (let name of Object.keys(params)) { + if (params[name] === SUPPRESS) { + delete params[name] + } + } + for (let name of Object.keys(params)) { + if (params[name] && params[name].name) { + params[name] = params[name].name + } + } + if (params.choices !== undefined) { + let choices_str = _choices_to_array(params.choices).map(String).join(', ') + params.choices = choices_str + } + // LEGACY (v1 compatibility): camelcase + for (let key of Object.keys(params)) { + let old_name = _to_legacy_name(key) + if (old_name !== key) { + params[old_name] = params[key] + } + } + // end + return sub(this._get_help_string(action), params) + } + + * _iter_indented_subactions(action) { + if (typeof action._get_subactions === 'function') { + this._indent() + yield* action._get_subactions() + this._dedent() + } + } + + _split_lines(text, width) { + text = text.replace(this._whitespace_matcher, ' ').trim() + // The textwrap module is used only for formatting help. + // Delay its import for speeding up the common usage of argparse. + let textwrap = require('./lib/textwrap') + return textwrap.wrap(text, { width }) + } + + _fill_text(text, width, indent) { + text = text.replace(this._whitespace_matcher, ' ').trim() + let textwrap = require('./lib/textwrap') + return textwrap.fill(text, { width, + initial_indent: indent, + subsequent_indent: indent }) + } + + _get_help_string(action) { + return action.help + } + + _get_default_metavar_for_optional(action) { + return action.dest.toUpperCase() + } + + _get_default_metavar_for_positional(action) { + return action.dest + } +})) + +HelpFormatter.prototype._Section = _callable(class _Section { + + constructor(formatter, parent, heading = undefined) { + this.formatter = formatter + this.parent = parent + this.heading = heading + this.items = [] + } + + format_help() { + // format the indented section + if (this.parent !== undefined) { + this.formatter._indent() + } + let item_help = this.formatter._join_parts(this.items.map(([ func, args ]) => func.apply(null, args))) + if (this.parent !== undefined) { + this.formatter._dedent() + } + + // return nothing if the section was empty + if (!item_help) { + return '' + } + + // add the heading if the section was non-empty + let heading + if (this.heading !== SUPPRESS && this.heading !== undefined) { + let current_indent = this.formatter._current_indent + heading = sub('%*s%s:\n', current_indent, '', this.heading) + } else { + heading = '' + } + + // join the section-initial newline, the heading and the help + return this.formatter._join_parts(['\n', heading, item_help, '\n']) + } +}) + + +const RawDescriptionHelpFormatter = _camelcase_alias(_callable(class RawDescriptionHelpFormatter extends HelpFormatter { + /* + * Help message formatter which retains any formatting in descriptions. + * + * Only the name of this class is considered a public API. All the methods + * provided by the class are considered an implementation detail. + */ + + _fill_text(text, width, indent) { + return splitlines(text, true).map(line => indent + line).join('') + } +})) + + +const RawTextHelpFormatter = _camelcase_alias(_callable(class RawTextHelpFormatter extends RawDescriptionHelpFormatter { + /* + * Help message formatter which retains formatting of all help text. + * + * Only the name of this class is considered a public API. All the methods + * provided by the class are considered an implementation detail. + */ + + _split_lines(text/*, width*/) { + return splitlines(text) + } +})) + + +const ArgumentDefaultsHelpFormatter = _camelcase_alias(_callable(class ArgumentDefaultsHelpFormatter extends HelpFormatter { + /* + * Help message formatter which adds default values to argument help. + * + * Only the name of this class is considered a public API. All the methods + * provided by the class are considered an implementation detail. + */ + + _get_help_string(action) { + let help = action.help + // LEGACY (v1 compatibility): additional check for defaultValue needed + if (!action.help.includes('%(default)') && !action.help.includes('%(defaultValue)')) { + if (action.default !== SUPPRESS) { + let defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] + if (action.option_strings.length || defaulting_nargs.includes(action.nargs)) { + help += ' (default: %(default)s)' + } + } + } + return help + } +})) + + +const MetavarTypeHelpFormatter = _camelcase_alias(_callable(class MetavarTypeHelpFormatter extends HelpFormatter { + /* + * Help message formatter which uses the argument 'type' as the default + * metavar value (instead of the argument 'dest') + * + * Only the name of this class is considered a public API. All the methods + * provided by the class are considered an implementation detail. + */ + + _get_default_metavar_for_optional(action) { + return typeof action.type === 'function' ? action.type.name : action.type + } + + _get_default_metavar_for_positional(action) { + return typeof action.type === 'function' ? action.type.name : action.type + } +})) + + +// ===================== +// Options and Arguments +// ===================== +function _get_action_name(argument) { + if (argument === undefined) { + return undefined + } else if (argument.option_strings.length) { + return argument.option_strings.join('/') + } else if (![ undefined, SUPPRESS ].includes(argument.metavar)) { + return argument.metavar + } else if (![ undefined, SUPPRESS ].includes(argument.dest)) { + return argument.dest + } else { + return undefined + } +} + + +const ArgumentError = _callable(class ArgumentError extends Error { + /* + * An error from creating or using an argument (optional or positional). + * + * The string value of this exception is the message, augmented with + * information about the argument that caused it. + */ + + constructor(argument, message) { + super() + this.name = 'ArgumentError' + this._argument_name = _get_action_name(argument) + this._message = message + this.message = this.str() + } + + str() { + let format + if (this._argument_name === undefined) { + format = '%(message)s' + } else { + format = 'argument %(argument_name)s: %(message)s' + } + return sub(format, { message: this._message, + argument_name: this._argument_name }) + } +}) + + +const ArgumentTypeError = _callable(class ArgumentTypeError extends Error { + /* + * An error from trying to convert a command line string to a type. + */ + + constructor(message) { + super(message) + this.name = 'ArgumentTypeError' + } +}) + + +// ============== +// Action classes +// ============== +const Action = _camelcase_alias(_callable(class Action extends _AttributeHolder(Function) { + /* + * Information about how to convert command line strings to Python objects. + * + * Action objects are used by an ArgumentParser to represent the information + * needed to parse a single argument from one or more strings from the + * command line. The keyword arguments to the Action constructor are also + * all attributes of Action instances. + * + * Keyword Arguments: + * + * - option_strings -- A list of command-line option strings which + * should be associated with this action. + * + * - dest -- The name of the attribute to hold the created object(s) + * + * - nargs -- The number of command-line arguments that should be + * consumed. By default, one argument will be consumed and a single + * value will be produced. Other values include: + * - N (an integer) consumes N arguments (and produces a list) + * - '?' consumes zero or one arguments + * - '*' consumes zero or more arguments (and produces a list) + * - '+' consumes one or more arguments (and produces a list) + * Note that the difference between the default and nargs=1 is that + * with the default, a single value will be produced, while with + * nargs=1, a list containing a single value will be produced. + * + * - const -- The value to be produced if the option is specified and the + * option uses an action that takes no values. + * + * - default -- The value to be produced if the option is not specified. + * + * - type -- A callable that accepts a single string argument, and + * returns the converted value. The standard Python types str, int, + * float, and complex are useful examples of such callables. If None, + * str is used. + * + * - choices -- A container of values that should be allowed. If not None, + * after a command-line argument has been converted to the appropriate + * type, an exception will be raised if it is not a member of this + * collection. + * + * - required -- True if the action must always be specified at the + * command line. This is only meaningful for optional command-line + * arguments. + * + * - help -- The help string describing the argument. + * + * - metavar -- The name to be used for the option's argument with the + * help string. If None, the 'dest' value will be used as the name. + */ + + constructor() { + let [ + option_strings, + dest, + nargs, + const_value, + default_value, + type, + choices, + required, + help, + metavar + ] = _parse_opts(arguments, { + option_strings: no_default, + dest: no_default, + nargs: undefined, + const: undefined, + default: undefined, + type: undefined, + choices: undefined, + required: false, + help: undefined, + metavar: undefined + }) + + // when this class is called as a function, redirect it to .call() method of itself + super('return arguments.callee.call.apply(arguments.callee, arguments)') + + this.option_strings = option_strings + this.dest = dest + this.nargs = nargs + this.const = const_value + this.default = default_value + this.type = type + this.choices = choices + this.required = required + this.help = help + this.metavar = metavar + } + + _get_kwargs() { + let names = [ + 'option_strings', + 'dest', + 'nargs', + 'const', + 'default', + 'type', + 'choices', + 'help', + 'metavar' + ] + return names.map(name => [ name, getattr(this, name) ]) + } + + format_usage() { + return this.option_strings[0] + } + + call(/*parser, namespace, values, option_string = undefined*/) { + throw new Error('.call() not defined') + } +})) + + +const BooleanOptionalAction = _camelcase_alias(_callable(class BooleanOptionalAction extends Action { + + constructor() { + let [ + option_strings, + dest, + default_value, + type, + choices, + required, + help, + metavar + ] = _parse_opts(arguments, { + option_strings: no_default, + dest: no_default, + default: undefined, + type: undefined, + choices: undefined, + required: false, + help: undefined, + metavar: undefined + }) + + let _option_strings = [] + for (let option_string of option_strings) { + _option_strings.push(option_string) + + if (option_string.startsWith('--')) { + option_string = '--no-' + option_string.slice(2) + _option_strings.push(option_string) + } + } + + if (help !== undefined && default_value !== undefined) { + help += ` (default: ${default_value})` + } + + super({ + option_strings: _option_strings, + dest, + nargs: 0, + default: default_value, + type, + choices, + required, + help, + metavar + }) + } + + call(parser, namespace, values, option_string = undefined) { + if (this.option_strings.includes(option_string)) { + setattr(namespace, this.dest, !option_string.startsWith('--no-')) + } + } + + format_usage() { + return this.option_strings.join(' | ') + } +})) + + +const _StoreAction = _callable(class _StoreAction extends Action { + + constructor() { + let [ + option_strings, + dest, + nargs, + const_value, + default_value, + type, + choices, + required, + help, + metavar + ] = _parse_opts(arguments, { + option_strings: no_default, + dest: no_default, + nargs: undefined, + const: undefined, + default: undefined, + type: undefined, + choices: undefined, + required: false, + help: undefined, + metavar: undefined + }) + + if (nargs === 0) { + throw new TypeError('nargs for store actions must be != 0; if you ' + + 'have nothing to store, actions such as store ' + + 'true or store const may be more appropriate') + } + if (const_value !== undefined && nargs !== OPTIONAL) { + throw new TypeError(sub('nargs must be %r to supply const', OPTIONAL)) + } + super({ + option_strings, + dest, + nargs, + const: const_value, + default: default_value, + type, + choices, + required, + help, + metavar + }) + } + + call(parser, namespace, values/*, option_string = undefined*/) { + setattr(namespace, this.dest, values) + } +}) + + +const _StoreConstAction = _callable(class _StoreConstAction extends Action { + + constructor() { + let [ + option_strings, + dest, + const_value, + default_value, + required, + help + //, metavar + ] = _parse_opts(arguments, { + option_strings: no_default, + dest: no_default, + const: no_default, + default: undefined, + required: false, + help: undefined, + metavar: undefined + }) + + super({ + option_strings, + dest, + nargs: 0, + const: const_value, + default: default_value, + required, + help + }) + } + + call(parser, namespace/*, values, option_string = undefined*/) { + setattr(namespace, this.dest, this.const) + } +}) + + +const _StoreTrueAction = _callable(class _StoreTrueAction extends _StoreConstAction { + + constructor() { + let [ + option_strings, + dest, + default_value, + required, + help + ] = _parse_opts(arguments, { + option_strings: no_default, + dest: no_default, + default: false, + required: false, + help: undefined + }) + + super({ + option_strings, + dest, + const: true, + default: default_value, + required, + help + }) + } +}) + + +const _StoreFalseAction = _callable(class _StoreFalseAction extends _StoreConstAction { + + constructor() { + let [ + option_strings, + dest, + default_value, + required, + help + ] = _parse_opts(arguments, { + option_strings: no_default, + dest: no_default, + default: true, + required: false, + help: undefined + }) + + super({ + option_strings, + dest, + const: false, + default: default_value, + required, + help + }) + } +}) + + +const _AppendAction = _callable(class _AppendAction extends Action { + + constructor() { + let [ + option_strings, + dest, + nargs, + const_value, + default_value, + type, + choices, + required, + help, + metavar + ] = _parse_opts(arguments, { + option_strings: no_default, + dest: no_default, + nargs: undefined, + const: undefined, + default: undefined, + type: undefined, + choices: undefined, + required: false, + help: undefined, + metavar: undefined + }) + + if (nargs === 0) { + throw new TypeError('nargs for append actions must be != 0; if arg ' + + 'strings are not supplying the value to append, ' + + 'the append const action may be more appropriate') + } + if (const_value !== undefined && nargs !== OPTIONAL) { + throw new TypeError(sub('nargs must be %r to supply const', OPTIONAL)) + } + super({ + option_strings, + dest, + nargs, + const: const_value, + default: default_value, + type, + choices, + required, + help, + metavar + }) + } + + call(parser, namespace, values/*, option_string = undefined*/) { + let items = getattr(namespace, this.dest, undefined) + items = _copy_items(items) + items.push(values) + setattr(namespace, this.dest, items) + } +}) + + +const _AppendConstAction = _callable(class _AppendConstAction extends Action { + + constructor() { + let [ + option_strings, + dest, + const_value, + default_value, + required, + help, + metavar + ] = _parse_opts(arguments, { + option_strings: no_default, + dest: no_default, + const: no_default, + default: undefined, + required: false, + help: undefined, + metavar: undefined + }) + + super({ + option_strings, + dest, + nargs: 0, + const: const_value, + default: default_value, + required, + help, + metavar + }) + } + + call(parser, namespace/*, values, option_string = undefined*/) { + let items = getattr(namespace, this.dest, undefined) + items = _copy_items(items) + items.push(this.const) + setattr(namespace, this.dest, items) + } +}) + + +const _CountAction = _callable(class _CountAction extends Action { + + constructor() { + let [ + option_strings, + dest, + default_value, + required, + help + ] = _parse_opts(arguments, { + option_strings: no_default, + dest: no_default, + default: undefined, + required: false, + help: undefined + }) + + super({ + option_strings, + dest, + nargs: 0, + default: default_value, + required, + help + }) + } + + call(parser, namespace/*, values, option_string = undefined*/) { + let count = getattr(namespace, this.dest, undefined) + if (count === undefined) { + count = 0 + } + setattr(namespace, this.dest, count + 1) + } +}) + + +const _HelpAction = _callable(class _HelpAction extends Action { + + constructor() { + let [ + option_strings, + dest, + default_value, + help + ] = _parse_opts(arguments, { + option_strings: no_default, + dest: SUPPRESS, + default: SUPPRESS, + help: undefined + }) + + super({ + option_strings, + dest, + default: default_value, + nargs: 0, + help + }) + } + + call(parser/*, namespace, values, option_string = undefined*/) { + parser.print_help() + parser.exit() + } +}) + + +const _VersionAction = _callable(class _VersionAction extends Action { + + constructor() { + let [ + option_strings, + version, + dest, + default_value, + help + ] = _parse_opts(arguments, { + option_strings: no_default, + version: undefined, + dest: SUPPRESS, + default: SUPPRESS, + help: "show program's version number and exit" + }) + + super({ + option_strings, + dest, + default: default_value, + nargs: 0, + help + }) + this.version = version + } + + call(parser/*, namespace, values, option_string = undefined*/) { + let version = this.version + if (version === undefined) { + version = parser.version + } + let formatter = parser._get_formatter() + formatter.add_text(version) + parser._print_message(formatter.format_help(), process.stdout) + parser.exit() + } +}) + + +const _SubParsersAction = _camelcase_alias(_callable(class _SubParsersAction extends Action { + + constructor() { + let [ + option_strings, + prog, + parser_class, + dest, + required, + help, + metavar + ] = _parse_opts(arguments, { + option_strings: no_default, + prog: no_default, + parser_class: no_default, + dest: SUPPRESS, + required: false, + help: undefined, + metavar: undefined + }) + + let name_parser_map = {} + + super({ + option_strings, + dest, + nargs: PARSER, + choices: name_parser_map, + required, + help, + metavar + }) + + this._prog_prefix = prog + this._parser_class = parser_class + this._name_parser_map = name_parser_map + this._choices_actions = [] + } + + add_parser() { + let [ + name, + kwargs + ] = _parse_opts(arguments, { + name: no_default, + '**kwargs': no_default + }) + + // set prog from the existing prefix + if (kwargs.prog === undefined) { + kwargs.prog = sub('%s %s', this._prog_prefix, name) + } + + let aliases = getattr(kwargs, 'aliases', []) + delete kwargs.aliases + + // create a pseudo-action to hold the choice help + if ('help' in kwargs) { + let help = kwargs.help + delete kwargs.help + let choice_action = this._ChoicesPseudoAction(name, aliases, help) + this._choices_actions.push(choice_action) + } + + // create the parser and add it to the map + let parser = new this._parser_class(kwargs) + this._name_parser_map[name] = parser + + // make parser available under aliases also + for (let alias of aliases) { + this._name_parser_map[alias] = parser + } + + return parser + } + + _get_subactions() { + return this._choices_actions + } + + call(parser, namespace, values/*, option_string = undefined*/) { + let parser_name = values[0] + let arg_strings = values.slice(1) + + // set the parser name if requested + if (this.dest !== SUPPRESS) { + setattr(namespace, this.dest, parser_name) + } + + // select the parser + if (hasattr(this._name_parser_map, parser_name)) { + parser = this._name_parser_map[parser_name] + } else { + let args = {parser_name, + choices: this._name_parser_map.join(', ')} + let msg = sub('unknown parser %(parser_name)r (choices: %(choices)s)', args) + throw new ArgumentError(this, msg) + } + + // parse all the remaining options into the namespace + // store any unrecognized options on the object, so that the top + // level parser can decide what to do with them + + // In case this subparser defines new defaults, we parse them + // in a new namespace object and then update the original + // namespace for the relevant parts. + let subnamespace + [ subnamespace, arg_strings ] = parser.parse_known_args(arg_strings, undefined) + for (let [ key, value ] of Object.entries(subnamespace)) { + setattr(namespace, key, value) + } + + if (arg_strings.length) { + setdefault(namespace, _UNRECOGNIZED_ARGS_ATTR, []) + getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).push(...arg_strings) + } + } +})) + + +_SubParsersAction.prototype._ChoicesPseudoAction = _callable(class _ChoicesPseudoAction extends Action { + constructor(name, aliases, help) { + let metavar = name, dest = name + if (aliases.length) { + metavar += sub(' (%s)', aliases.join(', ')) + } + super({ option_strings: [], dest, help, metavar }) + } +}) + + +const _ExtendAction = _callable(class _ExtendAction extends _AppendAction { + call(parser, namespace, values/*, option_string = undefined*/) { + let items = getattr(namespace, this.dest, undefined) + items = _copy_items(items) + items = items.concat(values) + setattr(namespace, this.dest, items) + } +}) + + +// ============== +// Type classes +// ============== +const FileType = _callable(class FileType extends Function { + /* + * Factory for creating file object types + * + * Instances of FileType are typically passed as type= arguments to the + * ArgumentParser add_argument() method. + * + * Keyword Arguments: + * - mode -- A string indicating how the file is to be opened. Accepts the + * same values as the builtin open() function. + * - bufsize -- The file's desired buffer size. Accepts the same values as + * the builtin open() function. + * - encoding -- The file's encoding. Accepts the same values as the + * builtin open() function. + * - errors -- A string indicating how encoding and decoding errors are to + * be handled. Accepts the same value as the builtin open() function. + */ + + constructor() { + let [ + flags, + encoding, + mode, + autoClose, + emitClose, + start, + end, + highWaterMark, + fs + ] = _parse_opts(arguments, { + flags: 'r', + encoding: undefined, + mode: undefined, // 0o666 + autoClose: undefined, // true + emitClose: undefined, // false + start: undefined, // 0 + end: undefined, // Infinity + highWaterMark: undefined, // 64 * 1024 + fs: undefined + }) + + // when this class is called as a function, redirect it to .call() method of itself + super('return arguments.callee.call.apply(arguments.callee, arguments)') + + Object.defineProperty(this, 'name', { + get() { + return sub('FileType(%r)', flags) + } + }) + this._flags = flags + this._options = {} + if (encoding !== undefined) this._options.encoding = encoding + if (mode !== undefined) this._options.mode = mode + if (autoClose !== undefined) this._options.autoClose = autoClose + if (emitClose !== undefined) this._options.emitClose = emitClose + if (start !== undefined) this._options.start = start + if (end !== undefined) this._options.end = end + if (highWaterMark !== undefined) this._options.highWaterMark = highWaterMark + if (fs !== undefined) this._options.fs = fs + } + + call(string) { + // the special argument "-" means sys.std{in,out} + if (string === '-') { + if (this._flags.includes('r')) { + return process.stdin + } else if (this._flags.includes('w')) { + return process.stdout + } else { + let msg = sub('argument "-" with mode %r', this._flags) + throw new TypeError(msg) + } + } + + // all other arguments are used as file names + let fd + try { + fd = fs.openSync(string, this._flags, this._options.mode) + } catch (e) { + let args = { filename: string, error: e.message } + let message = "can't open '%(filename)s': %(error)s" + throw new ArgumentTypeError(sub(message, args)) + } + + let options = Object.assign({ fd, flags: this._flags }, this._options) + if (this._flags.includes('r')) { + return fs.createReadStream(undefined, options) + } else if (this._flags.includes('w')) { + return fs.createWriteStream(undefined, options) + } else { + let msg = sub('argument "%s" with mode %r', string, this._flags) + throw new TypeError(msg) + } + } + + [util.inspect.custom]() { + let args = [ this._flags ] + let kwargs = Object.entries(this._options).map(([ k, v ]) => { + if (k === 'mode') v = { value: v, [util.inspect.custom]() { return '0o' + this.value.toString(8) } } + return [ k, v ] + }) + let args_str = [] + .concat(args.filter(arg => arg !== -1).map(repr)) + .concat(kwargs.filter(([/*kw*/, arg]) => arg !== undefined) + .map(([kw, arg]) => sub('%s=%r', kw, arg))) + .join(', ') + return sub('%s(%s)', this.constructor.name, args_str) + } + + toString() { + return this[util.inspect.custom]() + } +}) + +// =========================== +// Optional and Positional Parsing +// =========================== +const Namespace = _callable(class Namespace extends _AttributeHolder() { + /* + * Simple object for storing attributes. + * + * Implements equality by attribute names and values, and provides a simple + * string representation. + */ + + constructor(options = {}) { + super() + Object.assign(this, options) + } +}) + +// unset string tag to mimic plain object +Namespace.prototype[Symbol.toStringTag] = undefined + + +const _ActionsContainer = _camelcase_alias(_callable(class _ActionsContainer { + + constructor() { + let [ + description, + prefix_chars, + argument_default, + conflict_handler + ] = _parse_opts(arguments, { + description: no_default, + prefix_chars: no_default, + argument_default: no_default, + conflict_handler: no_default + }) + + this.description = description + this.argument_default = argument_default + this.prefix_chars = prefix_chars + this.conflict_handler = conflict_handler + + // set up registries + this._registries = {} + + // register actions + this.register('action', undefined, _StoreAction) + this.register('action', 'store', _StoreAction) + this.register('action', 'store_const', _StoreConstAction) + this.register('action', 'store_true', _StoreTrueAction) + this.register('action', 'store_false', _StoreFalseAction) + this.register('action', 'append', _AppendAction) + this.register('action', 'append_const', _AppendConstAction) + this.register('action', 'count', _CountAction) + this.register('action', 'help', _HelpAction) + this.register('action', 'version', _VersionAction) + this.register('action', 'parsers', _SubParsersAction) + this.register('action', 'extend', _ExtendAction) + // LEGACY (v1 compatibility): camelcase variants + ;[ 'storeConst', 'storeTrue', 'storeFalse', 'appendConst' ].forEach(old_name => { + let new_name = _to_new_name(old_name) + this.register('action', old_name, util.deprecate(this._registry_get('action', new_name), + sub('{action: "%s"} is renamed to {action: "%s"}', old_name, new_name))) + }) + // end + + // raise an exception if the conflict handler is invalid + this._get_handler() + + // action storage + this._actions = [] + this._option_string_actions = {} + + // groups + this._action_groups = [] + this._mutually_exclusive_groups = [] + + // defaults storage + this._defaults = {} + + // determines whether an "option" looks like a negative number + this._negative_number_matcher = /^-\d+$|^-\d*\.\d+$/ + + // whether or not there are any optionals that look like negative + // numbers -- uses a list so it can be shared and edited + this._has_negative_number_optionals = [] + } + + // ==================== + // Registration methods + // ==================== + register(registry_name, value, object) { + let registry = setdefault(this._registries, registry_name, {}) + registry[value] = object + } + + _registry_get(registry_name, value, default_value = undefined) { + return getattr(this._registries[registry_name], value, default_value) + } + + // ================================== + // Namespace default accessor methods + // ================================== + set_defaults(kwargs) { + Object.assign(this._defaults, kwargs) + + // if these defaults match any existing arguments, replace + // the previous default on the object with the new one + for (let action of this._actions) { + if (action.dest in kwargs) { + action.default = kwargs[action.dest] + } + } + } + + get_default(dest) { + for (let action of this._actions) { + if (action.dest === dest && action.default !== undefined) { + return action.default + } + } + return this._defaults[dest] + } + + + // ======================= + // Adding argument actions + // ======================= + add_argument() { + /* + * add_argument(dest, ..., name=value, ...) + * add_argument(option_string, option_string, ..., name=value, ...) + */ + let [ + args, + kwargs + ] = _parse_opts(arguments, { + '*args': no_default, + '**kwargs': no_default + }) + // LEGACY (v1 compatibility), old-style add_argument([ args ], { options }) + if (args.length === 1 && Array.isArray(args[0])) { + args = args[0] + deprecate('argument-array', + sub('use add_argument(%(args)s, {...}) instead of add_argument([ %(args)s ], { ... })', { + args: args.map(repr).join(', ') + })) + } + // end + + // if no positional args are supplied or only one is supplied and + // it doesn't look like an option string, parse a positional + // argument + let chars = this.prefix_chars + if (!args.length || args.length === 1 && !chars.includes(args[0][0])) { + if (args.length && 'dest' in kwargs) { + throw new TypeError('dest supplied twice for positional argument') + } + kwargs = this._get_positional_kwargs(...args, kwargs) + + // otherwise, we're adding an optional argument + } else { + kwargs = this._get_optional_kwargs(...args, kwargs) + } + + // if no default was supplied, use the parser-level default + if (!('default' in kwargs)) { + let dest = kwargs.dest + if (dest in this._defaults) { + kwargs.default = this._defaults[dest] + } else if (this.argument_default !== undefined) { + kwargs.default = this.argument_default + } + } + + // create the action object, and add it to the parser + let action_class = this._pop_action_class(kwargs) + if (typeof action_class !== 'function') { + throw new TypeError(sub('unknown action "%s"', action_class)) + } + // eslint-disable-next-line new-cap + let action = new action_class(kwargs) + + // raise an error if the action type is not callable + let type_func = this._registry_get('type', action.type, action.type) + if (typeof type_func !== 'function') { + throw new TypeError(sub('%r is not callable', type_func)) + } + + if (type_func === FileType) { + throw new TypeError(sub('%r is a FileType class object, instance of it' + + ' must be passed', type_func)) + } + + // raise an error if the metavar does not match the type + if ('_get_formatter' in this) { + try { + this._get_formatter()._format_args(action, undefined) + } catch (err) { + // check for 'invalid nargs value' is an artifact of TypeError and ValueError in js being the same + if (err instanceof TypeError && err.message !== 'invalid nargs value') { + throw new TypeError('length of metavar tuple does not match nargs') + } else { + throw err + } + } + } + + return this._add_action(action) + } + + add_argument_group() { + let group = _ArgumentGroup(this, ...arguments) + this._action_groups.push(group) + return group + } + + add_mutually_exclusive_group() { + // eslint-disable-next-line no-use-before-define + let group = _MutuallyExclusiveGroup(this, ...arguments) + this._mutually_exclusive_groups.push(group) + return group + } + + _add_action(action) { + // resolve any conflicts + this._check_conflict(action) + + // add to actions list + this._actions.push(action) + action.container = this + + // index the action by any option strings it has + for (let option_string of action.option_strings) { + this._option_string_actions[option_string] = action + } + + // set the flag if any option strings look like negative numbers + for (let option_string of action.option_strings) { + if (this._negative_number_matcher.test(option_string)) { + if (!this._has_negative_number_optionals.length) { + this._has_negative_number_optionals.push(true) + } + } + } + + // return the created action + return action + } + + _remove_action(action) { + _array_remove(this._actions, action) + } + + _add_container_actions(container) { + // collect groups by titles + let title_group_map = {} + for (let group of this._action_groups) { + if (group.title in title_group_map) { + let msg = 'cannot merge actions - two groups are named %r' + throw new TypeError(sub(msg, group.title)) + } + title_group_map[group.title] = group + } + + // map each action to its group + let group_map = new Map() + for (let group of container._action_groups) { + + // if a group with the title exists, use that, otherwise + // create a new group matching the container's group + if (!(group.title in title_group_map)) { + title_group_map[group.title] = this.add_argument_group({ + title: group.title, + description: group.description, + conflict_handler: group.conflict_handler + }) + } + + // map the actions to their new group + for (let action of group._group_actions) { + group_map.set(action, title_group_map[group.title]) + } + } + + // add container's mutually exclusive groups + // NOTE: if add_mutually_exclusive_group ever gains title= and + // description= then this code will need to be expanded as above + for (let group of container._mutually_exclusive_groups) { + let mutex_group = this.add_mutually_exclusive_group({ + required: group.required + }) + + // map the actions to their new mutex group + for (let action of group._group_actions) { + group_map.set(action, mutex_group) + } + } + + // add all actions to this container or their group + for (let action of container._actions) { + group_map.get(action)._add_action(action) + } + } + + _get_positional_kwargs() { + let [ + dest, + kwargs + ] = _parse_opts(arguments, { + dest: no_default, + '**kwargs': no_default + }) + + // make sure required is not specified + if ('required' in kwargs) { + let msg = "'required' is an invalid argument for positionals" + throw new TypeError(msg) + } + + // mark positional arguments as required if at least one is + // always required + if (![OPTIONAL, ZERO_OR_MORE].includes(kwargs.nargs)) { + kwargs.required = true + } + if (kwargs.nargs === ZERO_OR_MORE && !('default' in kwargs)) { + kwargs.required = true + } + + // return the keyword arguments with no option strings + return Object.assign(kwargs, { dest, option_strings: [] }) + } + + _get_optional_kwargs() { + let [ + args, + kwargs + ] = _parse_opts(arguments, { + '*args': no_default, + '**kwargs': no_default + }) + + // determine short and long option strings + let option_strings = [] + let long_option_strings = [] + let option_string + for (option_string of args) { + // error on strings that don't start with an appropriate prefix + if (!this.prefix_chars.includes(option_string[0])) { + let args = {option: option_string, + prefix_chars: this.prefix_chars} + let msg = 'invalid option string %(option)r: ' + + 'must start with a character %(prefix_chars)r' + throw new TypeError(sub(msg, args)) + } + + // strings starting with two prefix characters are long options + option_strings.push(option_string) + if (option_string.length > 1 && this.prefix_chars.includes(option_string[1])) { + long_option_strings.push(option_string) + } + } + + // infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x' + let dest = kwargs.dest + delete kwargs.dest + if (dest === undefined) { + let dest_option_string + if (long_option_strings.length) { + dest_option_string = long_option_strings[0] + } else { + dest_option_string = option_strings[0] + } + dest = _string_lstrip(dest_option_string, this.prefix_chars) + if (!dest) { + let msg = 'dest= is required for options like %r' + throw new TypeError(sub(msg, option_string)) + } + dest = dest.replace(/-/g, '_') + } + + // return the updated keyword arguments + return Object.assign(kwargs, { dest, option_strings }) + } + + _pop_action_class(kwargs, default_value = undefined) { + let action = getattr(kwargs, 'action', default_value) + delete kwargs.action + return this._registry_get('action', action, action) + } + + _get_handler() { + // determine function from conflict handler string + let handler_func_name = sub('_handle_conflict_%s', this.conflict_handler) + if (typeof this[handler_func_name] === 'function') { + return this[handler_func_name] + } else { + let msg = 'invalid conflict_resolution value: %r' + throw new TypeError(sub(msg, this.conflict_handler)) + } + } + + _check_conflict(action) { + + // find all options that conflict with this option + let confl_optionals = [] + for (let option_string of action.option_strings) { + if (hasattr(this._option_string_actions, option_string)) { + let confl_optional = this._option_string_actions[option_string] + confl_optionals.push([ option_string, confl_optional ]) + } + } + + // resolve any conflicts + if (confl_optionals.length) { + let conflict_handler = this._get_handler() + conflict_handler.call(this, action, confl_optionals) + } + } + + _handle_conflict_error(action, conflicting_actions) { + let message = conflicting_actions.length === 1 ? + 'conflicting option string: %s' : + 'conflicting option strings: %s' + let conflict_string = conflicting_actions.map(([ option_string/*, action*/ ]) => option_string).join(', ') + throw new ArgumentError(action, sub(message, conflict_string)) + } + + _handle_conflict_resolve(action, conflicting_actions) { + + // remove all conflicting options + for (let [ option_string, action ] of conflicting_actions) { + + // remove the conflicting option + _array_remove(action.option_strings, option_string) + delete this._option_string_actions[option_string] + + // if the option now has no option string, remove it from the + // container holding it + if (!action.option_strings.length) { + action.container._remove_action(action) + } + } + } +})) + + +const _ArgumentGroup = _callable(class _ArgumentGroup extends _ActionsContainer { + + constructor() { + let [ + container, + title, + description, + kwargs + ] = _parse_opts(arguments, { + container: no_default, + title: undefined, + description: undefined, + '**kwargs': no_default + }) + + // add any missing keyword arguments by checking the container + setdefault(kwargs, 'conflict_handler', container.conflict_handler) + setdefault(kwargs, 'prefix_chars', container.prefix_chars) + setdefault(kwargs, 'argument_default', container.argument_default) + super(Object.assign({ description }, kwargs)) + + // group attributes + this.title = title + this._group_actions = [] + + // share most attributes with the container + this._registries = container._registries + this._actions = container._actions + this._option_string_actions = container._option_string_actions + this._defaults = container._defaults + this._has_negative_number_optionals = + container._has_negative_number_optionals + this._mutually_exclusive_groups = container._mutually_exclusive_groups + } + + _add_action(action) { + action = super._add_action(action) + this._group_actions.push(action) + return action + } + + _remove_action(action) { + super._remove_action(action) + _array_remove(this._group_actions, action) + } +}) + + +const _MutuallyExclusiveGroup = _callable(class _MutuallyExclusiveGroup extends _ArgumentGroup { + + constructor() { + let [ + container, + required + ] = _parse_opts(arguments, { + container: no_default, + required: false + }) + + super(container) + this.required = required + this._container = container + } + + _add_action(action) { + if (action.required) { + let msg = 'mutually exclusive arguments must be optional' + throw new TypeError(msg) + } + action = this._container._add_action(action) + this._group_actions.push(action) + return action + } + + _remove_action(action) { + this._container._remove_action(action) + _array_remove(this._group_actions, action) + } +}) + + +const ArgumentParser = _camelcase_alias(_callable(class ArgumentParser extends _AttributeHolder(_ActionsContainer) { + /* + * Object for parsing command line strings into Python objects. + * + * Keyword Arguments: + * - prog -- The name of the program (default: sys.argv[0]) + * - usage -- A usage message (default: auto-generated from arguments) + * - description -- A description of what the program does + * - epilog -- Text following the argument descriptions + * - parents -- Parsers whose arguments should be copied into this one + * - formatter_class -- HelpFormatter class for printing help messages + * - prefix_chars -- Characters that prefix optional arguments + * - fromfile_prefix_chars -- Characters that prefix files containing + * additional arguments + * - argument_default -- The default value for all arguments + * - conflict_handler -- String indicating how to handle conflicts + * - add_help -- Add a -h/-help option + * - allow_abbrev -- Allow long options to be abbreviated unambiguously + * - exit_on_error -- Determines whether or not ArgumentParser exits with + * error info when an error occurs + */ + + constructor() { + let [ + prog, + usage, + description, + epilog, + parents, + formatter_class, + prefix_chars, + fromfile_prefix_chars, + argument_default, + conflict_handler, + add_help, + allow_abbrev, + exit_on_error, + debug, // LEGACY (v1 compatibility), debug mode + version // LEGACY (v1 compatibility), version + ] = _parse_opts(arguments, { + prog: undefined, + usage: undefined, + description: undefined, + epilog: undefined, + parents: [], + formatter_class: HelpFormatter, + prefix_chars: '-', + fromfile_prefix_chars: undefined, + argument_default: undefined, + conflict_handler: 'error', + add_help: true, + allow_abbrev: true, + exit_on_error: true, + debug: undefined, // LEGACY (v1 compatibility), debug mode + version: undefined // LEGACY (v1 compatibility), version + }) + + // LEGACY (v1 compatibility) + if (debug !== undefined) { + deprecate('debug', + 'The "debug" argument to ArgumentParser is deprecated. Please ' + + 'override ArgumentParser.exit function instead.' + ) + } + + if (version !== undefined) { + deprecate('version', + 'The "version" argument to ArgumentParser is deprecated. Please use ' + + "add_argument(..., { action: 'version', version: 'N', ... }) instead." + ) + } + // end + + super({ + description, + prefix_chars, + argument_default, + conflict_handler + }) + + // default setting for prog + if (prog === undefined) { + prog = path.basename(get_argv()[0] || '') + } + + this.prog = prog + this.usage = usage + this.epilog = epilog + this.formatter_class = formatter_class + this.fromfile_prefix_chars = fromfile_prefix_chars + this.add_help = add_help + this.allow_abbrev = allow_abbrev + this.exit_on_error = exit_on_error + // LEGACY (v1 compatibility), debug mode + this.debug = debug + // end + + this._positionals = this.add_argument_group('positional arguments') + this._optionals = this.add_argument_group('optional arguments') + this._subparsers = undefined + + // register types + function identity(string) { + return string + } + this.register('type', undefined, identity) + this.register('type', null, identity) + this.register('type', 'auto', identity) + this.register('type', 'int', function (x) { + let result = Number(x) + if (!Number.isInteger(result)) { + throw new TypeError(sub('could not convert string to int: %r', x)) + } + return result + }) + this.register('type', 'float', function (x) { + let result = Number(x) + if (isNaN(result)) { + throw new TypeError(sub('could not convert string to float: %r', x)) + } + return result + }) + this.register('type', 'str', String) + // LEGACY (v1 compatibility): custom types + this.register('type', 'string', + util.deprecate(String, 'use {type:"str"} or {type:String} instead of {type:"string"}')) + // end + + // add help argument if necessary + // (using explicit default to override global argument_default) + let default_prefix = prefix_chars.includes('-') ? '-' : prefix_chars[0] + if (this.add_help) { + this.add_argument( + default_prefix + 'h', + default_prefix.repeat(2) + 'help', + { + action: 'help', + default: SUPPRESS, + help: 'show this help message and exit' + } + ) + } + // LEGACY (v1 compatibility), version + if (version) { + this.add_argument( + default_prefix + 'v', + default_prefix.repeat(2) + 'version', + { + action: 'version', + default: SUPPRESS, + version: this.version, + help: "show program's version number and exit" + } + ) + } + // end + + // add parent arguments and defaults + for (let parent of parents) { + this._add_container_actions(parent) + Object.assign(this._defaults, parent._defaults) + } + } + + // ======================= + // Pretty __repr__ methods + // ======================= + _get_kwargs() { + let names = [ + 'prog', + 'usage', + 'description', + 'formatter_class', + 'conflict_handler', + 'add_help' + ] + return names.map(name => [ name, getattr(this, name) ]) + } + + // ================================== + // Optional/Positional adding methods + // ================================== + add_subparsers() { + let [ + kwargs + ] = _parse_opts(arguments, { + '**kwargs': no_default + }) + + if (this._subparsers !== undefined) { + this.error('cannot have multiple subparser arguments') + } + + // add the parser class to the arguments if it's not present + setdefault(kwargs, 'parser_class', this.constructor) + + if ('title' in kwargs || 'description' in kwargs) { + let title = getattr(kwargs, 'title', 'subcommands') + let description = getattr(kwargs, 'description', undefined) + delete kwargs.title + delete kwargs.description + this._subparsers = this.add_argument_group(title, description) + } else { + this._subparsers = this._positionals + } + + // prog defaults to the usage message of this parser, skipping + // optional arguments and with no "usage:" prefix + if (kwargs.prog === undefined) { + let formatter = this._get_formatter() + let positionals = this._get_positional_actions() + let groups = this._mutually_exclusive_groups + formatter.add_usage(this.usage, positionals, groups, '') + kwargs.prog = formatter.format_help().trim() + } + + // create the parsers action and add it to the positionals list + let parsers_class = this._pop_action_class(kwargs, 'parsers') + // eslint-disable-next-line new-cap + let action = new parsers_class(Object.assign({ option_strings: [] }, kwargs)) + this._subparsers._add_action(action) + + // return the created parsers action + return action + } + + _add_action(action) { + if (action.option_strings.length) { + this._optionals._add_action(action) + } else { + this._positionals._add_action(action) + } + return action + } + + _get_optional_actions() { + return this._actions.filter(action => action.option_strings.length) + } + + _get_positional_actions() { + return this._actions.filter(action => !action.option_strings.length) + } + + // ===================================== + // Command line argument parsing methods + // ===================================== + parse_args(args = undefined, namespace = undefined) { + let argv + [ args, argv ] = this.parse_known_args(args, namespace) + if (argv && argv.length > 0) { + let msg = 'unrecognized arguments: %s' + this.error(sub(msg, argv.join(' '))) + } + return args + } + + parse_known_args(args = undefined, namespace = undefined) { + if (args === undefined) { + args = get_argv().slice(1) + } + + // default Namespace built from parser defaults + if (namespace === undefined) { + namespace = new Namespace() + } + + // add any action defaults that aren't present + for (let action of this._actions) { + if (action.dest !== SUPPRESS) { + if (!hasattr(namespace, action.dest)) { + if (action.default !== SUPPRESS) { + setattr(namespace, action.dest, action.default) + } + } + } + } + + // add any parser defaults that aren't present + for (let dest of Object.keys(this._defaults)) { + if (!hasattr(namespace, dest)) { + setattr(namespace, dest, this._defaults[dest]) + } + } + + // parse the arguments and exit if there are any errors + if (this.exit_on_error) { + try { + [ namespace, args ] = this._parse_known_args(args, namespace) + } catch (err) { + if (err instanceof ArgumentError) { + this.error(err.message) + } else { + throw err + } + } + } else { + [ namespace, args ] = this._parse_known_args(args, namespace) + } + + if (hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) { + args = args.concat(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) + delattr(namespace, _UNRECOGNIZED_ARGS_ATTR) + } + + return [ namespace, args ] + } + + _parse_known_args(arg_strings, namespace) { + // replace arg strings that are file references + if (this.fromfile_prefix_chars !== undefined) { + arg_strings = this._read_args_from_files(arg_strings) + } + + // map all mutually exclusive arguments to the other arguments + // they can't occur with + let action_conflicts = new Map() + for (let mutex_group of this._mutually_exclusive_groups) { + let group_actions = mutex_group._group_actions + for (let [ i, mutex_action ] of Object.entries(mutex_group._group_actions)) { + let conflicts = action_conflicts.get(mutex_action) || [] + conflicts = conflicts.concat(group_actions.slice(0, +i)) + conflicts = conflicts.concat(group_actions.slice(+i + 1)) + action_conflicts.set(mutex_action, conflicts) + } + } + + // find all option indices, and determine the arg_string_pattern + // which has an 'O' if there is an option at an index, + // an 'A' if there is an argument, or a '-' if there is a '--' + let option_string_indices = {} + let arg_string_pattern_parts = [] + let arg_strings_iter = Object.entries(arg_strings)[Symbol.iterator]() + for (let [ i, arg_string ] of arg_strings_iter) { + + // all args after -- are non-options + if (arg_string === '--') { + arg_string_pattern_parts.push('-') + for ([ i, arg_string ] of arg_strings_iter) { + arg_string_pattern_parts.push('A') + } + + // otherwise, add the arg to the arg strings + // and note the index if it was an option + } else { + let option_tuple = this._parse_optional(arg_string) + let pattern + if (option_tuple === undefined) { + pattern = 'A' + } else { + option_string_indices[i] = option_tuple + pattern = 'O' + } + arg_string_pattern_parts.push(pattern) + } + } + + // join the pieces together to form the pattern + let arg_strings_pattern = arg_string_pattern_parts.join('') + + // converts arg strings to the appropriate and then takes the action + let seen_actions = new Set() + let seen_non_default_actions = new Set() + let extras + + let take_action = (action, argument_strings, option_string = undefined) => { + seen_actions.add(action) + let argument_values = this._get_values(action, argument_strings) + + // error if this argument is not allowed with other previously + // seen arguments, assuming that actions that use the default + // value don't really count as "present" + if (argument_values !== action.default) { + seen_non_default_actions.add(action) + for (let conflict_action of action_conflicts.get(action) || []) { + if (seen_non_default_actions.has(conflict_action)) { + let msg = 'not allowed with argument %s' + let action_name = _get_action_name(conflict_action) + throw new ArgumentError(action, sub(msg, action_name)) + } + } + } + + // take the action if we didn't receive a SUPPRESS value + // (e.g. from a default) + if (argument_values !== SUPPRESS) { + action(this, namespace, argument_values, option_string) + } + } + + // function to convert arg_strings into an optional action + let consume_optional = start_index => { + + // get the optional identified at this index + let option_tuple = option_string_indices[start_index] + let [ action, option_string, explicit_arg ] = option_tuple + + // identify additional optionals in the same arg string + // (e.g. -xyz is the same as -x -y -z if no args are required) + let action_tuples = [] + let stop + for (;;) { + + // if we found no optional action, skip it + if (action === undefined) { + extras.push(arg_strings[start_index]) + return start_index + 1 + } + + // if there is an explicit argument, try to match the + // optional's string arguments to only this + if (explicit_arg !== undefined) { + let arg_count = this._match_argument(action, 'A') + + // if the action is a single-dash option and takes no + // arguments, try to parse more single-dash options out + // of the tail of the option string + let chars = this.prefix_chars + if (arg_count === 0 && !chars.includes(option_string[1])) { + action_tuples.push([ action, [], option_string ]) + let char = option_string[0] + option_string = char + explicit_arg[0] + let new_explicit_arg = explicit_arg.slice(1) || undefined + let optionals_map = this._option_string_actions + if (hasattr(optionals_map, option_string)) { + action = optionals_map[option_string] + explicit_arg = new_explicit_arg + } else { + let msg = 'ignored explicit argument %r' + throw new ArgumentError(action, sub(msg, explicit_arg)) + } + + // if the action expect exactly one argument, we've + // successfully matched the option; exit the loop + } else if (arg_count === 1) { + stop = start_index + 1 + let args = [ explicit_arg ] + action_tuples.push([ action, args, option_string ]) + break + + // error if a double-dash option did not use the + // explicit argument + } else { + let msg = 'ignored explicit argument %r' + throw new ArgumentError(action, sub(msg, explicit_arg)) + } + + // if there is no explicit argument, try to match the + // optional's string arguments with the following strings + // if successful, exit the loop + } else { + let start = start_index + 1 + let selected_patterns = arg_strings_pattern.slice(start) + let arg_count = this._match_argument(action, selected_patterns) + stop = start + arg_count + let args = arg_strings.slice(start, stop) + action_tuples.push([ action, args, option_string ]) + break + } + } + + // add the Optional to the list and return the index at which + // the Optional's string args stopped + assert(action_tuples.length) + for (let [ action, args, option_string ] of action_tuples) { + take_action(action, args, option_string) + } + return stop + } + + // the list of Positionals left to be parsed; this is modified + // by consume_positionals() + let positionals = this._get_positional_actions() + + // function to convert arg_strings into positional actions + let consume_positionals = start_index => { + // match as many Positionals as possible + let selected_pattern = arg_strings_pattern.slice(start_index) + let arg_counts = this._match_arguments_partial(positionals, selected_pattern) + + // slice off the appropriate arg strings for each Positional + // and add the Positional and its args to the list + for (let i = 0; i < positionals.length && i < arg_counts.length; i++) { + let action = positionals[i] + let arg_count = arg_counts[i] + let args = arg_strings.slice(start_index, start_index + arg_count) + start_index += arg_count + take_action(action, args) + } + + // slice off the Positionals that we just parsed and return the + // index at which the Positionals' string args stopped + positionals = positionals.slice(arg_counts.length) + return start_index + } + + // consume Positionals and Optionals alternately, until we have + // passed the last option string + extras = [] + let start_index = 0 + let max_option_string_index = Math.max(-1, ...Object.keys(option_string_indices).map(Number)) + while (start_index <= max_option_string_index) { + + // consume any Positionals preceding the next option + let next_option_string_index = Math.min( + // eslint-disable-next-line no-loop-func + ...Object.keys(option_string_indices).map(Number).filter(index => index >= start_index) + ) + if (start_index !== next_option_string_index) { + let positionals_end_index = consume_positionals(start_index) + + // only try to parse the next optional if we didn't consume + // the option string during the positionals parsing + if (positionals_end_index > start_index) { + start_index = positionals_end_index + continue + } else { + start_index = positionals_end_index + } + } + + // if we consumed all the positionals we could and we're not + // at the index of an option string, there were extra arguments + if (!(start_index in option_string_indices)) { + let strings = arg_strings.slice(start_index, next_option_string_index) + extras = extras.concat(strings) + start_index = next_option_string_index + } + + // consume the next optional and any arguments for it + start_index = consume_optional(start_index) + } + + // consume any positionals following the last Optional + let stop_index = consume_positionals(start_index) + + // if we didn't consume all the argument strings, there were extras + extras = extras.concat(arg_strings.slice(stop_index)) + + // make sure all required actions were present and also convert + // action defaults which were not given as arguments + let required_actions = [] + for (let action of this._actions) { + if (!seen_actions.has(action)) { + if (action.required) { + required_actions.push(_get_action_name(action)) + } else { + // Convert action default now instead of doing it before + // parsing arguments to avoid calling convert functions + // twice (which may fail) if the argument was given, but + // only if it was defined already in the namespace + if (action.default !== undefined && + typeof action.default === 'string' && + hasattr(namespace, action.dest) && + action.default === getattr(namespace, action.dest)) { + setattr(namespace, action.dest, + this._get_value(action, action.default)) + } + } + } + } + + if (required_actions.length) { + this.error(sub('the following arguments are required: %s', + required_actions.join(', '))) + } + + // make sure all required groups had one option present + for (let group of this._mutually_exclusive_groups) { + if (group.required) { + let no_actions_used = true + for (let action of group._group_actions) { + if (seen_non_default_actions.has(action)) { + no_actions_used = false + break + } + } + + // if no actions were used, report the error + if (no_actions_used) { + let names = group._group_actions + .filter(action => action.help !== SUPPRESS) + .map(action => _get_action_name(action)) + let msg = 'one of the arguments %s is required' + this.error(sub(msg, names.join(' '))) + } + } + } + + // return the updated namespace and the extra arguments + return [ namespace, extras ] + } + + _read_args_from_files(arg_strings) { + // expand arguments referencing files + let new_arg_strings = [] + for (let arg_string of arg_strings) { + + // for regular arguments, just add them back into the list + if (!arg_string || !this.fromfile_prefix_chars.includes(arg_string[0])) { + new_arg_strings.push(arg_string) + + // replace arguments referencing files with the file content + } else { + try { + let args_file = fs.readFileSync(arg_string.slice(1), 'utf8') + let arg_strings = [] + for (let arg_line of splitlines(args_file)) { + for (let arg of this.convert_arg_line_to_args(arg_line)) { + arg_strings.push(arg) + } + } + arg_strings = this._read_args_from_files(arg_strings) + new_arg_strings = new_arg_strings.concat(arg_strings) + } catch (err) { + this.error(err.message) + } + } + } + + // return the modified argument list + return new_arg_strings + } + + convert_arg_line_to_args(arg_line) { + return [arg_line] + } + + _match_argument(action, arg_strings_pattern) { + // match the pattern for this action to the arg strings + let nargs_pattern = this._get_nargs_pattern(action) + let match = arg_strings_pattern.match(new RegExp('^' + nargs_pattern)) + + // raise an exception if we weren't able to find a match + if (match === null) { + let nargs_errors = { + undefined: 'expected one argument', + [OPTIONAL]: 'expected at most one argument', + [ONE_OR_MORE]: 'expected at least one argument' + } + let msg = nargs_errors[action.nargs] + if (msg === undefined) { + msg = sub(action.nargs === 1 ? 'expected %s argument' : 'expected %s arguments', action.nargs) + } + throw new ArgumentError(action, msg) + } + + // return the number of arguments matched + return match[1].length + } + + _match_arguments_partial(actions, arg_strings_pattern) { + // progressively shorten the actions list by slicing off the + // final actions until we find a match + let result = [] + for (let i of range(actions.length, 0, -1)) { + let actions_slice = actions.slice(0, i) + let pattern = actions_slice.map(action => this._get_nargs_pattern(action)).join('') + let match = arg_strings_pattern.match(new RegExp('^' + pattern)) + if (match !== null) { + result = result.concat(match.slice(1).map(string => string.length)) + break + } + } + + // return the list of arg string counts + return result + } + + _parse_optional(arg_string) { + // if it's an empty string, it was meant to be a positional + if (!arg_string) { + return undefined + } + + // if it doesn't start with a prefix, it was meant to be positional + if (!this.prefix_chars.includes(arg_string[0])) { + return undefined + } + + // if the option string is present in the parser, return the action + if (arg_string in this._option_string_actions) { + let action = this._option_string_actions[arg_string] + return [ action, arg_string, undefined ] + } + + // if it's just a single character, it was meant to be positional + if (arg_string.length === 1) { + return undefined + } + + // if the option string before the "=" is present, return the action + if (arg_string.includes('=')) { + let [ option_string, explicit_arg ] = _string_split(arg_string, '=', 1) + if (option_string in this._option_string_actions) { + let action = this._option_string_actions[option_string] + return [ action, option_string, explicit_arg ] + } + } + + // search through all possible prefixes of the option string + // and all actions in the parser for possible interpretations + let option_tuples = this._get_option_tuples(arg_string) + + // if multiple actions match, the option string was ambiguous + if (option_tuples.length > 1) { + let options = option_tuples.map(([ /*action*/, option_string/*, explicit_arg*/ ]) => option_string).join(', ') + let args = {option: arg_string, matches: options} + let msg = 'ambiguous option: %(option)s could match %(matches)s' + this.error(sub(msg, args)) + + // if exactly one action matched, this segmentation is good, + // so return the parsed action + } else if (option_tuples.length === 1) { + let [ option_tuple ] = option_tuples + return option_tuple + } + + // if it was not found as an option, but it looks like a negative + // number, it was meant to be positional + // unless there are negative-number-like options + if (this._negative_number_matcher.test(arg_string)) { + if (!this._has_negative_number_optionals.length) { + return undefined + } + } + + // if it contains a space, it was meant to be a positional + if (arg_string.includes(' ')) { + return undefined + } + + // it was meant to be an optional but there is no such option + // in this parser (though it might be a valid option in a subparser) + return [ undefined, arg_string, undefined ] + } + + _get_option_tuples(option_string) { + let result = [] + + // option strings starting with two prefix characters are only + // split at the '=' + let chars = this.prefix_chars + if (chars.includes(option_string[0]) && chars.includes(option_string[1])) { + if (this.allow_abbrev) { + let option_prefix, explicit_arg + if (option_string.includes('=')) { + [ option_prefix, explicit_arg ] = _string_split(option_string, '=', 1) + } else { + option_prefix = option_string + explicit_arg = undefined + } + for (let option_string of Object.keys(this._option_string_actions)) { + if (option_string.startsWith(option_prefix)) { + let action = this._option_string_actions[option_string] + let tup = [ action, option_string, explicit_arg ] + result.push(tup) + } + } + } + + // single character options can be concatenated with their arguments + // but multiple character options always have to have their argument + // separate + } else if (chars.includes(option_string[0]) && !chars.includes(option_string[1])) { + let option_prefix = option_string + let explicit_arg = undefined + let short_option_prefix = option_string.slice(0, 2) + let short_explicit_arg = option_string.slice(2) + + for (let option_string of Object.keys(this._option_string_actions)) { + if (option_string === short_option_prefix) { + let action = this._option_string_actions[option_string] + let tup = [ action, option_string, short_explicit_arg ] + result.push(tup) + } else if (option_string.startsWith(option_prefix)) { + let action = this._option_string_actions[option_string] + let tup = [ action, option_string, explicit_arg ] + result.push(tup) + } + } + + // shouldn't ever get here + } else { + this.error(sub('unexpected option string: %s', option_string)) + } + + // return the collected option tuples + return result + } + + _get_nargs_pattern(action) { + // in all examples below, we have to allow for '--' args + // which are represented as '-' in the pattern + let nargs = action.nargs + let nargs_pattern + + // the default (None) is assumed to be a single argument + if (nargs === undefined) { + nargs_pattern = '(-*A-*)' + + // allow zero or one arguments + } else if (nargs === OPTIONAL) { + nargs_pattern = '(-*A?-*)' + + // allow zero or more arguments + } else if (nargs === ZERO_OR_MORE) { + nargs_pattern = '(-*[A-]*)' + + // allow one or more arguments + } else if (nargs === ONE_OR_MORE) { + nargs_pattern = '(-*A[A-]*)' + + // allow any number of options or arguments + } else if (nargs === REMAINDER) { + nargs_pattern = '([-AO]*)' + + // allow one argument followed by any number of options or arguments + } else if (nargs === PARSER) { + nargs_pattern = '(-*A[-AO]*)' + + // suppress action, like nargs=0 + } else if (nargs === SUPPRESS) { + nargs_pattern = '(-*-*)' + + // all others should be integers + } else { + nargs_pattern = sub('(-*%s-*)', 'A'.repeat(nargs).split('').join('-*')) + } + + // if this is an optional action, -- is not allowed + if (action.option_strings.length) { + nargs_pattern = nargs_pattern.replace(/-\*/g, '') + nargs_pattern = nargs_pattern.replace(/-/g, '') + } + + // return the pattern + return nargs_pattern + } + + // ======================== + // Alt command line argument parsing, allowing free intermix + // ======================== + + parse_intermixed_args(args = undefined, namespace = undefined) { + let argv + [ args, argv ] = this.parse_known_intermixed_args(args, namespace) + if (argv.length) { + let msg = 'unrecognized arguments: %s' + this.error(sub(msg, argv.join(' '))) + } + return args + } + + parse_known_intermixed_args(args = undefined, namespace = undefined) { + // returns a namespace and list of extras + // + // positional can be freely intermixed with optionals. optionals are + // first parsed with all positional arguments deactivated. The 'extras' + // are then parsed. If the parser definition is incompatible with the + // intermixed assumptions (e.g. use of REMAINDER, subparsers) a + // TypeError is raised. + // + // positionals are 'deactivated' by setting nargs and default to + // SUPPRESS. This blocks the addition of that positional to the + // namespace + + let extras + let positionals = this._get_positional_actions() + let a = positionals.filter(action => [ PARSER, REMAINDER ].includes(action.nargs)) + if (a.length) { + throw new TypeError(sub('parse_intermixed_args: positional arg' + + ' with nargs=%s', a[0].nargs)) + } + + for (let group of this._mutually_exclusive_groups) { + for (let action of group._group_actions) { + if (positionals.includes(action)) { + throw new TypeError('parse_intermixed_args: positional in' + + ' mutuallyExclusiveGroup') + } + } + } + + let save_usage + try { + save_usage = this.usage + let remaining_args + try { + if (this.usage === undefined) { + // capture the full usage for use in error messages + this.usage = this.format_usage().slice(7) + } + for (let action of positionals) { + // deactivate positionals + action.save_nargs = action.nargs + // action.nargs = 0 + action.nargs = SUPPRESS + action.save_default = action.default + action.default = SUPPRESS + } + [ namespace, remaining_args ] = this.parse_known_args(args, + namespace) + for (let action of positionals) { + // remove the empty positional values from namespace + let attr = getattr(namespace, action.dest) + if (Array.isArray(attr) && attr.length === 0) { + // eslint-disable-next-line no-console + console.warn(sub('Do not expect %s in %s', action.dest, namespace)) + delattr(namespace, action.dest) + } + } + } finally { + // restore nargs and usage before exiting + for (let action of positionals) { + action.nargs = action.save_nargs + action.default = action.save_default + } + } + let optionals = this._get_optional_actions() + try { + // parse positionals. optionals aren't normally required, but + // they could be, so make sure they aren't. + for (let action of optionals) { + action.save_required = action.required + action.required = false + } + for (let group of this._mutually_exclusive_groups) { + group.save_required = group.required + group.required = false + } + [ namespace, extras ] = this.parse_known_args(remaining_args, + namespace) + } finally { + // restore parser values before exiting + for (let action of optionals) { + action.required = action.save_required + } + for (let group of this._mutually_exclusive_groups) { + group.required = group.save_required + } + } + } finally { + this.usage = save_usage + } + return [ namespace, extras ] + } + + // ======================== + // Value conversion methods + // ======================== + _get_values(action, arg_strings) { + // for everything but PARSER, REMAINDER args, strip out first '--' + if (![PARSER, REMAINDER].includes(action.nargs)) { + try { + _array_remove(arg_strings, '--') + } catch (err) {} + } + + let value + // optional argument produces a default when not present + if (!arg_strings.length && action.nargs === OPTIONAL) { + if (action.option_strings.length) { + value = action.const + } else { + value = action.default + } + if (typeof value === 'string') { + value = this._get_value(action, value) + this._check_value(action, value) + } + + // when nargs='*' on a positional, if there were no command-line + // args, use the default if it is anything other than None + } else if (!arg_strings.length && action.nargs === ZERO_OR_MORE && + !action.option_strings.length) { + if (action.default !== undefined) { + value = action.default + } else { + value = arg_strings + } + this._check_value(action, value) + + // single argument or optional argument produces a single value + } else if (arg_strings.length === 1 && [undefined, OPTIONAL].includes(action.nargs)) { + let arg_string = arg_strings[0] + value = this._get_value(action, arg_string) + this._check_value(action, value) + + // REMAINDER arguments convert all values, checking none + } else if (action.nargs === REMAINDER) { + value = arg_strings.map(v => this._get_value(action, v)) + + // PARSER arguments convert all values, but check only the first + } else if (action.nargs === PARSER) { + value = arg_strings.map(v => this._get_value(action, v)) + this._check_value(action, value[0]) + + // SUPPRESS argument does not put anything in the namespace + } else if (action.nargs === SUPPRESS) { + value = SUPPRESS + + // all other types of nargs produce a list + } else { + value = arg_strings.map(v => this._get_value(action, v)) + for (let v of value) { + this._check_value(action, v) + } + } + + // return the converted value + return value + } + + _get_value(action, arg_string) { + let type_func = this._registry_get('type', action.type, action.type) + if (typeof type_func !== 'function') { + let msg = '%r is not callable' + throw new ArgumentError(action, sub(msg, type_func)) + } + + // convert the value to the appropriate type + let result + try { + try { + result = type_func(arg_string) + } catch (err) { + // Dear TC39, why would you ever consider making es6 classes not callable? + // We had one universal interface, [[Call]], which worked for anything + // (with familiar this-instanceof guard for classes). Now we have two. + if (err instanceof TypeError && + /Class constructor .* cannot be invoked without 'new'/.test(err.message)) { + // eslint-disable-next-line new-cap + result = new type_func(arg_string) + } else { + throw err + } + } + + } catch (err) { + // ArgumentTypeErrors indicate errors + if (err instanceof ArgumentTypeError) { + //let name = getattr(action.type, 'name', repr(action.type)) + let msg = err.message + throw new ArgumentError(action, msg) + + // TypeErrors or ValueErrors also indicate errors + } else if (err instanceof TypeError) { + let name = getattr(action.type, 'name', repr(action.type)) + let args = {type: name, value: arg_string} + let msg = 'invalid %(type)s value: %(value)r' + throw new ArgumentError(action, sub(msg, args)) + } else { + throw err + } + } + + // return the converted value + return result + } + + _check_value(action, value) { + // converted value must be one of the choices (if specified) + if (action.choices !== undefined && !_choices_to_array(action.choices).includes(value)) { + let args = {value, + choices: _choices_to_array(action.choices).map(repr).join(', ')} + let msg = 'invalid choice: %(value)r (choose from %(choices)s)' + throw new ArgumentError(action, sub(msg, args)) + } + } + + // ======================= + // Help-formatting methods + // ======================= + format_usage() { + let formatter = this._get_formatter() + formatter.add_usage(this.usage, this._actions, + this._mutually_exclusive_groups) + return formatter.format_help() + } + + format_help() { + let formatter = this._get_formatter() + + // usage + formatter.add_usage(this.usage, this._actions, + this._mutually_exclusive_groups) + + // description + formatter.add_text(this.description) + + // positionals, optionals and user-defined groups + for (let action_group of this._action_groups) { + formatter.start_section(action_group.title) + formatter.add_text(action_group.description) + formatter.add_arguments(action_group._group_actions) + formatter.end_section() + } + + // epilog + formatter.add_text(this.epilog) + + // determine help from format above + return formatter.format_help() + } + + _get_formatter() { + // eslint-disable-next-line new-cap + return new this.formatter_class({ prog: this.prog }) + } + + // ===================== + // Help-printing methods + // ===================== + print_usage(file = undefined) { + if (file === undefined) file = process.stdout + this._print_message(this.format_usage(), file) + } + + print_help(file = undefined) { + if (file === undefined) file = process.stdout + this._print_message(this.format_help(), file) + } + + _print_message(message, file = undefined) { + if (message) { + if (file === undefined) file = process.stderr + file.write(message) + } + } + + // =============== + // Exiting methods + // =============== + exit(status = 0, message = undefined) { + if (message) { + this._print_message(message, process.stderr) + } + process.exit(status) + } + + error(message) { + /* + * error(message: string) + * + * Prints a usage message incorporating the message to stderr and + * exits. + * + * If you override this in a subclass, it should not return -- it + * should either exit or raise an exception. + */ + + // LEGACY (v1 compatibility), debug mode + if (this.debug === true) throw new Error(message) + // end + this.print_usage(process.stderr) + let args = {prog: this.prog, message: message} + this.exit(2, sub('%(prog)s: error: %(message)s\n', args)) + } +})) + + +module.exports = { + ArgumentParser, + ArgumentError, + ArgumentTypeError, + BooleanOptionalAction, + FileType, + HelpFormatter, + ArgumentDefaultsHelpFormatter, + RawDescriptionHelpFormatter, + RawTextHelpFormatter, + MetavarTypeHelpFormatter, + Namespace, + Action, + ONE_OR_MORE, + OPTIONAL, + PARSER, + REMAINDER, + SUPPRESS, + ZERO_OR_MORE +} + +// LEGACY (v1 compatibility), Const alias +Object.defineProperty(module.exports, 'Const', { + get() { + let result = {} + Object.entries({ ONE_OR_MORE, OPTIONAL, PARSER, REMAINDER, SUPPRESS, ZERO_OR_MORE }).forEach(([ n, v ]) => { + Object.defineProperty(result, n, { + get() { + deprecate(n, sub('use argparse.%s instead of argparse.Const.%s', n, n)) + return v + } + }) + }) + Object.entries({ _UNRECOGNIZED_ARGS_ATTR }).forEach(([ n, v ]) => { + Object.defineProperty(result, n, { + get() { + deprecate(n, sub('argparse.Const.%s is an internal symbol and will no longer be available', n)) + return v + } + }) + }) + return result + }, + enumerable: false +}) +// end diff --git a/node_modules/lv_font_conv/node_modules/argparse/lib/sub.js b/node_modules/lv_font_conv/node_modules/argparse/lib/sub.js new file mode 100644 index 00000000..e3eb3215 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/argparse/lib/sub.js @@ -0,0 +1,67 @@ +// Limited implementation of python % string operator, supports only %s and %r for now +// (other formats are not used here, but may appear in custom templates) + +'use strict' + +const { inspect } = require('util') + + +module.exports = function sub(pattern, ...values) { + let regex = /%(?:(%)|(-)?(\*)?(?:\((\w+)\))?([A-Za-z]))/g + + let result = pattern.replace(regex, function (_, is_literal, is_left_align, is_padded, name, format) { + if (is_literal) return '%' + + let padded_count = 0 + if (is_padded) { + if (values.length === 0) throw new TypeError('not enough arguments for format string') + padded_count = values.shift() + if (!Number.isInteger(padded_count)) throw new TypeError('* wants int') + } + + let str + if (name !== undefined) { + let dict = values[0] + if (typeof dict !== 'object' || dict === null) throw new TypeError('format requires a mapping') + if (!(name in dict)) throw new TypeError(`no such key: '${name}'`) + str = dict[name] + } else { + if (values.length === 0) throw new TypeError('not enough arguments for format string') + str = values.shift() + } + + switch (format) { + case 's': + str = String(str) + break + case 'r': + str = inspect(str) + break + case 'd': + case 'i': + if (typeof str !== 'number') { + throw new TypeError(`%${format} format: a number is required, not ${typeof str}`) + } + str = String(str.toFixed(0)) + break + default: + throw new TypeError(`unsupported format character '${format}'`) + } + + if (padded_count > 0) { + return is_left_align ? str.padEnd(padded_count) : str.padStart(padded_count) + } else { + return str + } + }) + + if (values.length) { + if (values.length === 1 && typeof values[0] === 'object' && values[0] !== null) { + // mapping + } else { + throw new TypeError('not all arguments converted during string formatting') + } + } + + return result +} diff --git a/node_modules/lv_font_conv/node_modules/argparse/lib/textwrap.js b/node_modules/lv_font_conv/node_modules/argparse/lib/textwrap.js new file mode 100644 index 00000000..23d51cdb --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/argparse/lib/textwrap.js @@ -0,0 +1,440 @@ +// Partial port of python's argparse module, version 3.9.0 (only wrap and fill functions): +// https://github.com/python/cpython/blob/v3.9.0b4/Lib/textwrap.py + +'use strict' + +/* + * Text wrapping and filling. + */ + +// Copyright (C) 1999-2001 Gregory P. Ward. +// Copyright (C) 2002, 2003 Python Software Foundation. +// Copyright (C) 2020 argparse.js authors +// Originally written by Greg Ward + +// Hardcode the recognized whitespace characters to the US-ASCII +// whitespace characters. The main reason for doing this is that +// some Unicode spaces (like \u00a0) are non-breaking whitespaces. +// +// This less funky little regex just split on recognized spaces. E.g. +// "Hello there -- you goof-ball, use the -b option!" +// splits into +// Hello/ /there/ /--/ /you/ /goof-ball,/ /use/ /the/ /-b/ /option!/ +const wordsep_simple_re = /([\t\n\x0b\x0c\r ]+)/ + +class TextWrapper { + /* + * Object for wrapping/filling text. The public interface consists of + * the wrap() and fill() methods; the other methods are just there for + * subclasses to override in order to tweak the default behaviour. + * If you want to completely replace the main wrapping algorithm, + * you'll probably have to override _wrap_chunks(). + * + * Several instance attributes control various aspects of wrapping: + * width (default: 70) + * the maximum width of wrapped lines (unless break_long_words + * is false) + * initial_indent (default: "") + * string that will be prepended to the first line of wrapped + * output. Counts towards the line's width. + * subsequent_indent (default: "") + * string that will be prepended to all lines save the first + * of wrapped output; also counts towards each line's width. + * expand_tabs (default: true) + * Expand tabs in input text to spaces before further processing. + * Each tab will become 0 .. 'tabsize' spaces, depending on its position + * in its line. If false, each tab is treated as a single character. + * tabsize (default: 8) + * Expand tabs in input text to 0 .. 'tabsize' spaces, unless + * 'expand_tabs' is false. + * replace_whitespace (default: true) + * Replace all whitespace characters in the input text by spaces + * after tab expansion. Note that if expand_tabs is false and + * replace_whitespace is true, every tab will be converted to a + * single space! + * fix_sentence_endings (default: false) + * Ensure that sentence-ending punctuation is always followed + * by two spaces. Off by default because the algorithm is + * (unavoidably) imperfect. + * break_long_words (default: true) + * Break words longer than 'width'. If false, those words will not + * be broken, and some lines might be longer than 'width'. + * break_on_hyphens (default: true) + * Allow breaking hyphenated words. If true, wrapping will occur + * preferably on whitespaces and right after hyphens part of + * compound words. + * drop_whitespace (default: true) + * Drop leading and trailing whitespace from lines. + * max_lines (default: None) + * Truncate wrapped lines. + * placeholder (default: ' [...]') + * Append to the last line of truncated text. + */ + + constructor(options = {}) { + let { + width = 70, + initial_indent = '', + subsequent_indent = '', + expand_tabs = true, + replace_whitespace = true, + fix_sentence_endings = false, + break_long_words = true, + drop_whitespace = true, + break_on_hyphens = true, + tabsize = 8, + max_lines = undefined, + placeholder=' [...]' + } = options + + this.width = width + this.initial_indent = initial_indent + this.subsequent_indent = subsequent_indent + this.expand_tabs = expand_tabs + this.replace_whitespace = replace_whitespace + this.fix_sentence_endings = fix_sentence_endings + this.break_long_words = break_long_words + this.drop_whitespace = drop_whitespace + this.break_on_hyphens = break_on_hyphens + this.tabsize = tabsize + this.max_lines = max_lines + this.placeholder = placeholder + } + + + // -- Private methods ----------------------------------------------- + // (possibly useful for subclasses to override) + + _munge_whitespace(text) { + /* + * _munge_whitespace(text : string) -> string + * + * Munge whitespace in text: expand tabs and convert all other + * whitespace characters to spaces. Eg. " foo\\tbar\\n\\nbaz" + * becomes " foo bar baz". + */ + if (this.expand_tabs) { + text = text.replace(/\t/g, ' '.repeat(this.tabsize)) // not strictly correct in js + } + if (this.replace_whitespace) { + text = text.replace(/[\t\n\x0b\x0c\r]/g, ' ') + } + return text + } + + _split(text) { + /* + * _split(text : string) -> [string] + * + * Split the text to wrap into indivisible chunks. Chunks are + * not quite the same as words; see _wrap_chunks() for full + * details. As an example, the text + * Look, goof-ball -- use the -b option! + * breaks into the following chunks: + * 'Look,', ' ', 'goof-', 'ball', ' ', '--', ' ', + * 'use', ' ', 'the', ' ', '-b', ' ', 'option!' + * if break_on_hyphens is True, or in: + * 'Look,', ' ', 'goof-ball', ' ', '--', ' ', + * 'use', ' ', 'the', ' ', '-b', ' ', option!' + * otherwise. + */ + let chunks = text.split(wordsep_simple_re) + chunks = chunks.filter(Boolean) + return chunks + } + + _handle_long_word(reversed_chunks, cur_line, cur_len, width) { + /* + * _handle_long_word(chunks : [string], + * cur_line : [string], + * cur_len : int, width : int) + * + * Handle a chunk of text (most likely a word, not whitespace) that + * is too long to fit in any line. + */ + // Figure out when indent is larger than the specified width, and make + // sure at least one character is stripped off on every pass + let space_left + if (width < 1) { + space_left = 1 + } else { + space_left = width - cur_len + } + + // If we're allowed to break long words, then do so: put as much + // of the next chunk onto the current line as will fit. + if (this.break_long_words) { + cur_line.push(reversed_chunks[reversed_chunks.length - 1].slice(0, space_left)) + reversed_chunks[reversed_chunks.length - 1] = reversed_chunks[reversed_chunks.length - 1].slice(space_left) + + // Otherwise, we have to preserve the long word intact. Only add + // it to the current line if there's nothing already there -- + // that minimizes how much we violate the width constraint. + } else if (!cur_line) { + cur_line.push(...reversed_chunks.pop()) + } + + // If we're not allowed to break long words, and there's already + // text on the current line, do nothing. Next time through the + // main loop of _wrap_chunks(), we'll wind up here again, but + // cur_len will be zero, so the next line will be entirely + // devoted to the long word that we can't handle right now. + } + + _wrap_chunks(chunks) { + /* + * _wrap_chunks(chunks : [string]) -> [string] + * + * Wrap a sequence of text chunks and return a list of lines of + * length 'self.width' or less. (If 'break_long_words' is false, + * some lines may be longer than this.) Chunks correspond roughly + * to words and the whitespace between them: each chunk is + * indivisible (modulo 'break_long_words'), but a line break can + * come between any two chunks. Chunks should not have internal + * whitespace; ie. a chunk is either all whitespace or a "word". + * Whitespace chunks will be removed from the beginning and end of + * lines, but apart from that whitespace is preserved. + */ + let lines = [] + let indent + if (this.width <= 0) { + throw Error(`invalid width ${this.width} (must be > 0)`) + } + if (this.max_lines !== undefined) { + if (this.max_lines > 1) { + indent = this.subsequent_indent + } else { + indent = this.initial_indent + } + if (indent.length + this.placeholder.trimStart().length > this.width) { + throw Error('placeholder too large for max width') + } + } + + // Arrange in reverse order so items can be efficiently popped + // from a stack of chucks. + chunks = chunks.reverse() + + while (chunks.length > 0) { + + // Start the list of chunks that will make up the current line. + // cur_len is just the length of all the chunks in cur_line. + let cur_line = [] + let cur_len = 0 + + // Figure out which static string will prefix this line. + let indent + if (lines) { + indent = this.subsequent_indent + } else { + indent = this.initial_indent + } + + // Maximum width for this line. + let width = this.width - indent.length + + // First chunk on line is whitespace -- drop it, unless this + // is the very beginning of the text (ie. no lines started yet). + if (this.drop_whitespace && chunks[chunks.length - 1].trim() === '' && lines.length > 0) { + chunks.pop() + } + + while (chunks.length > 0) { + let l = chunks[chunks.length - 1].length + + // Can at least squeeze this chunk onto the current line. + if (cur_len + l <= width) { + cur_line.push(chunks.pop()) + cur_len += l + + // Nope, this line is full. + } else { + break + } + } + + // The current line is full, and the next chunk is too big to + // fit on *any* line (not just this one). + if (chunks.length && chunks[chunks.length - 1].length > width) { + this._handle_long_word(chunks, cur_line, cur_len, width) + cur_len = cur_line.map(l => l.length).reduce((a, b) => a + b, 0) + } + + // If the last chunk on this line is all whitespace, drop it. + if (this.drop_whitespace && cur_line.length > 0 && cur_line[cur_line.length - 1].trim() === '') { + cur_len -= cur_line[cur_line.length - 1].length + cur_line.pop() + } + + if (cur_line) { + if (this.max_lines === undefined || + lines.length + 1 < this.max_lines || + (chunks.length === 0 || + this.drop_whitespace && + chunks.length === 1 && + !chunks[0].trim()) && cur_len <= width) { + // Convert current line back to a string and store it in + // list of all lines (return value). + lines.push(indent + cur_line.join('')) + } else { + let had_break = false + while (cur_line) { + if (cur_line[cur_line.length - 1].trim() && + cur_len + this.placeholder.length <= width) { + cur_line.push(this.placeholder) + lines.push(indent + cur_line.join('')) + had_break = true + break + } + cur_len -= cur_line[-1].length + cur_line.pop() + } + if (!had_break) { + if (lines) { + let prev_line = lines[lines.length - 1].trimEnd() + if (prev_line.length + this.placeholder.length <= + this.width) { + lines[lines.length - 1] = prev_line + this.placeholder + break + } + } + lines.push(indent + this.placeholder.lstrip()) + } + break + } + } + } + + return lines + } + + _split_chunks(text) { + text = this._munge_whitespace(text) + return this._split(text) + } + + // -- Public interface ---------------------------------------------- + + wrap(text) { + /* + * wrap(text : string) -> [string] + * + * Reformat the single paragraph in 'text' so it fits in lines of + * no more than 'self.width' columns, and return a list of wrapped + * lines. Tabs in 'text' are expanded with string.expandtabs(), + * and all other whitespace characters (including newline) are + * converted to space. + */ + let chunks = this._split_chunks(text) + // not implemented in js + //if (this.fix_sentence_endings) { + // this._fix_sentence_endings(chunks) + //} + return this._wrap_chunks(chunks) + } + + fill(text) { + /* + * fill(text : string) -> string + * + * Reformat the single paragraph in 'text' to fit in lines of no + * more than 'self.width' columns, and return a new string + * containing the entire wrapped paragraph. + */ + return this.wrap(text).join('\n') + } +} + + +// -- Convenience interface --------------------------------------------- + +function wrap(text, options = {}) { + /* + * Wrap a single paragraph of text, returning a list of wrapped lines. + * + * Reformat the single paragraph in 'text' so it fits in lines of no + * more than 'width' columns, and return a list of wrapped lines. By + * default, tabs in 'text' are expanded with string.expandtabs(), and + * all other whitespace characters (including newline) are converted to + * space. See TextWrapper class for available keyword args to customize + * wrapping behaviour. + */ + let { width = 70, ...kwargs } = options + let w = new TextWrapper(Object.assign({ width }, kwargs)) + return w.wrap(text) +} + +function fill(text, options = {}) { + /* + * Fill a single paragraph of text, returning a new string. + * + * Reformat the single paragraph in 'text' to fit in lines of no more + * than 'width' columns, and return a new string containing the entire + * wrapped paragraph. As with wrap(), tabs are expanded and other + * whitespace characters converted to space. See TextWrapper class for + * available keyword args to customize wrapping behaviour. + */ + let { width = 70, ...kwargs } = options + let w = new TextWrapper(Object.assign({ width }, kwargs)) + return w.fill(text) +} + +// -- Loosely related functionality ------------------------------------- + +let _whitespace_only_re = /^[ \t]+$/mg +let _leading_whitespace_re = /(^[ \t]*)(?:[^ \t\n])/mg + +function dedent(text) { + /* + * Remove any common leading whitespace from every line in `text`. + * + * This can be used to make triple-quoted strings line up with the left + * edge of the display, while still presenting them in the source code + * in indented form. + * + * Note that tabs and spaces are both treated as whitespace, but they + * are not equal: the lines " hello" and "\\thello" are + * considered to have no common leading whitespace. + * + * Entirely blank lines are normalized to a newline character. + */ + // Look for the longest leading string of spaces and tabs common to + // all lines. + let margin = undefined + text = text.replace(_whitespace_only_re, '') + let indents = text.match(_leading_whitespace_re) || [] + for (let indent of indents) { + indent = indent.slice(0, -1) + + if (margin === undefined) { + margin = indent + + // Current line more deeply indented than previous winner: + // no change (previous winner is still on top). + } else if (indent.startsWith(margin)) { + // pass + + // Current line consistent with and no deeper than previous winner: + // it's the new winner. + } else if (margin.startsWith(indent)) { + margin = indent + + // Find the largest common whitespace between current line and previous + // winner. + } else { + for (let i = 0; i < margin.length && i < indent.length; i++) { + if (margin[i] !== indent[i]) { + margin = margin.slice(0, i) + break + } + } + } + } + + if (margin) { + text = text.replace(new RegExp('^' + margin, 'mg'), '') + } + return text +} + +module.exports = { wrap, fill, dedent } diff --git a/node_modules/lv_font_conv/node_modules/argparse/package.json b/node_modules/lv_font_conv/node_modules/argparse/package.json new file mode 100644 index 00000000..22fb0a3e --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/argparse/package.json @@ -0,0 +1,67 @@ +{ + "_args": [ + [ + "argparse@2.0.1", + "/home/vitaly/Dropbox/Coding/lv_font_conv" + ] + ], + "_from": "argparse@2.0.1", + "_id": "argparse@2.0.1", + "_inBundle": false, + "_integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "_location": "/argparse", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "argparse@2.0.1", + "name": "argparse", + "escapedName": "argparse", + "rawSpec": "2.0.1", + "saveSpec": null, + "fetchSpec": "2.0.1" + }, + "_requiredBy": [ + "/", + "/mocha/js-yaml" + ], + "_resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "_spec": "2.0.1", + "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", + "bugs": { + "url": "https://github.com/nodeca/argparse/issues" + }, + "description": "CLI arguments parser. Native port of python's argparse.", + "devDependencies": { + "@babel/eslint-parser": "^7.11.0", + "@babel/plugin-syntax-class-properties": "^7.10.4", + "eslint": "^7.5.0", + "mocha": "^8.0.1", + "nyc": "^15.1.0" + }, + "files": [ + "argparse.js", + "lib/" + ], + "homepage": "https://github.com/nodeca/argparse#readme", + "keywords": [ + "cli", + "parser", + "argparse", + "option", + "args" + ], + "license": "Python-2.0", + "main": "argparse.js", + "name": "argparse", + "repository": { + "type": "git", + "url": "git+https://github.com/nodeca/argparse.git" + }, + "scripts": { + "coverage": "npm run test && nyc report --reporter html", + "lint": "eslint .", + "test": "npm run lint && nyc mocha" + }, + "version": "2.0.1" +} diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/.github/workflows/ci.yml b/node_modules/lv_font_conv/node_modules/bit-buffer/.github/workflows/ci.yml new file mode 100644 index 00000000..8f0f168b --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/bit-buffer/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: Node.js CI + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [10.x, 12.x, 14.x, 15.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci + - run: npm test + + lint: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [10.x, 12.x, 14.x, 15.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci + - run: npm run lint diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/LICENSE b/node_modules/lv_font_conv/node_modules/bit-buffer/LICENSE new file mode 100644 index 00000000..9194ecce --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/bit-buffer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 bit-buffer developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/README.md b/node_modules/lv_font_conv/node_modules/bit-buffer/README.md new file mode 100644 index 00000000..3ecf4ab7 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/bit-buffer/README.md @@ -0,0 +1,148 @@ +# BitBuffer + +![Node.js CI](https://github.com/inolen/bit-buffer/workflows/Node.js%20CI/badge.svg) + +BitBuffer provides two objects, `BitView` and `BitStream`. `BitView` is a wrapper for ArrayBuffers, similar to JavaScript's [DataView](https://developer.mozilla.org/en-US/docs/JavaScript/Typed_arrays/DataView), but with support for bit-level reads and writes. `BitStream` is a wrapper for a `BitView` used to help maintain your current buffer position, as well as to provide higher-level read / write operations such as for ASCII strings. + +## BitView + +### Attributes + +```javascript +bb.buffer // Underlying ArrayBuffer. +``` + +```javascript +bb.bigEndian = true; // Switch to big endian (default is little) +``` + +### Methods + +#### BitView(buffer, optional byteOffset, optional byteLength) + +Default constructor, takes in a single argument of an ArrayBuffer. Optional are the `byteOffset` and `byteLength` arguments to offset and truncate the view's representation of the buffer. + +### getBits(offset, bits, signed) + +Reads `bits` number of bits starting at `offset`, twiddling the bits appropriately to return a proper 32-bit signed or unsigned value. NOTE: While JavaScript numbers are 64-bit floating-point values, we don't bother with anything other than the first 32 bits. + +### getInt8, getUint8, getInt16, getUint16, getInt32, getUint32(offset) + +Shortcuts for getBits, setting the correct `bits` / `signed` values. + +### getFloat32(offset) + +Gets 32 bits from `offset`, and coerces and returns as a proper float32 value. + +### getFloat64(offset) + +Gets 64 bits from `offset`, and coerces and returns as a proper float64 value. + +### setBits(offset, value, bits) + +Sets `bits` number of bits at `offset`. + +### setInt8, setUint8, setInt16, setUint16, setInt32, setUint32(offset) + +Shortcuts for setBits, setting the correct `bits` count. + +### setFloat32(offset) + +Coerces a float32 to uint32 and sets at `offset`. + +### setFloat64(offset) + +Coerces a float64 to two uint32s and sets at `offset`. + + +## BitStream + +### Attributes + +```javascript +bb.byteIndex; // Get current index in bytes. +bb.byteIndex = 0; // Set current index in bytes. +``` + +```javascript +bb.view; // Underlying BitView +``` + +```javascript +bb.length; // Get the length of the stream in bits +``` + +```javascript +bb.bitsLeft; // The number of bits left in the stream +``` + +```javascript +bb.index; // Get the current index in bits +bb.index = 0// Set the current index in bits +``` + +```javascript +bb.bigEndian = true; // Switch to big endian (default is little) +``` + +### Methods + +#### BitStream(view) + +Default constructor, takes in a single argument of a `BitView`, `ArrayBuffer` or node `Buffer`. + +#### BitSteam(buffer, optional byteOffset, optional byteLength) + +Shortcut constructor that initializes a new `BitView(buffer, byteOffset, byteLength)` for the stream to use. + +#### readBits(bits, signed) + +Returns `bits` numbers of bits from the view at the current index, updating the index. + +#### writeBits(value, bits) + +Sets `bits` numbers of bits from `value` in the view at the current index, updating the index. + +#### readUint8(), readUint16(), readUint32(), readInt8(), readInt16(), readInt32() + +Read a 8, 16 or 32 bits (unsigned) integer at the current index, updating the index. + +#### writeUint8(value), writeUint16(value), writeUint32(value), writeInt8(value), writeInt16(value), writeInt32(value) + +Write 8, 16 or 32 bits from `value` as (unsigned) integer at the current index, updating the index. + +#### readFloat32(), readFloat64() + +Read a 32 or 64 bit floating point number at the current index, updating the index. + +#### writeFloat32(value), writeFloat64() + +Set 32 or 64 bits from `value` as floating point value at the current index, updating the index. + +#### readBoolean() + +Read a single bit from the view at the current index, updating the index. + +#### writeBoolean(value) + +Write a single bit to the view at the current index, updating the index. + +#### readASCIIString(optional bytes), readUTF8String(optional bytes) + +Reads bytes from the underlying view at the current index until either `bytes` count is reached or a 0x00 terminator is reached. + +#### writeASCIIString(string, optional bytes), writeUTF8String(string, optional bytes) + +Writes a string followed by a NULL character to the underlying view starting at the current index. If the string is longer than `bytes` it will be truncated, and if it is shorter 0x00 will be written in its place. + +#### readBitStream(length) + +Create a new `BitStream` from the underlying view starting the the current index and a length of `length` bits. Updating the index of the existing `BitStream` + +#### readArrayBuffer(byteLength) + +Read `byteLength` bytes of data from the underlying view as `ArrayBuffer`, updating the index. + +## license + +MIT diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.d.ts b/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.d.ts new file mode 100644 index 00000000..72c4b13e --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.d.ts @@ -0,0 +1,115 @@ +declare module 'bit-buffer' { + import {Buffer} from 'buffer'; + + export class BitView { + constructor(buffer: ArrayBuffer | Buffer, byteLength?: number); + + readonly buffer: Buffer; + readonly byteLength: number; + bigEndian: boolean; + + getBits(offset: number, bits: number, signed?: boolean): number; + + getInt8(offset: number): number; + + getInt16(offset: number): number; + + getInt32(offset: number): number; + + getUint8(offset: number): number; + + getUint16(offset: number): number; + + getUint32(offset: number): number; + + getFloat32(offset: number): number; + + getFloat64(offset: number): number; + + setBits(offset: number, value: number, bits: number); + + setInt8(offset: number); + + setInt16(offset: number); + + setInt32(offset: number); + + setUint8(offset: number); + + setUint16(offset: number); + + setUint32(offset: number); + + setFloat32(offset: number, value: number); + + setFloat64(offset: number, value: number); + } + + export class BitStream { + constructor(source: ArrayBuffer | Buffer | BitView, byteOffset?: number, byteLength?: number) + + readonly length: number; + readonly bitsLeft: number; + readonly buffer: Buffer; + readonly view: BitView; + byteIndex: number; + index: number; + bigEndian: boolean; + + readBits(bits: number, signed?: boolean): number; + + writeBits(value: number, bits: number); + + readBoolean(): boolean; + + readInt8(): number; + + readUint8(): number; + + readInt16(): number; + + readUint16(): number; + + readInt32(): number; + + readUint32(): number; + + readFloat32(): number; + + readFloat64(): number; + + writeBoolean(value: boolean); + + writeInt8(value: number); + + writeUint8(value: number); + + writeInt16(value: number); + + writeUint16(value: number); + + writeInt32(value: number); + + writeUint32(value: number); + + writeFloat32(value: number); + + writeFloat64(value: number); + + readASCIIString(length?: number): string; + + readUTF8String(length?: number): string; + + writeASCIIString(data: string, length?: number); + + writeUTF8String(data: string, length?: number); + + readBitStream(length: number): BitStream; + + readArrayBuffer(byteLength: number): Uint8Array; + + writeBitStream(stream: BitStream, length?: number); + + writeArrayBuffer(buffer: BitStream, length?: number); + } +} diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.js b/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.js new file mode 100644 index 00000000..60c7f795 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.js @@ -0,0 +1,502 @@ +(function (root) { + +/********************************************************** + * + * BitView + * + * BitView provides a similar interface to the standard + * DataView, but with support for bit-level reads / writes. + * + **********************************************************/ +var BitView = function (source, byteOffset, byteLength) { + var isBuffer = source instanceof ArrayBuffer || + (typeof Buffer !== 'undefined' && source instanceof Buffer); + + if (!isBuffer) { + throw new Error('Must specify a valid ArrayBuffer or Buffer.'); + } + + byteOffset = byteOffset || 0; + byteLength = byteLength || source.byteLength /* ArrayBuffer */ || source.length /* Buffer */; + + this._view = new Uint8Array(source.buffer || source, byteOffset, byteLength); + + this.bigEndian = false; +}; + +// Used to massage fp values so we can operate on them +// at the bit level. +BitView._scratch = new DataView(new ArrayBuffer(8)); + +Object.defineProperty(BitView.prototype, 'buffer', { + get: function () { return typeof Buffer !== 'undefined' ? Buffer.from(this._view.buffer) : this._view.buffer; }, + enumerable: true, + configurable: false +}); + +Object.defineProperty(BitView.prototype, 'byteLength', { + get: function () { return this._view.length; }, + enumerable: true, + configurable: false +}); + +BitView.prototype._setBit = function (offset, on) { + if (on) { + this._view[offset >> 3] |= 1 << (offset & 7); + } else { + this._view[offset >> 3] &= ~(1 << (offset & 7)); + } +}; + +BitView.prototype.getBits = function (offset, bits, signed) { + var available = (this._view.length * 8 - offset); + + if (bits > available) { + throw new Error('Cannot get ' + bits + ' bit(s) from offset ' + offset + ', ' + available + ' available'); + } + + var value = 0; + for (var i = 0; i < bits;) { + var remaining = bits - i; + var bitOffset = offset & 7; + var currentByte = this._view[offset >> 3]; + + // the max number of bits we can read from the current byte + var read = Math.min(remaining, 8 - bitOffset); + + var mask, readBits; + if (this.bigEndian) { + // create a mask with the correct bit width + mask = ~(0xFF << read); + // shift the bits we want to the start of the byte and mask of the rest + readBits = (currentByte >> (8 - read - bitOffset)) & mask; + + value <<= read; + value |= readBits; + } else { + // create a mask with the correct bit width + mask = ~(0xFF << read); + // shift the bits we want to the start of the byte and mask off the rest + readBits = (currentByte >> bitOffset) & mask; + + value |= readBits << i; + } + + offset += read; + i += read; + } + + if (signed) { + // If we're not working with a full 32 bits, check the + // imaginary MSB for this bit count and convert to a + // valid 32-bit signed value if set. + if (bits !== 32 && value & (1 << (bits - 1))) { + value |= -1 ^ ((1 << bits) - 1); + } + + return value; + } + + return value >>> 0; +}; + +BitView.prototype.setBits = function (offset, value, bits) { + var available = (this._view.length * 8 - offset); + + if (bits > available) { + throw new Error('Cannot set ' + bits + ' bit(s) from offset ' + offset + ', ' + available + ' available'); + } + + for (var i = 0; i < bits;) { + var remaining = bits - i; + var bitOffset = offset & 7; + var byteOffset = offset >> 3; + var wrote = Math.min(remaining, 8 - bitOffset); + + var mask, writeBits, destMask; + if (this.bigEndian) { + // create a mask with the correct bit width + mask = ~(~0 << wrote); + // shift the bits we want to the start of the byte and mask of the rest + writeBits = (value >> (bits - i - wrote)) & mask; + + var destShift = 8 - bitOffset - wrote; + // destination mask to zero all the bits we're changing first + destMask = ~(mask << destShift); + + this._view[byteOffset] = + (this._view[byteOffset] & destMask) + | (writeBits << destShift); + + } else { + // create a mask with the correct bit width + mask = ~(0xFF << wrote); + // shift the bits we want to the start of the byte and mask of the rest + writeBits = value & mask; + value >>= wrote; + + // destination mask to zero all the bits we're changing first + destMask = ~(mask << bitOffset); + + this._view[byteOffset] = + (this._view[byteOffset] & destMask) + | (writeBits << bitOffset); + } + + offset += wrote; + i += wrote; + } +}; + +BitView.prototype.getBoolean = function (offset) { + return this.getBits(offset, 1, false) !== 0; +}; +BitView.prototype.getInt8 = function (offset) { + return this.getBits(offset, 8, true); +}; +BitView.prototype.getUint8 = function (offset) { + return this.getBits(offset, 8, false); +}; +BitView.prototype.getInt16 = function (offset) { + return this.getBits(offset, 16, true); +}; +BitView.prototype.getUint16 = function (offset) { + return this.getBits(offset, 16, false); +}; +BitView.prototype.getInt32 = function (offset) { + return this.getBits(offset, 32, true); +}; +BitView.prototype.getUint32 = function (offset) { + return this.getBits(offset, 32, false); +}; +BitView.prototype.getFloat32 = function (offset) { + BitView._scratch.setUint32(0, this.getUint32(offset)); + return BitView._scratch.getFloat32(0); +}; +BitView.prototype.getFloat64 = function (offset) { + BitView._scratch.setUint32(0, this.getUint32(offset)); + // DataView offset is in bytes. + BitView._scratch.setUint32(4, this.getUint32(offset+32)); + return BitView._scratch.getFloat64(0); +}; + +BitView.prototype.setBoolean = function (offset, value) { + this.setBits(offset, value ? 1 : 0, 1); +}; +BitView.prototype.setInt8 = +BitView.prototype.setUint8 = function (offset, value) { + this.setBits(offset, value, 8); +}; +BitView.prototype.setInt16 = +BitView.prototype.setUint16 = function (offset, value) { + this.setBits(offset, value, 16); +}; +BitView.prototype.setInt32 = +BitView.prototype.setUint32 = function (offset, value) { + this.setBits(offset, value, 32); +}; +BitView.prototype.setFloat32 = function (offset, value) { + BitView._scratch.setFloat32(0, value); + this.setBits(offset, BitView._scratch.getUint32(0), 32); +}; +BitView.prototype.setFloat64 = function (offset, value) { + BitView._scratch.setFloat64(0, value); + this.setBits(offset, BitView._scratch.getUint32(0), 32); + this.setBits(offset+32, BitView._scratch.getUint32(4), 32); +}; +BitView.prototype.getArrayBuffer = function (offset, byteLength) { + var buffer = new Uint8Array(byteLength); + for (var i = 0; i < byteLength; i++) { + buffer[i] = this.getUint8(offset + (i * 8)); + } + return buffer; +}; + +/********************************************************** + * + * BitStream + * + * Small wrapper for a BitView to maintain your position, + * as well as to handle reading / writing of string data + * to the underlying buffer. + * + **********************************************************/ +var reader = function (name, size) { + return function () { + if (this._index + size > this._length) { + throw new Error('Trying to read past the end of the stream'); + } + var val = this._view[name](this._index); + this._index += size; + return val; + }; +}; + +var writer = function (name, size) { + return function (value) { + this._view[name](this._index, value); + this._index += size; + }; +}; + +function readASCIIString(stream, bytes) { + return readString(stream, bytes, false); +} + +function readUTF8String(stream, bytes) { + return readString(stream, bytes, true); +} + +function readString(stream, bytes, utf8) { + if (bytes === 0) { + return ''; + } + var i = 0; + var chars = []; + var append = true; + var fixedLength = !!bytes; + if (!bytes) { + bytes = Math.floor((stream._length - stream._index) / 8); + } + + // Read while we still have space available, or until we've + // hit the fixed byte length passed in. + while (i < bytes) { + var c = stream.readUint8(); + + // Stop appending chars once we hit 0x00 + if (c === 0x00) { + append = false; + + // If we don't have a fixed length to read, break out now. + if (!fixedLength) { + break; + } + } + if (append) { + chars.push(c); + } + + i++; + } + + var string = String.fromCharCode.apply(null, chars); + if (utf8) { + try { + return decodeURIComponent(escape(string)); // https://stackoverflow.com/a/17192845 + } catch (e) { + return string; + } + } else { + return string; + } +} + +function writeASCIIString(stream, string, bytes) { + var length = bytes || string.length + 1; // + 1 for NULL + + for (var i = 0; i < length; i++) { + stream.writeUint8(i < string.length ? string.charCodeAt(i) : 0x00); + } +} + +function writeUTF8String(stream, string, bytes) { + var byteArray = stringToByteArray(string); + + var length = bytes || byteArray.length + 1; // + 1 for NULL + for (var i = 0; i < length; i++) { + stream.writeUint8(i < byteArray.length ? byteArray[i] : 0x00); + } +} + +function stringToByteArray(str) { // https://gist.github.com/volodymyr-mykhailyk/2923227 + var b = [], i, unicode; + for (i = 0; i < str.length; i++) { + unicode = str.charCodeAt(i); + // 0x00000000 - 0x0000007f -> 0xxxxxxx + if (unicode <= 0x7f) { + b.push(unicode); + // 0x00000080 - 0x000007ff -> 110xxxxx 10xxxxxx + } else if (unicode <= 0x7ff) { + b.push((unicode >> 6) | 0xc0); + b.push((unicode & 0x3F) | 0x80); + // 0x00000800 - 0x0000ffff -> 1110xxxx 10xxxxxx 10xxxxxx + } else if (unicode <= 0xffff) { + b.push((unicode >> 12) | 0xe0); + b.push(((unicode >> 6) & 0x3f) | 0x80); + b.push((unicode & 0x3f) | 0x80); + // 0x00010000 - 0x001fffff -> 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + } else { + b.push((unicode >> 18) | 0xf0); + b.push(((unicode >> 12) & 0x3f) | 0x80); + b.push(((unicode >> 6) & 0x3f) | 0x80); + b.push((unicode & 0x3f) | 0x80); + } + } + + return b; +} + +var BitStream = function (source, byteOffset, byteLength) { + var isBuffer = source instanceof ArrayBuffer || + (typeof Buffer !== 'undefined' && source instanceof Buffer); + + if (!(source instanceof BitView) && !isBuffer) { + throw new Error('Must specify a valid BitView, ArrayBuffer or Buffer'); + } + + if (isBuffer) { + this._view = new BitView(source, byteOffset, byteLength); + } else { + this._view = source; + } + + this._index = 0; + this._startIndex = 0; + this._length = this._view.byteLength * 8; +}; + +Object.defineProperty(BitStream.prototype, 'index', { + get: function () { return this._index - this._startIndex; }, + set: function (val) { this._index = val + this._startIndex; }, + enumerable: true, + configurable: true +}); + +Object.defineProperty(BitStream.prototype, 'length', { + get: function () { return this._length - this._startIndex; }, + set: function (val) { this._length = val + this._startIndex; }, + enumerable : true, + configurable: true +}); + +Object.defineProperty(BitStream.prototype, 'bitsLeft', { + get: function () { return this._length - this._index; }, + enumerable : true, + configurable: true +}); + +Object.defineProperty(BitStream.prototype, 'byteIndex', { + // Ceil the returned value, over compensating for the amount of + // bits written to the stream. + get: function () { return Math.ceil(this._index / 8); }, + set: function (val) { this._index = val * 8; }, + enumerable: true, + configurable: true +}); + +Object.defineProperty(BitStream.prototype, 'buffer', { + get: function () { return this._view.buffer; }, + enumerable: true, + configurable: false +}); + +Object.defineProperty(BitStream.prototype, 'view', { + get: function () { return this._view; }, + enumerable: true, + configurable: false +}); + +Object.defineProperty(BitStream.prototype, 'bigEndian', { + get: function () { return this._view.bigEndian; }, + set: function (val) { this._view.bigEndian = val; }, + enumerable: true, + configurable: false +}); + +BitStream.prototype.readBits = function (bits, signed) { + var val = this._view.getBits(this._index, bits, signed); + this._index += bits; + return val; +}; + +BitStream.prototype.writeBits = function (value, bits) { + this._view.setBits(this._index, value, bits); + this._index += bits; +}; + +BitStream.prototype.readBoolean = reader('getBoolean', 1); +BitStream.prototype.readInt8 = reader('getInt8', 8); +BitStream.prototype.readUint8 = reader('getUint8', 8); +BitStream.prototype.readInt16 = reader('getInt16', 16); +BitStream.prototype.readUint16 = reader('getUint16', 16); +BitStream.prototype.readInt32 = reader('getInt32', 32); +BitStream.prototype.readUint32 = reader('getUint32', 32); +BitStream.prototype.readFloat32 = reader('getFloat32', 32); +BitStream.prototype.readFloat64 = reader('getFloat64', 64); + +BitStream.prototype.writeBoolean = writer('setBoolean', 1); +BitStream.prototype.writeInt8 = writer('setInt8', 8); +BitStream.prototype.writeUint8 = writer('setUint8', 8); +BitStream.prototype.writeInt16 = writer('setInt16', 16); +BitStream.prototype.writeUint16 = writer('setUint16', 16); +BitStream.prototype.writeInt32 = writer('setInt32', 32); +BitStream.prototype.writeUint32 = writer('setUint32', 32); +BitStream.prototype.writeFloat32 = writer('setFloat32', 32); +BitStream.prototype.writeFloat64 = writer('setFloat64', 64); + +BitStream.prototype.readASCIIString = function (bytes) { + return readASCIIString(this, bytes); +}; + +BitStream.prototype.readUTF8String = function (bytes) { + return readUTF8String(this, bytes); +}; + +BitStream.prototype.writeASCIIString = function (string, bytes) { + writeASCIIString(this, string, bytes); +}; + +BitStream.prototype.writeUTF8String = function (string, bytes) { + writeUTF8String(this, string, bytes); +}; +BitStream.prototype.readBitStream = function(bitLength) { + var slice = new BitStream(this._view); + slice._startIndex = this._index; + slice._index = this._index; + slice.length = bitLength; + this._index += bitLength; + return slice; +}; + +BitStream.prototype.writeBitStream = function(stream, length) { + if (!length) { + length = stream.bitsLeft; + } + + var bitsToWrite; + while (length > 0) { + bitsToWrite = Math.min(length, 32); + this.writeBits(stream.readBits(bitsToWrite), bitsToWrite); + length -= bitsToWrite; + } +}; + +BitStream.prototype.readArrayBuffer = function(byteLength) { + var buffer = this._view.getArrayBuffer(this._index, byteLength); + this._index += (byteLength * 8); + return buffer; +}; + +BitStream.prototype.writeArrayBuffer = function(buffer, byteLength) { + this.writeBitStream(new BitStream(buffer), byteLength * 8); +}; + +// AMD / RequireJS +if (typeof define !== 'undefined' && define.amd) { + define(function () { + return { + BitView: BitView, + BitStream: BitStream + }; + }); +} +// Node.js +else if (typeof module !== 'undefined' && module.exports) { + module.exports = { + BitView: BitView, + BitStream: BitStream + }; +} + +}(this)); diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/package.json b/node_modules/lv_font_conv/node_modules/bit-buffer/package.json new file mode 100644 index 00000000..f2e2d2ce --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/bit-buffer/package.json @@ -0,0 +1,71 @@ +{ + "_args": [ + [ + "bit-buffer@0.2.5", + "/home/vitaly/Dropbox/Coding/lv_font_conv" + ] + ], + "_from": "bit-buffer@0.2.5", + "_id": "bit-buffer@0.2.5", + "_inBundle": false, + "_integrity": "sha512-x1yGnmXvFg6e3DiyRztElbcn1bsCTFSoM/ncAzY62uE0JdTl5xlKJd0ooqLYoPbhdsnpehSIQrdIvclcZJYwiA==", + "_location": "/bit-buffer", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "bit-buffer@0.2.5", + "name": "bit-buffer", + "escapedName": "bit-buffer", + "rawSpec": "0.2.5", + "saveSpec": null, + "fetchSpec": "0.2.5" + }, + "_requiredBy": [ + "/" + ], + "_resolved": "https://registry.npmjs.org/bit-buffer/-/bit-buffer-0.2.5.tgz", + "_spec": "0.2.5", + "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", + "author": { + "name": "Anthony Pesch" + }, + "bugs": { + "url": "https://github.com/inolen/bit-buffer/issues" + }, + "contributors": [ + { + "name": "Robin Appelman" + } + ], + "description": "Bit-level reads and writes for ArrayBuffers", + "devDependencies": { + "@types/node": "^14.14.22", + "jshint": "^2.12.0", + "mocha": "^8.2.1" + }, + "directories": { + "test": "test" + }, + "gitHead": "cd4417237bed1f22dd5adfd8a6b961ea7234d9c9", + "homepage": "https://github.com/inolen/bit-buffer#readme", + "keywords": [ + "dataview", + "arraybuffer", + "bit", + "bits" + ], + "license": "MIT", + "main": "bit-buffer.js", + "name": "bit-buffer", + "repository": { + "type": "git", + "url": "git://github.com/inolen/bit-buffer.git" + }, + "scripts": { + "lint": "jshint bit-buffer.js", + "test": "mocha --ui tdd" + }, + "types": "./bit-buffer.d.ts", + "version": "0.2.5" +} diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/test.js b/node_modules/lv_font_conv/node_modules/bit-buffer/test.js new file mode 100644 index 00000000..a53fc87b --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/bit-buffer/test.js @@ -0,0 +1,628 @@ +var assert = require('assert'), + BitView = require('./bit-buffer').BitView, + BitStream = require('./bit-buffer').BitStream; + +suite('BitBuffer', function () { + var array, bv, bsw, bsr; + + setup(function () { + array = new ArrayBuffer(64); + bv = new BitView(array); + bsw = new BitStream(bv); + // Test initializing straight from the array. + bsr = new BitStream(array); + }); + + test('Min / max signed 5 bits', function () { + var signed_max = (1 << 4) - 1; + + bsw.writeBits(signed_max, 5); + bsw.writeBits(-signed_max - 1, 5); + assert(bsr.readBits(5, true) === signed_max); + assert(bsr.readBits(5, true) === -signed_max - 1); + }); + + test('Min / max unsigned 5 bits', function () { + var unsigned_max = (1 << 5) - 1; + + bsw.writeBits(unsigned_max, 5); + bsw.writeBits(-unsigned_max, 5); + assert.equal(bsr.readBits(5), unsigned_max); + assert.equal(bsr.readBits(5), 1); + }); + + test('Min / max int8', function () { + var signed_max = 0x7F; + + bsw.writeInt8(signed_max); + bsw.writeInt8(-signed_max - 1); + assert.equal(bsr.readInt8(), signed_max); + assert.equal(bsr.readInt8(), -signed_max - 1); + }); + + test('Min / max uint8', function () { + var unsigned_max = 0xFF; + + bsw.writeUint8(unsigned_max); + bsw.writeUint8(-unsigned_max); + assert.equal(bsr.readUint8(), unsigned_max); + assert.equal(bsr.readUint8(), 1); + }); + + test('Min / max int16', function () { + var signed_max = 0x7FFF; + + bsw.writeInt16(signed_max); + bsw.writeInt16(-signed_max - 1); + assert.equal(bsr.readInt16(), signed_max); + assert.equal(bsr.readInt16(), -signed_max - 1); + }); + + test('Min / max uint16', function () { + var unsigned_max = 0xFFFF; + + bsw.writeUint16(unsigned_max); + bsw.writeUint16(-unsigned_max); + assert.equal(bsr.readUint16(), unsigned_max); + assert.equal(bsr.readUint16(), 1); + }); + + test('Min / max int32', function () { + var signed_max = 0x7FFFFFFF; + + bsw.writeInt32(signed_max); + bsw.writeInt32(-signed_max - 1); + assert.equal(bsr.readInt32(), signed_max); + assert.equal(bsr.readInt32(), -signed_max - 1); + }); + + test('Min / max uint32', function () { + var unsigned_max = 0xFFFFFFFF; + + bsw.writeUint32(unsigned_max); + bsw.writeUint32(-unsigned_max); + assert.equal(bsr.readUint32(), unsigned_max); + assert.equal(bsr.readUint32(), 1); + }); + + test('Unaligned reads', function () { + bsw.writeBits(13, 5); + bsw.writeUint8(0xFF); + bsw.writeBits(14, 5); + + assert.equal(bsr.readBits(5), 13); + assert.equal(bsr.readUint8(), 0xFF); + assert.equal(bsr.readBits(5), 14); + }); + + test('Min / max float32 (normal values)', function () { + var scratch = new DataView(new ArrayBuffer(8)); + + scratch.setUint32(0, 0x00800000); + scratch.setUint32(4, 0x7f7fffff); + + var min = scratch.getFloat32(0); + var max = scratch.getFloat32(4); + + bsw.writeFloat32(min); + bsw.writeFloat32(max); + + assert.equal(bsr.readFloat32(), min); + assert.equal(bsr.readFloat32(), max); + }); + + test('Min / max float64 (normal values)', function () { + var scratch = new DataView(new ArrayBuffer(16)); + + scratch.setUint32(0, 0x00100000); + scratch.setUint32(4, 0x00000000); + scratch.setUint32(8, 0x7fefffff); + scratch.setUint32(12, 0xffffffff); + + var min = scratch.getFloat64(0); + var max = scratch.getFloat64(8); + + bsw.writeFloat64(min); + bsw.writeFloat64(max); + + assert.equal(bsr.readFloat64(), min); + assert.equal(bsr.readFloat64(), max); + }); + + test('Overwrite previous value with 0', function () { + bv.setUint8(0, 13); + bv.setUint8(0, 0); + + assert.equal(bv.getUint8(0), 0); + }); + + test('Read / write ASCII string, fixed length', function () { + var str = 'foobar'; + var len = 16; + + bsw.writeASCIIString(str, len); + assert.equal(bsw.byteIndex, len); + + assert.equal(bsr.readASCIIString(len), str); + assert.equal(bsr.byteIndex, len); + }); + + test('Read / write ASCII string, unknown length', function () { + var str = 'foobar'; + + bsw.writeASCIIString(str); + assert.equal(bsw.byteIndex, str.length + 1); // +1 for 0x00 + + assert.equal(bsr.readASCIIString(), str); + assert.equal(bsr.byteIndex, str.length + 1); + }); + + test('Read ASCII string, 0 length', function () { + var str = 'foobar'; + + bsw.writeASCIIString(str); + assert.equal(bsw.byteIndex, str.length + 1); // +1 for 0x00 + + assert.equal(bsr.readASCIIString(0), ''); + assert.equal(bsr.byteIndex, 0); + }); + + test('Read overflow', function () { + var exception = false; + + try { + bsr.readASCIIString(128); + } catch (e) { + exception = true; + } + + assert(exception); + }); + + test('Write overflow', function () { + var exception = false; + + try { + bsw.writeASCIIString('foobar', 128); + } catch (e) { + exception = true; + } + + assert(exception); + }); + + test('Get boolean', function () { + bv.setUint8(0, 1); + + assert(bv.getBoolean(0)); + + bv.setUint8(0, 0); + assert(!bv.getBoolean(0)); + }); + + test('Set boolean', function () { + bv.setBoolean(0, true); + + assert(bv.getBoolean(0)); + + bv.setBoolean(0, false); + + assert(!bv.getBoolean(0)); + }); + + test('Read boolean', function () { + bv.setBits(0, 1, 1); + bv.setBits(1, 0, 1); + + assert(bsr.readBoolean()); + assert(!bsr.readBoolean()); + }); + + test('Write boolean', function () { + bsr.writeBoolean(true); + assert.equal(bv.getBits(0, 1, false), 1); + bsr.writeBoolean(false); + assert.equal(bv.getBits(1, 1, false), 0); + }); + + test('Read / write UTF8 string, only ASCII characters', function () { + var str = 'foobar'; + + bsw.writeUTF8String(str); + assert(bsw.byteIndex === str.length + 1); // +1 for 0x00 + + assert.equal(bsr.readUTF8String(), str); + assert.equal(bsr.byteIndex, str.length + 1); + }); + + test('Read / write UTF8 string, non ASCII characters', function () { + var str = '日本語'; + + var bytes = [ + 0xE6, + 0x97, + 0xA5, + 0xE6, + 0x9C, + 0xAC, + 0xE8, + 0xAA, + 0x9E + ]; + + bsw.writeUTF8String(str); + + for (var i = 0; i < bytes.length; i++) { + assert.equal(bytes[i], bv.getBits(i * 8, 8)); + } + + assert.equal(bsw.byteIndex, bytes.length + 1); // +1 for 0x00 + + assert.equal(str, bsr.readUTF8String()); + assert.equal(bsr.byteIndex, bytes.length + 1); + }); + + test('readBitStream', function () { + bsw.writeBits(0xF0, 8); //0b11110000 + bsw.writeBits(0xF1, 8); //0b11110001 + bsr.readBits(3); //offset + var slice = bsr.readBitStream(8); + assert.equal(slice.readBits(6), 0x3E); //0b111110 + assert.equal(9, slice._index); + assert.equal(6, slice.index); + assert.equal(8, slice.length); + assert.equal(2, slice.bitsLeft); + + assert.equal(bsr._index, 11); + assert.equal((64 * 8) - 11, bsr.bitsLeft); + }); + + test('readBitStream overflow', function () { + bsw.writeBits(0xF0, 8); //0b11110000 + bsw.writeBits(0xF1, 8); //0b11110001 + bsr.readBits(3); //offset + var slice = bsr.readBitStream(4); + + var exception = false; + + try { + slice.readUint8(); + } catch (e) { + exception = true; + } + + assert(exception); + }); + + test('writeBitStream', function () { + var buf = new ArrayBuffer(64); + var sourceStream = new BitStream(buf); + + sourceStream.writeBits(0xF0, 8); //0b11110000 + sourceStream.writeBits(0xF1, 8); //0b11110001 + sourceStream.index = 0; + sourceStream.readBits(3); //offset + bsr.writeBitStream(sourceStream, 8); + assert.equal(8, bsr.index); + bsr.index = 0; + assert.equal(bsr.readBits(6), 0x3E); //0b00111110 + assert.equal(11, sourceStream.index); + + var bin = new Uint8Array(buf); + assert.equal(bin[0], 0xF0); + assert.equal(bin[1], 0xF1); + }); + + test('writeBitStream Buffer', function () { + var buf = Buffer.alloc(64); + var sourceStream = new BitStream(buf); + + sourceStream.writeBits(0xF0, 8); //0b11110000 + sourceStream.writeBits(0xF1, 8); //0b11110001 + sourceStream.index = 0; + sourceStream.readBits(3); //offset + bsr.writeBitStream(sourceStream, 8); + assert.equal(8, bsr.index); + bsr.index = 0; + assert.equal(bsr.readBits(6), 0x3E); //0b00111110 + assert.equal(11, sourceStream.index); + + var bin = new Uint8Array(buf.buffer); + assert.equal(bin[0], 0xF0); + assert.equal(bin[1], 0xF1); + }); + + test('writeBitStream long', function () { + var sourceStream = new BitStream(new ArrayBuffer(64)); + + sourceStream.writeBits(0xF0, 8); + sourceStream.writeBits(0xF1, 8); + sourceStream.writeBits(0xF1, 8); + sourceStream.writeBits(0xF1, 8); + sourceStream.writeBits(0xF1, 8); + sourceStream.index = 0; + sourceStream.readBits(3); //offset + bsr.index = 3; + bsr.writeBitStream(sourceStream, 35); + assert.equal(38, bsr.index); + bsr.index = 3; + assert.equal(bsr.readBits(35), 1044266558); + assert.equal(38, sourceStream.index); + }); + + test('readArrayBuffer', function () { + bsw.writeBits(0xF0, 8); //0b11110000 + bsw.writeBits(0xF1, 8); //0b11110001 + bsw.writeBits(0xF0, 8); //0b11110000 + bsr.readBits(3); //offset + + var buffer = bsr.readArrayBuffer(2); + + assert.equal(0x3E, buffer[0]); //0b00111110 + assert.equal(0x1E, buffer[1]); //0b00011110 + + assert.equal(3 + (2 * 8), bsr._index); + }); + + test('writeArrayBuffer', function () { + var source = new Uint8Array(4); + source[0] = 0xF0; + source[1] = 0xF1; + source[2] = 0xF1; + bsr.readBits(3); //offset + + bsr.writeArrayBuffer(source.buffer, 2); + assert.equal(19, bsr.index); + + bsr.index = 0; + + assert.equal(bsr.readBits(8), 128); + }); + + test('Get buffer from view', function () { + bv.setBits(0, 0xFFFFFFFF, 32); + var buffer = bv.buffer; + + assert.equal(64, buffer.length); + assert.equal(0xFFFF, buffer.readUInt16LE(0)); + }); + + test('Get buffer from stream', function () { + bsw.writeBits(0xFFFFFFFF, 32); + var buffer = bsr.buffer; + + assert.equal(64, buffer.length); + assert.equal(0xFFFF, buffer.readUInt16LE(0)); + }); +}); + +suite('Reading big/little endian', function () { + var array, u8, bv, bsw, bsr; + + setup(function () { + array = new ArrayBuffer(64); + u8 = new Uint8Array(array); + u8[0] = 0x01; + u8[1] = 0x02; + // Test initializing straight from the array. + bsr = new BitStream(array); + }); + + test('4b, little-endian', function () { + assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); + + var result = []; + result.push(bsr.readBits(4)); + result.push(bsr.readBits(4)); + result.push(bsr.readBits(4)); + result.push(bsr.readBits(4)); + + // 0000 0001 0000 0010 [01 02] + // [#2] [#1] [#4] [#3] + assert.deepEqual(result, [1, 0, 2, 0]); + }); + + test('8b, little-endian', function () { + assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); + + var result = []; + result.push(bsr.readBits(8)); + result.push(bsr.readBits(8)); + + // 0000 0001 0000 0010 [01 02] + // [ #1] [ #2] + assert.deepEqual(result, [1, 2]); + }); + + test('10b, little-endian', function () { + assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); + + var result = []; + result.push(bsr.readBits(10)); + + // 0000 0001 0000 0010 [01 02] + // ... #1] [ #2][#1... + assert.deepEqual(result, [513]); + }); + + test('16b, little-endian', function () { + assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); + + var result = []; + result.push(bsr.readBits(16)); + + // 0000 0001 0000 0010 [01 02] + // [ #1] + assert.deepEqual(result, [0x201]); + }); + + test('24b, little-endian', function () { + u8[2] = 0x03; + assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); + + var result = []; + result.push(bsr.readBits(24)); + + // 0000 0001 0000 0010 0000 0011 [01 02 03] + // [ #1] + assert.deepEqual(result, [0x30201]); + }); + + test('4b, big-endian', function () { + bsr.bigEndian = true; + assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); + + var result = []; + result.push(bsr.readBits(4)); + result.push(bsr.readBits(4)); + result.push(bsr.readBits(4)); + result.push(bsr.readBits(4)); + + // 0000 0001 0000 0010 [01 02] + // [#1] [#2] [#3] [#4] + assert.deepEqual(result, [0, 1, 0, 2]); + }); + + test('8b, big-endian', function () { + bsr.bigEndian = true; + assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); + + var result = []; + result.push(bsr.readBits(8)); + result.push(bsr.readBits(8)); + + // 0000 0001 0000 0010 [01 02] + // [ #1] [ #2] + assert.deepEqual(result, [1, 2]); + }); + + test('10b, big-endian', function () { + bsr.bigEndian = true; + assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); + + var result = []; + result.push(bsr.readBits(10)); + result.push(bsr.readBits(6)); + + // 0000 0001 0000 0010 [01 02] + // [ #1][ #2] + assert.deepEqual(result, [4, 2]); + }); + + test('16b, big-endian', function () { + bsr.bigEndian = true; + assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); + + var result = []; + result.push(bsr.readBits(16)); + + // 0000 0001 0000 0010 [01 02] + // [ #1] + assert.deepEqual(result, [0x102]); + }); + + test('24b, big-endian', function () { + u8[2] = 0x03; + bsr.bigEndian = true; + assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); + + var result = []; + result.push(bsr.readBits(24)); + + // 0000 0001 0000 0010 0000 0011 [01 02 03] + // [ #1] + assert.deepEqual(result, [0x10203]); + }); +}); + +suite('Writing big/little endian', function () { + var array, u8, bv, bsw, bsr; + + setup(function () { + array = new ArrayBuffer(2); + u8 = new Uint8Array(array); + bv = new BitView(array); + bsw = new BitStream(bv); + }); + + test('4b, little-endian', function () { + // 0000 0001 0000 0010 [01 02] + // [#2] [#1] [#4] [#3] + bsw.writeBits(1, 4); + bsw.writeBits(0, 4); + bsw.writeBits(2, 4); + bsw.writeBits(0, 4); + + assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); + }); + + test('8b, little-endian', function () { + // 0000 0001 0000 0010 [01 02] + // [ #1] [ #2] + bsw.writeBits(1, 8); + bsw.writeBits(2, 8); + + assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); + }); + + test('10b, little-endian', function () { + // 0000 0001 0000 0010 [01 02] + // ... #1] [ #2][#1... + bsw.writeBits(513, 10); + + assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); + }); + + test('16b, little-endian', function () { + // 0000 0001 0000 0010 [01 02] + // [ #1] + bsw.writeBits(0x201, 16); + + assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); + }); + + test('4b, big-endian', function () { + bsw.bigEndian = true; + + // 0000 0001 0000 0010 [01 02] + // [#1] [#2] [#3] [#4] + bsw.writeBits(0, 4); + bsw.writeBits(1, 4); + bsw.writeBits(0, 4); + bsw.writeBits(2, 4); + + assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); + }); + + test('8b, big-endian', function () { + bsw.bigEndian = true; + + // 0000 0001 0000 0010 [01 02] + // [ #1] [ #2] + bsw.writeBits(1, 8); + bsw.writeBits(2, 8); + + assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); + }); + + test('10b, big-endian', function () { + bsw.bigEndian = true; + + // 0000 0001 0000 0010 [01 02] + // [ #1][ #2] + bsw.writeBits(4, 10); + bsw.writeBits(2, 6); + + assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); + }); + + test('16b, big-endian', function () { + bsw.bigEndian = true; + + // 0000 0001 0000 0010 [01 02] + // [ #1] + bsw.writeBits(0x102, 16); + + assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); + }); +}); diff --git a/node_modules/lv_font_conv/node_modules/debug/LICENSE b/node_modules/lv_font_conv/node_modules/debug/LICENSE new file mode 100644 index 00000000..658c933d --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/debug/LICENSE @@ -0,0 +1,19 @@ +(The MIT License) + +Copyright (c) 2014 TJ Holowaychuk + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the 'Software'), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/node_modules/lv_font_conv/node_modules/debug/README.md b/node_modules/lv_font_conv/node_modules/debug/README.md new file mode 100644 index 00000000..88dae35d --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/debug/README.md @@ -0,0 +1,455 @@ +# debug +[![Build Status](https://travis-ci.org/visionmedia/debug.svg?branch=master)](https://travis-ci.org/visionmedia/debug) [![Coverage Status](https://coveralls.io/repos/github/visionmedia/debug/badge.svg?branch=master)](https://coveralls.io/github/visionmedia/debug?branch=master) [![Slack](https://visionmedia-community-slackin.now.sh/badge.svg)](https://visionmedia-community-slackin.now.sh/) [![OpenCollective](https://opencollective.com/debug/backers/badge.svg)](#backers) +[![OpenCollective](https://opencollective.com/debug/sponsors/badge.svg)](#sponsors) + + + +A tiny JavaScript debugging utility modelled after Node.js core's debugging +technique. Works in Node.js and web browsers. + +## Installation + +```bash +$ npm install debug +``` + +## Usage + +`debug` exposes a function; simply pass this function the name of your module, and it will return a decorated version of `console.error` for you to pass debug statements to. This will allow you to toggle the debug output for different parts of your module as well as the module as a whole. + +Example [_app.js_](./examples/node/app.js): + +```js +var debug = require('debug')('http') + , http = require('http') + , name = 'My App'; + +// fake app + +debug('booting %o', name); + +http.createServer(function(req, res){ + debug(req.method + ' ' + req.url); + res.end('hello\n'); +}).listen(3000, function(){ + debug('listening'); +}); + +// fake worker of some kind + +require('./worker'); +``` + +Example [_worker.js_](./examples/node/worker.js): + +```js +var a = require('debug')('worker:a') + , b = require('debug')('worker:b'); + +function work() { + a('doing lots of uninteresting work'); + setTimeout(work, Math.random() * 1000); +} + +work(); + +function workb() { + b('doing some work'); + setTimeout(workb, Math.random() * 2000); +} + +workb(); +``` + +The `DEBUG` environment variable is then used to enable these based on space or +comma-delimited names. + +Here are some examples: + +screen shot 2017-08-08 at 12 53 04 pm +screen shot 2017-08-08 at 12 53 38 pm +screen shot 2017-08-08 at 12 53 25 pm + +#### Windows command prompt notes + +##### CMD + +On Windows the environment variable is set using the `set` command. + +```cmd +set DEBUG=*,-not_this +``` + +Example: + +```cmd +set DEBUG=* & node app.js +``` + +##### PowerShell (VS Code default) + +PowerShell uses different syntax to set environment variables. + +```cmd +$env:DEBUG = "*,-not_this" +``` + +Example: + +```cmd +$env:DEBUG='app';node app.js +``` + +Then, run the program to be debugged as usual. + +npm script example: +```js + "windowsDebug": "@powershell -Command $env:DEBUG='*';node app.js", +``` + +## Namespace Colors + +Every debug instance has a color generated for it based on its namespace name. +This helps when visually parsing the debug output to identify which debug instance +a debug line belongs to. + +#### Node.js + +In Node.js, colors are enabled when stderr is a TTY. You also _should_ install +the [`supports-color`](https://npmjs.org/supports-color) module alongside debug, +otherwise debug will only use a small handful of basic colors. + + + +#### Web Browser + +Colors are also enabled on "Web Inspectors" that understand the `%c` formatting +option. These are WebKit web inspectors, Firefox ([since version +31](https://hacks.mozilla.org/2014/05/editable-box-model-multiple-selection-sublime-text-keys-much-more-firefox-developer-tools-episode-31/)) +and the Firebug plugin for Firefox (any version). + + + + +## Millisecond diff + +When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the "+NNNms" will show you how much time was spent between calls. + + + +When stdout is not a TTY, `Date#toISOString()` is used, making it more useful for logging the debug information as shown below: + + + + +## Conventions + +If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use ":" to separate features. For example "bodyParser" from Connect would then be "connect:bodyParser". If you append a "*" to the end of your name, it will always be enabled regardless of the setting of the DEBUG environment variable. You can then use it for normal output as well as debug output. + +## Wildcards + +The `*` character may be used as a wildcard. Suppose for example your library has +debuggers named "connect:bodyParser", "connect:compress", "connect:session", +instead of listing all three with +`DEBUG=connect:bodyParser,connect:compress,connect:session`, you may simply do +`DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`. + +You can also exclude specific debuggers by prefixing them with a "-" character. +For example, `DEBUG=*,-connect:*` would include all debuggers except those +starting with "connect:". + +## Environment Variables + +When running through Node.js, you can set a few environment variables that will +change the behavior of the debug logging: + +| Name | Purpose | +|-----------|-------------------------------------------------| +| `DEBUG` | Enables/disables specific debugging namespaces. | +| `DEBUG_HIDE_DATE` | Hide date from debug output (non-TTY). | +| `DEBUG_COLORS`| Whether or not to use colors in the debug output. | +| `DEBUG_DEPTH` | Object inspection depth. | +| `DEBUG_SHOW_HIDDEN` | Shows hidden properties on inspected objects. | + + +__Note:__ The environment variables beginning with `DEBUG_` end up being +converted into an Options object that gets used with `%o`/`%O` formatters. +See the Node.js documentation for +[`util.inspect()`](https://nodejs.org/api/util.html#util_util_inspect_object_options) +for the complete list. + +## Formatters + +Debug uses [printf-style](https://wikipedia.org/wiki/Printf_format_string) formatting. +Below are the officially supported formatters: + +| Formatter | Representation | +|-----------|----------------| +| `%O` | Pretty-print an Object on multiple lines. | +| `%o` | Pretty-print an Object all on a single line. | +| `%s` | String. | +| `%d` | Number (both integer and float). | +| `%j` | JSON. Replaced with the string '[Circular]' if the argument contains circular references. | +| `%%` | Single percent sign ('%'). This does not consume an argument. | + + +### Custom formatters + +You can add custom formatters by extending the `debug.formatters` object. +For example, if you wanted to add support for rendering a Buffer as hex with +`%h`, you could do something like: + +```js +const createDebug = require('debug') +createDebug.formatters.h = (v) => { + return v.toString('hex') +} + +// …elsewhere +const debug = createDebug('foo') +debug('this is hex: %h', new Buffer('hello world')) +// foo this is hex: 68656c6c6f20776f726c6421 +0ms +``` + + +## Browser Support + +You can build a browser-ready script using [browserify](https://github.com/substack/node-browserify), +or just use the [browserify-as-a-service](https://wzrd.in/) [build](https://wzrd.in/standalone/debug@latest), +if you don't want to build it yourself. + +Debug's enable state is currently persisted by `localStorage`. +Consider the situation shown below where you have `worker:a` and `worker:b`, +and wish to debug both. You can enable this using `localStorage.debug`: + +```js +localStorage.debug = 'worker:*' +``` + +And then refresh the page. + +```js +a = debug('worker:a'); +b = debug('worker:b'); + +setInterval(function(){ + a('doing some work'); +}, 1000); + +setInterval(function(){ + b('doing some work'); +}, 1200); +``` + + +## Output streams + + By default `debug` will log to stderr, however this can be configured per-namespace by overriding the `log` method: + +Example [_stdout.js_](./examples/node/stdout.js): + +```js +var debug = require('debug'); +var error = debug('app:error'); + +// by default stderr is used +error('goes to stderr!'); + +var log = debug('app:log'); +// set this namespace to log via console.log +log.log = console.log.bind(console); // don't forget to bind to console! +log('goes to stdout'); +error('still goes to stderr!'); + +// set all output to go via console.info +// overrides all per-namespace log settings +debug.log = console.info.bind(console); +error('now goes to stdout via console.info'); +log('still goes to stdout, but via console.info now'); +``` + +## Extend +You can simply extend debugger +```js +const log = require('debug')('auth'); + +//creates new debug instance with extended namespace +const logSign = log.extend('sign'); +const logLogin = log.extend('login'); + +log('hello'); // auth hello +logSign('hello'); //auth:sign hello +logLogin('hello'); //auth:login hello +``` + +## Set dynamically + +You can also enable debug dynamically by calling the `enable()` method : + +```js +let debug = require('debug'); + +console.log(1, debug.enabled('test')); + +debug.enable('test'); +console.log(2, debug.enabled('test')); + +debug.disable(); +console.log(3, debug.enabled('test')); + +``` + +print : +``` +1 false +2 true +3 false +``` + +Usage : +`enable(namespaces)` +`namespaces` can include modes separated by a colon and wildcards. + +Note that calling `enable()` completely overrides previously set DEBUG variable : + +``` +$ DEBUG=foo node -e 'var dbg = require("debug"); dbg.enable("bar"); console.log(dbg.enabled("foo"))' +=> false +``` + +`disable()` + +Will disable all namespaces. The functions returns the namespaces currently +enabled (and skipped). This can be useful if you want to disable debugging +temporarily without knowing what was enabled to begin with. + +For example: + +```js +let debug = require('debug'); +debug.enable('foo:*,-foo:bar'); +let namespaces = debug.disable(); +debug.enable(namespaces); +``` + +Note: There is no guarantee that the string will be identical to the initial +enable string, but semantically they will be identical. + +## Checking whether a debug target is enabled + +After you've created a debug instance, you can determine whether or not it is +enabled by checking the `enabled` property: + +```javascript +const debug = require('debug')('http'); + +if (debug.enabled) { + // do stuff... +} +``` + +You can also manually toggle this property to force the debug instance to be +enabled or disabled. + + +## Authors + + - TJ Holowaychuk + - Nathan Rajlich + - Andrew Rhyne + +## Backers + +Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/debug#backer)] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +## Sponsors + +Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/debug#sponsor)] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +## License + +(The MIT License) + +Copyright (c) 2014-2017 TJ Holowaychuk <tj@vision-media.ca> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/debug/package.json b/node_modules/lv_font_conv/node_modules/debug/package.json new file mode 100644 index 00000000..c9f15f8c --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/debug/package.json @@ -0,0 +1,111 @@ +{ + "_args": [ + [ + "debug@4.3.1", + "/home/vitaly/Dropbox/Coding/lv_font_conv" + ] + ], + "_from": "debug@4.3.1", + "_id": "debug@4.3.1", + "_inBundle": false, + "_integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "_location": "/debug", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "debug@4.3.1", + "name": "debug", + "escapedName": "debug", + "rawSpec": "4.3.1", + "saveSpec": null, + "fetchSpec": "4.3.1" + }, + "_requiredBy": [ + "/", + "/@babel/core", + "/@babel/helper-define-polyfill-provider", + "/@babel/traverse", + "/@eslint/eslintrc", + "/eslint", + "/istanbul-lib-source-maps", + "/mocha" + ], + "_resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "_spec": "4.3.1", + "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", + "author": { + "name": "TJ Holowaychuk", + "email": "tj@vision-media.ca" + }, + "browser": "./src/browser.js", + "bugs": { + "url": "https://github.com/visionmedia/debug/issues" + }, + "contributors": [ + { + "name": "Nathan Rajlich", + "email": "nathan@tootallnate.net", + "url": "http://n8.io" + }, + { + "name": "Andrew Rhyne", + "email": "rhyneandrew@gmail.com" + }, + { + "name": "Josh Junon", + "email": "josh@junon.me" + } + ], + "dependencies": { + "ms": "2.1.2" + }, + "description": "small debugging utility", + "devDependencies": { + "brfs": "^2.0.1", + "browserify": "^16.2.3", + "coveralls": "^3.0.2", + "istanbul": "^0.4.5", + "karma": "^3.1.4", + "karma-browserify": "^6.0.0", + "karma-chrome-launcher": "^2.2.0", + "karma-mocha": "^1.3.0", + "mocha": "^5.2.0", + "mocha-lcov-reporter": "^1.2.0", + "xo": "^0.23.0" + }, + "engines": { + "node": ">=6.0" + }, + "files": [ + "src", + "LICENSE", + "README.md" + ], + "homepage": "https://github.com/visionmedia/debug#readme", + "keywords": [ + "debug", + "log", + "debugger" + ], + "license": "MIT", + "main": "./src/index.js", + "name": "debug", + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + }, + "repository": { + "type": "git", + "url": "git://github.com/visionmedia/debug.git" + }, + "scripts": { + "lint": "xo", + "test": "npm run test:node && npm run test:browser && npm run lint", + "test:browser": "karma start --single-run", + "test:coverage": "cat ./coverage/lcov.info | coveralls", + "test:node": "istanbul cover _mocha -- test.js" + }, + "version": "4.3.1" +} diff --git a/node_modules/lv_font_conv/node_modules/debug/src/browser.js b/node_modules/lv_font_conv/node_modules/debug/src/browser.js new file mode 100644 index 00000000..cd0fc35d --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/debug/src/browser.js @@ -0,0 +1,269 @@ +/* eslint-env browser */ + +/** + * This is the web browser implementation of `debug()`. + */ + +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +exports.storage = localstorage(); +exports.destroy = (() => { + let warned = false; + + return () => { + if (!warned) { + warned = true; + console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); + } + }; +})(); + +/** + * Colors. + */ + +exports.colors = [ + '#0000CC', + '#0000FF', + '#0033CC', + '#0033FF', + '#0066CC', + '#0066FF', + '#0099CC', + '#0099FF', + '#00CC00', + '#00CC33', + '#00CC66', + '#00CC99', + '#00CCCC', + '#00CCFF', + '#3300CC', + '#3300FF', + '#3333CC', + '#3333FF', + '#3366CC', + '#3366FF', + '#3399CC', + '#3399FF', + '#33CC00', + '#33CC33', + '#33CC66', + '#33CC99', + '#33CCCC', + '#33CCFF', + '#6600CC', + '#6600FF', + '#6633CC', + '#6633FF', + '#66CC00', + '#66CC33', + '#9900CC', + '#9900FF', + '#9933CC', + '#9933FF', + '#99CC00', + '#99CC33', + '#CC0000', + '#CC0033', + '#CC0066', + '#CC0099', + '#CC00CC', + '#CC00FF', + '#CC3300', + '#CC3333', + '#CC3366', + '#CC3399', + '#CC33CC', + '#CC33FF', + '#CC6600', + '#CC6633', + '#CC9900', + '#CC9933', + '#CCCC00', + '#CCCC33', + '#FF0000', + '#FF0033', + '#FF0066', + '#FF0099', + '#FF00CC', + '#FF00FF', + '#FF3300', + '#FF3333', + '#FF3366', + '#FF3399', + '#FF33CC', + '#FF33FF', + '#FF6600', + '#FF6633', + '#FF9900', + '#FF9933', + '#FFCC00', + '#FFCC33' +]; + +/** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ + +// eslint-disable-next-line complexity +function useColors() { + // NB: In an Electron preload script, document will be defined but not fully + // initialized. Since we know we're in Chrome, we'll just detect this case + // explicitly + if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) { + return true; + } + + // Internet Explorer and Edge do not support colors. + if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) { + return false; + } + + // Is webkit? http://stackoverflow.com/a/16459606/376773 + // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 + return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || + // Is firebug? http://stackoverflow.com/a/398120/376773 + (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || + // Is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || + // Double check webkit in userAgent just in case we are in a worker + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); +} + +/** + * Colorize log arguments if enabled. + * + * @api public + */ + +function formatArgs(args) { + args[0] = (this.useColors ? '%c' : '') + + this.namespace + + (this.useColors ? ' %c' : ' ') + + args[0] + + (this.useColors ? '%c ' : ' ') + + '+' + module.exports.humanize(this.diff); + + if (!this.useColors) { + return; + } + + const c = 'color: ' + this.color; + args.splice(1, 0, c, 'color: inherit'); + + // The final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + let index = 0; + let lastC = 0; + args[0].replace(/%[a-zA-Z%]/g, match => { + if (match === '%%') { + return; + } + index++; + if (match === '%c') { + // We only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; + } + }); + + args.splice(lastC, 0, c); +} + +/** + * Invokes `console.debug()` when available. + * No-op when `console.debug` is not a "function". + * If `console.debug` is not available, falls back + * to `console.log`. + * + * @api public + */ +exports.log = console.debug || console.log || (() => {}); + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ +function save(namespaces) { + try { + if (namespaces) { + exports.storage.setItem('debug', namespaces); + } else { + exports.storage.removeItem('debug'); + } + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ +function load() { + let r; + try { + r = exports.storage.getItem('debug'); + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } + + // If debug isn't set in LS, and we're in Electron, try to load $DEBUG + if (!r && typeof process !== 'undefined' && 'env' in process) { + r = process.env.DEBUG; + } + + return r; +} + +/** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + +function localstorage() { + try { + // TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context + // The Browser also has localStorage in the global context. + return localStorage; + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } +} + +module.exports = require('./common')(exports); + +const {formatters} = module.exports; + +/** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ + +formatters.j = function (v) { + try { + return JSON.stringify(v); + } catch (error) { + return '[UnexpectedJSONParseError]: ' + error.message; + } +}; diff --git a/node_modules/lv_font_conv/node_modules/debug/src/common.js b/node_modules/lv_font_conv/node_modules/debug/src/common.js new file mode 100644 index 00000000..392a8e00 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/debug/src/common.js @@ -0,0 +1,261 @@ + +/** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + */ + +function setup(env) { + createDebug.debug = createDebug; + createDebug.default = createDebug; + createDebug.coerce = coerce; + createDebug.disable = disable; + createDebug.enable = enable; + createDebug.enabled = enabled; + createDebug.humanize = require('ms'); + createDebug.destroy = destroy; + + Object.keys(env).forEach(key => { + createDebug[key] = env[key]; + }); + + /** + * The currently active debug mode names, and names to skip. + */ + + createDebug.names = []; + createDebug.skips = []; + + /** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". + */ + createDebug.formatters = {}; + + /** + * Selects a color for a debug namespace + * @param {String} namespace The namespace string for the for the debug instance to be colored + * @return {Number|String} An ANSI color code for the given namespace + * @api private + */ + function selectColor(namespace) { + let hash = 0; + + for (let i = 0; i < namespace.length; i++) { + hash = ((hash << 5) - hash) + namespace.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + + return createDebug.colors[Math.abs(hash) % createDebug.colors.length]; + } + createDebug.selectColor = selectColor; + + /** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + function createDebug(namespace) { + let prevTime; + let enableOverride = null; + + function debug(...args) { + // Disabled? + if (!debug.enabled) { + return; + } + + const self = debug; + + // Set `diff` timestamp + const curr = Number(new Date()); + const ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; + + args[0] = createDebug.coerce(args[0]); + + if (typeof args[0] !== 'string') { + // Anything else let's inspect with %O + args.unshift('%O'); + } + + // Apply any `formatters` transformations + let index = 0; + args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => { + // If we encounter an escaped % then don't increase the array index + if (match === '%%') { + return '%'; + } + index++; + const formatter = createDebug.formatters[format]; + if (typeof formatter === 'function') { + const val = args[index]; + match = formatter.call(self, val); + + // Now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); + + // Apply env-specific formatting (colors, etc.) + createDebug.formatArgs.call(self, args); + + const logFn = self.log || createDebug.log; + logFn.apply(self, args); + } + + debug.namespace = namespace; + debug.useColors = createDebug.useColors(); + debug.color = createDebug.selectColor(namespace); + debug.extend = extend; + debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release. + + Object.defineProperty(debug, 'enabled', { + enumerable: true, + configurable: false, + get: () => enableOverride === null ? createDebug.enabled(namespace) : enableOverride, + set: v => { + enableOverride = v; + } + }); + + // Env-specific initialization logic for debug instances + if (typeof createDebug.init === 'function') { + createDebug.init(debug); + } + + return debug; + } + + function extend(namespace, delimiter) { + const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace); + newDebug.log = this.log; + return newDebug; + } + + /** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + function enable(namespaces) { + createDebug.save(namespaces); + + createDebug.names = []; + createDebug.skips = []; + + let i; + const split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); + const len = split.length; + + for (i = 0; i < len; i++) { + if (!split[i]) { + // ignore empty strings + continue; + } + + namespaces = split[i].replace(/\*/g, '.*?'); + + if (namespaces[0] === '-') { + createDebug.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); + } else { + createDebug.names.push(new RegExp('^' + namespaces + '$')); + } + } + } + + /** + * Disable debug output. + * + * @return {String} namespaces + * @api public + */ + function disable() { + const namespaces = [ + ...createDebug.names.map(toNamespace), + ...createDebug.skips.map(toNamespace).map(namespace => '-' + namespace) + ].join(','); + createDebug.enable(''); + return namespaces; + } + + /** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + function enabled(name) { + if (name[name.length - 1] === '*') { + return true; + } + + let i; + let len; + + for (i = 0, len = createDebug.skips.length; i < len; i++) { + if (createDebug.skips[i].test(name)) { + return false; + } + } + + for (i = 0, len = createDebug.names.length; i < len; i++) { + if (createDebug.names[i].test(name)) { + return true; + } + } + + return false; + } + + /** + * Convert regexp to namespace + * + * @param {RegExp} regxep + * @return {String} namespace + * @api private + */ + function toNamespace(regexp) { + return regexp.toString() + .substring(2, regexp.toString().length - 2) + .replace(/\.\*\?$/, '*'); + } + + /** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + function coerce(val) { + if (val instanceof Error) { + return val.stack || val.message; + } + return val; + } + + /** + * XXX DO NOT USE. This is a temporary stub function. + * XXX It WILL be removed in the next major release. + */ + function destroy() { + console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); + } + + createDebug.enable(createDebug.load()); + + return createDebug; +} + +module.exports = setup; diff --git a/node_modules/lv_font_conv/node_modules/debug/src/index.js b/node_modules/lv_font_conv/node_modules/debug/src/index.js new file mode 100644 index 00000000..bf4c57f2 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/debug/src/index.js @@ -0,0 +1,10 @@ +/** + * Detect Electron renderer / nwjs process, which is node, but we should + * treat as a browser. + */ + +if (typeof process === 'undefined' || process.type === 'renderer' || process.browser === true || process.__nwjs) { + module.exports = require('./browser.js'); +} else { + module.exports = require('./node.js'); +} diff --git a/node_modules/lv_font_conv/node_modules/debug/src/node.js b/node_modules/lv_font_conv/node_modules/debug/src/node.js new file mode 100644 index 00000000..79bc085c --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/debug/src/node.js @@ -0,0 +1,263 @@ +/** + * Module dependencies. + */ + +const tty = require('tty'); +const util = require('util'); + +/** + * This is the Node.js implementation of `debug()`. + */ + +exports.init = init; +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +exports.destroy = util.deprecate( + () => {}, + 'Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.' +); + +/** + * Colors. + */ + +exports.colors = [6, 2, 3, 4, 5, 1]; + +try { + // Optional dependency (as in, doesn't need to be installed, NOT like optionalDependencies in package.json) + // eslint-disable-next-line import/no-extraneous-dependencies + const supportsColor = require('supports-color'); + + if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) { + exports.colors = [ + 20, + 21, + 26, + 27, + 32, + 33, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 56, + 57, + 62, + 63, + 68, + 69, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 92, + 93, + 98, + 99, + 112, + 113, + 128, + 129, + 134, + 135, + 148, + 149, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 178, + 179, + 184, + 185, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 214, + 215, + 220, + 221 + ]; + } +} catch (error) { + // Swallow - we only care if `supports-color` is available; it doesn't have to be. +} + +/** + * Build up the default `inspectOpts` object from the environment variables. + * + * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js + */ + +exports.inspectOpts = Object.keys(process.env).filter(key => { + return /^debug_/i.test(key); +}).reduce((obj, key) => { + // Camel-case + const prop = key + .substring(6) + .toLowerCase() + .replace(/_([a-z])/g, (_, k) => { + return k.toUpperCase(); + }); + + // Coerce string value into JS value + let val = process.env[key]; + if (/^(yes|on|true|enabled)$/i.test(val)) { + val = true; + } else if (/^(no|off|false|disabled)$/i.test(val)) { + val = false; + } else if (val === 'null') { + val = null; + } else { + val = Number(val); + } + + obj[prop] = val; + return obj; +}, {}); + +/** + * Is stdout a TTY? Colored output is enabled when `true`. + */ + +function useColors() { + return 'colors' in exports.inspectOpts ? + Boolean(exports.inspectOpts.colors) : + tty.isatty(process.stderr.fd); +} + +/** + * Adds ANSI color escape codes if enabled. + * + * @api public + */ + +function formatArgs(args) { + const {namespace: name, useColors} = this; + + if (useColors) { + const c = this.color; + const colorCode = '\u001B[3' + (c < 8 ? c : '8;5;' + c); + const prefix = ` ${colorCode};1m${name} \u001B[0m`; + + args[0] = prefix + args[0].split('\n').join('\n' + prefix); + args.push(colorCode + 'm+' + module.exports.humanize(this.diff) + '\u001B[0m'); + } else { + args[0] = getDate() + name + ' ' + args[0]; + } +} + +function getDate() { + if (exports.inspectOpts.hideDate) { + return ''; + } + return new Date().toISOString() + ' '; +} + +/** + * Invokes `util.format()` with the specified arguments and writes to stderr. + */ + +function log(...args) { + return process.stderr.write(util.format(...args) + '\n'); +} + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ +function save(namespaces) { + if (namespaces) { + process.env.DEBUG = namespaces; + } else { + // If you set a process.env field to null or undefined, it gets cast to the + // string 'null' or 'undefined'. Just delete instead. + delete process.env.DEBUG; + } +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + +function load() { + return process.env.DEBUG; +} + +/** + * Init logic for `debug` instances. + * + * Create a new `inspectOpts` object in case `useColors` is set + * differently for a particular `debug` instance. + */ + +function init(debug) { + debug.inspectOpts = {}; + + const keys = Object.keys(exports.inspectOpts); + for (let i = 0; i < keys.length; i++) { + debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]]; + } +} + +module.exports = require('./common')(exports); + +const {formatters} = module.exports; + +/** + * Map %o to `util.inspect()`, all on a single line. + */ + +formatters.o = function (v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts) + .split('\n') + .map(str => str.trim()) + .join(' '); +}; + +/** + * Map %O to `util.inspect()`, allowing multiple lines if needed. + */ + +formatters.O = function (v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts); +}; diff --git a/node_modules/lv_font_conv/node_modules/make-error/LICENSE b/node_modules/lv_font_conv/node_modules/make-error/LICENSE new file mode 100644 index 00000000..9dcf797e --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/make-error/LICENSE @@ -0,0 +1,5 @@ +Copyright 2014 Julien Fontanet + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/make-error/README.md b/node_modules/lv_font_conv/node_modules/make-error/README.md new file mode 100644 index 00000000..5c089a26 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/make-error/README.md @@ -0,0 +1,112 @@ +# make-error + +[![Package Version](https://badgen.net/npm/v/make-error)](https://npmjs.org/package/make-error) [![Build Status](https://travis-ci.org/JsCommunity/make-error.png?branch=master)](https://travis-ci.org/JsCommunity/make-error) [![PackagePhobia](https://badgen.net/packagephobia/install/make-error)](https://packagephobia.now.sh/result?p=make-error) [![Latest Commit](https://badgen.net/github/last-commit/JsCommunity/make-error)](https://github.com/JsCommunity/make-error/commits/master) + +> Make your own error types! + +## Features + +- Compatible Node & browsers +- `instanceof` support +- `error.name` & `error.stack` support +- compatible with [CSP](https://en.wikipedia.org/wiki/Content_Security_Policy) (i.e. no `eval()`) + +## Installation + +### Node & [Browserify](http://browserify.org/)/[Webpack](https://webpack.js.org/) + +Installation of the [npm package](https://npmjs.org/package/make-error): + +``` +> npm install --save make-error +``` + +Then require the package: + +```javascript +var makeError = require("make-error"); +``` + +### Browser + +You can directly use the build provided at [unpkg.com](https://unpkg.com): + +```html + +``` + +## Usage + +### Basic named error + +```javascript +var CustomError = makeError("CustomError"); + +// Parameters are forwarded to the super class (here Error). +throw new CustomError("a message"); +``` + +### Advanced error class + +```javascript +function CustomError(customValue) { + CustomError.super.call(this, "custom error message"); + + this.customValue = customValue; +} +makeError(CustomError); + +// Feel free to extend the prototype. +CustomError.prototype.myMethod = function CustomError$myMethod() { + console.log("CustomError.myMethod (%s, %s)", this.code, this.message); +}; + +//----- + +try { + throw new CustomError(42); +} catch (error) { + error.myMethod(); +} +``` + +### Specialized error + +```javascript +var SpecializedError = makeError("SpecializedError", CustomError); + +throw new SpecializedError(42); +``` + +### Inheritance + +> Best for ES2015+. + +```javascript +import { BaseError } from "make-error"; + +class CustomError extends BaseError { + constructor() { + super("custom error message"); + } +} +``` + +## Related + +- [make-error-cause](https://www.npmjs.com/package/make-error-cause): Make your own error types, with a cause! + +## Contributions + +Contributions are _very_ welcomed, either on the documentation or on +the code. + +You may: + +- report any [issue](https://github.com/JsCommunity/make-error/issues) + you've encountered; +- fork and create a pull request. + +## License + +ISC © [Julien Fontanet](http://julien.isonoe.net) diff --git a/node_modules/lv_font_conv/node_modules/make-error/dist/make-error.js b/node_modules/lv_font_conv/node_modules/make-error/dist/make-error.js new file mode 100644 index 00000000..32444c69 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/make-error/dist/make-error.js @@ -0,0 +1 @@ +!function(f){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=f();else if("function"==typeof define&&define.amd)define([],f);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).makeError=f()}}(function(){return function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){return o(e[i][1][r]||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i; + +/** + * Set the constructor prototype to `BaseError`. + */ +declare function makeError(super_: { + new (...args: any[]): T; +}): makeError.Constructor; + +/** + * Create a specialized error instance. + */ +declare function makeError( + name: string | Function, + super_: K +): K & makeError.SpecializedConstructor; + +declare namespace makeError { + /** + * Use with ES2015+ inheritance. + */ + export class BaseError extends Error { + message: string; + name: string; + stack: string; + + constructor(message?: string); + } + + export interface Constructor { + new (message?: string): T; + super_: any; + prototype: T; + } + + export interface SpecializedConstructor { + super_: any; + prototype: T; + } +} + +export = makeError; diff --git a/node_modules/lv_font_conv/node_modules/make-error/index.js b/node_modules/lv_font_conv/node_modules/make-error/index.js new file mode 100644 index 00000000..fab60407 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/make-error/index.js @@ -0,0 +1,151 @@ +// ISC @ Julien Fontanet + +"use strict"; + +// =================================================================== + +var construct = typeof Reflect !== "undefined" ? Reflect.construct : undefined; +var defineProperty = Object.defineProperty; + +// ------------------------------------------------------------------- + +var captureStackTrace = Error.captureStackTrace; +if (captureStackTrace === undefined) { + captureStackTrace = function captureStackTrace(error) { + var container = new Error(); + + defineProperty(error, "stack", { + configurable: true, + get: function getStack() { + var stack = container.stack; + + // Replace property with value for faster future accesses. + defineProperty(this, "stack", { + configurable: true, + value: stack, + writable: true, + }); + + return stack; + }, + set: function setStack(stack) { + defineProperty(error, "stack", { + configurable: true, + value: stack, + writable: true, + }); + }, + }); + }; +} + +// ------------------------------------------------------------------- + +function BaseError(message) { + if (message !== undefined) { + defineProperty(this, "message", { + configurable: true, + value: message, + writable: true, + }); + } + + var cname = this.constructor.name; + if (cname !== undefined && cname !== this.name) { + defineProperty(this, "name", { + configurable: true, + value: cname, + writable: true, + }); + } + + captureStackTrace(this, this.constructor); +} + +BaseError.prototype = Object.create(Error.prototype, { + // See: https://github.com/JsCommunity/make-error/issues/4 + constructor: { + configurable: true, + value: BaseError, + writable: true, + }, +}); + +// ------------------------------------------------------------------- + +// Sets the name of a function if possible (depends of the JS engine). +var setFunctionName = (function() { + function setFunctionName(fn, name) { + return defineProperty(fn, "name", { + configurable: true, + value: name, + }); + } + try { + var f = function() {}; + setFunctionName(f, "foo"); + if (f.name === "foo") { + return setFunctionName; + } + } catch (_) {} +})(); + +// ------------------------------------------------------------------- + +function makeError(constructor, super_) { + if (super_ == null || super_ === Error) { + super_ = BaseError; + } else if (typeof super_ !== "function") { + throw new TypeError("super_ should be a function"); + } + + var name; + if (typeof constructor === "string") { + name = constructor; + constructor = + construct !== undefined + ? function() { + return construct(super_, arguments, this.constructor); + } + : function() { + super_.apply(this, arguments); + }; + + // If the name can be set, do it once and for all. + if (setFunctionName !== undefined) { + setFunctionName(constructor, name); + name = undefined; + } + } else if (typeof constructor !== "function") { + throw new TypeError("constructor should be either a string or a function"); + } + + // Also register the super constructor also as `constructor.super_` just + // like Node's `util.inherits()`. + // + // eslint-disable-next-line dot-notation + constructor.super_ = constructor["super"] = super_; + + var properties = { + constructor: { + configurable: true, + value: constructor, + writable: true, + }, + }; + + // If the name could not be set on the constructor, set it on the + // prototype. + if (name !== undefined) { + properties.name = { + configurable: true, + value: name, + writable: true, + }; + } + constructor.prototype = Object.create(super_.prototype, properties); + + return constructor; +} +exports = module.exports = makeError; +exports.BaseError = BaseError; diff --git a/node_modules/lv_font_conv/node_modules/make-error/package.json b/node_modules/lv_font_conv/node_modules/make-error/package.json new file mode 100644 index 00000000..e5f69904 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/make-error/package.json @@ -0,0 +1,95 @@ +{ + "_args": [ + [ + "make-error@1.3.6", + "/home/vitaly/Dropbox/Coding/lv_font_conv" + ] + ], + "_from": "make-error@1.3.6", + "_id": "make-error@1.3.6", + "_inBundle": false, + "_integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "_location": "/make-error", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "make-error@1.3.6", + "name": "make-error", + "escapedName": "make-error", + "rawSpec": "1.3.6", + "saveSpec": null, + "fetchSpec": "1.3.6" + }, + "_requiredBy": [ + "/" + ], + "_resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "_spec": "1.3.6", + "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", + "author": { + "name": "Julien Fontanet", + "email": "julien.fontanet@isonoe.net" + }, + "bugs": { + "url": "https://github.com/JsCommunity/make-error/issues" + }, + "description": "Make your own error types!", + "devDependencies": { + "browserify": "^16.2.3", + "eslint": "^6.5.1", + "eslint-config-prettier": "^6.4.0", + "eslint-config-standard": "^14.1.0", + "eslint-plugin-import": "^2.14.0", + "eslint-plugin-node": "^10.0.0", + "eslint-plugin-promise": "^4.0.1", + "eslint-plugin-standard": "^4.0.0", + "husky": "^3.0.9", + "jest": "^24", + "prettier": "^1.14.3", + "uglify-js": "^3.3.2" + }, + "files": [ + "dist/", + "index.js", + "index.d.ts" + ], + "homepage": "https://github.com/JsCommunity/make-error", + "husky": { + "hooks": { + "commit-msg": "npm run test" + } + }, + "jest": { + "testEnvironment": "node" + }, + "keywords": [ + "create", + "custom", + "derive", + "error", + "errors", + "extend", + "extending", + "extension", + "factory", + "inherit", + "make", + "subclass" + ], + "license": "ISC", + "main": "index.js", + "name": "make-error", + "repository": { + "type": "git", + "url": "git://github.com/JsCommunity/make-error.git" + }, + "scripts": { + "dev-test": "jest --watch", + "format": "prettier --write '**'", + "prepublishOnly": "mkdir -p dist && browserify -s makeError index.js | uglifyjs -c > dist/make-error.js", + "pretest": "eslint --ignore-path .gitignore .", + "test": "jest" + }, + "version": "1.3.6" +} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/CHANGELOG.md b/node_modules/lv_font_conv/node_modules/mkdirp/CHANGELOG.md new file mode 100644 index 00000000..81458380 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changers Lorgs! + +## 1.0 + +Full rewrite. Essentially a brand new module. + +- Return a promise instead of taking a callback. +- Use native `fs.mkdir(path, { recursive: true })` when available. +- Drop support for outdated Node.js versions. (Technically still works on + Node.js v8, but only 10 and above are officially supported.) + +## 0.x + +Original and most widely used recursive directory creation implementation +in JavaScript, dating back to 2010. diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/LICENSE b/node_modules/lv_font_conv/node_modules/mkdirp/LICENSE new file mode 100644 index 00000000..13fcd15f --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/LICENSE @@ -0,0 +1,21 @@ +Copyright James Halliday (mail@substack.net) and Isaac Z. Schlueter (i@izs.me) + +This project is free software released under the MIT license: + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/bin/cmd.js b/node_modules/lv_font_conv/node_modules/mkdirp/bin/cmd.js new file mode 100755 index 00000000..6e0aa8dc --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/bin/cmd.js @@ -0,0 +1,68 @@ +#!/usr/bin/env node + +const usage = () => ` +usage: mkdirp [DIR1,DIR2..] {OPTIONS} + + Create each supplied directory including any necessary parent directories + that don't yet exist. + + If the directory already exists, do nothing. + +OPTIONS are: + + -m If a directory needs to be created, set the mode as an octal + --mode= permission string. + + -v --version Print the mkdirp version number + + -h --help Print this helpful banner + + -p --print Print the first directories created for each path provided + + --manual Use manual implementation, even if native is available +` + +const dirs = [] +const opts = {} +let print = false +let dashdash = false +let manual = false +for (const arg of process.argv.slice(2)) { + if (dashdash) + dirs.push(arg) + else if (arg === '--') + dashdash = true + else if (arg === '--manual') + manual = true + else if (/^-h/.test(arg) || /^--help/.test(arg)) { + console.log(usage()) + process.exit(0) + } else if (arg === '-v' || arg === '--version') { + console.log(require('../package.json').version) + process.exit(0) + } else if (arg === '-p' || arg === '--print') { + print = true + } else if (/^-m/.test(arg) || /^--mode=/.test(arg)) { + const mode = parseInt(arg.replace(/^(-m|--mode=)/, ''), 8) + if (isNaN(mode)) { + console.error(`invalid mode argument: ${arg}\nMust be an octal number.`) + process.exit(1) + } + opts.mode = mode + } else + dirs.push(arg) +} + +const mkdirp = require('../') +const impl = manual ? mkdirp.manual : mkdirp +if (dirs.length === 0) + console.error(usage()) + +Promise.all(dirs.map(dir => impl(dir, opts))) + .then(made => print ? made.forEach(m => m && console.log(m)) : null) + .catch(er => { + console.error(er.message) + if (er.code) + console.error(' code: ' + er.code) + process.exit(1) + }) diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/index.js b/node_modules/lv_font_conv/node_modules/mkdirp/index.js new file mode 100644 index 00000000..ad7a16c9 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/index.js @@ -0,0 +1,31 @@ +const optsArg = require('./lib/opts-arg.js') +const pathArg = require('./lib/path-arg.js') + +const {mkdirpNative, mkdirpNativeSync} = require('./lib/mkdirp-native.js') +const {mkdirpManual, mkdirpManualSync} = require('./lib/mkdirp-manual.js') +const {useNative, useNativeSync} = require('./lib/use-native.js') + + +const mkdirp = (path, opts) => { + path = pathArg(path) + opts = optsArg(opts) + return useNative(opts) + ? mkdirpNative(path, opts) + : mkdirpManual(path, opts) +} + +const mkdirpSync = (path, opts) => { + path = pathArg(path) + opts = optsArg(opts) + return useNativeSync(opts) + ? mkdirpNativeSync(path, opts) + : mkdirpManualSync(path, opts) +} + +mkdirp.sync = mkdirpSync +mkdirp.native = (path, opts) => mkdirpNative(pathArg(path), optsArg(opts)) +mkdirp.manual = (path, opts) => mkdirpManual(pathArg(path), optsArg(opts)) +mkdirp.nativeSync = (path, opts) => mkdirpNativeSync(pathArg(path), optsArg(opts)) +mkdirp.manualSync = (path, opts) => mkdirpManualSync(pathArg(path), optsArg(opts)) + +module.exports = mkdirp diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/find-made.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/find-made.js new file mode 100644 index 00000000..022e492c --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/lib/find-made.js @@ -0,0 +1,29 @@ +const {dirname} = require('path') + +const findMade = (opts, parent, path = undefined) => { + // we never want the 'made' return value to be a root directory + if (path === parent) + return Promise.resolve() + + return opts.statAsync(parent).then( + st => st.isDirectory() ? path : undefined, // will fail later + er => er.code === 'ENOENT' + ? findMade(opts, dirname(parent), parent) + : undefined + ) +} + +const findMadeSync = (opts, parent, path = undefined) => { + if (path === parent) + return undefined + + try { + return opts.statSync(parent).isDirectory() ? path : undefined + } catch (er) { + return er.code === 'ENOENT' + ? findMadeSync(opts, dirname(parent), parent) + : undefined + } +} + +module.exports = {findMade, findMadeSync} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-manual.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-manual.js new file mode 100644 index 00000000..2eb18cd6 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-manual.js @@ -0,0 +1,64 @@ +const {dirname} = require('path') + +const mkdirpManual = (path, opts, made) => { + opts.recursive = false + const parent = dirname(path) + if (parent === path) { + return opts.mkdirAsync(path, opts).catch(er => { + // swallowed by recursive implementation on posix systems + // any other error is a failure + if (er.code !== 'EISDIR') + throw er + }) + } + + return opts.mkdirAsync(path, opts).then(() => made || path, er => { + if (er.code === 'ENOENT') + return mkdirpManual(parent, opts) + .then(made => mkdirpManual(path, opts, made)) + if (er.code !== 'EEXIST' && er.code !== 'EROFS') + throw er + return opts.statAsync(path).then(st => { + if (st.isDirectory()) + return made + else + throw er + }, () => { throw er }) + }) +} + +const mkdirpManualSync = (path, opts, made) => { + const parent = dirname(path) + opts.recursive = false + + if (parent === path) { + try { + return opts.mkdirSync(path, opts) + } catch (er) { + // swallowed by recursive implementation on posix systems + // any other error is a failure + if (er.code !== 'EISDIR') + throw er + else + return + } + } + + try { + opts.mkdirSync(path, opts) + return made || path + } catch (er) { + if (er.code === 'ENOENT') + return mkdirpManualSync(path, opts, mkdirpManualSync(parent, opts, made)) + if (er.code !== 'EEXIST' && er.code !== 'EROFS') + throw er + try { + if (!opts.statSync(path).isDirectory()) + throw er + } catch (_) { + throw er + } + } +} + +module.exports = {mkdirpManual, mkdirpManualSync} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-native.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-native.js new file mode 100644 index 00000000..c7a6b698 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-native.js @@ -0,0 +1,39 @@ +const {dirname} = require('path') +const {findMade, findMadeSync} = require('./find-made.js') +const {mkdirpManual, mkdirpManualSync} = require('./mkdirp-manual.js') + +const mkdirpNative = (path, opts) => { + opts.recursive = true + const parent = dirname(path) + if (parent === path) + return opts.mkdirAsync(path, opts) + + return findMade(opts, path).then(made => + opts.mkdirAsync(path, opts).then(() => made) + .catch(er => { + if (er.code === 'ENOENT') + return mkdirpManual(path, opts) + else + throw er + })) +} + +const mkdirpNativeSync = (path, opts) => { + opts.recursive = true + const parent = dirname(path) + if (parent === path) + return opts.mkdirSync(path, opts) + + const made = findMadeSync(opts, path) + try { + opts.mkdirSync(path, opts) + return made + } catch (er) { + if (er.code === 'ENOENT') + return mkdirpManualSync(path, opts) + else + throw er + } +} + +module.exports = {mkdirpNative, mkdirpNativeSync} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/opts-arg.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/opts-arg.js new file mode 100644 index 00000000..2fa4833f --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/lib/opts-arg.js @@ -0,0 +1,23 @@ +const { promisify } = require('util') +const fs = require('fs') +const optsArg = opts => { + if (!opts) + opts = { mode: 0o777, fs } + else if (typeof opts === 'object') + opts = { mode: 0o777, fs, ...opts } + else if (typeof opts === 'number') + opts = { mode: opts, fs } + else if (typeof opts === 'string') + opts = { mode: parseInt(opts, 8), fs } + else + throw new TypeError('invalid options argument') + + opts.mkdir = opts.mkdir || opts.fs.mkdir || fs.mkdir + opts.mkdirAsync = promisify(opts.mkdir) + opts.stat = opts.stat || opts.fs.stat || fs.stat + opts.statAsync = promisify(opts.stat) + opts.statSync = opts.statSync || opts.fs.statSync || fs.statSync + opts.mkdirSync = opts.mkdirSync || opts.fs.mkdirSync || fs.mkdirSync + return opts +} +module.exports = optsArg diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/path-arg.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/path-arg.js new file mode 100644 index 00000000..cc07de5a --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/lib/path-arg.js @@ -0,0 +1,29 @@ +const platform = process.env.__TESTING_MKDIRP_PLATFORM__ || process.platform +const { resolve, parse } = require('path') +const pathArg = path => { + if (/\0/.test(path)) { + // simulate same failure that node raises + throw Object.assign( + new TypeError('path must be a string without null bytes'), + { + path, + code: 'ERR_INVALID_ARG_VALUE', + } + ) + } + + path = resolve(path) + if (platform === 'win32') { + const badWinChars = /[*|"<>?:]/ + const {root} = parse(path) + if (badWinChars.test(path.substr(root.length))) { + throw Object.assign(new Error('Illegal characters in path.'), { + path, + code: 'EINVAL', + }) + } + } + + return path +} +module.exports = pathArg diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/use-native.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/use-native.js new file mode 100644 index 00000000..079361de --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/lib/use-native.js @@ -0,0 +1,10 @@ +const fs = require('fs') + +const version = process.env.__TESTING_MKDIRP_NODE_VERSION__ || process.version +const versArr = version.replace(/^v/, '').split('.') +const hasNative = +versArr[0] > 10 || +versArr[0] === 10 && +versArr[1] >= 12 + +const useNative = !hasNative ? () => false : opts => opts.mkdir === fs.mkdir +const useNativeSync = !hasNative ? () => false : opts => opts.mkdirSync === fs.mkdirSync + +module.exports = {useNative, useNativeSync} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/package.json b/node_modules/lv_font_conv/node_modules/mkdirp/package.json new file mode 100644 index 00000000..1fb2e3d9 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/package.json @@ -0,0 +1,78 @@ +{ + "_args": [ + [ + "mkdirp@1.0.4", + "/home/vitaly/Dropbox/Coding/lv_font_conv" + ] + ], + "_from": "mkdirp@1.0.4", + "_id": "mkdirp@1.0.4", + "_inBundle": false, + "_integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "_location": "/mkdirp", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "mkdirp@1.0.4", + "name": "mkdirp", + "escapedName": "mkdirp", + "rawSpec": "1.0.4", + "saveSpec": null, + "fetchSpec": "1.0.4" + }, + "_requiredBy": [ + "/" + ], + "_resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "_spec": "1.0.4", + "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "bugs": { + "url": "https://github.com/isaacs/node-mkdirp/issues" + }, + "description": "Recursively mkdir, like `mkdir -p`", + "devDependencies": { + "require-inject": "^1.4.4", + "tap": "^14.10.7" + }, + "engines": { + "node": ">=10" + }, + "files": [ + "bin", + "lib", + "index.js" + ], + "homepage": "https://github.com/isaacs/node-mkdirp#readme", + "keywords": [ + "mkdir", + "directory", + "make dir", + "make", + "dir", + "recursive", + "native" + ], + "license": "MIT", + "main": "index.js", + "name": "mkdirp", + "repository": { + "type": "git", + "url": "git+https://github.com/isaacs/node-mkdirp.git" + }, + "scripts": { + "postpublish": "git push origin --follow-tags", + "postversion": "npm publish", + "preversion": "npm test", + "snap": "tap", + "test": "tap" + }, + "tap": { + "check-coverage": true, + "coverage-map": "map.js" + }, + "version": "1.0.4" +} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/readme.markdown b/node_modules/lv_font_conv/node_modules/mkdirp/readme.markdown new file mode 100644 index 00000000..827de590 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/readme.markdown @@ -0,0 +1,266 @@ +# mkdirp + +Like `mkdir -p`, but in Node.js! + +Now with a modern API and no\* bugs! + +\* may contain some bugs + +# example + +## pow.js + +```js +const mkdirp = require('mkdirp') + +// return value is a Promise resolving to the first directory created +mkdirp('/tmp/foo/bar/baz').then(made => + console.log(`made directories, starting with ${made}`)) +``` + +Output (where `/tmp/foo` already exists) + +``` +made directories, starting with /tmp/foo/bar +``` + +Or, if you don't have time to wait around for promises: + +```js +const mkdirp = require('mkdirp') + +// return value is the first directory created +const made = mkdirp.sync('/tmp/foo/bar/baz') +console.log(`made directories, starting with ${made}`) +``` + +And now /tmp/foo/bar/baz exists, huzzah! + +# methods + +```js +const mkdirp = require('mkdirp') +``` + +## mkdirp(dir, [opts]) -> Promise + +Create a new directory and any necessary subdirectories at `dir` with octal +permission string `opts.mode`. If `opts` is a string or number, it will be +treated as the `opts.mode`. + +If `opts.mode` isn't specified, it defaults to `0o777 & +(~process.umask())`. + +Promise resolves to first directory `made` that had to be created, or +`undefined` if everything already exists. Promise rejects if any errors +are encountered. Note that, in the case of promise rejection, some +directories _may_ have been created, as recursive directory creation is not +an atomic operation. + +You can optionally pass in an alternate `fs` implementation by passing in +`opts.fs`. Your implementation should have `opts.fs.mkdir(path, opts, cb)` +and `opts.fs.stat(path, cb)`. + +You can also override just one or the other of `mkdir` and `stat` by +passing in `opts.stat` or `opts.mkdir`, or providing an `fs` option that +only overrides one of these. + +## mkdirp.sync(dir, opts) -> String|null + +Synchronously create a new directory and any necessary subdirectories at +`dir` with octal permission string `opts.mode`. If `opts` is a string or +number, it will be treated as the `opts.mode`. + +If `opts.mode` isn't specified, it defaults to `0o777 & +(~process.umask())`. + +Returns the first directory that had to be created, or undefined if +everything already exists. + +You can optionally pass in an alternate `fs` implementation by passing in +`opts.fs`. Your implementation should have `opts.fs.mkdirSync(path, mode)` +and `opts.fs.statSync(path)`. + +You can also override just one or the other of `mkdirSync` and `statSync` +by passing in `opts.statSync` or `opts.mkdirSync`, or providing an `fs` +option that only overrides one of these. + +## mkdirp.manual, mkdirp.manualSync + +Use the manual implementation (not the native one). This is the default +when the native implementation is not available or the stat/mkdir +implementation is overridden. + +## mkdirp.native, mkdirp.nativeSync + +Use the native implementation (not the manual one). This is the default +when the native implementation is available and stat/mkdir are not +overridden. + +# implementation + +On Node.js v10.12.0 and above, use the native `fs.mkdir(p, +{recursive:true})` option, unless `fs.mkdir`/`fs.mkdirSync` has been +overridden by an option. + +## native implementation + +- If the path is a root directory, then pass it to the underlying + implementation and return the result/error. (In this case, it'll either + succeed or fail, but we aren't actually creating any dirs.) +- Walk up the path statting each directory, to find the first path that + will be created, `made`. +- Call `fs.mkdir(path, { recursive: true })` (or `fs.mkdirSync`) +- If error, raise it to the caller. +- Return `made`. + +## manual implementation + +- Call underlying `fs.mkdir` implementation, with `recursive: false` +- If error: + - If path is a root directory, raise to the caller and do not handle it + - If ENOENT, mkdirp parent dir, store result as `made` + - stat(path) + - If error, raise original `mkdir` error + - If directory, return `made` + - Else, raise original `mkdir` error +- else + - return `undefined` if a root dir, or `made` if set, or `path` + +## windows vs unix caveat + +On Windows file systems, attempts to create a root directory (ie, a drive +letter or root UNC path) will fail. If the root directory exists, then it +will fail with `EPERM`. If the root directory does not exist, then it will +fail with `ENOENT`. + +On posix file systems, attempts to create a root directory (in recursive +mode) will succeed silently, as it is treated like just another directory +that already exists. (In non-recursive mode, of course, it fails with +`EEXIST`.) + +In order to preserve this system-specific behavior (and because it's not as +if we can create the parent of a root directory anyway), attempts to create +a root directory are passed directly to the `fs` implementation, and any +errors encountered are not handled. + +## native error caveat + +The native implementation (as of at least Node.js v13.4.0) does not provide +appropriate errors in some cases (see +[nodejs/node#31481](https://github.com/nodejs/node/issues/31481) and +[nodejs/node#28015](https://github.com/nodejs/node/issues/28015)). + +In order to work around this issue, the native implementation will fall +back to the manual implementation if an `ENOENT` error is encountered. + +# choosing a recursive mkdir implementation + +There are a few to choose from! Use the one that suits your needs best :D + +## use `fs.mkdir(path, {recursive: true}, cb)` if: + +- You wish to optimize performance even at the expense of other factors. +- You don't need to know the first dir created. +- You are ok with getting `ENOENT` as the error when some other problem is + the actual cause. +- You can limit your platforms to Node.js v10.12 and above. +- You're ok with using callbacks instead of promises. +- You don't need/want a CLI. +- You don't need to override the `fs` methods in use. + +## use this module (mkdirp 1.x) if: + +- You need to know the first directory that was created. +- You wish to use the native implementation if available, but fall back + when it's not. +- You prefer promise-returning APIs to callback-taking APIs. +- You want more useful error messages than the native recursive mkdir + provides (at least as of Node.js v13.4), and are ok with re-trying on + `ENOENT` to achieve this. +- You need (or at least, are ok with) a CLI. +- You need to override the `fs` methods in use. + +## use [`make-dir`](http://npm.im/make-dir) if: + +- You do not need to know the first dir created (and wish to save a few + `stat` calls when using the native implementation for this reason). +- You wish to use the native implementation if available, but fall back + when it's not. +- You prefer promise-returning APIs to callback-taking APIs. +- You are ok with occasionally getting `ENOENT` errors for failures that + are actually related to something other than a missing file system entry. +- You don't need/want a CLI. +- You need to override the `fs` methods in use. + +## use mkdirp 0.x if: + +- You need to know the first directory that was created. +- You need (or at least, are ok with) a CLI. +- You need to override the `fs` methods in use. +- You're ok with using callbacks instead of promises. +- You are not running on Windows, where the root-level ENOENT errors can + lead to infinite regress. +- You think vinyl just sounds warmer and richer for some weird reason. +- You are supporting truly ancient Node.js versions, before even the advent + of a `Promise` language primitive. (Please don't. You deserve better.) + +# cli + +This package also ships with a `mkdirp` command. + +``` +$ mkdirp -h + +usage: mkdirp [DIR1,DIR2..] {OPTIONS} + + Create each supplied directory including any necessary parent directories + that don't yet exist. + + If the directory already exists, do nothing. + +OPTIONS are: + + -m If a directory needs to be created, set the mode as an octal + --mode= permission string. + + -v --version Print the mkdirp version number + + -h --help Print this helpful banner + + -p --print Print the first directories created for each path provided + + --manual Use manual implementation, even if native is available +``` + +# install + +With [npm](http://npmjs.org) do: + +``` +npm install mkdirp +``` + +to get the library locally, or + +``` +npm install -g mkdirp +``` + +to get the command everywhere, or + +``` +npx mkdirp ... +``` + +to run the command without installing it globally. + +# platform support + +This module works on node v8, but only v10 and above are officially +supported, as Node v8 reached its LTS end of life 2020-01-01, which is in +the past, as of this writing. + +# license + +MIT diff --git a/node_modules/lv_font_conv/node_modules/ms/index.js b/node_modules/lv_font_conv/node_modules/ms/index.js new file mode 100644 index 00000000..c4498bcc --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/ms/index.js @@ -0,0 +1,162 @@ +/** + * Helpers. + */ + +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; +var w = d * 7; +var y = d * 365.25; + +/** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} [options] + * @throws {Error} throw an error if val is not a non-empty string or a number + * @return {String|Number} + * @api public + */ + +module.exports = function(val, options) { + options = options || {}; + var type = typeof val; + if (type === 'string' && val.length > 0) { + return parse(val); + } else if (type === 'number' && isFinite(val)) { + return options.long ? fmtLong(val) : fmtShort(val); + } + throw new Error( + 'val is not a non-empty string or a valid number. val=' + + JSON.stringify(val) + ); +}; + +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + str = String(str); + if (str.length > 100) { + return; + } + var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec( + str + ); + if (!match) { + return; + } + var n = parseFloat(match[1]); + var type = (match[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'yrs': + case 'yr': + case 'y': + return n * y; + case 'weeks': + case 'week': + case 'w': + return n * w; + case 'days': + case 'day': + case 'd': + return n * d; + case 'hours': + case 'hour': + case 'hrs': + case 'hr': + case 'h': + return n * h; + case 'minutes': + case 'minute': + case 'mins': + case 'min': + case 'm': + return n * m; + case 'seconds': + case 'second': + case 'secs': + case 'sec': + case 's': + return n * s; + case 'milliseconds': + case 'millisecond': + case 'msecs': + case 'msec': + case 'ms': + return n; + default: + return undefined; + } +} + +/** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtShort(ms) { + var msAbs = Math.abs(ms); + if (msAbs >= d) { + return Math.round(ms / d) + 'd'; + } + if (msAbs >= h) { + return Math.round(ms / h) + 'h'; + } + if (msAbs >= m) { + return Math.round(ms / m) + 'm'; + } + if (msAbs >= s) { + return Math.round(ms / s) + 's'; + } + return ms + 'ms'; +} + +/** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtLong(ms) { + var msAbs = Math.abs(ms); + if (msAbs >= d) { + return plural(ms, msAbs, d, 'day'); + } + if (msAbs >= h) { + return plural(ms, msAbs, h, 'hour'); + } + if (msAbs >= m) { + return plural(ms, msAbs, m, 'minute'); + } + if (msAbs >= s) { + return plural(ms, msAbs, s, 'second'); + } + return ms + ' ms'; +} + +/** + * Pluralization helper. + */ + +function plural(ms, msAbs, n, name) { + var isPlural = msAbs >= n * 1.5; + return Math.round(ms / n) + ' ' + name + (isPlural ? 's' : ''); +} diff --git a/node_modules/lv_font_conv/node_modules/ms/license.md b/node_modules/lv_font_conv/node_modules/ms/license.md new file mode 100644 index 00000000..69b61253 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/ms/license.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Zeit, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/ms/package.json b/node_modules/lv_font_conv/node_modules/ms/package.json new file mode 100644 index 00000000..6d514e71 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/ms/package.json @@ -0,0 +1,72 @@ +{ + "_args": [ + [ + "ms@2.1.2", + "/home/vitaly/Dropbox/Coding/lv_font_conv" + ] + ], + "_from": "ms@2.1.2", + "_id": "ms@2.1.2", + "_inBundle": false, + "_integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "_location": "/ms", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "ms@2.1.2", + "name": "ms", + "escapedName": "ms", + "rawSpec": "2.1.2", + "saveSpec": null, + "fetchSpec": "2.1.2" + }, + "_requiredBy": [ + "/debug" + ], + "_resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "_spec": "2.1.2", + "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", + "bugs": { + "url": "https://github.com/zeit/ms/issues" + }, + "description": "Tiny millisecond conversion utility", + "devDependencies": { + "eslint": "4.12.1", + "expect.js": "0.3.1", + "husky": "0.14.3", + "lint-staged": "5.0.0", + "mocha": "4.0.1" + }, + "eslintConfig": { + "extends": "eslint:recommended", + "env": { + "node": true, + "es6": true + } + }, + "files": [ + "index.js" + ], + "homepage": "https://github.com/zeit/ms#readme", + "license": "MIT", + "lint-staged": { + "*.js": [ + "npm run lint", + "prettier --single-quote --write", + "git add" + ] + }, + "main": "./index", + "name": "ms", + "repository": { + "type": "git", + "url": "git+https://github.com/zeit/ms.git" + }, + "scripts": { + "lint": "eslint lib/* bin/*", + "precommit": "lint-staged", + "test": "mocha tests.js" + }, + "version": "2.1.2" +} diff --git a/node_modules/lv_font_conv/node_modules/ms/readme.md b/node_modules/lv_font_conv/node_modules/ms/readme.md new file mode 100644 index 00000000..9a1996b1 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/ms/readme.md @@ -0,0 +1,60 @@ +# ms + +[![Build Status](https://travis-ci.org/zeit/ms.svg?branch=master)](https://travis-ci.org/zeit/ms) +[![Join the community on Spectrum](https://withspectrum.github.io/badge/badge.svg)](https://spectrum.chat/zeit) + +Use this package to easily convert various time formats to milliseconds. + +## Examples + +```js +ms('2 days') // 172800000 +ms('1d') // 86400000 +ms('10h') // 36000000 +ms('2.5 hrs') // 9000000 +ms('2h') // 7200000 +ms('1m') // 60000 +ms('5s') // 5000 +ms('1y') // 31557600000 +ms('100') // 100 +ms('-3 days') // -259200000 +ms('-1h') // -3600000 +ms('-200') // -200 +``` + +### Convert from Milliseconds + +```js +ms(60000) // "1m" +ms(2 * 60000) // "2m" +ms(-3 * 60000) // "-3m" +ms(ms('10 hours')) // "10h" +``` + +### Time Format Written-Out + +```js +ms(60000, { long: true }) // "1 minute" +ms(2 * 60000, { long: true }) // "2 minutes" +ms(-3 * 60000, { long: true }) // "-3 minutes" +ms(ms('10 hours'), { long: true }) // "10 hours" +``` + +## Features + +- Works both in [Node.js](https://nodejs.org) and in the browser +- If a number is supplied to `ms`, a string with a unit is returned +- If a string that contains the number is supplied, it returns it as a number (e.g.: it returns `100` for `'100'`) +- If you pass a string with a number and a valid unit, the number of equivalent milliseconds is returned + +## Related Packages + +- [ms.macro](https://github.com/knpwrs/ms.macro) - Run `ms` as a macro at build-time. + +## Caught a Bug? + +1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device +2. Link the package to the global module directory: `npm link` +3. Within the module you want to test your local development instance of ms, just link it to the dependencies: `npm link ms`. Instead of the default one from npm, Node.js will now use your clone of ms! + +As always, you can run the tests using: `npm test` diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/LICENSE b/node_modules/lv_font_conv/node_modules/opentype.js/LICENSE new file mode 100644 index 00000000..c9b39953 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/opentype.js/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2017 Frederik De Bleser + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/README.md b/node_modules/lv_font_conv/node_modules/opentype.js/README.md new file mode 100644 index 00000000..bcb557e8 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/opentype.js/README.md @@ -0,0 +1,313 @@ + +# opentype.js · [![Build Status](https://img.shields.io/travis/opentypejs/opentype.js.svg?style=flat-square)](https://travis-ci.org/opentypejs/opentype.js) [![npm](https://img.shields.io/npm/v/opentype.js.svg?style=flat-square)](https://www.npmjs.com/package/opentype.js) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://github.com/opentypejs/opentype.js/blob/master/LICENSE) [![david-dm](https://david-dm.org/opentypejs/opentype.js.svg)](https://david-dm.org/opentypejs/opentype.js) [![Gitter](https://badges.gitter.im/opentypejs/opentype.js.svg)](https://gitter.im/opentypejs/opentype.js) + +opentype.js is a JavaScript parser and writer for TrueType and OpenType fonts. + +It gives you access to the letterforms of text from the browser or Node.js. +See [https://opentype.js.org/](https://opentype.js.org/) for a live demo. + +Features +======== + +* Create a bézier path out of a piece of text. +* Support for composite glyphs (accented letters). +* Support for WOFF, OTF, TTF (both with TrueType `glyf` and PostScript `cff` outlines) +* Support for kerning (Using GPOS or the kern table). +* Support for ligatures. +* Support for TrueType font hinting. +* Support arabic text rendering (See issue #364 & PR #359 #361) +* A low memory mode is available as an option (see #329) +* Runs in the browser and Node.js. + +Installation +============ + +### Using [npm](http://npmjs.org/) package manager + + npm install opentype.js + + const opentype = require('opentype.js'); + + import opentype from 'opentype.js' + + import { load } from 'opentype.js' + +Using TypeScript? [See this example](examples/typescript) + +Note: OpenType.js uses ES6-style imports, so if you want to edit it and debug it in Node.js run `npm run build` first and use `npm run watch` to automatically rebuild when files change. + +### Directly + +[Download the latest ZIP](https://github.com/opentypejs/opentype.js/archive/master.zip) and grab the files in the `dist` +folder. These are compiled. + +### Using via a CDN + +To use via a CDN, include the following code in your html: + + + +### Using Bower (Deprecated [see official post](https://bower.io/blog/2017/how-to-migrate-away-from-bower/)) + +To install using [Bower](https://bower.io/), enter the following command in your project directory: + + bower install opentype.js + +You can then include them in your scripts using: + + + + +API +=== +### Loading a font +![OpenType.js example Hello World](https://raw.github.com/opentypejs/opentype.js/master/g/hello-world.png) + +Use `opentype.load(url, callback)` to load a font from a URL. Since this method goes out the network, it is asynchronous. +The callback gets `(err, font)` where `font` is a `Font` object. Check if the `err` is null before using the font. +```javascript +opentype.load('fonts/Roboto-Black.ttf', function(err, font) { + if (err) { + alert('Font could not be loaded: ' + err); + } else { + // Now let's display it on a canvas with id "canvas" + const ctx = document.getElementById('canvas').getContext('2d'); + + // Construct a Path object containing the letter shapes of the given text. + // The other parameters are x, y and fontSize. + // Note that y is the position of the baseline. + const path = font.getPath('Hello, World!', 0, 150, 72); + + // If you just want to draw the text you can also use font.draw(ctx, text, x, y, fontSize). + path.draw(ctx); + } +}); +``` + +You can also use `es6 async/await` syntax to load your fonts + +```javascript +async function make(){ + const font = await opentype.load('fonts/Roboto-Black.ttf'); + const path = font.getPath('Hello, World!', 0, 150, 72); + console.log(path); +} +``` + +If you already have an `ArrayBuffer`, you can use `opentype.parse(buffer)` to parse the buffer. This method always +returns a Font, but check `font.supported` to see if the font is in a supported format. (Fonts can be marked unsupported +if they have encoding tables we can't read). + + const font = opentype.parse(myBuffer); + +### Loading a font synchronously (Node.js) +Use `opentype.loadSync(url)` to load a font from a file and return a `Font` object. +Throws an error if the font could not be parsed. This only works in Node.js. + + const font = opentype.loadSync('fonts/Roboto-Black.ttf'); + +### Writing a font +Once you have a `Font` object (either by using `opentype.load` or by creating a new one from scratch) you can write it +back out as a binary file. + +In the browser, you can use `Font.download()` to instruct the browser to download a binary .OTF file. The name is based +on the font name. +```javascript +// Create the bézier paths for each of the glyphs. +// Note that the .notdef glyph is required. +const notdefGlyph = new opentype.Glyph({ + name: '.notdef', + unicode: 0, + advanceWidth: 650, + path: new opentype.Path() +}); + +const aPath = new opentype.Path(); +aPath.moveTo(100, 0); +aPath.lineTo(100, 700); +// more drawing instructions... +const aGlyph = new opentype.Glyph({ + name: 'A', + unicode: 65, + advanceWidth: 650, + path: aPath +}); + +const glyphs = [notdefGlyph, aGlyph]; +const font = new opentype.Font({ + familyName: 'OpenTypeSans', + styleName: 'Medium', + unitsPerEm: 1000, + ascender: 800, + descender: -200, + glyphs: glyphs}); +font.download(); +``` + +If you want to inspect the font, use `font.toTables()` to generate an object showing the data structures that map +directly to binary values. If you want to get an `ArrayBuffer`, use `font.toArrayBuffer()`. + + +### The Font object +A Font represents a loaded OpenType font file. It contains a set of glyphs and methods to draw text on a drawing context, or to get a path representing the text. + +* `glyphs`: an indexed list of Glyph objects. +* `unitsPerEm`: X/Y coordinates in fonts are stored as integers. This value determines the size of the grid. Common values are 2048 and 4096. +* `ascender`: Distance from baseline of highest ascender. In font units, not pixels. +* `descender`: Distance from baseline of lowest descender. In font units, not pixels. + +#### `Font.getPath(text, x, y, fontSize, options)` +Create a Path that represents the given text. +* `x`: Horizontal position of the beginning of the text. (default: 0) +* `y`: Vertical position of the *baseline* of the text. (default: 0) +* `fontSize`: Size of the text in pixels (default: 72). + +Options is an optional object containing: +* `kerning`: if true takes kerning information into account (default: true) +* `features`: an object with [OpenType feature tags](https://docs.microsoft.com/en-us/typography/opentype/spec/featuretags) as keys, and a boolean value to enable each feature. +Currently only ligature features "liga" and "rlig" are supported (default: true). +* `hinting`: if true uses TrueType font hinting if available (default: false). + +_Note: there is also `Font.getPaths` with the same arguments which returns a list of Paths._ + +#### `Font.draw(ctx, text, x, y, fontSize, options)` +Create a Path that represents the given text. +* `ctx`: A 2D drawing context, like Canvas. +* `x`: Horizontal position of the beginning of the text. (default: 0) +* `y`: Vertical position of the *baseline* of the text. (default: 0) +* `fontSize`: Size of the text in pixels (default: 72). + +Options is an optional object containing: +* `kerning`: if true takes kerning information into account (default: true) +* `features`: an object with [OpenType feature tags](https://docs.microsoft.com/en-us/typography/opentype/spec/featuretags) as keys, and a boolean value to enable each feature. +Currently only ligature features "liga" and "rlig" are supported (default: true). +* `hinting`: if true uses TrueType font hinting if available (default: false). + +#### `Font.drawPoints(ctx, text, x, y, fontSize, options)` +Draw the points of all glyphs in the text. On-curve points will be drawn in blue, off-curve points will be drawn in red. The arguments are the same as `Font.draw`. + +#### `Font.drawMetrics(ctx, text, x, y, fontSize, options)` +Draw lines indicating important font measurements for all glyphs in the text. +Black lines indicate the origin of the coordinate system (point 0,0). +Blue lines indicate the glyph bounding box. +Green line indicates the advance width of the glyph. + +#### `Font.stringToGlyphs(string)` +Convert the string to a list of glyph objects. +Note that there is no strict 1-to-1 correspondence between the string and glyph list due to +possible substitutions such as ligatures. The list of returned glyphs can be larger or smaller than the length of the given string. + +#### `Font.charToGlyph(char)` +Convert the character to a `Glyph` object. Returns null if the glyph could not be found. Note that this function assumes that there is a one-to-one mapping between the given character and a glyph; for complex scripts this might not be the case. + +#### `Font.getKerningValue(leftGlyph, rightGlyph)` +Retrieve the value of the [kerning pair](https://en.wikipedia.org/wiki/Kerning) between the left glyph (or its index) and the right glyph (or its index). If no kerning pair is found, return 0. The kerning value gets added to the advance width when calculating the spacing between glyphs. + +#### `Font.getAdvanceWidth(text, fontSize, options)` +Returns the advance width of a text. + +This is something different than Path.getBoundingBox() as for example a +suffixed whitespace increases the advancewidth but not the bounding box +or an overhanging letter like a calligraphic 'f' might have a quite larger +bounding box than its advance width. + +This corresponds to canvas2dContext.measureText(text).width +* `fontSize`: Size of the text in pixels (default: 72). +* `options`: See Font.getPath + +#### The Glyph object +A Glyph is an individual mark that often corresponds to a character. Some glyphs, such as ligatures, are a combination of many characters. Glyphs are the basic building blocks of a font. + +* `font`: A reference to the `Font` object. +* `name`: The glyph name (e.g. "Aring", "five") +* `unicode`: The primary unicode value of this glyph (can be `undefined`). +* `unicodes`: The list of unicode values for this glyph (most of the time this will be 1, can also be empty). +* `index`: The index number of the glyph. +* `advanceWidth`: The width to advance the pen when drawing this glyph. +* `xMin`, `yMin`, `xMax`, `yMax`: The bounding box of the glyph. +* `path`: The raw, unscaled path of the glyph. + +##### `Glyph.getPath(x, y, fontSize)` +Get a scaled glyph Path object we can draw on a drawing context. +* `x`: Horizontal position of the glyph. (default: 0) +* `y`: Vertical position of the *baseline* of the glyph. (default: 0) +* `fontSize`: Font size in pixels (default: 72). + +##### `Glyph.getBoundingBox()` +Calculate the minimum bounding box for the unscaled path of the given glyph. Returns an `opentype.BoundingBox` object that contains x1/y1/x2/y2. +If the glyph has no points (e.g. a space character), all coordinates will be zero. + +##### `Glyph.draw(ctx, x, y, fontSize)` +Draw the glyph on the given context. +* `ctx`: The drawing context. +* `x`: Horizontal position of the glyph. (default: 0) +* `y`: Vertical position of the *baseline* of the glyph. (default: 0) +* `fontSize`: Font size, in pixels (default: 72). + +##### `Glyph.drawPoints(ctx, x, y, fontSize)` +Draw the points of the glyph on the given context. +On-curve points will be drawn in blue, off-curve points will be drawn in red. +The arguments are the same as `Glyph.draw`. + +##### `Glyph.drawMetrics(ctx, x, y, fontSize)` +Draw lines indicating important font measurements for all glyphs in the text. +Black lines indicate the origin of the coordinate system (point 0,0). +Blue lines indicate the glyph bounding box. +Green line indicates the advance width of the glyph. +The arguments are the same as `Glyph.draw`. + +### The Path object +Once you have a path through `Font.getPath` or `Glyph.getPath`, you can use it. + +* `commands`: The path commands. Each command is a dictionary containing a type and coordinates. See below for examples. +* `fill`: The fill color of the `Path`. Color is a string representing a [CSS color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value). (default: 'black') +* `stroke`: The stroke color of the `Path`. Color is a string representing a [CSS color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value). (default: `null`: the path will not be stroked) +* `strokeWidth`: The line thickness of the `Path`. (default: 1, but since the `stroke` is null no stroke will be drawn) + +##### `Path.draw(ctx)` +Draw the path on the given 2D context. This uses the `fill`, `stroke` and `strokeWidth` properties of the `Path` object. +* `ctx`: The drawing context. + +##### `Path.getBoundingBox()` +Calculate the minimum bounding box for the given path. Returns an `opentype.BoundingBox` object that contains x1/y1/x2/y2. +If the path is empty (e.g. a space character), all coordinates will be zero. + +##### `Path.toPathData(decimalPlaces)` +Convert the Path to a string of path data instructions. +See https://www.w3.org/TR/SVG/paths.html#PathData +* `decimalPlaces`: The amount of decimal places for floating-point values. (default: 2) + +##### `Path.toSVG(decimalPlaces)` +Convert the path to a SVG <path> element, as a string. +* `decimalPlaces`: The amount of decimal places for floating-point values. (default: 2) + +#### Path commands +* **Move To**: Move to a new position. This creates a new contour. Example: `{type: 'M', x: 100, y: 200}` +* **Line To**: Draw a line from the previous position to the given coordinate. Example: `{type: 'L', x: 100, y: 200}` +* **Curve To**: Draw a bézier curve from the current position to the given coordinate. Example: `{type: 'C', x1: 0, y1: 50, x2: 100, y2: 200, x: 100, y: 200}` +* **Quad To**: Draw a quadratic bézier curve from the current position to the given coordinate. Example: `{type: 'Q', x1: 0, y1: 50, x: 100, y: 200}` +* **Close**: Close the path. If stroked, this will draw a line from the first to the last point of the contour. Example: `{type: 'Z'}` + + +## Versioning + +We use [SemVer](https://semver.org/) for versioning. + + +## License + +MIT + + +Thanks +====== +I would like to acknowledge the work of others without which opentype.js wouldn't be possible: + +* [pdf.js](https://mozilla.github.io/pdf.js/): for an awesome implementation of font parsing in the browser. +* [FreeType](https://www.freetype.org/): for the nitty-gritty details and filling in the gaps when the spec was incomplete. +* [ttf.js](https://ynakajima.github.io/ttf.js/demo/glyflist/): for hints about the TrueType parsing code. +* [CFF-glyphlet-fonts](https://pomax.github.io/CFF-glyphlet-fonts/): for a great explanation/implementation of CFF font writing. +* [tiny-inflate](https://github.com/foliojs/tiny-inflate): for WOFF decompression. +* [Microsoft Typography](https://docs.microsoft.com/en-us/typography/opentype/spec/otff): the go-to reference for all things OpenType. +* [Adobe Compact Font Format spec](http://download.microsoft.com/download/8/0/1/801a191c-029d-4af3-9642-555f6fe514ee/cff.pdf) and the [Adobe Type 2 Charstring spec](http://download.microsoft.com/download/8/0/1/801a191c-029d-4af3-9642-555f6fe514ee/type2.pdf): explains the data structures and commands for the CFF glyph format. +* All contributing authors mentioned in the [AUTHORS](https://github.com/opentypejs/opentype.js/blob/master/AUTHORS.md) file. diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/RELEASES.md b/node_modules/lv_font_conv/node_modules/opentype.js/RELEASES.md new file mode 100644 index 00000000..e1c698b1 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/opentype.js/RELEASES.md @@ -0,0 +1,267 @@ +1.3.3 (April 20, 2020) +===================== +* fix GlyphOptions with falsy values (#430) + +1.3.2 (April 20, 2020) +===================== +* Re-export named exports with a default export and add a TypeScript import example + +* 1.3.1 (April 13, 2020) +===================== +* Revert Fix Path.toPathData and Path.toSVG - X Axis is flipped (#369) + +1.3.0 (April 13, 2020) +===================== +* Forward os2 Table attributs during font construction (#422) +* Add default export + +1.2.1 (April 13, 2020) +===================== +* Fix Path.toPathData and Path.toSVG - X Axis is flipped (#369) +* Fix use of Promise / async/await in the load function (#427) +* Fix a bug for unsupported SUBSTITUTIONS #403 + +1.2.0 (April 13, 2020) +===================== +* Fix issue #385, merge default options with user options (#386) +* Adds support for browser Async/Await for .load() (#389) +* Introduce ES6 module build (#391) +* Fix test in featureQuery +* Remove Node 4 from Travis (#392) +* Update dependencies & build dist files + +1.1.0 (May 1, 2019) +===================== +* Support reading GSUB Single substitution format 1 (PR #382) (thanks @solomancode!) + +1.0.1 (April 19, 2019) +===================== +* Fix error if defaultLangSys is undefined (Issue #378) + +1.0.0 (April 17, 2019) +===================== +* Render arabic rtl text properly (PR #361, partial fix of #364) (thanks @solomancode!) +* #361 introduced a breaking change to `Font.prototype.defaultRenderOptions` +Before +```js +Font.prototype.defaultRenderOptions = { + kerning: true, + features: { + liga: true, + rlig: true + } +}; +``` + +Now +```js +Font.prototype.defaultRenderOptions = { + kerning: true, + features: [ + /** + * these 4 features are required to render Arabic text properly + * and shouldn't be turned off when rendering arabic text. + */ + { script: 'arab', tags: ['init', 'medi', 'fina', 'rlig'] }, + { script: 'latn', tags: ['liga', 'rlig'] } + ] +}; +``` + +Also as this project is now using SemVer, the breaking change required a new major version, 1.0.0! + +0.12.0 (April 17, 2019) +===================== +* Fix Glyph.getPath() issue (PR #362, fixes #363) (thanks @solomancode!) +* Add lowMemory mode (PR #329) (thanks @debussy2k!) +* Update README (PR #377) (thanks @jolg42!) + +0.11.0 (October 22, 2018) +===================== +* Support Arabic text rendering (PR #359, fixes #46) (thanks @solomancode!) + +0.10.0 (August 14, 2018) +===================== +* font.download(): use window.URL instead of window.requestFileSystem, which works on a larger set of browsers : Chrome (32+), Opera (19+), Firefox (26+), Safari (7.1+), and all of Edge. + +0.9.0 (June 21, 2018) +===================== +* Update/Migrate rollup, update all dependencies, add package-lock.json and fix circular dependency (thanks @jolg42!) +* Parse cmap table with platform id 0 as well (PR #350, fixes #348) (thanks @moyogo!) +* Prevent auto-generated postScriptName from containing whitespace (#339) (thanks @mqudsi!) +* Support non-Basic-Multilingual-Plane (BMP) characters (#338) (thanks @antonytse!) +* GPOS: display correct error message in some cases of malformed data (#336) (thanks @fpirsch!) +* Restore simple GPOS kerning in font.getKerningValue (#335) (thanks @fpirsch!) +* Fix duplicated lineTo when using `getPath` (#328) (thanks @jolg42!) +* Change example generate-font-node.js to be compatible with any Node.js version (thanks @jolg42!) + +0.8.0 (March 6, 2018) +===================== +* Fix loading font file on Android devices (thanks @maoamid!). +* Fix loading fonts from a local source (file://data/... for Android for example (thanks @IntuilabGit!). +* Fixing 2 issues when hinting "mutlu.ttf" (thanks @axkibe!). +* Add some support for OpenType font variations (thanks @taylorb-monotype!). +* Make cmap table format 12 if needed (thanks @Jolg42!). +* Enable uglify's mangle and compress optimizations for a ~30% smaller minified file. (thanks @lojjic & @Jolg42!). +* Better parsing of NULL pointers (thanks @fpirsch!). +* Fix bad path init (empty glyphs) (thanks @fpirsch!). +* Rewrite GPOS parsing (thanks @fpirsch!). +* Roboto-Black.ttf updated (thanks @Jolg42!). + +0.7.3 (July 18, 2017) +===================== +* Fix "Object x already has key" error in Safari (thanks @neiltron!). +* Fixed a bug where Font.getPaths() didn't pass options (thanks @keeslinp!). + +0.7.2 (June 7, 2017) +==================== +* WOFF fonts with cvt tables now parse correctly. +* Migrated to ES6 modules and let/const. +* Use Rollup to bundle the JavaScript. + +0.7.1 (Apr 25, 2017) +==================== +* Auto-generated glyph IDs (CID-keyed fonts) are now prefixed with "gid", e.g. "gid42". +* Fix ligature substitution for fonts with coverage table format 2. +* Better error messages when no valid cmap is found. + +0.7.0 (Apr 25, 2017) +==================== +* Add font hinting (thanks @axkibe!) +* Add support for CID-keyed fonts, thanks to @tshinnic. +* TrueType fonts with signature 'true' or 'typ1' are also supported. +* Fixing rounding issues. +* Add GSUB and kern output in font-inspector. +* Add font loading error callback. +* Dev server turns browser caching off. +* Add encoding support for variation adjustment deltas (thanks @brawer!). + +0.6.9 (Jan 17, 2017) +==================== +* Add ligature rendering (thanks @fpirsch!) + +0.6.8 (Jan 9, 2017) +========================= +* Add a `getBoundingBox` method to the `Path` and `Glyph` objects. + +0.6.7 (Jan 5, 2017) +========================= +* Add basic support for Mac OS X format kern tables. + +0.6.6 (October 25, 2016) +========================= +* Add support for letter-spacing and tracking (thanks @lachmanski!). +* Fixed a bug in the nameToGlyph function. + +0.6.5 (September 9, 2016) +========================= +* GSUB reading and writing by @fpirsch. This is still missing a user-friendly API. +* Add support for cmap table format 12, which enables support for Unicode characters outside of the 0x0 - 0xFFFF range. +* Better API documentation using [JSDoc](http://usejsdoc.org/). +* Accessing xMin/... metrics works before path load.
 + +0.6.4 (June 30, 2016) +========================= +* Add X/Y scale options to compute a streched path of a glyph. +* Correct reading/writing of font timestamps. +* examples/generate-font-node.js now generates "full" Latin font. +* Add OS/2 value options for weight, width and fsSelection. + +0.6.3 (May 10, 2016) +========================= +* Wrapped parseBuffer in a try/catch so it doesn't throw exceptions. Thanks @rBurgett! +* Fix a leaking global variable. Thanks @cuixiping! + +0.6.2 (March 11, 2016) +========================= +* Improve table writing to support nested subtables. Thanks @fpirsch! + +0.6.1 (February 20, 2016) +========================= +* Left side bearing is now correctly reported. +* Simplified code for including ascender / descender values. + +0.6.0 (December 1, 2015) +======================== +* Improvements to font writing: generated fonts now work properly on OS X. +* When creating a new font, ascender and descender are now required. + +0.5.1 (October 26, 2015) +======================== +* Add `Font.getPaths()` which returns a list of paths. + +0.5.0 (October 6, 2015) +======================= +* Read support for WOFF. + +0.4.11 (September 27, 2015) +=========================== +* Fix issue with loading of TrueType composite glyphs. +* Fix issue with missing hmtx values. +* Sensible getMetrics() values for empty glyphs (e.g. space). + +0.4.10 (July 30, 2015) +====================== +* Add loadSync method for Node.js. +* Unit tests for basic types and tables. +* Implement MACSTRING codec. +* Support multilingual names. +* Handle names of font variation axes and instances. + +0.4.9 (June 23, 2015) +===================== +* Improve memory usage by deferring glyph / path loading. Thanks @Pomax! +* Put examples in the "examples" directory. Use the local web server to see them. + +0.4.8 (June 3, 2015) +==================== +* Fix an issue with writing out fonts that have an UPM != 1000. + +0.4.6 (March 26, 2015) +====================== +* Fix issues with exporting/subsetting TrueType fonts. +* Improve validness of exported fonts. +* Empty paths (think: space) no longer contain a single closePath command. +* Fix issues with exporting fonts with TrueType half-point values. +* Expose the internal byte parsing algorithms as opentype._parse. + +0.4.5 (March 10, 2015) +====================== +* Add support for writing quad curves. +* Add support for CFF flex operators. +* Close CFF subpaths. + +0.4.4 (Dec 8, 2014) +=================== +* Solve issues with Browserify. + +0.4.3 (Nov 26, 2014) +==================== +* Un-break node.js support. + +0.4.2 (Nov 24, 2014) +==================== +* 2x speedup when writing fonts, thanks @louisremi! + +0.4.1 (Nov 10, 2014) +==================== +* Fix bug that prevented `npm install`. + +0.4.0 (Nov 10, 2014) +==================== +* Add support for font writing. + +0.3.0 (Jun 10, 2014) +==================== +* Support for GPOS kerning, which works in both PostScript and OpenType. +* Big performance improvements. +* The font and glyph inspector can visually debug a font. + +0.2.0 (Feb 7, 2014) +=================== +* Support for reading PostScript fonts. + +0.1.0 (Sep 27, 2013) +==================== +* Initial release. +* Supports reading TrueType CFF fonts. diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/bin/ot b/node_modules/lv_font_conv/node_modules/opentype.js/bin/ot new file mode 100755 index 00000000..af990cd2 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/opentype.js/bin/ot @@ -0,0 +1,84 @@ +#!/usr/bin/env node +/* eslint no-console: off */ + +import fs from 'fs'; +import path from 'path'; +import { load } from '../src/opentype'; + +// Print out information about the font on the console. +function printFontInfo(font) { + console.log(' glyphs:', font.glyphs.length); + console.log(' kerning pairs (kern table):', Object.keys(font.kerningPairs).length); + console.log(' kerning pairs (GPOS table):', (font.getGposKerningValue) ? 'yes' : 'no'); +} + +// Recursively walk a directory and execute the function for every file. +function walk(dir, fn) { + var files, i, file; + files = fs.readdirSync(dir); + for (i = 0; i < files.length; i += 1) { + file = files[i]; + var fullName = path.join(dir, file); + var stat = fs.statSync(fullName); + if (stat.isFile()) { + fn(fullName); + } else if (stat.isDirectory()) { + walk(fullName, fn); + } + } +} + +// Print out usage information. +function printUsage() { + console.log('Usage: ot command [dir|file]'); + console.log(); + console.log('Commands:'); + console.log(); + console.log(' info Get information of specified font or fonts in the specified directory.'); + console.log(); +} + +function fileInfo(file) { + load(file, function(err, font) { + console.log(path.basename(file)); + if (err) { + console.log(' (Error: ' + err + ')'); + } else if (!font.supported) { + console.log(' (Unsupported)'); + } else { + printFontInfo(font); + } + }); +} + +function recursiveInfo(fontDirectory) { + walk(fontDirectory, function(file) { + var ext = path.extname(file).toLowerCase(); + if (ext === '.ttf' || ext === '.otf') { + fileInfo(file); + } + }); +} + +if (process.argv.length < 3) { + printUsage(); +} else { + var command = process.argv[2]; + if (command === 'info') { + var fontpath = process.argv.length === 3 ? '.' : process.argv[3]; + if (fs.existsSync(fontpath)) { + var ext = path.extname(fontpath).toLowerCase(); + if (fs.statSync(fontpath).isDirectory()) { + recursiveInfo(fontpath); + } else if (ext === '.ttf' || ext === '.otf') { + fileInfo(fontpath); + } else { + printUsage(); + } + } else { + console.log('Path not found'); + } + } else { + printUsage(); + } +} diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/bin/server.js b/node_modules/lv_font_conv/node_modules/opentype.js/bin/server.js new file mode 100755 index 00000000..f6f450a4 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/opentype.js/bin/server.js @@ -0,0 +1,64 @@ +#!/usr/bin/env node + +var fs = require('fs'); +var http = require('http'); +var path = require('path'); +var rollup = require('rollup'); +var rollupConfig = require('../rollup.config'); + +var CONTENT_TYPES = { + '.html': 'text/html', + '.css': 'text/css', + '.png': 'image/png', + '.js': 'text/javascript', + '.ttf': 'font/otf', + '.otf': 'font/otf', + '.woff': 'font/woff', + '.woff2': 'font/woff2', +}; + +http.createServer(function(req, res) { + var rewrite = ''; + var url = req.url.substring(1); + if (url.length === 0) { + url = 'index.html'; + rewrite = ' -> ' + url; + } + + console.log('HTTP', req.url, rewrite); + var filePath = './' + url; + fs.readFile(filePath, function(err, data) { + if (err) { + res.writeHead(404, {'Content-Type': 'text/plain'}); + res.end('Error: ' + err); + } else { + var contentType = CONTENT_TYPES[path.extname(filePath)] || 'text/plain'; + res.writeHead(200, { + 'Content-Type': contentType, + 'Cache-Control': 'max-age=0' + }); + res.end(data); + } + }); +}).listen(8080); +console.log('Server running at http://localhost:8080/'); + +// Watch changes and rebundle +var watcher = rollup.watch(rollupConfig); +watcher.on('event', e => { + // event.code can be one of: + // START — the watcher is (re)starting + // BUNDLE_START — building an individual bundle + // BUNDLE_END — finished building a bundle + // END — finished building all bundles + // ERROR — encountered an error while bundling + // FATAL — encountered an unrecoverable error + + if (e.code === 'BUNDLE_START') { + console.log('Bundling...'); + } else if (e.code === 'BUNDLE_END') { + console.log('Bundled in ' + e.duration + 'ms.'); + } else if (e.code === 'ERROR' || e.code === 'FATAL') { + console.error(e.error); + } +}); diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/bin/test-render b/node_modules/lv_font_conv/node_modules/opentype.js/bin/test-render new file mode 100755 index 00000000..4bfc31e6 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/opentype.js/bin/test-render @@ -0,0 +1,96 @@ +#!/usr/bin/env node +// This is a command to test the text rendering compliance of OpenType.js. +// It is designed to operate with https://github.com/unicode-org/text-rendering-tests. +// +// Call it like this: +// +// ./bin/test-render --font=fonts/FiraSansOT-Medium.otf --testcase=TEST-1 --render=BALL +// +// The output will look like this: +// +// +// +// +// +// +// +// +// +// +// +// +// When viewing the SVG, it will be upside-down (since glyphs are designed Y-up). + +var opentype = require('../dist/opentype.js'); + +const SVG_FOOTER = ``; + +function printUsage() { + console.log('Usage: test-render --font=filename.otf --testcase=TEST_NAME --render=TEXT_TO_RENDER'); + console.log('This commands output the text to render as an SVG file.'); + console.log(); +} + +let filename; +let testcase; +let textToRender; +for (let i = 0; i < process.argv.length; i++) { + const arg = process.argv[i]; + if (arg.startsWith('--font=')) { + filename = arg.substring('--font='.length); + } else if (arg.startsWith('--testcase=')) { + testcase = arg.substring('--testcase='.length); + } else if (arg.startsWith('--render=')) { + textToRender = arg.substring('--render='.length); + } +} + +if (filename === undefined || testcase === undefined || textToRender === undefined) { + printUsage(); + process.exit(1); +} + +function renderSVG() { + var font = opentype.loadSync(filename); + + let svgSymbols = []; + let svgBody = []; + + var glyphSet = new Set(); + let x = 0; + const glyphs = font.stringToGlyphs(textToRender); + for (let i = 0; i < glyphs.length; i++) { + const glyph = glyphs[i]; + const symbolId = testcase + '.' + glyph.name; + if (!glyphSet.has(glyph)) { + glyphSet.add(glyph); + const svgPath = glyph.path.toSVG(); + svgSymbols.push(` ${svgPath}`); + } + svgBody.push(` `); + x += glyph.advanceWidth; + } + + let minX = 0; + let minY = Math.round(font.descender); + let width = Math.round(x); + let height = Math.round(font.ascender - font.descender); + let svgHeader = ` +`; + + return svgHeader + svgSymbols.join('\n') + svgBody.join('\n') + SVG_FOOTER; +} + +try { + var svg = renderSVG(); + console.log(svg); +} catch(e) { + console.error(e.stack); + process.exit(1); +} diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/dist/opentype.js b/node_modules/lv_font_conv/node_modules/opentype.js/dist/opentype.js new file mode 100644 index 00000000..11b0a548 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/opentype.js/dist/opentype.js @@ -0,0 +1,14254 @@ +/** + * https://opentype.js.org v1.3.3 | (c) Frederik De Bleser and other contributors | MIT License | Uses tiny-inflate by Devon Govett and string.prototype.codepointat polyfill by Mathias Bynens + */ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (global = global || self, factory(global.opentype = {})); +}(this, (function (exports) { 'use strict'; + + /*! https://mths.be/codepointat v0.2.0 by @mathias */ + if (!String.prototype.codePointAt) { + (function() { + var defineProperty = (function() { + // IE 8 only supports `Object.defineProperty` on DOM elements + try { + var object = {}; + var $defineProperty = Object.defineProperty; + var result = $defineProperty(object, object, object) && $defineProperty; + } catch(error) {} + return result; + }()); + var codePointAt = function(position) { + if (this == null) { + throw TypeError(); + } + var string = String(this); + var size = string.length; + // `ToInteger` + var index = position ? Number(position) : 0; + if (index != index) { // better `isNaN` + index = 0; + } + // Account for out-of-bounds indices: + if (index < 0 || index >= size) { + return undefined; + } + // Get the first code unit + var first = string.charCodeAt(index); + var second; + if ( // check if it’s the start of a surrogate pair + first >= 0xD800 && first <= 0xDBFF && // high surrogate + size > index + 1 // there is a next code unit + ) { + second = string.charCodeAt(index + 1); + if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate + // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; + } + } + return first; + }; + if (defineProperty) { + defineProperty(String.prototype, 'codePointAt', { + 'value': codePointAt, + 'configurable': true, + 'writable': true + }); + } else { + String.prototype.codePointAt = codePointAt; + } + }()); + } + + var TINF_OK = 0; + var TINF_DATA_ERROR = -3; + + function Tree() { + this.table = new Uint16Array(16); /* table of code length counts */ + this.trans = new Uint16Array(288); /* code -> symbol translation table */ + } + + function Data(source, dest) { + this.source = source; + this.sourceIndex = 0; + this.tag = 0; + this.bitcount = 0; + + this.dest = dest; + this.destLen = 0; + + this.ltree = new Tree(); /* dynamic length/symbol tree */ + this.dtree = new Tree(); /* dynamic distance tree */ + } + + /* --------------------------------------------------- * + * -- uninitialized global data (static structures) -- * + * --------------------------------------------------- */ + + var sltree = new Tree(); + var sdtree = new Tree(); + + /* extra bits and base tables for length codes */ + var length_bits = new Uint8Array(30); + var length_base = new Uint16Array(30); + + /* extra bits and base tables for distance codes */ + var dist_bits = new Uint8Array(30); + var dist_base = new Uint16Array(30); + + /* special ordering of code length codes */ + var clcidx = new Uint8Array([ + 16, 17, 18, 0, 8, 7, 9, 6, + 10, 5, 11, 4, 12, 3, 13, 2, + 14, 1, 15 + ]); + + /* used by tinf_decode_trees, avoids allocations every call */ + var code_tree = new Tree(); + var lengths = new Uint8Array(288 + 32); + + /* ----------------------- * + * -- utility functions -- * + * ----------------------- */ + + /* build extra bits and base tables */ + function tinf_build_bits_base(bits, base, delta, first) { + var i, sum; + + /* build bits table */ + for (i = 0; i < delta; ++i) { bits[i] = 0; } + for (i = 0; i < 30 - delta; ++i) { bits[i + delta] = i / delta | 0; } + + /* build base table */ + for (sum = first, i = 0; i < 30; ++i) { + base[i] = sum; + sum += 1 << bits[i]; + } + } + + /* build the fixed huffman trees */ + function tinf_build_fixed_trees(lt, dt) { + var i; + + /* build fixed length tree */ + for (i = 0; i < 7; ++i) { lt.table[i] = 0; } + + lt.table[7] = 24; + lt.table[8] = 152; + lt.table[9] = 112; + + for (i = 0; i < 24; ++i) { lt.trans[i] = 256 + i; } + for (i = 0; i < 144; ++i) { lt.trans[24 + i] = i; } + for (i = 0; i < 8; ++i) { lt.trans[24 + 144 + i] = 280 + i; } + for (i = 0; i < 112; ++i) { lt.trans[24 + 144 + 8 + i] = 144 + i; } + + /* build fixed distance tree */ + for (i = 0; i < 5; ++i) { dt.table[i] = 0; } + + dt.table[5] = 32; + + for (i = 0; i < 32; ++i) { dt.trans[i] = i; } + } + + /* given an array of code lengths, build a tree */ + var offs = new Uint16Array(16); + + function tinf_build_tree(t, lengths, off, num) { + var i, sum; + + /* clear code length count table */ + for (i = 0; i < 16; ++i) { t.table[i] = 0; } + + /* scan symbol lengths, and sum code length counts */ + for (i = 0; i < num; ++i) { t.table[lengths[off + i]]++; } + + t.table[0] = 0; + + /* compute offset table for distribution sort */ + for (sum = 0, i = 0; i < 16; ++i) { + offs[i] = sum; + sum += t.table[i]; + } + + /* create code->symbol translation table (symbols sorted by code) */ + for (i = 0; i < num; ++i) { + if (lengths[off + i]) { t.trans[offs[lengths[off + i]]++] = i; } + } + } + + /* ---------------------- * + * -- decode functions -- * + * ---------------------- */ + + /* get one bit from source stream */ + function tinf_getbit(d) { + /* check if tag is empty */ + if (!d.bitcount--) { + /* load next tag */ + d.tag = d.source[d.sourceIndex++]; + d.bitcount = 7; + } + + /* shift bit out of tag */ + var bit = d.tag & 1; + d.tag >>>= 1; + + return bit; + } + + /* read a num bit value from a stream and add base */ + function tinf_read_bits(d, num, base) { + if (!num) + { return base; } + + while (d.bitcount < 24) { + d.tag |= d.source[d.sourceIndex++] << d.bitcount; + d.bitcount += 8; + } + + var val = d.tag & (0xffff >>> (16 - num)); + d.tag >>>= num; + d.bitcount -= num; + return val + base; + } + + /* given a data stream and a tree, decode a symbol */ + function tinf_decode_symbol(d, t) { + while (d.bitcount < 24) { + d.tag |= d.source[d.sourceIndex++] << d.bitcount; + d.bitcount += 8; + } + + var sum = 0, cur = 0, len = 0; + var tag = d.tag; + + /* get more bits while code value is above sum */ + do { + cur = 2 * cur + (tag & 1); + tag >>>= 1; + ++len; + + sum += t.table[len]; + cur -= t.table[len]; + } while (cur >= 0); + + d.tag = tag; + d.bitcount -= len; + + return t.trans[sum + cur]; + } + + /* given a data stream, decode dynamic trees from it */ + function tinf_decode_trees(d, lt, dt) { + var hlit, hdist, hclen; + var i, num, length; + + /* get 5 bits HLIT (257-286) */ + hlit = tinf_read_bits(d, 5, 257); + + /* get 5 bits HDIST (1-32) */ + hdist = tinf_read_bits(d, 5, 1); + + /* get 4 bits HCLEN (4-19) */ + hclen = tinf_read_bits(d, 4, 4); + + for (i = 0; i < 19; ++i) { lengths[i] = 0; } + + /* read code lengths for code length alphabet */ + for (i = 0; i < hclen; ++i) { + /* get 3 bits code length (0-7) */ + var clen = tinf_read_bits(d, 3, 0); + lengths[clcidx[i]] = clen; + } + + /* build code length tree */ + tinf_build_tree(code_tree, lengths, 0, 19); + + /* decode code lengths for the dynamic trees */ + for (num = 0; num < hlit + hdist;) { + var sym = tinf_decode_symbol(d, code_tree); + + switch (sym) { + case 16: + /* copy previous code length 3-6 times (read 2 bits) */ + var prev = lengths[num - 1]; + for (length = tinf_read_bits(d, 2, 3); length; --length) { + lengths[num++] = prev; + } + break; + case 17: + /* repeat code length 0 for 3-10 times (read 3 bits) */ + for (length = tinf_read_bits(d, 3, 3); length; --length) { + lengths[num++] = 0; + } + break; + case 18: + /* repeat code length 0 for 11-138 times (read 7 bits) */ + for (length = tinf_read_bits(d, 7, 11); length; --length) { + lengths[num++] = 0; + } + break; + default: + /* values 0-15 represent the actual code lengths */ + lengths[num++] = sym; + break; + } + } + + /* build dynamic trees */ + tinf_build_tree(lt, lengths, 0, hlit); + tinf_build_tree(dt, lengths, hlit, hdist); + } + + /* ----------------------------- * + * -- block inflate functions -- * + * ----------------------------- */ + + /* given a stream and two trees, inflate a block of data */ + function tinf_inflate_block_data(d, lt, dt) { + while (1) { + var sym = tinf_decode_symbol(d, lt); + + /* check for end of block */ + if (sym === 256) { + return TINF_OK; + } + + if (sym < 256) { + d.dest[d.destLen++] = sym; + } else { + var length, dist, offs; + var i; + + sym -= 257; + + /* possibly get more bits from length code */ + length = tinf_read_bits(d, length_bits[sym], length_base[sym]); + + dist = tinf_decode_symbol(d, dt); + + /* possibly get more bits from distance code */ + offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]); + + /* copy match */ + for (i = offs; i < offs + length; ++i) { + d.dest[d.destLen++] = d.dest[i]; + } + } + } + } + + /* inflate an uncompressed block of data */ + function tinf_inflate_uncompressed_block(d) { + var length, invlength; + var i; + + /* unread from bitbuffer */ + while (d.bitcount > 8) { + d.sourceIndex--; + d.bitcount -= 8; + } + + /* get length */ + length = d.source[d.sourceIndex + 1]; + length = 256 * length + d.source[d.sourceIndex]; + + /* get one's complement of length */ + invlength = d.source[d.sourceIndex + 3]; + invlength = 256 * invlength + d.source[d.sourceIndex + 2]; + + /* check length */ + if (length !== (~invlength & 0x0000ffff)) + { return TINF_DATA_ERROR; } + + d.sourceIndex += 4; + + /* copy block */ + for (i = length; i; --i) + { d.dest[d.destLen++] = d.source[d.sourceIndex++]; } + + /* make sure we start next block on a byte boundary */ + d.bitcount = 0; + + return TINF_OK; + } + + /* inflate stream from source to dest */ + function tinf_uncompress(source, dest) { + var d = new Data(source, dest); + var bfinal, btype, res; + + do { + /* read final block flag */ + bfinal = tinf_getbit(d); + + /* read block type (2 bits) */ + btype = tinf_read_bits(d, 2, 0); + + /* decompress block */ + switch (btype) { + case 0: + /* decompress uncompressed block */ + res = tinf_inflate_uncompressed_block(d); + break; + case 1: + /* decompress block with fixed huffman trees */ + res = tinf_inflate_block_data(d, sltree, sdtree); + break; + case 2: + /* decompress block with dynamic huffman trees */ + tinf_decode_trees(d, d.ltree, d.dtree); + res = tinf_inflate_block_data(d, d.ltree, d.dtree); + break; + default: + res = TINF_DATA_ERROR; + } + + if (res !== TINF_OK) + { throw new Error('Data error'); } + + } while (!bfinal); + + if (d.destLen < d.dest.length) { + if (typeof d.dest.slice === 'function') + { return d.dest.slice(0, d.destLen); } + else + { return d.dest.subarray(0, d.destLen); } + } + + return d.dest; + } + + /* -------------------- * + * -- initialization -- * + * -------------------- */ + + /* build fixed huffman trees */ + tinf_build_fixed_trees(sltree, sdtree); + + /* build extra bits and base tables */ + tinf_build_bits_base(length_bits, length_base, 4, 3); + tinf_build_bits_base(dist_bits, dist_base, 2, 1); + + /* fix a special case */ + length_bits[28] = 0; + length_base[28] = 258; + + var tinyInflate = tinf_uncompress; + + // The Bounding Box object + + function derive(v0, v1, v2, v3, t) { + return Math.pow(1 - t, 3) * v0 + + 3 * Math.pow(1 - t, 2) * t * v1 + + 3 * (1 - t) * Math.pow(t, 2) * v2 + + Math.pow(t, 3) * v3; + } + /** + * A bounding box is an enclosing box that describes the smallest measure within which all the points lie. + * It is used to calculate the bounding box of a glyph or text path. + * + * On initialization, x1/y1/x2/y2 will be NaN. Check if the bounding box is empty using `isEmpty()`. + * + * @exports opentype.BoundingBox + * @class + * @constructor + */ + function BoundingBox() { + this.x1 = Number.NaN; + this.y1 = Number.NaN; + this.x2 = Number.NaN; + this.y2 = Number.NaN; + } + + /** + * Returns true if the bounding box is empty, that is, no points have been added to the box yet. + */ + BoundingBox.prototype.isEmpty = function() { + return isNaN(this.x1) || isNaN(this.y1) || isNaN(this.x2) || isNaN(this.y2); + }; + + /** + * Add the point to the bounding box. + * The x1/y1/x2/y2 coordinates of the bounding box will now encompass the given point. + * @param {number} x - The X coordinate of the point. + * @param {number} y - The Y coordinate of the point. + */ + BoundingBox.prototype.addPoint = function(x, y) { + if (typeof x === 'number') { + if (isNaN(this.x1) || isNaN(this.x2)) { + this.x1 = x; + this.x2 = x; + } + if (x < this.x1) { + this.x1 = x; + } + if (x > this.x2) { + this.x2 = x; + } + } + if (typeof y === 'number') { + if (isNaN(this.y1) || isNaN(this.y2)) { + this.y1 = y; + this.y2 = y; + } + if (y < this.y1) { + this.y1 = y; + } + if (y > this.y2) { + this.y2 = y; + } + } + }; + + /** + * Add a X coordinate to the bounding box. + * This extends the bounding box to include the X coordinate. + * This function is used internally inside of addBezier. + * @param {number} x - The X coordinate of the point. + */ + BoundingBox.prototype.addX = function(x) { + this.addPoint(x, null); + }; + + /** + * Add a Y coordinate to the bounding box. + * This extends the bounding box to include the Y coordinate. + * This function is used internally inside of addBezier. + * @param {number} y - The Y coordinate of the point. + */ + BoundingBox.prototype.addY = function(y) { + this.addPoint(null, y); + }; + + /** + * Add a Bézier curve to the bounding box. + * This extends the bounding box to include the entire Bézier. + * @param {number} x0 - The starting X coordinate. + * @param {number} y0 - The starting Y coordinate. + * @param {number} x1 - The X coordinate of the first control point. + * @param {number} y1 - The Y coordinate of the first control point. + * @param {number} x2 - The X coordinate of the second control point. + * @param {number} y2 - The Y coordinate of the second control point. + * @param {number} x - The ending X coordinate. + * @param {number} y - The ending Y coordinate. + */ + BoundingBox.prototype.addBezier = function(x0, y0, x1, y1, x2, y2, x, y) { + // This code is based on http://nishiohirokazu.blogspot.com/2009/06/how-to-calculate-bezier-curves-bounding.html + // and https://github.com/icons8/svg-path-bounding-box + + var p0 = [x0, y0]; + var p1 = [x1, y1]; + var p2 = [x2, y2]; + var p3 = [x, y]; + + this.addPoint(x0, y0); + this.addPoint(x, y); + + for (var i = 0; i <= 1; i++) { + var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i]; + var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i]; + var c = 3 * p1[i] - 3 * p0[i]; + + if (a === 0) { + if (b === 0) { continue; } + var t = -c / b; + if (0 < t && t < 1) { + if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t)); } + if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t)); } + } + continue; + } + + var b2ac = Math.pow(b, 2) - 4 * c * a; + if (b2ac < 0) { continue; } + var t1 = (-b + Math.sqrt(b2ac)) / (2 * a); + if (0 < t1 && t1 < 1) { + if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t1)); } + if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t1)); } + } + var t2 = (-b - Math.sqrt(b2ac)) / (2 * a); + if (0 < t2 && t2 < 1) { + if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t2)); } + if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t2)); } + } + } + }; + + /** + * Add a quadratic curve to the bounding box. + * This extends the bounding box to include the entire quadratic curve. + * @param {number} x0 - The starting X coordinate. + * @param {number} y0 - The starting Y coordinate. + * @param {number} x1 - The X coordinate of the control point. + * @param {number} y1 - The Y coordinate of the control point. + * @param {number} x - The ending X coordinate. + * @param {number} y - The ending Y coordinate. + */ + BoundingBox.prototype.addQuad = function(x0, y0, x1, y1, x, y) { + var cp1x = x0 + 2 / 3 * (x1 - x0); + var cp1y = y0 + 2 / 3 * (y1 - y0); + var cp2x = cp1x + 1 / 3 * (x - x0); + var cp2y = cp1y + 1 / 3 * (y - y0); + this.addBezier(x0, y0, cp1x, cp1y, cp2x, cp2y, x, y); + }; + + // Geometric objects + + /** + * A bézier path containing a set of path commands similar to a SVG path. + * Paths can be drawn on a context using `draw`. + * @exports opentype.Path + * @class + * @constructor + */ + function Path() { + this.commands = []; + this.fill = 'black'; + this.stroke = null; + this.strokeWidth = 1; + } + + /** + * @param {number} x + * @param {number} y + */ + Path.prototype.moveTo = function(x, y) { + this.commands.push({ + type: 'M', + x: x, + y: y + }); + }; + + /** + * @param {number} x + * @param {number} y + */ + Path.prototype.lineTo = function(x, y) { + this.commands.push({ + type: 'L', + x: x, + y: y + }); + }; + + /** + * Draws cubic curve + * @function + * curveTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control 1 + * @param {number} y1 - y of control 1 + * @param {number} x2 - x of control 2 + * @param {number} y2 - y of control 2 + * @param {number} x - x of path point + * @param {number} y - y of path point + */ + + /** + * Draws cubic curve + * @function + * bezierCurveTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control 1 + * @param {number} y1 - y of control 1 + * @param {number} x2 - x of control 2 + * @param {number} y2 - y of control 2 + * @param {number} x - x of path point + * @param {number} y - y of path point + * @see curveTo + */ + Path.prototype.curveTo = Path.prototype.bezierCurveTo = function(x1, y1, x2, y2, x, y) { + this.commands.push({ + type: 'C', + x1: x1, + y1: y1, + x2: x2, + y2: y2, + x: x, + y: y + }); + }; + + /** + * Draws quadratic curve + * @function + * quadraticCurveTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control + * @param {number} y1 - y of control + * @param {number} x - x of path point + * @param {number} y - y of path point + */ + + /** + * Draws quadratic curve + * @function + * quadTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control + * @param {number} y1 - y of control + * @param {number} x - x of path point + * @param {number} y - y of path point + */ + Path.prototype.quadTo = Path.prototype.quadraticCurveTo = function(x1, y1, x, y) { + this.commands.push({ + type: 'Q', + x1: x1, + y1: y1, + x: x, + y: y + }); + }; + + /** + * Closes the path + * @function closePath + * @memberof opentype.Path.prototype + */ + + /** + * Close the path + * @function close + * @memberof opentype.Path.prototype + */ + Path.prototype.close = Path.prototype.closePath = function() { + this.commands.push({ + type: 'Z' + }); + }; + + /** + * Add the given path or list of commands to the commands of this path. + * @param {Array} pathOrCommands - another opentype.Path, an opentype.BoundingBox, or an array of commands. + */ + Path.prototype.extend = function(pathOrCommands) { + if (pathOrCommands.commands) { + pathOrCommands = pathOrCommands.commands; + } else if (pathOrCommands instanceof BoundingBox) { + var box = pathOrCommands; + this.moveTo(box.x1, box.y1); + this.lineTo(box.x2, box.y1); + this.lineTo(box.x2, box.y2); + this.lineTo(box.x1, box.y2); + this.close(); + return; + } + + Array.prototype.push.apply(this.commands, pathOrCommands); + }; + + /** + * Calculate the bounding box of the path. + * @returns {opentype.BoundingBox} + */ + Path.prototype.getBoundingBox = function() { + var box = new BoundingBox(); + + var startX = 0; + var startY = 0; + var prevX = 0; + var prevY = 0; + for (var i = 0; i < this.commands.length; i++) { + var cmd = this.commands[i]; + switch (cmd.type) { + case 'M': + box.addPoint(cmd.x, cmd.y); + startX = prevX = cmd.x; + startY = prevY = cmd.y; + break; + case 'L': + box.addPoint(cmd.x, cmd.y); + prevX = cmd.x; + prevY = cmd.y; + break; + case 'Q': + box.addQuad(prevX, prevY, cmd.x1, cmd.y1, cmd.x, cmd.y); + prevX = cmd.x; + prevY = cmd.y; + break; + case 'C': + box.addBezier(prevX, prevY, cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + prevX = cmd.x; + prevY = cmd.y; + break; + case 'Z': + prevX = startX; + prevY = startY; + break; + default: + throw new Error('Unexpected path command ' + cmd.type); + } + } + if (box.isEmpty()) { + box.addPoint(0, 0); + } + return box; + }; + + /** + * Draw the path to a 2D context. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context. + */ + Path.prototype.draw = function(ctx) { + ctx.beginPath(); + for (var i = 0; i < this.commands.length; i += 1) { + var cmd = this.commands[i]; + if (cmd.type === 'M') { + ctx.moveTo(cmd.x, cmd.y); + } else if (cmd.type === 'L') { + ctx.lineTo(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y); + } else if (cmd.type === 'Z') { + ctx.closePath(); + } + } + + if (this.fill) { + ctx.fillStyle = this.fill; + ctx.fill(); + } + + if (this.stroke) { + ctx.strokeStyle = this.stroke; + ctx.lineWidth = this.strokeWidth; + ctx.stroke(); + } + }; + + /** + * Convert the Path to a string of path data instructions + * See http://www.w3.org/TR/SVG/paths.html#PathData + * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values + * @return {string} + */ + Path.prototype.toPathData = function(decimalPlaces) { + decimalPlaces = decimalPlaces !== undefined ? decimalPlaces : 2; + + function floatToString(v) { + if (Math.round(v) === v) { + return '' + Math.round(v); + } else { + return v.toFixed(decimalPlaces); + } + } + + function packValues() { + var arguments$1 = arguments; + + var s = ''; + for (var i = 0; i < arguments.length; i += 1) { + var v = arguments$1[i]; + if (v >= 0 && i > 0) { + s += ' '; + } + + s += floatToString(v); + } + + return s; + } + + var d = ''; + for (var i = 0; i < this.commands.length; i += 1) { + var cmd = this.commands[i]; + if (cmd.type === 'M') { + d += 'M' + packValues(cmd.x, cmd.y); + } else if (cmd.type === 'L') { + d += 'L' + packValues(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + d += 'C' + packValues(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + d += 'Q' + packValues(cmd.x1, cmd.y1, cmd.x, cmd.y); + } else if (cmd.type === 'Z') { + d += 'Z'; + } + } + + return d; + }; + + /** + * Convert the path to an SVG element, as a string. + * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values + * @return {string} + */ + Path.prototype.toSVG = function(decimalPlaces) { + var svg = '= 0 && v <= 255, 'Byte value should be between 0 and 255.'); + return [v]; + }; + /** + * @constant + * @type {number} + */ + sizeOf.BYTE = constant(1); + + /** + * Convert a 8-bit signed integer to a list of 1 byte. + * @param {string} + * @returns {Array} + */ + encode.CHAR = function(v) { + return [v.charCodeAt(0)]; + }; + + /** + * @constant + * @type {number} + */ + sizeOf.CHAR = constant(1); + + /** + * Convert an ASCII string to a list of bytes. + * @param {string} + * @returns {Array} + */ + encode.CHARARRAY = function(v) { + var b = []; + for (var i = 0; i < v.length; i += 1) { + b[i] = v.charCodeAt(i); + } + + return b; + }; + + /** + * @param {Array} + * @returns {number} + */ + sizeOf.CHARARRAY = function(v) { + return v.length; + }; + + /** + * Convert a 16-bit unsigned integer to a list of 2 bytes. + * @param {number} + * @returns {Array} + */ + encode.USHORT = function(v) { + return [(v >> 8) & 0xFF, v & 0xFF]; + }; + + /** + * @constant + * @type {number} + */ + sizeOf.USHORT = constant(2); + + /** + * Convert a 16-bit signed integer to a list of 2 bytes. + * @param {number} + * @returns {Array} + */ + encode.SHORT = function(v) { + // Two's complement + if (v >= LIMIT16) { + v = -(2 * LIMIT16 - v); + } + + return [(v >> 8) & 0xFF, v & 0xFF]; + }; + + /** + * @constant + * @type {number} + */ + sizeOf.SHORT = constant(2); + + /** + * Convert a 24-bit unsigned integer to a list of 3 bytes. + * @param {number} + * @returns {Array} + */ + encode.UINT24 = function(v) { + return [(v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; + }; + + /** + * @constant + * @type {number} + */ + sizeOf.UINT24 = constant(3); + + /** + * Convert a 32-bit unsigned integer to a list of 4 bytes. + * @param {number} + * @returns {Array} + */ + encode.ULONG = function(v) { + return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; + }; + + /** + * @constant + * @type {number} + */ + sizeOf.ULONG = constant(4); + + /** + * Convert a 32-bit unsigned integer to a list of 4 bytes. + * @param {number} + * @returns {Array} + */ + encode.LONG = function(v) { + // Two's complement + if (v >= LIMIT32) { + v = -(2 * LIMIT32 - v); + } + + return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; + }; + + /** + * @constant + * @type {number} + */ + sizeOf.LONG = constant(4); + + encode.FIXED = encode.ULONG; + sizeOf.FIXED = sizeOf.ULONG; + + encode.FWORD = encode.SHORT; + sizeOf.FWORD = sizeOf.SHORT; + + encode.UFWORD = encode.USHORT; + sizeOf.UFWORD = sizeOf.USHORT; + + /** + * Convert a 32-bit Apple Mac timestamp integer to a list of 8 bytes, 64-bit timestamp. + * @param {number} + * @returns {Array} + */ + encode.LONGDATETIME = function(v) { + return [0, 0, 0, 0, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; + }; + + /** + * @constant + * @type {number} + */ + sizeOf.LONGDATETIME = constant(8); + + /** + * Convert a 4-char tag to a list of 4 bytes. + * @param {string} + * @returns {Array} + */ + encode.TAG = function(v) { + check.argument(v.length === 4, 'Tag should be exactly 4 ASCII characters.'); + return [v.charCodeAt(0), + v.charCodeAt(1), + v.charCodeAt(2), + v.charCodeAt(3)]; + }; + + /** + * @constant + * @type {number} + */ + sizeOf.TAG = constant(4); + + // CFF data types /////////////////////////////////////////////////////////// + + encode.Card8 = encode.BYTE; + sizeOf.Card8 = sizeOf.BYTE; + + encode.Card16 = encode.USHORT; + sizeOf.Card16 = sizeOf.USHORT; + + encode.OffSize = encode.BYTE; + sizeOf.OffSize = sizeOf.BYTE; + + encode.SID = encode.USHORT; + sizeOf.SID = sizeOf.USHORT; + + // Convert a numeric operand or charstring number to a variable-size list of bytes. + /** + * Convert a numeric operand or charstring number to a variable-size list of bytes. + * @param {number} + * @returns {Array} + */ + encode.NUMBER = function(v) { + if (v >= -107 && v <= 107) { + return [v + 139]; + } else if (v >= 108 && v <= 1131) { + v = v - 108; + return [(v >> 8) + 247, v & 0xFF]; + } else if (v >= -1131 && v <= -108) { + v = -v - 108; + return [(v >> 8) + 251, v & 0xFF]; + } else if (v >= -32768 && v <= 32767) { + return encode.NUMBER16(v); + } else { + return encode.NUMBER32(v); + } + }; + + /** + * @param {number} + * @returns {number} + */ + sizeOf.NUMBER = function(v) { + return encode.NUMBER(v).length; + }; + + /** + * Convert a signed number between -32768 and +32767 to a three-byte value. + * This ensures we always use three bytes, but is not the most compact format. + * @param {number} + * @returns {Array} + */ + encode.NUMBER16 = function(v) { + return [28, (v >> 8) & 0xFF, v & 0xFF]; + }; + + /** + * @constant + * @type {number} + */ + sizeOf.NUMBER16 = constant(3); + + /** + * Convert a signed number between -(2^31) and +(2^31-1) to a five-byte value. + * This is useful if you want to be sure you always use four bytes, + * at the expense of wasting a few bytes for smaller numbers. + * @param {number} + * @returns {Array} + */ + encode.NUMBER32 = function(v) { + return [29, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; + }; + + /** + * @constant + * @type {number} + */ + sizeOf.NUMBER32 = constant(5); + + /** + * @param {number} + * @returns {Array} + */ + encode.REAL = function(v) { + var value = v.toString(); + + // Some numbers use an epsilon to encode the value. (e.g. JavaScript will store 0.0000001 as 1e-7) + // This code converts it back to a number without the epsilon. + var m = /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value); + if (m) { + var epsilon = parseFloat('1e' + ((m[2] ? +m[2] : 0) + m[1].length)); + value = (Math.round(v * epsilon) / epsilon).toString(); + } + + var nibbles = ''; + for (var i = 0, ii = value.length; i < ii; i += 1) { + var c = value[i]; + if (c === 'e') { + nibbles += value[++i] === '-' ? 'c' : 'b'; + } else if (c === '.') { + nibbles += 'a'; + } else if (c === '-') { + nibbles += 'e'; + } else { + nibbles += c; + } + } + + nibbles += (nibbles.length & 1) ? 'f' : 'ff'; + var out = [30]; + for (var i$1 = 0, ii$1 = nibbles.length; i$1 < ii$1; i$1 += 2) { + out.push(parseInt(nibbles.substr(i$1, 2), 16)); + } + + return out; + }; + + /** + * @param {number} + * @returns {number} + */ + sizeOf.REAL = function(v) { + return encode.REAL(v).length; + }; + + encode.NAME = encode.CHARARRAY; + sizeOf.NAME = sizeOf.CHARARRAY; + + encode.STRING = encode.CHARARRAY; + sizeOf.STRING = sizeOf.CHARARRAY; + + /** + * @param {DataView} data + * @param {number} offset + * @param {number} numBytes + * @returns {string} + */ + decode.UTF8 = function(data, offset, numBytes) { + var codePoints = []; + var numChars = numBytes; + for (var j = 0; j < numChars; j++, offset += 1) { + codePoints[j] = data.getUint8(offset); + } + + return String.fromCharCode.apply(null, codePoints); + }; + + /** + * @param {DataView} data + * @param {number} offset + * @param {number} numBytes + * @returns {string} + */ + decode.UTF16 = function(data, offset, numBytes) { + var codePoints = []; + var numChars = numBytes / 2; + for (var j = 0; j < numChars; j++, offset += 2) { + codePoints[j] = data.getUint16(offset); + } + + return String.fromCharCode.apply(null, codePoints); + }; + + /** + * Convert a JavaScript string to UTF16-BE. + * @param {string} + * @returns {Array} + */ + encode.UTF16 = function(v) { + var b = []; + for (var i = 0; i < v.length; i += 1) { + var codepoint = v.charCodeAt(i); + b[b.length] = (codepoint >> 8) & 0xFF; + b[b.length] = codepoint & 0xFF; + } + + return b; + }; + + /** + * @param {string} + * @returns {number} + */ + sizeOf.UTF16 = function(v) { + return v.length * 2; + }; + + // Data for converting old eight-bit Macintosh encodings to Unicode. + // This representation is optimized for decoding; encoding is slower + // and needs more memory. The assumption is that all opentype.js users + // want to open fonts, but saving a font will be comparatively rare + // so it can be more expensive. Keyed by IANA character set name. + // + // Python script for generating these strings: + // + // s = u''.join([chr(c).decode('mac_greek') for c in range(128, 256)]) + // print(s.encode('utf-8')) + /** + * @private + */ + var eightBitMacEncodings = { + 'x-mac-croatian': // Python: 'mac_croatian' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø' + + '¿¡¬√ƒ≈Ć«Č… ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ', + 'x-mac-cyrillic': // Python: 'mac_cyrillic' + 'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ†°Ґ£§•¶І®©™Ђђ≠Ѓѓ∞±≤≥іµґЈЄєЇїЉљЊњ' + + 'јЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю', + 'x-mac-gaelic': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/GAELIC.TXT + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØḂ±≤≥ḃĊċḊḋḞḟĠġṀæø' + + 'ṁṖṗɼƒſṠ«»… ÀÃÕŒœ–—“”‘’ṡẛÿŸṪ€‹›Ŷŷṫ·Ỳỳ⁊ÂÊÁËÈÍÎÏÌÓÔ♣ÒÚÛÙıÝýŴŵẄẅẀẁẂẃ', + 'x-mac-greek': // Python: 'mac_greek' + 'Ĺ²É³ÖÜ΅àâä΄¨çéèê룙î‰ôö¦€ùûü†ΓΔΘΛΞΠß®©ΣΪ§≠°·Α±≤≥¥ΒΕΖΗΙΚΜΦΫΨΩ' + + 'άΝ¬ΟΡ≈Τ«»… ΥΧΆΈœ–―“”‘’÷ΉΊΌΎέήίόΏύαβψδεφγηιξκλμνοπώρστθωςχυζϊϋΐΰ\u00AD', + 'x-mac-icelandic': // Python: 'mac_iceland' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûüÝ°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€ÐðÞþý·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + 'x-mac-inuit': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/INUIT.TXT + 'ᐃᐄᐅᐆᐊᐋᐱᐲᐳᐴᐸᐹᑉᑎᑏᑐᑑᑕᑖᑦᑭᑮᑯᑰᑲᑳᒃᒋᒌᒍᒎᒐᒑ°ᒡᒥᒦ•¶ᒧ®©™ᒨᒪᒫᒻᓂᓃᓄᓅᓇᓈᓐᓯᓰᓱᓲᓴᓵᔅᓕᓖᓗ' + + 'ᓘᓚᓛᓪᔨᔩᔪᔫᔭ… ᔮᔾᕕᕖᕗ–—“”‘’ᕘᕙᕚᕝᕆᕇᕈᕉᕋᕌᕐᕿᖀᖁᖂᖃᖄᖅᖏᖐᖑᖒᖓᖔᖕᙱᙲᙳᙴᙵᙶᖖᖠᖡᖢᖣᖤᖥᖦᕼŁł', + 'x-mac-ce': // Python: 'mac_latin2' + 'ÄĀāÉĄÖÜáąČäčĆć鏟ĎíďĒēĖóėôöõúĚěü†°Ę£§•¶ß®©™ę¨≠ģĮįĪ≤≥īĶ∂∑łĻļĽľĹĺŅ' + + 'ņѬ√ńŇ∆«»… ňŐÕőŌ–—“”‘’÷◊ōŔŕŘ‹›řŖŗŠ‚„šŚśÁŤťÍŽžŪÓÔūŮÚůŰűŲųÝýķŻŁżĢˇ', + macintosh: // Python: 'mac_roman' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + 'x-mac-romanian': // Python: 'mac_romanian' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ĂȘ∞±≤≥¥µ∂∑∏π∫ªºΩăș' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›Țț‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + 'x-mac-turkish': // Python: 'mac_turkish' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸĞğİıŞş‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙˆ˜¯˘˙˚¸˝˛ˇ' + }; + + /** + * Decodes an old-style Macintosh string. Returns either a Unicode JavaScript + * string, or 'undefined' if the encoding is unsupported. For example, we do + * not support Chinese, Japanese or Korean because these would need large + * mapping tables. + * @param {DataView} dataView + * @param {number} offset + * @param {number} dataLength + * @param {string} encoding + * @returns {string} + */ + decode.MACSTRING = function(dataView, offset, dataLength, encoding) { + var table = eightBitMacEncodings[encoding]; + if (table === undefined) { + return undefined; + } + + var result = ''; + for (var i = 0; i < dataLength; i++) { + var c = dataView.getUint8(offset + i); + // In all eight-bit Mac encodings, the characters 0x00..0x7F are + // mapped to U+0000..U+007F; we only need to look up the others. + if (c <= 0x7F) { + result += String.fromCharCode(c); + } else { + result += table[c & 0x7F]; + } + } + + return result; + }; + + // Helper function for encode.MACSTRING. Returns a dictionary for mapping + // Unicode character codes to their 8-bit MacOS equivalent. This table + // is not exactly a super cheap data structure, but we do not care because + // encoding Macintosh strings is only rarely needed in typical applications. + var macEncodingTableCache = typeof WeakMap === 'function' && new WeakMap(); + var macEncodingCacheKeys; + var getMacEncodingTable = function (encoding) { + // Since we use encoding as a cache key for WeakMap, it has to be + // a String object and not a literal. And at least on NodeJS 2.10.1, + // WeakMap requires that the same String instance is passed for cache hits. + if (!macEncodingCacheKeys) { + macEncodingCacheKeys = {}; + for (var e in eightBitMacEncodings) { + /*jshint -W053 */ // Suppress "Do not use String as a constructor." + macEncodingCacheKeys[e] = new String(e); + } + } + + var cacheKey = macEncodingCacheKeys[encoding]; + if (cacheKey === undefined) { + return undefined; + } + + // We can't do "if (cache.has(key)) {return cache.get(key)}" here: + // since garbage collection may run at any time, it could also kick in + // between the calls to cache.has() and cache.get(). In that case, + // we would return 'undefined' even though we do support the encoding. + if (macEncodingTableCache) { + var cachedTable = macEncodingTableCache.get(cacheKey); + if (cachedTable !== undefined) { + return cachedTable; + } + } + + var decodingTable = eightBitMacEncodings[encoding]; + if (decodingTable === undefined) { + return undefined; + } + + var encodingTable = {}; + for (var i = 0; i < decodingTable.length; i++) { + encodingTable[decodingTable.charCodeAt(i)] = i + 0x80; + } + + if (macEncodingTableCache) { + macEncodingTableCache.set(cacheKey, encodingTable); + } + + return encodingTable; + }; + + /** + * Encodes an old-style Macintosh string. Returns a byte array upon success. + * If the requested encoding is unsupported, or if the input string contains + * a character that cannot be expressed in the encoding, the function returns + * 'undefined'. + * @param {string} str + * @param {string} encoding + * @returns {Array} + */ + encode.MACSTRING = function(str, encoding) { + var table = getMacEncodingTable(encoding); + if (table === undefined) { + return undefined; + } + + var result = []; + for (var i = 0; i < str.length; i++) { + var c = str.charCodeAt(i); + + // In all eight-bit Mac encodings, the characters 0x00..0x7F are + // mapped to U+0000..U+007F; we only need to look up the others. + if (c >= 0x80) { + c = table[c]; + if (c === undefined) { + // str contains a Unicode character that cannot be encoded + // in the requested encoding. + return undefined; + } + } + result[i] = c; + // result.push(c); + } + + return result; + }; + + /** + * @param {string} str + * @param {string} encoding + * @returns {number} + */ + sizeOf.MACSTRING = function(str, encoding) { + var b = encode.MACSTRING(str, encoding); + if (b !== undefined) { + return b.length; + } else { + return 0; + } + }; + + // Helper for encode.VARDELTAS + function isByteEncodable(value) { + return value >= -128 && value <= 127; + } + + // Helper for encode.VARDELTAS + function encodeVarDeltaRunAsZeroes(deltas, pos, result) { + var runLength = 0; + var numDeltas = deltas.length; + while (pos < numDeltas && runLength < 64 && deltas[pos] === 0) { + ++pos; + ++runLength; + } + result.push(0x80 | (runLength - 1)); + return pos; + } + + // Helper for encode.VARDELTAS + function encodeVarDeltaRunAsBytes(deltas, offset, result) { + var runLength = 0; + var numDeltas = deltas.length; + var pos = offset; + while (pos < numDeltas && runLength < 64) { + var value = deltas[pos]; + if (!isByteEncodable(value)) { + break; + } + + // Within a byte-encoded run of deltas, a single zero is best + // stored literally as 0x00 value. However, if we have two or + // more zeroes in a sequence, it is better to start a new run. + // Fore example, the sequence of deltas [15, 15, 0, 15, 15] + // becomes 6 bytes (04 0F 0F 00 0F 0F) when storing the zero + // within the current run, but 7 bytes (01 0F 0F 80 01 0F 0F) + // when starting a new run. + if (value === 0 && pos + 1 < numDeltas && deltas[pos + 1] === 0) { + break; + } + + ++pos; + ++runLength; + } + result.push(runLength - 1); + for (var i = offset; i < pos; ++i) { + result.push((deltas[i] + 256) & 0xff); + } + return pos; + } + + // Helper for encode.VARDELTAS + function encodeVarDeltaRunAsWords(deltas, offset, result) { + var runLength = 0; + var numDeltas = deltas.length; + var pos = offset; + while (pos < numDeltas && runLength < 64) { + var value = deltas[pos]; + + // Within a word-encoded run of deltas, it is easiest to start + // a new run (with a different encoding) whenever we encounter + // a zero value. For example, the sequence [0x6666, 0, 0x7777] + // needs 7 bytes when storing the zero inside the current run + // (42 66 66 00 00 77 77), and equally 7 bytes when starting a + // new run (40 66 66 80 40 77 77). + if (value === 0) { + break; + } + + // Within a word-encoded run of deltas, a single value in the + // range (-128..127) should be encoded within the current run + // because it is more compact. For example, the sequence + // [0x6666, 2, 0x7777] becomes 7 bytes when storing the value + // literally (42 66 66 00 02 77 77), but 8 bytes when starting + // a new run (40 66 66 00 02 40 77 77). + if (isByteEncodable(value) && pos + 1 < numDeltas && isByteEncodable(deltas[pos + 1])) { + break; + } + + ++pos; + ++runLength; + } + result.push(0x40 | (runLength - 1)); + for (var i = offset; i < pos; ++i) { + var val = deltas[i]; + result.push(((val + 0x10000) >> 8) & 0xff, (val + 0x100) & 0xff); + } + return pos; + } + + /** + * Encode a list of variation adjustment deltas. + * + * Variation adjustment deltas are used in ‘gvar’ and ‘cvar’ tables. + * They indicate how points (in ‘gvar’) or values (in ‘cvar’) get adjusted + * when generating instances of variation fonts. + * + * @see https://www.microsoft.com/typography/otspec/gvar.htm + * @see https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html + * @param {Array} + * @return {Array} + */ + encode.VARDELTAS = function(deltas) { + var pos = 0; + var result = []; + while (pos < deltas.length) { + var value = deltas[pos]; + if (value === 0) { + pos = encodeVarDeltaRunAsZeroes(deltas, pos, result); + } else if (value >= -128 && value <= 127) { + pos = encodeVarDeltaRunAsBytes(deltas, pos, result); + } else { + pos = encodeVarDeltaRunAsWords(deltas, pos, result); + } + } + return result; + }; + + // Convert a list of values to a CFF INDEX structure. + // The values should be objects containing name / type / value. + /** + * @param {Array} l + * @returns {Array} + */ + encode.INDEX = function(l) { + //var offset, offsets, offsetEncoder, encodedOffsets, encodedOffset, data, + // i, v; + // Because we have to know which data type to use to encode the offsets, + // we have to go through the values twice: once to encode the data and + // calculate the offsets, then again to encode the offsets using the fitting data type. + var offset = 1; // First offset is always 1. + var offsets = [offset]; + var data = []; + for (var i = 0; i < l.length; i += 1) { + var v = encode.OBJECT(l[i]); + Array.prototype.push.apply(data, v); + offset += v.length; + offsets.push(offset); + } + + if (data.length === 0) { + return [0, 0]; + } + + var encodedOffsets = []; + var offSize = (1 + Math.floor(Math.log(offset) / Math.log(2)) / 8) | 0; + var offsetEncoder = [undefined, encode.BYTE, encode.USHORT, encode.UINT24, encode.ULONG][offSize]; + for (var i$1 = 0; i$1 < offsets.length; i$1 += 1) { + var encodedOffset = offsetEncoder(offsets[i$1]); + Array.prototype.push.apply(encodedOffsets, encodedOffset); + } + + return Array.prototype.concat(encode.Card16(l.length), + encode.OffSize(offSize), + encodedOffsets, + data); + }; + + /** + * @param {Array} + * @returns {number} + */ + sizeOf.INDEX = function(v) { + return encode.INDEX(v).length; + }; + + /** + * Convert an object to a CFF DICT structure. + * The keys should be numeric. + * The values should be objects containing name / type / value. + * @param {Object} m + * @returns {Array} + */ + encode.DICT = function(m) { + var d = []; + var keys = Object.keys(m); + var length = keys.length; + + for (var i = 0; i < length; i += 1) { + // Object.keys() return string keys, but our keys are always numeric. + var k = parseInt(keys[i], 0); + var v = m[k]; + // Value comes before the key. + d = d.concat(encode.OPERAND(v.value, v.type)); + d = d.concat(encode.OPERATOR(k)); + } + + return d; + }; + + /** + * @param {Object} + * @returns {number} + */ + sizeOf.DICT = function(m) { + return encode.DICT(m).length; + }; + + /** + * @param {number} + * @returns {Array} + */ + encode.OPERATOR = function(v) { + if (v < 1200) { + return [v]; + } else { + return [12, v - 1200]; + } + }; + + /** + * @param {Array} v + * @param {string} + * @returns {Array} + */ + encode.OPERAND = function(v, type) { + var d = []; + if (Array.isArray(type)) { + for (var i = 0; i < type.length; i += 1) { + check.argument(v.length === type.length, 'Not enough arguments given for type' + type); + d = d.concat(encode.OPERAND(v[i], type[i])); + } + } else { + if (type === 'SID') { + d = d.concat(encode.NUMBER(v)); + } else if (type === 'offset') { + // We make it easy for ourselves and always encode offsets as + // 4 bytes. This makes offset calculation for the top dict easier. + d = d.concat(encode.NUMBER32(v)); + } else if (type === 'number') { + d = d.concat(encode.NUMBER(v)); + } else if (type === 'real') { + d = d.concat(encode.REAL(v)); + } else { + throw new Error('Unknown operand type ' + type); + // FIXME Add support for booleans + } + } + + return d; + }; + + encode.OP = encode.BYTE; + sizeOf.OP = sizeOf.BYTE; + + // memoize charstring encoding using WeakMap if available + var wmm = typeof WeakMap === 'function' && new WeakMap(); + + /** + * Convert a list of CharString operations to bytes. + * @param {Array} + * @returns {Array} + */ + encode.CHARSTRING = function(ops) { + // See encode.MACSTRING for why we don't do "if (wmm && wmm.has(ops))". + if (wmm) { + var cachedValue = wmm.get(ops); + if (cachedValue !== undefined) { + return cachedValue; + } + } + + var d = []; + var length = ops.length; + + for (var i = 0; i < length; i += 1) { + var op = ops[i]; + d = d.concat(encode[op.type](op.value)); + } + + if (wmm) { + wmm.set(ops, d); + } + + return d; + }; + + /** + * @param {Array} + * @returns {number} + */ + sizeOf.CHARSTRING = function(ops) { + return encode.CHARSTRING(ops).length; + }; + + // Utility functions //////////////////////////////////////////////////////// + + /** + * Convert an object containing name / type / value to bytes. + * @param {Object} + * @returns {Array} + */ + encode.OBJECT = function(v) { + var encodingFunction = encode[v.type]; + check.argument(encodingFunction !== undefined, 'No encoding function for type ' + v.type); + return encodingFunction(v.value); + }; + + /** + * @param {Object} + * @returns {number} + */ + sizeOf.OBJECT = function(v) { + var sizeOfFunction = sizeOf[v.type]; + check.argument(sizeOfFunction !== undefined, 'No sizeOf function for type ' + v.type); + return sizeOfFunction(v.value); + }; + + /** + * Convert a table object to bytes. + * A table contains a list of fields containing the metadata (name, type and default value). + * The table itself has the field values set as attributes. + * @param {opentype.Table} + * @returns {Array} + */ + encode.TABLE = function(table) { + var d = []; + var length = table.fields.length; + var subtables = []; + var subtableOffsets = []; + + for (var i = 0; i < length; i += 1) { + var field = table.fields[i]; + var encodingFunction = encode[field.type]; + check.argument(encodingFunction !== undefined, 'No encoding function for field type ' + field.type + ' (' + field.name + ')'); + var value = table[field.name]; + if (value === undefined) { + value = field.value; + } + + var bytes = encodingFunction(value); + + if (field.type === 'TABLE') { + subtableOffsets.push(d.length); + d = d.concat([0, 0]); + subtables.push(bytes); + } else { + d = d.concat(bytes); + } + } + + for (var i$1 = 0; i$1 < subtables.length; i$1 += 1) { + var o = subtableOffsets[i$1]; + var offset = d.length; + check.argument(offset < 65536, 'Table ' + table.tableName + ' too big.'); + d[o] = offset >> 8; + d[o + 1] = offset & 0xff; + d = d.concat(subtables[i$1]); + } + + return d; + }; + + /** + * @param {opentype.Table} + * @returns {number} + */ + sizeOf.TABLE = function(table) { + var numBytes = 0; + var length = table.fields.length; + + for (var i = 0; i < length; i += 1) { + var field = table.fields[i]; + var sizeOfFunction = sizeOf[field.type]; + check.argument(sizeOfFunction !== undefined, 'No sizeOf function for field type ' + field.type + ' (' + field.name + ')'); + var value = table[field.name]; + if (value === undefined) { + value = field.value; + } + + numBytes += sizeOfFunction(value); + + // Subtables take 2 more bytes for offsets. + if (field.type === 'TABLE') { + numBytes += 2; + } + } + + return numBytes; + }; + + encode.RECORD = encode.TABLE; + sizeOf.RECORD = sizeOf.TABLE; + + // Merge in a list of bytes. + encode.LITERAL = function(v) { + return v; + }; + + sizeOf.LITERAL = function(v) { + return v.length; + }; + + // Table metadata + + /** + * @exports opentype.Table + * @class + * @param {string} tableName + * @param {Array} fields + * @param {Object} options + * @constructor + */ + function Table(tableName, fields, options) { + for (var i = 0; i < fields.length; i += 1) { + var field = fields[i]; + this[field.name] = field.value; + } + + this.tableName = tableName; + this.fields = fields; + if (options) { + var optionKeys = Object.keys(options); + for (var i$1 = 0; i$1 < optionKeys.length; i$1 += 1) { + var k = optionKeys[i$1]; + var v = options[k]; + if (this[k] !== undefined) { + this[k] = v; + } + } + } + } + + /** + * Encodes the table and returns an array of bytes + * @return {Array} + */ + Table.prototype.encode = function() { + return encode.TABLE(this); + }; + + /** + * Get the size of the table. + * @return {number} + */ + Table.prototype.sizeOf = function() { + return sizeOf.TABLE(this); + }; + + /** + * @private + */ + function ushortList(itemName, list, count) { + if (count === undefined) { + count = list.length; + } + var fields = new Array(list.length + 1); + fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; + for (var i = 0; i < list.length; i++) { + fields[i + 1] = {name: itemName + i, type: 'USHORT', value: list[i]}; + } + return fields; + } + + /** + * @private + */ + function tableList(itemName, records, itemCallback) { + var count = records.length; + var fields = new Array(count + 1); + fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; + for (var i = 0; i < count; i++) { + fields[i + 1] = {name: itemName + i, type: 'TABLE', value: itemCallback(records[i], i)}; + } + return fields; + } + + /** + * @private + */ + function recordList(itemName, records, itemCallback) { + var count = records.length; + var fields = []; + fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; + for (var i = 0; i < count; i++) { + fields = fields.concat(itemCallback(records[i], i)); + } + return fields; + } + + // Common Layout Tables + + /** + * @exports opentype.Coverage + * @class + * @param {opentype.Table} + * @constructor + * @extends opentype.Table + */ + function Coverage(coverageTable) { + if (coverageTable.format === 1) { + Table.call(this, 'coverageTable', + [{name: 'coverageFormat', type: 'USHORT', value: 1}] + .concat(ushortList('glyph', coverageTable.glyphs)) + ); + } else { + check.assert(false, 'Can\'t create coverage table format 2 yet.'); + } + } + Coverage.prototype = Object.create(Table.prototype); + Coverage.prototype.constructor = Coverage; + + function ScriptList(scriptListTable) { + Table.call(this, 'scriptListTable', + recordList('scriptRecord', scriptListTable, function(scriptRecord, i) { + var script = scriptRecord.script; + var defaultLangSys = script.defaultLangSys; + check.assert(!!defaultLangSys, 'Unable to write GSUB: script ' + scriptRecord.tag + ' has no default language system.'); + return [ + {name: 'scriptTag' + i, type: 'TAG', value: scriptRecord.tag}, + {name: 'script' + i, type: 'TABLE', value: new Table('scriptTable', [ + {name: 'defaultLangSys', type: 'TABLE', value: new Table('defaultLangSys', [ + {name: 'lookupOrder', type: 'USHORT', value: 0}, + {name: 'reqFeatureIndex', type: 'USHORT', value: defaultLangSys.reqFeatureIndex}] + .concat(ushortList('featureIndex', defaultLangSys.featureIndexes)))} + ].concat(recordList('langSys', script.langSysRecords, function(langSysRecord, i) { + var langSys = langSysRecord.langSys; + return [ + {name: 'langSysTag' + i, type: 'TAG', value: langSysRecord.tag}, + {name: 'langSys' + i, type: 'TABLE', value: new Table('langSys', [ + {name: 'lookupOrder', type: 'USHORT', value: 0}, + {name: 'reqFeatureIndex', type: 'USHORT', value: langSys.reqFeatureIndex} + ].concat(ushortList('featureIndex', langSys.featureIndexes)))} + ]; + })))} + ]; + }) + ); + } + ScriptList.prototype = Object.create(Table.prototype); + ScriptList.prototype.constructor = ScriptList; + + /** + * @exports opentype.FeatureList + * @class + * @param {opentype.Table} + * @constructor + * @extends opentype.Table + */ + function FeatureList(featureListTable) { + Table.call(this, 'featureListTable', + recordList('featureRecord', featureListTable, function(featureRecord, i) { + var feature = featureRecord.feature; + return [ + {name: 'featureTag' + i, type: 'TAG', value: featureRecord.tag}, + {name: 'feature' + i, type: 'TABLE', value: new Table('featureTable', [ + {name: 'featureParams', type: 'USHORT', value: feature.featureParams} ].concat(ushortList('lookupListIndex', feature.lookupListIndexes)))} + ]; + }) + ); + } + FeatureList.prototype = Object.create(Table.prototype); + FeatureList.prototype.constructor = FeatureList; + + /** + * @exports opentype.LookupList + * @class + * @param {opentype.Table} + * @param {Object} + * @constructor + * @extends opentype.Table + */ + function LookupList(lookupListTable, subtableMakers) { + Table.call(this, 'lookupListTable', tableList('lookup', lookupListTable, function(lookupTable) { + var subtableCallback = subtableMakers[lookupTable.lookupType]; + check.assert(!!subtableCallback, 'Unable to write GSUB lookup type ' + lookupTable.lookupType + ' tables.'); + return new Table('lookupTable', [ + {name: 'lookupType', type: 'USHORT', value: lookupTable.lookupType}, + {name: 'lookupFlag', type: 'USHORT', value: lookupTable.lookupFlag} + ].concat(tableList('subtable', lookupTable.subtables, subtableCallback))); + })); + } + LookupList.prototype = Object.create(Table.prototype); + LookupList.prototype.constructor = LookupList; + + // Record = same as Table, but inlined (a Table has an offset and its data is further in the stream) + // Don't use offsets inside Records (probable bug), only in Tables. + var table = { + Table: Table, + Record: Table, + Coverage: Coverage, + ScriptList: ScriptList, + FeatureList: FeatureList, + LookupList: LookupList, + ushortList: ushortList, + tableList: tableList, + recordList: recordList, + }; + + // Parsing utility functions + + // Retrieve an unsigned byte from the DataView. + function getByte(dataView, offset) { + return dataView.getUint8(offset); + } + + // Retrieve an unsigned 16-bit short from the DataView. + // The value is stored in big endian. + function getUShort(dataView, offset) { + return dataView.getUint16(offset, false); + } + + // Retrieve a signed 16-bit short from the DataView. + // The value is stored in big endian. + function getShort(dataView, offset) { + return dataView.getInt16(offset, false); + } + + // Retrieve an unsigned 32-bit long from the DataView. + // The value is stored in big endian. + function getULong(dataView, offset) { + return dataView.getUint32(offset, false); + } + + // Retrieve a 32-bit signed fixed-point number (16.16) from the DataView. + // The value is stored in big endian. + function getFixed(dataView, offset) { + var decimal = dataView.getInt16(offset, false); + var fraction = dataView.getUint16(offset + 2, false); + return decimal + fraction / 65535; + } + + // Retrieve a 4-character tag from the DataView. + // Tags are used to identify tables. + function getTag(dataView, offset) { + var tag = ''; + for (var i = offset; i < offset + 4; i += 1) { + tag += String.fromCharCode(dataView.getInt8(i)); + } + + return tag; + } + + // Retrieve an offset from the DataView. + // Offsets are 1 to 4 bytes in length, depending on the offSize argument. + function getOffset(dataView, offset, offSize) { + var v = 0; + for (var i = 0; i < offSize; i += 1) { + v <<= 8; + v += dataView.getUint8(offset + i); + } + + return v; + } + + // Retrieve a number of bytes from start offset to the end offset from the DataView. + function getBytes(dataView, startOffset, endOffset) { + var bytes = []; + for (var i = startOffset; i < endOffset; i += 1) { + bytes.push(dataView.getUint8(i)); + } + + return bytes; + } + + // Convert the list of bytes to a string. + function bytesToString(bytes) { + var s = ''; + for (var i = 0; i < bytes.length; i += 1) { + s += String.fromCharCode(bytes[i]); + } + + return s; + } + + var typeOffsets = { + byte: 1, + uShort: 2, + short: 2, + uLong: 4, + fixed: 4, + longDateTime: 8, + tag: 4 + }; + + // A stateful parser that changes the offset whenever a value is retrieved. + // The data is a DataView. + function Parser(data, offset) { + this.data = data; + this.offset = offset; + this.relativeOffset = 0; + } + + Parser.prototype.parseByte = function() { + var v = this.data.getUint8(this.offset + this.relativeOffset); + this.relativeOffset += 1; + return v; + }; + + Parser.prototype.parseChar = function() { + var v = this.data.getInt8(this.offset + this.relativeOffset); + this.relativeOffset += 1; + return v; + }; + + Parser.prototype.parseCard8 = Parser.prototype.parseByte; + + Parser.prototype.parseUShort = function() { + var v = this.data.getUint16(this.offset + this.relativeOffset); + this.relativeOffset += 2; + return v; + }; + + Parser.prototype.parseCard16 = Parser.prototype.parseUShort; + Parser.prototype.parseSID = Parser.prototype.parseUShort; + Parser.prototype.parseOffset16 = Parser.prototype.parseUShort; + + Parser.prototype.parseShort = function() { + var v = this.data.getInt16(this.offset + this.relativeOffset); + this.relativeOffset += 2; + return v; + }; + + Parser.prototype.parseF2Dot14 = function() { + var v = this.data.getInt16(this.offset + this.relativeOffset) / 16384; + this.relativeOffset += 2; + return v; + }; + + Parser.prototype.parseULong = function() { + var v = getULong(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v; + }; + + Parser.prototype.parseOffset32 = Parser.prototype.parseULong; + + Parser.prototype.parseFixed = function() { + var v = getFixed(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v; + }; + + Parser.prototype.parseString = function(length) { + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + var string = ''; + this.relativeOffset += length; + for (var i = 0; i < length; i++) { + string += String.fromCharCode(dataView.getUint8(offset + i)); + } + + return string; + }; + + Parser.prototype.parseTag = function() { + return this.parseString(4); + }; + + // LONGDATETIME is a 64-bit integer. + // JavaScript and unix timestamps traditionally use 32 bits, so we + // only take the last 32 bits. + // + Since until 2038 those bits will be filled by zeros we can ignore them. + Parser.prototype.parseLongDateTime = function() { + var v = getULong(this.data, this.offset + this.relativeOffset + 4); + // Subtract seconds between 01/01/1904 and 01/01/1970 + // to convert Apple Mac timestamp to Standard Unix timestamp + v -= 2082844800; + this.relativeOffset += 8; + return v; + }; + + Parser.prototype.parseVersion = function(minorBase) { + var major = getUShort(this.data, this.offset + this.relativeOffset); + + // How to interpret the minor version is very vague in the spec. 0x5000 is 5, 0x1000 is 1 + // Default returns the correct number if minor = 0xN000 where N is 0-9 + // Set minorBase to 1 for tables that use minor = N where N is 0-9 + var minor = getUShort(this.data, this.offset + this.relativeOffset + 2); + this.relativeOffset += 4; + if (minorBase === undefined) { minorBase = 0x1000; } + return major + minor / minorBase / 10; + }; + + Parser.prototype.skip = function(type, amount) { + if (amount === undefined) { + amount = 1; + } + + this.relativeOffset += typeOffsets[type] * amount; + }; + + ///// Parsing lists and records /////////////////////////////// + + // Parse a list of 32 bit unsigned integers. + Parser.prototype.parseULongList = function(count) { + if (count === undefined) { count = this.parseULong(); } + var offsets = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + offsets[i] = dataView.getUint32(offset); + offset += 4; + } + + this.relativeOffset += count * 4; + return offsets; + }; + + // Parse a list of 16 bit unsigned integers. The length of the list can be read on the stream + // or provided as an argument. + Parser.prototype.parseOffset16List = + Parser.prototype.parseUShortList = function(count) { + if (count === undefined) { count = this.parseUShort(); } + var offsets = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + offsets[i] = dataView.getUint16(offset); + offset += 2; + } + + this.relativeOffset += count * 2; + return offsets; + }; + + // Parses a list of 16 bit signed integers. + Parser.prototype.parseShortList = function(count) { + var list = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + list[i] = dataView.getInt16(offset); + offset += 2; + } + + this.relativeOffset += count * 2; + return list; + }; + + // Parses a list of bytes. + Parser.prototype.parseByteList = function(count) { + var list = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + list[i] = dataView.getUint8(offset++); + } + + this.relativeOffset += count; + return list; + }; + + /** + * Parse a list of items. + * Record count is optional, if omitted it is read from the stream. + * itemCallback is one of the Parser methods. + */ + Parser.prototype.parseList = function(count, itemCallback) { + if (!itemCallback) { + itemCallback = count; + count = this.parseUShort(); + } + var list = new Array(count); + for (var i = 0; i < count; i++) { + list[i] = itemCallback.call(this); + } + return list; + }; + + Parser.prototype.parseList32 = function(count, itemCallback) { + if (!itemCallback) { + itemCallback = count; + count = this.parseULong(); + } + var list = new Array(count); + for (var i = 0; i < count; i++) { + list[i] = itemCallback.call(this); + } + return list; + }; + + /** + * Parse a list of records. + * Record count is optional, if omitted it is read from the stream. + * Example of recordDescription: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } + */ + Parser.prototype.parseRecordList = function(count, recordDescription) { + // If the count argument is absent, read it in the stream. + if (!recordDescription) { + recordDescription = count; + count = this.parseUShort(); + } + var records = new Array(count); + var fields = Object.keys(recordDescription); + for (var i = 0; i < count; i++) { + var rec = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = recordDescription[fieldName]; + rec[fieldName] = fieldType.call(this); + } + records[i] = rec; + } + return records; + }; + + Parser.prototype.parseRecordList32 = function(count, recordDescription) { + // If the count argument is absent, read it in the stream. + if (!recordDescription) { + recordDescription = count; + count = this.parseULong(); + } + var records = new Array(count); + var fields = Object.keys(recordDescription); + for (var i = 0; i < count; i++) { + var rec = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = recordDescription[fieldName]; + rec[fieldName] = fieldType.call(this); + } + records[i] = rec; + } + return records; + }; + + // Parse a data structure into an object + // Example of description: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } + Parser.prototype.parseStruct = function(description) { + if (typeof description === 'function') { + return description.call(this); + } else { + var fields = Object.keys(description); + var struct = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = description[fieldName]; + struct[fieldName] = fieldType.call(this); + } + return struct; + } + }; + + /** + * Parse a GPOS valueRecord + * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record + * valueFormat is optional, if omitted it is read from the stream. + */ + Parser.prototype.parseValueRecord = function(valueFormat) { + if (valueFormat === undefined) { + valueFormat = this.parseUShort(); + } + if (valueFormat === 0) { + // valueFormat2 in kerning pairs is most often 0 + // in this case return undefined instead of an empty object, to save space + return; + } + var valueRecord = {}; + + if (valueFormat & 0x0001) { valueRecord.xPlacement = this.parseShort(); } + if (valueFormat & 0x0002) { valueRecord.yPlacement = this.parseShort(); } + if (valueFormat & 0x0004) { valueRecord.xAdvance = this.parseShort(); } + if (valueFormat & 0x0008) { valueRecord.yAdvance = this.parseShort(); } + + // Device table (non-variable font) / VariationIndex table (variable font) not supported + // https://docs.microsoft.com/fr-fr/typography/opentype/spec/chapter2#devVarIdxTbls + if (valueFormat & 0x0010) { valueRecord.xPlaDevice = undefined; this.parseShort(); } + if (valueFormat & 0x0020) { valueRecord.yPlaDevice = undefined; this.parseShort(); } + if (valueFormat & 0x0040) { valueRecord.xAdvDevice = undefined; this.parseShort(); } + if (valueFormat & 0x0080) { valueRecord.yAdvDevice = undefined; this.parseShort(); } + + return valueRecord; + }; + + /** + * Parse a list of GPOS valueRecords + * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record + * valueFormat and valueCount are read from the stream. + */ + Parser.prototype.parseValueRecordList = function() { + var valueFormat = this.parseUShort(); + var valueCount = this.parseUShort(); + var values = new Array(valueCount); + for (var i = 0; i < valueCount; i++) { + values[i] = this.parseValueRecord(valueFormat); + } + return values; + }; + + Parser.prototype.parsePointer = function(description) { + var structOffset = this.parseOffset16(); + if (structOffset > 0) { + // NULL offset => return undefined + return new Parser(this.data, this.offset + structOffset).parseStruct(description); + } + return undefined; + }; + + Parser.prototype.parsePointer32 = function(description) { + var structOffset = this.parseOffset32(); + if (structOffset > 0) { + // NULL offset => return undefined + return new Parser(this.data, this.offset + structOffset).parseStruct(description); + } + return undefined; + }; + + /** + * Parse a list of offsets to lists of 16-bit integers, + * or a list of offsets to lists of offsets to any kind of items. + * If itemCallback is not provided, a list of list of UShort is assumed. + * If provided, itemCallback is called on each item and must parse the item. + * See examples in tables/gsub.js + */ + Parser.prototype.parseListOfLists = function(itemCallback) { + var offsets = this.parseOffset16List(); + var count = offsets.length; + var relativeOffset = this.relativeOffset; + var list = new Array(count); + for (var i = 0; i < count; i++) { + var start = offsets[i]; + if (start === 0) { + // NULL offset + // Add i as owned property to list. Convenient with assert. + list[i] = undefined; + continue; + } + this.relativeOffset = start; + if (itemCallback) { + var subOffsets = this.parseOffset16List(); + var subList = new Array(subOffsets.length); + for (var j = 0; j < subOffsets.length; j++) { + this.relativeOffset = start + subOffsets[j]; + subList[j] = itemCallback.call(this); + } + list[i] = subList; + } else { + list[i] = this.parseUShortList(); + } + } + this.relativeOffset = relativeOffset; + return list; + }; + + ///// Complex tables parsing ////////////////////////////////// + + // Parse a coverage table in a GSUB, GPOS or GDEF table. + // https://www.microsoft.com/typography/OTSPEC/chapter2.htm + // parser.offset must point to the start of the table containing the coverage. + Parser.prototype.parseCoverage = function() { + var startOffset = this.offset + this.relativeOffset; + var format = this.parseUShort(); + var count = this.parseUShort(); + if (format === 1) { + return { + format: 1, + glyphs: this.parseUShortList(count) + }; + } else if (format === 2) { + var ranges = new Array(count); + for (var i = 0; i < count; i++) { + ranges[i] = { + start: this.parseUShort(), + end: this.parseUShort(), + index: this.parseUShort() + }; + } + return { + format: 2, + ranges: ranges + }; + } + throw new Error('0x' + startOffset.toString(16) + ': Coverage format must be 1 or 2.'); + }; + + // Parse a Class Definition Table in a GSUB, GPOS or GDEF table. + // https://www.microsoft.com/typography/OTSPEC/chapter2.htm + Parser.prototype.parseClassDef = function() { + var startOffset = this.offset + this.relativeOffset; + var format = this.parseUShort(); + if (format === 1) { + return { + format: 1, + startGlyph: this.parseUShort(), + classes: this.parseUShortList() + }; + } else if (format === 2) { + return { + format: 2, + ranges: this.parseRecordList({ + start: Parser.uShort, + end: Parser.uShort, + classId: Parser.uShort + }) + }; + } + throw new Error('0x' + startOffset.toString(16) + ': ClassDef format must be 1 or 2.'); + }; + + ///// Static methods /////////////////////////////////// + // These convenience methods can be used as callbacks and should be called with "this" context set to a Parser instance. + + Parser.list = function(count, itemCallback) { + return function() { + return this.parseList(count, itemCallback); + }; + }; + + Parser.list32 = function(count, itemCallback) { + return function() { + return this.parseList32(count, itemCallback); + }; + }; + + Parser.recordList = function(count, recordDescription) { + return function() { + return this.parseRecordList(count, recordDescription); + }; + }; + + Parser.recordList32 = function(count, recordDescription) { + return function() { + return this.parseRecordList32(count, recordDescription); + }; + }; + + Parser.pointer = function(description) { + return function() { + return this.parsePointer(description); + }; + }; + + Parser.pointer32 = function(description) { + return function() { + return this.parsePointer32(description); + }; + }; + + Parser.tag = Parser.prototype.parseTag; + Parser.byte = Parser.prototype.parseByte; + Parser.uShort = Parser.offset16 = Parser.prototype.parseUShort; + Parser.uShortList = Parser.prototype.parseUShortList; + Parser.uLong = Parser.offset32 = Parser.prototype.parseULong; + Parser.uLongList = Parser.prototype.parseULongList; + Parser.struct = Parser.prototype.parseStruct; + Parser.coverage = Parser.prototype.parseCoverage; + Parser.classDef = Parser.prototype.parseClassDef; + + ///// Script, Feature, Lookup lists /////////////////////////////////////////////// + // https://www.microsoft.com/typography/OTSPEC/chapter2.htm + + var langSysTable = { + reserved: Parser.uShort, + reqFeatureIndex: Parser.uShort, + featureIndexes: Parser.uShortList + }; + + Parser.prototype.parseScriptList = function() { + return this.parsePointer(Parser.recordList({ + tag: Parser.tag, + script: Parser.pointer({ + defaultLangSys: Parser.pointer(langSysTable), + langSysRecords: Parser.recordList({ + tag: Parser.tag, + langSys: Parser.pointer(langSysTable) + }) + }) + })) || []; + }; + + Parser.prototype.parseFeatureList = function() { + return this.parsePointer(Parser.recordList({ + tag: Parser.tag, + feature: Parser.pointer({ + featureParams: Parser.offset16, + lookupListIndexes: Parser.uShortList + }) + })) || []; + }; + + Parser.prototype.parseLookupList = function(lookupTableParsers) { + return this.parsePointer(Parser.list(Parser.pointer(function() { + var lookupType = this.parseUShort(); + check.argument(1 <= lookupType && lookupType <= 9, 'GPOS/GSUB lookup type ' + lookupType + ' unknown.'); + var lookupFlag = this.parseUShort(); + var useMarkFilteringSet = lookupFlag & 0x10; + return { + lookupType: lookupType, + lookupFlag: lookupFlag, + subtables: this.parseList(Parser.pointer(lookupTableParsers[lookupType])), + markFilteringSet: useMarkFilteringSet ? this.parseUShort() : undefined + }; + }))) || []; + }; + + Parser.prototype.parseFeatureVariationsList = function() { + return this.parsePointer32(function() { + var majorVersion = this.parseUShort(); + var minorVersion = this.parseUShort(); + check.argument(majorVersion === 1 && minorVersion < 1, 'GPOS/GSUB feature variations table unknown.'); + var featureVariations = this.parseRecordList32({ + conditionSetOffset: Parser.offset32, + featureTableSubstitutionOffset: Parser.offset32 + }); + return featureVariations; + }) || []; + }; + + var parse = { + getByte: getByte, + getCard8: getByte, + getUShort: getUShort, + getCard16: getUShort, + getShort: getShort, + getULong: getULong, + getFixed: getFixed, + getTag: getTag, + getOffset: getOffset, + getBytes: getBytes, + bytesToString: bytesToString, + Parser: Parser, + }; + + // The `cmap` table stores the mappings from characters to glyphs. + + function parseCmapTableFormat12(cmap, p) { + //Skip reserved. + p.parseUShort(); + + // Length in bytes of the sub-tables. + cmap.length = p.parseULong(); + cmap.language = p.parseULong(); + + var groupCount; + cmap.groupCount = groupCount = p.parseULong(); + cmap.glyphIndexMap = {}; + + for (var i = 0; i < groupCount; i += 1) { + var startCharCode = p.parseULong(); + var endCharCode = p.parseULong(); + var startGlyphId = p.parseULong(); + + for (var c = startCharCode; c <= endCharCode; c += 1) { + cmap.glyphIndexMap[c] = startGlyphId; + startGlyphId++; + } + } + } + + function parseCmapTableFormat4(cmap, p, data, start, offset) { + // Length in bytes of the sub-tables. + cmap.length = p.parseUShort(); + cmap.language = p.parseUShort(); + + // segCount is stored x 2. + var segCount; + cmap.segCount = segCount = p.parseUShort() >> 1; + + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + + // The "unrolled" mapping from character codes to glyph indices. + cmap.glyphIndexMap = {}; + var endCountParser = new parse.Parser(data, start + offset + 14); + var startCountParser = new parse.Parser(data, start + offset + 16 + segCount * 2); + var idDeltaParser = new parse.Parser(data, start + offset + 16 + segCount * 4); + var idRangeOffsetParser = new parse.Parser(data, start + offset + 16 + segCount * 6); + var glyphIndexOffset = start + offset + 16 + segCount * 8; + for (var i = 0; i < segCount - 1; i += 1) { + var glyphIndex = (void 0); + var endCount = endCountParser.parseUShort(); + var startCount = startCountParser.parseUShort(); + var idDelta = idDeltaParser.parseShort(); + var idRangeOffset = idRangeOffsetParser.parseUShort(); + for (var c = startCount; c <= endCount; c += 1) { + if (idRangeOffset !== 0) { + // The idRangeOffset is relative to the current position in the idRangeOffset array. + // Take the current offset in the idRangeOffset array. + glyphIndexOffset = (idRangeOffsetParser.offset + idRangeOffsetParser.relativeOffset - 2); + + // Add the value of the idRangeOffset, which will move us into the glyphIndex array. + glyphIndexOffset += idRangeOffset; + + // Then add the character index of the current segment, multiplied by 2 for USHORTs. + glyphIndexOffset += (c - startCount) * 2; + glyphIndex = parse.getUShort(data, glyphIndexOffset); + if (glyphIndex !== 0) { + glyphIndex = (glyphIndex + idDelta) & 0xFFFF; + } + } else { + glyphIndex = (c + idDelta) & 0xFFFF; + } + + cmap.glyphIndexMap[c] = glyphIndex; + } + } + } + + // Parse the `cmap` table. This table stores the mappings from characters to glyphs. + // There are many available formats, but we only support the Windows format 4 and 12. + // This function returns a `CmapEncoding` object or null if no supported format could be found. + function parseCmapTable(data, start) { + var cmap = {}; + cmap.version = parse.getUShort(data, start); + check.argument(cmap.version === 0, 'cmap table version should be 0.'); + + // The cmap table can contain many sub-tables, each with their own format. + // We're only interested in a "platform 0" (Unicode format) and "platform 3" (Windows format) table. + cmap.numTables = parse.getUShort(data, start + 2); + var offset = -1; + for (var i = cmap.numTables - 1; i >= 0; i -= 1) { + var platformId = parse.getUShort(data, start + 4 + (i * 8)); + var encodingId = parse.getUShort(data, start + 4 + (i * 8) + 2); + if ((platformId === 3 && (encodingId === 0 || encodingId === 1 || encodingId === 10)) || + (platformId === 0 && (encodingId === 0 || encodingId === 1 || encodingId === 2 || encodingId === 3 || encodingId === 4))) { + offset = parse.getULong(data, start + 4 + (i * 8) + 4); + break; + } + } + + if (offset === -1) { + // There is no cmap table in the font that we support. + throw new Error('No valid cmap sub-tables found.'); + } + + var p = new parse.Parser(data, start + offset); + cmap.format = p.parseUShort(); + + if (cmap.format === 12) { + parseCmapTableFormat12(cmap, p); + } else if (cmap.format === 4) { + parseCmapTableFormat4(cmap, p, data, start, offset); + } else { + throw new Error('Only format 4 and 12 cmap tables are supported (found format ' + cmap.format + ').'); + } + + return cmap; + } + + function addSegment(t, code, glyphIndex) { + t.segments.push({ + end: code, + start: code, + delta: -(code - glyphIndex), + offset: 0, + glyphIndex: glyphIndex + }); + } + + function addTerminatorSegment(t) { + t.segments.push({ + end: 0xFFFF, + start: 0xFFFF, + delta: 1, + offset: 0 + }); + } + + // Make cmap table, format 4 by default, 12 if needed only + function makeCmapTable(glyphs) { + // Plan 0 is the base Unicode Plan but emojis, for example are on another plan, and needs cmap 12 format (with 32bit) + var isPlan0Only = true; + var i; + + // Check if we need to add cmap format 12 or if format 4 only is fine + for (i = glyphs.length - 1; i > 0; i -= 1) { + var g = glyphs.get(i); + if (g.unicode > 65535) { + console.log('Adding CMAP format 12 (needed!)'); + isPlan0Only = false; + break; + } + } + + var cmapTable = [ + {name: 'version', type: 'USHORT', value: 0}, + {name: 'numTables', type: 'USHORT', value: isPlan0Only ? 1 : 2}, + + // CMAP 4 header + {name: 'platformID', type: 'USHORT', value: 3}, + {name: 'encodingID', type: 'USHORT', value: 1}, + {name: 'offset', type: 'ULONG', value: isPlan0Only ? 12 : (12 + 8)} + ]; + + if (!isPlan0Only) + { cmapTable = cmapTable.concat([ + // CMAP 12 header + {name: 'cmap12PlatformID', type: 'USHORT', value: 3}, // We encode only for PlatformID = 3 (Windows) because it is supported everywhere + {name: 'cmap12EncodingID', type: 'USHORT', value: 10}, + {name: 'cmap12Offset', type: 'ULONG', value: 0} + ]); } + + cmapTable = cmapTable.concat([ + // CMAP 4 Subtable + {name: 'format', type: 'USHORT', value: 4}, + {name: 'cmap4Length', type: 'USHORT', value: 0}, + {name: 'language', type: 'USHORT', value: 0}, + {name: 'segCountX2', type: 'USHORT', value: 0}, + {name: 'searchRange', type: 'USHORT', value: 0}, + {name: 'entrySelector', type: 'USHORT', value: 0}, + {name: 'rangeShift', type: 'USHORT', value: 0} + ]); + + var t = new table.Table('cmap', cmapTable); + + t.segments = []; + for (i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + for (var j = 0; j < glyph.unicodes.length; j += 1) { + addSegment(t, glyph.unicodes[j], i); + } + + t.segments = t.segments.sort(function (a, b) { + return a.start - b.start; + }); + } + + addTerminatorSegment(t); + + var segCount = t.segments.length; + var segCountToRemove = 0; + + // CMAP 4 + // Set up parallel segment arrays. + var endCounts = []; + var startCounts = []; + var idDeltas = []; + var idRangeOffsets = []; + var glyphIds = []; + + // CMAP 12 + var cmap12Groups = []; + + // Reminder this loop is not following the specification at 100% + // The specification -> find suites of characters and make a group + // Here we're doing one group for each letter + // Doing as the spec can save 8 times (or more) space + for (i = 0; i < segCount; i += 1) { + var segment = t.segments[i]; + + // CMAP 4 + if (segment.end <= 65535 && segment.start <= 65535) { + endCounts = endCounts.concat({name: 'end_' + i, type: 'USHORT', value: segment.end}); + startCounts = startCounts.concat({name: 'start_' + i, type: 'USHORT', value: segment.start}); + idDeltas = idDeltas.concat({name: 'idDelta_' + i, type: 'SHORT', value: segment.delta}); + idRangeOffsets = idRangeOffsets.concat({name: 'idRangeOffset_' + i, type: 'USHORT', value: segment.offset}); + if (segment.glyphId !== undefined) { + glyphIds = glyphIds.concat({name: 'glyph_' + i, type: 'USHORT', value: segment.glyphId}); + } + } else { + // Skip Unicode > 65535 (16bit unsigned max) for CMAP 4, will be added in CMAP 12 + segCountToRemove += 1; + } + + // CMAP 12 + // Skip Terminator Segment + if (!isPlan0Only && segment.glyphIndex !== undefined) { + cmap12Groups = cmap12Groups.concat({name: 'cmap12Start_' + i, type: 'ULONG', value: segment.start}); + cmap12Groups = cmap12Groups.concat({name: 'cmap12End_' + i, type: 'ULONG', value: segment.end}); + cmap12Groups = cmap12Groups.concat({name: 'cmap12Glyph_' + i, type: 'ULONG', value: segment.glyphIndex}); + } + } + + // CMAP 4 Subtable + t.segCountX2 = (segCount - segCountToRemove) * 2; + t.searchRange = Math.pow(2, Math.floor(Math.log((segCount - segCountToRemove)) / Math.log(2))) * 2; + t.entrySelector = Math.log(t.searchRange / 2) / Math.log(2); + t.rangeShift = t.segCountX2 - t.searchRange; + + t.fields = t.fields.concat(endCounts); + t.fields.push({name: 'reservedPad', type: 'USHORT', value: 0}); + t.fields = t.fields.concat(startCounts); + t.fields = t.fields.concat(idDeltas); + t.fields = t.fields.concat(idRangeOffsets); + t.fields = t.fields.concat(glyphIds); + + t.cmap4Length = 14 + // Subtable header + endCounts.length * 2 + + 2 + // reservedPad + startCounts.length * 2 + + idDeltas.length * 2 + + idRangeOffsets.length * 2 + + glyphIds.length * 2; + + if (!isPlan0Only) { + // CMAP 12 Subtable + var cmap12Length = 16 + // Subtable header + cmap12Groups.length * 4; + + t.cmap12Offset = 12 + (2 * 2) + 4 + t.cmap4Length; + t.fields = t.fields.concat([ + {name: 'cmap12Format', type: 'USHORT', value: 12}, + {name: 'cmap12Reserved', type: 'USHORT', value: 0}, + {name: 'cmap12Length', type: 'ULONG', value: cmap12Length}, + {name: 'cmap12Language', type: 'ULONG', value: 0}, + {name: 'cmap12nGroups', type: 'ULONG', value: cmap12Groups.length / 3} + ]); + + t.fields = t.fields.concat(cmap12Groups); + } + + return t; + } + + var cmap = { parse: parseCmapTable, make: makeCmapTable }; + + // Glyph encoding + + var cffStandardStrings = [ + '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', + 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', + 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', + 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling', + 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', + 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph', + 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', + 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', + 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', + 'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu', + 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', + 'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright', + 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', + 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex', + 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', + 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute', + 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', + 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', + 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', + 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff', 'onedotenleader', + 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', + 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', + 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', + 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', + 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', + 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', + 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', + 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', + 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', + 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', + 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', + 'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', + 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', + 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', + 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', + 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', + 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', + 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', + 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', + '001.001', '001.002', '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold']; + + var cffStandardEncoding = [ + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', + 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', + 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', + 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', + 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', + 'daggerdbl', 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', + 'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', 'tilde', + 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', + 'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', 'ordfeminine', '', '', '', + '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', + 'lslash', 'oslash', 'oe', 'germandbls']; + + var cffExpertEncoding = [ + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle', 'dollarsuperior', + 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', + 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', + 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', + 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', '', 'asuperior', + 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', '', '', 'isuperior', '', '', 'lsuperior', 'msuperior', + 'nsuperior', 'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior', '', 'ff', 'fi', 'fl', 'ffi', 'ffl', + 'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', + 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', + 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', + 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', '', '', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', + 'Brevesmall', 'Caronsmall', '', 'Dotaccentsmall', '', '', 'Macronsmall', '', '', 'figuredash', 'hypheninferior', + '', '', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters', + 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', '', + '', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', + 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', + 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', + 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', + 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', + 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', + 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', + 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', + 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall']; + + var standardNames = [ + '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', + 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', + 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', + 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', + 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', + 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', 'Odieresis', 'Udieresis', 'aacute', 'agrave', + 'acircumflex', 'adieresis', 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis', + 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex', 'odieresis', + 'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', + 'bullet', 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute', 'dieresis', 'notequal', + 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal', 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', + 'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', 'questiondown', + 'exclamdown', 'logicalnot', 'radical', 'florin', 'approxequal', 'Delta', 'guillemotleft', 'guillemotright', + 'ellipsis', 'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft', + 'quotedblright', 'quoteleft', 'quoteright', 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', + 'currency', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered', 'quotesinglbase', + 'quotedblbase', 'perthousand', 'Acircumflex', 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', + 'Icircumflex', 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute', 'Ucircumflex', + 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', + 'ogonek', 'caron', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar', 'Eth', 'eth', + 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply', 'onesuperior', 'twosuperior', 'threesuperior', + 'onehalf', 'onequarter', 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', 'scedilla', + 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat']; + + /** + * This is the encoding used for fonts created from scratch. + * It loops through all glyphs and finds the appropriate unicode value. + * Since it's linear time, other encodings will be faster. + * @exports opentype.DefaultEncoding + * @class + * @constructor + * @param {opentype.Font} + */ + function DefaultEncoding(font) { + this.font = font; + } + + DefaultEncoding.prototype.charToGlyphIndex = function(c) { + var code = c.codePointAt(0); + var glyphs = this.font.glyphs; + if (glyphs) { + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + for (var j = 0; j < glyph.unicodes.length; j += 1) { + if (glyph.unicodes[j] === code) { + return i; + } + } + } + } + return null; + }; + + /** + * @exports opentype.CmapEncoding + * @class + * @constructor + * @param {Object} cmap - a object with the cmap encoded data + */ + function CmapEncoding(cmap) { + this.cmap = cmap; + } + + /** + * @param {string} c - the character + * @return {number} The glyph index. + */ + CmapEncoding.prototype.charToGlyphIndex = function(c) { + return this.cmap.glyphIndexMap[c.codePointAt(0)] || 0; + }; + + /** + * @exports opentype.CffEncoding + * @class + * @constructor + * @param {string} encoding - The encoding + * @param {Array} charset - The character set. + */ + function CffEncoding(encoding, charset) { + this.encoding = encoding; + this.charset = charset; + } + + /** + * @param {string} s - The character + * @return {number} The index. + */ + CffEncoding.prototype.charToGlyphIndex = function(s) { + var code = s.codePointAt(0); + var charName = this.encoding[code]; + return this.charset.indexOf(charName); + }; + + /** + * @exports opentype.GlyphNames + * @class + * @constructor + * @param {Object} post + */ + function GlyphNames(post) { + switch (post.version) { + case 1: + this.names = standardNames.slice(); + break; + case 2: + this.names = new Array(post.numberOfGlyphs); + for (var i = 0; i < post.numberOfGlyphs; i++) { + if (post.glyphNameIndex[i] < standardNames.length) { + this.names[i] = standardNames[post.glyphNameIndex[i]]; + } else { + this.names[i] = post.names[post.glyphNameIndex[i] - standardNames.length]; + } + } + + break; + case 2.5: + this.names = new Array(post.numberOfGlyphs); + for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { + this.names[i$1] = standardNames[i$1 + post.glyphNameIndex[i$1]]; + } + + break; + case 3: + this.names = []; + break; + default: + this.names = []; + break; + } + } + + /** + * Gets the index of a glyph by name. + * @param {string} name - The glyph name + * @return {number} The index + */ + GlyphNames.prototype.nameToGlyphIndex = function(name) { + return this.names.indexOf(name); + }; + + /** + * @param {number} gid + * @return {string} + */ + GlyphNames.prototype.glyphIndexToName = function(gid) { + return this.names[gid]; + }; + + function addGlyphNamesAll(font) { + var glyph; + var glyphIndexMap = font.tables.cmap.glyphIndexMap; + var charCodes = Object.keys(glyphIndexMap); + + for (var i = 0; i < charCodes.length; i += 1) { + var c = charCodes[i]; + var glyphIndex = glyphIndexMap[c]; + glyph = font.glyphs.get(glyphIndex); + glyph.addUnicode(parseInt(c)); + } + + for (var i$1 = 0; i$1 < font.glyphs.length; i$1 += 1) { + glyph = font.glyphs.get(i$1); + if (font.cffEncoding) { + if (font.isCIDFont) { + glyph.name = 'gid' + i$1; + } else { + glyph.name = font.cffEncoding.charset[i$1]; + } + } else if (font.glyphNames.names) { + glyph.name = font.glyphNames.glyphIndexToName(i$1); + } + } + } + + function addGlyphNamesToUnicodeMap(font) { + font._IndexToUnicodeMap = {}; + + var glyphIndexMap = font.tables.cmap.glyphIndexMap; + var charCodes = Object.keys(glyphIndexMap); + + for (var i = 0; i < charCodes.length; i += 1) { + var c = charCodes[i]; + var glyphIndex = glyphIndexMap[c]; + if (font._IndexToUnicodeMap[glyphIndex] === undefined) { + font._IndexToUnicodeMap[glyphIndex] = { + unicodes: [parseInt(c)] + }; + } else { + font._IndexToUnicodeMap[glyphIndex].unicodes.push(parseInt(c)); + } + } + } + + /** + * @alias opentype.addGlyphNames + * @param {opentype.Font} + * @param {Object} + */ + function addGlyphNames(font, opt) { + if (opt.lowMemory) { + addGlyphNamesToUnicodeMap(font); + } else { + addGlyphNamesAll(font); + } + } + + // Drawing utility functions. + + // Draw a line on the given context from point `x1,y1` to point `x2,y2`. + function line(ctx, x1, y1, x2, y2) { + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); + } + + var draw = { line: line }; + + // The Glyph object + // import glyf from './tables/glyf' Can't be imported here, because it's a circular dependency + + function getPathDefinition(glyph, path) { + var _path = path || new Path(); + return { + configurable: true, + + get: function() { + if (typeof _path === 'function') { + _path = _path(); + } + + return _path; + }, + + set: function(p) { + _path = p; + } + }; + } + /** + * @typedef GlyphOptions + * @type Object + * @property {string} [name] - The glyph name + * @property {number} [unicode] + * @property {Array} [unicodes] + * @property {number} [xMin] + * @property {number} [yMin] + * @property {number} [xMax] + * @property {number} [yMax] + * @property {number} [advanceWidth] + */ + + // A Glyph is an individual mark that often corresponds to a character. + // Some glyphs, such as ligatures, are a combination of many characters. + // Glyphs are the basic building blocks of a font. + // + // The `Glyph` class contains utility methods for drawing the path and its points. + /** + * @exports opentype.Glyph + * @class + * @param {GlyphOptions} + * @constructor + */ + function Glyph(options) { + // By putting all the code on a prototype function (which is only declared once) + // we reduce the memory requirements for larger fonts by some 2% + this.bindConstructorValues(options); + } + + /** + * @param {GlyphOptions} + */ + Glyph.prototype.bindConstructorValues = function(options) { + this.index = options.index || 0; + + // These three values cannot be deferred for memory optimization: + this.name = options.name || null; + this.unicode = options.unicode || undefined; + this.unicodes = options.unicodes || options.unicode !== undefined ? [options.unicode] : []; + + // But by binding these values only when necessary, we reduce can + // the memory requirements by almost 3% for larger fonts. + if ('xMin' in options) { + this.xMin = options.xMin; + } + + if ('yMin' in options) { + this.yMin = options.yMin; + } + + if ('xMax' in options) { + this.xMax = options.xMax; + } + + if ('yMax' in options) { + this.yMax = options.yMax; + } + + if ('advanceWidth' in options) { + this.advanceWidth = options.advanceWidth; + } + + // The path for a glyph is the most memory intensive, and is bound as a value + // with a getter/setter to ensure we actually do path parsing only once the + // path is actually needed by anything. + Object.defineProperty(this, 'path', getPathDefinition(this, options.path)); + }; + + /** + * @param {number} + */ + Glyph.prototype.addUnicode = function(unicode) { + if (this.unicodes.length === 0) { + this.unicode = unicode; + } + + this.unicodes.push(unicode); + }; + + /** + * Calculate the minimum bounding box for this glyph. + * @return {opentype.BoundingBox} + */ + Glyph.prototype.getBoundingBox = function() { + return this.path.getBoundingBox(); + }; + + /** + * Convert the glyph to a Path we can draw on a drawing context. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {Object=} options - xScale, yScale to stretch the glyph. + * @param {opentype.Font} if hinting is to be used, the font + * @return {opentype.Path} + */ + Glyph.prototype.getPath = function(x, y, fontSize, options, font) { + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 72; + var commands; + var hPoints; + if (!options) { options = { }; } + var xScale = options.xScale; + var yScale = options.yScale; + + if (options.hinting && font && font.hinting) { + // in case of hinting, the hinting engine takes care + // of scaling the points (not the path) before hinting. + hPoints = this.path && font.hinting.exec(this, fontSize); + // in case the hinting engine failed hPoints is undefined + // and thus reverts to plain rending + } + + if (hPoints) { + // Call font.hinting.getCommands instead of `glyf.getPath(hPoints).commands` to avoid a circular dependency + commands = font.hinting.getCommands(hPoints); + x = Math.round(x); + y = Math.round(y); + // TODO in case of hinting xyScaling is not yet supported + xScale = yScale = 1; + } else { + commands = this.path.commands; + var scale = 1 / (this.path.unitsPerEm || 1000) * fontSize; + if (xScale === undefined) { xScale = scale; } + if (yScale === undefined) { yScale = scale; } + } + + var p = new Path(); + for (var i = 0; i < commands.length; i += 1) { + var cmd = commands[i]; + if (cmd.type === 'M') { + p.moveTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'L') { + p.lineTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'Q') { + p.quadraticCurveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), + x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'C') { + p.curveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), + x + (cmd.x2 * xScale), y + (-cmd.y2 * yScale), + x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'Z') { + p.closePath(); + } + } + + return p; + }; + + /** + * Split the glyph into contours. + * This function is here for backwards compatibility, and to + * provide raw access to the TrueType glyph outlines. + * @return {Array} + */ + Glyph.prototype.getContours = function() { + if (this.points === undefined) { + return []; + } + + var contours = []; + var currentContour = []; + for (var i = 0; i < this.points.length; i += 1) { + var pt = this.points[i]; + currentContour.push(pt); + if (pt.lastPointOfContour) { + contours.push(currentContour); + currentContour = []; + } + } + + check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); + return contours; + }; + + /** + * Calculate the xMin/yMin/xMax/yMax/lsb/rsb for a Glyph. + * @return {Object} + */ + Glyph.prototype.getMetrics = function() { + var commands = this.path.commands; + var xCoords = []; + var yCoords = []; + for (var i = 0; i < commands.length; i += 1) { + var cmd = commands[i]; + if (cmd.type !== 'Z') { + xCoords.push(cmd.x); + yCoords.push(cmd.y); + } + + if (cmd.type === 'Q' || cmd.type === 'C') { + xCoords.push(cmd.x1); + yCoords.push(cmd.y1); + } + + if (cmd.type === 'C') { + xCoords.push(cmd.x2); + yCoords.push(cmd.y2); + } + } + + var metrics = { + xMin: Math.min.apply(null, xCoords), + yMin: Math.min.apply(null, yCoords), + xMax: Math.max.apply(null, xCoords), + yMax: Math.max.apply(null, yCoords), + leftSideBearing: this.leftSideBearing + }; + + if (!isFinite(metrics.xMin)) { + metrics.xMin = 0; + } + + if (!isFinite(metrics.xMax)) { + metrics.xMax = this.advanceWidth; + } + + if (!isFinite(metrics.yMin)) { + metrics.yMin = 0; + } + + if (!isFinite(metrics.yMax)) { + metrics.yMax = 0; + } + + metrics.rightSideBearing = this.advanceWidth - metrics.leftSideBearing - (metrics.xMax - metrics.xMin); + return metrics; + }; + + /** + * Draw the glyph on the given context. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {Object=} options - xScale, yScale to stretch the glyph. + */ + Glyph.prototype.draw = function(ctx, x, y, fontSize, options) { + this.getPath(x, y, fontSize, options).draw(ctx); + }; + + /** + * Draw the points of the glyph. + * On-curve points will be drawn in blue, off-curve points will be drawn in red. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + */ + Glyph.prototype.drawPoints = function(ctx, x, y, fontSize) { + function drawCircles(l, x, y, scale) { + ctx.beginPath(); + for (var j = 0; j < l.length; j += 1) { + ctx.moveTo(x + (l[j].x * scale), y + (l[j].y * scale)); + ctx.arc(x + (l[j].x * scale), y + (l[j].y * scale), 2, 0, Math.PI * 2, false); + } + + ctx.closePath(); + ctx.fill(); + } + + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 24; + var scale = 1 / this.path.unitsPerEm * fontSize; + + var blueCircles = []; + var redCircles = []; + var path = this.path; + for (var i = 0; i < path.commands.length; i += 1) { + var cmd = path.commands[i]; + if (cmd.x !== undefined) { + blueCircles.push({x: cmd.x, y: -cmd.y}); + } + + if (cmd.x1 !== undefined) { + redCircles.push({x: cmd.x1, y: -cmd.y1}); + } + + if (cmd.x2 !== undefined) { + redCircles.push({x: cmd.x2, y: -cmd.y2}); + } + } + + ctx.fillStyle = 'blue'; + drawCircles(blueCircles, x, y, scale); + ctx.fillStyle = 'red'; + drawCircles(redCircles, x, y, scale); + }; + + /** + * Draw lines indicating important font measurements. + * Black lines indicate the origin of the coordinate system (point 0,0). + * Blue lines indicate the glyph bounding box. + * Green line indicates the advance width of the glyph. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + */ + Glyph.prototype.drawMetrics = function(ctx, x, y, fontSize) { + var scale; + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 24; + scale = 1 / this.path.unitsPerEm * fontSize; + ctx.lineWidth = 1; + + // Draw the origin + ctx.strokeStyle = 'black'; + draw.line(ctx, x, -10000, x, 10000); + draw.line(ctx, -10000, y, 10000, y); + + // This code is here due to memory optimization: by not using + // defaults in the constructor, we save a notable amount of memory. + var xMin = this.xMin || 0; + var yMin = this.yMin || 0; + var xMax = this.xMax || 0; + var yMax = this.yMax || 0; + var advanceWidth = this.advanceWidth || 0; + + // Draw the glyph box + ctx.strokeStyle = 'blue'; + draw.line(ctx, x + (xMin * scale), -10000, x + (xMin * scale), 10000); + draw.line(ctx, x + (xMax * scale), -10000, x + (xMax * scale), 10000); + draw.line(ctx, -10000, y + (-yMin * scale), 10000, y + (-yMin * scale)); + draw.line(ctx, -10000, y + (-yMax * scale), 10000, y + (-yMax * scale)); + + // Draw the advance width + ctx.strokeStyle = 'green'; + draw.line(ctx, x + (advanceWidth * scale), -10000, x + (advanceWidth * scale), 10000); + }; + + // The GlyphSet object + + // Define a property on the glyph that depends on the path being loaded. + function defineDependentProperty(glyph, externalName, internalName) { + Object.defineProperty(glyph, externalName, { + get: function() { + // Request the path property to make sure the path is loaded. + glyph.path; // jshint ignore:line + return glyph[internalName]; + }, + set: function(newValue) { + glyph[internalName] = newValue; + }, + enumerable: true, + configurable: true + }); + } + + /** + * A GlyphSet represents all glyphs available in the font, but modelled using + * a deferred glyph loader, for retrieving glyphs only once they are absolutely + * necessary, to keep the memory footprint down. + * @exports opentype.GlyphSet + * @class + * @param {opentype.Font} + * @param {Array} + */ + function GlyphSet(font, glyphs) { + this.font = font; + this.glyphs = {}; + if (Array.isArray(glyphs)) { + for (var i = 0; i < glyphs.length; i++) { + var glyph = glyphs[i]; + glyph.path.unitsPerEm = font.unitsPerEm; + this.glyphs[i] = glyph; + } + } + + this.length = (glyphs && glyphs.length) || 0; + } + + /** + * @param {number} index + * @return {opentype.Glyph} + */ + GlyphSet.prototype.get = function(index) { + // this.glyphs[index] is 'undefined' when low memory mode is on. glyph is pushed on request only. + if (this.glyphs[index] === undefined) { + this.font._push(index); + if (typeof this.glyphs[index] === 'function') { + this.glyphs[index] = this.glyphs[index](); + } + + var glyph = this.glyphs[index]; + var unicodeObj = this.font._IndexToUnicodeMap[index]; + + if (unicodeObj) { + for (var j = 0; j < unicodeObj.unicodes.length; j++) + { glyph.addUnicode(unicodeObj.unicodes[j]); } + } + + if (this.font.cffEncoding) { + if (this.font.isCIDFont) { + glyph.name = 'gid' + index; + } else { + glyph.name = this.font.cffEncoding.charset[index]; + } + } else if (this.font.glyphNames.names) { + glyph.name = this.font.glyphNames.glyphIndexToName(index); + } + + this.glyphs[index].advanceWidth = this.font._hmtxTableData[index].advanceWidth; + this.glyphs[index].leftSideBearing = this.font._hmtxTableData[index].leftSideBearing; + } else { + if (typeof this.glyphs[index] === 'function') { + this.glyphs[index] = this.glyphs[index](); + } + } + + return this.glyphs[index]; + }; + + /** + * @param {number} index + * @param {Object} + */ + GlyphSet.prototype.push = function(index, loader) { + this.glyphs[index] = loader; + this.length++; + }; + + /** + * @alias opentype.glyphLoader + * @param {opentype.Font} font + * @param {number} index + * @return {opentype.Glyph} + */ + function glyphLoader(font, index) { + return new Glyph({index: index, font: font}); + } + + /** + * Generate a stub glyph that can be filled with all metadata *except* + * the "points" and "path" properties, which must be loaded only once + * the glyph's path is actually requested for text shaping. + * @alias opentype.ttfGlyphLoader + * @param {opentype.Font} font + * @param {number} index + * @param {Function} parseGlyph + * @param {Object} data + * @param {number} position + * @param {Function} buildPath + * @return {opentype.Glyph} + */ + function ttfGlyphLoader(font, index, parseGlyph, data, position, buildPath) { + return function() { + var glyph = new Glyph({index: index, font: font}); + + glyph.path = function() { + parseGlyph(glyph, data, position); + var path = buildPath(font.glyphs, glyph); + path.unitsPerEm = font.unitsPerEm; + return path; + }; + + defineDependentProperty(glyph, 'xMin', '_xMin'); + defineDependentProperty(glyph, 'xMax', '_xMax'); + defineDependentProperty(glyph, 'yMin', '_yMin'); + defineDependentProperty(glyph, 'yMax', '_yMax'); + + return glyph; + }; + } + /** + * @alias opentype.cffGlyphLoader + * @param {opentype.Font} font + * @param {number} index + * @param {Function} parseCFFCharstring + * @param {string} charstring + * @return {opentype.Glyph} + */ + function cffGlyphLoader(font, index, parseCFFCharstring, charstring) { + return function() { + var glyph = new Glyph({index: index, font: font}); + + glyph.path = function() { + var path = parseCFFCharstring(font, glyph, charstring); + path.unitsPerEm = font.unitsPerEm; + return path; + }; + + return glyph; + }; + } + + var glyphset = { GlyphSet: GlyphSet, glyphLoader: glyphLoader, ttfGlyphLoader: ttfGlyphLoader, cffGlyphLoader: cffGlyphLoader }; + + // The `CFF` table contains the glyph outlines in PostScript format. + + // Custom equals function that can also check lists. + function equals(a, b) { + if (a === b) { + return true; + } else if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) { + return false; + } + + for (var i = 0; i < a.length; i += 1) { + if (!equals(a[i], b[i])) { + return false; + } + } + + return true; + } else { + return false; + } + } + + // Subroutines are encoded using the negative half of the number space. + // See type 2 chapter 4.7 "Subroutine operators". + function calcCFFSubroutineBias(subrs) { + var bias; + if (subrs.length < 1240) { + bias = 107; + } else if (subrs.length < 33900) { + bias = 1131; + } else { + bias = 32768; + } + + return bias; + } + + // Parse a `CFF` INDEX array. + // An index array consists of a list of offsets, then a list of objects at those offsets. + function parseCFFIndex(data, start, conversionFn) { + var offsets = []; + var objects = []; + var count = parse.getCard16(data, start); + var objectOffset; + var endOffset; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + var pos = start + 3; + for (var i = 0; i < count + 1; i += 1) { + offsets.push(parse.getOffset(data, pos, offsetSize)); + pos += offsetSize; + } + + // The total size of the index array is 4 header bytes + the value of the last offset. + endOffset = objectOffset + offsets[count]; + } else { + endOffset = start + 2; + } + + for (var i$1 = 0; i$1 < offsets.length - 1; i$1 += 1) { + var value = parse.getBytes(data, objectOffset + offsets[i$1], objectOffset + offsets[i$1 + 1]); + if (conversionFn) { + value = conversionFn(value); + } + + objects.push(value); + } + + return {objects: objects, startOffset: start, endOffset: endOffset}; + } + + function parseCFFIndexLowMemory(data, start) { + var offsets = []; + var count = parse.getCard16(data, start); + var objectOffset; + var endOffset; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + var pos = start + 3; + for (var i = 0; i < count + 1; i += 1) { + offsets.push(parse.getOffset(data, pos, offsetSize)); + pos += offsetSize; + } + + // The total size of the index array is 4 header bytes + the value of the last offset. + endOffset = objectOffset + offsets[count]; + } else { + endOffset = start + 2; + } + + return {offsets: offsets, startOffset: start, endOffset: endOffset}; + } + function getCffIndexObject(i, offsets, data, start, conversionFn) { + var count = parse.getCard16(data, start); + var objectOffset = 0; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + } + + var value = parse.getBytes(data, objectOffset + offsets[i], objectOffset + offsets[i + 1]); + if (conversionFn) { + value = conversionFn(value); + } + return value; + } + + // Parse a `CFF` DICT real value. + function parseFloatOperand(parser) { + var s = ''; + var eof = 15; + var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-']; + while (true) { + var b = parser.parseByte(); + var n1 = b >> 4; + var n2 = b & 15; + + if (n1 === eof) { + break; + } + + s += lookup[n1]; + + if (n2 === eof) { + break; + } + + s += lookup[n2]; + } + + return parseFloat(s); + } + + // Parse a `CFF` DICT operand. + function parseOperand(parser, b0) { + var b1; + var b2; + var b3; + var b4; + if (b0 === 28) { + b1 = parser.parseByte(); + b2 = parser.parseByte(); + return b1 << 8 | b2; + } + + if (b0 === 29) { + b1 = parser.parseByte(); + b2 = parser.parseByte(); + b3 = parser.parseByte(); + b4 = parser.parseByte(); + return b1 << 24 | b2 << 16 | b3 << 8 | b4; + } + + if (b0 === 30) { + return parseFloatOperand(parser); + } + + if (b0 >= 32 && b0 <= 246) { + return b0 - 139; + } + + if (b0 >= 247 && b0 <= 250) { + b1 = parser.parseByte(); + return (b0 - 247) * 256 + b1 + 108; + } + + if (b0 >= 251 && b0 <= 254) { + b1 = parser.parseByte(); + return -(b0 - 251) * 256 - b1 - 108; + } + + throw new Error('Invalid b0 ' + b0); + } + + // Convert the entries returned by `parseDict` to a proper dictionary. + // If a value is a list of one, it is unpacked. + function entriesToObject(entries) { + var o = {}; + for (var i = 0; i < entries.length; i += 1) { + var key = entries[i][0]; + var values = entries[i][1]; + var value = (void 0); + if (values.length === 1) { + value = values[0]; + } else { + value = values; + } + + if (o.hasOwnProperty(key) && !isNaN(o[key])) { + throw new Error('Object ' + o + ' already has key ' + key); + } + + o[key] = value; + } + + return o; + } + + // Parse a `CFF` DICT object. + // A dictionary contains key-value pairs in a compact tokenized format. + function parseCFFDict(data, start, size) { + start = start !== undefined ? start : 0; + var parser = new parse.Parser(data, start); + var entries = []; + var operands = []; + size = size !== undefined ? size : data.length; + + while (parser.relativeOffset < size) { + var op = parser.parseByte(); + + // The first byte for each dict item distinguishes between operator (key) and operand (value). + // Values <= 21 are operators. + if (op <= 21) { + // Two-byte operators have an initial escape byte of 12. + if (op === 12) { + op = 1200 + parser.parseByte(); + } + + entries.push([op, operands]); + operands = []; + } else { + // Since the operands (values) come before the operators (keys), we store all operands in a list + // until we encounter an operator. + operands.push(parseOperand(parser, op)); + } + } + + return entriesToObject(entries); + } + + // Given a String Index (SID), return the value of the string. + // Strings below index 392 are standard CFF strings and are not encoded in the font. + function getCFFString(strings, index) { + if (index <= 390) { + index = cffStandardStrings[index]; + } else { + index = strings[index - 391]; + } + + return index; + } + + // Interpret a dictionary and return a new dictionary with readable keys and values for missing entries. + // This function takes `meta` which is a list of objects containing `operand`, `name` and `default`. + function interpretDict(dict, meta, strings) { + var newDict = {}; + var value; + + // Because we also want to include missing values, we start out from the meta list + // and lookup values in the dict. + for (var i = 0; i < meta.length; i += 1) { + var m = meta[i]; + + if (Array.isArray(m.type)) { + var values = []; + values.length = m.type.length; + for (var j = 0; j < m.type.length; j++) { + value = dict[m.op] !== undefined ? dict[m.op][j] : undefined; + if (value === undefined) { + value = m.value !== undefined && m.value[j] !== undefined ? m.value[j] : null; + } + if (m.type[j] === 'SID') { + value = getCFFString(strings, value); + } + values[j] = value; + } + newDict[m.name] = values; + } else { + value = dict[m.op]; + if (value === undefined) { + value = m.value !== undefined ? m.value : null; + } + + if (m.type === 'SID') { + value = getCFFString(strings, value); + } + newDict[m.name] = value; + } + } + + return newDict; + } + + // Parse the CFF header. + function parseCFFHeader(data, start) { + var header = {}; + header.formatMajor = parse.getCard8(data, start); + header.formatMinor = parse.getCard8(data, start + 1); + header.size = parse.getCard8(data, start + 2); + header.offsetSize = parse.getCard8(data, start + 3); + header.startOffset = start; + header.endOffset = start + 4; + return header; + } + + var TOP_DICT_META = [ + {name: 'version', op: 0, type: 'SID'}, + {name: 'notice', op: 1, type: 'SID'}, + {name: 'copyright', op: 1200, type: 'SID'}, + {name: 'fullName', op: 2, type: 'SID'}, + {name: 'familyName', op: 3, type: 'SID'}, + {name: 'weight', op: 4, type: 'SID'}, + {name: 'isFixedPitch', op: 1201, type: 'number', value: 0}, + {name: 'italicAngle', op: 1202, type: 'number', value: 0}, + {name: 'underlinePosition', op: 1203, type: 'number', value: -100}, + {name: 'underlineThickness', op: 1204, type: 'number', value: 50}, + {name: 'paintType', op: 1205, type: 'number', value: 0}, + {name: 'charstringType', op: 1206, type: 'number', value: 2}, + { + name: 'fontMatrix', + op: 1207, + type: ['real', 'real', 'real', 'real', 'real', 'real'], + value: [0.001, 0, 0, 0.001, 0, 0] + }, + {name: 'uniqueId', op: 13, type: 'number'}, + {name: 'fontBBox', op: 5, type: ['number', 'number', 'number', 'number'], value: [0, 0, 0, 0]}, + {name: 'strokeWidth', op: 1208, type: 'number', value: 0}, + {name: 'xuid', op: 14, type: [], value: null}, + {name: 'charset', op: 15, type: 'offset', value: 0}, + {name: 'encoding', op: 16, type: 'offset', value: 0}, + {name: 'charStrings', op: 17, type: 'offset', value: 0}, + {name: 'private', op: 18, type: ['number', 'offset'], value: [0, 0]}, + {name: 'ros', op: 1230, type: ['SID', 'SID', 'number']}, + {name: 'cidFontVersion', op: 1231, type: 'number', value: 0}, + {name: 'cidFontRevision', op: 1232, type: 'number', value: 0}, + {name: 'cidFontType', op: 1233, type: 'number', value: 0}, + {name: 'cidCount', op: 1234, type: 'number', value: 8720}, + {name: 'uidBase', op: 1235, type: 'number'}, + {name: 'fdArray', op: 1236, type: 'offset'}, + {name: 'fdSelect', op: 1237, type: 'offset'}, + {name: 'fontName', op: 1238, type: 'SID'} + ]; + + var PRIVATE_DICT_META = [ + {name: 'subrs', op: 19, type: 'offset', value: 0}, + {name: 'defaultWidthX', op: 20, type: 'number', value: 0}, + {name: 'nominalWidthX', op: 21, type: 'number', value: 0} + ]; + + // Parse the CFF top dictionary. A CFF table can contain multiple fonts, each with their own top dictionary. + // The top dictionary contains the essential metadata for the font, together with the private dictionary. + function parseCFFTopDict(data, strings) { + var dict = parseCFFDict(data, 0, data.byteLength); + return interpretDict(dict, TOP_DICT_META, strings); + } + + // Parse the CFF private dictionary. We don't fully parse out all the values, only the ones we need. + function parseCFFPrivateDict(data, start, size, strings) { + var dict = parseCFFDict(data, start, size); + return interpretDict(dict, PRIVATE_DICT_META, strings); + } + + // Returns a list of "Top DICT"s found using an INDEX list. + // Used to read both the usual high-level Top DICTs and also the FDArray + // discovered inside CID-keyed fonts. When a Top DICT has a reference to + // a Private DICT that is read and saved into the Top DICT. + // + // In addition to the expected/optional values as outlined in TOP_DICT_META + // the following values might be saved into the Top DICT. + // + // _subrs [] array of local CFF subroutines from Private DICT + // _subrsBias bias value computed from number of subroutines + // (see calcCFFSubroutineBias() and parseCFFCharstring()) + // _defaultWidthX default widths for CFF characters + // _nominalWidthX bias added to width embedded within glyph description + // + // _privateDict saved copy of parsed Private DICT from Top DICT + function gatherCFFTopDicts(data, start, cffIndex, strings) { + var topDictArray = []; + for (var iTopDict = 0; iTopDict < cffIndex.length; iTopDict += 1) { + var topDictData = new DataView(new Uint8Array(cffIndex[iTopDict]).buffer); + var topDict = parseCFFTopDict(topDictData, strings); + topDict._subrs = []; + topDict._subrsBias = 0; + var privateSize = topDict.private[0]; + var privateOffset = topDict.private[1]; + if (privateSize !== 0 && privateOffset !== 0) { + var privateDict = parseCFFPrivateDict(data, privateOffset + start, privateSize, strings); + topDict._defaultWidthX = privateDict.defaultWidthX; + topDict._nominalWidthX = privateDict.nominalWidthX; + if (privateDict.subrs !== 0) { + var subrOffset = privateOffset + privateDict.subrs; + var subrIndex = parseCFFIndex(data, subrOffset + start); + topDict._subrs = subrIndex.objects; + topDict._subrsBias = calcCFFSubroutineBias(topDict._subrs); + } + topDict._privateDict = privateDict; + } + topDictArray.push(topDict); + } + return topDictArray; + } + + // Parse the CFF charset table, which contains internal names for all the glyphs. + // This function will return a list of glyph names. + // See Adobe TN #5176 chapter 13, "Charsets". + function parseCFFCharset(data, start, nGlyphs, strings) { + var sid; + var count; + var parser = new parse.Parser(data, start); + + // The .notdef glyph is not included, so subtract 1. + nGlyphs -= 1; + var charset = ['.notdef']; + + var format = parser.parseCard8(); + if (format === 0) { + for (var i = 0; i < nGlyphs; i += 1) { + sid = parser.parseSID(); + charset.push(getCFFString(strings, sid)); + } + } else if (format === 1) { + while (charset.length <= nGlyphs) { + sid = parser.parseSID(); + count = parser.parseCard8(); + for (var i$1 = 0; i$1 <= count; i$1 += 1) { + charset.push(getCFFString(strings, sid)); + sid += 1; + } + } + } else if (format === 2) { + while (charset.length <= nGlyphs) { + sid = parser.parseSID(); + count = parser.parseCard16(); + for (var i$2 = 0; i$2 <= count; i$2 += 1) { + charset.push(getCFFString(strings, sid)); + sid += 1; + } + } + } else { + throw new Error('Unknown charset format ' + format); + } + + return charset; + } + + // Parse the CFF encoding data. Only one encoding can be specified per font. + // See Adobe TN #5176 chapter 12, "Encodings". + function parseCFFEncoding(data, start, charset) { + var code; + var enc = {}; + var parser = new parse.Parser(data, start); + var format = parser.parseCard8(); + if (format === 0) { + var nCodes = parser.parseCard8(); + for (var i = 0; i < nCodes; i += 1) { + code = parser.parseCard8(); + enc[code] = i; + } + } else if (format === 1) { + var nRanges = parser.parseCard8(); + code = 1; + for (var i$1 = 0; i$1 < nRanges; i$1 += 1) { + var first = parser.parseCard8(); + var nLeft = parser.parseCard8(); + for (var j = first; j <= first + nLeft; j += 1) { + enc[j] = code; + code += 1; + } + } + } else { + throw new Error('Unknown encoding format ' + format); + } + + return new CffEncoding(enc, charset); + } + + // Take in charstring code and return a Glyph object. + // The encoding is described in the Type 2 Charstring Format + // https://www.microsoft.com/typography/OTSPEC/charstr2.htm + function parseCFFCharstring(font, glyph, code) { + var c1x; + var c1y; + var c2x; + var c2y; + var p = new Path(); + var stack = []; + var nStems = 0; + var haveWidth = false; + var open = false; + var x = 0; + var y = 0; + var subrs; + var subrsBias; + var defaultWidthX; + var nominalWidthX; + if (font.isCIDFont) { + var fdIndex = font.tables.cff.topDict._fdSelect[glyph.index]; + var fdDict = font.tables.cff.topDict._fdArray[fdIndex]; + subrs = fdDict._subrs; + subrsBias = fdDict._subrsBias; + defaultWidthX = fdDict._defaultWidthX; + nominalWidthX = fdDict._nominalWidthX; + } else { + subrs = font.tables.cff.topDict._subrs; + subrsBias = font.tables.cff.topDict._subrsBias; + defaultWidthX = font.tables.cff.topDict._defaultWidthX; + nominalWidthX = font.tables.cff.topDict._nominalWidthX; + } + var width = defaultWidthX; + + function newContour(x, y) { + if (open) { + p.closePath(); + } + + p.moveTo(x, y); + open = true; + } + + function parseStems() { + var hasWidthArg; + + // The number of stem operators on the stack is always even. + // If the value is uneven, that means a width is specified. + hasWidthArg = stack.length % 2 !== 0; + if (hasWidthArg && !haveWidth) { + width = stack.shift() + nominalWidthX; + } + + nStems += stack.length >> 1; + stack.length = 0; + haveWidth = true; + } + + function parse(code) { + var b1; + var b2; + var b3; + var b4; + var codeIndex; + var subrCode; + var jpx; + var jpy; + var c3x; + var c3y; + var c4x; + var c4y; + + var i = 0; + while (i < code.length) { + var v = code[i]; + i += 1; + switch (v) { + case 1: // hstem + parseStems(); + break; + case 3: // vstem + parseStems(); + break; + case 4: // vmoveto + if (stack.length > 1 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + + y += stack.pop(); + newContour(x, y); + break; + case 5: // rlineto + while (stack.length > 0) { + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + } + + break; + case 6: // hlineto + while (stack.length > 0) { + x += stack.shift(); + p.lineTo(x, y); + if (stack.length === 0) { + break; + } + + y += stack.shift(); + p.lineTo(x, y); + } + + break; + case 7: // vlineto + while (stack.length > 0) { + y += stack.shift(); + p.lineTo(x, y); + if (stack.length === 0) { + break; + } + + x += stack.shift(); + p.lineTo(x, y); + } + + break; + case 8: // rrcurveto + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 10: // callsubr + codeIndex = stack.pop() + subrsBias; + subrCode = subrs[codeIndex]; + if (subrCode) { + parse(subrCode); + } + + break; + case 11: // return + return; + case 12: // flex operators + v = code[i]; + i += 1; + switch (v) { + case 35: // flex + // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y + stack.shift(); // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = jpy + stack.shift(); // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + x = c4x + stack.shift(); // dx6 + y = c4y + stack.shift(); // dy6 + stack.shift(); // flex depth + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 34: // hflex + // |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |- + c1x = x + stack.shift(); // dx1 + c1y = y; // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y; // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = c2y; // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = y; // dy5 + x = c4x + stack.shift(); // dx6 + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 36: // hflex1 + // |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y; // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = c2y; // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + x = c4x + stack.shift(); // dx6 + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 37: // flex1 + // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y + stack.shift(); // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = jpy + stack.shift(); // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + if (Math.abs(c4x - x) > Math.abs(c4y - y)) { + x = c4x + stack.shift(); + } else { + y = c4y + stack.shift(); + } + + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + default: + console.log('Glyph ' + glyph.index + ': unknown operator ' + 1200 + v); + stack.length = 0; + } + break; + case 14: // endchar + if (stack.length > 0 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + + if (open) { + p.closePath(); + open = false; + } + + break; + case 18: // hstemhm + parseStems(); + break; + case 19: // hintmask + case 20: // cntrmask + parseStems(); + i += (nStems + 7) >> 3; + break; + case 21: // rmoveto + if (stack.length > 2 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + + y += stack.pop(); + x += stack.pop(); + newContour(x, y); + break; + case 22: // hmoveto + if (stack.length > 1 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + + x += stack.pop(); + newContour(x, y); + break; + case 23: // vstemhm + parseStems(); + break; + case 24: // rcurveline + while (stack.length > 2) { + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + break; + case 25: // rlinecurve + while (stack.length > 6) { + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + } + + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + break; + case 26: // vvcurveto + if (stack.length % 2) { + x += stack.shift(); + } + + while (stack.length > 0) { + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x; + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 27: // hhcurveto + if (stack.length % 2) { + y += stack.shift(); + } + + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y; + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 28: // shortint + b1 = code[i]; + b2 = code[i + 1]; + stack.push(((b1 << 24) | (b2 << 16)) >> 16); + i += 2; + break; + case 29: // callgsubr + codeIndex = stack.pop() + font.gsubrsBias; + subrCode = font.gsubrs[codeIndex]; + if (subrCode) { + parse(subrCode); + } + + break; + case 30: // vhcurveto + while (stack.length > 0) { + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + if (stack.length === 0) { + break; + } + + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + y = c2y + stack.shift(); + x = c2x + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 31: // hvcurveto + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + y = c2y + stack.shift(); + x = c2x + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + if (stack.length === 0) { + break; + } + + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + default: + if (v < 32) { + console.log('Glyph ' + glyph.index + ': unknown operator ' + v); + } else if (v < 247) { + stack.push(v - 139); + } else if (v < 251) { + b1 = code[i]; + i += 1; + stack.push((v - 247) * 256 + b1 + 108); + } else if (v < 255) { + b1 = code[i]; + i += 1; + stack.push(-(v - 251) * 256 - b1 - 108); + } else { + b1 = code[i]; + b2 = code[i + 1]; + b3 = code[i + 2]; + b4 = code[i + 3]; + i += 4; + stack.push(((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) / 65536); + } + } + } + } + + parse(code); + + glyph.advanceWidth = width; + return p; + } + + function parseCFFFDSelect(data, start, nGlyphs, fdArrayCount) { + var fdSelect = []; + var fdIndex; + var parser = new parse.Parser(data, start); + var format = parser.parseCard8(); + if (format === 0) { + // Simple list of nGlyphs elements + for (var iGid = 0; iGid < nGlyphs; iGid++) { + fdIndex = parser.parseCard8(); + if (fdIndex >= fdArrayCount) { + throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); + } + fdSelect.push(fdIndex); + } + } else if (format === 3) { + // Ranges + var nRanges = parser.parseCard16(); + var first = parser.parseCard16(); + if (first !== 0) { + throw new Error('CFF Table CID Font FDSelect format 3 range has bad initial GID ' + first); + } + var next; + for (var iRange = 0; iRange < nRanges; iRange++) { + fdIndex = parser.parseCard8(); + next = parser.parseCard16(); + if (fdIndex >= fdArrayCount) { + throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); + } + if (next > nGlyphs) { + throw new Error('CFF Table CID Font FDSelect format 3 range has bad GID ' + next); + } + for (; first < next; first++) { + fdSelect.push(fdIndex); + } + first = next; + } + if (next !== nGlyphs) { + throw new Error('CFF Table CID Font FDSelect format 3 range has bad final GID ' + next); + } + } else { + throw new Error('CFF Table CID Font FDSelect table has unsupported format ' + format); + } + return fdSelect; + } + + // Parse the `CFF` table, which contains the glyph outlines in PostScript format. + function parseCFFTable(data, start, font, opt) { + font.tables.cff = {}; + var header = parseCFFHeader(data, start); + var nameIndex = parseCFFIndex(data, header.endOffset, parse.bytesToString); + var topDictIndex = parseCFFIndex(data, nameIndex.endOffset); + var stringIndex = parseCFFIndex(data, topDictIndex.endOffset, parse.bytesToString); + var globalSubrIndex = parseCFFIndex(data, stringIndex.endOffset); + font.gsubrs = globalSubrIndex.objects; + font.gsubrsBias = calcCFFSubroutineBias(font.gsubrs); + + var topDictArray = gatherCFFTopDicts(data, start, topDictIndex.objects, stringIndex.objects); + if (topDictArray.length !== 1) { + throw new Error('CFF table has too many fonts in \'FontSet\' - count of fonts NameIndex.length = ' + topDictArray.length); + } + + var topDict = topDictArray[0]; + font.tables.cff.topDict = topDict; + + if (topDict._privateDict) { + font.defaultWidthX = topDict._privateDict.defaultWidthX; + font.nominalWidthX = topDict._privateDict.nominalWidthX; + } + + if (topDict.ros[0] !== undefined && topDict.ros[1] !== undefined) { + font.isCIDFont = true; + } + + if (font.isCIDFont) { + var fdArrayOffset = topDict.fdArray; + var fdSelectOffset = topDict.fdSelect; + if (fdArrayOffset === 0 || fdSelectOffset === 0) { + throw new Error('Font is marked as a CID font, but FDArray and/or FDSelect information is missing'); + } + fdArrayOffset += start; + var fdArrayIndex = parseCFFIndex(data, fdArrayOffset); + var fdArray = gatherCFFTopDicts(data, start, fdArrayIndex.objects, stringIndex.objects); + topDict._fdArray = fdArray; + fdSelectOffset += start; + topDict._fdSelect = parseCFFFDSelect(data, fdSelectOffset, font.numGlyphs, fdArray.length); + } + + var privateDictOffset = start + topDict.private[1]; + var privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict.private[0], stringIndex.objects); + font.defaultWidthX = privateDict.defaultWidthX; + font.nominalWidthX = privateDict.nominalWidthX; + + if (privateDict.subrs !== 0) { + var subrOffset = privateDictOffset + privateDict.subrs; + var subrIndex = parseCFFIndex(data, subrOffset); + font.subrs = subrIndex.objects; + font.subrsBias = calcCFFSubroutineBias(font.subrs); + } else { + font.subrs = []; + font.subrsBias = 0; + } + + // Offsets in the top dict are relative to the beginning of the CFF data, so add the CFF start offset. + var charStringsIndex; + if (opt.lowMemory) { + charStringsIndex = parseCFFIndexLowMemory(data, start + topDict.charStrings); + font.nGlyphs = charStringsIndex.offsets.length; + } else { + charStringsIndex = parseCFFIndex(data, start + topDict.charStrings); + font.nGlyphs = charStringsIndex.objects.length; + } + + var charset = parseCFFCharset(data, start + topDict.charset, font.nGlyphs, stringIndex.objects); + if (topDict.encoding === 0) { + // Standard encoding + font.cffEncoding = new CffEncoding(cffStandardEncoding, charset); + } else if (topDict.encoding === 1) { + // Expert encoding + font.cffEncoding = new CffEncoding(cffExpertEncoding, charset); + } else { + font.cffEncoding = parseCFFEncoding(data, start + topDict.encoding, charset); + } + + // Prefer the CMAP encoding to the CFF encoding. + font.encoding = font.encoding || font.cffEncoding; + + font.glyphs = new glyphset.GlyphSet(font); + if (opt.lowMemory) { + font._push = function(i) { + var charString = getCffIndexObject(i, charStringsIndex.offsets, data, start + topDict.charStrings); + font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); + }; + } else { + for (var i = 0; i < font.nGlyphs; i += 1) { + var charString = charStringsIndex.objects[i]; + font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); + } + } + } + + // Convert a string to a String ID (SID). + // The list of strings is modified in place. + function encodeString(s, strings) { + var sid; + + // Is the string in the CFF standard strings? + var i = cffStandardStrings.indexOf(s); + if (i >= 0) { + sid = i; + } + + // Is the string already in the string index? + i = strings.indexOf(s); + if (i >= 0) { + sid = i + cffStandardStrings.length; + } else { + sid = cffStandardStrings.length + strings.length; + strings.push(s); + } + + return sid; + } + + function makeHeader() { + return new table.Record('Header', [ + {name: 'major', type: 'Card8', value: 1}, + {name: 'minor', type: 'Card8', value: 0}, + {name: 'hdrSize', type: 'Card8', value: 4}, + {name: 'major', type: 'Card8', value: 1} + ]); + } + + function makeNameIndex(fontNames) { + var t = new table.Record('Name INDEX', [ + {name: 'names', type: 'INDEX', value: []} + ]); + t.names = []; + for (var i = 0; i < fontNames.length; i += 1) { + t.names.push({name: 'name_' + i, type: 'NAME', value: fontNames[i]}); + } + + return t; + } + + // Given a dictionary's metadata, create a DICT structure. + function makeDict(meta, attrs, strings) { + var m = {}; + for (var i = 0; i < meta.length; i += 1) { + var entry = meta[i]; + var value = attrs[entry.name]; + if (value !== undefined && !equals(value, entry.value)) { + if (entry.type === 'SID') { + value = encodeString(value, strings); + } + + m[entry.op] = {name: entry.name, type: entry.type, value: value}; + } + } + + return m; + } + + // The Top DICT houses the global font attributes. + function makeTopDict(attrs, strings) { + var t = new table.Record('Top DICT', [ + {name: 'dict', type: 'DICT', value: {}} + ]); + t.dict = makeDict(TOP_DICT_META, attrs, strings); + return t; + } + + function makeTopDictIndex(topDict) { + var t = new table.Record('Top DICT INDEX', [ + {name: 'topDicts', type: 'INDEX', value: []} + ]); + t.topDicts = [{name: 'topDict_0', type: 'TABLE', value: topDict}]; + return t; + } + + function makeStringIndex(strings) { + var t = new table.Record('String INDEX', [ + {name: 'strings', type: 'INDEX', value: []} + ]); + t.strings = []; + for (var i = 0; i < strings.length; i += 1) { + t.strings.push({name: 'string_' + i, type: 'STRING', value: strings[i]}); + } + + return t; + } + + function makeGlobalSubrIndex() { + // Currently we don't use subroutines. + return new table.Record('Global Subr INDEX', [ + {name: 'subrs', type: 'INDEX', value: []} + ]); + } + + function makeCharsets(glyphNames, strings) { + var t = new table.Record('Charsets', [ + {name: 'format', type: 'Card8', value: 0} + ]); + for (var i = 0; i < glyphNames.length; i += 1) { + var glyphName = glyphNames[i]; + var glyphSID = encodeString(glyphName, strings); + t.fields.push({name: 'glyph_' + i, type: 'SID', value: glyphSID}); + } + + return t; + } + + function glyphToOps(glyph) { + var ops = []; + var path = glyph.path; + ops.push({name: 'width', type: 'NUMBER', value: glyph.advanceWidth}); + var x = 0; + var y = 0; + for (var i = 0; i < path.commands.length; i += 1) { + var dx = (void 0); + var dy = (void 0); + var cmd = path.commands[i]; + if (cmd.type === 'Q') { + // CFF only supports bézier curves, so convert the quad to a bézier. + var _13 = 1 / 3; + var _23 = 2 / 3; + + // We're going to create a new command so we don't change the original path. + cmd = { + type: 'C', + x: cmd.x, + y: cmd.y, + x1: _13 * x + _23 * cmd.x1, + y1: _13 * y + _23 * cmd.y1, + x2: _13 * cmd.x + _23 * cmd.x1, + y2: _13 * cmd.y + _23 * cmd.y1 + }; + } + + if (cmd.type === 'M') { + dx = Math.round(cmd.x - x); + dy = Math.round(cmd.y - y); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rmoveto', type: 'OP', value: 21}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } else if (cmd.type === 'L') { + dx = Math.round(cmd.x - x); + dy = Math.round(cmd.y - y); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rlineto', type: 'OP', value: 5}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } else if (cmd.type === 'C') { + var dx1 = Math.round(cmd.x1 - x); + var dy1 = Math.round(cmd.y1 - y); + var dx2 = Math.round(cmd.x2 - cmd.x1); + var dy2 = Math.round(cmd.y2 - cmd.y1); + dx = Math.round(cmd.x - cmd.x2); + dy = Math.round(cmd.y - cmd.y2); + ops.push({name: 'dx1', type: 'NUMBER', value: dx1}); + ops.push({name: 'dy1', type: 'NUMBER', value: dy1}); + ops.push({name: 'dx2', type: 'NUMBER', value: dx2}); + ops.push({name: 'dy2', type: 'NUMBER', value: dy2}); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rrcurveto', type: 'OP', value: 8}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } + + // Contours are closed automatically. + } + + ops.push({name: 'endchar', type: 'OP', value: 14}); + return ops; + } + + function makeCharStringsIndex(glyphs) { + var t = new table.Record('CharStrings INDEX', [ + {name: 'charStrings', type: 'INDEX', value: []} + ]); + + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + var ops = glyphToOps(glyph); + t.charStrings.push({name: glyph.name, type: 'CHARSTRING', value: ops}); + } + + return t; + } + + function makePrivateDict(attrs, strings) { + var t = new table.Record('Private DICT', [ + {name: 'dict', type: 'DICT', value: {}} + ]); + t.dict = makeDict(PRIVATE_DICT_META, attrs, strings); + return t; + } + + function makeCFFTable(glyphs, options) { + var t = new table.Table('CFF ', [ + {name: 'header', type: 'RECORD'}, + {name: 'nameIndex', type: 'RECORD'}, + {name: 'topDictIndex', type: 'RECORD'}, + {name: 'stringIndex', type: 'RECORD'}, + {name: 'globalSubrIndex', type: 'RECORD'}, + {name: 'charsets', type: 'RECORD'}, + {name: 'charStringsIndex', type: 'RECORD'}, + {name: 'privateDict', type: 'RECORD'} + ]); + + var fontScale = 1 / options.unitsPerEm; + // We use non-zero values for the offsets so that the DICT encodes them. + // This is important because the size of the Top DICT plays a role in offset calculation, + // and the size shouldn't change after we've written correct offsets. + var attrs = { + version: options.version, + fullName: options.fullName, + familyName: options.familyName, + weight: options.weightName, + fontBBox: options.fontBBox || [0, 0, 0, 0], + fontMatrix: [fontScale, 0, 0, fontScale, 0, 0], + charset: 999, + encoding: 0, + charStrings: 999, + private: [0, 999] + }; + + var privateAttrs = {}; + + var glyphNames = []; + var glyph; + + // Skip first glyph (.notdef) + for (var i = 1; i < glyphs.length; i += 1) { + glyph = glyphs.get(i); + glyphNames.push(glyph.name); + } + + var strings = []; + + t.header = makeHeader(); + t.nameIndex = makeNameIndex([options.postScriptName]); + var topDict = makeTopDict(attrs, strings); + t.topDictIndex = makeTopDictIndex(topDict); + t.globalSubrIndex = makeGlobalSubrIndex(); + t.charsets = makeCharsets(glyphNames, strings); + t.charStringsIndex = makeCharStringsIndex(glyphs); + t.privateDict = makePrivateDict(privateAttrs, strings); + + // Needs to come at the end, to encode all custom strings used in the font. + t.stringIndex = makeStringIndex(strings); + + var startOffset = t.header.sizeOf() + + t.nameIndex.sizeOf() + + t.topDictIndex.sizeOf() + + t.stringIndex.sizeOf() + + t.globalSubrIndex.sizeOf(); + attrs.charset = startOffset; + + // We use the CFF standard encoding; proper encoding will be handled in cmap. + attrs.encoding = 0; + attrs.charStrings = attrs.charset + t.charsets.sizeOf(); + attrs.private[1] = attrs.charStrings + t.charStringsIndex.sizeOf(); + + // Recreate the Top DICT INDEX with the correct offsets. + topDict = makeTopDict(attrs, strings); + t.topDictIndex = makeTopDictIndex(topDict); + + return t; + } + + var cff = { parse: parseCFFTable, make: makeCFFTable }; + + // The `head` table contains global information about the font. + + // Parse the header `head` table + function parseHeadTable(data, start) { + var head = {}; + var p = new parse.Parser(data, start); + head.version = p.parseVersion(); + head.fontRevision = Math.round(p.parseFixed() * 1000) / 1000; + head.checkSumAdjustment = p.parseULong(); + head.magicNumber = p.parseULong(); + check.argument(head.magicNumber === 0x5F0F3CF5, 'Font header has wrong magic number.'); + head.flags = p.parseUShort(); + head.unitsPerEm = p.parseUShort(); + head.created = p.parseLongDateTime(); + head.modified = p.parseLongDateTime(); + head.xMin = p.parseShort(); + head.yMin = p.parseShort(); + head.xMax = p.parseShort(); + head.yMax = p.parseShort(); + head.macStyle = p.parseUShort(); + head.lowestRecPPEM = p.parseUShort(); + head.fontDirectionHint = p.parseShort(); + head.indexToLocFormat = p.parseShort(); + head.glyphDataFormat = p.parseShort(); + return head; + } + + function makeHeadTable(options) { + // Apple Mac timestamp epoch is 01/01/1904 not 01/01/1970 + var timestamp = Math.round(new Date().getTime() / 1000) + 2082844800; + var createdTimestamp = timestamp; + + if (options.createdTimestamp) { + createdTimestamp = options.createdTimestamp + 2082844800; + } + + return new table.Table('head', [ + {name: 'version', type: 'FIXED', value: 0x00010000}, + {name: 'fontRevision', type: 'FIXED', value: 0x00010000}, + {name: 'checkSumAdjustment', type: 'ULONG', value: 0}, + {name: 'magicNumber', type: 'ULONG', value: 0x5F0F3CF5}, + {name: 'flags', type: 'USHORT', value: 0}, + {name: 'unitsPerEm', type: 'USHORT', value: 1000}, + {name: 'created', type: 'LONGDATETIME', value: createdTimestamp}, + {name: 'modified', type: 'LONGDATETIME', value: timestamp}, + {name: 'xMin', type: 'SHORT', value: 0}, + {name: 'yMin', type: 'SHORT', value: 0}, + {name: 'xMax', type: 'SHORT', value: 0}, + {name: 'yMax', type: 'SHORT', value: 0}, + {name: 'macStyle', type: 'USHORT', value: 0}, + {name: 'lowestRecPPEM', type: 'USHORT', value: 0}, + {name: 'fontDirectionHint', type: 'SHORT', value: 2}, + {name: 'indexToLocFormat', type: 'SHORT', value: 0}, + {name: 'glyphDataFormat', type: 'SHORT', value: 0} + ], options); + } + + var head = { parse: parseHeadTable, make: makeHeadTable }; + + // The `hhea` table contains information for horizontal layout. + + // Parse the horizontal header `hhea` table + function parseHheaTable(data, start) { + var hhea = {}; + var p = new parse.Parser(data, start); + hhea.version = p.parseVersion(); + hhea.ascender = p.parseShort(); + hhea.descender = p.parseShort(); + hhea.lineGap = p.parseShort(); + hhea.advanceWidthMax = p.parseUShort(); + hhea.minLeftSideBearing = p.parseShort(); + hhea.minRightSideBearing = p.parseShort(); + hhea.xMaxExtent = p.parseShort(); + hhea.caretSlopeRise = p.parseShort(); + hhea.caretSlopeRun = p.parseShort(); + hhea.caretOffset = p.parseShort(); + p.relativeOffset += 8; + hhea.metricDataFormat = p.parseShort(); + hhea.numberOfHMetrics = p.parseUShort(); + return hhea; + } + + function makeHheaTable(options) { + return new table.Table('hhea', [ + {name: 'version', type: 'FIXED', value: 0x00010000}, + {name: 'ascender', type: 'FWORD', value: 0}, + {name: 'descender', type: 'FWORD', value: 0}, + {name: 'lineGap', type: 'FWORD', value: 0}, + {name: 'advanceWidthMax', type: 'UFWORD', value: 0}, + {name: 'minLeftSideBearing', type: 'FWORD', value: 0}, + {name: 'minRightSideBearing', type: 'FWORD', value: 0}, + {name: 'xMaxExtent', type: 'FWORD', value: 0}, + {name: 'caretSlopeRise', type: 'SHORT', value: 1}, + {name: 'caretSlopeRun', type: 'SHORT', value: 0}, + {name: 'caretOffset', type: 'SHORT', value: 0}, + {name: 'reserved1', type: 'SHORT', value: 0}, + {name: 'reserved2', type: 'SHORT', value: 0}, + {name: 'reserved3', type: 'SHORT', value: 0}, + {name: 'reserved4', type: 'SHORT', value: 0}, + {name: 'metricDataFormat', type: 'SHORT', value: 0}, + {name: 'numberOfHMetrics', type: 'USHORT', value: 0} + ], options); + } + + var hhea = { parse: parseHheaTable, make: makeHheaTable }; + + // The `hmtx` table contains the horizontal metrics for all glyphs. + + function parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs) { + var advanceWidth; + var leftSideBearing; + var p = new parse.Parser(data, start); + for (var i = 0; i < numGlyphs; i += 1) { + // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. + if (i < numMetrics) { + advanceWidth = p.parseUShort(); + leftSideBearing = p.parseShort(); + } + + var glyph = glyphs.get(i); + glyph.advanceWidth = advanceWidth; + glyph.leftSideBearing = leftSideBearing; + } + } + + function parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs) { + font._hmtxTableData = {}; + + var advanceWidth; + var leftSideBearing; + var p = new parse.Parser(data, start); + for (var i = 0; i < numGlyphs; i += 1) { + // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. + if (i < numMetrics) { + advanceWidth = p.parseUShort(); + leftSideBearing = p.parseShort(); + } + + font._hmtxTableData[i] = { + advanceWidth: advanceWidth, + leftSideBearing: leftSideBearing + }; + } + } + + // Parse the `hmtx` table, which contains the horizontal metrics for all glyphs. + // This function augments the glyph array, adding the advanceWidth and leftSideBearing to each glyph. + function parseHmtxTable(font, data, start, numMetrics, numGlyphs, glyphs, opt) { + if (opt.lowMemory) + { parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs); } + else + { parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs); } + } + + function makeHmtxTable(glyphs) { + var t = new table.Table('hmtx', []); + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + var advanceWidth = glyph.advanceWidth || 0; + var leftSideBearing = glyph.leftSideBearing || 0; + t.fields.push({name: 'advanceWidth_' + i, type: 'USHORT', value: advanceWidth}); + t.fields.push({name: 'leftSideBearing_' + i, type: 'SHORT', value: leftSideBearing}); + } + + return t; + } + + var hmtx = { parse: parseHmtxTable, make: makeHmtxTable }; + + // The `ltag` table stores IETF BCP-47 language tags. It allows supporting + + function makeLtagTable(tags) { + var result = new table.Table('ltag', [ + {name: 'version', type: 'ULONG', value: 1}, + {name: 'flags', type: 'ULONG', value: 0}, + {name: 'numTags', type: 'ULONG', value: tags.length} + ]); + + var stringPool = ''; + var stringPoolOffset = 12 + tags.length * 4; + for (var i = 0; i < tags.length; ++i) { + var pos = stringPool.indexOf(tags[i]); + if (pos < 0) { + pos = stringPool.length; + stringPool += tags[i]; + } + + result.fields.push({name: 'offset ' + i, type: 'USHORT', value: stringPoolOffset + pos}); + result.fields.push({name: 'length ' + i, type: 'USHORT', value: tags[i].length}); + } + + result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); + return result; + } + + function parseLtagTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 1, 'Unsupported ltag table version.'); + // The 'ltag' specification does not define any flags; skip the field. + p.skip('uLong', 1); + var numTags = p.parseULong(); + + var tags = []; + for (var i = 0; i < numTags; i++) { + var tag = ''; + var offset = start + p.parseUShort(); + var length = p.parseUShort(); + for (var j = offset; j < offset + length; ++j) { + tag += String.fromCharCode(data.getInt8(j)); + } + + tags.push(tag); + } + + return tags; + } + + var ltag = { make: makeLtagTable, parse: parseLtagTable }; + + // The `maxp` table establishes the memory requirements for the font. + + // Parse the maximum profile `maxp` table. + function parseMaxpTable(data, start) { + var maxp = {}; + var p = new parse.Parser(data, start); + maxp.version = p.parseVersion(); + maxp.numGlyphs = p.parseUShort(); + if (maxp.version === 1.0) { + maxp.maxPoints = p.parseUShort(); + maxp.maxContours = p.parseUShort(); + maxp.maxCompositePoints = p.parseUShort(); + maxp.maxCompositeContours = p.parseUShort(); + maxp.maxZones = p.parseUShort(); + maxp.maxTwilightPoints = p.parseUShort(); + maxp.maxStorage = p.parseUShort(); + maxp.maxFunctionDefs = p.parseUShort(); + maxp.maxInstructionDefs = p.parseUShort(); + maxp.maxStackElements = p.parseUShort(); + maxp.maxSizeOfInstructions = p.parseUShort(); + maxp.maxComponentElements = p.parseUShort(); + maxp.maxComponentDepth = p.parseUShort(); + } + + return maxp; + } + + function makeMaxpTable(numGlyphs) { + return new table.Table('maxp', [ + {name: 'version', type: 'FIXED', value: 0x00005000}, + {name: 'numGlyphs', type: 'USHORT', value: numGlyphs} + ]); + } + + var maxp = { parse: parseMaxpTable, make: makeMaxpTable }; + + // The `name` naming table. + + // NameIDs for the name table. + var nameTableNames = [ + 'copyright', // 0 + 'fontFamily', // 1 + 'fontSubfamily', // 2 + 'uniqueID', // 3 + 'fullName', // 4 + 'version', // 5 + 'postScriptName', // 6 + 'trademark', // 7 + 'manufacturer', // 8 + 'designer', // 9 + 'description', // 10 + 'manufacturerURL', // 11 + 'designerURL', // 12 + 'license', // 13 + 'licenseURL', // 14 + 'reserved', // 15 + 'preferredFamily', // 16 + 'preferredSubfamily', // 17 + 'compatibleFullName', // 18 + 'sampleText', // 19 + 'postScriptFindFontName', // 20 + 'wwsFamily', // 21 + 'wwsSubfamily' // 22 + ]; + + var macLanguages = { + 0: 'en', + 1: 'fr', + 2: 'de', + 3: 'it', + 4: 'nl', + 5: 'sv', + 6: 'es', + 7: 'da', + 8: 'pt', + 9: 'no', + 10: 'he', + 11: 'ja', + 12: 'ar', + 13: 'fi', + 14: 'el', + 15: 'is', + 16: 'mt', + 17: 'tr', + 18: 'hr', + 19: 'zh-Hant', + 20: 'ur', + 21: 'hi', + 22: 'th', + 23: 'ko', + 24: 'lt', + 25: 'pl', + 26: 'hu', + 27: 'es', + 28: 'lv', + 29: 'se', + 30: 'fo', + 31: 'fa', + 32: 'ru', + 33: 'zh', + 34: 'nl-BE', + 35: 'ga', + 36: 'sq', + 37: 'ro', + 38: 'cz', + 39: 'sk', + 40: 'si', + 41: 'yi', + 42: 'sr', + 43: 'mk', + 44: 'bg', + 45: 'uk', + 46: 'be', + 47: 'uz', + 48: 'kk', + 49: 'az-Cyrl', + 50: 'az-Arab', + 51: 'hy', + 52: 'ka', + 53: 'mo', + 54: 'ky', + 55: 'tg', + 56: 'tk', + 57: 'mn-CN', + 58: 'mn', + 59: 'ps', + 60: 'ks', + 61: 'ku', + 62: 'sd', + 63: 'bo', + 64: 'ne', + 65: 'sa', + 66: 'mr', + 67: 'bn', + 68: 'as', + 69: 'gu', + 70: 'pa', + 71: 'or', + 72: 'ml', + 73: 'kn', + 74: 'ta', + 75: 'te', + 76: 'si', + 77: 'my', + 78: 'km', + 79: 'lo', + 80: 'vi', + 81: 'id', + 82: 'tl', + 83: 'ms', + 84: 'ms-Arab', + 85: 'am', + 86: 'ti', + 87: 'om', + 88: 'so', + 89: 'sw', + 90: 'rw', + 91: 'rn', + 92: 'ny', + 93: 'mg', + 94: 'eo', + 128: 'cy', + 129: 'eu', + 130: 'ca', + 131: 'la', + 132: 'qu', + 133: 'gn', + 134: 'ay', + 135: 'tt', + 136: 'ug', + 137: 'dz', + 138: 'jv', + 139: 'su', + 140: 'gl', + 141: 'af', + 142: 'br', + 143: 'iu', + 144: 'gd', + 145: 'gv', + 146: 'ga', + 147: 'to', + 148: 'el-polyton', + 149: 'kl', + 150: 'az', + 151: 'nn' + }; + + // MacOS language ID → MacOS script ID + // + // Note that the script ID is not sufficient to determine what encoding + // to use in TrueType files. For some languages, MacOS used a modification + // of a mainstream script. For example, an Icelandic name would be stored + // with smRoman in the TrueType naming table, but the actual encoding + // is a special Icelandic version of the normal Macintosh Roman encoding. + // As another example, Inuktitut uses an 8-bit encoding for Canadian Aboriginal + // Syllables but MacOS had run out of available script codes, so this was + // done as a (pretty radical) "modification" of Ethiopic. + // + // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt + var macLanguageToScript = { + 0: 0, // langEnglish → smRoman + 1: 0, // langFrench → smRoman + 2: 0, // langGerman → smRoman + 3: 0, // langItalian → smRoman + 4: 0, // langDutch → smRoman + 5: 0, // langSwedish → smRoman + 6: 0, // langSpanish → smRoman + 7: 0, // langDanish → smRoman + 8: 0, // langPortuguese → smRoman + 9: 0, // langNorwegian → smRoman + 10: 5, // langHebrew → smHebrew + 11: 1, // langJapanese → smJapanese + 12: 4, // langArabic → smArabic + 13: 0, // langFinnish → smRoman + 14: 6, // langGreek → smGreek + 15: 0, // langIcelandic → smRoman (modified) + 16: 0, // langMaltese → smRoman + 17: 0, // langTurkish → smRoman (modified) + 18: 0, // langCroatian → smRoman (modified) + 19: 2, // langTradChinese → smTradChinese + 20: 4, // langUrdu → smArabic + 21: 9, // langHindi → smDevanagari + 22: 21, // langThai → smThai + 23: 3, // langKorean → smKorean + 24: 29, // langLithuanian → smCentralEuroRoman + 25: 29, // langPolish → smCentralEuroRoman + 26: 29, // langHungarian → smCentralEuroRoman + 27: 29, // langEstonian → smCentralEuroRoman + 28: 29, // langLatvian → smCentralEuroRoman + 29: 0, // langSami → smRoman + 30: 0, // langFaroese → smRoman (modified) + 31: 4, // langFarsi → smArabic (modified) + 32: 7, // langRussian → smCyrillic + 33: 25, // langSimpChinese → smSimpChinese + 34: 0, // langFlemish → smRoman + 35: 0, // langIrishGaelic → smRoman (modified) + 36: 0, // langAlbanian → smRoman + 37: 0, // langRomanian → smRoman (modified) + 38: 29, // langCzech → smCentralEuroRoman + 39: 29, // langSlovak → smCentralEuroRoman + 40: 0, // langSlovenian → smRoman (modified) + 41: 5, // langYiddish → smHebrew + 42: 7, // langSerbian → smCyrillic + 43: 7, // langMacedonian → smCyrillic + 44: 7, // langBulgarian → smCyrillic + 45: 7, // langUkrainian → smCyrillic (modified) + 46: 7, // langByelorussian → smCyrillic + 47: 7, // langUzbek → smCyrillic + 48: 7, // langKazakh → smCyrillic + 49: 7, // langAzerbaijani → smCyrillic + 50: 4, // langAzerbaijanAr → smArabic + 51: 24, // langArmenian → smArmenian + 52: 23, // langGeorgian → smGeorgian + 53: 7, // langMoldavian → smCyrillic + 54: 7, // langKirghiz → smCyrillic + 55: 7, // langTajiki → smCyrillic + 56: 7, // langTurkmen → smCyrillic + 57: 27, // langMongolian → smMongolian + 58: 7, // langMongolianCyr → smCyrillic + 59: 4, // langPashto → smArabic + 60: 4, // langKurdish → smArabic + 61: 4, // langKashmiri → smArabic + 62: 4, // langSindhi → smArabic + 63: 26, // langTibetan → smTibetan + 64: 9, // langNepali → smDevanagari + 65: 9, // langSanskrit → smDevanagari + 66: 9, // langMarathi → smDevanagari + 67: 13, // langBengali → smBengali + 68: 13, // langAssamese → smBengali + 69: 11, // langGujarati → smGujarati + 70: 10, // langPunjabi → smGurmukhi + 71: 12, // langOriya → smOriya + 72: 17, // langMalayalam → smMalayalam + 73: 16, // langKannada → smKannada + 74: 14, // langTamil → smTamil + 75: 15, // langTelugu → smTelugu + 76: 18, // langSinhalese → smSinhalese + 77: 19, // langBurmese → smBurmese + 78: 20, // langKhmer → smKhmer + 79: 22, // langLao → smLao + 80: 30, // langVietnamese → smVietnamese + 81: 0, // langIndonesian → smRoman + 82: 0, // langTagalog → smRoman + 83: 0, // langMalayRoman → smRoman + 84: 4, // langMalayArabic → smArabic + 85: 28, // langAmharic → smEthiopic + 86: 28, // langTigrinya → smEthiopic + 87: 28, // langOromo → smEthiopic + 88: 0, // langSomali → smRoman + 89: 0, // langSwahili → smRoman + 90: 0, // langKinyarwanda → smRoman + 91: 0, // langRundi → smRoman + 92: 0, // langNyanja → smRoman + 93: 0, // langMalagasy → smRoman + 94: 0, // langEsperanto → smRoman + 128: 0, // langWelsh → smRoman (modified) + 129: 0, // langBasque → smRoman + 130: 0, // langCatalan → smRoman + 131: 0, // langLatin → smRoman + 132: 0, // langQuechua → smRoman + 133: 0, // langGuarani → smRoman + 134: 0, // langAymara → smRoman + 135: 7, // langTatar → smCyrillic + 136: 4, // langUighur → smArabic + 137: 26, // langDzongkha → smTibetan + 138: 0, // langJavaneseRom → smRoman + 139: 0, // langSundaneseRom → smRoman + 140: 0, // langGalician → smRoman + 141: 0, // langAfrikaans → smRoman + 142: 0, // langBreton → smRoman (modified) + 143: 28, // langInuktitut → smEthiopic (modified) + 144: 0, // langScottishGaelic → smRoman (modified) + 145: 0, // langManxGaelic → smRoman (modified) + 146: 0, // langIrishGaelicScript → smRoman (modified) + 147: 0, // langTongan → smRoman + 148: 6, // langGreekAncient → smRoman + 149: 0, // langGreenlandic → smRoman + 150: 0, // langAzerbaijanRoman → smRoman + 151: 0 // langNynorsk → smRoman + }; + + // While Microsoft indicates a region/country for all its language + // IDs, we omit the region code if it's equal to the "most likely + // region subtag" according to Unicode CLDR. For scripts, we omit + // the subtag if it is equal to the Suppress-Script entry in the + // IANA language subtag registry for IETF BCP 47. + // + // For example, Microsoft states that its language code 0x041A is + // Croatian in Croatia. We transform this to the BCP 47 language code 'hr' + // and not 'hr-HR' because Croatia is the default country for Croatian, + // according to Unicode CLDR. As another example, Microsoft states + // that 0x101A is Croatian (Latin) in Bosnia-Herzegovina. We transform + // this to 'hr-BA' and not 'hr-Latn-BA' because Latin is the default script + // for the Croatian language, according to IANA. + // + // http://www.unicode.org/cldr/charts/latest/supplemental/likely_subtags.html + // http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry + var windowsLanguages = { + 0x0436: 'af', + 0x041C: 'sq', + 0x0484: 'gsw', + 0x045E: 'am', + 0x1401: 'ar-DZ', + 0x3C01: 'ar-BH', + 0x0C01: 'ar', + 0x0801: 'ar-IQ', + 0x2C01: 'ar-JO', + 0x3401: 'ar-KW', + 0x3001: 'ar-LB', + 0x1001: 'ar-LY', + 0x1801: 'ary', + 0x2001: 'ar-OM', + 0x4001: 'ar-QA', + 0x0401: 'ar-SA', + 0x2801: 'ar-SY', + 0x1C01: 'aeb', + 0x3801: 'ar-AE', + 0x2401: 'ar-YE', + 0x042B: 'hy', + 0x044D: 'as', + 0x082C: 'az-Cyrl', + 0x042C: 'az', + 0x046D: 'ba', + 0x042D: 'eu', + 0x0423: 'be', + 0x0845: 'bn', + 0x0445: 'bn-IN', + 0x201A: 'bs-Cyrl', + 0x141A: 'bs', + 0x047E: 'br', + 0x0402: 'bg', + 0x0403: 'ca', + 0x0C04: 'zh-HK', + 0x1404: 'zh-MO', + 0x0804: 'zh', + 0x1004: 'zh-SG', + 0x0404: 'zh-TW', + 0x0483: 'co', + 0x041A: 'hr', + 0x101A: 'hr-BA', + 0x0405: 'cs', + 0x0406: 'da', + 0x048C: 'prs', + 0x0465: 'dv', + 0x0813: 'nl-BE', + 0x0413: 'nl', + 0x0C09: 'en-AU', + 0x2809: 'en-BZ', + 0x1009: 'en-CA', + 0x2409: 'en-029', + 0x4009: 'en-IN', + 0x1809: 'en-IE', + 0x2009: 'en-JM', + 0x4409: 'en-MY', + 0x1409: 'en-NZ', + 0x3409: 'en-PH', + 0x4809: 'en-SG', + 0x1C09: 'en-ZA', + 0x2C09: 'en-TT', + 0x0809: 'en-GB', + 0x0409: 'en', + 0x3009: 'en-ZW', + 0x0425: 'et', + 0x0438: 'fo', + 0x0464: 'fil', + 0x040B: 'fi', + 0x080C: 'fr-BE', + 0x0C0C: 'fr-CA', + 0x040C: 'fr', + 0x140C: 'fr-LU', + 0x180C: 'fr-MC', + 0x100C: 'fr-CH', + 0x0462: 'fy', + 0x0456: 'gl', + 0x0437: 'ka', + 0x0C07: 'de-AT', + 0x0407: 'de', + 0x1407: 'de-LI', + 0x1007: 'de-LU', + 0x0807: 'de-CH', + 0x0408: 'el', + 0x046F: 'kl', + 0x0447: 'gu', + 0x0468: 'ha', + 0x040D: 'he', + 0x0439: 'hi', + 0x040E: 'hu', + 0x040F: 'is', + 0x0470: 'ig', + 0x0421: 'id', + 0x045D: 'iu', + 0x085D: 'iu-Latn', + 0x083C: 'ga', + 0x0434: 'xh', + 0x0435: 'zu', + 0x0410: 'it', + 0x0810: 'it-CH', + 0x0411: 'ja', + 0x044B: 'kn', + 0x043F: 'kk', + 0x0453: 'km', + 0x0486: 'quc', + 0x0487: 'rw', + 0x0441: 'sw', + 0x0457: 'kok', + 0x0412: 'ko', + 0x0440: 'ky', + 0x0454: 'lo', + 0x0426: 'lv', + 0x0427: 'lt', + 0x082E: 'dsb', + 0x046E: 'lb', + 0x042F: 'mk', + 0x083E: 'ms-BN', + 0x043E: 'ms', + 0x044C: 'ml', + 0x043A: 'mt', + 0x0481: 'mi', + 0x047A: 'arn', + 0x044E: 'mr', + 0x047C: 'moh', + 0x0450: 'mn', + 0x0850: 'mn-CN', + 0x0461: 'ne', + 0x0414: 'nb', + 0x0814: 'nn', + 0x0482: 'oc', + 0x0448: 'or', + 0x0463: 'ps', + 0x0415: 'pl', + 0x0416: 'pt', + 0x0816: 'pt-PT', + 0x0446: 'pa', + 0x046B: 'qu-BO', + 0x086B: 'qu-EC', + 0x0C6B: 'qu', + 0x0418: 'ro', + 0x0417: 'rm', + 0x0419: 'ru', + 0x243B: 'smn', + 0x103B: 'smj-NO', + 0x143B: 'smj', + 0x0C3B: 'se-FI', + 0x043B: 'se', + 0x083B: 'se-SE', + 0x203B: 'sms', + 0x183B: 'sma-NO', + 0x1C3B: 'sms', + 0x044F: 'sa', + 0x1C1A: 'sr-Cyrl-BA', + 0x0C1A: 'sr', + 0x181A: 'sr-Latn-BA', + 0x081A: 'sr-Latn', + 0x046C: 'nso', + 0x0432: 'tn', + 0x045B: 'si', + 0x041B: 'sk', + 0x0424: 'sl', + 0x2C0A: 'es-AR', + 0x400A: 'es-BO', + 0x340A: 'es-CL', + 0x240A: 'es-CO', + 0x140A: 'es-CR', + 0x1C0A: 'es-DO', + 0x300A: 'es-EC', + 0x440A: 'es-SV', + 0x100A: 'es-GT', + 0x480A: 'es-HN', + 0x080A: 'es-MX', + 0x4C0A: 'es-NI', + 0x180A: 'es-PA', + 0x3C0A: 'es-PY', + 0x280A: 'es-PE', + 0x500A: 'es-PR', + + // Microsoft has defined two different language codes for + // “Spanish with modern sorting” and “Spanish with traditional + // sorting”. This makes sense for collation APIs, and it would be + // possible to express this in BCP 47 language tags via Unicode + // extensions (eg., es-u-co-trad is Spanish with traditional + // sorting). However, for storing names in fonts, the distinction + // does not make sense, so we give “es” in both cases. + 0x0C0A: 'es', + 0x040A: 'es', + + 0x540A: 'es-US', + 0x380A: 'es-UY', + 0x200A: 'es-VE', + 0x081D: 'sv-FI', + 0x041D: 'sv', + 0x045A: 'syr', + 0x0428: 'tg', + 0x085F: 'tzm', + 0x0449: 'ta', + 0x0444: 'tt', + 0x044A: 'te', + 0x041E: 'th', + 0x0451: 'bo', + 0x041F: 'tr', + 0x0442: 'tk', + 0x0480: 'ug', + 0x0422: 'uk', + 0x042E: 'hsb', + 0x0420: 'ur', + 0x0843: 'uz-Cyrl', + 0x0443: 'uz', + 0x042A: 'vi', + 0x0452: 'cy', + 0x0488: 'wo', + 0x0485: 'sah', + 0x0478: 'ii', + 0x046A: 'yo' + }; + + // Returns a IETF BCP 47 language code, for example 'zh-Hant' + // for 'Chinese in the traditional script'. + function getLanguageCode(platformID, languageID, ltag) { + switch (platformID) { + case 0: // Unicode + if (languageID === 0xFFFF) { + return 'und'; + } else if (ltag) { + return ltag[languageID]; + } + + break; + + case 1: // Macintosh + return macLanguages[languageID]; + + case 3: // Windows + return windowsLanguages[languageID]; + } + + return undefined; + } + + var utf16 = 'utf-16'; + + // MacOS script ID → encoding. This table stores the default case, + // which can be overridden by macLanguageEncodings. + var macScriptEncodings = { + 0: 'macintosh', // smRoman + 1: 'x-mac-japanese', // smJapanese + 2: 'x-mac-chinesetrad', // smTradChinese + 3: 'x-mac-korean', // smKorean + 6: 'x-mac-greek', // smGreek + 7: 'x-mac-cyrillic', // smCyrillic + 9: 'x-mac-devanagai', // smDevanagari + 10: 'x-mac-gurmukhi', // smGurmukhi + 11: 'x-mac-gujarati', // smGujarati + 12: 'x-mac-oriya', // smOriya + 13: 'x-mac-bengali', // smBengali + 14: 'x-mac-tamil', // smTamil + 15: 'x-mac-telugu', // smTelugu + 16: 'x-mac-kannada', // smKannada + 17: 'x-mac-malayalam', // smMalayalam + 18: 'x-mac-sinhalese', // smSinhalese + 19: 'x-mac-burmese', // smBurmese + 20: 'x-mac-khmer', // smKhmer + 21: 'x-mac-thai', // smThai + 22: 'x-mac-lao', // smLao + 23: 'x-mac-georgian', // smGeorgian + 24: 'x-mac-armenian', // smArmenian + 25: 'x-mac-chinesesimp', // smSimpChinese + 26: 'x-mac-tibetan', // smTibetan + 27: 'x-mac-mongolian', // smMongolian + 28: 'x-mac-ethiopic', // smEthiopic + 29: 'x-mac-ce', // smCentralEuroRoman + 30: 'x-mac-vietnamese', // smVietnamese + 31: 'x-mac-extarabic' // smExtArabic + }; + + // MacOS language ID → encoding. This table stores the exceptional + // cases, which override macScriptEncodings. For writing MacOS naming + // tables, we need to emit a MacOS script ID. Therefore, we cannot + // merge macScriptEncodings into macLanguageEncodings. + // + // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt + var macLanguageEncodings = { + 15: 'x-mac-icelandic', // langIcelandic + 17: 'x-mac-turkish', // langTurkish + 18: 'x-mac-croatian', // langCroatian + 24: 'x-mac-ce', // langLithuanian + 25: 'x-mac-ce', // langPolish + 26: 'x-mac-ce', // langHungarian + 27: 'x-mac-ce', // langEstonian + 28: 'x-mac-ce', // langLatvian + 30: 'x-mac-icelandic', // langFaroese + 37: 'x-mac-romanian', // langRomanian + 38: 'x-mac-ce', // langCzech + 39: 'x-mac-ce', // langSlovak + 40: 'x-mac-ce', // langSlovenian + 143: 'x-mac-inuit', // langInuktitut + 146: 'x-mac-gaelic' // langIrishGaelicScript + }; + + function getEncoding(platformID, encodingID, languageID) { + switch (platformID) { + case 0: // Unicode + return utf16; + + case 1: // Apple Macintosh + return macLanguageEncodings[languageID] || macScriptEncodings[encodingID]; + + case 3: // Microsoft Windows + if (encodingID === 1 || encodingID === 10) { + return utf16; + } + + break; + } + + return undefined; + } + + // Parse the naming `name` table. + // FIXME: Format 1 additional fields are not supported yet. + // ltag is the content of the `ltag' table, such as ['en', 'zh-Hans', 'de-CH-1904']. + function parseNameTable(data, start, ltag) { + var name = {}; + var p = new parse.Parser(data, start); + var format = p.parseUShort(); + var count = p.parseUShort(); + var stringOffset = p.offset + p.parseUShort(); + for (var i = 0; i < count; i++) { + var platformID = p.parseUShort(); + var encodingID = p.parseUShort(); + var languageID = p.parseUShort(); + var nameID = p.parseUShort(); + var property = nameTableNames[nameID] || nameID; + var byteLength = p.parseUShort(); + var offset = p.parseUShort(); + var language = getLanguageCode(platformID, languageID, ltag); + var encoding = getEncoding(platformID, encodingID, languageID); + if (encoding !== undefined && language !== undefined) { + var text = (void 0); + if (encoding === utf16) { + text = decode.UTF16(data, stringOffset + offset, byteLength); + } else { + text = decode.MACSTRING(data, stringOffset + offset, byteLength, encoding); + } + + if (text) { + var translations = name[property]; + if (translations === undefined) { + translations = name[property] = {}; + } + + translations[language] = text; + } + } + } + + var langTagCount = 0; + if (format === 1) { + // FIXME: Also handle Microsoft's 'name' table 1. + langTagCount = p.parseUShort(); + } + + return name; + } + + // {23: 'foo'} → {'foo': 23} + // ['bar', 'baz'] → {'bar': 0, 'baz': 1} + function reverseDict(dict) { + var result = {}; + for (var key in dict) { + result[dict[key]] = parseInt(key); + } + + return result; + } + + function makeNameRecord(platformID, encodingID, languageID, nameID, length, offset) { + return new table.Record('NameRecord', [ + {name: 'platformID', type: 'USHORT', value: platformID}, + {name: 'encodingID', type: 'USHORT', value: encodingID}, + {name: 'languageID', type: 'USHORT', value: languageID}, + {name: 'nameID', type: 'USHORT', value: nameID}, + {name: 'length', type: 'USHORT', value: length}, + {name: 'offset', type: 'USHORT', value: offset} + ]); + } + + // Finds the position of needle in haystack, or -1 if not there. + // Like String.indexOf(), but for arrays. + function findSubArray(needle, haystack) { + var needleLength = needle.length; + var limit = haystack.length - needleLength + 1; + + loop: + for (var pos = 0; pos < limit; pos++) { + for (; pos < limit; pos++) { + for (var k = 0; k < needleLength; k++) { + if (haystack[pos + k] !== needle[k]) { + continue loop; + } + } + + return pos; + } + } + + return -1; + } + + function addStringToPool(s, pool) { + var offset = findSubArray(s, pool); + if (offset < 0) { + offset = pool.length; + var i = 0; + var len = s.length; + for (; i < len; ++i) { + pool.push(s[i]); + } + + } + + return offset; + } + + function makeNameTable(names, ltag) { + var nameID; + var nameIDs = []; + + var namesWithNumericKeys = {}; + var nameTableIds = reverseDict(nameTableNames); + for (var key in names) { + var id = nameTableIds[key]; + if (id === undefined) { + id = key; + } + + nameID = parseInt(id); + + if (isNaN(nameID)) { + throw new Error('Name table entry "' + key + '" does not exist, see nameTableNames for complete list.'); + } + + namesWithNumericKeys[nameID] = names[key]; + nameIDs.push(nameID); + } + + var macLanguageIds = reverseDict(macLanguages); + var windowsLanguageIds = reverseDict(windowsLanguages); + + var nameRecords = []; + var stringPool = []; + + for (var i = 0; i < nameIDs.length; i++) { + nameID = nameIDs[i]; + var translations = namesWithNumericKeys[nameID]; + for (var lang in translations) { + var text = translations[lang]; + + // For MacOS, we try to emit the name in the form that was introduced + // in the initial version of the TrueType spec (in the late 1980s). + // However, this can fail for various reasons: the requested BCP 47 + // language code might not have an old-style Mac equivalent; + // we might not have a codec for the needed character encoding; + // or the name might contain characters that cannot be expressed + // in the old-style Macintosh encoding. In case of failure, we emit + // the name in a more modern fashion (Unicode encoding with BCP 47 + // language tags) that is recognized by MacOS 10.5, released in 2009. + // If fonts were only read by operating systems, we could simply + // emit all names in the modern form; this would be much easier. + // However, there are many applications and libraries that read + // 'name' tables directly, and these will usually only recognize + // the ancient form (silently skipping the unrecognized names). + var macPlatform = 1; // Macintosh + var macLanguage = macLanguageIds[lang]; + var macScript = macLanguageToScript[macLanguage]; + var macEncoding = getEncoding(macPlatform, macScript, macLanguage); + var macName = encode.MACSTRING(text, macEncoding); + if (macName === undefined) { + macPlatform = 0; // Unicode + macLanguage = ltag.indexOf(lang); + if (macLanguage < 0) { + macLanguage = ltag.length; + ltag.push(lang); + } + + macScript = 4; // Unicode 2.0 and later + macName = encode.UTF16(text); + } + + var macNameOffset = addStringToPool(macName, stringPool); + nameRecords.push(makeNameRecord(macPlatform, macScript, macLanguage, + nameID, macName.length, macNameOffset)); + + var winLanguage = windowsLanguageIds[lang]; + if (winLanguage !== undefined) { + var winName = encode.UTF16(text); + var winNameOffset = addStringToPool(winName, stringPool); + nameRecords.push(makeNameRecord(3, 1, winLanguage, + nameID, winName.length, winNameOffset)); + } + } + } + + nameRecords.sort(function(a, b) { + return ((a.platformID - b.platformID) || + (a.encodingID - b.encodingID) || + (a.languageID - b.languageID) || + (a.nameID - b.nameID)); + }); + + var t = new table.Table('name', [ + {name: 'format', type: 'USHORT', value: 0}, + {name: 'count', type: 'USHORT', value: nameRecords.length}, + {name: 'stringOffset', type: 'USHORT', value: 6 + nameRecords.length * 12} + ]); + + for (var r = 0; r < nameRecords.length; r++) { + t.fields.push({name: 'record_' + r, type: 'RECORD', value: nameRecords[r]}); + } + + t.fields.push({name: 'strings', type: 'LITERAL', value: stringPool}); + return t; + } + + var _name = { parse: parseNameTable, make: makeNameTable }; + + // The `OS/2` table contains metrics required in OpenType fonts. + + var unicodeRanges = [ + {begin: 0x0000, end: 0x007F}, // Basic Latin + {begin: 0x0080, end: 0x00FF}, // Latin-1 Supplement + {begin: 0x0100, end: 0x017F}, // Latin Extended-A + {begin: 0x0180, end: 0x024F}, // Latin Extended-B + {begin: 0x0250, end: 0x02AF}, // IPA Extensions + {begin: 0x02B0, end: 0x02FF}, // Spacing Modifier Letters + {begin: 0x0300, end: 0x036F}, // Combining Diacritical Marks + {begin: 0x0370, end: 0x03FF}, // Greek and Coptic + {begin: 0x2C80, end: 0x2CFF}, // Coptic + {begin: 0x0400, end: 0x04FF}, // Cyrillic + {begin: 0x0530, end: 0x058F}, // Armenian + {begin: 0x0590, end: 0x05FF}, // Hebrew + {begin: 0xA500, end: 0xA63F}, // Vai + {begin: 0x0600, end: 0x06FF}, // Arabic + {begin: 0x07C0, end: 0x07FF}, // NKo + {begin: 0x0900, end: 0x097F}, // Devanagari + {begin: 0x0980, end: 0x09FF}, // Bengali + {begin: 0x0A00, end: 0x0A7F}, // Gurmukhi + {begin: 0x0A80, end: 0x0AFF}, // Gujarati + {begin: 0x0B00, end: 0x0B7F}, // Oriya + {begin: 0x0B80, end: 0x0BFF}, // Tamil + {begin: 0x0C00, end: 0x0C7F}, // Telugu + {begin: 0x0C80, end: 0x0CFF}, // Kannada + {begin: 0x0D00, end: 0x0D7F}, // Malayalam + {begin: 0x0E00, end: 0x0E7F}, // Thai + {begin: 0x0E80, end: 0x0EFF}, // Lao + {begin: 0x10A0, end: 0x10FF}, // Georgian + {begin: 0x1B00, end: 0x1B7F}, // Balinese + {begin: 0x1100, end: 0x11FF}, // Hangul Jamo + {begin: 0x1E00, end: 0x1EFF}, // Latin Extended Additional + {begin: 0x1F00, end: 0x1FFF}, // Greek Extended + {begin: 0x2000, end: 0x206F}, // General Punctuation + {begin: 0x2070, end: 0x209F}, // Superscripts And Subscripts + {begin: 0x20A0, end: 0x20CF}, // Currency Symbol + {begin: 0x20D0, end: 0x20FF}, // Combining Diacritical Marks For Symbols + {begin: 0x2100, end: 0x214F}, // Letterlike Symbols + {begin: 0x2150, end: 0x218F}, // Number Forms + {begin: 0x2190, end: 0x21FF}, // Arrows + {begin: 0x2200, end: 0x22FF}, // Mathematical Operators + {begin: 0x2300, end: 0x23FF}, // Miscellaneous Technical + {begin: 0x2400, end: 0x243F}, // Control Pictures + {begin: 0x2440, end: 0x245F}, // Optical Character Recognition + {begin: 0x2460, end: 0x24FF}, // Enclosed Alphanumerics + {begin: 0x2500, end: 0x257F}, // Box Drawing + {begin: 0x2580, end: 0x259F}, // Block Elements + {begin: 0x25A0, end: 0x25FF}, // Geometric Shapes + {begin: 0x2600, end: 0x26FF}, // Miscellaneous Symbols + {begin: 0x2700, end: 0x27BF}, // Dingbats + {begin: 0x3000, end: 0x303F}, // CJK Symbols And Punctuation + {begin: 0x3040, end: 0x309F}, // Hiragana + {begin: 0x30A0, end: 0x30FF}, // Katakana + {begin: 0x3100, end: 0x312F}, // Bopomofo + {begin: 0x3130, end: 0x318F}, // Hangul Compatibility Jamo + {begin: 0xA840, end: 0xA87F}, // Phags-pa + {begin: 0x3200, end: 0x32FF}, // Enclosed CJK Letters And Months + {begin: 0x3300, end: 0x33FF}, // CJK Compatibility + {begin: 0xAC00, end: 0xD7AF}, // Hangul Syllables + {begin: 0xD800, end: 0xDFFF}, // Non-Plane 0 * + {begin: 0x10900, end: 0x1091F}, // Phoenicia + {begin: 0x4E00, end: 0x9FFF}, // CJK Unified Ideographs + {begin: 0xE000, end: 0xF8FF}, // Private Use Area (plane 0) + {begin: 0x31C0, end: 0x31EF}, // CJK Strokes + {begin: 0xFB00, end: 0xFB4F}, // Alphabetic Presentation Forms + {begin: 0xFB50, end: 0xFDFF}, // Arabic Presentation Forms-A + {begin: 0xFE20, end: 0xFE2F}, // Combining Half Marks + {begin: 0xFE10, end: 0xFE1F}, // Vertical Forms + {begin: 0xFE50, end: 0xFE6F}, // Small Form Variants + {begin: 0xFE70, end: 0xFEFF}, // Arabic Presentation Forms-B + {begin: 0xFF00, end: 0xFFEF}, // Halfwidth And Fullwidth Forms + {begin: 0xFFF0, end: 0xFFFF}, // Specials + {begin: 0x0F00, end: 0x0FFF}, // Tibetan + {begin: 0x0700, end: 0x074F}, // Syriac + {begin: 0x0780, end: 0x07BF}, // Thaana + {begin: 0x0D80, end: 0x0DFF}, // Sinhala + {begin: 0x1000, end: 0x109F}, // Myanmar + {begin: 0x1200, end: 0x137F}, // Ethiopic + {begin: 0x13A0, end: 0x13FF}, // Cherokee + {begin: 0x1400, end: 0x167F}, // Unified Canadian Aboriginal Syllabics + {begin: 0x1680, end: 0x169F}, // Ogham + {begin: 0x16A0, end: 0x16FF}, // Runic + {begin: 0x1780, end: 0x17FF}, // Khmer + {begin: 0x1800, end: 0x18AF}, // Mongolian + {begin: 0x2800, end: 0x28FF}, // Braille Patterns + {begin: 0xA000, end: 0xA48F}, // Yi Syllables + {begin: 0x1700, end: 0x171F}, // Tagalog + {begin: 0x10300, end: 0x1032F}, // Old Italic + {begin: 0x10330, end: 0x1034F}, // Gothic + {begin: 0x10400, end: 0x1044F}, // Deseret + {begin: 0x1D000, end: 0x1D0FF}, // Byzantine Musical Symbols + {begin: 0x1D400, end: 0x1D7FF}, // Mathematical Alphanumeric Symbols + {begin: 0xFF000, end: 0xFFFFD}, // Private Use (plane 15) + {begin: 0xFE00, end: 0xFE0F}, // Variation Selectors + {begin: 0xE0000, end: 0xE007F}, // Tags + {begin: 0x1900, end: 0x194F}, // Limbu + {begin: 0x1950, end: 0x197F}, // Tai Le + {begin: 0x1980, end: 0x19DF}, // New Tai Lue + {begin: 0x1A00, end: 0x1A1F}, // Buginese + {begin: 0x2C00, end: 0x2C5F}, // Glagolitic + {begin: 0x2D30, end: 0x2D7F}, // Tifinagh + {begin: 0x4DC0, end: 0x4DFF}, // Yijing Hexagram Symbols + {begin: 0xA800, end: 0xA82F}, // Syloti Nagri + {begin: 0x10000, end: 0x1007F}, // Linear B Syllabary + {begin: 0x10140, end: 0x1018F}, // Ancient Greek Numbers + {begin: 0x10380, end: 0x1039F}, // Ugaritic + {begin: 0x103A0, end: 0x103DF}, // Old Persian + {begin: 0x10450, end: 0x1047F}, // Shavian + {begin: 0x10480, end: 0x104AF}, // Osmanya + {begin: 0x10800, end: 0x1083F}, // Cypriot Syllabary + {begin: 0x10A00, end: 0x10A5F}, // Kharoshthi + {begin: 0x1D300, end: 0x1D35F}, // Tai Xuan Jing Symbols + {begin: 0x12000, end: 0x123FF}, // Cuneiform + {begin: 0x1D360, end: 0x1D37F}, // Counting Rod Numerals + {begin: 0x1B80, end: 0x1BBF}, // Sundanese + {begin: 0x1C00, end: 0x1C4F}, // Lepcha + {begin: 0x1C50, end: 0x1C7F}, // Ol Chiki + {begin: 0xA880, end: 0xA8DF}, // Saurashtra + {begin: 0xA900, end: 0xA92F}, // Kayah Li + {begin: 0xA930, end: 0xA95F}, // Rejang + {begin: 0xAA00, end: 0xAA5F}, // Cham + {begin: 0x10190, end: 0x101CF}, // Ancient Symbols + {begin: 0x101D0, end: 0x101FF}, // Phaistos Disc + {begin: 0x102A0, end: 0x102DF}, // Carian + {begin: 0x1F030, end: 0x1F09F} // Domino Tiles + ]; + + function getUnicodeRange(unicode) { + for (var i = 0; i < unicodeRanges.length; i += 1) { + var range = unicodeRanges[i]; + if (unicode >= range.begin && unicode < range.end) { + return i; + } + } + + return -1; + } + + // Parse the OS/2 and Windows metrics `OS/2` table + function parseOS2Table(data, start) { + var os2 = {}; + var p = new parse.Parser(data, start); + os2.version = p.parseUShort(); + os2.xAvgCharWidth = p.parseShort(); + os2.usWeightClass = p.parseUShort(); + os2.usWidthClass = p.parseUShort(); + os2.fsType = p.parseUShort(); + os2.ySubscriptXSize = p.parseShort(); + os2.ySubscriptYSize = p.parseShort(); + os2.ySubscriptXOffset = p.parseShort(); + os2.ySubscriptYOffset = p.parseShort(); + os2.ySuperscriptXSize = p.parseShort(); + os2.ySuperscriptYSize = p.parseShort(); + os2.ySuperscriptXOffset = p.parseShort(); + os2.ySuperscriptYOffset = p.parseShort(); + os2.yStrikeoutSize = p.parseShort(); + os2.yStrikeoutPosition = p.parseShort(); + os2.sFamilyClass = p.parseShort(); + os2.panose = []; + for (var i = 0; i < 10; i++) { + os2.panose[i] = p.parseByte(); + } + + os2.ulUnicodeRange1 = p.parseULong(); + os2.ulUnicodeRange2 = p.parseULong(); + os2.ulUnicodeRange3 = p.parseULong(); + os2.ulUnicodeRange4 = p.parseULong(); + os2.achVendID = String.fromCharCode(p.parseByte(), p.parseByte(), p.parseByte(), p.parseByte()); + os2.fsSelection = p.parseUShort(); + os2.usFirstCharIndex = p.parseUShort(); + os2.usLastCharIndex = p.parseUShort(); + os2.sTypoAscender = p.parseShort(); + os2.sTypoDescender = p.parseShort(); + os2.sTypoLineGap = p.parseShort(); + os2.usWinAscent = p.parseUShort(); + os2.usWinDescent = p.parseUShort(); + if (os2.version >= 1) { + os2.ulCodePageRange1 = p.parseULong(); + os2.ulCodePageRange2 = p.parseULong(); + } + + if (os2.version >= 2) { + os2.sxHeight = p.parseShort(); + os2.sCapHeight = p.parseShort(); + os2.usDefaultChar = p.parseUShort(); + os2.usBreakChar = p.parseUShort(); + os2.usMaxContent = p.parseUShort(); + } + + return os2; + } + + function makeOS2Table(options) { + return new table.Table('OS/2', [ + {name: 'version', type: 'USHORT', value: 0x0003}, + {name: 'xAvgCharWidth', type: 'SHORT', value: 0}, + {name: 'usWeightClass', type: 'USHORT', value: 0}, + {name: 'usWidthClass', type: 'USHORT', value: 0}, + {name: 'fsType', type: 'USHORT', value: 0}, + {name: 'ySubscriptXSize', type: 'SHORT', value: 650}, + {name: 'ySubscriptYSize', type: 'SHORT', value: 699}, + {name: 'ySubscriptXOffset', type: 'SHORT', value: 0}, + {name: 'ySubscriptYOffset', type: 'SHORT', value: 140}, + {name: 'ySuperscriptXSize', type: 'SHORT', value: 650}, + {name: 'ySuperscriptYSize', type: 'SHORT', value: 699}, + {name: 'ySuperscriptXOffset', type: 'SHORT', value: 0}, + {name: 'ySuperscriptYOffset', type: 'SHORT', value: 479}, + {name: 'yStrikeoutSize', type: 'SHORT', value: 49}, + {name: 'yStrikeoutPosition', type: 'SHORT', value: 258}, + {name: 'sFamilyClass', type: 'SHORT', value: 0}, + {name: 'bFamilyType', type: 'BYTE', value: 0}, + {name: 'bSerifStyle', type: 'BYTE', value: 0}, + {name: 'bWeight', type: 'BYTE', value: 0}, + {name: 'bProportion', type: 'BYTE', value: 0}, + {name: 'bContrast', type: 'BYTE', value: 0}, + {name: 'bStrokeVariation', type: 'BYTE', value: 0}, + {name: 'bArmStyle', type: 'BYTE', value: 0}, + {name: 'bLetterform', type: 'BYTE', value: 0}, + {name: 'bMidline', type: 'BYTE', value: 0}, + {name: 'bXHeight', type: 'BYTE', value: 0}, + {name: 'ulUnicodeRange1', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange2', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange3', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange4', type: 'ULONG', value: 0}, + {name: 'achVendID', type: 'CHARARRAY', value: 'XXXX'}, + {name: 'fsSelection', type: 'USHORT', value: 0}, + {name: 'usFirstCharIndex', type: 'USHORT', value: 0}, + {name: 'usLastCharIndex', type: 'USHORT', value: 0}, + {name: 'sTypoAscender', type: 'SHORT', value: 0}, + {name: 'sTypoDescender', type: 'SHORT', value: 0}, + {name: 'sTypoLineGap', type: 'SHORT', value: 0}, + {name: 'usWinAscent', type: 'USHORT', value: 0}, + {name: 'usWinDescent', type: 'USHORT', value: 0}, + {name: 'ulCodePageRange1', type: 'ULONG', value: 0}, + {name: 'ulCodePageRange2', type: 'ULONG', value: 0}, + {name: 'sxHeight', type: 'SHORT', value: 0}, + {name: 'sCapHeight', type: 'SHORT', value: 0}, + {name: 'usDefaultChar', type: 'USHORT', value: 0}, + {name: 'usBreakChar', type: 'USHORT', value: 0}, + {name: 'usMaxContext', type: 'USHORT', value: 0} + ], options); + } + + var os2 = { parse: parseOS2Table, make: makeOS2Table, unicodeRanges: unicodeRanges, getUnicodeRange: getUnicodeRange }; + + // The `post` table stores additional PostScript information, such as glyph names. + + // Parse the PostScript `post` table + function parsePostTable(data, start) { + var post = {}; + var p = new parse.Parser(data, start); + post.version = p.parseVersion(); + post.italicAngle = p.parseFixed(); + post.underlinePosition = p.parseShort(); + post.underlineThickness = p.parseShort(); + post.isFixedPitch = p.parseULong(); + post.minMemType42 = p.parseULong(); + post.maxMemType42 = p.parseULong(); + post.minMemType1 = p.parseULong(); + post.maxMemType1 = p.parseULong(); + switch (post.version) { + case 1: + post.names = standardNames.slice(); + break; + case 2: + post.numberOfGlyphs = p.parseUShort(); + post.glyphNameIndex = new Array(post.numberOfGlyphs); + for (var i = 0; i < post.numberOfGlyphs; i++) { + post.glyphNameIndex[i] = p.parseUShort(); + } + + post.names = []; + for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { + if (post.glyphNameIndex[i$1] >= standardNames.length) { + var nameLength = p.parseChar(); + post.names.push(p.parseString(nameLength)); + } + } + + break; + case 2.5: + post.numberOfGlyphs = p.parseUShort(); + post.offset = new Array(post.numberOfGlyphs); + for (var i$2 = 0; i$2 < post.numberOfGlyphs; i$2++) { + post.offset[i$2] = p.parseChar(); + } + + break; + } + return post; + } + + function makePostTable() { + return new table.Table('post', [ + {name: 'version', type: 'FIXED', value: 0x00030000}, + {name: 'italicAngle', type: 'FIXED', value: 0}, + {name: 'underlinePosition', type: 'FWORD', value: 0}, + {name: 'underlineThickness', type: 'FWORD', value: 0}, + {name: 'isFixedPitch', type: 'ULONG', value: 0}, + {name: 'minMemType42', type: 'ULONG', value: 0}, + {name: 'maxMemType42', type: 'ULONG', value: 0}, + {name: 'minMemType1', type: 'ULONG', value: 0}, + {name: 'maxMemType1', type: 'ULONG', value: 0} + ]); + } + + var post = { parse: parsePostTable, make: makePostTable }; + + // The `GSUB` table contains ligatures, among other things. + + var subtableParsers = new Array(9); // subtableParsers[0] is unused + + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#SS + subtableParsers[1] = function parseLookup1() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + if (substFormat === 1) { + return { + substFormat: 1, + coverage: this.parsePointer(Parser.coverage), + deltaGlyphId: this.parseUShort() + }; + } else if (substFormat === 2) { + return { + substFormat: 2, + coverage: this.parsePointer(Parser.coverage), + substitute: this.parseOffset16List() + }; + } + check.assert(false, '0x' + start.toString(16) + ': lookup type 1 format must be 1 or 2.'); + }; + + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#MS + subtableParsers[2] = function parseLookup2() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Multiple Substitution Subtable identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + sequences: this.parseListOfLists() + }; + }; + + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#AS + subtableParsers[3] = function parseLookup3() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Alternate Substitution Subtable identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + alternateSets: this.parseListOfLists() + }; + }; + + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#LS + subtableParsers[4] = function parseLookup4() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB ligature table identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + ligatureSets: this.parseListOfLists(function() { + return { + ligGlyph: this.parseUShort(), + components: this.parseUShortList(this.parseUShort() - 1) + }; + }) + }; + }; + + var lookupRecordDesc = { + sequenceIndex: Parser.uShort, + lookupListIndex: Parser.uShort + }; + + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CSF + subtableParsers[5] = function parseLookup5() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + + if (substFormat === 1) { + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + ruleSets: this.parseListOfLists(function() { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + input: this.parseUShortList(glyphCount - 1), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 2) { + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + classDef: this.parsePointer(Parser.classDef), + classSets: this.parseListOfLists(function() { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + classes: this.parseUShortList(glyphCount - 1), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 3) { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + substFormat: substFormat, + coverages: this.parseList(glyphCount, Parser.pointer(Parser.coverage)), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; + } + check.assert(false, '0x' + start.toString(16) + ': lookup type 5 format must be 1, 2 or 3.'); + }; + + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CC + subtableParsers[6] = function parseLookup6() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + if (substFormat === 1) { + return { + substFormat: 1, + coverage: this.parsePointer(Parser.coverage), + chainRuleSets: this.parseListOfLists(function() { + return { + backtrack: this.parseUShortList(), + input: this.parseUShortList(this.parseShort() - 1), + lookahead: this.parseUShortList(), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 2) { + return { + substFormat: 2, + coverage: this.parsePointer(Parser.coverage), + backtrackClassDef: this.parsePointer(Parser.classDef), + inputClassDef: this.parsePointer(Parser.classDef), + lookaheadClassDef: this.parsePointer(Parser.classDef), + chainClassSet: this.parseListOfLists(function() { + return { + backtrack: this.parseUShortList(), + input: this.parseUShortList(this.parseShort() - 1), + lookahead: this.parseUShortList(), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 3) { + return { + substFormat: 3, + backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), + inputCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + } + check.assert(false, '0x' + start.toString(16) + ': lookup type 6 format must be 1, 2 or 3.'); + }; + + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#ES + subtableParsers[7] = function parseLookup7() { + // Extension Substitution subtable + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Extension Substitution subtable identifier-format must be 1'); + var extensionLookupType = this.parseUShort(); + var extensionParser = new Parser(this.data, this.offset + this.parseULong()); + return { + substFormat: 1, + lookupType: extensionLookupType, + extension: subtableParsers[extensionLookupType].call(extensionParser) + }; + }; + + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#RCCS + subtableParsers[8] = function parseLookup8() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Reverse Chaining Contextual Single Substitution Subtable identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), + substitutes: this.parseUShortList() + }; + }; + + // https://www.microsoft.com/typography/OTSPEC/gsub.htm + function parseGsubTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GSUB table version.'); + if (tableVersion === 1) { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers) + }; + } else { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers), + variations: p.parseFeatureVariationsList() + }; + } + + } + + // GSUB Writing ////////////////////////////////////////////// + var subtableMakers = new Array(9); + + subtableMakers[1] = function makeLookup1(subtable) { + if (subtable.substFormat === 1) { + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}, + {name: 'deltaGlyphID', type: 'USHORT', value: subtable.deltaGlyphId} + ]); + } else { + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 2}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.ushortList('substitute', subtable.substitute))); + } + }; + + subtableMakers[3] = function makeLookup3(subtable) { + check.assert(subtable.substFormat === 1, 'Lookup type 3 substFormat must be 1.'); + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('altSet', subtable.alternateSets, function(alternateSet) { + return new table.Table('alternateSetTable', table.ushortList('alternate', alternateSet)); + }))); + }; + + subtableMakers[4] = function makeLookup4(subtable) { + check.assert(subtable.substFormat === 1, 'Lookup type 4 substFormat must be 1.'); + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('ligSet', subtable.ligatureSets, function(ligatureSet) { + return new table.Table('ligatureSetTable', table.tableList('ligature', ligatureSet, function(ligature) { + return new table.Table('ligatureTable', + [{name: 'ligGlyph', type: 'USHORT', value: ligature.ligGlyph}] + .concat(table.ushortList('component', ligature.components, ligature.components.length + 1)) + ); + })); + }))); + }; + + function makeGsubTable(gsub) { + return new table.Table('GSUB', [ + {name: 'version', type: 'ULONG', value: 0x10000}, + {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gsub.scripts)}, + {name: 'features', type: 'TABLE', value: new table.FeatureList(gsub.features)}, + {name: 'lookups', type: 'TABLE', value: new table.LookupList(gsub.lookups, subtableMakers)} + ]); + } + + var gsub = { parse: parseGsubTable, make: makeGsubTable }; + + // The `GPOS` table contains kerning pairs, among other things. + + // Parse the metadata `meta` table. + // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6meta.html + function parseMetaTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 1, 'Unsupported META table version.'); + p.parseULong(); // flags - currently unused and set to 0 + p.parseULong(); // tableOffset + var numDataMaps = p.parseULong(); + + var tags = {}; + for (var i = 0; i < numDataMaps; i++) { + var tag = p.parseTag(); + var dataOffset = p.parseULong(); + var dataLength = p.parseULong(); + var text = decode.UTF8(data, start + dataOffset, dataLength); + + tags[tag] = text; + } + return tags; + } + + function makeMetaTable(tags) { + var numTags = Object.keys(tags).length; + var stringPool = ''; + var stringPoolOffset = 16 + numTags * 12; + + var result = new table.Table('meta', [ + {name: 'version', type: 'ULONG', value: 1}, + {name: 'flags', type: 'ULONG', value: 0}, + {name: 'offset', type: 'ULONG', value: stringPoolOffset}, + {name: 'numTags', type: 'ULONG', value: numTags} + ]); + + for (var tag in tags) { + var pos = stringPool.length; + stringPool += tags[tag]; + + result.fields.push({name: 'tag ' + tag, type: 'TAG', value: tag}); + result.fields.push({name: 'offset ' + tag, type: 'ULONG', value: stringPoolOffset + pos}); + result.fields.push({name: 'length ' + tag, type: 'ULONG', value: tags[tag].length}); + } + + result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); + + return result; + } + + var meta = { parse: parseMetaTable, make: makeMetaTable }; + + // The `sfnt` wrapper provides organization for the tables in the font. + + function log2(v) { + return Math.log(v) / Math.log(2) | 0; + } + + function computeCheckSum(bytes) { + while (bytes.length % 4 !== 0) { + bytes.push(0); + } + + var sum = 0; + for (var i = 0; i < bytes.length; i += 4) { + sum += (bytes[i] << 24) + + (bytes[i + 1] << 16) + + (bytes[i + 2] << 8) + + (bytes[i + 3]); + } + + sum %= Math.pow(2, 32); + return sum; + } + + function makeTableRecord(tag, checkSum, offset, length) { + return new table.Record('Table Record', [ + {name: 'tag', type: 'TAG', value: tag !== undefined ? tag : ''}, + {name: 'checkSum', type: 'ULONG', value: checkSum !== undefined ? checkSum : 0}, + {name: 'offset', type: 'ULONG', value: offset !== undefined ? offset : 0}, + {name: 'length', type: 'ULONG', value: length !== undefined ? length : 0} + ]); + } + + function makeSfntTable(tables) { + var sfnt = new table.Table('sfnt', [ + {name: 'version', type: 'TAG', value: 'OTTO'}, + {name: 'numTables', type: 'USHORT', value: 0}, + {name: 'searchRange', type: 'USHORT', value: 0}, + {name: 'entrySelector', type: 'USHORT', value: 0}, + {name: 'rangeShift', type: 'USHORT', value: 0} + ]); + sfnt.tables = tables; + sfnt.numTables = tables.length; + var highestPowerOf2 = Math.pow(2, log2(sfnt.numTables)); + sfnt.searchRange = 16 * highestPowerOf2; + sfnt.entrySelector = log2(highestPowerOf2); + sfnt.rangeShift = sfnt.numTables * 16 - sfnt.searchRange; + + var recordFields = []; + var tableFields = []; + + var offset = sfnt.sizeOf() + (makeTableRecord().sizeOf() * sfnt.numTables); + while (offset % 4 !== 0) { + offset += 1; + tableFields.push({name: 'padding', type: 'BYTE', value: 0}); + } + + for (var i = 0; i < tables.length; i += 1) { + var t = tables[i]; + check.argument(t.tableName.length === 4, 'Table name' + t.tableName + ' is invalid.'); + var tableLength = t.sizeOf(); + var tableRecord = makeTableRecord(t.tableName, computeCheckSum(t.encode()), offset, tableLength); + recordFields.push({name: tableRecord.tag + ' Table Record', type: 'RECORD', value: tableRecord}); + tableFields.push({name: t.tableName + ' table', type: 'RECORD', value: t}); + offset += tableLength; + check.argument(!isNaN(offset), 'Something went wrong calculating the offset.'); + while (offset % 4 !== 0) { + offset += 1; + tableFields.push({name: 'padding', type: 'BYTE', value: 0}); + } + } + + // Table records need to be sorted alphabetically. + recordFields.sort(function(r1, r2) { + if (r1.value.tag > r2.value.tag) { + return 1; + } else { + return -1; + } + }); + + sfnt.fields = sfnt.fields.concat(recordFields); + sfnt.fields = sfnt.fields.concat(tableFields); + return sfnt; + } + + // Get the metrics for a character. If the string has more than one character + // this function returns metrics for the first available character. + // You can provide optional fallback metrics if no characters are available. + function metricsForChar(font, chars, notFoundMetrics) { + for (var i = 0; i < chars.length; i += 1) { + var glyphIndex = font.charToGlyphIndex(chars[i]); + if (glyphIndex > 0) { + var glyph = font.glyphs.get(glyphIndex); + return glyph.getMetrics(); + } + } + + return notFoundMetrics; + } + + function average(vs) { + var sum = 0; + for (var i = 0; i < vs.length; i += 1) { + sum += vs[i]; + } + + return sum / vs.length; + } + + // Convert the font object to a SFNT data structure. + // This structure contains all the necessary tables and metadata to create a binary OTF file. + function fontToSfntTable(font) { + var xMins = []; + var yMins = []; + var xMaxs = []; + var yMaxs = []; + var advanceWidths = []; + var leftSideBearings = []; + var rightSideBearings = []; + var firstCharIndex; + var lastCharIndex = 0; + var ulUnicodeRange1 = 0; + var ulUnicodeRange2 = 0; + var ulUnicodeRange3 = 0; + var ulUnicodeRange4 = 0; + + for (var i = 0; i < font.glyphs.length; i += 1) { + var glyph = font.glyphs.get(i); + var unicode = glyph.unicode | 0; + + if (isNaN(glyph.advanceWidth)) { + throw new Error('Glyph ' + glyph.name + ' (' + i + '): advanceWidth is not a number.'); + } + + if (firstCharIndex > unicode || firstCharIndex === undefined) { + // ignore .notdef char + if (unicode > 0) { + firstCharIndex = unicode; + } + } + + if (lastCharIndex < unicode) { + lastCharIndex = unicode; + } + + var position = os2.getUnicodeRange(unicode); + if (position < 32) { + ulUnicodeRange1 |= 1 << position; + } else if (position < 64) { + ulUnicodeRange2 |= 1 << position - 32; + } else if (position < 96) { + ulUnicodeRange3 |= 1 << position - 64; + } else if (position < 123) { + ulUnicodeRange4 |= 1 << position - 96; + } else { + throw new Error('Unicode ranges bits > 123 are reserved for internal usage'); + } + // Skip non-important characters. + if (glyph.name === '.notdef') { continue; } + var metrics = glyph.getMetrics(); + xMins.push(metrics.xMin); + yMins.push(metrics.yMin); + xMaxs.push(metrics.xMax); + yMaxs.push(metrics.yMax); + leftSideBearings.push(metrics.leftSideBearing); + rightSideBearings.push(metrics.rightSideBearing); + advanceWidths.push(glyph.advanceWidth); + } + + var globals = { + xMin: Math.min.apply(null, xMins), + yMin: Math.min.apply(null, yMins), + xMax: Math.max.apply(null, xMaxs), + yMax: Math.max.apply(null, yMaxs), + advanceWidthMax: Math.max.apply(null, advanceWidths), + advanceWidthAvg: average(advanceWidths), + minLeftSideBearing: Math.min.apply(null, leftSideBearings), + maxLeftSideBearing: Math.max.apply(null, leftSideBearings), + minRightSideBearing: Math.min.apply(null, rightSideBearings) + }; + globals.ascender = font.ascender; + globals.descender = font.descender; + + var headTable = head.make({ + flags: 3, // 00000011 (baseline for font at y=0; left sidebearing point at x=0) + unitsPerEm: font.unitsPerEm, + xMin: globals.xMin, + yMin: globals.yMin, + xMax: globals.xMax, + yMax: globals.yMax, + lowestRecPPEM: 3, + createdTimestamp: font.createdTimestamp + }); + + var hheaTable = hhea.make({ + ascender: globals.ascender, + descender: globals.descender, + advanceWidthMax: globals.advanceWidthMax, + minLeftSideBearing: globals.minLeftSideBearing, + minRightSideBearing: globals.minRightSideBearing, + xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin), + numberOfHMetrics: font.glyphs.length + }); + + var maxpTable = maxp.make(font.glyphs.length); + + var os2Table = os2.make(Object.assign({ + xAvgCharWidth: Math.round(globals.advanceWidthAvg), + usFirstCharIndex: firstCharIndex, + usLastCharIndex: lastCharIndex, + ulUnicodeRange1: ulUnicodeRange1, + ulUnicodeRange2: ulUnicodeRange2, + ulUnicodeRange3: ulUnicodeRange3, + ulUnicodeRange4: ulUnicodeRange4, + // See http://typophile.com/node/13081 for more info on vertical metrics. + // We get metrics for typical characters (such as "x" for xHeight). + // We provide some fallback characters if characters are unavailable: their + // ordering was chosen experimentally. + sTypoAscender: globals.ascender, + sTypoDescender: globals.descender, + sTypoLineGap: 0, + usWinAscent: globals.yMax, + usWinDescent: Math.abs(globals.yMin), + ulCodePageRange1: 1, // FIXME: hard-code Latin 1 support for now + sxHeight: metricsForChar(font, 'xyvw', {yMax: Math.round(globals.ascender / 2)}).yMax, + sCapHeight: metricsForChar(font, 'HIKLEFJMNTZBDPRAGOQSUVWXY', globals).yMax, + usDefaultChar: font.hasChar(' ') ? 32 : 0, // Use space as the default character, if available. + usBreakChar: font.hasChar(' ') ? 32 : 0, // Use space as the break character, if available. + }, font.tables.os2)); + + var hmtxTable = hmtx.make(font.glyphs); + var cmapTable = cmap.make(font.glyphs); + + var englishFamilyName = font.getEnglishName('fontFamily'); + var englishStyleName = font.getEnglishName('fontSubfamily'); + var englishFullName = englishFamilyName + ' ' + englishStyleName; + var postScriptName = font.getEnglishName('postScriptName'); + if (!postScriptName) { + postScriptName = englishFamilyName.replace(/\s/g, '') + '-' + englishStyleName; + } + + var names = {}; + for (var n in font.names) { + names[n] = font.names[n]; + } + + if (!names.uniqueID) { + names.uniqueID = {en: font.getEnglishName('manufacturer') + ':' + englishFullName}; + } + + if (!names.postScriptName) { + names.postScriptName = {en: postScriptName}; + } + + if (!names.preferredFamily) { + names.preferredFamily = font.names.fontFamily; + } + + if (!names.preferredSubfamily) { + names.preferredSubfamily = font.names.fontSubfamily; + } + + var languageTags = []; + var nameTable = _name.make(names, languageTags); + var ltagTable = (languageTags.length > 0 ? ltag.make(languageTags) : undefined); + + var postTable = post.make(); + var cffTable = cff.make(font.glyphs, { + version: font.getEnglishName('version'), + fullName: englishFullName, + familyName: englishFamilyName, + weightName: englishStyleName, + postScriptName: postScriptName, + unitsPerEm: font.unitsPerEm, + fontBBox: [0, globals.yMin, globals.ascender, globals.advanceWidthMax] + }); + + var metaTable = (font.metas && Object.keys(font.metas).length > 0) ? meta.make(font.metas) : undefined; + + // The order does not matter because makeSfntTable() will sort them. + var tables = [headTable, hheaTable, maxpTable, os2Table, nameTable, cmapTable, postTable, cffTable, hmtxTable]; + if (ltagTable) { + tables.push(ltagTable); + } + // Optional tables + if (font.tables.gsub) { + tables.push(gsub.make(font.tables.gsub)); + } + if (metaTable) { + tables.push(metaTable); + } + + var sfntTable = makeSfntTable(tables); + + // Compute the font's checkSum and store it in head.checkSumAdjustment. + var bytes = sfntTable.encode(); + var checkSum = computeCheckSum(bytes); + var tableFields = sfntTable.fields; + var checkSumAdjusted = false; + for (var i$1 = 0; i$1 < tableFields.length; i$1 += 1) { + if (tableFields[i$1].name === 'head table') { + tableFields[i$1].value.checkSumAdjustment = 0xB1B0AFBA - checkSum; + checkSumAdjusted = true; + break; + } + } + + if (!checkSumAdjusted) { + throw new Error('Could not find head table with checkSum to adjust.'); + } + + return sfntTable; + } + + var sfnt = { make: makeSfntTable, fontToTable: fontToSfntTable, computeCheckSum: computeCheckSum }; + + // The Layout object is the prototype of Substitution objects, and provides + + function searchTag(arr, tag) { + /* jshint bitwise: false */ + var imin = 0; + var imax = arr.length - 1; + while (imin <= imax) { + var imid = (imin + imax) >>> 1; + var val = arr[imid].tag; + if (val === tag) { + return imid; + } else if (val < tag) { + imin = imid + 1; + } else { imax = imid - 1; } + } + // Not found: return -1-insertion point + return -imin - 1; + } + + function binSearch(arr, value) { + /* jshint bitwise: false */ + var imin = 0; + var imax = arr.length - 1; + while (imin <= imax) { + var imid = (imin + imax) >>> 1; + var val = arr[imid]; + if (val === value) { + return imid; + } else if (val < value) { + imin = imid + 1; + } else { imax = imid - 1; } + } + // Not found: return -1-insertion point + return -imin - 1; + } + + // binary search in a list of ranges (coverage, class definition) + function searchRange(ranges, value) { + // jshint bitwise: false + var range; + var imin = 0; + var imax = ranges.length - 1; + while (imin <= imax) { + var imid = (imin + imax) >>> 1; + range = ranges[imid]; + var start = range.start; + if (start === value) { + return range; + } else if (start < value) { + imin = imid + 1; + } else { imax = imid - 1; } + } + if (imin > 0) { + range = ranges[imin - 1]; + if (value > range.end) { return 0; } + return range; + } + } + + /** + * @exports opentype.Layout + * @class + */ + function Layout(font, tableName) { + this.font = font; + this.tableName = tableName; + } + + Layout.prototype = { + + /** + * Binary search an object by "tag" property + * @instance + * @function searchTag + * @memberof opentype.Layout + * @param {Array} arr + * @param {string} tag + * @return {number} + */ + searchTag: searchTag, + + /** + * Binary search in a list of numbers + * @instance + * @function binSearch + * @memberof opentype.Layout + * @param {Array} arr + * @param {number} value + * @return {number} + */ + binSearch: binSearch, + + /** + * Get or create the Layout table (GSUB, GPOS etc). + * @param {boolean} create - Whether to create a new one. + * @return {Object} The GSUB or GPOS table. + */ + getTable: function(create) { + var layout = this.font.tables[this.tableName]; + if (!layout && create) { + layout = this.font.tables[this.tableName] = this.createDefaultTable(); + } + return layout; + }, + + /** + * Returns all scripts in the substitution table. + * @instance + * @return {Array} + */ + getScriptNames: function() { + var layout = this.getTable(); + if (!layout) { return []; } + return layout.scripts.map(function(script) { + return script.tag; + }); + }, + + /** + * Returns the best bet for a script name. + * Returns 'DFLT' if it exists. + * If not, returns 'latn' if it exists. + * If neither exist, returns undefined. + */ + getDefaultScriptName: function() { + var layout = this.getTable(); + if (!layout) { return; } + var hasLatn = false; + for (var i = 0; i < layout.scripts.length; i++) { + var name = layout.scripts[i].tag; + if (name === 'DFLT') { return name; } + if (name === 'latn') { hasLatn = true; } + } + if (hasLatn) { return 'latn'; } + }, + + /** + * Returns all LangSysRecords in the given script. + * @instance + * @param {string} [script='DFLT'] + * @param {boolean} create - forces the creation of this script table if it doesn't exist. + * @return {Object} An object with tag and script properties. + */ + getScriptTable: function(script, create) { + var layout = this.getTable(create); + if (layout) { + script = script || 'DFLT'; + var scripts = layout.scripts; + var pos = searchTag(layout.scripts, script); + if (pos >= 0) { + return scripts[pos].script; + } else if (create) { + var scr = { + tag: script, + script: { + defaultLangSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []}, + langSysRecords: [] + } + }; + scripts.splice(-1 - pos, 0, scr); + return scr.script; + } + } + }, + + /** + * Returns a language system table + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {boolean} create - forces the creation of this langSysTable if it doesn't exist. + * @return {Object} + */ + getLangSysTable: function(script, language, create) { + var scriptTable = this.getScriptTable(script, create); + if (scriptTable) { + if (!language || language === 'dflt' || language === 'DFLT') { + return scriptTable.defaultLangSys; + } + var pos = searchTag(scriptTable.langSysRecords, language); + if (pos >= 0) { + return scriptTable.langSysRecords[pos].langSys; + } else if (create) { + var langSysRecord = { + tag: language, + langSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []} + }; + scriptTable.langSysRecords.splice(-1 - pos, 0, langSysRecord); + return langSysRecord.langSys; + } + } + }, + + /** + * Get a specific feature table. + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {string} feature - One of the codes listed at https://www.microsoft.com/typography/OTSPEC/featurelist.htm + * @param {boolean} create - forces the creation of the feature table if it doesn't exist. + * @return {Object} + */ + getFeatureTable: function(script, language, feature, create) { + var langSysTable = this.getLangSysTable(script, language, create); + if (langSysTable) { + var featureRecord; + var featIndexes = langSysTable.featureIndexes; + var allFeatures = this.font.tables[this.tableName].features; + // The FeatureIndex array of indices is in arbitrary order, + // even if allFeatures is sorted alphabetically by feature tag. + for (var i = 0; i < featIndexes.length; i++) { + featureRecord = allFeatures[featIndexes[i]]; + if (featureRecord.tag === feature) { + return featureRecord.feature; + } + } + if (create) { + var index = allFeatures.length; + // Automatic ordering of features would require to shift feature indexes in the script list. + check.assert(index === 0 || feature >= allFeatures[index - 1].tag, 'Features must be added in alphabetical order.'); + featureRecord = { + tag: feature, + feature: { params: 0, lookupListIndexes: [] } + }; + allFeatures.push(featureRecord); + featIndexes.push(index); + return featureRecord.feature; + } + } + }, + + /** + * Get the lookup tables of a given type for a script/language/feature. + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {string} feature - 4-letter feature code + * @param {number} lookupType - 1 to 9 + * @param {boolean} create - forces the creation of the lookup table if it doesn't exist, with no subtables. + * @return {Object[]} + */ + getLookupTables: function(script, language, feature, lookupType, create) { + var featureTable = this.getFeatureTable(script, language, feature, create); + var tables = []; + if (featureTable) { + var lookupTable; + var lookupListIndexes = featureTable.lookupListIndexes; + var allLookups = this.font.tables[this.tableName].lookups; + // lookupListIndexes are in no particular order, so use naive search. + for (var i = 0; i < lookupListIndexes.length; i++) { + lookupTable = allLookups[lookupListIndexes[i]]; + if (lookupTable.lookupType === lookupType) { + tables.push(lookupTable); + } + } + if (tables.length === 0 && create) { + lookupTable = { + lookupType: lookupType, + lookupFlag: 0, + subtables: [], + markFilteringSet: undefined + }; + var index = allLookups.length; + allLookups.push(lookupTable); + lookupListIndexes.push(index); + return [lookupTable]; + } + } + return tables; + }, + + /** + * Find a glyph in a class definition table + * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table + * @param {object} classDefTable - an OpenType Layout class definition table + * @param {number} glyphIndex - the index of the glyph to find + * @returns {number} -1 if not found + */ + getGlyphClass: function(classDefTable, glyphIndex) { + switch (classDefTable.format) { + case 1: + if (classDefTable.startGlyph <= glyphIndex && glyphIndex < classDefTable.startGlyph + classDefTable.classes.length) { + return classDefTable.classes[glyphIndex - classDefTable.startGlyph]; + } + return 0; + case 2: + var range = searchRange(classDefTable.ranges, glyphIndex); + return range ? range.classId : 0; + } + }, + + /** + * Find a glyph in a coverage table + * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-table + * @param {object} coverageTable - an OpenType Layout coverage table + * @param {number} glyphIndex - the index of the glyph to find + * @returns {number} -1 if not found + */ + getCoverageIndex: function(coverageTable, glyphIndex) { + switch (coverageTable.format) { + case 1: + var index = binSearch(coverageTable.glyphs, glyphIndex); + return index >= 0 ? index : -1; + case 2: + var range = searchRange(coverageTable.ranges, glyphIndex); + return range ? range.index + glyphIndex - range.start : -1; + } + }, + + /** + * Returns the list of glyph indexes of a coverage table. + * Format 1: the list is stored raw + * Format 2: compact list as range records. + * @instance + * @param {Object} coverageTable + * @return {Array} + */ + expandCoverage: function(coverageTable) { + if (coverageTable.format === 1) { + return coverageTable.glyphs; + } else { + var glyphs = []; + var ranges = coverageTable.ranges; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + var start = range.start; + var end = range.end; + for (var j = start; j <= end; j++) { + glyphs.push(j); + } + } + return glyphs; + } + } + + }; + + // The Position object provides utility methods to manipulate + + /** + * @exports opentype.Position + * @class + * @extends opentype.Layout + * @param {opentype.Font} + * @constructor + */ + function Position(font) { + Layout.call(this, font, 'gpos'); + } + + Position.prototype = Layout.prototype; + + /** + * Init some data for faster and easier access later. + */ + Position.prototype.init = function() { + var script = this.getDefaultScriptName(); + this.defaultKerningTables = this.getKerningTables(script); + }; + + /** + * Find a glyph pair in a list of lookup tables of type 2 and retrieve the xAdvance kerning value. + * + * @param {integer} leftIndex - left glyph index + * @param {integer} rightIndex - right glyph index + * @returns {integer} + */ + Position.prototype.getKerningValue = function(kerningLookups, leftIndex, rightIndex) { + for (var i = 0; i < kerningLookups.length; i++) { + var subtables = kerningLookups[i].subtables; + for (var j = 0; j < subtables.length; j++) { + var subtable = subtables[j]; + var covIndex = this.getCoverageIndex(subtable.coverage, leftIndex); + if (covIndex < 0) { continue; } + switch (subtable.posFormat) { + case 1: + // Search Pair Adjustment Positioning Format 1 + var pairSet = subtable.pairSets[covIndex]; + for (var k = 0; k < pairSet.length; k++) { + var pair = pairSet[k]; + if (pair.secondGlyph === rightIndex) { + return pair.value1 && pair.value1.xAdvance || 0; + } + } + break; // left glyph found, not right glyph - try next subtable + case 2: + // Search Pair Adjustment Positioning Format 2 + var class1 = this.getGlyphClass(subtable.classDef1, leftIndex); + var class2 = this.getGlyphClass(subtable.classDef2, rightIndex); + var pair$1 = subtable.classRecords[class1][class2]; + return pair$1.value1 && pair$1.value1.xAdvance || 0; + } + } + } + return 0; + }; + + /** + * List all kerning lookup tables. + * + * @param {string} [script='DFLT'] - use font.position.getDefaultScriptName() for a better default value + * @param {string} [language='dflt'] + * @return {object[]} The list of kerning lookup tables (may be empty), or undefined if there is no GPOS table (and we should use the kern table) + */ + Position.prototype.getKerningTables = function(script, language) { + if (this.font.tables.gpos) { + return this.getLookupTables(script, language, 'kern', 2); + } + }; + + // The Substitution object provides utility methods to manipulate + + /** + * @exports opentype.Substitution + * @class + * @extends opentype.Layout + * @param {opentype.Font} + * @constructor + */ + function Substitution(font) { + Layout.call(this, font, 'gsub'); + } + + // Check if 2 arrays of primitives are equal. + function arraysEqual(ar1, ar2) { + var n = ar1.length; + if (n !== ar2.length) { return false; } + for (var i = 0; i < n; i++) { + if (ar1[i] !== ar2[i]) { return false; } + } + return true; + } + + // Find the first subtable of a lookup table in a particular format. + function getSubstFormat(lookupTable, format, defaultSubtable) { + var subtables = lookupTable.subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + if (subtable.substFormat === format) { + return subtable; + } + } + if (defaultSubtable) { + subtables.push(defaultSubtable); + return defaultSubtable; + } + return undefined; + } + + Substitution.prototype = Layout.prototype; + + /** + * Create a default GSUB table. + * @return {Object} gsub - The GSUB table. + */ + Substitution.prototype.createDefaultTable = function() { + // Generate a default empty GSUB table with just a DFLT script and dflt lang sys. + return { + version: 1, + scripts: [{ + tag: 'DFLT', + script: { + defaultLangSys: { reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: [] }, + langSysRecords: [] + } + }], + features: [], + lookups: [] + }; + }; + + /** + * List all single substitutions (lookup type 1) for a given script, language, and feature. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @param {string} feature - 4-character feature name ('aalt', 'salt', 'ss01'...) + * @return {Array} substitutions - The list of substitutions. + */ + Substitution.prototype.getSingle = function(feature, script, language) { + var substitutions = []; + var lookupTables = this.getLookupTables(script, language, feature, 1); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var j = (void 0); + if (subtable.substFormat === 1) { + var delta = subtable.deltaGlyphId; + for (j = 0; j < glyphs.length; j++) { + var glyph = glyphs[j]; + substitutions.push({ sub: glyph, by: glyph + delta }); + } + } else { + var substitute = subtable.substitute; + for (j = 0; j < glyphs.length; j++) { + substitutions.push({ sub: glyphs[j], by: substitute[j] }); + } + } + } + } + return substitutions; + }; + + /** + * List all alternates (lookup type 3) for a given script, language, and feature. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @param {string} feature - 4-character feature name ('aalt', 'salt'...) + * @return {Array} alternates - The list of alternates + */ + Substitution.prototype.getAlternates = function(feature, script, language) { + var alternates = []; + var lookupTables = this.getLookupTables(script, language, feature, 3); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var alternateSets = subtable.alternateSets; + for (var j = 0; j < glyphs.length; j++) { + alternates.push({ sub: glyphs[j], by: alternateSets[j] }); + } + } + } + return alternates; + }; + + /** + * List all ligatures (lookup type 4) for a given script, language, and feature. + * The result is an array of ligature objects like { sub: [ids], by: id } + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @return {Array} ligatures - The list of ligatures. + */ + Substitution.prototype.getLigatures = function(feature, script, language) { + var ligatures = []; + var lookupTables = this.getLookupTables(script, language, feature, 4); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var ligatureSets = subtable.ligatureSets; + for (var j = 0; j < glyphs.length; j++) { + var startGlyph = glyphs[j]; + var ligSet = ligatureSets[j]; + for (var k = 0; k < ligSet.length; k++) { + var lig = ligSet[k]; + ligatures.push({ + sub: [startGlyph].concat(lig.components), + by: lig.ligGlyph + }); + } + } + } + } + return ligatures; + }; + + /** + * Add or modify a single substitution (lookup type 1) + * Format 2, more flexible, is always used. + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {Object} substitution - { sub: id, delta: number } for format 1 or { sub: id, by: id } for format 2. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ + Substitution.prototype.addSingle = function(feature, substitution, script, language) { + var lookupTable = this.getLookupTables(script, language, feature, 1, true)[0]; + var subtable = getSubstFormat(lookupTable, 2, { // lookup type 1 subtable, format 2, coverage format 1 + substFormat: 2, + coverage: {format: 1, glyphs: []}, + substitute: [] + }); + check.assert(subtable.coverage.format === 1, 'Ligature: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.substitute.splice(pos, 0, 0); + } + subtable.substitute[pos] = substitution.by; + }; + + /** + * Add or modify an alternate substitution (lookup type 1) + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {Object} substitution - { sub: id, by: [ids] } + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ + Substitution.prototype.addAlternate = function(feature, substitution, script, language) { + var lookupTable = this.getLookupTables(script, language, feature, 3, true)[0]; + var subtable = getSubstFormat(lookupTable, 1, { // lookup type 3 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: {format: 1, glyphs: []}, + alternateSets: [] + }); + check.assert(subtable.coverage.format === 1, 'Ligature: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.alternateSets.splice(pos, 0, 0); + } + subtable.alternateSets[pos] = substitution.by; + }; + + /** + * Add a ligature (lookup type 4) + * Ligatures with more components must be stored ahead of those with fewer components in order to be found + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {Object} ligature - { sub: [ids], by: id } + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ + Substitution.prototype.addLigature = function(feature, ligature, script, language) { + var lookupTable = this.getLookupTables(script, language, feature, 4, true)[0]; + var subtable = lookupTable.subtables[0]; + if (!subtable) { + subtable = { // lookup type 4 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: { format: 1, glyphs: [] }, + ligatureSets: [] + }; + lookupTable.subtables[0] = subtable; + } + check.assert(subtable.coverage.format === 1, 'Ligature: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = ligature.sub[0]; + var ligComponents = ligature.sub.slice(1); + var ligatureTable = { + ligGlyph: ligature.by, + components: ligComponents + }; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos >= 0) { + // ligatureSet already exists + var ligatureSet = subtable.ligatureSets[pos]; + for (var i = 0; i < ligatureSet.length; i++) { + // If ligature already exists, return. + if (arraysEqual(ligatureSet[i].components, ligComponents)) { + return; + } + } + // ligature does not exist: add it. + ligatureSet.push(ligatureTable); + } else { + // Create a new ligatureSet and add coverage for the first glyph. + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.ligatureSets.splice(pos, 0, [ligatureTable]); + } + }; + + /** + * List all feature data for a given script and language. + * @param {string} feature - 4-letter feature name + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @return {Array} substitutions - The list of substitutions. + */ + Substitution.prototype.getFeature = function(feature, script, language) { + if (/ss\d\d/.test(feature)) { + // ss01 - ss20 + return this.getSingle(feature, script, language); + } + switch (feature) { + case 'aalt': + case 'salt': + return this.getSingle(feature, script, language) + .concat(this.getAlternates(feature, script, language)); + case 'dlig': + case 'liga': + case 'rlig': return this.getLigatures(feature, script, language); + } + return undefined; + }; + + /** + * Add a substitution to a feature for a given script and language. + * @param {string} feature - 4-letter feature name + * @param {Object} sub - the substitution to add (an object like { sub: id or [ids], by: id or [ids] }) + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ + Substitution.prototype.add = function(feature, sub, script, language) { + if (/ss\d\d/.test(feature)) { + // ss01 - ss20 + return this.addSingle(feature, sub, script, language); + } + switch (feature) { + case 'aalt': + case 'salt': + if (typeof sub.by === 'number') { + return this.addSingle(feature, sub, script, language); + } + return this.addAlternate(feature, sub, script, language); + case 'dlig': + case 'liga': + case 'rlig': + return this.addLigature(feature, sub, script, language); + } + return undefined; + }; + + function isBrowser() { + return typeof window !== 'undefined'; + } + + function nodeBufferToArrayBuffer(buffer) { + var ab = new ArrayBuffer(buffer.length); + var view = new Uint8Array(ab); + for (var i = 0; i < buffer.length; ++i) { + view[i] = buffer[i]; + } + + return ab; + } + + function arrayBufferToNodeBuffer(ab) { + var buffer = new Buffer(ab.byteLength); + var view = new Uint8Array(ab); + for (var i = 0; i < buffer.length; ++i) { + buffer[i] = view[i]; + } + + return buffer; + } + + function checkArgument(expression, message) { + if (!expression) { + throw message; + } + } + + // The `glyf` table describes the glyphs in TrueType outline format. + + // Parse the coordinate data for a glyph. + function parseGlyphCoordinate(p, flag, previousValue, shortVectorBitMask, sameBitMask) { + var v; + if ((flag & shortVectorBitMask) > 0) { + // The coordinate is 1 byte long. + v = p.parseByte(); + // The `same` bit is re-used for short values to signify the sign of the value. + if ((flag & sameBitMask) === 0) { + v = -v; + } + + v = previousValue + v; + } else { + // The coordinate is 2 bytes long. + // If the `same` bit is set, the coordinate is the same as the previous coordinate. + if ((flag & sameBitMask) > 0) { + v = previousValue; + } else { + // Parse the coordinate as a signed 16-bit delta value. + v = previousValue + p.parseShort(); + } + } + + return v; + } + + // Parse a TrueType glyph. + function parseGlyph(glyph, data, start) { + var p = new parse.Parser(data, start); + glyph.numberOfContours = p.parseShort(); + glyph._xMin = p.parseShort(); + glyph._yMin = p.parseShort(); + glyph._xMax = p.parseShort(); + glyph._yMax = p.parseShort(); + var flags; + var flag; + + if (glyph.numberOfContours > 0) { + // This glyph is not a composite. + var endPointIndices = glyph.endPointIndices = []; + for (var i = 0; i < glyph.numberOfContours; i += 1) { + endPointIndices.push(p.parseUShort()); + } + + glyph.instructionLength = p.parseUShort(); + glyph.instructions = []; + for (var i$1 = 0; i$1 < glyph.instructionLength; i$1 += 1) { + glyph.instructions.push(p.parseByte()); + } + + var numberOfCoordinates = endPointIndices[endPointIndices.length - 1] + 1; + flags = []; + for (var i$2 = 0; i$2 < numberOfCoordinates; i$2 += 1) { + flag = p.parseByte(); + flags.push(flag); + // If bit 3 is set, we repeat this flag n times, where n is the next byte. + if ((flag & 8) > 0) { + var repeatCount = p.parseByte(); + for (var j = 0; j < repeatCount; j += 1) { + flags.push(flag); + i$2 += 1; + } + } + } + + check.argument(flags.length === numberOfCoordinates, 'Bad flags.'); + + if (endPointIndices.length > 0) { + var points = []; + var point; + // X/Y coordinates are relative to the previous point, except for the first point which is relative to 0,0. + if (numberOfCoordinates > 0) { + for (var i$3 = 0; i$3 < numberOfCoordinates; i$3 += 1) { + flag = flags[i$3]; + point = {}; + point.onCurve = !!(flag & 1); + point.lastPointOfContour = endPointIndices.indexOf(i$3) >= 0; + points.push(point); + } + + var px = 0; + for (var i$4 = 0; i$4 < numberOfCoordinates; i$4 += 1) { + flag = flags[i$4]; + point = points[i$4]; + point.x = parseGlyphCoordinate(p, flag, px, 2, 16); + px = point.x; + } + + var py = 0; + for (var i$5 = 0; i$5 < numberOfCoordinates; i$5 += 1) { + flag = flags[i$5]; + point = points[i$5]; + point.y = parseGlyphCoordinate(p, flag, py, 4, 32); + py = point.y; + } + } + + glyph.points = points; + } else { + glyph.points = []; + } + } else if (glyph.numberOfContours === 0) { + glyph.points = []; + } else { + glyph.isComposite = true; + glyph.points = []; + glyph.components = []; + var moreComponents = true; + while (moreComponents) { + flags = p.parseUShort(); + var component = { + glyphIndex: p.parseUShort(), + xScale: 1, + scale01: 0, + scale10: 0, + yScale: 1, + dx: 0, + dy: 0 + }; + if ((flags & 1) > 0) { + // The arguments are words + if ((flags & 2) > 0) { + // values are offset + component.dx = p.parseShort(); + component.dy = p.parseShort(); + } else { + // values are matched points + component.matchedPoints = [p.parseUShort(), p.parseUShort()]; + } + + } else { + // The arguments are bytes + if ((flags & 2) > 0) { + // values are offset + component.dx = p.parseChar(); + component.dy = p.parseChar(); + } else { + // values are matched points + component.matchedPoints = [p.parseByte(), p.parseByte()]; + } + } + + if ((flags & 8) > 0) { + // We have a scale + component.xScale = component.yScale = p.parseF2Dot14(); + } else if ((flags & 64) > 0) { + // We have an X / Y scale + component.xScale = p.parseF2Dot14(); + component.yScale = p.parseF2Dot14(); + } else if ((flags & 128) > 0) { + // We have a 2x2 transformation + component.xScale = p.parseF2Dot14(); + component.scale01 = p.parseF2Dot14(); + component.scale10 = p.parseF2Dot14(); + component.yScale = p.parseF2Dot14(); + } + + glyph.components.push(component); + moreComponents = !!(flags & 32); + } + if (flags & 0x100) { + // We have instructions + glyph.instructionLength = p.parseUShort(); + glyph.instructions = []; + for (var i$6 = 0; i$6 < glyph.instructionLength; i$6 += 1) { + glyph.instructions.push(p.parseByte()); + } + } + } + } + + // Transform an array of points and return a new array. + function transformPoints(points, transform) { + var newPoints = []; + for (var i = 0; i < points.length; i += 1) { + var pt = points[i]; + var newPt = { + x: transform.xScale * pt.x + transform.scale01 * pt.y + transform.dx, + y: transform.scale10 * pt.x + transform.yScale * pt.y + transform.dy, + onCurve: pt.onCurve, + lastPointOfContour: pt.lastPointOfContour + }; + newPoints.push(newPt); + } + + return newPoints; + } + + function getContours(points) { + var contours = []; + var currentContour = []; + for (var i = 0; i < points.length; i += 1) { + var pt = points[i]; + currentContour.push(pt); + if (pt.lastPointOfContour) { + contours.push(currentContour); + currentContour = []; + } + } + + check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); + return contours; + } + + // Convert the TrueType glyph outline to a Path. + function getPath(points) { + var p = new Path(); + if (!points) { + return p; + } + + var contours = getContours(points); + + for (var contourIndex = 0; contourIndex < contours.length; ++contourIndex) { + var contour = contours[contourIndex]; + + var prev = null; + var curr = contour[contour.length - 1]; + var next = contour[0]; + + if (curr.onCurve) { + p.moveTo(curr.x, curr.y); + } else { + if (next.onCurve) { + p.moveTo(next.x, next.y); + } else { + // If both first and last points are off-curve, start at their middle. + var start = {x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5}; + p.moveTo(start.x, start.y); + } + } + + for (var i = 0; i < contour.length; ++i) { + prev = curr; + curr = next; + next = contour[(i + 1) % contour.length]; + + if (curr.onCurve) { + // This is a straight line. + p.lineTo(curr.x, curr.y); + } else { + var prev2 = prev; + var next2 = next; + + if (!prev.onCurve) { + prev2 = { x: (curr.x + prev.x) * 0.5, y: (curr.y + prev.y) * 0.5 }; + } + + if (!next.onCurve) { + next2 = { x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5 }; + } + + p.quadraticCurveTo(curr.x, curr.y, next2.x, next2.y); + } + } + + p.closePath(); + } + return p; + } + + function buildPath(glyphs, glyph) { + if (glyph.isComposite) { + for (var j = 0; j < glyph.components.length; j += 1) { + var component = glyph.components[j]; + var componentGlyph = glyphs.get(component.glyphIndex); + // Force the ttfGlyphLoader to parse the glyph. + componentGlyph.getPath(); + if (componentGlyph.points) { + var transformedPoints = (void 0); + if (component.matchedPoints === undefined) { + // component positioned by offset + transformedPoints = transformPoints(componentGlyph.points, component); + } else { + // component positioned by matched points + if ((component.matchedPoints[0] > glyph.points.length - 1) || + (component.matchedPoints[1] > componentGlyph.points.length - 1)) { + throw Error('Matched points out of range in ' + glyph.name); + } + var firstPt = glyph.points[component.matchedPoints[0]]; + var secondPt = componentGlyph.points[component.matchedPoints[1]]; + var transform = { + xScale: component.xScale, scale01: component.scale01, + scale10: component.scale10, yScale: component.yScale, + dx: 0, dy: 0 + }; + secondPt = transformPoints([secondPt], transform)[0]; + transform.dx = firstPt.x - secondPt.x; + transform.dy = firstPt.y - secondPt.y; + transformedPoints = transformPoints(componentGlyph.points, transform); + } + glyph.points = glyph.points.concat(transformedPoints); + } + } + } + + return getPath(glyph.points); + } + + function parseGlyfTableAll(data, start, loca, font) { + var glyphs = new glyphset.GlyphSet(font); + + // The last element of the loca table is invalid. + for (var i = 0; i < loca.length - 1; i += 1) { + var offset = loca[i]; + var nextOffset = loca[i + 1]; + if (offset !== nextOffset) { + glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); + } else { + glyphs.push(i, glyphset.glyphLoader(font, i)); + } + } + + return glyphs; + } + + function parseGlyfTableOnLowMemory(data, start, loca, font) { + var glyphs = new glyphset.GlyphSet(font); + + font._push = function(i) { + var offset = loca[i]; + var nextOffset = loca[i + 1]; + if (offset !== nextOffset) { + glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); + } else { + glyphs.push(i, glyphset.glyphLoader(font, i)); + } + }; + + return glyphs; + } + + // Parse all the glyphs according to the offsets from the `loca` table. + function parseGlyfTable(data, start, loca, font, opt) { + if (opt.lowMemory) + { return parseGlyfTableOnLowMemory(data, start, loca, font); } + else + { return parseGlyfTableAll(data, start, loca, font); } + } + + var glyf = { getPath: getPath, parse: parseGlyfTable}; + + /* A TrueType font hinting interpreter. + * + * (c) 2017 Axel Kittenberger + * + * This interpreter has been implemented according to this documentation: + * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM05/Chap5.html + * + * According to the documentation F24DOT6 values are used for pixels. + * That means calculation is 1/64 pixel accurate and uses integer operations. + * However, Javascript has floating point operations by default and only + * those are available. One could make a case to simulate the 1/64 accuracy + * exactly by truncating after every division operation + * (for example with << 0) to get pixel exactly results as other TrueType + * implementations. It may make sense since some fonts are pixel optimized + * by hand using DELTAP instructions. The current implementation doesn't + * and rather uses full floating point precision. + * + * xScale, yScale and rotation is currently ignored. + * + * A few non-trivial instructions are missing as I didn't encounter yet + * a font that used them to test a possible implementation. + * + * Some fonts seem to use undocumented features regarding the twilight zone. + * Only some of them are implemented as they were encountered. + * + * The exports.DEBUG statements are removed on the minified distribution file. + */ + + var instructionTable; + var exec; + var execGlyph; + var execComponent; + + /* + * Creates a hinting object. + * + * There ought to be exactly one + * for each truetype font that is used for hinting. + */ + function Hinting(font) { + // the font this hinting object is for + this.font = font; + + this.getCommands = function (hPoints) { + return glyf.getPath(hPoints).commands; + }; + + // cached states + this._fpgmState = + this._prepState = + undefined; + + // errorState + // 0 ... all okay + // 1 ... had an error in a glyf, + // continue working but stop spamming + // the console + // 2 ... error at prep, stop hinting at this ppem + // 3 ... error at fpeg, stop hinting for this font at all + this._errorState = 0; + } + + /* + * Not rounding. + */ + function roundOff(v) { + return v; + } + + /* + * Rounding to grid. + */ + function roundToGrid(v) { + //Rounding in TT is supposed to "symmetrical around zero" + return Math.sign(v) * Math.round(Math.abs(v)); + } + + /* + * Rounding to double grid. + */ + function roundToDoubleGrid(v) { + return Math.sign(v) * Math.round(Math.abs(v * 2)) / 2; + } + + /* + * Rounding to half grid. + */ + function roundToHalfGrid(v) { + return Math.sign(v) * (Math.round(Math.abs(v) + 0.5) - 0.5); + } + + /* + * Rounding to up to grid. + */ + function roundUpToGrid(v) { + return Math.sign(v) * Math.ceil(Math.abs(v)); + } + + /* + * Rounding to down to grid. + */ + function roundDownToGrid(v) { + return Math.sign(v) * Math.floor(Math.abs(v)); + } + + /* + * Super rounding. + */ + var roundSuper = function (v) { + var period = this.srPeriod; + var phase = this.srPhase; + var threshold = this.srThreshold; + var sign = 1; + + if (v < 0) { + v = -v; + sign = -1; + } + + v += threshold - phase; + + v = Math.trunc(v / period) * period; + + v += phase; + + // according to http://xgridfit.sourceforge.net/round.html + if (v < 0) { return phase * sign; } + + return v * sign; + }; + + /* + * Unit vector of x-axis. + */ + var xUnitVector = { + x: 1, + + y: 0, + + axis: 'x', + + // Gets the projected distance between two points. + // o1/o2 ... if true, respective original position is used. + distance: function (p1, p2, o1, o2) { + return (o1 ? p1.xo : p1.x) - (o2 ? p2.xo : p2.x); + }, + + // Moves point p so the moved position has the same relative + // position to the moved positions of rp1 and rp2 than the + // original positions had. + // + // See APPENDIX on INTERPOLATE at the bottom of this file. + interpolate: function (p, rp1, rp2, pv) { + var do1; + var do2; + var doa1; + var doa2; + var dm1; + var dm2; + var dt; + + if (!pv || pv === this) { + do1 = p.xo - rp1.xo; + do2 = p.xo - rp2.xo; + dm1 = rp1.x - rp1.xo; + dm2 = rp2.x - rp2.xo; + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + p.x = p.xo + (dm1 + dm2) / 2; + return; + } + + p.x = p.xo + (dm1 * doa2 + dm2 * doa1) / dt; + return; + } + + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + xUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } + + xUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); + }, + + // Slope of line normal to this + normalSlope: Number.NEGATIVE_INFINITY, + + // Sets the point 'p' relative to point 'rp' + // by the distance 'd'. + // + // See APPENDIX on SETRELATIVE at the bottom of this file. + // + // p ... point to set + // rp ... reference point + // d ... distance on projection vector + // pv ... projection vector (undefined = this) + // org ... if true, uses the original position of rp as reference. + setRelative: function (p, rp, d, pv, org) { + if (!pv || pv === this) { + p.x = (org ? rp.xo : rp.x) + d; + return; + } + + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d * pv.x; + var rpdy = rpy + d * pv.y; + + p.x = rpdx + (p.y - rpdy) / pv.normalSlope; + }, + + // Slope of vector line. + slope: 0, + + // Touches the point p. + touch: function (p) { + p.xTouched = true; + }, + + // Tests if a point p is touched. + touched: function (p) { + return p.xTouched; + }, + + // Untouches the point p. + untouch: function (p) { + p.xTouched = false; + } + }; + + /* + * Unit vector of y-axis. + */ + var yUnitVector = { + x: 0, + + y: 1, + + axis: 'y', + + // Gets the projected distance between two points. + // o1/o2 ... if true, respective original position is used. + distance: function (p1, p2, o1, o2) { + return (o1 ? p1.yo : p1.y) - (o2 ? p2.yo : p2.y); + }, + + // Moves point p so the moved position has the same relative + // position to the moved positions of rp1 and rp2 than the + // original positions had. + // + // See APPENDIX on INTERPOLATE at the bottom of this file. + interpolate: function (p, rp1, rp2, pv) { + var do1; + var do2; + var doa1; + var doa2; + var dm1; + var dm2; + var dt; + + if (!pv || pv === this) { + do1 = p.yo - rp1.yo; + do2 = p.yo - rp2.yo; + dm1 = rp1.y - rp1.yo; + dm2 = rp2.y - rp2.yo; + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + p.y = p.yo + (dm1 + dm2) / 2; + return; + } + + p.y = p.yo + (dm1 * doa2 + dm2 * doa1) / dt; + return; + } + + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + yUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } + + yUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); + }, + + // Slope of line normal to this. + normalSlope: 0, + + // Sets the point 'p' relative to point 'rp' + // by the distance 'd' + // + // See APPENDIX on SETRELATIVE at the bottom of this file. + // + // p ... point to set + // rp ... reference point + // d ... distance on projection vector + // pv ... projection vector (undefined = this) + // org ... if true, uses the original position of rp as reference. + setRelative: function (p, rp, d, pv, org) { + if (!pv || pv === this) { + p.y = (org ? rp.yo : rp.y) + d; + return; + } + + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d * pv.x; + var rpdy = rpy + d * pv.y; + + p.y = rpdy + pv.normalSlope * (p.x - rpdx); + }, + + // Slope of vector line. + slope: Number.POSITIVE_INFINITY, + + // Touches the point p. + touch: function (p) { + p.yTouched = true; + }, + + // Tests if a point p is touched. + touched: function (p) { + return p.yTouched; + }, + + // Untouches the point p. + untouch: function (p) { + p.yTouched = false; + } + }; + + Object.freeze(xUnitVector); + Object.freeze(yUnitVector); + + /* + * Creates a unit vector that is not x- or y-axis. + */ + function UnitVector(x, y) { + this.x = x; + this.y = y; + this.axis = undefined; + this.slope = y / x; + this.normalSlope = -x / y; + Object.freeze(this); + } + + /* + * Gets the projected distance between two points. + * o1/o2 ... if true, respective original position is used. + */ + UnitVector.prototype.distance = function(p1, p2, o1, o2) { + return ( + this.x * xUnitVector.distance(p1, p2, o1, o2) + + this.y * yUnitVector.distance(p1, p2, o1, o2) + ); + }; + + /* + * Moves point p so the moved position has the same relative + * position to the moved positions of rp1 and rp2 than the + * original positions had. + * + * See APPENDIX on INTERPOLATE at the bottom of this file. + */ + UnitVector.prototype.interpolate = function(p, rp1, rp2, pv) { + var dm1; + var dm2; + var do1; + var do2; + var doa1; + var doa2; + var dt; + + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + this.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } + + this.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); + }; + + /* + * Sets the point 'p' relative to point 'rp' + * by the distance 'd' + * + * See APPENDIX on SETRELATIVE at the bottom of this file. + * + * p ... point to set + * rp ... reference point + * d ... distance on projection vector + * pv ... projection vector (undefined = this) + * org ... if true, uses the original position of rp as reference. + */ + UnitVector.prototype.setRelative = function(p, rp, d, pv, org) { + pv = pv || this; + + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d * pv.x; + var rpdy = rpy + d * pv.y; + + var pvns = pv.normalSlope; + var fvs = this.slope; + + var px = p.x; + var py = p.y; + + p.x = (fvs * px - pvns * rpdx + rpdy - py) / (fvs - pvns); + p.y = fvs * (p.x - px) + py; + }; + + /* + * Touches the point p. + */ + UnitVector.prototype.touch = function(p) { + p.xTouched = true; + p.yTouched = true; + }; + + /* + * Returns a unit vector with x/y coordinates. + */ + function getUnitVector(x, y) { + var d = Math.sqrt(x * x + y * y); + + x /= d; + y /= d; + + if (x === 1 && y === 0) { return xUnitVector; } + else if (x === 0 && y === 1) { return yUnitVector; } + else { return new UnitVector(x, y); } + } + + /* + * Creates a point in the hinting engine. + */ + function HPoint( + x, + y, + lastPointOfContour, + onCurve + ) { + this.x = this.xo = Math.round(x * 64) / 64; // hinted x value and original x-value + this.y = this.yo = Math.round(y * 64) / 64; // hinted y value and original y-value + + this.lastPointOfContour = lastPointOfContour; + this.onCurve = onCurve; + this.prevPointOnContour = undefined; + this.nextPointOnContour = undefined; + this.xTouched = false; + this.yTouched = false; + + Object.preventExtensions(this); + } + + /* + * Returns the next touched point on the contour. + * + * v ... unit vector to test touch axis. + */ + HPoint.prototype.nextTouched = function(v) { + var p = this.nextPointOnContour; + + while (!v.touched(p) && p !== this) { p = p.nextPointOnContour; } + + return p; + }; + + /* + * Returns the previous touched point on the contour + * + * v ... unit vector to test touch axis. + */ + HPoint.prototype.prevTouched = function(v) { + var p = this.prevPointOnContour; + + while (!v.touched(p) && p !== this) { p = p.prevPointOnContour; } + + return p; + }; + + /* + * The zero point. + */ + var HPZero = Object.freeze(new HPoint(0, 0)); + + /* + * The default state of the interpreter. + * + * Note: Freezing the defaultState and then deriving from it + * makes the V8 Javascript engine going awkward, + * so this is avoided, albeit the defaultState shouldn't + * ever change. + */ + var defaultState = { + cvCutIn: 17 / 16, // control value cut in + deltaBase: 9, + deltaShift: 0.125, + loop: 1, // loops some instructions + minDis: 1, // minimum distance + autoFlip: true + }; + + /* + * The current state of the interpreter. + * + * env ... 'fpgm' or 'prep' or 'glyf' + * prog ... the program + */ + function State(env, prog) { + this.env = env; + this.stack = []; + this.prog = prog; + + switch (env) { + case 'glyf' : + this.zp0 = this.zp1 = this.zp2 = 1; + this.rp0 = this.rp1 = this.rp2 = 0; + /* fall through */ + case 'prep' : + this.fv = this.pv = this.dpv = xUnitVector; + this.round = roundToGrid; + } + } + + /* + * Executes a glyph program. + * + * This does the hinting for each glyph. + * + * Returns an array of moved points. + * + * glyph: the glyph to hint + * ppem: the size the glyph is rendered for + */ + Hinting.prototype.exec = function(glyph, ppem) { + if (typeof ppem !== 'number') { + throw new Error('Point size is not a number!'); + } + + // Received a fatal error, don't do any hinting anymore. + if (this._errorState > 2) { return; } + + var font = this.font; + var prepState = this._prepState; + + if (!prepState || prepState.ppem !== ppem) { + var fpgmState = this._fpgmState; + + if (!fpgmState) { + // Executes the fpgm state. + // This is used by fonts to define functions. + State.prototype = defaultState; + + fpgmState = + this._fpgmState = + new State('fpgm', font.tables.fpgm); + + fpgmState.funcs = [ ]; + fpgmState.font = font; + + if (exports.DEBUG) { + console.log('---EXEC FPGM---'); + fpgmState.step = -1; + } + + try { + exec(fpgmState); + } catch (e) { + console.log('Hinting error in FPGM:' + e); + this._errorState = 3; + return; + } + } + + // Executes the prep program for this ppem setting. + // This is used by fonts to set cvt values + // depending on to be rendered font size. + + State.prototype = fpgmState; + prepState = + this._prepState = + new State('prep', font.tables.prep); + + prepState.ppem = ppem; + + // Creates a copy of the cvt table + // and scales it to the current ppem setting. + var oCvt = font.tables.cvt; + if (oCvt) { + var cvt = prepState.cvt = new Array(oCvt.length); + var scale = ppem / font.unitsPerEm; + for (var c = 0; c < oCvt.length; c++) { + cvt[c] = oCvt[c] * scale; + } + } else { + prepState.cvt = []; + } + + if (exports.DEBUG) { + console.log('---EXEC PREP---'); + prepState.step = -1; + } + + try { + exec(prepState); + } catch (e) { + if (this._errorState < 2) { + console.log('Hinting error in PREP:' + e); + } + this._errorState = 2; + } + } + + if (this._errorState > 1) { return; } + + try { + return execGlyph(glyph, prepState); + } catch (e) { + if (this._errorState < 1) { + console.log('Hinting error:' + e); + console.log('Note: further hinting errors are silenced'); + } + this._errorState = 1; + return undefined; + } + }; + + /* + * Executes the hinting program for a glyph. + */ + execGlyph = function(glyph, prepState) { + // original point positions + var xScale = prepState.ppem / prepState.font.unitsPerEm; + var yScale = xScale; + var components = glyph.components; + var contours; + var gZone; + var state; + + State.prototype = prepState; + if (!components) { + state = new State('glyf', glyph.instructions); + if (exports.DEBUG) { + console.log('---EXEC GLYPH---'); + state.step = -1; + } + execComponent(glyph, state, xScale, yScale); + gZone = state.gZone; + } else { + var font = prepState.font; + gZone = []; + contours = []; + for (var i = 0; i < components.length; i++) { + var c = components[i]; + var cg = font.glyphs.get(c.glyphIndex); + + state = new State('glyf', cg.instructions); + + if (exports.DEBUG) { + console.log('---EXEC COMP ' + i + '---'); + state.step = -1; + } + + execComponent(cg, state, xScale, yScale); + // appends the computed points to the result array + // post processes the component points + var dx = Math.round(c.dx * xScale); + var dy = Math.round(c.dy * yScale); + var gz = state.gZone; + var cc = state.contours; + for (var pi = 0; pi < gz.length; pi++) { + var p = gz[pi]; + p.xTouched = p.yTouched = false; + p.xo = p.x = p.x + dx; + p.yo = p.y = p.y + dy; + } + + var gLen = gZone.length; + gZone.push.apply(gZone, gz); + for (var j = 0; j < cc.length; j++) { + contours.push(cc[j] + gLen); + } + } + + if (glyph.instructions && !state.inhibitGridFit) { + // the composite has instructions on its own + state = new State('glyf', glyph.instructions); + + state.gZone = state.z0 = state.z1 = state.z2 = gZone; + + state.contours = contours; + + // note: HPZero cannot be used here, since + // the point might be modified + gZone.push( + new HPoint(0, 0), + new HPoint(Math.round(glyph.advanceWidth * xScale), 0) + ); + + if (exports.DEBUG) { + console.log('---EXEC COMPOSITE---'); + state.step = -1; + } + + exec(state); + + gZone.length -= 2; + } + } + + return gZone; + }; + + /* + * Executes the hinting program for a component of a multi-component glyph + * or of the glyph itself for a non-component glyph. + */ + execComponent = function(glyph, state, xScale, yScale) + { + var points = glyph.points || []; + var pLen = points.length; + var gZone = state.gZone = state.z0 = state.z1 = state.z2 = []; + var contours = state.contours = []; + + // Scales the original points and + // makes copies for the hinted points. + var cp; // current point + for (var i = 0; i < pLen; i++) { + cp = points[i]; + + gZone[i] = new HPoint( + cp.x * xScale, + cp.y * yScale, + cp.lastPointOfContour, + cp.onCurve + ); + } + + // Chain links the contours. + var sp; // start point + var np; // next point + + for (var i$1 = 0; i$1 < pLen; i$1++) { + cp = gZone[i$1]; + + if (!sp) { + sp = cp; + contours.push(i$1); + } + + if (cp.lastPointOfContour) { + cp.nextPointOnContour = sp; + sp.prevPointOnContour = cp; + sp = undefined; + } else { + np = gZone[i$1 + 1]; + cp.nextPointOnContour = np; + np.prevPointOnContour = cp; + } + } + + if (state.inhibitGridFit) { return; } + + if (exports.DEBUG) { + console.log('PROCESSING GLYPH', state.stack); + for (var i$2 = 0; i$2 < pLen; i$2++) { + console.log(i$2, gZone[i$2].x, gZone[i$2].y); + } + } + + gZone.push( + new HPoint(0, 0), + new HPoint(Math.round(glyph.advanceWidth * xScale), 0) + ); + + exec(state); + + // Removes the extra points. + gZone.length -= 2; + + if (exports.DEBUG) { + console.log('FINISHED GLYPH', state.stack); + for (var i$3 = 0; i$3 < pLen; i$3++) { + console.log(i$3, gZone[i$3].x, gZone[i$3].y); + } + } + }; + + /* + * Executes the program loaded in state. + */ + exec = function(state) { + var prog = state.prog; + + if (!prog) { return; } + + var pLen = prog.length; + var ins; + + for (state.ip = 0; state.ip < pLen; state.ip++) { + if (exports.DEBUG) { state.step++; } + ins = instructionTable[prog[state.ip]]; + + if (!ins) { + throw new Error( + 'unknown instruction: 0x' + + Number(prog[state.ip]).toString(16) + ); + } + + ins(state); + + // very extensive debugging for each step + /* + if (exports.DEBUG) { + var da; + if (state.gZone) { + da = []; + for (let i = 0; i < state.gZone.length; i++) + { + da.push(i + ' ' + + state.gZone[i].x * 64 + ' ' + + state.gZone[i].y * 64 + ' ' + + (state.gZone[i].xTouched ? 'x' : '') + + (state.gZone[i].yTouched ? 'y' : '') + ); + } + console.log('GZ', da); + } + + if (state.tZone) { + da = []; + for (let i = 0; i < state.tZone.length; i++) { + da.push(i + ' ' + + state.tZone[i].x * 64 + ' ' + + state.tZone[i].y * 64 + ' ' + + (state.tZone[i].xTouched ? 'x' : '') + + (state.tZone[i].yTouched ? 'y' : '') + ); + } + console.log('TZ', da); + } + + if (state.stack.length > 10) { + console.log( + state.stack.length, + '...', state.stack.slice(state.stack.length - 10) + ); + } else { + console.log(state.stack.length, state.stack); + } + } + */ + } + }; + + /* + * Initializes the twilight zone. + * + * This is only done if a SZPx instruction + * refers to the twilight zone. + */ + function initTZone(state) + { + var tZone = state.tZone = new Array(state.gZone.length); + + // no idea if this is actually correct... + for (var i = 0; i < tZone.length; i++) + { + tZone[i] = new HPoint(0, 0); + } + } + + /* + * Skips the instruction pointer ahead over an IF/ELSE block. + * handleElse .. if true breaks on matching ELSE + */ + function skip(state, handleElse) + { + var prog = state.prog; + var ip = state.ip; + var nesting = 1; + var ins; + + do { + ins = prog[++ip]; + if (ins === 0x58) // IF + { nesting++; } + else if (ins === 0x59) // EIF + { nesting--; } + else if (ins === 0x40) // NPUSHB + { ip += prog[ip + 1] + 1; } + else if (ins === 0x41) // NPUSHW + { ip += 2 * prog[ip + 1] + 1; } + else if (ins >= 0xB0 && ins <= 0xB7) // PUSHB + { ip += ins - 0xB0 + 1; } + else if (ins >= 0xB8 && ins <= 0xBF) // PUSHW + { ip += (ins - 0xB8 + 1) * 2; } + else if (handleElse && nesting === 1 && ins === 0x1B) // ELSE + { break; } + } while (nesting > 0); + + state.ip = ip; + } + + /*----------------------------------------------------------* + * And then a lot of instructions... * + *----------------------------------------------------------*/ + + // SVTCA[a] Set freedom and projection Vectors To Coordinate Axis + // 0x00-0x01 + function SVTCA(v, state) { + if (exports.DEBUG) { console.log(state.step, 'SVTCA[' + v.axis + ']'); } + + state.fv = state.pv = state.dpv = v; + } + + // SPVTCA[a] Set Projection Vector to Coordinate Axis + // 0x02-0x03 + function SPVTCA(v, state) { + if (exports.DEBUG) { console.log(state.step, 'SPVTCA[' + v.axis + ']'); } + + state.pv = state.dpv = v; + } + + // SFVTCA[a] Set Freedom Vector to Coordinate Axis + // 0x04-0x05 + function SFVTCA(v, state) { + if (exports.DEBUG) { console.log(state.step, 'SFVTCA[' + v.axis + ']'); } + + state.fv = v; + } + + // SPVTL[a] Set Projection Vector To Line + // 0x06-0x07 + function SPVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; + + if (exports.DEBUG) { console.log('SPVTL[' + a + ']', p2i, p1i); } + + var dx; + var dy; + + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } + + state.pv = state.dpv = getUnitVector(dx, dy); + } + + // SFVTL[a] Set Freedom Vector To Line + // 0x08-0x09 + function SFVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; + + if (exports.DEBUG) { console.log('SFVTL[' + a + ']', p2i, p1i); } + + var dx; + var dy; + + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } + + state.fv = getUnitVector(dx, dy); + } + + // SPVFS[] Set Projection Vector From Stack + // 0x0A + function SPVFS(state) { + var stack = state.stack; + var y = stack.pop(); + var x = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } + + state.pv = state.dpv = getUnitVector(x, y); + } + + // SFVFS[] Set Freedom Vector From Stack + // 0x0B + function SFVFS(state) { + var stack = state.stack; + var y = stack.pop(); + var x = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } + + state.fv = getUnitVector(x, y); + } + + // GPV[] Get Projection Vector + // 0x0C + function GPV(state) { + var stack = state.stack; + var pv = state.pv; + + if (exports.DEBUG) { console.log(state.step, 'GPV[]'); } + + stack.push(pv.x * 0x4000); + stack.push(pv.y * 0x4000); + } + + // GFV[] Get Freedom Vector + // 0x0C + function GFV(state) { + var stack = state.stack; + var fv = state.fv; + + if (exports.DEBUG) { console.log(state.step, 'GFV[]'); } + + stack.push(fv.x * 0x4000); + stack.push(fv.y * 0x4000); + } + + // SFVTPV[] Set Freedom Vector To Projection Vector + // 0x0E + function SFVTPV(state) { + state.fv = state.pv; + + if (exports.DEBUG) { console.log(state.step, 'SFVTPV[]'); } + } + + // ISECT[] moves point p to the InterSECTion of two lines + // 0x0F + function ISECT(state) + { + var stack = state.stack; + var pa0i = stack.pop(); + var pa1i = stack.pop(); + var pb0i = stack.pop(); + var pb1i = stack.pop(); + var pi = stack.pop(); + var z0 = state.z0; + var z1 = state.z1; + var pa0 = z0[pa0i]; + var pa1 = z0[pa1i]; + var pb0 = z1[pb0i]; + var pb1 = z1[pb1i]; + var p = state.z2[pi]; + + if (exports.DEBUG) { console.log('ISECT[], ', pa0i, pa1i, pb0i, pb1i, pi); } + + // math from + // en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line + + var x1 = pa0.x; + var y1 = pa0.y; + var x2 = pa1.x; + var y2 = pa1.y; + var x3 = pb0.x; + var y3 = pb0.y; + var x4 = pb1.x; + var y4 = pb1.y; + + var div = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); + var f1 = x1 * y2 - y1 * x2; + var f2 = x3 * y4 - y3 * x4; + + p.x = (f1 * (x3 - x4) - f2 * (x1 - x2)) / div; + p.y = (f1 * (y3 - y4) - f2 * (y1 - y2)) / div; + } + + // SRP0[] Set Reference Point 0 + // 0x10 + function SRP0(state) { + state.rp0 = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SRP0[]', state.rp0); } + } + + // SRP1[] Set Reference Point 1 + // 0x11 + function SRP1(state) { + state.rp1 = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SRP1[]', state.rp1); } + } + + // SRP1[] Set Reference Point 2 + // 0x12 + function SRP2(state) { + state.rp2 = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SRP2[]', state.rp2); } + } + + // SZP0[] Set Zone Pointer 0 + // 0x13 + function SZP0(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SZP0[]', n); } + + state.zp0 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z0 = state.tZone; + break; + case 1 : + state.z0 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } + } + + // SZP1[] Set Zone Pointer 1 + // 0x14 + function SZP1(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SZP1[]', n); } + + state.zp1 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z1 = state.tZone; + break; + case 1 : + state.z1 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } + } + + // SZP2[] Set Zone Pointer 2 + // 0x15 + function SZP2(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SZP2[]', n); } + + state.zp2 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z2 = state.tZone; + break; + case 1 : + state.z2 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } + } + + // SZPS[] Set Zone PointerS + // 0x16 + function SZPS(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SZPS[]', n); } + + state.zp0 = state.zp1 = state.zp2 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z0 = state.z1 = state.z2 = state.tZone; + break; + case 1 : + state.z0 = state.z1 = state.z2 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } + } + + // SLOOP[] Set LOOP variable + // 0x17 + function SLOOP(state) { + state.loop = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SLOOP[]', state.loop); } + } + + // RTG[] Round To Grid + // 0x18 + function RTG(state) { + if (exports.DEBUG) { console.log(state.step, 'RTG[]'); } + + state.round = roundToGrid; + } + + // RTHG[] Round To Half Grid + // 0x19 + function RTHG(state) { + if (exports.DEBUG) { console.log(state.step, 'RTHG[]'); } + + state.round = roundToHalfGrid; + } + + // SMD[] Set Minimum Distance + // 0x1A + function SMD(state) { + var d = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SMD[]', d); } + + state.minDis = d / 0x40; + } + + // ELSE[] ELSE clause + // 0x1B + function ELSE(state) { + // This instruction has been reached by executing a then branch + // so it just skips ahead until matching EIF. + // + // In case the IF was negative the IF[] instruction already + // skipped forward over the ELSE[] + + if (exports.DEBUG) { console.log(state.step, 'ELSE[]'); } + + skip(state, false); + } + + // JMPR[] JuMP Relative + // 0x1C + function JMPR(state) { + var o = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'JMPR[]', o); } + + // A jump by 1 would do nothing. + state.ip += o - 1; + } + + // SCVTCI[] Set Control Value Table Cut-In + // 0x1D + function SCVTCI(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SCVTCI[]', n); } + + state.cvCutIn = n / 0x40; + } + + // DUP[] DUPlicate top stack element + // 0x20 + function DUP(state) { + var stack = state.stack; + + if (exports.DEBUG) { console.log(state.step, 'DUP[]'); } + + stack.push(stack[stack.length - 1]); + } + + // POP[] POP top stack element + // 0x21 + function POP(state) { + if (exports.DEBUG) { console.log(state.step, 'POP[]'); } + + state.stack.pop(); + } + + // CLEAR[] CLEAR the stack + // 0x22 + function CLEAR(state) { + if (exports.DEBUG) { console.log(state.step, 'CLEAR[]'); } + + state.stack.length = 0; + } + + // SWAP[] SWAP the top two elements on the stack + // 0x23 + function SWAP(state) { + var stack = state.stack; + + var a = stack.pop(); + var b = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SWAP[]'); } + + stack.push(a); + stack.push(b); + } + + // DEPTH[] DEPTH of the stack + // 0x24 + function DEPTH(state) { + var stack = state.stack; + + if (exports.DEBUG) { console.log(state.step, 'DEPTH[]'); } + + stack.push(stack.length); + } + + // LOOPCALL[] LOOPCALL function + // 0x2A + function LOOPCALL(state) { + var stack = state.stack; + var fn = stack.pop(); + var c = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'LOOPCALL[]', fn, c); } + + // saves callers program + var cip = state.ip; + var cprog = state.prog; + + state.prog = state.funcs[fn]; + + // executes the function + for (var i = 0; i < c; i++) { + exec(state); + + if (exports.DEBUG) { console.log( + ++state.step, + i + 1 < c ? 'next loopcall' : 'done loopcall', + i + ); } + } + + // restores the callers program + state.ip = cip; + state.prog = cprog; + } + + // CALL[] CALL function + // 0x2B + function CALL(state) { + var fn = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'CALL[]', fn); } + + // saves callers program + var cip = state.ip; + var cprog = state.prog; + + state.prog = state.funcs[fn]; + + // executes the function + exec(state); + + // restores the callers program + state.ip = cip; + state.prog = cprog; + + if (exports.DEBUG) { console.log(++state.step, 'returning from', fn); } + } + + // CINDEX[] Copy the INDEXed element to the top of the stack + // 0x25 + function CINDEX(state) { + var stack = state.stack; + var k = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'CINDEX[]', k); } + + // In case of k == 1, it copies the last element after popping + // thus stack.length - k. + stack.push(stack[stack.length - k]); + } + + // MINDEX[] Move the INDEXed element to the top of the stack + // 0x26 + function MINDEX(state) { + var stack = state.stack; + var k = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MINDEX[]', k); } + + stack.push(stack.splice(stack.length - k, 1)[0]); + } + + // FDEF[] Function DEFinition + // 0x2C + function FDEF(state) { + if (state.env !== 'fpgm') { throw new Error('FDEF not allowed here'); } + var stack = state.stack; + var prog = state.prog; + var ip = state.ip; + + var fn = stack.pop(); + var ipBegin = ip; + + if (exports.DEBUG) { console.log(state.step, 'FDEF[]', fn); } + + while (prog[++ip] !== 0x2D){ } + + state.ip = ip; + state.funcs[fn] = prog.slice(ipBegin + 1, ip); + } + + // MDAP[a] Move Direct Absolute Point + // 0x2E-0x2F + function MDAP(round, state) { + var pi = state.stack.pop(); + var p = state.z0[pi]; + var fv = state.fv; + var pv = state.pv; + + if (exports.DEBUG) { console.log(state.step, 'MDAP[' + round + ']', pi); } + + var d = pv.distance(p, HPZero); + + if (round) { d = state.round(d); } + + fv.setRelative(p, HPZero, d, pv); + fv.touch(p); + + state.rp0 = state.rp1 = pi; + } + + // IUP[a] Interpolate Untouched Points through the outline + // 0x30 + function IUP(v, state) { + var z2 = state.z2; + var pLen = z2.length - 2; + var cp; + var pp; + var np; + + if (exports.DEBUG) { console.log(state.step, 'IUP[' + v.axis + ']'); } + + for (var i = 0; i < pLen; i++) { + cp = z2[i]; // current point + + // if this point has been touched go on + if (v.touched(cp)) { continue; } + + pp = cp.prevTouched(v); + + // no point on the contour has been touched? + if (pp === cp) { continue; } + + np = cp.nextTouched(v); + + if (pp === np) { + // only one point on the contour has been touched + // so simply moves the point like that + + v.setRelative(cp, cp, v.distance(pp, pp, false, true), v, true); + } + + v.interpolate(cp, pp, np, v); + } + } + + // SHP[] SHift Point using reference point + // 0x32-0x33 + function SHP(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + var loop = state.loop; + var z2 = state.z2; + + while (loop--) + { + var pi = stack.pop(); + var p = z2[pi]; + + var d = pv.distance(rp, rp, false, true); + fv.setRelative(p, p, d, pv); + fv.touch(p); + + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? + 'loop ' + (state.loop - loop) + ': ' : + '' + ) + + 'SHP[' + (a ? 'rp1' : 'rp2') + ']', pi + ); + } + } + + state.loop = 1; + } + + // SHC[] SHift Contour using reference point + // 0x36-0x37 + function SHC(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + var ci = stack.pop(); + var sp = state.z2[state.contours[ci]]; + var p = sp; + + if (exports.DEBUG) { console.log(state.step, 'SHC[' + a + ']', ci); } + + var d = pv.distance(rp, rp, false, true); + + do { + if (p !== rp) { fv.setRelative(p, p, d, pv); } + p = p.nextPointOnContour; + } while (p !== sp); + } + + // SHZ[] SHift Zone using reference point + // 0x36-0x37 + function SHZ(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + + var e = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SHZ[' + a + ']', e); } + + var z; + switch (e) { + case 0 : z = state.tZone; break; + case 1 : z = state.gZone; break; + default : throw new Error('Invalid zone'); + } + + var p; + var d = pv.distance(rp, rp, false, true); + var pLen = z.length - 2; + for (var i = 0; i < pLen; i++) + { + p = z[i]; + fv.setRelative(p, p, d, pv); + //if (p !== rp) fv.setRelative(p, p, d, pv); + } + } + + // SHPIX[] SHift point by a PIXel amount + // 0x38 + function SHPIX(state) { + var stack = state.stack; + var loop = state.loop; + var fv = state.fv; + var d = stack.pop() / 0x40; + var z2 = state.z2; + + while (loop--) { + var pi = stack.pop(); + var p = z2[pi]; + + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + + 'SHPIX[]', pi, d + ); + } + + fv.setRelative(p, p, d); + fv.touch(p); + } + + state.loop = 1; + } + + // IP[] Interpolate Point + // 0x39 + function IP(state) { + var stack = state.stack; + var rp1i = state.rp1; + var rp2i = state.rp2; + var loop = state.loop; + var rp1 = state.z0[rp1i]; + var rp2 = state.z1[rp2i]; + var fv = state.fv; + var pv = state.dpv; + var z2 = state.z2; + + while (loop--) { + var pi = stack.pop(); + var p = z2[pi]; + + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + + 'IP[]', pi, rp1i, '<->', rp2i + ); + } + + fv.interpolate(p, rp1, rp2, pv); + + fv.touch(p); + } + + state.loop = 1; + } + + // MSIRP[a] Move Stack Indirect Relative Point + // 0x3A-0x3B + function MSIRP(a, state) { + var stack = state.stack; + var d = stack.pop() / 64; + var pi = stack.pop(); + var p = state.z1[pi]; + var rp0 = state.z0[state.rp0]; + var fv = state.fv; + var pv = state.pv; + + fv.setRelative(p, rp0, d, pv); + fv.touch(p); + + if (exports.DEBUG) { console.log(state.step, 'MSIRP[' + a + ']', d, pi); } + + state.rp1 = state.rp0; + state.rp2 = pi; + if (a) { state.rp0 = pi; } + } + + // ALIGNRP[] Align to reference point. + // 0x3C + function ALIGNRP(state) { + var stack = state.stack; + var rp0i = state.rp0; + var rp0 = state.z0[rp0i]; + var loop = state.loop; + var fv = state.fv; + var pv = state.pv; + var z1 = state.z1; + + while (loop--) { + var pi = stack.pop(); + var p = z1[pi]; + + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + + 'ALIGNRP[]', pi + ); + } + + fv.setRelative(p, rp0, 0, pv); + fv.touch(p); + } + + state.loop = 1; + } + + // RTG[] Round To Double Grid + // 0x3D + function RTDG(state) { + if (exports.DEBUG) { console.log(state.step, 'RTDG[]'); } + + state.round = roundToDoubleGrid; + } + + // MIAP[a] Move Indirect Absolute Point + // 0x3E-0x3F + function MIAP(round, state) { + var stack = state.stack; + var n = stack.pop(); + var pi = stack.pop(); + var p = state.z0[pi]; + var fv = state.fv; + var pv = state.pv; + var cv = state.cvt[n]; + + if (exports.DEBUG) { + console.log( + state.step, + 'MIAP[' + round + ']', + n, '(', cv, ')', pi + ); + } + + var d = pv.distance(p, HPZero); + + if (round) { + if (Math.abs(d - cv) < state.cvCutIn) { d = cv; } + + d = state.round(d); + } + + fv.setRelative(p, HPZero, d, pv); + + if (state.zp0 === 0) { + p.xo = p.x; + p.yo = p.y; + } + + fv.touch(p); + + state.rp0 = state.rp1 = pi; + } + + // NPUSB[] PUSH N Bytes + // 0x40 + function NPUSHB(state) { + var prog = state.prog; + var ip = state.ip; + var stack = state.stack; + + var n = prog[++ip]; + + if (exports.DEBUG) { console.log(state.step, 'NPUSHB[]', n); } + + for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } + + state.ip = ip; + } + + // NPUSHW[] PUSH N Words + // 0x41 + function NPUSHW(state) { + var ip = state.ip; + var prog = state.prog; + var stack = state.stack; + var n = prog[++ip]; + + if (exports.DEBUG) { console.log(state.step, 'NPUSHW[]', n); } + + for (var i = 0; i < n; i++) { + var w = (prog[++ip] << 8) | prog[++ip]; + if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } + stack.push(w); + } + + state.ip = ip; + } + + // WS[] Write Store + // 0x42 + function WS(state) { + var stack = state.stack; + var store = state.store; + + if (!store) { store = state.store = []; } + + var v = stack.pop(); + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'WS', v, l); } + + store[l] = v; + } + + // RS[] Read Store + // 0x43 + function RS(state) { + var stack = state.stack; + var store = state.store; + + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'RS', l); } + + var v = (store && store[l]) || 0; + + stack.push(v); + } + + // WCVTP[] Write Control Value Table in Pixel units + // 0x44 + function WCVTP(state) { + var stack = state.stack; + + var v = stack.pop(); + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'WCVTP', v, l); } + + state.cvt[l] = v / 0x40; + } + + // RCVT[] Read Control Value Table entry + // 0x45 + function RCVT(state) { + var stack = state.stack; + var cvte = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'RCVT', cvte); } + + stack.push(state.cvt[cvte] * 0x40); + } + + // GC[] Get Coordinate projected onto the projection vector + // 0x46-0x47 + function GC(a, state) { + var stack = state.stack; + var pi = stack.pop(); + var p = state.z2[pi]; + + if (exports.DEBUG) { console.log(state.step, 'GC[' + a + ']', pi); } + + stack.push(state.dpv.distance(p, HPZero, a, false) * 0x40); + } + + // MD[a] Measure Distance + // 0x49-0x4A + function MD(a, state) { + var stack = state.stack; + var pi2 = stack.pop(); + var pi1 = stack.pop(); + var p2 = state.z1[pi2]; + var p1 = state.z0[pi1]; + var d = state.dpv.distance(p1, p2, a, a); + + if (exports.DEBUG) { console.log(state.step, 'MD[' + a + ']', pi2, pi1, '->', d); } + + state.stack.push(Math.round(d * 64)); + } + + // MPPEM[] Measure Pixels Per EM + // 0x4B + function MPPEM(state) { + if (exports.DEBUG) { console.log(state.step, 'MPPEM[]'); } + state.stack.push(state.ppem); + } + + // FLIPON[] set the auto FLIP Boolean to ON + // 0x4D + function FLIPON(state) { + if (exports.DEBUG) { console.log(state.step, 'FLIPON[]'); } + state.autoFlip = true; + } + + // LT[] Less Than + // 0x50 + function LT(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'LT[]', e2, e1); } + + stack.push(e1 < e2 ? 1 : 0); + } + + // LTEQ[] Less Than or EQual + // 0x53 + function LTEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'LTEQ[]', e2, e1); } + + stack.push(e1 <= e2 ? 1 : 0); + } + + // GTEQ[] Greater Than + // 0x52 + function GT(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'GT[]', e2, e1); } + + stack.push(e1 > e2 ? 1 : 0); + } + + // GTEQ[] Greater Than or EQual + // 0x53 + function GTEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'GTEQ[]', e2, e1); } + + stack.push(e1 >= e2 ? 1 : 0); + } + + // EQ[] EQual + // 0x54 + function EQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'EQ[]', e2, e1); } + + stack.push(e2 === e1 ? 1 : 0); + } + + // NEQ[] Not EQual + // 0x55 + function NEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'NEQ[]', e2, e1); } + + stack.push(e2 !== e1 ? 1 : 0); + } + + // ODD[] ODD + // 0x56 + function ODD(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ODD[]', n); } + + stack.push(Math.trunc(n) % 2 ? 1 : 0); + } + + // EVEN[] EVEN + // 0x57 + function EVEN(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'EVEN[]', n); } + + stack.push(Math.trunc(n) % 2 ? 0 : 1); + } + + // IF[] IF test + // 0x58 + function IF(state) { + var test = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'IF[]', test); } + + // if test is true it just continues + // if not the ip is skipped until matching ELSE or EIF + if (!test) { + skip(state, true); + + if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } + } + } + + // EIF[] End IF + // 0x59 + function EIF(state) { + // this can be reached normally when + // executing an else branch. + // -> just ignore it + + if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } + } + + // AND[] logical AND + // 0x5A + function AND(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'AND[]', e2, e1); } + + stack.push(e2 && e1 ? 1 : 0); + } + + // OR[] logical OR + // 0x5B + function OR(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'OR[]', e2, e1); } + + stack.push(e2 || e1 ? 1 : 0); + } + + // NOT[] logical NOT + // 0x5C + function NOT(state) { + var stack = state.stack; + var e = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'NOT[]', e); } + + stack.push(e ? 0 : 1); + } + + // DELTAP1[] DELTA exception P1 + // DELTAP2[] DELTA exception P2 + // DELTAP3[] DELTA exception P3 + // 0x5D, 0x71, 0x72 + function DELTAP123(b, state) { + var stack = state.stack; + var n = stack.pop(); + var fv = state.fv; + var pv = state.pv; + var ppem = state.ppem; + var base = state.deltaBase + (b - 1) * 16; + var ds = state.deltaShift; + var z0 = state.z0; + + if (exports.DEBUG) { console.log(state.step, 'DELTAP[' + b + ']', n, stack); } + + for (var i = 0; i < n; i++) { + var pi = stack.pop(); + var arg = stack.pop(); + var appem = base + ((arg & 0xF0) >> 4); + if (appem !== ppem) { continue; } + + var mag = (arg & 0x0F) - 8; + if (mag >= 0) { mag++; } + if (exports.DEBUG) { console.log(state.step, 'DELTAPFIX', pi, 'by', mag * ds); } + + var p = z0[pi]; + fv.setRelative(p, p, mag * ds, pv); + } + } + + // SDB[] Set Delta Base in the graphics state + // 0x5E + function SDB(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SDB[]', n); } + + state.deltaBase = n; + } + + // SDS[] Set Delta Shift in the graphics state + // 0x5F + function SDS(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SDS[]', n); } + + state.deltaShift = Math.pow(0.5, n); + } + + // ADD[] ADD + // 0x60 + function ADD(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ADD[]', n2, n1); } + + stack.push(n1 + n2); + } + + // SUB[] SUB + // 0x61 + function SUB(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SUB[]', n2, n1); } + + stack.push(n1 - n2); + } + + // DIV[] DIV + // 0x62 + function DIV(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'DIV[]', n2, n1); } + + stack.push(n1 * 64 / n2); + } + + // MUL[] MUL + // 0x63 + function MUL(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MUL[]', n2, n1); } + + stack.push(n1 * n2 / 64); + } + + // ABS[] ABSolute value + // 0x64 + function ABS(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ABS[]', n); } + + stack.push(Math.abs(n)); + } + + // NEG[] NEGate + // 0x65 + function NEG(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'NEG[]', n); } + + stack.push(-n); + } + + // FLOOR[] FLOOR + // 0x66 + function FLOOR(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'FLOOR[]', n); } + + stack.push(Math.floor(n / 0x40) * 0x40); + } + + // CEILING[] CEILING + // 0x67 + function CEILING(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'CEILING[]', n); } + + stack.push(Math.ceil(n / 0x40) * 0x40); + } + + // ROUND[ab] ROUND value + // 0x68-0x6B + function ROUND(dt, state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ROUND[]'); } + + stack.push(state.round(n / 0x40) * 0x40); + } + + // WCVTF[] Write Control Value Table in Funits + // 0x70 + function WCVTF(state) { + var stack = state.stack; + var v = stack.pop(); + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'WCVTF[]', v, l); } + + state.cvt[l] = v * state.ppem / state.font.unitsPerEm; + } + + // DELTAC1[] DELTA exception C1 + // DELTAC2[] DELTA exception C2 + // DELTAC3[] DELTA exception C3 + // 0x73, 0x74, 0x75 + function DELTAC123(b, state) { + var stack = state.stack; + var n = stack.pop(); + var ppem = state.ppem; + var base = state.deltaBase + (b - 1) * 16; + var ds = state.deltaShift; + + if (exports.DEBUG) { console.log(state.step, 'DELTAC[' + b + ']', n, stack); } + + for (var i = 0; i < n; i++) { + var c = stack.pop(); + var arg = stack.pop(); + var appem = base + ((arg & 0xF0) >> 4); + if (appem !== ppem) { continue; } + + var mag = (arg & 0x0F) - 8; + if (mag >= 0) { mag++; } + + var delta = mag * ds; + + if (exports.DEBUG) { console.log(state.step, 'DELTACFIX', c, 'by', delta); } + + state.cvt[c] += delta; + } + } + + // SROUND[] Super ROUND + // 0x76 + function SROUND(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SROUND[]', n); } + + state.round = roundSuper; + + var period; + + switch (n & 0xC0) { + case 0x00: + period = 0.5; + break; + case 0x40: + period = 1; + break; + case 0x80: + period = 2; + break; + default: + throw new Error('invalid SROUND value'); + } + + state.srPeriod = period; + + switch (n & 0x30) { + case 0x00: + state.srPhase = 0; + break; + case 0x10: + state.srPhase = 0.25 * period; + break; + case 0x20: + state.srPhase = 0.5 * period; + break; + case 0x30: + state.srPhase = 0.75 * period; + break; + default: throw new Error('invalid SROUND value'); + } + + n &= 0x0F; + + if (n === 0) { state.srThreshold = 0; } + else { state.srThreshold = (n / 8 - 0.5) * period; } + } + + // S45ROUND[] Super ROUND 45 degrees + // 0x77 + function S45ROUND(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'S45ROUND[]', n); } + + state.round = roundSuper; + + var period; + + switch (n & 0xC0) { + case 0x00: + period = Math.sqrt(2) / 2; + break; + case 0x40: + period = Math.sqrt(2); + break; + case 0x80: + period = 2 * Math.sqrt(2); + break; + default: + throw new Error('invalid S45ROUND value'); + } + + state.srPeriod = period; + + switch (n & 0x30) { + case 0x00: + state.srPhase = 0; + break; + case 0x10: + state.srPhase = 0.25 * period; + break; + case 0x20: + state.srPhase = 0.5 * period; + break; + case 0x30: + state.srPhase = 0.75 * period; + break; + default: + throw new Error('invalid S45ROUND value'); + } + + n &= 0x0F; + + if (n === 0) { state.srThreshold = 0; } + else { state.srThreshold = (n / 8 - 0.5) * period; } + } + + // ROFF[] Round Off + // 0x7A + function ROFF(state) { + if (exports.DEBUG) { console.log(state.step, 'ROFF[]'); } + + state.round = roundOff; + } + + // RUTG[] Round Up To Grid + // 0x7C + function RUTG(state) { + if (exports.DEBUG) { console.log(state.step, 'RUTG[]'); } + + state.round = roundUpToGrid; + } + + // RDTG[] Round Down To Grid + // 0x7D + function RDTG(state) { + if (exports.DEBUG) { console.log(state.step, 'RDTG[]'); } + + state.round = roundDownToGrid; + } + + // SCANCTRL[] SCAN conversion ConTRoL + // 0x85 + function SCANCTRL(state) { + var n = state.stack.pop(); + + // ignored by opentype.js + + if (exports.DEBUG) { console.log(state.step, 'SCANCTRL[]', n); } + } + + // SDPVTL[a] Set Dual Projection Vector To Line + // 0x86-0x87 + function SDPVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; + + if (exports.DEBUG) { console.log(state.step, 'SDPVTL[' + a + ']', p2i, p1i); } + + var dx; + var dy; + + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } + + state.dpv = getUnitVector(dx, dy); + } + + // GETINFO[] GET INFOrmation + // 0x88 + function GETINFO(state) { + var stack = state.stack; + var sel = stack.pop(); + var r = 0; + + if (exports.DEBUG) { console.log(state.step, 'GETINFO[]', sel); } + + // v35 as in no subpixel hinting + if (sel & 0x01) { r = 35; } + + // TODO rotation and stretch currently not supported + // and thus those GETINFO are always 0. + + // opentype.js is always gray scaling + if (sel & 0x20) { r |= 0x1000; } + + stack.push(r); + } + + // ROLL[] ROLL the top three stack elements + // 0x8A + function ROLL(state) { + var stack = state.stack; + var a = stack.pop(); + var b = stack.pop(); + var c = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ROLL[]'); } + + stack.push(b); + stack.push(a); + stack.push(c); + } + + // MAX[] MAXimum of top two stack elements + // 0x8B + function MAX(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MAX[]', e2, e1); } + + stack.push(Math.max(e1, e2)); + } + + // MIN[] MINimum of top two stack elements + // 0x8C + function MIN(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MIN[]', e2, e1); } + + stack.push(Math.min(e1, e2)); + } + + // SCANTYPE[] SCANTYPE + // 0x8D + function SCANTYPE(state) { + var n = state.stack.pop(); + // ignored by opentype.js + if (exports.DEBUG) { console.log(state.step, 'SCANTYPE[]', n); } + } + + // INSTCTRL[] INSTCTRL + // 0x8D + function INSTCTRL(state) { + var s = state.stack.pop(); + var v = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'INSTCTRL[]', s, v); } + + switch (s) { + case 1 : state.inhibitGridFit = !!v; return; + case 2 : state.ignoreCvt = !!v; return; + default: throw new Error('invalid INSTCTRL[] selector'); + } + } + + // PUSHB[abc] PUSH Bytes + // 0xB0-0xB7 + function PUSHB(n, state) { + var stack = state.stack; + var prog = state.prog; + var ip = state.ip; + + if (exports.DEBUG) { console.log(state.step, 'PUSHB[' + n + ']'); } + + for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } + + state.ip = ip; + } + + // PUSHW[abc] PUSH Words + // 0xB8-0xBF + function PUSHW(n, state) { + var ip = state.ip; + var prog = state.prog; + var stack = state.stack; + + if (exports.DEBUG) { console.log(state.ip, 'PUSHW[' + n + ']'); } + + for (var i = 0; i < n; i++) { + var w = (prog[++ip] << 8) | prog[++ip]; + if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } + stack.push(w); + } + + state.ip = ip; + } + + // MDRP[abcde] Move Direct Relative Point + // 0xD0-0xEF + // (if indirect is 0) + // + // and + // + // MIRP[abcde] Move Indirect Relative Point + // 0xE0-0xFF + // (if indirect is 1) + + function MDRP_MIRP(indirect, setRp0, keepD, ro, dt, state) { + var stack = state.stack; + var cvte = indirect && stack.pop(); + var pi = stack.pop(); + var rp0i = state.rp0; + var rp = state.z0[rp0i]; + var p = state.z1[pi]; + + var md = state.minDis; + var fv = state.fv; + var pv = state.dpv; + var od; // original distance + var d; // moving distance + var sign; // sign of distance + var cv; + + d = od = pv.distance(p, rp, true, true); + sign = d >= 0 ? 1 : -1; // Math.sign would be 0 in case of 0 + + // TODO consider autoFlip + d = Math.abs(d); + + if (indirect) { + cv = state.cvt[cvte]; + + if (ro && Math.abs(d - cv) < state.cvCutIn) { d = cv; } + } + + if (keepD && d < md) { d = md; } + + if (ro) { d = state.round(d); } + + fv.setRelative(p, rp, sign * d, pv); + fv.touch(p); + + if (exports.DEBUG) { + console.log( + state.step, + (indirect ? 'MIRP[' : 'MDRP[') + + (setRp0 ? 'M' : 'm') + + (keepD ? '>' : '_') + + (ro ? 'R' : '_') + + (dt === 0 ? 'Gr' : (dt === 1 ? 'Bl' : (dt === 2 ? 'Wh' : ''))) + + ']', + indirect ? + cvte + '(' + state.cvt[cvte] + ',' + cv + ')' : + '', + pi, + '(d =', od, '->', sign * d, ')' + ); + } + + state.rp1 = state.rp0; + state.rp2 = pi; + if (setRp0) { state.rp0 = pi; } + } + + /* + * The instruction table. + */ + instructionTable = [ + /* 0x00 */ SVTCA.bind(undefined, yUnitVector), + /* 0x01 */ SVTCA.bind(undefined, xUnitVector), + /* 0x02 */ SPVTCA.bind(undefined, yUnitVector), + /* 0x03 */ SPVTCA.bind(undefined, xUnitVector), + /* 0x04 */ SFVTCA.bind(undefined, yUnitVector), + /* 0x05 */ SFVTCA.bind(undefined, xUnitVector), + /* 0x06 */ SPVTL.bind(undefined, 0), + /* 0x07 */ SPVTL.bind(undefined, 1), + /* 0x08 */ SFVTL.bind(undefined, 0), + /* 0x09 */ SFVTL.bind(undefined, 1), + /* 0x0A */ SPVFS, + /* 0x0B */ SFVFS, + /* 0x0C */ GPV, + /* 0x0D */ GFV, + /* 0x0E */ SFVTPV, + /* 0x0F */ ISECT, + /* 0x10 */ SRP0, + /* 0x11 */ SRP1, + /* 0x12 */ SRP2, + /* 0x13 */ SZP0, + /* 0x14 */ SZP1, + /* 0x15 */ SZP2, + /* 0x16 */ SZPS, + /* 0x17 */ SLOOP, + /* 0x18 */ RTG, + /* 0x19 */ RTHG, + /* 0x1A */ SMD, + /* 0x1B */ ELSE, + /* 0x1C */ JMPR, + /* 0x1D */ SCVTCI, + /* 0x1E */ undefined, // TODO SSWCI + /* 0x1F */ undefined, // TODO SSW + /* 0x20 */ DUP, + /* 0x21 */ POP, + /* 0x22 */ CLEAR, + /* 0x23 */ SWAP, + /* 0x24 */ DEPTH, + /* 0x25 */ CINDEX, + /* 0x26 */ MINDEX, + /* 0x27 */ undefined, // TODO ALIGNPTS + /* 0x28 */ undefined, + /* 0x29 */ undefined, // TODO UTP + /* 0x2A */ LOOPCALL, + /* 0x2B */ CALL, + /* 0x2C */ FDEF, + /* 0x2D */ undefined, // ENDF (eaten by FDEF) + /* 0x2E */ MDAP.bind(undefined, 0), + /* 0x2F */ MDAP.bind(undefined, 1), + /* 0x30 */ IUP.bind(undefined, yUnitVector), + /* 0x31 */ IUP.bind(undefined, xUnitVector), + /* 0x32 */ SHP.bind(undefined, 0), + /* 0x33 */ SHP.bind(undefined, 1), + /* 0x34 */ SHC.bind(undefined, 0), + /* 0x35 */ SHC.bind(undefined, 1), + /* 0x36 */ SHZ.bind(undefined, 0), + /* 0x37 */ SHZ.bind(undefined, 1), + /* 0x38 */ SHPIX, + /* 0x39 */ IP, + /* 0x3A */ MSIRP.bind(undefined, 0), + /* 0x3B */ MSIRP.bind(undefined, 1), + /* 0x3C */ ALIGNRP, + /* 0x3D */ RTDG, + /* 0x3E */ MIAP.bind(undefined, 0), + /* 0x3F */ MIAP.bind(undefined, 1), + /* 0x40 */ NPUSHB, + /* 0x41 */ NPUSHW, + /* 0x42 */ WS, + /* 0x43 */ RS, + /* 0x44 */ WCVTP, + /* 0x45 */ RCVT, + /* 0x46 */ GC.bind(undefined, 0), + /* 0x47 */ GC.bind(undefined, 1), + /* 0x48 */ undefined, // TODO SCFS + /* 0x49 */ MD.bind(undefined, 0), + /* 0x4A */ MD.bind(undefined, 1), + /* 0x4B */ MPPEM, + /* 0x4C */ undefined, // TODO MPS + /* 0x4D */ FLIPON, + /* 0x4E */ undefined, // TODO FLIPOFF + /* 0x4F */ undefined, // TODO DEBUG + /* 0x50 */ LT, + /* 0x51 */ LTEQ, + /* 0x52 */ GT, + /* 0x53 */ GTEQ, + /* 0x54 */ EQ, + /* 0x55 */ NEQ, + /* 0x56 */ ODD, + /* 0x57 */ EVEN, + /* 0x58 */ IF, + /* 0x59 */ EIF, + /* 0x5A */ AND, + /* 0x5B */ OR, + /* 0x5C */ NOT, + /* 0x5D */ DELTAP123.bind(undefined, 1), + /* 0x5E */ SDB, + /* 0x5F */ SDS, + /* 0x60 */ ADD, + /* 0x61 */ SUB, + /* 0x62 */ DIV, + /* 0x63 */ MUL, + /* 0x64 */ ABS, + /* 0x65 */ NEG, + /* 0x66 */ FLOOR, + /* 0x67 */ CEILING, + /* 0x68 */ ROUND.bind(undefined, 0), + /* 0x69 */ ROUND.bind(undefined, 1), + /* 0x6A */ ROUND.bind(undefined, 2), + /* 0x6B */ ROUND.bind(undefined, 3), + /* 0x6C */ undefined, // TODO NROUND[ab] + /* 0x6D */ undefined, // TODO NROUND[ab] + /* 0x6E */ undefined, // TODO NROUND[ab] + /* 0x6F */ undefined, // TODO NROUND[ab] + /* 0x70 */ WCVTF, + /* 0x71 */ DELTAP123.bind(undefined, 2), + /* 0x72 */ DELTAP123.bind(undefined, 3), + /* 0x73 */ DELTAC123.bind(undefined, 1), + /* 0x74 */ DELTAC123.bind(undefined, 2), + /* 0x75 */ DELTAC123.bind(undefined, 3), + /* 0x76 */ SROUND, + /* 0x77 */ S45ROUND, + /* 0x78 */ undefined, // TODO JROT[] + /* 0x79 */ undefined, // TODO JROF[] + /* 0x7A */ ROFF, + /* 0x7B */ undefined, + /* 0x7C */ RUTG, + /* 0x7D */ RDTG, + /* 0x7E */ POP, // actually SANGW, supposed to do only a pop though + /* 0x7F */ POP, // actually AA, supposed to do only a pop though + /* 0x80 */ undefined, // TODO FLIPPT + /* 0x81 */ undefined, // TODO FLIPRGON + /* 0x82 */ undefined, // TODO FLIPRGOFF + /* 0x83 */ undefined, + /* 0x84 */ undefined, + /* 0x85 */ SCANCTRL, + /* 0x86 */ SDPVTL.bind(undefined, 0), + /* 0x87 */ SDPVTL.bind(undefined, 1), + /* 0x88 */ GETINFO, + /* 0x89 */ undefined, // TODO IDEF + /* 0x8A */ ROLL, + /* 0x8B */ MAX, + /* 0x8C */ MIN, + /* 0x8D */ SCANTYPE, + /* 0x8E */ INSTCTRL, + /* 0x8F */ undefined, + /* 0x90 */ undefined, + /* 0x91 */ undefined, + /* 0x92 */ undefined, + /* 0x93 */ undefined, + /* 0x94 */ undefined, + /* 0x95 */ undefined, + /* 0x96 */ undefined, + /* 0x97 */ undefined, + /* 0x98 */ undefined, + /* 0x99 */ undefined, + /* 0x9A */ undefined, + /* 0x9B */ undefined, + /* 0x9C */ undefined, + /* 0x9D */ undefined, + /* 0x9E */ undefined, + /* 0x9F */ undefined, + /* 0xA0 */ undefined, + /* 0xA1 */ undefined, + /* 0xA2 */ undefined, + /* 0xA3 */ undefined, + /* 0xA4 */ undefined, + /* 0xA5 */ undefined, + /* 0xA6 */ undefined, + /* 0xA7 */ undefined, + /* 0xA8 */ undefined, + /* 0xA9 */ undefined, + /* 0xAA */ undefined, + /* 0xAB */ undefined, + /* 0xAC */ undefined, + /* 0xAD */ undefined, + /* 0xAE */ undefined, + /* 0xAF */ undefined, + /* 0xB0 */ PUSHB.bind(undefined, 1), + /* 0xB1 */ PUSHB.bind(undefined, 2), + /* 0xB2 */ PUSHB.bind(undefined, 3), + /* 0xB3 */ PUSHB.bind(undefined, 4), + /* 0xB4 */ PUSHB.bind(undefined, 5), + /* 0xB5 */ PUSHB.bind(undefined, 6), + /* 0xB6 */ PUSHB.bind(undefined, 7), + /* 0xB7 */ PUSHB.bind(undefined, 8), + /* 0xB8 */ PUSHW.bind(undefined, 1), + /* 0xB9 */ PUSHW.bind(undefined, 2), + /* 0xBA */ PUSHW.bind(undefined, 3), + /* 0xBB */ PUSHW.bind(undefined, 4), + /* 0xBC */ PUSHW.bind(undefined, 5), + /* 0xBD */ PUSHW.bind(undefined, 6), + /* 0xBE */ PUSHW.bind(undefined, 7), + /* 0xBF */ PUSHW.bind(undefined, 8), + /* 0xC0 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 0), + /* 0xC1 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 1), + /* 0xC2 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 2), + /* 0xC3 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 3), + /* 0xC4 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 0), + /* 0xC5 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 1), + /* 0xC6 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 2), + /* 0xC7 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 3), + /* 0xC8 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 0), + /* 0xC9 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 1), + /* 0xCA */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 2), + /* 0xCB */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 3), + /* 0xCC */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 0), + /* 0xCD */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 1), + /* 0xCE */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 2), + /* 0xCF */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 3), + /* 0xD0 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 0), + /* 0xD1 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 1), + /* 0xD2 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 2), + /* 0xD3 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 3), + /* 0xD4 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 0), + /* 0xD5 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 1), + /* 0xD6 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 2), + /* 0xD7 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 3), + /* 0xD8 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 0), + /* 0xD9 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 1), + /* 0xDA */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 2), + /* 0xDB */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 3), + /* 0xDC */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 0), + /* 0xDD */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 1), + /* 0xDE */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 2), + /* 0xDF */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 3), + /* 0xE0 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 0), + /* 0xE1 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 1), + /* 0xE2 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 2), + /* 0xE3 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 3), + /* 0xE4 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 0), + /* 0xE5 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 1), + /* 0xE6 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 2), + /* 0xE7 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 3), + /* 0xE8 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 0), + /* 0xE9 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 1), + /* 0xEA */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 2), + /* 0xEB */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 3), + /* 0xEC */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 0), + /* 0xED */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 1), + /* 0xEE */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 2), + /* 0xEF */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 3), + /* 0xF0 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 0), + /* 0xF1 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 1), + /* 0xF2 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 2), + /* 0xF3 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 3), + /* 0xF4 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 0), + /* 0xF5 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 1), + /* 0xF6 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 2), + /* 0xF7 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 3), + /* 0xF8 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 0), + /* 0xF9 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 1), + /* 0xFA */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 2), + /* 0xFB */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 3), + /* 0xFC */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 0), + /* 0xFD */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 1), + /* 0xFE */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 2), + /* 0xFF */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 3) + ]; + + /***************************** + Mathematical Considerations + ****************************** + + fv ... refers to freedom vector + pv ... refers to projection vector + rp ... refers to reference point + p ... refers to to point being operated on + d ... refers to distance + + SETRELATIVE: + ============ + + case freedom vector == x-axis: + ------------------------------ + + (pv) + .-' + rpd .-' + .-* + d .-'90°' + .-' ' + .-' ' + *-' ' b + rp ' + ' + ' + p *----------*-------------- (fv) + pm + + rpdx = rpx + d * pv.x + rpdy = rpy + d * pv.y + + equation of line b + + y - rpdy = pvns * (x- rpdx) + + y = p.y + + x = rpdx + ( p.y - rpdy ) / pvns + + + case freedom vector == y-axis: + ------------------------------ + + * pm + |\ + | \ + | \ + | \ + | \ + | \ + | \ + | \ + | \ + | \ b + | \ + | \ + | \ .-' (pv) + | 90° \.-' + | .-'* rpd + | .-' + * *-' d + p rp + + rpdx = rpx + d * pv.x + rpdy = rpy + d * pv.y + + equation of line b: + pvns ... normal slope to pv + + y - rpdy = pvns * (x - rpdx) + + x = p.x + + y = rpdy + pvns * (p.x - rpdx) + + + + generic case: + ------------- + + + .'(fv) + .' + .* pm + .' ! + .' . + .' ! + .' . b + .' ! + * . + p ! + 90° . ... (pv) + ...-*-''' + ...---''' rpd + ...---''' d + *--''' + rp + + rpdx = rpx + d * pv.x + rpdy = rpy + d * pv.y + + equation of line b: + pvns... normal slope to pv + + y - rpdy = pvns * (x - rpdx) + + equation of freedom vector line: + fvs ... slope of freedom vector (=fy/fx) + + y - py = fvs * (x - px) + + + on pm both equations are true for same x/y + + y - rpdy = pvns * (x - rpdx) + + y - py = fvs * (x - px) + + form to y and set equal: + + pvns * (x - rpdx) + rpdy = fvs * (x - px) + py + + expand: + + pvns * x - pvns * rpdx + rpdy = fvs * x - fvs * px + py + + switch: + + fvs * x - fvs * px + py = pvns * x - pvns * rpdx + rpdy + + solve for x: + + fvs * x - pvns * x = fvs * px - pvns * rpdx - py + rpdy + + + + fvs * px - pvns * rpdx + rpdy - py + x = ----------------------------------- + fvs - pvns + + and: + + y = fvs * (x - px) + py + + + + INTERPOLATE: + ============ + + Examples of point interpolation. + + The weight of the movement of the reference point gets bigger + the further the other reference point is away, thus the safest + option (that is avoiding 0/0 divisions) is to weight the + original distance of the other point by the sum of both distances. + + If the sum of both distances is 0, then move the point by the + arithmetic average of the movement of both reference points. + + + + + (+6) + rp1o *---->*rp1 + . . (+12) + . . rp2o *---------->* rp2 + . . . . + . . . . + . 10 20 . . + |.........|...................| . + . . . + . . (+8) . + po *------>*p . + . . . + . 12 . 24 . + |...........|.......................| + 36 + + + ------- + + + + (+10) + rp1o *-------->*rp1 + . . (-10) + . . rp2 *<---------* rpo2 + . . . . + . . . . + . 10 . 30 . . + |.........|.............................| + . . + . (+5) . + po *--->* p . + . . . + . . 20 . + |....|..............| + 5 15 + + + ------- + + + (+10) + rp1o *-------->*rp1 + . . + . . + rp2o *-------->*rp2 + + + (+10) + po *-------->* p + + ------- + + + (+10) + rp1o *-------->*rp1 + . . + . .(+30) + rp2o *---------------------------->*rp2 + + + (+25) + po *----------------------->* p + + + + vim: set ts=4 sw=4 expandtab: + *****/ + + /** + * Converts a string into a list of tokens. + */ + + /** + * Create a new token + * @param {string} char a single char + */ + function Token(char) { + this.char = char; + this.state = {}; + this.activeState = null; + } + + /** + * Create a new context range + * @param {number} startIndex range start index + * @param {number} endOffset range end index offset + * @param {string} contextName owner context name + */ + function ContextRange(startIndex, endOffset, contextName) { + this.contextName = contextName; + this.startIndex = startIndex; + this.endOffset = endOffset; + } + + /** + * Check context start and end + * @param {string} contextName a unique context name + * @param {function} checkStart a predicate function the indicates a context's start + * @param {function} checkEnd a predicate function the indicates a context's end + */ + function ContextChecker(contextName, checkStart, checkEnd) { + this.contextName = contextName; + this.openRange = null; + this.ranges = []; + this.checkStart = checkStart; + this.checkEnd = checkEnd; + } + + /** + * @typedef ContextParams + * @type Object + * @property {array} context context items + * @property {number} currentIndex current item index + */ + + /** + * Create a context params + * @param {array} context a list of items + * @param {number} currentIndex current item index + */ + function ContextParams(context, currentIndex) { + this.context = context; + this.index = currentIndex; + this.length = context.length; + this.current = context[currentIndex]; + this.backtrack = context.slice(0, currentIndex); + this.lookahead = context.slice(currentIndex + 1); + } + + /** + * Create an event instance + * @param {string} eventId event unique id + */ + function Event(eventId) { + this.eventId = eventId; + this.subscribers = []; + } + + /** + * Initialize a core events and auto subscribe required event handlers + * @param {any} events an object that enlists core events handlers + */ + function initializeCoreEvents(events) { + var this$1 = this; + + var coreEvents = [ + 'start', 'end', 'next', 'newToken', 'contextStart', + 'contextEnd', 'insertToken', 'removeToken', 'removeRange', + 'replaceToken', 'replaceRange', 'composeRUD', 'updateContextsRanges' + ]; + + coreEvents.forEach(function (eventId) { + Object.defineProperty(this$1.events, eventId, { + value: new Event(eventId) + }); + }); + + if (!!events) { + coreEvents.forEach(function (eventId) { + var event = events[eventId]; + if (typeof event === 'function') { + this$1.events[eventId].subscribe(event); + } + }); + } + var requiresContextUpdate = [ + 'insertToken', 'removeToken', 'removeRange', + 'replaceToken', 'replaceRange', 'composeRUD' + ]; + requiresContextUpdate.forEach(function (eventId) { + this$1.events[eventId].subscribe( + this$1.updateContextsRanges + ); + }); + } + + /** + * Converts a string into a list of tokens + * @param {any} events tokenizer core events + */ + function Tokenizer(events) { + this.tokens = []; + this.registeredContexts = {}; + this.contextCheckers = []; + this.events = {}; + this.registeredModifiers = []; + + initializeCoreEvents.call(this, events); + } + + /** + * Sets the state of a token, usually called by a state modifier. + * @param {string} key state item key + * @param {any} value state item value + */ + Token.prototype.setState = function(key, value) { + this.state[key] = value; + this.activeState = { key: key, value: this.state[key] }; + return this.activeState; + }; + + Token.prototype.getState = function (stateId) { + return this.state[stateId] || null; + }; + + /** + * Checks if an index exists in the tokens list. + * @param {number} index token index + */ + Tokenizer.prototype.inboundIndex = function(index) { + return index >= 0 && index < this.tokens.length; + }; + + /** + * Compose and apply a list of operations (replace, update, delete) + * @param {array} RUDs replace, update and delete operations + * TODO: Perf. Optimization (lengthBefore === lengthAfter ? dispatch once) + */ + Tokenizer.prototype.composeRUD = function (RUDs) { + var this$1 = this; + + var silent = true; + var state = RUDs.map(function (RUD) { return ( + this$1[RUD[0]].apply(this$1, RUD.slice(1).concat(silent)) + ); }); + var hasFAILObject = function (obj) { return ( + typeof obj === 'object' && + obj.hasOwnProperty('FAIL') + ); }; + if (state.every(hasFAILObject)) { + return { + FAIL: "composeRUD: one or more operations hasn't completed successfully", + report: state.filter(hasFAILObject) + }; + } + this.dispatch('composeRUD', [state.filter(function (op) { return !hasFAILObject(op); })]); + }; + + /** + * Replace a range of tokens with a list of tokens + * @param {number} startIndex range start index + * @param {number} offset range offset + * @param {token} tokens a list of tokens to replace + * @param {boolean} silent dispatch events and update context ranges + */ + Tokenizer.prototype.replaceRange = function (startIndex, offset, tokens, silent) { + offset = offset !== null ? offset : this.tokens.length; + var isTokenType = tokens.every(function (token) { return token instanceof Token; }); + if (!isNaN(startIndex) && this.inboundIndex(startIndex) && isTokenType) { + var replaced = this.tokens.splice.apply( + this.tokens, [startIndex, offset].concat(tokens) + ); + if (!silent) { this.dispatch('replaceToken', [startIndex, offset, tokens]); } + return [replaced, tokens]; + } else { + return { FAIL: 'replaceRange: invalid tokens or startIndex.' }; + } + }; + + /** + * Replace a token with another token + * @param {number} index token index + * @param {token} token a token to replace + * @param {boolean} silent dispatch events and update context ranges + */ + Tokenizer.prototype.replaceToken = function (index, token, silent) { + if (!isNaN(index) && this.inboundIndex(index) && token instanceof Token) { + var replaced = this.tokens.splice(index, 1, token); + if (!silent) { this.dispatch('replaceToken', [index, token]); } + return [replaced[0], token]; + } else { + return { FAIL: 'replaceToken: invalid token or index.' }; + } + }; + + /** + * Removes a range of tokens + * @param {number} startIndex range start index + * @param {number} offset range offset + * @param {boolean} silent dispatch events and update context ranges + */ + Tokenizer.prototype.removeRange = function(startIndex, offset, silent) { + offset = !isNaN(offset) ? offset : this.tokens.length; + var tokens = this.tokens.splice(startIndex, offset); + if (!silent) { this.dispatch('removeRange', [tokens, startIndex, offset]); } + return tokens; + }; + + /** + * Remove a token at a certain index + * @param {number} index token index + * @param {boolean} silent dispatch events and update context ranges + */ + Tokenizer.prototype.removeToken = function(index, silent) { + if (!isNaN(index) && this.inboundIndex(index)) { + var token = this.tokens.splice(index, 1); + if (!silent) { this.dispatch('removeToken', [token, index]); } + return token; + } else { + return { FAIL: 'removeToken: invalid token index.' }; + } + }; + + /** + * Insert a list of tokens at a certain index + * @param {array} tokens a list of tokens to insert + * @param {number} index insert the list of tokens at index + * @param {boolean} silent dispatch events and update context ranges + */ + Tokenizer.prototype.insertToken = function (tokens, index, silent) { + var tokenType = tokens.every( + function (token) { return token instanceof Token; } + ); + if (tokenType) { + this.tokens.splice.apply( + this.tokens, [index, 0].concat(tokens) + ); + if (!silent) { this.dispatch('insertToken', [tokens, index]); } + return tokens; + } else { + return { FAIL: 'insertToken: invalid token(s).' }; + } + }; + + /** + * A state modifier that is called on 'newToken' event + * @param {string} modifierId state modifier id + * @param {function} condition a predicate function that returns true or false + * @param {function} modifier a function to update token state + */ + Tokenizer.prototype.registerModifier = function(modifierId, condition, modifier) { + this.events.newToken.subscribe(function(token, contextParams) { + var conditionParams = [token, contextParams]; + var canApplyModifier = ( + condition === null || + condition.apply(this, conditionParams) === true + ); + var modifierParams = [token, contextParams]; + if (canApplyModifier) { + var newStateValue = modifier.apply(this, modifierParams); + token.setState(modifierId, newStateValue); + } + }); + this.registeredModifiers.push(modifierId); + }; + + /** + * Subscribe a handler to an event + * @param {function} eventHandler an event handler function + */ + Event.prototype.subscribe = function (eventHandler) { + if (typeof eventHandler === 'function') { + return ((this.subscribers.push(eventHandler)) - 1); + } else { + return { FAIL: ("invalid '" + (this.eventId) + "' event handler")}; + } + }; + + /** + * Unsubscribe an event handler + * @param {string} subsId subscription id + */ + Event.prototype.unsubscribe = function (subsId) { + this.subscribers.splice(subsId, 1); + }; + + /** + * Sets context params current value index + * @param {number} index context params current value index + */ + ContextParams.prototype.setCurrentIndex = function(index) { + this.index = index; + this.current = this.context[index]; + this.backtrack = this.context.slice(0, index); + this.lookahead = this.context.slice(index + 1); + }; + + /** + * Get an item at an offset from the current value + * example (current value is 3): + * 1 2 [3] 4 5 | items values + * -2 -1 0 1 2 | offset values + * @param {number} offset an offset from current value index + */ + ContextParams.prototype.get = function (offset) { + switch (true) { + case (offset === 0): + return this.current; + case (offset < 0 && Math.abs(offset) <= this.backtrack.length): + return this.backtrack.slice(offset)[0]; + case (offset > 0 && offset <= this.lookahead.length): + return this.lookahead[offset - 1]; + default: + return null; + } + }; + + /** + * Converts a context range into a string value + * @param {contextRange} range a context range + */ + Tokenizer.prototype.rangeToText = function (range) { + if (range instanceof ContextRange) { + return ( + this.getRangeTokens(range) + .map(function (token) { return token.char; }).join('') + ); + } + }; + + /** + * Converts all tokens into a string + */ + Tokenizer.prototype.getText = function () { + return this.tokens.map(function (token) { return token.char; }).join(''); + }; + + /** + * Get a context by name + * @param {string} contextName context name to get + */ + Tokenizer.prototype.getContext = function (contextName) { + var context = this.registeredContexts[contextName]; + return !!context ? context : null; + }; + + /** + * Subscribes a new event handler to an event + * @param {string} eventName event name to subscribe to + * @param {function} eventHandler a function to be invoked on event + */ + Tokenizer.prototype.on = function(eventName, eventHandler) { + var event = this.events[eventName]; + if (!!event) { + return event.subscribe(eventHandler); + } else { + return null; + } + }; + + /** + * Dispatches an event + * @param {string} eventName event name + * @param {any} args event handler arguments + */ + Tokenizer.prototype.dispatch = function(eventName, args) { + var this$1 = this; + + var event = this.events[eventName]; + if (event instanceof Event) { + event.subscribers.forEach(function (subscriber) { + subscriber.apply(this$1, args || []); + }); + } + }; + + /** + * Register a new context checker + * @param {string} contextName a unique context name + * @param {function} contextStartCheck a predicate function that returns true on context start + * @param {function} contextEndCheck a predicate function that returns true on context end + * TODO: call tokenize on registration to update context ranges with the new context. + */ + Tokenizer.prototype.registerContextChecker = function(contextName, contextStartCheck, contextEndCheck) { + if (!!this.getContext(contextName)) { return { + FAIL: + ("context name '" + contextName + "' is already registered.") + }; } + if (typeof contextStartCheck !== 'function') { return { + FAIL: + "missing context start check." + }; } + if (typeof contextEndCheck !== 'function') { return { + FAIL: + "missing context end check." + }; } + var contextCheckers = new ContextChecker( + contextName, contextStartCheck, contextEndCheck + ); + this.registeredContexts[contextName] = contextCheckers; + this.contextCheckers.push(contextCheckers); + return contextCheckers; + }; + + /** + * Gets a context range tokens + * @param {contextRange} range a context range + */ + Tokenizer.prototype.getRangeTokens = function(range) { + var endIndex = range.startIndex + range.endOffset; + return [].concat( + this.tokens + .slice(range.startIndex, endIndex) + ); + }; + + /** + * Gets the ranges of a context + * @param {string} contextName context name + */ + Tokenizer.prototype.getContextRanges = function(contextName) { + var context = this.getContext(contextName); + if (!!context) { + return context.ranges; + } else { + return { FAIL: ("context checker '" + contextName + "' is not registered.") }; + } + }; + + /** + * Resets context ranges to run context update + */ + Tokenizer.prototype.resetContextsRanges = function () { + var registeredContexts = this.registeredContexts; + for (var contextName in registeredContexts) { + if (registeredContexts.hasOwnProperty(contextName)) { + var context = registeredContexts[contextName]; + context.ranges = []; + } + } + }; + + /** + * Updates context ranges + */ + Tokenizer.prototype.updateContextsRanges = function () { + this.resetContextsRanges(); + var chars = this.tokens.map(function (token) { return token.char; }); + for (var i = 0; i < chars.length; i++) { + var contextParams = new ContextParams(chars, i); + this.runContextCheck(contextParams); + } + this.dispatch('updateContextsRanges', [this.registeredContexts]); + }; + + /** + * Sets the end offset of an open range + * @param {number} offset range end offset + * @param {string} contextName context name + */ + Tokenizer.prototype.setEndOffset = function (offset, contextName) { + var startIndex = this.getContext(contextName).openRange.startIndex; + var range = new ContextRange(startIndex, offset, contextName); + var ranges = this.getContext(contextName).ranges; + range.rangeId = contextName + "." + (ranges.length); + ranges.push(range); + this.getContext(contextName).openRange = null; + return range; + }; + + /** + * Runs a context check on the current context + * @param {contextParams} contextParams current context params + */ + Tokenizer.prototype.runContextCheck = function(contextParams) { + var this$1 = this; + + var index = contextParams.index; + this.contextCheckers.forEach(function (contextChecker) { + var contextName = contextChecker.contextName; + var openRange = this$1.getContext(contextName).openRange; + if (!openRange && contextChecker.checkStart(contextParams)) { + openRange = new ContextRange(index, null, contextName); + this$1.getContext(contextName).openRange = openRange; + this$1.dispatch('contextStart', [contextName, index]); + } + if (!!openRange && contextChecker.checkEnd(contextParams)) { + var offset = (index - openRange.startIndex) + 1; + var range = this$1.setEndOffset(offset, contextName); + this$1.dispatch('contextEnd', [contextName, range]); + } + }); + }; + + /** + * Converts a text into a list of tokens + * @param {string} text a text to tokenize + */ + Tokenizer.prototype.tokenize = function (text) { + this.tokens = []; + this.resetContextsRanges(); + var chars = Array.from(text); + this.dispatch('start'); + for (var i = 0; i < chars.length; i++) { + var char = chars[i]; + var contextParams = new ContextParams(chars, i); + this.dispatch('next', [contextParams]); + this.runContextCheck(contextParams); + var token = new Token(char); + this.tokens.push(token); + this.dispatch('newToken', [token, contextParams]); + } + this.dispatch('end', [this.tokens]); + return this.tokens; + }; + + // ╭─┄┄┄────────────────────────┄─────────────────────────────────────────────╮ + // ┊ Character Class Assertions ┊ Checks if a char belongs to a certain class ┊ + // ╰─╾──────────────────────────┄─────────────────────────────────────────────╯ + // jscs:disable maximumLineLength + /** + * Check if a char is Arabic + * @param {string} c a single char + */ + function isArabicChar(c) { + return /[\u0600-\u065F\u066A-\u06D2\u06FA-\u06FF]/.test(c); + } + + /** + * Check if a char is an isolated arabic char + * @param {string} c a single char + */ + function isIsolatedArabicChar(char) { + return /[\u0630\u0690\u0621\u0631\u0661\u0671\u0622\u0632\u0672\u0692\u06C2\u0623\u0673\u0693\u06C3\u0624\u0694\u06C4\u0625\u0675\u0695\u06C5\u06E5\u0676\u0696\u06C6\u0627\u0677\u0697\u06C7\u0648\u0688\u0698\u06C8\u0689\u0699\u06C9\u068A\u06CA\u066B\u068B\u06CB\u068C\u068D\u06CD\u06FD\u068E\u06EE\u06FE\u062F\u068F\u06CF\u06EF]/.test(char); + } + + /** + * Check if a char is an Arabic Tashkeel char + * @param {string} c a single char + */ + function isTashkeelArabicChar(char) { + return /[\u0600-\u0605\u060C-\u060E\u0610-\u061B\u061E\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED]/.test(char); + } + + /** + * Check if a char is Latin + * @param {string} c a single char + */ + function isLatinChar(c) { + return /[A-z]/.test(c); + } + + /** + * Check if a char is whitespace char + * @param {string} c a single char + */ + function isWhiteSpace(c) { + return /\s/.test(c); + } + + /** + * Query a feature by some of it's properties to lookup a glyph substitution. + */ + + /** + * Create feature query instance + * @param {Font} font opentype font instance + */ + function FeatureQuery(font) { + this.font = font; + this.features = {}; + } + + /** + * @typedef SubstitutionAction + * @type Object + * @property {number} id substitution type + * @property {string} tag feature tag + * @property {any} substitution substitution value(s) + */ + + /** + * Create a substitution action instance + * @param {SubstitutionAction} action + */ + function SubstitutionAction(action) { + this.id = action.id; + this.tag = action.tag; + this.substitution = action.substitution; + } + + /** + * Lookup a coverage table + * @param {number} glyphIndex glyph index + * @param {CoverageTable} coverage coverage table + */ + function lookupCoverage(glyphIndex, coverage) { + if (!glyphIndex) { return -1; } + switch (coverage.format) { + case 1: + return coverage.glyphs.indexOf(glyphIndex); + + case 2: + var ranges = coverage.ranges; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (glyphIndex >= range.start && glyphIndex <= range.end) { + var offset = glyphIndex - range.start; + return range.index + offset; + } + } + break; + default: + return -1; // not found + } + return -1; + } + + /** + * Handle a single substitution - format 1 + * @param {ContextParams} contextParams context params to lookup + */ + function singleSubstitutionFormat1(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { return null; } + return glyphIndex + subtable.deltaGlyphId; + } + + /** + * Handle a single substitution - format 2 + * @param {ContextParams} contextParams context params to lookup + */ + function singleSubstitutionFormat2(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { return null; } + return subtable.substitute[substituteIndex]; + } + + /** + * Lookup a list of coverage tables + * @param {any} coverageList a list of coverage tables + * @param {ContextParams} contextParams context params to lookup + */ + function lookupCoverageList(coverageList, contextParams) { + var lookupList = []; + for (var i = 0; i < coverageList.length; i++) { + var coverage = coverageList[i]; + var glyphIndex = contextParams.current; + glyphIndex = Array.isArray(glyphIndex) ? glyphIndex[0] : glyphIndex; + var lookupIndex = lookupCoverage(glyphIndex, coverage); + if (lookupIndex !== -1) { + lookupList.push(lookupIndex); + } + } + if (lookupList.length !== coverageList.length) { return -1; } + return lookupList; + } + + /** + * Handle chaining context substitution - format 3 + * @param {ContextParams} contextParams context params to lookup + */ + function chainingSubstitutionFormat3(contextParams, subtable) { + var lookupsCount = ( + subtable.inputCoverage.length + + subtable.lookaheadCoverage.length + + subtable.backtrackCoverage.length + ); + if (contextParams.context.length < lookupsCount) { return []; } + // INPUT LOOKUP // + var inputLookups = lookupCoverageList( + subtable.inputCoverage, contextParams + ); + if (inputLookups === -1) { return []; } + // LOOKAHEAD LOOKUP // + var lookaheadOffset = subtable.inputCoverage.length - 1; + if (contextParams.lookahead.length < subtable.lookaheadCoverage.length) { return []; } + var lookaheadContext = contextParams.lookahead.slice(lookaheadOffset); + while (lookaheadContext.length && isTashkeelArabicChar(lookaheadContext[0].char)) { + lookaheadContext.shift(); + } + var lookaheadParams = new ContextParams(lookaheadContext, 0); + var lookaheadLookups = lookupCoverageList( + subtable.lookaheadCoverage, lookaheadParams + ); + // BACKTRACK LOOKUP // + var backtrackContext = [].concat(contextParams.backtrack); + backtrackContext.reverse(); + while (backtrackContext.length && isTashkeelArabicChar(backtrackContext[0].char)) { + backtrackContext.shift(); + } + if (backtrackContext.length < subtable.backtrackCoverage.length) { return []; } + var backtrackParams = new ContextParams(backtrackContext, 0); + var backtrackLookups = lookupCoverageList( + subtable.backtrackCoverage, backtrackParams + ); + var contextRulesMatch = ( + inputLookups.length === subtable.inputCoverage.length && + lookaheadLookups.length === subtable.lookaheadCoverage.length && + backtrackLookups.length === subtable.backtrackCoverage.length + ); + var substitutions = []; + if (contextRulesMatch) { + for (var i = 0; i < subtable.lookupRecords.length; i++) { + var lookupRecord = subtable.lookupRecords[i]; + var lookupListIndex = lookupRecord.lookupListIndex; + var lookupTable = this.getLookupByIndex(lookupListIndex); + for (var s = 0; s < lookupTable.subtables.length; s++) { + var subtable$1 = lookupTable.subtables[s]; + var lookup = this.getLookupMethod(lookupTable, subtable$1); + var substitutionType = this.getSubstitutionType(lookupTable, subtable$1); + if (substitutionType === '12') { + for (var n = 0; n < inputLookups.length; n++) { + var glyphIndex = contextParams.get(n); + var substitution = lookup(glyphIndex); + if (substitution) { substitutions.push(substitution); } + } + } + } + } + } + return substitutions; + } + + /** + * Handle ligature substitution - format 1 + * @param {ContextParams} contextParams context params to lookup + */ + function ligatureSubstitutionFormat1(contextParams, subtable) { + // COVERAGE LOOKUP // + var glyphIndex = contextParams.current; + var ligSetIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (ligSetIndex === -1) { return null; } + // COMPONENTS LOOKUP + // (!) note, components are ordered in the written direction. + var ligature; + var ligatureSet = subtable.ligatureSets[ligSetIndex]; + for (var s = 0; s < ligatureSet.length; s++) { + ligature = ligatureSet[s]; + for (var l = 0; l < ligature.components.length; l++) { + var lookaheadItem = contextParams.lookahead[l]; + var component = ligature.components[l]; + if (lookaheadItem !== component) { break; } + if (l === ligature.components.length - 1) { return ligature; } + } + } + return null; + } + + /** + * Handle decomposition substitution - format 1 + * @param {number} glyphIndex glyph index + * @param {any} subtable subtable + */ + function decompositionSubstitutionFormat1(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { return null; } + return subtable.sequences[substituteIndex]; + } + + /** + * Get default script features indexes + */ + FeatureQuery.prototype.getDefaultScriptFeaturesIndexes = function () { + var scripts = this.font.tables.gsub.scripts; + for (var s = 0; s < scripts.length; s++) { + var script = scripts[s]; + if (script.tag === 'DFLT') { return ( + script.script.defaultLangSys.featureIndexes + ); } + } + return []; + }; + + /** + * Get feature indexes of a specific script + * @param {string} scriptTag script tag + */ + FeatureQuery.prototype.getScriptFeaturesIndexes = function(scriptTag) { + var tables = this.font.tables; + if (!tables.gsub) { return []; } + if (!scriptTag) { return this.getDefaultScriptFeaturesIndexes(); } + var scripts = this.font.tables.gsub.scripts; + for (var i = 0; i < scripts.length; i++) { + var script = scripts[i]; + if (script.tag === scriptTag && script.script.defaultLangSys) { + return script.script.defaultLangSys.featureIndexes; + } else { + var langSysRecords = script.langSysRecords; + if (!!langSysRecords) { + for (var j = 0; j < langSysRecords.length; j++) { + var langSysRecord = langSysRecords[j]; + if (langSysRecord.tag === scriptTag) { + var langSys = langSysRecord.langSys; + return langSys.featureIndexes; + } + } + } + } + } + return this.getDefaultScriptFeaturesIndexes(); + }; + + /** + * Map a feature tag to a gsub feature + * @param {any} features gsub features + * @param {string} scriptTag script tag + */ + FeatureQuery.prototype.mapTagsToFeatures = function (features, scriptTag) { + var tags = {}; + for (var i = 0; i < features.length; i++) { + var tag = features[i].tag; + var feature = features[i].feature; + tags[tag] = feature; + } + this.features[scriptTag].tags = tags; + }; + + /** + * Get features of a specific script + * @param {string} scriptTag script tag + */ + FeatureQuery.prototype.getScriptFeatures = function (scriptTag) { + var features = this.features[scriptTag]; + if (this.features.hasOwnProperty(scriptTag)) { return features; } + var featuresIndexes = this.getScriptFeaturesIndexes(scriptTag); + if (!featuresIndexes) { return null; } + var gsub = this.font.tables.gsub; + features = featuresIndexes.map(function (index) { return gsub.features[index]; }); + this.features[scriptTag] = features; + this.mapTagsToFeatures(features, scriptTag); + return features; + }; + + /** + * Get substitution type + * @param {any} lookupTable lookup table + * @param {any} subtable subtable + */ + FeatureQuery.prototype.getSubstitutionType = function(lookupTable, subtable) { + var lookupType = lookupTable.lookupType.toString(); + var substFormat = subtable.substFormat.toString(); + return lookupType + substFormat; + }; + + /** + * Get lookup method + * @param {any} lookupTable lookup table + * @param {any} subtable subtable + */ + FeatureQuery.prototype.getLookupMethod = function(lookupTable, subtable) { + var this$1 = this; + + var substitutionType = this.getSubstitutionType(lookupTable, subtable); + switch (substitutionType) { + case '11': + return function (glyphIndex) { return singleSubstitutionFormat1.apply( + this$1, [glyphIndex, subtable] + ); }; + case '12': + return function (glyphIndex) { return singleSubstitutionFormat2.apply( + this$1, [glyphIndex, subtable] + ); }; + case '63': + return function (contextParams) { return chainingSubstitutionFormat3.apply( + this$1, [contextParams, subtable] + ); }; + case '41': + return function (contextParams) { return ligatureSubstitutionFormat1.apply( + this$1, [contextParams, subtable] + ); }; + case '21': + return function (glyphIndex) { return decompositionSubstitutionFormat1.apply( + this$1, [glyphIndex, subtable] + ); }; + default: + throw new Error( + "lookupType: " + (lookupTable.lookupType) + " - " + + "substFormat: " + (subtable.substFormat) + " " + + "is not yet supported" + ); + } + }; + + /** + * [ LOOKUP TYPES ] + * ------------------------------- + * Single 1; + * Multiple 2; + * Alternate 3; + * Ligature 4; + * Context 5; + * ChainingContext 6; + * ExtensionSubstitution 7; + * ReverseChainingContext 8; + * ------------------------------- + * + */ + + /** + * @typedef FQuery + * @type Object + * @param {string} tag feature tag + * @param {string} script feature script + * @param {ContextParams} contextParams context params + */ + + /** + * Lookup a feature using a query parameters + * @param {FQuery} query feature query + */ + FeatureQuery.prototype.lookupFeature = function (query) { + var contextParams = query.contextParams; + var currentIndex = contextParams.index; + var feature = this.getFeature({ + tag: query.tag, script: query.script + }); + if (!feature) { return new Error( + "font '" + (this.font.names.fullName.en) + "' " + + "doesn't support feature '" + (query.tag) + "' " + + "for script '" + (query.script) + "'." + ); } + var lookups = this.getFeatureLookups(feature); + var substitutions = [].concat(contextParams.context); + for (var l = 0; l < lookups.length; l++) { + var lookupTable = lookups[l]; + var subtables = this.getLookupSubtables(lookupTable); + for (var s = 0; s < subtables.length; s++) { + var subtable = subtables[s]; + var substType = this.getSubstitutionType(lookupTable, subtable); + var lookup = this.getLookupMethod(lookupTable, subtable); + var substitution = (void 0); + switch (substType) { + case '11': + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 11, tag: query.tag, substitution: substitution + })); + } + break; + case '12': + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 12, tag: query.tag, substitution: substitution + })); + } + break; + case '63': + substitution = lookup(contextParams); + if (Array.isArray(substitution) && substitution.length) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 63, tag: query.tag, substitution: substitution + })); + } + break; + case '41': + substitution = lookup(contextParams); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 41, tag: query.tag, substitution: substitution + })); + } + break; + case '21': + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 21, tag: query.tag, substitution: substitution + })); + } + break; + } + contextParams = new ContextParams(substitutions, currentIndex); + if (Array.isArray(substitution) && !substitution.length) { continue; } + substitution = null; + } + } + return substitutions.length ? substitutions : null; + }; + + /** + * Checks if a font supports a specific features + * @param {FQuery} query feature query object + */ + FeatureQuery.prototype.supports = function (query) { + if (!query.script) { return false; } + this.getScriptFeatures(query.script); + var supportedScript = this.features.hasOwnProperty(query.script); + if (!query.tag) { return supportedScript; } + var supportedFeature = ( + this.features[query.script].some(function (feature) { return feature.tag === query.tag; }) + ); + return supportedScript && supportedFeature; + }; + + /** + * Get lookup table subtables + * @param {any} lookupTable lookup table + */ + FeatureQuery.prototype.getLookupSubtables = function (lookupTable) { + return lookupTable.subtables || null; + }; + + /** + * Get lookup table by index + * @param {number} index lookup table index + */ + FeatureQuery.prototype.getLookupByIndex = function (index) { + var lookups = this.font.tables.gsub.lookups; + return lookups[index] || null; + }; + + /** + * Get lookup tables for a feature + * @param {string} feature + */ + FeatureQuery.prototype.getFeatureLookups = function (feature) { + // TODO: memoize + return feature.lookupListIndexes.map(this.getLookupByIndex.bind(this)); + }; + + /** + * Query a feature by it's properties + * @param {any} query an object that describes the properties of a query + */ + FeatureQuery.prototype.getFeature = function getFeature(query) { + if (!this.font) { return { FAIL: "No font was found"}; } + if (!this.features.hasOwnProperty(query.script)) { + this.getScriptFeatures(query.script); + } + var scriptFeatures = this.features[query.script]; + if (!scriptFeatures) { return ( + { FAIL: ("No feature for script " + (query.script))} + ); } + if (!scriptFeatures.tags[query.tag]) { return null; } + return this.features[query.script].tags[query.tag]; + }; + + /** + * Arabic word context checkers + */ + + function arabicWordStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? arabic first char + (prevChar === null && isArabicChar(char)) || + // ? arabic char preceded with a non arabic char + (!isArabicChar(prevChar) && isArabicChar(char)) + ); + } + + function arabicWordEndCheck(contextParams) { + var nextChar = contextParams.get(1); + return ( + // ? last arabic char + (nextChar === null) || + // ? next char is not arabic + (!isArabicChar(nextChar)) + ); + } + + var arabicWordCheck = { + startCheck: arabicWordStartCheck, + endCheck: arabicWordEndCheck + }; + + /** + * Arabic sentence context checkers + */ + + function arabicSentenceStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? an arabic char preceded with a non arabic char + (isArabicChar(char) || isTashkeelArabicChar(char)) && + !isArabicChar(prevChar) + ); + } + + function arabicSentenceEndCheck(contextParams) { + var nextChar = contextParams.get(1); + switch (true) { + case nextChar === null: + return true; + case (!isArabicChar(nextChar) && !isTashkeelArabicChar(nextChar)): + var nextIsWhitespace = isWhiteSpace(nextChar); + if (!nextIsWhitespace) { return true; } + if (nextIsWhitespace) { + var arabicCharAhead = false; + arabicCharAhead = ( + contextParams.lookahead.some( + function (c) { return isArabicChar(c) || isTashkeelArabicChar(c); } + ) + ); + if (!arabicCharAhead) { return true; } + } + break; + default: + return false; + } + } + + var arabicSentenceCheck = { + startCheck: arabicSentenceStartCheck, + endCheck: arabicSentenceEndCheck + }; + + /** + * Apply single substitution format 1 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ + function singleSubstitutionFormat1$1(action, tokens, index) { + tokens[index].setState(action.tag, action.substitution); + } + + /** + * Apply single substitution format 2 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ + function singleSubstitutionFormat2$1(action, tokens, index) { + tokens[index].setState(action.tag, action.substitution); + } + + /** + * Apply chaining context substitution format 3 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ + function chainingSubstitutionFormat3$1(action, tokens, index) { + action.substitution.forEach(function (subst, offset) { + var token = tokens[index + offset]; + token.setState(action.tag, subst); + }); + } + + /** + * Apply ligature substitution format 1 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ + function ligatureSubstitutionFormat1$1(action, tokens, index) { + var token = tokens[index]; + token.setState(action.tag, action.substitution.ligGlyph); + var compsCount = action.substitution.components.length; + for (var i = 0; i < compsCount; i++) { + token = tokens[index + i + 1]; + token.setState('deleted', true); + } + } + + /** + * Supported substitutions + */ + var SUBSTITUTIONS = { + 11: singleSubstitutionFormat1$1, + 12: singleSubstitutionFormat2$1, + 63: chainingSubstitutionFormat3$1, + 41: ligatureSubstitutionFormat1$1 + }; + + /** + * Apply substitutions to a list of tokens + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ + function applySubstitution(action, tokens, index) { + if (action instanceof SubstitutionAction && SUBSTITUTIONS[action.id]) { + SUBSTITUTIONS[action.id](action, tokens, index); + } + } + + /** + * Apply Arabic presentation forms to a range of tokens + */ + + /** + * Check if a char can be connected to it's preceding char + * @param {ContextParams} charContextParams context params of a char + */ + function willConnectPrev(charContextParams) { + var backtrack = [].concat(charContextParams.backtrack); + for (var i = backtrack.length - 1; i >= 0; i--) { + var prevChar = backtrack[i]; + var isolated = isIsolatedArabicChar(prevChar); + var tashkeel = isTashkeelArabicChar(prevChar); + if (!isolated && !tashkeel) { return true; } + if (isolated) { return false; } + } + return false; + } + + /** + * Check if a char can be connected to it's proceeding char + * @param {ContextParams} charContextParams context params of a char + */ + function willConnectNext(charContextParams) { + if (isIsolatedArabicChar(charContextParams.current)) { return false; } + for (var i = 0; i < charContextParams.lookahead.length; i++) { + var nextChar = charContextParams.lookahead[i]; + var tashkeel = isTashkeelArabicChar(nextChar); + if (!tashkeel) { return true; } + } + return false; + } + + /** + * Apply arabic presentation forms to a list of tokens + * @param {ContextRange} range a range of tokens + */ + function arabicPresentationForms(range) { + var this$1 = this; + + var script = 'arab'; + var tags = this.featuresTags[script]; + var tokens = this.tokenizer.getRangeTokens(range); + if (tokens.length === 1) { return; } + var contextParams = new ContextParams( + tokens.map(function (token) { return token.getState('glyphIndex'); } + ), 0); + var charContextParams = new ContextParams( + tokens.map(function (token) { return token.char; } + ), 0); + tokens.forEach(function (token, index) { + if (isTashkeelArabicChar(token.char)) { return; } + contextParams.setCurrentIndex(index); + charContextParams.setCurrentIndex(index); + var CONNECT = 0; // 2 bits 00 (10: can connect next) (01: can connect prev) + if (willConnectPrev(charContextParams)) { CONNECT |= 1; } + if (willConnectNext(charContextParams)) { CONNECT |= 2; } + var tag; + switch (CONNECT) { + case 1: (tag = 'fina'); break; + case 2: (tag = 'init'); break; + case 3: (tag = 'medi'); break; + } + if (tags.indexOf(tag) === -1) { return; } + var substitutions = this$1.query.lookupFeature({ + tag: tag, script: script, contextParams: contextParams + }); + if (substitutions instanceof Error) { return console.info(substitutions.message); } + substitutions.forEach(function (action, index) { + if (action instanceof SubstitutionAction) { + applySubstitution(action, tokens, index); + contextParams.context[index] = action.substitution; + } + }); + }); + } + + /** + * Apply Arabic required ligatures feature to a range of tokens + */ + + /** + * Update context params + * @param {any} tokens a list of tokens + * @param {number} index current item index + */ + function getContextParams(tokens, index) { + var context = tokens.map(function (token) { return token.activeState.value; }); + return new ContextParams(context, index || 0); + } + + /** + * Apply Arabic required ligatures to a context range + * @param {ContextRange} range a range of tokens + */ + function arabicRequiredLigatures(range) { + var this$1 = this; + + var script = 'arab'; + var tokens = this.tokenizer.getRangeTokens(range); + var contextParams = getContextParams(tokens); + contextParams.context.forEach(function (glyphIndex, index) { + contextParams.setCurrentIndex(index); + var substitutions = this$1.query.lookupFeature({ + tag: 'rlig', script: script, contextParams: contextParams + }); + if (substitutions.length) { + substitutions.forEach( + function (action) { return applySubstitution(action, tokens, index); } + ); + contextParams = getContextParams(tokens); + } + }); + } + + /** + * Latin word context checkers + */ + + function latinWordStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? latin first char + (prevChar === null && isLatinChar(char)) || + // ? latin char preceded with a non latin char + (!isLatinChar(prevChar) && isLatinChar(char)) + ); + } + + function latinWordEndCheck(contextParams) { + var nextChar = contextParams.get(1); + return ( + // ? last latin char + (nextChar === null) || + // ? next char is not latin + (!isLatinChar(nextChar)) + ); + } + + var latinWordCheck = { + startCheck: latinWordStartCheck, + endCheck: latinWordEndCheck + }; + + /** + * Apply Latin ligature feature to a range of tokens + */ + + /** + * Update context params + * @param {any} tokens a list of tokens + * @param {number} index current item index + */ + function getContextParams$1(tokens, index) { + var context = tokens.map(function (token) { return token.activeState.value; }); + return new ContextParams(context, index || 0); + } + + /** + * Apply Arabic required ligatures to a context range + * @param {ContextRange} range a range of tokens + */ + function latinLigature(range) { + var this$1 = this; + + var script = 'latn'; + var tokens = this.tokenizer.getRangeTokens(range); + var contextParams = getContextParams$1(tokens); + contextParams.context.forEach(function (glyphIndex, index) { + contextParams.setCurrentIndex(index); + var substitutions = this$1.query.lookupFeature({ + tag: 'liga', script: script, contextParams: contextParams + }); + if (substitutions.length) { + substitutions.forEach( + function (action) { return applySubstitution(action, tokens, index); } + ); + contextParams = getContextParams$1(tokens); + } + }); + } + + /** + * Infer bidirectional properties for a given text and apply + * the corresponding layout rules. + */ + + /** + * Create Bidi. features + * @param {string} baseDir text base direction. value either 'ltr' or 'rtl' + */ + function Bidi(baseDir) { + this.baseDir = baseDir || 'ltr'; + this.tokenizer = new Tokenizer(); + this.featuresTags = {}; + } + + /** + * Sets Bidi text + * @param {string} text a text input + */ + Bidi.prototype.setText = function (text) { + this.text = text; + }; + + /** + * Store essential context checks: + * arabic word check for applying gsub features + * arabic sentence check for adjusting arabic layout + */ + Bidi.prototype.contextChecks = ({ + latinWordCheck: latinWordCheck, + arabicWordCheck: arabicWordCheck, + arabicSentenceCheck: arabicSentenceCheck + }); + + /** + * Register arabic word check + */ + function registerContextChecker(checkId) { + var check = this.contextChecks[(checkId + "Check")]; + return this.tokenizer.registerContextChecker( + checkId, check.startCheck, check.endCheck + ); + } + + /** + * Perform pre tokenization procedure then + * tokenize text input + */ + function tokenizeText() { + registerContextChecker.call(this, 'latinWord'); + registerContextChecker.call(this, 'arabicWord'); + registerContextChecker.call(this, 'arabicSentence'); + return this.tokenizer.tokenize(this.text); + } + + /** + * Reverse arabic sentence layout + * TODO: check base dir before applying adjustments - priority low + */ + function reverseArabicSentences() { + var this$1 = this; + + var ranges = this.tokenizer.getContextRanges('arabicSentence'); + ranges.forEach(function (range) { + var rangeTokens = this$1.tokenizer.getRangeTokens(range); + this$1.tokenizer.replaceRange( + range.startIndex, + range.endOffset, + rangeTokens.reverse() + ); + }); + } + + /** + * Register supported features tags + * @param {script} script script tag + * @param {Array} tags features tags list + */ + Bidi.prototype.registerFeatures = function (script, tags) { + var this$1 = this; + + var supportedTags = tags.filter( + function (tag) { return this$1.query.supports({script: script, tag: tag}); } + ); + if (!this.featuresTags.hasOwnProperty(script)) { + this.featuresTags[script] = supportedTags; + } else { + this.featuresTags[script] = + this.featuresTags[script].concat(supportedTags); + } + }; + + /** + * Apply GSUB features + * @param {Array} tagsList a list of features tags + * @param {string} script a script tag + * @param {Font} font opentype font instance + */ + Bidi.prototype.applyFeatures = function (font, features) { + if (!font) { throw new Error( + 'No valid font was provided to apply features' + ); } + if (!this.query) { this.query = new FeatureQuery(font); } + for (var f = 0; f < features.length; f++) { + var feature = features[f]; + if (!this.query.supports({script: feature.script})) { continue; } + this.registerFeatures(feature.script, feature.tags); + } + }; + + /** + * Register a state modifier + * @param {string} modifierId state modifier id + * @param {function} condition a predicate function that returns true or false + * @param {function} modifier a modifier function to set token state + */ + Bidi.prototype.registerModifier = function (modifierId, condition, modifier) { + this.tokenizer.registerModifier(modifierId, condition, modifier); + }; + + /** + * Check if 'glyphIndex' is registered + */ + function checkGlyphIndexStatus() { + if (this.tokenizer.registeredModifiers.indexOf('glyphIndex') === -1) { + throw new Error( + 'glyphIndex modifier is required to apply ' + + 'arabic presentation features.' + ); + } + } + + /** + * Apply arabic presentation forms features + */ + function applyArabicPresentationForms() { + var this$1 = this; + + var script = 'arab'; + if (!this.featuresTags.hasOwnProperty(script)) { return; } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges('arabicWord'); + ranges.forEach(function (range) { + arabicPresentationForms.call(this$1, range); + }); + } + + /** + * Apply required arabic ligatures + */ + function applyArabicRequireLigatures() { + var this$1 = this; + + var script = 'arab'; + if (!this.featuresTags.hasOwnProperty(script)) { return; } + var tags = this.featuresTags[script]; + if (tags.indexOf('rlig') === -1) { return; } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges('arabicWord'); + ranges.forEach(function (range) { + arabicRequiredLigatures.call(this$1, range); + }); + } + + /** + * Apply required arabic ligatures + */ + function applyLatinLigatures() { + var this$1 = this; + + var script = 'latn'; + if (!this.featuresTags.hasOwnProperty(script)) { return; } + var tags = this.featuresTags[script]; + if (tags.indexOf('liga') === -1) { return; } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges('latinWord'); + ranges.forEach(function (range) { + latinLigature.call(this$1, range); + }); + } + + /** + * Check if a context is registered + * @param {string} contextId context id + */ + Bidi.prototype.checkContextReady = function (contextId) { + return !!this.tokenizer.getContext(contextId); + }; + + /** + * Apply features to registered contexts + */ + Bidi.prototype.applyFeaturesToContexts = function () { + if (this.checkContextReady('arabicWord')) { + applyArabicPresentationForms.call(this); + applyArabicRequireLigatures.call(this); + } + if (this.checkContextReady('latinWord')) { + applyLatinLigatures.call(this); + } + if (this.checkContextReady('arabicSentence')) { + reverseArabicSentences.call(this); + } + }; + + /** + * process text input + * @param {string} text an input text + */ + Bidi.prototype.processText = function(text) { + if (!this.text || this.text !== text) { + this.setText(text); + tokenizeText.call(this); + this.applyFeaturesToContexts(); + } + }; + + /** + * Process a string of text to identify and adjust + * bidirectional text entities. + * @param {string} text input text + */ + Bidi.prototype.getBidiText = function (text) { + this.processText(text); + return this.tokenizer.getText(); + }; + + /** + * Get the current state index of each token + * @param {text} text an input text + */ + Bidi.prototype.getTextGlyphs = function (text) { + this.processText(text); + var indexes = []; + for (var i = 0; i < this.tokenizer.tokens.length; i++) { + var token = this.tokenizer.tokens[i]; + if (token.state.deleted) { continue; } + var index = token.activeState.value; + indexes.push(Array.isArray(index) ? index[0] : index); + } + return indexes; + }; + + // The Font object + + /** + * @typedef FontOptions + * @type Object + * @property {Boolean} empty - whether to create a new empty font + * @property {string} familyName + * @property {string} styleName + * @property {string=} fullName + * @property {string=} postScriptName + * @property {string=} designer + * @property {string=} designerURL + * @property {string=} manufacturer + * @property {string=} manufacturerURL + * @property {string=} license + * @property {string=} licenseURL + * @property {string=} version + * @property {string=} description + * @property {string=} copyright + * @property {string=} trademark + * @property {Number} unitsPerEm + * @property {Number} ascender + * @property {Number} descender + * @property {Number} createdTimestamp + * @property {string=} weightClass + * @property {string=} widthClass + * @property {string=} fsSelection + */ + + /** + * A Font represents a loaded OpenType font file. + * It contains a set of glyphs and methods to draw text on a drawing context, + * or to get a path representing the text. + * @exports opentype.Font + * @class + * @param {FontOptions} + * @constructor + */ + function Font(options) { + options = options || {}; + options.tables = options.tables || {}; + + if (!options.empty) { + // Check that we've provided the minimum set of names. + checkArgument(options.familyName, 'When creating a new Font object, familyName is required.'); + checkArgument(options.styleName, 'When creating a new Font object, styleName is required.'); + checkArgument(options.unitsPerEm, 'When creating a new Font object, unitsPerEm is required.'); + checkArgument(options.ascender, 'When creating a new Font object, ascender is required.'); + checkArgument(options.descender <= 0, 'When creating a new Font object, negative descender value is required.'); + + // OS X will complain if the names are empty, so we put a single space everywhere by default. + this.names = { + fontFamily: {en: options.familyName || ' '}, + fontSubfamily: {en: options.styleName || ' '}, + fullName: {en: options.fullName || options.familyName + ' ' + options.styleName}, + // postScriptName may not contain any whitespace + postScriptName: {en: options.postScriptName || (options.familyName + options.styleName).replace(/\s/g, '')}, + designer: {en: options.designer || ' '}, + designerURL: {en: options.designerURL || ' '}, + manufacturer: {en: options.manufacturer || ' '}, + manufacturerURL: {en: options.manufacturerURL || ' '}, + license: {en: options.license || ' '}, + licenseURL: {en: options.licenseURL || ' '}, + version: {en: options.version || 'Version 0.1'}, + description: {en: options.description || ' '}, + copyright: {en: options.copyright || ' '}, + trademark: {en: options.trademark || ' '} + }; + this.unitsPerEm = options.unitsPerEm || 1000; + this.ascender = options.ascender; + this.descender = options.descender; + this.createdTimestamp = options.createdTimestamp; + this.tables = Object.assign(options.tables, { + os2: Object.assign({ + usWeightClass: options.weightClass || this.usWeightClasses.MEDIUM, + usWidthClass: options.widthClass || this.usWidthClasses.MEDIUM, + fsSelection: options.fsSelection || this.fsSelectionValues.REGULAR, + }, options.tables.os2) + }); + } + + this.supported = true; // Deprecated: parseBuffer will throw an error if font is not supported. + this.glyphs = new glyphset.GlyphSet(this, options.glyphs || []); + this.encoding = new DefaultEncoding(this); + this.position = new Position(this); + this.substitution = new Substitution(this); + this.tables = this.tables || {}; + + // needed for low memory mode only. + this._push = null; + this._hmtxTableData = {}; + + Object.defineProperty(this, 'hinting', { + get: function() { + if (this._hinting) { return this._hinting; } + if (this.outlinesFormat === 'truetype') { + return (this._hinting = new Hinting(this)); + } + } + }); + } + + /** + * Check if the font has a glyph for the given character. + * @param {string} + * @return {Boolean} + */ + Font.prototype.hasChar = function(c) { + return this.encoding.charToGlyphIndex(c) !== null; + }; + + /** + * Convert the given character to a single glyph index. + * Note that this function assumes that there is a one-to-one mapping between + * the given character and a glyph; for complex scripts this might not be the case. + * @param {string} + * @return {Number} + */ + Font.prototype.charToGlyphIndex = function(s) { + return this.encoding.charToGlyphIndex(s); + }; + + /** + * Convert the given character to a single Glyph object. + * Note that this function assumes that there is a one-to-one mapping between + * the given character and a glyph; for complex scripts this might not be the case. + * @param {string} + * @return {opentype.Glyph} + */ + Font.prototype.charToGlyph = function(c) { + var glyphIndex = this.charToGlyphIndex(c); + var glyph = this.glyphs.get(glyphIndex); + if (!glyph) { + // .notdef + glyph = this.glyphs.get(0); + } + + return glyph; + }; + + /** + * Update features + * @param {any} options features options + */ + Font.prototype.updateFeatures = function (options) { + // TODO: update all features options not only 'latn'. + return this.defaultRenderOptions.features.map(function (feature) { + if (feature.script === 'latn') { + return { + script: 'latn', + tags: feature.tags.filter(function (tag) { return options[tag]; }) + }; + } else { + return feature; + } + }); + }; + + /** + * Convert the given text to a list of Glyph objects. + * Note that there is no strict one-to-one mapping between characters and + * glyphs, so the list of returned glyphs can be larger or smaller than the + * length of the given string. + * @param {string} + * @param {GlyphRenderOptions} [options] + * @return {opentype.Glyph[]} + */ + Font.prototype.stringToGlyphs = function(s, options) { + var this$1 = this; + + + var bidi = new Bidi(); + + // Create and register 'glyphIndex' state modifier + var charToGlyphIndexMod = function (token) { return this$1.charToGlyphIndex(token.char); }; + bidi.registerModifier('glyphIndex', null, charToGlyphIndexMod); + + // roll-back to default features + var features = options ? + this.updateFeatures(options.features) : + this.defaultRenderOptions.features; + + bidi.applyFeatures(this, features); + + var indexes = bidi.getTextGlyphs(s); + + var length = indexes.length; + + // convert glyph indexes to glyph objects + var glyphs = new Array(length); + var notdef = this.glyphs.get(0); + for (var i = 0; i < length; i += 1) { + glyphs[i] = this.glyphs.get(indexes[i]) || notdef; + } + return glyphs; + }; + + /** + * @param {string} + * @return {Number} + */ + Font.prototype.nameToGlyphIndex = function(name) { + return this.glyphNames.nameToGlyphIndex(name); + }; + + /** + * @param {string} + * @return {opentype.Glyph} + */ + Font.prototype.nameToGlyph = function(name) { + var glyphIndex = this.nameToGlyphIndex(name); + var glyph = this.glyphs.get(glyphIndex); + if (!glyph) { + // .notdef + glyph = this.glyphs.get(0); + } + + return glyph; + }; + + /** + * @param {Number} + * @return {String} + */ + Font.prototype.glyphIndexToName = function(gid) { + if (!this.glyphNames.glyphIndexToName) { + return ''; + } + + return this.glyphNames.glyphIndexToName(gid); + }; + + /** + * Retrieve the value of the kerning pair between the left glyph (or its index) + * and the right glyph (or its index). If no kerning pair is found, return 0. + * The kerning value gets added to the advance width when calculating the spacing + * between glyphs. + * For GPOS kerning, this method uses the default script and language, which covers + * most use cases. To have greater control, use font.position.getKerningValue . + * @param {opentype.Glyph} leftGlyph + * @param {opentype.Glyph} rightGlyph + * @return {Number} + */ + Font.prototype.getKerningValue = function(leftGlyph, rightGlyph) { + leftGlyph = leftGlyph.index || leftGlyph; + rightGlyph = rightGlyph.index || rightGlyph; + var gposKerning = this.position.defaultKerningTables; + if (gposKerning) { + return this.position.getKerningValue(gposKerning, leftGlyph, rightGlyph); + } + // "kern" table + return this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0; + }; + + /** + * @typedef GlyphRenderOptions + * @type Object + * @property {string} [script] - script used to determine which features to apply. By default, 'DFLT' or 'latn' is used. + * See https://www.microsoft.com/typography/otspec/scripttags.htm + * @property {string} [language='dflt'] - language system used to determine which features to apply. + * See https://www.microsoft.com/typography/developers/opentype/languagetags.aspx + * @property {boolean} [kerning=true] - whether to include kerning values + * @property {object} [features] - OpenType Layout feature tags. Used to enable or disable the features of the given script/language system. + * See https://www.microsoft.com/typography/otspec/featuretags.htm + */ + Font.prototype.defaultRenderOptions = { + kerning: true, + features: [ + /** + * these 4 features are required to render Arabic text properly + * and shouldn't be turned off when rendering arabic text. + */ + { script: 'arab', tags: ['init', 'medi', 'fina', 'rlig'] }, + { script: 'latn', tags: ['liga', 'rlig'] } + ] + }; + + /** + * Helper function that invokes the given callback for each glyph in the given text. + * The callback gets `(glyph, x, y, fontSize, options)`.* @param {string} text + * @param {string} text - The text to apply. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @param {Function} callback + */ + Font.prototype.forEachGlyph = function(text, x, y, fontSize, options, callback) { + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 72; + options = Object.assign({}, this.defaultRenderOptions, options); + var fontScale = 1 / this.unitsPerEm * fontSize; + var glyphs = this.stringToGlyphs(text, options); + var kerningLookups; + if (options.kerning) { + var script = options.script || this.position.getDefaultScriptName(); + kerningLookups = this.position.getKerningTables(script, options.language); + } + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs[i]; + callback.call(this, glyph, x, y, fontSize, options); + if (glyph.advanceWidth) { + x += glyph.advanceWidth * fontScale; + } + + if (options.kerning && i < glyphs.length - 1) { + // We should apply position adjustment lookups in a more generic way. + // Here we only use the xAdvance value. + var kerningValue = kerningLookups ? + this.position.getKerningValue(kerningLookups, glyph.index, glyphs[i + 1].index) : + this.getKerningValue(glyph, glyphs[i + 1]); + x += kerningValue * fontScale; + } + + if (options.letterSpacing) { + x += options.letterSpacing * fontSize; + } else if (options.tracking) { + x += (options.tracking / 1000) * fontSize; + } + } + return x; + }; + + /** + * Create a Path object that represents the given text. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @return {opentype.Path} + */ + Font.prototype.getPath = function(text, x, y, fontSize, options) { + var fullPath = new Path(); + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); + fullPath.extend(glyphPath); + }); + return fullPath; + }; + + /** + * Create an array of Path objects that represent the glyphs of a given text. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @return {opentype.Path[]} + */ + Font.prototype.getPaths = function(text, x, y, fontSize, options) { + var glyphPaths = []; + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); + glyphPaths.push(glyphPath); + }); + + return glyphPaths; + }; + + /** + * Returns the advance width of a text. + * + * This is something different than Path.getBoundingBox() as for example a + * suffixed whitespace increases the advanceWidth but not the bounding box + * or an overhanging letter like a calligraphic 'f' might have a quite larger + * bounding box than its advance width. + * + * This corresponds to canvas2dContext.measureText(text).width + * + * @param {string} text - The text to create. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @return advance width + */ + Font.prototype.getAdvanceWidth = function(text, fontSize, options) { + return this.forEachGlyph(text, 0, 0, fontSize, options, function() {}); + }; + + /** + * Draw the text on the given drawing context. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + */ + Font.prototype.draw = function(ctx, text, x, y, fontSize, options) { + this.getPath(text, x, y, fontSize, options).draw(ctx); + }; + + /** + * Draw the points of all glyphs in the text. + * On-curve points will be drawn in blue, off-curve points will be drawn in red. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + */ + Font.prototype.drawPoints = function(ctx, text, x, y, fontSize, options) { + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + glyph.drawPoints(ctx, gX, gY, gFontSize); + }); + }; + + /** + * Draw lines indicating important font measurements for all glyphs in the text. + * Black lines indicate the origin of the coordinate system (point 0,0). + * Blue lines indicate the glyph bounding box. + * Green line indicates the advance width of the glyph. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + */ + Font.prototype.drawMetrics = function(ctx, text, x, y, fontSize, options) { + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + glyph.drawMetrics(ctx, gX, gY, gFontSize); + }); + }; + + /** + * @param {string} + * @return {string} + */ + Font.prototype.getEnglishName = function(name) { + var translations = this.names[name]; + if (translations) { + return translations.en; + } + }; + + /** + * Validate + */ + Font.prototype.validate = function() { + var _this = this; + + function assert(predicate, message) { + } + + function assertNamePresent(name) { + var englishName = _this.getEnglishName(name); + assert(englishName && englishName.trim().length > 0); + } + + // Identification information + assertNamePresent('fontFamily'); + assertNamePresent('weightName'); + assertNamePresent('manufacturer'); + assertNamePresent('copyright'); + assertNamePresent('version'); + + // Dimension information + assert(this.unitsPerEm > 0); + }; + + /** + * Convert the font object to a SFNT data structure. + * This structure contains all the necessary tables and metadata to create a binary OTF file. + * @return {opentype.Table} + */ + Font.prototype.toTables = function() { + return sfnt.fontToTable(this); + }; + /** + * @deprecated Font.toBuffer is deprecated. Use Font.toArrayBuffer instead. + */ + Font.prototype.toBuffer = function() { + console.warn('Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.'); + return this.toArrayBuffer(); + }; + /** + * Converts a `opentype.Font` into an `ArrayBuffer` + * @return {ArrayBuffer} + */ + Font.prototype.toArrayBuffer = function() { + var sfntTable = this.toTables(); + var bytes = sfntTable.encode(); + var buffer = new ArrayBuffer(bytes.length); + var intArray = new Uint8Array(buffer); + for (var i = 0; i < bytes.length; i++) { + intArray[i] = bytes[i]; + } + + return buffer; + }; + + /** + * Initiate a download of the OpenType font. + */ + Font.prototype.download = function(fileName) { + var familyName = this.getEnglishName('fontFamily'); + var styleName = this.getEnglishName('fontSubfamily'); + fileName = fileName || familyName.replace(/\s/g, '') + '-' + styleName + '.otf'; + var arrayBuffer = this.toArrayBuffer(); + + if (isBrowser()) { + window.URL = window.URL || window.webkitURL; + + if (window.URL) { + var dataView = new DataView(arrayBuffer); + var blob = new Blob([dataView], {type: 'font/opentype'}); + + var link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = fileName; + + var event = document.createEvent('MouseEvents'); + event.initEvent('click', true, false); + link.dispatchEvent(event); + } else { + console.warn('Font file could not be downloaded. Try using a different browser.'); + } + } else { + var fs = require('fs'); + var buffer = arrayBufferToNodeBuffer(arrayBuffer); + fs.writeFileSync(fileName, buffer); + } + }; + /** + * @private + */ + Font.prototype.fsSelectionValues = { + ITALIC: 0x001, //1 + UNDERSCORE: 0x002, //2 + NEGATIVE: 0x004, //4 + OUTLINED: 0x008, //8 + STRIKEOUT: 0x010, //16 + BOLD: 0x020, //32 + REGULAR: 0x040, //64 + USER_TYPO_METRICS: 0x080, //128 + WWS: 0x100, //256 + OBLIQUE: 0x200 //512 + }; + + /** + * @private + */ + Font.prototype.usWidthClasses = { + ULTRA_CONDENSED: 1, + EXTRA_CONDENSED: 2, + CONDENSED: 3, + SEMI_CONDENSED: 4, + MEDIUM: 5, + SEMI_EXPANDED: 6, + EXPANDED: 7, + EXTRA_EXPANDED: 8, + ULTRA_EXPANDED: 9 + }; + + /** + * @private + */ + Font.prototype.usWeightClasses = { + THIN: 100, + EXTRA_LIGHT: 200, + LIGHT: 300, + NORMAL: 400, + MEDIUM: 500, + SEMI_BOLD: 600, + BOLD: 700, + EXTRA_BOLD: 800, + BLACK: 900 + }; + + // The `fvar` table stores font variation axes and instances. + + function addName(name, names) { + var nameString = JSON.stringify(name); + var nameID = 256; + for (var nameKey in names) { + var n = parseInt(nameKey); + if (!n || n < 256) { + continue; + } + + if (JSON.stringify(names[nameKey]) === nameString) { + return n; + } + + if (nameID <= n) { + nameID = n + 1; + } + } + + names[nameID] = name; + return nameID; + } + + function makeFvarAxis(n, axis, names) { + var nameID = addName(axis.name, names); + return [ + {name: 'tag_' + n, type: 'TAG', value: axis.tag}, + {name: 'minValue_' + n, type: 'FIXED', value: axis.minValue << 16}, + {name: 'defaultValue_' + n, type: 'FIXED', value: axis.defaultValue << 16}, + {name: 'maxValue_' + n, type: 'FIXED', value: axis.maxValue << 16}, + {name: 'flags_' + n, type: 'USHORT', value: 0}, + {name: 'nameID_' + n, type: 'USHORT', value: nameID} + ]; + } + + function parseFvarAxis(data, start, names) { + var axis = {}; + var p = new parse.Parser(data, start); + axis.tag = p.parseTag(); + axis.minValue = p.parseFixed(); + axis.defaultValue = p.parseFixed(); + axis.maxValue = p.parseFixed(); + p.skip('uShort', 1); // reserved for flags; no values defined + axis.name = names[p.parseUShort()] || {}; + return axis; + } + + function makeFvarInstance(n, inst, axes, names) { + var nameID = addName(inst.name, names); + var fields = [ + {name: 'nameID_' + n, type: 'USHORT', value: nameID}, + {name: 'flags_' + n, type: 'USHORT', value: 0} + ]; + + for (var i = 0; i < axes.length; ++i) { + var axisTag = axes[i].tag; + fields.push({ + name: 'axis_' + n + ' ' + axisTag, + type: 'FIXED', + value: inst.coordinates[axisTag] << 16 + }); + } + + return fields; + } + + function parseFvarInstance(data, start, axes, names) { + var inst = {}; + var p = new parse.Parser(data, start); + inst.name = names[p.parseUShort()] || {}; + p.skip('uShort', 1); // reserved for flags; no values defined + + inst.coordinates = {}; + for (var i = 0; i < axes.length; ++i) { + inst.coordinates[axes[i].tag] = p.parseFixed(); + } + + return inst; + } + + function makeFvarTable(fvar, names) { + var result = new table.Table('fvar', [ + {name: 'version', type: 'ULONG', value: 0x10000}, + {name: 'offsetToData', type: 'USHORT', value: 0}, + {name: 'countSizePairs', type: 'USHORT', value: 2}, + {name: 'axisCount', type: 'USHORT', value: fvar.axes.length}, + {name: 'axisSize', type: 'USHORT', value: 20}, + {name: 'instanceCount', type: 'USHORT', value: fvar.instances.length}, + {name: 'instanceSize', type: 'USHORT', value: 4 + fvar.axes.length * 4} + ]); + result.offsetToData = result.sizeOf(); + + for (var i = 0; i < fvar.axes.length; i++) { + result.fields = result.fields.concat(makeFvarAxis(i, fvar.axes[i], names)); + } + + for (var j = 0; j < fvar.instances.length; j++) { + result.fields = result.fields.concat(makeFvarInstance(j, fvar.instances[j], fvar.axes, names)); + } + + return result; + } + + function parseFvarTable(data, start, names) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 0x00010000, 'Unsupported fvar table version.'); + var offsetToData = p.parseOffset16(); + // Skip countSizePairs. + p.skip('uShort', 1); + var axisCount = p.parseUShort(); + var axisSize = p.parseUShort(); + var instanceCount = p.parseUShort(); + var instanceSize = p.parseUShort(); + + var axes = []; + for (var i = 0; i < axisCount; i++) { + axes.push(parseFvarAxis(data, start + offsetToData + i * axisSize, names)); + } + + var instances = []; + var instanceStart = start + offsetToData + axisCount * axisSize; + for (var j = 0; j < instanceCount; j++) { + instances.push(parseFvarInstance(data, instanceStart + j * instanceSize, axes, names)); + } + + return {axes: axes, instances: instances}; + } + + var fvar = { make: makeFvarTable, parse: parseFvarTable }; + + // The `GPOS` table contains kerning pairs, among other things. + + var subtableParsers$1 = new Array(10); // subtableParsers[0] is unused + + // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-1-single-adjustment-positioning-subtable + // this = Parser instance + subtableParsers$1[1] = function parseLookup1() { + var start = this.offset + this.relativeOffset; + var posformat = this.parseUShort(); + if (posformat === 1) { + return { + posFormat: 1, + coverage: this.parsePointer(Parser.coverage), + value: this.parseValueRecord() + }; + } else if (posformat === 2) { + return { + posFormat: 2, + coverage: this.parsePointer(Parser.coverage), + values: this.parseValueRecordList() + }; + } + check.assert(false, '0x' + start.toString(16) + ': GPOS lookup type 1 format must be 1 or 2.'); + }; + + // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-2-pair-adjustment-positioning-subtable + subtableParsers$1[2] = function parseLookup2() { + var start = this.offset + this.relativeOffset; + var posFormat = this.parseUShort(); + check.assert(posFormat === 1 || posFormat === 2, '0x' + start.toString(16) + ': GPOS lookup type 2 format must be 1 or 2.'); + var coverage = this.parsePointer(Parser.coverage); + var valueFormat1 = this.parseUShort(); + var valueFormat2 = this.parseUShort(); + if (posFormat === 1) { + // Adjustments for Glyph Pairs + return { + posFormat: posFormat, + coverage: coverage, + valueFormat1: valueFormat1, + valueFormat2: valueFormat2, + pairSets: this.parseList(Parser.pointer(Parser.list(function() { + return { // pairValueRecord + secondGlyph: this.parseUShort(), + value1: this.parseValueRecord(valueFormat1), + value2: this.parseValueRecord(valueFormat2) + }; + }))) + }; + } else if (posFormat === 2) { + var classDef1 = this.parsePointer(Parser.classDef); + var classDef2 = this.parsePointer(Parser.classDef); + var class1Count = this.parseUShort(); + var class2Count = this.parseUShort(); + return { + // Class Pair Adjustment + posFormat: posFormat, + coverage: coverage, + valueFormat1: valueFormat1, + valueFormat2: valueFormat2, + classDef1: classDef1, + classDef2: classDef2, + class1Count: class1Count, + class2Count: class2Count, + classRecords: this.parseList(class1Count, Parser.list(class2Count, function() { + return { + value1: this.parseValueRecord(valueFormat1), + value2: this.parseValueRecord(valueFormat2) + }; + })) + }; + } + }; + + subtableParsers$1[3] = function parseLookup3() { return { error: 'GPOS Lookup 3 not supported' }; }; + subtableParsers$1[4] = function parseLookup4() { return { error: 'GPOS Lookup 4 not supported' }; }; + subtableParsers$1[5] = function parseLookup5() { return { error: 'GPOS Lookup 5 not supported' }; }; + subtableParsers$1[6] = function parseLookup6() { return { error: 'GPOS Lookup 6 not supported' }; }; + subtableParsers$1[7] = function parseLookup7() { return { error: 'GPOS Lookup 7 not supported' }; }; + subtableParsers$1[8] = function parseLookup8() { return { error: 'GPOS Lookup 8 not supported' }; }; + subtableParsers$1[9] = function parseLookup9() { return { error: 'GPOS Lookup 9 not supported' }; }; + + // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos + function parseGposTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GPOS table version ' + tableVersion); + + if (tableVersion === 1) { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers$1) + }; + } else { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers$1), + variations: p.parseFeatureVariationsList() + }; + } + + } + + // GPOS Writing ////////////////////////////////////////////// + // NOT SUPPORTED + var subtableMakers$1 = new Array(10); + + function makeGposTable(gpos) { + return new table.Table('GPOS', [ + {name: 'version', type: 'ULONG', value: 0x10000}, + {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gpos.scripts)}, + {name: 'features', type: 'TABLE', value: new table.FeatureList(gpos.features)}, + {name: 'lookups', type: 'TABLE', value: new table.LookupList(gpos.lookups, subtableMakers$1)} + ]); + } + + var gpos = { parse: parseGposTable, make: makeGposTable }; + + // The `kern` table contains kerning pairs. + + function parseWindowsKernTable(p) { + var pairs = {}; + // Skip nTables. + p.skip('uShort'); + var subtableVersion = p.parseUShort(); + check.argument(subtableVersion === 0, 'Unsupported kern sub-table version.'); + // Skip subtableLength, subtableCoverage + p.skip('uShort', 2); + var nPairs = p.parseUShort(); + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + for (var i = 0; i < nPairs; i += 1) { + var leftIndex = p.parseUShort(); + var rightIndex = p.parseUShort(); + var value = p.parseShort(); + pairs[leftIndex + ',' + rightIndex] = value; + } + return pairs; + } + + function parseMacKernTable(p) { + var pairs = {}; + // The Mac kern table stores the version as a fixed (32 bits) but we only loaded the first 16 bits. + // Skip the rest. + p.skip('uShort'); + var nTables = p.parseULong(); + //check.argument(nTables === 1, 'Only 1 subtable is supported (got ' + nTables + ').'); + if (nTables > 1) { + console.warn('Only the first kern subtable is supported.'); + } + p.skip('uLong'); + var coverage = p.parseUShort(); + var subtableVersion = coverage & 0xFF; + p.skip('uShort'); + if (subtableVersion === 0) { + var nPairs = p.parseUShort(); + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + for (var i = 0; i < nPairs; i += 1) { + var leftIndex = p.parseUShort(); + var rightIndex = p.parseUShort(); + var value = p.parseShort(); + pairs[leftIndex + ',' + rightIndex] = value; + } + } + return pairs; + } + + // Parse the `kern` table which contains kerning pairs. + function parseKernTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseUShort(); + if (tableVersion === 0) { + return parseWindowsKernTable(p); + } else if (tableVersion === 1) { + return parseMacKernTable(p); + } else { + throw new Error('Unsupported kern table version (' + tableVersion + ').'); + } + } + + var kern = { parse: parseKernTable }; + + // The `loca` table stores the offsets to the locations of the glyphs in the font. + + // Parse the `loca` table. This table stores the offsets to the locations of the glyphs in the font, + // relative to the beginning of the glyphData table. + // The number of glyphs stored in the `loca` table is specified in the `maxp` table (under numGlyphs) + // The loca table has two versions: a short version where offsets are stored as uShorts, and a long + // version where offsets are stored as uLongs. The `head` table specifies which version to use + // (under indexToLocFormat). + function parseLocaTable(data, start, numGlyphs, shortVersion) { + var p = new parse.Parser(data, start); + var parseFn = shortVersion ? p.parseUShort : p.parseULong; + // There is an extra entry after the last index element to compute the length of the last glyph. + // That's why we use numGlyphs + 1. + var glyphOffsets = []; + for (var i = 0; i < numGlyphs + 1; i += 1) { + var glyphOffset = parseFn.call(p); + if (shortVersion) { + // The short table version stores the actual offset divided by 2. + glyphOffset *= 2; + } + + glyphOffsets.push(glyphOffset); + } + + return glyphOffsets; + } + + var loca = { parse: parseLocaTable }; + + // opentype.js + + /** + * The opentype library. + * @namespace opentype + */ + + // File loaders ///////////////////////////////////////////////////////// + /** + * Loads a font from a file. The callback throws an error message as the first parameter if it fails + * and the font as an ArrayBuffer in the second parameter if it succeeds. + * @param {string} path - The path of the file + * @param {Function} callback - The function to call when the font load completes + */ + function loadFromFile(path, callback) { + var fs = require('fs'); + fs.readFile(path, function(err, buffer) { + if (err) { + return callback(err.message); + } + + callback(null, nodeBufferToArrayBuffer(buffer)); + }); + } + /** + * Loads a font from a URL. The callback throws an error message as the first parameter if it fails + * and the font as an ArrayBuffer in the second parameter if it succeeds. + * @param {string} url - The URL of the font file. + * @param {Function} callback - The function to call when the font load completes + */ + function loadFromUrl(url, callback) { + var request = new XMLHttpRequest(); + request.open('get', url, true); + request.responseType = 'arraybuffer'; + request.onload = function() { + if (request.response) { + return callback(null, request.response); + } else { + return callback('Font could not be loaded: ' + request.statusText); + } + }; + + request.onerror = function () { + callback('Font could not be loaded'); + }; + + request.send(); + } + + // Table Directory Entries ////////////////////////////////////////////// + /** + * Parses OpenType table entries. + * @param {DataView} + * @param {Number} + * @return {Object[]} + */ + function parseOpenTypeTableEntries(data, numTables) { + var tableEntries = []; + var p = 12; + for (var i = 0; i < numTables; i += 1) { + var tag = parse.getTag(data, p); + var checksum = parse.getULong(data, p + 4); + var offset = parse.getULong(data, p + 8); + var length = parse.getULong(data, p + 12); + tableEntries.push({tag: tag, checksum: checksum, offset: offset, length: length, compression: false}); + p += 16; + } + + return tableEntries; + } + + /** + * Parses WOFF table entries. + * @param {DataView} + * @param {Number} + * @return {Object[]} + */ + function parseWOFFTableEntries(data, numTables) { + var tableEntries = []; + var p = 44; // offset to the first table directory entry. + for (var i = 0; i < numTables; i += 1) { + var tag = parse.getTag(data, p); + var offset = parse.getULong(data, p + 4); + var compLength = parse.getULong(data, p + 8); + var origLength = parse.getULong(data, p + 12); + var compression = (void 0); + if (compLength < origLength) { + compression = 'WOFF'; + } else { + compression = false; + } + + tableEntries.push({tag: tag, offset: offset, compression: compression, + compressedLength: compLength, length: origLength}); + p += 20; + } + + return tableEntries; + } + + /** + * @typedef TableData + * @type Object + * @property {DataView} data - The DataView + * @property {number} offset - The data offset. + */ + + /** + * @param {DataView} + * @param {Object} + * @return {TableData} + */ + function uncompressTable(data, tableEntry) { + if (tableEntry.compression === 'WOFF') { + var inBuffer = new Uint8Array(data.buffer, tableEntry.offset + 2, tableEntry.compressedLength - 2); + var outBuffer = new Uint8Array(tableEntry.length); + tinyInflate(inBuffer, outBuffer); + if (outBuffer.byteLength !== tableEntry.length) { + throw new Error('Decompression error: ' + tableEntry.tag + ' decompressed length doesn\'t match recorded length'); + } + + var view = new DataView(outBuffer.buffer, 0); + return {data: view, offset: 0}; + } else { + return {data: data, offset: tableEntry.offset}; + } + } + + // Public API /////////////////////////////////////////////////////////// + + /** + * Parse the OpenType file data (as an ArrayBuffer) and return a Font object. + * Throws an error if the font could not be parsed. + * @param {ArrayBuffer} + * @param {Object} opt - options for parsing + * @return {opentype.Font} + */ + function parseBuffer(buffer, opt) { + opt = (opt === undefined || opt === null) ? {} : opt; + + var indexToLocFormat; + var ltagTable; + + // Since the constructor can also be called to create new fonts from scratch, we indicate this + // should be an empty font that we'll fill with our own data. + var font = new Font({empty: true}); + + // OpenType fonts use big endian byte ordering. + // We can't rely on typed array view types, because they operate with the endianness of the host computer. + // Instead we use DataViews where we can specify endianness. + var data = new DataView(buffer, 0); + var numTables; + var tableEntries = []; + var signature = parse.getTag(data, 0); + if (signature === String.fromCharCode(0, 1, 0, 0) || signature === 'true' || signature === 'typ1') { + font.outlinesFormat = 'truetype'; + numTables = parse.getUShort(data, 4); + tableEntries = parseOpenTypeTableEntries(data, numTables); + } else if (signature === 'OTTO') { + font.outlinesFormat = 'cff'; + numTables = parse.getUShort(data, 4); + tableEntries = parseOpenTypeTableEntries(data, numTables); + } else if (signature === 'wOFF') { + var flavor = parse.getTag(data, 4); + if (flavor === String.fromCharCode(0, 1, 0, 0)) { + font.outlinesFormat = 'truetype'; + } else if (flavor === 'OTTO') { + font.outlinesFormat = 'cff'; + } else { + throw new Error('Unsupported OpenType flavor ' + signature); + } + + numTables = parse.getUShort(data, 12); + tableEntries = parseWOFFTableEntries(data, numTables); + } else { + throw new Error('Unsupported OpenType signature ' + signature); + } + + var cffTableEntry; + var fvarTableEntry; + var glyfTableEntry; + var gposTableEntry; + var gsubTableEntry; + var hmtxTableEntry; + var kernTableEntry; + var locaTableEntry; + var nameTableEntry; + var metaTableEntry; + var p; + + for (var i = 0; i < numTables; i += 1) { + var tableEntry = tableEntries[i]; + var table = (void 0); + switch (tableEntry.tag) { + case 'cmap': + table = uncompressTable(data, tableEntry); + font.tables.cmap = cmap.parse(table.data, table.offset); + font.encoding = new CmapEncoding(font.tables.cmap); + break; + case 'cvt ' : + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.cvt = p.parseShortList(tableEntry.length / 2); + break; + case 'fvar': + fvarTableEntry = tableEntry; + break; + case 'fpgm' : + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.fpgm = p.parseByteList(tableEntry.length); + break; + case 'head': + table = uncompressTable(data, tableEntry); + font.tables.head = head.parse(table.data, table.offset); + font.unitsPerEm = font.tables.head.unitsPerEm; + indexToLocFormat = font.tables.head.indexToLocFormat; + break; + case 'hhea': + table = uncompressTable(data, tableEntry); + font.tables.hhea = hhea.parse(table.data, table.offset); + font.ascender = font.tables.hhea.ascender; + font.descender = font.tables.hhea.descender; + font.numberOfHMetrics = font.tables.hhea.numberOfHMetrics; + break; + case 'hmtx': + hmtxTableEntry = tableEntry; + break; + case 'ltag': + table = uncompressTable(data, tableEntry); + ltagTable = ltag.parse(table.data, table.offset); + break; + case 'maxp': + table = uncompressTable(data, tableEntry); + font.tables.maxp = maxp.parse(table.data, table.offset); + font.numGlyphs = font.tables.maxp.numGlyphs; + break; + case 'name': + nameTableEntry = tableEntry; + break; + case 'OS/2': + table = uncompressTable(data, tableEntry); + font.tables.os2 = os2.parse(table.data, table.offset); + break; + case 'post': + table = uncompressTable(data, tableEntry); + font.tables.post = post.parse(table.data, table.offset); + font.glyphNames = new GlyphNames(font.tables.post); + break; + case 'prep' : + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.prep = p.parseByteList(tableEntry.length); + break; + case 'glyf': + glyfTableEntry = tableEntry; + break; + case 'loca': + locaTableEntry = tableEntry; + break; + case 'CFF ': + cffTableEntry = tableEntry; + break; + case 'kern': + kernTableEntry = tableEntry; + break; + case 'GPOS': + gposTableEntry = tableEntry; + break; + case 'GSUB': + gsubTableEntry = tableEntry; + break; + case 'meta': + metaTableEntry = tableEntry; + break; + } + } + + var nameTable = uncompressTable(data, nameTableEntry); + font.tables.name = _name.parse(nameTable.data, nameTable.offset, ltagTable); + font.names = font.tables.name; + + if (glyfTableEntry && locaTableEntry) { + var shortVersion = indexToLocFormat === 0; + var locaTable = uncompressTable(data, locaTableEntry); + var locaOffsets = loca.parse(locaTable.data, locaTable.offset, font.numGlyphs, shortVersion); + var glyfTable = uncompressTable(data, glyfTableEntry); + font.glyphs = glyf.parse(glyfTable.data, glyfTable.offset, locaOffsets, font, opt); + } else if (cffTableEntry) { + var cffTable = uncompressTable(data, cffTableEntry); + cff.parse(cffTable.data, cffTable.offset, font, opt); + } else { + throw new Error('Font doesn\'t contain TrueType or CFF outlines.'); + } + + var hmtxTable = uncompressTable(data, hmtxTableEntry); + hmtx.parse(font, hmtxTable.data, hmtxTable.offset, font.numberOfHMetrics, font.numGlyphs, font.glyphs, opt); + addGlyphNames(font, opt); + + if (kernTableEntry) { + var kernTable = uncompressTable(data, kernTableEntry); + font.kerningPairs = kern.parse(kernTable.data, kernTable.offset); + } else { + font.kerningPairs = {}; + } + + if (gposTableEntry) { + var gposTable = uncompressTable(data, gposTableEntry); + font.tables.gpos = gpos.parse(gposTable.data, gposTable.offset); + font.position.init(); + } + + if (gsubTableEntry) { + var gsubTable = uncompressTable(data, gsubTableEntry); + font.tables.gsub = gsub.parse(gsubTable.data, gsubTable.offset); + } + + if (fvarTableEntry) { + var fvarTable = uncompressTable(data, fvarTableEntry); + font.tables.fvar = fvar.parse(fvarTable.data, fvarTable.offset, font.names); + } + + if (metaTableEntry) { + var metaTable = uncompressTable(data, metaTableEntry); + font.tables.meta = meta.parse(metaTable.data, metaTable.offset); + font.metas = font.tables.meta; + } + + return font; + } + + /** + * Asynchronously load the font from a URL or a filesystem. When done, call the callback + * with two arguments `(err, font)`. The `err` will be null on success, + * the `font` is a Font object. + * We use the node.js callback convention so that + * opentype.js can integrate with frameworks like async.js. + * @alias opentype.load + * @param {string} url - The URL of the font to load. + * @param {Function} callback - The callback. + */ + function load(url, callback, opt) { + var isNode = typeof window === 'undefined'; + var loadFn = isNode ? loadFromFile : loadFromUrl; + + return new Promise(function (resolve, reject) { + loadFn(url, function(err, arrayBuffer) { + if (err) { + if (callback) { + return callback(err); + } else { + reject(err); + } + } + var font; + try { + font = parseBuffer(arrayBuffer, opt); + } catch (e) { + if (callback) { + return callback(e, null); + } else { + reject(e); + } + } + if (callback) { + return callback(null, font); + } else { + resolve(font); + } + }); + }); + } + + /** + * Synchronously load the font from a URL or file. + * When done, returns the font object or throws an error. + * @alias opentype.loadSync + * @param {string} url - The URL of the font to load. + * @param {Object} opt - opt.lowMemory + * @return {opentype.Font} + */ + function loadSync(url, opt) { + var fs = require('fs'); + var buffer = fs.readFileSync(url); + return parseBuffer(nodeBufferToArrayBuffer(buffer), opt); + } + + var opentype = /*#__PURE__*/Object.freeze({ + __proto__: null, + Font: Font, + Glyph: Glyph, + Path: Path, + BoundingBox: BoundingBox, + _parse: parse, + parse: parseBuffer, + load: load, + loadSync: loadSync + }); + + exports.BoundingBox = BoundingBox; + exports.Font = Font; + exports.Glyph = Glyph; + exports.Path = Path; + exports._parse = parse; + exports.default = opentype; + exports.load = load; + exports.loadSync = loadSync; + exports.parse = parseBuffer; + + Object.defineProperty(exports, '__esModule', { value: true }); + +}))); +//# sourceMappingURL=opentype.js.map diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/package.json b/node_modules/lv_font_conv/node_modules/opentype.js/package.json new file mode 100644 index 00000000..8c75ccad --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/opentype.js/package.json @@ -0,0 +1,102 @@ +{ + "_args": [ + [ + "opentype.js@1.3.3", + "/home/vitaly/Dropbox/Coding/lv_font_conv" + ] + ], + "_from": "opentype.js@1.3.3", + "_id": "opentype.js@1.3.3", + "_inBundle": false, + "_integrity": "sha512-/qIY/+WnKGlPIIPhbeNjynfD2PO15G9lA/xqlX2bDH+4lc3Xz5GCQ68mqxj3DdUv6AJqCeaPvuAoH8mVL0zcuA==", + "_location": "/opentype.js", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "opentype.js@1.3.3", + "name": "opentype.js", + "escapedName": "opentype.js", + "rawSpec": "1.3.3", + "saveSpec": null, + "fetchSpec": "1.3.3" + }, + "_requiredBy": [ + "/" + ], + "_resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.3.tgz", + "_spec": "1.3.3", + "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", + "author": { + "name": "Frederik De Bleser", + "email": "frederik@debleser.be" + }, + "bin": { + "ot": "bin/ot" + }, + "browser": { + "fs": false + }, + "bugs": { + "url": "https://github.com/opentypejs/opentype.js/issues" + }, + "dependencies": { + "string.prototype.codepointat": "^0.2.1", + "tiny-inflate": "^1.0.3" + }, + "description": "OpenType font parser", + "devDependencies": { + "@babel/preset-env": "^7.9.5", + "buble": "^0.20.0", + "cross-env": "^7.0.2", + "jscs": "^3.0.7", + "jshint": "^2.11.0", + "mocha": "^7.1.1", + "reify": "^0.20.12", + "rollup": "^1.32.1", + "rollup-plugin-buble": "^0.19.8", + "rollup-plugin-commonjs": "^10.1.0", + "rollup-plugin-license": "^0.9.0", + "rollup-plugin-node-resolve": "^5.2.0", + "uglify-js": "^3.8.1" + }, + "engines": { + "node": ">= 8.0.0" + }, + "files": [ + "LICENSE", + "RELEASES.md", + "README.md", + "bin", + "dist", + "src" + ], + "homepage": "https://github.com/opentypejs/opentype.js#readme", + "keywords": [ + "graphics", + "fonts", + "font", + "opentype", + "otf", + "ttf", + "woff", + "type" + ], + "license": "MIT", + "main": "dist/opentype.js", + "module": "dist/opentype.module.js", + "name": "opentype.js", + "repository": { + "type": "git", + "url": "git://github.com/opentypejs/opentype.js.git" + }, + "scripts": { + "build": "rollup -c", + "dist": "npm run test && npm run build && npm run minify", + "minify": "uglifyjs --source-map \"url='opentype.min.js.map'\" --compress --mangle --output ./dist/opentype.min.js -- ./dist/opentype.js", + "start": "node ./bin/server.js", + "test": "mocha --require reify --recursive && jshint . && jscs .", + "watch": "rollup -c -w" + }, + "version": "1.3.3" +} diff --git a/node_modules/lv_font_conv/node_modules/pngjs/LICENSE b/node_modules/lv_font_conv/node_modules/pngjs/LICENSE new file mode 100644 index 00000000..6942e254 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/LICENSE @@ -0,0 +1,20 @@ +pngjs2 original work Copyright (c) 2015 Luke Page & Original Contributors +pngjs derived work Copyright (c) 2012 Kuba Niegowski + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/pngjs/README.md b/node_modules/lv_font_conv/node_modules/pngjs/README.md new file mode 100644 index 00000000..2aef03d9 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/README.md @@ -0,0 +1,433 @@ +[![Build Status](https://travis-ci.com/lukeapage/pngjs.svg?branch=master)](https://travis-ci.com/lukeapage/pngjs) [![Build status](https://ci.appveyor.com/api/projects/status/qo5x8ayutr028108/branch/master?svg=true)](https://ci.appveyor.com/project/lukeapage/pngjs/branch/master) [![codecov](https://codecov.io/gh/lukeapage/pngjs/branch/master/graph/badge.svg)](https://codecov.io/gh/lukeapage/pngjs) [![npm version](https://badge.fury.io/js/pngjs.svg)](http://badge.fury.io/js/pngjs) + +# pngjs + +Simple PNG encoder/decoder for Node.js with no dependencies. + +Based on the original [pngjs](https://github.com/niegowski/node-pngjs) with the follow enhancements. + +- Support for reading 1,2,4 & 16 bit files +- Support for reading interlace files +- Support for reading `tTRNS` transparent colours +- Support for writing colortype 0 (grayscale), colortype 2 (RGB), colortype 4 (grayscale alpha) and colortype 6 (RGBA) +- Sync interface as well as async +- API compatible with pngjs and node-pngjs + +Known lack of support for: + +- Extended PNG e.g. Animation +- Writing in colortype 3 (indexed color) + +# Table of Contents + +- [Requirements](#requirements) +- [Comparison Table](#comparison-table) +- [Tests](#tests) +- [Installation](#installation) +- [Browser](#browser) +- [Example](#example) +- [Async API](#async-api) +- [Sync API](#sync-api) +- [Changelog](#changelog) + +# Comparison Table + +| Name | Forked From | Sync | Async | 16 Bit | 1/2/4 Bit | Interlace | Gamma | Encodes | Tested | +| ------------- | ----------- | ---- | ----- | ------ | --------- | --------- | ------ | ------- | ------ | +| pngjs | | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | +| node-png | pngjs | No | Yes | No | No | No | Hidden | Yes | Manual | +| png-coder | pngjs | No | Yes | Yes | No | No | Hidden | Yes | Manual | +| pngparse | | No | Yes | No | Yes | No | No | No | Yes | +| pngparse-sync | pngparse | Yes | No | No | Yes | No | No | No | Yes | +| png-async | | No | Yes | No | No | No | No | Yes | Yes | +| png-js | | No | Yes | No | No | No | No | No | No | + +Native C++ node decoders: + +- png +- png-sync (sync version of above) +- pixel-png +- png-img + +# Tests + +Tested using [PNG Suite](http://www.schaik.com/pngsuite/). We read every file into pngjs, output it in standard 8bit colour, synchronously and asynchronously, then compare the original with the newly saved images. + +To run the tests, fetch the repo (tests are not distributed via npm) and install with `npm i`, run `npm test`. + +The only thing not converted is gamma correction - this is because multiple vendors will do gamma correction differently, so the tests will have different results on different browsers. + +# Installation + +``` +$ npm install pngjs --save +``` + +# Browser + +The package has been build with a [Browserify](browserify.org) version (`npm run browserify`) and you can use the browser version by including in your code: + +``` +import { PNG } from 'pngjs/browser'; +``` + +# Example + +```js +var fs = require("fs"), + PNG = require("pngjs").PNG; + +fs.createReadStream("in.png") + .pipe( + new PNG({ + filterType: 4, + }) + ) + .on("parsed", function () { + for (var y = 0; y < this.height; y++) { + for (var x = 0; x < this.width; x++) { + var idx = (this.width * y + x) << 2; + + // invert color + this.data[idx] = 255 - this.data[idx]; + this.data[idx + 1] = 255 - this.data[idx + 1]; + this.data[idx + 2] = 255 - this.data[idx + 2]; + + // and reduce opacity + this.data[idx + 3] = this.data[idx + 3] >> 1; + } + } + + this.pack().pipe(fs.createWriteStream("out.png")); + }); +``` + +For more examples see `examples` folder. + +# Async API + +As input any color type is accepted (grayscale, rgb, palette, grayscale with alpha, rgb with alpha) but 8 bit per sample (channel) is the only supported bit depth. Interlaced mode is not supported. + +## Class: PNG + +`PNG` is readable and writable `Stream`. + +### Options + +- `width` - use this with `height` if you want to create png from scratch +- `height` - as above +- `checkCRC` - whether parser should be strict about checksums in source stream (default: `true`) +- `deflateChunkSize` - chunk size used for deflating data chunks, this should be power of 2 and must not be less than 256 and more than 32\*1024 (default: 32 kB) +- `deflateLevel` - compression level for deflate (default: 9) +- `deflateStrategy` - compression strategy for deflate (default: 3) +- `deflateFactory` - deflate stream factory (default: `zlib.createDeflate`) +- `filterType` - png filtering method for scanlines (default: -1 => auto, accepts array of numbers 0-4) +- `colorType` - the output colorType - see constants. 0 = grayscale, no alpha, 2 = color, no alpha, 4 = grayscale & alpha, 6 = color & alpha. Default currently 6, but in the future may calculate best mode. +- `inputColorType` - the input colorType - see constants. Default is 6 (RGBA) +- `bitDepth` - the bitDepth of the output, 8 or 16 bits. Input data is expected to have this bit depth. + 16 bit data is expected in the system endianness (Default: 8) +- `inputHasAlpha` - whether the input bitmap has 4 bytes per pixel (rgb and alpha) or 3 (rgb - no alpha). +- `bgColor` - an object containing red, green, and blue values between 0 and 255 + that is used when packing a PNG if alpha is not to be included (default: 255,255,255) + +### Event "metadata" + +`function(metadata) { }` +Image's header has been parsed, metadata contains this information: + +- `width` image size in pixels +- `height` image size in pixels +- `palette` image is paletted +- `color` image is not grayscale +- `alpha` image contains alpha channel +- `interlace` image is interlaced + +### Event: "parsed" + +`function(data) { }` +Input image has been completely parsed, `data` is complete and ready for modification. + +### Event: "error" + +`function(error) { }` + +### png.parse(data, [callback]) + +Parses PNG file data. Can be `String` or `Buffer`. Alternatively you can stream data to instance of PNG. + +Optional `callback` is once called on `error` or `parsed`. The callback gets +two arguments `(err, data)`. + +Returns `this` for method chaining. + +#### Example + +```js +new PNG({ filterType: 4 }).parse(imageData, function (error, data) { + console.log(error, data); +}); +``` + +### png.pack() + +Starts converting data to PNG file Stream. + +Returns `this` for method chaining. + +### png.bitblt(dst, sx, sy, w, h, dx, dy) + +Helper for image manipulation, copies a rectangle of pixels from current (i.e. the source) image (`sx`, `sy`, `w`, `h`) to `dst` image (at `dx`, `dy`). + +Returns `this` for method chaining. + +For example, the following code copies the top-left 100x50 px of `in.png` into dst and writes it to `out.png`: + +```js +var dst = new PNG({ width: 100, height: 50 }); +fs.createReadStream("in.png") + .pipe(new PNG()) + .on("parsed", function () { + this.bitblt(dst, 0, 0, 100, 50, 0, 0); + dst.pack().pipe(fs.createWriteStream("out.png")); + }); +``` + +### Property: adjustGamma() + +Helper that takes data and adjusts it to be gamma corrected. Note that it is not 100% reliable with transparent colours because that requires knowing the background colour the bitmap is rendered on to. + +In tests against PNG suite it compared 100% with chrome on all 8 bit and below images. On IE there were some differences. + +The following example reads a file, adjusts the gamma (which sets the gamma to 0) and writes it out again, effectively removing any gamma correction from the image. + +```js +fs.createReadStream("in.png") + .pipe(new PNG()) + .on("parsed", function () { + this.adjustGamma(); + this.pack().pipe(fs.createWriteStream("out.png")); + }); +``` + +### Property: width + +Width of image in pixels + +### Property: height + +Height of image in pixels + +### Property: data + +Buffer of image pixel data. Every pixel consists 4 bytes: R, G, B, A (opacity). + +### Property: gamma + +Gamma of image (0 if not specified) + +## Packing a PNG and removing alpha (RGBA to RGB) + +When removing the alpha channel from an image, there needs to be a background color to correctly +convert each pixel's transparency to the appropriate RGB value. By default, pngjs will flatten +the image against a white background. You can override this in the options: + +```js +var fs = require("fs"), + PNG = require("pngjs").PNG; + +fs.createReadStream("in.png") + .pipe( + new PNG({ + colorType: 2, + bgColor: { + red: 0, + green: 255, + blue: 0, + }, + }) + ) + .on("parsed", function () { + this.pack().pipe(fs.createWriteStream("out.png")); + }); +``` + +# Sync API + +## PNG.sync + +### PNG.sync.read(buffer) + +Take a buffer and returns a PNG image. The properties on the image include the meta data and `data` as per the async API above. + +``` +var data = fs.readFileSync('in.png'); +var png = PNG.sync.read(data); +``` + +### PNG.sync.write(png) + +Take a PNG image and returns a buffer. The properties on the image include the meta data and `data` as per the async API above. + +``` +var data = fs.readFileSync('in.png'); +var png = PNG.sync.read(data); +var options = { colorType: 6 }; +var buffer = PNG.sync.write(png, options); +fs.writeFileSync('out.png', buffer); +``` + +### PNG.adjustGamma(src) + +Adjusts the gamma of a sync image. See the async adjustGamma. + +``` +var data = fs.readFileSync('in.png'); +var png = PNG.sync.read(data); +PNG.adjustGamma(png); +``` + +# Changelog + +### 6.0.0 - 24/10/2020 + +- BREAKING - Sync version now throws if there is unexpected content at the end of the stream. +- BREAKING - Drop support for node 10 (Though nothing incompatible in this release yet) +- Reduce the number of files included in the package + +### 5.1.0 - 13/09/2020 + +- Add option to skip rescaling + +### 5.0.0 - 15/04/2020 + +- Drop support for Node 8 +- Browserified bundle may now contain ES20(15-20) code if the supported node version supports it. Please run the browserified version through babel if you need to support older browsers. + +### 4.0.1 - 15/04/2020 + +- Fix to possible null reference in nextTick of async method + +### 4.0.0 - 09/04/2020 + +- Fix issue in newer nodes with using Buffer +- Fix async issue with some png files +- Drop support for Node 4 & 6 + +### 3.4.0 - 09/03/2019 + +- Include whether the png has alpha in the meta data +- emit an error if the image is truncated instead of hanging +- Add a browserified version +- speed up some mapping functions + +### 3.3.3 - 19/04/2018 + +- Real fix for node 9 + +### 3.3.2 - 16/02/2018 + +- Fix for node 9 + +### 3.3.1 - 15/11/2017 + +- Bugfixes and removal of es6 + +### 3.3.0 + +- Add writing 16 bit channels and support for grayscale input + +### 3.2.0 - 30/04/2017 + +- Support for encoding 8-bit grayscale images + +### 3.1.0 - 30/04/2017 + +- Support for pngs with zlib chunks that are malformed after valid data + +### 3.0.1 - 16/02/2017 + +- Fix single pixel pngs + +### 3.0.0 - 03/08/2016 + +- Drop support for node below v4 and iojs. Pin to 2.3.0 to use with old, unsupported or patched node versions. + +### 2.3.0 - 22/04/2016 + +- Support for sync in node 0.10 + +### 2.2.0 - 04/12/2015 + +- Add sync write api +- Fix newfile example +- Correct comparison table + +### 2.1.0 - 28/10/2015 + +- rename package to pngjs +- added 'bgColor' option + +### 2.0.0 - 08/10/2015 + +- fixes to readme +- _breaking change_ - bitblt on the png prototype now doesn't take a unused, unnecessary src first argument + +### 1.2.0 - 13/09/2015 + +- support passing colorType to write PNG's and writing bitmaps without alpha information + +### 1.1.0 - 07/09/2015 + +- support passing a deflate factory for controlled compression + +### 1.0.2 - 22/08/2015 + +- Expose all PNG creation info + +### 1.0.1 - 21/08/2015 + +- Fix non square interlaced files + +### 1.0.0 - 08/08/2015 + +- More tests +- source linted +- maintainability refactorings +- async API - exceptions in reading now emit warnings +- documentation improvement - sync api now documented, adjustGamma documented +- breaking change - gamma chunk is now written. previously a read then write would destroy gamma information, now it is persisted. + +### 0.0.3 - 03/08/2015 + +- Error handling fixes +- ignore files for smaller npm footprint + +### 0.0.2 - 02/08/2015 + +- Bugfixes to interlacing, support for transparent colours + +### 0.0.1 - 02/08/2015 + +- Initial release, see pngjs for older changelog. + +# License + +(The MIT License) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/bitmapper.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/bitmapper.js new file mode 100644 index 00000000..18378a02 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/bitmapper.js @@ -0,0 +1,267 @@ +"use strict"; + +let interlaceUtils = require("./interlace"); + +let pixelBppMapper = [ + // 0 - dummy entry + function () {}, + + // 1 - L + // 0: 0, 1: 0, 2: 0, 3: 0xff + function (pxData, data, pxPos, rawPos) { + if (rawPos === data.length) { + throw new Error("Ran out of data"); + } + + let pixel = data[rawPos]; + pxData[pxPos] = pixel; + pxData[pxPos + 1] = pixel; + pxData[pxPos + 2] = pixel; + pxData[pxPos + 3] = 0xff; + }, + + // 2 - LA + // 0: 0, 1: 0, 2: 0, 3: 1 + function (pxData, data, pxPos, rawPos) { + if (rawPos + 1 >= data.length) { + throw new Error("Ran out of data"); + } + + let pixel = data[rawPos]; + pxData[pxPos] = pixel; + pxData[pxPos + 1] = pixel; + pxData[pxPos + 2] = pixel; + pxData[pxPos + 3] = data[rawPos + 1]; + }, + + // 3 - RGB + // 0: 0, 1: 1, 2: 2, 3: 0xff + function (pxData, data, pxPos, rawPos) { + if (rawPos + 2 >= data.length) { + throw new Error("Ran out of data"); + } + + pxData[pxPos] = data[rawPos]; + pxData[pxPos + 1] = data[rawPos + 1]; + pxData[pxPos + 2] = data[rawPos + 2]; + pxData[pxPos + 3] = 0xff; + }, + + // 4 - RGBA + // 0: 0, 1: 1, 2: 2, 3: 3 + function (pxData, data, pxPos, rawPos) { + if (rawPos + 3 >= data.length) { + throw new Error("Ran out of data"); + } + + pxData[pxPos] = data[rawPos]; + pxData[pxPos + 1] = data[rawPos + 1]; + pxData[pxPos + 2] = data[rawPos + 2]; + pxData[pxPos + 3] = data[rawPos + 3]; + }, +]; + +let pixelBppCustomMapper = [ + // 0 - dummy entry + function () {}, + + // 1 - L + // 0: 0, 1: 0, 2: 0, 3: 0xff + function (pxData, pixelData, pxPos, maxBit) { + let pixel = pixelData[0]; + pxData[pxPos] = pixel; + pxData[pxPos + 1] = pixel; + pxData[pxPos + 2] = pixel; + pxData[pxPos + 3] = maxBit; + }, + + // 2 - LA + // 0: 0, 1: 0, 2: 0, 3: 1 + function (pxData, pixelData, pxPos) { + let pixel = pixelData[0]; + pxData[pxPos] = pixel; + pxData[pxPos + 1] = pixel; + pxData[pxPos + 2] = pixel; + pxData[pxPos + 3] = pixelData[1]; + }, + + // 3 - RGB + // 0: 0, 1: 1, 2: 2, 3: 0xff + function (pxData, pixelData, pxPos, maxBit) { + pxData[pxPos] = pixelData[0]; + pxData[pxPos + 1] = pixelData[1]; + pxData[pxPos + 2] = pixelData[2]; + pxData[pxPos + 3] = maxBit; + }, + + // 4 - RGBA + // 0: 0, 1: 1, 2: 2, 3: 3 + function (pxData, pixelData, pxPos) { + pxData[pxPos] = pixelData[0]; + pxData[pxPos + 1] = pixelData[1]; + pxData[pxPos + 2] = pixelData[2]; + pxData[pxPos + 3] = pixelData[3]; + }, +]; + +function bitRetriever(data, depth) { + let leftOver = []; + let i = 0; + + function split() { + if (i === data.length) { + throw new Error("Ran out of data"); + } + let byte = data[i]; + i++; + let byte8, byte7, byte6, byte5, byte4, byte3, byte2, byte1; + switch (depth) { + default: + throw new Error("unrecognised depth"); + case 16: + byte2 = data[i]; + i++; + leftOver.push((byte << 8) + byte2); + break; + case 4: + byte2 = byte & 0x0f; + byte1 = byte >> 4; + leftOver.push(byte1, byte2); + break; + case 2: + byte4 = byte & 3; + byte3 = (byte >> 2) & 3; + byte2 = (byte >> 4) & 3; + byte1 = (byte >> 6) & 3; + leftOver.push(byte1, byte2, byte3, byte4); + break; + case 1: + byte8 = byte & 1; + byte7 = (byte >> 1) & 1; + byte6 = (byte >> 2) & 1; + byte5 = (byte >> 3) & 1; + byte4 = (byte >> 4) & 1; + byte3 = (byte >> 5) & 1; + byte2 = (byte >> 6) & 1; + byte1 = (byte >> 7) & 1; + leftOver.push(byte1, byte2, byte3, byte4, byte5, byte6, byte7, byte8); + break; + } + } + + return { + get: function (count) { + while (leftOver.length < count) { + split(); + } + let returner = leftOver.slice(0, count); + leftOver = leftOver.slice(count); + return returner; + }, + resetAfterLine: function () { + leftOver.length = 0; + }, + end: function () { + if (i !== data.length) { + throw new Error("extra data found"); + } + }, + }; +} + +function mapImage8Bit(image, pxData, getPxPos, bpp, data, rawPos) { + // eslint-disable-line max-params + let imageWidth = image.width; + let imageHeight = image.height; + let imagePass = image.index; + for (let y = 0; y < imageHeight; y++) { + for (let x = 0; x < imageWidth; x++) { + let pxPos = getPxPos(x, y, imagePass); + pixelBppMapper[bpp](pxData, data, pxPos, rawPos); + rawPos += bpp; //eslint-disable-line no-param-reassign + } + } + return rawPos; +} + +function mapImageCustomBit(image, pxData, getPxPos, bpp, bits, maxBit) { + // eslint-disable-line max-params + let imageWidth = image.width; + let imageHeight = image.height; + let imagePass = image.index; + for (let y = 0; y < imageHeight; y++) { + for (let x = 0; x < imageWidth; x++) { + let pixelData = bits.get(bpp); + let pxPos = getPxPos(x, y, imagePass); + pixelBppCustomMapper[bpp](pxData, pixelData, pxPos, maxBit); + } + bits.resetAfterLine(); + } +} + +exports.dataToBitMap = function (data, bitmapInfo) { + let width = bitmapInfo.width; + let height = bitmapInfo.height; + let depth = bitmapInfo.depth; + let bpp = bitmapInfo.bpp; + let interlace = bitmapInfo.interlace; + let bits; + + if (depth !== 8) { + bits = bitRetriever(data, depth); + } + let pxData; + if (depth <= 8) { + pxData = Buffer.alloc(width * height * 4); + } else { + pxData = new Uint16Array(width * height * 4); + } + let maxBit = Math.pow(2, depth) - 1; + let rawPos = 0; + let images; + let getPxPos; + + if (interlace) { + images = interlaceUtils.getImagePasses(width, height); + getPxPos = interlaceUtils.getInterlaceIterator(width, height); + } else { + let nonInterlacedPxPos = 0; + getPxPos = function () { + let returner = nonInterlacedPxPos; + nonInterlacedPxPos += 4; + return returner; + }; + images = [{ width: width, height: height }]; + } + + for (let imageIndex = 0; imageIndex < images.length; imageIndex++) { + if (depth === 8) { + rawPos = mapImage8Bit( + images[imageIndex], + pxData, + getPxPos, + bpp, + data, + rawPos + ); + } else { + mapImageCustomBit( + images[imageIndex], + pxData, + getPxPos, + bpp, + bits, + maxBit + ); + } + } + if (depth === 8) { + if (rawPos !== data.length) { + throw new Error("extra data found"); + } + } else { + bits.end(); + } + + return pxData; +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/bitpacker.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/bitpacker.js new file mode 100644 index 00000000..d7a4e656 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/bitpacker.js @@ -0,0 +1,158 @@ +"use strict"; + +let constants = require("./constants"); + +module.exports = function (dataIn, width, height, options) { + let outHasAlpha = + [constants.COLORTYPE_COLOR_ALPHA, constants.COLORTYPE_ALPHA].indexOf( + options.colorType + ) !== -1; + if (options.colorType === options.inputColorType) { + let bigEndian = (function () { + let buffer = new ArrayBuffer(2); + new DataView(buffer).setInt16(0, 256, true /* littleEndian */); + // Int16Array uses the platform's endianness. + return new Int16Array(buffer)[0] !== 256; + })(); + // If no need to convert to grayscale and alpha is present/absent in both, take a fast route + if (options.bitDepth === 8 || (options.bitDepth === 16 && bigEndian)) { + return dataIn; + } + } + + // map to a UInt16 array if data is 16bit, fix endianness below + let data = options.bitDepth !== 16 ? dataIn : new Uint16Array(dataIn.buffer); + + let maxValue = 255; + let inBpp = constants.COLORTYPE_TO_BPP_MAP[options.inputColorType]; + if (inBpp === 4 && !options.inputHasAlpha) { + inBpp = 3; + } + let outBpp = constants.COLORTYPE_TO_BPP_MAP[options.colorType]; + if (options.bitDepth === 16) { + maxValue = 65535; + outBpp *= 2; + } + let outData = Buffer.alloc(width * height * outBpp); + + let inIndex = 0; + let outIndex = 0; + + let bgColor = options.bgColor || {}; + if (bgColor.red === undefined) { + bgColor.red = maxValue; + } + if (bgColor.green === undefined) { + bgColor.green = maxValue; + } + if (bgColor.blue === undefined) { + bgColor.blue = maxValue; + } + + function getRGBA() { + let red; + let green; + let blue; + let alpha = maxValue; + switch (options.inputColorType) { + case constants.COLORTYPE_COLOR_ALPHA: + alpha = data[inIndex + 3]; + red = data[inIndex]; + green = data[inIndex + 1]; + blue = data[inIndex + 2]; + break; + case constants.COLORTYPE_COLOR: + red = data[inIndex]; + green = data[inIndex + 1]; + blue = data[inIndex + 2]; + break; + case constants.COLORTYPE_ALPHA: + alpha = data[inIndex + 1]; + red = data[inIndex]; + green = red; + blue = red; + break; + case constants.COLORTYPE_GRAYSCALE: + red = data[inIndex]; + green = red; + blue = red; + break; + default: + throw new Error( + "input color type:" + + options.inputColorType + + " is not supported at present" + ); + } + + if (options.inputHasAlpha) { + if (!outHasAlpha) { + alpha /= maxValue; + red = Math.min( + Math.max(Math.round((1 - alpha) * bgColor.red + alpha * red), 0), + maxValue + ); + green = Math.min( + Math.max(Math.round((1 - alpha) * bgColor.green + alpha * green), 0), + maxValue + ); + blue = Math.min( + Math.max(Math.round((1 - alpha) * bgColor.blue + alpha * blue), 0), + maxValue + ); + } + } + return { red: red, green: green, blue: blue, alpha: alpha }; + } + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + let rgba = getRGBA(data, inIndex); + + switch (options.colorType) { + case constants.COLORTYPE_COLOR_ALPHA: + case constants.COLORTYPE_COLOR: + if (options.bitDepth === 8) { + outData[outIndex] = rgba.red; + outData[outIndex + 1] = rgba.green; + outData[outIndex + 2] = rgba.blue; + if (outHasAlpha) { + outData[outIndex + 3] = rgba.alpha; + } + } else { + outData.writeUInt16BE(rgba.red, outIndex); + outData.writeUInt16BE(rgba.green, outIndex + 2); + outData.writeUInt16BE(rgba.blue, outIndex + 4); + if (outHasAlpha) { + outData.writeUInt16BE(rgba.alpha, outIndex + 6); + } + } + break; + case constants.COLORTYPE_ALPHA: + case constants.COLORTYPE_GRAYSCALE: { + // Convert to grayscale and alpha + let grayscale = (rgba.red + rgba.green + rgba.blue) / 3; + if (options.bitDepth === 8) { + outData[outIndex] = grayscale; + if (outHasAlpha) { + outData[outIndex + 1] = rgba.alpha; + } + } else { + outData.writeUInt16BE(grayscale, outIndex); + if (outHasAlpha) { + outData.writeUInt16BE(rgba.alpha, outIndex + 2); + } + } + break; + } + default: + throw new Error("unrecognised color Type " + options.colorType); + } + + inIndex += inBpp; + outIndex += outBpp; + } + } + + return outData; +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/chunkstream.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/chunkstream.js new file mode 100644 index 00000000..95b46d48 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/chunkstream.js @@ -0,0 +1,189 @@ +"use strict"; + +let util = require("util"); +let Stream = require("stream"); + +let ChunkStream = (module.exports = function () { + Stream.call(this); + + this._buffers = []; + this._buffered = 0; + + this._reads = []; + this._paused = false; + + this._encoding = "utf8"; + this.writable = true; +}); +util.inherits(ChunkStream, Stream); + +ChunkStream.prototype.read = function (length, callback) { + this._reads.push({ + length: Math.abs(length), // if length < 0 then at most this length + allowLess: length < 0, + func: callback, + }); + + process.nextTick( + function () { + this._process(); + + // its paused and there is not enought data then ask for more + if (this._paused && this._reads && this._reads.length > 0) { + this._paused = false; + + this.emit("drain"); + } + }.bind(this) + ); +}; + +ChunkStream.prototype.write = function (data, encoding) { + if (!this.writable) { + this.emit("error", new Error("Stream not writable")); + return false; + } + + let dataBuffer; + if (Buffer.isBuffer(data)) { + dataBuffer = data; + } else { + dataBuffer = Buffer.from(data, encoding || this._encoding); + } + + this._buffers.push(dataBuffer); + this._buffered += dataBuffer.length; + + this._process(); + + // ok if there are no more read requests + if (this._reads && this._reads.length === 0) { + this._paused = true; + } + + return this.writable && !this._paused; +}; + +ChunkStream.prototype.end = function (data, encoding) { + if (data) { + this.write(data, encoding); + } + + this.writable = false; + + // already destroyed + if (!this._buffers) { + return; + } + + // enqueue or handle end + if (this._buffers.length === 0) { + this._end(); + } else { + this._buffers.push(null); + this._process(); + } +}; + +ChunkStream.prototype.destroySoon = ChunkStream.prototype.end; + +ChunkStream.prototype._end = function () { + if (this._reads.length > 0) { + this.emit("error", new Error("Unexpected end of input")); + } + + this.destroy(); +}; + +ChunkStream.prototype.destroy = function () { + if (!this._buffers) { + return; + } + + this.writable = false; + this._reads = null; + this._buffers = null; + + this.emit("close"); +}; + +ChunkStream.prototype._processReadAllowingLess = function (read) { + // ok there is any data so that we can satisfy this request + this._reads.shift(); // == read + + // first we need to peek into first buffer + let smallerBuf = this._buffers[0]; + + // ok there is more data than we need + if (smallerBuf.length > read.length) { + this._buffered -= read.length; + this._buffers[0] = smallerBuf.slice(read.length); + + read.func.call(this, smallerBuf.slice(0, read.length)); + } else { + // ok this is less than maximum length so use it all + this._buffered -= smallerBuf.length; + this._buffers.shift(); // == smallerBuf + + read.func.call(this, smallerBuf); + } +}; + +ChunkStream.prototype._processRead = function (read) { + this._reads.shift(); // == read + + let pos = 0; + let count = 0; + let data = Buffer.alloc(read.length); + + // create buffer for all data + while (pos < read.length) { + let buf = this._buffers[count++]; + let len = Math.min(buf.length, read.length - pos); + + buf.copy(data, pos, 0, len); + pos += len; + + // last buffer wasn't used all so just slice it and leave + if (len !== buf.length) { + this._buffers[--count] = buf.slice(len); + } + } + + // remove all used buffers + if (count > 0) { + this._buffers.splice(0, count); + } + + this._buffered -= read.length; + + read.func.call(this, data); +}; + +ChunkStream.prototype._process = function () { + try { + // as long as there is any data and read requests + while (this._buffered > 0 && this._reads && this._reads.length > 0) { + let read = this._reads[0]; + + // read any data (but no more than length) + if (read.allowLess) { + this._processReadAllowingLess(read); + } else if (this._buffered >= read.length) { + // ok we can meet some expectations + + this._processRead(read); + } else { + // not enought data to satisfy first request in queue + // so we need to wait for more + break; + } + } + + if (this._buffers && !this.writable) { + this._end(); + } + } catch (ex) { + this.emit("error", ex); + } +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/constants.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/constants.js new file mode 100644 index 00000000..21fdad68 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/constants.js @@ -0,0 +1,32 @@ +"use strict"; + +module.exports = { + PNG_SIGNATURE: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], + + TYPE_IHDR: 0x49484452, + TYPE_IEND: 0x49454e44, + TYPE_IDAT: 0x49444154, + TYPE_PLTE: 0x504c5445, + TYPE_tRNS: 0x74524e53, // eslint-disable-line camelcase + TYPE_gAMA: 0x67414d41, // eslint-disable-line camelcase + + // color-type bits + COLORTYPE_GRAYSCALE: 0, + COLORTYPE_PALETTE: 1, + COLORTYPE_COLOR: 2, + COLORTYPE_ALPHA: 4, // e.g. grayscale and alpha + + // color-type combinations + COLORTYPE_PALETTE_COLOR: 3, + COLORTYPE_COLOR_ALPHA: 6, + + COLORTYPE_TO_BPP_MAP: { + 0: 1, + 2: 3, + 3: 1, + 4: 2, + 6: 4, + }, + + GAMMA_DIVISION: 100000, +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/crc.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/crc.js new file mode 100644 index 00000000..950ec8ae --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/crc.js @@ -0,0 +1,40 @@ +"use strict"; + +let crcTable = []; + +(function () { + for (let i = 0; i < 256; i++) { + let currentCrc = i; + for (let j = 0; j < 8; j++) { + if (currentCrc & 1) { + currentCrc = 0xedb88320 ^ (currentCrc >>> 1); + } else { + currentCrc = currentCrc >>> 1; + } + } + crcTable[i] = currentCrc; + } +})(); + +let CrcCalculator = (module.exports = function () { + this._crc = -1; +}); + +CrcCalculator.prototype.write = function (data) { + for (let i = 0; i < data.length; i++) { + this._crc = crcTable[(this._crc ^ data[i]) & 0xff] ^ (this._crc >>> 8); + } + return true; +}; + +CrcCalculator.prototype.crc32 = function () { + return this._crc ^ -1; +}; + +CrcCalculator.crc32 = function (buf) { + let crc = -1; + for (let i = 0; i < buf.length; i++) { + crc = crcTable[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8); + } + return crc ^ -1; +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-pack.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-pack.js new file mode 100644 index 00000000..32c85c40 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-pack.js @@ -0,0 +1,171 @@ +"use strict"; + +let paethPredictor = require("./paeth-predictor"); + +function filterNone(pxData, pxPos, byteWidth, rawData, rawPos) { + for (let x = 0; x < byteWidth; x++) { + rawData[rawPos + x] = pxData[pxPos + x]; + } +} + +function filterSumNone(pxData, pxPos, byteWidth) { + let sum = 0; + let length = pxPos + byteWidth; + + for (let i = pxPos; i < length; i++) { + sum += Math.abs(pxData[i]); + } + return sum; +} + +function filterSub(pxData, pxPos, byteWidth, rawData, rawPos, bpp) { + for (let x = 0; x < byteWidth; x++) { + let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; + let val = pxData[pxPos + x] - left; + + rawData[rawPos + x] = val; + } +} + +function filterSumSub(pxData, pxPos, byteWidth, bpp) { + let sum = 0; + for (let x = 0; x < byteWidth; x++) { + let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; + let val = pxData[pxPos + x] - left; + + sum += Math.abs(val); + } + + return sum; +} + +function filterUp(pxData, pxPos, byteWidth, rawData, rawPos) { + for (let x = 0; x < byteWidth; x++) { + let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; + let val = pxData[pxPos + x] - up; + + rawData[rawPos + x] = val; + } +} + +function filterSumUp(pxData, pxPos, byteWidth) { + let sum = 0; + let length = pxPos + byteWidth; + for (let x = pxPos; x < length; x++) { + let up = pxPos > 0 ? pxData[x - byteWidth] : 0; + let val = pxData[x] - up; + + sum += Math.abs(val); + } + + return sum; +} + +function filterAvg(pxData, pxPos, byteWidth, rawData, rawPos, bpp) { + for (let x = 0; x < byteWidth; x++) { + let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; + let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; + let val = pxData[pxPos + x] - ((left + up) >> 1); + + rawData[rawPos + x] = val; + } +} + +function filterSumAvg(pxData, pxPos, byteWidth, bpp) { + let sum = 0; + for (let x = 0; x < byteWidth; x++) { + let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; + let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; + let val = pxData[pxPos + x] - ((left + up) >> 1); + + sum += Math.abs(val); + } + + return sum; +} + +function filterPaeth(pxData, pxPos, byteWidth, rawData, rawPos, bpp) { + for (let x = 0; x < byteWidth; x++) { + let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; + let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; + let upleft = + pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0; + let val = pxData[pxPos + x] - paethPredictor(left, up, upleft); + + rawData[rawPos + x] = val; + } +} + +function filterSumPaeth(pxData, pxPos, byteWidth, bpp) { + let sum = 0; + for (let x = 0; x < byteWidth; x++) { + let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; + let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; + let upleft = + pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0; + let val = pxData[pxPos + x] - paethPredictor(left, up, upleft); + + sum += Math.abs(val); + } + + return sum; +} + +let filters = { + 0: filterNone, + 1: filterSub, + 2: filterUp, + 3: filterAvg, + 4: filterPaeth, +}; + +let filterSums = { + 0: filterSumNone, + 1: filterSumSub, + 2: filterSumUp, + 3: filterSumAvg, + 4: filterSumPaeth, +}; + +module.exports = function (pxData, width, height, options, bpp) { + let filterTypes; + if (!("filterType" in options) || options.filterType === -1) { + filterTypes = [0, 1, 2, 3, 4]; + } else if (typeof options.filterType === "number") { + filterTypes = [options.filterType]; + } else { + throw new Error("unrecognised filter types"); + } + + if (options.bitDepth === 16) { + bpp *= 2; + } + let byteWidth = width * bpp; + let rawPos = 0; + let pxPos = 0; + let rawData = Buffer.alloc((byteWidth + 1) * height); + + let sel = filterTypes[0]; + + for (let y = 0; y < height; y++) { + if (filterTypes.length > 1) { + // find best filter for this line (with lowest sum of values) + let min = Infinity; + + for (let i = 0; i < filterTypes.length; i++) { + let sum = filterSums[filterTypes[i]](pxData, pxPos, byteWidth, bpp); + if (sum < min) { + sel = filterTypes[i]; + min = sum; + } + } + } + + rawData[rawPos] = sel; + rawPos++; + filters[sel](pxData, pxPos, byteWidth, rawData, rawPos, bpp); + rawPos += byteWidth; + pxPos += byteWidth; + } + return rawData; +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-async.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-async.js new file mode 100644 index 00000000..832b86cd --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-async.js @@ -0,0 +1,24 @@ +"use strict"; + +let util = require("util"); +let ChunkStream = require("./chunkstream"); +let Filter = require("./filter-parse"); + +let FilterAsync = (module.exports = function (bitmapInfo) { + ChunkStream.call(this); + + let buffers = []; + let that = this; + this._filter = new Filter(bitmapInfo, { + read: this.read.bind(this), + write: function (buffer) { + buffers.push(buffer); + }, + complete: function () { + that.emit("complete", Buffer.concat(buffers)); + }, + }); + + this._filter.start(); +}); +util.inherits(FilterAsync, ChunkStream); diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-sync.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-sync.js new file mode 100644 index 00000000..6924d161 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-sync.js @@ -0,0 +1,21 @@ +"use strict"; + +let SyncReader = require("./sync-reader"); +let Filter = require("./filter-parse"); + +exports.process = function (inBuffer, bitmapInfo) { + let outBuffers = []; + let reader = new SyncReader(inBuffer); + let filter = new Filter(bitmapInfo, { + read: reader.read.bind(reader), + write: function (bufferPart) { + outBuffers.push(bufferPart); + }, + complete: function () {}, + }); + + filter.start(); + reader.process(); + + return Buffer.concat(outBuffers); +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse.js new file mode 100644 index 00000000..3a32e5ee --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse.js @@ -0,0 +1,177 @@ +"use strict"; + +let interlaceUtils = require("./interlace"); +let paethPredictor = require("./paeth-predictor"); + +function getByteWidth(width, bpp, depth) { + let byteWidth = width * bpp; + if (depth !== 8) { + byteWidth = Math.ceil(byteWidth / (8 / depth)); + } + return byteWidth; +} + +let Filter = (module.exports = function (bitmapInfo, dependencies) { + let width = bitmapInfo.width; + let height = bitmapInfo.height; + let interlace = bitmapInfo.interlace; + let bpp = bitmapInfo.bpp; + let depth = bitmapInfo.depth; + + this.read = dependencies.read; + this.write = dependencies.write; + this.complete = dependencies.complete; + + this._imageIndex = 0; + this._images = []; + if (interlace) { + let passes = interlaceUtils.getImagePasses(width, height); + for (let i = 0; i < passes.length; i++) { + this._images.push({ + byteWidth: getByteWidth(passes[i].width, bpp, depth), + height: passes[i].height, + lineIndex: 0, + }); + } + } else { + this._images.push({ + byteWidth: getByteWidth(width, bpp, depth), + height: height, + lineIndex: 0, + }); + } + + // when filtering the line we look at the pixel to the left + // the spec also says it is done on a byte level regardless of the number of pixels + // so if the depth is byte compatible (8 or 16) we subtract the bpp in order to compare back + // a pixel rather than just a different byte part. However if we are sub byte, we ignore. + if (depth === 8) { + this._xComparison = bpp; + } else if (depth === 16) { + this._xComparison = bpp * 2; + } else { + this._xComparison = 1; + } +}); + +Filter.prototype.start = function () { + this.read( + this._images[this._imageIndex].byteWidth + 1, + this._reverseFilterLine.bind(this) + ); +}; + +Filter.prototype._unFilterType1 = function ( + rawData, + unfilteredLine, + byteWidth +) { + let xComparison = this._xComparison; + let xBiggerThan = xComparison - 1; + + for (let x = 0; x < byteWidth; x++) { + let rawByte = rawData[1 + x]; + let f1Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0; + unfilteredLine[x] = rawByte + f1Left; + } +}; + +Filter.prototype._unFilterType2 = function ( + rawData, + unfilteredLine, + byteWidth +) { + let lastLine = this._lastLine; + + for (let x = 0; x < byteWidth; x++) { + let rawByte = rawData[1 + x]; + let f2Up = lastLine ? lastLine[x] : 0; + unfilteredLine[x] = rawByte + f2Up; + } +}; + +Filter.prototype._unFilterType3 = function ( + rawData, + unfilteredLine, + byteWidth +) { + let xComparison = this._xComparison; + let xBiggerThan = xComparison - 1; + let lastLine = this._lastLine; + + for (let x = 0; x < byteWidth; x++) { + let rawByte = rawData[1 + x]; + let f3Up = lastLine ? lastLine[x] : 0; + let f3Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0; + let f3Add = Math.floor((f3Left + f3Up) / 2); + unfilteredLine[x] = rawByte + f3Add; + } +}; + +Filter.prototype._unFilterType4 = function ( + rawData, + unfilteredLine, + byteWidth +) { + let xComparison = this._xComparison; + let xBiggerThan = xComparison - 1; + let lastLine = this._lastLine; + + for (let x = 0; x < byteWidth; x++) { + let rawByte = rawData[1 + x]; + let f4Up = lastLine ? lastLine[x] : 0; + let f4Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0; + let f4UpLeft = x > xBiggerThan && lastLine ? lastLine[x - xComparison] : 0; + let f4Add = paethPredictor(f4Left, f4Up, f4UpLeft); + unfilteredLine[x] = rawByte + f4Add; + } +}; + +Filter.prototype._reverseFilterLine = function (rawData) { + let filter = rawData[0]; + let unfilteredLine; + let currentImage = this._images[this._imageIndex]; + let byteWidth = currentImage.byteWidth; + + if (filter === 0) { + unfilteredLine = rawData.slice(1, byteWidth + 1); + } else { + unfilteredLine = Buffer.alloc(byteWidth); + + switch (filter) { + case 1: + this._unFilterType1(rawData, unfilteredLine, byteWidth); + break; + case 2: + this._unFilterType2(rawData, unfilteredLine, byteWidth); + break; + case 3: + this._unFilterType3(rawData, unfilteredLine, byteWidth); + break; + case 4: + this._unFilterType4(rawData, unfilteredLine, byteWidth); + break; + default: + throw new Error("Unrecognised filter type - " + filter); + } + } + + this.write(unfilteredLine); + + currentImage.lineIndex++; + if (currentImage.lineIndex >= currentImage.height) { + this._lastLine = null; + this._imageIndex++; + currentImage = this._images[this._imageIndex]; + } else { + this._lastLine = unfilteredLine; + } + + if (currentImage) { + // read, using the byte width that may be from the new current image + this.read(currentImage.byteWidth + 1, this._reverseFilterLine.bind(this)); + } else { + this._lastLine = null; + this.complete(); + } +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/format-normaliser.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/format-normaliser.js new file mode 100644 index 00000000..209b66bb --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/format-normaliser.js @@ -0,0 +1,93 @@ +"use strict"; + +function dePalette(indata, outdata, width, height, palette) { + let pxPos = 0; + // use values from palette + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + let color = palette[indata[pxPos]]; + + if (!color) { + throw new Error("index " + indata[pxPos] + " not in palette"); + } + + for (let i = 0; i < 4; i++) { + outdata[pxPos + i] = color[i]; + } + pxPos += 4; + } + } +} + +function replaceTransparentColor(indata, outdata, width, height, transColor) { + let pxPos = 0; + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + let makeTrans = false; + + if (transColor.length === 1) { + if (transColor[0] === indata[pxPos]) { + makeTrans = true; + } + } else if ( + transColor[0] === indata[pxPos] && + transColor[1] === indata[pxPos + 1] && + transColor[2] === indata[pxPos + 2] + ) { + makeTrans = true; + } + if (makeTrans) { + for (let i = 0; i < 4; i++) { + outdata[pxPos + i] = 0; + } + } + pxPos += 4; + } + } +} + +function scaleDepth(indata, outdata, width, height, depth) { + let maxOutSample = 255; + let maxInSample = Math.pow(2, depth) - 1; + let pxPos = 0; + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + for (let i = 0; i < 4; i++) { + outdata[pxPos + i] = Math.floor( + (indata[pxPos + i] * maxOutSample) / maxInSample + 0.5 + ); + } + pxPos += 4; + } + } +} + +module.exports = function (indata, imageData, skipRescale = false) { + let depth = imageData.depth; + let width = imageData.width; + let height = imageData.height; + let colorType = imageData.colorType; + let transColor = imageData.transColor; + let palette = imageData.palette; + + let outdata = indata; // only different for 16 bits + + if (colorType === 3) { + // paletted + dePalette(indata, outdata, width, height, palette); + } else { + if (transColor) { + replaceTransparentColor(indata, outdata, width, height, transColor); + } + // if it needs scaling + if (depth !== 8 && !skipRescale) { + // if we need to change the buffer size + if (depth === 16) { + outdata = Buffer.alloc(width * height * 4); + } + scaleDepth(indata, outdata, width, height, depth); + } + } + return outdata; +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/interlace.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/interlace.js new file mode 100644 index 00000000..a035cb15 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/interlace.js @@ -0,0 +1,95 @@ +"use strict"; + +// Adam 7 +// 0 1 2 3 4 5 6 7 +// 0 x 6 4 6 x 6 4 6 +// 1 7 7 7 7 7 7 7 7 +// 2 5 6 5 6 5 6 5 6 +// 3 7 7 7 7 7 7 7 7 +// 4 3 6 4 6 3 6 4 6 +// 5 7 7 7 7 7 7 7 7 +// 6 5 6 5 6 5 6 5 6 +// 7 7 7 7 7 7 7 7 7 + +let imagePasses = [ + { + // pass 1 - 1px + x: [0], + y: [0], + }, + { + // pass 2 - 1px + x: [4], + y: [0], + }, + { + // pass 3 - 2px + x: [0, 4], + y: [4], + }, + { + // pass 4 - 4px + x: [2, 6], + y: [0, 4], + }, + { + // pass 5 - 8px + x: [0, 2, 4, 6], + y: [2, 6], + }, + { + // pass 6 - 16px + x: [1, 3, 5, 7], + y: [0, 2, 4, 6], + }, + { + // pass 7 - 32px + x: [0, 1, 2, 3, 4, 5, 6, 7], + y: [1, 3, 5, 7], + }, +]; + +exports.getImagePasses = function (width, height) { + let images = []; + let xLeftOver = width % 8; + let yLeftOver = height % 8; + let xRepeats = (width - xLeftOver) / 8; + let yRepeats = (height - yLeftOver) / 8; + for (let i = 0; i < imagePasses.length; i++) { + let pass = imagePasses[i]; + let passWidth = xRepeats * pass.x.length; + let passHeight = yRepeats * pass.y.length; + for (let j = 0; j < pass.x.length; j++) { + if (pass.x[j] < xLeftOver) { + passWidth++; + } else { + break; + } + } + for (let j = 0; j < pass.y.length; j++) { + if (pass.y[j] < yLeftOver) { + passHeight++; + } else { + break; + } + } + if (passWidth > 0 && passHeight > 0) { + images.push({ width: passWidth, height: passHeight, index: i }); + } + } + return images; +}; + +exports.getInterlaceIterator = function (width) { + return function (x, y, pass) { + let outerXLeftOver = x % imagePasses[pass].x.length; + let outerX = + ((x - outerXLeftOver) / imagePasses[pass].x.length) * 8 + + imagePasses[pass].x[outerXLeftOver]; + let outerYLeftOver = y % imagePasses[pass].y.length; + let outerY = + ((y - outerYLeftOver) / imagePasses[pass].y.length) * 8 + + imagePasses[pass].y[outerYLeftOver]; + return outerX * 4 + outerY * width * 4; + }; +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-async.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-async.js new file mode 100644 index 00000000..f3df73aa --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-async.js @@ -0,0 +1,50 @@ +"use strict"; + +let util = require("util"); +let Stream = require("stream"); +let constants = require("./constants"); +let Packer = require("./packer"); + +let PackerAsync = (module.exports = function (opt) { + Stream.call(this); + + let options = opt || {}; + + this._packer = new Packer(options); + this._deflate = this._packer.createDeflate(); + + this.readable = true; +}); +util.inherits(PackerAsync, Stream); + +PackerAsync.prototype.pack = function (data, width, height, gamma) { + // Signature + this.emit("data", Buffer.from(constants.PNG_SIGNATURE)); + this.emit("data", this._packer.packIHDR(width, height)); + + if (gamma) { + this.emit("data", this._packer.packGAMA(gamma)); + } + + let filteredData = this._packer.filterData(data, width, height); + + // compress it + this._deflate.on("error", this.emit.bind(this, "error")); + + this._deflate.on( + "data", + function (compressedData) { + this.emit("data", this._packer.packIDAT(compressedData)); + }.bind(this) + ); + + this._deflate.on( + "end", + function () { + this.emit("data", this._packer.packIEND()); + this.emit("end"); + }.bind(this) + ); + + this._deflate.end(filteredData); +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-sync.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-sync.js new file mode 100644 index 00000000..f5ab0b3d --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-sync.js @@ -0,0 +1,56 @@ +"use strict"; + +let hasSyncZlib = true; +let zlib = require("zlib"); +if (!zlib.deflateSync) { + hasSyncZlib = false; +} +let constants = require("./constants"); +let Packer = require("./packer"); + +module.exports = function (metaData, opt) { + if (!hasSyncZlib) { + throw new Error( + "To use the sync capability of this library in old node versions, please pin pngjs to v2.3.0" + ); + } + + let options = opt || {}; + + let packer = new Packer(options); + + let chunks = []; + + // Signature + chunks.push(Buffer.from(constants.PNG_SIGNATURE)); + + // Header + chunks.push(packer.packIHDR(metaData.width, metaData.height)); + + if (metaData.gamma) { + chunks.push(packer.packGAMA(metaData.gamma)); + } + + let filteredData = packer.filterData( + metaData.data, + metaData.width, + metaData.height + ); + + // compress it + let compressedData = zlib.deflateSync( + filteredData, + packer.getDeflateOptions() + ); + filteredData = null; + + if (!compressedData || !compressedData.length) { + throw new Error("bad png - invalid compressed data response"); + } + chunks.push(packer.packIDAT(compressedData)); + + // End + chunks.push(packer.packIEND()); + + return Buffer.concat(chunks); +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/packer.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/packer.js new file mode 100644 index 00000000..4aba12c8 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/packer.js @@ -0,0 +1,129 @@ +"use strict"; + +let constants = require("./constants"); +let CrcStream = require("./crc"); +let bitPacker = require("./bitpacker"); +let filter = require("./filter-pack"); +let zlib = require("zlib"); + +let Packer = (module.exports = function (options) { + this._options = options; + + options.deflateChunkSize = options.deflateChunkSize || 32 * 1024; + options.deflateLevel = + options.deflateLevel != null ? options.deflateLevel : 9; + options.deflateStrategy = + options.deflateStrategy != null ? options.deflateStrategy : 3; + options.inputHasAlpha = + options.inputHasAlpha != null ? options.inputHasAlpha : true; + options.deflateFactory = options.deflateFactory || zlib.createDeflate; + options.bitDepth = options.bitDepth || 8; + // This is outputColorType + options.colorType = + typeof options.colorType === "number" + ? options.colorType + : constants.COLORTYPE_COLOR_ALPHA; + options.inputColorType = + typeof options.inputColorType === "number" + ? options.inputColorType + : constants.COLORTYPE_COLOR_ALPHA; + + if ( + [ + constants.COLORTYPE_GRAYSCALE, + constants.COLORTYPE_COLOR, + constants.COLORTYPE_COLOR_ALPHA, + constants.COLORTYPE_ALPHA, + ].indexOf(options.colorType) === -1 + ) { + throw new Error( + "option color type:" + options.colorType + " is not supported at present" + ); + } + if ( + [ + constants.COLORTYPE_GRAYSCALE, + constants.COLORTYPE_COLOR, + constants.COLORTYPE_COLOR_ALPHA, + constants.COLORTYPE_ALPHA, + ].indexOf(options.inputColorType) === -1 + ) { + throw new Error( + "option input color type:" + + options.inputColorType + + " is not supported at present" + ); + } + if (options.bitDepth !== 8 && options.bitDepth !== 16) { + throw new Error( + "option bit depth:" + options.bitDepth + " is not supported at present" + ); + } +}); + +Packer.prototype.getDeflateOptions = function () { + return { + chunkSize: this._options.deflateChunkSize, + level: this._options.deflateLevel, + strategy: this._options.deflateStrategy, + }; +}; + +Packer.prototype.createDeflate = function () { + return this._options.deflateFactory(this.getDeflateOptions()); +}; + +Packer.prototype.filterData = function (data, width, height) { + // convert to correct format for filtering (e.g. right bpp and bit depth) + let packedData = bitPacker(data, width, height, this._options); + + // filter pixel data + let bpp = constants.COLORTYPE_TO_BPP_MAP[this._options.colorType]; + let filteredData = filter(packedData, width, height, this._options, bpp); + return filteredData; +}; + +Packer.prototype._packChunk = function (type, data) { + let len = data ? data.length : 0; + let buf = Buffer.alloc(len + 12); + + buf.writeUInt32BE(len, 0); + buf.writeUInt32BE(type, 4); + + if (data) { + data.copy(buf, 8); + } + + buf.writeInt32BE( + CrcStream.crc32(buf.slice(4, buf.length - 4)), + buf.length - 4 + ); + return buf; +}; + +Packer.prototype.packGAMA = function (gamma) { + let buf = Buffer.alloc(4); + buf.writeUInt32BE(Math.floor(gamma * constants.GAMMA_DIVISION), 0); + return this._packChunk(constants.TYPE_gAMA, buf); +}; + +Packer.prototype.packIHDR = function (width, height) { + let buf = Buffer.alloc(13); + buf.writeUInt32BE(width, 0); + buf.writeUInt32BE(height, 4); + buf[8] = this._options.bitDepth; // Bit depth + buf[9] = this._options.colorType; // colorType + buf[10] = 0; // compression + buf[11] = 0; // filter + buf[12] = 0; // interlace + + return this._packChunk(constants.TYPE_IHDR, buf); +}; + +Packer.prototype.packIDAT = function (data) { + return this._packChunk(constants.TYPE_IDAT, data); +}; + +Packer.prototype.packIEND = function () { + return this._packChunk(constants.TYPE_IEND, null); +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/paeth-predictor.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/paeth-predictor.js new file mode 100644 index 00000000..9634497d --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/paeth-predictor.js @@ -0,0 +1,16 @@ +"use strict"; + +module.exports = function paethPredictor(left, above, upLeft) { + let paeth = left + above - upLeft; + let pLeft = Math.abs(paeth - left); + let pAbove = Math.abs(paeth - above); + let pUpLeft = Math.abs(paeth - upLeft); + + if (pLeft <= pAbove && pLeft <= pUpLeft) { + return left; + } + if (pAbove <= pUpLeft) { + return above; + } + return upLeft; +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-async.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-async.js new file mode 100644 index 00000000..1aacde34 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-async.js @@ -0,0 +1,169 @@ +"use strict"; + +let util = require("util"); +let zlib = require("zlib"); +let ChunkStream = require("./chunkstream"); +let FilterAsync = require("./filter-parse-async"); +let Parser = require("./parser"); +let bitmapper = require("./bitmapper"); +let formatNormaliser = require("./format-normaliser"); + +let ParserAsync = (module.exports = function (options) { + ChunkStream.call(this); + + this._parser = new Parser(options, { + read: this.read.bind(this), + error: this._handleError.bind(this), + metadata: this._handleMetaData.bind(this), + gamma: this.emit.bind(this, "gamma"), + palette: this._handlePalette.bind(this), + transColor: this._handleTransColor.bind(this), + finished: this._finished.bind(this), + inflateData: this._inflateData.bind(this), + simpleTransparency: this._simpleTransparency.bind(this), + headersFinished: this._headersFinished.bind(this), + }); + this._options = options; + this.writable = true; + + this._parser.start(); +}); +util.inherits(ParserAsync, ChunkStream); + +ParserAsync.prototype._handleError = function (err) { + this.emit("error", err); + + this.writable = false; + + this.destroy(); + + if (this._inflate && this._inflate.destroy) { + this._inflate.destroy(); + } + + if (this._filter) { + this._filter.destroy(); + // For backward compatibility with Node 7 and below. + // Suppress errors due to _inflate calling write() even after + // it's destroy()'ed. + this._filter.on("error", function () {}); + } + + this.errord = true; +}; + +ParserAsync.prototype._inflateData = function (data) { + if (!this._inflate) { + if (this._bitmapInfo.interlace) { + this._inflate = zlib.createInflate(); + + this._inflate.on("error", this.emit.bind(this, "error")); + this._filter.on("complete", this._complete.bind(this)); + + this._inflate.pipe(this._filter); + } else { + let rowSize = + ((this._bitmapInfo.width * + this._bitmapInfo.bpp * + this._bitmapInfo.depth + + 7) >> + 3) + + 1; + let imageSize = rowSize * this._bitmapInfo.height; + let chunkSize = Math.max(imageSize, zlib.Z_MIN_CHUNK); + + this._inflate = zlib.createInflate({ chunkSize: chunkSize }); + let leftToInflate = imageSize; + + let emitError = this.emit.bind(this, "error"); + this._inflate.on("error", function (err) { + if (!leftToInflate) { + return; + } + + emitError(err); + }); + this._filter.on("complete", this._complete.bind(this)); + + let filterWrite = this._filter.write.bind(this._filter); + this._inflate.on("data", function (chunk) { + if (!leftToInflate) { + return; + } + + if (chunk.length > leftToInflate) { + chunk = chunk.slice(0, leftToInflate); + } + + leftToInflate -= chunk.length; + + filterWrite(chunk); + }); + + this._inflate.on("end", this._filter.end.bind(this._filter)); + } + } + this._inflate.write(data); +}; + +ParserAsync.prototype._handleMetaData = function (metaData) { + this._metaData = metaData; + this._bitmapInfo = Object.create(metaData); + + this._filter = new FilterAsync(this._bitmapInfo); +}; + +ParserAsync.prototype._handleTransColor = function (transColor) { + this._bitmapInfo.transColor = transColor; +}; + +ParserAsync.prototype._handlePalette = function (palette) { + this._bitmapInfo.palette = palette; +}; + +ParserAsync.prototype._simpleTransparency = function () { + this._metaData.alpha = true; +}; + +ParserAsync.prototype._headersFinished = function () { + // Up until this point, we don't know if we have a tRNS chunk (alpha) + // so we can't emit metadata any earlier + this.emit("metadata", this._metaData); +}; + +ParserAsync.prototype._finished = function () { + if (this.errord) { + return; + } + + if (!this._inflate) { + this.emit("error", "No Inflate block"); + } else { + // no more data to inflate + this._inflate.end(); + } +}; + +ParserAsync.prototype._complete = function (filteredData) { + if (this.errord) { + return; + } + + let normalisedBitmapData; + + try { + let bitmapData = bitmapper.dataToBitMap(filteredData, this._bitmapInfo); + + normalisedBitmapData = formatNormaliser( + bitmapData, + this._bitmapInfo, + this._options.skipRescale + ); + bitmapData = null; + } catch (ex) { + this._handleError(ex); + return; + } + + this.emit("parsed", normalisedBitmapData); +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-sync.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-sync.js new file mode 100644 index 00000000..76cb134b --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-sync.js @@ -0,0 +1,112 @@ +"use strict"; + +let hasSyncZlib = true; +let zlib = require("zlib"); +let inflateSync = require("./sync-inflate"); +if (!zlib.deflateSync) { + hasSyncZlib = false; +} +let SyncReader = require("./sync-reader"); +let FilterSync = require("./filter-parse-sync"); +let Parser = require("./parser"); +let bitmapper = require("./bitmapper"); +let formatNormaliser = require("./format-normaliser"); + +module.exports = function (buffer, options) { + if (!hasSyncZlib) { + throw new Error( + "To use the sync capability of this library in old node versions, please pin pngjs to v2.3.0" + ); + } + + let err; + function handleError(_err_) { + err = _err_; + } + + let metaData; + function handleMetaData(_metaData_) { + metaData = _metaData_; + } + + function handleTransColor(transColor) { + metaData.transColor = transColor; + } + + function handlePalette(palette) { + metaData.palette = palette; + } + + function handleSimpleTransparency() { + metaData.alpha = true; + } + + let gamma; + function handleGamma(_gamma_) { + gamma = _gamma_; + } + + let inflateDataList = []; + function handleInflateData(inflatedData) { + inflateDataList.push(inflatedData); + } + + let reader = new SyncReader(buffer); + + let parser = new Parser(options, { + read: reader.read.bind(reader), + error: handleError, + metadata: handleMetaData, + gamma: handleGamma, + palette: handlePalette, + transColor: handleTransColor, + inflateData: handleInflateData, + simpleTransparency: handleSimpleTransparency, + }); + + parser.start(); + reader.process(); + + if (err) { + throw err; + } + + //join together the inflate datas + let inflateData = Buffer.concat(inflateDataList); + inflateDataList.length = 0; + + let inflatedData; + if (metaData.interlace) { + inflatedData = zlib.inflateSync(inflateData); + } else { + let rowSize = + ((metaData.width * metaData.bpp * metaData.depth + 7) >> 3) + 1; + let imageSize = rowSize * metaData.height; + inflatedData = inflateSync(inflateData, { + chunkSize: imageSize, + maxLength: imageSize, + }); + } + inflateData = null; + + if (!inflatedData || !inflatedData.length) { + throw new Error("bad png - invalid inflate data response"); + } + + let unfilteredData = FilterSync.process(inflatedData, metaData); + inflateData = null; + + let bitmapData = bitmapper.dataToBitMap(unfilteredData, metaData); + unfilteredData = null; + + let normalisedBitmapData = formatNormaliser( + bitmapData, + metaData, + options.skipRescale + ); + + metaData.data = normalisedBitmapData; + metaData.gamma = gamma || 0; + + return metaData; +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/parser.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/parser.js new file mode 100644 index 00000000..51a8f2a5 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/parser.js @@ -0,0 +1,290 @@ +"use strict"; + +let constants = require("./constants"); +let CrcCalculator = require("./crc"); + +let Parser = (module.exports = function (options, dependencies) { + this._options = options; + options.checkCRC = options.checkCRC !== false; + + this._hasIHDR = false; + this._hasIEND = false; + this._emittedHeadersFinished = false; + + // input flags/metadata + this._palette = []; + this._colorType = 0; + + this._chunks = {}; + this._chunks[constants.TYPE_IHDR] = this._handleIHDR.bind(this); + this._chunks[constants.TYPE_IEND] = this._handleIEND.bind(this); + this._chunks[constants.TYPE_IDAT] = this._handleIDAT.bind(this); + this._chunks[constants.TYPE_PLTE] = this._handlePLTE.bind(this); + this._chunks[constants.TYPE_tRNS] = this._handleTRNS.bind(this); + this._chunks[constants.TYPE_gAMA] = this._handleGAMA.bind(this); + + this.read = dependencies.read; + this.error = dependencies.error; + this.metadata = dependencies.metadata; + this.gamma = dependencies.gamma; + this.transColor = dependencies.transColor; + this.palette = dependencies.palette; + this.parsed = dependencies.parsed; + this.inflateData = dependencies.inflateData; + this.finished = dependencies.finished; + this.simpleTransparency = dependencies.simpleTransparency; + this.headersFinished = dependencies.headersFinished || function () {}; +}); + +Parser.prototype.start = function () { + this.read(constants.PNG_SIGNATURE.length, this._parseSignature.bind(this)); +}; + +Parser.prototype._parseSignature = function (data) { + let signature = constants.PNG_SIGNATURE; + + for (let i = 0; i < signature.length; i++) { + if (data[i] !== signature[i]) { + this.error(new Error("Invalid file signature")); + return; + } + } + this.read(8, this._parseChunkBegin.bind(this)); +}; + +Parser.prototype._parseChunkBegin = function (data) { + // chunk content length + let length = data.readUInt32BE(0); + + // chunk type + let type = data.readUInt32BE(4); + let name = ""; + for (let i = 4; i < 8; i++) { + name += String.fromCharCode(data[i]); + } + + //console.log('chunk ', name, length); + + // chunk flags + let ancillary = Boolean(data[4] & 0x20); // or critical + // priv = Boolean(data[5] & 0x20), // or public + // safeToCopy = Boolean(data[7] & 0x20); // or unsafe + + if (!this._hasIHDR && type !== constants.TYPE_IHDR) { + this.error(new Error("Expected IHDR on beggining")); + return; + } + + this._crc = new CrcCalculator(); + this._crc.write(Buffer.from(name)); + + if (this._chunks[type]) { + return this._chunks[type](length); + } + + if (!ancillary) { + this.error(new Error("Unsupported critical chunk type " + name)); + return; + } + + this.read(length + 4, this._skipChunk.bind(this)); +}; + +Parser.prototype._skipChunk = function (/*data*/) { + this.read(8, this._parseChunkBegin.bind(this)); +}; + +Parser.prototype._handleChunkEnd = function () { + this.read(4, this._parseChunkEnd.bind(this)); +}; + +Parser.prototype._parseChunkEnd = function (data) { + let fileCrc = data.readInt32BE(0); + let calcCrc = this._crc.crc32(); + + // check CRC + if (this._options.checkCRC && calcCrc !== fileCrc) { + this.error(new Error("Crc error - " + fileCrc + " - " + calcCrc)); + return; + } + + if (!this._hasIEND) { + this.read(8, this._parseChunkBegin.bind(this)); + } +}; + +Parser.prototype._handleIHDR = function (length) { + this.read(length, this._parseIHDR.bind(this)); +}; +Parser.prototype._parseIHDR = function (data) { + this._crc.write(data); + + let width = data.readUInt32BE(0); + let height = data.readUInt32BE(4); + let depth = data[8]; + let colorType = data[9]; // bits: 1 palette, 2 color, 4 alpha + let compr = data[10]; + let filter = data[11]; + let interlace = data[12]; + + // console.log(' width', width, 'height', height, + // 'depth', depth, 'colorType', colorType, + // 'compr', compr, 'filter', filter, 'interlace', interlace + // ); + + if ( + depth !== 8 && + depth !== 4 && + depth !== 2 && + depth !== 1 && + depth !== 16 + ) { + this.error(new Error("Unsupported bit depth " + depth)); + return; + } + if (!(colorType in constants.COLORTYPE_TO_BPP_MAP)) { + this.error(new Error("Unsupported color type")); + return; + } + if (compr !== 0) { + this.error(new Error("Unsupported compression method")); + return; + } + if (filter !== 0) { + this.error(new Error("Unsupported filter method")); + return; + } + if (interlace !== 0 && interlace !== 1) { + this.error(new Error("Unsupported interlace method")); + return; + } + + this._colorType = colorType; + + let bpp = constants.COLORTYPE_TO_BPP_MAP[this._colorType]; + + this._hasIHDR = true; + + this.metadata({ + width: width, + height: height, + depth: depth, + interlace: Boolean(interlace), + palette: Boolean(colorType & constants.COLORTYPE_PALETTE), + color: Boolean(colorType & constants.COLORTYPE_COLOR), + alpha: Boolean(colorType & constants.COLORTYPE_ALPHA), + bpp: bpp, + colorType: colorType, + }); + + this._handleChunkEnd(); +}; + +Parser.prototype._handlePLTE = function (length) { + this.read(length, this._parsePLTE.bind(this)); +}; +Parser.prototype._parsePLTE = function (data) { + this._crc.write(data); + + let entries = Math.floor(data.length / 3); + // console.log('Palette:', entries); + + for (let i = 0; i < entries; i++) { + this._palette.push([data[i * 3], data[i * 3 + 1], data[i * 3 + 2], 0xff]); + } + + this.palette(this._palette); + + this._handleChunkEnd(); +}; + +Parser.prototype._handleTRNS = function (length) { + this.simpleTransparency(); + this.read(length, this._parseTRNS.bind(this)); +}; +Parser.prototype._parseTRNS = function (data) { + this._crc.write(data); + + // palette + if (this._colorType === constants.COLORTYPE_PALETTE_COLOR) { + if (this._palette.length === 0) { + this.error(new Error("Transparency chunk must be after palette")); + return; + } + if (data.length > this._palette.length) { + this.error(new Error("More transparent colors than palette size")); + return; + } + for (let i = 0; i < data.length; i++) { + this._palette[i][3] = data[i]; + } + this.palette(this._palette); + } + + // for colorType 0 (grayscale) and 2 (rgb) + // there might be one gray/color defined as transparent + if (this._colorType === constants.COLORTYPE_GRAYSCALE) { + // grey, 2 bytes + this.transColor([data.readUInt16BE(0)]); + } + if (this._colorType === constants.COLORTYPE_COLOR) { + this.transColor([ + data.readUInt16BE(0), + data.readUInt16BE(2), + data.readUInt16BE(4), + ]); + } + + this._handleChunkEnd(); +}; + +Parser.prototype._handleGAMA = function (length) { + this.read(length, this._parseGAMA.bind(this)); +}; +Parser.prototype._parseGAMA = function (data) { + this._crc.write(data); + this.gamma(data.readUInt32BE(0) / constants.GAMMA_DIVISION); + + this._handleChunkEnd(); +}; + +Parser.prototype._handleIDAT = function (length) { + if (!this._emittedHeadersFinished) { + this._emittedHeadersFinished = true; + this.headersFinished(); + } + this.read(-length, this._parseIDAT.bind(this, length)); +}; +Parser.prototype._parseIDAT = function (length, data) { + this._crc.write(data); + + if ( + this._colorType === constants.COLORTYPE_PALETTE_COLOR && + this._palette.length === 0 + ) { + throw new Error("Expected palette not found"); + } + + this.inflateData(data); + let leftOverLength = length - data.length; + + if (leftOverLength > 0) { + this._handleIDAT(leftOverLength); + } else { + this._handleChunkEnd(); + } +}; + +Parser.prototype._handleIEND = function (length) { + this.read(length, this._parseIEND.bind(this)); +}; +Parser.prototype._parseIEND = function (data) { + this._crc.write(data); + + this._hasIEND = true; + this._handleChunkEnd(); + + if (this.finished) { + this.finished(); + } +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/png-sync.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/png-sync.js new file mode 100644 index 00000000..68cac9bc --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/png-sync.js @@ -0,0 +1,12 @@ +"use strict"; + +let parse = require("./parser-sync"); +let pack = require("./packer-sync"); + +exports.read = function (buffer, options) { + return parse(buffer, options || {}); +}; + +exports.write = function (png, options) { + return pack(png, options); +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/png.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/png.js new file mode 100644 index 00000000..0b8af3f7 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/png.js @@ -0,0 +1,194 @@ +"use strict"; + +let util = require("util"); +let Stream = require("stream"); +let Parser = require("./parser-async"); +let Packer = require("./packer-async"); +let PNGSync = require("./png-sync"); + +let PNG = (exports.PNG = function (options) { + Stream.call(this); + + options = options || {}; // eslint-disable-line no-param-reassign + + // coerce pixel dimensions to integers (also coerces undefined -> 0): + this.width = options.width | 0; + this.height = options.height | 0; + + this.data = + this.width > 0 && this.height > 0 + ? Buffer.alloc(4 * this.width * this.height) + : null; + + if (options.fill && this.data) { + this.data.fill(0); + } + + this.gamma = 0; + this.readable = this.writable = true; + + this._parser = new Parser(options); + + this._parser.on("error", this.emit.bind(this, "error")); + this._parser.on("close", this._handleClose.bind(this)); + this._parser.on("metadata", this._metadata.bind(this)); + this._parser.on("gamma", this._gamma.bind(this)); + this._parser.on( + "parsed", + function (data) { + this.data = data; + this.emit("parsed", data); + }.bind(this) + ); + + this._packer = new Packer(options); + this._packer.on("data", this.emit.bind(this, "data")); + this._packer.on("end", this.emit.bind(this, "end")); + this._parser.on("close", this._handleClose.bind(this)); + this._packer.on("error", this.emit.bind(this, "error")); +}); +util.inherits(PNG, Stream); + +PNG.sync = PNGSync; + +PNG.prototype.pack = function () { + if (!this.data || !this.data.length) { + this.emit("error", "No data provided"); + return this; + } + + process.nextTick( + function () { + this._packer.pack(this.data, this.width, this.height, this.gamma); + }.bind(this) + ); + + return this; +}; + +PNG.prototype.parse = function (data, callback) { + if (callback) { + let onParsed, onError; + + onParsed = function (parsedData) { + this.removeListener("error", onError); + + this.data = parsedData; + callback(null, this); + }.bind(this); + + onError = function (err) { + this.removeListener("parsed", onParsed); + + callback(err, null); + }.bind(this); + + this.once("parsed", onParsed); + this.once("error", onError); + } + + this.end(data); + return this; +}; + +PNG.prototype.write = function (data) { + this._parser.write(data); + return true; +}; + +PNG.prototype.end = function (data) { + this._parser.end(data); +}; + +PNG.prototype._metadata = function (metadata) { + this.width = metadata.width; + this.height = metadata.height; + + this.emit("metadata", metadata); +}; + +PNG.prototype._gamma = function (gamma) { + this.gamma = gamma; +}; + +PNG.prototype._handleClose = function () { + if (!this._parser.writable && !this._packer.readable) { + this.emit("close"); + } +}; + +PNG.bitblt = function (src, dst, srcX, srcY, width, height, deltaX, deltaY) { + // eslint-disable-line max-params + // coerce pixel dimensions to integers (also coerces undefined -> 0): + /* eslint-disable no-param-reassign */ + srcX |= 0; + srcY |= 0; + width |= 0; + height |= 0; + deltaX |= 0; + deltaY |= 0; + /* eslint-enable no-param-reassign */ + + if ( + srcX > src.width || + srcY > src.height || + srcX + width > src.width || + srcY + height > src.height + ) { + throw new Error("bitblt reading outside image"); + } + + if ( + deltaX > dst.width || + deltaY > dst.height || + deltaX + width > dst.width || + deltaY + height > dst.height + ) { + throw new Error("bitblt writing outside image"); + } + + for (let y = 0; y < height; y++) { + src.data.copy( + dst.data, + ((deltaY + y) * dst.width + deltaX) << 2, + ((srcY + y) * src.width + srcX) << 2, + ((srcY + y) * src.width + srcX + width) << 2 + ); + } +}; + +PNG.prototype.bitblt = function ( + dst, + srcX, + srcY, + width, + height, + deltaX, + deltaY +) { + // eslint-disable-line max-params + + PNG.bitblt(this, dst, srcX, srcY, width, height, deltaX, deltaY); + return this; +}; + +PNG.adjustGamma = function (src) { + if (src.gamma) { + for (let y = 0; y < src.height; y++) { + for (let x = 0; x < src.width; x++) { + let idx = (src.width * y + x) << 2; + + for (let i = 0; i < 3; i++) { + let sample = src.data[idx + i] / 255; + sample = Math.pow(sample, 1 / 2.2 / src.gamma); + src.data[idx + i] = Math.round(sample * 255); + } + } + } + src.gamma = 0; + } +}; + +PNG.prototype.adjustGamma = function () { + PNG.adjustGamma(this); +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-inflate.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-inflate.js new file mode 100644 index 00000000..4da0d5f0 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-inflate.js @@ -0,0 +1,168 @@ +"use strict"; + +let assert = require("assert").ok; +let zlib = require("zlib"); +let util = require("util"); + +let kMaxLength = require("buffer").kMaxLength; + +function Inflate(opts) { + if (!(this instanceof Inflate)) { + return new Inflate(opts); + } + + if (opts && opts.chunkSize < zlib.Z_MIN_CHUNK) { + opts.chunkSize = zlib.Z_MIN_CHUNK; + } + + zlib.Inflate.call(this, opts); + + // Node 8 --> 9 compatibility check + this._offset = this._offset === undefined ? this._outOffset : this._offset; + this._buffer = this._buffer || this._outBuffer; + + if (opts && opts.maxLength != null) { + this._maxLength = opts.maxLength; + } +} + +function createInflate(opts) { + return new Inflate(opts); +} + +function _close(engine, callback) { + if (callback) { + process.nextTick(callback); + } + + // Caller may invoke .close after a zlib error (which will null _handle). + if (!engine._handle) { + return; + } + + engine._handle.close(); + engine._handle = null; +} + +Inflate.prototype._processChunk = function (chunk, flushFlag, asyncCb) { + if (typeof asyncCb === "function") { + return zlib.Inflate._processChunk.call(this, chunk, flushFlag, asyncCb); + } + + let self = this; + + let availInBefore = chunk && chunk.length; + let availOutBefore = this._chunkSize - this._offset; + let leftToInflate = this._maxLength; + let inOff = 0; + + let buffers = []; + let nread = 0; + + let error; + this.on("error", function (err) { + error = err; + }); + + function handleChunk(availInAfter, availOutAfter) { + if (self._hadError) { + return; + } + + let have = availOutBefore - availOutAfter; + assert(have >= 0, "have should not go down"); + + if (have > 0) { + let out = self._buffer.slice(self._offset, self._offset + have); + self._offset += have; + + if (out.length > leftToInflate) { + out = out.slice(0, leftToInflate); + } + + buffers.push(out); + nread += out.length; + leftToInflate -= out.length; + + if (leftToInflate === 0) { + return false; + } + } + + if (availOutAfter === 0 || self._offset >= self._chunkSize) { + availOutBefore = self._chunkSize; + self._offset = 0; + self._buffer = Buffer.allocUnsafe(self._chunkSize); + } + + if (availOutAfter === 0) { + inOff += availInBefore - availInAfter; + availInBefore = availInAfter; + + return true; + } + + return false; + } + + assert(this._handle, "zlib binding closed"); + let res; + do { + res = this._handle.writeSync( + flushFlag, + chunk, // in + inOff, // in_off + availInBefore, // in_len + this._buffer, // out + this._offset, //out_off + availOutBefore + ); // out_len + // Node 8 --> 9 compatibility check + res = res || this._writeState; + } while (!this._hadError && handleChunk(res[0], res[1])); + + if (this._hadError) { + throw error; + } + + if (nread >= kMaxLength) { + _close(this); + throw new RangeError( + "Cannot create final Buffer. It would be larger than 0x" + + kMaxLength.toString(16) + + " bytes" + ); + } + + let buf = Buffer.concat(buffers, nread); + _close(this); + + return buf; +}; + +util.inherits(Inflate, zlib.Inflate); + +function zlibBufferSync(engine, buffer) { + if (typeof buffer === "string") { + buffer = Buffer.from(buffer); + } + if (!(buffer instanceof Buffer)) { + throw new TypeError("Not a string or buffer"); + } + + let flushFlag = engine._finishFlushFlag; + if (flushFlag == null) { + flushFlag = zlib.Z_FINISH; + } + + return engine._processChunk(buffer, flushFlag); +} + +function inflateSync(buffer, opts) { + return zlibBufferSync(new Inflate(opts), buffer); +} + +module.exports = exports = inflateSync; +exports.Inflate = Inflate; +exports.createInflate = createInflate; +exports.inflateSync = inflateSync; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-reader.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-reader.js new file mode 100644 index 00000000..213d1a75 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-reader.js @@ -0,0 +1,45 @@ +"use strict"; + +let SyncReader = (module.exports = function (buffer) { + this._buffer = buffer; + this._reads = []; +}); + +SyncReader.prototype.read = function (length, callback) { + this._reads.push({ + length: Math.abs(length), // if length < 0 then at most this length + allowLess: length < 0, + func: callback, + }); +}; + +SyncReader.prototype.process = function () { + // as long as there is any data and read requests + while (this._reads.length > 0 && this._buffer.length) { + let read = this._reads[0]; + + if ( + this._buffer.length && + (this._buffer.length >= read.length || read.allowLess) + ) { + // ok there is any data so that we can satisfy this request + this._reads.shift(); // == read + + let buf = this._buffer; + + this._buffer = buf.slice(read.length); + + read.func.call(this, buf.slice(0, read.length)); + } else { + break; + } + } + + if (this._reads.length > 0) { + throw new Error("There are some read requests waitng on finished stream"); + } + + if (this._buffer.length > 0) { + throw new Error("unrecognised content at end of stream"); + } +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/package.json b/node_modules/lv_font_conv/node_modules/pngjs/package.json new file mode 100644 index 00000000..1f696a7e --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/package.json @@ -0,0 +1,129 @@ +{ + "_args": [ + [ + "pngjs@6.0.0", + "/home/vitaly/Dropbox/Coding/lv_font_conv" + ] + ], + "_from": "pngjs@6.0.0", + "_id": "pngjs@6.0.0", + "_inBundle": false, + "_integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "_location": "/pngjs", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "pngjs@6.0.0", + "name": "pngjs", + "escapedName": "pngjs", + "rawSpec": "6.0.0", + "saveSpec": null, + "fetchSpec": "6.0.0" + }, + "_requiredBy": [ + "/" + ], + "_resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "_spec": "6.0.0", + "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", + "bugs": { + "url": "https://github.com/lukeapage/pngjs/issues" + }, + "contributors": [ + { + "name": "Alexandre Paré" + }, + { + "name": "Gaurav Mali" + }, + { + "name": "Gusts Kaksis" + }, + { + "name": "Kuba Niegowski" + }, + { + "name": "Luke Page" + }, + { + "name": "Pietajan De Potter" + }, + { + "name": "Steven Sojka" + }, + { + "name": "liangzeng" + }, + { + "name": "Michael Vogt" + }, + { + "name": "Xin-Xin Wang" + }, + { + "name": "toriningen" + }, + { + "name": "Eugene Kulabuhov" + } + ], + "description": "PNG encoder/decoder in pure JS, supporting any bit size & interlace, async & sync with full test suite.", + "devDependencies": { + "browserify": "17.0.0", + "buffer-equal": "1.0.0", + "codecov": "3.7.1", + "connect": "3.7.0", + "eslint": "7.8.1", + "eslint-config-prettier": "6.14.0", + "nyc": "15.1.0", + "prettier": "2.1.1", + "puppeteer": "5.4.0", + "serve-static": "1.14.1", + "tap-dot": "2.0.0", + "tape": "5.0.1" + }, + "directories": { + "lib": "lib", + "example": "examples", + "test": "test" + }, + "engines": { + "node": ">=12.13.0" + }, + "files": [ + "browser.js", + "lib/" + ], + "homepage": "https://github.com/lukeapage/pngjs", + "keywords": [ + "PNG", + "decoder", + "encoder", + "js-png", + "node-png", + "parser", + "png", + "png-js", + "png-parse", + "pngjs" + ], + "license": "MIT", + "main": "./lib/png.js", + "name": "pngjs", + "repository": { + "type": "git", + "url": "git://github.com/lukeapage/pngjs.git" + }, + "scripts": { + "browserify": "browserify lib/png.js --standalone png > browser.js", + "build": "yarn prepublish", + "coverage": "nyc --reporter=lcov --reporter=text-summary tape test/*-spec.js nolarge", + "lint": "eslint .", + "prepublish": "yarn browserify", + "prettier:check": "prettier --check .", + "prettier:write": "prettier --write .", + "test": "yarn lint && yarn prettier:check && tape test/*-spec.js | tap-dot && node test/run-compare" + }, + "version": "6.0.0" +} diff --git a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/LICENSE-MIT.txt b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/LICENSE-MIT.txt new file mode 100644 index 00000000..a41e0a7e --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/LICENSE-MIT.txt @@ -0,0 +1,20 @@ +Copyright Mathias Bynens + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/README.md b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/README.md new file mode 100644 index 00000000..d648b879 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/README.md @@ -0,0 +1,47 @@ +# ES6 `String.prototype.codePointAt` polyfill [![Build status](https://travis-ci.org/mathiasbynens/String.prototype.codePointAt.svg?branch=master)](https://travis-ci.org/mathiasbynens/String.prototype.codePointAt) + +A robust & optimized ES3-compatible polyfill for [the `String.prototype.codePointAt` method in ECMAScript 6](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-string.prototype.codepointat). + +Other polyfills for `String.prototype.codePointAt` are available: + +* by [Norbert Lindenberg](http://norbertlindenberg.com/) (fails some tests) +* by [Steven Levithan](http://stevenlevithan.com/) (fails some tests) +* by [Paul Miller](http://paulmillr.com/) (~~[fails some tests](https://github.com/paulmillr/es6-shim/issues/166)~~ passes all tests) + +## Installation + +In a browser: + +```html + +``` + +Via [npm](http://npmjs.org/): + +```bash +npm install string.prototype.codepointat +``` + +Then, in [Node.js](http://nodejs.org/): + +```js +require('string.prototype.codepointat'); + +// On Windows and on Mac systems with default settings, case doesn’t matter, +// which allows you to do this instead: +require('String.prototype.codePointAt'); +``` + +## Notes + +[A polyfill + test suite for `String.fromCodePoint`](https://mths.be/fromcodepoint) is available, too. + +## Author + +| [![twitter/mathias](https://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") | +|---| +| [Mathias Bynens](https://mathiasbynens.be/) | + +## License + +This polyfill is available under the [MIT](https://mths.be/mit) license. diff --git a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/codepointat.js b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/codepointat.js new file mode 100644 index 00000000..f724c892 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/codepointat.js @@ -0,0 +1,54 @@ +/*! https://mths.be/codepointat v0.2.0 by @mathias */ +if (!String.prototype.codePointAt) { + (function() { + 'use strict'; // needed to support `apply`/`call` with `undefined`/`null` + var defineProperty = (function() { + // IE 8 only supports `Object.defineProperty` on DOM elements + try { + var object = {}; + var $defineProperty = Object.defineProperty; + var result = $defineProperty(object, object, object) && $defineProperty; + } catch(error) {} + return result; + }()); + var codePointAt = function(position) { + if (this == null) { + throw TypeError(); + } + var string = String(this); + var size = string.length; + // `ToInteger` + var index = position ? Number(position) : 0; + if (index != index) { // better `isNaN` + index = 0; + } + // Account for out-of-bounds indices: + if (index < 0 || index >= size) { + return undefined; + } + // Get the first code unit + var first = string.charCodeAt(index); + var second; + if ( // check if it’s the start of a surrogate pair + first >= 0xD800 && first <= 0xDBFF && // high surrogate + size > index + 1 // there is a next code unit + ) { + second = string.charCodeAt(index + 1); + if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate + // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; + } + } + return first; + }; + if (defineProperty) { + defineProperty(String.prototype, 'codePointAt', { + 'value': codePointAt, + 'configurable': true, + 'writable': true + }); + } else { + String.prototype.codePointAt = codePointAt; + } + }()); +} diff --git a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/package.json b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/package.json new file mode 100644 index 00000000..cee4e79d --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/package.json @@ -0,0 +1,62 @@ +{ + "_args": [ + [ + "string.prototype.codepointat@0.2.1", + "/home/vitaly/Dropbox/Coding/lv_font_conv" + ] + ], + "_from": "string.prototype.codepointat@0.2.1", + "_id": "string.prototype.codepointat@0.2.1", + "_inBundle": false, + "_integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==", + "_location": "/string.prototype.codepointat", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "string.prototype.codepointat@0.2.1", + "name": "string.prototype.codepointat", + "escapedName": "string.prototype.codepointat", + "rawSpec": "0.2.1", + "saveSpec": null, + "fetchSpec": "0.2.1" + }, + "_requiredBy": [ + "/opentype.js" + ], + "_resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", + "_spec": "0.2.1", + "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", + "author": { + "name": "Mathias Bynens", + "url": "https://mathiasbynens.be/" + }, + "bugs": { + "url": "https://github.com/mathiasbynens/String.prototype.codePointAt/issues" + }, + "description": "A robust & optimized `String.prototype.codePointAt` polyfill, based on the ECMAScript 6 specification.", + "files": [ + "LICENSE-MIT.txt", + "codepointat.js" + ], + "homepage": "https://mths.be/codepointat", + "keywords": [ + "string", + "unicode", + "es6", + "ecmascript", + "polyfill" + ], + "license": "MIT", + "main": "codepointat.js", + "name": "string.prototype.codepointat", + "repository": { + "type": "git", + "url": "git+https://github.com/mathiasbynens/String.prototype.codePointAt.git" + }, + "scripts": { + "cover": "istanbul cover --report html --verbose --dir coverage tests/tests.js", + "test": "node tests/tests.js" + }, + "version": "0.2.1" +} diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/LICENSE b/node_modules/lv_font_conv/node_modules/tiny-inflate/LICENSE new file mode 100644 index 00000000..62914548 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/tiny-inflate/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2015-present Devon Govett + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/index.js b/node_modules/lv_font_conv/node_modules/tiny-inflate/index.js new file mode 100644 index 00000000..44d1151b --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/tiny-inflate/index.js @@ -0,0 +1,375 @@ +var TINF_OK = 0; +var TINF_DATA_ERROR = -3; + +function Tree() { + this.table = new Uint16Array(16); /* table of code length counts */ + this.trans = new Uint16Array(288); /* code -> symbol translation table */ +} + +function Data(source, dest) { + this.source = source; + this.sourceIndex = 0; + this.tag = 0; + this.bitcount = 0; + + this.dest = dest; + this.destLen = 0; + + this.ltree = new Tree(); /* dynamic length/symbol tree */ + this.dtree = new Tree(); /* dynamic distance tree */ +} + +/* --------------------------------------------------- * + * -- uninitialized global data (static structures) -- * + * --------------------------------------------------- */ + +var sltree = new Tree(); +var sdtree = new Tree(); + +/* extra bits and base tables for length codes */ +var length_bits = new Uint8Array(30); +var length_base = new Uint16Array(30); + +/* extra bits and base tables for distance codes */ +var dist_bits = new Uint8Array(30); +var dist_base = new Uint16Array(30); + +/* special ordering of code length codes */ +var clcidx = new Uint8Array([ + 16, 17, 18, 0, 8, 7, 9, 6, + 10, 5, 11, 4, 12, 3, 13, 2, + 14, 1, 15 +]); + +/* used by tinf_decode_trees, avoids allocations every call */ +var code_tree = new Tree(); +var lengths = new Uint8Array(288 + 32); + +/* ----------------------- * + * -- utility functions -- * + * ----------------------- */ + +/* build extra bits and base tables */ +function tinf_build_bits_base(bits, base, delta, first) { + var i, sum; + + /* build bits table */ + for (i = 0; i < delta; ++i) bits[i] = 0; + for (i = 0; i < 30 - delta; ++i) bits[i + delta] = i / delta | 0; + + /* build base table */ + for (sum = first, i = 0; i < 30; ++i) { + base[i] = sum; + sum += 1 << bits[i]; + } +} + +/* build the fixed huffman trees */ +function tinf_build_fixed_trees(lt, dt) { + var i; + + /* build fixed length tree */ + for (i = 0; i < 7; ++i) lt.table[i] = 0; + + lt.table[7] = 24; + lt.table[8] = 152; + lt.table[9] = 112; + + for (i = 0; i < 24; ++i) lt.trans[i] = 256 + i; + for (i = 0; i < 144; ++i) lt.trans[24 + i] = i; + for (i = 0; i < 8; ++i) lt.trans[24 + 144 + i] = 280 + i; + for (i = 0; i < 112; ++i) lt.trans[24 + 144 + 8 + i] = 144 + i; + + /* build fixed distance tree */ + for (i = 0; i < 5; ++i) dt.table[i] = 0; + + dt.table[5] = 32; + + for (i = 0; i < 32; ++i) dt.trans[i] = i; +} + +/* given an array of code lengths, build a tree */ +var offs = new Uint16Array(16); + +function tinf_build_tree(t, lengths, off, num) { + var i, sum; + + /* clear code length count table */ + for (i = 0; i < 16; ++i) t.table[i] = 0; + + /* scan symbol lengths, and sum code length counts */ + for (i = 0; i < num; ++i) t.table[lengths[off + i]]++; + + t.table[0] = 0; + + /* compute offset table for distribution sort */ + for (sum = 0, i = 0; i < 16; ++i) { + offs[i] = sum; + sum += t.table[i]; + } + + /* create code->symbol translation table (symbols sorted by code) */ + for (i = 0; i < num; ++i) { + if (lengths[off + i]) t.trans[offs[lengths[off + i]]++] = i; + } +} + +/* ---------------------- * + * -- decode functions -- * + * ---------------------- */ + +/* get one bit from source stream */ +function tinf_getbit(d) { + /* check if tag is empty */ + if (!d.bitcount--) { + /* load next tag */ + d.tag = d.source[d.sourceIndex++]; + d.bitcount = 7; + } + + /* shift bit out of tag */ + var bit = d.tag & 1; + d.tag >>>= 1; + + return bit; +} + +/* read a num bit value from a stream and add base */ +function tinf_read_bits(d, num, base) { + if (!num) + return base; + + while (d.bitcount < 24) { + d.tag |= d.source[d.sourceIndex++] << d.bitcount; + d.bitcount += 8; + } + + var val = d.tag & (0xffff >>> (16 - num)); + d.tag >>>= num; + d.bitcount -= num; + return val + base; +} + +/* given a data stream and a tree, decode a symbol */ +function tinf_decode_symbol(d, t) { + while (d.bitcount < 24) { + d.tag |= d.source[d.sourceIndex++] << d.bitcount; + d.bitcount += 8; + } + + var sum = 0, cur = 0, len = 0; + var tag = d.tag; + + /* get more bits while code value is above sum */ + do { + cur = 2 * cur + (tag & 1); + tag >>>= 1; + ++len; + + sum += t.table[len]; + cur -= t.table[len]; + } while (cur >= 0); + + d.tag = tag; + d.bitcount -= len; + + return t.trans[sum + cur]; +} + +/* given a data stream, decode dynamic trees from it */ +function tinf_decode_trees(d, lt, dt) { + var hlit, hdist, hclen; + var i, num, length; + + /* get 5 bits HLIT (257-286) */ + hlit = tinf_read_bits(d, 5, 257); + + /* get 5 bits HDIST (1-32) */ + hdist = tinf_read_bits(d, 5, 1); + + /* get 4 bits HCLEN (4-19) */ + hclen = tinf_read_bits(d, 4, 4); + + for (i = 0; i < 19; ++i) lengths[i] = 0; + + /* read code lengths for code length alphabet */ + for (i = 0; i < hclen; ++i) { + /* get 3 bits code length (0-7) */ + var clen = tinf_read_bits(d, 3, 0); + lengths[clcidx[i]] = clen; + } + + /* build code length tree */ + tinf_build_tree(code_tree, lengths, 0, 19); + + /* decode code lengths for the dynamic trees */ + for (num = 0; num < hlit + hdist;) { + var sym = tinf_decode_symbol(d, code_tree); + + switch (sym) { + case 16: + /* copy previous code length 3-6 times (read 2 bits) */ + var prev = lengths[num - 1]; + for (length = tinf_read_bits(d, 2, 3); length; --length) { + lengths[num++] = prev; + } + break; + case 17: + /* repeat code length 0 for 3-10 times (read 3 bits) */ + for (length = tinf_read_bits(d, 3, 3); length; --length) { + lengths[num++] = 0; + } + break; + case 18: + /* repeat code length 0 for 11-138 times (read 7 bits) */ + for (length = tinf_read_bits(d, 7, 11); length; --length) { + lengths[num++] = 0; + } + break; + default: + /* values 0-15 represent the actual code lengths */ + lengths[num++] = sym; + break; + } + } + + /* build dynamic trees */ + tinf_build_tree(lt, lengths, 0, hlit); + tinf_build_tree(dt, lengths, hlit, hdist); +} + +/* ----------------------------- * + * -- block inflate functions -- * + * ----------------------------- */ + +/* given a stream and two trees, inflate a block of data */ +function tinf_inflate_block_data(d, lt, dt) { + while (1) { + var sym = tinf_decode_symbol(d, lt); + + /* check for end of block */ + if (sym === 256) { + return TINF_OK; + } + + if (sym < 256) { + d.dest[d.destLen++] = sym; + } else { + var length, dist, offs; + var i; + + sym -= 257; + + /* possibly get more bits from length code */ + length = tinf_read_bits(d, length_bits[sym], length_base[sym]); + + dist = tinf_decode_symbol(d, dt); + + /* possibly get more bits from distance code */ + offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]); + + /* copy match */ + for (i = offs; i < offs + length; ++i) { + d.dest[d.destLen++] = d.dest[i]; + } + } + } +} + +/* inflate an uncompressed block of data */ +function tinf_inflate_uncompressed_block(d) { + var length, invlength; + var i; + + /* unread from bitbuffer */ + while (d.bitcount > 8) { + d.sourceIndex--; + d.bitcount -= 8; + } + + /* get length */ + length = d.source[d.sourceIndex + 1]; + length = 256 * length + d.source[d.sourceIndex]; + + /* get one's complement of length */ + invlength = d.source[d.sourceIndex + 3]; + invlength = 256 * invlength + d.source[d.sourceIndex + 2]; + + /* check length */ + if (length !== (~invlength & 0x0000ffff)) + return TINF_DATA_ERROR; + + d.sourceIndex += 4; + + /* copy block */ + for (i = length; i; --i) + d.dest[d.destLen++] = d.source[d.sourceIndex++]; + + /* make sure we start next block on a byte boundary */ + d.bitcount = 0; + + return TINF_OK; +} + +/* inflate stream from source to dest */ +function tinf_uncompress(source, dest) { + var d = new Data(source, dest); + var bfinal, btype, res; + + do { + /* read final block flag */ + bfinal = tinf_getbit(d); + + /* read block type (2 bits) */ + btype = tinf_read_bits(d, 2, 0); + + /* decompress block */ + switch (btype) { + case 0: + /* decompress uncompressed block */ + res = tinf_inflate_uncompressed_block(d); + break; + case 1: + /* decompress block with fixed huffman trees */ + res = tinf_inflate_block_data(d, sltree, sdtree); + break; + case 2: + /* decompress block with dynamic huffman trees */ + tinf_decode_trees(d, d.ltree, d.dtree); + res = tinf_inflate_block_data(d, d.ltree, d.dtree); + break; + default: + res = TINF_DATA_ERROR; + } + + if (res !== TINF_OK) + throw new Error('Data error'); + + } while (!bfinal); + + if (d.destLen < d.dest.length) { + if (typeof d.dest.slice === 'function') + return d.dest.slice(0, d.destLen); + else + return d.dest.subarray(0, d.destLen); + } + + return d.dest; +} + +/* -------------------- * + * -- initialization -- * + * -------------------- */ + +/* build fixed huffman trees */ +tinf_build_fixed_trees(sltree, sdtree); + +/* build extra bits and base tables */ +tinf_build_bits_base(length_bits, length_base, 4, 3); +tinf_build_bits_base(dist_bits, dist_base, 2, 1); + +/* fix a special case */ +length_bits[28] = 0; +length_base[28] = 258; + +module.exports = tinf_uncompress; diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/package.json b/node_modules/lv_font_conv/node_modules/tiny-inflate/package.json new file mode 100644 index 00000000..53399e20 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/tiny-inflate/package.json @@ -0,0 +1,60 @@ +{ + "_args": [ + [ + "tiny-inflate@1.0.3", + "/home/vitaly/Dropbox/Coding/lv_font_conv" + ] + ], + "_from": "tiny-inflate@1.0.3", + "_id": "tiny-inflate@1.0.3", + "_inBundle": false, + "_integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "_location": "/tiny-inflate", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "tiny-inflate@1.0.3", + "name": "tiny-inflate", + "escapedName": "tiny-inflate", + "rawSpec": "1.0.3", + "saveSpec": null, + "fetchSpec": "1.0.3" + }, + "_requiredBy": [ + "/opentype.js", + "/unicode-trie" + ], + "_resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "_spec": "1.0.3", + "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", + "author": { + "name": "Devon Govett", + "email": "devongovett@gmail.com" + }, + "bugs": { + "url": "https://github.com/devongovett/tiny-inflate/issues" + }, + "description": "A tiny inflate implementation", + "devDependencies": { + "mocha": "^2.1.0" + }, + "homepage": "https://github.com/devongovett/tiny-inflate", + "keywords": [ + "inflate", + "zlib", + "gzip", + "zip" + ], + "license": "MIT", + "main": "index.js", + "name": "tiny-inflate", + "repository": { + "type": "git", + "url": "git://github.com/devongovett/tiny-inflate.git" + }, + "scripts": { + "test": "mocha" + }, + "version": "1.0.3" +} diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/readme.md b/node_modules/lv_font_conv/node_modules/tiny-inflate/readme.md new file mode 100644 index 00000000..dd8c408c --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/tiny-inflate/readme.md @@ -0,0 +1,31 @@ +# tiny-inflate + +This is a port of Joergen Ibsen's [tiny inflate](https://bitbucket.org/jibsen/tinf) to JavaScript. +Minified it is about 3KB, or 1.3KB gzipped. While being very small, it is also reasonably fast +(about 30% - 50% slower than [pako](https://github.com/nodeca/pako) on average), and should be +good enough for many applications. If you need the absolute best performance, however, you'll +need to use a larger library such as pako that contains additional optimizations. + +## Installation + + npm install tiny-inflate + +## Example + +To use tiny-inflate, you need two things: a buffer of data compressed with deflate, +and the decompressed size (often stored in a file header) to allocate your output buffer. +Input and output buffers can be either node `Buffer`s, or `Uint8Array`s. + +```javascript +var inflate = require('tiny-inflate'); + +var compressedBuffer = new Bufer([ ... ]); +var decompressedSize = ...; +var outputBuffer = new Buffer(decompressedSize); + +inflate(compressedBuffer, outputBuffer); +``` + +## License + +MIT diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/test/index.js b/node_modules/lv_font_conv/node_modules/tiny-inflate/test/index.js new file mode 100644 index 00000000..f4e8c88f --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/tiny-inflate/test/index.js @@ -0,0 +1,75 @@ +var inflate = require('../'); +var zlib = require('zlib'); +var fs = require('fs'); +var assert = require('assert'); +var uncompressed = fs.readFileSync(__dirname + '/lorem.txt'); + +describe('tiny-inflate', function() { + var compressed, noCompression, fixed; + + function deflate(buf, options, fn) { + var chunks = []; + zlib.createDeflateRaw(options) + .on('data', function(chunk) { + chunks.push(chunk); + }) + .on('error', fn) + .on('end', function() { + fn(null, Buffer.concat(chunks)); + }) + .end(buf); + } + + before(function(done) { + zlib.deflateRaw(uncompressed, function(err, data) { + compressed = data; + done(); + }); + }); + + before(function(done) { + deflate(uncompressed, { level: zlib.Z_NO_COMPRESSION }, function(err, data) { + noCompression = data; + done(); + }); + }); + + before(function(done) { + deflate(uncompressed, { strategy: zlib.Z_FIXED }, function(err, data) { + fixed = data; + done(); + }); + }); + + it('should inflate some data', function() { + var out = Buffer.alloc(uncompressed.length); + inflate(compressed, out); + assert.deepEqual(out, uncompressed); + }); + + it('should slice output buffer', function() { + var out = Buffer.alloc(uncompressed.length + 1024); + var res = inflate(compressed, out); + assert.deepEqual(res, uncompressed); + assert.equal(res.length, uncompressed.length); + }); + + it('should handle uncompressed blocks', function() { + var out = Buffer.alloc(uncompressed.length); + inflate(noCompression, out); + assert.deepEqual(out, uncompressed); + }); + + it('should handle fixed huffman blocks', function() { + var out = Buffer.alloc(uncompressed.length); + inflate(fixed, out); + assert.deepEqual(out, uncompressed); + }); + + it('should handle typed arrays', function() { + var input = new Uint8Array(compressed); + var out = new Uint8Array(uncompressed.length); + inflate(input, out); + assert.deepEqual(out, new Uint8Array(uncompressed)); + }); +}); diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/test/lorem.txt b/node_modules/lv_font_conv/node_modules/tiny-inflate/test/lorem.txt new file mode 100644 index 00000000..c37b0a59 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/tiny-inflate/test/lorem.txt @@ -0,0 +1,199 @@ +Lorem ipsum dolor sit amet, sea pertinax pertinacia appellantur in, est ad esse assentior mediocritatem, magna populo menandri cum te. Vel augue menandri eu, at integre appareat splendide duo. Est ne tollit ullamcorper, eu pro falli diceret perpetua, sea ferri numquam legendos ut. Diceret suscipiantur at nec, his ei nulla mentitum efficiantur. Errem saepe ei vis. + + Per melius aperiri eu. Et interesset philosophia vim, graece denique intellegam duo at, te vix quot apeirian dignissim. Ei essent percipitur nam, natum possit interpretaris sea ea. Cum assum adipisci cotidieque ut, ut veri tollit duo. Erat idque volutpat mea ut, mel nominati splendide vulputate ea. + + No ferri partem ceteros pro. Everti volumus menandri at pro. Cum illud euripidis cu, mazim deterruisset ei eum. Ex alia dolorem insolens per, malis clita laboramus duo ut, ridens appareat philosophia ea quo. + + Vix elit tantas phaedrum et, ea quo vide facete scriptorem. Ut facer laboramus definitiones has, viris dictas regione at eos. Noluisse constituto vix at, nec malorum rationibus te. Nec numquam definiebas id, vim id liber munere. Simul discere reprimique qui eu. Et aeterno aperiri disputando vix. + + Usu ne denique albucius gloriatur. Pri in novum electram, ei amet electram quo. Vix summo recusabo dissentiunt no. Natum mediocrem maiestatis ut eum, vocibus nominavi has in, affert civibus te nam. Cibo minim ex nec. + + Ipsum tibique deseruisse vel ut. Ad laudem iracundia eam. Eum id legere scripta nominavi, vim melius ceteros et, ea tale enim nec. Aeque facete signiferumque ne est, vis ei sint persequeris. Ei magna veritus nec, enim aliquando ex pro. + + Verear noluisse qui eu, id mutat possit nec. Ad est melius placerat, soluta facilisi et vel. Id his simul consul praesent, no nam nihil perfecto, quo in mucius corrumpit. Assum ceteros cotidieque et nam, illum quaeque cum et. Numquam laoreet his at, in vis suas quando suscipit. + + Ne graece efficiendi interpretaris cum. In vide dictas cotidieque sed. Ea est augue vidisse. Eu epicurei salutandi est, etiam oratio imperdiet ex his. Te pri novum meliore sensibus. An nec eius dicant, iracundia consectetuer per ex. An iuvaret meliore constituto eam, latine detraxit mea eu, mel eu aperiri moderatius. + + Per commodo virtute eu. Eius euripidis nam eu, cum ne praesent vituperata, vix at integre verterem posidonium. Eu adhuc labores eam, temporibus reformidans eos id. Feugiat labores nam ne, eu sed nostro veritus, mea in consul evertitur repudiandae. Cu eos detracto voluptaria consetetur. + + Sit vidit mundi offendit ad. Usu semper vivendo eloquentiam eu. Nonumes deleniti ex vim, fabulas forensibus ad eam. Mazim admodum petentium sed et, has eu eirmod eruditi laoreet. An eam hinc erant. Et semper accumsan similique cum. Sea at inani error. + + Te commodo delicata abhorreant cum, iusto lucilius ut sed, ea his evertitur scripserit. Te eligendi scriptorem sit. Mel quodsi meliore dissentias ei, cum deserunt moderatius ex, error sanctus adversarium eam et. Eam et appareat placerat tincidunt, ius an quidam putant delenit, utinam partem quo ea. Mei legendos constituto scribentur eu, usu ea consul facilisis. Purto veritus lobortis te sea. + + Ne sea ludus solet decore, in cum erat dicta labitur. Doctus maluisset scripserit qui in. Qui at postea audiam, has ut aperiam dissentiet, vix ut harum nemore integre. Sit ad lucilius deseruisse, iuvaret percipit in pro. Sed referrentur voluptatibus ne, his ei nullam omnesque aliquando. + + Ius te purto augue fierent, mea et decore feugiat definiebas. Dolor abhorreant deseruisse at sit, corpora tacimates duo ut. Dicta equidem ne has. At pri elit magna vocibus, ipsum vidisse definiebas id cum. Ex pri laudem assentior. + + Aliquip referrentur id vim, sea labores nominati recteque id. Torquatos adversarium no mel, pri eu impedit fastidii, in usu tollit electram. Ad esse facer nec, mel id laudem adipisci, pri eu vocent efficiantur. Cu est posse possit. Vis definiebas neglegentur ad, pro facer sanctus propriae id. Sit epicurei comprehensam signiferumque ea, ad eam vero admodum scaevola. + + Nec an vivendo ocurreret, qui id nihil fastidii ocurreret. Vis ei mazim zril, dictas aperiri aliquando per ad, sit ne harum postea appellantur. No munere periculis reprimique duo, ut nam unum animal repudiandae. Cibo vocent dissentias te mea. Sea te elit denique volutpat, nec ne epicuri mentitum delicata. + + Ius eros aeterno torquatos ex, et per nisl accusata. Ne nam nonumy discere, in vim nemore commune. Cu pri nibh pertinacia assueverit, duo percipit definitionem ne, delectus voluptaria et vix. Mutat appellantur mea ad, ad pericula suavitate nam. Primis rationibus mei eu. Ad dolorem verterem deserunt eam, in decore probatus consulatu sed. Quo ut ullum epicuri. + + Quod contentiones ea quo. Et nihil conclusionemque mel, offendit perfecto eam eu. Suas case nam et. Ex elit doctus civibus mei, eam facilis scaevola ne. His elit scripta constituto eu, mea tantas petentium ut. + + Eos dicam intellegam id. Cum omnes concludaturque ea. Qui in semper legendos intellegebat. Option mediocrem eam id. Doctus principes deterruisset pro ad, qui ea fugit populo repudiare. + + Sed ne summo percipitur. Eu porro persius vix, an vix esse fastidii delicatissimi, elit rationibus dissentias cu eam. Te dolorem scriptorem mei, no mei aeque tibique. Iisque molestie ei ius. + + Ius eu vide salutatus. Ut modus errem nam, latine vocibus referrentur an has, his laudem docendi at. Mei in enim aeterno, sea id dolores placerat signiferumque. Mea modo semper maluisset at, vel te magna eruditi. Eam in verear tacimates concludaturque. Nam cu porro mediocritatem, platonem splendide in has, dicit dolor populo mei et. Melius saperet tacimates nam an, mentitum fabellas assueverit duo id, sea an nihil tritani inciderint. + + At vim option nominati quaerendum. Dolor vituperata dissentiunt eu nec, an modo appareat suscipiantur sea. In vim suas eligendi sadipscing. Ne nihil molestie ius, sumo aperiri argumentum sit ea, eius principes te sit. Eam no fugit mazim alterum. Sed ignota sententiae in. At pro illud reprimique. + + At nec vivendo luptatum, vel congue oratio cu. Audire sententiae ius an. Antiopam scripserit et duo, illud commune te his. Posse disputationi vix in, quod oporteat id eam. Legimus admodum docendi cum at, cu postea deserunt periculis per. + + Sonet clita ponderum sea ei, est ex semper fabellas. Te vix elitr congue phaedrum, ea eum argumentum eloquentiam. Te elitr suavitate definitionem eum. Est inciderint comprehensam cu, ei ius dolor dignissim assueverit. + + Eos ut dolorum albucius placerat, mel erat mentitum cu. Nec copiosae periculis in, ex vix everti consectetuer. Pro at dicat utroque, iracundia sententiae in usu, sit no cibo natum alienum. Duo audiam placerat ei. Cu mel philosophia disputationi. + + Eam fierent maiestatis instructior no, cu sea iusto iriure voluptatum. Vide numquam vivendo ex est, an vel nulla similique posidonium. No mea tritani molestiae, pro te placerat sensibus. Mel ad posse mucius vocent, te populo timeam democritum mel. + + Pri suscipit luptatum eu, et persius accumsan argumentum sea. In paulo tempor possit sea. Vidisse viderer ut vix, vix ex hinc solet. Altera principes qui in, est utroque scaevola eu. + + Lobortis gubergren mediocritatem ne has, te eum adhuc pericula vulputate, cu antiopam posidonium percipitur mei. Mandamus sapientem et sed. Ea dicta quodsi aperiri sea, ad putent posidonium sea. Duo et molestie erroribus, facete gloriatur eam te, ne eam accusam interesset instructior. Eam posidonium reformidans at. Duo menandri pericula te, qui accumsan facilisi expetendis ut. Et eum dicunt persius pertinacia, vis recteque eloquentiam te. + + Habemus blandit at pri, ad quando consequat per. Mea id adhuc dolores definitionem, in platonem mediocrem abhorreant per, vis cu hinc harum qualisque. Et purto lorem intellegam vel, summo apeirian cum no. At vel nonumy volutpat quaerendum. Ludus fastidii fabellas ut eum, no nec assum homero sanctus. + + His at doming forensibus honestatis, et nostrum praesent eum. At dolores patrioque sententiae eos, quaeque corpora qui no. Nec postea senserit persecuti ei, ad sea invenire voluptatibus, ne veniam lobortis repudiandae mea. Et nam integre democritum. Ex populo menandri qui. Ei duo aeterno accommodare, nibh ponderum iudicabit id vel, cu eos platonem postulant omittantur. + + Mollis molestie nam an, duo an option patrioque posidonium. Putant luptatum aliquando mea ei. Ut quas semper perfecto mei, ei ius justo possit epicurei. Cum odio omnium eu, gubergren definitionem eum ex. Tempor definitionem pri at, ad sit nihil postea moderatius. + + An cum verear scribentur, duo et purto tempor euripidis. Aeterno postulant ut eam, no diam inermis argumentum eos, posse blandit incorrupte ne pri. Est commodo laoreet conclusionemque ei. Graeci tacimates ei eum. Ad nec forensibus voluptaria, mea in alii putent, sed facer ceteros ad. + + Vidisse vivendo placerat duo ex, pri malorum definitionem ex. Decore virtute accumsan cum ad, ex eum postea putant euismod. Mei id alia populo democritum, mei ea adhuc posse. Et primis hendrerit signiferumque nam, ei vis modo lobortis recteque. Saepe persius aliquid et sit, vel everti feugait ne. + + Qui ex laoreet argumentum temporibus, agam tacimates intellegam ad qui. At wisi perfecto has, an sit viris definitionem. No quo sint tincidunt, vim adhuc expetenda ut, nam ad everti expetendis. Adhuc soleat doming nec eu, dicta summo sapientem mei te, nostro mediocrem dignissim ex eos. Id alienum mediocritatem mea, te error iriure placerat pri, feugiat platonem vituperatoribus mea te. + + Quo elit legere consequuntur in, in fabulas ancillae mea, sed scripta mentitum tacimates ei. Mazim phaedrum interpretaris et per, te patrioque assueverit vim. Populo commodo imperdiet vix ut. Mea modo bonorum ut, cu pri enim posse efficiantur. + + Sea veri ancillae adipisci te, quot tota modus ad sea. Suas malorum per ex, mazim rationibus ad vix. Ius ullum deserunt id. Quas corrumpit constituam no mei. Ut quo deleniti atomorum omittantur. Clita feugait docendi ei cum, no mea modo menandri, nam mundi doming an. No altera commodo pri. + + Vel fastidii convenire ex. Vim soleat maluisset te, et expetendis sadipscing liberavisse has. Per sale facilisis accommodare in. Cu option incorrupte nec. + + An eam vocibus intellegat, possit aliquid ex eum, tale mentitum oportere at duo. Ad eam audiam consectetuer, eu meliore verterem mediocritatem has, nam etiam reprimique ut. Nullam adipisci mei in, ad duo quem simul veniam. Vis at tempor sententiae. Te essent iisque aperiam vis. + + Te vix etiam quando ullamcorper, mel ei offendit iudicabit necessitatibus, usu assum facilisi sensibus ex. Alterum adversarium vis ad. Odio fierent deleniti ex cum. Quo an bonorum inciderint, harum simul maiorum in mel, ex legimus alienum corrumpit has. Pri soluta lobortis adipiscing te, eos clita ponderum mandamus at, elit meis assum mea in. Tale intellegebat cu vim, facilis expetenda democritum duo te, cu his causae dissentiet liberavisse. + + Primis latine epicurei no mea. Nam ad quis putant everti, no fugit minimum disputando vix. Laudem neglegentur te qui. Vel splendide efficiendi at, sed liber urbanitas no. Id his atqui inermis scriptorem, vituperata adversarium eos cu, pro in movet accommodare. + + Munere indoctum eu duo. Id eam duis voluptua expetenda, et prodesset inciderint ius. An nibh elitr deseruisse usu. Idque copiosae nam ea, ne tempor omittantur definitionem vis, et cum sumo principes. Quo rebum viderer minimum ex, est at melius blandit. + + Ut adolescens definitiones sed. Est ut erant legendos, in quo facilis salutatus. Agam expetenda salutatus ut sit, quo ex fuisset repudiandae, tale probo aliquip mea id. No vix diceret scaevola. Posidonium conclusionemque ut est. Quo porro menandri assentior ei. + + Vocent neglegentur intellegebat sit id. Et ullum accusam sea, ex nam aeque ubique ocurreret, ea cum quidam euismod. Eam ne zril alienum. Mea id veritus alienum. Mei simul appareat nominati no. Cum option accumsan ea, ne eos legimus dissentiunt. + + Everti prodesset scripserit ea cum, eam nostrud adolescens deterruisset an. Deseruisse definiebas eos ne, mel ex nisl meliore consulatu, per te scripta gubergren. Vix cu novum admodum recusabo, te omnes similique efficiantur nec. Suas novum semper duo ex. Vocibus cotidieque cu qui, at sale malorum intellegam mel. Quo errem accumsan ullamcorper cu, eu quo liber quidam conceptam. + + Scripta habemus quaestio id usu. Id vis utroque forensibus, cu simul fabulas efficiantur vis, ad mel cibo quas feugait. Eros expetendis in cum. No cum aeterno menandri consetetur, quo ex alterum probatus. Eu audire tritani ius, ei labore commune detraxit est. Unum sapientem cotidieque ei vel, ullum prompta per ex. Recteque persequeris quo ei, volutpat quaerendum ex sea. + + Qui phaedrum dissentiunt ne, sea debet fuisset ut. Ridens virtute pro an. Eos at stet modus iisque. Usu ea esse sententiae, deleniti salutandi ne sit, semper graecis sensibus vis ne. Ea corrumpit assueverit mel, in ignota quodsi nominati vix. + + Sea equidem vivendo ut, sed cibo nusquam id, nostro integre te mei. Etiam tractatos et duo, ut ludus dolore pri. Eius hendrerit cu vel, quo ei quodsi causae ullamcorper, per cibo nihil at. Ea voluptatum incorrupte duo, dolore debitis no usu. Ei clita concludaturque cum. Quo quas quando persecuti cu, eruditi scripserit cum eu. + + Ex eleifend philosophia has. Sint repudiandae in sea, et prima latine persecuti eum, pro ea everti expetendis. At his stet facete minimum, sed in delenit maiestatis. Cum ei omittam contentiones, suas sale melius ei cum, mel id vocent propriae necessitatibus. Vix amet nibh ea, autem movet ne vel. + + Accusata percipitur ut vim. Decore tritani scriptorem vis eu, vis volutpat reprehendunt ea. Postea inciderint vix an, no dicunt tamquam mel, usu id illud sensibus expetendis. Ex his aliquip blandit appellantur. + + Officiis evertitur ut mea, cu illud omnesque scripserit eam. Simul vituperatoribus an eum, mel quidam disputando cu, nibh probatus consequat ei mei. Eam populo appareat inimicus ei. Ne officiis definitiones his, vis vero simul similique ei. Iudico oratio elaboraret vim et, id posse nemore eirmod has. + + Pro in veniam consul expetenda, an est movet consequuntur, ex ius admodum recusabo ullamcorper. Mei an utroque ceteros singulis, id eum iudico latine. At est reque lorem intellegat. Quando corpora qui ea. Quo vocent salutatus id. + + Wisi ignota concludaturque est ex. Mea ad inermis vituperatoribus. Dolor persius inimicus ad nec, etiam dignissim qui ex. Ridens quodsi sed ex. Pro in etiam antiopam, eos graece eripuit ad. Affert soluta mei te, pri illud graecis id, vel sint vivendo at. Mea eu veri dicta offendit, cu has sint copiosae. + + Duo libris salutatus ad, porro principes mel ex. Nec iudico consectetuer no. Ut pri causae qualisque democritum. Habeo homero iuvaret at mei. Atqui aliquam eu mea. Gubergren delicatissimi vim ad, amet quodsi efficiendi te ius. Everti latine vulputate pro te, in nam falli definiebas, duo harum graeco nusquam no. + + Per ne aeterno appareat, melius verear tamquam eam id, mei ei invidunt atomorum. Eu doctus viderer eam, sea eu possit dolorem appetere, efficiantur necessitatibus mel cu. Ad est suas officiis, assum erant eum cu, eum erat vitae te. Habemus scaevola no per, nominati adipiscing et eam. + + Ei stet quidam scaevola quo, ius cu noster officiis. Has dolorum vulputate voluptaria ea, ei ius audiam liberavisse. Duo in accumsan constituam, sea esse deseruisse ad. Ne eam eligendi sensibus. Ea rebum porro interesset sed, te alii tritani singulis vis, vel enim liber ne. Movet numquam salutatus eu vim. Nam ad deleniti interpretaris conclusionemque. + + Ea offendit apeirian reprimique ius, accusam incorrupte voluptatibus ei duo. Hinc ponderum detraxit vel te, has no labore regione. Sint impetus duo ex, cu has liber soluta fierent. Vix ut cibo mollis deseruisse. + + Cum id tale disputationi, usu adhuc tritani ea. Id vim volumus quaerendum delicatissimi, no vix dolorum legimus corpora, justo dicit id duo. Ancillae concludaturque at usu. Vim diceret singulis incorrupte eu, oratio nullam quo et. Et sea mediocrem vituperatoribus, fugit tacimates deterruisset cum et. Mel ne sale soleat, vim ad labitur equidem, eos ea justo noluisse. + + An nam consectetuer necessitatibus, eos ei mazim persecuti. Libris explicari dissentiunt te vis, te per veniam sadipscing. Ut diceret euismod vix, duo discere inermis ea. No sit veri sensibus cotidieque, inermis sadipscing reprehendunt qui ad, elitr referrentur repudiandae eu est. Vis quem probo postulant no. Ad viris tollit ullamcorper pri, congue discere ad usu. + + Eam te cetero reprehendunt. His ad ferri feugiat invenire, oratio indoctum id pro. Errem omittam sed et, est no quando omnesque platonem, sed quis philosophia ei. Vix ut porro aeque habemus, eu eam consul nominati omittantur. + + At pri everti indoctum, ullum adipiscing instructior qui ad. Usu ignota omittam ex. At audire vocibus pericula vix, usu ex reque feugait. Ne sea electram salutandi moderatius, te qui verterem scripserit adversarium, an sed tantas lobortis intellegat. + + Ad quas suscipit atomorum duo, quo saepe maiestatis eu. Nostro expetenda ea usu, in atqui doming eam. Vis utroque consulatu ne. Zril noster scripta in eum, vim ad dicam facete legendos, et sit civibus consequat. Eum autem periculis ex. Sed ne nemore eligendi, his legimus verterem ad. + + Est ad amet possit latine. Sed ne legere populo, has pericula scribentur voluptatibus eu. Usu in nonumy vituperata. Aeque oratio gubergren mea ad, quo eu debet dolorum contentiones. Veri expetenda ex mel, eu veniam apeirian vis, aeterno debitis id his. + + Ei ius consul nonumes. Id qui porro periculis, quando dolore iisque qui id. Quo ex simul convenire, vix ei erat petentium, mea cu clita causae. Tale facilisis ex pri, vim ne vide laudem mnesarchum. Duo option blandit ex. Eu est modus vitae, nam in latine maiorum. + + Est an quis quaeque disputando, sit an postulant expetenda, dolor erroribus consequuntur ad vel. Ius phaedrum cotidieque ei, omnis persius copiosae eu vim. Pri facer consequat eu, esse copiosae facilisis mei ne. Ubique convenire no sit. + + Nec regione prompta no. Et quo recteque concludaturque, et ius nostro mollis regione. Ad aliquid lucilius scriptorem eam. Sit at summo eligendi omittantur, ius ea paulo option referrentur. Rationibus inciderint mediocritatem sea id, in dicant assentior sea, quidam copiosae reprehendunt in usu. Cu appetere scripserit vix. Mea mollis audiam aliquam ea, te qui adhuc nonumes deserunt. + + Duo ad facilis consequuntur, vis vide mutat in. At inani ludus eam, an sit quod primis, ea est integre consetetur. Has dolorem salutandi te, tota doctus sit ne, cum ex minimum convenire. Ne legere deterruisset vel, partem phaedrum ne pro, vim modo facete fabellas ex. Cu vix possim eleifend posidonium. Ne est sumo impetus. No quo ubique neglegentur, in usu aliquando scripserit reformidans. + + Vix et numquam expetendis. Ei quas senserit vel, ne has placerat conclusionemque. Noster perpetua euripidis ex sit. Ex mel vidit nonumy vituperata, duo ad nostrum liberavisse. Primis signiferumque duo te, ius te hinc aeque laoreet, ne eius voluptua pri. Dico eros copiosae sed ei, duo cu laudem propriae gubergren. + + Unum erant oratio duo cu, qui no audiam fabulas ornatus. Nihil omnium offendit ad cum, ea ius inermis appetere nominati. Ei nam vero oratio corrumpit. Atqui voluptatibus mei id, at duo assum nostrud aliquando. At nec laudem ridens phaedrum. Dicunt qualisque eum an, ex natum persecuti adipiscing vix, tritani consulatu persecuti nam id. + + In pro mundi percipit, eum tibique eloquentiam in, mea illud ullum altera ex. Veniam epicuri ex mea, quot eruditi definiebas eu duo. Vel augue regione consectetuer ei, appetere moderatius eos in. Laoreet lucilius vim eu. At oratio eirmod qui. + + Altera labitur qui ei, in eam libris primis. Eirmod audiam te vel, eu mei case vide ponderum, an principes persecuti neglegentur mei. Ferri vulputate instructior no vix, in est vidisse detraxit molestiae. Te sit quot choro adipisci, no labore indoctum deterruisset cum. Elit ancillae appetere usu at, mundi dissentias te quo. Mentitum erroribus ad pri. + + Usu ei vero possit appetere. Id erroribus constituam quo. Sit id quidam pertinacia, epicuri delicata eu has, debet melius evertitur ut sed. Id est alienum voluptua. Sit eu dico discere accusata, cu mazim viderer numquam usu, has an solum pertinax. Vel natum summo te, mel integre perfecto consetetur et. + + Postea luptatum menandri cu has, no nam neglegentur necessitatibus. Deseruisse reprehendunt ne mea, lorem tollit nonumes ne vim. Eu pro amet populo, omnesque ponderum sadipscing et ius, ad debet consequat dissentiet vix. Ius nulla aliquip complectitur et, id sed repudiare necessitatibus, et erant legimus invidunt vel. Magna labore democritum vis ei. + + Ne has consulatu reprehendunt, ad nam nulla integre admodum, no has everti impedit perpetua. Tempor antiopam dissentias pro et, ex per legere electram. Ut ius brute omnesque consequat, an his dissentias persequeris. Dicta ludus tritani eam ei. + + Minim rationibus et usu, eam in elit senserit. Stet harum qualisque eu has. Cu decore nostrud sit, mea magna iracundia te, vix tritani convenire imperdiet at. Te pri dictas appetere, brute velit ius ad. Veri dicit legere pri at, sea tation fierent molestie eu. Eum et paulo consul. + + Veri iusto mei id, reque invidunt ne his, te agam dolore electram nam. Est ullum oporteat facilisi eu, dicunt officiis ad eos. Vis ut populo similique. Et minim platonem percipitur usu. Et usu saperet alienum consequuntur, per ad luptatum concludaturque, cibo duis definitionem vix an. + + Ancillae iracundia eu vix, mazim conceptam no qui. Sit nihil epicuri voluptatibus in, an sale debet vis. Porro congue senserit quo an. Facer constituam vel no, facete pertinacia adolescens pro ut, errem nullam menandri ius an. Ex tractatos periculis interpretaris eum, vim ornatus patrioque an. Ius magna iudicabit reprehendunt at, ea viris ornatus sit, eum in vidisse percipitur. + + Vel agam interpretaris ex, zril deserunt electram in duo. Eu eam vero atqui maiorum. Ne pri alia meis decore, vis ea expetenda dissentiet. Cu quis evertitur intellegat cum, ea sed posse expetendis liberavisse, sed ne quis deseruisse. Solum ignota causae ad quo, ferri erant est id, vim ut choro liberavisse. Ne nam causae reformidans, possit percipitur id has. Case tota id has, pri tation ancillae sensibus te. + + Posse aperiam sit id, est in dicam iracundia. Eum mollis dolores te, at vis laoreet habemus fuisset. Vim nibh civibus signiferumque eu. Fierent reformidans an quo. Exerci dissentiunt ex pri, per illum debitis et. + + Vel tota exerci facilis eu. Est primis atomorum id. Hinc insolens dissentiet duo et, iusto inermis salutatus vel ne. Corpora propriae vituperata ex est, sit quod ignota deterruisset no, elitr tempor suscipit ex nam. Eripuit repudiandae his cu, fabulas inermis accumsan ei eum. Nisl molestie ex pri, sea veniam graecis expetenda eu. + + Vel possim moderatius cu, sea no dicit aliquip erroribus, eam te debet partem. Sea no graeco vocent probatus, qui ut fugit delectus definiebas. Id soluta viderer per, tritani disputando necessitatibus ad vix. Eleifend facilisis percipitur at pri. Sit dicat adipisci ex, mundi solet doming nec te, ne maiorum tractatos evertitur sit. At velit propriae eos, mel alii invenire id. + + Ei per minim voluptaria, ea mel scaevola mediocrem euripidis. Et quo wisi sonet eirmod, consul tritani delenit vis id, per id mutat facer. Accusam patrioque eu cum, te cum quot saepe scribentur. Eros summo mnesarchum sed ut, ad possit nostrum comprehensam ius. + + In elitr nullam eam, cu mea mentitum omnesque. Ex has wisi consequat. Sed simul offendit argumentum no, solet corpora lucilius cu vis. Nec ut legimus suscipit, ne usu mucius latine, nusquam mentitum perpetua ad vel. Vidisse feugait facilisis an sea. + + Mel ne oblique menandri, nam graecis antiopam id. Id sanctus referrentur contentiones eam, et eam purto consequat, te vix quod tantas bonorum. Fabellas sapientem consulatu eu per. Iudico possim per ei, eu vide adversarium per. Usu vero essent albucius et, nec veritus ancillae prodesset eu, ipsum probatus cu sea. In quod tantas iudicabit eam, his et odio augue iuvaret. + + Eum an iisque oportere dignissim, est convenire molestiae interesset at, eam id dico audiam quaerendum. Est prompta ocurreret adversarium id, sumo legendos iracundia pri ea, quo ei agam aeque. Ei vix omnes conclusionemque, nec ut novum urbanitas honestatis. Sumo moderatius eos no, ea saperet impedit petentium vel. Vim in harum blandit, posse gubergren deterruisset ex ius. Ei error menandri platonem duo, partiendo qualisque nam te. + + Erat nobis maluisset at sed. Ius no dolor soluta. Has ne sumo brute suavitate, his ne viris aeterno omittantur. Te usu delenit philosophia. Mei wisi libris deseruisse eu. + + Tantas constituam per ei, doctus timeam ea vim. Quo ei putent delicata, eu quo aeterno labores. Qui liber eripuit singulis te, qui diam appareat similique ex, aliquip feugait noluisse cu vis. Dicant recteque definitiones ex mea. Cu exerci dictas eleifend duo, id nonumes denique vix, pri magna facer saperet ei. + + Aeque quodsi partiendo in est, partem malorum intellegebat et per, mea no porro ipsum oratio. Mel sale meliore fuisset ad, per aeque aperiam nominavi ex. Ad tantas meliore placerat mel, mel in prompta torquatos, nulla mollis tamquam pri cu. An quo noster intellegat. Utroque antiopam similique ius eu, at duo ullum apeirian reprimique, vide quaeque assueverit eam ea. + + Mel no quod viris latine, no platonem dissentiunt ius, quis amet gloriatur no eos. Ei mea ancillae probatus. Ad molestiae moderatius vim. Ex soluta meliore molestiae has, ne sea quas natum. Movet verterem vis no, vis amet homero an. Ius dicta tantas id. + + Pri ut modus disputationi, iudico ignota commune eam ut. Vim integre eripuit appareat in, malorum inermis perpetua his cu. Altera alienum mediocrem eu vis. Omnis honestatis repudiandae in qui, his quis duis te, novum rationibus cu est. Eos ut dicant molestie, pri argumentum quaerendum adversarium te. Ea cetero deserunt conceptam pri. + + Augue utroque iudicabit ei vim, ea per dico debet. Cu per regione feugait, sensibus necessitatibus cu cum, at mundi aliquando per. In cum latine evertitur definitionem. Sit movet aperiri liberavisse ad, pro paulo veniam eu. Vel ut cotidieque definitionem, adhuc movet intellegebat quo ne. + + Eum deleniti gubergren an, dolor utamur omittam ea ius. Eam movet possim accommodare et. Ius cu malorum tibique, ludus eligendi id pro. Solum vulputate efficiendi ex quo. + + Regione fierent eu per. No amet iuvaret efficiantur usu. Sit minim sensibus no, his principes gloriatur adversarium no. Ubique definitionem ne vis, vis propriae intellegebat te. Tibique suscipiantur his no. + + Eu quo elit cetero scripserit, maiestatis scripserit pri in, vim cu tibique eligendi. Ne tritani gubergren vituperatoribus quo, melius facilisi ne has. Id pro vivendo fuisset, ex causae utroque deleniti mea. Ad eam percipit perfecto, mutat justo essent at vim. Eros novum duo et, in pri melius blandit. Affert albucius eos ad. Singulis mnesarchum ea mel, eos dictas nominavi reprimique no. + + Ei qui congue voluptatibus. Nam no tritani elaboraret. Conceptam rationibus expetendis duo no. Vivendo reprimique cum te, qui timeam copiosae id, modo delicata ius ad. Nam cu dico aliquip, eu pro legimus officiis delicata, fugit quando ea per. + + In eos equidem accommodare. Electram principes ad usu. Nec adversarium disputationi in, sed feugait lucilius ut. Ludus hendrerit cu ius. + + Cum suscipit gloriatur ea. Id aeterno principes euripidis nam, mea id probo graeco verterem, vulputate ullamcorper definitionem in pri. Mel cu detraxit assueverit. Quo et posse fastidii, ei ponderum delicata sed, has brute forensibus ut. Quo option pericula ea, nam gubergren assueverit te. + + Graece doming intellegebat duo in, ullum clita expetenda nam at. Diam alienum menandri sit id. Unum clita consulatu duo id. Per in mucius legendos scribentur, sea quod phaedrum ut, facete animal dissentias no usu. Quo te dico suavitate. Est te erant congue vivendum, oporteat forensibus in his. + + Pro no eleifend reprehendunt, ut meis consetetur argumentum mei. Id vis harum ornatus cotidieque. Inani libris volumus ea qui. Ius at suas percipit voluptatum, pro solet invidunt honestatis ei, et nam delectus reprimique instructior. Nam id tacimates argumentum dissentiet, mei an sint adipiscing. + + Quando cotidieque sit at, ei sed tantas ancillae verterem, cum nibh omittam ut. Erant laboramus moderatius te eum. Civibus adipiscing sed ne, vix eu erant euripidis. Illud qualisque at nec, id tale sint facete per. Vero autem democritum eam an. + + Mel mazim prodesset ad. An vis alii suas congue, vim veri illum iisque et, in modus perfecto deseruisse vel. Sed summo fuisset fierent an. Vel eu bonorum ornatus, alii decore nec ad. Eu dicta constituto mea. + + Id sint stet graece usu. Cu sit essent reformidans, eos eius ridens et. Usu voluptaria posidonium cu. \ No newline at end of file diff --git a/node_modules/lv_font_conv/package.json b/node_modules/lv_font_conv/package.json new file mode 100644 index 00000000..e0f1464f --- /dev/null +++ b/node_modules/lv_font_conv/package.json @@ -0,0 +1,61 @@ +{ + "name": "lv_font_conv", + "version": "1.5.2", + "description": "Rasterize vector fonts for embedded use. Supports subsettings & merge.", + "keywords": [ + "font", + "convertor", + "embedded" + ], + "repository": "lvgl/lv_font_conv", + "license": "MIT", + "files": [ + "lv_font_conv.js", + "lib/" + ], + "bin": { + "lv_font_conv": "lv_font_conv.js" + }, + "scripts": { + "start": "parcel ./web/index.html --open", + "build": "parcel build ./web/index.html ./web/content.html --public-url ./", + "build:dockerimage": "docker build -t lv_font_conv_freetype ./support", + "build:freetype": "docker run --rm -v $(pwd):/src/lv_font_conv -it lv_font_conv_freetype ./lv_font_conv/support/build.sh", + "lint": "eslint .", + "test": "npm run lint && nyc mocha --recursive", + "coverage": "npm run test && nyc report --reporter html", + "shrink-deps": "shx rm -rf node_modules/opentype.js/src node_modules/opentype.js/dist/opentype.{m,js.m}* node_modules/pngjs/browser.js", + "prepublishOnly": "npm run shrink-deps" + }, + "dependencies": { + "argparse": "^2.0.0", + "bit-buffer": "^0.2.5", + "debug": "^4.1.1", + "make-error": "^1.3.5", + "mkdirp": "^1.0.4", + "opentype.js": "^1.1.0", + "pngjs": "^6.0.0" + }, + "bundledDependencies": [ + "argparse", + "bit-buffer", + "debug", + "make-error", + "mkdirp", + "opentype.js", + "pngjs" + ], + "devDependencies": { + "eslint": "^7.21.0", + "file-saver": "^2.0.2", + "mocha": "^8.3.0", + "nyc": "^15.1.0", + "parcel-bundler": "^1.12.4", + "posthtml-include": "^1.6.2", + "roboto-fontface": "^0.10.0", + "shx": "^0.3.2" + }, + "browserslist": [ + "last 1 Chrome version" + ] +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..b8320c49 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,183 @@ +{ + "name": "InfiniTime", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "dependencies": { + "lv_font_conv": "^1.5.2" + } + }, + "node_modules/lv_font_conv": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/lv_font_conv/-/lv_font_conv-1.5.2.tgz", + "integrity": "sha512-0UapRSTkVP/pnB8Z4r2HDHx5p2dJx/xUG1+14u/WXo59mwuC7BahR+Bnx/66jKoDrG1wFQwn9ZzoyMxRHOD9bg==", + "bundleDependencies": [ + "argparse", + "bit-buffer", + "debug", + "make-error", + "mkdirp", + "opentype.js", + "pngjs" + ], + "dependencies": { + "argparse": "^2.0.0", + "bit-buffer": "^0.2.5", + "debug": "^4.1.1", + "make-error": "^1.3.5", + "mkdirp": "^1.0.4", + "opentype.js": "^1.1.0", + "pngjs": "^6.0.0" + }, + "bin": { + "lv_font_conv": "lv_font_conv.js" + } + }, + "node_modules/lv_font_conv/node_modules/argparse": { + "version": "2.0.1", + "inBundle": true, + "license": "Python-2.0" + }, + "node_modules/lv_font_conv/node_modules/bit-buffer": { + "version": "0.2.5", + "inBundle": true, + "license": "MIT" + }, + "node_modules/lv_font_conv/node_modules/debug": { + "version": "4.3.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/lv_font_conv/node_modules/make-error": { + "version": "1.3.6", + "inBundle": true, + "license": "ISC" + }, + "node_modules/lv_font_conv/node_modules/mkdirp": { + "version": "1.0.4", + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lv_font_conv/node_modules/ms": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/lv_font_conv/node_modules/opentype.js": { + "version": "1.3.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "string.prototype.codepointat": "^0.2.1", + "tiny-inflate": "^1.0.3" + }, + "bin": { + "ot": "bin/ot" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/lv_font_conv/node_modules/pngjs": { + "version": "6.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/lv_font_conv/node_modules/string.prototype.codepointat": { + "version": "0.2.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/lv_font_conv/node_modules/tiny-inflate": { + "version": "1.0.3", + "inBundle": true, + "license": "MIT" + } + }, + "dependencies": { + "lv_font_conv": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/lv_font_conv/-/lv_font_conv-1.5.2.tgz", + "integrity": "sha512-0UapRSTkVP/pnB8Z4r2HDHx5p2dJx/xUG1+14u/WXo59mwuC7BahR+Bnx/66jKoDrG1wFQwn9ZzoyMxRHOD9bg==", + "requires": { + "argparse": "^2.0.0", + "bit-buffer": "^0.2.5", + "debug": "^4.1.1", + "make-error": "^1.3.5", + "mkdirp": "^1.0.4", + "opentype.js": "^1.1.0", + "pngjs": "^6.0.0" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "bundled": true + }, + "bit-buffer": { + "version": "0.2.5", + "bundled": true + }, + "debug": { + "version": "4.3.1", + "bundled": true, + "requires": { + "ms": "2.1.2" + } + }, + "make-error": { + "version": "1.3.6", + "bundled": true + }, + "mkdirp": { + "version": "1.0.4", + "bundled": true + }, + "ms": { + "version": "2.1.2", + "bundled": true + }, + "opentype.js": { + "version": "1.3.3", + "bundled": true, + "requires": { + "string.prototype.codepointat": "^0.2.1", + "tiny-inflate": "^1.0.3" + } + }, + "pngjs": { + "version": "6.0.0", + "bundled": true + }, + "string.prototype.codepointat": { + "version": "0.2.1", + "bundled": true + }, + "tiny-inflate": { + "version": "1.0.3", + "bundled": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..d339766c --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "lv_font_conv": "^1.5.2" + } +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fd8ece62..0229ce17 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -371,6 +371,7 @@ list(APPEND SOURCE_FILES displayapp/screens/StopWatch.cpp displayapp/screens/BatteryIcon.cpp displayapp/screens/BleIcon.cpp + displayapp/screens/AlarmIcon.cpp displayapp/screens/NotificationIcon.cpp displayapp/screens/SystemInfo.cpp displayapp/screens/Label.cpp diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json index 41c383c0..793b8851 100644 --- a/src/displayapp/fonts/fonts.json +++ b/src/displayapp/fonts/fonts.json @@ -7,7 +7,7 @@ }, { "file": "FontAwesome5-Solid+Brands+Regular.woff", - "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743" + "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf0f3, 0xf1f6" } ], "bpp": 1, diff --git a/src/displayapp/screens/AlarmIcon.cpp b/src/displayapp/screens/AlarmIcon.cpp new file mode 100644 index 00000000..fda87130 --- /dev/null +++ b/src/displayapp/screens/AlarmIcon.cpp @@ -0,0 +1,11 @@ +#include "displayapp/screens/AlarmIcon.h" +#include "displayapp/screens/Symbols.h" +using namespace Pinetime::Applications::Screens; + +const char* AlarmIcon::GetIcon(bool isSet) { + if (isSet) { + return Symbols::bell; + } + + return Symbols::notbell; +} diff --git a/src/displayapp/screens/AlarmIcon.h b/src/displayapp/screens/AlarmIcon.h new file mode 100644 index 00000000..678a4cb7 --- /dev/null +++ b/src/displayapp/screens/AlarmIcon.h @@ -0,0 +1,14 @@ +#pragma once + +#include "components/alarm/AlarmController.h" + +namespace Pinetime { + namespace Applications { + namespace Screens { + class AlarmIcon { + public: + static const char* GetIcon(bool isSet); + }; + } + } +} diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index bd958b28..41c4d42d 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -13,6 +13,7 @@ namespace Pinetime { static constexpr const char* clock = "\xEF\x80\x97"; static constexpr const char* bell = "\xEF\x83\xB3"; static constexpr const char* info = "\xEF\x84\xA9"; + static constexpr const char* notbell = "\xEF\x87\xB6"; static constexpr const char* list = "\xEF\x80\xBA"; static constexpr const char* sun = "\xEF\x86\x85"; static constexpr const char* check = "\xEF\x95\xA0"; diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index 4c6fc196..2d295d0d 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -4,9 +4,11 @@ #include #include "displayapp/screens/Symbols.h" #include "displayapp/screens/BleIcon.h" +#include "displayapp/screens/AlarmIcon.h" #include "components/settings/Settings.h" #include "components/battery/BatteryController.h" #include "components/ble/BleController.h" +#include "components/alarm/AlarmController.h" #include "components/ble/NotificationManager.h" #include "components/motion/MotionController.h" @@ -122,7 +124,8 @@ namespace { WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, - Controllers::NotificationManager& notificationManager, + Controllers::AlarmController& alarmController, + Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::MotionController& motionController, Controllers::FS& filesystem) @@ -130,6 +133,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, dateTimeController {dateTimeController}, batteryController {batteryController}, bleController {bleController}, + alarmController {alarmController}, notificationManager {notificationManager}, settingsController {settingsController}, motionController {motionController} { @@ -228,7 +232,18 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, bleIcon = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); lv_label_set_text_static(bleIcon, Symbols::bluetooth); - lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_TOP_MID, 0, 0); + + alarmIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(alarmIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_label_set_text_static(alarmIcon, Symbols::notbell); + lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + + labelAlarm = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_obj_set_style_local_text_font(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(labelAlarm, alarmIcon, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); + lv_label_set_text_static(labelAlarm, "00:00"); stepValue = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); @@ -449,9 +464,20 @@ void WatchFaceInfineat::Refresh() { bleRadioEnabled = bleController.IsRadioEnabled(); if (bleState.IsUpdated()) { lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); - lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 3); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_TOP_MID, 0, 3); } - + alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; + lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); + lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + if (alarmState) { + uint8_t alarmHours = alarmController.Hours(); + uint8_t alarmMinutes = alarmController.Minutes(); + lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); + } + else { + lv_label_set_text_static(labelAlarm, Symbols::none); + } + stepCount = motionController.NbSteps(); if (stepCount.IsUpdated()) { lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); diff --git a/src/displayapp/screens/WatchFaceInfineat.h b/src/displayapp/screens/WatchFaceInfineat.h index 55c43f98..68821c45 100644 --- a/src/displayapp/screens/WatchFaceInfineat.h +++ b/src/displayapp/screens/WatchFaceInfineat.h @@ -28,6 +28,7 @@ namespace Pinetime { WatchFaceInfineat(Controllers::DateTime& dateTimeController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, + Controllers::AlarmController& alarmController, Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::MotionController& motionController, @@ -52,6 +53,7 @@ namespace Pinetime { Utility::DirtyValue isCharging {}; Utility::DirtyValue bleState {}; Utility::DirtyValue bleRadioEnabled {}; + bool alarmState {}; Utility::DirtyValue> currentDateTime {}; Utility::DirtyValue stepCount {}; Utility::DirtyValue notificationState {}; @@ -71,6 +73,8 @@ namespace Pinetime { lv_obj_t* dateContainer; lv_obj_t* labelDate; lv_obj_t* bleIcon; + lv_obj_t* labelAlarm; + lv_obj_t* alarmIcon; lv_obj_t* stepIcon; lv_obj_t* stepValue; lv_obj_t* notificationIcon; @@ -87,6 +91,7 @@ namespace Pinetime { Controllers::DateTime& dateTimeController; const Controllers::Battery& batteryController; const Controllers::Ble& bleController; + Controllers::AlarmController& alarmController; Controllers::NotificationManager& notificationManager; Controllers::Settings& settingsController; Controllers::MotionController& motionController; @@ -109,6 +114,7 @@ namespace Pinetime { return new Screens::WatchFaceInfineat(controllers.dateTimeController, controllers.batteryController, controllers.bleController, + controllers.alarmController, controllers.notificationManager, controllers.settingsController, controllers.motionController, From ab901b890b3a07fa6c08a1f2bc71ac7c6e0161b1 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Tue, 28 May 2024 13:40:16 +0200 Subject: [PATCH 006/101] added option to show or hide alarm status --- src/components/settings/Settings.h | 14 +++++- src/displayapp/screens/WatchFaceInfineat.cpp | 51 +++++++++++++++----- src/displayapp/screens/WatchFaceInfineat.h | 2 + 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 06312077..73225ac1 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -47,7 +47,8 @@ namespace Pinetime { struct WatchFaceInfineat { bool showSideCover = true; - int colorIndex = 0; + bool showAlarmStatus = true; + int colorIndex = 0; }; Settings(Pinetime::Controllers::FS& fs); @@ -123,6 +124,17 @@ namespace Pinetime { return settings.watchFaceInfineat.showSideCover; }; + void SetInfineatShowAlarmStatus(bool show) { + if (show != settings.watchFaceInfineat.showAlarmStatus) { + settings.watchFaceInfineat.showAlarmStatus = show; + settingsChanged = true; + } + }; + + bool GetInfineatShowAlarmStatus() const { + return settings.watchFaceInfineat.showAlarmStatus; + }; + void SetInfineatColorIndex(int index) { if (index != settings.watchFaceInfineat.colorIndex) { settings.watchFaceInfineat.colorIndex = index; diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index 2d295d0d..350fa651 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -232,19 +232,20 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, bleIcon = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); lv_label_set_text_static(bleIcon, Symbols::bluetooth); - lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_TOP_MID, 0, 0); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + + labelAlarm = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_obj_set_style_local_text_font(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); + lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); + lv_label_set_text_static(labelAlarm, "00:00"); alarmIcon = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(alarmIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); lv_label_set_text_static(alarmIcon, Symbols::notbell); - lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); - labelAlarm = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); - lv_obj_set_style_local_text_font(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); - lv_obj_align(labelAlarm, alarmIcon, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); - lv_label_set_text_static(labelAlarm, "00:00"); - stepValue = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); @@ -290,7 +291,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, btnToggleCover = lv_btn_create(lv_scr_act(), nullptr); btnToggleCover->user_data = this; lv_obj_set_size(btnToggleCover, 60, 60); - lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_CENTER, 0, 80); + lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 15,-15); lv_obj_set_style_local_bg_opa(btnToggleCover, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); const char* labelToggle = settingsController.GetInfineatShowSideCover() ? "ON" : "OFF"; lblToggle = lv_label_create(btnToggleCover, nullptr); @@ -298,6 +299,17 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, lv_obj_set_event_cb(btnToggleCover, event_handler); lv_obj_set_hidden(btnToggleCover, true); + btnToggleAlarm = lv_btn_create(lv_scr_act(), nullptr); + btnToggleAlarm->user_data = this; + lv_obj_set_size(btnToggleAlarm, 60, 60); + lv_obj_align(btnToggleAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -15, -15); + lv_obj_set_style_local_bg_opa(btnToggleAlarm, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + const char* labelToggleAlarm = settingsController.GetInfineatShowAlarmStatus() ? Symbols::notbell : Symbols::bell; + lblAlarm = lv_label_create(btnToggleAlarm, nullptr); + lv_label_set_text_static(lblAlarm, labelToggleAlarm); + lv_obj_set_event_cb(btnToggleAlarm, event_handler); + lv_obj_set_hidden(btnToggleAlarm, true); + // Button to access the settings btnSettings = lv_btn_create(lv_scr_act(), nullptr); btnSettings->user_data = this; @@ -347,6 +359,7 @@ void WatchFaceInfineat::CloseMenu() { lv_obj_set_hidden(btnNextColor, true); lv_obj_set_hidden(btnPrevColor, true); lv_obj_set_hidden(btnToggleCover, true); + lv_obj_set_hidden(btnToggleAlarm, true); } bool WatchFaceInfineat::OnButtonPushed() { @@ -361,6 +374,7 @@ void WatchFaceInfineat::UpdateSelected(lv_obj_t* object, lv_event_t event) { if (event == LV_EVENT_CLICKED) { bool showSideCover = settingsController.GetInfineatShowSideCover(); int colorIndex = settingsController.GetInfineatColorIndex(); + bool showAlarmStatus = settingsController.GetInfineatShowAlarmStatus(); if (object == btnSettings) { lv_obj_set_hidden(btnSettings, true); @@ -368,6 +382,7 @@ void WatchFaceInfineat::UpdateSelected(lv_obj_t* object, lv_event_t event) { lv_obj_set_hidden(btnNextColor, !showSideCover); lv_obj_set_hidden(btnPrevColor, !showSideCover); lv_obj_set_hidden(btnToggleCover, false); + lv_obj_set_hidden(btnToggleAlarm, false); } if (object == btnClose) { CloseMenu(); @@ -383,6 +398,15 @@ void WatchFaceInfineat::UpdateSelected(lv_obj_t* object, lv_event_t event) { const char* labelToggle = showSideCover ? "OFF" : "ON"; lv_label_set_text_static(lblToggle, labelToggle); } + if (object == btnToggleAlarm) { + settingsController.SetInfineatShowAlarmStatus(!showAlarmStatus); + lv_obj_set_hidden(labelAlarm, showAlarmStatus); + lv_obj_set_hidden(alarmIcon, showAlarmStatus); + const char* labelToggleAlarm = showAlarmStatus ? Symbols::notbell : Symbols::bell; + lv_label_set_text_static(lblAlarm, labelToggleAlarm); + } + + if (object == btnNextColor) { colorIndex = (colorIndex + 1) % nColors; settingsController.SetInfineatColorIndex(colorIndex); @@ -464,18 +488,23 @@ void WatchFaceInfineat::Refresh() { bleRadioEnabled = bleController.IsRadioEnabled(); if (bleState.IsUpdated()) { lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); - lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_TOP_MID, 0, 3); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 3); } alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); - lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); if (alarmState) { uint8_t alarmHours = alarmController.Hours(); uint8_t alarmMinutes = alarmController.Minutes(); lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); + lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); + lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); + lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); + } else { lv_label_set_text_static(labelAlarm, Symbols::none); + lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + lv_obj_align(alarmIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); } stepCount = motionController.NbSteps(); diff --git a/src/displayapp/screens/WatchFaceInfineat.h b/src/displayapp/screens/WatchFaceInfineat.h index 68821c45..70598b26 100644 --- a/src/displayapp/screens/WatchFaceInfineat.h +++ b/src/displayapp/screens/WatchFaceInfineat.h @@ -81,10 +81,12 @@ namespace Pinetime { lv_obj_t* btnClose; lv_obj_t* btnNextColor; lv_obj_t* btnToggleCover; + lv_obj_t* btnToggleAlarm; lv_obj_t* btnPrevColor; lv_obj_t* btnSettings; lv_obj_t* labelBtnSettings; lv_obj_t* lblToggle; + lv_obj_t* lblAlarm; lv_obj_t* lines[nLines]; From 1af813a2ef92ec5cd38e9320514eb5e5d5c308d4 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Tue, 28 May 2024 16:44:33 +0200 Subject: [PATCH 007/101] fixed alarm status reappearing when alarm changed, even if not set to be displayed. TODO : clean comments related to this, and add am/pm format for alarm --- src/displayapp/screens/Symbols.h | 2 +- src/displayapp/screens/WatchFaceInfineat.cpp | 80 +++++++++++++++----- src/displayapp/screens/WatchFaceInfineat.h | 4 +- 3 files changed, 63 insertions(+), 23 deletions(-) diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index 41c4d42d..cf1b8aad 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -12,8 +12,8 @@ namespace Pinetime { static constexpr const char* shoe = "\xEF\x95\x8B"; static constexpr const char* clock = "\xEF\x80\x97"; static constexpr const char* bell = "\xEF\x83\xB3"; - static constexpr const char* info = "\xEF\x84\xA9"; static constexpr const char* notbell = "\xEF\x87\xB6"; + static constexpr const char* info = "\xEF\x84\xA9"; static constexpr const char* list = "\xEF\x80\xBA"; static constexpr const char* sun = "\xEF\x86\x85"; static constexpr const char* check = "\xEF\x95\xA0"; diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index 350fa651..1fe1e0cd 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -125,7 +125,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, Controllers::AlarmController& alarmController, - Controllers::NotificationManager& notificationManager, + Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::MotionController& motionController, Controllers::FS& filesystem) @@ -245,6 +245,13 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, lv_obj_set_style_local_text_color(alarmIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); lv_label_set_text_static(alarmIcon, Symbols::notbell); lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); + + // if we don't show alarm status + if (!settingsController.GetInfineatShowAlarmStatus()) { + //ToggleShowAlarmStatus(false); + lv_obj_set_hidden(labelAlarm, true); + lv_obj_set_hidden(alarmIcon, true); + } stepValue = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); @@ -304,7 +311,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, lv_obj_set_size(btnToggleAlarm, 60, 60); lv_obj_align(btnToggleAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -15, -15); lv_obj_set_style_local_bg_opa(btnToggleAlarm, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); - const char* labelToggleAlarm = settingsController.GetInfineatShowAlarmStatus() ? Symbols::notbell : Symbols::bell; + const char* labelToggleAlarm = settingsController.GetInfineatShowAlarmStatus() ? Symbols::bell : Symbols::notbell; lblAlarm = lv_label_create(btnToggleAlarm, nullptr); lv_label_set_text_static(lblAlarm, labelToggleAlarm); lv_obj_set_event_cb(btnToggleAlarm, event_handler); @@ -398,11 +405,13 @@ void WatchFaceInfineat::UpdateSelected(lv_obj_t* object, lv_event_t event) { const char* labelToggle = showSideCover ? "OFF" : "ON"; lv_label_set_text_static(lblToggle, labelToggle); } + //si je change l'état de l'alarme, ca la re affiche if (object == btnToggleAlarm) { settingsController.SetInfineatShowAlarmStatus(!showAlarmStatus); - lv_obj_set_hidden(labelAlarm, showAlarmStatus); - lv_obj_set_hidden(alarmIcon, showAlarmStatus); - const char* labelToggleAlarm = showAlarmStatus ? Symbols::notbell : Symbols::bell; + bool newShowAlarmStatus = settingsController.GetInfineatShowAlarmStatus(); + lv_obj_set_hidden(labelAlarm, !newShowAlarmStatus); + lv_obj_set_hidden(alarmIcon, !newShowAlarmStatus); + const char* labelToggleAlarm = newShowAlarmStatus ? Symbols::bell : Symbols::notbell; lv_label_set_text_static(lblAlarm, labelToggleAlarm); } @@ -490,23 +499,27 @@ void WatchFaceInfineat::Refresh() { lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 3); } - alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; - lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); - if (alarmState) { - uint8_t alarmHours = alarmController.Hours(); - uint8_t alarmMinutes = alarmController.Minutes(); - lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); - lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); - lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); - lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); - + + if (settingsController.GetInfineatShowAlarmStatus()) { + alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; + // sets the icon as bell or barred bell + lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); + //displays the time of the alarm or nothing + if (alarmState) { + uint8_t alarmHours = alarmController.Hours(); + uint8_t alarmMinutes = alarmController.Minutes(); + lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); + lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); + lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); + lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); + + } + else { + lv_label_set_text_static(labelAlarm, Symbols::none); + lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + lv_obj_align(alarmIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); + } } - else { - lv_label_set_text_static(labelAlarm, Symbols::none); - lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); - lv_obj_align(alarmIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); - } - stepCount = motionController.NbSteps(); if (stepCount.IsUpdated()) { lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); @@ -541,6 +554,31 @@ void WatchFaceInfineat::ToggleBatteryIndicatorColor(bool showSideCover) { lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); } } +/* +void WatchFaceInfineat::ToggleShowAlarmStatus(bool showAlarmStatus) { + // If show alarm option is on, check alarm state to display + if (showAlarmStatus) { + alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; + // sets the icon as bell or barred bell + lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); + //displays the time of the alarm or nothing + if (alarmState) { + uint8_t alarmHours = alarmController.Hours(); + uint8_t alarmMinutes = alarmController.Minutes(); + lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); + lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); + lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); + lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); + + } + else { + lv_label_set_text_static(labelAlarm, Symbols::none); + lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + lv_obj_align(alarmIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); + } + } +} +*/ bool WatchFaceInfineat::IsAvailable(Pinetime::Controllers::FS& filesystem) { lfs_file file = {}; diff --git a/src/displayapp/screens/WatchFaceInfineat.h b/src/displayapp/screens/WatchFaceInfineat.h index 70598b26..5e258dcf 100644 --- a/src/displayapp/screens/WatchFaceInfineat.h +++ b/src/displayapp/screens/WatchFaceInfineat.h @@ -81,7 +81,7 @@ namespace Pinetime { lv_obj_t* btnClose; lv_obj_t* btnNextColor; lv_obj_t* btnToggleCover; - lv_obj_t* btnToggleAlarm; + lv_obj_t* btnToggleAlarm; lv_obj_t* btnPrevColor; lv_obj_t* btnSettings; lv_obj_t* labelBtnSettings; @@ -101,6 +101,8 @@ namespace Pinetime { void SetBatteryLevel(uint8_t batteryPercent); void ToggleBatteryIndicatorColor(bool showSideCover); + void ToggleShowAlarmStatus(bool showAlarmStatus); + lv_task_t* taskRefresh; lv_font_t* font_teko = nullptr; lv_font_t* font_bebas = nullptr; From 735083a0422f2ada6a7f0d4fe7dd15aca54c5d67 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Tue, 28 May 2024 17:45:31 +0200 Subject: [PATCH 008/101] AMPM for alarm seems to work - but overloads the screen, of course. check tomorrow all cases --- src/displayapp/screens/WatchFaceInfineat.cpp | 56 ++++++++++---------- src/displayapp/screens/WatchFaceInfineat.h | 1 + 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index 1fe1e0cd..db96c991 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -148,6 +148,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, font_bebas = lv_font_load("F:/fonts/bebas.bin"); } + // Side Cover static constexpr lv_point_t linePoints[nLines][2] = {{{30, 25}, {68, -8}}, {{26, 167}, {43, 216}}, @@ -241,14 +242,19 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); lv_label_set_text_static(labelAlarm, "00:00"); + labelTimeAmPmAlarm = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(labelTimeAmPmAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_label_set_text_static(labelTimeAmPmAlarm, ""); + lv_obj_set_style_local_text_color(labelTimeAmPmAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_obj_align(labelTimeAmPmAlarm, labelAlarm, LV_ALIGN_OUT_TOP_RIGHT, 0, 0); + alarmIcon = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(alarmIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); lv_label_set_text_static(alarmIcon, Symbols::notbell); lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); - // if we don't show alarm status + // don't show the icons jsut set if we don't show alarm status if (!settingsController.GetInfineatShowAlarmStatus()) { - //ToggleShowAlarmStatus(false); lv_obj_set_hidden(labelAlarm, true); lv_obj_set_hidden(alarmIcon, true); } @@ -405,7 +411,7 @@ void WatchFaceInfineat::UpdateSelected(lv_obj_t* object, lv_event_t event) { const char* labelToggle = showSideCover ? "OFF" : "ON"; lv_label_set_text_static(lblToggle, labelToggle); } - //si je change l'état de l'alarme, ca la re affiche + if (object == btnToggleAlarm) { settingsController.SetInfineatShowAlarmStatus(!showAlarmStatus); bool newShowAlarmStatus = settingsController.GetInfineatShowAlarmStatus(); @@ -504,11 +510,28 @@ void WatchFaceInfineat::Refresh() { alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; // sets the icon as bell or barred bell lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); - //displays the time of the alarm or nothing + //displays the time of the alarm or nothing if the alarm is not set if (alarmState) { uint8_t alarmHours = alarmController.Hours(); uint8_t alarmMinutes = alarmController.Minutes(); + //handles the am pm format. + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + char ampmChar[3] = "AM"; + if (alarmHours == 0) { + alarmHours = 12; + } else if (alarmHours == 12) { + ampmChar[0]='P'; + } else if (alarmHours > 12) { + alarmHours = alarmHours - 12; + ampmChar[0]='P'; + } + lv_label_set_text(labelTimeAmPmAlarm, ampmChar); + lv_obj_set_style_local_text_font(labelTimeAmPmAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(labelTimeAmPmAlarm, labelAlarm, LV_ALIGN_OUT_TOP_RIGHT, 0, 0); + } + lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); + lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); @@ -554,31 +577,6 @@ void WatchFaceInfineat::ToggleBatteryIndicatorColor(bool showSideCover) { lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); } } -/* -void WatchFaceInfineat::ToggleShowAlarmStatus(bool showAlarmStatus) { - // If show alarm option is on, check alarm state to display - if (showAlarmStatus) { - alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; - // sets the icon as bell or barred bell - lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); - //displays the time of the alarm or nothing - if (alarmState) { - uint8_t alarmHours = alarmController.Hours(); - uint8_t alarmMinutes = alarmController.Minutes(); - lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); - lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); - lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); - lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); - - } - else { - lv_label_set_text_static(labelAlarm, Symbols::none); - lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); - lv_obj_align(alarmIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); - } - } -} -*/ bool WatchFaceInfineat::IsAvailable(Pinetime::Controllers::FS& filesystem) { lfs_file file = {}; diff --git a/src/displayapp/screens/WatchFaceInfineat.h b/src/displayapp/screens/WatchFaceInfineat.h index 5e258dcf..7ea134f2 100644 --- a/src/displayapp/screens/WatchFaceInfineat.h +++ b/src/displayapp/screens/WatchFaceInfineat.h @@ -74,6 +74,7 @@ namespace Pinetime { lv_obj_t* labelDate; lv_obj_t* bleIcon; lv_obj_t* labelAlarm; + lv_obj_t* labelTimeAmPmAlarm; lv_obj_t* alarmIcon; lv_obj_t* stepIcon; lv_obj_t* stepValue; From 48cfd48e395668ac31e133a057b4905ddaca6c07 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 11:18:10 +0200 Subject: [PATCH 009/101] update settings of watchfaceInifineat with alarm settings so it looks better --- src/displayapp/screens/WatchFaceInfineat.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index db96c991..1849e0fb 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -304,7 +304,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, btnToggleCover = lv_btn_create(lv_scr_act(), nullptr); btnToggleCover->user_data = this; lv_obj_set_size(btnToggleCover, 60, 60); - lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 15,-15); + lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_CENTER, 0,0); lv_obj_set_style_local_bg_opa(btnToggleCover, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); const char* labelToggle = settingsController.GetInfineatShowSideCover() ? "ON" : "OFF"; lblToggle = lv_label_create(btnToggleCover, nullptr); @@ -315,7 +315,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, btnToggleAlarm = lv_btn_create(lv_scr_act(), nullptr); btnToggleAlarm->user_data = this; lv_obj_set_size(btnToggleAlarm, 60, 60); - lv_obj_align(btnToggleAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -15, -15); + lv_obj_align(btnToggleAlarm, lv_scr_act(), LV_ALIGN_CENTER, 0, 80); lv_obj_set_style_local_bg_opa(btnToggleAlarm, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); const char* labelToggleAlarm = settingsController.GetInfineatShowAlarmStatus() ? Symbols::bell : Symbols::notbell; lblAlarm = lv_label_create(btnToggleAlarm, nullptr); From edb61e7658c8ee3457b847bd822180c601b14a5b Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 12:08:25 +0200 Subject: [PATCH 010/101] fixed that label ampm stays shown when alarm status is hidden --- .../alarmStatusOnInfineat.md | 22 ++++++++++++++++++ .../infineat_alarm_notset.png | Bin 0 -> 6312 bytes .../infineat_alarm_set_12hrs.png | Bin 0 -> 6649 bytes .../infineat_alarm_set_24hrs.png | Bin 0 -> 6286 bytes .../infineat_settings.png | Bin 0 -> 7646 bytes src/displayapp/screens/WatchFaceInfineat.cpp | 2 ++ 6 files changed, 24 insertions(+) create mode 100644 doc/alarmStatusOnInfineat/alarmStatusOnInfineat.md create mode 100644 doc/alarmStatusOnInfineat/infineat_alarm_notset.png create mode 100644 doc/alarmStatusOnInfineat/infineat_alarm_set_12hrs.png create mode 100644 doc/alarmStatusOnInfineat/infineat_alarm_set_24hrs.png create mode 100644 doc/alarmStatusOnInfineat/infineat_settings.png diff --git a/doc/alarmStatusOnInfineat/alarmStatusOnInfineat.md b/doc/alarmStatusOnInfineat/alarmStatusOnInfineat.md new file mode 100644 index 00000000..22885a84 --- /dev/null +++ b/doc/alarmStatusOnInfineat/alarmStatusOnInfineat.md @@ -0,0 +1,22 @@ +# [InfiniTime : show alarm status on infineat watchface](https://github.com/Eve1374/InfiniTime/tree/alarm-status-on-infineat) +- I forked from [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime) and added a branch alarm-status-on-infineat +- I modified the watchface settings to have the possibility to show alarm status on watchface + +Here are pictures with alarm set in 12 and 24hrs format : + +![alarm set and shown, 12hrs format](infineat_alarm_set_12hrs.png "alarm set and shown, 12hrs format") +![alarm set and shown, 24hrs format](infineat_alarm_set_24hrs.png "alarm set and shown, 24hrs format") + +Alarm not set : + +![alarm shown and not set](infineat_alarm_notset.png "alarm shown and not set") + +Settings view : + +![settings](infineat_settings.png "settings modified with a button to turn on or off alarm display") + + +## Possible further development : +- Move this setting to the Alarm app and include alarm display in all watchfaces ? + + diff --git a/doc/alarmStatusOnInfineat/infineat_alarm_notset.png b/doc/alarmStatusOnInfineat/infineat_alarm_notset.png new file mode 100644 index 0000000000000000000000000000000000000000..a8883db00ecbc263f434564fa9b143e69fd31ae3 GIT binary patch literal 6312 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 azudktU3C@JdxQ7~0?=01S1VJ6zWqPFIzyQN literal 0 HcmV?d00001 diff --git a/doc/alarmStatusOnInfineat/infineat_alarm_set_12hrs.png b/doc/alarmStatusOnInfineat/infineat_alarm_set_12hrs.png new file mode 100644 index 0000000000000000000000000000000000000000..259c09833f9e8cdfee2144c64412497e07b1f15c GIT binary patch literal 6649 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 GgZ>8t;MQ3H literal 0 HcmV?d00001 diff --git a/doc/alarmStatusOnInfineat/infineat_alarm_set_24hrs.png b/doc/alarmStatusOnInfineat/infineat_alarm_set_24hrs.png new file mode 100644 index 0000000000000000000000000000000000000000..d31de7980157ae4cbe52482f1f9393cca795abde GIT binary patch literal 6286 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 ICFuMA0c@dJw*UYD literal 0 HcmV?d00001 diff --git a/doc/alarmStatusOnInfineat/infineat_settings.png b/doc/alarmStatusOnInfineat/infineat_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..0dffd5e781ae4b945d25c629d4f88d9c7435a770 GIT binary patch literal 7646 zcmYLu1yoeu7w*hB3@}JbDBaR2Dd51+tDV2U0RN(uf3FVz+Ak>344Fq$S$UT#^8+*hm5<+eBGlZ_v0+(l$ zz-;uU>d~88+4Wv2;r*ef3<94#Z5MskCj8fZ7T0f{xUB80lwamrccn(5xBCbwL7Xk$ zrd<6jDKEc~Dqn!nY3fz{W>3&C_g<&%ZHSeP8&Rt4TcE*8 z&T>E5q5Ru%8@yn_aO}NXG{`Z#8I5Gu0}WS!+?3|SM(T?E$jK&G(-ZUPwc@>ZS0Y|w zAbHNok)124#e^;&<}ijO8;z%BQokeh`_~1ZW<5PHx=&o9eRxs)OPQjItTGehb1?{Y zy522)SrZlAtCTOwj{%BF9$3hsz2#%?j8>RZ3fb8zcN9{_YXWcK=>?W7kPfj+qS|RsPea zsKEoCzn|M8`{2ZuwgHtBb{cqis!&~8gYj9uevPuC>`1X?ROKdL=MYQ>e(SiEpR$5L zAab>jXS(DNZOHXmu_yEC=^qZ@@#^kk$*4HP%y`$ zzRj{zAZ?NvVgb2+cr9d8wDx9!i+Mtse@PP?#8*7aHO7ba_u5Fj_9?us{ze~zWFE+L zfNhEx%n$TyohV>{WP)jWnIkG%VN|yKDz^kXYb5L5BKO{D@5Apn4q+oFMIQ4hc5)%g z%eR=`h1*6JY?`X#`+g0laUMa=C)=vm;4R-SVMhuX%YxiX0XGQ{r$GI3!C%agSDg%A zS06)bL2H>%r*>)Xg>)PKoxQ!~y*6B(O!Pyt(pF{eCEUA4GVX)Dy*)+85Ps#t1Ngv< z@*2CjlY%1-tS=!2G|9kL@(Vi^U?BELY8F$PbfoQm3WN0gozL~WLNAK+KtYmNeQ`=k zGgM_+gOYVA)bgzHG&kxsQ)&>W|EU%~vD%`TkMabBeV?21u(dw|%o7=~{WM!*&Lg*x02%M^(9t!?un{nt@(sB3&e^|>9|jM53W|3AnQ;JP^3#gg__LleQ&l$Mq>tp+Ge3Ms zv+r>C8J@+%OeOG7b#@=F+eU&4B0tX}Ws~JOv4a!PMp~5sk9iT!We&~7M>32-v1W!~ z6kdbAVLmfnlYhqA!j5+b=lE|}ED7)5Fo`@XX!0?(p9#*q^a!h%DRdOaK^yy_NIX+S z%6oP^*HHeYl<<)^&D%fwl%|U!v~e69fKR`o-G7U6?&A!uVW+^8P_8ABh>@oXgm-eV zsM*kQlJcMzk7VvW+ijw+!~dc~t5@((h~2y(V-fQZpxy~s?+*-KeE`wrg*z&~#nV7s z8XYn$2H0hm=wyAzf&{NMa{;Jld2}rg^fgvO>!4!vX5JJ7i%17Xj(i@ji%faPDGi-Z0(~3N zEGX^?Y-EJ>sWO7-U!0!iOM~Wb#uga)U?gA=nG2^>)7??mCcb)B4&Jg5ILSAZz`)w7 zk51w#$8je^AocHB{19a-B+k=swSIHt6}gDxrZ;10%#q_PCWuqa%(k3LBwpG9hYMY+^ff`=<=r_~P8b_Hw`I3#DDZP6$buHqeV8mlu+7f4P(4{o})A zaMIgQ{6_tAhxO$Ke#_(W;^ILv_zzO{r2uqIc0%BF)&HI+rCJ!UMM5-Z<2}{emY1#Z z8E=fuPD&rE&HriEHclUGce1nElgQdn1xA^XDnHjUwVl2k@S{tBQ6fov+Z zMS7Qq^1Lmo791)LMEs}^R#lK+*pVep7ZV>y7@S6V*Q`xJU0sI1$c}+1+ya!td`lzy zz4)4BybcD#06d31tNX9q+Dc336n0CSE!92yPuU~aRh99izp3Y4p9l@8Q7A3r06tl0 zAGz5SKD2tA!}G(x6VkaGf?;!D)aSuaWstmjAU@xRnYG?l1s^E zfZI}TQ)E0f((jnVya3>G*D{tGzIuv`6+}6!vE^#PrOFvRK3<(7-+bpL9ZBT}uID7k z!G&J+Q))l%YvZ*>7k9V}?_ape9W{l$&Da{)Z@VO`tY#T@r%Xr#MERX!*PG*Oah(#0 zX@U~mH$S*L&bHJLu`wlfpRBb}iD$nCt8dNt0!9+Z{pV1=$o~l#A{0a+B3*j5zUnxv zj#uY(s>3Z%NQZIZx9{cGi`(4c=-n<-g=-g?PZr__;q&;sA&F7??-u4xIlejw5KIsf zmrxg`O5q_%AGQq$N^T7&QPFAA9jhLAla>UU;g|Y~Ya*l_{p(hql_A7QS9*UaQF07rN6 z|H|b(RFoo5Ps7a2iv~jw4GU3?-lcfTPmQA(^KCj94DNbn@SQqs)mk+gxwVFFCm)lx46diGr;b@3gd5|Aihj#O0Fsq z&&klpdR48vmZ6MM9+Lcf4llW`&<>;YPq=2kkx%|;Ei;|v_Fs!@3!TU*rhakSx!ykA zJvU9z;1~H(5RwY<0&MUsn&p!B7yi0;q*$Y-LMKCFfx`GWCqk5jw}+}rA1WU4-Lwx) zDmuB?<6lX~_{O}F{)Y7aVb!bWbTQTm_f2$qcT_yN-nPZ!=rIwK5GeSkif?DJ`GwFA z{Y*&*NvE>sSToL-yu?0;5hd&SEgD$hG7r;gI{Dy-8HU4nDXVn8CpO@Jjyf7XWOPlp zu#OsC47H=MEDF`LS6{DFLVIz1{2L?rRVE(q7F2#Kpb{d@YOH)@`L^xU=jz zYGR&Q@M!1$wctk#s`^QmN;4*Yg4D#B+kAR8II*k8rDHzhw`XGb)&<+_@d7MhI6vT{}q14}~Tj^F+DCHrRGH(GmMJ7r1-DPLqDk$pl9;Ui@oUgYj0gzelJz+(gI4@Yg~1#Pz`Y$YNbT$t1Lm0^7p!Pi|=RlPn( zHGk|Izz)RUdl`5o8%da0EMgj|%IK$ZQi8%qq; zCuzfpxEv59=%Vfd+gSN6z;Q8lP9Q!OqT7`#G^Z+t~;9Nihh2ausZm z8Jx0kG)X8TU48GsZMfN zpWXS)kgLdp%K~yGFLw3C@vTUvHX_Y7-! zBt^%Eo@O2H?&=kP*KhxN&IxKp=t|C+)dG1+rTorMsVcWQ`tI8g>$z2B+gZP#IH(FW zm~Zu8zRiy&k3Yi=s9bAp>k|6K$I4bxOW?CFRb)1m-~838NRy@fnz_KM)B`7=f-^^3C@p*W?$($iR{&ak50(AyOZ zlokvVab;VINX6i)V{3kPa~xhiOF6jpn@k@(Mlc03`(FO60vE+zrst7StVQzPh1VS5 z-MFZ3LwGsF=k-BMT$FWN^l%PJ{GaM#*!KN;Rm;sTKhVZpLu&gf4|ViZo1fU362K$bVx&1i$hcu+!xp<>sPk0rpO28 z;~ZiPtcu$vQ?sLvJ|;E9eJJGyjQ&4pG2IaLp|}J%A6lVv(XiMYM!ZgN^cH#T?@6*bRSetru#znSyJNaEW zZ4KDdd9cV9GJA;-EA&5E|J1@KZPNNw-|2vNX?cuUG@u%Naa|Qi(-~b4@?Y(Z#w5KF zP0s8;%2!|;tot2ZAu0f{8_Be&rK0DNwu?@~gsJnBGn@DmPy^N&Mp|TSZEbPI0yK;Z zNJ1I9@VtMu1?~F>;Pwa$ni7G1(6!8P#HBl3iB`6k>h=@5n%t4-MiE%yBc^|4%VXW_ zyr;tHc(DoQ);C95&8Utbps+Q4xLPMfskbaxE~bnn9AfoOJf7KnKsx#dx*c;ivH zRN(Ay@o%MH?8f#DlG0@E^R%4MGCF6#St6S7ACE2{kgtdJfJ+N-@8`1NY>UAwISj3l0v&)jP6nIZrFU3+77 zWcNV+#vqAIA{H>sGnpU|Fj3$2Rig46u7w3fFez&=-Z|PxI5y|%hW6O#Q19i22wnk7 zb_Q>j{o%eu6qp&S3ehPb@_NxS?vJ83M!#KCW2Rrqy#@PLB@mU`8t{RDy<^rLx-k z5e>^yTGNCVOu7SKpO;ZQZo&5{0`54tk2lz-U)d35xvxZw!^=^$xFHmk6pB(O$RfbC zmdje7+`()73#KM9Dfi}=yjJ(SKeDYnz?p5H4qJ%21y4chU1~~fC=o8tJe5OyO#%*l znPdy)q&1^rEAh<`^vI3E{FNhWO^j$kXD3(6(-Yiq6%5ETGfpNf@17s(U8d&LHUr>6 zLIT{u2UCcqw`TSR-rGakL6!`(!{HIhBWh`tP*4xG51J%N3jvt^z9PL5R?ttFZLIQ- z`x3_{U{;J(g8ZexEU&uj3*Z5}#@VAMX#8mnp_nh6%iy2Y{V`n4N#Fe9GBDR!pr!9e zoim(}_M9vjUG>x70S#0hAv|Xp5abae=t>|X2OP4Y(ul3QLj#Pi79y}nb`zj2n#<0X z&Fv?6EXD=reh`I8q#Jd)k{c;J1518v^@q6gX8~P=l&i?y&F|725G6 zh+R>*3s$HVxDrm0B_X`E6Kc^&-_OS4)Z*Ow?hLG8P(KgU-s=@q6M!3M>L~DTAd-Y)R?!qNax07j3i*`@GE|7 z`7I=yQ%)-^U+FOwyUz52EFvz%=e% z3lTtZ7?y#lt#m%Btqf_W#}^YXOoCk;K>2b%Z=5e28-EN1&-KbP7B#5*w@;if;<=&BxxfJ-XB{*zq z^O+>^eZcC;uspYvpoC<+@ZGGR1bv7ymm9RPdTZ;PMm9UdH+gc7>32j5NHm9L)V}%e z-x%0WMPNgKFh$}MFcZfiblO66c6j=NjBjC2CMSb2 zIlLAms!l%pV%?&)dwhgL@RCmTE$QYnBWx||w2S$SQfCT`O*VobCK(|H?Xg_|bBS}?Wgz7*r0KKhg%-qANX>_jhx`5r+RoKL4|}bHDtR7d;W>2v3?=b1 zJpIc{D#iQ3ZvB?y3<$vDX0GFDC*@r%Qoo+gq=Ez;c&Vnhaw9k+ANH7_9g{lX&(VINs#2zUwVx~T2Hwr;^`6Vd>n^^mVys!lB z<2H%OvqnAA2Ak+?vhKlWbMM4R*)|QBiMYN~5MTqYwrxotyM5>Ju)D(*#mffzNAF$| z@QZO#C?R9zS|@&JE;bMZG)McA{*xZG=We{s9z>Al;?MyXUwISjEA1gf)S2p=V5rg{ z>!9nzj@$s*d%EZxaZ&o|`89*Gw{imm6mD3k|0k)fz4ioJdlFqB zTD9Xce6}C4k?=xm=jBEfsQ`fIVn}h0Gr+j}*x{WDua#QwKrZE%GJPzRa~ZZ01cVfD z8g&-Kj4RAnwPFP7e`kB_DcM7sUwoS7zSkIK%ET5HgruflyqylDcKrK+!9V&dO7azw z1A5sO*Y@;&cKAUQ^{i6uAFf3wRNeB*J_$FmgIoM6hb7W3h9=h}{L)55r7QqE;iVj^ zw2k_9;?==FXn+L{dXwE@V?lMaBYxVAXuBv(Z{PZT3uBS1OTmfH*aFIp*GD~1pWsP$t`u36$@k_F4g_f#YKhEiq}}UZ7it#zvKzrD18QJ+fxZvxhImWwXeQ hr&pfjnJxd#opE9BMx>kv6?WeiprW7&FPAe9`9GU+tFZt8 literal 0 HcmV?d00001 diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index 1849e0fb..20d0beb3 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -257,6 +257,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, if (!settingsController.GetInfineatShowAlarmStatus()) { lv_obj_set_hidden(labelAlarm, true); lv_obj_set_hidden(alarmIcon, true); + lv_obj_set_hidden(labelTimeAmPmAlarm, true); } stepValue = lv_label_create(lv_scr_act(), nullptr); @@ -417,6 +418,7 @@ void WatchFaceInfineat::UpdateSelected(lv_obj_t* object, lv_event_t event) { bool newShowAlarmStatus = settingsController.GetInfineatShowAlarmStatus(); lv_obj_set_hidden(labelAlarm, !newShowAlarmStatus); lv_obj_set_hidden(alarmIcon, !newShowAlarmStatus); + lv_obj_set_hidden(labelTimeAmPmAlarm, !newShowAlarmStatus); const char* labelToggleAlarm = newShowAlarmStatus ? Symbols::bell : Symbols::notbell; lv_label_set_text_static(lblAlarm, labelToggleAlarm); } From a5246941b015492b4efafe999c3995339837a945 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Tue, 7 May 2024 15:12:52 +0200 Subject: [PATCH 011/101] =?UTF-8?q?compile=20sans=20avoir=20modifi=C3=A9?= =?UTF-8?q?=20de=20code=20ok?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- compile.sh | 7 +++++++ make_pine.sh | 6 ++++++ make_pine_mcu.sh | 6 ++++++ 3 files changed, 19 insertions(+) create mode 100755 compile.sh create mode 100755 make_pine.sh create mode 100755 make_pine_mcu.sh diff --git a/compile.sh b/compile.sh new file mode 100755 index 00000000..6ee6c930 --- /dev/null +++ b/compile.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +rm -r build +cp make_pine.sh build/ + +cmake -DARM_NONE_EABI_TOOLCHAIN_PATH=/home/eve/Work/gcc-arm-none-eabi-10.3-2021.10 -DNRF5_SDK_PATH=/home/eve/Work/nRF5_SDK_17.1.0_ddde560 -DTARGET_DEVICE=PINETIME -DBUILD_DFU=1 -DBUILD_RESOURCES=1 -B build -DCMAKE_BUILD_TYPE=Release + diff --git a/make_pine.sh b/make_pine.sh new file mode 100755 index 00000000..006c2421 --- /dev/null +++ b/make_pine.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +#cp ./displayapp/apps/Apps.h ../src/displayapp/apps/Apps.h + +make -j4 pinetime-app + diff --git a/make_pine_mcu.sh b/make_pine_mcu.sh new file mode 100755 index 00000000..d7af1cb8 --- /dev/null +++ b/make_pine_mcu.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +#cp ./displayapp/apps/Apps.h ../src/displayapp/apps/Apps.h + +make -j4 pinetime-mcuboot-app + From b16e13f47180a922c097ba2169f58d729a7d990a Mon Sep 17 00:00:00 2001 From: ecarlett Date: Thu, 23 May 2024 11:07:12 +0200 Subject: [PATCH 012/101] add WatchFaceInfineatColors that has different color sets than infineat --- doc/palettes.xcf | Bin 0 -> 151003 bytes src/CMakeLists.txt | 3 +- src/components/settings/Settings.h | 7 + src/displayapp/UserApps.h | 1 + src/displayapp/apps/Apps.h.in | 1 + src/displayapp/apps/CMakeLists.txt | 3 +- .../screens/WatchFaceInfineatColors.cpp | 510 ++++++++++++++++++ .../screens/WatchFaceInfineatColors.h | 123 +++++ .../screens/settings/SettingWatchFace.h | 1 + 9 files changed, 647 insertions(+), 2 deletions(-) create mode 100644 doc/palettes.xcf create mode 100644 src/displayapp/screens/WatchFaceInfineatColors.cpp create mode 100644 src/displayapp/screens/WatchFaceInfineatColors.h diff --git a/doc/palettes.xcf b/doc/palettes.xcf new file mode 100644 index 0000000000000000000000000000000000000000..bfad74e04e73ac4ee19c70cfbfa222a5e9d36d36 GIT binary patch literal 151003 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 z wakeUpMode {0}; uint16_t shakeWakeThreshold = 150; diff --git a/src/displayapp/UserApps.h b/src/displayapp/UserApps.h index 67bbfa7d..76ffa684 100644 --- a/src/displayapp/UserApps.h +++ b/src/displayapp/UserApps.h @@ -12,6 +12,7 @@ #include "displayapp/screens/WatchFaceAnalog.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" #include "displayapp/screens/WatchFaceInfineat.h" +#include "displayapp/screens/WatchFaceInfineatColors.h" #include "displayapp/screens/WatchFacePineTimeStyle.h" #include "displayapp/screens/WatchFaceTerminal.h" diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index 2104a267..a591497b 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -51,6 +51,7 @@ namespace Pinetime { PineTimeStyle, Terminal, Infineat, + InfineatColors, CasioStyleG7710, }; diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index d7858760..8925b246 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -26,7 +26,8 @@ else() set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::PineTimeStyle") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::InfineatColors") + #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware") endif() diff --git a/src/displayapp/screens/WatchFaceInfineatColors.cpp b/src/displayapp/screens/WatchFaceInfineatColors.cpp new file mode 100644 index 00000000..88241942 --- /dev/null +++ b/src/displayapp/screens/WatchFaceInfineatColors.cpp @@ -0,0 +1,510 @@ +#include "displayapp/screens/WatchFaceInfineatColors.h" + +#include +#include +#include "displayapp/screens/Symbols.h" +#include "displayapp/screens/BleIcon.h" +#include "components/settings/Settings.h" +#include "components/battery/BatteryController.h" +#include "components/ble/BleController.h" +#include "components/ble/NotificationManager.h" +#include "components/motion/MotionController.h" + +using namespace Pinetime::Applications::Screens; + +namespace { + void event_handler(lv_obj_t* obj, lv_event_t event) { + auto* screen = static_cast(obj->user_data); + screen->UpdateSelected(obj, event); + } + + enum class colors { + orange, + blue, + green, + rainbow, + vivid, + pink, + nordGreen, + }; + + constexpr int nColors = 7; // must match number of colors in InfineatColorsColors + + constexpr int nLines = WatchFaceInfineatColors::nLines; + + constexpr std::array orangeColors = {LV_COLOR_MAKE(0xfd, 0x87, 0x2b), + LV_COLOR_MAKE(0xdb, 0x33, 0x16), + LV_COLOR_MAKE(0x6f, 0x10, 0x00), + LV_COLOR_MAKE(0xfd, 0x7a, 0x0a), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xe8, 0x51, 0x02), + LV_COLOR_MAKE(0xea, 0x1c, 0x00)}; + constexpr std::array blueColors = {LV_COLOR_MAKE(0xe7, 0xf8, 0xff), + LV_COLOR_MAKE(0x22, 0x32, 0xd0), + LV_COLOR_MAKE(0x18, 0x2a, 0x8b), + LV_COLOR_MAKE(0xe7, 0xf8, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0x59, 0x91, 0xff), + LV_COLOR_MAKE(0x16, 0x36, 0xff)}; + constexpr std::array greenColors = {LV_COLOR_MAKE(0xb8, 0xff, 0x9b), + LV_COLOR_MAKE(0x08, 0x86, 0x08), + LV_COLOR_MAKE(0x00, 0x4a, 0x00), + LV_COLOR_MAKE(0xb8, 0xff, 0x9b), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0x62, 0xd5, 0x15), + LV_COLOR_MAKE(0x00, 0x74, 0x00)}; + constexpr std::array rainbowColors = {LV_COLOR_MAKE(0x2d, 0xa4, 0x00), //vert petit triangle le plus haut + LV_COLOR_MAKE(0xac, 0x09, 0xc4), //purple petit triangle bas côté horloge + LV_COLOR_MAKE(0xfe, 0x03, 0x03), //rouge 2 petits triangles à côté de batterie + LV_COLOR_MAKE(0x0d, 0x57, 0xff), //bleu petit triangle le plus bas + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xe0, 0xb9, 0x00), //jaune gd triangle haut + LV_COLOR_MAKE(0xe8, 0x51, 0x02)}; //orange gd triangle bas + constexpr std::array rainbowVividColors ={LV_COLOR_MAKE(0xa5, 0xeb, 0x64), + LV_COLOR_MAKE(0xfc, 0x42, 0xb5), + LV_COLOR_MAKE(0xe7, 0xc1, 0xff), + LV_COLOR_MAKE(0x11, 0xdf, 0xfa), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xec, 0x5d), + LV_COLOR_MAKE(0xff, 0x93, 0xaf)}; + + constexpr std::array pinkColors = {LV_COLOR_MAKE(0xff, 0xe5, 0xec), + LV_COLOR_MAKE(0xff, 0xb3, 0xc6), + LV_COLOR_MAKE(0xfb, 0x6f, 0x92), + LV_COLOR_MAKE(0xff, 0xe5, 0xec), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xc2, 0xd1), + LV_COLOR_MAKE(0xff, 0x8f, 0xab)}; + constexpr std::array nordGreenColors = {LV_COLOR_MAKE(0xd5, 0xf0, 0xe9), + LV_COLOR_MAKE(0x23, 0x83, 0x73), + LV_COLOR_MAKE(0x1d, 0x41, 0x3f), + LV_COLOR_MAKE(0xd5, 0xf0, 0xe9), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0x2f, 0xb8, 0xa2), + LV_COLOR_MAKE(0x11, 0x70, 0x5a)}; + + constexpr const std::array* returnColor(colors color) { + if (color == colors::orange) { + return &orangeColors; + } + if (color == colors::blue) { + return &blueColors; + } + if (color == colors::green) { + return &greenColors; + } + if (color == colors::rainbow) { + return &rainbowColors; + } + if (color == colors::vivid) { + return &rainbowVividColors; + } + if (color == colors::pink) { + return &pinkColors; + } + return &nordGreenColors; + } +} + +WatchFaceInfineatColors::WatchFaceInfineatColors(Controllers::DateTime& dateTimeController, + const Controllers::Battery& batteryController, + const Controllers::Ble& bleController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::MotionController& motionController, + Controllers::FS& filesystem) + : currentDateTime {{}}, + dateTimeController {dateTimeController}, + batteryController {batteryController}, + bleController {bleController}, + notificationManager {notificationManager}, + settingsController {settingsController}, + motionController {motionController} { + lfs_file f = {}; + if (filesystem.FileOpen(&f, "/fonts/teko.bin", LFS_O_RDONLY) >= 0) { + filesystem.FileClose(&f); + font_teko = lv_font_load("F:/fonts/teko.bin"); + } + + if (filesystem.FileOpen(&f, "/fonts/bebas.bin", LFS_O_RDONLY) >= 0) { + filesystem.FileClose(&f); + font_bebas = lv_font_load("F:/fonts/bebas.bin"); + } + + // Side Cover + static constexpr lv_point_t linePoints[nLines][2] = {{{30, 25}, {68, -8}}, + {{26, 167}, {43, 216}}, + {{27, 40}, {27, 196}}, + {{12, 182}, {65, 249}}, + {{17, 99}, {17, 144}}, + {{14, 81}, {40, 127}}, + {{14, 163}, {40, 118}}, + {{-20, 124}, {25, -11}}, + {{-29, 89}, {27, 254}}}; + + static constexpr lv_style_int_t lineWidths[nLines] = {18, 15, 14, 22, 20, 18, 18, 52, 48}; + + const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); + for (int i = 0; i < nLines; i++) { + lines[i] = lv_line_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_line_width(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, lineWidths[i]); + lv_color_t color = (*colors)[i]; + lv_obj_set_style_local_line_color(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, color); + lv_line_set_points(lines[i], linePoints[i], 2); + } + + logoPine = lv_img_create(lv_scr_act(), nullptr); + lv_img_set_src(logoPine, "F:/images/pine_small.bin"); + lv_obj_set_pos(logoPine, 15, 106); + + lineBattery = lv_line_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_line_width(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 24); + lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); + lv_obj_set_style_local_line_opa(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 190); + lineBatteryPoints[0] = {27, 105}; + lineBatteryPoints[1] = {27, 106}; + lv_line_set_points(lineBattery, lineBatteryPoints, 2); + lv_obj_move_foreground(lineBattery); + + notificationIcon = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); + lv_obj_set_style_local_radius(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE); + lv_obj_set_size(notificationIcon, 13, 13); + lv_obj_set_hidden(notificationIcon, true); + + if (!settingsController.GetInfineatShowSideCover()) { + ToggleBatteryIndicatorColor(false); + for (auto& line : lines) { + lv_obj_set_hidden(line, true); + } + } + + timeContainer = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_opa(timeContainer, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); + lv_obj_set_size(timeContainer, 185, 185); + lv_obj_align(timeContainer, lv_scr_act(), LV_ALIGN_CENTER, 0, -10); + + labelHour = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_static(labelHour, "01"); + lv_obj_set_style_local_text_font(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); + lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 0); + + labelMinutes = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); + lv_label_set_text_static(labelMinutes, "00"); + lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + + labelTimeAmPm = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + + lv_label_set_text_static(labelTimeAmPm, ""); + lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 15); + + dateContainer = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_opa(dateContainer, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); + lv_obj_set_size(dateContainer, 60, 30); + lv_obj_align(dateContainer, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 5); + + static constexpr lv_color_t grayColor = LV_COLOR_MAKE(0x99, 0x99, 0x99); + labelDate = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_obj_set_style_local_text_font(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(labelDate, dateContainer, LV_ALIGN_IN_TOP_MID, 0, 0); + lv_label_set_text_static(labelDate, "Mon 01"); + + bleIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_label_set_text_static(bleIcon, Symbols::bluetooth); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + + stepValue = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 10, 0); + lv_label_set_text_static(stepValue, "0"); + + stepIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_label_set_text_static(stepIcon, Symbols::shoe); + lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); + + // Setting buttons + btnClose = lv_btn_create(lv_scr_act(), nullptr); + btnClose->user_data = this; + lv_obj_set_size(btnClose, 60, 60); + lv_obj_align(btnClose, lv_scr_act(), LV_ALIGN_CENTER, 0, -80); + lv_obj_set_style_local_bg_opa(btnClose, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + lv_obj_t* lblClose = lv_label_create(btnClose, nullptr); + lv_label_set_text_static(lblClose, "X"); + lv_obj_set_event_cb(btnClose, event_handler); + lv_obj_set_hidden(btnClose, true); + + btnNextColor = lv_btn_create(lv_scr_act(), nullptr); + btnNextColor->user_data = this; + lv_obj_set_size(btnNextColor, 60, 60); + lv_obj_align(btnNextColor, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -15, 0); + lv_obj_set_style_local_bg_opa(btnNextColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + lv_obj_t* lblNextColor = lv_label_create(btnNextColor, nullptr); + lv_label_set_text_static(lblNextColor, ">"); + lv_obj_set_event_cb(btnNextColor, event_handler); + lv_obj_set_hidden(btnNextColor, true); + + btnPrevColor = lv_btn_create(lv_scr_act(), nullptr); + btnPrevColor->user_data = this; + lv_obj_set_size(btnPrevColor, 60, 60); + lv_obj_align(btnPrevColor, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 15, 0); + lv_obj_set_style_local_bg_opa(btnPrevColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + lv_obj_t* lblPrevColor = lv_label_create(btnPrevColor, nullptr); + lv_label_set_text_static(lblPrevColor, "<"); + lv_obj_set_event_cb(btnPrevColor, event_handler); + lv_obj_set_hidden(btnPrevColor, true); + + btnToggleCover = lv_btn_create(lv_scr_act(), nullptr); + btnToggleCover->user_data = this; + lv_obj_set_size(btnToggleCover, 60, 60); + lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_CENTER, 0, 80); + lv_obj_set_style_local_bg_opa(btnToggleCover, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + const char* labelToggle = settingsController.GetInfineatShowSideCover() ? "ON" : "OFF"; + lblToggle = lv_label_create(btnToggleCover, nullptr); + lv_label_set_text_static(lblToggle, labelToggle); + lv_obj_set_event_cb(btnToggleCover, event_handler); + lv_obj_set_hidden(btnToggleCover, true); + + // Button to access the settings + btnSettings = lv_btn_create(lv_scr_act(), nullptr); + btnSettings->user_data = this; + lv_obj_set_size(btnSettings, 150, 150); + lv_obj_align(btnSettings, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); + lv_obj_set_style_local_radius(btnSettings, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 30); + lv_obj_set_style_local_bg_opa(btnSettings, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + lv_obj_set_event_cb(btnSettings, event_handler); + labelBtnSettings = lv_label_create(btnSettings, nullptr); + lv_obj_set_style_local_text_font(labelBtnSettings, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48); + lv_label_set_text_static(labelBtnSettings, Symbols::settings); + lv_obj_set_hidden(btnSettings, true); + + taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); + Refresh(); +} + +WatchFaceInfineatColors::~WatchFaceInfineatColors() { + lv_task_del(taskRefresh); + + if (font_bebas != nullptr) { + lv_font_free(font_bebas); + } + if (font_teko != nullptr) { + lv_font_free(font_teko); + } + + lv_obj_clean(lv_scr_act()); +} + +bool WatchFaceInfineatColors::OnTouchEvent(Pinetime::Applications::TouchEvents event) { + if ((event == Pinetime::Applications::TouchEvents::LongTap) && lv_obj_get_hidden(btnSettings)) { + lv_obj_set_hidden(btnSettings, false); + savedTick = lv_tick_get(); + return true; + } + // Prevent screen from sleeping when double tapping with settings on + if ((event == Pinetime::Applications::TouchEvents::DoubleTap) && !lv_obj_get_hidden(btnClose)) { + return true; + } + return false; +} + +void WatchFaceInfineatColors::CloseMenu() { + settingsController.SaveSettings(); + lv_obj_set_hidden(btnClose, true); + lv_obj_set_hidden(btnNextColor, true); + lv_obj_set_hidden(btnPrevColor, true); + lv_obj_set_hidden(btnToggleCover, true); +} + +bool WatchFaceInfineatColors::OnButtonPushed() { + if (!lv_obj_get_hidden(btnClose)) { + CloseMenu(); + return true; + } + return false; +} + +void WatchFaceInfineatColors::UpdateSelected(lv_obj_t* object, lv_event_t event) { + if (event == LV_EVENT_CLICKED) { + bool showSideCover = settingsController.GetInfineatShowSideCover(); + int colorIndex = settingsController.GetInfineatColorIndex(); + + if (object == btnSettings) { + lv_obj_set_hidden(btnSettings, true); + lv_obj_set_hidden(btnClose, false); + lv_obj_set_hidden(btnNextColor, !showSideCover); + lv_obj_set_hidden(btnPrevColor, !showSideCover); + lv_obj_set_hidden(btnToggleCover, false); + } + if (object == btnClose) { + CloseMenu(); + } + if (object == btnToggleCover) { + settingsController.SetInfineatShowSideCover(!showSideCover); + ToggleBatteryIndicatorColor(!showSideCover); + for (auto& line : lines) { + lv_obj_set_hidden(line, showSideCover); + } + lv_obj_set_hidden(btnNextColor, showSideCover); + lv_obj_set_hidden(btnPrevColor, showSideCover); + const char* labelToggle = showSideCover ? "OFF" : "ON"; + lv_label_set_text_static(lblToggle, labelToggle); + } + if (object == btnNextColor) { + colorIndex = (colorIndex + 1) % nColors; + settingsController.SetInfineatColorIndex(colorIndex); + } + if (object == btnPrevColor) { + colorIndex -= 1; + if (colorIndex < 0) + colorIndex = nColors - 1; + settingsController.SetInfineatColorIndex(colorIndex); + } + if (object == btnNextColor || object == btnPrevColor) { + const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); + for (int i = 0; i < nLines; i++) { + lv_color_t color = (*colors)[i]; + lv_obj_set_style_local_line_color(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, color); + } + lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); + lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); + } + } +} + +void WatchFaceInfineatColors::Refresh() { + notificationState = notificationManager.AreNewNotificationsAvailable(); + if (notificationState.IsUpdated()) { + lv_obj_set_hidden(notificationIcon, !notificationState.Get()); + lv_obj_align(notificationIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0); + } + + currentDateTime = std::chrono::time_point_cast(dateTimeController.CurrentDateTime()); + if (currentDateTime.IsUpdated()) { + uint8_t hour = dateTimeController.Hours(); + uint8_t minute = dateTimeController.Minutes(); + + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + char ampmChar[3] = "AM"; + if (hour == 0) { + hour = 12; + } else if (hour == 12) { + ampmChar[0] = 'P'; + } else if (hour > 12) { + hour = hour - 12; + ampmChar[0] = 'P'; + } + lv_label_set_text(labelTimeAmPm, ampmChar); + } + lv_label_set_text_fmt(labelHour, "%02d", hour); + lv_label_set_text_fmt(labelMinutes, "%02d", minute); + + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 10); + lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 5); + lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + } + + currentDate = std::chrono::time_point_cast(currentDateTime.Get()); + if (currentDate.IsUpdated()) { + uint8_t day = dateTimeController.Day(); + Controllers::DateTime::Days dayOfWeek = dateTimeController.DayOfWeek(); + lv_label_set_text_fmt(labelDate, "%s %02d", dateTimeController.DayOfWeekShortToStringLow(dayOfWeek), day); + lv_obj_realign(labelDate); + } + } + + batteryPercentRemaining = batteryController.PercentRemaining(); + isCharging = batteryController.IsCharging(); + if (batteryController.IsCharging()) { // Charging battery animation + chargingBatteryPercent += 1; + if (chargingBatteryPercent > 100) { + chargingBatteryPercent = batteryPercentRemaining.Get(); + } + SetBatteryLevel(chargingBatteryPercent); + } else if (isCharging.IsUpdated() || batteryPercentRemaining.IsUpdated()) { + chargingBatteryPercent = batteryPercentRemaining.Get(); + SetBatteryLevel(chargingBatteryPercent); + } + + bleState = bleController.IsConnected(); + bleRadioEnabled = bleController.IsRadioEnabled(); + if (bleState.IsUpdated()) { + lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 3); + } + + stepCount = motionController.NbSteps(); + if (stepCount.IsUpdated()) { + lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); + lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 10, 0); + lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); + } + + if (!lv_obj_get_hidden(btnSettings)) { + if ((savedTick > 0) && (lv_tick_get() - savedTick > 3000)) { + lv_obj_set_hidden(btnSettings, true); + savedTick = 0; + } + } +} + +void WatchFaceInfineatColors::SetBatteryLevel(uint8_t batteryPercent) { + // starting point (y) + Pine64 logo height * (100 - batteryPercent) / 100 + lineBatteryPoints[1] = {27, static_cast(105 + 32 * (100 - batteryPercent) / 100)}; + lv_line_set_points(lineBattery, lineBatteryPoints, 2); +} + +void WatchFaceInfineatColors::ToggleBatteryIndicatorColor(bool showSideCover) { + if (!showSideCover) { // make indicator and notification icon color white + lv_obj_set_style_local_image_recolor_opa(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_100); + lv_obj_set_style_local_image_recolor(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + } else { + lv_obj_set_style_local_image_recolor_opa(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_0); + const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); + lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); + lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); + } +} + +bool WatchFaceInfineatColors::IsAvailable(Pinetime::Controllers::FS& filesystem) { + lfs_file file = {}; + + if (filesystem.FileOpen(&file, "/fonts/teko.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + if (filesystem.FileOpen(&file, "/fonts/bebas.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + if (filesystem.FileOpen(&file, "/images/pine_small.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + return true; +} diff --git a/src/displayapp/screens/WatchFaceInfineatColors.h b/src/displayapp/screens/WatchFaceInfineatColors.h new file mode 100644 index 00000000..3a164278 --- /dev/null +++ b/src/displayapp/screens/WatchFaceInfineatColors.h @@ -0,0 +1,123 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "displayapp/screens/Screen.h" +#include "components/datetime/DateTimeController.h" +#include "utility/DirtyValue.h" +#include "displayapp/apps/Apps.h" + +namespace Pinetime { + namespace Controllers { + class Settings; + class Battery; + class Ble; + class NotificationManager; + class MotionController; + } + + namespace Applications { + namespace Screens { + + class WatchFaceInfineatColors : public Screen { + public: + static constexpr int nLines = 9; + WatchFaceInfineatColors(Controllers::DateTime& dateTimeController, + const Controllers::Battery& batteryController, + const Controllers::Ble& bleController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::MotionController& motionController, + Controllers::FS& fs); + + ~WatchFaceInfineatColors() override; + + bool OnTouchEvent(TouchEvents event) override; + bool OnButtonPushed() override; + void UpdateSelected(lv_obj_t* object, lv_event_t event); + void CloseMenu(); + + void Refresh() override; + + static bool IsAvailable(Pinetime::Controllers::FS& filesystem); + + private: + uint32_t savedTick = 0; + uint8_t chargingBatteryPercent = 101; // not a mistake ;) + + Utility::DirtyValue batteryPercentRemaining {}; + Utility::DirtyValue isCharging {}; + Utility::DirtyValue bleState {}; + Utility::DirtyValue bleRadioEnabled {}; + Utility::DirtyValue> currentDateTime {}; + Utility::DirtyValue stepCount {}; + Utility::DirtyValue notificationState {}; + Utility::DirtyValue> currentDate; + + // Lines making up the side cover + lv_obj_t* lineBattery; + + lv_point_t lineBatteryPoints[2]; + + lv_obj_t* logoPine; + + lv_obj_t* timeContainer; + lv_obj_t* labelHour; + lv_obj_t* labelMinutes; + lv_obj_t* labelTimeAmPm; + lv_obj_t* dateContainer; + lv_obj_t* labelDate; + lv_obj_t* bleIcon; + lv_obj_t* stepIcon; + lv_obj_t* stepValue; + lv_obj_t* notificationIcon; + lv_obj_t* btnClose; + lv_obj_t* btnNextColor; + lv_obj_t* btnToggleCover; + lv_obj_t* btnPrevColor; + lv_obj_t* btnSettings; + lv_obj_t* labelBtnSettings; + lv_obj_t* lblToggle; + + lv_obj_t* lines[nLines]; + + Controllers::DateTime& dateTimeController; + const Controllers::Battery& batteryController; + const Controllers::Ble& bleController; + Controllers::NotificationManager& notificationManager; + Controllers::Settings& settingsController; + Controllers::MotionController& motionController; + + void SetBatteryLevel(uint8_t batteryPercent); + void ToggleBatteryIndicatorColor(bool showSideCover); + + lv_task_t* taskRefresh; + lv_font_t* font_teko = nullptr; + lv_font_t* font_bebas = nullptr; + }; + } + + template <> + struct WatchFaceTraits { + static constexpr WatchFace watchFace = WatchFace::InfineatColors; + static constexpr const char* name = "InfineatColors face"; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::WatchFaceInfineatColors(controllers.dateTimeController, + controllers.batteryController, + controllers.bleController, + controllers.notificationManager, + controllers.settingsController, + controllers.motionController, + controllers.filesystem); + }; + + static bool IsAvailable(Pinetime::Controllers::FS& filesystem) { + return Screens::WatchFaceInfineatColors::IsAvailable(filesystem); + } + }; + } +} diff --git a/src/displayapp/screens/settings/SettingWatchFace.h b/src/displayapp/screens/settings/SettingWatchFace.h index 4c75b0ab..1ff03e22 100644 --- a/src/displayapp/screens/settings/SettingWatchFace.h +++ b/src/displayapp/screens/settings/SettingWatchFace.h @@ -10,6 +10,7 @@ #include "displayapp/screens/Symbols.h" #include "displayapp/screens/CheckboxList.h" #include "displayapp/screens/WatchFaceInfineat.h" +#include "displayapp/screens/WatchFaceInfineatColors.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" namespace Pinetime { From d7de641b8da2f95c788a3a3ecc1c131757834a23 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Thu, 23 May 2024 16:17:36 +0200 Subject: [PATCH 013/101] added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number modified: make_pine_mcu.sh modified: src/CMakeLists.txt modified: src/components/settings/Settings.h modified: src/displayapp/UserApps.h modified: src/displayapp/apps/Apps.h.in modified: src/displayapp/apps/CMakeLists.txt modified: src/displayapp/fonts/fonts.json modified: src/displayapp/screens/AlarmIcon.cpp new file: src/displayapp/screens/README.md modified: src/displayapp/screens/Symbols.h modified: src/displayapp/screens/WatchFaceInfineatColors.cpp modified: src/displayapp/screens/WatchFaceInfineatColors.h new file: src/displayapp/screens/WatchFaceMeow.cpp new file: src/displayapp/screens/WatchFaceMeow.h modified: src/displayapp/screens/settings/SettingWatchFace.h --- make_pine_mcu.sh | 2 +- src/CMakeLists.txt | 6 + src/components/settings/Settings.h | 7 +- src/displayapp/UserApps.h | 1 + src/displayapp/apps/Apps.h.in | 1 + src/displayapp/apps/CMakeLists.txt | 9 +- src/displayapp/fonts/fonts.json | 4 + src/displayapp/screens/AlarmIcon.cpp | 7 + src/displayapp/screens/README.md | 8 + src/displayapp/screens/Symbols.h | 5 +- .../screens/WatchFaceInfineatColors.cpp | 20 +- .../screens/WatchFaceInfineatColors.h | 1 + src/displayapp/screens/WatchFaceMeow.cpp | 561 ++++++++++++++++++ src/displayapp/screens/WatchFaceMeow.h | 130 ++++ .../screens/settings/SettingWatchFace.h | 3 + 15 files changed, 751 insertions(+), 14 deletions(-) create mode 100644 src/displayapp/screens/README.md create mode 100644 src/displayapp/screens/WatchFaceMeow.cpp create mode 100644 src/displayapp/screens/WatchFaceMeow.h diff --git a/make_pine_mcu.sh b/make_pine_mcu.sh index d7af1cb8..87c00ff2 100755 --- a/make_pine_mcu.sh +++ b/make_pine_mcu.sh @@ -2,5 +2,5 @@ #cp ./displayapp/apps/Apps.h ../src/displayapp/apps/Apps.h -make -j4 pinetime-mcuboot-app +make clean -j4 pinetime-mcuboot-app diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b7f50de1..9a579eff 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -371,7 +371,11 @@ list(APPEND SOURCE_FILES displayapp/screens/StopWatch.cpp displayapp/screens/BatteryIcon.cpp displayapp/screens/BleIcon.cpp +<<<<<<< HEAD displayapp/screens/AlarmIcon.cpp +======= + displayapp/screens/AlarmIcon.cpp +>>>>>>> f5dfbc44 (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) displayapp/screens/NotificationIcon.cpp displayapp/screens/SystemInfo.cpp displayapp/screens/Label.cpp @@ -427,6 +431,7 @@ list(APPEND SOURCE_FILES displayapp/screens/WatchFaceTerminal.cpp displayapp/screens/WatchFacePineTimeStyle.cpp displayapp/screens/WatchFaceInfineatColors.cpp + displayapp/screens/WatchFaceMeow.cpp #displayapp/screens/WatchFaceCasioStyleG7710.cpp ## @@ -597,6 +602,7 @@ set(INCLUDE_FILES displayapp/screens/Paddle.h displayapp/screens/BatteryIcon.h displayapp/screens/BleIcon.h + displayapp/screens/AlarmIcon.h displayapp/screens/NotificationIcon.h displayapp/screens/SystemInfo.h displayapp/screens/ScreenList.h diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 099f0dcf..16dcdfd2 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -56,7 +56,11 @@ namespace Pinetime { int colorIndex = 0; }; - + struct WatchFaceMeow { + bool showSideCover = true; + int colorIndex = 0; + }; + Settings(Pinetime::Controllers::FS& fs); Settings(const Settings&) = delete; @@ -322,6 +326,7 @@ namespace Pinetime { WatchFaceInfineat watchFaceInfineat; WatchFaceInfineatColors watchFaceInfineatColors; + WatchFaceMeow watchFaceMeow; std::bitset<5> wakeUpMode {0}; uint16_t shakeWakeThreshold = 150; diff --git a/src/displayapp/UserApps.h b/src/displayapp/UserApps.h index 76ffa684..f76af024 100644 --- a/src/displayapp/UserApps.h +++ b/src/displayapp/UserApps.h @@ -13,6 +13,7 @@ #include "displayapp/screens/WatchFaceCasioStyleG7710.h" #include "displayapp/screens/WatchFaceInfineat.h" #include "displayapp/screens/WatchFaceInfineatColors.h" +#include "displayapp/screens/WatchFaceMeow.h" #include "displayapp/screens/WatchFacePineTimeStyle.h" #include "displayapp/screens/WatchFaceTerminal.h" diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index a591497b..e12a1ab9 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -52,6 +52,7 @@ namespace Pinetime { Terminal, Infineat, InfineatColors, + Meow, CasioStyleG7710, }; diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index 8925b246..ae6b1a46 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -14,7 +14,7 @@ else () set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Metronome") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Navigation") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Weather") - #set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Motion") + set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Motion") set(USERAPP_TYPES "${DEFAULT_USER_APP_TYPES}" CACHE STRING "List of user apps to build into the firmware") endif () @@ -22,11 +22,12 @@ if(DEFINED ENABLE_WATCHFACES) set(WATCHFACE_TYPES ${ENABLE_WATCHFACES} CACHE STRING "List of watch faces to build into the firmware") else() set(DEFAULT_WATCHFACE_TYPES "WatchFace::Digital") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Analog") + #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Analog") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::PineTimeStyle") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") + #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") + #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::InfineatColors") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Meow") #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware") endif() diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json index 793b8851..2a9eea43 100644 --- a/src/displayapp/fonts/fonts.json +++ b/src/displayapp/fonts/fonts.json @@ -7,7 +7,11 @@ }, { "file": "FontAwesome5-Solid+Brands+Regular.woff", +<<<<<<< HEAD "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf0f3, 0xf1f6" +======= + "range": "0xf294, 0xf242, 0xf54b, 0xf1b0, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf4ba, 0xf236" +>>>>>>> f5dfbc44 (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) } ], "bpp": 1, diff --git a/src/displayapp/screens/AlarmIcon.cpp b/src/displayapp/screens/AlarmIcon.cpp index fda87130..0ea1a8cb 100644 --- a/src/displayapp/screens/AlarmIcon.cpp +++ b/src/displayapp/screens/AlarmIcon.cpp @@ -4,8 +4,15 @@ using namespace Pinetime::Applications::Screens; const char* AlarmIcon::GetIcon(bool isSet) { if (isSet) { +<<<<<<< HEAD return Symbols::bell; } return Symbols::notbell; +======= + return Symbols::bird; + } + + return Symbols::zzz; +>>>>>>> f5dfbc44 (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) } diff --git a/src/displayapp/screens/README.md b/src/displayapp/screens/README.md new file mode 100644 index 00000000..4f785766 --- /dev/null +++ b/src/displayapp/screens/README.md @@ -0,0 +1,8 @@ + to edit with new watch face : + +/src/displayapp/apps/Apps.h.in +/src/components/settings/Settings.h +/src/displayapp/UserApps.h +/src/displayapp/apps/CMakeLists.txt +CMakelists.txt + diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index cf1b8aad..d392d72a 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -1,5 +1,5 @@ #pragma once - +//check https://github.com/InfiniTimeOrg/InfiniTime/blob/main/src/displayapp/fonts/README.md to add new symbols namespace Pinetime { namespace Applications { namespace Screens { @@ -10,6 +10,7 @@ namespace Pinetime { static constexpr const char* bluetooth = "\xEF\x8A\x94"; static constexpr const char* plug = "\xEF\x87\xA6"; static constexpr const char* shoe = "\xEF\x95\x8B"; + static constexpr const char* paw = "\xEF\x86\xB0"; static constexpr const char* clock = "\xEF\x80\x97"; static constexpr const char* bell = "\xEF\x83\xB3"; static constexpr const char* notbell = "\xEF\x87\xB6"; @@ -40,6 +41,8 @@ namespace Pinetime { static constexpr const char* eye = "\xEF\x81\xAE"; static constexpr const char* home = "\xEF\x80\x95"; static constexpr const char* sleep = "\xEE\xBD\x84"; + static constexpr const char* bird = "\xEF\x92\xBA"; + static constexpr const char* zzz = "\xEF\x88\xB6"; // fontawesome_weathericons.c // static constexpr const char* sun = "\xEF\x86\x85"; diff --git a/src/displayapp/screens/WatchFaceInfineatColors.cpp b/src/displayapp/screens/WatchFaceInfineatColors.cpp index 88241942..ab963493 100644 --- a/src/displayapp/screens/WatchFaceInfineatColors.cpp +++ b/src/displayapp/screens/WatchFaceInfineatColors.cpp @@ -96,7 +96,10 @@ namespace { LV_COLOR_MAKE(0xff, 0xff, 0xff), LV_COLOR_MAKE(0x2f, 0xb8, 0xa2), LV_COLOR_MAKE(0x11, 0x70, 0x5a)}; - + //define colors for texts and symbols + //static constexpr lv_color_t grayColor = LV_COLOR_MAKE(0x99, 0x99, 0x99); + static constexpr lv_color_t pinkColor = LV_COLOR_MAKE(0xff, 0x93, 0xaf); + constexpr const std::array* returnColor(colors color) { if (color == colors::orange) { return &orangeColors; @@ -201,15 +204,18 @@ WatchFaceInfineatColors::WatchFaceInfineatColors(Controllers::DateTime& dateTime labelHour = lv_label_create(lv_scr_act(), nullptr); lv_label_set_text_static(labelHour, "01"); lv_obj_set_style_local_text_font(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); + lv_obj_set_style_local_text_color(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 0); labelMinutes = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_font(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); + lv_obj_set_style_local_text_color(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_label_set_text_static(labelMinutes, "00"); lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); labelTimeAmPm = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_font(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_set_style_local_text_color(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_label_set_text_static(labelTimeAmPm, ""); lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 15); @@ -219,29 +225,29 @@ WatchFaceInfineatColors::WatchFaceInfineatColors(Controllers::DateTime& dateTime lv_obj_set_size(dateContainer, 60, 30); lv_obj_align(dateContainer, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 5); - static constexpr lv_color_t grayColor = LV_COLOR_MAKE(0x99, 0x99, 0x99); labelDate = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_obj_set_style_local_text_color(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_obj_set_style_local_text_font(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); lv_obj_align(labelDate, dateContainer, LV_ALIGN_IN_TOP_MID, 0, 0); lv_label_set_text_static(labelDate, "Mon 01"); bleIcon = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_label_set_text_static(bleIcon, Symbols::bluetooth); lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); stepValue = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 10, 0); lv_label_set_text_static(stepValue, "0"); stepIcon = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); - lv_label_set_text_static(stepIcon, Symbols::shoe); + lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_label_set_text_static(stepIcon, Symbols::paw); lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); + // Setting buttons btnClose = lv_btn_create(lv_scr_act(), nullptr); btnClose->user_data = this; diff --git a/src/displayapp/screens/WatchFaceInfineatColors.h b/src/displayapp/screens/WatchFaceInfineatColors.h index 3a164278..27557c2d 100644 --- a/src/displayapp/screens/WatchFaceInfineatColors.h +++ b/src/displayapp/screens/WatchFaceInfineatColors.h @@ -72,6 +72,7 @@ namespace Pinetime { lv_obj_t* labelDate; lv_obj_t* bleIcon; lv_obj_t* stepIcon; + lv_obj_t* pawIcon; lv_obj_t* stepValue; lv_obj_t* notificationIcon; lv_obj_t* btnClose; diff --git a/src/displayapp/screens/WatchFaceMeow.cpp b/src/displayapp/screens/WatchFaceMeow.cpp new file mode 100644 index 00000000..e8324534 --- /dev/null +++ b/src/displayapp/screens/WatchFaceMeow.cpp @@ -0,0 +1,561 @@ +#include "displayapp/screens/WatchFaceMeow.h" + +#include +#include +#include "displayapp/screens/Symbols.h" +#include "displayapp/screens/BleIcon.h" +#include "displayapp/screens/AlarmIcon.h" +#include "components/settings/Settings.h" +#include "components/battery/BatteryController.h" +#include "components/ble/BleController.h" +#include "components/alarm/AlarmController.h" +#include "components/ble/NotificationManager.h" +#include "components/motion/MotionController.h" + +using namespace Pinetime::Applications::Screens; + +namespace { + void event_handler(lv_obj_t* obj, lv_event_t event) { + auto* screen = static_cast(obj->user_data); + screen->UpdateSelected(obj, event); + } + + enum class colors { + orange, + blue, + green, + rainbow, + vivid, + pink, + nordGreen, + }; + + constexpr int nColors = 7; // must match number of colors in InfineatColorsColors + + constexpr int nLines = WatchFaceMeow::nLines; + + constexpr std::array orangeColors = {LV_COLOR_MAKE(0xfd, 0x87, 0x2b), + LV_COLOR_MAKE(0xdb, 0x33, 0x16), + LV_COLOR_MAKE(0x6f, 0x10, 0x00), + LV_COLOR_MAKE(0xfd, 0x7a, 0x0a), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xe8, 0x51, 0x02), + LV_COLOR_MAKE(0xea, 0x1c, 0x00)}; + constexpr std::array blueColors = {LV_COLOR_MAKE(0xe7, 0xf8, 0xff), + LV_COLOR_MAKE(0x22, 0x32, 0xd0), + LV_COLOR_MAKE(0x18, 0x2a, 0x8b), + LV_COLOR_MAKE(0xe7, 0xf8, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0x59, 0x91, 0xff), + LV_COLOR_MAKE(0x16, 0x36, 0xff)}; + constexpr std::array greenColors = {LV_COLOR_MAKE(0xb8, 0xff, 0x9b), + LV_COLOR_MAKE(0x08, 0x86, 0x08), + LV_COLOR_MAKE(0x00, 0x4a, 0x00), + LV_COLOR_MAKE(0xb8, 0xff, 0x9b), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0x62, 0xd5, 0x15), + LV_COLOR_MAKE(0x00, 0x74, 0x00)}; + constexpr std::array rainbowColors = {LV_COLOR_MAKE(0x2d, 0xa4, 0x00), //vert petit triangle le plus haut + LV_COLOR_MAKE(0xac, 0x09, 0xc4), //purple petit triangle bas côté horloge + LV_COLOR_MAKE(0xfe, 0x03, 0x03), //rouge 2 petits triangles à côté de batterie + LV_COLOR_MAKE(0x0d, 0x57, 0xff), //bleu petit triangle le plus bas + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xe0, 0xb9, 0x00), //jaune gd triangle haut + LV_COLOR_MAKE(0xe8, 0x51, 0x02)}; //orange gd triangle bas + constexpr std::array rainbowVividColors ={LV_COLOR_MAKE(0xa5, 0xeb, 0x64), + LV_COLOR_MAKE(0xfc, 0x42, 0xb5), + LV_COLOR_MAKE(0xe7, 0xc1, 0xff), + LV_COLOR_MAKE(0x11, 0xdf, 0xfa), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xec, 0x5d), + LV_COLOR_MAKE(0xff, 0x93, 0xaf)}; + + constexpr std::array pinkColors = {LV_COLOR_MAKE(0xff, 0xe5, 0xec), + LV_COLOR_MAKE(0xff, 0xb3, 0xc6), + LV_COLOR_MAKE(0xfb, 0x6f, 0x92), + LV_COLOR_MAKE(0xff, 0xe5, 0xec), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xc2, 0xd1), + LV_COLOR_MAKE(0xff, 0x8f, 0xab)}; + constexpr std::array nordGreenColors = {LV_COLOR_MAKE(0xd5, 0xf0, 0xe9), + LV_COLOR_MAKE(0x23, 0x83, 0x73), + LV_COLOR_MAKE(0x1d, 0x41, 0x3f), + LV_COLOR_MAKE(0xd5, 0xf0, 0xe9), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0x2f, 0xb8, 0xa2), + LV_COLOR_MAKE(0x11, 0x70, 0x5a)}; + + //define colors for texts and symbols + //static constexpr lv_color_t grayColor = LV_COLOR_MAKE(0x99, 0x99, 0x99); + static constexpr lv_color_t grayColor = LV_COLOR_MAKE(0x99, 0x99, 0x99); + static constexpr lv_color_t pinkColor = LV_COLOR_MAKE(0xfc, 0x42, 0xb5); + + constexpr const std::array* returnColor(colors color) { + if (color == colors::orange) { + return &orangeColors; + } + if (color == colors::blue) { + return &blueColors; + } + if (color == colors::green) { + return &greenColors; + } + if (color == colors::rainbow) { + return &rainbowColors; + } + if (color == colors::vivid) { + return &rainbowVividColors; + } + if (color == colors::pink) { + return &pinkColors; + } + return &nordGreenColors; + } +} + +WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, + const Controllers::Battery& batteryController, + const Controllers::Ble& bleController, + Controllers::AlarmController& alarmController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::MotionController& motionController, + Controllers::FS& filesystem) + : currentDateTime {{}}, + dateTimeController {dateTimeController}, + batteryController {batteryController}, + bleController {bleController}, + alarmController {alarmController}, + notificationManager {notificationManager}, + settingsController {settingsController}, + motionController {motionController} { + lfs_file f = {}; + if (filesystem.FileOpen(&f, "/fonts/teko.bin", LFS_O_RDONLY) >= 0) { + filesystem.FileClose(&f); + font_teko = lv_font_load("F:/fonts/teko.bin"); + } + + if (filesystem.FileOpen(&f, "/fonts/bebas.bin", LFS_O_RDONLY) >= 0) { + filesystem.FileClose(&f); + font_bebas = lv_font_load("F:/fonts/bebas.bin"); + } + + // Side Cover + static constexpr lv_point_t linePoints[nLines][2] = {{{30, 25}, {68, -8}}, + {{26, 167}, {43, 216}}, + {{27, 40}, {27, 196}}, + {{12, 182}, {65, 249}}, + {{17, 99}, {17, 144}}, + {{14, 81}, {40, 127}}, + {{14, 163}, {40, 118}}, + {{-20, 124}, {25, -11}}, + {{-29, 89}, {27, 254}}}; + + static constexpr lv_style_int_t lineWidths[nLines] = {18, 15, 14, 22, 20, 18, 18, 52, 48}; + + const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); + for (int i = 0; i < nLines; i++) { + lines[i] = lv_line_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_line_width(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, lineWidths[i]); + lv_color_t color = (*colors)[i]; + lv_obj_set_style_local_line_color(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, color); + lv_line_set_points(lines[i], linePoints[i], 2); + } + + logoPine = lv_img_create(lv_scr_act(), nullptr); + lv_img_set_src(logoPine, "F:/images/pine_small.bin"); + lv_obj_set_pos(logoPine, 15, 106); + + lineBattery = lv_line_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_line_width(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 24); + lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); + lv_obj_set_style_local_line_opa(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 190); + lineBatteryPoints[0] = {27, 105}; + lineBatteryPoints[1] = {27, 106}; + lv_line_set_points(lineBattery, lineBatteryPoints, 2); + lv_obj_move_foreground(lineBattery); + + notificationIcon = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); + lv_obj_set_style_local_radius(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE); + lv_obj_set_size(notificationIcon, 13, 13); + lv_obj_set_hidden(notificationIcon, true); + + if (!settingsController.GetInfineatShowSideCover()) { + ToggleBatteryIndicatorColor(false); + for (auto& line : lines) { + lv_obj_set_hidden(line, true); + } + } + + timeContainer = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_opa(timeContainer, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); + lv_obj_set_size(timeContainer, 185, 185); + lv_obj_align(timeContainer, lv_scr_act(), LV_ALIGN_CENTER, 0, -10); + + labelHour = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_static(labelHour, "01"); + lv_obj_set_style_local_text_font(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); + lv_obj_set_style_local_text_color(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 0); + + labelMinutes = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); + lv_obj_set_style_local_text_color(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_label_set_text_static(labelMinutes, "00"); + lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + + labelTimeAmPm = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_set_style_local_text_color(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + + lv_label_set_text_static(labelTimeAmPm, ""); + lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 15); + + dateContainer = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_opa(dateContainer, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); + lv_obj_set_size(dateContainer, 60, 30); + lv_obj_align(dateContainer, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 5); + + labelDate = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_obj_set_style_local_text_font(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(labelDate, dateContainer, LV_ALIGN_IN_TOP_MID, 0, 0); + lv_label_set_text_static(labelDate, "Mon 01"); + + bleIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_label_set_text_static(bleIcon, Symbols::bluetooth); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + + + //version par defaut de l'icône avant qu'il aie regardé le statut de l'alarme ? + alarmIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(alarmIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_label_set_text_static(alarmIcon, Symbols::paw); + lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, -10); + //version par defaut de l'icône avant qu'il aie regardé le statut de l'alarme ? + labelAlarm = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_obj_set_style_local_text_font(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(labelAlarm, alarmIcon, LV_ALIGN_OUT_BOTTOM_MID, -15, 10); + lv_label_set_text_static(labelAlarm, "00:00"); + + + stepValue = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 10, 0); + lv_label_set_text_static(stepValue, "0"); + + stepIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_label_set_text_static(stepIcon, Symbols::paw); + lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); + + + // Setting buttons + btnClose = lv_btn_create(lv_scr_act(), nullptr); + btnClose->user_data = this; + lv_obj_set_size(btnClose, 60, 60); + lv_obj_align(btnClose, lv_scr_act(), LV_ALIGN_CENTER, 0, -80); + lv_obj_set_style_local_bg_opa(btnClose, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + lv_obj_t* lblClose = lv_label_create(btnClose, nullptr); + lv_label_set_text_static(lblClose, "X"); + lv_obj_set_event_cb(btnClose, event_handler); + lv_obj_set_hidden(btnClose, true); + + btnNextColor = lv_btn_create(lv_scr_act(), nullptr); + btnNextColor->user_data = this; + lv_obj_set_size(btnNextColor, 60, 60); + lv_obj_align(btnNextColor, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -15, 0); + lv_obj_set_style_local_bg_opa(btnNextColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + lv_obj_t* lblNextColor = lv_label_create(btnNextColor, nullptr); + lv_label_set_text_static(lblNextColor, ">"); + lv_obj_set_event_cb(btnNextColor, event_handler); + lv_obj_set_hidden(btnNextColor, true); + + btnPrevColor = lv_btn_create(lv_scr_act(), nullptr); + btnPrevColor->user_data = this; + lv_obj_set_size(btnPrevColor, 60, 60); + lv_obj_align(btnPrevColor, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 15, 0); + lv_obj_set_style_local_bg_opa(btnPrevColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + lv_obj_t* lblPrevColor = lv_label_create(btnPrevColor, nullptr); + lv_label_set_text_static(lblPrevColor, "<"); + lv_obj_set_event_cb(btnPrevColor, event_handler); + lv_obj_set_hidden(btnPrevColor, true); + + btnToggleCover = lv_btn_create(lv_scr_act(), nullptr); + btnToggleCover->user_data = this; + lv_obj_set_size(btnToggleCover, 60, 60); + lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_CENTER, 0, 80); + lv_obj_set_style_local_bg_opa(btnToggleCover, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + const char* labelToggle = settingsController.GetInfineatShowSideCover() ? "ON" : "OFF"; + lblToggle = lv_label_create(btnToggleCover, nullptr); + lv_label_set_text_static(lblToggle, labelToggle); + lv_obj_set_event_cb(btnToggleCover, event_handler); + lv_obj_set_hidden(btnToggleCover, true); + + // Button to access the settings + btnSettings = lv_btn_create(lv_scr_act(), nullptr); + btnSettings->user_data = this; + lv_obj_set_size(btnSettings, 150, 150); + lv_obj_align(btnSettings, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); + lv_obj_set_style_local_radius(btnSettings, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 30); + lv_obj_set_style_local_bg_opa(btnSettings, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + lv_obj_set_event_cb(btnSettings, event_handler); + labelBtnSettings = lv_label_create(btnSettings, nullptr); + lv_obj_set_style_local_text_font(labelBtnSettings, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48); + lv_label_set_text_static(labelBtnSettings, Symbols::settings); + lv_obj_set_hidden(btnSettings, true); + + taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); + Refresh(); +} + +WatchFaceMeow::~WatchFaceMeow() { + lv_task_del(taskRefresh); + + if (font_bebas != nullptr) { + lv_font_free(font_bebas); + } + if (font_teko != nullptr) { + lv_font_free(font_teko); + } + + lv_obj_clean(lv_scr_act()); +} + +bool WatchFaceMeow::OnTouchEvent(Pinetime::Applications::TouchEvents event) { + if ((event == Pinetime::Applications::TouchEvents::LongTap) && lv_obj_get_hidden(btnSettings)) { + lv_obj_set_hidden(btnSettings, false); + savedTick = lv_tick_get(); + return true; + } + // Prevent screen from sleeping when double tapping with settings on + if ((event == Pinetime::Applications::TouchEvents::DoubleTap) && !lv_obj_get_hidden(btnClose)) { + return true; + } + return false; +} + +void WatchFaceMeow::CloseMenu() { + settingsController.SaveSettings(); + lv_obj_set_hidden(btnClose, true); + lv_obj_set_hidden(btnNextColor, true); + lv_obj_set_hidden(btnPrevColor, true); + lv_obj_set_hidden(btnToggleCover, true); +} + +bool WatchFaceMeow::OnButtonPushed() { + if (!lv_obj_get_hidden(btnClose)) { + CloseMenu(); + return true; + } + return false; +} + +void WatchFaceMeow::UpdateSelected(lv_obj_t* object, lv_event_t event) { + if (event == LV_EVENT_CLICKED) { + bool showSideCover = settingsController.GetInfineatShowSideCover(); + int colorIndex = settingsController.GetInfineatColorIndex(); + + if (object == btnSettings) { + lv_obj_set_hidden(btnSettings, true); + lv_obj_set_hidden(btnClose, false); + lv_obj_set_hidden(btnNextColor, !showSideCover); + lv_obj_set_hidden(btnPrevColor, !showSideCover); + lv_obj_set_hidden(btnToggleCover, false); + } + if (object == btnClose) { + CloseMenu(); + } + if (object == btnToggleCover) { + settingsController.SetInfineatShowSideCover(!showSideCover); + ToggleBatteryIndicatorColor(!showSideCover); + for (auto& line : lines) { + lv_obj_set_hidden(line, showSideCover); + } + lv_obj_set_hidden(btnNextColor, showSideCover); + lv_obj_set_hidden(btnPrevColor, showSideCover); + const char* labelToggle = showSideCover ? "OFF" : "ON"; + lv_label_set_text_static(lblToggle, labelToggle); + } + if (object == btnNextColor) { + colorIndex = (colorIndex + 1) % nColors; + settingsController.SetInfineatColorIndex(colorIndex); + } + if (object == btnPrevColor) { + colorIndex -= 1; + if (colorIndex < 0) + colorIndex = nColors - 1; + settingsController.SetInfineatColorIndex(colorIndex); + } + if (object == btnNextColor || object == btnPrevColor) { + const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); + for (int i = 0; i < nLines; i++) { + lv_color_t color = (*colors)[i]; + lv_obj_set_style_local_line_color(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, color); + } + lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); + lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); + } + } +} + +void WatchFaceMeow::Refresh() { + notificationState = notificationManager.AreNewNotificationsAvailable(); + if (notificationState.IsUpdated()) { + lv_obj_set_hidden(notificationIcon, !notificationState.Get()); + lv_obj_align(notificationIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0); + } + + currentDateTime = std::chrono::time_point_cast(dateTimeController.CurrentDateTime()); + if (currentDateTime.IsUpdated()) { + uint8_t hour = dateTimeController.Hours(); + uint8_t minute = dateTimeController.Minutes(); + + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + char ampmChar[3] = "AM"; + if (hour == 0) { + hour = 12; + } else if (hour == 12) { + ampmChar[0] = 'P'; + } else if (hour > 12) { + hour = hour - 12; + ampmChar[0] = 'P'; + } + lv_label_set_text(labelTimeAmPm, ampmChar); + } + lv_label_set_text_fmt(labelHour, "%02d", hour); + lv_label_set_text_fmt(labelMinutes, "%02d", minute); + + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 10); + lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 5); + lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + } + + currentDate = std::chrono::time_point_cast(currentDateTime.Get()); + if (currentDate.IsUpdated()) { + uint8_t day = dateTimeController.Day(); + Controllers::DateTime::Days dayOfWeek = dateTimeController.DayOfWeek(); + lv_label_set_text_fmt(labelDate, "%s %02d", dateTimeController.DayOfWeekShortToStringLow(dayOfWeek), day); + lv_obj_realign(labelDate); + } + } + + batteryPercentRemaining = batteryController.PercentRemaining(); + isCharging = batteryController.IsCharging(); + if (batteryController.IsCharging()) { // Charging battery animation + chargingBatteryPercent += 1; + if (chargingBatteryPercent > 100) { + chargingBatteryPercent = batteryPercentRemaining.Get(); + } + SetBatteryLevel(chargingBatteryPercent); + } else if (isCharging.IsUpdated() || batteryPercentRemaining.IsUpdated()) { + chargingBatteryPercent = batteryPercentRemaining.Get(); + SetBatteryLevel(chargingBatteryPercent); + } + + bleState = bleController.IsConnected(); + bleRadioEnabled = bleController.IsRadioEnabled(); + if (bleState.IsUpdated()) { + //bleState.Get : in displayapp/widgets/StatusIcons.cpp: bleState = bleController.IsConnected(); + //dynamic icons have their definitions in displayApp/screens/BleIcon.h / cpp + lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 3); + } + + //Add alarm state and time + // AlarmState is an enum type in class AlarmController that is in namespace controllers + alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; + lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); + lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, -3); + if (alarmState) { + uint8_t alarmHours = alarmController.Hours(); + uint8_t alarmMinutes = alarmController.Minutes(); + lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); + } + else { + lv_label_set_text_static(labelAlarm, Symbols::none); + } + + //lv_label_set_text_fmt(labelMinutes, "%02d", minute); +/* + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 5); + lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + } + */ + + + stepCount = motionController.NbSteps(); + if (stepCount.IsUpdated()) { + lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); + lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 10, 0); + lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); + } + + if (!lv_obj_get_hidden(btnSettings)) { + if ((savedTick > 0) && (lv_tick_get() - savedTick > 3000)) { + lv_obj_set_hidden(btnSettings, true); + savedTick = 0; + } + } +} + +void WatchFaceMeow::SetBatteryLevel(uint8_t batteryPercent) { + // starting point (y) + Pine64 logo height * (100 - batteryPercent) / 100 + lineBatteryPoints[1] = {27, static_cast(105 + 32 * (100 - batteryPercent) / 100)}; + lv_line_set_points(lineBattery, lineBatteryPoints, 2); +} + +void WatchFaceMeow::ToggleBatteryIndicatorColor(bool showSideCover) { + if (!showSideCover) { // make indicator and notification icon color white + lv_obj_set_style_local_image_recolor_opa(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_100); + lv_obj_set_style_local_image_recolor(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + } else { + lv_obj_set_style_local_image_recolor_opa(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_0); + const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); + lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); + lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); + } +} + +bool WatchFaceMeow::IsAvailable(Pinetime::Controllers::FS& filesystem) { + lfs_file file = {}; + + if (filesystem.FileOpen(&file, "/fonts/teko.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + if (filesystem.FileOpen(&file, "/fonts/bebas.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + if (filesystem.FileOpen(&file, "/images/pine_small.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + return true; +} diff --git a/src/displayapp/screens/WatchFaceMeow.h b/src/displayapp/screens/WatchFaceMeow.h new file mode 100644 index 00000000..8978f5bf --- /dev/null +++ b/src/displayapp/screens/WatchFaceMeow.h @@ -0,0 +1,130 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "displayapp/screens/Screen.h" +#include "components/datetime/DateTimeController.h" +#include "utility/DirtyValue.h" +#include "displayapp/apps/Apps.h" + +namespace Pinetime { + namespace Controllers { + class Settings; + class Battery; + class Ble; + class NotificationManager; + class MotionController; + } + + namespace Applications { + namespace Screens { + + class WatchFaceMeow : public Screen { + public: + static constexpr int nLines = 9; + WatchFaceMeow(Controllers::DateTime& dateTimeController, + const Controllers::Battery& batteryController, + const Controllers::Ble& bleController, + Controllers::AlarmController& alarmController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::MotionController& motionController, + Controllers::FS& fs); + + ~WatchFaceMeow() override; + + bool OnTouchEvent(TouchEvents event) override; + bool OnButtonPushed() override; + void UpdateSelected(lv_obj_t* object, lv_event_t event); + void CloseMenu(); + + void Refresh() override; + + static bool IsAvailable(Pinetime::Controllers::FS& filesystem); + + private: + uint32_t savedTick = 0; + uint8_t chargingBatteryPercent = 101; // not a mistake ;) + + Utility::DirtyValue batteryPercentRemaining {}; + Utility::DirtyValue isCharging {}; + Utility::DirtyValue bleState {}; + Utility::DirtyValue bleRadioEnabled {}; + bool alarmState {}; + Utility::DirtyValue> currentDateTime {}; + Utility::DirtyValue stepCount {}; + Utility::DirtyValue notificationState {}; + Utility::DirtyValue> currentDate; + + // Lines making up the side cover + lv_obj_t* lineBattery; + + lv_point_t lineBatteryPoints[2]; + + lv_obj_t* logoPine; + + lv_obj_t* timeContainer; + lv_obj_t* labelHour; + lv_obj_t* labelMinutes; + lv_obj_t* labelTimeAmPm; + lv_obj_t* dateContainer; + lv_obj_t* labelDate; + lv_obj_t* bleIcon; + lv_obj_t* labelAlarm; + lv_obj_t* alarmIcon; + lv_obj_t* stepIcon; + lv_obj_t* pawIcon; + lv_obj_t* stepValue; + lv_obj_t* notificationIcon; + lv_obj_t* btnClose; + lv_obj_t* btnNextColor; + lv_obj_t* btnToggleCover; + lv_obj_t* btnPrevColor; + lv_obj_t* btnSettings; + lv_obj_t* labelBtnSettings; + lv_obj_t* lblToggle; + + lv_obj_t* lines[nLines]; + + Controllers::DateTime& dateTimeController; + const Controllers::Battery& batteryController; + const Controllers::Ble& bleController; + Controllers::AlarmController& alarmController; + Controllers::NotificationManager& notificationManager; + Controllers::Settings& settingsController; + Controllers::MotionController& motionController; + + void SetBatteryLevel(uint8_t batteryPercent); + void ToggleBatteryIndicatorColor(bool showSideCover); + + lv_task_t* taskRefresh; + lv_font_t* font_teko = nullptr; + lv_font_t* font_bebas = nullptr; + }; + } + + template <> + struct WatchFaceTraits { + static constexpr WatchFace watchFace = WatchFace::Meow; + static constexpr const char* name = "Meow face"; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::WatchFaceMeow(controllers.dateTimeController, + controllers.batteryController, + controllers.bleController, + controllers.alarmController, + controllers.notificationManager, + controllers.settingsController, + controllers.motionController, + controllers.filesystem); + }; + + static bool IsAvailable(Pinetime::Controllers::FS& filesystem) { + return Screens::WatchFaceMeow::IsAvailable(filesystem); + } + }; + } +} diff --git a/src/displayapp/screens/settings/SettingWatchFace.h b/src/displayapp/screens/settings/SettingWatchFace.h index 1ff03e22..4425bdcd 100644 --- a/src/displayapp/screens/settings/SettingWatchFace.h +++ b/src/displayapp/screens/settings/SettingWatchFace.h @@ -11,8 +11,11 @@ #include "displayapp/screens/CheckboxList.h" #include "displayapp/screens/WatchFaceInfineat.h" #include "displayapp/screens/WatchFaceInfineatColors.h" +#include "displayapp/screens/WatchFaceMeow.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" + + namespace Pinetime { namespace Applications { From 1ec43e27de7b6b25940c66b6fc5def2f6a0ac63b Mon Sep 17 00:00:00 2001 From: ecarlett Date: Thu, 23 May 2024 16:36:07 +0200 Subject: [PATCH 014/101] =?UTF-8?q?c'est=20mieux=20align=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- compile.sh | 3 ++- src/displayapp/screens/WatchFaceMeow.cpp | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/compile.sh b/compile.sh index 6ee6c930..fc3a6836 100755 --- a/compile.sh +++ b/compile.sh @@ -1,7 +1,8 @@ #!/bin/bash rm -r build -cp make_pine.sh build/ cmake -DARM_NONE_EABI_TOOLCHAIN_PATH=/home/eve/Work/gcc-arm-none-eabi-10.3-2021.10 -DNRF5_SDK_PATH=/home/eve/Work/nRF5_SDK_17.1.0_ddde560 -DTARGET_DEVICE=PINETIME -DBUILD_DFU=1 -DBUILD_RESOURCES=1 -B build -DCMAKE_BUILD_TYPE=Release +cp make_pine_mcu.sh build/ + diff --git a/src/displayapp/screens/WatchFaceMeow.cpp b/src/displayapp/screens/WatchFaceMeow.cpp index e8324534..5b1c94c3 100644 --- a/src/displayapp/screens/WatchFaceMeow.cpp +++ b/src/displayapp/screens/WatchFaceMeow.cpp @@ -240,19 +240,19 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, bleIcon = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_label_set_text_static(bleIcon, Symbols::bluetooth); - lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_TOP_MID, 0, -10); //version par defaut de l'icône avant qu'il aie regardé le statut de l'alarme ? alarmIcon = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(alarmIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_label_set_text_static(alarmIcon, Symbols::paw); - lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, -10); + lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); //version par defaut de l'icône avant qu'il aie regardé le statut de l'alarme ? labelAlarm = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_obj_set_style_local_text_font(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); - lv_obj_align(labelAlarm, alarmIcon, LV_ALIGN_OUT_BOTTOM_MID, -15, 10); + lv_obj_align(labelAlarm, alarmIcon, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); lv_label_set_text_static(labelAlarm, "00:00"); @@ -478,14 +478,14 @@ void WatchFaceMeow::Refresh() { //bleState.Get : in displayapp/widgets/StatusIcons.cpp: bleState = bleController.IsConnected(); //dynamic icons have their definitions in displayApp/screens/BleIcon.h / cpp lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); - lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 3); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_TOP_MID, 0, -10); } //Add alarm state and time // AlarmState is an enum type in class AlarmController that is in namespace controllers alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); - lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, -3); + lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); if (alarmState) { uint8_t alarmHours = alarmController.Hours(); uint8_t alarmMinutes = alarmController.Minutes(); From 82e96cf8d7b8c604d7165ba6497f9c1545719843 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Mon, 27 May 2024 14:13:30 +0200 Subject: [PATCH 015/101] InfiniTime firmware for pinetime with a new watchface. Note that some other watchfaces are disabled from compilation to save memory space, in case. The WatchFaceMeow, named Meow in the interface, displays the status of the alarm on the screen --- README.md | 9 +++- build/src/pinetime-mcuboot-app-dfu-1.14.0.zip | Bin 0 -> 378895 bytes src/Version.h.in | 2 +- src/displayapp/screens/README.md | 14 ++--- src/displayapp/screens/WatchFaceMeow.cpp | 48 +++++++++++------- 5 files changed, 47 insertions(+), 26 deletions(-) create mode 100644 build/src/pinetime-mcuboot-app-dfu-1.14.0.zip diff --git a/README.md b/README.md index e4f6707f..b58821ef 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,16 @@ -# [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime) +# Infinitime with a new watchface ![InfiniTime logo](doc/logo/infinitime-logo-small.jpg "InfiniTime Logo") Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devices/pinetime/) with many features, written in modern C++. +## Quick notes on this InfiniTime version + +- I copied the source code from this git repo : [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime) +- I added a watch face "WatchFaceMeow" whose main features are to be pink and have info about the alarm status +- I stored the compile commands in scripts compile.sh to run from InfiniTime/ folder, and make_pine_mcu.sh to build the image must be run from InfiniTime/build/ (compile.sh copies make_pine_mcu.sh to build/ +- The file to flash to the pinetime is InfiniTime/build/pinetime-mcuboot-app-dfu-1.14.0.zip : I didn't change the version compared to the one I downloaded from [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime) so make sure not to keep keep a copy of it + ## New to InfiniTime? - [Getting started with InfiniTime](doc/gettingStarted/gettingStarted-1.0.md) diff --git a/build/src/pinetime-mcuboot-app-dfu-1.14.0.zip b/build/src/pinetime-mcuboot-app-dfu-1.14.0.zip new file mode 100644 index 0000000000000000000000000000000000000000..4904d149c01b8f34763e30cbadf8c98a7b703961 GIT binary patch literal 378895 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

g07$tydb60cWII2A`KR>!Qc4##SC zJPLY=p;pm_g&Jz7A4f&wa-cI7xdbCaz_oLesNQIqL?1Me&i*wh)+u%aYt829uVMcdYeuu<3-IaL9vB_me%)l5=7g8j^JW$` z$_MUKdJ<7u2+h`@w#%%hO7DDQ@I%jU%(H;m6E(*@lfauLXq=vnIZAlp`K3tc622TAjT=1C#!m+pBA{*m z1RgeX;a|h;O>gJ+QXOgZ_QUJTFTEC|I%>U~`c~%@R==AE&v~+;F{L*l#`^RQKc|@B zr1GgyK85D6=XLl;QEfT++)#ZhA_{ylzw{fRfmJoJ9z2c8sIFx5kcyR&^Zl+oR<|HL z`(t-ykY9iNi8Rzu1zi^9lsu?zP}YzZGpQCFP>2gF?3;>G`=wnW&bXGvv-L}F;&_c) z1aj05RI*rCZjG%Ay`e=Gu@1PC`}fSYyt-$W7;b)5~Ks_PdxB zc_5u=6`-%~3auvIu^Niov;uO0Pr4_$85DorZzw9mTVMTGm*kcuHyRbW}G zlF4g@zoo=_A~Y~i04$1Mx+%mPA9kVNXqQ<9@TO%s)>yy`QCob8N*{)1a~9es)_-}c zC*wESfkQO~Zo%Q^C5V0qrMw>U)m5`^cyV<45)1m%!*NTwXP;7w~xj)tlnecm! zwZa9qrau$VurZNtNx-keLS=l@%=un}g@x9fbZB3=scr6`c__Ch0z$pmpvx$>(Ck6E zCL&hnXSg0y$e;Vw-2%@8{!Tn6;PYujTU$WqI`>)0V9B# zUUTY!oE4y}Z?Fgr)H@n1L8%NL#7|QnLBzVp50Chk#`^HLrdl^&VKe-5-^W`dyHht8 zz7-xCbVw*7)}lSG0JW3=;w9h z0fW5~&K%mI2uXN~#Z< zaoz|2dMY*TasAT%0CCVz+dsi}6h8z{m5fr%bH6D65EcRjECfF^5dtZfYXzQSMEVGf zBh>MsChuBtLp5KRXwhK?Aa8oY9i^BGsu5AtBpz(Vug(NdF`%%%46kmXSoQ4zE(cK< z_b8jy2g!r%8us!irz1A^?UF~3+)ybPKaRg$Beqi=;Zu->u{PYg2PIC8x@4}V4o@}0 z60CIbtOx&6-YMT)L|>+eo%e-`vZt623Nv!`!<_2Tk}r91-A&bFf91KgDVX|INbi z=9Zji3B6Dc&9ct00g_P^{?@PV;^N1TMJf75*(p9Z=uWwjUv)gc9V|aCx63t@Gp5d8LdgaRl$W>mk_yZubdBl1C2^>2c>I)D>j;$zY1Kk6#*-q zo?$iI&xDKjVk5x!!hXd_RME0cSJOD|{0C z7Qpc^6MGq-^i72JGU(w?-L|yJcH7cY=7(t5K{e6a*v`h6#9HN&&9?p0x`3n&=)6TD ze3=Uxbijrr7`79ZMX%|XoMG&BwakVXUIY!)!SHN&Z~3Gi@I4eR_Q57vXzgtaNV2?d zf~~mMIPzj4dYE4>F>oP;sWH0-Z1Y-pwD_cn(4b5O(*M?IB1#;T0+Ba8e74`?m+oP$ zk_pUOp|#*F*rO4XfabwKWYezF?XA0-wqM@fYHzX~4;W2`?VIeSwtaFw{2BA({*3u1 zoCC>|59SpII8-XYp^~px0qIhL5?_p}pj)A`pU07&r@DL|{RQG*Esnou0G>^ z&RwKS;0%wA>$K-sKD#?1SM3_!0(9vYeXIC_Z_+EweV=nVeXGzLr)bFUdgj-NoOzwi zJwk3R^hi6AyVAV{V~#OB#Iej?$L1TO@b{1_9q>ISnXNmL3cN9C&wSYb6ZTjMDaa>% z4cT0axmX?6Vg{kP;JwIxw9#Ea3pYUfpeV!HQM&Vq2yaxn5`7{tyaLdF`*2m$2)Qef zcJ;GeFUku{LM>^C_}Wu}d{ZJU+AF|>HPfVm2XA7ywE7f$%JSIEOiWR*pP4BBGMD#= z`^O6&#u^GO3wEcMVY^J%J2FT@Qh9BvR?OsC?0!8%dT9@?1Vaz4A0G`8^4I1@E{NY= z_&-X()vAEGn2D0iKri~^pp8mkA_hq4bV|lr=G=N$T=G)#8D#({4V*M{Ce_ z__g_gIaguMM*V>X?FCci3}{9F8kvx-xAjV&Mu7YAA@)ahFVJ8{>m<8RdI2o(>Gr*)x*s42apv93A3C+NH zHcDB9vyYgMF_q_;0M=i-;2Q{iZFgXmE=L?gu9gR$Ob-tCeNlSXF(k6;UzDC3+9MuRurHCx;h&U}33x;2We#|CI8<4V<>A*TNc9@I$ zD{*HEiwuC=bb5Oma++;z!T%X}|EU12&4FM{GN7m}KKM05Vmn%hHaij_UXLjDE#Ts4 zEkx!HoOZ<&YJ4qc@tHYmJ_=^$sj%gT`1AVd++158CppDKB-79#1~Pb zrO4{J)(v)^nx)nxXjgj?tBY#bpdND$(d+`FN@Pic8e~Pbn{^i_feGCwO$Clv1~;KB zjR3>^7u80~eB?`4(!i1hjSxIVp1j5#p>MT?FqOEX4(T&^XHkp;9GaGt6SDLba`1(F zsvVgs=VlMt+JAKfZPN(+tjOiH5B&0oEd3Gp{5?z>#Ey)L1t1`oK0wO3_){1)+BF_( z&()a!2;GZ*;MV5Ud)hDyCAANSMqn@IWE;lB-S{K9;nU3u?xRWtw_zLkN5eXT5|T7c za*SbzPr4ty$zan#C)GdyE{v&|XH<+eYAo|?y7Pu-6SP(D()Y>^YG6wki!lbqAXt{h z1m{l`5&1)Vetk>P)M2m|ktbDQ%pa=UJaxdWE4c0fLzUR)NiXnu^aVX0Eqr6}w5e#W zkazVw2MdEw`XW>%^V~Nc;ueca2iTW^q@onvm+S%P5WlrymF4=SQZSb3@p~_Rb@*K~ z_y^CZ=^wk`IU|mQ2ArPW8GRp~LD_C4Vwo#_D+d42W&jSn0UXST5wYtVtT=~Vm$%0~ ziE1&H!^E5P8k;wK;YO@;XTvXgE@KtF4*x9CE!w%f&)j?AxwXd~!`JzwPhg2A_FL+; zXV5dB6=M7`K4?AoomR^XXalrP=-FgMY{K5`S4Rj1idi*U=Am_S9M;H9_$H49ln+w7 zJ_>0>>Dz*_tn@vJo%~qrHB31EJ8(!U9o1@kpmF;p$Ve2KjruLkw=~ne5N_Iq*agf7 zsTcf=B!$wGcZWoy$BlR!g!h|2k$qocD2n-(Pl_`Q>Df-tFKu8t-TZ)Rs`|l9V8n}{ z6@(@+6tl(AaS3+t@PYJwGiX_G6zkdv#whe#=%=%+mK`xZqvWYtwE7sdI`o3ZYwT-0 zgIF7hQ)6FdS)ddALjQtI&=35-!gkq1{gq-;Q5h%4%Q%XaQEkm)Wi(lH;$<9-GWLQU zly+$sWtUv!D8kWsIeBUV-um~T(zhn00}hztbjExJ=)H8F5*YA|n5tr4S0c*VD-8~I zdsI`stfn+-zlQfZ#&0$qUkEmu!HT3m0ehtgOdo0BH|{v+p>aiJ9okzH!&H`g^k!&5 zA#ta=1pF<>K6{fbV=3`^5BW00JQ>Pv?6*zuWH7>$LC4lH>U;1bgdWgj9TuFcpxUpI0Jvfcz+OO%0*wIbx_D{Ve%01kB%=V?-5OtAaDEX|TM~JUsETuwYe2Ahy>`|cf=^-^M{h0q)KdL#_Hr%aZN9Tb$hgLQ? z4ZH^6h6@qy@~|TfwKfl?FJAXX(OhzfNkm0u+(d2desJ{M3$Kec3%I^l9GTep zjzTNNYPc&Z0xv=JcsM{(B-mEDgI*5O)FNO$Ghq{5g5L^641vD^mw9k8{087h&^RDv z$R_Iy+VMY^(6AqzcgfJ{Z3m-X07ws21MrJatS+tzg?yFH7^m;>+JKaWE82Q7#9dEl zs0W@a)uOC_J`}qCnLO|4TVUXZr;K}ab2=o%T$!X}k^_PIoO>$pxM?h8xb>fYJHUVx=AAdcEBb2u@-n(ZED2X@crq~+mZx(JClh^O5TD$!7$5c zV67Hg(@>99CH+z%qWSU#3PdO055M?&OV<@$&S>ax&M!RW8GQiw;qd5q+a?A|Ja&ww z^N>36Ex46(UYV*Pif9t|7Hb<`2=z;vhVkG|Uv#n%t?)bGg;p06juUaqdwj__wR)A-k5~x`oSZAz#6_or-FF$N=y1w@QThU_ol%aAi`03#!KZ~@2SjCAyEDgV)eTU zr!BDj!MjmG4E)=!*H|8|J_OmX#=>F5P6>#3O7P_dM4G)GCeJ^bz1Cs&Qeb}LYttVX z4#xC!*u=;-HVqm_vg%DN*Wg<}BZGHXPv4Ml_!JSz4}xWlION_yG=SB`%7&p{Rf#dj zV{H9G2Df-9F(@)N7UgN(Nawed1#$@F$Am#_!vp@xtYYQQ@1bm+9@5PN$f zj}r0m66xY4LYe}YtQ_4TR+2=11(gI>zbjX)OudpWpl%rR05@fet2HpGu{AIO(SuJQ z5(=2W5kHS}%FqW>I{}M${i!lK|13(S;OfC14s@b~IZ3GJ!TxZ@a)`B&7%vfVq3d-` zH%b{V5!%R|9xG2%taM`P7!Bky;)L#%{vd1P?|mtXH~2si?Q>YP@ptb9|2@@pNX;!~WV9Pd^!>tV0d5^6g4Wy-XaG@mW z)|7YPPn+ak0v)0b`ZgK-bsX5eD2{Aw9j5Q}Wr!@-z0y~q*eIY_EQ+sU)6)-dsv?^g@>vWW;U6Ij`eD0?m|yD?t&T$Ft( zEBjQGeY&2@CRU>qpth!C^l8GUz}rs9E+bG?)aJcX1RP0I8Cq&8Lwix$HKgUT`*MM* zirv?VwCU`=>5zNoTN6;<#t?VYt=ASF5#slpAKc%PrI!)+fsf0Hz0!XTCN!hmh7$NF z9uF$6>mH(`I|2n;p>yGjeBF+e1Ipd~(&2!zF6AJ#-9H9J$ZRF7T{Y#9xN7=$lCLJ& zVwRaUZGBPxF?6sTbg-PT3&+Rt4W1~@NGa|gZs77Xdy4wI9mny7zDrXg?Xn{YQcMpb z&){0e@qo&N=Pm)o-ly7+m?Z_rfgIWc+%sR8jD6}U*`#~Ccyloi22Ue5nNc?B|y9&0hK>oDI0W8eNMtQb7jHB}wfOzRrNK^TCPY(P&f)Dz~p5Z`qk z>yz{F_2=kIt0{F3Qg6{GH>X-ORx{?>TYxl1yhY>vF4CtyjP^4F3ziDpmKo9zJZJ;6 zfHrF%*Z}_plPMq9i(=PheG%j?TrUD5Z6+brF2LKC;#>tQ`YD%Fc$$vC@T45dwz5S3))tuzCF z(6b7m*Sj9byuukWtg*ARa@rMsV#*bM8t>vJCSMuqZ;c^i3wl%;;w;Is^f?%EV>#}{ zOwfrtX-pb}u`#(z-dwUveieWFN?-*FOtGqADThWYw5b~Lq(Y*kV&}gKweI|;`jzT* zOKCNq|5=8inxFAWM)IuR_Ya>eI{AGm2e=o2p?3~Qivl!%Ao3Wzk7@o`6d!}chjAMl zgSX+UEYnY6%wjYKmthQAL-Y)d!9|Fgn#$fe4{5itv2`2LbSy0&X-n9BOOQ5#r5Ta7 zjNM0jk61j6>(a%*_HV_BmERI~qEG6S#jWf6D-cao2a6D)lYP=9c{}!UWdG`s#@t9f zCx3J!H5oP`pY&&$+e$tH%q~={vhF;pft|7+Ud@D`@=5Q>;xvVIgqz}0iXuOk&l8}F zlHvbPS~0`cVx$@#1y397o~mZ@-e?JN(|+hSeD8ie?k^dfY*W<(QN}@L2lGDZ*3a}W z8L_rJ8}C_R7Okz8Uk z%Bw(F)9(vG!`AUFWIZ=L`myJJ^sOM|ieBkhRNR`gb;uf)^JsB(X+`DErgHPv(yhck zS^DS@J7|n4G}&|+>!r?QsNflw)2GbKo66`6*F!H;23cak3kojqY)qEWOnC~rnJVn4 zbU~-$-lJzdC;W=xVB}`~R>=FaJH`$6NaXjpq8k2d$Rn|t=H9?Y ztmex*^;67oIp@y6F*&KR1X8>sNY*Wd8|!yJ^~?entv|b?!~@wQ)kc$l0IopzX3fDY z$JMN~Dh}|^Uzf&$jWi!~FCqoNM+ewe?(>YH=vXT+k$>Tc+0dZh&T1<+oO(iQLvpgkTPN90pxybU~)z35IHN4Emg2>4FSjMw1ACPfAIu=|Me2L`C|E`xsVY zv6gC_!BR@xi6Kqj^3I$*bN}!htv5kKyeK5wr{TL%oKVUM>@hcD!Dv_>z$f2Gna*0W zD?+ke53EGVKLB@LKS_67t>QqjmD=&K2+htEZy|-v&bs*QOr9)hY<33z8nGB=+fyxy z0~}`OQ6@<$3>Q9h?#gWV2lT=RK=x3dEP$?1cdS?XVdTt~3uT)c5(Lpg=Dwsh5!HQEuX1H z>uv4Dc0~b~^W$KJf0Eth93}IY((hJz`6NF7eYA(h{fx|Uq{BYNxFDJkA--&t2jhie zKgMSGUc`G+Rt(MX#&?HO{~7+Md?-L{EzP5Q;a9@frF>z}f^8~b zv#oB)weodZ-`Q}foy)oFj>}-z<554J;WUP73&xqTTHon#Kbx@#Rn!BlTFlG7NBS#t z1~C@>9?1oE{g{rwNBSe!^<&!q9_d4P;=fWg-cnjM*)h&?x#~X0D_)I|p7E;Vk^^JA zJceZGm9~W|x5RM9YWVsqQ!5{d;T?LUGz9*(N9uPCoXG*TmwkzTFcqeK(0kzQU37>|!N1q7~WAd+FftWZ+Ku@hOag(_h z4ECgh3C$XmB|FUCA}aEh{a^CUrF=2k^faHD`25>5QeYz(eev6!$@}#=d6p5@7a>n= zlsR}N!p1J&Rx(th@4w579mphu1KFDIupp=R136Oghtal;Id<~0m?ZQ7Z>cLL6gYNA}Yhyx=T2 zLp)=O-#7&rR76;W4q6*@^wpZpUjoHioBJbeU7o!NCCvj~fyNX(Uo40u7v}?31LsLv zvq{#Ii0P}+aIh9Xd1R*b1!n0T@!#sRo9ct!Xhcu^EJK%pl{)Ye@Ufh>!K1AMzmv@C zhWQO(>bNJU0w3Dju9rR9`rTt*F`saW4Q*pSxtp8P>Y_GU3OfKjE$(?1^D)IFF2oIlpZ#lS1vj@f}&4`}U#tXWwOFWhYV9AFe>>7meGImM9<4E!|M z>*h0gP799#1!N7%O$_Vd+nQFS$7mxjuUM*#Xm>+*^r07%S5lfzZxIj;!iZhvD1DEV z2Mj_tW|vH+Rig+1G&gC%>Hvu|hi0Y`_&(6Mo3vPk%Oey6Xpv=gannxH(4y`b8Cfh} znq%rIY92;?oq1%<=o)+j)gu=_y{fzK_KbhXw^e>B8yzXmlsz-7jXNdj6M4QhwPqAh zzmf#+B;!wAXCA1LZ{a%ni@4PKdLR(eV9{E~;`QvpZ&AwyC$&>bJ)P+qQfhU3sGKWh zeiCL8lw%yqF-kX7jym8koDOb!k?)9O?oQ|Ogcoklpt5|1*=nc^Psy`ylwqT+Z^ip< zXCC;tzex3;N?`r}C_##Rw@0+T=BbC|Jpf+vE9J*M1=fp9>io4^iyi+vY^|qO9BMJ` z^WUG7@4TpoX08xp#TD-GIxr$Dz^0eRZC{qKkApY7ZbS+zHMQF`c?QZ%t(GElEgcV} zLN22@eVr+-hU6D5qIXaWAzn`OM$7!$96Raa^pkUU{v0imVPRj#Jk%ls=(E%!RFBJ1 zuH|Fo-fI3Jmis=+opRphxfeC%CJi7q*Ol^5J!@FKOA$5LQ1Q1bCm1GdH^xNlo5bol zimg53SJb1LY#&}JS*nNpw11&!y#B?|2%65?fJP9FpX<_+@FELZqk!{PaznZOLr&XG zIg*{?9bkToEW4o{PUrTc=hcS%V1yZ6Fa|78Z?`)#9qDJVrl>GZX*Eau=|SjB?ONZm z@QkGnuQ`;;g&M=(isKMLEIo zJKLfwUjX%?Z3Y72mNV!dJf!ej562J{J(3=pD$FfxmzDwDz$lDW9&7?^tm?X@*P}*o zo&LNYzIymugn5Z%MhpIEuA+Hr$SzMDgWqA^C;mswJ8|vDkS;@*_bg_6hdl_k_g;)i zhxNX;$}NOJQMfs~B;7p-@52((I^P@o3RsXBE`B3?b)%5iJ_l07?KV`uT0N!evy4Mk z;Ishec0E{pORK-B-hedkjkIsv?|B}9EIub@x5qwt7-peX=+1kkhh&wV15@J;E{K}jU-EJ3oD-nQO}W{M7w-2PHBBVg$6(ZcZ(h# z+MTW`NSQm>1nK^a*z^d)MIeUZ$e4V<-d%(D{y?TTflKLZARVcW<8pJss9oAX~{>m|`T>wo?hYcKS}qZ}($2p?|3Q=QE&Ha9@#l6!Ff1OlqR`20B>+?rxHeq)UFcEM1qIw&-oK@?Qq0 zG__Zc^i`DAUZt?o_tCXT64p%HMC37%VUJ^VB_8RQ-{as5P27_`(g)Eoi^>A*iN7axN+JEIN)(e zJAoZHb^=jp$$P}lB^?`%Z%_!|*Yq}S0x&4>Em+R{&wIdxZ?vUhuiGuT(DQmI-ZQ+= z(_x!IzOu&yzRv>8G7pV`?M6ryHkg+dFHS8gzE+efuBE41gGH!ixAaEvDLgYA@y`DX zs{o~p0P_Xa=7DG-?uY^l{{g)IdL$!|jwI!`f{%^T?*g_aDt!`+ojuRa-i%+{8ED;- zp)Rz}1}1r5Jnh*?YkUtm(rvQegkJO~JX?q!dk&Ftjp(D#xMtY--5R94ALQ!-@wf5Wc#R**2yj|_Z~OwVv))!?2B7Pdw+|F z9@s7EFfvaCl8>GWB-M077XXYFb_>6Phq4kD64G6b3O+q>6U7Gz9(OiXz3b%X)|i7<04ohvgH&iv$a7&gXRs%XZMZ9Tt}!(|2HU}KgFVr*rfQUXC1e?LL#WZIyBbA-~5TEA3L?7yZbY+kDmA;@Luw<;TW0J-(zSmr8SlL z^%PnYFvrQzn?C%$YP!~4gWUl2n^Hu`$v2Ttnr#Pp^lnC5EsD=g!?D9u;+}~SwQYxE zRBPp97u4g=rQF6D8m(lirv*M4QD<9J+Li09*ad+(H!fMKEfHvR&i}vwn|6kWBSDgXw*oHQ27vD9TphN#u6X+dE0ts zTkMvEL9&^3OAp8tZ3TAU=-JgKk70 z(&O81K^`Uew!{t5Es+N%JX@Gt5=uo6kieNiv3y8Q7pn{o7XQ_L z*(PoWqngML+srPY>;YqX3e!E&%(MgE3?wtru>*U0(qhu_ z4{1aw|vZAT7JVB!i~Rn>M&_eE3<4_CE-84@@XL`&uMNi~Jl z3QLy7;M`mdw#EjfWvnF+@k1 z8t*6Ks9I$AH}Ka* zof<@vhi*B~>`G{uS#@`DE7-P(X-C~K{y>(cPkJ1De{}b2&K~$gk(O8IqJ0fN1o%5^|XlB$j@EgsWF}i^~=jUjfP|(-R=!m zg+#B>h&{q}+D#WhrdbBwF@<$?mKsPk!W5;~4uB69*gfG-*BuX~IDyyk@gapmXC1ve zdC!0}E0W%zw8qdz1JX^A(RGOp$@S@k>%o0W<%E=JDTc_9qLToST`F zT$|LeywjWc(_%vVE${qxq!?0MTi*H4v_H2e`kzcZgg@%RE+>Vw1D$shPff^j|Rx2-{^{gv-vFDY#HB7 ztdSb)uzE=U9m_lO@}{LsNFm!O?avhriu$n@V$6K7B+HVF-(M7wrPPi$Hdy`&pWk}( zTk-=(=eQEUeXTO3TJpDkrk-(j>4F&-KJjX&v{sF(Zv!s!UTBy;@khmxbt8UzSFtw4 z=jiHwf?vf5=u0?9zGH^Dcs}fe73)9f|8Rw+^8-HRx246Yzr`H|j)LN1jG&eCJ4M51 z!-_l2`GgA8zykm2g;@V8Go_(q51UA%GameJ83*7Q9@PQ`sjYqNO4%_XcOQ!al?KfI z!8RUt>`0IYuPe>KkK$+siyzpJIK(;7P0XGsb9sd`{+ z8>eSaJ~!2Fcr5kgRQ&+vg}t(_qiN?lMBhyEiAkTzijIHooC)@Ye9LU`N^pBKog`aS z!nzxlr`ULq5O!f98|3A@zDDG$hiou_ILL^reoBwM*8rlEO|r#u9~bJuI=&5g{}gftdH;pr zb4I8$U3QoBC?t-UY-Nl`2~3Tn$$F$-#Ie`iIP1h2NcGbPq@%$k$XQ*$v?a3N6@!!7 zXb&X83;v=fCLxg)kMQvHUaHsEtqN|0lgUZIm_q)O^Bz;=?Qb8p1ig>j+y1w^xyb|4 z)9{GVu{{4Re9*-;nWu~ciyV~SivP_gf8S1#sq zs_!0y`uCANkYNzzRcBCKn*oJ>)_}Rwr4z9ISXIeT{nbIYadcXJ$s8 zqp4c79CDOaljbWGQOBVD;?ga4$GYki%-*ldDhJymm#S7VOvYXJA;+wv#} zX&w)F1x=CVUyg4*skpl-emxJ@(;P>gbQO1r`z^1-W?ovIi6<`u8*;^y;?oJkT!!kS z&hM2%NChKi4lBhKVq7TTYDrSN4lbi(VBGJP7QQ2Iw2QrT!?|2DX`T@bNX7grmAsH#5vf zw{#^sCTIJ$4J_}HDE)RxTErbSvHd`o)IFFmRcY0n?~;BH%C-t9CG8lxqzNYrPmxsA zE&1CtIcnd3wbKmvTQ++s-7C@av#>)WFK{kba`CypbT5m~Q{C|Y$M}KOj0a2c*U?PQH{A< zFROuHCt3T2D525GlK1K}U~V-*OBxdTl?TS;kSwM?xC?3h@Py~|S(wXbz(@YN)Dhu~ zzjvvzvmC_!Lk-`GZfO&Ii}a@R>S3%L_3DmuXRzN~qkiwK8f$}ycP7clJ(TtnbvH|+ zHAH~sforeAxFdPB7VXt$#5de4gQ5-=f zJZ042M>f7YRF(CkU9Z}OUX@^QQ_eKx3pXD=Q7ZIH?+ z`qBkWuIMI>HPv6e9|*j9ZCwljF$|Ff_ah!3V~}ZBlaWxjCPPtIXI266S0z{A4K0W> z7|Wd^G8`Dphn*#M^zXUPztOs1s)t(kw*N(oOkgeBfIMQYn)|=VLkPxvjyG}e9BLT8 zXTL>*R98LMjR7eMt??e@d2+^ zQyY@OV>`S)*1FT;ZJH${!ApX6|EoNj+$XQCI>PC%Lu&>c09M%_9VysMQpP@c8GOcv zo%3y6-BKgZG(o3&_5@h*sO2tsx}^QVKJ10R@oeIhdqh|g75Ex;)`W&S3y(Ips_rS? z7Ycl@tkmuvtg~Ixl_0R|;LPFcU&<6wH$khcrxFtfdj_PK*f;tsY-Q8~hDs}l;ORwa zr?S$%i&cn9JCT((m6evp^8W9Yc+B@o{CAZ2R#xImarTC8#ObQR8}Jl#F9w%lYO34p zQwZz&w_(=So9G&iJvyebDS^=@HWwGSX{N%<1k7sx@~l+TeT&*HK80dEp8VQFde3QB zCB%>pNv%NWooHFXv=ZIYKd~k+kMlQl15c~B6=6o`l1gNXAzcVbq)QT*mV)9l`YJiU zMzhdX1if50`*H@~c<5}BL_`zP(r8$OE0=J^WO`#8UE7AE<=6hOsR?Wk^Gt7)-p@wO zJQFv4wwom25N!E;w$0{+? z1$X_lg8TNAf^*|obF5pMJ6Ht$LkuUNnL_brN!vFG?62L@v_WCocvr&ti)|Ezsm!*h zg&4`=Ud_)HO-pc*pX0MwE%U9UH@hM2g9o-t+Bf*EM`OASFUPMv+9_K1h|NV)1a}lz z84fbw<#1Wq-#kyUm6kU;te?BpH>uoP5XmK$OShEs9?48z$nNck?J&>ug*xFJt@#1D zOOj;l(?gXTo6MT_r%H(_wM&{BCO@|>$p|k2{-z?|c1JOMc3)@O`RZs0j8Ejp7nQc* z`1c{9hK_&3j!7!(l6Er=(F&^_W&aA6>T(=K!70Yq(C^<7b)C>*UD8RJBJ{zg4W#(5 zj|kAaK%zxNJ18aGbo~%CsXCoteFyPL%FPqqiN0{sd$4oq-S6W6SQl~C&aP;yrr)-T zcLFIKP}E@k*0_1tlSTxq{Et-xaJ9-Gs3^2m?vtQq4N)6>Y1#v|M`*-hoxueJzXDat;)Z_?3C=X+lA~i8z3VI z`xjCU4eDF3bseU*9SmK)LQgyu>aTkE#uHcEUtLk%^yIZK4}a>Re))6A%G3{~pyOH6 zbVV6IV^nI12+dv69ne~mY`6k3rb!0ql7{2U5Z){*bzs(|-x2J~(9yfW*VSt9Wec!^ zC+Gykx34pgcV#2eDEy~Q(BA9QGb*6%`-Y`(NYUuekEt*ZbERPpN^*f~6KP3B`!mJN z)#lan6*QNW^ZFoKQpUd5cdYg(#XIem8iORIehFJy-Z@BRN~4=(sReT8QlZvl9b-tS z{q(Bl4t`%0=*UF)h@l@v5tnFFa?P+>{y@r7MeTKIv;5dN^_0Z(6rFrTQo^W&gaif6 zNQ6tJ`6v|@pV+w|4tQcmI?*3L(uiH2CozUs;*aDliqlQ;U>%rcI$)hB!Y}5FX0e8j zTB3?(J$~PUFC6LnyQGbS)QvV!I8_vb+_F1e2aw-uqmvX1gK0{BbPIELPo({*42 zX#_7v5@PNOVOqK3eMP?H@zZXz_^>2EGq{|CN zd?_?EBJRsr%Uc=KoHSQO5pOCN+6`)**^!uyhTiz`MIjq|J=(u5+__XUCDUHc^y{LV z?Z6hMFC>dGxT$@$4mHMW`QkXw!NP9oleR_3tr<4EZt1yzFdao|NGII!;>hu^}V zTIoNRH}R|3?~E_0{;)DebSLvJX^~3zc6}ME+nR`}?TUxy!zi&r8GFIQMdkS6AfHWT zoIUtQxv=OQn1tvKM1?Yb9=~I1{0<|#gRZ&|W4K$I9H7wgop5uI$<8$%#l{f)NdrWZ}noG%A>ILH~Sq^T)Nb^dL5Z2{* zt(LpbqgKSPM7265&-rgTy}?CSVOhSWVQMlv#uBX*x%JkU)K>A&e z6(Gh;&;@qQ)HpLim!t+Oevf2?J`IdJfx>$^r4w2QMQHpWVrGgoO^o!Vq zu^gSy-zixPu2?*BXe1^fZf1K8)=9F}N$|Z>SdX^MVK{&;*!_*R!g!qUE=Z9^NX0bD zKSIPf(yw86Eyv=-75;#4gZ`Mu$|FI_O2>N!q~~SScH#vOpVJL#7ZP2B^B3}jBX11Ew0X4F zH+tatq^J6S{RaiN_+te}nj?Oe<@Es2zJyr|91mEpb+E0CKSg?@p*t;L zH>CUM?bZKw-$-^J7vu6+r~>YLG`KuQZic)S;irMi1Ajk)m&Ow|%hEYupEbqel41>j z^v!Lyk!L0(gr|thqYzvkbC#;HUTEF4LV$}B3@0}eo@im0^xpfiv@7ljZRD@(mCi*k zd6K85G1N}~AaCTYS{x(7Wz-boj$Ww4`1-hQ3d6N;LrewL)ZYX7NKbdccRUqGfyp}lORsbya@zs}{PdQ>Iv4Yd$a201 ztNlDH?1O?ZlirbL!5TLBnp0sp9?0h=IJe=t!urUL?H=rPB7bM!!O z$Em)4$UNmdX%H!|zX0_g^E&ZjOhY#X%PXayftLVz1JLoQsIsAR44Qf8xBdojqnwgUN+=$S`lYcJJy76dE#>fK zL8&xt@SRXMh?bYk!!9H=XzCGBrQ{?YMiQ?9oEGC>A{M@Z40t5By3LcIfkF^KO@}ansi9?R){O*^m*VC)zO(* z$PX-IIlU8pD|aqCC!yr{n=*8%%HRi0hn36P{Y^G4NREF z;lF9#?Q;6mVUVdGMP#&D`nf1Ys%36q-qcjfi|C1Ctt!N^+mDjO%5dBh!~K!(#%OCM zVheY{Pj^-uJkk|gZ2Y7cGts7T$~^5CG@d80@obEZ=lS5~38@qcL`5DK-;b)S-^RI- zGH3+P?~*ROH}fRzKbi&`VM{2$j;751hI3`{%VymLP0MxIR*u4l=DM_0<|lmZ@ygw<0e_xqj_c`~QR)p_zJd$By(!Oy(tQA~9*Zlrr6M=;0#7IWN_@$p2qej)d* z>l?Q!SzJbqyB6gdhV$>@GSb>vRNeWo?RzbfG{+R6*&&W+Vc_3E0`H_7++vV(jQl?9Ji5l>*f zS#_Qwx7}!^=P31E``1(8 z$7$dq5J%6Ts8!U@t*SL^&#N$kRkfdb@~RVYU!tYS(Ft}!W&QHb%7iBnWj+T|_beBr zKEw@EBeGfDbCj>iol~`P-0c}njvNP+ zB{C?!+upYd4Zl=aa1Vje&YHNCPH(IZi}Nna{peJ6?GsOOiyCpEwKG*#V| zm1xOvG&w%?@S7K@`Lk8lzkAgC)zBdF#{TCL+x1nARlxtIC$$@J7oQL9P0~s9wf*R8 z8uXUY2UNa=p+EGWQ9XEQRgMr+<*%q-svdqWu>p2j?D_8QNVjZT@Ikv1R!(Z2H{l!c z7G|X?b?P}-ZS_g}DK?y7sWm5FT$S_S0puVI-P^?Oy~mUQD;FYc=o7$E40i7feol=! z`O60nt}=)rm7z7Dqo{`ulMPkG6Xb_vGyRD@GfF=k&#cqYGrRvU&rl@Db*7;jP`pv_ zc82KN5U0Vd*Gi9M9yCVPoiSB39#4PsiU;7 ztF%DW-KATOuf|Q$>~3(r*$%ry_FM95lXf@8i*{BsEa@TDn1+nU?ArY+Wat`dGQrW( z#v;VGp-x{w9_44~Fn^7NFZfP5+oVAZ%;6V?)t6SqeAcNw8=UBGLR|*!W$x7BohFS> z)DR|mi;FPiD)+2|6IeTGaw_F)eU9}f)-mw85(4&?C*aSXd%2ur){8p{A$GEA)fF5T-TT3hhm0Ie5WbZg0^6NhQ2CAA+tg< zUV;dPgh@zg9)WURi9BJ~)KeWqv=*%=w3@VdhU%=y*~aYIBM-;8K6O{9-y)VZ^urF! zcz+KpsIoc~)>~Q}_J_b#eKg!?GXfJWS32z1q^H8av)y0yWUHZSW9u>V*z;?v3{^J! zPl|Wq&W5lJ7T&9_W9FpK3{~rB)xG|f^RD9CGxu;@Hs{^WseG3MMUd^}V)!PVytkOv z> z20~16enZwyC+!Dciee4dV|_L$LxN=`{L@lf$8DzbA}h`sRlX;~X&+-{E6V#2%3Fl;TBEsD8||uiSvR(h z-^^tvfxAj$1-_|3@HGyelNF8SSt{#YDB(4!Yj6pEH)8xvFg@M!P!{|ySFtmWZ&-rT z3%;iYV^@o;sfLE;qZ2UPA9*n7W_ zi>s7XD)fcQ*59-qHYa@k{jdGnpv~rB{Q@&iU?gG`zU-v$*!9^;c?!z=@SxVuYp;6_ zo0GmMt;(sw9si>@d<6tmtT#OME`9OU;0GwRLB~Y8xB0`Zol+?*#YHWvvpX4{k~np| zQd?k%Q)fzyJr=F}q-?THx^eb6&N#zIXvagg=OG32IqUkB&?~GmDg8+*rGK6VK60~q zq-xo^AmCQ|SBdL$CdoqHOXiT95bK@NMaWrMn^r<1z@1vDg0$y!xjKE^*3GRBJ6- zc@n%CE!+-7T_S6FR1)5e#nVYcES*>n8YxqcS6V0hw;nVxl3Rs7;-EgXTApD0MVp|% zCcPS3bfcfFX&F}*>l63He=|1U-_HvfW$3Hr^sPsM*NA*wAyY*~>Yvskyrp=7@;qh^ zJ~p!a;hd_ovZ6*467KtnJjoIBjHh=!7+Ne(;!4pcCz%Ybl~}D~$CWbg7}mo7u-h3s zS5C{=Y;0Ff{8|6Uc=uk+eZ1jxzmgY&%Im|5f7Y)ANA@Z`LxL7}N?)C%*@ol5MZ{IA z&F#WcE+@lA2;;Jaub~}lv}zxlwwp8rX)+PYr98__qjD_8#J?Lda^3^0fk^|Z9| zl?~jKyIleh37yhbpt?IH$6)r;F|QqdH}V@?_cRdw7V)MO=S)a(ol<#7!S22f?9#wY zV7(+S3VK`WNoekok`hnJF#RcB`d`LN4-c!gO>4hjha%L$)J8Svl%~TU0)9wQHFW3v zO~g0aDXkj(J**fCH{n(~;W^U*YsmW)tqjt^HCH$E>C;VX|3llPv9|ewwN0nQBj;EP zAx1$v*AAv2=wyf$Z!D|;nsZ!E$5l1{k}!hL!17sWy0l^OkBl}`EA8WdCNEsd85g;X z@TK~ALvr>}*7}#Bcl{x(!qZLr)%tTrtmtaYAAb%|9nM7@CylT>g@NLw_-35p!>dUJ zm)9B|FtifKUV~bHmhOu}I~yC1@4b(7eF`GU!!DO&w5yQU?^s?(@SeZ8G?$Ea5oy0= zX|E#}eg_@;~M zQm;O6&ij7|d-u4euB>l-pGz(zTp}PMpbf+eC0e7XRq0G)2uGB*cxgMfPM-vIcwk6UVH7e*ZQu4#72@5eUs0MfldZL6Om>E>qGu?TJyDd*Wo%2xkKj) z?N25r-i8<;sc%Um*^F!I;|?z>CE^lS<79+I&#pGmxUDi1+df&EZ;v--uHR)+yLyVK zA2!kTSjRW&*4LRBToC(A~Z@Qe%`pEZdh3L&N#d{6r`4t?M6nju7YQxw;r-;M7cWR#*$ zJrwk~sq6@jQj1`p|6z*%JmZQ;&(HgSBAneyW}L;dp>JjKf1FJfP63~H*!4NpXL5OA z=e?94Ito%PiHhwF#P>XMGRAMEx%eg*1c0@E*I!+Z~*+O2t8Q_8Gm_I_(AGbg2t=VO6kMWKCGo%rR(=f4d~A`@Itx+=Yk5scy>ShJM(rXn0t1r zVgG^7oVtH&&(;`M>EzFwnA?s0Z*Ap4zp8X=-=&hWzMjXcJ(o)Md3qkVH|=ODhc1G0 z(Yj;pD@|d~R3M%+;ygt!C*D%2{zQMQeT^yl85&rFidVPxkWb=otoG6;o|M3pK3U{?2yL*3#TvagDSt_XSdIq484R z7_aQ>biA@fMo6{w`sV@Z5HT|FJiZ&b4@k+Y7^!&0!n3J3#h*T`MZ9^ctO4lho&2A)jUH@e||KM#Z6i94Or z9h{@2wn}GeUrEuk+mBpy-#z&H5oS^4<+}&pID$BZ%gn5CA{~jl2ea$xNK{LsK*xag zJtW>%+W!l&${CTlj6SK7Byni8=yNO(Bx<%sH?>LFQBmjlSsJ%cU3t| ziLhh$L0jxY&c6^pX{Bq{xhe~>tXXP0P6kT#)lquYkyzS9zr8$4Yd+2u>g{nidwV(h z%+RWo#^u*iZ!dv=6V^=mgJ&-nkULfZAFI!QSn@sh+P5r&_k|%Ex=VD&o@X$x{`jt^ z=a218FPfC6pYQt--+simR@1h2%l7K+c`vBV`(DU{buz5Ux_#ev`g}zou_N?OY2UXc z)t+xl9?QZBb?3IM$){0n$8)B8yxMTIyu;Xk+)g7OI326oMtv&_VsF;^$`Gx!TB>yq zPCtVFA0^$S7dhCr4E7zNEvQu7edT*xEikOSk6OO(5Vb|feH@%)bL`q{nsh$Z76spm z5YL?gJ^JRUBdInX+|w%2{8*xPLZT6x{R<5J@NBeLDSh~2TxpShyP()|rQwJL)PEYC zV><)tTdvlElIAA;*Kg2&9d~7*zeA#5A<=gn>G%H=H2?cD56_LRBXpc5Ltl%92Ynnt zpFLym>;Cf*PtP-lJl%h$egU_^7vORh?X86KjMg%+EgYD!hytd))-7@C|2OyDef8Yo zgO{zy5yFPAhqR$QHpag_I4kP)2S()r>n6AV9XO#xx7f|0U!FWH3Xgst(jDMMvZTYd z`d$ue7L|b2_Mm?uJT_JXFQ51-?JILr;c?vWzZ-O@j-^Ky=M2m1$dN@o*04g0Joxd! z(UXB~jJ%uM%+sv8h!-IGei0D~@bN=_l!D{e2sBmJupfDLn5V|U=KZ*T3C}mryKrLL)Q>xFjKS^uW3dGJ93X8TYL^ZHHdRKa>tgz z!umTbzqUx8FwX`3&kp_QX1sESy?vkf@=ZO6?#~oLhlByV&$BY@LFI1j)&~6JuZJO4 z=;S!6heTc9*r#OacQONUJDT^JSgcmDU0d!tIS@6aDlx^JW}j<@4r!0>s`hPRJ{pKh zu0k&Q+2%rfFEEq>Wn1RbC-*0&+4~b>&Asj|hK_+K?bgIXeAmF={YOb(p|1+eh zy?`C~a>NwHHY7gv0xX{NI=(s)7PSWr6w7lp?yPwVNKS~teZ;^!DH?ks&TW8ZiElJp zO(OC!0>u*FXa>eG2a|Caz8}1E6~3vYZ@)J1h^DJEaV{DDzh(*(DOO}8YVydn%$xCE zN3R^+GNt{kD@T9-R#S^~{q~ikPaba4!&7(LH5wsC9Mfy5Z7&CShH3CVm=4=oWblw_ zQM*2o#_L%V@6rROM`avXS%%DxdSm~6We=wug-1Be<yaHaC3f8e$$1yT7H`BKs$7ef$zF_1#j6 zG-{?%OezWU9=_{84*hSsmxgGKS4%a}AbQJ4DD{5-##<;K4Y07v8@gn7gZynqhk4g(EQKHFFiXqz)7_QhLY+( zz+|G_8{-X!M)Adwt$N}e+qmLBwB`jA@G2fBFL@QK8&5|>w$1cCXGf@M|BwG zDD|3O1IcED$AekAGULi*ScQ8bJk{uftIUhx9Z&7P|H1hyEiVykkmJZ0Ejp^2Iz&|f zJ?Q_B8l@{3=1X(AG(TkC& z(y^nINs|(_HKDeIjG0K3E!RKdn!QCgoVm zO{!yD_8ZW>njoP5m*<13<@Rq`XB&34hfK0gTEpLpwC%?bM?iZsG#fMrLL6th(YJIK z{r&nF7twrIbQWzHTwBjT4|w>rqdCp zeKCN%dGe~||Ep(51HX*fSkJz2OX;Ua4Vp2 zHWc1MrGAvQe#@Zl2#Yq%tfy^AtLL1d`uYg@4lIETa^D$}ZRV1G{~foKy9u@b%lWOSdz-1o#X6sEZJW&KtIyN#QTR^r zv}o^6LA%0SOB(Z}R@RE_te?ae+=om*-DqcM%rysYX(zasN>c&S6#fM_r0IYBi_lUI zE&UFBvt{u7VcODv9lo`tS!h{EZijgBS7`bFbCY&aABLcDeM<3QhT3*byOFsJ%>VE%43FxtvS+XB&8 zLk-=~7uaaz$N_G)_BimkX{3(^XOMFtJK)thV~zMUcJfb(6svu^y$z@0pw;ma?B3(d zU%=agF{qkEGxhxLf|eqy$Z@OmWILl-O;?p@q zVDoscqDvEC{`sNL9);M#yY^D_;&3~3=%3(aWDBCDTC~&v#P8)4*$#Pr^sqQU7oTr< z9lj``EXT*M8A_s%tNl3qW8X9}UCg|n-KPRdJACvkrvhZRxj{x9_Y@!rO;!^Fr8vyCdBU1c}eXGcPFjzkWQ$YI9U zfw=H522MWQG4Szn8XrDs^V$JcRygoC*`$(3Hs=lSGRwe{@sE@+^RomV=w&Crk~`s} z9bRcR0C8(1DymcFgvU@vns6DIHo#5Ky4(Ly{e8$g5X_)s^kz5jtnu+GTIVhPd+KQ( zd!kf!1IbmN&=^dM7jF;5m(UYi-kF)zEHGWo3a+a*zfa_ z%;((;;XO`IKNCjrF6njyP0XNg#5uZnr~Qxi_w9bW8kx71hzeKEzvy;iw-havbf?`J zU~XLh5!dXE>-TZZX-Qg-E11^p@|9ECKaP0q7u~a^>&qhs`?v1LrR(mI5?nV**B3?_ zaGeP4jkbip*^;+!*6{NYTEjv5%x#}H9xz3_g10BMj|0YUw8|vRE|)oIS?`UYj?00- z&>?do7s@ExJ-a+F>p$W<0n*LR=l60RHB)Z-3VpzvE6tQAmfqZ-ywI>i;2l)5DDihK zl`nQh1@@f|W4~ypo&v;s%B~U^zm!&xq zs4PJ~19*+4(m4Z3GsCja!$o=$_In7{-8-=NpjlU_m%ahACAAuvrMnLYW)PykSF}RD zADMU?{h9(Ok#@$kH2n12iS>K-#M146_Sh0*&D`v`U; zpVG`st0mthMith zv_HBxq|ujEn3J@eFCm9g@#eFTLo*DOSv;tJ3{)0o{?G1&K$yYVNG1Ka8}evwVB%P5 z4WW`KN;fTwucA`!EBCy>L=B!IvZ^4w#irtl-oG!a|Ephmn~uYaU6}1*{cbuo-`dk} zQ##-YX~#}e=P;FabpXwFj zPPzGRC8WyOoe-5u$d<7?A$paPg!wlljOh9RQQx3Xg2oc{FRsZ1@s@o;1|8XNaG!UkJs_}Z- zOYPM2Y-}7~Ye#f9WKs|)`oD~(`%QoA<5CbGWZ z9NxocYSI`)xx>!X30;BRV^WEl<3?osF(UJiv6sh(P1h{)xRV2H&VSrhrUmWg_m?aw zSppA|+zGI>jLXLsd&yJP{rftmYOR-lvKqt9}#cTCKA?TkC|v?gkzD|*nralUw{d2JTsI%=9N9>v_B z>C&VYitJsx<+IP;HY2KYvo`esX6|h>RGkLtN;^X)l{n2cQ~F+^$N-wdRC6p4dlw;A z4jA^XbBQe`nMc9@>I}u!!^!uHp}KtNUMpx)MHid;FY&e+-*)C}UGCdvbak%Q{@uM? z!JN};&$%^`qy;1g-@Gn{i z;7;+o91UH)+DB90tP&pVlX1#+TAMH+F+U~i>YdVxwMYf~QUZI9{`xCdsD_Jmc{)`_ zgfCS|ri3{j7IL6d-Y&r3aO8ATSlUixkul6U9sgfhr<z;rgG4KR1Uc- zMR+tRQ&8{uY{?8^`X!Pg@1CiTbs6;Gh;+sY`)<` zOzkAoHQgqhn|s_eD*6ZfRkGjXuZF$kR!x-+EABMhJw_q)&cJzbinFWLsoT*q zEvwOOLTL(G+8Wi~seRu+$r{ePReQl2FVjA-POK)E>3^<)fM$o{mSs)26SH1-a5pSo?)KQ z|5qR(Z~gqg?$O`ref`KU&ms@7^xmz{)9cNtuevrucUo4OkH1Sx3sO@s2Jm*~IM>=# z#+8C|M=D?9+fy*61*z;H`7?uGOd|Eh)w&e(r+F3fCgBgZhXYDA55HiUV<3v$!6Ld- z@r3+kPGB!&n%2SJ7PESOrjR0JgJXigBU4xM!h^!!F1uuM=+WWl47O9f{1c~o*?A;2 zx6ieSaY$!wyY=1CUSpKO#<)6~esaeF2b(nYx+h%aNfbV`7LhJm7P42)Cpy$dC4U(? z4jn^3u<(3_Pl!8ILgcwI`lkYXN4kwTeyPock@tb^LQ7Gg6sZn)mw$qB{8cK^-ZY5c zS=lrfd_z?jceQ#CeY>OSvn805YIWr!&G!or3)*gaUyJ#I)_GJc)oFi|HP}gJDX0aa zo#+&}lVnyxIE0BH%)lgbfxoMkBa+E)&e~5H2+qZcR3yTZC3^O zI<+GfnK~%O%qsI40opZDHm=DUCt`%Gbsm*E+`+o)vQpdY1k@H@+KFNv^5vx@+_pyhZcVc25!_rIvoSB(hTF zk@MK!s8bump?~+giYAkpKZ_AgXJd+ha%A%E@SD`bL4DQ4H>I|W(M~d}-}vYECT}vU zMDKE|WEDiGNrB{=gC5Nw4k~Lx+z|~eUR~Mxy;Aqr%dGB0;28(~?;PiB$WiJt+A@=9 z~n~G@c%z-eTnVaZh`dB_{S%T5zebZzb z>}FpwjdQ6Y*un0#N3Ym7hKb0JH07g#9m&s|xHBu8aLT3QEbzGLX0(Rmp`ahC9u_MU1mre!)ExdP*vgpjoG;D$!PD zN3b6DESfZ-;BmGt7TT{J=d=0Qy4W z3!ni%D%b+Fo#z8KVk)5LKN!i(6Roe9;?Cd{h<;o3wue(=ztX@tGHsg5+aLupg=0%d z>O7nZJ(5Ukb%%Mko2}9~V=SP9Xzn$jG|Rf4* z8?DH5v~iMn5RwMewV!|^%T43Y=)7Apb%Q0GZg9({5Xz7}Gjq@$13zzZqn9C}7OZUI zJzJ;XU8cY|t}l#f5wpORO!`l*Xy+#bfL4bzs}#mzuc&dJLXIImJ=2UBD5-8#U&2g; zM^vX64OEygcAS$DI7Gp7-j|0g6HmE$;RW|^gw0?Iu@opjbcw0E1 zGCT>K9Zat>g>e3qV&YN@uqfk4S)Hzg;`4crpC|^ebTH{O@7JqrEU147>b{F?9UDO$ zOT1)NN=c?y$we-s7%D-a50KPL7mfX_uGypcU2dOdCGc0BR?ENL#(oB)#3Cq9A;K{ku_5IRU4fG769X^m)Q;S~T{`L?ya!pp zY0o_|(g_qL;tAtlGp#rsbr7R&;&s%wgaC7Ak%X5T^v9ro?-rvl_Wxz1z9<@p;3a}? zQq(Ks9Qy>#;oF>1ul?3I*_mWCK*Mc<%(0PloeFs z=&BU>za#nKB&@J=K2MmRgI(9khmd1{ofFmsEpiW5?G(Dkg-~TH&?E1NNf zDlMWMlbjb|!C*4z4tA2G%^g*zbWF)Xmf*}u$Q8_k9;pFMbGooh$|xB0ZvdqW7faEW z@G5XBdh_n9PuE94?!4Ri2Y2(8(k&CEdWUhZ;T)j2zRp%?qcwfVoY_~Jtl!LCF>V5) z-oBrB-1;b|H*zWbwMMfyub9ko(I zHw*CgBFv#hnBR-x0}0#!JZX@gTnS&HP;XGxPz9nSQZRs6t_$htE8R-A%9L2JV3Outw##(}4Sg=lFUk5(AH=2EnPt9f!-_hO;N z&7icuLGxZFs1LB-_knS$+MW2p&OVMKdfuUS{OUq(U&6zx!S2Eju2f^iY%BiWwzr@K zKG|eA=7AREq2CaOTs5Tn$sXXh5btfJh!6DL1zUvTdUoS*(Wj7>Ax<%R!%8XNPjY57 z?%#>~u=J$1#|V?EVvs$g{pzFjHT3SZX4CWSkZy7^ZH=4X3j~(ow&IHgtIc~&`#brn zXeuxBn@|^udZ9Up4A4HFWh!X9Zx?8*7fGY>5L!&I+t5@YN4#k}<`iEQC`^pl+IM1; z>HxCeI#{o9B#^I02`>)wPF)^w%C<&2Yx1I;J$Z6x&y%aobOxy$-Ih#25MYt2-YYCY zPccCAWxXjQbT6*jP2b-*oSmh!O*qdWg9y7X3^rNJoY@}~ljJi63xq`&p=hZ#-7d;U zMsJxtGy-a8LPYdncV3DpyD^)XP9|OENCHk+X&)CB=A4MX%SD|{<3KC9aSqMFuHMO* zIkL&73v@Ie5w`{6b}x2E_>8JnccZk5$o8&iG6LtMqDfYfv`pE@vSZqh1D?y-UXuL} z*yn?Hf9ay*@)^d*Vfv&~=_yV97!*1-wo{u5r%d50B5(Gt+>v5yD<&1caMXd}`wa)h z^Ox05rMMUCcZ0AVbGAd2c*w=Y4D=LvG6m#M%_8piLN&76+MtZWB%-J${;D~&wA{%|1EMIEu zzr)0Nk6*+3Enjk(>S`1l<3q^vouT;FCGgss`f;y()i(i)9iQF!RF_N0I{QE{g;Y;wobHifi?vt=j^IRM>5L$5GTH@-&qy%v{XwAwwfY38m|e2>E;2g>p-2xkHlP@E_ z$ft@6mr^uK)|-dbs?UY<7TpS9@yQ*Wcl%J8nO<|=`{BRW(a5ICozWuJ32x@n)p#R& zlwwb3w=x+@5%xv-m#{B=ElvZyf4P}-hxvkAznSP4x@68t7F`63we2MIZd%XROiExJ zs$EGrla^79FtcwPq-i!<`o%CmS$lC7{Kq-((6z9Gz-O)?sjDX zd>Buk>J58Y{c6y!8sR6)FFr5s4NQO+T{qQ{c&z|CIxIIEy)it{m9EbsnSYTH6Fzjj z=(=B{mJzB4gtTvH+snoaTZ?1fI9;$q*f((xR*QQ&zX`n7!1;m;sK#yT*4uutvFUN* zxf>etIB3XG(2$!uaV+W$st6+zU@M+Yrb9NGKLifBxjZ--@wOu*2C;rB|-jb2H3>pgc$NTtN zcI!C022Z5pX#tPJYI>2y3QGHKh8YMN+JM`DNFnGSvKm{mZF@rJY|3A^n4*Lj>;$fQnFTWMuHkJ-Z+o`! zM`1Sv3WK+7_-$Z)@WXLrfraJ}pu11nt{s@SU4eUc%PPu+97G-l=X?$wE4GSgV=N(5 zB_>00z+SAtZ2KY@eLyBO6bFG)hm}x;I7wfuy51zK7D=v?f-lA?TYJ%-#=poe6zI=iFSJj3JY$jvL|!XC`8=B-LlP5M9+3SYAz$ zx+;bJ&CDWuv8+ONAn4y2i~vt#$A{5cj&n22sH zHCCu@?a4%Ra@d|M#aBw`1UoqPSE~Zb*J#hnD`T9L4vG zNlr>p*2)~;1XB0lvyb(@cb(BiK>`tFWNpyT1+HtjOHi2OXPDX$tAxt0F~(6Z%vy^Pt?Bf;v)~7D%u@SI817t0HONxtgz#E#HcA zsch)=qC&dzxZD23=o#1DwifKox{jb z`UcSd&h-yB{cX4qzZL%STMB;r+SP%XxQZNikIjFIp*;US+K(`?3)Ps-A(VZ+<(&=8)SeB^runYC;)laH?`>p7AVn)1B3_LVNR@P!A(o`) zQC%^Up>_}OwizZDUD_BOh<1^s`< z_42k9XtSn(G78+OOM!+>Dj1xV8iY|`yM?siM6tQ};X;z8?y9jdbEuV?VkF#X=^ST5 zjfJjydM z2VW7>7nlK~>0eSQfe7+2Tcz-2(spFj*ew`3?ZroJTMJ^%%w1!4k?#`7OWSP&wkK@0 z$n0onrDszL@mTR}wChj6+LTewD5Ld7Mlizwr0gb0S@xxLAm4{}9Xw>U?Be$>@U-kQ zvhsZAmI3pXo|$#^Mm!w#CdC`t4G9JAQAf2d5bo$kc2C4l%oHuQT2oqQb8%H6#ax+s zeq~~h?E}->9zs&v7a8xxX22NziKRw&=}P3&raUc}yvU=NdF<?CNRDl{u&jUsG6c=uV(#2! zOZAdvbnlY*fy^O0Nb`BnRX{G-T4|K-+dgE1y{N}-mPV}w{l|jFwvz#6^;j z_1nP7qEGu=3@oc`6Jp?jo6R`r$uQmT;WP(moTL8KQD{86hi}lT2;!_~0o4uV(0U*x z}ILFrWrozJ~l#-NfP!W>>0CLWtG)ft+pUefTXUH_6QGR2N_y> z(~6{Br!OW97V6s|)&5lDQE*AFgXKAWTT@7d412?s((He8cyp^vlDF($=2a>c=&Yb} zAuwu@_N98%?X>$~5vf=Kn;YfqPe;CUGk1E5+Du<`))aqRP*wQDde&QVO;?~_Yb_{R zTT@WCc5HUd9+f%ktoowNL6WYw9&tl&#I7S3NDp7T?V zcB3)b6aP=_4TwfdXNI~cTzH791bVmf+y!S5zO>56&vBlllTGuqc#AG6jJ)#O`?dh&He zI`k1Nb&xRO^NIGXo3?$dA1EI<1$EJj4UceicFHRf&rUB|C`&$jHZa#dXQLAP$33v} zt}C4|NbXE>>549}`m?9t5y4mKAk*#@L0zY_J|ghlLH}wg3j1qUsu1#1yHuE0v=h6j zeCxm6J%xL-zBzBTqVCAMVL*oCxj;f_56ULHFItQCX7zWv+>fHy8j58ovl26VqENPe zYvGwj$S}iJQ*PfGF14K%Li_YxrVmelZ>9UB^mU;b|H`PY2vVyJBNwBy9(JE7xa5u* zBy)IIV{v!U=LO}kSzTm_??*dEL3O&-T6Ch3_ua*^6-);2TYz0VajX!|_NsJr1yTcp zSKFwg{Ds!N;%+EjfxB0)hf1s|QaG$dGKU(ugMl_=Yq3@^VG;F%?qBK@0U8W+gODyz zC((Z2l?pk>a5YwI(SvSlQHI%S?ZMw3tK6AkmOFUo#YEl_8ohm&H+mCfhc1uJ0XiS( zJeY)e@CHZMN4iSM&aNxERQQTXd4^~?@OB^8wktQ{9bZX2g7LXS_$JWM&`3u~Mb}CO zQGh?YQ;Jq_{JFN`Z;LJ#AS14Lnza_qKO5pS%4g8H=Y;r-O8tLhKA#oo_}jr}ZN-=H zR&o(j@p{6&u$12`P^p9d+MeLre?!Jn``b!s#rO#`|2@%Kgf$6m)uXMo<^vvC*fH7@ zh%}AVop$esd|e={gmj%S?3V19lFZ93E56TGRS;)Z%zrPmDq3G?xS9~M=e^@oaum-6 z^DaZ-C_rGLWKi<5$f&T9L|Q{!D;B4te4tV@BmYJ)XIh|?zFk?K{7TyHcHB3#yrY?5fPcExLm?WPY@& z4uv^Z852K97I@_2qFA)_oRZBf!fKCQvjy02)GCfMTn$6VqE2eja#7|iuqC#tC&?Th zSYN`NVP3KnCk-q=X9reumAOvRDhn+hO9^IKW)>^Toyc~CT^Xo1>!C*|aJCVe0U?{` zsDJfPd|Ni~ARmGzkuyxWyi`!&-LE?-=Ol>e6PD^~RnVzb$X7!9rXhppi><9~h7qGM_qcv9GHrALD&pdC6hNigSICd=J{qew$fhWbeUUjCE zW6wPgxf%|n!(Ml|;C9ne3vJ zIu&)2u9MsF(RTyaom3+_W4`Sl?qu*Mi|j zI^*vL4*{7k zTQwWsNBt#%h}sqC;q`$C=h*38L~Vp)6j^u6Tj^??ieJ|bQJPx@_4S#{BI+KJw2&91 zd(C3#Th9o6;*dDe*<(q7jr!Fe6y#ZaJ_%%Ze9_DN{YWAB0jEE~Mblaj6_Od5;#v{3h zUiM%l(6T-5;q%~)(K z@xCycDy^Q(&Mwr0)|Gg(D7FQ(fGTJKNwDVsx1H950P`$3=Tz?md?xB1x78NM?J2{K zlX%PR%8W#41TP}fC4FzJJr>;dgh-}`xNS-6Zs;t#mu|A{$%Z`2XS^#%DK#dH*7;O# z+`K!V7$Mg_6Yu-?l_#>mE|ZLpZN&lDZ%+Q;Wso1%z(YA zm55Ymprrw^1!p9ycaQqZuOTOC+vu$EUmYwK3mohB`KAzS^Fzv)3i2nfifCtQ=Du}a1L0&xypFwJMel=&#PVg zWiLZ$U8ui0YQq`ky^MKYhR-AJ**Mi4_16LMoq8^OafJ5smcfOkbXB9aB6LTqnm*>e#Bn}E7M)#LhSwwWW*mks%h32J-GBY*^nCg3E+*~!9VB6 z;ry8SfbsrxJ*{X(&L@Ed_88+Qz2OVsO`&sU$xa`*VlTKSC$n!?D%VZV0i0QWk&Z~r z!svsnj`rHAK(>86&Rd?iZlLzw*|cN*J*OQ`WwTI1bP zjTh3|y=FS)N(RksQ@(I-vaybIXfxjg?A1}ucu0$+>`3ZYQElvx#f<#@b(v2q_2H_K zc=WM8f0L~!|6Cwd!U@|l`0M&bLL^qUQUBqg1|JD)zj}ZDM6A{?iDUPFM5Y9i`rpzT zzC--4%hYbS591l+h#hC*xpXYQ!*{An)4g=KP+I(ALs%iRPJRHV_H%&fTOtR2HOKwVWn_7lF#_m=`b?xp7B-u~mKchQ| ztDYz2Xp0s-|I>9%YgFr}y~@2e_L;yV+td;Z8BBcHY%;7bBfyXIab3`l0FQb0suPKUegcoN@iuHI1M@uwZdG69`?Q1Q8`SvhJWEC&WN z`f)2}g~p&c$mr;^*c0LG=L^`klYkVhtYy3(2QqT!-}g;m(9Qr);7)jT(D|*tZfHy{ zT54JfMCcL!!Rzn2BN6>h%W6Pb_2TPzlISy`H%3Z#AH&@k*zotmvqJ}-Ufh2l8|j>u z&Ah^R)x(+_=K*r8LVLu2xcS4JKwe%X)qcc(1-rCoFY=XK5CP*7Xbj z)3e?c`2Gw&;3W&jVad)0n%SIsXLA*Mg>?8b=^uq3v~N+kxV?<`$KiqEGE>ayjb0ru z*}imoYv?TY@+h^xT$iNsr@+=x(J_Cnt~`I4^&YNLvB%U`mSXHH(;5fu8Z*vI5HAUP zqH>O`LcW{R@lst7d4lnDi8!0y2Q2~^$>Cx84OKQ%*8I*y_)}P2p0%*fz=j3Q@jCb% zK25Q|%Xg6}pSt4*$rkDZadJSloPl<*)m~SQdT#_~uukd9^pg8&eeLPyL3^y;dI~yg zDvP_TNCowl%2-DGV#FVZGt>49d)#S7qC$37UsR=-be8Uw+T5#cE%5S1=HLF(_^rR) zWm{?^douf;*RfZOPf+=~5PMuo;R@#FCGbwSix)Py(>AQM;hmH{wkJ03F)iw(HPBnn zxtojk7Pk1G9{FJ{Nqre7vuge~D3jU`>hkq#Pr|DVxP^oE=pXabtVL^^i=BmaYsrkA z@O>Jcy~}j4oO<%n+0eJ1)}O3W->Cn4*HQngf2=oPFQwsU;%_XUhQEpY^w8ehNNtYv zoR{*Aq2*P`oKgS3qDbQMOpP-rWLO)pqiy#CP(g%b;ZeDpNgi9O-LM-Mzo_dg+N zy;gc7UCf1Lk9E`)Unoc{AwYhbR?_3m3>`AncbOt2nnj7s8- z`WQSPkM@lC&wM~-|ErKYqwqhWwjL9(s*+M7Yl-*R=8$1&&v#Ja7IOu1LM+TaO#wbI@41NdIt3#wCcxCD|KVu6u-*B znUW*+;9WUX?m$mnGJ;(2BvlS1@F^Epx{Q#YjNv|4q*Iq41#}u^9dXQFmV=(5a;yV! z&f_jBt75`g{hlTwmNrgX%Iv9Vnzr=zY*Jyo`T}ZuG(h*%uq2|t*9VWe)y_4Imx|%@ zbv|)vvgA9Gu+(_ul7P~Z2`Yi^(?aROD);$)1-}0s3B9@MqehbHM zhw@Lkt&gGC*ieniq=@w8^`C;{R*@6PX#n62wM_{7k$qx|Eg9xBxoY^d(u-1Q}$+czAEjly(mhzDQ1nGU=??j%QOZ;yGO=cCVTP*|_(5psq09 zu`kIkq=_;}?Qz#U#d}Sp|GcDi#kyTy+YGZ|@UGxc{O+CYuG65WupE1NuVzoYgx`dfS}qQFHm<*=JY zyx)V%B7Eg4NPHcv4Au893)R$y8TY`&l|q74?*Z`ned2A>IAmiSXj{5qPc)117>RWl ziM6CIpPoFuOY5P{%xRM(9=%R+EY6YbosT@+Td^}Fs`;{0y;BwmeOsrU3OrxUcq;=k z=XC6mBMnPXhfJ@1m0a=^KGmDLaM~-BW{fxs+Qcqh8Y~-nN6JB&j@EG$m}|zycxPRH zXzX|)%e_kY5&mH$Dh|CCL;dwPj&n`QskKV_uwgQ&JU1OHcL`m^>FAC6tHEJkiIhqW zyt$sNrz;YR{Z0wBr%0N6!Hpf!gnG@vP;dMR@dkhP9^lc7f2I*z+QM*~-@gOQ474iZgLm#Y$Eo=t`SMk;{^#ehDq@MZ-WN<&^9G&madr7L&j! ze*mZa9-I;joiH3V#6IWuqyDc)lFJxE(0_XLN%4@G5 zgU?V;eveIOWp&l|>in9=>k4(2L;31%C8QgjTd9J48KwFm`jo4@bU}$-1N^!mX~uYj zVuVqD7=@5Kxa|q*zeBvUhYuC5Z8Zq2js%n$2}_B$ceD;RD)79PB7QM5Qe4=xDWNZh z9W1ZgRfWjZAGVNGuge5{jRmM%99q#U*4hu2r{B1=z2|0k^KhL-SqRc4&_iH76j5LZ{$ zM|j>F#Xk_3V%*lJ(b)T9VmNOUN4_{HghxA z82p;iWtPMXp@s~u z3wr`+d=enU-trnewn%-r@K$2v(t?x)lO(&D53vk4-rgYCQWnClLA(oxsQ-%0XCpJr z#5)HldD{^4LG`8gg1^s@(>?BDQopTX1D*-zzX_P_G%j2f-ZXsuYWQ&69r1z4ZZ5$5 z^ol?u66Xm0o!=!qkDMAOec_1P>WwE}CG@D{z%TZ~^Eo&dUV!agpvx?zlojy8BpzT) z2x*%un$1~hr>CYV#KO&+(!yL%rA51@rV){uhnQMDkI#>8#5?JkMbQ#!gE=e4g}aG< z_rc9iru`^R7HScNfk-Sp)jn!4|F|yR%*{&|T(Gy(^NvkHcHS56l#=d5TAQQ(E8qbZ zc*N}kYhJ{dwbuggSJ^_*8j{cmz)AzGxNdB*HehS-p^yeR>Z|qr`qXijN zy{UonpKDe0TQWu)cR-Pl#-BS>l%BFhuEp41XSAGmA9Ow%4~Y9j(;~YkMwrn^(H~fC z{Q$93?C!QekCtD*)rq9Sw)3QRYkzz%1-alOnmGuMqvZN*A-iUkF zCcdR1)7}BbociAIdpK|5wXr)gvE=vzh){zU8*l~2uKL}taYQ@zsl_$*|3RCsSC?DM zf!<<-%trr!*MSu110FW5(}EUt&Lo=${y?i*r)ZD*R{%l#2dlPKo}nFfacuE%K~iVTZ@30~x)a3`ZMuwjBxkZF!c& zgq4XIdYxszWnLFEJI``ghR$NOW$5R^9=;#ntd<(v1buqfr2=NorM&(4D^L3j5#;;x zF4^KUm^n2TUBM;Gg8`jprzx^at>5oq*h?13H#V%@T9A@QQkZnMJtfbYcPWo}Gl1P0 z@?0bOFE20)QC|9|ZXlIjU>2dY^uOWSa{*;7lFC|?-7XK6l#V-=8VgDLO4Jn~j_s^2 zPgj6a4Lz1KZZ(kCtpyKH=K6~^<>yr?Fe1s0VP8K3Yjl1qM&t>!-p3A)OK(Q@nl)kE zmG?IFP1B=;WuZtTH4z0ngeg*^b|{qMQbMwi3OFo{gvFx)=z?1P3&8c{@=f;ygcrsq1xgVo{ti8vQT%g92 z;nI^DT6)2a((~Hd!$YO#rR1S>^!8qb_Lg40)QXv~>DHbm`q=g)-6Y*58%c}$Dm_;{ zZLJo+KY2C~j<{?RHM@(0&Yz_F**!`3qg#I;nSSHvb^n1(hZ`P{rF#x|A)ad+a$T* z&81!l#Cu`1RFc$_ga7U&#=DRghnSDqK3w)p0p^~Se)~7#ZdV3$TIN@be!O{HSEC{d zQsb2yXZo3~^8e9$Tq`qAekEy@Cf$5ho9{4<EH1KNho1vGa{8_>i^|N4>(LM?o(7*&~Rvv?jhsPlp4n)88sy4NJIj(pFz zn>cv4rE1S3lMmx)MlRc8&!v*JS(`nCTX#Y7d(&?OzS?Uh&bb+=Vcf;gZ$%erdDxGr zi||ycCVXPH=iAK(y3wlW18!R1ciabDS+}$@x;wvZtd%R<{@-mpoWNwff!GdYZ@+k< z^_QMK@xOW&*=zeZ#2^xU@cm6cCHx+qmm8wLk;?swUdIg5>rFm4@y^3eY^!)iSl-Pz z*CZe^qwm{I#lS6M(5B7sy!Tz)1uVF+_Pr>z>(>|2W9prKY8B0>oi5V0-sf7GkYW2b ztoEn4?_$Nu8qA%`DsHd?tb2`xS_cnD(y>QOmq?nBP{YprR=3kJcD7X~m zOOiBHezeq&PdH4q?al<6zw0RGg?$Ah$y3_1KB;Garu64l3n$%CM^!m7ElIX1jWq=^ zK>a7)?CY{RzG@#%N015AD+|!oL1TpP?-IIh$K`17bWKrnYdlu%KMYq}Q(IXfIwv~& zKCIf+!{e4k=X78N9hXyPQR~V|;qR{N=;d{96-6%77FY`+mYOn|VJ@Y5m|-%LOdbY4 zySzc`rCI`|PuFD{2J#YgDY0g-1720ftE6Os)Y-ovKIjByA~Ql zTuxl}E|i^*>^5<ltT~4U=@v6P=lC&qoflz;RlakN-zqZo7Q}ba2E+pO1s4hF&x*IBr@W z-MA7_fjLtD83Jk0$HsJ*3C9?{%JonZC(`E`EqsZlm;zIcG^+~HcDu*)qBA%3BE`M% zt_CXYI6-e=y-#1yOk#nK&o$~S3!oQvV_igS$A{rZ4J{LL!Y5C%z22a_uuG?x>M{vq zWiaA|W=ZZKyXi2nxnkRa)RwM8RFT3#vq-SJ<+~3D;tblVhgfHIkY1VLPQk zW^_S9ATv$LdGHP$kI170yMRe`^u3LUjt|RL?BVx>Eu|W?Y!3mKum`Yb7zw?VME$@$ z+znn)vccfzO`HJq6JWvZ#JVi)X>O-`)N5{fm+qFBwAA)0li%e^z`85kSfTYK?HbnM zk`_*wOtDFF%#F(?Gu>~Sco($x-;tTu;Ja-=${z`z1Wyl(@d?ZVss}I&IblvC=;UG* z&tVo3WK~QslVrNG1JyuU*?*yX*A2SgVgvg+12WGHmQ`-1NH1I8bTdICwSx zP&}edHqn{%mIgYcd860LzAdeE{R2s6tO1)g4t|!0`F))mPWo+O{D(u&Uu}YfkO4nA z*1R?)%kuJYzyA+m?C^x&FgSNxuEtp_usR~1$|+bjbEZhD(0`F^>jWvwpH9rsBkEMg z=r);|j28-KF3auv{+ScQ?0@Yl{3-rEPtT^0dX=kEXYb1TT6FbB$GCcxrsqvhK)ORy z1>QO3xR5V5(RI1aGye~JZvxlUwe64ZlaWaV2a1Rqh*1-FI3VEMN&-X^ z1rB9!=#+pXT3c}Fwe_}Dsco$t1e|aJr_Sw#R(rK-wY9ftYg^-tqUQJA2|=*-zP|hJ z{lCxq{Ci+!?Y+<5`|NpGYp=DLCOKqL$ctHfiOL6>0vN?dCT3O8XlPXO^lT9;Uc;V@ zc!(ZmUZmUuE@@(Octy}@U!1sJ+j6V1_CkJCT&PDoqT>%;(=nLm4(vs{LK~*$J#I60B*ZS*P1_rGbK_S! z=kVrR+eTH71NRfP@DomJ6x6+w^6uy${ktg?SQEI1VcTyEx*4Z;o#~1;k^WgttZDm= zsGD@>cKNO*gob^y1@~r!PivCT-!!^v)Q|}1zligCCl)=FvCjq#;_Ml(^|qk%k3>a` zf$R%|>fYa0k!ezIy24 zl(A=<)vJbuLJMQ;{^k&@s|*He_Gl4aIos@Cwl!;E3O)PpwZ3dr@5Ir%SmcVaM<9lv z(^QuH1I+UCQr?^$te=+>qJQ27Jzb#2t!t{pj?;0Y`0fT<1Wh+m$9TL8IcxZ@xE<7j zG^_0^a6>3kJ&4HPhHN0x2avH|^t9+};M?t-HHxO3j_g;kmz{rl>+Gs)hU^HV*Lsx> zEf%L<o*^iuD(L#J=+tM`zgqOEY8%2;qF^vZ*ou#`nq@bH;4JB z^zw#=9yQE;Wa+&`Fb{#bFDrp5{@wSnZd?l8nok(K)}u{(2%PDosdT4s9dfRU@N9CY zbhtC6YJBBohr3*VTc&#rUVjZAl$c1*S;!hq*i-hu8}2N-h`jZ|{YFzg9$oZTzju~c z9Mi85RA5&f{Wh*Aw2xyZWIUe&t#q`X1T90WOO$(sd4v8a#>vNwX54z`)O*~QY)IGH%)aY}ZUIHd}39?&OCo=xelXC{b= zFilK`0x|&%sBj0dH!qna2JOHUHn!J9NXv*X6*I(sk#fX9allsseb^0pP=*9Jzk!}v zu%Gt*y>+DibL5wrceOLW3-cGP?ps^UG_{T!c1IO%ilS=~nCl_w6Dv1%N$E1WhiOw* z6(>TiS78lB?6PJ(qz;KaquVw_3r<8DH4fdf)96S1n9sL*dVc!c%GIp;?}uWoEU@?$5scb-UPB2958 zZ6ZjZICXF6=m>SZiKfK|w@dumnihsQ9KPFyJD|&Bt9T@xap`=EJv->&?dMraFQt;m zIK?3%{f_;}ti`SNp5o(uK#aQqSwTu4-CE8>NKfv#>5E%Ag^iT=-H)i$#ShKTy>Y?Qqe=&wqBp%w zic(myI>(50wFz#+a9609-WB?A#LrDsM+Lp{U_CUt#_EavC5I{G4Dz=QeL%W{N{}@; zT+(BNkdXz+YPeI19wcsN`RK-vCXgziQbd>T;H;1?Wi)8P&?1C0jcsNFcInd4uR>O}T)K4d!a*jDuDoLs#wLv1WFQf{ z>Z`8##$dO4ZPT?#?AWedgQhfgBshiJaOZ&_QGI!P`nHxUm(qeU^M^!^+J;zi`lSVf zdU*-6`<3dJ8`rM{3lpKgCiaCe+&Jaydr5!|3FC91<+7dA3UHsU*rTSU`SM%Xm#Ht` zOnV;ZxCLRn-BaLCR}F=N7I8Nwd|f>gUwiEZOQ$cYuDt+?$mYwrC6p&C5+A`XU7kf( zmG;u*QbCinB`|P?a}NMLOVZA2p6H>6J6J7F_5c?em)ypRDc{zAz$aHOlXVE1${F;*PO0=b7%_x`Y$U z%R67K;A0vUCQtL`3yi1HEQU5)@rC0hOl(SGp8rcYDc)0;E{;Av}>xb(OaOC80 zoy}MYiK&Bmd`N|x#wybP9OD$7Fr)GD?i)(M(N7t z0W6&#<&Z?DBY{`70wcj5=%Vs&oDS(TSH0w+Ua-!xdcHU<(R0;{$SFNByUFT4O)HjD z{cm&!va&gy4PKq06Nh!hI(!1g{=eT9y?COoQIc3szn#toNE7QLoF*F2fQW95(ibzJ zd6Du8Lh0^cC_B_MV%*7X)zVd6&uxILjOQ)kl`dM*>P?V}6Rz*xvxiAoNh0>*#xtt> z5E{gxKTotu4vmREP1*AYL{R-y!PM6(dXE+AB32MbWv)Ni5ISENMoVxdxT$LSh6`Rq z2-#lfEYLD>qSZ9TeMhbYg+vktS`9|KghqKvkG;b6(pbt5@rV^d<{>uGbM-8nPm3VN zN5{xEW3FWodv_x1tBd$fh>$#5Z?d;FFd`X?`$paLG_@TFVxW)huDK}H&oBbKP~k=PWyl>M^7L$)3n2M*0um9kNRt-uWVb( z*NJ~|#p!JG5mswYAJH|7$JtySA38zVmx6GqSB^ zD#3Lg(b)etEpzDquw@3K&9TmwDb>vn`LgN$+CFyM)mA&Btp>EVW>04rVz)Mbhjw}z z>(twTeSr3Dc2|n4&0@?-4un+ja+A0DOtZ|on%fO2oP^$JrryF@H$5?$)0+XS|7ZX>w9_){CDXUW}-WRRgwtEkZA>!2i%a%u)Q#U z`~yCPc8O4gUC-OWRi-erPd(LMN)+^#r}e?V1d+bMaXYwR@EttB;h{G)xIzo*9OxXo znfUi?-vmtyVP&PwS`wDHra8&KtXbS6DJR>Q}A1bvs|2bQ8QOChWT76ri~e z9^}@PDkj(9uNK(t|4OTwy1U)>!Y#r?$HDm!;W#*jqCQY z_>rZ}Nd^5OV+5_1bNcIhO1d6Di~g&%%Y<@$uR6I&?lk$!x7p+-m+O3a_{c@gN$h#s zRLRKtoi*Rvy6N^JZ+%S$b5PH4eY~WHi8Z4s0r8qjK22RdnkbZ0KwB1ayE z-=^{&{%T6egU(Ux`zURO@^!0k`ubxZirH^#eRf_|d5<9ZRcrcN_1VU%QtT0-E0eC? zdt>EsN=#|BfjaSV8U|-7Fl)}dRaL$nHX5d?G)6ZRr$#WVX^6dL_r2KDk)x{eZuNR2 z^`~|E4XUT{yS{F$^WX7!wNS?lUKE5?i8odSD#|6qEZ1ZWMk>OuG~KfK>%dE-bC!1C z_$62Yh3mloPx=1QK)Z*exhX1A)f73O>AN_DL`-h<2Ni;ils1LWC-$>sO8BUz-1+`; zK|FeU0_nHAU2RSa3Dfr~$Tli!(|z+m!Vc+J7qy#&4bTT;rx$+n{`MS` zQm~_^iAvcks<&c4PEV^{b13l*KbjY`C?8pOY2>jpl7M5L@>o6Bw#puUPSKjC3${M3 zY5LwaMcVneVU7k;?1{n`px=nDOfKFe^6YG^#O-~s3Wph7bUDh1p2w^zm77!Qs>+2| zWkpoajj)l*j1&8yn;V?p_Q&~W+@me8(r25eb5-DjYwP^&RQm_BlyY ze^bx?ktYU>{z(0$G#odTXX#Ih!pvcM(Qz(>p?rusz0oj7r6>JA!YO1|`Oz9458b!= z?1Q$@*$hs2ZA;eR93~`sBaSroj9?lAAyKQ+SKHh>pto&)W}@2CxLWFq9b1ao54$%a zf9-iCWvvSGJCI*Rkyq`&(Em-{aQMu^9Vo*O=M=d90gz>O8*M zNz+2xy2St3c<8J4JvSTWHyNw8OQ)C@1y>8WP1i{JG;W$^04PCfIg!crfmG&(tSB$q z@sgDG11V%`k};FX4U9b7KqYdRt+SC6oG-+A7`pORHIJ;LxqeZ6@t8~NtN`u!yscA8 zZB|veOz+xl;_grbvtDMHUQI`3+NKqE!mDX75DMuXu~nrT%Ej8vrJvd;U5q}A-NE<| zh&bhMm-=}$QoZ)fkQE$mS%96Zz20QsuK9@x1!%%>OOeG+AiZJ#K`_Z<*kWjPUh)$o zS$r$`TGg$k*OuH$zP|a^((BLG{n}*TN8=UPrGl7hSDerRE$mX=j1?HpzJaepq9ou~ z2ot0U-EjM@w25K6LApawPjSNthD8k2S3NWG?v0fg|C*fr-~Rs+Lu zc0FR_C6Q=%{dYn1{_|d=nRQ*D>pENCRZn>(Ea;2h)>m;JClw|`cgVWdcQ;Kpjp`n% z|HT%pn{M*1uHtT;3@}X$+RS-3^)ZDeW&~An0s3=IA@G;34^0fve`OPZ3L?~hX>-LP za>kLZ7uC~vGV~wWggDvSN?*WPCMts!O2>Rn0LHcn^#ic4dd;D5&5otSKKu3}Xs{EU z*nT0^uk#7I=DB-gxKn4yFOB;+dh(8H`?+&t?%inTdT5#B8}MPyLc%>I1k&wvWfV_) z#%k28Ot`ueJ?+Qi*P3C0X4C8VQMgTItD01MD6Q?#&Q)Z^Q|m2tVX0c>bP8 zI!l>j1E5!>WiN-M3u=gw(fy688q6pu1seGz=tL?d|V&XYXOz1j{(p|h$=pFV)-*M-KQgYT|-s|GrEzuI{gm#Ud zzBF+iJ?n-c745xQ+YP&3x-X2WTW%%;d~osveq)yF!%SzJD=L~Knf1Y@fS%p;fhN*# zgLzpsG=4S>Fa^xdGN(O~R!3COjdo5DF2kJLV?OQxVtYt~QNCYEX4F#`30mF&6ZCh) z4A5^vn`Va0(KCZK>A7kW@%Pimnyt~aJZW!e#O|bv-Crnf`WC*w)o(Hjs$&b}=Ant$ z%~0C^Lf4uTeH8S?xhc@}hBf|b$Pz8xCWNMADJe|ZXf7Nr1eZTphxOyCX}xN$y<=Hc z&tH9~q@S*=UIdTLIsnSnw4jHPwnwssr%g#+Bd(C_1>Y?F7L7e=3KQ}QUi8Ty^=uQb!|gC2l3`fy{k zNpG%fCJp}0K9B~D$6Y3@U!Nv_aoBMh2jv3_8*wKcZlW|cW)ASDg+D7YurjypeS=7NPai`<^ zInR?AGeV9vKN%h4e23ld;IX>>m~X>OxbsNRrD;F`#;BJEQJ3>C9J3oYT?fkx_J3pt5 z?)*;Co!_wUaZ?WQ6Jzfu)a!H{c#SXHq}6ks`#$=HG}67_73bcs8hgKYvG?1Iz28=h z9j?9Kll62bKxbRmdb}s38?A=BHi> z9jFklb4u;^Hn@?pgAckkrmL}_-XvmQz4w%cpn~Kl>I&;f{{!`u+D|l+bGUaPJOt7? zAXp(E#QsIy+qi2W??=&Rq`xQl+rg)Z-2e`~=-e5|iqjKnYR+&$!;pv`ctd&j?l&&? zG%ovsR#=ei!o;rqjgNcp;@iD*hfA*;jr6%vg%m}*Qnl<}s_en-LKP%yJ%sj&u?@is zfn#HdJ(dkhBp3WaKk>tD14IT;6JICAB-!I7k?vXO#vI!Osa2i%q?4Oh&D;`PIYg!R zac7FpEx~PQ-S=Ii9{*Fw$WU+=o3Ho2^~?>Amca9H*Wx7Gr58A_lOf=?N-jBAE3~-3 zz^N~XlnRDtCr%H3wWhYE+3o>3&%~dEiKIn(<8`NRk%-_Vj}@|fufw;rG$&Dzp#Djs z{thZqdsZJ)6P#)w+Gt9rS-Juv5{bfY*-HAtm0_{+r0zJyeVr&rH?80VUz9;&@1}K8 zumR~{WH0MT)VW6Z9e2hisB?Tx^F;=z?W?Tx3^8(&j6B?B`pSI3L%kx!eKBh69ei7a z_GMUshv57@)z~cfd>LZ>*s9feA|LQ+m$vu^B>u!C;;z%XuO(5R&U{1*K7TEVT;6N- zz`Z@bBK1!~^@TawG(&QxGT7-EGMm}MxV#nv}22|NsmuC&~=d%Du1AuYalpLM2%VNj-V zxt~9qH2b6=!H}r_$wR%NMX+Y$1z%<(_kr#;-J~zl8y~I4Nh*N>Zpnc~&z3_yZ)_f?qT$O4@>n^ccR~ZcS_R;<%(c;V3aoglA!BtukLLt7Mp!)n} zE(%}IPF&xk9Cd%=I(WvhXek;=i|>^ct)+{Wk`Va$3a;x(!Ei(3hrzm^h%Ny--J!MA zCefY$K1kR6eM-`n&+DLDE9ZibFOyJZ)9HS)$#iRMYF(49QTK#RFw~G31GyBS3j zjbv2*OxO*@T0WOAEOPl6Yvkv-Ocz(wkc$gz$;I+jDH90Ic^%>FRTV7|135UhDf zADBMA%Veue@+jq(HglGT;0QLJL{Qd$^urWD9+J4BzQdCdrO29LyqF0Gr5tAds z1LX9jq+Y#T&ThB%FNolk!SCQ^vHsxMk@&uOt$tD>K zi_Cn!T-L5_x4ld^@rA{*G~-OXE^2e-O3b-AvK&J(;(;^%@4e#r;#}U4V=Sa0NOar~ zmK_p5h>SHXki`s=#jr7PWM+I!Y;^2EBN;Jb1e6}Sl95PblhOK#q6O*vX^YjFWV~!& zA#E=w78E;kMBnF@rMZ<|lfP+V**T!7EyC=@CM8*9#+)b3^WoLuuPQ*no^ zjxiJ!%L}+GMvV+eIz9id3o#rc@|7{VE<#UX=)GJ9HH7+S*)Yzo9OmkzB zWMeg2xff0Je45$^{@g3xvBo^UaDmKZG!!6^yXh+n8Tu%<(G8M}5kQO-31tFRw_(&-9{}V*V+w)fal3^&CLukHygM3X6)#yyBc$qzHK^B-sTe$nYG(&zwne z3-FNwl9M%y3|CPQIPA>qbq;$os*D4zBm_d~~zqg8}l0?;ubX|rpb$;=g#Y=8-n z510#h98d~a3@8Wq&K8sBfS(7n@9#8D@vAt737~OWUUS9S{+o;6B4i_gzITspI&)#3 zvpz}+XqO|C4d!BV{w$?ZNfc=93Y>vsXU?SEJI^r7C>uW~6o*Wy1Duiy3;9CW-Os3Z zkSD|+@HQc5fZgA?Xnh@n+8Y?$mH{eBRMe2DD3$Y2N)2oeo zZ-u`BQ+Un=&95LvzC8UEm&fgX~-nWl%JcmT}zJhchnHCo`I~UN0=cXGwKZ_3oH~RrzK?`0Y(GT%8|ozE{4^i1Fht ze|LSj>&IPRv^zNK&t0GH`gPSeZ5s4>Y&+IJfJXszoH&d=P1iuWAu2N+0g6=lhPmch zhGNX>tTM}-|G+oV&Nnl24YSC&Ht*>7V~L@#&^*@|m1~??>^2wDdC_etDlRm%vtvBu zi;N_j&&Q}#(i*PK<_g(nEppDVuDI`ayx*()Gj0A#ic1Rf+df2cj0J|mVpm+PW>*xg zmbP>n^K+v3nNfvUmbiSkcW?!niAiw*naLN(G|*NZXbCEmkyrX5$7A{MbQD= zmE1O)%Ws>t2=ivaeO_E{_j_zt(f2~T9PjtqmIS`oJky-*Y`Cr8Xz$V*ti4nF^w)t3!wmN$c>&ojj}}q6!Upt=Vt0xxUh7ZC~p5JGOewZy!yorQ@r1`QlqWv@hj0 zw|2R?&+WeW+<7SEiw$V#F61iXRy9L z-)(fKq0R38M4Lm0wA|Ff=OMq_xOM!g~ z#m>EY7_bQTIAFSu*S6Zn0DHqe1=t7J25~?@($@o+(h^u;>Q4hq(>ETN?)9eu z2LaCn?hZU3I2d>ZF!lc;Fpc*OU>ff(;2yvq1BU>g1E%G04VZq%yVw=3Com1C0;b_O zU>a^3umZRUn1*`>n1*`|n0{{`a8KZmfO`R-1nv#|E3gb$RN{&^0GP(x8<@r$15D%9 z0n>P=0n>PSU>dI#I23p(a2W7vV7emT0Ne+-2AGCF3fvd?1aLp#pMk@HZv)ft(z&ki z4*}Eg;lMQfa9|pKA}|e~3rxc=0;b{D0gHh*0n>E94NTL$ADD*w449_-EHF*?C19Fv z2XKGjQ7B(pZ+EQtX?@P@#yVstAzJ|L_bSmbEXgX!Bhg+lUa?+rUh!T7y#{$Dcnzjo zm8^m+Yzty@+U<(Wd$5)PBmrnVQ-SGPCcW5LPy~uZ37I0pp3%9NQRK*FQ)SrfaCB!w zZPVyGCuM`$@@D;U&#fPbii$!$&xx(>d?^kf|2Rt%0Ng%bikw5+6xqncp>0G_q{wkm zoNkfn;{~tWvEJL|O=v)V01pG&hi@ga$cE57fr97+FDG>c#1-mXM5gj3GLvDhQD(?3 zG#YXi;Kmf!zRtOvz|IeOET;RfnXMZ_IZ4em8j6gxOq?6QNp#)eq@y_fVT(p7J2Jk| z%e@~=b5Syid08Q!o0~;q8mFJN=e+Y5oaLmU9F_;p?ldM-^Y2M z4?5=4?Sa1E{}I{@fR;n2Pky`C4fvRl!2tR``BT?>g;}LAlX30lUu-C`amRjFmTkxd zg|(2+BNxlol8euQI{M-k_?6|i^G>tVN|>c}H2D2HR&l%fIer{;z9vG}0_b;Ng5BBP zvUw0Z+FFwt*NT?7^FlGrPxRk0h5~4~lCNChKEpuxYcu-lGuVfEI2=MO`_qg00<Y@a`#E}ci;p-`#f$rO-LSKEr6!=-X4DiO5|Q!rFr`1Yqa&V zE`OAM>ZGNf8;qKDJ=D+QZ%|Hv_Ho^jxqZ0GX7hQ_S^@3-Xt7qDg(PEO%*|aO%cfRj zrIiThq}e2zi=58X7sv!=5Hl3fW=csl%jWXA#YJd)h8$Y??odU=hT@VU^jTNUx>Hg5 zJ?h4Wv;x|vL6K`F8HMHobg=j~<6!zkxP}0d%o}o@Z&BaaeV*$`rS176@($P!XdiE? zp#*!LAt=u~vU{b4@yiZc{LdI00qy-~nTtU}95Mty7(a%{GBXSKB9L*=JHzLh%&2Pg zYsKRw{bj=+mSv91y3du&MKj<8!oY+<2?+^<2P27t@%h1sdN6_wh7%p1n|q^X+UWI< z-XP@QCD6_L`&GScrTNopaVZA4eN*NbRcpv}cLqmBXeHbLY(E5~Q{5WkANkn^@O2&>K7o|%Z zfgn?nd(1ZP%DE7{n5>^t1lYtT2Nzx#F)dzk>Aj0zWAX&=)WOpaf8!Q4H`vz!1QpQZe}i@OQuufa?GO+@*l7 zfbIZ?Z3!-C)LOH-pMjP_+cJTW_hj%;=AC8MlUn9T1vJ0@2 z{JMV;`E}E7a<9kJ^4j6akZ%C(>rGD=j;#;%Q|`-hack{XEFFu{vqhC?|`N| z$0(av02=IHTon!?L!JR8VqJ@O#TXx)ti?pkBzwqjJ7>65(n|MsP+!A9km@_8Cxrb<++b~yo`Yd#&$+-V(*f|;(LCjfUn=-PWwKNA6T-TMfz9zfTwdBBeW=(= z=;nGK9TR0j4shP&#J&++9K>K#kT1~Rn>Ktn-mIX!5mHsTu!dNYKm`iCm+`CI2 z1GEo^WI7-EzMG68NdWr(;n3gK=~TQcZGFEo%=JFqSS-sfDJ(SR7dv^F1P%}Sb~XHe z2x$N9_F-w>G0jp2C-}*CWQ+Vejtd$EL#6`ShaV44RFac*;%rtfV=ODf0=LMWFS68C zM69?c{8bJGWCgmH@xN>l?p~z)GkT+V>wXI!xXiPLs{gn)v0B9eFn+c8u4|Z$V z;o#S4c3H89y7!z!*v){W_s5Z2l0q^@P9$SBX=FGVM$*v4)ADm#_ishcBNo3_ET?OI zcWk&jl>8%}A>x5Z51=*~Ug=y4ps+YdahIV)qHQvy}Spa6Ix#K)vJPJL_5s9S^%)?grm3 zslVbADp1$AvKD4;5^lJyI9X=0wj6Z%`N{gjxhUBiQ<{laz02hv4L@1`{!$;`VCmmq za=Anu>$2kYF}DK0?;YrJa5(1NUVD43JKyFshIE5Yhfx3p%x?qJeD?x%C6P2cg30SR z4u1mUcsSq)XhJ)QhTOy%jBG#!Xh9S3#sXXgJeU0JcoHG6fm1XU{_lb&RvF&`+2(yF`-a|^%$xC7v}r49~NZnOKdp~^tJ zRe70tGrf*IEeGdkS&!}4X#?0MH|I`*ssA8Sc$h7dG3VWu%!m6yrLWHJg zkNpFs`|nMxgD6BX$DSCghzlWv>(DS;J=g9_?z7MevBMgJa z#W<%n(PshN;1fF;%G^; zPqN2)#!87t1w)z%BVg7rXM_!Jd#f7W5ULs~q$=xcVwHMJXXJ)ak*&U}hPMctk|tF( zY{C0Vq00I?qf&1as~VIKDI|&prG3PxwB5>uOQ7q6U8C+3Bq8a(;{`WWyOq{AgbII^ zwSrM;e0@|`Mg)n)?4#0dDB+300@WveL^VakD0VA%E2pMvSb=(i7sCaWjZM{a1;fT? zaGbfUEJmvi@(_5bInF8=!KnRHM|-qb0yytzZ*I61(l;FXfKqJ_sF10IYBN-pko#}Eq58^K6n{a*C>5k27%hqw zM0$i_GOv-S+%3@6@>+O1>eG-aRucR4NniLe>Z`Gzcyv--W!HGrEfZ9URGCx8crD!| zj{G=mdd6t4%R@i%7-sE z>pja>%LMcgjycYM%MS>el~Kb8*dzSo!p@pvMWZEc-xr$vJzwvXdB1O|v`~MS`Q!XY zffIwy1g+(N3Ys-7T2aC0m%n$D*ULM;-pP93aDMF?AFXKO zSMgiLn5^C5=yk$xNWHMz|HMUXt@ zG^rHS?+)F++^az;;Ov>6%ASlEP_-}dKt<~KIw{Pf6|d|b{9Mic%a;1O3zp?xgUY?x z)m|$sA8Wm})|xLY2Q3xKm#Sq7MmdQv6}~FW@XBow-m}q!Dp)CB_%H z_f-}1R=y}>+5Qi9r+BU5D!B@7*`7IRSNIHWG1pL`=DMl!(ugumtx<0hX(Z}5MJjcL zx2l>S?dPGY;jQ?7UPdUphO;{3Y($)0&@a=7r*b+G1$R=Fr1poc50{7$Lcn=q))R5U zC^}P2A{Y1qv|!IA$r<0N4_nuW4y(I}#cG9k`9no)k>@iHUGX?i#*}qqYIme_D+0AU z)3`AYWpER@bS{m{;F7q!vUwS4oPX*9{I*!a*e51wxx_MWHjSIkJ-H}_i!ZCNuHd}6 zc&mu@<_1~2v3j)GZ>v`IqGj z&I$p`X=B(j?R%DgShicLEEOo_b;_Sf!_(v~Ml~uGSXse)E^p#XEMv;DHAYK-g(!10 ziHp-LNie;pwyI++WzQbUPtw>e{VW26HgA*jP5dGC%_VY+wdUN@eySKghc8oKwxpJE zd@5hWFIFE`)8hU%pZY%c^p$9TexUW6e1ZBg%LI{tRliXw8XZ<1xEj}QtljPz^lV@k zcC|++ueXd{BCysFjXHEkszr-$o!F-4v-pX87xf}FqfXLr2YLpL5k>onmN)PLtUv`V zAdw6wXNAb|mwcjT`O>*7NNu{Mn@Yr6W~^9gV;VLu(qJyxi237f4;9%gQN5=wT`6UU ziNEATkw-W|q?DDiOO~wR|NgY^N}?_i8mcLgm#N?9k}Po+nKM1n zyq@pFhpELHt-1@NUGkP+6VFp>ay_4; zIHJC_gw)1bJXNdt6U)+hk>;@av~L&84yVIv73x}nYN(>2LaeI92uEAx*hmq(a+y|L zrp~Z@?X*nC_&PpA$Oc4qwNB(E z>Jnzk-r1gF?%mfq#6GSF*B6j$* zmHhZ;1nPyF9V=J+Hu8*$=7T63Fkc+>Yk*yZ)D-Z;$_U1f^1!udt)hLE+Zo?;yi~Q3 zr}e0=5x^wo65(?N-!Ox&zDm=ySb?omepstbODgkcQ#n1`!Z#?@=&$~%3)7NX-zMQL z!P|nix1HI}oB%-o1f0;SSrh=gTRC#kf#r+T1*rl})ZT0jwsPH7L^g07(uEb1An%GKij~cZQ#!~@MjzNzupEY zo!>!R^nXhGKeNrn6QI0+PSqXh|8dpE)bYPV|L<-2Pqc+QQ4c2dx8n$SfQo$o^BcGS z9Pi-%Pybi;j$DBW|DSRN{=M{niStZva`-Y(_9u|PfJb0`ASb}Z2N-OhOv>#U;02VC zspL^o`aoWQp3Ej=L>tHt!ctO3E=b>zzDY`m!|xQ?$rPCTe(_i7K0$BsOXLmluM)<- zPdvzDuVm1z8sZSHQ+C_t;^C}Q9@*yR5qnwP;3TNoaWRx>B z{XJMsf8%zJN=*R|=+*6`QpY+mgP8rCF^o_3P|>(w+4stBHR7YO38E-JW}UK*yc^}_ z*<5YMvpGQi2zO-SKjn_3{~mXQ?zR6gha|flhh(yoLo)Rba7d!tI3!)191`#kGrEF9 zVrb)#Wd0KlN!NeMA*mznIV6;m5%#~#$uK*)G~*x0rJ2>nr5XPKE)Aosw$Dmqz$2+f z3;VqmL%DbRoIJ%n_JY(Kpw0S$1JYdY*xOJmSJhb7SSXG63(H1JJSzqlMY&(Ocg~c2 zaq^_Ri~#nxd=xo)!uT{f;;7;O0c!q6ehvQ~e*;{Z4}O;`!-Fd`YSLrB!ozu?X^-N&6NdLVbEsRMVW=wIf}G%@#cXVSr)A=_NsnTh{| zJ2T5FCwHa<+?hvOxif_|lsl98Uvg&_v~g#iYUR#Y9+~$*?##UVxHFHC zlC&qlojFj`o;x#d%7WkJ&RFi_&dlq;omuc-aAy`gfIBlC+?i4*ccv8FnduMY&SbW6 zXD0m1+?j<=?#$v=?#$9Z#GR?G`F-w8`5)xYyvMk>GwJ^lcP63wzr&qLsQ#aDXI`)V z*SRw*{sY{Z*Q@_1cjoo#|B5^Fdi8&bJCn8jx7?X=+y9f?nd+MV1b623>fh(iO#P2= zXR2%d=iHf+r%ew6;58%!`mo~Qb?btugomoOeU^CnLG zB00}~>`|&tTvWYBw`A0_e|hAPMX*S!_`(vQ+(H(q&uh+$j%W^Rl|gH#tx$V&JID*u zYAi!=CS9!7tPpd;<(n+pMQY6xD;8Tw&1rF^MWi|mZrl+yQ+|X?UNTctVo6%Yl@0ex z(!{BB>fye9Rg2XJW4HJ+;58mrH`Me{mBqd38?9nAmNM-o0bGgFClY6fY8t_BWK;r9 z5XEo|i!%l|MKQcov;;2NLtOW$hFB{X7`1^97bWyka{+7xAI7dzdhDd3H}YFZ1<#c2 z3E^-~f$kwZb~0=sf2{i2{yBSInPydsgRUJo?{#7C!`@c@JQ1n0#T%zY!fhdUVecL9 z8h#pcmU*goH||A|SpBFtGN|Ohg*}npr$sM@2sjbW9uPG>gLa5-h%HlB_y&Try3AK$&op@_Jq2aODrSb=bfv^?IExYm-6)jt3DX=WU*_DDNJ1p6j z5_WUi>lyn~-<`6HziOF)RrHsdW13x-Pab;8vxJebeT8EWe6c6jJKR?sHo&hU>~l_Q ziGi;z)3#1|`;je^)+rOJEBP2cm8-PWvi>-aacb9N{IYN@&Y=`qrua_a6U%7)CG5LX zKE*i`-J);!an@3G4NhLr7V*ihYW~UPoA@0THMdTAm07#byKaJ47QYevVwWwzYa>7E zp^-t$0)Gf<3hJi_R9Nxu0NKHR!)rAui^e>&&5~sqz3k}~4Hc76mh@yoEnA&d1Ae(P zExvJl68uV3af%+SKOe@Q;u^U_;42fJyipNk|S)(mL%z*vqsJv6>|fl{lfNW-umL zHC27bqsMZSa`rrMPein62S=&z_{=_$uky9E3O#MIeo z?#x@;%%!N8rD#*1?ApjLYl}Zvl}OWswD_rVcz>L0pfUTYGI$v^`w`YtwTAa+rQmIQ zEUimzGrRIkar)w9ZNX}Yvu=S$`n0tq&Xdv< zNG0y>wlZGq632>|h$XlI2p7j-y6~2L_!Ivc~{Kk_E{~W4*$*^F^5JNE)AAYdh zaNhlL!wNH1DrkoAjZ?nbX@THo7W6R|5=}uN)yqgmm*fL-iKb*0WT6Vk`0QecpwA_V z#%wZLGmhwzIHE~SgAC48GJ4`Tz!;*Lm`u`>QptF21{pVg5=l(b5{Si+j1nrFm_sEz zp`Oo7(jml{j`$&JN-`!Uk;zGkBwdq1(h(1yipRyIdN1_k0R}GmcrOm3WlEo4HZX8Ygj*6o2N(%R1_ZSE zIkSl&?NujGxza-752lX42r)v&z+cF8One*?-yt{A_dCSg??CfQ$h<>qujoI&Xo*it z^^4rO!0J!@kQP^(t&LpwAw#wDXYY=@J)eQ(0#Z&>91sLSEOT}y^msw)(JA|$4dHjl zvi;Vb&xfxPh+F4F*8r7umbr9rwdzWw@|Ofkc%bgWobQswq$)0yq{FwxNN5KK%CG9+ zK&5#)I8fQi4h~e|P6r2Mr=x?<{SH(oONSty_d7`Mcc5xoIt1~$-vRcHJ|KhH(E$Q- z_dB4r+t&pZG3nqSy59j(OdWkt`5=GN9ku5dWGyt#EXp?&W*BqLIwE-Y?T!x2yKnoG zy>~T`h%bf$lyQ8%5z^`|%UI}_ZL^GovN_r&B#2dj*4r8VtT!MM;7DA&-4k(v5HfO*A-6sQyvYB(py}g-4pUtnV>P43kvykT4`Zup3zxDGr>rK8 z3A&w+g$+&53mZ1(=8j=qnq&{(n7QELxxZFF?6d0P!`rRF!!I9I57!TxJ^cOn6~l!K zHVuz`bIKGi-qpcB?sq0ezg3KcG ztW4-{$Zkt6xg>IhS!uiXkFIg;-bv#+~UmanJ8S^4E%{O-2Dng-B#Yz9laMo!naHnkS${bvv& zuoZ9-&>!zq`ZJ_=0R z^@`T_D_h@dkVg7`3ow1(gfza3IA1|t&jNZQj|RX-z&`*gyi*{r(*b`++WrC<512^P zHQv>J(bJ&IqqX56PCT@Tp>ZR#Xl`L9#MlcL+-p^CXXtY(X#eiw%5MY5|E%p)KmANx zLwc{>G>X2eV+_kb$ob~^kMgWHQ66QRI8NFveHxOR!F zQB|5<<+>MkI;rN(c9q-Mr2^+#_^COr`iNfUD#xjLu6o%IJDuy!<_nyTx>n zs>ijkQ=N9lVW&Fns$i$%%~J&er>^-Muv6W3#zM3w;M(N^r!KpwB7svUUAGkir%t-c zVgV#B(f47edM~~x5jb^Q_{|kKb;B{w2uK>-ZHUKNk{Al18!|IHFXvuW(D9Kv8s`>g zn#>P&&*fe2%m5ERA;AR|m}zoI z3W-5#y1GWW+{~hE3@O4V_ef!WybypU>{Lt%f z-}-)Et5>I<0cw8!(7F>zgEwB?5PKo2^eN%I%j#FN|MC64=43ANo6AiP&AB$AevnDM z^W)EEM9-NV`&mTb%YILezF>QHy(ReWx7_*hDf-X*4IlQ|(CgdMKiMYfY23GD-Q0=$ zFVxyr4|(%M{Cm@$7%U{84iS=v2MS5WiSR64lPE<}eL z+SFwLHNXm>O?P1Slk=XqJcmEZGP5G?{(bA*kIFuL>ekd```gDhz5CZ!3mbObS>1Qw zh#Q~8ZT`ol$4*3d6TL4#9Q3^SMQLa2w-@@=lz1+T4DOLXZA047FKhdsS1n3!BEQIP zS5Ke)@qxjk$4vQK97G6%XGi)?)vo%?^EYuhIoyV^m*ja7jI=%uKjL-?Snyi zE02%ck~`_@PsYdJ@z^t8p$vI#`+Gl}Dej%Jf5q~G;X7uI6khi=#80kUa_}OPRmUpWEo-=7)i1OQ(u{ZGlT3u)46;43C5nx&R+-#VzIG_&H z;KGDrr#W|SCZ9DM?R6aQMRm%-#Q)+Sbneo%Ti`=M-Gh6C^z7AJ78)k+qv+c&y#IiR$S9?X zjgE^o)s8}dBn|l>j^#K6*%sPB_$nbK1@cM@Y3w+*W8x1b za$>nr?ygW@1jShJpyeY@2#iA`l*Ac~Qe0}Hr z&&=L?_2oFBq3!$q-bi=O?3tM}XU;kEv9q%~ANi9%{piO&{)xNy-*a#Ofj|4?r~drY z|MN4S{fp22<>$ZfS6{sEe|_oxFMs8$4?Os_uYcpAhaVY;4n8{g*#CZf=$n82fBxps zw+;_~`)|MV-S7R~_y7I}PyFy7p8UtBM&i*Qjs4S)kBtB1pa11wfBLhhC-O(1nf&>` z{o+`1{Ff&RCyP1^!|v|)SRUT@$9q4}_n{B}$wxna_q_)``RUJm?h9Z1(pUJJNL6+1 z{Dq6MH*eyb)10?(quRKgukP^WwGC>^wmqBWODcR#FDp$?Tx(Tl_5HO^&%0pZhD9IG ze*28tC2L>2ZRtI~@iz^}8qYrajpuGVZ{PW!zi{Z~BP%9XW-eWP+0x4!S{l~Aq_w_H z`#*j4_pbcv>puL3cmG!FRr9X?)^C5{n%BSe_}e~x?Nz_~FYkEI@1OOq{nvTde`5Wj z8}@Ac#~asd{=k+eZd(5C-@AG5wgUyJ3I^gWuSwmi!WKXcE$C4G4%Qk zw{8@01It1JcI`+3AECN+YbW1O-N7*lyLMbE-fHnK6Ymw0O1|K?%LX=Wy!nQmo$AV) z2sBAZqWR7W-?(~^rKN3r#ZJA2<2v|$%Blrv;L=iHwWPlB)}8hnxrDcDxNXZOL2l*K zo`7u|ZrHR*<~m_=>tytv7LJ2@9+dM3W>$nhPU%*h;0 z9qk)7Zj#S|R+8}*k=wScM zkZ#A?rnglR8G3wYw4><;J`u;KT#}%*s2p`00|hrJX9n&Rg5L(Rz;5Z`!bJ^NKlh zG|kCj$tb34_P6Xe-%B4)ZMvP)NG{=5VKKFvYj>cqc2*v+9QoY(_1iaeHeG-fG_{H1 zE_~Jun^D2@%D~OQt*6eAs_6CdG47^zK8i1D-6CJu+#~{S<~Cr8hi2`(NajAK%bVie zxcGtP-)!2k?(@x0yzGOoe)9I8|IW=fzhlFfu5P`h=9;FS2Y>e34`=f~`PBEnzxVzP ze{J&L1r}(D2N|51$8}y^Llbd_wM=6yJc(C@*yVs6L1r^7)3iy(Mnv z>?8i;_?C$O{A3u6D}P9N`}iaseJ_Vd@vXu{jTO{*(HYKW_PPAK^=`fSZ>V=$ve_25 z*q$lGPNMR1Z!q1HO5yUeB1}4oDz==!e@bx>G6yvjJ$EST%DWG#Ak5~SBBwIBiPH7n z*jS+}=*w2)s!=85QxJFyMwxLHV&;R5~U((OqTU(%NaYCpNXqo7#~H>JbHZLWrW}BF_b- zaxodV=CfUyr5ik4GMf3VwDdYU?DImxrZn#Y@07c4fx$9M=)*Jm!EKb(a5CBq}1X9=&%u6onDt?rY@5Hpf0%MA<8$FRuRfn#GO=i zA~da1JcnCH{YZwxW_t9|B-iOa0(sFMmBdoYCNoC>l@y(0;T}@no4fCuE232X9C=K0 zpkwSqz@CU2MMTpTA<7}bE9-y;)RuSR$-&`VPESnq_2=8Qn|)rMX=oL%#31cbTHtf= zke4U2&gg8nZhTc-%;deW;O6fb>Px(`v{*G+w$yWYbEbLOk|mkyKH#EEZQVzJt~+1d z4Lr-ss2>|xt@sFpx!#k)LExFh&#$uZEx;vQ-;=_BP2n0g1`56x=q{bF_5;uKs@0ED zctN$qSF78BXL>d2A)u67TVwORUEtZ(>NDoLTK${Ar5UxV7T8#&x`2!7YFPrSW#M2x zYiV_?gVtjLJa=P->?~H5UWCb=&4~i%s|(nq^IG+K^+xp;^?r38(vb0b?@wxsT+UED z7Af#_;Gm9_b5s*dv<_8Qps_gbV1X}KM)d!Kc{g_Q2|Z5I#vss@4YQ$_i8FCF1m#8` zGkHNKg6t}#YXcQ|y^J01V5|Zc3La;Gsf;f2#39hxP-S&*M3A8hmx$LTo;;9HM8jHy zmx+Bq;=^ATUAbz@!e5qi#o)Z)g_*dJVRGQBg0SYnOzgsTLbXb;SO*W=BQ&UbwPOD-m zw*mVBr^284%Q4X=)e@#mXN|n4IMp>apX*jWN)K zZZAiR_Ci+DV{}FqqcbmrSo~iBQJV9WT7rc}yB->$m7^gY@k`udMw>}CGgF$V${{&# zC{~$CswLv|2B?+j`2;%7DG=d|YLaWZRf}{52`Ou@kdl7PG@(EuiSS|~A<5)kF%6p% z8P1M`Lv+F~kjTiAmG&mKY$5~dIKodRQYZQ8fM4We#2x`^D(Ca*L4Gl1O1YAI>QDHX z@e`)!`Y9n2z!Q^0q)BHdeRD<_l&YDQ2PxC?L}Y#lk_b1pWg3%_I-5w~EQ?3j&T&W- zC`4LZ(TkOq`yg3dr+I3MWlArm8VHjiL`=D&9Yic#OYMXeDg9SQ!C^`(2Q4}PC&{VI z(SmZvsrGP;eT2#GkGY})bM~K{|5y1@ zpb_5lhL~E;(_t>qQI5M(`O)np0k@Jt=|&g#GPK%*y3vvwTM?LYMLedfmO6^fi`aO^jUQ8@Z|5cCSY=0%zx1EpaAfVz*W^c?rgxWKUG!_J zCki(GzSwT&7+BL=^oMEJ;fKoa<(4OwalH4~`{hnb_ouFnOm80o-@xsb+P5mo5|mXsSS8&kRv>#hjjT!1hVopwP{RxNQV z>p+r46kLv(dSMhMF-8le7{i}j2um*XUBp3e9byaIiXtE{$rfnmeq{P%kzs6_n+m9> zJ04FJb1DjHg_%6WOy7NwrkVF5r+}rSWro&_9$@+eeMh?<5alXH6w2?%Hjb)vqhKhrqC{kq}QGG&yIrkfM~e8eoBqDQAF{7XHk zPh>hp$u)aEZ$%IzTMXZHcTxcenm$nED9zE0pKpS|oH5Hy1qJl+8{#+i!WQwYOfoIX zN`D*{yq-AQ7iRBOSymEhv$0Gk0{Ui|<77zokZSBwU0qH-myH5?RGKVRuDAm$E?QHi zFLn;BSX#62CF_9ays2zy&(KTiNWa$A1>o5&^VHa)11nzg z;)Ul*pT0ny`p)hGVf)UFrUrO0>OCKA-z! zxm77-qluA$+{tL(;L;oXW5sjpHoRAlBzw=etNAg-VbI-$;On{m1Bus@`1606yQdgb zsj#WjfBev)LnnLht5vz|(fc`^X=9=h6~3-th1_N&roAa z`{-bAeM4(YYio-$ICyYSu0Rge?-^5dnR!DG1U*iCs3TMjt}}=|IZ=Cqx?}(0^&C{3 zccS$z^%+o0OMWO}2ImRCcQnb3Jb>p72Ypea?|=N8tkJW`p6MReg*N};0#^-<;+Sy; z{c-csI`?3bZ2L`Ytr7tL^kWX*|t``0j}EHJ@@!vd#k%|vbgckp1xH_TJ^pp z6SifdMz=dI_;rJ;4tMmApXeRUHy8cG>-LR3I5AcjLoR)KATpAQ5n_mE>Rk-JR>=8Y z-_Y36q@a)G8F55tCyOEkB$ZxUq13xp)?^O)OR|=XP@v(PK!er|Wk=62lt+~a2hU1` zeCzg&uNfXo#@E2#Xu>lTtEea~bF!fQVT5b-iDuhJ^AvIK@Rqy3bwmnjX!9SGGA-A; zkLI=iv{-;XQAjISwh2!RLuwkfQ|tC}!sN)B!>bWayYG=TZgca+Ov)f#YDq*!PMI*R zV!5~|$t}NY!~XG>L#yit^PP=uczDn7g+Ffn%-{X+{{MEz@Scg5@rJQ*^aop74*6)M zvoVWSq@so8LoH(u?yilJdMBbgi5nCuk9m&N&RJs(M&QMkv3==3j4VZN&2YZHA*14! zS|nK~7o1?;a7+5H^L3*7)eT3M-39Msdp&Qw!SzRqUhkNmd=jB9A3m1KDmX@kX)RL0 z-pP@%e5N}Z->plMJpQCeHsHwbXl#J)bm(Z*&S~jZedtiF8#rkNe{A4z+`cABrcuxN z4Weg8m25c+Bb4i6_2Q>4TQq9w9eeQUj-)7h7=5t4=vjfFBrBVo8$B^l7+!s7vekHp zo(?KXk1G}cpOqN>jr~|}xNgAMBnlHYu`Zi}%5uf}-?8Pix46~4Wn@y!v((eLh}Rp; zSJVw{qw9{ngUtnLV2@wEdGqF9?A-jPTW|Qtg;(xh6OBGOW@8E+`=4%zliQaaCUL$C zo3QnK1T;PxOhAO9P+XL_fUD9&e^UU1~ zlp3???`AjdQPJ7zy@U64HJV<$R@G&PPCQUDyWs~IPon18jT!5e-nHaGSZu#!JKDDr zFO#u$OsYdq+`W49wr_p>{>wL;;7%-LX@&u~!QsE)jSicZJ?xDZ z*K^|rca**cTRC2MYGTX%ViILT89s9C4!^%|XmB)ndfon_zprEO*n>M7rWgnv{%8{T z!#_Os@Wg6lQYS(Vm~jfn(|dxcCK1o-7N&Q7(@kNHrdyX$Q6cz7mldV818~!ue8mFB z26%euMD4;|&Occ+t$B+6Y2#CMj|9W!TB~>OV9_X@PSEH)(XA6NeO&u{jlLPQtS&t7 zNUNJ2%rEcl7#YnQO&91O&~%~Gmo5(z*}eK}Nz6onJ8qvxxZEWN+AXx_T$jm1#S7f5 z4EF9sezAGJEmOsM#q^NJ?vE-=B-;~rb!8?(Ceu8Oh*XQ2akj+jc$7I2Hu`E$n={^; z?SH6M-#e-A9o8RX8qUPJkmK$*b2M?s^4X!-Yyt>)N1$@s;-*~2J~xhoMSoHcXmKa>aM91}oLN@0O3{xy~E?=K2kn<4oK#9mHVW1fox>-pG#&myr2Ru;>#<-0y zx^QOhn|O+qWP8T?`g;2YJW^q^x#LN4d@N3gX_F-?KNQBqC|G+Ek7Y2i5uQx8DY))Z z+3th+9FMIc*2K82ip;&eh_RV_OD|?g#yq7+ykeF$kPMex;vlLO*$hF-f?nbVC0UIqx~PNn?z9_PehZ3NmOqVyY=dLKHIPM z#O}S>=3)4(&rT-K1dnBl4tMxGEJ?)TlE$oB+RUz<(Sw6MAuB4FSV14h1_yNy&sH3a z$u*nxxrLOQ5MD|i;1Jf#}lp5w*9Rtwb?jvf1c+n$@_ z%R&}`j^}wy;zwjLyvA$_3e}qN;_?&v@JW5FOY3_BSxkvNxS8Tu?g&+VAdNsj$TOOv zDk9$?^ai5rNX#l5lIFIb;xvP|J{hhK$Fcq_O3(6KFo?!_dhW`2+AjvOs?WMdxHJ=X ze&B0&f7S1aTiots`^P_VIn$J-RdVa^#!i@tvd?(B4TThQ#lZeiJ^{bLj-dDobw#^E zS$<#Wb;n-RnPpOv&m3g;68mW|_D9SUg`8^ec=8$fJZ&$uW{Ylb(HTord#DymU&8i` zfv~u;r#Rf_pMP)eEqRpe!}Vg(06j|~OEr1$^&?-e(vy9< zEts5q)cc?+w0h%XMV*__kLyl%Otm^8FXXN6Io2BNnHYck=^#6Y{a-}@hsV~#y75|V8wks4Tjaii06S+skhkDRv#s9 zcZ7vuG+LImu-U=4+En#=CHsM}SQw9Z=7I(L`8Nc;cNP(*9_rKY z8aVk?{fn3CkqI3PuqXN037wf}cJ3=@n~XBOD%-01M~8#{LO~xL(|`X(ef7Y}yCiKX zTXf=(;`O_TeQ>6a(2uVy?AAjQg;w!)>&KgTR4%b1dwB^evnGU>hV`^%XLYoEB>#(G z^t-ydUvoqAQe7D45hu@5iwLahoGa#kNWJ!ub7_+ipY`fw6S`2!)K@D;hB~)4Gmm$u z>K4{J>=#gV3zj%1d1bP$j_bFi_>I6!ZOy#RK=-Unakqh0#rFaiEO4AZ5jfwe`ZHin zZKm*PU~N^V<}<+ROttz8i9gFJ9ux@O?*ix7SE(@r9W|NaYd9cnKJgsiYSNvb!Zj(p zDuvgja3?Ucuv+Z`)?_mbsN`Q*<-A{@Tb226dVK&mkMjNsxbRG;Fa#7no-nXV{cC!C z5-8;_;=SG3p)Z^}EfrqjEsub04tw%3a=7K`l|)Mn0MiT z^UiV#yW|@DT|nXU6LL-d&jQhh`ig;$dQ@Pxs_#GF9&mRDV{>=iS%hL;vdr zR;fcNdy~^Mnb+tgw8s2E|yvLcw_ondU5>NiW zFp%?E)9b%B@w_K%prd}4LY=}hT*_JO=<@_p-YW&3QFZcapxDE8DSjt#;bN!w-+{v4 zCsO?94Xjd+044q56d$MX8G*=?rBf5X7`OoWH3H`WF9gyLoP3qUlkQDGk^4J=l5eZQ zSE<_tqW||Ae3kmsH2%+1_$7&l-hhFQI+R{NArSrl3sC4KDXg8(HTqm8kap25koY!% z^;IXY0SZ6YrTCkH3l}-XJAuOAz7)UTz$*2*^!mXRJ`R-p-<_|PELoyX)+_)r0=#@d z_~MtGq4c}BJ`4O_!2=&zpt9^SRR19HXQ|?u3&E3)?~R?Y=!`1;d%!baq)xt1Ao%-I z`1fi2Scm<3Pz zFJ?&({3-Co#GktuNc=k%6HmE0xh9^M=h;#AV$S}S{bXy@8`PV4d-2VjN4Z^nL_MP} zaQ?{oqVr?tgtNTr6;-dU`kkr|R~@Z-GE>OBtNLrzXV<)`=K7kOYVNJ+ula24YZ!F6 zd4M8zSawXTWP1$r%vdp_;I=~MVZL}WBa_)%B11KEQ@j9=n<1CU5HlX;7#TgnvkPt; z#bdbFGTUIaKzZCc3Ku=s0GD#l{iC9!feq4#V#n><-tv-3n~QFDi7bu z6|>(+6ak8IAun^c|4qrsPI7&{&g4d2<)Toe4i`R#!57`8wzD7%W>Nw zP=;v;B!LsWke@M|*k}aLEGUTeqd&XT)7XOXc+M_m;2EPeJDZGW?8$hBW8)b!H$8~; zf^s7;YVt*j#+yaIH-Ubi@INel`-oW3gk}*3*DYQwp6Tt-78oCqiQHV$hLTq7-!`L2 z8fYx0W(H3)KAMq|;z>io6O!;G(Lq6Dj;uaF%;Q$mbQ~-gNiyU4_}DQXa2uoBo6l=mPJdq}zv0W;O1dSI-$ad}?A+vCUR?tDO=wM98 z4y_jpn$;1X`dU%94rD)L)ZEQ~KqO(oS&~$3j*#%-<{D{eu z_SB4rQaTAGjmH`(9&IBFrSVep&={Ycj7V!hGYCo)8YE}{ zLt;sqJ(O9{vHS;^Sm-3&8<~w@Xb-xHtYNZyKNjkS<`5EhbU*3bqAzIK{mL#Y=>-e2 z_m8Doxbc-6E2w!92-$=cP^4_aqEt^fp)E@5n2SL>0cBZI;e@)~XQ!N4f)xSCmAGG7 z5Yk0(yCH&L8ZBJK)HL9EjG#pjI$?`nAh#3r0wIC`jUYe=Ls}YXKWGFYXxad1h6`4S<~8uwg`eIXlRQdcn43=o}kD63kc#A7Lmy#h{Vq$h=V#t5InxYB7BV?Mm5io z=!cVFh_*e7MFhIH=nFa@2s&h{F^ENw8jA>cJHn0iL24ZFT4Pu0{3172h z-6s(;6f{jn>;VfEi!}4(7&Ix-nb?Y%GDJp4T%{`tFdB0u(*nEzZUbh2ABkPh33XRj z*VlYv;ReQ~X2>s?xgIEzw&f#_NJ7ShCE!zIfkcRuOb@0LU{*vdI_An)f~c3rs32lu zQ5>Tf`9-mjU_%+)p$yg_ussGHoFB1DCIPxegJ6jFgtAeGKsskq8?{N5PLpPmG^sak zlPaAib&sb>eV+7-bBdN%m7TP_s$d{8G3;bD5Fy&6#`Xne>|X+&js+l;v^+*t zkU`rb{_~0Ox`lDSl1%zzlb5$5ED?XqN{bn~jiE!fF%|82k-;$D8|V~gsW`MOx$^Rj zI6ty#!b)t6Cmo_V2B%0MmzG+<&?N{l!&m}-Y-7l4tq$un2Fe7|+A>!};;?dKsp9$Q z)^y}LLZ2XH+1)I?OKS^2(7I4`DEV-LkalOdkoIP5gU-$}Yu9;0;W13XI4 zoPedE4Vq&idl8!8H>7xBOIY$EiWjB>e!`1=(I))=N&X#jF|VN_PYl5{12G5@xG@Yw zYb-@#0wQ#b7UUU5S?{rO4N?|wh?Krq3`53V>5@T2*8aI-Q=36Jkw7eVu|P3l<2=cS zvxZH;kLA*uoCyS>0d%YuN^>yIi!Dt1XqXKxqMTCfN1PuSU?&L@JS~Y5Tc@OL2Ng)o zEcCfYl*%vzmfCs;14Buzh+(d?X&&FLi;no*OW={W>7i*!ju zsM5F+X{%t$e&G@XX%RC8QmOaM1gT6FNaLTK;=fHED{C{5;(TF00!p2U~|ni$g(nHt%(BU+$uTA=-ij8U+){%pbAzfcml zGvY9T$OKANFE_!G9?H$6)S$`;J9Lq|Wc@D$!cM**TC%KJ#g-T=b6R4((oAiY%4#f| zP|18thHux4D9lgraHRD!0f|ZOhd!Wp#`5Y~^@;$OO3c3?;x+!-$GONp2IcPFV#I$oxaprgbcO z!aMc8FiF(sNfX`f*^6qQ49DI{+?8)rT^Vng!gdm756 zA;p?S>X=KFLh^#Dae*7#09#B;1cS6$OIs`v^urQCCZuT&U)EEM^VoG=3b}C_GOL#o z_TmyjCR-^%EFmRGC?&`QEhR|XPYDuoV`v}_Zo4dpgi?Z{a!XJ`mY_`LEJ3jlOHe{7 zK|*c}Ihq?ZEkQylK|(3PfRH5^ge8I!vIKoXmY}o~H&!?aS%MxROVEpnFdUkYA!tHA zcr*931Wm&*1Wm||i8b?8L(p`EhM)-_pA=C;a>2~l;+p$!>= z24@IL)oqn+NC?`H5DfU)D%+47mvF+N9};P*EJDDE4s3`jd!{-3ik=~t*207>#}W)| z9KJ1IE=Ba#$;93|BS;gpLX)5si$VUsOVG@%r3VU(a9I;GP*%i^3uh;YOqH!|O%+2) zmZ{dtQ)6pLq_n0`5p_wtWs#P|SklGJ1lH)FWDN*a#a!U5O_%HwM2VM@E=&3=>uhEM z(`Lvxo7o5~ZdQWRG0VLMJv=%6J|qeBM4(u;m24#axJ3je5HT>zGgk%yAuV~A0_{SR zkVy+P3r<{D<_IOZqDA98(_2VZdp&C!T^_uG$fhV)u2d<*IB~NCTq1dTCC?O!*y@)uxY|C?V%RjjI4Pzq2igMDxtWkI&Bcg=u&CXp6>>4xW`->hJ_yMz z&znszFFD!INWmdK4(l<_&N2iMrHHcV!xWL$d)BO`+^|X#u}eJUDz}T0u*+@Hi!ITz zVoSzUAyz7`oVUbKIj_h`T&bOw^OpMtFER-#x8#*#Y=m8pk#M<~64g>aVxFSvnA$M> zo3#a~B5?%A8UX@kL#WvmsX-fw^RSACjb@d>ivsF_4&)J~n1q`TB2iE%-v)y9q9dPN zqRk}PaY4}Rf2EXtcq)ydl&5hP@u zgCNF0Ratr*h-^%0jn$h-Z9*H);NV0mdVeG~Ngdu{GA?_}GN6L~$RINEy)JRyc!`J~ z8H6j}pAqK|*%!3U1RiU>37ls{It<}G*QVWtyr{Srdq-b3DUc2yK*e+Q)Z1y zdJP%d%{qsyChCafL2MpgNWWr@$Kz2!OawKuZ4*BT1z|qz4y3EH&Dl%m3}~T*@M1~n z$VV#0*{wVT{(T4|&&_%+?Tby~iErAjuNasWz zNd@ofaici~T-#OqvR5yXh|o-$gb%ZTC5*`emZ=n}tmY`k) z>;kY%>F!i7i62WA6%3k`GCD^F;g3@oE>n8ESsHp7MneY~r*-K$`D(nW59NC*n(HN~I`>}1NGy*$ypiP*RG*(-EjNi(G6)2-1r}Plri@g|{ zObKmPnyDWdM35qw_=N^xCw|D14OY_-PI+oJY>2Z~keY*6LX_uS6E=msOm1BE^avwo z63<~{p`qcWlR2j}?<*lP+KNH>>2rCv^awvlrv0>9iP4Xc{lG;B0K{L=LtPJK>Gz*qBlb!p_8H zt}X_I>IhJ>We0|=F@cDP!IcrIdQw#rK@{eO<{#VIMsb+fJzmaPF7*VTKgPXtw1RxWy5w=LNm67l%8u`0C5ZGxxt`_@T>H8m^Ym9T2d+K$=B)bTngiEfxow_0 zYyN?2U%SKAU#~lG?eA=Z=Q9pm`Xr>?W2J-Y?n^Cn}Z5EebpL-jk}}z$+&(r@5YH6ce#8FEcVX74Lemx&jQ7wj)fOfgJ zp$!fnsp?^eGufocjpq4rzt;Xg7C{$oIi8Fi-8go|@Db?(gGe

g07$tydb60cWII2A`KR>!Qc4##SC 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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dc0a8494..568544f1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -427,9 +427,8 @@ list(APPEND SOURCE_FILES displayapp/screens/WatchFaceMeow.cpp displayapp/screens/WatchFaceTerminal.cpp displayapp/screens/WatchFacePineTimeStyle.cpp - displayapp/screens/WatchFaceInfineatColors.cpp displayapp/screens/WatchFaceMeow.cpp - #displayapp/screens/WatchFaceCasioStyleG7710.cpp + displayapp/screens/WatchFaceCasioStyleG7710.cpp ## diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 9237ea7f..1a9abcf4 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -320,9 +320,7 @@ namespace Pinetime { PineTimeStyle PTS; WatchFaceInfineat watchFaceInfineat; - WatchFaceInfineatColors watchFaceInfineatColors; - WatchFaceMeow watchFaceMeow; - + WatchFaceMeow watchFaceMeow; std::bitset<5> wakeUpMode {0}; diff --git a/src/displayapp/screens/WatchFaceInfineatColors.cpp b/src/displayapp/screens/WatchFaceInfineatColors.cpp deleted file mode 100644 index ab963493..00000000 --- a/src/displayapp/screens/WatchFaceInfineatColors.cpp +++ /dev/null @@ -1,516 +0,0 @@ -#include "displayapp/screens/WatchFaceInfineatColors.h" - -#include -#include -#include "displayapp/screens/Symbols.h" -#include "displayapp/screens/BleIcon.h" -#include "components/settings/Settings.h" -#include "components/battery/BatteryController.h" -#include "components/ble/BleController.h" -#include "components/ble/NotificationManager.h" -#include "components/motion/MotionController.h" - -using namespace Pinetime::Applications::Screens; - -namespace { - void event_handler(lv_obj_t* obj, lv_event_t event) { - auto* screen = static_cast(obj->user_data); - screen->UpdateSelected(obj, event); - } - - enum class colors { - orange, - blue, - green, - rainbow, - vivid, - pink, - nordGreen, - }; - - constexpr int nColors = 7; // must match number of colors in InfineatColorsColors - - constexpr int nLines = WatchFaceInfineatColors::nLines; - - constexpr std::array orangeColors = {LV_COLOR_MAKE(0xfd, 0x87, 0x2b), - LV_COLOR_MAKE(0xdb, 0x33, 0x16), - LV_COLOR_MAKE(0x6f, 0x10, 0x00), - LV_COLOR_MAKE(0xfd, 0x7a, 0x0a), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xe8, 0x51, 0x02), - LV_COLOR_MAKE(0xea, 0x1c, 0x00)}; - constexpr std::array blueColors = {LV_COLOR_MAKE(0xe7, 0xf8, 0xff), - LV_COLOR_MAKE(0x22, 0x32, 0xd0), - LV_COLOR_MAKE(0x18, 0x2a, 0x8b), - LV_COLOR_MAKE(0xe7, 0xf8, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0x59, 0x91, 0xff), - LV_COLOR_MAKE(0x16, 0x36, 0xff)}; - constexpr std::array greenColors = {LV_COLOR_MAKE(0xb8, 0xff, 0x9b), - LV_COLOR_MAKE(0x08, 0x86, 0x08), - LV_COLOR_MAKE(0x00, 0x4a, 0x00), - LV_COLOR_MAKE(0xb8, 0xff, 0x9b), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0x62, 0xd5, 0x15), - LV_COLOR_MAKE(0x00, 0x74, 0x00)}; - constexpr std::array rainbowColors = {LV_COLOR_MAKE(0x2d, 0xa4, 0x00), //vert petit triangle le plus haut - LV_COLOR_MAKE(0xac, 0x09, 0xc4), //purple petit triangle bas côté horloge - LV_COLOR_MAKE(0xfe, 0x03, 0x03), //rouge 2 petits triangles à côté de batterie - LV_COLOR_MAKE(0x0d, 0x57, 0xff), //bleu petit triangle le plus bas - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xe0, 0xb9, 0x00), //jaune gd triangle haut - LV_COLOR_MAKE(0xe8, 0x51, 0x02)}; //orange gd triangle bas - constexpr std::array rainbowVividColors ={LV_COLOR_MAKE(0xa5, 0xeb, 0x64), - LV_COLOR_MAKE(0xfc, 0x42, 0xb5), - LV_COLOR_MAKE(0xe7, 0xc1, 0xff), - LV_COLOR_MAKE(0x11, 0xdf, 0xfa), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xec, 0x5d), - LV_COLOR_MAKE(0xff, 0x93, 0xaf)}; - - constexpr std::array pinkColors = {LV_COLOR_MAKE(0xff, 0xe5, 0xec), - LV_COLOR_MAKE(0xff, 0xb3, 0xc6), - LV_COLOR_MAKE(0xfb, 0x6f, 0x92), - LV_COLOR_MAKE(0xff, 0xe5, 0xec), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xc2, 0xd1), - LV_COLOR_MAKE(0xff, 0x8f, 0xab)}; - constexpr std::array nordGreenColors = {LV_COLOR_MAKE(0xd5, 0xf0, 0xe9), - LV_COLOR_MAKE(0x23, 0x83, 0x73), - LV_COLOR_MAKE(0x1d, 0x41, 0x3f), - LV_COLOR_MAKE(0xd5, 0xf0, 0xe9), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0x2f, 0xb8, 0xa2), - LV_COLOR_MAKE(0x11, 0x70, 0x5a)}; - //define colors for texts and symbols - //static constexpr lv_color_t grayColor = LV_COLOR_MAKE(0x99, 0x99, 0x99); - static constexpr lv_color_t pinkColor = LV_COLOR_MAKE(0xff, 0x93, 0xaf); - - constexpr const std::array* returnColor(colors color) { - if (color == colors::orange) { - return &orangeColors; - } - if (color == colors::blue) { - return &blueColors; - } - if (color == colors::green) { - return &greenColors; - } - if (color == colors::rainbow) { - return &rainbowColors; - } - if (color == colors::vivid) { - return &rainbowVividColors; - } - if (color == colors::pink) { - return &pinkColors; - } - return &nordGreenColors; - } -} - -WatchFaceInfineatColors::WatchFaceInfineatColors(Controllers::DateTime& dateTimeController, - const Controllers::Battery& batteryController, - const Controllers::Ble& bleController, - Controllers::NotificationManager& notificationManager, - Controllers::Settings& settingsController, - Controllers::MotionController& motionController, - Controllers::FS& filesystem) - : currentDateTime {{}}, - dateTimeController {dateTimeController}, - batteryController {batteryController}, - bleController {bleController}, - notificationManager {notificationManager}, - settingsController {settingsController}, - motionController {motionController} { - lfs_file f = {}; - if (filesystem.FileOpen(&f, "/fonts/teko.bin", LFS_O_RDONLY) >= 0) { - filesystem.FileClose(&f); - font_teko = lv_font_load("F:/fonts/teko.bin"); - } - - if (filesystem.FileOpen(&f, "/fonts/bebas.bin", LFS_O_RDONLY) >= 0) { - filesystem.FileClose(&f); - font_bebas = lv_font_load("F:/fonts/bebas.bin"); - } - - // Side Cover - static constexpr lv_point_t linePoints[nLines][2] = {{{30, 25}, {68, -8}}, - {{26, 167}, {43, 216}}, - {{27, 40}, {27, 196}}, - {{12, 182}, {65, 249}}, - {{17, 99}, {17, 144}}, - {{14, 81}, {40, 127}}, - {{14, 163}, {40, 118}}, - {{-20, 124}, {25, -11}}, - {{-29, 89}, {27, 254}}}; - - static constexpr lv_style_int_t lineWidths[nLines] = {18, 15, 14, 22, 20, 18, 18, 52, 48}; - - const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); - for (int i = 0; i < nLines; i++) { - lines[i] = lv_line_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_line_width(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, lineWidths[i]); - lv_color_t color = (*colors)[i]; - lv_obj_set_style_local_line_color(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, color); - lv_line_set_points(lines[i], linePoints[i], 2); - } - - logoPine = lv_img_create(lv_scr_act(), nullptr); - lv_img_set_src(logoPine, "F:/images/pine_small.bin"); - lv_obj_set_pos(logoPine, 15, 106); - - lineBattery = lv_line_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_line_width(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 24); - lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); - lv_obj_set_style_local_line_opa(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 190); - lineBatteryPoints[0] = {27, 105}; - lineBatteryPoints[1] = {27, 106}; - lv_line_set_points(lineBattery, lineBatteryPoints, 2); - lv_obj_move_foreground(lineBattery); - - notificationIcon = lv_obj_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); - lv_obj_set_style_local_radius(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE); - lv_obj_set_size(notificationIcon, 13, 13); - lv_obj_set_hidden(notificationIcon, true); - - if (!settingsController.GetInfineatShowSideCover()) { - ToggleBatteryIndicatorColor(false); - for (auto& line : lines) { - lv_obj_set_hidden(line, true); - } - } - - timeContainer = lv_obj_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_bg_opa(timeContainer, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); - lv_obj_set_size(timeContainer, 185, 185); - lv_obj_align(timeContainer, lv_scr_act(), LV_ALIGN_CENTER, 0, -10); - - labelHour = lv_label_create(lv_scr_act(), nullptr); - lv_label_set_text_static(labelHour, "01"); - lv_obj_set_style_local_text_font(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); - lv_obj_set_style_local_text_color(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 0); - - labelMinutes = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_font(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); - lv_obj_set_style_local_text_color(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_label_set_text_static(labelMinutes, "00"); - lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); - - labelTimeAmPm = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_font(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); - lv_obj_set_style_local_text_color(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - - lv_label_set_text_static(labelTimeAmPm, ""); - lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 15); - - dateContainer = lv_obj_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_bg_opa(dateContainer, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); - lv_obj_set_size(dateContainer, 60, 30); - lv_obj_align(dateContainer, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 5); - - labelDate = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_obj_set_style_local_text_font(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); - lv_obj_align(labelDate, dateContainer, LV_ALIGN_IN_TOP_MID, 0, 0); - lv_label_set_text_static(labelDate, "Mon 01"); - - bleIcon = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_label_set_text_static(bleIcon, Symbols::bluetooth); - lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); - - stepValue = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); - lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 10, 0); - lv_label_set_text_static(stepValue, "0"); - - stepIcon = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_label_set_text_static(stepIcon, Symbols::paw); - lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); - - - // Setting buttons - btnClose = lv_btn_create(lv_scr_act(), nullptr); - btnClose->user_data = this; - lv_obj_set_size(btnClose, 60, 60); - lv_obj_align(btnClose, lv_scr_act(), LV_ALIGN_CENTER, 0, -80); - lv_obj_set_style_local_bg_opa(btnClose, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); - lv_obj_t* lblClose = lv_label_create(btnClose, nullptr); - lv_label_set_text_static(lblClose, "X"); - lv_obj_set_event_cb(btnClose, event_handler); - lv_obj_set_hidden(btnClose, true); - - btnNextColor = lv_btn_create(lv_scr_act(), nullptr); - btnNextColor->user_data = this; - lv_obj_set_size(btnNextColor, 60, 60); - lv_obj_align(btnNextColor, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -15, 0); - lv_obj_set_style_local_bg_opa(btnNextColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); - lv_obj_t* lblNextColor = lv_label_create(btnNextColor, nullptr); - lv_label_set_text_static(lblNextColor, ">"); - lv_obj_set_event_cb(btnNextColor, event_handler); - lv_obj_set_hidden(btnNextColor, true); - - btnPrevColor = lv_btn_create(lv_scr_act(), nullptr); - btnPrevColor->user_data = this; - lv_obj_set_size(btnPrevColor, 60, 60); - lv_obj_align(btnPrevColor, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 15, 0); - lv_obj_set_style_local_bg_opa(btnPrevColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); - lv_obj_t* lblPrevColor = lv_label_create(btnPrevColor, nullptr); - lv_label_set_text_static(lblPrevColor, "<"); - lv_obj_set_event_cb(btnPrevColor, event_handler); - lv_obj_set_hidden(btnPrevColor, true); - - btnToggleCover = lv_btn_create(lv_scr_act(), nullptr); - btnToggleCover->user_data = this; - lv_obj_set_size(btnToggleCover, 60, 60); - lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_CENTER, 0, 80); - lv_obj_set_style_local_bg_opa(btnToggleCover, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); - const char* labelToggle = settingsController.GetInfineatShowSideCover() ? "ON" : "OFF"; - lblToggle = lv_label_create(btnToggleCover, nullptr); - lv_label_set_text_static(lblToggle, labelToggle); - lv_obj_set_event_cb(btnToggleCover, event_handler); - lv_obj_set_hidden(btnToggleCover, true); - - // Button to access the settings - btnSettings = lv_btn_create(lv_scr_act(), nullptr); - btnSettings->user_data = this; - lv_obj_set_size(btnSettings, 150, 150); - lv_obj_align(btnSettings, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); - lv_obj_set_style_local_radius(btnSettings, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 30); - lv_obj_set_style_local_bg_opa(btnSettings, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); - lv_obj_set_event_cb(btnSettings, event_handler); - labelBtnSettings = lv_label_create(btnSettings, nullptr); - lv_obj_set_style_local_text_font(labelBtnSettings, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48); - lv_label_set_text_static(labelBtnSettings, Symbols::settings); - lv_obj_set_hidden(btnSettings, true); - - taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); - Refresh(); -} - -WatchFaceInfineatColors::~WatchFaceInfineatColors() { - lv_task_del(taskRefresh); - - if (font_bebas != nullptr) { - lv_font_free(font_bebas); - } - if (font_teko != nullptr) { - lv_font_free(font_teko); - } - - lv_obj_clean(lv_scr_act()); -} - -bool WatchFaceInfineatColors::OnTouchEvent(Pinetime::Applications::TouchEvents event) { - if ((event == Pinetime::Applications::TouchEvents::LongTap) && lv_obj_get_hidden(btnSettings)) { - lv_obj_set_hidden(btnSettings, false); - savedTick = lv_tick_get(); - return true; - } - // Prevent screen from sleeping when double tapping with settings on - if ((event == Pinetime::Applications::TouchEvents::DoubleTap) && !lv_obj_get_hidden(btnClose)) { - return true; - } - return false; -} - -void WatchFaceInfineatColors::CloseMenu() { - settingsController.SaveSettings(); - lv_obj_set_hidden(btnClose, true); - lv_obj_set_hidden(btnNextColor, true); - lv_obj_set_hidden(btnPrevColor, true); - lv_obj_set_hidden(btnToggleCover, true); -} - -bool WatchFaceInfineatColors::OnButtonPushed() { - if (!lv_obj_get_hidden(btnClose)) { - CloseMenu(); - return true; - } - return false; -} - -void WatchFaceInfineatColors::UpdateSelected(lv_obj_t* object, lv_event_t event) { - if (event == LV_EVENT_CLICKED) { - bool showSideCover = settingsController.GetInfineatShowSideCover(); - int colorIndex = settingsController.GetInfineatColorIndex(); - - if (object == btnSettings) { - lv_obj_set_hidden(btnSettings, true); - lv_obj_set_hidden(btnClose, false); - lv_obj_set_hidden(btnNextColor, !showSideCover); - lv_obj_set_hidden(btnPrevColor, !showSideCover); - lv_obj_set_hidden(btnToggleCover, false); - } - if (object == btnClose) { - CloseMenu(); - } - if (object == btnToggleCover) { - settingsController.SetInfineatShowSideCover(!showSideCover); - ToggleBatteryIndicatorColor(!showSideCover); - for (auto& line : lines) { - lv_obj_set_hidden(line, showSideCover); - } - lv_obj_set_hidden(btnNextColor, showSideCover); - lv_obj_set_hidden(btnPrevColor, showSideCover); - const char* labelToggle = showSideCover ? "OFF" : "ON"; - lv_label_set_text_static(lblToggle, labelToggle); - } - if (object == btnNextColor) { - colorIndex = (colorIndex + 1) % nColors; - settingsController.SetInfineatColorIndex(colorIndex); - } - if (object == btnPrevColor) { - colorIndex -= 1; - if (colorIndex < 0) - colorIndex = nColors - 1; - settingsController.SetInfineatColorIndex(colorIndex); - } - if (object == btnNextColor || object == btnPrevColor) { - const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); - for (int i = 0; i < nLines; i++) { - lv_color_t color = (*colors)[i]; - lv_obj_set_style_local_line_color(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, color); - } - lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); - lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); - } - } -} - -void WatchFaceInfineatColors::Refresh() { - notificationState = notificationManager.AreNewNotificationsAvailable(); - if (notificationState.IsUpdated()) { - lv_obj_set_hidden(notificationIcon, !notificationState.Get()); - lv_obj_align(notificationIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0); - } - - currentDateTime = std::chrono::time_point_cast(dateTimeController.CurrentDateTime()); - if (currentDateTime.IsUpdated()) { - uint8_t hour = dateTimeController.Hours(); - uint8_t minute = dateTimeController.Minutes(); - - if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { - char ampmChar[3] = "AM"; - if (hour == 0) { - hour = 12; - } else if (hour == 12) { - ampmChar[0] = 'P'; - } else if (hour > 12) { - hour = hour - 12; - ampmChar[0] = 'P'; - } - lv_label_set_text(labelTimeAmPm, ampmChar); - } - lv_label_set_text_fmt(labelHour, "%02d", hour); - lv_label_set_text_fmt(labelMinutes, "%02d", minute); - - if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { - lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 10); - lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 5); - lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); - } - - currentDate = std::chrono::time_point_cast(currentDateTime.Get()); - if (currentDate.IsUpdated()) { - uint8_t day = dateTimeController.Day(); - Controllers::DateTime::Days dayOfWeek = dateTimeController.DayOfWeek(); - lv_label_set_text_fmt(labelDate, "%s %02d", dateTimeController.DayOfWeekShortToStringLow(dayOfWeek), day); - lv_obj_realign(labelDate); - } - } - - batteryPercentRemaining = batteryController.PercentRemaining(); - isCharging = batteryController.IsCharging(); - if (batteryController.IsCharging()) { // Charging battery animation - chargingBatteryPercent += 1; - if (chargingBatteryPercent > 100) { - chargingBatteryPercent = batteryPercentRemaining.Get(); - } - SetBatteryLevel(chargingBatteryPercent); - } else if (isCharging.IsUpdated() || batteryPercentRemaining.IsUpdated()) { - chargingBatteryPercent = batteryPercentRemaining.Get(); - SetBatteryLevel(chargingBatteryPercent); - } - - bleState = bleController.IsConnected(); - bleRadioEnabled = bleController.IsRadioEnabled(); - if (bleState.IsUpdated()) { - lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); - lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 3); - } - - stepCount = motionController.NbSteps(); - if (stepCount.IsUpdated()) { - lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); - lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 10, 0); - lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); - } - - if (!lv_obj_get_hidden(btnSettings)) { - if ((savedTick > 0) && (lv_tick_get() - savedTick > 3000)) { - lv_obj_set_hidden(btnSettings, true); - savedTick = 0; - } - } -} - -void WatchFaceInfineatColors::SetBatteryLevel(uint8_t batteryPercent) { - // starting point (y) + Pine64 logo height * (100 - batteryPercent) / 100 - lineBatteryPoints[1] = {27, static_cast(105 + 32 * (100 - batteryPercent) / 100)}; - lv_line_set_points(lineBattery, lineBatteryPoints, 2); -} - -void WatchFaceInfineatColors::ToggleBatteryIndicatorColor(bool showSideCover) { - if (!showSideCover) { // make indicator and notification icon color white - lv_obj_set_style_local_image_recolor_opa(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_100); - lv_obj_set_style_local_image_recolor(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); - lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); - lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); - } else { - lv_obj_set_style_local_image_recolor_opa(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_0); - const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); - lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); - lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); - } -} - -bool WatchFaceInfineatColors::IsAvailable(Pinetime::Controllers::FS& filesystem) { - lfs_file file = {}; - - if (filesystem.FileOpen(&file, "/fonts/teko.bin", LFS_O_RDONLY) < 0) { - return false; - } - - filesystem.FileClose(&file); - if (filesystem.FileOpen(&file, "/fonts/bebas.bin", LFS_O_RDONLY) < 0) { - return false; - } - - filesystem.FileClose(&file); - if (filesystem.FileOpen(&file, "/images/pine_small.bin", LFS_O_RDONLY) < 0) { - return false; - } - - filesystem.FileClose(&file); - return true; -} diff --git a/src/displayapp/screens/WatchFaceInfineatColors.h b/src/displayapp/screens/WatchFaceInfineatColors.h deleted file mode 100644 index 27557c2d..00000000 --- a/src/displayapp/screens/WatchFaceInfineatColors.h +++ /dev/null @@ -1,124 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include "displayapp/screens/Screen.h" -#include "components/datetime/DateTimeController.h" -#include "utility/DirtyValue.h" -#include "displayapp/apps/Apps.h" - -namespace Pinetime { - namespace Controllers { - class Settings; - class Battery; - class Ble; - class NotificationManager; - class MotionController; - } - - namespace Applications { - namespace Screens { - - class WatchFaceInfineatColors : public Screen { - public: - static constexpr int nLines = 9; - WatchFaceInfineatColors(Controllers::DateTime& dateTimeController, - const Controllers::Battery& batteryController, - const Controllers::Ble& bleController, - Controllers::NotificationManager& notificationManager, - Controllers::Settings& settingsController, - Controllers::MotionController& motionController, - Controllers::FS& fs); - - ~WatchFaceInfineatColors() override; - - bool OnTouchEvent(TouchEvents event) override; - bool OnButtonPushed() override; - void UpdateSelected(lv_obj_t* object, lv_event_t event); - void CloseMenu(); - - void Refresh() override; - - static bool IsAvailable(Pinetime::Controllers::FS& filesystem); - - private: - uint32_t savedTick = 0; - uint8_t chargingBatteryPercent = 101; // not a mistake ;) - - Utility::DirtyValue batteryPercentRemaining {}; - Utility::DirtyValue isCharging {}; - Utility::DirtyValue bleState {}; - Utility::DirtyValue bleRadioEnabled {}; - Utility::DirtyValue> currentDateTime {}; - Utility::DirtyValue stepCount {}; - Utility::DirtyValue notificationState {}; - Utility::DirtyValue> currentDate; - - // Lines making up the side cover - lv_obj_t* lineBattery; - - lv_point_t lineBatteryPoints[2]; - - lv_obj_t* logoPine; - - lv_obj_t* timeContainer; - lv_obj_t* labelHour; - lv_obj_t* labelMinutes; - lv_obj_t* labelTimeAmPm; - lv_obj_t* dateContainer; - lv_obj_t* labelDate; - lv_obj_t* bleIcon; - lv_obj_t* stepIcon; - lv_obj_t* pawIcon; - lv_obj_t* stepValue; - lv_obj_t* notificationIcon; - lv_obj_t* btnClose; - lv_obj_t* btnNextColor; - lv_obj_t* btnToggleCover; - lv_obj_t* btnPrevColor; - lv_obj_t* btnSettings; - lv_obj_t* labelBtnSettings; - lv_obj_t* lblToggle; - - lv_obj_t* lines[nLines]; - - Controllers::DateTime& dateTimeController; - const Controllers::Battery& batteryController; - const Controllers::Ble& bleController; - Controllers::NotificationManager& notificationManager; - Controllers::Settings& settingsController; - Controllers::MotionController& motionController; - - void SetBatteryLevel(uint8_t batteryPercent); - void ToggleBatteryIndicatorColor(bool showSideCover); - - lv_task_t* taskRefresh; - lv_font_t* font_teko = nullptr; - lv_font_t* font_bebas = nullptr; - }; - } - - template <> - struct WatchFaceTraits { - static constexpr WatchFace watchFace = WatchFace::InfineatColors; - static constexpr const char* name = "InfineatColors face"; - - static Screens::Screen* Create(AppControllers& controllers) { - return new Screens::WatchFaceInfineatColors(controllers.dateTimeController, - controllers.batteryController, - controllers.bleController, - controllers.notificationManager, - controllers.settingsController, - controllers.motionController, - controllers.filesystem); - }; - - static bool IsAvailable(Pinetime::Controllers::FS& filesystem) { - return Screens::WatchFaceInfineatColors::IsAvailable(filesystem); - } - }; - } -} From 25e81b159b2b76b6d6340affc8ebe2398733dfc2 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 15:54:56 +0200 Subject: [PATCH 031/101] now there is my watchface in pink and stuff adn the Infineat watchface with alarm status but the icons are not the same as on branch alarm-on-infineat --- src/displayapp/screens/settings/SettingWatchFace.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/displayapp/screens/settings/SettingWatchFace.h b/src/displayapp/screens/settings/SettingWatchFace.h index 359f12a6..0e74e2f9 100644 --- a/src/displayapp/screens/settings/SettingWatchFace.h +++ b/src/displayapp/screens/settings/SettingWatchFace.h @@ -10,7 +10,7 @@ #include "displayapp/screens/Symbols.h" #include "displayapp/screens/CheckboxList.h" #include "displayapp/screens/WatchFaceInfineat.h" -include "displayapp/screens/WatchFaceMeow.h" +#include "displayapp/screens/WatchFaceMeow.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" From 10c470ef719412a3d208dc52ea62c32ae2b58ddf Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 16:19:59 +0200 Subject: [PATCH 032/101] update doc of main branch --- README.md | 86 ++++------------------------------- doc/ui/infineat_settings.png | Bin 0 -> 7646 bytes doc/ui/meow_alarmset.png | Bin 0 -> 6100 bytes 3 files changed, 8 insertions(+), 78 deletions(-) create mode 100644 doc/ui/infineat_settings.png create mode 100644 doc/ui/meow_alarmset.png diff --git a/README.md b/README.md index e4f6707f..45b354a4 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,18 @@ -# [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime) +# [InfiniTime](https://github.com/Eve1374/InfiniTime) ![InfiniTime logo](doc/logo/infinitime-logo-small.jpg "InfiniTime Logo") Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devices/pinetime/) with many features, written in modern C++. -## New to InfiniTime? +## Welcome to my InfiniTime fork ?! -- [Getting started with InfiniTime](doc/gettingStarted/gettingStarted-1.0.md) -- [Updating the software](doc/gettingStarted/updating-software.md) -- [About the firmware and bootloader](doc/gettingStarted/about-software.md) -- [PineTimeStyle Watch face](https://wiki.pine64.org/wiki/PineTimeStyle) - - [Weather integration](https://wiki.pine64.org/wiki/Infinitime-Weather) +Branches : -### Companion apps +- main : shows this doc +- alarm-status-on-infineat : shows the alarm status on infineat, can be enabled or disabled from the settigns menu that is updated accordingly : -- [Gadgetbridge](https://gadgetbridge.org/) (Android) -- [AmazFish](https://openrepos.net/content/piggz/amazfish/) (SailfishOS) -- [Siglo](https://github.com/alexr4535/siglo) (Linux) -- [InfiniLink](https://github.com/InfiniTimeOrg/InfiniLink) (iOS) -- [ITD](https://gitea.elara.ws/Elara6331/itd) (Linux) -- [WatchMate](https://github.com/azymohliad/watchmate) (Linux) +![Infineat settings](doc/ui/infineat_settings.png "Infineat settings") -***Note**: We removed mentions to NRFConnect as this app is closed source and recent versions do not work anymore with InfiniTime (the last version known to work is 4.24.3). If you used NRFConnect in the past, we recommend you switch to [Gadgetbridge](https://gadgetbridge.org/).* +- my-custom-infinitime : branch were I put things that I want for myself, like a watchface with paw instead of shoe icon for steps counter : -## Development - -- [InfiniTime Vision](doc/InfiniTimeVision.md) -- [Rough structure of the code](doc/code/Intro.md) -- [How to implement an application](doc/code/Apps.md) -- [Generate the fonts and symbols](src/displayapp/fonts/README.md) -- [Tips on designing an app UI](doc/ui_guidelines.md) -- [Bootloader, OTA and DFU](bootloader/README.md) -- [External resources](doc/ExternalResources.md) - -### Contributing - -- [How to contribute?](CONTRIBUTING.md) -- [Coding conventions](doc/coding-convention.md) - -### Build, flash and debug - -- [InfiniTime simulator](https://github.com/InfiniTimeOrg/InfiniSim) -- [Build the project](doc/buildAndProgram.md) -- [Build the project with Docker](doc/buildWithDocker.md) -- [Build the project with VSCode](doc/buildWithVScode.md) -- [Flash the firmware using OpenOCD and STLinkV2](doc/openOCD.md) -- [Flash the firmware using SWD interface](doc/SWD.md) -- [Flash the firmware using JLink](doc/jlink.md) -- [Flash the firmware using GDB](doc/gdb.md) -- [Stub using NRF52-DK](doc/PinetimeStubWithNrf52DK.md) - -### API - -- [BLE implementation and API](doc/ble.md) - -### Architecture and technical topics - -- [Memory analysis](doc/MemoryAnalysis.md) - -### Project management - -- [Maintainer's guide](doc/maintainer-guide.md) -- [Versioning](doc/versioning.md) -- [Project branches](doc/branches.md) -- [Files included in the release notes](doc/filesInReleaseNotes.md) -- [Files needed by the factory](doc/files-needed-by-factory.md) - -## Licenses - -This project is released under the GNU General Public License version 3 or, at your option, any later version. - -It integrates the following projects: - -- RTOS: **[FreeRTOS](https://freertos.org)** under the MIT license -- UI: **[LittleVGL/LVGL](https://lvgl.io/)** under the MIT license -- BLE stack: **[NimBLE](https://github.com/apache/mynewt-nimble)** under the Apache 2.0 license -- Font: **[Jetbrains Mono](https://www.jetbrains.com/fr-fr/lp/mono/)** under the Apache 2.0 license - -## Credits - -I’m not working alone on this project. First, many people create pull requests for this project. Then, there is the whole #pinetime community: a lot of people all around the world who are hacking, searching, experimenting and programming the Pinetime. We exchange our ideas, experiments and code in the chat rooms and forums. - -Here are some people I would like to highlight: - -- [Atc1441](https://github.com/atc1441/): He works on an Arduino based firmware for the Pinetime and many other smartwatches based on similar hardware. He was of great help when I was implementing support for the BMA421 motion sensor and I²C driver. -- [Koen](https://github.com/bosmoment): He’s working on a firmware based on RiotOS. He integrated similar libs as me: NimBLE, LittleVGL,… His help was invaluable too! -- [Lup Yuen Lee](https://github.com/lupyuen): He is everywhere: he works on a Rust firmware, builds a MCUBoot based bootloader for the Pinetime, designs a Flutter based companion app for smartphones and writes a lot of articles about the Pinetime! +![Meow watchface](doc/ui/meow_alarmset.png "Meow watchface") diff --git a/doc/ui/infineat_settings.png b/doc/ui/infineat_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..0dffd5e781ae4b945d25c629d4f88d9c7435a770 GIT binary patch literal 7646 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 literal 0 HcmV?d00001 diff --git a/doc/ui/meow_alarmset.png b/doc/ui/meow_alarmset.png new file mode 100644 index 0000000000000000000000000000000000000000..e0b4b3f787e9c0f363ef0747f25c7ea53c079eff GIT binary patch literal 6100 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 literal 0 HcmV?d00001 From 48f182c25f840e457ceeeeae2305c9d4883c7bd1 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 16:21:07 +0200 Subject: [PATCH 033/101] update README with link to original repo --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 45b354a4..4ae71b8c 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devices/pinetime/) with many features, written in modern C++. +Original repo : [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime) + + ## Welcome to my InfiniTime fork ?! Branches : From 961c51083e681b94d64c48520d02aee2bce139ef Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 16:27:58 +0200 Subject: [PATCH 034/101] copied a memo I made with files to modify when ADDING a watchface. may be incomplete.. --- src/displayapp/screens/addWatchface.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/displayapp/screens/addWatchface.md diff --git a/src/displayapp/screens/addWatchface.md b/src/displayapp/screens/addWatchface.md new file mode 100644 index 00000000..aa8459e8 --- /dev/null +++ b/src/displayapp/screens/addWatchface.md @@ -0,0 +1,11 @@ +# Add a new watchface : +## Modify the following files with the names of your source files : + +- /src/displayapp/apps/Apps.h.in +- /src/components/settings/Settings.h +- /src/displayapp/screens/settings/SettingWatchFace.h +- /src/displayapp/UserApps.h +- /src/displayapp/apps/CMakeLists.txt +- CMakelists.txt + + From 13773a9b2540f859f189111e8eeeb7a4c2aab8a8 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 17:38:21 +0200 Subject: [PATCH 035/101] modified the battery icon in Meow to be a cat face. add pictures accordingly. doesnt work on pt --- draft_pictures/cat.xcf | Bin 97057 -> 105537 bytes draft_pictures/cat_clean.xcf | Bin 0 -> 18361 bytes draft_pictures/cat_small.xcf | Bin 0 -> 1846 bytes src/displayapp/screens/WatchFaceMeow.cpp | 4 ++-- src/resources/images.json | 9 ++++++++- src/resources/images/cat_clean.png | Bin 0 -> 1344 bytes 6 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 draft_pictures/cat_clean.xcf create mode 100644 draft_pictures/cat_small.xcf create mode 100644 src/resources/images/cat_clean.png diff --git a/draft_pictures/cat.xcf b/draft_pictures/cat.xcf index f1df51f8ca87820d122e937e168457535113c542..6bd6671c8e4ca121d0f3e2d4adc94ca226181841 100644 GIT binary patch delta 28754 zcmeI5d8}kvUB~aOz22*O_w7q>uV44;b*8(gd%Alz2Amlbg^+=u79a#MGit<$BqC%& zJdzoLQ6NBNY`}J4lqhljP#pdsnh2Aq7?&W45sWd2K_xCE7-I}EbNzmP=bWlrcdD0H z{a(dHUUKelIp?0`x17E1d-}iX&waM>!3WdUhaURZQ*TP_uYSzKM?U4@Q$Oxuc&3kk zyZ>-{pm71Za^)NEOdsxlK7C@LIg%==Jlg+>*YDd``KV{t(*U~xZuFme{i>_J&6Cx4 z2l$}?pAPWT0X`Stmje7+fZy`4mIk;G;8uX=1AHLBR|oiJgAXOOw*|(#1N=~cPY3wv z0G|u+O96f@z;9jorN`2ZE1!5Qtxfi5UHO;C($1AnJ(kW-mVuS~e=e8#{`~7MDzy49 zecgkN#-%$BA3fiHb$`46k@WW~jiY89jh+|cBToeQ!2qwCaWv0)viZ3HzZl?GJ!}Po zZN1ggb6@aq<^3M+{~Hgt*8)7@;pumHcW2y1|E%SA5ble&87of8-7i zAK4D@Vt_9V@cRRNB*33}_RZB-rY@`I7n6FPW);Coo(rZ~I>4(LTw*KN#47d<>4sQU zyxiUxVm0wdRfG;kJro#WBg-ve88$7$anC!IODmh;=_Y&a7T%i8?@VH-=B}w)*Yd{T zS(h|u2a2c_unXcL-fA|AAzoBd*BcY4(v`G2)Zh>sYH)}Zby(DCRAGc=6-GKL(exh) zGD02`>T(jLxLYbL>JeDAs_#^luBg>p)a4|qr2V_4ORv7-^Tw)l6M5|8OHzYp1!qN{5$r1dcC`?Ao8X+_X)6=#lruCFdeW)@cEYfEVaE+y z5O&O}2DWY3qOhY@QLrP1EeYE)Y*~eGS_8li8MY$qpfzVz=?)mSmdVQZTkF=u+)z7c z>AI3OlX=58vi)+OO1>sY%U7~)z9G1zG~0p;SuH;%I4}C7;GEzo!H(eVg2?5JU`y%F z2{uHZ7p#fCkX4+z&1!x^!%9cYT+EY(H>D{5{Z10?&o!(hJyS+1d&h>6n`mA?a z&-vadJr{aw`MhG5dZ+W{-l=@0N6*>qEsCmU4VNG2RTU{TKPYUmSIJuWp`JQs!#0J@ z_vrKJ4|7qQN;NmaIV?`MM?C3LrGe8K;T#cnMqCESig4(*XcrkrMKlx`JFAL>t{3QS z;WhCYRe{G8D)0h*Tr@>9-qr<=_cU+rMe0Pa7N~~epokIf21HX4$Mg~rMZ6{cQ3cvg zT$yf*zoifhFVi!MLyFa2n#)@>Ox&+w;zd%)eG2Y7`u7I$9sPgD|Nr0le>LyFb>$UK zy#s5joFLpb2?Xg;=<}^0;uiI&du>3@1*@2X)*&-L^V1aMEq7W239{I2bY8$vBJ{_}htLw0}F{ z#n}=@fgR(G!>C%6CLvhOr zN0T9@)sAr@O!HNvBh2$PXszKqGJxxr?1j($%07lw4Pr-=6^&T&N%2(eNHWB7t1^u^ zl4NWp2`k&0#vuRVG>i>JmTb*U)7Vse+18vlyKyEqbI`A#Z45L#*Od zr>Pm+z{aA@tz-X3lW7^-7+Z6D*0YThn>y^!@MrtytOruVn5^d|jNK`Exd@v&pMY*@ z(~Z2mL?tZJL4wk9)-)mUn~&tS!4pG&F$JYX$zsUdZV@v<28%s*`*n-!n7 zz|H*Amb@9yG)qkUWH^x;-7e{IQZ>4p(ehA(9nhW_CMzf&{Tr+k|ND%332df2wVI`nq6G-CQopVN<_N>gO{i+wIVYLp*3Ziigk3R zfP|2Qr<;}`G!^BYRB0$Erh!-0)*L(}>l>uGNkdEu95QKz_VC1`&jwQS8D&UeMIMJ9 z@aXT!g9>LlFrly_Z^6`2H>pt0lBuhfXG8t_n(@+M?G;i>@QrLi6{txihgCt~jPdhq zO={!h)TgT?3;7Y@)`+BOhbFXqL5{mAh-ASJ6>#jykpU401koKh21E}9-32G*eBs7kO8u$k?mH=Sb7dU87PF(Ul9vu>TEDAK>LlPBd+FXesl&;l=U&rG zmB#CT*PWE}Rd46l^U}R@QtAIDymarJ)T#B}Bd_PB-um~#N!9*1tlsKSI+2so{#_bM zCvsA^dqe3&PU;53>n#tZ6FI5$zgt7;L{7@Ynx1_ZhSG_g)U|hQD7_gcb?w*WmE``s zliaDn{cOI_zj)?*@By}~Hx)R=V}M~B=b%FE8ZUR~*^I0Y>!9sO!e>1eIz`O&nZbVwy-e|A|!u7s%)(sF9PtP}2zlu$i1>s{9T9PZt>9YTp>1+G5 z^X9sH4!uKv#{YKJp_SiG{MqfcL+{X^o!Ry-IbC;n{qZ{{>`;ahX)9s0B8K)dVE zJM?Gof4k_=*0Y!Ww@eTHci?}>VK`!!9k@(i+o5;p&-mZKf4=_&^vT!?0*OTEdRT~2)#pxzL7t>*8gadbWxL|Gny2g=)dgl zmt%uW&W>pAMAajX*KoWJzlbw@GU&5XxC28%a55z0GESis4!?*~F)9eZi2E6pp&V3Jz2L2Fd z66qHWn>rX|@_1+fwQp&3gxa$-x@n7yLy7BHEjP7|YfB-!W;ACa-W1;I%~|FuP_K1* zU5XaApefj<-JxLyde|`CI~7KC7d5H&7;}rmNxUb)%rH`QNn5heI$SSMe>XQHo^O3dCICmWjz%uM7)02F=E^WLw6(Ay_c3AjeFG4)dygqh<1-H zePG;3kulAT+X-PnGopYa#ND;@@rLfu?o=#&yrH|PS^9WG4{Pu#So(NF=Z@TN_vzy; zeY~N&J?PU1);(hB1>>ee3P!IB9#gXP@rLg9piduf>EjI@|3?Bxtoz)ZK7G8UkMGhi zPu8bD`?2pjn5Nl>A3R*s2vvX&u&KxqqCfVn>zF0ClP-=Ki#$%gI050ri+M1cP#j1U zc^n`+ z5g(khDfpzJ4ORQiv*%L}U+=oOP;4W6t_GA^*3{GilFAPgx0INsa-IT29JOqQ+sp&X z$5yjhrR9VwiidZqS(oXFFkdO3e>Iy^TEqgkLi;JUn$0UQ5;V3Tb*y9yO3XZjHWJIv z1ecT&UKC_kGF#SRHdT*lR+ENR6Crcbz9E{bK+ow|cfY1woZ7lfj{_=%djJTR1kib) z`K|sTD^R;%imOPtn<|X7wd}C6_1Ko)A-4&m&rS(Li1m!?;mE>cw7-#YeVkU;R4IOqN{cIM9ltq3DI8eyE2L`H6wYOsRI;hs z5*|dfuxx@T(YC`MjQFzUQvtuHzdjf6rY5`CrR7 zkm+8#hP#e$sA_xe8hoDOTD~z2*O2{P(>K)qJ$DUv9p9LSYpDO7WZ2j>*HHPB3>&-V z8vbI#j)#q1a}77yu(4~d;U*h4cFi?jGTE>(aE<;Szx*rKhiX#DM2^79)bA9tPJJPl zbvP?;0(&<;EtAQ(9v{zTDZFT#cC6N!hB7m6^TUa8Y`Fedk-2o-RiN<5XEGTBd|T*C zw2=pdKd&tWW-Kueil@4)gfw69w|>i!>@q#y(_8pAm8BMO?}KGbI{5W2&5n7Jidem& z0lOT7XjPD65G%Qyak*W1EAcr-t!vIeTAvPiQZ;5+s{T&2J0$v#NmeZ&M`qW2LG!hff>4PB6 z0R!eCC16(!G1GcYShHD)9$Hh@tgHRNE%9{P%VwSE23v7itJl`H8|M;r9hx7v%H_gN z>nkGX^iid|q;FSwhkbNp=-X=lv|WD0L4730>ZtI_B@9 z?@rIrkTt8=@y33Dvyqmtu%Sq6p_juXgv|WD0rv?xV-Wkdb|#1nZ-=g@tyPYf2aF4a5fiw1-docE(NY9tlxckSK)yV0B4hH##B9j??S4a3= z%BQBUwB^F@P4r%&t7@$m(t3kzi!`7ee}!z z8>3H!Z;w9pszATNzMcA`zwOB%dmzA92l(ay|02M@^YAU-=iyI>@AbYt@ZZ(;{C915 z_;caoz)$>vr{90t!zUjN@a+a4N}l{9&$#lB9$tNohrjyu0RPj&r)mK%2e=*J#br+KEg`;b&U@8cggm_Gj30scdP-!vI~B7Eod>0k57Ke_1PZ~51fXa3C7 z&je9^=EI);LdgCrk9qnVq0Qeo@9A&-lfiWU-#k3?w;sOaGXZ|y;6v#{p+0Zy`G|M= zilmQ!z|)U^EWqCg@OK0B!c8Cl(`R4&+ktP&w9_F1o-Cx{$+rF72sDqd~d_UU-)d|z|vp*NzZtn|2l5^z5||q|MzZ{fV)>+ z1K;-pRs`=cX#I$2zVxW879&s#OY!*evxm9_tan(+cv!P-Z??Yg)k5_i3k~&1F*KES z5a=M9`fYV#aHU>A+0=J<`lH769jUYXZ1npQ{0Kgi%QrwNj+CMcv8( zE@=%3T-KUWi&ViQ23I&e*ScLB3*3CEXfr_zRf7i&uCuZgy`eR4)A;)(=}NMqb^eCn zk{0IMf(v;qIVLzS`lR5T;3>h5;O&Alf@cIOi;i$j z9+e4rLzyfmr?m%gn^yg|DXyb9GVTswUGGs~guGv|%4C8?97!Ejm{$n+q7jH}OQEL2 zm(TToc;%IDpR(_&*mYImfU348cu;U9ZzYG6a#?Uwa8ZFG2%Cy0>qs9KJtvx?2+-XE zeMC5Np>zUt+tciyP}iyn3e6FqbslOs^tN!c83PK?EPOy87frz^mT+7$LYd$v)Ie|o zIJ_b5DyHpYdVvTuA91vg5NRcGWx6dMvWx|n=^4dsDrkC`tSFZkEBG!Wl|S*8x(46= zRG#656Mcto{_!f`j|TD`2iku}XxCY*YF%DKl^gOJs@IU$0Gsj}s?(I$09*1Js?*|j zl3;rP-H<`T8AU`$Mzf+Lq@a%IGDMhN@gt<7bD|@p0h#DBq=EDL_@1?NUJwaHzXl?r z#W4_tEX(gbJS9^loRn)5PRN8~I4-j;2ABM^rSfl_QZ5gpg=0d1$IZ&Q;N@nMB&y<) z)tsNx)1tfpi`}&El`r-GzICpDU;B>!7uz$A&Q9H6@?JeJNv;I?@7Sr+pN}fPpq>+yju!*GB~!m zRKh9apaSwJ;^=O_LNjz+(EAj0dMg|8{y{HUq}N5aogP>m#<2#YK=U&gUWSKJttikd zdJFcIpc^UDbTg!dM?>iOiZtC4WsxViTPQJj2|ZFNGNYxmrc6_@j(P)ms;!`y2A*yy#NO5oTBJJxRv@B}tz?jh9VL*O&nQC*!we#iu>c+xPo=#pRjmR= z-h!#4Zc?F~CH-(qh1CX(E#a}}KCGTT;v2eT%RsZLEn2O61la|rz%{9@Y9ao74Urq_xYPa@)f%wr1bj4x-w@RSmxpl4(tJgk)S74Qyz5CiA8sd9@0V#>MRE5EsUJ(! z|DunRYB6(?I2So4oib-Rt3ocSf~;ZYtc5MXTx%8#siWB)Fs^x4w5Ev#P;gxHEa-hd zA`B;4R2b=Ke|p=D2$7`1X^|hdeCv=1GoI)+Txtg=9V!7QGksy-q(;HDfKwTLZQrDd z7t{@BCVfg};GX*D1o&iK^HCtXcy!1>RKQE@;!y}K1@{FLcgCh+OkiC0T;lsP%+C+8 zbyX?1H!;m3kDPrMXUi^M4DF9hgyjW{5HTU&M%ts_q>>kw2%FlFl$s(cEHd(SL~1;& zGK!D?dO*ClRFzLeRY$@=%$5vE01q4<@+EW5o}=Q1Jl|nZ{~@odw0^V!Qek}{$ao(2 zak@3=R8Y)3G8c^JE8#~V{h-+_3m!tCFwUB3dE{YD91%xd&2dXtJkoS!nN$vZF08zf zSe85Q8xV7{CPq%y0?e8a5!R_AU&B*b4dUg86U1R<9u7MK-yuZ)Nt2%br-M}(BlCUw zZ6=YrjLGJotdeNOaJ6*SYF-PgDn5tmO@hgGQ zGvbUpRDeoxa{(CXhE1EHnHcIHs8}35l;du$kKpDyIP>vTI1*sjD3$YZ;{?edk=z__ z)>_`x$jgNiS_)3fF))w&iBm^>HkXjvtljGH_>7!Y_cL17g0@<#|14uKbN_-D5N%U6 zZ>bU#PAllPsbpQ-WX!~n2*1$E0?yB^*aGwFJfbeOA^fNYyVDx_PZ})T*QmZp|Nh3Z zpj~Gy>xMQl5Kz;9!CF<~sD@t5!+DNsrk&}uovr&eU1=$cfr1+$hDB>ZBDh6LWJ?Y0O!P=xNSdP5 zi;f6}icx|~M5@t<1WEtVKS;nOV$>Mp5{>Z>gBtlmuHVmDrnk@6*Up>wYK+t5^tYUI z&-tC-`E7TZ_uO|%pZ`bkfy?94vAusD+ZWk?`|oiu+2x@BKR)!st%&m^fzv5t_)x#kVAM)@C2U~yPU{AY)1K)CR z`Iv`W9UOmygPRUJxclP{UiO%WPdRw`<(E3-s?RyN?=lD9`m%%j^B(qjIN{-T5BHu} zQ@%Rh9p%#B$>4#BxRAt2U-G5{r3rUM(O+4LQai(G zFd4jUGI{pETTFYpJqj(KUKAD5J+>9o3%vxaiX^O>Q@bDo^A<(>0tHAmygnVXNsqx{!1^P!{hmYD}2Py4Sh^W@REt+gY4jf~;aC{7-j*>v?b;jKEr zt%7Y5VNwPI+$`8GxJj^6{TtPQ8`QcoaJ}$uH4Bq>-7#iz=^D|C)K;tYsjX7$*IX+- zoDduk4P2}?uC^otha^i$^q|I+qaKS7MZ>}wH=MQz4n&>lkYJymw^s(sIz2{=wL-cC z*9mqAB0QVgB-o;XlY%ADTLlZEw+ZG&uhGIC!c+0qTD+t=`n9=5+IXMce24m&w{sR2 z?S{K#|7`q9w1cZ44VuZ~I5oTOQOn}E)R3+B; znLfBut;6(2ORUHBMe7xZ)}>43x}9?A!KjojliM#5TrM~ewWKR#e*Jtr52%6Aqv_Wv%n#dF^(-96QlCIJP49?<2R^k)8q_2`DQ;eyFg)8Yc^^K_Il{HL0 zRS3OAdFH^u-sI7P{mFd?2N%v`;XJJMEcnMM|2Q&yy^+&|Y9Hs@t_H->s7`=1Di(zl zi*NBb^kyG9z&B;0$VMMAh%l6HI5iQ4axFv)s--s*1zhNG5rNLtYD56=RUT&?bsV1W z@Yv#vTPc4h0)SIuwI(l2O$<x|onjTDUoE{xxE#fWf^sBK3 zkZ3djOdgpYN}ieSN={Dqn~QDFuzFqNcI86FGg{>h(zv8zW>hmCb!!BQotLtyG0|wf z+Mchn2Ntv~?Vqb{|8Hsgg8FRxMCZgvjA!pmUVmVFXN!!UxB=XnJb7R;*>Kx3-Rt)Y zb{L!&2hByTg57Ez8eN>d>)+#DDct=B7+fpB?9%}Jfu{n%ogQZCf=UAT#VFOl;$zT#X={#j&wrwX!UW zdu8aQYAyDGflDIXm}luh(bycx!H`Y3+YbzAo&t~~LRKN-GdXb%_eBV*>nu(6O&>fq z!o7Burdnri<^=C`w3%ga5h)L;n2bf(O(l(B9XcO0geDwawi7}lDA&Xq!$M&Uyu1<< zJT&Vzq~{|gF;NGG4j;ooZQ$4pS^&pBIB&)W96m<`q;b47#sKLP zZZu(PHNbf@uwAM`#Y_Z^+F6VX+ob_#y{%U5L(&4bbwkl&*-=5TT}M#TMvAk-#_H$z zn3H+fK&-K7As6ZVVY+jL8QU|&1JFQD6BQyZafjq&2S=$5a8hHJMwVJ$iu{P#cqi5V z&%sNZ=cGmo=45MoY4e=a;5@-|&*-HZ(!AqOxq((b;9$Cvv zo9Cq3e@LtIO{H}?Df=gkHXxPO<)o%@skAO9HH}N9(&F5mROjEQR9csla{fg22Bgxu zob>iq-E@%s=ATN>#YvNEcJ5=#% zM0nI(R=;6_ECZVIg^GhZxf)H5B2c#a{z{q%?y8s6USCO5^00=g9b*S%7?x&or0!HX zgaDOvuM#C%>hk?PDs!@Qmr5E+97=?4Kc-Wq4~aF)YYz{gG1neHkQxd)%cBJ5LWJq{ zJ_cnTb_OqDTD^HQ&;eH=m;p>T69)L4d#)1 zQRhlM-EJ2h7M|0qKLi+7(U*AvnfLKRCP3;_zq~BrqHskcORwauYy_iOQ10>_%tr+iC#rnZoWag zPM0cQ(lQMS0ZPGHw#^wVq8?#h%ebcaP)>(smI41&B z((J{ZPeXju%<8+{TTjq4w;~8oNryvijBgsZNOk8t^+Kp=zRBZKR9e+J#c#H6np)0z z9(>bSM>>OZ+Ox-zf-g4SH%%?)Jk1}Qrp~{<#MvOeQ!f`Rm5^&s+Tf<76a)S~De!vX`^$GP#z@bN#R~gJe za3*BpN}NSm9DWu@Fa(63#Sxy3j)<=fy1K>__N>4|mZ`+KRl^40pTgb4Op^VGiWuVG zidpC${jE^tNb$BpS&SlLg^C)+uZT>Cm2yN+Lf!2J#c!8YF`tXH2&ak~Q|r*@YQSC< zLF0l`PQhRSzFhx6~f}wv+LAQJG)!2hDyOVsG&wQ z1CAQOH+hJyfHM|+QjLWeHz{oZw@N?<`AmhE()rXdc$+$aTh+LTNx%^Ze2W^3(YIdq zk~P#Uo;B1gUc+cjT~-?mb^bayBdLq%&Omr6rLTO^s&4Yi8V8arsNR@*qEHFa67 zgl>@0n!2pEaYn1UI%}y}yoS{_&S*J*LgaY}wX=qrHCmIk)GUtQH#XE(HCp#v(9dOD9_>5r>yl91nj~vX|CLTD6Gw=-GgF_&AVDi3`O;Wo6Q3Nkl10MRi zH4ZxuN?|+?jo>*!fy(E2RdXP0 z7_(SBgkl_m4v9txXr9XrYaZSt^0m`4(Y%^QlPRwsVWghHUzSkVq-q=skGDp&7=u@7 zQs8Qhr*EwqaGe@_>>hPfQm{ix2sE-;4Gm7Jp{XruXaZQ~{I{vGJD$C66l7<7+R6m% zikGEKl9wRPzDxdlt)f0LDd{V(e0s5kE&AH4#eTM;?@UvZ`@a5XwliC}_8-h1nX=}L z^4Ld~hr;2_#l$rnH8DNAJl(>zelX=`nRrbLNB_aBn`GiOE!;HU#A{kOA7eM!#HHag zTe!}@*(P4o!ukB6$tGUY!g>8nDLcO?Uem;DTDbJzfGA$m#A{kO{x|<9Uem;DTDWOQ z6t8LGH7$IxO+Eh+#gm_0{}QSpUl50B7%Dg9)p!I~)JP-eFfJrfl4~^{If)y27nJc5 zH+SF}E)2{?iV`>~*#sUqiv~V)Xz;_WY~Y3QJ+kyLFFY1UP!cGcWCMPFGuug44ZGuF zgy14BrflGXaB-NBG4vIc*||2$N0TM-xrTWV7-7(oE1t^Y$u&X;s=`m*r%k8xd4tB2 zM8>*tPL1$WJ?INO`blxI{bb4&LM%?Hk@q|VEi20}(YQ7v!bx7zIk}gQNL*B^=ZwIS z7|37FQ2x5J=94K+pd$;~Y1j4qMPG+D!8LBT6h-dObPy6ApVP4>$6+uu)JSxJ$(d%= z&cYN2;W#?A2$crM!EPxdF+{FqE?&4~1?L#}+an-C4SO76mPRecs*Vd8gzCVVct|)q zL>QX;-qL-)!Z{CXmlV6~2;nveM>EiyJPy4@IGaGg?b0gvP8CqQwUJ$s9#ikoyLrCi zZ`a#T%D%kddi@3Fq~I>?#^mm?Z|o3^i)jsWQ=`d{5_JThghstASdAClkzIQ?vKk9~ zG!Pn^tmURAEXD=FeX98BY+r-j)TQRL`|0WK*f=*eeA;*peyTATP|3Pw1KpJKNA>t_ zEkDH;n&GAfYsN3n;-}4WQ}=g*XZO=)xvAnm=ZXdzkxqj*aa_S2Yr1JT|FijNn7Gky zYMfQO5R0VqOQ&;oQy2e3w3~N2)duF~raFIAi~BVqozB@!`7*bG>2%I+`VL}#jl?`zVIPyFDWFUGkW?)Eo3ik`YgD(2&LE z#t%C3Z9N{2dbq{IM?CzhgEtR4`0n>Qc);`Te96&w{?Wm^|K{M~`yIU3{~*ng9gaS7 zg@t>gBM&;p(Qi6@kZAo{Uw?H#fSakQ~z@GbgPF0+WUZsrxHg`&3O2*hmU&r1rHzh@GBmE z-9vXW@zl3H9oBo+(--u{p1amj?a6cHzT~k&|IE@y;x+#UeNNzk diff --git a/draft_pictures/cat_clean.xcf b/draft_pictures/cat_clean.xcf new file mode 100644 index 0000000000000000000000000000000000000000..b554ef179d5e16775450e00bcd95508ac8345bda GIT binary patch literal 18361 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 JLH$g<{tu&V1{VMT literal 0 HcmV?d00001 diff --git a/draft_pictures/cat_small.xcf b/draft_pictures/cat_small.xcf new file mode 100644 index 0000000000000000000000000000000000000000..76c7751d249a931636095194a823061a79946aed GIT binary patch literal 1846 zcmeHIT})eb6hFN!EiEl~6A&YF-fcQTgQ0uCJ#B{J$0E~ZAGv1n!BX0K18cEm1hSA3 zr^aP5CVuRtQVL~TMoacVMobj9=@M~66gHEYIG4<%QCTeuD^ON?Jg4-UyJSB2;G5^> zch2ve^FOEmdzzkll)bG|htn;!*Vyeq@*ts{3CR({N`g*Ds)SIu<&&SQ7{p5n27-ma zMa%_48DWgD!b7dAmAwv+tJ>pNnkWR^Csr9~>uhmJ0bh&KRAs4jdz`)&pI@rXnQFhM z(IM639(xs~8}F00OSy;B9f4MdQ}KEvdkuM=_jq}ydY@NmlN7J>4Of6Gd~SEZ)h6Z6 zYxpv~_b}gCBf1wf4pHN?8ebh8zhhCI!`bZd`}TQrugzm&F%o!8NLa&sb2n)kD}m!& zYR+>quvWuYX*iFZ&$CDbl^1EaRl{u>E)wU@Q^iDt>$(5qc}@l%V~!+b*J-%=wX5$$ zDHdVJvdw9A6#07z-uvXZ=3O-|h$0BGUbKQiHi#BLy+JMz3&ALxMJ%?kF4O4J9RDX> zVxb-gdJ#+YGC~1$LJ-IkWF2|biz*Qc$(kubw#+Q+!6;&hNj3r+3&aB10tV3_7Xj+1 zay|7iTe-&*f{cYmV*lUwznt&VQynzn^tx6z)qXrahc_!sw(>`wuC-Z}3?55ybo%sk?=QD;>E(|Foc-)drXTT{ z=fmMWrfrC1C2?gIANuO||i~|FVF0GvVkT5sGGa^$dRSz@dJO^a$7uQzP+c z@?-+>%i~wErztW%b#(GOTo}M;a+}Q}3O@-T@3@{5%ntS8$tjG# zmilHQa-lzXaUc=t#rekH@bd79;Kr_xcKnHVJ4X@Ub}C!zKOH{1bN1#)JqTxzI^Zn* z?p%EfiOpM2g|hK-D2;P;Kfp`#GqJO^meE6qG5eO?nP~FI`anYxvHgSq&wW3JiJ{?x zgV98DI|dQII`=4m-W|KX&>xyO(tYgxFRle~VzABtGpk?=lJByEM b#F!;V-BtOV>0!+QHAf-S1~q;_w}^iM?F8FJ literal 0 HcmV?d00001 diff --git a/src/displayapp/screens/WatchFaceMeow.cpp b/src/displayapp/screens/WatchFaceMeow.cpp index a4abadaa..b6fb030b 100644 --- a/src/displayapp/screens/WatchFaceMeow.cpp +++ b/src/displayapp/screens/WatchFaceMeow.cpp @@ -193,7 +193,7 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, //Battery indicator logoPine = lv_img_create(lv_scr_act(), nullptr); - lv_img_set_src(logoPine, "F:/images/pine_small.bin"); + lv_img_set_src(logoPine, "F:/images/cat_small.bin"); lv_obj_set_pos(logoPine, 15, 106); lineBattery = lv_line_create(lv_scr_act(), nullptr); @@ -565,7 +565,7 @@ bool WatchFaceMeow::IsAvailable(Pinetime::Controllers::FS& filesystem) { } filesystem.FileClose(&file); - if (filesystem.FileOpen(&file, "/images/pine_small.bin", LFS_O_RDONLY) < 0) { + if (filesystem.FileOpen(&file, "/images/cat_small.bin", LFS_O_RDONLY) < 0) { return false; } diff --git a/src/resources/images.json b/src/resources/images.json index e4247188..2591bcbb 100644 --- a/src/resources/images.json +++ b/src/resources/images.json @@ -1,11 +1,18 @@ { - "pine_small" : { + "pine_small" : { "sources": "images/pine_logo.png", "color_format": "CF_TRUE_COLOR_ALPHA", "output_format": "bin", "binary_format": "ARGB8565_RBSWAP", "target_path": "/images/" }, + "cat_small" : { + "sources": "images/cat_clean.png", + "color_format": "CF_TRUE_COLOR_ALPHA", + "output_format": "bin", + "binary_format": "ARGB8565_RBSWAP", + "target_path": "/images/" + }, "navigation0" : { "sources": "images/navigation0.png", "color_format": "CF_INDEXED_1_BIT", diff --git a/src/resources/images/cat_clean.png b/src/resources/images/cat_clean.png new file mode 100644 index 0000000000000000000000000000000000000000..6f8da93a9c060c3405fff35442a8c96114417fdb GIT binary patch literal 1344 zcmV-G1;6@EX>4Tx04R}tkv&MmKpe$iTZ>XE9qb^Y5TQC*5EXIMDionYs1;guFuC*#nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|?BJy6A|?JWDYS_3;J6>}?mh0_0scmXsb<$WplX(p zP9}tGZdK@hMG!-XU>G5Znfjb4rrU7TlmpZjz4DtVIuK7n|a>4rtTK|H-_ z>74h8qpTz;#OK6g23?T&k?XR{Z=8z``*~*6$fW0qqr^h7gXIopB|{~iB91AlM*04% z%L?Z$&T6H`TKD8H4Cl3#Wv<2)@|%#|Y593pDGt{e5iP%@ZK-3|wh#f3*S3e3D*o zYq29BvJG5Zw>5bWxZDBypLEHP94SE4Unl_YXY@@upzjt4t+~Cm_Hp_EWT>mu4RCM> zj20<--Q(RooxS~grq$mMT6=PxXbY~@-zvhh?@bWyut84^Sxb|E8%u)?Gw2#PL`(OV=HJ#@hyf((h+_|S`*d94i7 z(j>DZMxln6I(E!$scqDzmt_nG70M5Wy=Q&j|G(C^F8`gSl&-{Gm{XQz=@eGG-5wWQ zZ`E)yF2TZ-(#TUFy#XKK9-LDP`#3c5c}i*Y{~)~+OVE!g7*@ljn2axQ4OaXY@^-uZ zDCQ%>h(v6QksO{!alsy=?t4Xb?PYO*o+Py!kdBrDAE9GUCwXA(jZ%o zRT#j2?CR<18IQ-YHf(ei)X-^Wh;*KMBWYVi0GC{SJJEmvJxl zg}tW{e{PS%8*#x9(xVvYbUMu_%*S}JYyWz@8n_YIiA^=!hh>q=@j>3bmV{$CEu!Ar zwf|{w+=%-z64Ptp=?Z)okzR*y0#pS_vx{gN7Q}l8+XDX=zK$F8)sQd|>*L_n_%?v^ zqf5G12R}rSSK?N*ussTS6)vk`Y*!%`;~*}_hOi&Pyr{+Z;q2Ks`*YwMSQs2IrqShT zKYqk)oEOo&i%~ez>2zL<$Yun1JGMqwY{TJ5Q7>u@%#Gjg7>(Dq81p>RG7;OnBQ2A$G|RFLtyb$G{ti`J9M{jH zlU^%|;$LTt?uwLUxdU@kN*iNz7ll?V8XO#46C`(sk{!k#yigRy)}cCGKc0A3Rkb_M z^9-}1N2la@zPqol?}5;RGeU44DT-p?cpUW;eHp5%+L!0~x@h>ly}iAYn$2d4NO$3> zq9}Hr2wywdXQwR76?g`_vMiezoiZOYilXR08TKD9tNxU0K26vF0000Q literal 0 HcmV?d00001 From d81356c8f7a875c7cf520de0d111f980097ebed7 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 17:42:59 +0200 Subject: [PATCH 036/101] revert meow to pine icon, rework on cat later --- src/displayapp/screens/WatchFaceMeow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/displayapp/screens/WatchFaceMeow.cpp b/src/displayapp/screens/WatchFaceMeow.cpp index b6fb030b..a4abadaa 100644 --- a/src/displayapp/screens/WatchFaceMeow.cpp +++ b/src/displayapp/screens/WatchFaceMeow.cpp @@ -193,7 +193,7 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, //Battery indicator logoPine = lv_img_create(lv_scr_act(), nullptr); - lv_img_set_src(logoPine, "F:/images/cat_small.bin"); + lv_img_set_src(logoPine, "F:/images/pine_small.bin"); lv_obj_set_pos(logoPine, 15, 106); lineBattery = lv_line_create(lv_scr_act(), nullptr); @@ -565,7 +565,7 @@ bool WatchFaceMeow::IsAvailable(Pinetime::Controllers::FS& filesystem) { } filesystem.FileClose(&file); - if (filesystem.FileOpen(&file, "/images/cat_small.bin", LFS_O_RDONLY) < 0) { + if (filesystem.FileOpen(&file, "/images/pine_small.bin", LFS_O_RDONLY) < 0) { return false; } From 8657f71d489df8ad692c92d16afb25e37a11d530 Mon Sep 17 00:00:00 2001 From: Eve C Date: Thu, 30 May 2024 08:39:51 +0200 Subject: [PATCH 037/101] The watchface infineat is modified with alarm status and time set shown in the bottom right corner. This feature can be enabled / disabled using the settings menu that is updated accordingly. The alarm time is shown in the same time format as the hour. Doc with screenshots is in doc/alarmStatusOnInfineat/alarmStatusOnInfineat.md --- .gitignore | 1 + .../alarmStatusOnInfineat.md | 22 +++ .../infineat_alarm_notset.png | Bin 0 -> 6312 bytes .../infineat_alarm_set_12hrs.png | Bin 0 -> 6649 bytes .../infineat_alarm_set_24hrs.png | Bin 0 -> 6286 bytes .../infineat_settings.png | Bin 0 -> 7646 bytes package-lock.json | 183 ++++++++++++++++++ package.json | 5 + src/CMakeLists.txt | 1 + src/components/settings/Settings.h | 14 +- src/displayapp/fonts/fonts.json | 2 +- src/displayapp/screens/AlarmIcon.cpp | 11 ++ src/displayapp/screens/AlarmIcon.h | 14 ++ src/displayapp/screens/Symbols.h | 1 + src/displayapp/screens/WatchFaceInfineat.cpp | 95 ++++++++- src/displayapp/screens/WatchFaceInfineat.h | 11 ++ 16 files changed, 357 insertions(+), 3 deletions(-) create mode 100644 doc/alarmStatusOnInfineat/alarmStatusOnInfineat.md create mode 100644 doc/alarmStatusOnInfineat/infineat_alarm_notset.png create mode 100644 doc/alarmStatusOnInfineat/infineat_alarm_set_12hrs.png create mode 100644 doc/alarmStatusOnInfineat/infineat_alarm_set_24hrs.png create mode 100644 doc/alarmStatusOnInfineat/infineat_settings.png create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/displayapp/screens/AlarmIcon.cpp create mode 100644 src/displayapp/screens/AlarmIcon.h diff --git a/.gitignore b/.gitignore index 81e49ae0..32ae7f6f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ cmake_install.cmake Makefile build tools +node_modules # Resulting binary files *.a diff --git a/doc/alarmStatusOnInfineat/alarmStatusOnInfineat.md b/doc/alarmStatusOnInfineat/alarmStatusOnInfineat.md new file mode 100644 index 00000000..22885a84 --- /dev/null +++ b/doc/alarmStatusOnInfineat/alarmStatusOnInfineat.md @@ -0,0 +1,22 @@ +# [InfiniTime : show alarm status on infineat watchface](https://github.com/Eve1374/InfiniTime/tree/alarm-status-on-infineat) +- I forked from [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime) and added a branch alarm-status-on-infineat +- I modified the watchface settings to have the possibility to show alarm status on watchface + +Here are pictures with alarm set in 12 and 24hrs format : + +![alarm set and shown, 12hrs format](infineat_alarm_set_12hrs.png "alarm set and shown, 12hrs format") +![alarm set and shown, 24hrs format](infineat_alarm_set_24hrs.png "alarm set and shown, 24hrs format") + +Alarm not set : + +![alarm shown and not set](infineat_alarm_notset.png "alarm shown and not set") + +Settings view : + +![settings](infineat_settings.png "settings modified with a button to turn on or off alarm display") + + +## Possible further development : +- Move this setting to the Alarm app and include alarm display in all watchfaces ? + + diff --git a/doc/alarmStatusOnInfineat/infineat_alarm_notset.png b/doc/alarmStatusOnInfineat/infineat_alarm_notset.png new file mode 100644 index 0000000000000000000000000000000000000000..a8883db00ecbc263f434564fa9b143e69fd31ae3 GIT binary patch literal 6312 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 azudktU3C@JdxQ7~0?=01S1VJ6zWqPFIzyQN literal 0 HcmV?d00001 diff --git a/doc/alarmStatusOnInfineat/infineat_alarm_set_12hrs.png b/doc/alarmStatusOnInfineat/infineat_alarm_set_12hrs.png new file mode 100644 index 0000000000000000000000000000000000000000..259c09833f9e8cdfee2144c64412497e07b1f15c GIT binary patch literal 6649 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 GgZ>8t;MQ3H literal 0 HcmV?d00001 diff --git a/doc/alarmStatusOnInfineat/infineat_alarm_set_24hrs.png b/doc/alarmStatusOnInfineat/infineat_alarm_set_24hrs.png new file mode 100644 index 0000000000000000000000000000000000000000..d31de7980157ae4cbe52482f1f9393cca795abde GIT binary patch literal 6286 zcmXY01z1yW7vC5#lvI=sr39own1ZN?q@W<3qXmgcBaDy^DH$Oth%`*;9wCTBkRClc z$H*ZyzWsf^=ehU(?mh20=dE+jJ?Gx&m)fedSJ|!t003HbHDz5A?*4aDQIf8T`Wwjr z0E@o5@)JGZEZl6+YmN;#<*sU_p8aDwc|9?CJwt`tVo5e#>e!ce$(=?Dq8m8wb23Lg zW3xVSmvI;4`wn7O{}NrnWPSHDPN?@Sfs+pt&M{6gB^W|p@`s1Q{WYzWNvM_zG|3p* z$cltg^1&`KrhfdmgpBOtSwDy4li-eI73r!kaKgoa{6X6Y5ZgCdOEc`Wz6+t5P5JKD z>n(Xt26X27+Z$%zQid2S}@navN@#j(&$Fh3!V&Jc4*Ry z2Nv8Au=*f1D1))?MZUe7OYk$q;LcO#S5F^3BZ5qS5~Dr|A%IN!iqn4Jk58oq%RRni z4F5;bQLGYKm3*i`J-DPMU1%r_;;0X>XlM+}sA>y^n!XH;x}Mww=oQU%JBM-M1A?@n zu~Eq%n*efVif%yZDvRFpX0$cCa!TGP1tc@tLJ^z*dQLaUbKKk##&nJUjy_OUhEc_^ zW`ELecPK1OJ}dEY1&J7MqPXTED-a2}9b#fwO2$zbU~yWNAi(FmFLkZ9a+DmRaiVZJ zLeU?vC4fRJk1pymVv#Ec+dgX&K6V!FZ=yyMKLH%1Ye5`;ft7(r518hc!pNPtOQ>Mq z0%S`AXW=Z+&2ZM`pj_gWD=%^sN}9D)RaF^HhSO{6AGzpW!T3cb3p1a zSC72G8YoAwHafJ{!R%7MhLr_Mq zJY&VZ+qZG8hEvnI2EV|*h9@l){c*FX^J%mC7^;^FsFf9uT?lG9%|7W&YfDQ_%^!~- z5Q;K}fnub60WmY1dMYVRS)Mz(1x*QYVx7Nodk=JkGuvZbYR1FDYT9>4=7nBrpd|P0#;Iq22vZC0-=l;uw@v z)Ovy2d^}8B+}%v=%dTGkUJb56i6MHu$v(K=*9l-OOm_#`pFF>jLjt_!4KC306l)EN z^c86x84|RLlVJ8|(kei%Y2c%<+{-T{bs8C5nC7ni3_)Q#GUrR#RuYr$=oMHD+9Cgo z;5DdQC`p(%g$kK%gV!9Txn<=cC2S zplugGPNpq&)UfCfpgKSx=Fa3l;Q#gy#QzVC|DHsX{)6khNq{wDMaC;hYQdbTgUU)W z4^b2(>?9!vT;O`q$$!)0d;SV!h5?ZMYq*CJ$&K!Iat;1ZY^(;rJWjfa9HnF)vj**drCO`*QOfyAE!F+KZVQ5Y*3PV_dZ$)v;$Ac zG6hE3h}0+`ih3H^9#Wo!sP*T0-wnv!VeME>yJLsTiMl0(N*$*!KzC!-dj2>Z^+Cw5 zJ<$yYON-wm*}2)pls;?ZEgrS2u%P4Jfy+p38msm0#JDbo+J_6kx|$~}=Bt&1=6)+z z{|KF`Df20yLNhK}aYGLA*4qa*`Sj0531Q%dmH;uI4Rl)K{q$>aNPeAw3<~jU?RuAl z#NK|`&#r5=4?hDd^%T?C&lXukMy~s*{bM27_w`oIUPP7owSN>5RPC=dcX2p@KT9D{ zcho9+>Jt($aW@y;y7!AcpH6dhJdC2j;>64q`x7vb^?-NtkFtxVT5wnD;7Vm6PFO~Z z@vU!u9X}sRhotx*F?x@=_Zde{vCEY!u$2AAnQk=WPp-u*t9+FobTHl(kLb#+hI0h^ zZRX?AI%{x45{YHqqhS5fFn2wxvWt*+FuQ0j+8iG45fROH?JnvqX>3IaxjnngOYPSf zYBP<1T>Vkhvt!)m;p2U14P~8f(hAl$AY{vVnR?p#?sqBc$Z%j4pR9N^MweW%eFt$! z7c2*bE7{CLZXpB2BxyitMZU(-BwF1pX6c18X%;<1M;dtUvTlvC6$f&n0Bodk(o#?c z$r}@U@ZbT@X=h_IRbRM6 z1#vOJ_?Iejx{7CQ^k*ToL1_Amho?_C9W3v&Z$OlBs{p_a^g8S|DIA`eD(?FnJO)|(Kuc8bZ@N!y=OI02N z5<2%@fLe6g!F2khuv}>x@V%an(|$Z`+`Q$P?u4hU6l-2tL&+JYf_-(*Eurf{f8pzB z;S=cq%1au+^mMQv)jHAWD3ayk>0mu{19Z=4JzyO>H}z2AeD^jCAmRT!J;+Y!EIs0B z&|9je$}TT+Qtz;b%-KP6SIj5McB;zCs2>JRhBh`f_5>>BGu3SwXiQhOzD?hsgzIg? zznK&IL~l_EAMeh6HT22lbx?uryEWbrE$phTuUwcio%rNlZa!Pl%7tB4uUPy@-jy;) zOr>`Qjo%SXk9_{uRB@Y@UArS>(x!6~2DPQJeJ6#ap(fgJQ&d5ag+BWDa3FRwp=wb& zG&h>as5>njzCKg7CLGu{K0yeyFqr;@;I5vV(DXh+b;v>uSwGN8pO*Vof;Es=>kOMohXPO&^L;;;sL*mIhOYwR) z+2MBxsRz$bw$BvD4JHzsvqKGw&DokW#kRfJ>o|$0yDJ-LvzSz+vZGG7n`dO(<>2N) zOP%HbZ|)`|WAl)MKOvP{NIFxA>IK(Bi6&axYU6eD2j{&%G9S+Lx{l#Q(Vc(m{fwiF zZRaOlk>)L5Y&ip`M#k>ug2aPyldy5lbYsTJE@NB&YQY;`jN?@+}M<)H_px2GoY{@?Y*CY|{7}LOEn$B#SJhmx>}R_kH{`?cNg~ zux9bFUfRU0Vzg2wPao&M86JrL_N=6VC$f64CT?fT)p;||a5u3%HrS|r=qRj9)7x=q|y$ru^T?)h`@H2o5PvOCY0 z(VvGP{N8_%W5AwN$p-c3MH%BfXZMXqYO>kokQ9&vj%B%heS8ZU;L+}a+uz*>t=5Us z0{u?~%qBk6)NCWT%{$KBR(UBbQ|*tENlAxQ6t7_RR%z+fEn`e`W9j_g7FzOfk(V}H zA(xoQz?YwBv)i^{hz1bF-E#Sp)J!j-ZKVY)2-;z@q7ryS-d zs8{M3nA{R=p6+yK&F6Q)z~z04ydKe?;Yz?AI5Wx9CvB|rf&iq1&%0wzDEp1jZ*5_U z2`_Ws9I)1i5(Q=sIW5+*(rXv>qs)^_cq=9{N-p9P(4wIoFyUjhjsRzp`iF2ska*@$ zTl?5G6sQ-BZ4%3R55f--<-Z@ga!%%mc$!Dz&tM!56UCmg>^n&X%ofcIPV&adTxQl$ z>;+T&KOx696ErX+CzLp#prRe8civRV3@&I<Z5jlNRS1otBNx zbun$*KSzt%h9sZcS=~XazpLjeJ#Iak6BQxOThHR6Hg!%o8grUn*f%u3t6rVBJ1e{4 z$FpMKP>drvZ(9adAN9dt7M#Otk`?tuKU1S zuvq?%Wzag~)4yXAZ^}nn^s~4cJj#}PR6nx(N;mc}bfbnkxCc4P>ZZ^6Eca8n5*={g zE$SLVN@RBVY;qCBT;9f^?p6YO6K5ucRfbbv$LnhA#4mG@jHgd2Sh7)(RY6osP@E|@ zd|s6p*tW?3MFU6~W9LyP$L^$iIlP~Iz06&P?Y)G!cE*`&ok}3n%^*5NxPQyK>}KlF zna22~X3tMxo!KvobgpctojgAhI`EDnvRzpwO&`)|+Yhd=W7 zXh5n7+|MqnFMitkfQ|8D+Nbq7-;@yj{1fwy@iXAYKhx-Zrz@pS&1#h}{zFxw%+Z~6 zaKQ^vPY!U0eem<*L72%qJAn9S{$y=WMP5ZZo|=&@U|FfREcxf|=}J_Ed%cp`&) zzBhvq3LxPl|Bls#Lki0IZPOxR>dituwT2evj;_`+K$Z1FyxrZ|v=37^(QB(Q9N3j3 zJ51&IS)#*ymZ+j(dQ@o)2NclYrMd%6J^S-x`S?yDS7`A~nH$*5`;EsuFW^B}a||y_ z!oM)hJs%E8a$OcY?wz=#LNi=qakXRj0-Ug~G(_~$zR2Wge*N~_apEOC4WQl?EmSbr z?%js#(!62N1{8Iho?2{Y{dXCHy`!_o-Ka6p z8ns{f{XI~?!Ynn7Tg=|Rj=MDl3&3?Q+DFS*s68yRUCaB!T||v03+625nmo#*(RZzb zc}|_LEZ&nk?OCK1SxZ}z{mfsZIjCvO0(U6&JJAP(jabt2dJT$}fAXOTu21xs-|c6Qs%KB@jc+G4yx>{ngWsf<5>36tFc__p=_ z8cBbp3^-Ch%vT9ywtLm+``xsmSC4YvAE%?2qY_scJ{l>3X!N)~Sok9&CM^#(uJTBB zldDm|3VW-}0uFf6`jT#yFdY1T%GpUOH$ystJVBDv>)>NtU9mB3(jp#pjS+GoKMn%yxb zQU*6GcBN~CPk@y9h#|J%7eAe-bl&KQqX0EaA_XJyYwf>>vH+mqYgW@Qls2S3sonfd z4NK_|q5Z+|YSEWiR<}x#Zs$1-J(*GXO)bID8fPC7?L?~43JyE7p{&^wse_wuT!Wjh zIl?ry9GJ6YMRGIC8iAB~8P}BjSd#F|JWhAC=#gIiH1pIxWDtSsdwR6!HP6@Yf^qLZ zR9`|k-ko`Ke6aTwEdL6IU?}6dShF5@NzKS{s4Q!~EOktKynkMQ@`(!yFfual33rP} zeJ_X2=e=aD*o@U1^G-60CWrVuq7Pzkknv*52tCqVHV`c6B6JELjtkd>bgJ{AFqEnI zN>V!U+>E2F9WEZswNa5=Ieyq#^Wvc`MPi&S84dJyJq>^bG89>Dc}23g@Fgp!;oSk& zueN+l*qIB3nO-pfbh)2Fo2#U?vRp&Xt9X1m%9(!t`7o*Y1)=t{X9h0C6&{gE+ZfC@ zfCJuvhPa|BeXWihgYTJlb)y|Vdl}#z^-aVz5f(<_-o<|+yb-U1(dgFzBwYr}WvAMa z8}@s(?zaH#TxAdqAhzD4S&?gdt9Q3=1R-azy+Nyftf0=Fo1Eqw%xsW$40k;ol{lq8 z)}OH`lwwm8Q!WiNM0nSfb%U}j>lYe!+6EKaAy=heEpTa2px5o&qgdI5)fG^vouT#X zPbVby7BR#p6|RDf+s@TW{96!(ORYQnWM)kzHsWX*Ny5oRs7a}!W2Q>VnqgJBB| za!9*QV;p7hNbi}Xr}bc7>f#GG2G-@_Ha(iE7#Alw4RRI$ldIuQI1swsXmxm$`rAyS zZ-kwV*7Evi(MR@}pcDl=vX+*nONEA+?pTMUjlc)Rg~JsHD+w09P$}Sm8wSSpK)>CU zLq_6cQLsjLqrXUwp@fmN-Nh1Y=9PF>>Vm;+p+Jmwa?tCP=}My-5o*(xzVI94l9lyOajJa)X0Dr1dGB=`Y0C>6C111V(7*E6 zks^sRSj#kJ`E_@9532CjMyP3RQbLwrdp356A1?KeCWehOl&DVvm=)+xzzgX0<7DoGdQ;M(FNFDy@rxh8*RAGYVXQueTwP#Ss=IzlB2>Zowy zoS18>!+Vg@{ItiK!!~$)`Qce9;Y8vL(+p2(j~ylj7P+?jA#DyyiV>a(;m7Xj#Nl!* z6MxY#w|5m1D1s{+rY_=C;y8AwAY2>~O!3EDNBI2nxT)}IO1F)U~93opuxsfD@#9~S;?Qku+!mwC|q#~WLk+oCDl-DEL#hre1C zDC4{3(>NcF&lv)Ey-RGdq}pa zD)Crypo~!u*JM+R6ExQ_Tp~>7i&4@pYV&>Y4bv|T`$et-c1~T6|k*qOR zQndhm1Rw9j#GQtkjDAvXRsT{-Z({P>>O3~eQYFatZ|#WD8LDS-uuFX5BY6(KuCgJh zS?>sC!2sQ8OP6X|?lk2^=D2JdXiav`ktfz^a`S1Ik`$wTx)DQ<9q;G3-TL^)U7;{B zRjJ{4N$~9R?Bcn$Em85=TvbjJK)igF!8tmc=d}qXX%7TEbFCyi_oM#K#!RVB&ubWq zQIVBXi4_2$*ha;yY%ZoY%Y65IhuA_VhBDjR_3t|r_t2|x0d7<$k1$3=yK_^yOwmnh zo=sYiW5Ma5j>~Z1XnX8FK4nfS z=}Ws)gIDU(=kb)LC*{d2@_q{iLo1Y)+r1eRjXg3YxJ%%@^n6yjOx_C8e+Gd1Gi~Kc ICFuMA0c@dJw*UYD literal 0 HcmV?d00001 diff --git a/doc/alarmStatusOnInfineat/infineat_settings.png b/doc/alarmStatusOnInfineat/infineat_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..0dffd5e781ae4b945d25c629d4f88d9c7435a770 GIT binary patch literal 7646 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 literal 0 HcmV?d00001 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..b8320c49 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,183 @@ +{ + "name": "InfiniTime", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "dependencies": { + "lv_font_conv": "^1.5.2" + } + }, + "node_modules/lv_font_conv": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/lv_font_conv/-/lv_font_conv-1.5.2.tgz", + "integrity": "sha512-0UapRSTkVP/pnB8Z4r2HDHx5p2dJx/xUG1+14u/WXo59mwuC7BahR+Bnx/66jKoDrG1wFQwn9ZzoyMxRHOD9bg==", + "bundleDependencies": [ + "argparse", + "bit-buffer", + "debug", + "make-error", + "mkdirp", + "opentype.js", + "pngjs" + ], + "dependencies": { + "argparse": "^2.0.0", + "bit-buffer": "^0.2.5", + "debug": "^4.1.1", + "make-error": "^1.3.5", + "mkdirp": "^1.0.4", + "opentype.js": "^1.1.0", + "pngjs": "^6.0.0" + }, + "bin": { + "lv_font_conv": "lv_font_conv.js" + } + }, + "node_modules/lv_font_conv/node_modules/argparse": { + "version": "2.0.1", + "inBundle": true, + "license": "Python-2.0" + }, + "node_modules/lv_font_conv/node_modules/bit-buffer": { + "version": "0.2.5", + "inBundle": true, + "license": "MIT" + }, + "node_modules/lv_font_conv/node_modules/debug": { + "version": "4.3.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/lv_font_conv/node_modules/make-error": { + "version": "1.3.6", + "inBundle": true, + "license": "ISC" + }, + "node_modules/lv_font_conv/node_modules/mkdirp": { + "version": "1.0.4", + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lv_font_conv/node_modules/ms": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/lv_font_conv/node_modules/opentype.js": { + "version": "1.3.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "string.prototype.codepointat": "^0.2.1", + "tiny-inflate": "^1.0.3" + }, + "bin": { + "ot": "bin/ot" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/lv_font_conv/node_modules/pngjs": { + "version": "6.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/lv_font_conv/node_modules/string.prototype.codepointat": { + "version": "0.2.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/lv_font_conv/node_modules/tiny-inflate": { + "version": "1.0.3", + "inBundle": true, + "license": "MIT" + } + }, + "dependencies": { + "lv_font_conv": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/lv_font_conv/-/lv_font_conv-1.5.2.tgz", + "integrity": "sha512-0UapRSTkVP/pnB8Z4r2HDHx5p2dJx/xUG1+14u/WXo59mwuC7BahR+Bnx/66jKoDrG1wFQwn9ZzoyMxRHOD9bg==", + "requires": { + "argparse": "^2.0.0", + "bit-buffer": "^0.2.5", + "debug": "^4.1.1", + "make-error": "^1.3.5", + "mkdirp": "^1.0.4", + "opentype.js": "^1.1.0", + "pngjs": "^6.0.0" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "bundled": true + }, + "bit-buffer": { + "version": "0.2.5", + "bundled": true + }, + "debug": { + "version": "4.3.1", + "bundled": true, + "requires": { + "ms": "2.1.2" + } + }, + "make-error": { + "version": "1.3.6", + "bundled": true + }, + "mkdirp": { + "version": "1.0.4", + "bundled": true + }, + "ms": { + "version": "2.1.2", + "bundled": true + }, + "opentype.js": { + "version": "1.3.3", + "bundled": true, + "requires": { + "string.prototype.codepointat": "^0.2.1", + "tiny-inflate": "^1.0.3" + } + }, + "pngjs": { + "version": "6.0.0", + "bundled": true + }, + "string.prototype.codepointat": { + "version": "0.2.1", + "bundled": true + }, + "tiny-inflate": { + "version": "1.0.3", + "bundled": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..d339766c --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "lv_font_conv": "^1.5.2" + } +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fd8ece62..0229ce17 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -371,6 +371,7 @@ list(APPEND SOURCE_FILES displayapp/screens/StopWatch.cpp displayapp/screens/BatteryIcon.cpp displayapp/screens/BleIcon.cpp + displayapp/screens/AlarmIcon.cpp displayapp/screens/NotificationIcon.cpp displayapp/screens/SystemInfo.cpp displayapp/screens/Label.cpp diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 06312077..73225ac1 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -47,7 +47,8 @@ namespace Pinetime { struct WatchFaceInfineat { bool showSideCover = true; - int colorIndex = 0; + bool showAlarmStatus = true; + int colorIndex = 0; }; Settings(Pinetime::Controllers::FS& fs); @@ -123,6 +124,17 @@ namespace Pinetime { return settings.watchFaceInfineat.showSideCover; }; + void SetInfineatShowAlarmStatus(bool show) { + if (show != settings.watchFaceInfineat.showAlarmStatus) { + settings.watchFaceInfineat.showAlarmStatus = show; + settingsChanged = true; + } + }; + + bool GetInfineatShowAlarmStatus() const { + return settings.watchFaceInfineat.showAlarmStatus; + }; + void SetInfineatColorIndex(int index) { if (index != settings.watchFaceInfineat.colorIndex) { settings.watchFaceInfineat.colorIndex = index; diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json index 41c383c0..793b8851 100644 --- a/src/displayapp/fonts/fonts.json +++ b/src/displayapp/fonts/fonts.json @@ -7,7 +7,7 @@ }, { "file": "FontAwesome5-Solid+Brands+Regular.woff", - "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743" + "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf0f3, 0xf1f6" } ], "bpp": 1, diff --git a/src/displayapp/screens/AlarmIcon.cpp b/src/displayapp/screens/AlarmIcon.cpp new file mode 100644 index 00000000..fda87130 --- /dev/null +++ b/src/displayapp/screens/AlarmIcon.cpp @@ -0,0 +1,11 @@ +#include "displayapp/screens/AlarmIcon.h" +#include "displayapp/screens/Symbols.h" +using namespace Pinetime::Applications::Screens; + +const char* AlarmIcon::GetIcon(bool isSet) { + if (isSet) { + return Symbols::bell; + } + + return Symbols::notbell; +} diff --git a/src/displayapp/screens/AlarmIcon.h b/src/displayapp/screens/AlarmIcon.h new file mode 100644 index 00000000..678a4cb7 --- /dev/null +++ b/src/displayapp/screens/AlarmIcon.h @@ -0,0 +1,14 @@ +#pragma once + +#include "components/alarm/AlarmController.h" + +namespace Pinetime { + namespace Applications { + namespace Screens { + class AlarmIcon { + public: + static const char* GetIcon(bool isSet); + }; + } + } +} diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index bd958b28..cf1b8aad 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -12,6 +12,7 @@ namespace Pinetime { static constexpr const char* shoe = "\xEF\x95\x8B"; static constexpr const char* clock = "\xEF\x80\x97"; static constexpr const char* bell = "\xEF\x83\xB3"; + static constexpr const char* notbell = "\xEF\x87\xB6"; static constexpr const char* info = "\xEF\x84\xA9"; static constexpr const char* list = "\xEF\x80\xBA"; static constexpr const char* sun = "\xEF\x86\x85"; diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index 4c6fc196..20d0beb3 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -4,9 +4,11 @@ #include #include "displayapp/screens/Symbols.h" #include "displayapp/screens/BleIcon.h" +#include "displayapp/screens/AlarmIcon.h" #include "components/settings/Settings.h" #include "components/battery/BatteryController.h" #include "components/ble/BleController.h" +#include "components/alarm/AlarmController.h" #include "components/ble/NotificationManager.h" #include "components/motion/MotionController.h" @@ -122,6 +124,7 @@ namespace { WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, + Controllers::AlarmController& alarmController, Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::MotionController& motionController, @@ -130,6 +133,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, dateTimeController {dateTimeController}, batteryController {batteryController}, bleController {bleController}, + alarmController {alarmController}, notificationManager {notificationManager}, settingsController {settingsController}, motionController {motionController} { @@ -144,6 +148,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, font_bebas = lv_font_load("F:/fonts/bebas.bin"); } + // Side Cover static constexpr lv_point_t linePoints[nLines][2] = {{{30, 25}, {68, -8}}, {{26, 167}, {43, 216}}, @@ -230,6 +235,31 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, lv_label_set_text_static(bleIcon, Symbols::bluetooth); lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + labelAlarm = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_obj_set_style_local_text_font(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); + lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); + lv_label_set_text_static(labelAlarm, "00:00"); + + labelTimeAmPmAlarm = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(labelTimeAmPmAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_label_set_text_static(labelTimeAmPmAlarm, ""); + lv_obj_set_style_local_text_color(labelTimeAmPmAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_obj_align(labelTimeAmPmAlarm, labelAlarm, LV_ALIGN_OUT_TOP_RIGHT, 0, 0); + + alarmIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(alarmIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_label_set_text_static(alarmIcon, Symbols::notbell); + lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); + + // don't show the icons jsut set if we don't show alarm status + if (!settingsController.GetInfineatShowAlarmStatus()) { + lv_obj_set_hidden(labelAlarm, true); + lv_obj_set_hidden(alarmIcon, true); + lv_obj_set_hidden(labelTimeAmPmAlarm, true); + } + stepValue = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); @@ -275,7 +305,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, btnToggleCover = lv_btn_create(lv_scr_act(), nullptr); btnToggleCover->user_data = this; lv_obj_set_size(btnToggleCover, 60, 60); - lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_CENTER, 0, 80); + lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_CENTER, 0,0); lv_obj_set_style_local_bg_opa(btnToggleCover, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); const char* labelToggle = settingsController.GetInfineatShowSideCover() ? "ON" : "OFF"; lblToggle = lv_label_create(btnToggleCover, nullptr); @@ -283,6 +313,17 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, lv_obj_set_event_cb(btnToggleCover, event_handler); lv_obj_set_hidden(btnToggleCover, true); + btnToggleAlarm = lv_btn_create(lv_scr_act(), nullptr); + btnToggleAlarm->user_data = this; + lv_obj_set_size(btnToggleAlarm, 60, 60); + lv_obj_align(btnToggleAlarm, lv_scr_act(), LV_ALIGN_CENTER, 0, 80); + lv_obj_set_style_local_bg_opa(btnToggleAlarm, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + const char* labelToggleAlarm = settingsController.GetInfineatShowAlarmStatus() ? Symbols::bell : Symbols::notbell; + lblAlarm = lv_label_create(btnToggleAlarm, nullptr); + lv_label_set_text_static(lblAlarm, labelToggleAlarm); + lv_obj_set_event_cb(btnToggleAlarm, event_handler); + lv_obj_set_hidden(btnToggleAlarm, true); + // Button to access the settings btnSettings = lv_btn_create(lv_scr_act(), nullptr); btnSettings->user_data = this; @@ -332,6 +373,7 @@ void WatchFaceInfineat::CloseMenu() { lv_obj_set_hidden(btnNextColor, true); lv_obj_set_hidden(btnPrevColor, true); lv_obj_set_hidden(btnToggleCover, true); + lv_obj_set_hidden(btnToggleAlarm, true); } bool WatchFaceInfineat::OnButtonPushed() { @@ -346,6 +388,7 @@ void WatchFaceInfineat::UpdateSelected(lv_obj_t* object, lv_event_t event) { if (event == LV_EVENT_CLICKED) { bool showSideCover = settingsController.GetInfineatShowSideCover(); int colorIndex = settingsController.GetInfineatColorIndex(); + bool showAlarmStatus = settingsController.GetInfineatShowAlarmStatus(); if (object == btnSettings) { lv_obj_set_hidden(btnSettings, true); @@ -353,6 +396,7 @@ void WatchFaceInfineat::UpdateSelected(lv_obj_t* object, lv_event_t event) { lv_obj_set_hidden(btnNextColor, !showSideCover); lv_obj_set_hidden(btnPrevColor, !showSideCover); lv_obj_set_hidden(btnToggleCover, false); + lv_obj_set_hidden(btnToggleAlarm, false); } if (object == btnClose) { CloseMenu(); @@ -368,6 +412,18 @@ void WatchFaceInfineat::UpdateSelected(lv_obj_t* object, lv_event_t event) { const char* labelToggle = showSideCover ? "OFF" : "ON"; lv_label_set_text_static(lblToggle, labelToggle); } + + if (object == btnToggleAlarm) { + settingsController.SetInfineatShowAlarmStatus(!showAlarmStatus); + bool newShowAlarmStatus = settingsController.GetInfineatShowAlarmStatus(); + lv_obj_set_hidden(labelAlarm, !newShowAlarmStatus); + lv_obj_set_hidden(alarmIcon, !newShowAlarmStatus); + lv_obj_set_hidden(labelTimeAmPmAlarm, !newShowAlarmStatus); + const char* labelToggleAlarm = newShowAlarmStatus ? Symbols::bell : Symbols::notbell; + lv_label_set_text_static(lblAlarm, labelToggleAlarm); + } + + if (object == btnNextColor) { colorIndex = (colorIndex + 1) % nColors; settingsController.SetInfineatColorIndex(colorIndex); @@ -452,6 +508,43 @@ void WatchFaceInfineat::Refresh() { lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 3); } + if (settingsController.GetInfineatShowAlarmStatus()) { + alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; + // sets the icon as bell or barred bell + lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); + //displays the time of the alarm or nothing if the alarm is not set + if (alarmState) { + uint8_t alarmHours = alarmController.Hours(); + uint8_t alarmMinutes = alarmController.Minutes(); + //handles the am pm format. + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + char ampmChar[3] = "AM"; + if (alarmHours == 0) { + alarmHours = 12; + } else if (alarmHours == 12) { + ampmChar[0]='P'; + } else if (alarmHours > 12) { + alarmHours = alarmHours - 12; + ampmChar[0]='P'; + } + lv_label_set_text(labelTimeAmPmAlarm, ampmChar); + lv_obj_set_style_local_text_font(labelTimeAmPmAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(labelTimeAmPmAlarm, labelAlarm, LV_ALIGN_OUT_TOP_RIGHT, 0, 0); + } + + lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); + + lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); + lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); + lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); + + } + else { + lv_label_set_text_static(labelAlarm, Symbols::none); + lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + lv_obj_align(alarmIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); + } + } stepCount = motionController.NbSteps(); if (stepCount.IsUpdated()) { lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); diff --git a/src/displayapp/screens/WatchFaceInfineat.h b/src/displayapp/screens/WatchFaceInfineat.h index 55c43f98..7ea134f2 100644 --- a/src/displayapp/screens/WatchFaceInfineat.h +++ b/src/displayapp/screens/WatchFaceInfineat.h @@ -28,6 +28,7 @@ namespace Pinetime { WatchFaceInfineat(Controllers::DateTime& dateTimeController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, + Controllers::AlarmController& alarmController, Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::MotionController& motionController, @@ -52,6 +53,7 @@ namespace Pinetime { Utility::DirtyValue isCharging {}; Utility::DirtyValue bleState {}; Utility::DirtyValue bleRadioEnabled {}; + bool alarmState {}; Utility::DirtyValue> currentDateTime {}; Utility::DirtyValue stepCount {}; Utility::DirtyValue notificationState {}; @@ -71,22 +73,28 @@ namespace Pinetime { lv_obj_t* dateContainer; lv_obj_t* labelDate; lv_obj_t* bleIcon; + lv_obj_t* labelAlarm; + lv_obj_t* labelTimeAmPmAlarm; + lv_obj_t* alarmIcon; lv_obj_t* stepIcon; lv_obj_t* stepValue; lv_obj_t* notificationIcon; lv_obj_t* btnClose; lv_obj_t* btnNextColor; lv_obj_t* btnToggleCover; + lv_obj_t* btnToggleAlarm; lv_obj_t* btnPrevColor; lv_obj_t* btnSettings; lv_obj_t* labelBtnSettings; lv_obj_t* lblToggle; + lv_obj_t* lblAlarm; lv_obj_t* lines[nLines]; Controllers::DateTime& dateTimeController; const Controllers::Battery& batteryController; const Controllers::Ble& bleController; + Controllers::AlarmController& alarmController; Controllers::NotificationManager& notificationManager; Controllers::Settings& settingsController; Controllers::MotionController& motionController; @@ -94,6 +102,8 @@ namespace Pinetime { void SetBatteryLevel(uint8_t batteryPercent); void ToggleBatteryIndicatorColor(bool showSideCover); + void ToggleShowAlarmStatus(bool showAlarmStatus); + lv_task_t* taskRefresh; lv_font_t* font_teko = nullptr; lv_font_t* font_bebas = nullptr; @@ -109,6 +119,7 @@ namespace Pinetime { return new Screens::WatchFaceInfineat(controllers.dateTimeController, controllers.batteryController, controllers.bleController, + controllers.alarmController, controllers.notificationManager, controllers.settingsController, controllers.motionController, From a0c9b4d5fc160d56e59b1f87761a91bd8eada4cf Mon Sep 17 00:00:00 2001 From: Eve C Date: Thu, 30 May 2024 13:23:25 +0200 Subject: [PATCH 038/101] cat as battery icon : modified the lines of infineat and position of picture --- .gitignore | 1 + draft_pictures/cat_small.xcf | Bin 1846 -> 2316 bytes draft_pictures/coordinates.xcf | Bin 0 -> 1366918 bytes node_modules/.package-lock.json | 113 - node_modules/lv_font_conv/CHANGELOG.md | 164 - node_modules/lv_font_conv/LICENSE | 22 - node_modules/lv_font_conv/README.md | 150 - node_modules/lv_font_conv/lib/app_error.js | 9 - node_modules/lv_font_conv/lib/cli.js | 318 - .../lv_font_conv/lib/collect_font_data.js | 173 - node_modules/lv_font_conv/lib/convert.js | 30 - .../lib/font/cmap_build_subtables.js | 105 - .../lv_font_conv/lib/font/compress.js | 107 - node_modules/lv_font_conv/lib/font/font.js | 131 - .../lv_font_conv/lib/font/table_cmap.js | 201 - .../lv_font_conv/lib/font/table_glyf.js | 147 - .../lv_font_conv/lib/font/table_head.js | 99 - .../lv_font_conv/lib/font/table_kern.js | 256 - .../lv_font_conv/lib/font/table_loca.js | 42 - .../lv_font_conv/lib/freetype/index.js | 317 - .../lv_font_conv/lib/freetype/render.c | 83 - node_modules/lv_font_conv/lib/ranger.js | 51 - node_modules/lv_font_conv/lib/utils.js | 131 - node_modules/lv_font_conv/lib/writers/bin.js | 17 - node_modules/lv_font_conv/lib/writers/dump.js | 68 - .../lv_font_conv/lib/writers/lvgl/index.js | 17 - .../lv_font_conv/lib/writers/lvgl/lv_font.js | 98 - .../lib/writers/lvgl/lv_table_cmap.js | 125 - .../lib/writers/lvgl/lv_table_glyf.js | 121 - .../lib/writers/lvgl/lv_table_head.js | 99 - .../lib/writers/lvgl/lv_table_kern.js | 121 - node_modules/lv_font_conv/lv_font_conv.js | 17 - .../node_modules/argparse/CHANGELOG.md | 216 - .../node_modules/argparse/LICENSE | 254 - .../node_modules/argparse/README.md | 84 - .../node_modules/argparse/argparse.js | 3707 ---- .../node_modules/argparse/lib/sub.js | 67 - .../node_modules/argparse/lib/textwrap.js | 440 - .../node_modules/argparse/package.json | 67 - .../bit-buffer/.github/workflows/ci.yml | 36 - .../node_modules/bit-buffer/LICENSE | 21 - .../node_modules/bit-buffer/README.md | 148 - .../node_modules/bit-buffer/bit-buffer.d.ts | 115 - .../node_modules/bit-buffer/bit-buffer.js | 502 - .../node_modules/bit-buffer/package.json | 71 - .../node_modules/bit-buffer/test.js | 628 - .../lv_font_conv/node_modules/debug/LICENSE | 19 - .../lv_font_conv/node_modules/debug/README.md | 455 - .../node_modules/debug/package.json | 111 - .../node_modules/debug/src/browser.js | 269 - .../node_modules/debug/src/common.js | 261 - .../node_modules/debug/src/index.js | 10 - .../node_modules/debug/src/node.js | 263 - .../node_modules/make-error/LICENSE | 5 - .../node_modules/make-error/README.md | 112 - .../make-error/dist/make-error.js | 1 - .../node_modules/make-error/index.d.ts | 47 - .../node_modules/make-error/index.js | 151 - .../node_modules/make-error/package.json | 95 - .../node_modules/mkdirp/CHANGELOG.md | 15 - .../lv_font_conv/node_modules/mkdirp/LICENSE | 21 - .../node_modules/mkdirp/bin/cmd.js | 68 - .../lv_font_conv/node_modules/mkdirp/index.js | 31 - .../node_modules/mkdirp/lib/find-made.js | 29 - .../node_modules/mkdirp/lib/mkdirp-manual.js | 64 - .../node_modules/mkdirp/lib/mkdirp-native.js | 39 - .../node_modules/mkdirp/lib/opts-arg.js | 23 - .../node_modules/mkdirp/lib/path-arg.js | 29 - .../node_modules/mkdirp/lib/use-native.js | 10 - .../node_modules/mkdirp/package.json | 78 - .../node_modules/mkdirp/readme.markdown | 266 - .../lv_font_conv/node_modules/ms/index.js | 162 - .../lv_font_conv/node_modules/ms/license.md | 21 - .../lv_font_conv/node_modules/ms/package.json | 72 - .../lv_font_conv/node_modules/ms/readme.md | 60 - .../node_modules/opentype.js/LICENSE | 20 - .../node_modules/opentype.js/README.md | 313 - .../node_modules/opentype.js/RELEASES.md | 267 - .../node_modules/opentype.js/bin/ot | 84 - .../node_modules/opentype.js/bin/server.js | 64 - .../node_modules/opentype.js/bin/test-render | 96 - .../node_modules/opentype.js/dist/opentype.js | 14254 ---------------- .../node_modules/opentype.js/package.json | 102 - .../lv_font_conv/node_modules/pngjs/LICENSE | 20 - .../lv_font_conv/node_modules/pngjs/README.md | 433 - .../node_modules/pngjs/lib/bitmapper.js | 267 - .../node_modules/pngjs/lib/bitpacker.js | 158 - .../node_modules/pngjs/lib/chunkstream.js | 189 - .../node_modules/pngjs/lib/constants.js | 32 - .../node_modules/pngjs/lib/crc.js | 40 - .../node_modules/pngjs/lib/filter-pack.js | 171 - .../pngjs/lib/filter-parse-async.js | 24 - .../pngjs/lib/filter-parse-sync.js | 21 - .../node_modules/pngjs/lib/filter-parse.js | 177 - .../pngjs/lib/format-normaliser.js | 93 - .../node_modules/pngjs/lib/interlace.js | 95 - .../node_modules/pngjs/lib/packer-async.js | 50 - .../node_modules/pngjs/lib/packer-sync.js | 56 - .../node_modules/pngjs/lib/packer.js | 129 - .../node_modules/pngjs/lib/paeth-predictor.js | 16 - .../node_modules/pngjs/lib/parser-async.js | 169 - .../node_modules/pngjs/lib/parser-sync.js | 112 - .../node_modules/pngjs/lib/parser.js | 290 - .../node_modules/pngjs/lib/png-sync.js | 12 - .../node_modules/pngjs/lib/png.js | 194 - .../node_modules/pngjs/lib/sync-inflate.js | 168 - .../node_modules/pngjs/lib/sync-reader.js | 45 - .../node_modules/pngjs/package.json | 129 - .../LICENSE-MIT.txt | 20 - .../string.prototype.codepointat/README.md | 47 - .../codepointat.js | 54 - .../string.prototype.codepointat/package.json | 62 - .../node_modules/tiny-inflate/LICENSE | 21 - .../node_modules/tiny-inflate/index.js | 375 - .../node_modules/tiny-inflate/package.json | 60 - .../node_modules/tiny-inflate/readme.md | 31 - .../node_modules/tiny-inflate/test/index.js | 75 - .../node_modules/tiny-inflate/test/lorem.txt | 199 - node_modules/lv_font_conv/package.json | 61 - src/displayapp/screens/WatchFaceMeow.cpp | 39 +- src/resources/images/cat_clean.png | Bin 1344 -> 1778 bytes 121 files changed, 23 insertions(+), 31787 deletions(-) create mode 100644 draft_pictures/coordinates.xcf delete mode 100644 node_modules/.package-lock.json delete mode 100644 node_modules/lv_font_conv/CHANGELOG.md delete mode 100644 node_modules/lv_font_conv/LICENSE delete mode 100644 node_modules/lv_font_conv/README.md delete mode 100644 node_modules/lv_font_conv/lib/app_error.js delete mode 100644 node_modules/lv_font_conv/lib/cli.js delete mode 100644 node_modules/lv_font_conv/lib/collect_font_data.js delete mode 100644 node_modules/lv_font_conv/lib/convert.js delete mode 100644 node_modules/lv_font_conv/lib/font/cmap_build_subtables.js delete mode 100644 node_modules/lv_font_conv/lib/font/compress.js delete mode 100644 node_modules/lv_font_conv/lib/font/font.js delete mode 100644 node_modules/lv_font_conv/lib/font/table_cmap.js delete mode 100644 node_modules/lv_font_conv/lib/font/table_glyf.js delete mode 100644 node_modules/lv_font_conv/lib/font/table_head.js delete mode 100644 node_modules/lv_font_conv/lib/font/table_kern.js delete mode 100644 node_modules/lv_font_conv/lib/font/table_loca.js delete mode 100644 node_modules/lv_font_conv/lib/freetype/index.js delete mode 100644 node_modules/lv_font_conv/lib/freetype/render.c delete mode 100644 node_modules/lv_font_conv/lib/ranger.js delete mode 100644 node_modules/lv_font_conv/lib/utils.js delete mode 100644 node_modules/lv_font_conv/lib/writers/bin.js delete mode 100644 node_modules/lv_font_conv/lib/writers/dump.js delete mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/index.js delete mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/lv_font.js delete mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/lv_table_cmap.js delete mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/lv_table_glyf.js delete mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/lv_table_head.js delete mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/lv_table_kern.js delete mode 100755 node_modules/lv_font_conv/lv_font_conv.js delete mode 100644 node_modules/lv_font_conv/node_modules/argparse/CHANGELOG.md delete mode 100644 node_modules/lv_font_conv/node_modules/argparse/LICENSE delete mode 100644 node_modules/lv_font_conv/node_modules/argparse/README.md delete mode 100644 node_modules/lv_font_conv/node_modules/argparse/argparse.js delete mode 100644 node_modules/lv_font_conv/node_modules/argparse/lib/sub.js delete mode 100644 node_modules/lv_font_conv/node_modules/argparse/lib/textwrap.js delete mode 100644 node_modules/lv_font_conv/node_modules/argparse/package.json delete mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/.github/workflows/ci.yml delete mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/LICENSE delete mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/README.md delete mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.d.ts delete mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.js delete mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/package.json delete mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/test.js delete mode 100644 node_modules/lv_font_conv/node_modules/debug/LICENSE delete mode 100644 node_modules/lv_font_conv/node_modules/debug/README.md delete mode 100644 node_modules/lv_font_conv/node_modules/debug/package.json delete mode 100644 node_modules/lv_font_conv/node_modules/debug/src/browser.js delete mode 100644 node_modules/lv_font_conv/node_modules/debug/src/common.js delete mode 100644 node_modules/lv_font_conv/node_modules/debug/src/index.js delete mode 100644 node_modules/lv_font_conv/node_modules/debug/src/node.js delete mode 100644 node_modules/lv_font_conv/node_modules/make-error/LICENSE delete mode 100644 node_modules/lv_font_conv/node_modules/make-error/README.md delete mode 100644 node_modules/lv_font_conv/node_modules/make-error/dist/make-error.js delete mode 100644 node_modules/lv_font_conv/node_modules/make-error/index.d.ts delete mode 100644 node_modules/lv_font_conv/node_modules/make-error/index.js delete mode 100644 node_modules/lv_font_conv/node_modules/make-error/package.json delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/CHANGELOG.md delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/LICENSE delete mode 100755 node_modules/lv_font_conv/node_modules/mkdirp/bin/cmd.js delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/index.js delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/find-made.js delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-manual.js delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-native.js delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/opts-arg.js delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/path-arg.js delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/use-native.js delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/package.json delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/readme.markdown delete mode 100644 node_modules/lv_font_conv/node_modules/ms/index.js delete mode 100644 node_modules/lv_font_conv/node_modules/ms/license.md delete mode 100644 node_modules/lv_font_conv/node_modules/ms/package.json delete mode 100644 node_modules/lv_font_conv/node_modules/ms/readme.md delete mode 100644 node_modules/lv_font_conv/node_modules/opentype.js/LICENSE delete mode 100644 node_modules/lv_font_conv/node_modules/opentype.js/README.md delete mode 100644 node_modules/lv_font_conv/node_modules/opentype.js/RELEASES.md delete mode 100755 node_modules/lv_font_conv/node_modules/opentype.js/bin/ot delete mode 100755 node_modules/lv_font_conv/node_modules/opentype.js/bin/server.js delete mode 100755 node_modules/lv_font_conv/node_modules/opentype.js/bin/test-render delete mode 100644 node_modules/lv_font_conv/node_modules/opentype.js/dist/opentype.js delete mode 100644 node_modules/lv_font_conv/node_modules/opentype.js/package.json delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/LICENSE delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/README.md delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/bitmapper.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/bitpacker.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/chunkstream.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/constants.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/crc.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/filter-pack.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-async.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-sync.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/format-normaliser.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/interlace.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/packer-async.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/packer-sync.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/packer.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/paeth-predictor.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/parser-async.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/parser-sync.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/parser.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/png-sync.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/png.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/sync-inflate.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/sync-reader.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/package.json delete mode 100644 node_modules/lv_font_conv/node_modules/string.prototype.codepointat/LICENSE-MIT.txt delete mode 100644 node_modules/lv_font_conv/node_modules/string.prototype.codepointat/README.md delete mode 100644 node_modules/lv_font_conv/node_modules/string.prototype.codepointat/codepointat.js delete mode 100644 node_modules/lv_font_conv/node_modules/string.prototype.codepointat/package.json delete mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/LICENSE delete mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/index.js delete mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/package.json delete mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/readme.md delete mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/test/index.js delete mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/test/lorem.txt delete mode 100644 node_modules/lv_font_conv/package.json diff --git a/.gitignore b/.gitignore index 13a8b796..73a489dd 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ tools # My stuff #draft_pictures +node_modules # Resulting binary files *.a diff --git a/draft_pictures/cat_small.xcf b/draft_pictures/cat_small.xcf index 76c7751d249a931636095194a823061a79946aed..254bdd310bf51b7d13e52cc142ba3bcccb7dac04 100644 GIT binary patch literal 2316 zcmeHJeQZ-z6hD2h-(B~{7!Vckfru=y$kbpo8bA;g!34>Ch(su(tIbA7*1^`TUB^c| z$2t+11XLtAzC>aS2`q!*0B1sEQ-b3|EwuLZ+pinqg;%Njm!AKY$ z;~e>6gj0kR8>P;$FSppN8MXrZGMWOG=fDJKp)1cSI`i}F%hHV#a%|c8dHDt6gs96X zur0NSlcQH=I>}2PB4>)xi^;{#6_#wf!zN}|&6GHB@Gv{DL7JYFNE0%e?}mePO|a3U`mDgM92|NdK)^|@~i zhdyr3JTPs)urLjJO}jmNz;q`NTBI5R-RAm>JDb6z`0qu0DgnAiwPIzk8D7RS`%$Un z%+NXI5(fR)3{z1lkx|)VFu|D@?;>uy9-YL4rx1HBPfyFr@{C!s!JU;g{ddH$7YEcH zAGTK9;xY2`^Eony9pTNt;Ng>q9l9wOE?!)*es6@wu;QuM)u={!Z+!`eeLV)ga>tJM z{2C0k90>eTCaY1|_7YcmKaYRYhgg#vd*vezy zkKbaTU3M!RmczcLLq&)U$0oYu)sf~pUpJy$rjU-ujv^|hauJ5S%A6`ZySKUS*PV!U z-y>5MPXrr};#y_<6rx`3MvgV-E?v%XSE#J`NqPQZalr%SNYYVSt@S0 zn@1(k&LQsRq4U+!{T1H1<1XV*``hT#q;KXhM0ds8k8sd7yMJvjULBpj>1J67Ptccf zJk`WhI|n^4+{NFv->iO2b;$P_hrx9K{E}$3CzH+QG1oY3EBR<6!2ETU_Yu2n$G6Si zMSJ_6+Z^feb9k-0q$U+W=MM&C43}V8(}GKZ_D?Ovk?S>xeO0|PcxW@OIky#+qPx}i z%KYn2Zz*p4u4NyFYfjHoY2fz6%?EH*?K#DD1I9*j2T_t4V`?&77@Q>Wjv*HZu4hAk4SsS+fi6`3`Ze#hJ^b*k^sP zoq1_)pq(-GpSLrS_QZ20$2e<+DFmKCHlDybTB5}}5nGyI`c?|!9Vn*hg9!`b8ci7n KJE52V68{EcShnc^ literal 1846 zcmeHIT})eb6hFN!EiEl~6A&YF-fcQTgQ0uCJ#B{J$0E~ZAGv1n!BX0K18cEm1hSA3 zr^aP5CVuRtQVL~TMoacVMobj9=@M~66gHEYIG4<%QCTeuD^ON?Jg4-UyJSB2;G5^> zch2ve^FOEmdzzkll)bG|htn;!*Vyeq@*ts{3CR({N`g*Ds)SIu<&&SQ7{p5n27-ma zMa%_48DWgD!b7dAmAwv+tJ>pNnkWR^Csr9~>uhmJ0bh&KRAs4jdz`)&pI@rXnQFhM z(IM639(xs~8}F00OSy;B9f4MdQ}KEvdkuM=_jq}ydY@NmlN7J>4Of6Gd~SEZ)h6Z6 zYxpv~_b}gCBf1wf4pHN?8ebh8zhhCI!`bZd`}TQrugzm&F%o!8NLa&sb2n)kD}m!& zYR+>quvWuYX*iFZ&$CDbl^1EaRl{u>E)wU@Q^iDt>$(5qc}@l%V~!+b*J-%=wX5$$ zDHdVJvdw9A6#07z-uvXZ=3O-|h$0BGUbKQiHi#BLy+JMz3&ALxMJ%?kF4O4J9RDX> zVxb-gdJ#+YGC~1$LJ-IkWF2|biz*Qc$(kubw#+Q+!6;&hNj3r+3&aB10tV3_7Xj+1 zay|7iTe-&*f{cYmV*lUwznt&VQynzn^tx6z)qXrahc_!sw(>`wuC-Z}3?55ybo%sk?=QD;>E(|Foc-)drXTT{ z=fmMWrfrC1C2?gIANuO||i~|FVF0GvVkT5sGGa^$dRSz@dJO^a$7uQzP+c z@?-+>%i~wErztW%b#(GOTo}M;a+}Q}3O@-T@3@{5%ntS8$tjG# zmilHQa-lzXaUc=t#rekH@bd79;Kr_xcKnHVJ4X@Ub}C!zKOH{1bN1#)JqTxzI^Zn* z?p%EfiOpM2g|hK-D2;P;Kfp`#GqJO^meE6qG5eO?nP~FI`anYxvHgSq&wW3JiJ{?x zgV98DI|dQII`=4m-W|KX&>xyO(tYgxFRle~VzABtGpk?=lJByEM b#F!;V-BtOV>0!+QHAf-S1~q;_w}^iM?F8FJ diff --git a/draft_pictures/coordinates.xcf b/draft_pictures/coordinates.xcf new file mode 100644 index 0000000000000000000000000000000000000000..e9e6d040cd1c7b33629e6522e3f3d1155dac751c GIT binary patch literal 1366918 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ⅈkIp#eUsr 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 J(d|xhpsyVbyHzc&r3y~Lsuqj zFVfASYgc`hYhB0G&gRfHhpstv?N!j`TGy_1AlJI0-irmpnW$1zC}_u4rjAXRjVd)o zsab1L)0UqKi{{XE0E?#9y2@T;yqUm!%puctj{Uh1mfT9>xx<0qF(vzR4$`6T#=5cQ z%%>mO1-pvbn>f=*D$KOlX##GI=ls5X5c>0FkAt%7j`j+r-YUvmE)zZRo_Sv#i_W@z z$m}8Jzs{9sLW@$`xeak3%170H(5@2mg55U8j8J#K?hbjb+WXCJY=k<)o+*9NdHcIK z`WLCqE#4<9LRg>he;qnqZ{7dx`*-cKzaZ|ym`~6GM^A|y2zCY=4;O($11+vIMv64v zV2mpEJZJT@rq|KUwmXC-r<|*f6MDy>bHsVwK0uA$bdgK4e}bdc(?tJ=-}htpkae?( zp95UNvFkg||K;1JV1MQgkB&Q?7~?S`AZZ^9XxGxQU)RyUMf?wdVo%^FW|YY`=Wmy!FK z8@cSC&{1UGiDQ%JM(-vwxk;pMEKP;IadQmckp!92FH zH=zF%0%6TQV(ztXy7~^KfY)3&iNfj9NpswO%N{G93Qpq5K!gf=0)K|H!D)Nae$`Cb z7Y#R@DD*@x?Z(OQZj+N^(kGW)eA&E0@QGJSxGLDM+Mn?2Xz;ST%;YEmDGu}NRc@~O z3N?AsyxL3cfAwpr=tq~XyRbNFuedEM&OHdTC_G0RwsxJj(DC(a%%{)N9~d1|QA_rP zoY7)+=tu(QDd}3P97jnWyY=B0k+dA^2}$tXyOH3bgdOXd?pxqy-oM7mAhY`=%{+d0 zq^5X%-N{tKAhaHQ=t17|7JTmHi;n)f8NRq>hBuk7a;CcDWIxwo-K8fs0P&Kv2cjCO zXEiISJvC|&;-JC!?Y6&hHOh{<39^xIqegcH3D_t1>1XQW=Ii}Vno0hRMX!;rNdD;w zNqF77p;hlPE$$mP{JqwDP>-Ajv!<66!nOe7`iSrjxBAeJdcO`oNaF9J{$c2j%;>=+ z>quy?+%y+i8>8NLydaJc$4g#nu9d(6n%n8@qv({p6s*-c6hy3L(Hj_l>` zoFn_4(eI;`$W#ZPw)}B5@VU-DV=aHKv(I(*JE`T*b@oofpX=;pH zToU2cgLV{s1Irq|IE3*I?C;I<`v_!i0$hT&mCZ@cGL8$lN33I%kior8;lvo(3tYEt zf4li!i={mvjzCF94_z|xZ{~%47^6(XYY)l9>gG-hVq238_vnxpZ9t_0bI0?jmyTag zkQuedv`V@_r=d6_w72HkOO$j>NUlF7&G-C(s&PQpb!h{uV1ov~XzP1r;b}5j!?UTL z4t7&1?BG}&#U=kn=n;RfQd*w3eS&s(#2dvh4*hQ#Z+~CQta}?2&s)P9$Zjahoo9bX z3q1NB<`hG0n}TsyXbJj85e-@}e2cm8gMIF@OJ>71vV+#AU^6#MJo`3hoSS=v2it)k zpWgQ0xQFXO%o(B>3{a243EgY%nwh!(CDIB)P zY|VV#zP|SeQ6he7UL~!Y>m7dS@KA!m$GumX?*+et`45)}o#1zk+{Zz6xk<$5AR~Og zj8BE)7|`Rgme=jSGOEpcVX4V887UptFE%S;lomz)fJxfGY|!!uEwstY_$fJF{vDHF zGXc4^?d(Wy$>m_3206(RO|-E1Z4%Hevawg-cgUxQUMU%O}eO68m4JVQNhI&X- z$>P|;#MX4)pn#@n(+6P(ZZEso)GbPGrfMp;os?YnSXZLanIIcxisTqUBZ$@EVj>{A zZELUAoi&fUyhafCOyf|JNc7A89oRpZ7x&2y&coZtrD5TsLZ?c{smL4w9s8_p?86(z z;}RMdrGVkm?HY1L2@qn>!<(u+VTMK!Oi^CGR_AN=(51XrfaPoT1WS3oR>!R3`C2{6 zVxF(nZqJdg)edFm`|1cEZQXR|`|5mOJ;}B^U#s)Ay0dN1_tp8ndeVJ0fhz<1CsZJH zsjRq^GO&L{qq4>s)wr(ABlSH{4!5gCZRN`T%9u)95#Jf`{pdUy4$m zwXGR6TieWU2e;8nT%deuI;9*AI78u+@{DcFpf%fe{yMe2V4gzx-NrH*#=ceQ^%+{j zyOMKt!Zys6{`;10Y<4wOy(Y~I&m;T^rAU1*3Or|3_#?km=A?OwcGX75LzLOQ+$=6V zap(8!@H{DK{$QTlr#nUzkyhi%X*+s`{m?{t8xc356LQ+R@HAl?-Xqo~ZItR??+o)b z-twGlyZ3H8e^xCQpXA-ms_nect^(KXb~AoxAr`i!jP>3_kyAz!lgiz5m6&y6=ejseO%e zh5G#s``XMA)0f+RX=wJAhZUs0Y5z*h+BGLY^t(Z`m+YO{A$hJ#bR6kV3;@g-nO7JZ zH{iEtIOfTaZ6{x$i^WjSpdyu8GCY5XB6Cq~tm!Z#P#{F;sbLlBktR35Ac!PRV*0?ZO&{H(Jn#%6s@H1I8>(NlpZM(&KxQmksok0OjGaKeJw(A zhtu#m>f%wC^f@g8F=*_bKrn|#al)ldqq*(`m&D`m&C@afBe?Zu!n7tMU9o3u+#-pPi$ z=;Zw23+Hxn^F=qsPItEsY)Yy8Hj1!2xPB@S55LU^GYu4y3Qid6Xx8wpX8+7?y=neD zqg}rg$`Umf%yUi;5MGp}>&f2VcD@`4c{ADo&%VQMy^gnSosdq1w3(SS4^0Xo8l-RV z*>gOLqEY2Z&50`6tjvtccdFz_*{bJNC$u?eXb37qtxz;in(I`*>S-xJ8)_@=iK^Y@ z8C1={)%1DnUY&|MQZ8FUOVZ9JhKmiW4F$!VzQAMUtoOYLhurgtXGljCPYBj?Jz zgH}tS#sW(9;S&r8E*z$~2t_Zs3e?nMj0haGt=v$F+;+OWuxz}y<$LN{vz=V2k3;oz zfL;~#@qg2-`Cuj?eK+c1ccWgUF20vb_JZSF$#-Hf=QF3g@D9E(d&cRC&m=rNBWLNC z(G(xCuc9gbnmvZ*_!WNtl*n{1ZzuOtq0&*B@s7$^gLN?}eK%@5H)I-OZc=s1+B%Nb zOsYxqW*V@KSCDG(ZIp^l6vT?_=3=A5Hbjxbbz4mJCn+)A8xcGVZqxi_o4LCARTp?k zrT7d&LhnK;t0{<(&_cMKe4XhgGAyMDUT}}BV|jU>tkJ>`Vfj({ilfIphA8!A6}*Kl zN|RLmbCIVf&y~1A`HC`FSQAAqHETG#n@W`c*j~OIiEq=+J9vkO;#W%rN^Zmw*95R+ z49~{|fjV5O-ggm_vcYvmbSetsLGA8vNAE!M9cugzcG$u=X@85S{IL^^`KK>y;432x}cW2x?--F}c`JIn{CXM|3^^l`}}iISnhoOpTI76 zKL2lEmU};634-O$=g`bk0q>gQU3|*X_u${9Io{Rx;XQMm5AT`le0a}X?}zuybw1I% zmUtJRa`Zj;cWH@t^?i8HQs={amO3Bav()?HJxiUB-t~V6wq(9ySA)RasHr$bK50|t zOZK)q1-xO<#A8=zrA#fDFWR?*OZKw8YOdH7zOPV1eCiDMeLPcG;J&1TcA<0tx{)ct z^8Xdq@?&fVH;b$GHur|yNRw)oKOb0QzHF~yA*y39Q7aoGQm>=;Zxy`~=5f3He}>PG zlQ!kvqX9yS1ZHb}7nlF{ux4q}J2j*p zt{iGuTK?~0U9QpU!F0X%x_hrox9(rau z1*cKbm6!ikE&gXl2HPN3Bbn-HS%Fd}%-I|t+23@x2BE7;%m1}^gvQUQP`7KdK?w_4 zdHHVw+x=alOny(9&)f6CS$p~u=i*Yz%lAUl{#=)+%wr@Kxb&=Sa%S`&6re^l8c;l0Ieb zgDG@zXPb%X2kmLf`D|c!!}85wgKz*`gQ)UnC})E7DMaHM^;AeT^s!=$U6k}0wKzo% zV)8$x=$?xe-6>MdT&(CGp4uy>=McT2pxPAYNok#l$0&70w`jMD?zvdeotzpcy5|x^ z_ptO%4SPlOT!QFUdW?uJC8U?<5=FOi;zW0SOzv$kQQl8r?x|I|!x>M&MqURDs_ptOWA{x;> zmms2trN@cxxdahCNqW41o=X(n2|52^MfXyy=uVMpmSRQs@YG(>y%Zz5lhQg9uUB-7 zcB|-KiWS|-sd1uvDM54(OYhXMS9C8Wh;F6FiSC5-@=~JcR!*GgPL+=6UP=_*!*k+9 z_fmrB9+o~Rx|b3~_oO+S0iWl9fa}eE2 z38H&gdYtH9N)X+{(&I$;QiAB7Bt2eqFC~iZgdFw*+P(|92liEhKFBRlUKQ*LO3Q&? ztP#L}9k;>168u#GD%h0EN!7m0Rn_4s1mawT1t2E0zvL!vu7M$G&o< ze3=_luM&#+RklPI?Mm^Ay;NQ<)TXcC)A*eIz4u-MQE;WyPj=uyc|UrYMeYatmVGW* zum_5Z_CT=Tk?pq!*!k}F7J~7}7URhbrP<|Z4hm&`Fj6W_MC zxV839an(_-+RJ8taV)y>P^$8>sZFm2m!}t-9Sc(^Ols4#yQW!*oV-n@%QnR#(5AXX09RkoMRt((NQZZb~D=!Am+7CRh)f`G# z_DbUr^4^G06UR*+O^Cp{zQH5BNl!IPK5zERw8P-jJ&daCmqt?Y&JI;m{@<>(?Xb13 z?Tl)vMHerZTJXpFq8UzCZqaw|Gf!JBP5)4%qZYI&Pc3le@m@z~XkpX_ZSx1{O!VI7 z`wUoeaUof-T3Qq#!{Ibq^29#M{q}8nqgND(yoN9#+=a;6MAJJg6)HNJU13k|(46IIo2!ctG z*_G)*qg*YuinNmwR)c4g=%%YhsJKRa2%Ycw{!Aa8c^2{2yOy{?Z8wA2YQdv8NeT~% z7fTj2KT#zROpQqNL#RGkHGMR}2Zcr3E-B>w>*kCJK2;6cl+mV3>*fc+<=)&Nn64T= z#Trj^^Ub;PS##dLX&1~B(^c=>*|TRVA~_4mo93Gc>P2(ToNb&tOV)Wu7(l2r17!h9 zdb3N}DQvoGp|DWcT7j}U6t+?-ap?}mfaxk)#S&(MQc$T>O|4LYO24Ow1QbyPP?;_j zPe4^(_Dj1WszRy!kGv_OFclg{DV==taPgr%{}?=4c?6B)L$vB4^1h1^{eJJ!#fN(` z3uG1^q8VhhA6?`&j^>_!Ech4@aQ-Jti^YrfgLcrDRU*r+BQA^BIGazEn$#Qd9FXM& zx7+QaPesJEVqL%F1vQ@4NI$M(IVhrM^&Js3Se&Y+0l|}qPZ=4SLA9U4LIFQ7hxv%7 zf&7BYXB73kW*W@;gVqXRU|9QsfnLZ}(PObkIIV*#c)=(>Pus#i4i04vt73Ftg;DgiLf6{?$nDS-&U)K3-jI{!@yKT5 z$qc30s5Ai6>`=fY$`CM(MOPk5Rh~1H24I>U3Yee*FpZ=EFwKqtOmjgMz%=Vk4aY%_ zY-Sd~G!9{9b_~EY2Vj~V37F;pOta$vra1u9ID}1YnBZI|?v4G62);aKOY{9bkfN7!;y3 z2bgAu0wzZVU=mfYRN4ZAEU$HdX?7%FqD%*vW`_bMQ3(po4h2lS)d8lVR7J{ifN3@s zFwHx_B$^N~Y0Aa}rg;DpZ;Sv_0^RgHfT;>#8bap)Qy(2jXtr-H=>n#C08WhFm)+9Pzb;@-vgNDO%=d27XhZ(4i$iD zz6UVP2T+ygB0vNcfa&=3d=Frn2QYQ>9bh`%1x)h*rnwGanstCF$^JdnfJtOsz;t0evSba@l8s6O zFb&lSl|(s?hAE;OpMR^97Dq-s*$>*0&jJIX{a8mNLdar#cP;q z4ls#kl!mDWVB(DXT@odTF+3m}&r~1pw1htcIxuV5$`t08HKdKEPB90h371LekY6ZP75*BEVF`iS#VJ z*`@3PrdkA;YIXs@)TQV^ApldY2Qbyl0)S~LM#EI=0Zg?3s`64tgX93y#&oR*Fx3D| z-Fyd_HoAbR24Gt10H#pG6lDUKY5=CCUJcVm7ckWTOiR5QrYH-*#GQ(rtPn7Dv!-hR zrlnrMq#(1K8m0xF)ktpwrcd_jm&gV#E%m^qT5rB3AF#C41xtOo1((bCb)b@RL!h)U zT~m4pl)A}99-#FROzKGnBtaj7q`>*?`jtgAn2RQ8oji5&lvK*H*XS%-=-AcT70iSb zZF?MBM?lnnxp4*4gix9eX34XQc(HED+Y^+Adasa%uVD|I&k-4ak6P~rJ{;k#3VhxI8 z<|wyL-m$9{tY^hrvZ|TYJwb;8W&5$ClTtK%3`D5W7yXmRkJIzmK#Hwd!b4Ghlm{{z z%#q(}Enw|*IoQN@c|~-bJk!l-pU^`wSs11UbDCi#+O0MS;B{xUt5+~Zun@`W(SQC=(#QZlByFwU#*?MX%s{@@c-;7`++ z7x128xpYzY*$M~O#cu^nP7DbhBgv}{({_W?u>Xq%X_KXC%Ic0 zb{#=hJSm1Bq-rm!W->i9?>d}jPnhGHlMGz56r~#v)5;dgCOvoJxL;o63bYZVD%5DA z4^S3^Szhlt`3-YQ6Q~?aWAw0#g@GJ~Jj!8xKX1=g&K1t`+?)0gqVi`5o^xE9L2Bu& zvpWr?C_s%xYM>0-5tTqbb|hP|r4k>+nvS4Uf+^~c16Se}Zd@u(2IF(ttiJtaZ& zuzjTXkbSuD5N&$Ieg!Y}hue>QkM6+Q6g+GnG7pYLR9Y;^ML`omQ-C|RZ3A5-48rUn zQkA4eBaTS72Q%h9RPLF9qC`bE^;02SFcfi7wj0Ld82-46czqo@QsG+$7z?;K3j2Py zYNyO=$k>*}yeuBTq-_H0QA2Tipq(Of z59y$JMx-y^kw8gr^w0(LVLT-mxtFqoOlDCP&JIB)Q!^n&4EmU!nkCQ9#v+rc(vZBu zEXYJpCLxn4GwYB^71d@uGMNHMHyWOgJnflkO|EJBdU>hfMmYkbxbL(=Ey&6I={7*L|oqk%_aB7UxZ9l-X<~; zT^C1yOt4|b(K%7mr30BH(1A=eZ#F4e4Z6sL8PkVMW(SZ7cdcByIApRAqciEr8AK-CEZ}WF zGPw{TlZA0~CJRG!Cgg#TyZEC=o3b!eXVS@yM<(Rb14+naVW`felRFHVKr@cc1hUaO zlkth%My40y4C+Eqf{&#$fo?pVi7RlV&Sb$MlhHa8=tk&F=!-<1iOWmWnMApCQ5-T^ z7^*X&m1F2k)M;brOk82(=}c7T@pL9oc96*yO~?R%HVv=N zi3lEf9hFk69H@zq;h+qJBKU{-B{3+zX6|r@39u36RF>2$SFhUZC{un?_}E^;{BxQA zKQxyMA8`Qd5_9Eq_FLw!_QT*>>7u!2R!j=&V8!moH1q&hIXsV%Xu<9`G8OITf`bDb z#X89GtM6lP{2W&-JRUs7A*}=E5b1a28E;|r)-BFw9Wy`X3YZ(j-??dSm2MKx{{|MP zH4ef3((bljHCJX_Ukf!~&_S5!XxbK_1QqxaM z@3j?6<`lPRh!@cyvOj>?_@9eniT9$?zk$VpgDtF)SyGFIJ1rGchb`4fq zgwlF`%SnZkT!Nyj;idYPWY96b%}%<_g^De&xoy(0RUr%XbbV|Mc7Qe#ks8uA<{?EW z_khD<|G@m(vEl8qTI^#f%#4^5Yi?CKimlaSmg^oA-OEyk|HEqh3u2c6Hg#_;6>*1k zUUd!R5+%nV;msSG(*c~TnY`}aOynZ3es>;`$nX#`M_f2uZi|AObmEuP!W!eCsrPlR zN%0EnEKNa&vrcjLRzszg42k6Ct4?HMQFKz{>$F6(b6q)JBV@TKtNA zFnAQu{HS@b^oV(&@QD4M{l2-M3f*JAQ+TxWka^g9GzF=qMVmF|I94^SE|LRW^Uk`v zR%+1OpF>Y`-EnxUxX#UgnUaSK-U)_UQ;Ovghl>pqZSKA^^RL)-L4%x&Q)Dn!1FsoY z=MD2y9;jPgV5fyjk2KKNsU?48T7Q$3ecku-hN?n{3P<6uGaaKFy2c}v%NKb(DIY=J`{xxqp&@~6?ovla?(h+VsNOzmGokjGi zaCa)=yMwqVc#A|#4$^lN&?n*V9HeJ~=~E!@6r>?y%|Uu5$UbY#of_5WAU#pH*@?@~TW)8@TfsKv>+n7~6dxg_)S22MG7+-W4HFN~1N4YCz zf9CW$;*4hth+2o9ui6V7UA{o@4SZkh$CXQFMR}#m=9+zn+N7h#LD)QrZ_5+7Ej?); zv-g>23WRIH<3%Be4{#(g%zMf{X6`cs)UErzbl)?H^xl16S{Qg>MDe!>(7ejMKJse0 zW#8pw_D9@Q&7l})a3awY5|a*-si_Xw=5eoa9Srw!J>hcn$j+TN741p4zJpS%vkP_lE(!#>GZSY`c<=_gX<}%`&W^x zZpLC~hsWeZ8LlF_S}q`v`r^Qvv$NEpqzPB37OR5RGt~sQ9UXqBb#=H;-z%(VSm)j7 zh#xgupD;I&G@2_>905`pA1Bem5xNUcaD39e)vd@^snd001))x7Jc3Bl$w5GC@^xlN zhSZFgBruW{sT<1T7v)lA1J1~Yd443NZvHGNyl&^Y@Q% z)c(8nVe^Q6!2X^2wtW~UnTPH7%!9bfJZzNzko|7)5u91RVA9e{vM|e;2^;WG6EEQx z2ofWs=LWu3>jWxMm=bw#kY}D_EVHG@f!g4C$;^`1*EImmRYKyZF8C8tLmYaTcX&|q z*LCp5I%$ovem9uap%h(bhVTS6h-mz<$8CgyG2Wu>)prD?We=nSyJcNT{kV}diM4;y**+lok6ic9@qq zN_`#AnaliE44G@#R4#j$_NRXY9~fQGjlax6`ODaY#r@pepo;~bw_oMl@N9rv?X712 zzDcEtN{;*`i0a_|cg-5GA&4~{VKgzb^kq`9tww&iC*xxf^S9A)GdCsVk$RaLjP}9ROJJ{U4_5IhbX{lq?3f)thxNfOUUL?A>!NU48^h^eX42q|y zF>s~!dRzd2504uZ;t4H*V^KFk%p? zUOOfEw{($2aHe#=cn%K79fn9TXNV#5b6ilLB`D6D1x1u0dJMVey>r=;7k2Nq#(Ble zaN88OP7o=kL@2c>yk+Dwga1shhsZG{Q{aB|f>+7@fiFDp0FE&@#Mti@af~s`qxL&QnQ=}t-lM4>cW#StLgH5*~^yBFaQA zS}zgAMG66hC4|_`)OD|UcOF>;Gs{OuRl-jPA;IMvKw_;c0V3}_|JM=bL9tcRrA=AW z=NNum*gpi6$6@RtOU1#HNaCOd{s>&u|l2)NXPzaArIKDLemo-~byLTxm7;IwIQc?!p{oIz5- z=mU2F(J9j?D5(C96JEM>jg!Y8;~DcZH&$HZhNUY+O2BW1$PHKR3YSSi z&0KC>x}5%r{reB$C9^;Bj(Ohh=LV%kVu+B2&kX)Eg$0a&U&P|}4fCAc-�uH=bBs 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

Y2Q4 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[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 literal 0 HcmV?d00001 diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json deleted file mode 100644 index a519c455..00000000 --- a/node_modules/.package-lock.json +++ /dev/null @@ -1,113 +0,0 @@ -{ - "name": "InfiniTime", - "lockfileVersion": 2, - "requires": true, - "packages": { - "node_modules/lv_font_conv": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/lv_font_conv/-/lv_font_conv-1.5.2.tgz", - "integrity": "sha512-0UapRSTkVP/pnB8Z4r2HDHx5p2dJx/xUG1+14u/WXo59mwuC7BahR+Bnx/66jKoDrG1wFQwn9ZzoyMxRHOD9bg==", - "bundleDependencies": [ - "argparse", - "bit-buffer", - "debug", - "make-error", - "mkdirp", - "opentype.js", - "pngjs" - ], - "dependencies": { - "argparse": "^2.0.0", - "bit-buffer": "^0.2.5", - "debug": "^4.1.1", - "make-error": "^1.3.5", - "mkdirp": "^1.0.4", - "opentype.js": "^1.1.0", - "pngjs": "^6.0.0" - }, - "bin": { - "lv_font_conv": "lv_font_conv.js" - } - }, - "node_modules/lv_font_conv/node_modules/argparse": { - "version": "2.0.1", - "inBundle": true, - "license": "Python-2.0" - }, - "node_modules/lv_font_conv/node_modules/bit-buffer": { - "version": "0.2.5", - "inBundle": true, - "license": "MIT" - }, - "node_modules/lv_font_conv/node_modules/debug": { - "version": "4.3.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/lv_font_conv/node_modules/make-error": { - "version": "1.3.6", - "inBundle": true, - "license": "ISC" - }, - "node_modules/lv_font_conv/node_modules/mkdirp": { - "version": "1.0.4", - "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lv_font_conv/node_modules/ms": { - "version": "2.1.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/lv_font_conv/node_modules/opentype.js": { - "version": "1.3.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "string.prototype.codepointat": "^0.2.1", - "tiny-inflate": "^1.0.3" - }, - "bin": { - "ot": "bin/ot" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/lv_font_conv/node_modules/pngjs": { - "version": "6.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/lv_font_conv/node_modules/string.prototype.codepointat": { - "version": "0.2.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/lv_font_conv/node_modules/tiny-inflate": { - "version": "1.0.3", - "inBundle": true, - "license": "MIT" - } - } -} diff --git a/node_modules/lv_font_conv/CHANGELOG.md b/node_modules/lv_font_conv/CHANGELOG.md deleted file mode 100644 index 94512228..00000000 --- a/node_modules/lv_font_conv/CHANGELOG.md +++ /dev/null @@ -1,164 +0,0 @@ -1.5.2 / 2021-07-18 ------------------- - -- Fixed lvgl version check for v8+, #64. - - -1.5.1 / 2021-04-06 ------------------- - -- Fixed fail of CMAP generation for edge cases, #62. -- Dev deps bump. - - -1.5.0 / 2021-03-08 ------------------- - -- More `const` in generated font (for v8+), #59. - - -1.4.1 / 2021-01-26 ------------------- - -- Fix charcodes padding in comments, #54. - - -1.4.0 / 2021-01-03 ------------------- - -- Added OTF fonts support. -- Added `--use-color-info` for limited multi-tone glyphs support. - - -1.3.1 / 2020-12-28 ------------------- - -- Unify `lvgl.h` include. -- Updated repo refs (littlevgl => lvgl). -- Deps bump. -- Moved CI to github actions. - - -1.3.0 / 2020-10-25 ------------------- - -- Drop `lodash` use. -- Deps bump. - - -1.2.1 / 2020-10-24 ------------------- - -- Reduced npm package size (drop unneeded files before publish). - - -1.2.0 / 2020-10-24 ------------------- - -- Bump FreeType to 2.10.4. -- Bundle dependencies to npm package. - - -1.1.3 / 2020-09-22 ------------------- - -- lvgl: added `LV_FONT_FMT_TXT_LARGE` check or very large fonts. - - -1.1.2 / 2020-08-23 ------------------- - -- Fix: skip `glyph.advanceWidth` for monospace fonts, #43. -- Spec fix: version size should be 4 bytes, #44. -- Spec fix: bbox x/y bits => unsigned, #45. -- Bump argparse. -- Cleanup help formatter. - - -1.1.1 / 2020-08-01 ------------------- - -- `--version` should show number from `package.json`. - - -1.1.0 / 2020-07-27 ------------------- - -- Added `post.underlinePosition` & `post.underlineThickness` info to font header. - - -1.0.0 / 2020-06-26 ------------------- - -- Maintenance release. -- Set package version 1.x, to label package as stable. -- Deps bump. - - -0.4.3 / 2020-03-05 ------------------- - -- Enabled `--bpp 8` mode. - - -0.4.2 / 2020-01-05 ------------------- - -- Added `--lv_include` option to set alternate `lvgl.h` path. -- Added guards to hide `.subpx` property for lvgl 6.0 (supported from 6.1 only), #32. -- Dev deps bump - - -0.4.1 / 2019-12-09 ------------------- - -- Allow memory growth for FreeType build, #29. -- Dev deps bump. -- Web build update. - - -0.4.0 / 2019-11-29 ------------------- - -- Note, this release is for lvgl 6.1 and has potentially breaking changes - (see below). If you have compatibility issues with lvgl 6.0 - use previous - versions or update your code. -- Spec change: added subpixels info field to font header (header size increased). -- Updated `bin` & `lvgl` writers to match new spec. -- lvgl: fixed data type for kerning values (needs appropriate update - in LittlevGL 6.1+). -- Fix errors display (disable emscripten error catcher). - - -0.3.1 / 2019-10-24 ------------------- - -- Fixed "out of range" error for big `--size`. - - -0.3.0 / 2019-10-12 ------------------- - -- Added beta options `--lcd` & `--lcd-v` for subpixel rendering (still need - header info update). -- Added FreeType data properties to dump info. -- Fixed glyph width (missed fractional part after switch to FreeType). -- Fixed missed sigh for negative X/Y bitmap offsets. -- Deps bump. - - -0.2.0 / 2019-09-26 ------------------- - -- Use FreeType renderer. Should solve all regressions, reported in 0.1.0. -- Enforced light autohinting (horizontal lines only). -- Use special hinter for monochrome output (improve quality). -- API changed to async. -- Fix: added missed `.bitmap_format` field to lvgl writer. -- Fix: changed struct fields init order to match declaration, #25. - - -0.1.0 / 2019-09-03 ------------------- - -- First release. diff --git a/node_modules/lv_font_conv/LICENSE b/node_modules/lv_font_conv/LICENSE deleted file mode 100644 index 4dc31bfa..00000000 --- a/node_modules/lv_font_conv/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) 2018 authors - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/lv_font_conv/README.md b/node_modules/lv_font_conv/README.md deleted file mode 100644 index 6716ebd9..00000000 --- a/node_modules/lv_font_conv/README.md +++ /dev/null @@ -1,150 +0,0 @@ -lv_font_conv - font convertor to compact bitmap format -====================================================== - -[![CI](https://github.com/lvgl/lv_font_conv/workflows/CI/badge.svg?branch=master)](https://github.com/lvgl/lv_font_conv/actions) -[![NPM version](https://img.shields.io/npm/v/lv_font_conv.svg?style=flat)](https://www.npmjs.org/package/lv_font_conv) - -Converts TTF/WOFF/OTF fonts to __[compact format](https://github.com/lvgl/lv_font_conv/blob/master/doc/font_spec.md)__, suitable for small embedded systems. Main features are: - -- Allows bitonal and anti-aliased glyphs (1-4 bits per pixel). -- Preserves kerning info. -- Compression. -- Users can select required glyphs only (subsetting). -- Multiple font sources can be merged. -- Simple CLI interface, easy to integrate into external build systems. - - -## Install the script - -[node.js](https://nodejs.org/en/download/) v10+ required. - -Global install of the last version, execute as "lv_font_conv" - -```sh -# install release from npm registry -npm i lv_font_conv -g -# install from github's repo, master branch -npm i lvgl/lv_font_conv -g -``` - -**run via [npx](https://www.npmjs.com/package/npx) without install** - -```sh -# run from npm registry -npx lv_font_conv -h -# run from github master -npx github:lvgl/lv_font_conv -h -``` - -Note, runing via `npx` may take some time until modules installed, be patient. - - -## CLI params - -Common: - -- `--bpp` - bits per pixel (antialiasing). -- `--size` - output font size (pixels). -- `-o`, `--output` - output path (file or directory, depends on format). -- `--format` - output format. - - `--format dump` - dump glyph images and font info, useful for debug. - - `--format bin` - dump font in binary form (as described in [spec](https://github.com/lvgl/lv_font_conv/blob/master/doc/font_spec.md)). - - `--format lvgl` - dump font in [LittlevGL](https://github.com/lvgl/lvgl) format. -- `--force-fast-kern-format` - always use more fast kering storage format, - at cost of some size. If size difference appears, it will be displayed. -- `--lcd` - generate bitmaps with 3x horizontal resolution, for subpixel - smoothing. -- `--lcd-v` - generate bitmaps with 3x vertical resolution, for subpixel - smoothing. -- `--use-color-info` - try to use glyph color info from font to create - grayscale icons. Since gray tones are emulated via transparency, result - will be good on contrast background only. -- `--lv-include` - only with `--format lvgl`, set alternate path for `lvgl.h`. - -Per font: - -- `--font` - path to font file (ttf/woff/woff2/otf). May be used multiple time for - merge. -- `-r`, `--range` - single glyph or range + optional mapping, belongs to - previously declared `--font`. Can be used multiple times. Examples: - - `-r 0x1F450` - single value, dec or hex format. - - `-r 0x1F450-0x1F470` - range. - - `-r '0x1F450=>0xF005'` - single glyph with mapping. - - `-r '0x1F450-0x1F470=>0xF005'` - range with mapping. - - `-r 0x1F450 -r 0x1F451-0x1F470` - 2 ranges. - - `-r 0x1F450,0x1F451-0x1F470` - the same as above, but defined with single `-r`. -- `--symbols` - list of characters to copy (instead of numeric format in `-r`). - - `--symbols 0123456789.,` - extract chars to display numbers. -- `--autohint-off` - do not force autohinting ("light" is on by default). -- `--autohint-strong` - use more strong autohinting (will break kerning). - -Additional debug options: - -- `--no-compress` - disable built-in RLE compression. -- `--no-prefilter` - disable bitmap lines filter (XOR), used to improve - compression ratio. -- `--no-kerning` - drop kerning info to reduce size (not recommended). -- `--full-info` - don't shorten 'font_info.json' (include pixels data). - - -## Examples - -Merge english from Roboto Regular and icons from Font Awesome, and show debug -info: - -`env DEBUG=* lv_font_conv --font Roboto-Regular.ttf -r 0x20-0x7F --font FontAwesome.ttf -r 0xFE00=>0x81 --size 16 --format bin --bpp 3 --no-compress -o output.font` - -Merge english & russian from Roboto Regular, and show debug info: - -`env DEBUG=* lv_font_conv --font Roboto-Regular.ttf -r 0x20-0x7F -r 0x401,0x410-0x44F,0x451 --size 16 --format bin --bpp 3 --no-compress -o output.font` - -Dump all Roboto glyphs to inspect icons and font details: - -`lv_font_conv --font Roboto-Regular.ttf -r 0x20-0x7F --size 16 --format dump --bpp 3 -o ./dump` - -**Note**. Option `--no-compress` exists temporary, to avoid confusion until LVGL -adds compression support. - - -## Technical notes - -### Supported output formats - -1. **bin** - universal binary format, as described in https://github.com/lvgl/lv_font_conv/tree/master/doc. -2. **lvgl** - format for LittlevGL, C file. Has minor limitations and a bit - bigger size, because C does not allow to effectively define relative offsets - in data blocks. -3. **dump** - create folder with each glyph in separate image, and other font - data as `json`. Useful for debug. - -### Merged font metrics - -When multiple fonts merged into one, sources can have different metrics. Result -will follow principles below: - -1. No scaling. Glyphs will have exactly the same size, as intended by font authors. -2. The same baseline. -3. `OS/2` metrics (`sTypoAscender`, `sTypoDescender`, `sTypoLineGap`) will be - used from the first font in list. -4. `hhea` metrics (`ascender`, `descender`), defined as max/min point of all - font glyphs, are recalculated, according to new glyphs set. - - -## Development - -Current package includes WebAssembly build of FreeType with some helper -functions. Everything is wrapped into Docker and requires zero knowledge about -additional tools install. See `package.json` for additional commands. You may -need those if decide to upgrade FreeType or update helpers. - -This builds image with emscripten & freetype, usually should be done only once: - -``` -npm run build:dockerimage -``` - -This compiles helpers and creates WebAssembly files: - -``` -npm run build:freetype -``` diff --git a/node_modules/lv_font_conv/lib/app_error.js b/node_modules/lv_font_conv/lib/app_error.js deleted file mode 100644 index 98e9052e..00000000 --- a/node_modules/lv_font_conv/lib/app_error.js +++ /dev/null @@ -1,9 +0,0 @@ -// Custom Error type to simplify error messaging -// -'use strict'; - - -//const ExtendableError = require('es6-error'); -//module.exports = class AppError extends ExtendableError {}; - -module.exports = require('make-error')('AppError'); diff --git a/node_modules/lv_font_conv/lib/cli.js b/node_modules/lv_font_conv/lib/cli.js deleted file mode 100644 index a73a9b22..00000000 --- a/node_modules/lv_font_conv/lib/cli.js +++ /dev/null @@ -1,318 +0,0 @@ -// Parse input arguments and execute convertor - -'use strict'; - - -const argparse = require('argparse'); -const fs = require('fs'); -const mkdirp = require('mkdirp'); -const path = require('path'); -const convert = require('./convert'); - - -class ActionFontAdd extends argparse.Action { - call(parser, namespace, value/*, option_string*/) { - let items = (namespace[this.dest] || []).slice(); - items.push({ source_path: value, ranges: [] }); - namespace[this.dest] = items; - } -} - - -// add range or symbols to font; -// need to merge them into one array here so overrides work correctly -class ActionFontRangeAdd extends argparse.Action { - call(parser, namespace, value, option_string) { - let fonts = namespace.font || []; - - if (fonts.length === 0) { - parser.error(`argument ${option_string}: Only allowed after --font`); - } - - let lastFont = fonts[fonts.length - 1]; - - // { symbols: 'ABC' }, or { range: [ 65, 67, 65 ] } - lastFont.ranges.push({ [this.dest]: value }); - } -} - - -// add hinting option to font; -class ActionFontStoreTrue extends argparse.Action { - constructor(options) { - options = options || {}; - options.const = true; - options.default = options.default !== null ? options.default : false; - options.nargs = 0; - super(options); - } - - call(parser, namespace, value, option_string) { - let fonts = namespace.font || []; - - if (fonts.length === 0) { - parser.error(`argument ${option_string}: Only allowed after --font`); - } - - let lastFont = fonts[fonts.length - 1]; - - lastFont[this.dest] = this.const; - } -} - - -// Formatter with support of `\n` in Help texts. -class RawTextHelpFormatter2 extends argparse.RawDescriptionHelpFormatter { - // executes parent _split_lines for each line of the help, then flattens the result - _split_lines(text, width) { - return [].concat(...text.split('\n').map(line => super._split_lines(line, width))); - } -} - - -// parse decimal or hex code in unicode range -function unicode_point(str) { - let m = /^(?:(?:0x([0-9a-f]+))|([0-9]+))$/i.exec(str.trim()); - - if (!m) throw new TypeError(`${str} is not a number`); - - let [ , hex, dec ] = m; - - let value = hex ? parseInt(hex, 16) : parseInt(dec, 10); - - if (value > 0x10FFFF) throw new TypeError(`${str} is out of unicode range`); - - return value; -} - - -// parse range -function range(str) { - let result = []; - - for (let s of str.split(',')) { - let m = /^(.+?)(?:-(.+?))?(?:=>(.+?))?$/i.exec(s); - - let [ , start, end, mapped_start ] = m; - - if (!end) end = start; - if (!mapped_start) mapped_start = start; - - start = unicode_point(start); - end = unicode_point(end); - - if (start > end) throw new TypeError(`Invalid range: ${s}`); - - mapped_start = unicode_point(mapped_start); - - result.push(start, end, mapped_start); - } - - return result; -} - - -// exclude negative numbers and non-numbers -function positive_int(str) { - if (!/^\d+$/.test(str)) throw new TypeError(`${str} is not a valid number`); - - let n = parseInt(str, 10); - - if (n <= 0) throw new TypeError(`${str} is not a valid number`); - - return n; -} - - -module.exports.run = async function (argv, debug = false) { - - // - // Configure CLI - // - - let parser = new argparse.ArgumentParser({ - add_help: true, - formatter_class: RawTextHelpFormatter2 - }); - - if (debug) { - parser.exit = function (status, message) { - throw new Error(message); - }; - } - - parser.add_argument('-v', '--version', { - action: 'version', - version: require('../package.json').version - }); - - parser.add_argument('--size', { - metavar: 'PIXELS', - type: positive_int, - required: true, - help: 'Output font size, pixels.' - }); - - parser.add_argument('-o', '--output', { - metavar: '', - help: 'Output path.' - }); - - parser.add_argument('--bpp', { - choices: [ 1, 2, 3, 4, 8 ], - type: positive_int, - required: true, - help: 'Bits per pixel, for antialiasing.' - }); - - let lcd_group = parser.add_mutually_exclusive_group(); - - lcd_group.add_argument('--lcd', { - action: 'store_true', - default: false, - help: 'Enable subpixel rendering (horizontal pixel layout).' - }); - - lcd_group.add_argument('--lcd-v', { - action: 'store_true', - default: false, - help: 'Enable subpixel rendering (vertical pixel layout).' - }); - - parser.add_argument('--use-color-info', { - dest: 'use_color_info', - action: 'store_true', - default: false, - help: 'Try to use glyph color info from font to create grayscale icons. ' + - 'Since gray tones are emulated via transparency, result will be good on contrast background only.' - }); - - parser.add_argument('--format', { - choices: convert.formats, - required: true, - help: 'Output format.' - }); - - parser.add_argument('--font', { - metavar: '', - action: ActionFontAdd, - required: true, - help: 'Source font path. Can be used multiple times to merge glyphs from different fonts.' - }); - - parser.add_argument('-r', '--range', { - type: range, - action: ActionFontRangeAdd, - help: ` -Range of glyphs to copy. Can be used multiple times, belongs to previously declared "--font". Examples: - -r 0x1F450 - -r 0x20-0x7F - -r 32-127 - -r 32-127,0x1F450 - -r '0x1F450=>0xF005' - -r '0x1F450-0x1F470=>0xF005' -` - }); - - parser.add_argument('--symbols', { - action: ActionFontRangeAdd, - help: ` -List of characters to copy, belongs to previously declared "--font". Examples: - --symbols ,.0123456789 - --symbols abcdefghigklmnopqrstuvwxyz -` - }); - - parser.add_argument('--autohint-off', { - type: range, - action: ActionFontStoreTrue, - help: 'Disable autohinting for previously declared "--font"' - }); - - parser.add_argument('--autohint-strong', { - type: range, - action: ActionFontStoreTrue, - help: 'Use more strong autohinting for previously declared "--font" (will break kerning)' - }); - - parser.add_argument('--force-fast-kern-format', { - dest: 'fast_kerning', - action: 'store_true', - default: false, - help: 'Always use kern classes instead of pairs (might be larger but faster).' - }); - - parser.add_argument('--no-compress', { - dest: 'no_compress', - action: 'store_true', - default: false, - help: 'Disable built-in RLE compression.' - }); - - parser.add_argument('--no-prefilter', { - dest: 'no_prefilter', - action: 'store_true', - default: false, - help: 'Disable bitmap lines filter (XOR), used to improve compression ratio.' - }); - - parser.add_argument('--no-kerning', { - dest: 'no_kerning', - action: 'store_true', - default: false, - help: 'Drop kerning info to reduce size (not recommended).' - }); - - parser.add_argument('--lv-include', { - metavar: '', - help: 'Set alternate "lvgl.h" path (for --format lvgl).' - }); - - parser.add_argument('--full-info', { - dest: 'full_info', - action: 'store_true', - default: false, - help: 'Don\'t shorten "font_info.json" (include pixels data).' - }); - - // - // Process CLI options - // - - let args = parser.parse_args(argv.length ? argv : [ '-h' ]); - - for (let font of args.font) { - if (font.ranges.length === 0) { - parser.error(`You need to specify either "--range" or "--symbols" for font "${font.source_path}"`); - } - - try { - font.source_bin = fs.readFileSync(font.source_path); - } catch (err) { - parser.error(`Cannot read file "${font.source_path}": ${err.message}`); - } - } - - // - // Convert - // - - let files = await convert(args); - - // - // Store files - // - - for (let [ filename, data ] of Object.entries(files)) { - let dir = path.dirname(filename); - - mkdirp.sync(dir); - - fs.writeFileSync(filename, data); - } - -}; - - -// export for tests -module.exports._range = range; diff --git a/node_modules/lv_font_conv/lib/collect_font_data.js b/node_modules/lv_font_conv/lib/collect_font_data.js deleted file mode 100644 index 227c5835..00000000 --- a/node_modules/lv_font_conv/lib/collect_font_data.js +++ /dev/null @@ -1,173 +0,0 @@ -// Read fonts - -'use strict'; - - -const opentype = require('opentype.js'); -const ft_render = require('./freetype'); -const AppError = require('./app_error'); -const Ranger = require('./ranger'); - - -module.exports = async function collect_font_data(args) { - await ft_render.init(); - - // Duplicate font options as k/v for quick access - let fonts_options = {}; - args.font.forEach(f => { fonts_options[f.source_path] = f; }); - - // read fonts - let fonts_opentype = {}; - let fonts_freetype = {}; - - for (let { source_path, source_bin } of args.font) { - // don't load font again if it's specified multiple times in args - if (fonts_opentype[source_path]) continue; - - try { - let b = source_bin; - - if (Buffer.isBuffer(b)) { - // node.js Buffer -> ArrayBuffer - b = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength); - } - - fonts_opentype[source_path] = opentype.parse(b); - } catch (err) { - throw new AppError(`Cannot load font "${source_path}": ${err.message}`); - } - - fonts_freetype[source_path] = ft_render.fontface_create(source_bin, args.size); - } - - // merge all ranges - let ranger = new Ranger(); - - for (let { source_path, ranges } of args.font) { - let font = fonts_freetype[source_path]; - - for (let item of ranges) { - /* eslint-disable max-depth */ - if (item.range) { - for (let i = 0; i < item.range.length; i += 3) { - let range = item.range.slice(i, i + 3); - let chars = ranger.add_range(source_path, ...range); - let is_empty = true; - - for (let code of chars) { - if (ft_render.glyph_exists(font, code)) { - is_empty = false; - break; - } - } - - if (is_empty) { - let a = '0x' + range[0].toString(16); - let b = '0x' + range[1].toString(16); - throw new AppError(`Font "${source_path}" doesn't have any characters included in range ${a}-${b}`); - } - } - } - - if (item.symbols) { - let chars = ranger.add_symbols(source_path, item.symbols); - let is_empty = true; - - for (let code of chars) { - if (ft_render.glyph_exists(font, code)) { - is_empty = false; - break; - } - } - - if (is_empty) { - throw new AppError(`Font "${source_path}" doesn't have any characters included in "${item.symbols}"`); - } - } - } - } - - let mapping = ranger.get(); - let glyphs = []; - let all_dst_charcodes = Object.keys(mapping).sort((a, b) => a - b).map(Number); - - for (let dst_code of all_dst_charcodes) { - let src_code = mapping[dst_code].code; - let src_font = mapping[dst_code].font; - - if (!ft_render.glyph_exists(fonts_freetype[src_font], src_code)) continue; - - let ft_result = ft_render.glyph_render( - fonts_freetype[src_font], - src_code, - { - autohint_off: fonts_options[src_font].autohint_off, - autohint_strong: fonts_options[src_font].autohint_strong, - lcd: args.lcd, - lcd_v: args.lcd_v, - mono: !args.lcd && !args.lcd_v && args.bpp === 1, - use_color_info: args.use_color_info - } - ); - - glyphs.push({ - code: dst_code, - advanceWidth: ft_result.advance_x, - bbox: { - x: ft_result.x, - y: ft_result.y - ft_result.height, - width: ft_result.width, - height: ft_result.height - }, - kerning: {}, - freetype: ft_result.freetype, - pixels: ft_result.pixels - }); - } - - if (!args.no_kerning) { - let existing_dst_charcodes = glyphs.map(g => g.code); - - for (let { code, kerning } of glyphs) { - let src_code = mapping[code].code; - let src_font = mapping[code].font; - let font = fonts_opentype[src_font]; - let glyph = font.charToGlyph(String.fromCodePoint(src_code)); - - for (let dst_code2 of existing_dst_charcodes) { - // can't merge kerning values from 2 different fonts - if (mapping[dst_code2].font !== src_font) continue; - - let src_code2 = mapping[dst_code2].code; - let glyph2 = font.charToGlyph(String.fromCodePoint(src_code2)); - let krn_value = font.getKerningValue(glyph, glyph2); - - if (krn_value) kerning[dst_code2] = krn_value * args.size / font.unitsPerEm; - - //let krn_value = ft_render.get_kerning(font, src_code, src_code2).x; - //if (krn_value) kerning[dst_code2] = krn_value; - } - } - } - - let first_font = fonts_freetype[args.font[0].source_path]; - let first_font_scale = args.size / first_font.units_per_em; - let os2_metrics = ft_render.fontface_os2_table(first_font); - let post_table = fonts_opentype[args.font[0].source_path].tables.post; - - for (let font of Object.values(fonts_freetype)) ft_render.fontface_destroy(font); - - ft_render.destroy(); - - return { - ascent: Math.max(...glyphs.map(g => g.bbox.y + g.bbox.height)), - descent: Math.min(...glyphs.map(g => g.bbox.y)), - typoAscent: Math.round(os2_metrics.typoAscent * first_font_scale), - typoDescent: Math.round(os2_metrics.typoDescent * first_font_scale), - typoLineGap: Math.round(os2_metrics.typoLineGap * first_font_scale), - size: args.size, - glyphs, - underlinePosition: Math.round(post_table.underlinePosition * first_font_scale), - underlineThickness: Math.round(post_table.underlineThickness * first_font_scale) - }; -}; diff --git a/node_modules/lv_font_conv/lib/convert.js b/node_modules/lv_font_conv/lib/convert.js deleted file mode 100644 index 0cf088a0..00000000 --- a/node_modules/lv_font_conv/lib/convert.js +++ /dev/null @@ -1,30 +0,0 @@ -// Internal API to convert input data into output font data -// Used by both CLI and Web wrappers. -'use strict'; - -const collect_font_data = require('./collect_font_data'); - -let writers = { - dump: require('./writers/dump'), - bin: require('./writers/bin'), - lvgl: require('./writers/lvgl') -}; - - -// -// Input: -// - args like from CLI (optionally extended with binary content of files) -// -// Output: -// - { name1: bin_data1, name2: bin_data2, ... } -// -// returns hash with files to write -// -module.exports = async function convert(args) { - let font_data = await collect_font_data(args); - let files = writers[args.format](args, font_data); - - return files; -}; - -module.exports.formats = Object.keys(writers); diff --git a/node_modules/lv_font_conv/lib/font/cmap_build_subtables.js b/node_modules/lv_font_conv/lib/font/cmap_build_subtables.js deleted file mode 100644 index c80a219e..00000000 --- a/node_modules/lv_font_conv/lib/font/cmap_build_subtables.js +++ /dev/null @@ -1,105 +0,0 @@ -// Find an optimal configuration of cmap tables representing set of codepoints, -// using simple breadth-first algorithm -// -// Assume that: -// - codepoints have one-to-one correspondence to glyph ids -// - glyph ids are always bigger for bigger codepoints -// - glyph ids are always consecutive (1..N without gaps) -// -// This way we can omit glyph ids from all calculations entirely: if codepoints -// fit in format0, then glyph ids also will. -// -// format6 is not considered, because if glyph ids can be delta-coded, -// multiple format0 tables are guaranteed to be smaller than a single format6. -// -// sparse format is not used because as long as glyph ids are consecutive, -// sparse_tiny will always be preferred. -// - -'use strict'; - - -function estimate_format0_tiny_size(/*start_code, end_code*/) { - return 16; -} - -function estimate_format0_size(start_code, end_code) { - return 16 + (end_code - start_code + 1); -} - -//function estimate_sparse_size(count) { -// return 16 + count * 4; -//} - -function estimate_sparse_tiny_size(count) { - return 16 + count * 2; -} - -module.exports = function cmap_split(all_codepoints) { - all_codepoints = all_codepoints.sort((a, b) => a - b); - - let min_paths = []; - - for (let i = 0; i < all_codepoints.length; i++) { - let min = { dist: Infinity }; - - for (let j = 0; j <= i; j++) { - let prev_dist = (j - 1 >= 0) ? min_paths[j - 1].dist : 0; - let s; - - if (all_codepoints[i] - all_codepoints[j] < 256) { - s = estimate_format0_size(all_codepoints[j], all_codepoints[i]); - - /* eslint-disable max-depth */ - if (prev_dist + s < min.dist) { - min = { - dist: prev_dist + s, - start: j, - end: i, - format: 'format0' - }; - } - } - - if (all_codepoints[i] - all_codepoints[j] < 256 && all_codepoints[i] - i === all_codepoints[j] - j) { - s = estimate_format0_tiny_size(all_codepoints[j], all_codepoints[i]); - - /* eslint-disable max-depth */ - if (prev_dist + s < min.dist) { - min = { - dist: prev_dist + s, - start: j, - end: i, - format: 'format0_tiny' - }; - } - } - - // tiny sparse will always be preferred over full sparse because glyph ids are consecutive - if (all_codepoints[i] - all_codepoints[j] < 65536) { - s = estimate_sparse_tiny_size(i - j + 1); - - if (prev_dist + s < min.dist) { - min = { - dist: prev_dist + s, - start: j, - end: i, - format: 'sparse_tiny' - }; - } - } - } - - min_paths[i] = min; - } - - let result = []; - - for (let i = all_codepoints.length - 1; i >= 0;) { - let path = min_paths[i]; - result.unshift([ path.format, all_codepoints.slice(path.start, path.end + 1) ]); - i = path.start - 1; - } - - return result; -}; diff --git a/node_modules/lv_font_conv/lib/font/compress.js b/node_modules/lv_font_conv/lib/font/compress.js deleted file mode 100644 index aa30a3a1..00000000 --- a/node_modules/lv_font_conv/lib/font/compress.js +++ /dev/null @@ -1,107 +0,0 @@ -'use strict'; - -//const debug = require('debug')('compress'); - -function count_same(arr, offset) { - let same = 1; - let val = arr[offset]; - - for (let i = offset + 1; i < arr.length; i++) { - if (arr[i] !== val) break; - same++; - } - - return same; -} - -// -// Compress pixels with RLE-like algorythm (modified I3BN) -// -// 1. Require minimal repeat count (1) to enter I3BN mode -// 2. Increased 1-bit-replaced repeat limit (2 => 10) -// 3. Length of direct repetition counter reduced (8 => 6 bits). -// -// pixels - flat array of pixels (one per entry) -// options.bpp - bits per pixels -// -module.exports = function compress(bitStream, pixels, options) { - const opts = Object.assign({}, { repeat: 1 }, options); - - // Minimal repetitions count to enable RLE mode. - const RLE_SKIP_COUNT = 1; - // Number of repeats, when `1` used to replace data - // If more - write as number - const RLE_BIT_COLLAPSED_COUNT = 10; - - const RLE_COUNTER_BITS = 6; // (2^bits - 1) - max value - const RLE_COUNTER_MAX = (1 << RLE_COUNTER_BITS) - 1; - // Force flush if counter dencity exceeded. - const RLE_MAX_REPEATS = RLE_COUNTER_MAX + RLE_BIT_COLLAPSED_COUNT + 1; - - //let bits_start_offset = bitStream.index; - - let offset = 0; - - while (offset < pixels.length) { - const p = pixels[offset]; - - let same = count_same(pixels, offset); - - // Clamp value because RLE counter density is limited - if (same > RLE_MAX_REPEATS + RLE_SKIP_COUNT) { - same = RLE_MAX_REPEATS + RLE_SKIP_COUNT; - } - - //debug(`offset: ${offset}, count: ${same}, pixel: ${p}`); - - offset += same; - - // If not enough for RLE - write as is. - if (same <= RLE_SKIP_COUNT) { - for (let i = 0; i < same; i++) { - bitStream.writeBits(p, opts.bpp); - //debug(`==> ${opts.bpp} bits`); - } - continue; - } - - // First, write "skipped" head as is. - for (let i = 0; i < RLE_SKIP_COUNT; i++) { - bitStream.writeBits(p, opts.bpp); - //debug(`==> ${opts.bpp} bits`); - } - - same -= RLE_SKIP_COUNT; - - // Not reached state to use counter => dump bit-extended - if (same <= RLE_BIT_COLLAPSED_COUNT) { - bitStream.writeBits(p, opts.bpp); - //debug(`==> ${opts.bpp} bits (val)`); - for (let i = 0; i < same; i++) { - /*eslint-disable max-depth*/ - if (i < same - 1) { - bitStream.writeBits(1, 1); - //debug('==> 1 bit (rle repeat)'); - } else { - bitStream.writeBits(0, 1); - //debug('==> 1 bit (rle repeat last)'); - } - } - continue; - } - - same -= RLE_BIT_COLLAPSED_COUNT + 1; - - bitStream.writeBits(p, opts.bpp); - //debug(`==> ${opts.bpp} bits (val)`); - - for (let i = 0; i < RLE_BIT_COLLAPSED_COUNT + 1; i++) { - bitStream.writeBits(1, 1); - //debug('==> 1 bit (rle repeat)'); - } - bitStream.writeBits(same, RLE_COUNTER_BITS); - //debug(`==> 4 bits (rle repeat count ${same})`); - } - - //debug(`output bits: ${bitStream.index - bits_start_offset}`); -}; diff --git a/node_modules/lv_font_conv/lib/font/font.js b/node_modules/lv_font_conv/lib/font/font.js deleted file mode 100644 index e5743629..00000000 --- a/node_modules/lv_font_conv/lib/font/font.js +++ /dev/null @@ -1,131 +0,0 @@ -// Font class to generate tables -'use strict'; - -const u = require('../utils'); -const debug = require('debug')('font'); -const Head = require('./table_head'); -const Cmap = require('./table_cmap'); -const Glyf = require('./table_glyf'); -const Loca = require('./table_loca'); -const Kern = require('./table_kern'); - -class Font { - constructor(fontData, options) { - this.src = fontData; - - this.opts = options; - - // Map chars to IDs (zero is reserved) - this.glyph_id = { 0: 0 }; - - this.last_id = 1; - this.createIDs(); - debug(`last_id: ${this.last_id}`); - - this.init_tables(); - - this.minY = Math.min(...this.src.glyphs.map(g => g.bbox.y)); - debug(`minY: ${this.minY}`); - this.maxY = Math.max(...this.src.glyphs.map(g => g.bbox.y + g.bbox.height)); - debug(`maxY: ${this.maxY}`); - - // 0 => 1 byte, 1 => 2 bytes - this.glyphIdFormat = Math.max(...Object.values(this.glyph_id)) > 255 ? 1 : 0; - debug(`glyphIdFormat: ${this.glyphIdFormat}`); - - // 1.0 by default, will be stored in font as FP12.4 - this.kerningScale = 1.0; - let kerningMax = Math.max(...this.src.glyphs.map(g => Object.values(g.kerning).map(Math.abs)).flat()); - if (kerningMax >= 7.5) this.kerningScale = Math.ceil(kerningMax / 7.5 * 16) / 16; - debug(`kerningScale: ${this.kerningScale}`); - - // 0 => int, 1 => FP4 - this.advanceWidthFormat = this.hasKerning() ? 1 : 0; - debug(`advanceWidthFormat: ${this.advanceWidthFormat}`); - - this.xy_bits = Math.max(...this.src.glyphs.map(g => Math.max( - u.signed_bits(g.bbox.x), u.signed_bits(g.bbox.y) - ))); - debug(`xy_bits: ${this.xy_bits}`); - - this.wh_bits = Math.max(...this.src.glyphs.map(g => Math.max( - u.unsigned_bits(g.bbox.width), u.unsigned_bits(g.bbox.height) - ))); - debug(`wh_bits: ${this.wh_bits}`); - - this.advanceWidthBits = Math.max(...this.src.glyphs.map( - g => u.signed_bits(this.widthToInt(g.advanceWidth)) - )); - debug(`advanceWidthBits: ${this.advanceWidthBits}`); - - let glyphs = this.src.glyphs; - - this.monospaced = glyphs.every((v, i, arr) => v.advanceWidth === arr[0].advanceWidth); - debug(`monospaced: ${this.monospaced}`); - - // This should stay in the end, because depends on previous variables - // 0 => 2 bytes, 1 => 4 bytes - this.indexToLocFormat = this.glyf.getSize() > 65535 ? 1 : 0; - debug(`indexToLocFormat: ${this.indexToLocFormat}`); - - this.subpixels_mode = options.lcd ? 1 : (options.lcd_v ? 2 : 0); - debug(`subpixels_mode: ${this.subpixels_mode}`); - } - - init_tables() { - this.head = new Head(this); - this.glyf = new Glyf(this); - this.cmap = new Cmap(this); - this.loca = new Loca(this); - this.kern = new Kern(this); - } - - createIDs() { - // Simplified, don't check dupes - this.last_id = 1; - - for (let i = 0; i < this.src.glyphs.length; i++) { - // reserve zero for special cases - this.glyph_id[this.src.glyphs[i].code] = this.last_id; - this.last_id++; - } - } - - hasKerning() { - if (this.opts.no_kerning) return false; - - for (let glyph of this.src.glyphs) { - if (glyph.kerning && Object.keys(glyph.kerning).length) return true; - } - return false; - } - - // Returns integer width, depending on format - widthToInt(val) { - if (this.advanceWidthFormat === 0) return Math.round(val); - - return Math.round(val * 16); - } - - // Convert kerning to FP4.4, useable for writer. Apply `kerningScale`. - kernToFP(val) { - return Math.round(val / this.kerningScale * 16); - } - - toBin() { - const result = Buffer.concat([ - this.head.toBin(), - this.cmap.toBin(), - this.loca.toBin(), - this.glyf.toBin(), - this.kern.toBin() - ]); - - debug(`font size: ${result.length}`); - - return result; - } -} - - -module.exports = Font; diff --git a/node_modules/lv_font_conv/lib/font/table_cmap.js b/node_modules/lv_font_conv/lib/font/table_cmap.js deleted file mode 100644 index 8e94bde1..00000000 --- a/node_modules/lv_font_conv/lib/font/table_cmap.js +++ /dev/null @@ -1,201 +0,0 @@ -'use strict'; - - -const build_subtables = require('./cmap_build_subtables'); -const u = require('../utils'); -const debug = require('debug')('font.table.cmap'); - - -const O_SIZE = 0; -const O_LABEL = O_SIZE + 4; -const O_COUNT = O_LABEL + 4; - -const HEAD_LENGTH = O_COUNT + 4; - -const SUB_FORMAT_0 = 0; -const SUB_FORMAT_0_TINY = 2; -const SUB_FORMAT_SPARSE = 1; -const SUB_FORMAT_SPARSE_TINY = 3; - - -class Cmap { - constructor(font) { - this.font = font; - this.label = 'cmap'; - - this.sub_heads = []; - this.sub_data = []; - - this.compiled = false; - } - - compile() { - if (this.compiled) return; - this.compiled = true; - - const f = this.font; - - let subtables_plan = build_subtables(f.src.glyphs.map(g => g.code)); - - const count_format0 = subtables_plan.filter(s => s[0] === 'format0').length; - const count_sparse = subtables_plan.length - count_format0; - debug(`${subtables_plan.length} subtable(s): ${count_format0} "format 0", ${count_sparse} "sparse"`); - - for (let [ format, codepoints ] of subtables_plan) { - let g = this.glyphByCode(codepoints[0]); - let start_glyph_id = f.glyph_id[g.code]; - let min_code = codepoints[0]; - let max_code = codepoints[codepoints.length - 1]; - let entries_count = max_code - min_code + 1; - let format_code = 0; - - if (format === 'format0_tiny') { - format_code = SUB_FORMAT_0_TINY; - this.sub_data.push(Buffer.alloc(0)); - } else if (format === 'format0') { - format_code = SUB_FORMAT_0; - this.sub_data.push(this.create_format0_data(min_code, max_code, start_glyph_id)); - } else if (format === 'sparse_tiny') { - entries_count = codepoints.length; - format_code = SUB_FORMAT_SPARSE_TINY; - this.sub_data.push(this.create_sparse_tiny_data(codepoints, start_glyph_id)); - } else { // assume format === 'sparse' - entries_count = codepoints.length; - format_code = SUB_FORMAT_SPARSE; - this.sub_data.push(this.create_sparse_data(codepoints, start_glyph_id)); - } - - this.sub_heads.push(this.createSubHeader( - min_code, - max_code - min_code + 1, - start_glyph_id, - entries_count, - format_code - )); - } - - this.subHeaderUpdateAllOffsets(); - } - - createSubHeader(rangeStart, rangeLen, glyphIdOffset, total, type) { - const buf = Buffer.alloc(16); - - // buf.writeUInt32LE(offset, 0); offset unknown at this moment - buf.writeUInt32LE(rangeStart, 4); - buf.writeUInt16LE(rangeLen, 8); - buf.writeUInt16LE(glyphIdOffset, 10); - buf.writeUInt16LE(total, 12); - buf.writeUInt8(type, 14); - - return buf; - } - - subHeaderUpdateOffset(header, val) { - header.writeUInt32LE(val, 0); - } - - subHeaderUpdateAllOffsets() { - for (let i = 0; i < this.sub_heads.length; i++) { - const offset = HEAD_LENGTH + - u.sum(this.sub_heads.map(h => h.length)) + - u.sum(this.sub_data.slice(0, i).map(d => d.length)); - - this.subHeaderUpdateOffset(this.sub_heads[i], offset); - } - } - - glyphByCode(code) { - for (let g of this.font.src.glyphs) { - if (g.code === code) return g; - } - - return null; - } - - - collect_format0_data(min_code, max_code, start_glyph_id) { - let data = []; - - for (let i = min_code; i <= max_code; i++) { - const g = this.glyphByCode(i); - - if (!g) { - data.push(0); - continue; - } - - const id_delta = this.font.glyph_id[g.code] - start_glyph_id; - - if (id_delta < 0 || id_delta > 255) throw new Error('Glyph ID delta out of Format 0 range'); - - data.push(id_delta); - } - - return data; - } - - create_format0_data(min_code, max_code, start_glyph_id) { - const data = this.collect_format0_data(min_code, max_code, start_glyph_id); - - return u.balign4(Buffer.from(data)); - } - - collect_sparse_data(codepoints, start_glyph_id) { - let codepoints_list = []; - let ids_list = []; - - for (let code of codepoints) { - let g = this.glyphByCode(code); - let id = this.font.glyph_id[g.code]; - - let code_delta = code - codepoints[0]; - let id_delta = id - start_glyph_id; - - if (code_delta < 0 || code_delta > 65535) throw new Error('Codepoint delta out of range'); - if (id_delta < 0 || id_delta > 65535) throw new Error('Glyph ID delta out of range'); - - codepoints_list.push(code_delta); - ids_list.push(id_delta); - } - - return { - codes: codepoints_list, - ids: ids_list - }; - } - - create_sparse_data(codepoints, start_glyph_id) { - const data = this.collect_sparse_data(codepoints, start_glyph_id); - - return u.balign4(Buffer.concat([ - u.bFromA16(data.codes), - u.bFromA16(data.ids) - ])); - } - - create_sparse_tiny_data(codepoints, start_glyph_id) { - const data = this.collect_sparse_data(codepoints, start_glyph_id); - - return u.balign4(u.bFromA16(data.codes)); - } - - toBin() { - if (!this.compiled) this.compile(); - - const buf = Buffer.concat([ - Buffer.alloc(HEAD_LENGTH), - Buffer.concat(this.sub_heads), - Buffer.concat(this.sub_data) - ]); - debug(`table size = ${buf.length}`); - - buf.writeUInt32LE(buf.length, O_SIZE); - buf.write(this.label, O_LABEL); - buf.writeUInt32LE(this.sub_heads.length, O_COUNT); - - return buf; - } -} - - -module.exports = Cmap; diff --git a/node_modules/lv_font_conv/lib/font/table_glyf.js b/node_modules/lv_font_conv/lib/font/table_glyf.js deleted file mode 100644 index e7a62999..00000000 --- a/node_modules/lv_font_conv/lib/font/table_glyf.js +++ /dev/null @@ -1,147 +0,0 @@ -'use strict'; - -const u = require('../utils'); -const { BitStream } = require('bit-buffer'); -const debug = require('debug')('font.table.glyf'); -const compress = require('./compress'); - - -const O_SIZE = 0; -const O_LABEL = O_SIZE + 4; - -const HEAD_LENGTH = O_LABEL + 4; - - -class Glyf { - constructor(font) { - this.font = font; - this.label = 'glyf'; - - this.compiled = false; - - this.binData = []; - } - - // convert 8-bit opacity to bpp-bit - pixelsToBpp(pixels) { - const bpp = this.font.opts.bpp; - return pixels.map(line => line.map(p => (p >>> (8 - bpp)))); - } - - // Returns "binary stream" (Buffer) of compiled glyph data - compileGlyph(glyph) { - // Allocate memory, enough for eny storage formats - const buf = Buffer.alloc(100 + glyph.bbox.width * glyph.bbox.height * 4); - const bs = new BitStream(buf); - bs.bigEndian = true; - const f = this.font; - - // Store Width - if (!f.monospaced) { - let w = f.widthToInt(glyph.advanceWidth); - bs.writeBits(w, f.advanceWidthBits); - } - - // Store X, Y - bs.writeBits(glyph.bbox.x, f.xy_bits); - bs.writeBits(glyph.bbox.y, f.xy_bits); - bs.writeBits(glyph.bbox.width, f.wh_bits); - bs.writeBits(glyph.bbox.height, f.wh_bits); - - const pixels = this.pixelsToBpp(glyph.pixels); - - this.storePixels(bs, pixels); - - // Shrink size - const result = Buffer.alloc(bs.byteIndex); - buf.copy(result, 0, 0, bs.byteIndex); - - return result; - } - - storePixels(bitStream, pixels) { - if (this.getCompressionCode() === 0) this.storePixelsRaw(bitStream, pixels); - else this.storePixelsCompressed(bitStream, pixels); - } - - storePixelsRaw(bitStream, pixels) { - const bpp = this.font.opts.bpp; - - for (let y = 0; y < pixels.length; y++) { - const line = pixels[y]; - for (let x = 0; x < line.length; x++) { - bitStream.writeBits(line[x], bpp); - } - } - } - - storePixelsCompressed(bitStream, pixels) { - let p; - - if (this.font.opts.no_prefilter) p = pixels.flat(); - else p = u.prefilter(pixels).flat(); - - compress(bitStream, p, this.font.opts); - } - - // Create internal struct with binary data for each glyph - // Needed to calculate offsets & build final result - compile() { - this.compiled = true; - - this.binData = [ - Buffer.alloc(0) // Reserve id 0 - ]; - - const f = this.font; - - f.src.glyphs.forEach(g => { - const id = f.glyph_id[g.code]; - - this.binData[id] = this.compileGlyph(g); - }); - } - - toBin() { - if (!this.compiled) this.compile(); - - const buf = u.balign4(Buffer.concat([ - Buffer.alloc(HEAD_LENGTH), - Buffer.concat(this.binData) - ])); - - buf.writeUInt32LE(buf.length, O_SIZE); - buf.write(this.label, O_LABEL); - - debug(`table size = ${buf.length}`); - - return buf; - } - - getSize() { - if (!this.compiled) this.compile(); - - return u.align4(HEAD_LENGTH + u.sum(this.binData.map(b => b.length))); - } - - getOffset(id) { - if (!this.compiled) this.compile(); - - let offset = HEAD_LENGTH; - - for (let i = 0; i < id; i++) offset += this.binData[i].length; - - return offset; - } - - getCompressionCode() { - if (this.font.opts.no_compress) return 0; - if (this.font.opts.bpp === 1) return 0; - - if (this.font.opts.no_prefilter) return 2; - return 1; - } -} - - -module.exports = Glyf; diff --git a/node_modules/lv_font_conv/lib/font/table_head.js b/node_modules/lv_font_conv/lib/font/table_head.js deleted file mode 100644 index 4cb9f676..00000000 --- a/node_modules/lv_font_conv/lib/font/table_head.js +++ /dev/null @@ -1,99 +0,0 @@ -'use strict'; - - -const u = require('../utils'); -const debug = require('debug')('font.table.head'); - -const O_SIZE = 0; -const O_LABEL = O_SIZE + 4; -const O_VERSION = O_LABEL + 4; -const O_TABLES = O_VERSION + 4; -const O_FONT_SIZE = O_TABLES + 2; -const O_ASCENT = O_FONT_SIZE + 2; -const O_DESCENT = O_ASCENT + 2; -const O_TYPO_ASCENT = O_DESCENT + 2; -const O_TYPO_DESCENT = O_TYPO_ASCENT + 2; -const O_TYPO_LINE_GAP = O_TYPO_DESCENT + 2; -const O_MIN_Y = O_TYPO_LINE_GAP + 2; -const O_MAX_Y = O_MIN_Y + 2; -const O_DEF_ADVANCE_WIDTH = O_MAX_Y + 2; -const O_KERNING_SCALE = O_DEF_ADVANCE_WIDTH + 2; -const O_INDEX_TO_LOC_FORMAT = O_KERNING_SCALE + 2; -const O_GLYPH_ID_FORMAT = O_INDEX_TO_LOC_FORMAT + 1; -const O_ADVANCE_WIDTH_FORMAT = O_GLYPH_ID_FORMAT + 1; -const O_BITS_PER_PIXEL = O_ADVANCE_WIDTH_FORMAT + 1; -const O_XY_BITS = O_BITS_PER_PIXEL + 1; -const O_WH_BITS = O_XY_BITS + 1; -const O_ADVANCE_WIDTH_BITS = O_WH_BITS + 1; -const O_COMPRESSION_ID = O_ADVANCE_WIDTH_BITS + 1; -const O_SUBPIXELS_MODE = O_COMPRESSION_ID + 1; -const O_TMP_RESERVED1 = O_SUBPIXELS_MODE + 1; -const O_UNDERLINE_POSITION = O_TMP_RESERVED1 + 1; -const O_UNDERLINE_THICKNESS = O_UNDERLINE_POSITION + 2; -const HEAD_LENGTH = u.align4(O_UNDERLINE_THICKNESS + 2); - - -class Head { - constructor(font) { - this.font = font; - this.label = 'head'; - this.version = 1; - } - - toBin() { - const buf = Buffer.alloc(HEAD_LENGTH); - debug(`table size = ${buf.length}`); - - buf.writeUInt32LE(HEAD_LENGTH, O_SIZE); - buf.write(this.label, O_LABEL); - buf.writeUInt32LE(this.version, O_VERSION); - - const f = this.font; - - const tables_count = f.hasKerning() ? 4 : 3; - - buf.writeUInt16LE(tables_count, O_TABLES); - - buf.writeUInt16LE(f.src.size, O_FONT_SIZE); - buf.writeUInt16LE(f.src.ascent, O_ASCENT); - buf.writeInt16LE(f.src.descent, O_DESCENT); - - buf.writeUInt16LE(f.src.typoAscent, O_TYPO_ASCENT); - buf.writeInt16LE(f.src.typoDescent, O_TYPO_DESCENT); - buf.writeUInt16LE(f.src.typoLineGap, O_TYPO_LINE_GAP); - - buf.writeInt16LE(f.minY, O_MIN_Y); - buf.writeInt16LE(f.maxY, O_MAX_Y); - - if (f.monospaced) { - buf.writeUInt16LE(f.widthToInt(f.src.glyphs[0].advanceWidth), O_DEF_ADVANCE_WIDTH); - } else { - buf.writeUInt16LE(0, O_DEF_ADVANCE_WIDTH); - } - - buf.writeUInt16LE(Math.round(f.kerningScale * 16), O_KERNING_SCALE); // FP12.4 - - buf.writeUInt8(f.indexToLocFormat, O_INDEX_TO_LOC_FORMAT); - buf.writeUInt8(f.glyphIdFormat, O_GLYPH_ID_FORMAT); - buf.writeUInt8(f.advanceWidthFormat, O_ADVANCE_WIDTH_FORMAT); - - buf.writeUInt8(f.opts.bpp, O_BITS_PER_PIXEL); - buf.writeUInt8(f.xy_bits, O_XY_BITS); - buf.writeUInt8(f.wh_bits, O_WH_BITS); - - if (f.monospaced) buf.writeUInt8(0, O_ADVANCE_WIDTH_BITS); - else buf.writeUInt8(f.advanceWidthBits, O_ADVANCE_WIDTH_BITS); - - buf.writeUInt8(f.glyf.getCompressionCode(), O_COMPRESSION_ID); - - buf.writeUInt8(f.subpixels_mode, O_SUBPIXELS_MODE); - - buf.writeInt16LE(f.src.underlinePosition, O_UNDERLINE_POSITION); - buf.writeUInt16LE(f.src.underlineThickness, O_UNDERLINE_POSITION); - - return buf; - } -} - - -module.exports = Head; diff --git a/node_modules/lv_font_conv/lib/font/table_kern.js b/node_modules/lv_font_conv/lib/font/table_kern.js deleted file mode 100644 index e879b3c7..00000000 --- a/node_modules/lv_font_conv/lib/font/table_kern.js +++ /dev/null @@ -1,256 +0,0 @@ -'use strict'; - -const u = require('../utils'); -const debug = require('debug')('font.table.kern'); - - -const O_SIZE = 0; -const O_LABEL = O_SIZE + 4; -const O_FORMAT = O_LABEL + 4; - -const HEAD_LENGTH = u.align4(O_FORMAT + 1); - - -class Kern { - constructor(font) { - this.font = font; - this.label = 'kern'; - this.format3_forced = false; - } - - collect_format0_data() { - const f = this.font; - const glyphs = u.sort_by(this.font.src.glyphs, g => f.glyph_id[g.code]); - const kernSorted = []; - - for (let g of glyphs) { - if (!g.kerning || !Object.keys(g.kerning).length) continue; - - const glyph_id = f.glyph_id[g.code]; - const paired = u.sort_by(Object.keys(g.kerning), code => f.glyph_id[code]); - - for (let code of paired) { - const glyph_id2 = f.glyph_id[code]; - kernSorted.push([ glyph_id, glyph_id2, g.kerning[code] ]); - } - } - - return kernSorted; - } - - create_format0_data() { - const f = this.font; - const glyphs = this.font.src.glyphs; - const kernSorted = this.collect_format0_data(); - - const count = kernSorted.length; - - const kerned_glyphs = glyphs.filter(g => Object.keys(g.kerning).length).length; - const kerning_list_max = Math.max(...glyphs.map(g => Object.keys(g.kerning).length)); - debug(`${kerned_glyphs} kerned glyphs of ${glyphs.length}, ${kerning_list_max} max list, ${count} total pairs`); - - const subheader = Buffer.alloc(4); - - subheader.writeUInt32LE(count, 0); - - const pairs_buf = Buffer.alloc((f.glyphIdFormat ? 4 : 2) * count); - - // Write kerning pairs - for (let i = 0; i < count; i++) { - if (f.glyphIdFormat === 0) { - pairs_buf.writeUInt8(kernSorted[i][0], 2 * i); - pairs_buf.writeUInt8(kernSorted[i][1], 2 * i + 1); - } else { - pairs_buf.writeUInt16LE(kernSorted[i][0], 4 * i); - pairs_buf.writeUInt16LE(kernSorted[i][1], 4 * i + 2); - } - } - - const values_buf = Buffer.alloc(count); - - // Write kerning values - for (let i = 0; i < count; i++) { - values_buf.writeInt8(f.kernToFP(kernSorted[i][2]), i); // FP4.4 - } - - let buf = Buffer.concat([ - subheader, - pairs_buf, - values_buf - ]); - - let buf_aligned = u.balign4(buf); - - debug(`table format0 size = ${buf_aligned.length}`); - return buf_aligned; - } - - collect_format3_data() { - const f = this.font; - const glyphs = u.sort_by(this.font.src.glyphs, g => f.glyph_id[g.code]); - - // extract kerning pairs for each character; - // left kernings are kerning values based on left char (already there), - // right kernings are kerning values based on right char (extracted from left) - const left_kernings = {}; - const right_kernings = {}; - - for (let g of glyphs) { - if (!g.kerning || !Object.keys(g.kerning).length) continue; - - const paired = Object.keys(g.kerning); - - left_kernings[g.code] = g.kerning; - - for (let code of paired) { - right_kernings[code] = right_kernings[code] || {}; - right_kernings[code][g.code] = g.kerning[code]; - } - } - - // input: - // - kernings, char => { hash: String, [char1]: Number, [char2]: Number, ... } - // - // returns: - // - array of [ char1, char2, ... ] - // - function build_classes(kernings) { - const classes = []; - - for (let code of Object.keys(kernings)) { - // for each kerning table calculate unique value representing it; - // keys needs to be sorted for this (but we're using numeric keys, so - // sorting happens automatically and can't be changed) - const hash = JSON.stringify(kernings[code]); - - classes[hash] = classes[hash] || []; - classes[hash].push(Number(code)); - } - - return Object.values(classes); - } - - const left_classes = build_classes(left_kernings); - debug(`unique left classes: ${left_classes.length}`); - - const right_classes = build_classes(right_kernings); - debug(`unique right classes: ${right_classes.length}`); - - if (left_classes.length >= 255 || right_classes.length >= 255) { - debug('too many classes for format3 subtable'); - return null; - } - - function kern_class_mapping(classes) { - const arr = Array(f.last_id).fill(0); - - classes.forEach((members, idx) => { - for (let code of members) { - arr[f.glyph_id[code]] = idx + 1; - } - }); - - return arr; - } - - function kern_class_values() { - const arr = []; - - for (let left_class of left_classes) { - for (let right_class of right_classes) { - let code1 = left_class[0]; - let code2 = right_class[0]; - arr.push(left_kernings[code1][code2] || 0); - } - } - - return arr; - } - - return { - left_classes: left_classes.length, - right_classes: right_classes.length, - left_mapping: kern_class_mapping(left_classes), - right_mapping: kern_class_mapping(right_classes), - values: kern_class_values() - }; - } - - create_format3_data() { - const f = this.font; - const { - left_classes, - right_classes, - left_mapping, - right_mapping, - values - } = this.collect_format3_data(); - - const subheader = Buffer.alloc(4); - subheader.writeUInt16LE(f.last_id); - subheader.writeUInt8(left_classes, 2); - subheader.writeUInt8(right_classes, 3); - - let buf = Buffer.concat([ - subheader, - Buffer.from(left_mapping), - Buffer.from(right_mapping), - Buffer.from(values.map(v => f.kernToFP(v))) - ]); - - let buf_aligned = u.balign4(buf); - - debug(`table format3 size = ${buf_aligned.length}`); - return buf_aligned; - } - - should_use_format3() { - if (!this.font.hasKerning()) return false; - - const format0_data = this.create_format0_data(); - const format3_data = this.create_format3_data(); - - if (format3_data && format3_data.length <= format0_data.length) return true; - - if (this.font.opts.fast_kerning && format3_data) { - this.format3_forced = true; - return true; - } - - return false; - } - - toBin() { - if (!this.font.hasKerning()) return Buffer.alloc(0); - - const format0_data = this.create_format0_data(); - const format3_data = this.create_format3_data(); - - let header = Buffer.alloc(HEAD_LENGTH); - - let data = format0_data; - header.writeUInt8(0, O_FORMAT); - - /* eslint-disable no-console */ - - if (this.should_use_format3()) { - data = format3_data; - header.writeUInt8(3, O_FORMAT); - - if (this.format3_forced) { - let diff = format3_data.length - format0_data.length; - console.log(`Forced faster kerning format (via classes). Size increase is ${diff} bytes.`); - } - } else if (this.font.opts.fast_kerning) { - console.log('Forced faster kerning format (via classes), but data exceeds it\'s limits. Continue use pairs.'); - } - - header.writeUInt32LE(header.length + data.length, O_SIZE); - header.write(this.label, O_LABEL); - - return Buffer.concat([ header, data ]); - } -} - - -module.exports = Kern; diff --git a/node_modules/lv_font_conv/lib/font/table_loca.js b/node_modules/lv_font_conv/lib/font/table_loca.js deleted file mode 100644 index 4aa50d22..00000000 --- a/node_modules/lv_font_conv/lib/font/table_loca.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - - -const u = require('../utils'); -const debug = require('debug')('font.table.loca'); - - -const O_SIZE = 0; -const O_LABEL = O_SIZE + 4; -const O_COUNT = O_LABEL + 4; - -const HEAD_LENGTH = O_COUNT + 4; - - -class Loca { - constructor(font) { - this.font = font; - this.label = 'loca'; - } - - toBin() { - const f = this.font; - - const offsets = [ ...Array(f.last_id).keys() ].map(i => f.glyf.getOffset(i)); - - const buf = u.balign4(Buffer.concat([ - Buffer.alloc(HEAD_LENGTH), - f.indexToLocFormat ? u.bFromA32(offsets) : u.bFromA16(offsets) - ])); - - buf.writeUInt32LE(buf.length, O_SIZE); - buf.write(this.label, O_LABEL); - buf.writeUInt32LE(f.last_id, O_COUNT); - - debug(`table size = ${buf.length}`); - - return buf; - } -} - - -module.exports = Loca; diff --git a/node_modules/lv_font_conv/lib/freetype/index.js b/node_modules/lv_font_conv/lib/freetype/index.js deleted file mode 100644 index d05f68c3..00000000 --- a/node_modules/lv_font_conv/lib/freetype/index.js +++ /dev/null @@ -1,317 +0,0 @@ -'use strict'; - - -const ft_render_fabric = require('./build/ft_render'); - -let m = null; // compiled module instance -let library = 0; // pointer to library struct in wasm memory - - -// workaround because of bug in emscripten: -// https://github.com/emscripten-core/emscripten/issues/5820 -const runtime_initialized = new Promise(resolve => { - ft_render_fabric().then(module_instance => { - m = module_instance; - resolve(); - }); -}); - -function from_16_16(fixed_point) { - return fixed_point / (1 << 16); -} - -function from_26_6(fixed_point) { - return fixed_point / (1 << 6); -} - -function int8_to_uint8(value) { - return value >= 0 ? value : value + 0x100; -} - -let FT_New_Memory_Face, - FT_Set_Char_Size, - FT_Set_Pixel_Sizes, - FT_Get_Char_Index, - FT_Load_Glyph, - FT_Get_Sfnt_Table, - FT_Get_Kerning, - FT_Done_Face; - -module.exports.init = async function () { - await runtime_initialized; - m._init_constants(); - - FT_New_Memory_Face = module.exports.FT_New_Memory_Face = - m.cwrap('FT_New_Memory_Face', 'number', [ 'number', 'number', 'number', 'number', 'number' ]); - - FT_Set_Char_Size = module.exports.FT_Set_Char_Size = - m.cwrap('FT_Set_Char_Size', 'number', [ 'number', 'number', 'number', 'number', 'number' ]); - - FT_Set_Pixel_Sizes = module.exports.FT_Set_Pixel_Sizes = - m.cwrap('FT_Set_Pixel_Sizes', 'number', [ 'number', 'number', 'number' ]); - - FT_Get_Char_Index = module.exports.FT_Get_Char_Index = - m.cwrap('FT_Get_Char_Index', 'number', [ 'number', 'number' ]); - - FT_Load_Glyph = module.exports.FT_Load_Glyph = - m.cwrap('FT_Load_Glyph', 'number', [ 'number', 'number', 'number' ]); - - FT_Get_Sfnt_Table = module.exports.FT_Get_Sfnt_Table = - m.cwrap('FT_Get_Sfnt_Table', 'number', [ 'number', 'number' ]); - - FT_Get_Kerning = module.exports.FT_Get_Kerning = - m.cwrap('FT_Get_Kerning', 'number', [ 'number', 'number', 'number', 'number', 'number' ]); - - FT_Done_Face = module.exports.FT_Done_Face = - m.cwrap('FT_Done_Face', 'number', [ 'number' ]); - - if (!library) { - let ptr = m._malloc(4); - - try { - let error = m.ccall('FT_Init_FreeType', 'number', [ 'number' ], [ ptr ]); - - if (error) throw new Error(`error in FT_Init_FreeType: ${error}`); - - library = m.getValue(ptr, 'i32'); - } finally { - m._free(ptr); - } - } -}; - - -module.exports.fontface_create = function (source, size) { - let error; - let face = { - ptr: 0, - font: m._malloc(source.length) - }; - - m.writeArrayToMemory(source, face.font); - - let ptr = m._malloc(4); - - try { - error = FT_New_Memory_Face(library, face.font, source.length, 0, ptr); - - if (error) throw new Error(`error in FT_New_Memory_Face: ${error}`); - - face.ptr = m.getValue(ptr, 'i32'); - } finally { - m._free(ptr); - } - - error = FT_Set_Char_Size(face.ptr, 0, size * 64, 300, 300); - - if (error) throw new Error(`error in FT_Set_Char_Size: ${error}`); - - error = FT_Set_Pixel_Sizes(face.ptr, 0, size); - - if (error) throw new Error(`error in FT_Set_Pixel_Sizes: ${error}`); - - let units_per_em = m.getValue(face.ptr + m.OFFSET_FACE_UNITS_PER_EM, 'i16'); - let ascender = m.getValue(face.ptr + m.OFFSET_FACE_ASCENDER, 'i16'); - let descender = m.getValue(face.ptr + m.OFFSET_FACE_DESCENDER, 'i16'); - let height = m.getValue(face.ptr + m.OFFSET_FACE_HEIGHT, 'i16'); - - return Object.assign(face, { - units_per_em, - ascender, - descender, - height - }); -}; - - -module.exports.fontface_os2_table = function (face) { - let sfnt_ptr = FT_Get_Sfnt_Table(face.ptr, m.FT_SFNT_OS2); - - if (!sfnt_ptr) throw new Error('os/2 table not found for this font'); - - let typoAscent = m.getValue(sfnt_ptr + m.OFFSET_TT_OS2_ASCENDER, 'i16'); - let typoDescent = m.getValue(sfnt_ptr + m.OFFSET_TT_OS2_DESCENDER, 'i16'); - let typoLineGap = m.getValue(sfnt_ptr + m.OFFSET_TT_OS2_LINEGAP, 'i16'); - - return { - typoAscent, - typoDescent, - typoLineGap - }; -}; - - -module.exports.get_kerning = function (face, code1, code2) { - let glyph1 = FT_Get_Char_Index(face.ptr, code1); - let glyph2 = FT_Get_Char_Index(face.ptr, code2); - let ptr = m._malloc(4 * 2); - - try { - let error = FT_Get_Kerning(face.ptr, glyph1, glyph2, m.FT_KERNING_DEFAULT, ptr); - - if (error) throw new Error(`error in FT_Get_Kerning: ${error}`); - } finally { - m._free(ptr); - } - - return { - x: from_26_6(m.getValue(ptr, 'i32')), - y: from_26_6(m.getValue(ptr + 4, 'i32')) - }; -}; - - -module.exports.glyph_exists = function (face, code) { - let glyph_index = FT_Get_Char_Index(face.ptr, code); - - return glyph_index !== 0; -}; - - -module.exports.glyph_render = function (face, code, opts = {}) { - let glyph_index = FT_Get_Char_Index(face.ptr, code); - - if (glyph_index === 0) throw new Error(`glyph does not exist for codepoint ${code}`); - - let load_flags = m.FT_LOAD_RENDER; - - if (opts.mono) { - load_flags |= m.FT_LOAD_TARGET_MONO; - - } else if (opts.lcd) { - load_flags |= m.FT_LOAD_TARGET_LCD; - - } else if (opts.lcd_v) { - load_flags |= m.FT_LOAD_TARGET_LCD_V; - - } else { - /* eslint-disable no-lonely-if */ - - // Use "light" by default, it changes horizontal lines only. - // "normal" is more strong (with vertical lines), but will break kerning, if - // no additional care taken. More advanced rendering requires upper level - // layout support (via Harfbuzz, for example). - if (!opts.autohint_strong) load_flags |= m.FT_LOAD_TARGET_LIGHT; - else load_flags |= m.FT_LOAD_TARGET_NORMAL; - } - - if (opts.autohint_off) load_flags |= m.FT_LOAD_NO_AUTOHINT; - else load_flags |= m.FT_LOAD_FORCE_AUTOHINT; - - if (opts.use_color_info) load_flags |= m.FT_LOAD_COLOR; - - let error = FT_Load_Glyph(face.ptr, glyph_index, load_flags); - - if (error) throw new Error(`error in FT_Load_Glyph: ${error}`); - - let glyph = m.getValue(face.ptr + m.OFFSET_FACE_GLYPH, 'i32'); - - let glyph_data = { - glyph_index: m.getValue(glyph + m.OFFSET_GLYPH_INDEX, 'i32'), - metrics: { - width: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_WIDTH, 'i32')), - height: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_HEIGHT, 'i32')), - horiBearingX: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_HORI_BEARING_X, 'i32')), - horiBearingY: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_HORI_BEARING_Y, 'i32')), - horiAdvance: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_HORI_ADVANCE, 'i32')), - vertBearingX: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_VERT_BEARING_X, 'i32')), - vertBearingY: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_VERT_BEARING_Y, 'i32')), - vertAdvance: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_VERT_ADVANCE, 'i32')) - }, - linearHoriAdvance: from_16_16(m.getValue(glyph + m.OFFSET_GLYPH_LINEAR_HORI_ADVANCE, 'i32')), - linearVertAdvance: from_16_16(m.getValue(glyph + m.OFFSET_GLYPH_LINEAR_VERT_ADVANCE, 'i32')), - advance: { - x: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_ADVANCE_X, 'i32')), - y: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_ADVANCE_Y, 'i32')) - }, - bitmap: { - width: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_WIDTH, 'i32'), - rows: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_ROWS, 'i32'), - pitch: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_PITCH, 'i32'), - num_grays: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_NUM_GRAYS, 'i16'), - pixel_mode: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_PIXEL_MODE, 'i8'), - palette_mode: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_PALETTE_MODE, 'i8') - }, - bitmap_left: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_LEFT, 'i32'), - bitmap_top: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_TOP, 'i32'), - lsb_delta: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_LSB_DELTA, 'i32')), - rsb_delta: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_RSB_DELTA, 'i32')) - }; - - let g_w = glyph_data.bitmap.width; - let g_h = glyph_data.bitmap.rows; - let g_x = glyph_data.bitmap_left; - let g_y = glyph_data.bitmap_top; - - let buffer = m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_BUFFER, 'i32'); - let pitch = Math.abs(glyph_data.bitmap.pitch); - - let advance_x = glyph_data.linearHoriAdvance; - let advance_y = glyph_data.linearVertAdvance; - - let pixel_mode = glyph_data.bitmap.pixel_mode; - - let output = []; - - for (let y = 0; y < g_h; y++) { - let row_start = buffer + y * pitch; - let line = []; - - for (let x = 0; x < g_w; x++) { - if (pixel_mode === m.FT_PIXEL_MODE_MONO) { - let value = m.getValue(row_start + ~~(x / 8), 'i8'); - line.push(value & (1 << (7 - (x % 8))) ? 255 : 0); - } else if (pixel_mode === m.FT_PIXEL_MODE_BGRA) { - let blue = int8_to_uint8(m.getValue(row_start + (x * 4) + 0, 'i8')); - let green = int8_to_uint8(m.getValue(row_start + (x * 4) + 1, 'i8')); - let red = int8_to_uint8(m.getValue(row_start + (x * 4) + 2, 'i8')); - let alpha = int8_to_uint8(m.getValue(row_start + (x * 4) + 3, 'i8')); - // convert RGBA to grayscale - let grayscale = Math.round(0.299 * red + 0.587 * green + 0.114 * blue); - if (grayscale > 255) grayscale = 255; - // meld grayscale into alpha channel - alpha = ((255 - grayscale) * alpha) / 255; - line.push(alpha); - } else { - let value = m.getValue(row_start + x, 'i8'); - line.push(int8_to_uint8(value)); - } - } - - output.push(line); - } - - return { - x: g_x, - y: g_y, - width: g_w, - height: g_h, - advance_x, - advance_y, - pixels: output, - freetype: glyph_data - }; -}; - - -module.exports.fontface_destroy = function (face) { - let error = FT_Done_Face(face.ptr); - - if (error) throw new Error(`error in FT_Done_Face: ${error}`); - - m._free(face.font); - face.ptr = 0; - face.font = 0; -}; - - -module.exports.destroy = function () { - let error = m.ccall('FT_Done_FreeType', 'number', [ 'number' ], [ library ]); - - if (error) throw new Error(`error in FT_Done_FreeType: ${error}`); - - library = 0; - - // don't unload wasm - slows down tests too much - //m = null; -}; diff --git a/node_modules/lv_font_conv/lib/freetype/render.c b/node_modules/lv_font_conv/lib/freetype/render.c deleted file mode 100644 index 901d4974..00000000 --- a/node_modules/lv_font_conv/lib/freetype/render.c +++ /dev/null @@ -1,83 +0,0 @@ -#include -#include -#include FT_FREETYPE_H -#include FT_TRUETYPE_TABLES_H - -static void set_js_variable(char* name, int value) { - char buffer[strlen(name) + 32]; - sprintf(buffer, "Module.%s = %d;", name, value); - emscripten_run_script(buffer); -} - -// Expose constants, used in calls from js -void init_constants() -{ - set_js_variable("FT_LOAD_DEFAULT", FT_LOAD_DEFAULT); - set_js_variable("FT_LOAD_NO_HINTING", FT_LOAD_NO_HINTING); - set_js_variable("FT_LOAD_RENDER", FT_LOAD_RENDER); - set_js_variable("FT_LOAD_FORCE_AUTOHINT", FT_LOAD_FORCE_AUTOHINT); - set_js_variable("FT_LOAD_PEDANTIC", FT_LOAD_PEDANTIC); - set_js_variable("FT_LOAD_MONOCHROME", FT_LOAD_MONOCHROME); - set_js_variable("FT_LOAD_NO_AUTOHINT", FT_LOAD_NO_AUTOHINT); - set_js_variable("FT_LOAD_COLOR", FT_LOAD_COLOR); - - set_js_variable("FT_LOAD_TARGET_NORMAL", FT_LOAD_TARGET_NORMAL); - set_js_variable("FT_LOAD_TARGET_LIGHT", FT_LOAD_TARGET_LIGHT); - set_js_variable("FT_LOAD_TARGET_MONO", FT_LOAD_TARGET_MONO); - set_js_variable("FT_LOAD_TARGET_LCD", FT_LOAD_TARGET_LCD); - set_js_variable("FT_LOAD_TARGET_LCD_V", FT_LOAD_TARGET_LCD_V); - - set_js_variable("FT_RENDER_MODE_NORMAL", FT_RENDER_MODE_NORMAL); - set_js_variable("FT_RENDER_MODE_MONO", FT_RENDER_MODE_MONO); - set_js_variable("FT_RENDER_MODE_LCD", FT_RENDER_MODE_LCD); - set_js_variable("FT_RENDER_MODE_LCD_V", FT_RENDER_MODE_LCD_V); - - set_js_variable("FT_KERNING_DEFAULT", FT_KERNING_DEFAULT); - set_js_variable("FT_KERNING_UNFITTED", FT_KERNING_UNFITTED); - set_js_variable("FT_KERNING_UNSCALED", FT_KERNING_UNSCALED); - - set_js_variable("FT_SFNT_OS2", FT_SFNT_OS2); - - set_js_variable("FT_FACE_FLAG_COLOR", FT_FACE_FLAG_COLOR); - - set_js_variable("FT_PIXEL_MODE_MONO", FT_PIXEL_MODE_MONO); - set_js_variable("FT_PIXEL_MODE_BGRA", FT_PIXEL_MODE_BGRA); - - set_js_variable("OFFSET_FACE_GLYPH", offsetof(FT_FaceRec, glyph)); - set_js_variable("OFFSET_FACE_UNITS_PER_EM", offsetof(FT_FaceRec, units_per_EM)); - set_js_variable("OFFSET_FACE_ASCENDER", offsetof(FT_FaceRec, ascender)); - set_js_variable("OFFSET_FACE_DESCENDER", offsetof(FT_FaceRec, descender)); - set_js_variable("OFFSET_FACE_HEIGHT", offsetof(FT_FaceRec, height)); - set_js_variable("OFFSET_FACE_FACE_FLAGS", offsetof(FT_FaceRec, face_flags)); - - set_js_variable("OFFSET_GLYPH_BITMAP_WIDTH", offsetof(FT_GlyphSlotRec, bitmap.width)); - set_js_variable("OFFSET_GLYPH_BITMAP_ROWS", offsetof(FT_GlyphSlotRec, bitmap.rows)); - set_js_variable("OFFSET_GLYPH_BITMAP_PITCH", offsetof(FT_GlyphSlotRec, bitmap.pitch)); - set_js_variable("OFFSET_GLYPH_BITMAP_BUFFER", offsetof(FT_GlyphSlotRec, bitmap.buffer)); - set_js_variable("OFFSET_GLYPH_BITMAP_NUM_GRAYS", offsetof(FT_GlyphSlotRec, bitmap.num_grays)); - set_js_variable("OFFSET_GLYPH_BITMAP_PIXEL_MODE", offsetof(FT_GlyphSlotRec, bitmap.pixel_mode)); - set_js_variable("OFFSET_GLYPH_BITMAP_PALETTE_MODE", offsetof(FT_GlyphSlotRec, bitmap.palette_mode)); - - set_js_variable("OFFSET_GLYPH_METRICS_WIDTH", offsetof(FT_GlyphSlotRec, metrics.width)); - set_js_variable("OFFSET_GLYPH_METRICS_HEIGHT", offsetof(FT_GlyphSlotRec, metrics.height)); - set_js_variable("OFFSET_GLYPH_METRICS_HORI_BEARING_X", offsetof(FT_GlyphSlotRec, metrics.horiBearingX)); - set_js_variable("OFFSET_GLYPH_METRICS_HORI_BEARING_Y", offsetof(FT_GlyphSlotRec, metrics.horiBearingY)); - set_js_variable("OFFSET_GLYPH_METRICS_HORI_ADVANCE", offsetof(FT_GlyphSlotRec, metrics.horiAdvance)); - set_js_variable("OFFSET_GLYPH_METRICS_VERT_BEARING_X", offsetof(FT_GlyphSlotRec, metrics.vertBearingX)); - set_js_variable("OFFSET_GLYPH_METRICS_VERT_BEARING_Y", offsetof(FT_GlyphSlotRec, metrics.vertBearingY)); - set_js_variable("OFFSET_GLYPH_METRICS_VERT_ADVANCE", offsetof(FT_GlyphSlotRec, metrics.vertAdvance)); - - set_js_variable("OFFSET_GLYPH_BITMAP_LEFT", offsetof(FT_GlyphSlotRec, bitmap_left)); - set_js_variable("OFFSET_GLYPH_BITMAP_TOP", offsetof(FT_GlyphSlotRec, bitmap_top)); - set_js_variable("OFFSET_GLYPH_INDEX", offsetof(FT_GlyphSlotRec, glyph_index)); - set_js_variable("OFFSET_GLYPH_LINEAR_HORI_ADVANCE", offsetof(FT_GlyphSlotRec, linearHoriAdvance)); - set_js_variable("OFFSET_GLYPH_LINEAR_VERT_ADVANCE", offsetof(FT_GlyphSlotRec, linearVertAdvance)); - set_js_variable("OFFSET_GLYPH_ADVANCE_X", offsetof(FT_GlyphSlotRec, advance.x)); - set_js_variable("OFFSET_GLYPH_ADVANCE_Y", offsetof(FT_GlyphSlotRec, advance.y)); - set_js_variable("OFFSET_GLYPH_LSB_DELTA", offsetof(FT_GlyphSlotRec, lsb_delta)); - set_js_variable("OFFSET_GLYPH_RSB_DELTA", offsetof(FT_GlyphSlotRec, rsb_delta)); - - set_js_variable("OFFSET_TT_OS2_ASCENDER", offsetof(TT_OS2, sTypoAscender)); - set_js_variable("OFFSET_TT_OS2_DESCENDER", offsetof(TT_OS2, sTypoDescender)); - set_js_variable("OFFSET_TT_OS2_LINEGAP", offsetof(TT_OS2, sTypoLineGap)); -} diff --git a/node_modules/lv_font_conv/lib/ranger.js b/node_modules/lv_font_conv/lib/ranger.js deleted file mode 100644 index 34372d75..00000000 --- a/node_modules/lv_font_conv/lib/ranger.js +++ /dev/null @@ -1,51 +0,0 @@ -// Merge ranges into single object - -'use strict'; - - -class Ranger { - constructor() { - this.data = {}; - } - - // input: - // -r 0x1F450 - single value, dec or hex format - // -r 0x1F450-0x1F470 - range - // -r 0x1F450=>0xF005 - single glyph with mapping - // -r 0x1F450-0x1F470=>0xF005 - range with mapping - add_range(font, start, end, mapped_start) { - let offset = mapped_start - start; - let output = []; - - for (let i = start; i <= end; i++) { - this._set_char(font, i, i + offset); - output.push(i); - } - - return output; - } - - // input: characters to copy, e.g. '1234567890abcdef' - add_symbols(font, str) { - let output = []; - - for (let chr of str) { - let code = chr.codePointAt(0); - this._set_char(font, code, code); - output.push(code); - } - - return output; - } - - _set_char(font, code, mapped_to) { - this.data[mapped_to] = { font, code }; - } - - get() { - return this.data; - } -} - - -module.exports = Ranger; diff --git a/node_modules/lv_font_conv/lib/utils.js b/node_modules/lv_font_conv/lib/utils.js deleted file mode 100644 index 0ca79322..00000000 --- a/node_modules/lv_font_conv/lib/utils.js +++ /dev/null @@ -1,131 +0,0 @@ -'use strict'; - - -function set_byte_depth(depth) { - return function (byte) { - // calculate significant bits, e.g. for depth=2 it's 0, 1, 2 or 3 - let value = ~~(byte / (256 >> depth)); - - // spread those bits around 0..255 range, e.g. for depth=2 it's 0, 85, 170 or 255 - let scale = (2 << (depth - 1)) - 1; - - return (value * 0xFFFF / scale) >> 8; - }; -} - - -module.exports.set_depth = function set_depth(glyph, depth) { - let pixels = []; - let fn = set_byte_depth(depth); - - for (let y = 0; y < glyph.bbox.height; y++) { - pixels.push(glyph.pixels[y].map(fn)); - } - - return Object.assign({}, glyph, { pixels }); -}; - - -function count_bits(val) { - let count = 0; - val = ~~val; - - while (val) { - count++; - val >>= 1; - } - - return count; -} - -// Minimal number of bits to store unsigned value -module.exports.unsigned_bits = count_bits; - -// Minimal number of bits to store signed value -module.exports.signed_bits = function signed_bits(val) { - if (val >= 0) return count_bits(val) + 1; - - return count_bits(Math.abs(val) - 1) + 1; -}; - -// Align value to 4x - useful to create word-aligned arrays -function align4(size) { - if (size % 4 === 0) return size; - return size + 4 - (size % 4); -} -module.exports.align4 = align4; - -// Align buffer length to 4x (returns copy with zero-filled tail) -module.exports.balign4 = function balign4(buf) { - let buf_aligned = Buffer.alloc(align4(buf.length)); - buf.copy(buf_aligned); - return buf_aligned; -}; - -// Pre-filter image to improve compression ratio -// In this case - XOR lines, because it's very effective -// in decompressor and does not depend on bpp. -module.exports.prefilter = function prefilter(pixels) { - return pixels.map((line, l_idx, arr) => { - if (l_idx === 0) return line.slice(); - - return line.map((p, idx) => p ^ arr[l_idx - 1][idx]); - }); -}; - - -// Convert array with uint16 data to buffer -module.exports.bFromA16 = function bFromA16(arr) { - const buf = Buffer.alloc(arr.length * 2); - - for (let i = 0; i < arr.length; i++) buf.writeUInt16LE(arr[i], i * 2); - - return buf; -}; - -// Convert array with uint32 data to buffer -module.exports.bFromA32 = function bFromA32(arr) { - const buf = Buffer.alloc(arr.length * 4); - - for (let i = 0; i < arr.length; i++) buf.writeUInt32LE(arr[i], i * 4); - - return buf; -}; - - -function chunk(arr, size) { - const result = []; - for (let i = 0; i < arr.length; i += size) { - result.push(arr.slice(i, i + size)); - } - return result; -} - -// Dump long array to multiline format with X columns and Y indent -module.exports.long_dump = function long_dump(arr, options = {}) { - const defaults = { - col: 8, - indent: 4, - hex: false - }; - - let opts = Object.assign({}, defaults, options); - let indent = ' '.repeat(opts.indent); - - return chunk(Array.from(arr), opts.col) - .map(l => l.map(v => (opts.hex ? `0x${v.toString(16)}` : v.toString()))) - .map(l => `${indent}${l.join(', ')}`) - .join(',\n'); -}; - -// stable sort by pick() result -module.exports.sort_by = function sort_by(arr, pick) { - return arr - .map((el, idx) => ({ el, idx })) - .sort((a, b) => (pick(a.el) - pick(b.el)) || (a.idx - b.idx)) - .map(({ el }) => el); -}; - -module.exports.sum = function sum(arr) { - return arr.reduce((a, v) => a + v, 0); -}; diff --git a/node_modules/lv_font_conv/lib/writers/bin.js b/node_modules/lv_font_conv/lib/writers/bin.js deleted file mode 100644 index bb482080..00000000 --- a/node_modules/lv_font_conv/lib/writers/bin.js +++ /dev/null @@ -1,17 +0,0 @@ -// Write font in binary format -'use strict'; - - -const AppError = require('../app_error'); -const Font = require('../font/font'); - - -module.exports = function write_images(args, fontData) { - if (!args.output) throw new AppError('Output is required for "bin" writer'); - - const font = new Font(fontData, args); - - return { - [args.output]: font.toBin() - }; -}; diff --git a/node_modules/lv_font_conv/lib/writers/dump.js b/node_modules/lv_font_conv/lib/writers/dump.js deleted file mode 100644 index 150d3b99..00000000 --- a/node_modules/lv_font_conv/lib/writers/dump.js +++ /dev/null @@ -1,68 +0,0 @@ -// Write font data into png images - -'use strict'; - - -const path = require('path'); -const { PNG } = require('pngjs'); -const AppError = require('../app_error'); -const utils = require('../utils'); - -const normal_color = [ 255, 255, 255 ]; -const outside_color = [ 255, 127, 184 ]; - - -module.exports = function write_images(args, font) { - if (!args.output) throw new AppError('Output is required for "dump" writer'); - - let files = {}; - - let glyphs = font.glyphs.map(glyph => utils.set_depth(glyph, args.bpp)); - - for (let glyph of glyphs) { - let { code, advanceWidth, bbox, pixels } = glyph; - - advanceWidth = Math.round(advanceWidth); - - let minX = bbox.x; - let maxX = Math.max(bbox.x + bbox.width - 1, bbox.x); - let minY = Math.min(bbox.y, font.typoDescent); - let maxY = Math.max(bbox.y + bbox.height - 1, font.typoAscent); - - let png = new PNG({ width: maxX - minX + 1, height: maxY - minY + 1 }); - - /* eslint-disable max-depth */ - for (let pos = 0, y = maxY; y >= minY; y--) { - for (let x = minX; x <= maxX; x++) { - let value = 0; - - if (x >= bbox.x && x < bbox.x + bbox.width && y >= bbox.y && y < bbox.y + bbox.height) { - value = pixels[bbox.height - (y - bbox.y) - 1][x - bbox.x]; - } - - let r, g, b; - - if (x < 0 || x >= advanceWidth || y < font.typoDescent || y > font.typoAscent) { - [ r, g, b ] = outside_color; - } else { - [ r, g, b ] = normal_color; - } - - png.data[pos++] = (255 - value) * r / 255; - png.data[pos++] = (255 - value) * g / 255; - png.data[pos++] = (255 - value) * b / 255; - png.data[pos++] = 255; - } - } - - - files[path.join(args.output, `${code.toString(16)}.png`)] = PNG.sync.write(png); - } - - files[path.join(args.output, 'font_info.json')] = JSON.stringify( - font, - (k, v) => (k === 'pixels' && !args.full_info ? undefined : v), - 2); - - return files; -}; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/index.js b/node_modules/lv_font_conv/lib/writers/lvgl/index.js deleted file mode 100644 index b592104f..00000000 --- a/node_modules/lv_font_conv/lib/writers/lvgl/index.js +++ /dev/null @@ -1,17 +0,0 @@ -// Write font in lvgl format -'use strict'; - - -const AppError = require('../../app_error'); -const Font = require('./lv_font'); - - -module.exports = function write_images(args, fontData) { - if (!args.output) throw new AppError('Output is required for "lvgl" writer'); - - const font = new Font(fontData, args); - - return { - [args.output]: font.toLVGL() - }; -}; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/lv_font.js b/node_modules/lv_font_conv/lib/writers/lvgl/lv_font.js deleted file mode 100644 index e3ad9c72..00000000 --- a/node_modules/lv_font_conv/lib/writers/lvgl/lv_font.js +++ /dev/null @@ -1,98 +0,0 @@ -'use strict'; - - -const path = require('path'); - -const Font = require('../../font/font'); -const Head = require('./lv_table_head'); -const Cmap = require('./lv_table_cmap'); -const Glyf = require('./lv_table_glyf'); -const Kern = require('./lv_table_kern'); -const AppError = require('../../app_error'); - - -class LvFont extends Font { - constructor(fontData, options) { - super(fontData, options); - - const ext = path.extname(options.output); - this.font_name = path.basename(options.output, ext); - - if (options.bpp === 3 & options.no_compress) { - throw new AppError('LittlevGL supports "--bpp 3" with compression only'); - } - } - - init_tables() { - this.head = new Head(this); - this.glyf = new Glyf(this); - this.cmap = new Cmap(this); - this.kern = new Kern(this); - } - - large_format_guard() { - let guard_required = false; - let glyphs_bin_size = 0; - - this.glyf.lv_data.forEach(d => { - glyphs_bin_size += d.bin.length; - - if (d.glyph.bbox.width > 255 || - d.glyph.bbox.height > 255 || - Math.abs(d.glyph.bbox.x) > 127 || - Math.abs(d.glyph.bbox.y) > 127 || - Math.round(d.glyph.advanceWidth * 16) > 4096) { - guard_required = true; - } - }); - - if (glyphs_bin_size > 1024 * 1024) guard_required = true; - - if (!guard_required) return ''; - - return ` -#if (LV_FONT_FMT_TXT_LARGE == 0) -# error "Too large font or glyphs in ${this.font_name.toUpperCase()}. Enable LV_FONT_FMT_TXT_LARGE in lv_conf.h") -#endif -`.trimLeft(); - } - - toLVGL() { - let guard_name = this.font_name.toUpperCase(); - - return `/******************************************************************************* - * Size: ${this.src.size} px - * Bpp: ${this.opts.bpp} - * Opts: ${process.argv.slice(2).join(' ')} - ******************************************************************************/ - -#ifdef LV_LVGL_H_INCLUDE_SIMPLE -#include "lvgl.h" -#else -#include "${this.opts.lv_include || 'lvgl/lvgl.h'}" -#endif - -#ifndef ${guard_name} -#define ${guard_name} 1 -#endif - -#if ${guard_name} - -${this.glyf.toLVGL()} - -${this.cmap.toLVGL()} - -${this.kern.toLVGL()} - -${this.head.toLVGL()} - -${this.large_format_guard()} - -#endif /*#if ${guard_name}*/ - -`; - } -} - - -module.exports = LvFont; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_cmap.js b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_cmap.js deleted file mode 100644 index 56c1e6aa..00000000 --- a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_cmap.js +++ /dev/null @@ -1,125 +0,0 @@ -'use strict'; - - -const u = require('../../utils'); -const build_subtables = require('../../font/cmap_build_subtables'); -const Cmap = require('../../font/table_cmap'); - - -class LvCmap extends Cmap { - constructor(font) { - super(font); - - this.lv_compiled = false; - this.lv_subtables = []; - } - - lv_format2enum(name) { - switch (name) { - case 'format0_tiny': return 'LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY'; - case 'format0': return 'LV_FONT_FMT_TXT_CMAP_FORMAT0_FULL'; - case 'sparse_tiny': return 'LV_FONT_FMT_TXT_CMAP_SPARSE_TINY'; - case 'sparse': return 'LV_FONT_FMT_TXT_CMAP_SPARSE_FULL'; - default: throw new Error('Unknown subtable format'); - } - } - - lv_compile() { - if (this.lv_compiled) return; - this.lv_compiled = true; - - const f = this.font; - - let subtables_plan = build_subtables(f.src.glyphs.map(g => g.code)); - let idx = 0; - - for (let [ format, codepoints ] of subtables_plan) { - let g = this.glyphByCode(codepoints[0]); - let start_glyph_id = f.glyph_id[g.code]; - let min_code = codepoints[0]; - let max_code = codepoints[codepoints.length - 1]; - - let has_charcodes = false; - let has_ids = false; - let defs = ''; - let entries_count = 0; - - if (format === 'format0_tiny') { - // use default empty values - } else if (format === 'format0') { - has_ids = true; - let d = this.collect_format0_data(min_code, max_code, start_glyph_id); - entries_count = d.length; - - defs = ` -static const uint8_t glyph_id_ofs_list_${idx}[] = { -${u.long_dump(d)} -}; -`.trim(); - - } else if (format === 'sparse_tiny') { - has_charcodes = true; - let d = this.collect_sparse_data(codepoints, start_glyph_id); - entries_count = d.codes.length; - - defs = ` -static const uint16_t unicode_list_${idx}[] = { -${u.long_dump(d.codes, { hex: true })} -}; -`.trim(); - - } else { // assume format === 'sparse' - has_charcodes = true; - has_ids = true; - let d = this.collect_sparse_data(codepoints, start_glyph_id); - entries_count = d.codes.length; - - defs = ` -static const uint16_t unicode_list_${idx}[] = { -${u.long_dump(d.codes, { hex: true })} -}; -static const uint16_t glyph_id_ofs_list_${idx}[] = { -${u.long_dump(d.ids)} -}; -`.trim(); - } - - const u_list = has_charcodes ? `unicode_list_${idx}` : 'NULL'; - const id_list = has_ids ? `glyph_id_ofs_list_${idx}` : 'NULL'; - - /* eslint-disable max-len */ - const head = ` { - .range_start = ${min_code}, .range_length = ${max_code - min_code + 1}, .glyph_id_start = ${start_glyph_id}, - .unicode_list = ${u_list}, .glyph_id_ofs_list = ${id_list}, .list_length = ${entries_count}, .type = ${this.lv_format2enum(format)} - }`; - - this.lv_subtables.push({ - defs, - head - }); - - idx++; - } - } - - toLVGL() { - this.lv_compile(); - - return ` -/*--------------------- - * CHARACTER MAPPING - *--------------------*/ - -${this.lv_subtables.map(d => d.defs).filter(Boolean).join('\n\n')} - -/*Collect the unicode lists and glyph_id offsets*/ -static const lv_font_fmt_txt_cmap_t cmaps[] = -{ -${this.lv_subtables.map(d => d.head).join(',\n')} -}; - `.trim(); - } -} - - -module.exports = LvCmap; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_glyf.js b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_glyf.js deleted file mode 100644 index 3f14851f..00000000 --- a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_glyf.js +++ /dev/null @@ -1,121 +0,0 @@ -'use strict'; - - -const { BitStream } = require('bit-buffer'); -const u = require('../../utils'); -const Glyf = require('../../font/table_glyf'); - - -class LvGlyf extends Glyf { - constructor(font) { - super(font); - - this.lv_data = []; - this.lv_compiled = false; - } - - lv_bitmap(glyph) { - const buf = Buffer.alloc(100 + glyph.bbox.width * glyph.bbox.height * 4); - const bs = new BitStream(buf); - bs.bigEndian = true; - - const pixels = this.font.glyf.pixelsToBpp(glyph.pixels); - - this.font.glyf.storePixels(bs, pixels); - - const glyph_bitmap = Buffer.alloc(bs.byteIndex); - buf.copy(glyph_bitmap, 0, 0, bs.byteIndex); - - return glyph_bitmap; - } - - lv_compile() { - if (this.lv_compiled) return; - - this.lv_compiled = true; - - const f = this.font; - this.lv_data = []; - let offset = 0; - - f.src.glyphs.forEach(g => { - const id = f.glyph_id[g.code]; - const bin = this.lv_bitmap(g); - this.lv_data[id] = { - bin, - offset, - glyph: g - }; - offset += bin.length; - }); - } - - to_lv_bitmaps() { - this.lv_compile(); - - let result = []; - this.lv_data.forEach((d, idx) => { - if (idx === 0) return; - const code_hex = d.glyph.code.toString(16).toUpperCase(); - const code_str = JSON.stringify(String.fromCodePoint(d.glyph.code)); - - let txt = ` /* U+${code_hex.padStart(4, '0')} ${code_str} */ -${u.long_dump(d.bin, { hex: true })}`; - - if (idx < this.lv_data.length - 1) { - // skip comma for zero data - txt += d.bin.length ? ',\n\n' : '\n'; - } - - result.push(txt); - }); - - return result.join(''); - } - - to_lv_glyph_dsc() { - this.lv_compile(); - - /* eslint-disable max-len */ - - let result = [ ' {.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */' ]; - - this.lv_data.forEach(d => { - const idx = d.offset, - adv_w = Math.round(d.glyph.advanceWidth * 16), - h = d.glyph.bbox.height, - w = d.glyph.bbox.width, - x = d.glyph.bbox.x, - y = d.glyph.bbox.y; - result.push(` {.bitmap_index = ${idx}, .adv_w = ${adv_w}, .box_w = ${w}, .box_h = ${h}, .ofs_x = ${x}, .ofs_y = ${y}}`); - }); - - return result.join(',\n'); - } - - - toLVGL() { - return ` -/*----------------- - * BITMAPS - *----------------*/ - -/*Store the image of the glyphs*/ -static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = { -${this.to_lv_bitmaps()} -}; - - -/*--------------------- - * GLYPH DESCRIPTION - *--------------------*/ - -static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = { -${this.to_lv_glyph_dsc()} -}; -`.trim(); - } -} - - -module.exports = LvGlyf; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_head.js b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_head.js deleted file mode 100644 index f5c0173e..00000000 --- a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_head.js +++ /dev/null @@ -1,99 +0,0 @@ -'use strict'; - - -const Head = require('../../font/table_head'); - - -class LvHead extends Head { - constructor(font) { - super(font); - } - - kern_ref() { - const f = this.font; - - if (!f.hasKerning()) { - return { - scale: '0', - dsc: 'NULL', - classes: '0' - }; - } - - if (!f.kern.should_use_format3()) { - return { - scale: `${Math.round(f.kerningScale * 16)}`, - dsc: '&kern_pairs', - classes: '0' - }; - } - - return { - scale: `${Math.round(f.kerningScale * 16)}`, - dsc: '&kern_classes', - classes: '1' - }; - } - - toLVGL() { - const f = this.font; - const kern = this.kern_ref(); - const subpixels = (f.subpixels_mode === 0) ? 'LV_FONT_SUBPX_NONE' : - (f.subpixels_mode === 1) ? 'LV_FONT_SUBPX_HOR' : 'LV_FONT_SUBPX_VER'; - - return ` -/*-------------------- - * ALL CUSTOM DATA - *--------------------*/ - -#if LV_VERSION_CHECK(8, 0, 0) -/*Store all the custom data of the font*/ -static lv_font_fmt_txt_glyph_cache_t cache; -static const lv_font_fmt_txt_dsc_t font_dsc = { -#else -static lv_font_fmt_txt_dsc_t font_dsc = { -#endif - .glyph_bitmap = glyph_bitmap, - .glyph_dsc = glyph_dsc, - .cmaps = cmaps, - .kern_dsc = ${kern.dsc}, - .kern_scale = ${kern.scale}, - .cmap_num = ${f.cmap.toBin().readUInt32LE(8)}, - .bpp = ${f.opts.bpp}, - .kern_classes = ${kern.classes}, - .bitmap_format = ${f.glyf.getCompressionCode()}, -#if LV_VERSION_CHECK(8, 0, 0) - .cache = &cache -#endif -}; - - -/*----------------- - * PUBLIC FONT - *----------------*/ - -/*Initialize a public general font descriptor*/ -#if LV_VERSION_CHECK(8, 0, 0) -const lv_font_t ${f.font_name} = { -#else -lv_font_t ${f.font_name} = { -#endif - .get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/ - .get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/ - .line_height = ${f.src.ascent - f.src.descent}, /*The maximum line height required by the font*/ - .base_line = ${-f.src.descent}, /*Baseline measured from the bottom of the line*/ -#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0) - .subpx = ${subpixels}, -#endif -#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8 - .underline_position = ${f.src.underlinePosition}, - .underline_thickness = ${f.src.underlineThickness}, -#endif - .dsc = &font_dsc /*The custom font data. Will be accessed by \`get_glyph_bitmap/dsc\` */ -}; -`.trim(); - } -} - - -module.exports = LvHead; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_kern.js b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_kern.js deleted file mode 100644 index e50ba427..00000000 --- a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_kern.js +++ /dev/null @@ -1,121 +0,0 @@ -'use strict'; - - -const u = require('../../utils'); -const Kern = require('../../font/table_kern'); - - -class LvKern extends Kern { - constructor(font) { - super(font); - } - - to_lv_format0() { - const f = this.font; - let kern_pairs = this.collect_format0_data(); - - return ` -/*----------------- - * KERNING - *----------------*/ - - -/*Pair left and right glyphs for kerning*/ -static const ${f.glyphIdFormat ? 'uint16_t' : 'uint8_t'} kern_pair_glyph_ids[] = -{ -${kern_pairs.map(pair => ` ${pair[0]}, ${pair[1]}`).join(',\n')} -}; - -/* Kerning between the respective left and right glyphs - * 4.4 format which needs to scaled with \`kern_scale\`*/ -static const int8_t kern_pair_values[] = -{ -${u.long_dump(kern_pairs.map(pair => f.kernToFP(pair[2])))} -}; - -/*Collect the kern pair's data in one place*/ -static const lv_font_fmt_txt_kern_pair_t kern_pairs = -{ - .glyph_ids = kern_pair_glyph_ids, - .values = kern_pair_values, - .pair_cnt = ${kern_pairs.length}, - .glyph_ids_size = ${f.glyphIdFormat} -}; - - -`.trim(); - } - - to_lv_format3() { - const f = this.font; - const { - left_classes, - right_classes, - left_mapping, - right_mapping, - values - } = this.collect_format3_data(); - - return ` -/*----------------- - * KERNING - *----------------*/ - - -/*Map glyph_ids to kern left classes*/ -static const uint8_t kern_left_class_mapping[] = -{ -${u.long_dump(left_mapping)} -}; - -/*Map glyph_ids to kern right classes*/ -static const uint8_t kern_right_class_mapping[] = -{ -${u.long_dump(right_mapping)} -}; - -/*Kern values between classes*/ -static const int8_t kern_class_values[] = -{ -${u.long_dump(values.map(v => f.kernToFP(v)))} -}; - - -/*Collect the kern class' data in one place*/ -static const lv_font_fmt_txt_kern_classes_t kern_classes = -{ - .class_pair_values = kern_class_values, - .left_class_mapping = kern_left_class_mapping, - .right_class_mapping = kern_right_class_mapping, - .left_class_cnt = ${left_classes}, - .right_class_cnt = ${right_classes}, -}; - - -`.trim(); - } - - toLVGL() { - const f = this.font; - - if (!f.hasKerning()) return ''; - - /* eslint-disable no-console */ - - if (f.kern.should_use_format3()) { - if (f.kern.format3_forced) { - let diff = this.create_format3_data().length - this.create_format0_data().length; - console.log(`Forced faster kerning format (via classes). Size increase is ${diff} bytes.`); - } - return this.to_lv_format3(); - } - - if (this.font.opts.fast_kerning) { - console.log('Forced faster kerning format (via classes), but data exceeds it\'s limits. Continue use pairs.'); - } - return this.to_lv_format0(); - } -} - - -module.exports = LvKern; diff --git a/node_modules/lv_font_conv/lv_font_conv.js b/node_modules/lv_font_conv/lv_font_conv.js deleted file mode 100755 index f76cbde4..00000000 --- a/node_modules/lv_font_conv/lv_font_conv.js +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const AppError = require('./lib/app_error'); - -require('./lib/cli').run(process.argv.slice(2)).catch(err => { - /*eslint-disable no-console*/ - if (err instanceof AppError) { - // Try to beautify normal errors - console.error(err.message.trim()); - } else { - // Print crashes - console.error(err.stack); - } - process.exit(1); -}); diff --git a/node_modules/lv_font_conv/node_modules/argparse/CHANGELOG.md b/node_modules/lv_font_conv/node_modules/argparse/CHANGELOG.md deleted file mode 100644 index dc39ed69..00000000 --- a/node_modules/lv_font_conv/node_modules/argparse/CHANGELOG.md +++ /dev/null @@ -1,216 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - - -## [2.0.1] - 2020-08-29 -### Fixed -- Fix issue with `process.argv` when used with interpreters (`coffee`, `ts-node`, etc.), #150. - - -## [2.0.0] - 2020-08-14 -### Changed -- Full rewrite. Now port from python 3.9.0 & more precise following. - See [doc](./doc) for difference and migration info. -- node.js 10+ required -- Removed most of local docs in favour of original ones. - - -## [1.0.10] - 2018-02-15 -### Fixed -- Use .concat instead of + for arrays, #122. - - -## [1.0.9] - 2016-09-29 -### Changed -- Rerelease after 1.0.8 - deps cleanup. - - -## [1.0.8] - 2016-09-29 -### Changed -- Maintenance (deps bump, fix node 6.5+ tests, coverage report). - - -## [1.0.7] - 2016-03-17 -### Changed -- Teach `addArgument` to accept string arg names. #97, @tomxtobin. - - -## [1.0.6] - 2016-02-06 -### Changed -- Maintenance: moved to eslint & updated CS. - - -## [1.0.5] - 2016-02-05 -### Changed -- Removed lodash dependency to significantly reduce install size. - Thanks to @mourner. - - -## [1.0.4] - 2016-01-17 -### Changed -- Maintenance: lodash update to 4.0.0. - - -## [1.0.3] - 2015-10-27 -### Fixed -- Fix parse `=` in args: `--examplepath="C:\myfolder\env=x64"`. #84, @CatWithApple. - - -## [1.0.2] - 2015-03-22 -### Changed -- Relaxed lodash version dependency. - - -## [1.0.1] - 2015-02-20 -### Changed -- Changed dependencies to be compatible with ancient nodejs. - - -## [1.0.0] - 2015-02-19 -### Changed -- Maintenance release. -- Replaced `underscore` with `lodash`. -- Bumped version to 1.0.0 to better reflect semver meaning. -- HISTORY.md -> CHANGELOG.md - - -## [0.1.16] - 2013-12-01 -### Changed -- Maintenance release. Updated dependencies and docs. - - -## [0.1.15] - 2013-05-13 -### Fixed -- Fixed #55, @trebor89 - - -## [0.1.14] - 2013-05-12 -### Fixed -- Fixed #62, @maxtaco - - -## [0.1.13] - 2013-04-08 -### Changed -- Added `.npmignore` to reduce package size - - -## [0.1.12] - 2013-02-10 -### Fixed -- Fixed conflictHandler (#46), @hpaulj - - -## [0.1.11] - 2013-02-07 -### Added -- Added 70+ tests (ported from python), @hpaulj -- Added conflictHandler, @applepicke -- Added fromfilePrefixChar, @hpaulj - -### Fixed -- Multiple bugfixes, @hpaulj - - -## [0.1.10] - 2012-12-30 -### Added -- Added [mutual exclusion](http://docs.python.org/dev/library/argparse.html#mutual-exclusion) - support, thanks to @hpaulj - -### Fixed -- Fixed options check for `storeConst` & `appendConst` actions, thanks to @hpaulj - - -## [0.1.9] - 2012-12-27 -### Fixed -- Fixed option dest interferens with other options (issue #23), thanks to @hpaulj -- Fixed default value behavior with `*` positionals, thanks to @hpaulj -- Improve `getDefault()` behavior, thanks to @hpaulj -- Improve negative argument parsing, thanks to @hpaulj - - -## [0.1.8] - 2012-12-01 -### Fixed -- Fixed parser parents (issue #19), thanks to @hpaulj -- Fixed negative argument parse (issue #20), thanks to @hpaulj - - -## [0.1.7] - 2012-10-14 -### Fixed -- Fixed 'choices' argument parse (issue #16) -- Fixed stderr output (issue #15) - - -## [0.1.6] - 2012-09-09 -### Fixed -- Fixed check for conflict of options (thanks to @tomxtobin) - - -## [0.1.5] - 2012-09-03 -### Fixed -- Fix parser #setDefaults method (thanks to @tomxtobin) - - -## [0.1.4] - 2012-07-30 -### Fixed -- Fixed pseudo-argument support (thanks to @CGamesPlay) -- Fixed addHelp default (should be true), if not set (thanks to @benblank) - - -## [0.1.3] - 2012-06-27 -### Fixed -- Fixed formatter api name: Formatter -> HelpFormatter - - -## [0.1.2] - 2012-05-29 -### Fixed -- Removed excess whitespace in help -- Fixed error reporting, when parcer with subcommands - called with empty arguments - -### Added -- Added basic tests - - -## [0.1.1] - 2012-05-23 -### Fixed -- Fixed line wrapping in help formatter -- Added better error reporting on invalid arguments - - -## [0.1.0] - 2012-05-16 -### Added -- First release. - - -[2.0.1]: https://github.com/nodeca/argparse/compare/2.0.0...2.0.1 -[2.0.0]: https://github.com/nodeca/argparse/compare/1.0.10...2.0.0 -[1.0.10]: https://github.com/nodeca/argparse/compare/1.0.9...1.0.10 -[1.0.9]: https://github.com/nodeca/argparse/compare/1.0.8...1.0.9 -[1.0.8]: https://github.com/nodeca/argparse/compare/1.0.7...1.0.8 -[1.0.7]: https://github.com/nodeca/argparse/compare/1.0.6...1.0.7 -[1.0.6]: https://github.com/nodeca/argparse/compare/1.0.5...1.0.6 -[1.0.5]: https://github.com/nodeca/argparse/compare/1.0.4...1.0.5 -[1.0.4]: https://github.com/nodeca/argparse/compare/1.0.3...1.0.4 -[1.0.3]: https://github.com/nodeca/argparse/compare/1.0.2...1.0.3 -[1.0.2]: https://github.com/nodeca/argparse/compare/1.0.1...1.0.2 -[1.0.1]: https://github.com/nodeca/argparse/compare/1.0.0...1.0.1 -[1.0.0]: https://github.com/nodeca/argparse/compare/0.1.16...1.0.0 -[0.1.16]: https://github.com/nodeca/argparse/compare/0.1.15...0.1.16 -[0.1.15]: https://github.com/nodeca/argparse/compare/0.1.14...0.1.15 -[0.1.14]: https://github.com/nodeca/argparse/compare/0.1.13...0.1.14 -[0.1.13]: https://github.com/nodeca/argparse/compare/0.1.12...0.1.13 -[0.1.12]: https://github.com/nodeca/argparse/compare/0.1.11...0.1.12 -[0.1.11]: https://github.com/nodeca/argparse/compare/0.1.10...0.1.11 -[0.1.10]: https://github.com/nodeca/argparse/compare/0.1.9...0.1.10 -[0.1.9]: https://github.com/nodeca/argparse/compare/0.1.8...0.1.9 -[0.1.8]: https://github.com/nodeca/argparse/compare/0.1.7...0.1.8 -[0.1.7]: https://github.com/nodeca/argparse/compare/0.1.6...0.1.7 -[0.1.6]: https://github.com/nodeca/argparse/compare/0.1.5...0.1.6 -[0.1.5]: https://github.com/nodeca/argparse/compare/0.1.4...0.1.5 -[0.1.4]: https://github.com/nodeca/argparse/compare/0.1.3...0.1.4 -[0.1.3]: https://github.com/nodeca/argparse/compare/0.1.2...0.1.3 -[0.1.2]: https://github.com/nodeca/argparse/compare/0.1.1...0.1.2 -[0.1.1]: https://github.com/nodeca/argparse/compare/0.1.0...0.1.1 -[0.1.0]: https://github.com/nodeca/argparse/releases/tag/0.1.0 diff --git a/node_modules/lv_font_conv/node_modules/argparse/LICENSE b/node_modules/lv_font_conv/node_modules/argparse/LICENSE deleted file mode 100644 index 66a3ac80..00000000 --- a/node_modules/lv_font_conv/node_modules/argparse/LICENSE +++ /dev/null @@ -1,254 +0,0 @@ -A. HISTORY OF THE SOFTWARE -========================== - -Python was created in the early 1990s by Guido van Rossum at Stichting -Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands -as a successor of a language called ABC. Guido remains Python's -principal author, although it includes many contributions from others. - -In 1995, Guido continued his work on Python at the Corporation for -National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) -in Reston, Virginia where he released several versions of the -software. - -In May 2000, Guido and the Python core development team moved to -BeOpen.com to form the BeOpen PythonLabs team. In October of the same -year, the PythonLabs team moved to Digital Creations, which became -Zope Corporation. In 2001, the Python Software Foundation (PSF, see -https://www.python.org/psf/) was formed, a non-profit organization -created specifically to own Python-related Intellectual Property. -Zope Corporation was a sponsoring member of the PSF. - -All Python releases are Open Source (see http://www.opensource.org for -the Open Source Definition). Historically, most, but not all, Python -releases have also been GPL-compatible; the table below summarizes -the various releases. - - Release Derived Year Owner GPL- - from compatible? (1) - - 0.9.0 thru 1.2 1991-1995 CWI yes - 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes - 1.6 1.5.2 2000 CNRI no - 2.0 1.6 2000 BeOpen.com no - 1.6.1 1.6 2001 CNRI yes (2) - 2.1 2.0+1.6.1 2001 PSF no - 2.0.1 2.0+1.6.1 2001 PSF yes - 2.1.1 2.1+2.0.1 2001 PSF yes - 2.1.2 2.1.1 2002 PSF yes - 2.1.3 2.1.2 2002 PSF yes - 2.2 and above 2.1.1 2001-now PSF yes - -Footnotes: - -(1) GPL-compatible doesn't mean that we're distributing Python under - the GPL. All Python licenses, unlike the GPL, let you distribute - a modified version without making your changes open source. The - GPL-compatible licenses make it possible to combine Python with - other software that is released under the GPL; the others don't. - -(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, - because its license has a choice of law clause. According to - CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 - is "not incompatible" with the GPL. - -Thanks to the many outside volunteers who have worked under Guido's -direction to make these releases possible. - - -B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON -=============================================================== - -PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 --------------------------------------------- - -1. This LICENSE AGREEMENT is between the Python Software Foundation -("PSF"), and the Individual or Organization ("Licensee") accessing and -otherwise using this software ("Python") in source or binary form and -its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, PSF hereby -grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, -analyze, test, perform and/or display publicly, prepare derivative works, -distribute, and otherwise use Python alone or in any derivative version, -provided, however, that PSF's License Agreement and PSF's notice of copyright, -i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation; -All Rights Reserved" are retained in Python alone or in any derivative version -prepared by Licensee. - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python. - -4. PSF is making Python available to Licensee on an "AS IS" -basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any -relationship of agency, partnership, or joint venture between PSF and -Licensee. This License Agreement does not grant permission to use PSF -trademarks or trade name in a trademark sense to endorse or promote -products or services of Licensee, or any third party. - -8. By copying, installing or otherwise using Python, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - - -BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 -------------------------------------------- - -BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 - -1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an -office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the -Individual or Organization ("Licensee") accessing and otherwise using -this software in source or binary form and its associated -documentation ("the Software"). - -2. Subject to the terms and conditions of this BeOpen Python License -Agreement, BeOpen hereby grants Licensee a non-exclusive, -royalty-free, world-wide license to reproduce, analyze, test, perform -and/or display publicly, prepare derivative works, distribute, and -otherwise use the Software alone or in any derivative version, -provided, however, that the BeOpen Python License is retained in the -Software, alone or in any derivative version prepared by Licensee. - -3. BeOpen is making the Software available to Licensee on an "AS IS" -basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE -SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS -AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY -DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -5. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -6. This License Agreement shall be governed by and interpreted in all -respects by the law of the State of California, excluding conflict of -law provisions. Nothing in this License Agreement shall be deemed to -create any relationship of agency, partnership, or joint venture -between BeOpen and Licensee. This License Agreement does not grant -permission to use BeOpen trademarks or trade names in a trademark -sense to endorse or promote products or services of Licensee, or any -third party. As an exception, the "BeOpen Python" logos available at -http://www.pythonlabs.com/logos.html may be used according to the -permissions granted on that web page. - -7. By copying, installing or otherwise using the software, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - - -CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 ---------------------------------------- - -1. This LICENSE AGREEMENT is between the Corporation for National -Research Initiatives, having an office at 1895 Preston White Drive, -Reston, VA 20191 ("CNRI"), and the Individual or Organization -("Licensee") accessing and otherwise using Python 1.6.1 software in -source or binary form and its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, CNRI -hereby grants Licensee a nonexclusive, royalty-free, world-wide -license to reproduce, analyze, test, perform and/or display publicly, -prepare derivative works, distribute, and otherwise use Python 1.6.1 -alone or in any derivative version, provided, however, that CNRI's -License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) -1995-2001 Corporation for National Research Initiatives; All Rights -Reserved" are retained in Python 1.6.1 alone or in any derivative -version prepared by Licensee. Alternately, in lieu of CNRI's License -Agreement, Licensee may substitute the following text (omitting the -quotes): "Python 1.6.1 is made available subject to the terms and -conditions in CNRI's License Agreement. This Agreement together with -Python 1.6.1 may be located on the Internet using the following -unique, persistent identifier (known as a handle): 1895.22/1013. This -Agreement may also be obtained from a proxy server on the Internet -using the following URL: http://hdl.handle.net/1895.22/1013". - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python 1.6.1 or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python 1.6.1. - -4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" -basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. This License Agreement shall be governed by the federal -intellectual property law of the United States, including without -limitation the federal copyright law, and, to the extent such -U.S. federal law does not apply, by the law of the Commonwealth of -Virginia, excluding Virginia's conflict of law provisions. -Notwithstanding the foregoing, with regard to derivative works based -on Python 1.6.1 that incorporate non-separable material that was -previously distributed under the GNU General Public License (GPL), the -law of the Commonwealth of Virginia shall govern this License -Agreement only as to issues arising under or with respect to -Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this -License Agreement shall be deemed to create any relationship of -agency, partnership, or joint venture between CNRI and Licensee. This -License Agreement does not grant permission to use CNRI trademarks or -trade name in a trademark sense to endorse or promote products or -services of Licensee, or any third party. - -8. By clicking on the "ACCEPT" button where indicated, or by copying, -installing or otherwise using Python 1.6.1, Licensee agrees to be -bound by the terms and conditions of this License Agreement. - - ACCEPT - - -CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 --------------------------------------------------- - -Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, -The Netherlands. All rights reserved. - -Permission to use, copy, modify, and distribute this software and its -documentation for any purpose and without fee is hereby granted, -provided that the above copyright notice appear in all copies and that -both that copyright notice and this permission notice appear in -supporting documentation, and that the name of Stichting Mathematisch -Centrum or CWI not be used in advertising or publicity pertaining to -distribution of the software without specific, written prior -permission. - -STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO -THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE -FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT -OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/argparse/README.md b/node_modules/lv_font_conv/node_modules/argparse/README.md deleted file mode 100644 index 550b5c9b..00000000 --- a/node_modules/lv_font_conv/node_modules/argparse/README.md +++ /dev/null @@ -1,84 +0,0 @@ -argparse -======== - -[![Build Status](https://secure.travis-ci.org/nodeca/argparse.svg?branch=master)](http://travis-ci.org/nodeca/argparse) -[![NPM version](https://img.shields.io/npm/v/argparse.svg)](https://www.npmjs.org/package/argparse) - -CLI arguments parser for node.js, with [sub-commands](https://docs.python.org/3.9/library/argparse.html#sub-commands) support. Port of python's [argparse](http://docs.python.org/dev/library/argparse.html) (version [3.9.0](https://github.com/python/cpython/blob/v3.9.0rc1/Lib/argparse.py)). - -**Difference with original.** - -- JS has no keyword arguments support. - - Pass options instead: `new ArgumentParser({ description: 'example', add_help: true })`. -- JS has no python's types `int`, `float`, ... - - Use string-typed names: `.add_argument('-b', { type: 'int', help: 'help' })`. -- `%r` format specifier uses `require('util').inspect()`. - -More details in [doc](./doc). - - -Example -------- - -`test.js` file: - -```javascript -#!/usr/bin/env node -'use strict'; - -const { ArgumentParser } = require('argparse'); -const { version } = require('./package.json'); - -const parser = new ArgumentParser({ - description: 'Argparse example' -}); - -parser.add_argument('-v', '--version', { action: 'version', version }); -parser.add_argument('-f', '--foo', { help: 'foo bar' }); -parser.add_argument('-b', '--bar', { help: 'bar foo' }); -parser.add_argument('--baz', { help: 'baz bar' }); - -console.dir(parser.parse_args()); -``` - -Display help: - -``` -$ ./test.js -h -usage: test.js [-h] [-v] [-f FOO] [-b BAR] [--baz BAZ] - -Argparse example - -optional arguments: - -h, --help show this help message and exit - -v, --version show program's version number and exit - -f FOO, --foo FOO foo bar - -b BAR, --bar BAR bar foo - --baz BAZ baz bar -``` - -Parse arguments: - -``` -$ ./test.js -f=3 --bar=4 --baz 5 -{ foo: '3', bar: '4', baz: '5' } -``` - - -API docs --------- - -Since this is a port with minimal divergence, there's no separate documentation. -Use original one instead, with notes about difference. - -1. [Original doc](https://docs.python.org/3.9/library/argparse.html). -2. [Original tutorial](https://docs.python.org/3.9/howto/argparse.html). -3. [Difference with python](./doc). - - -argparse for enterprise ------------------------ - -Available as part of the Tidelift Subscription - -The maintainers of argparse and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-argparse?utm_source=npm-argparse&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/node_modules/lv_font_conv/node_modules/argparse/argparse.js b/node_modules/lv_font_conv/node_modules/argparse/argparse.js deleted file mode 100644 index 2b8c8c63..00000000 --- a/node_modules/lv_font_conv/node_modules/argparse/argparse.js +++ /dev/null @@ -1,3707 +0,0 @@ -// Port of python's argparse module, version 3.9.0: -// https://github.com/python/cpython/blob/v3.9.0rc1/Lib/argparse.py - -'use strict' - -// Copyright (C) 2010-2020 Python Software Foundation. -// Copyright (C) 2020 argparse.js authors - -/* - * Command-line parsing library - * - * This module is an optparse-inspired command-line parsing library that: - * - * - handles both optional and positional arguments - * - produces highly informative usage messages - * - supports parsers that dispatch to sub-parsers - * - * The following is a simple usage example that sums integers from the - * command-line and writes the result to a file:: - * - * parser = argparse.ArgumentParser( - * description='sum the integers at the command line') - * parser.add_argument( - * 'integers', metavar='int', nargs='+', type=int, - * help='an integer to be summed') - * parser.add_argument( - * '--log', default=sys.stdout, type=argparse.FileType('w'), - * help='the file where the sum should be written') - * args = parser.parse_args() - * args.log.write('%s' % sum(args.integers)) - * args.log.close() - * - * The module contains the following public classes: - * - * - ArgumentParser -- The main entry point for command-line parsing. As the - * example above shows, the add_argument() method is used to populate - * the parser with actions for optional and positional arguments. Then - * the parse_args() method is invoked to convert the args at the - * command-line into an object with attributes. - * - * - ArgumentError -- The exception raised by ArgumentParser objects when - * there are errors with the parser's actions. Errors raised while - * parsing the command-line are caught by ArgumentParser and emitted - * as command-line messages. - * - * - FileType -- A factory for defining types of files to be created. As the - * example above shows, instances of FileType are typically passed as - * the type= argument of add_argument() calls. - * - * - Action -- The base class for parser actions. Typically actions are - * selected by passing strings like 'store_true' or 'append_const' to - * the action= argument of add_argument(). However, for greater - * customization of ArgumentParser actions, subclasses of Action may - * be defined and passed as the action= argument. - * - * - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter, - * ArgumentDefaultsHelpFormatter -- Formatter classes which - * may be passed as the formatter_class= argument to the - * ArgumentParser constructor. HelpFormatter is the default, - * RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser - * not to change the formatting for help text, and - * ArgumentDefaultsHelpFormatter adds information about argument defaults - * to the help. - * - * All other classes in this module are considered implementation details. - * (Also note that HelpFormatter and RawDescriptionHelpFormatter are only - * considered public as object names -- the API of the formatter objects is - * still considered an implementation detail.) - */ - -const SUPPRESS = '==SUPPRESS==' - -const OPTIONAL = '?' -const ZERO_OR_MORE = '*' -const ONE_OR_MORE = '+' -const PARSER = 'A...' -const REMAINDER = '...' -const _UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args' - - -// ================================== -// Utility functions used for porting -// ================================== -const assert = require('assert') -const util = require('util') -const fs = require('fs') -const sub = require('./lib/sub') -const path = require('path') -const repr = util.inspect - -function get_argv() { - // omit first argument (which is assumed to be interpreter - `node`, `coffee`, `ts-node`, etc.) - return process.argv.slice(1) -} - -function get_terminal_size() { - return { - columns: +process.env.COLUMNS || process.stdout.columns || 80 - } -} - -function hasattr(object, name) { - return Object.prototype.hasOwnProperty.call(object, name) -} - -function getattr(object, name, value) { - return hasattr(object, name) ? object[name] : value -} - -function setattr(object, name, value) { - object[name] = value -} - -function setdefault(object, name, value) { - if (!hasattr(object, name)) object[name] = value - return object[name] -} - -function delattr(object, name) { - delete object[name] -} - -function range(from, to, step=1) { - // range(10) is equivalent to range(0, 10) - if (arguments.length === 1) [ to, from ] = [ from, 0 ] - if (typeof from !== 'number' || typeof to !== 'number' || typeof step !== 'number') { - throw new TypeError('argument cannot be interpreted as an integer') - } - if (step === 0) throw new TypeError('range() arg 3 must not be zero') - - let result = [] - if (step > 0) { - for (let i = from; i < to; i += step) result.push(i) - } else { - for (let i = from; i > to; i += step) result.push(i) - } - return result -} - -function splitlines(str, keepends = false) { - let result - if (!keepends) { - result = str.split(/\r\n|[\n\r\v\f\x1c\x1d\x1e\x85\u2028\u2029]/) - } else { - result = [] - let parts = str.split(/(\r\n|[\n\r\v\f\x1c\x1d\x1e\x85\u2028\u2029])/) - for (let i = 0; i < parts.length; i += 2) { - result.push(parts[i] + (i + 1 < parts.length ? parts[i + 1] : '')) - } - } - if (!result[result.length - 1]) result.pop() - return result -} - -function _string_lstrip(string, prefix_chars) { - let idx = 0 - while (idx < string.length && prefix_chars.includes(string[idx])) idx++ - return idx ? string.slice(idx) : string -} - -function _string_split(string, sep, maxsplit) { - let result = string.split(sep) - if (result.length > maxsplit) { - result = result.slice(0, maxsplit).concat([ result.slice(maxsplit).join(sep) ]) - } - return result -} - -function _array_equal(array1, array2) { - if (array1.length !== array2.length) return false - for (let i = 0; i < array1.length; i++) { - if (array1[i] !== array2[i]) return false - } - return true -} - -function _array_remove(array, item) { - let idx = array.indexOf(item) - if (idx === -1) throw new TypeError(sub('%r not in list', item)) - array.splice(idx, 1) -} - -// normalize choices to array; -// this isn't required in python because `in` and `map` operators work with anything, -// but in js dealing with multiple types here is too clunky -function _choices_to_array(choices) { - if (choices === undefined) { - return [] - } else if (Array.isArray(choices)) { - return choices - } else if (choices !== null && typeof choices[Symbol.iterator] === 'function') { - return Array.from(choices) - } else if (typeof choices === 'object' && choices !== null) { - return Object.keys(choices) - } else { - throw new Error(sub('invalid choices value: %r', choices)) - } -} - -// decorator that allows a class to be called without new -function _callable(cls) { - let result = { // object is needed for inferred class name - [cls.name]: function (...args) { - let this_class = new.target === result || !new.target - return Reflect.construct(cls, args, this_class ? cls : new.target) - } - } - result[cls.name].prototype = cls.prototype - // fix default tag for toString, e.g. [object Action] instead of [object Object] - cls.prototype[Symbol.toStringTag] = cls.name - return result[cls.name] -} - -function _alias(object, from, to) { - try { - let name = object.constructor.name - Object.defineProperty(object, from, { - value: util.deprecate(object[to], sub('%s.%s() is renamed to %s.%s()', - name, from, name, to)), - enumerable: false - }) - } catch {} -} - -// decorator that allows snake_case class methods to be called with camelCase and vice versa -function _camelcase_alias(_class) { - for (let name of Object.getOwnPropertyNames(_class.prototype)) { - let camelcase = name.replace(/\w_[a-z]/g, s => s[0] + s[2].toUpperCase()) - if (camelcase !== name) _alias(_class.prototype, camelcase, name) - } - return _class -} - -function _to_legacy_name(key) { - key = key.replace(/\w_[a-z]/g, s => s[0] + s[2].toUpperCase()) - if (key === 'default') key = 'defaultValue' - if (key === 'const') key = 'constant' - return key -} - -function _to_new_name(key) { - if (key === 'defaultValue') key = 'default' - if (key === 'constant') key = 'const' - key = key.replace(/[A-Z]/g, c => '_' + c.toLowerCase()) - return key -} - -// parse options -let no_default = Symbol('no_default_value') -function _parse_opts(args, descriptor) { - function get_name() { - let stack = new Error().stack.split('\n') - .map(x => x.match(/^ at (.*) \(.*\)$/)) - .filter(Boolean) - .map(m => m[1]) - .map(fn => fn.match(/[^ .]*$/)[0]) - - if (stack.length && stack[0] === get_name.name) stack.shift() - if (stack.length && stack[0] === _parse_opts.name) stack.shift() - return stack.length ? stack[0] : '' - } - - args = Array.from(args) - let kwargs = {} - let result = [] - let last_opt = args.length && args[args.length - 1] - - if (typeof last_opt === 'object' && last_opt !== null && !Array.isArray(last_opt) && - (!last_opt.constructor || last_opt.constructor.name === 'Object')) { - kwargs = Object.assign({}, args.pop()) - } - - // LEGACY (v1 compatibility): camelcase - let renames = [] - for (let key of Object.keys(descriptor)) { - let old_name = _to_legacy_name(key) - if (old_name !== key && (old_name in kwargs)) { - if (key in kwargs) { - // default and defaultValue specified at the same time, happens often in old tests - //throw new TypeError(sub('%s() got multiple values for argument %r', get_name(), key)) - } else { - kwargs[key] = kwargs[old_name] - } - renames.push([ old_name, key ]) - delete kwargs[old_name] - } - } - if (renames.length) { - let name = get_name() - deprecate('camelcase_' + name, sub('%s(): following options are renamed: %s', - name, renames.map(([ a, b ]) => sub('%r -> %r', a, b)))) - } - // end - - let missing_positionals = [] - let positional_count = args.length - - for (let [ key, def ] of Object.entries(descriptor)) { - if (key[0] === '*') { - if (key.length > 0 && key[1] === '*') { - // LEGACY (v1 compatibility): camelcase - let renames = [] - for (let key of Object.keys(kwargs)) { - let new_name = _to_new_name(key) - if (new_name !== key && (key in kwargs)) { - if (new_name in kwargs) { - // default and defaultValue specified at the same time, happens often in old tests - //throw new TypeError(sub('%s() got multiple values for argument %r', get_name(), new_name)) - } else { - kwargs[new_name] = kwargs[key] - } - renames.push([ key, new_name ]) - delete kwargs[key] - } - } - if (renames.length) { - let name = get_name() - deprecate('camelcase_' + name, sub('%s(): following options are renamed: %s', - name, renames.map(([ a, b ]) => sub('%r -> %r', a, b)))) - } - // end - result.push(kwargs) - kwargs = {} - } else { - result.push(args) - args = [] - } - } else if (key in kwargs && args.length > 0) { - throw new TypeError(sub('%s() got multiple values for argument %r', get_name(), key)) - } else if (key in kwargs) { - result.push(kwargs[key]) - delete kwargs[key] - } else if (args.length > 0) { - result.push(args.shift()) - } else if (def !== no_default) { - result.push(def) - } else { - missing_positionals.push(key) - } - } - - if (Object.keys(kwargs).length) { - throw new TypeError(sub('%s() got an unexpected keyword argument %r', - get_name(), Object.keys(kwargs)[0])) - } - - if (args.length) { - let from = Object.entries(descriptor).filter(([ k, v ]) => k[0] !== '*' && v !== no_default).length - let to = Object.entries(descriptor).filter(([ k ]) => k[0] !== '*').length - throw new TypeError(sub('%s() takes %s positional argument%s but %s %s given', - get_name(), - from === to ? sub('from %s to %s', from, to) : to, - from === to && to === 1 ? '' : 's', - positional_count, - positional_count === 1 ? 'was' : 'were')) - } - - if (missing_positionals.length) { - let strs = missing_positionals.map(repr) - if (strs.length > 1) strs[strs.length - 1] = 'and ' + strs[strs.length - 1] - let str_joined = strs.join(strs.length === 2 ? '' : ', ') - throw new TypeError(sub('%s() missing %i required positional argument%s: %s', - get_name(), strs.length, strs.length === 1 ? '' : 's', str_joined)) - } - - return result -} - -let _deprecations = {} -function deprecate(id, string) { - _deprecations[id] = _deprecations[id] || util.deprecate(() => {}, string) - _deprecations[id]() -} - - -// ============================= -// Utility functions and classes -// ============================= -function _AttributeHolder(cls = Object) { - /* - * Abstract base class that provides __repr__. - * - * The __repr__ method returns a string in the format:: - * ClassName(attr=name, attr=name, ...) - * The attributes are determined either by a class-level attribute, - * '_kwarg_names', or by inspecting the instance __dict__. - */ - - return class _AttributeHolder extends cls { - [util.inspect.custom]() { - let type_name = this.constructor.name - let arg_strings = [] - let star_args = {} - for (let arg of this._get_args()) { - arg_strings.push(repr(arg)) - } - for (let [ name, value ] of this._get_kwargs()) { - if (/^[a-z_][a-z0-9_$]*$/i.test(name)) { - arg_strings.push(sub('%s=%r', name, value)) - } else { - star_args[name] = value - } - } - if (Object.keys(star_args).length) { - arg_strings.push(sub('**%s', repr(star_args))) - } - return sub('%s(%s)', type_name, arg_strings.join(', ')) - } - - toString() { - return this[util.inspect.custom]() - } - - _get_kwargs() { - return Object.entries(this) - } - - _get_args() { - return [] - } - } -} - - -function _copy_items(items) { - if (items === undefined) { - return [] - } - return items.slice(0) -} - - -// =============== -// Formatting Help -// =============== -const HelpFormatter = _camelcase_alias(_callable(class HelpFormatter { - /* - * Formatter for generating usage messages and argument help strings. - * - * Only the name of this class is considered a public API. All the methods - * provided by the class are considered an implementation detail. - */ - - constructor() { - let [ - prog, - indent_increment, - max_help_position, - width - ] = _parse_opts(arguments, { - prog: no_default, - indent_increment: 2, - max_help_position: 24, - width: undefined - }) - - // default setting for width - if (width === undefined) { - width = get_terminal_size().columns - width -= 2 - } - - this._prog = prog - this._indent_increment = indent_increment - this._max_help_position = Math.min(max_help_position, - Math.max(width - 20, indent_increment * 2)) - this._width = width - - this._current_indent = 0 - this._level = 0 - this._action_max_length = 0 - - this._root_section = this._Section(this, undefined) - this._current_section = this._root_section - - this._whitespace_matcher = /[ \t\n\r\f\v]+/g // equivalent to python /\s+/ with ASCII flag - this._long_break_matcher = /\n\n\n+/g - } - - // =============================== - // Section and indentation methods - // =============================== - _indent() { - this._current_indent += this._indent_increment - this._level += 1 - } - - _dedent() { - this._current_indent -= this._indent_increment - assert(this._current_indent >= 0, 'Indent decreased below 0.') - this._level -= 1 - } - - _add_item(func, args) { - this._current_section.items.push([ func, args ]) - } - - // ======================== - // Message building methods - // ======================== - start_section(heading) { - this._indent() - let section = this._Section(this, this._current_section, heading) - this._add_item(section.format_help.bind(section), []) - this._current_section = section - } - - end_section() { - this._current_section = this._current_section.parent - this._dedent() - } - - add_text(text) { - if (text !== SUPPRESS && text !== undefined) { - this._add_item(this._format_text.bind(this), [text]) - } - } - - add_usage(usage, actions, groups, prefix = undefined) { - if (usage !== SUPPRESS) { - let args = [ usage, actions, groups, prefix ] - this._add_item(this._format_usage.bind(this), args) - } - } - - add_argument(action) { - if (action.help !== SUPPRESS) { - - // find all invocations - let invocations = [this._format_action_invocation(action)] - for (let subaction of this._iter_indented_subactions(action)) { - invocations.push(this._format_action_invocation(subaction)) - } - - // update the maximum item length - let invocation_length = Math.max(...invocations.map(invocation => invocation.length)) - let action_length = invocation_length + this._current_indent - this._action_max_length = Math.max(this._action_max_length, - action_length) - - // add the item to the list - this._add_item(this._format_action.bind(this), [action]) - } - } - - add_arguments(actions) { - for (let action of actions) { - this.add_argument(action) - } - } - - // ======================= - // Help-formatting methods - // ======================= - format_help() { - let help = this._root_section.format_help() - if (help) { - help = help.replace(this._long_break_matcher, '\n\n') - help = help.replace(/^\n+|\n+$/g, '') + '\n' - } - return help - } - - _join_parts(part_strings) { - return part_strings.filter(part => part && part !== SUPPRESS).join('') - } - - _format_usage(usage, actions, groups, prefix) { - if (prefix === undefined) { - prefix = 'usage: ' - } - - // if usage is specified, use that - if (usage !== undefined) { - usage = sub(usage, { prog: this._prog }) - - // if no optionals or positionals are available, usage is just prog - } else if (usage === undefined && !actions.length) { - usage = sub('%(prog)s', { prog: this._prog }) - - // if optionals and positionals are available, calculate usage - } else if (usage === undefined) { - let prog = sub('%(prog)s', { prog: this._prog }) - - // split optionals from positionals - let optionals = [] - let positionals = [] - for (let action of actions) { - if (action.option_strings.length) { - optionals.push(action) - } else { - positionals.push(action) - } - } - - // build full usage string - let action_usage = this._format_actions_usage([].concat(optionals).concat(positionals), groups) - usage = [ prog, action_usage ].map(String).join(' ') - - // wrap the usage parts if it's too long - let text_width = this._width - this._current_indent - if (prefix.length + usage.length > text_width) { - - // break usage into wrappable parts - let part_regexp = /\(.*?\)+(?=\s|$)|\[.*?\]+(?=\s|$)|\S+/g - let opt_usage = this._format_actions_usage(optionals, groups) - let pos_usage = this._format_actions_usage(positionals, groups) - let opt_parts = opt_usage.match(part_regexp) || [] - let pos_parts = pos_usage.match(part_regexp) || [] - assert(opt_parts.join(' ') === opt_usage) - assert(pos_parts.join(' ') === pos_usage) - - // helper for wrapping lines - let get_lines = (parts, indent, prefix = undefined) => { - let lines = [] - let line = [] - let line_len - if (prefix !== undefined) { - line_len = prefix.length - 1 - } else { - line_len = indent.length - 1 - } - for (let part of parts) { - if (line_len + 1 + part.length > text_width && line) { - lines.push(indent + line.join(' ')) - line = [] - line_len = indent.length - 1 - } - line.push(part) - line_len += part.length + 1 - } - if (line.length) { - lines.push(indent + line.join(' ')) - } - if (prefix !== undefined) { - lines[0] = lines[0].slice(indent.length) - } - return lines - } - - let lines - - // if prog is short, follow it with optionals or positionals - if (prefix.length + prog.length <= 0.75 * text_width) { - let indent = ' '.repeat(prefix.length + prog.length + 1) - if (opt_parts.length) { - lines = get_lines([prog].concat(opt_parts), indent, prefix) - lines = lines.concat(get_lines(pos_parts, indent)) - } else if (pos_parts.length) { - lines = get_lines([prog].concat(pos_parts), indent, prefix) - } else { - lines = [prog] - } - - // if prog is long, put it on its own line - } else { - let indent = ' '.repeat(prefix.length) - let parts = [].concat(opt_parts).concat(pos_parts) - lines = get_lines(parts, indent) - if (lines.length > 1) { - lines = [] - lines = lines.concat(get_lines(opt_parts, indent)) - lines = lines.concat(get_lines(pos_parts, indent)) - } - lines = [prog].concat(lines) - } - - // join lines into usage - usage = lines.join('\n') - } - } - - // prefix with 'usage:' - return sub('%s%s\n\n', prefix, usage) - } - - _format_actions_usage(actions, groups) { - // find group indices and identify actions in groups - let group_actions = new Set() - let inserts = {} - for (let group of groups) { - let start = actions.indexOf(group._group_actions[0]) - if (start === -1) { - continue - } else { - let end = start + group._group_actions.length - if (_array_equal(actions.slice(start, end), group._group_actions)) { - for (let action of group._group_actions) { - group_actions.add(action) - } - if (!group.required) { - if (start in inserts) { - inserts[start] += ' [' - } else { - inserts[start] = '[' - } - if (end in inserts) { - inserts[end] += ']' - } else { - inserts[end] = ']' - } - } else { - if (start in inserts) { - inserts[start] += ' (' - } else { - inserts[start] = '(' - } - if (end in inserts) { - inserts[end] += ')' - } else { - inserts[end] = ')' - } - } - for (let i of range(start + 1, end)) { - inserts[i] = '|' - } - } - } - } - - // collect all actions format strings - let parts = [] - for (let [ i, action ] of Object.entries(actions)) { - - // suppressed arguments are marked with None - // remove | separators for suppressed arguments - if (action.help === SUPPRESS) { - parts.push(undefined) - if (inserts[+i] === '|') { - delete inserts[+i] - } else if (inserts[+i + 1] === '|') { - delete inserts[+i + 1] - } - - // produce all arg strings - } else if (!action.option_strings.length) { - let default_value = this._get_default_metavar_for_positional(action) - let part = this._format_args(action, default_value) - - // if it's in a group, strip the outer [] - if (group_actions.has(action)) { - if (part[0] === '[' && part[part.length - 1] === ']') { - part = part.slice(1, -1) - } - } - - // add the action string to the list - parts.push(part) - - // produce the first way to invoke the option in brackets - } else { - let option_string = action.option_strings[0] - let part - - // if the Optional doesn't take a value, format is: - // -s or --long - if (action.nargs === 0) { - part = action.format_usage() - - // if the Optional takes a value, format is: - // -s ARGS or --long ARGS - } else { - let default_value = this._get_default_metavar_for_optional(action) - let args_string = this._format_args(action, default_value) - part = sub('%s %s', option_string, args_string) - } - - // make it look optional if it's not required or in a group - if (!action.required && !group_actions.has(action)) { - part = sub('[%s]', part) - } - - // add the action string to the list - parts.push(part) - } - } - - // insert things at the necessary indices - for (let i of Object.keys(inserts).map(Number).sort((a, b) => b - a)) { - parts.splice(+i, 0, inserts[+i]) - } - - // join all the action items with spaces - let text = parts.filter(Boolean).join(' ') - - // clean up separators for mutually exclusive groups - text = text.replace(/([\[(]) /g, '$1') - text = text.replace(/ ([\])])/g, '$1') - text = text.replace(/[\[(] *[\])]/g, '') - text = text.replace(/\(([^|]*)\)/g, '$1', text) - text = text.trim() - - // return the text - return text - } - - _format_text(text) { - if (text.includes('%(prog)')) { - text = sub(text, { prog: this._prog }) - } - let text_width = Math.max(this._width - this._current_indent, 11) - let indent = ' '.repeat(this._current_indent) - return this._fill_text(text, text_width, indent) + '\n\n' - } - - _format_action(action) { - // determine the required width and the entry label - let help_position = Math.min(this._action_max_length + 2, - this._max_help_position) - let help_width = Math.max(this._width - help_position, 11) - let action_width = help_position - this._current_indent - 2 - let action_header = this._format_action_invocation(action) - let indent_first - - // no help; start on same line and add a final newline - if (!action.help) { - let tup = [ this._current_indent, '', action_header ] - action_header = sub('%*s%s\n', ...tup) - - // short action name; start on the same line and pad two spaces - } else if (action_header.length <= action_width) { - let tup = [ this._current_indent, '', action_width, action_header ] - action_header = sub('%*s%-*s ', ...tup) - indent_first = 0 - - // long action name; start on the next line - } else { - let tup = [ this._current_indent, '', action_header ] - action_header = sub('%*s%s\n', ...tup) - indent_first = help_position - } - - // collect the pieces of the action help - let parts = [action_header] - - // if there was help for the action, add lines of help text - if (action.help) { - let help_text = this._expand_help(action) - let help_lines = this._split_lines(help_text, help_width) - parts.push(sub('%*s%s\n', indent_first, '', help_lines[0])) - for (let line of help_lines.slice(1)) { - parts.push(sub('%*s%s\n', help_position, '', line)) - } - - // or add a newline if the description doesn't end with one - } else if (!action_header.endsWith('\n')) { - parts.push('\n') - } - - // if there are any sub-actions, add their help as well - for (let subaction of this._iter_indented_subactions(action)) { - parts.push(this._format_action(subaction)) - } - - // return a single string - return this._join_parts(parts) - } - - _format_action_invocation(action) { - if (!action.option_strings.length) { - let default_value = this._get_default_metavar_for_positional(action) - let metavar = this._metavar_formatter(action, default_value)(1)[0] - return metavar - - } else { - let parts = [] - - // if the Optional doesn't take a value, format is: - // -s, --long - if (action.nargs === 0) { - parts = parts.concat(action.option_strings) - - // if the Optional takes a value, format is: - // -s ARGS, --long ARGS - } else { - let default_value = this._get_default_metavar_for_optional(action) - let args_string = this._format_args(action, default_value) - for (let option_string of action.option_strings) { - parts.push(sub('%s %s', option_string, args_string)) - } - } - - return parts.join(', ') - } - } - - _metavar_formatter(action, default_metavar) { - let result - if (action.metavar !== undefined) { - result = action.metavar - } else if (action.choices !== undefined) { - let choice_strs = _choices_to_array(action.choices).map(String) - result = sub('{%s}', choice_strs.join(',')) - } else { - result = default_metavar - } - - function format(tuple_size) { - if (Array.isArray(result)) { - return result - } else { - return Array(tuple_size).fill(result) - } - } - return format - } - - _format_args(action, default_metavar) { - let get_metavar = this._metavar_formatter(action, default_metavar) - let result - if (action.nargs === undefined) { - result = sub('%s', ...get_metavar(1)) - } else if (action.nargs === OPTIONAL) { - result = sub('[%s]', ...get_metavar(1)) - } else if (action.nargs === ZERO_OR_MORE) { - let metavar = get_metavar(1) - if (metavar.length === 2) { - result = sub('[%s [%s ...]]', ...metavar) - } else { - result = sub('[%s ...]', ...metavar) - } - } else if (action.nargs === ONE_OR_MORE) { - result = sub('%s [%s ...]', ...get_metavar(2)) - } else if (action.nargs === REMAINDER) { - result = '...' - } else if (action.nargs === PARSER) { - result = sub('%s ...', ...get_metavar(1)) - } else if (action.nargs === SUPPRESS) { - result = '' - } else { - let formats - try { - formats = range(action.nargs).map(() => '%s') - } catch (err) { - throw new TypeError('invalid nargs value') - } - result = sub(formats.join(' '), ...get_metavar(action.nargs)) - } - return result - } - - _expand_help(action) { - let params = Object.assign({ prog: this._prog }, action) - for (let name of Object.keys(params)) { - if (params[name] === SUPPRESS) { - delete params[name] - } - } - for (let name of Object.keys(params)) { - if (params[name] && params[name].name) { - params[name] = params[name].name - } - } - if (params.choices !== undefined) { - let choices_str = _choices_to_array(params.choices).map(String).join(', ') - params.choices = choices_str - } - // LEGACY (v1 compatibility): camelcase - for (let key of Object.keys(params)) { - let old_name = _to_legacy_name(key) - if (old_name !== key) { - params[old_name] = params[key] - } - } - // end - return sub(this._get_help_string(action), params) - } - - * _iter_indented_subactions(action) { - if (typeof action._get_subactions === 'function') { - this._indent() - yield* action._get_subactions() - this._dedent() - } - } - - _split_lines(text, width) { - text = text.replace(this._whitespace_matcher, ' ').trim() - // The textwrap module is used only for formatting help. - // Delay its import for speeding up the common usage of argparse. - let textwrap = require('./lib/textwrap') - return textwrap.wrap(text, { width }) - } - - _fill_text(text, width, indent) { - text = text.replace(this._whitespace_matcher, ' ').trim() - let textwrap = require('./lib/textwrap') - return textwrap.fill(text, { width, - initial_indent: indent, - subsequent_indent: indent }) - } - - _get_help_string(action) { - return action.help - } - - _get_default_metavar_for_optional(action) { - return action.dest.toUpperCase() - } - - _get_default_metavar_for_positional(action) { - return action.dest - } -})) - -HelpFormatter.prototype._Section = _callable(class _Section { - - constructor(formatter, parent, heading = undefined) { - this.formatter = formatter - this.parent = parent - this.heading = heading - this.items = [] - } - - format_help() { - // format the indented section - if (this.parent !== undefined) { - this.formatter._indent() - } - let item_help = this.formatter._join_parts(this.items.map(([ func, args ]) => func.apply(null, args))) - if (this.parent !== undefined) { - this.formatter._dedent() - } - - // return nothing if the section was empty - if (!item_help) { - return '' - } - - // add the heading if the section was non-empty - let heading - if (this.heading !== SUPPRESS && this.heading !== undefined) { - let current_indent = this.formatter._current_indent - heading = sub('%*s%s:\n', current_indent, '', this.heading) - } else { - heading = '' - } - - // join the section-initial newline, the heading and the help - return this.formatter._join_parts(['\n', heading, item_help, '\n']) - } -}) - - -const RawDescriptionHelpFormatter = _camelcase_alias(_callable(class RawDescriptionHelpFormatter extends HelpFormatter { - /* - * Help message formatter which retains any formatting in descriptions. - * - * Only the name of this class is considered a public API. All the methods - * provided by the class are considered an implementation detail. - */ - - _fill_text(text, width, indent) { - return splitlines(text, true).map(line => indent + line).join('') - } -})) - - -const RawTextHelpFormatter = _camelcase_alias(_callable(class RawTextHelpFormatter extends RawDescriptionHelpFormatter { - /* - * Help message formatter which retains formatting of all help text. - * - * Only the name of this class is considered a public API. All the methods - * provided by the class are considered an implementation detail. - */ - - _split_lines(text/*, width*/) { - return splitlines(text) - } -})) - - -const ArgumentDefaultsHelpFormatter = _camelcase_alias(_callable(class ArgumentDefaultsHelpFormatter extends HelpFormatter { - /* - * Help message formatter which adds default values to argument help. - * - * Only the name of this class is considered a public API. All the methods - * provided by the class are considered an implementation detail. - */ - - _get_help_string(action) { - let help = action.help - // LEGACY (v1 compatibility): additional check for defaultValue needed - if (!action.help.includes('%(default)') && !action.help.includes('%(defaultValue)')) { - if (action.default !== SUPPRESS) { - let defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] - if (action.option_strings.length || defaulting_nargs.includes(action.nargs)) { - help += ' (default: %(default)s)' - } - } - } - return help - } -})) - - -const MetavarTypeHelpFormatter = _camelcase_alias(_callable(class MetavarTypeHelpFormatter extends HelpFormatter { - /* - * Help message formatter which uses the argument 'type' as the default - * metavar value (instead of the argument 'dest') - * - * Only the name of this class is considered a public API. All the methods - * provided by the class are considered an implementation detail. - */ - - _get_default_metavar_for_optional(action) { - return typeof action.type === 'function' ? action.type.name : action.type - } - - _get_default_metavar_for_positional(action) { - return typeof action.type === 'function' ? action.type.name : action.type - } -})) - - -// ===================== -// Options and Arguments -// ===================== -function _get_action_name(argument) { - if (argument === undefined) { - return undefined - } else if (argument.option_strings.length) { - return argument.option_strings.join('/') - } else if (![ undefined, SUPPRESS ].includes(argument.metavar)) { - return argument.metavar - } else if (![ undefined, SUPPRESS ].includes(argument.dest)) { - return argument.dest - } else { - return undefined - } -} - - -const ArgumentError = _callable(class ArgumentError extends Error { - /* - * An error from creating or using an argument (optional or positional). - * - * The string value of this exception is the message, augmented with - * information about the argument that caused it. - */ - - constructor(argument, message) { - super() - this.name = 'ArgumentError' - this._argument_name = _get_action_name(argument) - this._message = message - this.message = this.str() - } - - str() { - let format - if (this._argument_name === undefined) { - format = '%(message)s' - } else { - format = 'argument %(argument_name)s: %(message)s' - } - return sub(format, { message: this._message, - argument_name: this._argument_name }) - } -}) - - -const ArgumentTypeError = _callable(class ArgumentTypeError extends Error { - /* - * An error from trying to convert a command line string to a type. - */ - - constructor(message) { - super(message) - this.name = 'ArgumentTypeError' - } -}) - - -// ============== -// Action classes -// ============== -const Action = _camelcase_alias(_callable(class Action extends _AttributeHolder(Function) { - /* - * Information about how to convert command line strings to Python objects. - * - * Action objects are used by an ArgumentParser to represent the information - * needed to parse a single argument from one or more strings from the - * command line. The keyword arguments to the Action constructor are also - * all attributes of Action instances. - * - * Keyword Arguments: - * - * - option_strings -- A list of command-line option strings which - * should be associated with this action. - * - * - dest -- The name of the attribute to hold the created object(s) - * - * - nargs -- The number of command-line arguments that should be - * consumed. By default, one argument will be consumed and a single - * value will be produced. Other values include: - * - N (an integer) consumes N arguments (and produces a list) - * - '?' consumes zero or one arguments - * - '*' consumes zero or more arguments (and produces a list) - * - '+' consumes one or more arguments (and produces a list) - * Note that the difference between the default and nargs=1 is that - * with the default, a single value will be produced, while with - * nargs=1, a list containing a single value will be produced. - * - * - const -- The value to be produced if the option is specified and the - * option uses an action that takes no values. - * - * - default -- The value to be produced if the option is not specified. - * - * - type -- A callable that accepts a single string argument, and - * returns the converted value. The standard Python types str, int, - * float, and complex are useful examples of such callables. If None, - * str is used. - * - * - choices -- A container of values that should be allowed. If not None, - * after a command-line argument has been converted to the appropriate - * type, an exception will be raised if it is not a member of this - * collection. - * - * - required -- True if the action must always be specified at the - * command line. This is only meaningful for optional command-line - * arguments. - * - * - help -- The help string describing the argument. - * - * - metavar -- The name to be used for the option's argument with the - * help string. If None, the 'dest' value will be used as the name. - */ - - constructor() { - let [ - option_strings, - dest, - nargs, - const_value, - default_value, - type, - choices, - required, - help, - metavar - ] = _parse_opts(arguments, { - option_strings: no_default, - dest: no_default, - nargs: undefined, - const: undefined, - default: undefined, - type: undefined, - choices: undefined, - required: false, - help: undefined, - metavar: undefined - }) - - // when this class is called as a function, redirect it to .call() method of itself - super('return arguments.callee.call.apply(arguments.callee, arguments)') - - this.option_strings = option_strings - this.dest = dest - this.nargs = nargs - this.const = const_value - this.default = default_value - this.type = type - this.choices = choices - this.required = required - this.help = help - this.metavar = metavar - } - - _get_kwargs() { - let names = [ - 'option_strings', - 'dest', - 'nargs', - 'const', - 'default', - 'type', - 'choices', - 'help', - 'metavar' - ] - return names.map(name => [ name, getattr(this, name) ]) - } - - format_usage() { - return this.option_strings[0] - } - - call(/*parser, namespace, values, option_string = undefined*/) { - throw new Error('.call() not defined') - } -})) - - -const BooleanOptionalAction = _camelcase_alias(_callable(class BooleanOptionalAction extends Action { - - constructor() { - let [ - option_strings, - dest, - default_value, - type, - choices, - required, - help, - metavar - ] = _parse_opts(arguments, { - option_strings: no_default, - dest: no_default, - default: undefined, - type: undefined, - choices: undefined, - required: false, - help: undefined, - metavar: undefined - }) - - let _option_strings = [] - for (let option_string of option_strings) { - _option_strings.push(option_string) - - if (option_string.startsWith('--')) { - option_string = '--no-' + option_string.slice(2) - _option_strings.push(option_string) - } - } - - if (help !== undefined && default_value !== undefined) { - help += ` (default: ${default_value})` - } - - super({ - option_strings: _option_strings, - dest, - nargs: 0, - default: default_value, - type, - choices, - required, - help, - metavar - }) - } - - call(parser, namespace, values, option_string = undefined) { - if (this.option_strings.includes(option_string)) { - setattr(namespace, this.dest, !option_string.startsWith('--no-')) - } - } - - format_usage() { - return this.option_strings.join(' | ') - } -})) - - -const _StoreAction = _callable(class _StoreAction extends Action { - - constructor() { - let [ - option_strings, - dest, - nargs, - const_value, - default_value, - type, - choices, - required, - help, - metavar - ] = _parse_opts(arguments, { - option_strings: no_default, - dest: no_default, - nargs: undefined, - const: undefined, - default: undefined, - type: undefined, - choices: undefined, - required: false, - help: undefined, - metavar: undefined - }) - - if (nargs === 0) { - throw new TypeError('nargs for store actions must be != 0; if you ' + - 'have nothing to store, actions such as store ' + - 'true or store const may be more appropriate') - } - if (const_value !== undefined && nargs !== OPTIONAL) { - throw new TypeError(sub('nargs must be %r to supply const', OPTIONAL)) - } - super({ - option_strings, - dest, - nargs, - const: const_value, - default: default_value, - type, - choices, - required, - help, - metavar - }) - } - - call(parser, namespace, values/*, option_string = undefined*/) { - setattr(namespace, this.dest, values) - } -}) - - -const _StoreConstAction = _callable(class _StoreConstAction extends Action { - - constructor() { - let [ - option_strings, - dest, - const_value, - default_value, - required, - help - //, metavar - ] = _parse_opts(arguments, { - option_strings: no_default, - dest: no_default, - const: no_default, - default: undefined, - required: false, - help: undefined, - metavar: undefined - }) - - super({ - option_strings, - dest, - nargs: 0, - const: const_value, - default: default_value, - required, - help - }) - } - - call(parser, namespace/*, values, option_string = undefined*/) { - setattr(namespace, this.dest, this.const) - } -}) - - -const _StoreTrueAction = _callable(class _StoreTrueAction extends _StoreConstAction { - - constructor() { - let [ - option_strings, - dest, - default_value, - required, - help - ] = _parse_opts(arguments, { - option_strings: no_default, - dest: no_default, - default: false, - required: false, - help: undefined - }) - - super({ - option_strings, - dest, - const: true, - default: default_value, - required, - help - }) - } -}) - - -const _StoreFalseAction = _callable(class _StoreFalseAction extends _StoreConstAction { - - constructor() { - let [ - option_strings, - dest, - default_value, - required, - help - ] = _parse_opts(arguments, { - option_strings: no_default, - dest: no_default, - default: true, - required: false, - help: undefined - }) - - super({ - option_strings, - dest, - const: false, - default: default_value, - required, - help - }) - } -}) - - -const _AppendAction = _callable(class _AppendAction extends Action { - - constructor() { - let [ - option_strings, - dest, - nargs, - const_value, - default_value, - type, - choices, - required, - help, - metavar - ] = _parse_opts(arguments, { - option_strings: no_default, - dest: no_default, - nargs: undefined, - const: undefined, - default: undefined, - type: undefined, - choices: undefined, - required: false, - help: undefined, - metavar: undefined - }) - - if (nargs === 0) { - throw new TypeError('nargs for append actions must be != 0; if arg ' + - 'strings are not supplying the value to append, ' + - 'the append const action may be more appropriate') - } - if (const_value !== undefined && nargs !== OPTIONAL) { - throw new TypeError(sub('nargs must be %r to supply const', OPTIONAL)) - } - super({ - option_strings, - dest, - nargs, - const: const_value, - default: default_value, - type, - choices, - required, - help, - metavar - }) - } - - call(parser, namespace, values/*, option_string = undefined*/) { - let items = getattr(namespace, this.dest, undefined) - items = _copy_items(items) - items.push(values) - setattr(namespace, this.dest, items) - } -}) - - -const _AppendConstAction = _callable(class _AppendConstAction extends Action { - - constructor() { - let [ - option_strings, - dest, - const_value, - default_value, - required, - help, - metavar - ] = _parse_opts(arguments, { - option_strings: no_default, - dest: no_default, - const: no_default, - default: undefined, - required: false, - help: undefined, - metavar: undefined - }) - - super({ - option_strings, - dest, - nargs: 0, - const: const_value, - default: default_value, - required, - help, - metavar - }) - } - - call(parser, namespace/*, values, option_string = undefined*/) { - let items = getattr(namespace, this.dest, undefined) - items = _copy_items(items) - items.push(this.const) - setattr(namespace, this.dest, items) - } -}) - - -const _CountAction = _callable(class _CountAction extends Action { - - constructor() { - let [ - option_strings, - dest, - default_value, - required, - help - ] = _parse_opts(arguments, { - option_strings: no_default, - dest: no_default, - default: undefined, - required: false, - help: undefined - }) - - super({ - option_strings, - dest, - nargs: 0, - default: default_value, - required, - help - }) - } - - call(parser, namespace/*, values, option_string = undefined*/) { - let count = getattr(namespace, this.dest, undefined) - if (count === undefined) { - count = 0 - } - setattr(namespace, this.dest, count + 1) - } -}) - - -const _HelpAction = _callable(class _HelpAction extends Action { - - constructor() { - let [ - option_strings, - dest, - default_value, - help - ] = _parse_opts(arguments, { - option_strings: no_default, - dest: SUPPRESS, - default: SUPPRESS, - help: undefined - }) - - super({ - option_strings, - dest, - default: default_value, - nargs: 0, - help - }) - } - - call(parser/*, namespace, values, option_string = undefined*/) { - parser.print_help() - parser.exit() - } -}) - - -const _VersionAction = _callable(class _VersionAction extends Action { - - constructor() { - let [ - option_strings, - version, - dest, - default_value, - help - ] = _parse_opts(arguments, { - option_strings: no_default, - version: undefined, - dest: SUPPRESS, - default: SUPPRESS, - help: "show program's version number and exit" - }) - - super({ - option_strings, - dest, - default: default_value, - nargs: 0, - help - }) - this.version = version - } - - call(parser/*, namespace, values, option_string = undefined*/) { - let version = this.version - if (version === undefined) { - version = parser.version - } - let formatter = parser._get_formatter() - formatter.add_text(version) - parser._print_message(formatter.format_help(), process.stdout) - parser.exit() - } -}) - - -const _SubParsersAction = _camelcase_alias(_callable(class _SubParsersAction extends Action { - - constructor() { - let [ - option_strings, - prog, - parser_class, - dest, - required, - help, - metavar - ] = _parse_opts(arguments, { - option_strings: no_default, - prog: no_default, - parser_class: no_default, - dest: SUPPRESS, - required: false, - help: undefined, - metavar: undefined - }) - - let name_parser_map = {} - - super({ - option_strings, - dest, - nargs: PARSER, - choices: name_parser_map, - required, - help, - metavar - }) - - this._prog_prefix = prog - this._parser_class = parser_class - this._name_parser_map = name_parser_map - this._choices_actions = [] - } - - add_parser() { - let [ - name, - kwargs - ] = _parse_opts(arguments, { - name: no_default, - '**kwargs': no_default - }) - - // set prog from the existing prefix - if (kwargs.prog === undefined) { - kwargs.prog = sub('%s %s', this._prog_prefix, name) - } - - let aliases = getattr(kwargs, 'aliases', []) - delete kwargs.aliases - - // create a pseudo-action to hold the choice help - if ('help' in kwargs) { - let help = kwargs.help - delete kwargs.help - let choice_action = this._ChoicesPseudoAction(name, aliases, help) - this._choices_actions.push(choice_action) - } - - // create the parser and add it to the map - let parser = new this._parser_class(kwargs) - this._name_parser_map[name] = parser - - // make parser available under aliases also - for (let alias of aliases) { - this._name_parser_map[alias] = parser - } - - return parser - } - - _get_subactions() { - return this._choices_actions - } - - call(parser, namespace, values/*, option_string = undefined*/) { - let parser_name = values[0] - let arg_strings = values.slice(1) - - // set the parser name if requested - if (this.dest !== SUPPRESS) { - setattr(namespace, this.dest, parser_name) - } - - // select the parser - if (hasattr(this._name_parser_map, parser_name)) { - parser = this._name_parser_map[parser_name] - } else { - let args = {parser_name, - choices: this._name_parser_map.join(', ')} - let msg = sub('unknown parser %(parser_name)r (choices: %(choices)s)', args) - throw new ArgumentError(this, msg) - } - - // parse all the remaining options into the namespace - // store any unrecognized options on the object, so that the top - // level parser can decide what to do with them - - // In case this subparser defines new defaults, we parse them - // in a new namespace object and then update the original - // namespace for the relevant parts. - let subnamespace - [ subnamespace, arg_strings ] = parser.parse_known_args(arg_strings, undefined) - for (let [ key, value ] of Object.entries(subnamespace)) { - setattr(namespace, key, value) - } - - if (arg_strings.length) { - setdefault(namespace, _UNRECOGNIZED_ARGS_ATTR, []) - getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).push(...arg_strings) - } - } -})) - - -_SubParsersAction.prototype._ChoicesPseudoAction = _callable(class _ChoicesPseudoAction extends Action { - constructor(name, aliases, help) { - let metavar = name, dest = name - if (aliases.length) { - metavar += sub(' (%s)', aliases.join(', ')) - } - super({ option_strings: [], dest, help, metavar }) - } -}) - - -const _ExtendAction = _callable(class _ExtendAction extends _AppendAction { - call(parser, namespace, values/*, option_string = undefined*/) { - let items = getattr(namespace, this.dest, undefined) - items = _copy_items(items) - items = items.concat(values) - setattr(namespace, this.dest, items) - } -}) - - -// ============== -// Type classes -// ============== -const FileType = _callable(class FileType extends Function { - /* - * Factory for creating file object types - * - * Instances of FileType are typically passed as type= arguments to the - * ArgumentParser add_argument() method. - * - * Keyword Arguments: - * - mode -- A string indicating how the file is to be opened. Accepts the - * same values as the builtin open() function. - * - bufsize -- The file's desired buffer size. Accepts the same values as - * the builtin open() function. - * - encoding -- The file's encoding. Accepts the same values as the - * builtin open() function. - * - errors -- A string indicating how encoding and decoding errors are to - * be handled. Accepts the same value as the builtin open() function. - */ - - constructor() { - let [ - flags, - encoding, - mode, - autoClose, - emitClose, - start, - end, - highWaterMark, - fs - ] = _parse_opts(arguments, { - flags: 'r', - encoding: undefined, - mode: undefined, // 0o666 - autoClose: undefined, // true - emitClose: undefined, // false - start: undefined, // 0 - end: undefined, // Infinity - highWaterMark: undefined, // 64 * 1024 - fs: undefined - }) - - // when this class is called as a function, redirect it to .call() method of itself - super('return arguments.callee.call.apply(arguments.callee, arguments)') - - Object.defineProperty(this, 'name', { - get() { - return sub('FileType(%r)', flags) - } - }) - this._flags = flags - this._options = {} - if (encoding !== undefined) this._options.encoding = encoding - if (mode !== undefined) this._options.mode = mode - if (autoClose !== undefined) this._options.autoClose = autoClose - if (emitClose !== undefined) this._options.emitClose = emitClose - if (start !== undefined) this._options.start = start - if (end !== undefined) this._options.end = end - if (highWaterMark !== undefined) this._options.highWaterMark = highWaterMark - if (fs !== undefined) this._options.fs = fs - } - - call(string) { - // the special argument "-" means sys.std{in,out} - if (string === '-') { - if (this._flags.includes('r')) { - return process.stdin - } else if (this._flags.includes('w')) { - return process.stdout - } else { - let msg = sub('argument "-" with mode %r', this._flags) - throw new TypeError(msg) - } - } - - // all other arguments are used as file names - let fd - try { - fd = fs.openSync(string, this._flags, this._options.mode) - } catch (e) { - let args = { filename: string, error: e.message } - let message = "can't open '%(filename)s': %(error)s" - throw new ArgumentTypeError(sub(message, args)) - } - - let options = Object.assign({ fd, flags: this._flags }, this._options) - if (this._flags.includes('r')) { - return fs.createReadStream(undefined, options) - } else if (this._flags.includes('w')) { - return fs.createWriteStream(undefined, options) - } else { - let msg = sub('argument "%s" with mode %r', string, this._flags) - throw new TypeError(msg) - } - } - - [util.inspect.custom]() { - let args = [ this._flags ] - let kwargs = Object.entries(this._options).map(([ k, v ]) => { - if (k === 'mode') v = { value: v, [util.inspect.custom]() { return '0o' + this.value.toString(8) } } - return [ k, v ] - }) - let args_str = [] - .concat(args.filter(arg => arg !== -1).map(repr)) - .concat(kwargs.filter(([/*kw*/, arg]) => arg !== undefined) - .map(([kw, arg]) => sub('%s=%r', kw, arg))) - .join(', ') - return sub('%s(%s)', this.constructor.name, args_str) - } - - toString() { - return this[util.inspect.custom]() - } -}) - -// =========================== -// Optional and Positional Parsing -// =========================== -const Namespace = _callable(class Namespace extends _AttributeHolder() { - /* - * Simple object for storing attributes. - * - * Implements equality by attribute names and values, and provides a simple - * string representation. - */ - - constructor(options = {}) { - super() - Object.assign(this, options) - } -}) - -// unset string tag to mimic plain object -Namespace.prototype[Symbol.toStringTag] = undefined - - -const _ActionsContainer = _camelcase_alias(_callable(class _ActionsContainer { - - constructor() { - let [ - description, - prefix_chars, - argument_default, - conflict_handler - ] = _parse_opts(arguments, { - description: no_default, - prefix_chars: no_default, - argument_default: no_default, - conflict_handler: no_default - }) - - this.description = description - this.argument_default = argument_default - this.prefix_chars = prefix_chars - this.conflict_handler = conflict_handler - - // set up registries - this._registries = {} - - // register actions - this.register('action', undefined, _StoreAction) - this.register('action', 'store', _StoreAction) - this.register('action', 'store_const', _StoreConstAction) - this.register('action', 'store_true', _StoreTrueAction) - this.register('action', 'store_false', _StoreFalseAction) - this.register('action', 'append', _AppendAction) - this.register('action', 'append_const', _AppendConstAction) - this.register('action', 'count', _CountAction) - this.register('action', 'help', _HelpAction) - this.register('action', 'version', _VersionAction) - this.register('action', 'parsers', _SubParsersAction) - this.register('action', 'extend', _ExtendAction) - // LEGACY (v1 compatibility): camelcase variants - ;[ 'storeConst', 'storeTrue', 'storeFalse', 'appendConst' ].forEach(old_name => { - let new_name = _to_new_name(old_name) - this.register('action', old_name, util.deprecate(this._registry_get('action', new_name), - sub('{action: "%s"} is renamed to {action: "%s"}', old_name, new_name))) - }) - // end - - // raise an exception if the conflict handler is invalid - this._get_handler() - - // action storage - this._actions = [] - this._option_string_actions = {} - - // groups - this._action_groups = [] - this._mutually_exclusive_groups = [] - - // defaults storage - this._defaults = {} - - // determines whether an "option" looks like a negative number - this._negative_number_matcher = /^-\d+$|^-\d*\.\d+$/ - - // whether or not there are any optionals that look like negative - // numbers -- uses a list so it can be shared and edited - this._has_negative_number_optionals = [] - } - - // ==================== - // Registration methods - // ==================== - register(registry_name, value, object) { - let registry = setdefault(this._registries, registry_name, {}) - registry[value] = object - } - - _registry_get(registry_name, value, default_value = undefined) { - return getattr(this._registries[registry_name], value, default_value) - } - - // ================================== - // Namespace default accessor methods - // ================================== - set_defaults(kwargs) { - Object.assign(this._defaults, kwargs) - - // if these defaults match any existing arguments, replace - // the previous default on the object with the new one - for (let action of this._actions) { - if (action.dest in kwargs) { - action.default = kwargs[action.dest] - } - } - } - - get_default(dest) { - for (let action of this._actions) { - if (action.dest === dest && action.default !== undefined) { - return action.default - } - } - return this._defaults[dest] - } - - - // ======================= - // Adding argument actions - // ======================= - add_argument() { - /* - * add_argument(dest, ..., name=value, ...) - * add_argument(option_string, option_string, ..., name=value, ...) - */ - let [ - args, - kwargs - ] = _parse_opts(arguments, { - '*args': no_default, - '**kwargs': no_default - }) - // LEGACY (v1 compatibility), old-style add_argument([ args ], { options }) - if (args.length === 1 && Array.isArray(args[0])) { - args = args[0] - deprecate('argument-array', - sub('use add_argument(%(args)s, {...}) instead of add_argument([ %(args)s ], { ... })', { - args: args.map(repr).join(', ') - })) - } - // end - - // if no positional args are supplied or only one is supplied and - // it doesn't look like an option string, parse a positional - // argument - let chars = this.prefix_chars - if (!args.length || args.length === 1 && !chars.includes(args[0][0])) { - if (args.length && 'dest' in kwargs) { - throw new TypeError('dest supplied twice for positional argument') - } - kwargs = this._get_positional_kwargs(...args, kwargs) - - // otherwise, we're adding an optional argument - } else { - kwargs = this._get_optional_kwargs(...args, kwargs) - } - - // if no default was supplied, use the parser-level default - if (!('default' in kwargs)) { - let dest = kwargs.dest - if (dest in this._defaults) { - kwargs.default = this._defaults[dest] - } else if (this.argument_default !== undefined) { - kwargs.default = this.argument_default - } - } - - // create the action object, and add it to the parser - let action_class = this._pop_action_class(kwargs) - if (typeof action_class !== 'function') { - throw new TypeError(sub('unknown action "%s"', action_class)) - } - // eslint-disable-next-line new-cap - let action = new action_class(kwargs) - - // raise an error if the action type is not callable - let type_func = this._registry_get('type', action.type, action.type) - if (typeof type_func !== 'function') { - throw new TypeError(sub('%r is not callable', type_func)) - } - - if (type_func === FileType) { - throw new TypeError(sub('%r is a FileType class object, instance of it' + - ' must be passed', type_func)) - } - - // raise an error if the metavar does not match the type - if ('_get_formatter' in this) { - try { - this._get_formatter()._format_args(action, undefined) - } catch (err) { - // check for 'invalid nargs value' is an artifact of TypeError and ValueError in js being the same - if (err instanceof TypeError && err.message !== 'invalid nargs value') { - throw new TypeError('length of metavar tuple does not match nargs') - } else { - throw err - } - } - } - - return this._add_action(action) - } - - add_argument_group() { - let group = _ArgumentGroup(this, ...arguments) - this._action_groups.push(group) - return group - } - - add_mutually_exclusive_group() { - // eslint-disable-next-line no-use-before-define - let group = _MutuallyExclusiveGroup(this, ...arguments) - this._mutually_exclusive_groups.push(group) - return group - } - - _add_action(action) { - // resolve any conflicts - this._check_conflict(action) - - // add to actions list - this._actions.push(action) - action.container = this - - // index the action by any option strings it has - for (let option_string of action.option_strings) { - this._option_string_actions[option_string] = action - } - - // set the flag if any option strings look like negative numbers - for (let option_string of action.option_strings) { - if (this._negative_number_matcher.test(option_string)) { - if (!this._has_negative_number_optionals.length) { - this._has_negative_number_optionals.push(true) - } - } - } - - // return the created action - return action - } - - _remove_action(action) { - _array_remove(this._actions, action) - } - - _add_container_actions(container) { - // collect groups by titles - let title_group_map = {} - for (let group of this._action_groups) { - if (group.title in title_group_map) { - let msg = 'cannot merge actions - two groups are named %r' - throw new TypeError(sub(msg, group.title)) - } - title_group_map[group.title] = group - } - - // map each action to its group - let group_map = new Map() - for (let group of container._action_groups) { - - // if a group with the title exists, use that, otherwise - // create a new group matching the container's group - if (!(group.title in title_group_map)) { - title_group_map[group.title] = this.add_argument_group({ - title: group.title, - description: group.description, - conflict_handler: group.conflict_handler - }) - } - - // map the actions to their new group - for (let action of group._group_actions) { - group_map.set(action, title_group_map[group.title]) - } - } - - // add container's mutually exclusive groups - // NOTE: if add_mutually_exclusive_group ever gains title= and - // description= then this code will need to be expanded as above - for (let group of container._mutually_exclusive_groups) { - let mutex_group = this.add_mutually_exclusive_group({ - required: group.required - }) - - // map the actions to their new mutex group - for (let action of group._group_actions) { - group_map.set(action, mutex_group) - } - } - - // add all actions to this container or their group - for (let action of container._actions) { - group_map.get(action)._add_action(action) - } - } - - _get_positional_kwargs() { - let [ - dest, - kwargs - ] = _parse_opts(arguments, { - dest: no_default, - '**kwargs': no_default - }) - - // make sure required is not specified - if ('required' in kwargs) { - let msg = "'required' is an invalid argument for positionals" - throw new TypeError(msg) - } - - // mark positional arguments as required if at least one is - // always required - if (![OPTIONAL, ZERO_OR_MORE].includes(kwargs.nargs)) { - kwargs.required = true - } - if (kwargs.nargs === ZERO_OR_MORE && !('default' in kwargs)) { - kwargs.required = true - } - - // return the keyword arguments with no option strings - return Object.assign(kwargs, { dest, option_strings: [] }) - } - - _get_optional_kwargs() { - let [ - args, - kwargs - ] = _parse_opts(arguments, { - '*args': no_default, - '**kwargs': no_default - }) - - // determine short and long option strings - let option_strings = [] - let long_option_strings = [] - let option_string - for (option_string of args) { - // error on strings that don't start with an appropriate prefix - if (!this.prefix_chars.includes(option_string[0])) { - let args = {option: option_string, - prefix_chars: this.prefix_chars} - let msg = 'invalid option string %(option)r: ' + - 'must start with a character %(prefix_chars)r' - throw new TypeError(sub(msg, args)) - } - - // strings starting with two prefix characters are long options - option_strings.push(option_string) - if (option_string.length > 1 && this.prefix_chars.includes(option_string[1])) { - long_option_strings.push(option_string) - } - } - - // infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x' - let dest = kwargs.dest - delete kwargs.dest - if (dest === undefined) { - let dest_option_string - if (long_option_strings.length) { - dest_option_string = long_option_strings[0] - } else { - dest_option_string = option_strings[0] - } - dest = _string_lstrip(dest_option_string, this.prefix_chars) - if (!dest) { - let msg = 'dest= is required for options like %r' - throw new TypeError(sub(msg, option_string)) - } - dest = dest.replace(/-/g, '_') - } - - // return the updated keyword arguments - return Object.assign(kwargs, { dest, option_strings }) - } - - _pop_action_class(kwargs, default_value = undefined) { - let action = getattr(kwargs, 'action', default_value) - delete kwargs.action - return this._registry_get('action', action, action) - } - - _get_handler() { - // determine function from conflict handler string - let handler_func_name = sub('_handle_conflict_%s', this.conflict_handler) - if (typeof this[handler_func_name] === 'function') { - return this[handler_func_name] - } else { - let msg = 'invalid conflict_resolution value: %r' - throw new TypeError(sub(msg, this.conflict_handler)) - } - } - - _check_conflict(action) { - - // find all options that conflict with this option - let confl_optionals = [] - for (let option_string of action.option_strings) { - if (hasattr(this._option_string_actions, option_string)) { - let confl_optional = this._option_string_actions[option_string] - confl_optionals.push([ option_string, confl_optional ]) - } - } - - // resolve any conflicts - if (confl_optionals.length) { - let conflict_handler = this._get_handler() - conflict_handler.call(this, action, confl_optionals) - } - } - - _handle_conflict_error(action, conflicting_actions) { - let message = conflicting_actions.length === 1 ? - 'conflicting option string: %s' : - 'conflicting option strings: %s' - let conflict_string = conflicting_actions.map(([ option_string/*, action*/ ]) => option_string).join(', ') - throw new ArgumentError(action, sub(message, conflict_string)) - } - - _handle_conflict_resolve(action, conflicting_actions) { - - // remove all conflicting options - for (let [ option_string, action ] of conflicting_actions) { - - // remove the conflicting option - _array_remove(action.option_strings, option_string) - delete this._option_string_actions[option_string] - - // if the option now has no option string, remove it from the - // container holding it - if (!action.option_strings.length) { - action.container._remove_action(action) - } - } - } -})) - - -const _ArgumentGroup = _callable(class _ArgumentGroup extends _ActionsContainer { - - constructor() { - let [ - container, - title, - description, - kwargs - ] = _parse_opts(arguments, { - container: no_default, - title: undefined, - description: undefined, - '**kwargs': no_default - }) - - // add any missing keyword arguments by checking the container - setdefault(kwargs, 'conflict_handler', container.conflict_handler) - setdefault(kwargs, 'prefix_chars', container.prefix_chars) - setdefault(kwargs, 'argument_default', container.argument_default) - super(Object.assign({ description }, kwargs)) - - // group attributes - this.title = title - this._group_actions = [] - - // share most attributes with the container - this._registries = container._registries - this._actions = container._actions - this._option_string_actions = container._option_string_actions - this._defaults = container._defaults - this._has_negative_number_optionals = - container._has_negative_number_optionals - this._mutually_exclusive_groups = container._mutually_exclusive_groups - } - - _add_action(action) { - action = super._add_action(action) - this._group_actions.push(action) - return action - } - - _remove_action(action) { - super._remove_action(action) - _array_remove(this._group_actions, action) - } -}) - - -const _MutuallyExclusiveGroup = _callable(class _MutuallyExclusiveGroup extends _ArgumentGroup { - - constructor() { - let [ - container, - required - ] = _parse_opts(arguments, { - container: no_default, - required: false - }) - - super(container) - this.required = required - this._container = container - } - - _add_action(action) { - if (action.required) { - let msg = 'mutually exclusive arguments must be optional' - throw new TypeError(msg) - } - action = this._container._add_action(action) - this._group_actions.push(action) - return action - } - - _remove_action(action) { - this._container._remove_action(action) - _array_remove(this._group_actions, action) - } -}) - - -const ArgumentParser = _camelcase_alias(_callable(class ArgumentParser extends _AttributeHolder(_ActionsContainer) { - /* - * Object for parsing command line strings into Python objects. - * - * Keyword Arguments: - * - prog -- The name of the program (default: sys.argv[0]) - * - usage -- A usage message (default: auto-generated from arguments) - * - description -- A description of what the program does - * - epilog -- Text following the argument descriptions - * - parents -- Parsers whose arguments should be copied into this one - * - formatter_class -- HelpFormatter class for printing help messages - * - prefix_chars -- Characters that prefix optional arguments - * - fromfile_prefix_chars -- Characters that prefix files containing - * additional arguments - * - argument_default -- The default value for all arguments - * - conflict_handler -- String indicating how to handle conflicts - * - add_help -- Add a -h/-help option - * - allow_abbrev -- Allow long options to be abbreviated unambiguously - * - exit_on_error -- Determines whether or not ArgumentParser exits with - * error info when an error occurs - */ - - constructor() { - let [ - prog, - usage, - description, - epilog, - parents, - formatter_class, - prefix_chars, - fromfile_prefix_chars, - argument_default, - conflict_handler, - add_help, - allow_abbrev, - exit_on_error, - debug, // LEGACY (v1 compatibility), debug mode - version // LEGACY (v1 compatibility), version - ] = _parse_opts(arguments, { - prog: undefined, - usage: undefined, - description: undefined, - epilog: undefined, - parents: [], - formatter_class: HelpFormatter, - prefix_chars: '-', - fromfile_prefix_chars: undefined, - argument_default: undefined, - conflict_handler: 'error', - add_help: true, - allow_abbrev: true, - exit_on_error: true, - debug: undefined, // LEGACY (v1 compatibility), debug mode - version: undefined // LEGACY (v1 compatibility), version - }) - - // LEGACY (v1 compatibility) - if (debug !== undefined) { - deprecate('debug', - 'The "debug" argument to ArgumentParser is deprecated. Please ' + - 'override ArgumentParser.exit function instead.' - ) - } - - if (version !== undefined) { - deprecate('version', - 'The "version" argument to ArgumentParser is deprecated. Please use ' + - "add_argument(..., { action: 'version', version: 'N', ... }) instead." - ) - } - // end - - super({ - description, - prefix_chars, - argument_default, - conflict_handler - }) - - // default setting for prog - if (prog === undefined) { - prog = path.basename(get_argv()[0] || '') - } - - this.prog = prog - this.usage = usage - this.epilog = epilog - this.formatter_class = formatter_class - this.fromfile_prefix_chars = fromfile_prefix_chars - this.add_help = add_help - this.allow_abbrev = allow_abbrev - this.exit_on_error = exit_on_error - // LEGACY (v1 compatibility), debug mode - this.debug = debug - // end - - this._positionals = this.add_argument_group('positional arguments') - this._optionals = this.add_argument_group('optional arguments') - this._subparsers = undefined - - // register types - function identity(string) { - return string - } - this.register('type', undefined, identity) - this.register('type', null, identity) - this.register('type', 'auto', identity) - this.register('type', 'int', function (x) { - let result = Number(x) - if (!Number.isInteger(result)) { - throw new TypeError(sub('could not convert string to int: %r', x)) - } - return result - }) - this.register('type', 'float', function (x) { - let result = Number(x) - if (isNaN(result)) { - throw new TypeError(sub('could not convert string to float: %r', x)) - } - return result - }) - this.register('type', 'str', String) - // LEGACY (v1 compatibility): custom types - this.register('type', 'string', - util.deprecate(String, 'use {type:"str"} or {type:String} instead of {type:"string"}')) - // end - - // add help argument if necessary - // (using explicit default to override global argument_default) - let default_prefix = prefix_chars.includes('-') ? '-' : prefix_chars[0] - if (this.add_help) { - this.add_argument( - default_prefix + 'h', - default_prefix.repeat(2) + 'help', - { - action: 'help', - default: SUPPRESS, - help: 'show this help message and exit' - } - ) - } - // LEGACY (v1 compatibility), version - if (version) { - this.add_argument( - default_prefix + 'v', - default_prefix.repeat(2) + 'version', - { - action: 'version', - default: SUPPRESS, - version: this.version, - help: "show program's version number and exit" - } - ) - } - // end - - // add parent arguments and defaults - for (let parent of parents) { - this._add_container_actions(parent) - Object.assign(this._defaults, parent._defaults) - } - } - - // ======================= - // Pretty __repr__ methods - // ======================= - _get_kwargs() { - let names = [ - 'prog', - 'usage', - 'description', - 'formatter_class', - 'conflict_handler', - 'add_help' - ] - return names.map(name => [ name, getattr(this, name) ]) - } - - // ================================== - // Optional/Positional adding methods - // ================================== - add_subparsers() { - let [ - kwargs - ] = _parse_opts(arguments, { - '**kwargs': no_default - }) - - if (this._subparsers !== undefined) { - this.error('cannot have multiple subparser arguments') - } - - // add the parser class to the arguments if it's not present - setdefault(kwargs, 'parser_class', this.constructor) - - if ('title' in kwargs || 'description' in kwargs) { - let title = getattr(kwargs, 'title', 'subcommands') - let description = getattr(kwargs, 'description', undefined) - delete kwargs.title - delete kwargs.description - this._subparsers = this.add_argument_group(title, description) - } else { - this._subparsers = this._positionals - } - - // prog defaults to the usage message of this parser, skipping - // optional arguments and with no "usage:" prefix - if (kwargs.prog === undefined) { - let formatter = this._get_formatter() - let positionals = this._get_positional_actions() - let groups = this._mutually_exclusive_groups - formatter.add_usage(this.usage, positionals, groups, '') - kwargs.prog = formatter.format_help().trim() - } - - // create the parsers action and add it to the positionals list - let parsers_class = this._pop_action_class(kwargs, 'parsers') - // eslint-disable-next-line new-cap - let action = new parsers_class(Object.assign({ option_strings: [] }, kwargs)) - this._subparsers._add_action(action) - - // return the created parsers action - return action - } - - _add_action(action) { - if (action.option_strings.length) { - this._optionals._add_action(action) - } else { - this._positionals._add_action(action) - } - return action - } - - _get_optional_actions() { - return this._actions.filter(action => action.option_strings.length) - } - - _get_positional_actions() { - return this._actions.filter(action => !action.option_strings.length) - } - - // ===================================== - // Command line argument parsing methods - // ===================================== - parse_args(args = undefined, namespace = undefined) { - let argv - [ args, argv ] = this.parse_known_args(args, namespace) - if (argv && argv.length > 0) { - let msg = 'unrecognized arguments: %s' - this.error(sub(msg, argv.join(' '))) - } - return args - } - - parse_known_args(args = undefined, namespace = undefined) { - if (args === undefined) { - args = get_argv().slice(1) - } - - // default Namespace built from parser defaults - if (namespace === undefined) { - namespace = new Namespace() - } - - // add any action defaults that aren't present - for (let action of this._actions) { - if (action.dest !== SUPPRESS) { - if (!hasattr(namespace, action.dest)) { - if (action.default !== SUPPRESS) { - setattr(namespace, action.dest, action.default) - } - } - } - } - - // add any parser defaults that aren't present - for (let dest of Object.keys(this._defaults)) { - if (!hasattr(namespace, dest)) { - setattr(namespace, dest, this._defaults[dest]) - } - } - - // parse the arguments and exit if there are any errors - if (this.exit_on_error) { - try { - [ namespace, args ] = this._parse_known_args(args, namespace) - } catch (err) { - if (err instanceof ArgumentError) { - this.error(err.message) - } else { - throw err - } - } - } else { - [ namespace, args ] = this._parse_known_args(args, namespace) - } - - if (hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) { - args = args.concat(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) - delattr(namespace, _UNRECOGNIZED_ARGS_ATTR) - } - - return [ namespace, args ] - } - - _parse_known_args(arg_strings, namespace) { - // replace arg strings that are file references - if (this.fromfile_prefix_chars !== undefined) { - arg_strings = this._read_args_from_files(arg_strings) - } - - // map all mutually exclusive arguments to the other arguments - // they can't occur with - let action_conflicts = new Map() - for (let mutex_group of this._mutually_exclusive_groups) { - let group_actions = mutex_group._group_actions - for (let [ i, mutex_action ] of Object.entries(mutex_group._group_actions)) { - let conflicts = action_conflicts.get(mutex_action) || [] - conflicts = conflicts.concat(group_actions.slice(0, +i)) - conflicts = conflicts.concat(group_actions.slice(+i + 1)) - action_conflicts.set(mutex_action, conflicts) - } - } - - // find all option indices, and determine the arg_string_pattern - // which has an 'O' if there is an option at an index, - // an 'A' if there is an argument, or a '-' if there is a '--' - let option_string_indices = {} - let arg_string_pattern_parts = [] - let arg_strings_iter = Object.entries(arg_strings)[Symbol.iterator]() - for (let [ i, arg_string ] of arg_strings_iter) { - - // all args after -- are non-options - if (arg_string === '--') { - arg_string_pattern_parts.push('-') - for ([ i, arg_string ] of arg_strings_iter) { - arg_string_pattern_parts.push('A') - } - - // otherwise, add the arg to the arg strings - // and note the index if it was an option - } else { - let option_tuple = this._parse_optional(arg_string) - let pattern - if (option_tuple === undefined) { - pattern = 'A' - } else { - option_string_indices[i] = option_tuple - pattern = 'O' - } - arg_string_pattern_parts.push(pattern) - } - } - - // join the pieces together to form the pattern - let arg_strings_pattern = arg_string_pattern_parts.join('') - - // converts arg strings to the appropriate and then takes the action - let seen_actions = new Set() - let seen_non_default_actions = new Set() - let extras - - let take_action = (action, argument_strings, option_string = undefined) => { - seen_actions.add(action) - let argument_values = this._get_values(action, argument_strings) - - // error if this argument is not allowed with other previously - // seen arguments, assuming that actions that use the default - // value don't really count as "present" - if (argument_values !== action.default) { - seen_non_default_actions.add(action) - for (let conflict_action of action_conflicts.get(action) || []) { - if (seen_non_default_actions.has(conflict_action)) { - let msg = 'not allowed with argument %s' - let action_name = _get_action_name(conflict_action) - throw new ArgumentError(action, sub(msg, action_name)) - } - } - } - - // take the action if we didn't receive a SUPPRESS value - // (e.g. from a default) - if (argument_values !== SUPPRESS) { - action(this, namespace, argument_values, option_string) - } - } - - // function to convert arg_strings into an optional action - let consume_optional = start_index => { - - // get the optional identified at this index - let option_tuple = option_string_indices[start_index] - let [ action, option_string, explicit_arg ] = option_tuple - - // identify additional optionals in the same arg string - // (e.g. -xyz is the same as -x -y -z if no args are required) - let action_tuples = [] - let stop - for (;;) { - - // if we found no optional action, skip it - if (action === undefined) { - extras.push(arg_strings[start_index]) - return start_index + 1 - } - - // if there is an explicit argument, try to match the - // optional's string arguments to only this - if (explicit_arg !== undefined) { - let arg_count = this._match_argument(action, 'A') - - // if the action is a single-dash option and takes no - // arguments, try to parse more single-dash options out - // of the tail of the option string - let chars = this.prefix_chars - if (arg_count === 0 && !chars.includes(option_string[1])) { - action_tuples.push([ action, [], option_string ]) - let char = option_string[0] - option_string = char + explicit_arg[0] - let new_explicit_arg = explicit_arg.slice(1) || undefined - let optionals_map = this._option_string_actions - if (hasattr(optionals_map, option_string)) { - action = optionals_map[option_string] - explicit_arg = new_explicit_arg - } else { - let msg = 'ignored explicit argument %r' - throw new ArgumentError(action, sub(msg, explicit_arg)) - } - - // if the action expect exactly one argument, we've - // successfully matched the option; exit the loop - } else if (arg_count === 1) { - stop = start_index + 1 - let args = [ explicit_arg ] - action_tuples.push([ action, args, option_string ]) - break - - // error if a double-dash option did not use the - // explicit argument - } else { - let msg = 'ignored explicit argument %r' - throw new ArgumentError(action, sub(msg, explicit_arg)) - } - - // if there is no explicit argument, try to match the - // optional's string arguments with the following strings - // if successful, exit the loop - } else { - let start = start_index + 1 - let selected_patterns = arg_strings_pattern.slice(start) - let arg_count = this._match_argument(action, selected_patterns) - stop = start + arg_count - let args = arg_strings.slice(start, stop) - action_tuples.push([ action, args, option_string ]) - break - } - } - - // add the Optional to the list and return the index at which - // the Optional's string args stopped - assert(action_tuples.length) - for (let [ action, args, option_string ] of action_tuples) { - take_action(action, args, option_string) - } - return stop - } - - // the list of Positionals left to be parsed; this is modified - // by consume_positionals() - let positionals = this._get_positional_actions() - - // function to convert arg_strings into positional actions - let consume_positionals = start_index => { - // match as many Positionals as possible - let selected_pattern = arg_strings_pattern.slice(start_index) - let arg_counts = this._match_arguments_partial(positionals, selected_pattern) - - // slice off the appropriate arg strings for each Positional - // and add the Positional and its args to the list - for (let i = 0; i < positionals.length && i < arg_counts.length; i++) { - let action = positionals[i] - let arg_count = arg_counts[i] - let args = arg_strings.slice(start_index, start_index + arg_count) - start_index += arg_count - take_action(action, args) - } - - // slice off the Positionals that we just parsed and return the - // index at which the Positionals' string args stopped - positionals = positionals.slice(arg_counts.length) - return start_index - } - - // consume Positionals and Optionals alternately, until we have - // passed the last option string - extras = [] - let start_index = 0 - let max_option_string_index = Math.max(-1, ...Object.keys(option_string_indices).map(Number)) - while (start_index <= max_option_string_index) { - - // consume any Positionals preceding the next option - let next_option_string_index = Math.min( - // eslint-disable-next-line no-loop-func - ...Object.keys(option_string_indices).map(Number).filter(index => index >= start_index) - ) - if (start_index !== next_option_string_index) { - let positionals_end_index = consume_positionals(start_index) - - // only try to parse the next optional if we didn't consume - // the option string during the positionals parsing - if (positionals_end_index > start_index) { - start_index = positionals_end_index - continue - } else { - start_index = positionals_end_index - } - } - - // if we consumed all the positionals we could and we're not - // at the index of an option string, there were extra arguments - if (!(start_index in option_string_indices)) { - let strings = arg_strings.slice(start_index, next_option_string_index) - extras = extras.concat(strings) - start_index = next_option_string_index - } - - // consume the next optional and any arguments for it - start_index = consume_optional(start_index) - } - - // consume any positionals following the last Optional - let stop_index = consume_positionals(start_index) - - // if we didn't consume all the argument strings, there were extras - extras = extras.concat(arg_strings.slice(stop_index)) - - // make sure all required actions were present and also convert - // action defaults which were not given as arguments - let required_actions = [] - for (let action of this._actions) { - if (!seen_actions.has(action)) { - if (action.required) { - required_actions.push(_get_action_name(action)) - } else { - // Convert action default now instead of doing it before - // parsing arguments to avoid calling convert functions - // twice (which may fail) if the argument was given, but - // only if it was defined already in the namespace - if (action.default !== undefined && - typeof action.default === 'string' && - hasattr(namespace, action.dest) && - action.default === getattr(namespace, action.dest)) { - setattr(namespace, action.dest, - this._get_value(action, action.default)) - } - } - } - } - - if (required_actions.length) { - this.error(sub('the following arguments are required: %s', - required_actions.join(', '))) - } - - // make sure all required groups had one option present - for (let group of this._mutually_exclusive_groups) { - if (group.required) { - let no_actions_used = true - for (let action of group._group_actions) { - if (seen_non_default_actions.has(action)) { - no_actions_used = false - break - } - } - - // if no actions were used, report the error - if (no_actions_used) { - let names = group._group_actions - .filter(action => action.help !== SUPPRESS) - .map(action => _get_action_name(action)) - let msg = 'one of the arguments %s is required' - this.error(sub(msg, names.join(' '))) - } - } - } - - // return the updated namespace and the extra arguments - return [ namespace, extras ] - } - - _read_args_from_files(arg_strings) { - // expand arguments referencing files - let new_arg_strings = [] - for (let arg_string of arg_strings) { - - // for regular arguments, just add them back into the list - if (!arg_string || !this.fromfile_prefix_chars.includes(arg_string[0])) { - new_arg_strings.push(arg_string) - - // replace arguments referencing files with the file content - } else { - try { - let args_file = fs.readFileSync(arg_string.slice(1), 'utf8') - let arg_strings = [] - for (let arg_line of splitlines(args_file)) { - for (let arg of this.convert_arg_line_to_args(arg_line)) { - arg_strings.push(arg) - } - } - arg_strings = this._read_args_from_files(arg_strings) - new_arg_strings = new_arg_strings.concat(arg_strings) - } catch (err) { - this.error(err.message) - } - } - } - - // return the modified argument list - return new_arg_strings - } - - convert_arg_line_to_args(arg_line) { - return [arg_line] - } - - _match_argument(action, arg_strings_pattern) { - // match the pattern for this action to the arg strings - let nargs_pattern = this._get_nargs_pattern(action) - let match = arg_strings_pattern.match(new RegExp('^' + nargs_pattern)) - - // raise an exception if we weren't able to find a match - if (match === null) { - let nargs_errors = { - undefined: 'expected one argument', - [OPTIONAL]: 'expected at most one argument', - [ONE_OR_MORE]: 'expected at least one argument' - } - let msg = nargs_errors[action.nargs] - if (msg === undefined) { - msg = sub(action.nargs === 1 ? 'expected %s argument' : 'expected %s arguments', action.nargs) - } - throw new ArgumentError(action, msg) - } - - // return the number of arguments matched - return match[1].length - } - - _match_arguments_partial(actions, arg_strings_pattern) { - // progressively shorten the actions list by slicing off the - // final actions until we find a match - let result = [] - for (let i of range(actions.length, 0, -1)) { - let actions_slice = actions.slice(0, i) - let pattern = actions_slice.map(action => this._get_nargs_pattern(action)).join('') - let match = arg_strings_pattern.match(new RegExp('^' + pattern)) - if (match !== null) { - result = result.concat(match.slice(1).map(string => string.length)) - break - } - } - - // return the list of arg string counts - return result - } - - _parse_optional(arg_string) { - // if it's an empty string, it was meant to be a positional - if (!arg_string) { - return undefined - } - - // if it doesn't start with a prefix, it was meant to be positional - if (!this.prefix_chars.includes(arg_string[0])) { - return undefined - } - - // if the option string is present in the parser, return the action - if (arg_string in this._option_string_actions) { - let action = this._option_string_actions[arg_string] - return [ action, arg_string, undefined ] - } - - // if it's just a single character, it was meant to be positional - if (arg_string.length === 1) { - return undefined - } - - // if the option string before the "=" is present, return the action - if (arg_string.includes('=')) { - let [ option_string, explicit_arg ] = _string_split(arg_string, '=', 1) - if (option_string in this._option_string_actions) { - let action = this._option_string_actions[option_string] - return [ action, option_string, explicit_arg ] - } - } - - // search through all possible prefixes of the option string - // and all actions in the parser for possible interpretations - let option_tuples = this._get_option_tuples(arg_string) - - // if multiple actions match, the option string was ambiguous - if (option_tuples.length > 1) { - let options = option_tuples.map(([ /*action*/, option_string/*, explicit_arg*/ ]) => option_string).join(', ') - let args = {option: arg_string, matches: options} - let msg = 'ambiguous option: %(option)s could match %(matches)s' - this.error(sub(msg, args)) - - // if exactly one action matched, this segmentation is good, - // so return the parsed action - } else if (option_tuples.length === 1) { - let [ option_tuple ] = option_tuples - return option_tuple - } - - // if it was not found as an option, but it looks like a negative - // number, it was meant to be positional - // unless there are negative-number-like options - if (this._negative_number_matcher.test(arg_string)) { - if (!this._has_negative_number_optionals.length) { - return undefined - } - } - - // if it contains a space, it was meant to be a positional - if (arg_string.includes(' ')) { - return undefined - } - - // it was meant to be an optional but there is no such option - // in this parser (though it might be a valid option in a subparser) - return [ undefined, arg_string, undefined ] - } - - _get_option_tuples(option_string) { - let result = [] - - // option strings starting with two prefix characters are only - // split at the '=' - let chars = this.prefix_chars - if (chars.includes(option_string[0]) && chars.includes(option_string[1])) { - if (this.allow_abbrev) { - let option_prefix, explicit_arg - if (option_string.includes('=')) { - [ option_prefix, explicit_arg ] = _string_split(option_string, '=', 1) - } else { - option_prefix = option_string - explicit_arg = undefined - } - for (let option_string of Object.keys(this._option_string_actions)) { - if (option_string.startsWith(option_prefix)) { - let action = this._option_string_actions[option_string] - let tup = [ action, option_string, explicit_arg ] - result.push(tup) - } - } - } - - // single character options can be concatenated with their arguments - // but multiple character options always have to have their argument - // separate - } else if (chars.includes(option_string[0]) && !chars.includes(option_string[1])) { - let option_prefix = option_string - let explicit_arg = undefined - let short_option_prefix = option_string.slice(0, 2) - let short_explicit_arg = option_string.slice(2) - - for (let option_string of Object.keys(this._option_string_actions)) { - if (option_string === short_option_prefix) { - let action = this._option_string_actions[option_string] - let tup = [ action, option_string, short_explicit_arg ] - result.push(tup) - } else if (option_string.startsWith(option_prefix)) { - let action = this._option_string_actions[option_string] - let tup = [ action, option_string, explicit_arg ] - result.push(tup) - } - } - - // shouldn't ever get here - } else { - this.error(sub('unexpected option string: %s', option_string)) - } - - // return the collected option tuples - return result - } - - _get_nargs_pattern(action) { - // in all examples below, we have to allow for '--' args - // which are represented as '-' in the pattern - let nargs = action.nargs - let nargs_pattern - - // the default (None) is assumed to be a single argument - if (nargs === undefined) { - nargs_pattern = '(-*A-*)' - - // allow zero or one arguments - } else if (nargs === OPTIONAL) { - nargs_pattern = '(-*A?-*)' - - // allow zero or more arguments - } else if (nargs === ZERO_OR_MORE) { - nargs_pattern = '(-*[A-]*)' - - // allow one or more arguments - } else if (nargs === ONE_OR_MORE) { - nargs_pattern = '(-*A[A-]*)' - - // allow any number of options or arguments - } else if (nargs === REMAINDER) { - nargs_pattern = '([-AO]*)' - - // allow one argument followed by any number of options or arguments - } else if (nargs === PARSER) { - nargs_pattern = '(-*A[-AO]*)' - - // suppress action, like nargs=0 - } else if (nargs === SUPPRESS) { - nargs_pattern = '(-*-*)' - - // all others should be integers - } else { - nargs_pattern = sub('(-*%s-*)', 'A'.repeat(nargs).split('').join('-*')) - } - - // if this is an optional action, -- is not allowed - if (action.option_strings.length) { - nargs_pattern = nargs_pattern.replace(/-\*/g, '') - nargs_pattern = nargs_pattern.replace(/-/g, '') - } - - // return the pattern - return nargs_pattern - } - - // ======================== - // Alt command line argument parsing, allowing free intermix - // ======================== - - parse_intermixed_args(args = undefined, namespace = undefined) { - let argv - [ args, argv ] = this.parse_known_intermixed_args(args, namespace) - if (argv.length) { - let msg = 'unrecognized arguments: %s' - this.error(sub(msg, argv.join(' '))) - } - return args - } - - parse_known_intermixed_args(args = undefined, namespace = undefined) { - // returns a namespace and list of extras - // - // positional can be freely intermixed with optionals. optionals are - // first parsed with all positional arguments deactivated. The 'extras' - // are then parsed. If the parser definition is incompatible with the - // intermixed assumptions (e.g. use of REMAINDER, subparsers) a - // TypeError is raised. - // - // positionals are 'deactivated' by setting nargs and default to - // SUPPRESS. This blocks the addition of that positional to the - // namespace - - let extras - let positionals = this._get_positional_actions() - let a = positionals.filter(action => [ PARSER, REMAINDER ].includes(action.nargs)) - if (a.length) { - throw new TypeError(sub('parse_intermixed_args: positional arg' + - ' with nargs=%s', a[0].nargs)) - } - - for (let group of this._mutually_exclusive_groups) { - for (let action of group._group_actions) { - if (positionals.includes(action)) { - throw new TypeError('parse_intermixed_args: positional in' + - ' mutuallyExclusiveGroup') - } - } - } - - let save_usage - try { - save_usage = this.usage - let remaining_args - try { - if (this.usage === undefined) { - // capture the full usage for use in error messages - this.usage = this.format_usage().slice(7) - } - for (let action of positionals) { - // deactivate positionals - action.save_nargs = action.nargs - // action.nargs = 0 - action.nargs = SUPPRESS - action.save_default = action.default - action.default = SUPPRESS - } - [ namespace, remaining_args ] = this.parse_known_args(args, - namespace) - for (let action of positionals) { - // remove the empty positional values from namespace - let attr = getattr(namespace, action.dest) - if (Array.isArray(attr) && attr.length === 0) { - // eslint-disable-next-line no-console - console.warn(sub('Do not expect %s in %s', action.dest, namespace)) - delattr(namespace, action.dest) - } - } - } finally { - // restore nargs and usage before exiting - for (let action of positionals) { - action.nargs = action.save_nargs - action.default = action.save_default - } - } - let optionals = this._get_optional_actions() - try { - // parse positionals. optionals aren't normally required, but - // they could be, so make sure they aren't. - for (let action of optionals) { - action.save_required = action.required - action.required = false - } - for (let group of this._mutually_exclusive_groups) { - group.save_required = group.required - group.required = false - } - [ namespace, extras ] = this.parse_known_args(remaining_args, - namespace) - } finally { - // restore parser values before exiting - for (let action of optionals) { - action.required = action.save_required - } - for (let group of this._mutually_exclusive_groups) { - group.required = group.save_required - } - } - } finally { - this.usage = save_usage - } - return [ namespace, extras ] - } - - // ======================== - // Value conversion methods - // ======================== - _get_values(action, arg_strings) { - // for everything but PARSER, REMAINDER args, strip out first '--' - if (![PARSER, REMAINDER].includes(action.nargs)) { - try { - _array_remove(arg_strings, '--') - } catch (err) {} - } - - let value - // optional argument produces a default when not present - if (!arg_strings.length && action.nargs === OPTIONAL) { - if (action.option_strings.length) { - value = action.const - } else { - value = action.default - } - if (typeof value === 'string') { - value = this._get_value(action, value) - this._check_value(action, value) - } - - // when nargs='*' on a positional, if there were no command-line - // args, use the default if it is anything other than None - } else if (!arg_strings.length && action.nargs === ZERO_OR_MORE && - !action.option_strings.length) { - if (action.default !== undefined) { - value = action.default - } else { - value = arg_strings - } - this._check_value(action, value) - - // single argument or optional argument produces a single value - } else if (arg_strings.length === 1 && [undefined, OPTIONAL].includes(action.nargs)) { - let arg_string = arg_strings[0] - value = this._get_value(action, arg_string) - this._check_value(action, value) - - // REMAINDER arguments convert all values, checking none - } else if (action.nargs === REMAINDER) { - value = arg_strings.map(v => this._get_value(action, v)) - - // PARSER arguments convert all values, but check only the first - } else if (action.nargs === PARSER) { - value = arg_strings.map(v => this._get_value(action, v)) - this._check_value(action, value[0]) - - // SUPPRESS argument does not put anything in the namespace - } else if (action.nargs === SUPPRESS) { - value = SUPPRESS - - // all other types of nargs produce a list - } else { - value = arg_strings.map(v => this._get_value(action, v)) - for (let v of value) { - this._check_value(action, v) - } - } - - // return the converted value - return value - } - - _get_value(action, arg_string) { - let type_func = this._registry_get('type', action.type, action.type) - if (typeof type_func !== 'function') { - let msg = '%r is not callable' - throw new ArgumentError(action, sub(msg, type_func)) - } - - // convert the value to the appropriate type - let result - try { - try { - result = type_func(arg_string) - } catch (err) { - // Dear TC39, why would you ever consider making es6 classes not callable? - // We had one universal interface, [[Call]], which worked for anything - // (with familiar this-instanceof guard for classes). Now we have two. - if (err instanceof TypeError && - /Class constructor .* cannot be invoked without 'new'/.test(err.message)) { - // eslint-disable-next-line new-cap - result = new type_func(arg_string) - } else { - throw err - } - } - - } catch (err) { - // ArgumentTypeErrors indicate errors - if (err instanceof ArgumentTypeError) { - //let name = getattr(action.type, 'name', repr(action.type)) - let msg = err.message - throw new ArgumentError(action, msg) - - // TypeErrors or ValueErrors also indicate errors - } else if (err instanceof TypeError) { - let name = getattr(action.type, 'name', repr(action.type)) - let args = {type: name, value: arg_string} - let msg = 'invalid %(type)s value: %(value)r' - throw new ArgumentError(action, sub(msg, args)) - } else { - throw err - } - } - - // return the converted value - return result - } - - _check_value(action, value) { - // converted value must be one of the choices (if specified) - if (action.choices !== undefined && !_choices_to_array(action.choices).includes(value)) { - let args = {value, - choices: _choices_to_array(action.choices).map(repr).join(', ')} - let msg = 'invalid choice: %(value)r (choose from %(choices)s)' - throw new ArgumentError(action, sub(msg, args)) - } - } - - // ======================= - // Help-formatting methods - // ======================= - format_usage() { - let formatter = this._get_formatter() - formatter.add_usage(this.usage, this._actions, - this._mutually_exclusive_groups) - return formatter.format_help() - } - - format_help() { - let formatter = this._get_formatter() - - // usage - formatter.add_usage(this.usage, this._actions, - this._mutually_exclusive_groups) - - // description - formatter.add_text(this.description) - - // positionals, optionals and user-defined groups - for (let action_group of this._action_groups) { - formatter.start_section(action_group.title) - formatter.add_text(action_group.description) - formatter.add_arguments(action_group._group_actions) - formatter.end_section() - } - - // epilog - formatter.add_text(this.epilog) - - // determine help from format above - return formatter.format_help() - } - - _get_formatter() { - // eslint-disable-next-line new-cap - return new this.formatter_class({ prog: this.prog }) - } - - // ===================== - // Help-printing methods - // ===================== - print_usage(file = undefined) { - if (file === undefined) file = process.stdout - this._print_message(this.format_usage(), file) - } - - print_help(file = undefined) { - if (file === undefined) file = process.stdout - this._print_message(this.format_help(), file) - } - - _print_message(message, file = undefined) { - if (message) { - if (file === undefined) file = process.stderr - file.write(message) - } - } - - // =============== - // Exiting methods - // =============== - exit(status = 0, message = undefined) { - if (message) { - this._print_message(message, process.stderr) - } - process.exit(status) - } - - error(message) { - /* - * error(message: string) - * - * Prints a usage message incorporating the message to stderr and - * exits. - * - * If you override this in a subclass, it should not return -- it - * should either exit or raise an exception. - */ - - // LEGACY (v1 compatibility), debug mode - if (this.debug === true) throw new Error(message) - // end - this.print_usage(process.stderr) - let args = {prog: this.prog, message: message} - this.exit(2, sub('%(prog)s: error: %(message)s\n', args)) - } -})) - - -module.exports = { - ArgumentParser, - ArgumentError, - ArgumentTypeError, - BooleanOptionalAction, - FileType, - HelpFormatter, - ArgumentDefaultsHelpFormatter, - RawDescriptionHelpFormatter, - RawTextHelpFormatter, - MetavarTypeHelpFormatter, - Namespace, - Action, - ONE_OR_MORE, - OPTIONAL, - PARSER, - REMAINDER, - SUPPRESS, - ZERO_OR_MORE -} - -// LEGACY (v1 compatibility), Const alias -Object.defineProperty(module.exports, 'Const', { - get() { - let result = {} - Object.entries({ ONE_OR_MORE, OPTIONAL, PARSER, REMAINDER, SUPPRESS, ZERO_OR_MORE }).forEach(([ n, v ]) => { - Object.defineProperty(result, n, { - get() { - deprecate(n, sub('use argparse.%s instead of argparse.Const.%s', n, n)) - return v - } - }) - }) - Object.entries({ _UNRECOGNIZED_ARGS_ATTR }).forEach(([ n, v ]) => { - Object.defineProperty(result, n, { - get() { - deprecate(n, sub('argparse.Const.%s is an internal symbol and will no longer be available', n)) - return v - } - }) - }) - return result - }, - enumerable: false -}) -// end diff --git a/node_modules/lv_font_conv/node_modules/argparse/lib/sub.js b/node_modules/lv_font_conv/node_modules/argparse/lib/sub.js deleted file mode 100644 index e3eb3215..00000000 --- a/node_modules/lv_font_conv/node_modules/argparse/lib/sub.js +++ /dev/null @@ -1,67 +0,0 @@ -// Limited implementation of python % string operator, supports only %s and %r for now -// (other formats are not used here, but may appear in custom templates) - -'use strict' - -const { inspect } = require('util') - - -module.exports = function sub(pattern, ...values) { - let regex = /%(?:(%)|(-)?(\*)?(?:\((\w+)\))?([A-Za-z]))/g - - let result = pattern.replace(regex, function (_, is_literal, is_left_align, is_padded, name, format) { - if (is_literal) return '%' - - let padded_count = 0 - if (is_padded) { - if (values.length === 0) throw new TypeError('not enough arguments for format string') - padded_count = values.shift() - if (!Number.isInteger(padded_count)) throw new TypeError('* wants int') - } - - let str - if (name !== undefined) { - let dict = values[0] - if (typeof dict !== 'object' || dict === null) throw new TypeError('format requires a mapping') - if (!(name in dict)) throw new TypeError(`no such key: '${name}'`) - str = dict[name] - } else { - if (values.length === 0) throw new TypeError('not enough arguments for format string') - str = values.shift() - } - - switch (format) { - case 's': - str = String(str) - break - case 'r': - str = inspect(str) - break - case 'd': - case 'i': - if (typeof str !== 'number') { - throw new TypeError(`%${format} format: a number is required, not ${typeof str}`) - } - str = String(str.toFixed(0)) - break - default: - throw new TypeError(`unsupported format character '${format}'`) - } - - if (padded_count > 0) { - return is_left_align ? str.padEnd(padded_count) : str.padStart(padded_count) - } else { - return str - } - }) - - if (values.length) { - if (values.length === 1 && typeof values[0] === 'object' && values[0] !== null) { - // mapping - } else { - throw new TypeError('not all arguments converted during string formatting') - } - } - - return result -} diff --git a/node_modules/lv_font_conv/node_modules/argparse/lib/textwrap.js b/node_modules/lv_font_conv/node_modules/argparse/lib/textwrap.js deleted file mode 100644 index 23d51cdb..00000000 --- a/node_modules/lv_font_conv/node_modules/argparse/lib/textwrap.js +++ /dev/null @@ -1,440 +0,0 @@ -// Partial port of python's argparse module, version 3.9.0 (only wrap and fill functions): -// https://github.com/python/cpython/blob/v3.9.0b4/Lib/textwrap.py - -'use strict' - -/* - * Text wrapping and filling. - */ - -// Copyright (C) 1999-2001 Gregory P. Ward. -// Copyright (C) 2002, 2003 Python Software Foundation. -// Copyright (C) 2020 argparse.js authors -// Originally written by Greg Ward - -// Hardcode the recognized whitespace characters to the US-ASCII -// whitespace characters. The main reason for doing this is that -// some Unicode spaces (like \u00a0) are non-breaking whitespaces. -// -// This less funky little regex just split on recognized spaces. E.g. -// "Hello there -- you goof-ball, use the -b option!" -// splits into -// Hello/ /there/ /--/ /you/ /goof-ball,/ /use/ /the/ /-b/ /option!/ -const wordsep_simple_re = /([\t\n\x0b\x0c\r ]+)/ - -class TextWrapper { - /* - * Object for wrapping/filling text. The public interface consists of - * the wrap() and fill() methods; the other methods are just there for - * subclasses to override in order to tweak the default behaviour. - * If you want to completely replace the main wrapping algorithm, - * you'll probably have to override _wrap_chunks(). - * - * Several instance attributes control various aspects of wrapping: - * width (default: 70) - * the maximum width of wrapped lines (unless break_long_words - * is false) - * initial_indent (default: "") - * string that will be prepended to the first line of wrapped - * output. Counts towards the line's width. - * subsequent_indent (default: "") - * string that will be prepended to all lines save the first - * of wrapped output; also counts towards each line's width. - * expand_tabs (default: true) - * Expand tabs in input text to spaces before further processing. - * Each tab will become 0 .. 'tabsize' spaces, depending on its position - * in its line. If false, each tab is treated as a single character. - * tabsize (default: 8) - * Expand tabs in input text to 0 .. 'tabsize' spaces, unless - * 'expand_tabs' is false. - * replace_whitespace (default: true) - * Replace all whitespace characters in the input text by spaces - * after tab expansion. Note that if expand_tabs is false and - * replace_whitespace is true, every tab will be converted to a - * single space! - * fix_sentence_endings (default: false) - * Ensure that sentence-ending punctuation is always followed - * by two spaces. Off by default because the algorithm is - * (unavoidably) imperfect. - * break_long_words (default: true) - * Break words longer than 'width'. If false, those words will not - * be broken, and some lines might be longer than 'width'. - * break_on_hyphens (default: true) - * Allow breaking hyphenated words. If true, wrapping will occur - * preferably on whitespaces and right after hyphens part of - * compound words. - * drop_whitespace (default: true) - * Drop leading and trailing whitespace from lines. - * max_lines (default: None) - * Truncate wrapped lines. - * placeholder (default: ' [...]') - * Append to the last line of truncated text. - */ - - constructor(options = {}) { - let { - width = 70, - initial_indent = '', - subsequent_indent = '', - expand_tabs = true, - replace_whitespace = true, - fix_sentence_endings = false, - break_long_words = true, - drop_whitespace = true, - break_on_hyphens = true, - tabsize = 8, - max_lines = undefined, - placeholder=' [...]' - } = options - - this.width = width - this.initial_indent = initial_indent - this.subsequent_indent = subsequent_indent - this.expand_tabs = expand_tabs - this.replace_whitespace = replace_whitespace - this.fix_sentence_endings = fix_sentence_endings - this.break_long_words = break_long_words - this.drop_whitespace = drop_whitespace - this.break_on_hyphens = break_on_hyphens - this.tabsize = tabsize - this.max_lines = max_lines - this.placeholder = placeholder - } - - - // -- Private methods ----------------------------------------------- - // (possibly useful for subclasses to override) - - _munge_whitespace(text) { - /* - * _munge_whitespace(text : string) -> string - * - * Munge whitespace in text: expand tabs and convert all other - * whitespace characters to spaces. Eg. " foo\\tbar\\n\\nbaz" - * becomes " foo bar baz". - */ - if (this.expand_tabs) { - text = text.replace(/\t/g, ' '.repeat(this.tabsize)) // not strictly correct in js - } - if (this.replace_whitespace) { - text = text.replace(/[\t\n\x0b\x0c\r]/g, ' ') - } - return text - } - - _split(text) { - /* - * _split(text : string) -> [string] - * - * Split the text to wrap into indivisible chunks. Chunks are - * not quite the same as words; see _wrap_chunks() for full - * details. As an example, the text - * Look, goof-ball -- use the -b option! - * breaks into the following chunks: - * 'Look,', ' ', 'goof-', 'ball', ' ', '--', ' ', - * 'use', ' ', 'the', ' ', '-b', ' ', 'option!' - * if break_on_hyphens is True, or in: - * 'Look,', ' ', 'goof-ball', ' ', '--', ' ', - * 'use', ' ', 'the', ' ', '-b', ' ', option!' - * otherwise. - */ - let chunks = text.split(wordsep_simple_re) - chunks = chunks.filter(Boolean) - return chunks - } - - _handle_long_word(reversed_chunks, cur_line, cur_len, width) { - /* - * _handle_long_word(chunks : [string], - * cur_line : [string], - * cur_len : int, width : int) - * - * Handle a chunk of text (most likely a word, not whitespace) that - * is too long to fit in any line. - */ - // Figure out when indent is larger than the specified width, and make - // sure at least one character is stripped off on every pass - let space_left - if (width < 1) { - space_left = 1 - } else { - space_left = width - cur_len - } - - // If we're allowed to break long words, then do so: put as much - // of the next chunk onto the current line as will fit. - if (this.break_long_words) { - cur_line.push(reversed_chunks[reversed_chunks.length - 1].slice(0, space_left)) - reversed_chunks[reversed_chunks.length - 1] = reversed_chunks[reversed_chunks.length - 1].slice(space_left) - - // Otherwise, we have to preserve the long word intact. Only add - // it to the current line if there's nothing already there -- - // that minimizes how much we violate the width constraint. - } else if (!cur_line) { - cur_line.push(...reversed_chunks.pop()) - } - - // If we're not allowed to break long words, and there's already - // text on the current line, do nothing. Next time through the - // main loop of _wrap_chunks(), we'll wind up here again, but - // cur_len will be zero, so the next line will be entirely - // devoted to the long word that we can't handle right now. - } - - _wrap_chunks(chunks) { - /* - * _wrap_chunks(chunks : [string]) -> [string] - * - * Wrap a sequence of text chunks and return a list of lines of - * length 'self.width' or less. (If 'break_long_words' is false, - * some lines may be longer than this.) Chunks correspond roughly - * to words and the whitespace between them: each chunk is - * indivisible (modulo 'break_long_words'), but a line break can - * come between any two chunks. Chunks should not have internal - * whitespace; ie. a chunk is either all whitespace or a "word". - * Whitespace chunks will be removed from the beginning and end of - * lines, but apart from that whitespace is preserved. - */ - let lines = [] - let indent - if (this.width <= 0) { - throw Error(`invalid width ${this.width} (must be > 0)`) - } - if (this.max_lines !== undefined) { - if (this.max_lines > 1) { - indent = this.subsequent_indent - } else { - indent = this.initial_indent - } - if (indent.length + this.placeholder.trimStart().length > this.width) { - throw Error('placeholder too large for max width') - } - } - - // Arrange in reverse order so items can be efficiently popped - // from a stack of chucks. - chunks = chunks.reverse() - - while (chunks.length > 0) { - - // Start the list of chunks that will make up the current line. - // cur_len is just the length of all the chunks in cur_line. - let cur_line = [] - let cur_len = 0 - - // Figure out which static string will prefix this line. - let indent - if (lines) { - indent = this.subsequent_indent - } else { - indent = this.initial_indent - } - - // Maximum width for this line. - let width = this.width - indent.length - - // First chunk on line is whitespace -- drop it, unless this - // is the very beginning of the text (ie. no lines started yet). - if (this.drop_whitespace && chunks[chunks.length - 1].trim() === '' && lines.length > 0) { - chunks.pop() - } - - while (chunks.length > 0) { - let l = chunks[chunks.length - 1].length - - // Can at least squeeze this chunk onto the current line. - if (cur_len + l <= width) { - cur_line.push(chunks.pop()) - cur_len += l - - // Nope, this line is full. - } else { - break - } - } - - // The current line is full, and the next chunk is too big to - // fit on *any* line (not just this one). - if (chunks.length && chunks[chunks.length - 1].length > width) { - this._handle_long_word(chunks, cur_line, cur_len, width) - cur_len = cur_line.map(l => l.length).reduce((a, b) => a + b, 0) - } - - // If the last chunk on this line is all whitespace, drop it. - if (this.drop_whitespace && cur_line.length > 0 && cur_line[cur_line.length - 1].trim() === '') { - cur_len -= cur_line[cur_line.length - 1].length - cur_line.pop() - } - - if (cur_line) { - if (this.max_lines === undefined || - lines.length + 1 < this.max_lines || - (chunks.length === 0 || - this.drop_whitespace && - chunks.length === 1 && - !chunks[0].trim()) && cur_len <= width) { - // Convert current line back to a string and store it in - // list of all lines (return value). - lines.push(indent + cur_line.join('')) - } else { - let had_break = false - while (cur_line) { - if (cur_line[cur_line.length - 1].trim() && - cur_len + this.placeholder.length <= width) { - cur_line.push(this.placeholder) - lines.push(indent + cur_line.join('')) - had_break = true - break - } - cur_len -= cur_line[-1].length - cur_line.pop() - } - if (!had_break) { - if (lines) { - let prev_line = lines[lines.length - 1].trimEnd() - if (prev_line.length + this.placeholder.length <= - this.width) { - lines[lines.length - 1] = prev_line + this.placeholder - break - } - } - lines.push(indent + this.placeholder.lstrip()) - } - break - } - } - } - - return lines - } - - _split_chunks(text) { - text = this._munge_whitespace(text) - return this._split(text) - } - - // -- Public interface ---------------------------------------------- - - wrap(text) { - /* - * wrap(text : string) -> [string] - * - * Reformat the single paragraph in 'text' so it fits in lines of - * no more than 'self.width' columns, and return a list of wrapped - * lines. Tabs in 'text' are expanded with string.expandtabs(), - * and all other whitespace characters (including newline) are - * converted to space. - */ - let chunks = this._split_chunks(text) - // not implemented in js - //if (this.fix_sentence_endings) { - // this._fix_sentence_endings(chunks) - //} - return this._wrap_chunks(chunks) - } - - fill(text) { - /* - * fill(text : string) -> string - * - * Reformat the single paragraph in 'text' to fit in lines of no - * more than 'self.width' columns, and return a new string - * containing the entire wrapped paragraph. - */ - return this.wrap(text).join('\n') - } -} - - -// -- Convenience interface --------------------------------------------- - -function wrap(text, options = {}) { - /* - * Wrap a single paragraph of text, returning a list of wrapped lines. - * - * Reformat the single paragraph in 'text' so it fits in lines of no - * more than 'width' columns, and return a list of wrapped lines. By - * default, tabs in 'text' are expanded with string.expandtabs(), and - * all other whitespace characters (including newline) are converted to - * space. See TextWrapper class for available keyword args to customize - * wrapping behaviour. - */ - let { width = 70, ...kwargs } = options - let w = new TextWrapper(Object.assign({ width }, kwargs)) - return w.wrap(text) -} - -function fill(text, options = {}) { - /* - * Fill a single paragraph of text, returning a new string. - * - * Reformat the single paragraph in 'text' to fit in lines of no more - * than 'width' columns, and return a new string containing the entire - * wrapped paragraph. As with wrap(), tabs are expanded and other - * whitespace characters converted to space. See TextWrapper class for - * available keyword args to customize wrapping behaviour. - */ - let { width = 70, ...kwargs } = options - let w = new TextWrapper(Object.assign({ width }, kwargs)) - return w.fill(text) -} - -// -- Loosely related functionality ------------------------------------- - -let _whitespace_only_re = /^[ \t]+$/mg -let _leading_whitespace_re = /(^[ \t]*)(?:[^ \t\n])/mg - -function dedent(text) { - /* - * Remove any common leading whitespace from every line in `text`. - * - * This can be used to make triple-quoted strings line up with the left - * edge of the display, while still presenting them in the source code - * in indented form. - * - * Note that tabs and spaces are both treated as whitespace, but they - * are not equal: the lines " hello" and "\\thello" are - * considered to have no common leading whitespace. - * - * Entirely blank lines are normalized to a newline character. - */ - // Look for the longest leading string of spaces and tabs common to - // all lines. - let margin = undefined - text = text.replace(_whitespace_only_re, '') - let indents = text.match(_leading_whitespace_re) || [] - for (let indent of indents) { - indent = indent.slice(0, -1) - - if (margin === undefined) { - margin = indent - - // Current line more deeply indented than previous winner: - // no change (previous winner is still on top). - } else if (indent.startsWith(margin)) { - // pass - - // Current line consistent with and no deeper than previous winner: - // it's the new winner. - } else if (margin.startsWith(indent)) { - margin = indent - - // Find the largest common whitespace between current line and previous - // winner. - } else { - for (let i = 0; i < margin.length && i < indent.length; i++) { - if (margin[i] !== indent[i]) { - margin = margin.slice(0, i) - break - } - } - } - } - - if (margin) { - text = text.replace(new RegExp('^' + margin, 'mg'), '') - } - return text -} - -module.exports = { wrap, fill, dedent } diff --git a/node_modules/lv_font_conv/node_modules/argparse/package.json b/node_modules/lv_font_conv/node_modules/argparse/package.json deleted file mode 100644 index 22fb0a3e..00000000 --- a/node_modules/lv_font_conv/node_modules/argparse/package.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "_args": [ - [ - "argparse@2.0.1", - "/home/vitaly/Dropbox/Coding/lv_font_conv" - ] - ], - "_from": "argparse@2.0.1", - "_id": "argparse@2.0.1", - "_inBundle": false, - "_integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "_location": "/argparse", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "argparse@2.0.1", - "name": "argparse", - "escapedName": "argparse", - "rawSpec": "2.0.1", - "saveSpec": null, - "fetchSpec": "2.0.1" - }, - "_requiredBy": [ - "/", - "/mocha/js-yaml" - ], - "_resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "_spec": "2.0.1", - "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", - "bugs": { - "url": "https://github.com/nodeca/argparse/issues" - }, - "description": "CLI arguments parser. Native port of python's argparse.", - "devDependencies": { - "@babel/eslint-parser": "^7.11.0", - "@babel/plugin-syntax-class-properties": "^7.10.4", - "eslint": "^7.5.0", - "mocha": "^8.0.1", - "nyc": "^15.1.0" - }, - "files": [ - "argparse.js", - "lib/" - ], - "homepage": "https://github.com/nodeca/argparse#readme", - "keywords": [ - "cli", - "parser", - "argparse", - "option", - "args" - ], - "license": "Python-2.0", - "main": "argparse.js", - "name": "argparse", - "repository": { - "type": "git", - "url": "git+https://github.com/nodeca/argparse.git" - }, - "scripts": { - "coverage": "npm run test && nyc report --reporter html", - "lint": "eslint .", - "test": "npm run lint && nyc mocha" - }, - "version": "2.0.1" -} diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/.github/workflows/ci.yml b/node_modules/lv_font_conv/node_modules/bit-buffer/.github/workflows/ci.yml deleted file mode 100644 index 8f0f168b..00000000 --- a/node_modules/lv_font_conv/node_modules/bit-buffer/.github/workflows/ci.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Node.js CI - -on: [push, pull_request] - -jobs: - test: - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [10.x, 12.x, 14.x, 15.x] - - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: npm ci - - run: npm test - - lint: - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [10.x, 12.x, 14.x, 15.x] - - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: npm ci - - run: npm run lint diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/LICENSE b/node_modules/lv_font_conv/node_modules/bit-buffer/LICENSE deleted file mode 100644 index 9194ecce..00000000 --- a/node_modules/lv_font_conv/node_modules/bit-buffer/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 bit-buffer developers - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/README.md b/node_modules/lv_font_conv/node_modules/bit-buffer/README.md deleted file mode 100644 index 3ecf4ab7..00000000 --- a/node_modules/lv_font_conv/node_modules/bit-buffer/README.md +++ /dev/null @@ -1,148 +0,0 @@ -# BitBuffer - -![Node.js CI](https://github.com/inolen/bit-buffer/workflows/Node.js%20CI/badge.svg) - -BitBuffer provides two objects, `BitView` and `BitStream`. `BitView` is a wrapper for ArrayBuffers, similar to JavaScript's [DataView](https://developer.mozilla.org/en-US/docs/JavaScript/Typed_arrays/DataView), but with support for bit-level reads and writes. `BitStream` is a wrapper for a `BitView` used to help maintain your current buffer position, as well as to provide higher-level read / write operations such as for ASCII strings. - -## BitView - -### Attributes - -```javascript -bb.buffer // Underlying ArrayBuffer. -``` - -```javascript -bb.bigEndian = true; // Switch to big endian (default is little) -``` - -### Methods - -#### BitView(buffer, optional byteOffset, optional byteLength) - -Default constructor, takes in a single argument of an ArrayBuffer. Optional are the `byteOffset` and `byteLength` arguments to offset and truncate the view's representation of the buffer. - -### getBits(offset, bits, signed) - -Reads `bits` number of bits starting at `offset`, twiddling the bits appropriately to return a proper 32-bit signed or unsigned value. NOTE: While JavaScript numbers are 64-bit floating-point values, we don't bother with anything other than the first 32 bits. - -### getInt8, getUint8, getInt16, getUint16, getInt32, getUint32(offset) - -Shortcuts for getBits, setting the correct `bits` / `signed` values. - -### getFloat32(offset) - -Gets 32 bits from `offset`, and coerces and returns as a proper float32 value. - -### getFloat64(offset) - -Gets 64 bits from `offset`, and coerces and returns as a proper float64 value. - -### setBits(offset, value, bits) - -Sets `bits` number of bits at `offset`. - -### setInt8, setUint8, setInt16, setUint16, setInt32, setUint32(offset) - -Shortcuts for setBits, setting the correct `bits` count. - -### setFloat32(offset) - -Coerces a float32 to uint32 and sets at `offset`. - -### setFloat64(offset) - -Coerces a float64 to two uint32s and sets at `offset`. - - -## BitStream - -### Attributes - -```javascript -bb.byteIndex; // Get current index in bytes. -bb.byteIndex = 0; // Set current index in bytes. -``` - -```javascript -bb.view; // Underlying BitView -``` - -```javascript -bb.length; // Get the length of the stream in bits -``` - -```javascript -bb.bitsLeft; // The number of bits left in the stream -``` - -```javascript -bb.index; // Get the current index in bits -bb.index = 0// Set the current index in bits -``` - -```javascript -bb.bigEndian = true; // Switch to big endian (default is little) -``` - -### Methods - -#### BitStream(view) - -Default constructor, takes in a single argument of a `BitView`, `ArrayBuffer` or node `Buffer`. - -#### BitSteam(buffer, optional byteOffset, optional byteLength) - -Shortcut constructor that initializes a new `BitView(buffer, byteOffset, byteLength)` for the stream to use. - -#### readBits(bits, signed) - -Returns `bits` numbers of bits from the view at the current index, updating the index. - -#### writeBits(value, bits) - -Sets `bits` numbers of bits from `value` in the view at the current index, updating the index. - -#### readUint8(), readUint16(), readUint32(), readInt8(), readInt16(), readInt32() - -Read a 8, 16 or 32 bits (unsigned) integer at the current index, updating the index. - -#### writeUint8(value), writeUint16(value), writeUint32(value), writeInt8(value), writeInt16(value), writeInt32(value) - -Write 8, 16 or 32 bits from `value` as (unsigned) integer at the current index, updating the index. - -#### readFloat32(), readFloat64() - -Read a 32 or 64 bit floating point number at the current index, updating the index. - -#### writeFloat32(value), writeFloat64() - -Set 32 or 64 bits from `value` as floating point value at the current index, updating the index. - -#### readBoolean() - -Read a single bit from the view at the current index, updating the index. - -#### writeBoolean(value) - -Write a single bit to the view at the current index, updating the index. - -#### readASCIIString(optional bytes), readUTF8String(optional bytes) - -Reads bytes from the underlying view at the current index until either `bytes` count is reached or a 0x00 terminator is reached. - -#### writeASCIIString(string, optional bytes), writeUTF8String(string, optional bytes) - -Writes a string followed by a NULL character to the underlying view starting at the current index. If the string is longer than `bytes` it will be truncated, and if it is shorter 0x00 will be written in its place. - -#### readBitStream(length) - -Create a new `BitStream` from the underlying view starting the the current index and a length of `length` bits. Updating the index of the existing `BitStream` - -#### readArrayBuffer(byteLength) - -Read `byteLength` bytes of data from the underlying view as `ArrayBuffer`, updating the index. - -## license - -MIT diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.d.ts b/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.d.ts deleted file mode 100644 index 72c4b13e..00000000 --- a/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.d.ts +++ /dev/null @@ -1,115 +0,0 @@ -declare module 'bit-buffer' { - import {Buffer} from 'buffer'; - - export class BitView { - constructor(buffer: ArrayBuffer | Buffer, byteLength?: number); - - readonly buffer: Buffer; - readonly byteLength: number; - bigEndian: boolean; - - getBits(offset: number, bits: number, signed?: boolean): number; - - getInt8(offset: number): number; - - getInt16(offset: number): number; - - getInt32(offset: number): number; - - getUint8(offset: number): number; - - getUint16(offset: number): number; - - getUint32(offset: number): number; - - getFloat32(offset: number): number; - - getFloat64(offset: number): number; - - setBits(offset: number, value: number, bits: number); - - setInt8(offset: number); - - setInt16(offset: number); - - setInt32(offset: number); - - setUint8(offset: number); - - setUint16(offset: number); - - setUint32(offset: number); - - setFloat32(offset: number, value: number); - - setFloat64(offset: number, value: number); - } - - export class BitStream { - constructor(source: ArrayBuffer | Buffer | BitView, byteOffset?: number, byteLength?: number) - - readonly length: number; - readonly bitsLeft: number; - readonly buffer: Buffer; - readonly view: BitView; - byteIndex: number; - index: number; - bigEndian: boolean; - - readBits(bits: number, signed?: boolean): number; - - writeBits(value: number, bits: number); - - readBoolean(): boolean; - - readInt8(): number; - - readUint8(): number; - - readInt16(): number; - - readUint16(): number; - - readInt32(): number; - - readUint32(): number; - - readFloat32(): number; - - readFloat64(): number; - - writeBoolean(value: boolean); - - writeInt8(value: number); - - writeUint8(value: number); - - writeInt16(value: number); - - writeUint16(value: number); - - writeInt32(value: number); - - writeUint32(value: number); - - writeFloat32(value: number); - - writeFloat64(value: number); - - readASCIIString(length?: number): string; - - readUTF8String(length?: number): string; - - writeASCIIString(data: string, length?: number); - - writeUTF8String(data: string, length?: number); - - readBitStream(length: number): BitStream; - - readArrayBuffer(byteLength: number): Uint8Array; - - writeBitStream(stream: BitStream, length?: number); - - writeArrayBuffer(buffer: BitStream, length?: number); - } -} diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.js b/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.js deleted file mode 100644 index 60c7f795..00000000 --- a/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.js +++ /dev/null @@ -1,502 +0,0 @@ -(function (root) { - -/********************************************************** - * - * BitView - * - * BitView provides a similar interface to the standard - * DataView, but with support for bit-level reads / writes. - * - **********************************************************/ -var BitView = function (source, byteOffset, byteLength) { - var isBuffer = source instanceof ArrayBuffer || - (typeof Buffer !== 'undefined' && source instanceof Buffer); - - if (!isBuffer) { - throw new Error('Must specify a valid ArrayBuffer or Buffer.'); - } - - byteOffset = byteOffset || 0; - byteLength = byteLength || source.byteLength /* ArrayBuffer */ || source.length /* Buffer */; - - this._view = new Uint8Array(source.buffer || source, byteOffset, byteLength); - - this.bigEndian = false; -}; - -// Used to massage fp values so we can operate on them -// at the bit level. -BitView._scratch = new DataView(new ArrayBuffer(8)); - -Object.defineProperty(BitView.prototype, 'buffer', { - get: function () { return typeof Buffer !== 'undefined' ? Buffer.from(this._view.buffer) : this._view.buffer; }, - enumerable: true, - configurable: false -}); - -Object.defineProperty(BitView.prototype, 'byteLength', { - get: function () { return this._view.length; }, - enumerable: true, - configurable: false -}); - -BitView.prototype._setBit = function (offset, on) { - if (on) { - this._view[offset >> 3] |= 1 << (offset & 7); - } else { - this._view[offset >> 3] &= ~(1 << (offset & 7)); - } -}; - -BitView.prototype.getBits = function (offset, bits, signed) { - var available = (this._view.length * 8 - offset); - - if (bits > available) { - throw new Error('Cannot get ' + bits + ' bit(s) from offset ' + offset + ', ' + available + ' available'); - } - - var value = 0; - for (var i = 0; i < bits;) { - var remaining = bits - i; - var bitOffset = offset & 7; - var currentByte = this._view[offset >> 3]; - - // the max number of bits we can read from the current byte - var read = Math.min(remaining, 8 - bitOffset); - - var mask, readBits; - if (this.bigEndian) { - // create a mask with the correct bit width - mask = ~(0xFF << read); - // shift the bits we want to the start of the byte and mask of the rest - readBits = (currentByte >> (8 - read - bitOffset)) & mask; - - value <<= read; - value |= readBits; - } else { - // create a mask with the correct bit width - mask = ~(0xFF << read); - // shift the bits we want to the start of the byte and mask off the rest - readBits = (currentByte >> bitOffset) & mask; - - value |= readBits << i; - } - - offset += read; - i += read; - } - - if (signed) { - // If we're not working with a full 32 bits, check the - // imaginary MSB for this bit count and convert to a - // valid 32-bit signed value if set. - if (bits !== 32 && value & (1 << (bits - 1))) { - value |= -1 ^ ((1 << bits) - 1); - } - - return value; - } - - return value >>> 0; -}; - -BitView.prototype.setBits = function (offset, value, bits) { - var available = (this._view.length * 8 - offset); - - if (bits > available) { - throw new Error('Cannot set ' + bits + ' bit(s) from offset ' + offset + ', ' + available + ' available'); - } - - for (var i = 0; i < bits;) { - var remaining = bits - i; - var bitOffset = offset & 7; - var byteOffset = offset >> 3; - var wrote = Math.min(remaining, 8 - bitOffset); - - var mask, writeBits, destMask; - if (this.bigEndian) { - // create a mask with the correct bit width - mask = ~(~0 << wrote); - // shift the bits we want to the start of the byte and mask of the rest - writeBits = (value >> (bits - i - wrote)) & mask; - - var destShift = 8 - bitOffset - wrote; - // destination mask to zero all the bits we're changing first - destMask = ~(mask << destShift); - - this._view[byteOffset] = - (this._view[byteOffset] & destMask) - | (writeBits << destShift); - - } else { - // create a mask with the correct bit width - mask = ~(0xFF << wrote); - // shift the bits we want to the start of the byte and mask of the rest - writeBits = value & mask; - value >>= wrote; - - // destination mask to zero all the bits we're changing first - destMask = ~(mask << bitOffset); - - this._view[byteOffset] = - (this._view[byteOffset] & destMask) - | (writeBits << bitOffset); - } - - offset += wrote; - i += wrote; - } -}; - -BitView.prototype.getBoolean = function (offset) { - return this.getBits(offset, 1, false) !== 0; -}; -BitView.prototype.getInt8 = function (offset) { - return this.getBits(offset, 8, true); -}; -BitView.prototype.getUint8 = function (offset) { - return this.getBits(offset, 8, false); -}; -BitView.prototype.getInt16 = function (offset) { - return this.getBits(offset, 16, true); -}; -BitView.prototype.getUint16 = function (offset) { - return this.getBits(offset, 16, false); -}; -BitView.prototype.getInt32 = function (offset) { - return this.getBits(offset, 32, true); -}; -BitView.prototype.getUint32 = function (offset) { - return this.getBits(offset, 32, false); -}; -BitView.prototype.getFloat32 = function (offset) { - BitView._scratch.setUint32(0, this.getUint32(offset)); - return BitView._scratch.getFloat32(0); -}; -BitView.prototype.getFloat64 = function (offset) { - BitView._scratch.setUint32(0, this.getUint32(offset)); - // DataView offset is in bytes. - BitView._scratch.setUint32(4, this.getUint32(offset+32)); - return BitView._scratch.getFloat64(0); -}; - -BitView.prototype.setBoolean = function (offset, value) { - this.setBits(offset, value ? 1 : 0, 1); -}; -BitView.prototype.setInt8 = -BitView.prototype.setUint8 = function (offset, value) { - this.setBits(offset, value, 8); -}; -BitView.prototype.setInt16 = -BitView.prototype.setUint16 = function (offset, value) { - this.setBits(offset, value, 16); -}; -BitView.prototype.setInt32 = -BitView.prototype.setUint32 = function (offset, value) { - this.setBits(offset, value, 32); -}; -BitView.prototype.setFloat32 = function (offset, value) { - BitView._scratch.setFloat32(0, value); - this.setBits(offset, BitView._scratch.getUint32(0), 32); -}; -BitView.prototype.setFloat64 = function (offset, value) { - BitView._scratch.setFloat64(0, value); - this.setBits(offset, BitView._scratch.getUint32(0), 32); - this.setBits(offset+32, BitView._scratch.getUint32(4), 32); -}; -BitView.prototype.getArrayBuffer = function (offset, byteLength) { - var buffer = new Uint8Array(byteLength); - for (var i = 0; i < byteLength; i++) { - buffer[i] = this.getUint8(offset + (i * 8)); - } - return buffer; -}; - -/********************************************************** - * - * BitStream - * - * Small wrapper for a BitView to maintain your position, - * as well as to handle reading / writing of string data - * to the underlying buffer. - * - **********************************************************/ -var reader = function (name, size) { - return function () { - if (this._index + size > this._length) { - throw new Error('Trying to read past the end of the stream'); - } - var val = this._view[name](this._index); - this._index += size; - return val; - }; -}; - -var writer = function (name, size) { - return function (value) { - this._view[name](this._index, value); - this._index += size; - }; -}; - -function readASCIIString(stream, bytes) { - return readString(stream, bytes, false); -} - -function readUTF8String(stream, bytes) { - return readString(stream, bytes, true); -} - -function readString(stream, bytes, utf8) { - if (bytes === 0) { - return ''; - } - var i = 0; - var chars = []; - var append = true; - var fixedLength = !!bytes; - if (!bytes) { - bytes = Math.floor((stream._length - stream._index) / 8); - } - - // Read while we still have space available, or until we've - // hit the fixed byte length passed in. - while (i < bytes) { - var c = stream.readUint8(); - - // Stop appending chars once we hit 0x00 - if (c === 0x00) { - append = false; - - // If we don't have a fixed length to read, break out now. - if (!fixedLength) { - break; - } - } - if (append) { - chars.push(c); - } - - i++; - } - - var string = String.fromCharCode.apply(null, chars); - if (utf8) { - try { - return decodeURIComponent(escape(string)); // https://stackoverflow.com/a/17192845 - } catch (e) { - return string; - } - } else { - return string; - } -} - -function writeASCIIString(stream, string, bytes) { - var length = bytes || string.length + 1; // + 1 for NULL - - for (var i = 0; i < length; i++) { - stream.writeUint8(i < string.length ? string.charCodeAt(i) : 0x00); - } -} - -function writeUTF8String(stream, string, bytes) { - var byteArray = stringToByteArray(string); - - var length = bytes || byteArray.length + 1; // + 1 for NULL - for (var i = 0; i < length; i++) { - stream.writeUint8(i < byteArray.length ? byteArray[i] : 0x00); - } -} - -function stringToByteArray(str) { // https://gist.github.com/volodymyr-mykhailyk/2923227 - var b = [], i, unicode; - for (i = 0; i < str.length; i++) { - unicode = str.charCodeAt(i); - // 0x00000000 - 0x0000007f -> 0xxxxxxx - if (unicode <= 0x7f) { - b.push(unicode); - // 0x00000080 - 0x000007ff -> 110xxxxx 10xxxxxx - } else if (unicode <= 0x7ff) { - b.push((unicode >> 6) | 0xc0); - b.push((unicode & 0x3F) | 0x80); - // 0x00000800 - 0x0000ffff -> 1110xxxx 10xxxxxx 10xxxxxx - } else if (unicode <= 0xffff) { - b.push((unicode >> 12) | 0xe0); - b.push(((unicode >> 6) & 0x3f) | 0x80); - b.push((unicode & 0x3f) | 0x80); - // 0x00010000 - 0x001fffff -> 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - } else { - b.push((unicode >> 18) | 0xf0); - b.push(((unicode >> 12) & 0x3f) | 0x80); - b.push(((unicode >> 6) & 0x3f) | 0x80); - b.push((unicode & 0x3f) | 0x80); - } - } - - return b; -} - -var BitStream = function (source, byteOffset, byteLength) { - var isBuffer = source instanceof ArrayBuffer || - (typeof Buffer !== 'undefined' && source instanceof Buffer); - - if (!(source instanceof BitView) && !isBuffer) { - throw new Error('Must specify a valid BitView, ArrayBuffer or Buffer'); - } - - if (isBuffer) { - this._view = new BitView(source, byteOffset, byteLength); - } else { - this._view = source; - } - - this._index = 0; - this._startIndex = 0; - this._length = this._view.byteLength * 8; -}; - -Object.defineProperty(BitStream.prototype, 'index', { - get: function () { return this._index - this._startIndex; }, - set: function (val) { this._index = val + this._startIndex; }, - enumerable: true, - configurable: true -}); - -Object.defineProperty(BitStream.prototype, 'length', { - get: function () { return this._length - this._startIndex; }, - set: function (val) { this._length = val + this._startIndex; }, - enumerable : true, - configurable: true -}); - -Object.defineProperty(BitStream.prototype, 'bitsLeft', { - get: function () { return this._length - this._index; }, - enumerable : true, - configurable: true -}); - -Object.defineProperty(BitStream.prototype, 'byteIndex', { - // Ceil the returned value, over compensating for the amount of - // bits written to the stream. - get: function () { return Math.ceil(this._index / 8); }, - set: function (val) { this._index = val * 8; }, - enumerable: true, - configurable: true -}); - -Object.defineProperty(BitStream.prototype, 'buffer', { - get: function () { return this._view.buffer; }, - enumerable: true, - configurable: false -}); - -Object.defineProperty(BitStream.prototype, 'view', { - get: function () { return this._view; }, - enumerable: true, - configurable: false -}); - -Object.defineProperty(BitStream.prototype, 'bigEndian', { - get: function () { return this._view.bigEndian; }, - set: function (val) { this._view.bigEndian = val; }, - enumerable: true, - configurable: false -}); - -BitStream.prototype.readBits = function (bits, signed) { - var val = this._view.getBits(this._index, bits, signed); - this._index += bits; - return val; -}; - -BitStream.prototype.writeBits = function (value, bits) { - this._view.setBits(this._index, value, bits); - this._index += bits; -}; - -BitStream.prototype.readBoolean = reader('getBoolean', 1); -BitStream.prototype.readInt8 = reader('getInt8', 8); -BitStream.prototype.readUint8 = reader('getUint8', 8); -BitStream.prototype.readInt16 = reader('getInt16', 16); -BitStream.prototype.readUint16 = reader('getUint16', 16); -BitStream.prototype.readInt32 = reader('getInt32', 32); -BitStream.prototype.readUint32 = reader('getUint32', 32); -BitStream.prototype.readFloat32 = reader('getFloat32', 32); -BitStream.prototype.readFloat64 = reader('getFloat64', 64); - -BitStream.prototype.writeBoolean = writer('setBoolean', 1); -BitStream.prototype.writeInt8 = writer('setInt8', 8); -BitStream.prototype.writeUint8 = writer('setUint8', 8); -BitStream.prototype.writeInt16 = writer('setInt16', 16); -BitStream.prototype.writeUint16 = writer('setUint16', 16); -BitStream.prototype.writeInt32 = writer('setInt32', 32); -BitStream.prototype.writeUint32 = writer('setUint32', 32); -BitStream.prototype.writeFloat32 = writer('setFloat32', 32); -BitStream.prototype.writeFloat64 = writer('setFloat64', 64); - -BitStream.prototype.readASCIIString = function (bytes) { - return readASCIIString(this, bytes); -}; - -BitStream.prototype.readUTF8String = function (bytes) { - return readUTF8String(this, bytes); -}; - -BitStream.prototype.writeASCIIString = function (string, bytes) { - writeASCIIString(this, string, bytes); -}; - -BitStream.prototype.writeUTF8String = function (string, bytes) { - writeUTF8String(this, string, bytes); -}; -BitStream.prototype.readBitStream = function(bitLength) { - var slice = new BitStream(this._view); - slice._startIndex = this._index; - slice._index = this._index; - slice.length = bitLength; - this._index += bitLength; - return slice; -}; - -BitStream.prototype.writeBitStream = function(stream, length) { - if (!length) { - length = stream.bitsLeft; - } - - var bitsToWrite; - while (length > 0) { - bitsToWrite = Math.min(length, 32); - this.writeBits(stream.readBits(bitsToWrite), bitsToWrite); - length -= bitsToWrite; - } -}; - -BitStream.prototype.readArrayBuffer = function(byteLength) { - var buffer = this._view.getArrayBuffer(this._index, byteLength); - this._index += (byteLength * 8); - return buffer; -}; - -BitStream.prototype.writeArrayBuffer = function(buffer, byteLength) { - this.writeBitStream(new BitStream(buffer), byteLength * 8); -}; - -// AMD / RequireJS -if (typeof define !== 'undefined' && define.amd) { - define(function () { - return { - BitView: BitView, - BitStream: BitStream - }; - }); -} -// Node.js -else if (typeof module !== 'undefined' && module.exports) { - module.exports = { - BitView: BitView, - BitStream: BitStream - }; -} - -}(this)); diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/package.json b/node_modules/lv_font_conv/node_modules/bit-buffer/package.json deleted file mode 100644 index f2e2d2ce..00000000 --- a/node_modules/lv_font_conv/node_modules/bit-buffer/package.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "_args": [ - [ - "bit-buffer@0.2.5", - "/home/vitaly/Dropbox/Coding/lv_font_conv" - ] - ], - "_from": "bit-buffer@0.2.5", - "_id": "bit-buffer@0.2.5", - "_inBundle": false, - "_integrity": "sha512-x1yGnmXvFg6e3DiyRztElbcn1bsCTFSoM/ncAzY62uE0JdTl5xlKJd0ooqLYoPbhdsnpehSIQrdIvclcZJYwiA==", - "_location": "/bit-buffer", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "bit-buffer@0.2.5", - "name": "bit-buffer", - "escapedName": "bit-buffer", - "rawSpec": "0.2.5", - "saveSpec": null, - "fetchSpec": "0.2.5" - }, - "_requiredBy": [ - "/" - ], - "_resolved": "https://registry.npmjs.org/bit-buffer/-/bit-buffer-0.2.5.tgz", - "_spec": "0.2.5", - "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", - "author": { - "name": "Anthony Pesch" - }, - "bugs": { - "url": "https://github.com/inolen/bit-buffer/issues" - }, - "contributors": [ - { - "name": "Robin Appelman" - } - ], - "description": "Bit-level reads and writes for ArrayBuffers", - "devDependencies": { - "@types/node": "^14.14.22", - "jshint": "^2.12.0", - "mocha": "^8.2.1" - }, - "directories": { - "test": "test" - }, - "gitHead": "cd4417237bed1f22dd5adfd8a6b961ea7234d9c9", - "homepage": "https://github.com/inolen/bit-buffer#readme", - "keywords": [ - "dataview", - "arraybuffer", - "bit", - "bits" - ], - "license": "MIT", - "main": "bit-buffer.js", - "name": "bit-buffer", - "repository": { - "type": "git", - "url": "git://github.com/inolen/bit-buffer.git" - }, - "scripts": { - "lint": "jshint bit-buffer.js", - "test": "mocha --ui tdd" - }, - "types": "./bit-buffer.d.ts", - "version": "0.2.5" -} diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/test.js b/node_modules/lv_font_conv/node_modules/bit-buffer/test.js deleted file mode 100644 index a53fc87b..00000000 --- a/node_modules/lv_font_conv/node_modules/bit-buffer/test.js +++ /dev/null @@ -1,628 +0,0 @@ -var assert = require('assert'), - BitView = require('./bit-buffer').BitView, - BitStream = require('./bit-buffer').BitStream; - -suite('BitBuffer', function () { - var array, bv, bsw, bsr; - - setup(function () { - array = new ArrayBuffer(64); - bv = new BitView(array); - bsw = new BitStream(bv); - // Test initializing straight from the array. - bsr = new BitStream(array); - }); - - test('Min / max signed 5 bits', function () { - var signed_max = (1 << 4) - 1; - - bsw.writeBits(signed_max, 5); - bsw.writeBits(-signed_max - 1, 5); - assert(bsr.readBits(5, true) === signed_max); - assert(bsr.readBits(5, true) === -signed_max - 1); - }); - - test('Min / max unsigned 5 bits', function () { - var unsigned_max = (1 << 5) - 1; - - bsw.writeBits(unsigned_max, 5); - bsw.writeBits(-unsigned_max, 5); - assert.equal(bsr.readBits(5), unsigned_max); - assert.equal(bsr.readBits(5), 1); - }); - - test('Min / max int8', function () { - var signed_max = 0x7F; - - bsw.writeInt8(signed_max); - bsw.writeInt8(-signed_max - 1); - assert.equal(bsr.readInt8(), signed_max); - assert.equal(bsr.readInt8(), -signed_max - 1); - }); - - test('Min / max uint8', function () { - var unsigned_max = 0xFF; - - bsw.writeUint8(unsigned_max); - bsw.writeUint8(-unsigned_max); - assert.equal(bsr.readUint8(), unsigned_max); - assert.equal(bsr.readUint8(), 1); - }); - - test('Min / max int16', function () { - var signed_max = 0x7FFF; - - bsw.writeInt16(signed_max); - bsw.writeInt16(-signed_max - 1); - assert.equal(bsr.readInt16(), signed_max); - assert.equal(bsr.readInt16(), -signed_max - 1); - }); - - test('Min / max uint16', function () { - var unsigned_max = 0xFFFF; - - bsw.writeUint16(unsigned_max); - bsw.writeUint16(-unsigned_max); - assert.equal(bsr.readUint16(), unsigned_max); - assert.equal(bsr.readUint16(), 1); - }); - - test('Min / max int32', function () { - var signed_max = 0x7FFFFFFF; - - bsw.writeInt32(signed_max); - bsw.writeInt32(-signed_max - 1); - assert.equal(bsr.readInt32(), signed_max); - assert.equal(bsr.readInt32(), -signed_max - 1); - }); - - test('Min / max uint32', function () { - var unsigned_max = 0xFFFFFFFF; - - bsw.writeUint32(unsigned_max); - bsw.writeUint32(-unsigned_max); - assert.equal(bsr.readUint32(), unsigned_max); - assert.equal(bsr.readUint32(), 1); - }); - - test('Unaligned reads', function () { - bsw.writeBits(13, 5); - bsw.writeUint8(0xFF); - bsw.writeBits(14, 5); - - assert.equal(bsr.readBits(5), 13); - assert.equal(bsr.readUint8(), 0xFF); - assert.equal(bsr.readBits(5), 14); - }); - - test('Min / max float32 (normal values)', function () { - var scratch = new DataView(new ArrayBuffer(8)); - - scratch.setUint32(0, 0x00800000); - scratch.setUint32(4, 0x7f7fffff); - - var min = scratch.getFloat32(0); - var max = scratch.getFloat32(4); - - bsw.writeFloat32(min); - bsw.writeFloat32(max); - - assert.equal(bsr.readFloat32(), min); - assert.equal(bsr.readFloat32(), max); - }); - - test('Min / max float64 (normal values)', function () { - var scratch = new DataView(new ArrayBuffer(16)); - - scratch.setUint32(0, 0x00100000); - scratch.setUint32(4, 0x00000000); - scratch.setUint32(8, 0x7fefffff); - scratch.setUint32(12, 0xffffffff); - - var min = scratch.getFloat64(0); - var max = scratch.getFloat64(8); - - bsw.writeFloat64(min); - bsw.writeFloat64(max); - - assert.equal(bsr.readFloat64(), min); - assert.equal(bsr.readFloat64(), max); - }); - - test('Overwrite previous value with 0', function () { - bv.setUint8(0, 13); - bv.setUint8(0, 0); - - assert.equal(bv.getUint8(0), 0); - }); - - test('Read / write ASCII string, fixed length', function () { - var str = 'foobar'; - var len = 16; - - bsw.writeASCIIString(str, len); - assert.equal(bsw.byteIndex, len); - - assert.equal(bsr.readASCIIString(len), str); - assert.equal(bsr.byteIndex, len); - }); - - test('Read / write ASCII string, unknown length', function () { - var str = 'foobar'; - - bsw.writeASCIIString(str); - assert.equal(bsw.byteIndex, str.length + 1); // +1 for 0x00 - - assert.equal(bsr.readASCIIString(), str); - assert.equal(bsr.byteIndex, str.length + 1); - }); - - test('Read ASCII string, 0 length', function () { - var str = 'foobar'; - - bsw.writeASCIIString(str); - assert.equal(bsw.byteIndex, str.length + 1); // +1 for 0x00 - - assert.equal(bsr.readASCIIString(0), ''); - assert.equal(bsr.byteIndex, 0); - }); - - test('Read overflow', function () { - var exception = false; - - try { - bsr.readASCIIString(128); - } catch (e) { - exception = true; - } - - assert(exception); - }); - - test('Write overflow', function () { - var exception = false; - - try { - bsw.writeASCIIString('foobar', 128); - } catch (e) { - exception = true; - } - - assert(exception); - }); - - test('Get boolean', function () { - bv.setUint8(0, 1); - - assert(bv.getBoolean(0)); - - bv.setUint8(0, 0); - assert(!bv.getBoolean(0)); - }); - - test('Set boolean', function () { - bv.setBoolean(0, true); - - assert(bv.getBoolean(0)); - - bv.setBoolean(0, false); - - assert(!bv.getBoolean(0)); - }); - - test('Read boolean', function () { - bv.setBits(0, 1, 1); - bv.setBits(1, 0, 1); - - assert(bsr.readBoolean()); - assert(!bsr.readBoolean()); - }); - - test('Write boolean', function () { - bsr.writeBoolean(true); - assert.equal(bv.getBits(0, 1, false), 1); - bsr.writeBoolean(false); - assert.equal(bv.getBits(1, 1, false), 0); - }); - - test('Read / write UTF8 string, only ASCII characters', function () { - var str = 'foobar'; - - bsw.writeUTF8String(str); - assert(bsw.byteIndex === str.length + 1); // +1 for 0x00 - - assert.equal(bsr.readUTF8String(), str); - assert.equal(bsr.byteIndex, str.length + 1); - }); - - test('Read / write UTF8 string, non ASCII characters', function () { - var str = '日本語'; - - var bytes = [ - 0xE6, - 0x97, - 0xA5, - 0xE6, - 0x9C, - 0xAC, - 0xE8, - 0xAA, - 0x9E - ]; - - bsw.writeUTF8String(str); - - for (var i = 0; i < bytes.length; i++) { - assert.equal(bytes[i], bv.getBits(i * 8, 8)); - } - - assert.equal(bsw.byteIndex, bytes.length + 1); // +1 for 0x00 - - assert.equal(str, bsr.readUTF8String()); - assert.equal(bsr.byteIndex, bytes.length + 1); - }); - - test('readBitStream', function () { - bsw.writeBits(0xF0, 8); //0b11110000 - bsw.writeBits(0xF1, 8); //0b11110001 - bsr.readBits(3); //offset - var slice = bsr.readBitStream(8); - assert.equal(slice.readBits(6), 0x3E); //0b111110 - assert.equal(9, slice._index); - assert.equal(6, slice.index); - assert.equal(8, slice.length); - assert.equal(2, slice.bitsLeft); - - assert.equal(bsr._index, 11); - assert.equal((64 * 8) - 11, bsr.bitsLeft); - }); - - test('readBitStream overflow', function () { - bsw.writeBits(0xF0, 8); //0b11110000 - bsw.writeBits(0xF1, 8); //0b11110001 - bsr.readBits(3); //offset - var slice = bsr.readBitStream(4); - - var exception = false; - - try { - slice.readUint8(); - } catch (e) { - exception = true; - } - - assert(exception); - }); - - test('writeBitStream', function () { - var buf = new ArrayBuffer(64); - var sourceStream = new BitStream(buf); - - sourceStream.writeBits(0xF0, 8); //0b11110000 - sourceStream.writeBits(0xF1, 8); //0b11110001 - sourceStream.index = 0; - sourceStream.readBits(3); //offset - bsr.writeBitStream(sourceStream, 8); - assert.equal(8, bsr.index); - bsr.index = 0; - assert.equal(bsr.readBits(6), 0x3E); //0b00111110 - assert.equal(11, sourceStream.index); - - var bin = new Uint8Array(buf); - assert.equal(bin[0], 0xF0); - assert.equal(bin[1], 0xF1); - }); - - test('writeBitStream Buffer', function () { - var buf = Buffer.alloc(64); - var sourceStream = new BitStream(buf); - - sourceStream.writeBits(0xF0, 8); //0b11110000 - sourceStream.writeBits(0xF1, 8); //0b11110001 - sourceStream.index = 0; - sourceStream.readBits(3); //offset - bsr.writeBitStream(sourceStream, 8); - assert.equal(8, bsr.index); - bsr.index = 0; - assert.equal(bsr.readBits(6), 0x3E); //0b00111110 - assert.equal(11, sourceStream.index); - - var bin = new Uint8Array(buf.buffer); - assert.equal(bin[0], 0xF0); - assert.equal(bin[1], 0xF1); - }); - - test('writeBitStream long', function () { - var sourceStream = new BitStream(new ArrayBuffer(64)); - - sourceStream.writeBits(0xF0, 8); - sourceStream.writeBits(0xF1, 8); - sourceStream.writeBits(0xF1, 8); - sourceStream.writeBits(0xF1, 8); - sourceStream.writeBits(0xF1, 8); - sourceStream.index = 0; - sourceStream.readBits(3); //offset - bsr.index = 3; - bsr.writeBitStream(sourceStream, 35); - assert.equal(38, bsr.index); - bsr.index = 3; - assert.equal(bsr.readBits(35), 1044266558); - assert.equal(38, sourceStream.index); - }); - - test('readArrayBuffer', function () { - bsw.writeBits(0xF0, 8); //0b11110000 - bsw.writeBits(0xF1, 8); //0b11110001 - bsw.writeBits(0xF0, 8); //0b11110000 - bsr.readBits(3); //offset - - var buffer = bsr.readArrayBuffer(2); - - assert.equal(0x3E, buffer[0]); //0b00111110 - assert.equal(0x1E, buffer[1]); //0b00011110 - - assert.equal(3 + (2 * 8), bsr._index); - }); - - test('writeArrayBuffer', function () { - var source = new Uint8Array(4); - source[0] = 0xF0; - source[1] = 0xF1; - source[2] = 0xF1; - bsr.readBits(3); //offset - - bsr.writeArrayBuffer(source.buffer, 2); - assert.equal(19, bsr.index); - - bsr.index = 0; - - assert.equal(bsr.readBits(8), 128); - }); - - test('Get buffer from view', function () { - bv.setBits(0, 0xFFFFFFFF, 32); - var buffer = bv.buffer; - - assert.equal(64, buffer.length); - assert.equal(0xFFFF, buffer.readUInt16LE(0)); - }); - - test('Get buffer from stream', function () { - bsw.writeBits(0xFFFFFFFF, 32); - var buffer = bsr.buffer; - - assert.equal(64, buffer.length); - assert.equal(0xFFFF, buffer.readUInt16LE(0)); - }); -}); - -suite('Reading big/little endian', function () { - var array, u8, bv, bsw, bsr; - - setup(function () { - array = new ArrayBuffer(64); - u8 = new Uint8Array(array); - u8[0] = 0x01; - u8[1] = 0x02; - // Test initializing straight from the array. - bsr = new BitStream(array); - }); - - test('4b, little-endian', function () { - assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); - - var result = []; - result.push(bsr.readBits(4)); - result.push(bsr.readBits(4)); - result.push(bsr.readBits(4)); - result.push(bsr.readBits(4)); - - // 0000 0001 0000 0010 [01 02] - // [#2] [#1] [#4] [#3] - assert.deepEqual(result, [1, 0, 2, 0]); - }); - - test('8b, little-endian', function () { - assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); - - var result = []; - result.push(bsr.readBits(8)); - result.push(bsr.readBits(8)); - - // 0000 0001 0000 0010 [01 02] - // [ #1] [ #2] - assert.deepEqual(result, [1, 2]); - }); - - test('10b, little-endian', function () { - assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); - - var result = []; - result.push(bsr.readBits(10)); - - // 0000 0001 0000 0010 [01 02] - // ... #1] [ #2][#1... - assert.deepEqual(result, [513]); - }); - - test('16b, little-endian', function () { - assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); - - var result = []; - result.push(bsr.readBits(16)); - - // 0000 0001 0000 0010 [01 02] - // [ #1] - assert.deepEqual(result, [0x201]); - }); - - test('24b, little-endian', function () { - u8[2] = 0x03; - assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); - - var result = []; - result.push(bsr.readBits(24)); - - // 0000 0001 0000 0010 0000 0011 [01 02 03] - // [ #1] - assert.deepEqual(result, [0x30201]); - }); - - test('4b, big-endian', function () { - bsr.bigEndian = true; - assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); - - var result = []; - result.push(bsr.readBits(4)); - result.push(bsr.readBits(4)); - result.push(bsr.readBits(4)); - result.push(bsr.readBits(4)); - - // 0000 0001 0000 0010 [01 02] - // [#1] [#2] [#3] [#4] - assert.deepEqual(result, [0, 1, 0, 2]); - }); - - test('8b, big-endian', function () { - bsr.bigEndian = true; - assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); - - var result = []; - result.push(bsr.readBits(8)); - result.push(bsr.readBits(8)); - - // 0000 0001 0000 0010 [01 02] - // [ #1] [ #2] - assert.deepEqual(result, [1, 2]); - }); - - test('10b, big-endian', function () { - bsr.bigEndian = true; - assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); - - var result = []; - result.push(bsr.readBits(10)); - result.push(bsr.readBits(6)); - - // 0000 0001 0000 0010 [01 02] - // [ #1][ #2] - assert.deepEqual(result, [4, 2]); - }); - - test('16b, big-endian', function () { - bsr.bigEndian = true; - assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); - - var result = []; - result.push(bsr.readBits(16)); - - // 0000 0001 0000 0010 [01 02] - // [ #1] - assert.deepEqual(result, [0x102]); - }); - - test('24b, big-endian', function () { - u8[2] = 0x03; - bsr.bigEndian = true; - assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); - - var result = []; - result.push(bsr.readBits(24)); - - // 0000 0001 0000 0010 0000 0011 [01 02 03] - // [ #1] - assert.deepEqual(result, [0x10203]); - }); -}); - -suite('Writing big/little endian', function () { - var array, u8, bv, bsw, bsr; - - setup(function () { - array = new ArrayBuffer(2); - u8 = new Uint8Array(array); - bv = new BitView(array); - bsw = new BitStream(bv); - }); - - test('4b, little-endian', function () { - // 0000 0001 0000 0010 [01 02] - // [#2] [#1] [#4] [#3] - bsw.writeBits(1, 4); - bsw.writeBits(0, 4); - bsw.writeBits(2, 4); - bsw.writeBits(0, 4); - - assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); - }); - - test('8b, little-endian', function () { - // 0000 0001 0000 0010 [01 02] - // [ #1] [ #2] - bsw.writeBits(1, 8); - bsw.writeBits(2, 8); - - assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); - }); - - test('10b, little-endian', function () { - // 0000 0001 0000 0010 [01 02] - // ... #1] [ #2][#1... - bsw.writeBits(513, 10); - - assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); - }); - - test('16b, little-endian', function () { - // 0000 0001 0000 0010 [01 02] - // [ #1] - bsw.writeBits(0x201, 16); - - assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); - }); - - test('4b, big-endian', function () { - bsw.bigEndian = true; - - // 0000 0001 0000 0010 [01 02] - // [#1] [#2] [#3] [#4] - bsw.writeBits(0, 4); - bsw.writeBits(1, 4); - bsw.writeBits(0, 4); - bsw.writeBits(2, 4); - - assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); - }); - - test('8b, big-endian', function () { - bsw.bigEndian = true; - - // 0000 0001 0000 0010 [01 02] - // [ #1] [ #2] - bsw.writeBits(1, 8); - bsw.writeBits(2, 8); - - assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); - }); - - test('10b, big-endian', function () { - bsw.bigEndian = true; - - // 0000 0001 0000 0010 [01 02] - // [ #1][ #2] - bsw.writeBits(4, 10); - bsw.writeBits(2, 6); - - assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); - }); - - test('16b, big-endian', function () { - bsw.bigEndian = true; - - // 0000 0001 0000 0010 [01 02] - // [ #1] - bsw.writeBits(0x102, 16); - - assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); - }); -}); diff --git a/node_modules/lv_font_conv/node_modules/debug/LICENSE b/node_modules/lv_font_conv/node_modules/debug/LICENSE deleted file mode 100644 index 658c933d..00000000 --- a/node_modules/lv_font_conv/node_modules/debug/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -(The MIT License) - -Copyright (c) 2014 TJ Holowaychuk - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software -and associated documentation files (the 'Software'), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT -LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/node_modules/lv_font_conv/node_modules/debug/README.md b/node_modules/lv_font_conv/node_modules/debug/README.md deleted file mode 100644 index 88dae35d..00000000 --- a/node_modules/lv_font_conv/node_modules/debug/README.md +++ /dev/null @@ -1,455 +0,0 @@ -# debug -[![Build Status](https://travis-ci.org/visionmedia/debug.svg?branch=master)](https://travis-ci.org/visionmedia/debug) [![Coverage Status](https://coveralls.io/repos/github/visionmedia/debug/badge.svg?branch=master)](https://coveralls.io/github/visionmedia/debug?branch=master) [![Slack](https://visionmedia-community-slackin.now.sh/badge.svg)](https://visionmedia-community-slackin.now.sh/) [![OpenCollective](https://opencollective.com/debug/backers/badge.svg)](#backers) -[![OpenCollective](https://opencollective.com/debug/sponsors/badge.svg)](#sponsors) - - - -A tiny JavaScript debugging utility modelled after Node.js core's debugging -technique. Works in Node.js and web browsers. - -## Installation - -```bash -$ npm install debug -``` - -## Usage - -`debug` exposes a function; simply pass this function the name of your module, and it will return a decorated version of `console.error` for you to pass debug statements to. This will allow you to toggle the debug output for different parts of your module as well as the module as a whole. - -Example [_app.js_](./examples/node/app.js): - -```js -var debug = require('debug')('http') - , http = require('http') - , name = 'My App'; - -// fake app - -debug('booting %o', name); - -http.createServer(function(req, res){ - debug(req.method + ' ' + req.url); - res.end('hello\n'); -}).listen(3000, function(){ - debug('listening'); -}); - -// fake worker of some kind - -require('./worker'); -``` - -Example [_worker.js_](./examples/node/worker.js): - -```js -var a = require('debug')('worker:a') - , b = require('debug')('worker:b'); - -function work() { - a('doing lots of uninteresting work'); - setTimeout(work, Math.random() * 1000); -} - -work(); - -function workb() { - b('doing some work'); - setTimeout(workb, Math.random() * 2000); -} - -workb(); -``` - -The `DEBUG` environment variable is then used to enable these based on space or -comma-delimited names. - -Here are some examples: - -screen shot 2017-08-08 at 12 53 04 pm -screen shot 2017-08-08 at 12 53 38 pm -screen shot 2017-08-08 at 12 53 25 pm - -#### Windows command prompt notes - -##### CMD - -On Windows the environment variable is set using the `set` command. - -```cmd -set DEBUG=*,-not_this -``` - -Example: - -```cmd -set DEBUG=* & node app.js -``` - -##### PowerShell (VS Code default) - -PowerShell uses different syntax to set environment variables. - -```cmd -$env:DEBUG = "*,-not_this" -``` - -Example: - -```cmd -$env:DEBUG='app';node app.js -``` - -Then, run the program to be debugged as usual. - -npm script example: -```js - "windowsDebug": "@powershell -Command $env:DEBUG='*';node app.js", -``` - -## Namespace Colors - -Every debug instance has a color generated for it based on its namespace name. -This helps when visually parsing the debug output to identify which debug instance -a debug line belongs to. - -#### Node.js - -In Node.js, colors are enabled when stderr is a TTY. You also _should_ install -the [`supports-color`](https://npmjs.org/supports-color) module alongside debug, -otherwise debug will only use a small handful of basic colors. - - - -#### Web Browser - -Colors are also enabled on "Web Inspectors" that understand the `%c` formatting -option. These are WebKit web inspectors, Firefox ([since version -31](https://hacks.mozilla.org/2014/05/editable-box-model-multiple-selection-sublime-text-keys-much-more-firefox-developer-tools-episode-31/)) -and the Firebug plugin for Firefox (any version). - - - - -## Millisecond diff - -When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the "+NNNms" will show you how much time was spent between calls. - - - -When stdout is not a TTY, `Date#toISOString()` is used, making it more useful for logging the debug information as shown below: - - - - -## Conventions - -If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use ":" to separate features. For example "bodyParser" from Connect would then be "connect:bodyParser". If you append a "*" to the end of your name, it will always be enabled regardless of the setting of the DEBUG environment variable. You can then use it for normal output as well as debug output. - -## Wildcards - -The `*` character may be used as a wildcard. Suppose for example your library has -debuggers named "connect:bodyParser", "connect:compress", "connect:session", -instead of listing all three with -`DEBUG=connect:bodyParser,connect:compress,connect:session`, you may simply do -`DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`. - -You can also exclude specific debuggers by prefixing them with a "-" character. -For example, `DEBUG=*,-connect:*` would include all debuggers except those -starting with "connect:". - -## Environment Variables - -When running through Node.js, you can set a few environment variables that will -change the behavior of the debug logging: - -| Name | Purpose | -|-----------|-------------------------------------------------| -| `DEBUG` | Enables/disables specific debugging namespaces. | -| `DEBUG_HIDE_DATE` | Hide date from debug output (non-TTY). | -| `DEBUG_COLORS`| Whether or not to use colors in the debug output. | -| `DEBUG_DEPTH` | Object inspection depth. | -| `DEBUG_SHOW_HIDDEN` | Shows hidden properties on inspected objects. | - - -__Note:__ The environment variables beginning with `DEBUG_` end up being -converted into an Options object that gets used with `%o`/`%O` formatters. -See the Node.js documentation for -[`util.inspect()`](https://nodejs.org/api/util.html#util_util_inspect_object_options) -for the complete list. - -## Formatters - -Debug uses [printf-style](https://wikipedia.org/wiki/Printf_format_string) formatting. -Below are the officially supported formatters: - -| Formatter | Representation | -|-----------|----------------| -| `%O` | Pretty-print an Object on multiple lines. | -| `%o` | Pretty-print an Object all on a single line. | -| `%s` | String. | -| `%d` | Number (both integer and float). | -| `%j` | JSON. Replaced with the string '[Circular]' if the argument contains circular references. | -| `%%` | Single percent sign ('%'). This does not consume an argument. | - - -### Custom formatters - -You can add custom formatters by extending the `debug.formatters` object. -For example, if you wanted to add support for rendering a Buffer as hex with -`%h`, you could do something like: - -```js -const createDebug = require('debug') -createDebug.formatters.h = (v) => { - return v.toString('hex') -} - -// …elsewhere -const debug = createDebug('foo') -debug('this is hex: %h', new Buffer('hello world')) -// foo this is hex: 68656c6c6f20776f726c6421 +0ms -``` - - -## Browser Support - -You can build a browser-ready script using [browserify](https://github.com/substack/node-browserify), -or just use the [browserify-as-a-service](https://wzrd.in/) [build](https://wzrd.in/standalone/debug@latest), -if you don't want to build it yourself. - -Debug's enable state is currently persisted by `localStorage`. -Consider the situation shown below where you have `worker:a` and `worker:b`, -and wish to debug both. You can enable this using `localStorage.debug`: - -```js -localStorage.debug = 'worker:*' -``` - -And then refresh the page. - -```js -a = debug('worker:a'); -b = debug('worker:b'); - -setInterval(function(){ - a('doing some work'); -}, 1000); - -setInterval(function(){ - b('doing some work'); -}, 1200); -``` - - -## Output streams - - By default `debug` will log to stderr, however this can be configured per-namespace by overriding the `log` method: - -Example [_stdout.js_](./examples/node/stdout.js): - -```js -var debug = require('debug'); -var error = debug('app:error'); - -// by default stderr is used -error('goes to stderr!'); - -var log = debug('app:log'); -// set this namespace to log via console.log -log.log = console.log.bind(console); // don't forget to bind to console! -log('goes to stdout'); -error('still goes to stderr!'); - -// set all output to go via console.info -// overrides all per-namespace log settings -debug.log = console.info.bind(console); -error('now goes to stdout via console.info'); -log('still goes to stdout, but via console.info now'); -``` - -## Extend -You can simply extend debugger -```js -const log = require('debug')('auth'); - -//creates new debug instance with extended namespace -const logSign = log.extend('sign'); -const logLogin = log.extend('login'); - -log('hello'); // auth hello -logSign('hello'); //auth:sign hello -logLogin('hello'); //auth:login hello -``` - -## Set dynamically - -You can also enable debug dynamically by calling the `enable()` method : - -```js -let debug = require('debug'); - -console.log(1, debug.enabled('test')); - -debug.enable('test'); -console.log(2, debug.enabled('test')); - -debug.disable(); -console.log(3, debug.enabled('test')); - -``` - -print : -``` -1 false -2 true -3 false -``` - -Usage : -`enable(namespaces)` -`namespaces` can include modes separated by a colon and wildcards. - -Note that calling `enable()` completely overrides previously set DEBUG variable : - -``` -$ DEBUG=foo node -e 'var dbg = require("debug"); dbg.enable("bar"); console.log(dbg.enabled("foo"))' -=> false -``` - -`disable()` - -Will disable all namespaces. The functions returns the namespaces currently -enabled (and skipped). This can be useful if you want to disable debugging -temporarily without knowing what was enabled to begin with. - -For example: - -```js -let debug = require('debug'); -debug.enable('foo:*,-foo:bar'); -let namespaces = debug.disable(); -debug.enable(namespaces); -``` - -Note: There is no guarantee that the string will be identical to the initial -enable string, but semantically they will be identical. - -## Checking whether a debug target is enabled - -After you've created a debug instance, you can determine whether or not it is -enabled by checking the `enabled` property: - -```javascript -const debug = require('debug')('http'); - -if (debug.enabled) { - // do stuff... -} -``` - -You can also manually toggle this property to force the debug instance to be -enabled or disabled. - - -## Authors - - - TJ Holowaychuk - - Nathan Rajlich - - Andrew Rhyne - -## Backers - -Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/debug#backer)] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -## Sponsors - -Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/debug#sponsor)] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -## License - -(The MIT License) - -Copyright (c) 2014-2017 TJ Holowaychuk <tj@vision-media.ca> - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/debug/package.json b/node_modules/lv_font_conv/node_modules/debug/package.json deleted file mode 100644 index c9f15f8c..00000000 --- a/node_modules/lv_font_conv/node_modules/debug/package.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "_args": [ - [ - "debug@4.3.1", - "/home/vitaly/Dropbox/Coding/lv_font_conv" - ] - ], - "_from": "debug@4.3.1", - "_id": "debug@4.3.1", - "_inBundle": false, - "_integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "_location": "/debug", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "debug@4.3.1", - "name": "debug", - "escapedName": "debug", - "rawSpec": "4.3.1", - "saveSpec": null, - "fetchSpec": "4.3.1" - }, - "_requiredBy": [ - "/", - "/@babel/core", - "/@babel/helper-define-polyfill-provider", - "/@babel/traverse", - "/@eslint/eslintrc", - "/eslint", - "/istanbul-lib-source-maps", - "/mocha" - ], - "_resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "_spec": "4.3.1", - "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", - "author": { - "name": "TJ Holowaychuk", - "email": "tj@vision-media.ca" - }, - "browser": "./src/browser.js", - "bugs": { - "url": "https://github.com/visionmedia/debug/issues" - }, - "contributors": [ - { - "name": "Nathan Rajlich", - "email": "nathan@tootallnate.net", - "url": "http://n8.io" - }, - { - "name": "Andrew Rhyne", - "email": "rhyneandrew@gmail.com" - }, - { - "name": "Josh Junon", - "email": "josh@junon.me" - } - ], - "dependencies": { - "ms": "2.1.2" - }, - "description": "small debugging utility", - "devDependencies": { - "brfs": "^2.0.1", - "browserify": "^16.2.3", - "coveralls": "^3.0.2", - "istanbul": "^0.4.5", - "karma": "^3.1.4", - "karma-browserify": "^6.0.0", - "karma-chrome-launcher": "^2.2.0", - "karma-mocha": "^1.3.0", - "mocha": "^5.2.0", - "mocha-lcov-reporter": "^1.2.0", - "xo": "^0.23.0" - }, - "engines": { - "node": ">=6.0" - }, - "files": [ - "src", - "LICENSE", - "README.md" - ], - "homepage": "https://github.com/visionmedia/debug#readme", - "keywords": [ - "debug", - "log", - "debugger" - ], - "license": "MIT", - "main": "./src/index.js", - "name": "debug", - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - }, - "repository": { - "type": "git", - "url": "git://github.com/visionmedia/debug.git" - }, - "scripts": { - "lint": "xo", - "test": "npm run test:node && npm run test:browser && npm run lint", - "test:browser": "karma start --single-run", - "test:coverage": "cat ./coverage/lcov.info | coveralls", - "test:node": "istanbul cover _mocha -- test.js" - }, - "version": "4.3.1" -} diff --git a/node_modules/lv_font_conv/node_modules/debug/src/browser.js b/node_modules/lv_font_conv/node_modules/debug/src/browser.js deleted file mode 100644 index cd0fc35d..00000000 --- a/node_modules/lv_font_conv/node_modules/debug/src/browser.js +++ /dev/null @@ -1,269 +0,0 @@ -/* eslint-env browser */ - -/** - * This is the web browser implementation of `debug()`. - */ - -exports.formatArgs = formatArgs; -exports.save = save; -exports.load = load; -exports.useColors = useColors; -exports.storage = localstorage(); -exports.destroy = (() => { - let warned = false; - - return () => { - if (!warned) { - warned = true; - console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); - } - }; -})(); - -/** - * Colors. - */ - -exports.colors = [ - '#0000CC', - '#0000FF', - '#0033CC', - '#0033FF', - '#0066CC', - '#0066FF', - '#0099CC', - '#0099FF', - '#00CC00', - '#00CC33', - '#00CC66', - '#00CC99', - '#00CCCC', - '#00CCFF', - '#3300CC', - '#3300FF', - '#3333CC', - '#3333FF', - '#3366CC', - '#3366FF', - '#3399CC', - '#3399FF', - '#33CC00', - '#33CC33', - '#33CC66', - '#33CC99', - '#33CCCC', - '#33CCFF', - '#6600CC', - '#6600FF', - '#6633CC', - '#6633FF', - '#66CC00', - '#66CC33', - '#9900CC', - '#9900FF', - '#9933CC', - '#9933FF', - '#99CC00', - '#99CC33', - '#CC0000', - '#CC0033', - '#CC0066', - '#CC0099', - '#CC00CC', - '#CC00FF', - '#CC3300', - '#CC3333', - '#CC3366', - '#CC3399', - '#CC33CC', - '#CC33FF', - '#CC6600', - '#CC6633', - '#CC9900', - '#CC9933', - '#CCCC00', - '#CCCC33', - '#FF0000', - '#FF0033', - '#FF0066', - '#FF0099', - '#FF00CC', - '#FF00FF', - '#FF3300', - '#FF3333', - '#FF3366', - '#FF3399', - '#FF33CC', - '#FF33FF', - '#FF6600', - '#FF6633', - '#FF9900', - '#FF9933', - '#FFCC00', - '#FFCC33' -]; - -/** - * Currently only WebKit-based Web Inspectors, Firefox >= v31, - * and the Firebug extension (any Firefox version) are known - * to support "%c" CSS customizations. - * - * TODO: add a `localStorage` variable to explicitly enable/disable colors - */ - -// eslint-disable-next-line complexity -function useColors() { - // NB: In an Electron preload script, document will be defined but not fully - // initialized. Since we know we're in Chrome, we'll just detect this case - // explicitly - if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) { - return true; - } - - // Internet Explorer and Edge do not support colors. - if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) { - return false; - } - - // Is webkit? http://stackoverflow.com/a/16459606/376773 - // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 - return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || - // Is firebug? http://stackoverflow.com/a/398120/376773 - (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || - // Is firefox >= v31? - // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || - // Double check webkit in userAgent just in case we are in a worker - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); -} - -/** - * Colorize log arguments if enabled. - * - * @api public - */ - -function formatArgs(args) { - args[0] = (this.useColors ? '%c' : '') + - this.namespace + - (this.useColors ? ' %c' : ' ') + - args[0] + - (this.useColors ? '%c ' : ' ') + - '+' + module.exports.humanize(this.diff); - - if (!this.useColors) { - return; - } - - const c = 'color: ' + this.color; - args.splice(1, 0, c, 'color: inherit'); - - // The final "%c" is somewhat tricky, because there could be other - // arguments passed either before or after the %c, so we need to - // figure out the correct index to insert the CSS into - let index = 0; - let lastC = 0; - args[0].replace(/%[a-zA-Z%]/g, match => { - if (match === '%%') { - return; - } - index++; - if (match === '%c') { - // We only are interested in the *last* %c - // (the user may have provided their own) - lastC = index; - } - }); - - args.splice(lastC, 0, c); -} - -/** - * Invokes `console.debug()` when available. - * No-op when `console.debug` is not a "function". - * If `console.debug` is not available, falls back - * to `console.log`. - * - * @api public - */ -exports.log = console.debug || console.log || (() => {}); - -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ -function save(namespaces) { - try { - if (namespaces) { - exports.storage.setItem('debug', namespaces); - } else { - exports.storage.removeItem('debug'); - } - } catch (error) { - // Swallow - // XXX (@Qix-) should we be logging these? - } -} - -/** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ -function load() { - let r; - try { - r = exports.storage.getItem('debug'); - } catch (error) { - // Swallow - // XXX (@Qix-) should we be logging these? - } - - // If debug isn't set in LS, and we're in Electron, try to load $DEBUG - if (!r && typeof process !== 'undefined' && 'env' in process) { - r = process.env.DEBUG; - } - - return r; -} - -/** - * Localstorage attempts to return the localstorage. - * - * This is necessary because safari throws - * when a user disables cookies/localstorage - * and you attempt to access it. - * - * @return {LocalStorage} - * @api private - */ - -function localstorage() { - try { - // TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context - // The Browser also has localStorage in the global context. - return localStorage; - } catch (error) { - // Swallow - // XXX (@Qix-) should we be logging these? - } -} - -module.exports = require('./common')(exports); - -const {formatters} = module.exports; - -/** - * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. - */ - -formatters.j = function (v) { - try { - return JSON.stringify(v); - } catch (error) { - return '[UnexpectedJSONParseError]: ' + error.message; - } -}; diff --git a/node_modules/lv_font_conv/node_modules/debug/src/common.js b/node_modules/lv_font_conv/node_modules/debug/src/common.js deleted file mode 100644 index 392a8e00..00000000 --- a/node_modules/lv_font_conv/node_modules/debug/src/common.js +++ /dev/null @@ -1,261 +0,0 @@ - -/** - * This is the common logic for both the Node.js and web browser - * implementations of `debug()`. - */ - -function setup(env) { - createDebug.debug = createDebug; - createDebug.default = createDebug; - createDebug.coerce = coerce; - createDebug.disable = disable; - createDebug.enable = enable; - createDebug.enabled = enabled; - createDebug.humanize = require('ms'); - createDebug.destroy = destroy; - - Object.keys(env).forEach(key => { - createDebug[key] = env[key]; - }); - - /** - * The currently active debug mode names, and names to skip. - */ - - createDebug.names = []; - createDebug.skips = []; - - /** - * Map of special "%n" handling functions, for the debug "format" argument. - * - * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". - */ - createDebug.formatters = {}; - - /** - * Selects a color for a debug namespace - * @param {String} namespace The namespace string for the for the debug instance to be colored - * @return {Number|String} An ANSI color code for the given namespace - * @api private - */ - function selectColor(namespace) { - let hash = 0; - - for (let i = 0; i < namespace.length; i++) { - hash = ((hash << 5) - hash) + namespace.charCodeAt(i); - hash |= 0; // Convert to 32bit integer - } - - return createDebug.colors[Math.abs(hash) % createDebug.colors.length]; - } - createDebug.selectColor = selectColor; - - /** - * Create a debugger with the given `namespace`. - * - * @param {String} namespace - * @return {Function} - * @api public - */ - function createDebug(namespace) { - let prevTime; - let enableOverride = null; - - function debug(...args) { - // Disabled? - if (!debug.enabled) { - return; - } - - const self = debug; - - // Set `diff` timestamp - const curr = Number(new Date()); - const ms = curr - (prevTime || curr); - self.diff = ms; - self.prev = prevTime; - self.curr = curr; - prevTime = curr; - - args[0] = createDebug.coerce(args[0]); - - if (typeof args[0] !== 'string') { - // Anything else let's inspect with %O - args.unshift('%O'); - } - - // Apply any `formatters` transformations - let index = 0; - args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => { - // If we encounter an escaped % then don't increase the array index - if (match === '%%') { - return '%'; - } - index++; - const formatter = createDebug.formatters[format]; - if (typeof formatter === 'function') { - const val = args[index]; - match = formatter.call(self, val); - - // Now we need to remove `args[index]` since it's inlined in the `format` - args.splice(index, 1); - index--; - } - return match; - }); - - // Apply env-specific formatting (colors, etc.) - createDebug.formatArgs.call(self, args); - - const logFn = self.log || createDebug.log; - logFn.apply(self, args); - } - - debug.namespace = namespace; - debug.useColors = createDebug.useColors(); - debug.color = createDebug.selectColor(namespace); - debug.extend = extend; - debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release. - - Object.defineProperty(debug, 'enabled', { - enumerable: true, - configurable: false, - get: () => enableOverride === null ? createDebug.enabled(namespace) : enableOverride, - set: v => { - enableOverride = v; - } - }); - - // Env-specific initialization logic for debug instances - if (typeof createDebug.init === 'function') { - createDebug.init(debug); - } - - return debug; - } - - function extend(namespace, delimiter) { - const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace); - newDebug.log = this.log; - return newDebug; - } - - /** - * Enables a debug mode by namespaces. This can include modes - * separated by a colon and wildcards. - * - * @param {String} namespaces - * @api public - */ - function enable(namespaces) { - createDebug.save(namespaces); - - createDebug.names = []; - createDebug.skips = []; - - let i; - const split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); - const len = split.length; - - for (i = 0; i < len; i++) { - if (!split[i]) { - // ignore empty strings - continue; - } - - namespaces = split[i].replace(/\*/g, '.*?'); - - if (namespaces[0] === '-') { - createDebug.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); - } else { - createDebug.names.push(new RegExp('^' + namespaces + '$')); - } - } - } - - /** - * Disable debug output. - * - * @return {String} namespaces - * @api public - */ - function disable() { - const namespaces = [ - ...createDebug.names.map(toNamespace), - ...createDebug.skips.map(toNamespace).map(namespace => '-' + namespace) - ].join(','); - createDebug.enable(''); - return namespaces; - } - - /** - * Returns true if the given mode name is enabled, false otherwise. - * - * @param {String} name - * @return {Boolean} - * @api public - */ - function enabled(name) { - if (name[name.length - 1] === '*') { - return true; - } - - let i; - let len; - - for (i = 0, len = createDebug.skips.length; i < len; i++) { - if (createDebug.skips[i].test(name)) { - return false; - } - } - - for (i = 0, len = createDebug.names.length; i < len; i++) { - if (createDebug.names[i].test(name)) { - return true; - } - } - - return false; - } - - /** - * Convert regexp to namespace - * - * @param {RegExp} regxep - * @return {String} namespace - * @api private - */ - function toNamespace(regexp) { - return regexp.toString() - .substring(2, regexp.toString().length - 2) - .replace(/\.\*\?$/, '*'); - } - - /** - * Coerce `val`. - * - * @param {Mixed} val - * @return {Mixed} - * @api private - */ - function coerce(val) { - if (val instanceof Error) { - return val.stack || val.message; - } - return val; - } - - /** - * XXX DO NOT USE. This is a temporary stub function. - * XXX It WILL be removed in the next major release. - */ - function destroy() { - console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); - } - - createDebug.enable(createDebug.load()); - - return createDebug; -} - -module.exports = setup; diff --git a/node_modules/lv_font_conv/node_modules/debug/src/index.js b/node_modules/lv_font_conv/node_modules/debug/src/index.js deleted file mode 100644 index bf4c57f2..00000000 --- a/node_modules/lv_font_conv/node_modules/debug/src/index.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Detect Electron renderer / nwjs process, which is node, but we should - * treat as a browser. - */ - -if (typeof process === 'undefined' || process.type === 'renderer' || process.browser === true || process.__nwjs) { - module.exports = require('./browser.js'); -} else { - module.exports = require('./node.js'); -} diff --git a/node_modules/lv_font_conv/node_modules/debug/src/node.js b/node_modules/lv_font_conv/node_modules/debug/src/node.js deleted file mode 100644 index 79bc085c..00000000 --- a/node_modules/lv_font_conv/node_modules/debug/src/node.js +++ /dev/null @@ -1,263 +0,0 @@ -/** - * Module dependencies. - */ - -const tty = require('tty'); -const util = require('util'); - -/** - * This is the Node.js implementation of `debug()`. - */ - -exports.init = init; -exports.log = log; -exports.formatArgs = formatArgs; -exports.save = save; -exports.load = load; -exports.useColors = useColors; -exports.destroy = util.deprecate( - () => {}, - 'Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.' -); - -/** - * Colors. - */ - -exports.colors = [6, 2, 3, 4, 5, 1]; - -try { - // Optional dependency (as in, doesn't need to be installed, NOT like optionalDependencies in package.json) - // eslint-disable-next-line import/no-extraneous-dependencies - const supportsColor = require('supports-color'); - - if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) { - exports.colors = [ - 20, - 21, - 26, - 27, - 32, - 33, - 38, - 39, - 40, - 41, - 42, - 43, - 44, - 45, - 56, - 57, - 62, - 63, - 68, - 69, - 74, - 75, - 76, - 77, - 78, - 79, - 80, - 81, - 92, - 93, - 98, - 99, - 112, - 113, - 128, - 129, - 134, - 135, - 148, - 149, - 160, - 161, - 162, - 163, - 164, - 165, - 166, - 167, - 168, - 169, - 170, - 171, - 172, - 173, - 178, - 179, - 184, - 185, - 196, - 197, - 198, - 199, - 200, - 201, - 202, - 203, - 204, - 205, - 206, - 207, - 208, - 209, - 214, - 215, - 220, - 221 - ]; - } -} catch (error) { - // Swallow - we only care if `supports-color` is available; it doesn't have to be. -} - -/** - * Build up the default `inspectOpts` object from the environment variables. - * - * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js - */ - -exports.inspectOpts = Object.keys(process.env).filter(key => { - return /^debug_/i.test(key); -}).reduce((obj, key) => { - // Camel-case - const prop = key - .substring(6) - .toLowerCase() - .replace(/_([a-z])/g, (_, k) => { - return k.toUpperCase(); - }); - - // Coerce string value into JS value - let val = process.env[key]; - if (/^(yes|on|true|enabled)$/i.test(val)) { - val = true; - } else if (/^(no|off|false|disabled)$/i.test(val)) { - val = false; - } else if (val === 'null') { - val = null; - } else { - val = Number(val); - } - - obj[prop] = val; - return obj; -}, {}); - -/** - * Is stdout a TTY? Colored output is enabled when `true`. - */ - -function useColors() { - return 'colors' in exports.inspectOpts ? - Boolean(exports.inspectOpts.colors) : - tty.isatty(process.stderr.fd); -} - -/** - * Adds ANSI color escape codes if enabled. - * - * @api public - */ - -function formatArgs(args) { - const {namespace: name, useColors} = this; - - if (useColors) { - const c = this.color; - const colorCode = '\u001B[3' + (c < 8 ? c : '8;5;' + c); - const prefix = ` ${colorCode};1m${name} \u001B[0m`; - - args[0] = prefix + args[0].split('\n').join('\n' + prefix); - args.push(colorCode + 'm+' + module.exports.humanize(this.diff) + '\u001B[0m'); - } else { - args[0] = getDate() + name + ' ' + args[0]; - } -} - -function getDate() { - if (exports.inspectOpts.hideDate) { - return ''; - } - return new Date().toISOString() + ' '; -} - -/** - * Invokes `util.format()` with the specified arguments and writes to stderr. - */ - -function log(...args) { - return process.stderr.write(util.format(...args) + '\n'); -} - -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ -function save(namespaces) { - if (namespaces) { - process.env.DEBUG = namespaces; - } else { - // If you set a process.env field to null or undefined, it gets cast to the - // string 'null' or 'undefined'. Just delete instead. - delete process.env.DEBUG; - } -} - -/** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ - -function load() { - return process.env.DEBUG; -} - -/** - * Init logic for `debug` instances. - * - * Create a new `inspectOpts` object in case `useColors` is set - * differently for a particular `debug` instance. - */ - -function init(debug) { - debug.inspectOpts = {}; - - const keys = Object.keys(exports.inspectOpts); - for (let i = 0; i < keys.length; i++) { - debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]]; - } -} - -module.exports = require('./common')(exports); - -const {formatters} = module.exports; - -/** - * Map %o to `util.inspect()`, all on a single line. - */ - -formatters.o = function (v) { - this.inspectOpts.colors = this.useColors; - return util.inspect(v, this.inspectOpts) - .split('\n') - .map(str => str.trim()) - .join(' '); -}; - -/** - * Map %O to `util.inspect()`, allowing multiple lines if needed. - */ - -formatters.O = function (v) { - this.inspectOpts.colors = this.useColors; - return util.inspect(v, this.inspectOpts); -}; diff --git a/node_modules/lv_font_conv/node_modules/make-error/LICENSE b/node_modules/lv_font_conv/node_modules/make-error/LICENSE deleted file mode 100644 index 9dcf797e..00000000 --- a/node_modules/lv_font_conv/node_modules/make-error/LICENSE +++ /dev/null @@ -1,5 +0,0 @@ -Copyright 2014 Julien Fontanet - -Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/make-error/README.md b/node_modules/lv_font_conv/node_modules/make-error/README.md deleted file mode 100644 index 5c089a26..00000000 --- a/node_modules/lv_font_conv/node_modules/make-error/README.md +++ /dev/null @@ -1,112 +0,0 @@ -# make-error - -[![Package Version](https://badgen.net/npm/v/make-error)](https://npmjs.org/package/make-error) [![Build Status](https://travis-ci.org/JsCommunity/make-error.png?branch=master)](https://travis-ci.org/JsCommunity/make-error) [![PackagePhobia](https://badgen.net/packagephobia/install/make-error)](https://packagephobia.now.sh/result?p=make-error) [![Latest Commit](https://badgen.net/github/last-commit/JsCommunity/make-error)](https://github.com/JsCommunity/make-error/commits/master) - -> Make your own error types! - -## Features - -- Compatible Node & browsers -- `instanceof` support -- `error.name` & `error.stack` support -- compatible with [CSP](https://en.wikipedia.org/wiki/Content_Security_Policy) (i.e. no `eval()`) - -## Installation - -### Node & [Browserify](http://browserify.org/)/[Webpack](https://webpack.js.org/) - -Installation of the [npm package](https://npmjs.org/package/make-error): - -``` -> npm install --save make-error -``` - -Then require the package: - -```javascript -var makeError = require("make-error"); -``` - -### Browser - -You can directly use the build provided at [unpkg.com](https://unpkg.com): - -```html - -``` - -## Usage - -### Basic named error - -```javascript -var CustomError = makeError("CustomError"); - -// Parameters are forwarded to the super class (here Error). -throw new CustomError("a message"); -``` - -### Advanced error class - -```javascript -function CustomError(customValue) { - CustomError.super.call(this, "custom error message"); - - this.customValue = customValue; -} -makeError(CustomError); - -// Feel free to extend the prototype. -CustomError.prototype.myMethod = function CustomError$myMethod() { - console.log("CustomError.myMethod (%s, %s)", this.code, this.message); -}; - -//----- - -try { - throw new CustomError(42); -} catch (error) { - error.myMethod(); -} -``` - -### Specialized error - -```javascript -var SpecializedError = makeError("SpecializedError", CustomError); - -throw new SpecializedError(42); -``` - -### Inheritance - -> Best for ES2015+. - -```javascript -import { BaseError } from "make-error"; - -class CustomError extends BaseError { - constructor() { - super("custom error message"); - } -} -``` - -## Related - -- [make-error-cause](https://www.npmjs.com/package/make-error-cause): Make your own error types, with a cause! - -## Contributions - -Contributions are _very_ welcomed, either on the documentation or on -the code. - -You may: - -- report any [issue](https://github.com/JsCommunity/make-error/issues) - you've encountered; -- fork and create a pull request. - -## License - -ISC © [Julien Fontanet](http://julien.isonoe.net) diff --git a/node_modules/lv_font_conv/node_modules/make-error/dist/make-error.js b/node_modules/lv_font_conv/node_modules/make-error/dist/make-error.js deleted file mode 100644 index 32444c69..00000000 --- a/node_modules/lv_font_conv/node_modules/make-error/dist/make-error.js +++ /dev/null @@ -1 +0,0 @@ -!function(f){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=f();else if("function"==typeof define&&define.amd)define([],f);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).makeError=f()}}(function(){return function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){return o(e[i][1][r]||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i; - -/** - * Set the constructor prototype to `BaseError`. - */ -declare function makeError(super_: { - new (...args: any[]): T; -}): makeError.Constructor; - -/** - * Create a specialized error instance. - */ -declare function makeError( - name: string | Function, - super_: K -): K & makeError.SpecializedConstructor; - -declare namespace makeError { - /** - * Use with ES2015+ inheritance. - */ - export class BaseError extends Error { - message: string; - name: string; - stack: string; - - constructor(message?: string); - } - - export interface Constructor { - new (message?: string): T; - super_: any; - prototype: T; - } - - export interface SpecializedConstructor { - super_: any; - prototype: T; - } -} - -export = makeError; diff --git a/node_modules/lv_font_conv/node_modules/make-error/index.js b/node_modules/lv_font_conv/node_modules/make-error/index.js deleted file mode 100644 index fab60407..00000000 --- a/node_modules/lv_font_conv/node_modules/make-error/index.js +++ /dev/null @@ -1,151 +0,0 @@ -// ISC @ Julien Fontanet - -"use strict"; - -// =================================================================== - -var construct = typeof Reflect !== "undefined" ? Reflect.construct : undefined; -var defineProperty = Object.defineProperty; - -// ------------------------------------------------------------------- - -var captureStackTrace = Error.captureStackTrace; -if (captureStackTrace === undefined) { - captureStackTrace = function captureStackTrace(error) { - var container = new Error(); - - defineProperty(error, "stack", { - configurable: true, - get: function getStack() { - var stack = container.stack; - - // Replace property with value for faster future accesses. - defineProperty(this, "stack", { - configurable: true, - value: stack, - writable: true, - }); - - return stack; - }, - set: function setStack(stack) { - defineProperty(error, "stack", { - configurable: true, - value: stack, - writable: true, - }); - }, - }); - }; -} - -// ------------------------------------------------------------------- - -function BaseError(message) { - if (message !== undefined) { - defineProperty(this, "message", { - configurable: true, - value: message, - writable: true, - }); - } - - var cname = this.constructor.name; - if (cname !== undefined && cname !== this.name) { - defineProperty(this, "name", { - configurable: true, - value: cname, - writable: true, - }); - } - - captureStackTrace(this, this.constructor); -} - -BaseError.prototype = Object.create(Error.prototype, { - // See: https://github.com/JsCommunity/make-error/issues/4 - constructor: { - configurable: true, - value: BaseError, - writable: true, - }, -}); - -// ------------------------------------------------------------------- - -// Sets the name of a function if possible (depends of the JS engine). -var setFunctionName = (function() { - function setFunctionName(fn, name) { - return defineProperty(fn, "name", { - configurable: true, - value: name, - }); - } - try { - var f = function() {}; - setFunctionName(f, "foo"); - if (f.name === "foo") { - return setFunctionName; - } - } catch (_) {} -})(); - -// ------------------------------------------------------------------- - -function makeError(constructor, super_) { - if (super_ == null || super_ === Error) { - super_ = BaseError; - } else if (typeof super_ !== "function") { - throw new TypeError("super_ should be a function"); - } - - var name; - if (typeof constructor === "string") { - name = constructor; - constructor = - construct !== undefined - ? function() { - return construct(super_, arguments, this.constructor); - } - : function() { - super_.apply(this, arguments); - }; - - // If the name can be set, do it once and for all. - if (setFunctionName !== undefined) { - setFunctionName(constructor, name); - name = undefined; - } - } else if (typeof constructor !== "function") { - throw new TypeError("constructor should be either a string or a function"); - } - - // Also register the super constructor also as `constructor.super_` just - // like Node's `util.inherits()`. - // - // eslint-disable-next-line dot-notation - constructor.super_ = constructor["super"] = super_; - - var properties = { - constructor: { - configurable: true, - value: constructor, - writable: true, - }, - }; - - // If the name could not be set on the constructor, set it on the - // prototype. - if (name !== undefined) { - properties.name = { - configurable: true, - value: name, - writable: true, - }; - } - constructor.prototype = Object.create(super_.prototype, properties); - - return constructor; -} -exports = module.exports = makeError; -exports.BaseError = BaseError; diff --git a/node_modules/lv_font_conv/node_modules/make-error/package.json b/node_modules/lv_font_conv/node_modules/make-error/package.json deleted file mode 100644 index e5f69904..00000000 --- a/node_modules/lv_font_conv/node_modules/make-error/package.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "_args": [ - [ - "make-error@1.3.6", - "/home/vitaly/Dropbox/Coding/lv_font_conv" - ] - ], - "_from": "make-error@1.3.6", - "_id": "make-error@1.3.6", - "_inBundle": false, - "_integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "_location": "/make-error", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "make-error@1.3.6", - "name": "make-error", - "escapedName": "make-error", - "rawSpec": "1.3.6", - "saveSpec": null, - "fetchSpec": "1.3.6" - }, - "_requiredBy": [ - "/" - ], - "_resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "_spec": "1.3.6", - "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", - "author": { - "name": "Julien Fontanet", - "email": "julien.fontanet@isonoe.net" - }, - "bugs": { - "url": "https://github.com/JsCommunity/make-error/issues" - }, - "description": "Make your own error types!", - "devDependencies": { - "browserify": "^16.2.3", - "eslint": "^6.5.1", - "eslint-config-prettier": "^6.4.0", - "eslint-config-standard": "^14.1.0", - "eslint-plugin-import": "^2.14.0", - "eslint-plugin-node": "^10.0.0", - "eslint-plugin-promise": "^4.0.1", - "eslint-plugin-standard": "^4.0.0", - "husky": "^3.0.9", - "jest": "^24", - "prettier": "^1.14.3", - "uglify-js": "^3.3.2" - }, - "files": [ - "dist/", - "index.js", - "index.d.ts" - ], - "homepage": "https://github.com/JsCommunity/make-error", - "husky": { - "hooks": { - "commit-msg": "npm run test" - } - }, - "jest": { - "testEnvironment": "node" - }, - "keywords": [ - "create", - "custom", - "derive", - "error", - "errors", - "extend", - "extending", - "extension", - "factory", - "inherit", - "make", - "subclass" - ], - "license": "ISC", - "main": "index.js", - "name": "make-error", - "repository": { - "type": "git", - "url": "git://github.com/JsCommunity/make-error.git" - }, - "scripts": { - "dev-test": "jest --watch", - "format": "prettier --write '**'", - "prepublishOnly": "mkdir -p dist && browserify -s makeError index.js | uglifyjs -c > dist/make-error.js", - "pretest": "eslint --ignore-path .gitignore .", - "test": "jest" - }, - "version": "1.3.6" -} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/CHANGELOG.md b/node_modules/lv_font_conv/node_modules/mkdirp/CHANGELOG.md deleted file mode 100644 index 81458380..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/CHANGELOG.md +++ /dev/null @@ -1,15 +0,0 @@ -# Changers Lorgs! - -## 1.0 - -Full rewrite. Essentially a brand new module. - -- Return a promise instead of taking a callback. -- Use native `fs.mkdir(path, { recursive: true })` when available. -- Drop support for outdated Node.js versions. (Technically still works on - Node.js v8, but only 10 and above are officially supported.) - -## 0.x - -Original and most widely used recursive directory creation implementation -in JavaScript, dating back to 2010. diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/LICENSE b/node_modules/lv_font_conv/node_modules/mkdirp/LICENSE deleted file mode 100644 index 13fcd15f..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -Copyright James Halliday (mail@substack.net) and Isaac Z. Schlueter (i@izs.me) - -This project is free software released under the MIT license: - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/bin/cmd.js b/node_modules/lv_font_conv/node_modules/mkdirp/bin/cmd.js deleted file mode 100755 index 6e0aa8dc..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/bin/cmd.js +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env node - -const usage = () => ` -usage: mkdirp [DIR1,DIR2..] {OPTIONS} - - Create each supplied directory including any necessary parent directories - that don't yet exist. - - If the directory already exists, do nothing. - -OPTIONS are: - - -m If a directory needs to be created, set the mode as an octal - --mode= permission string. - - -v --version Print the mkdirp version number - - -h --help Print this helpful banner - - -p --print Print the first directories created for each path provided - - --manual Use manual implementation, even if native is available -` - -const dirs = [] -const opts = {} -let print = false -let dashdash = false -let manual = false -for (const arg of process.argv.slice(2)) { - if (dashdash) - dirs.push(arg) - else if (arg === '--') - dashdash = true - else if (arg === '--manual') - manual = true - else if (/^-h/.test(arg) || /^--help/.test(arg)) { - console.log(usage()) - process.exit(0) - } else if (arg === '-v' || arg === '--version') { - console.log(require('../package.json').version) - process.exit(0) - } else if (arg === '-p' || arg === '--print') { - print = true - } else if (/^-m/.test(arg) || /^--mode=/.test(arg)) { - const mode = parseInt(arg.replace(/^(-m|--mode=)/, ''), 8) - if (isNaN(mode)) { - console.error(`invalid mode argument: ${arg}\nMust be an octal number.`) - process.exit(1) - } - opts.mode = mode - } else - dirs.push(arg) -} - -const mkdirp = require('../') -const impl = manual ? mkdirp.manual : mkdirp -if (dirs.length === 0) - console.error(usage()) - -Promise.all(dirs.map(dir => impl(dir, opts))) - .then(made => print ? made.forEach(m => m && console.log(m)) : null) - .catch(er => { - console.error(er.message) - if (er.code) - console.error(' code: ' + er.code) - process.exit(1) - }) diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/index.js b/node_modules/lv_font_conv/node_modules/mkdirp/index.js deleted file mode 100644 index ad7a16c9..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/index.js +++ /dev/null @@ -1,31 +0,0 @@ -const optsArg = require('./lib/opts-arg.js') -const pathArg = require('./lib/path-arg.js') - -const {mkdirpNative, mkdirpNativeSync} = require('./lib/mkdirp-native.js') -const {mkdirpManual, mkdirpManualSync} = require('./lib/mkdirp-manual.js') -const {useNative, useNativeSync} = require('./lib/use-native.js') - - -const mkdirp = (path, opts) => { - path = pathArg(path) - opts = optsArg(opts) - return useNative(opts) - ? mkdirpNative(path, opts) - : mkdirpManual(path, opts) -} - -const mkdirpSync = (path, opts) => { - path = pathArg(path) - opts = optsArg(opts) - return useNativeSync(opts) - ? mkdirpNativeSync(path, opts) - : mkdirpManualSync(path, opts) -} - -mkdirp.sync = mkdirpSync -mkdirp.native = (path, opts) => mkdirpNative(pathArg(path), optsArg(opts)) -mkdirp.manual = (path, opts) => mkdirpManual(pathArg(path), optsArg(opts)) -mkdirp.nativeSync = (path, opts) => mkdirpNativeSync(pathArg(path), optsArg(opts)) -mkdirp.manualSync = (path, opts) => mkdirpManualSync(pathArg(path), optsArg(opts)) - -module.exports = mkdirp diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/find-made.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/find-made.js deleted file mode 100644 index 022e492c..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/lib/find-made.js +++ /dev/null @@ -1,29 +0,0 @@ -const {dirname} = require('path') - -const findMade = (opts, parent, path = undefined) => { - // we never want the 'made' return value to be a root directory - if (path === parent) - return Promise.resolve() - - return opts.statAsync(parent).then( - st => st.isDirectory() ? path : undefined, // will fail later - er => er.code === 'ENOENT' - ? findMade(opts, dirname(parent), parent) - : undefined - ) -} - -const findMadeSync = (opts, parent, path = undefined) => { - if (path === parent) - return undefined - - try { - return opts.statSync(parent).isDirectory() ? path : undefined - } catch (er) { - return er.code === 'ENOENT' - ? findMadeSync(opts, dirname(parent), parent) - : undefined - } -} - -module.exports = {findMade, findMadeSync} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-manual.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-manual.js deleted file mode 100644 index 2eb18cd6..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-manual.js +++ /dev/null @@ -1,64 +0,0 @@ -const {dirname} = require('path') - -const mkdirpManual = (path, opts, made) => { - opts.recursive = false - const parent = dirname(path) - if (parent === path) { - return opts.mkdirAsync(path, opts).catch(er => { - // swallowed by recursive implementation on posix systems - // any other error is a failure - if (er.code !== 'EISDIR') - throw er - }) - } - - return opts.mkdirAsync(path, opts).then(() => made || path, er => { - if (er.code === 'ENOENT') - return mkdirpManual(parent, opts) - .then(made => mkdirpManual(path, opts, made)) - if (er.code !== 'EEXIST' && er.code !== 'EROFS') - throw er - return opts.statAsync(path).then(st => { - if (st.isDirectory()) - return made - else - throw er - }, () => { throw er }) - }) -} - -const mkdirpManualSync = (path, opts, made) => { - const parent = dirname(path) - opts.recursive = false - - if (parent === path) { - try { - return opts.mkdirSync(path, opts) - } catch (er) { - // swallowed by recursive implementation on posix systems - // any other error is a failure - if (er.code !== 'EISDIR') - throw er - else - return - } - } - - try { - opts.mkdirSync(path, opts) - return made || path - } catch (er) { - if (er.code === 'ENOENT') - return mkdirpManualSync(path, opts, mkdirpManualSync(parent, opts, made)) - if (er.code !== 'EEXIST' && er.code !== 'EROFS') - throw er - try { - if (!opts.statSync(path).isDirectory()) - throw er - } catch (_) { - throw er - } - } -} - -module.exports = {mkdirpManual, mkdirpManualSync} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-native.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-native.js deleted file mode 100644 index c7a6b698..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-native.js +++ /dev/null @@ -1,39 +0,0 @@ -const {dirname} = require('path') -const {findMade, findMadeSync} = require('./find-made.js') -const {mkdirpManual, mkdirpManualSync} = require('./mkdirp-manual.js') - -const mkdirpNative = (path, opts) => { - opts.recursive = true - const parent = dirname(path) - if (parent === path) - return opts.mkdirAsync(path, opts) - - return findMade(opts, path).then(made => - opts.mkdirAsync(path, opts).then(() => made) - .catch(er => { - if (er.code === 'ENOENT') - return mkdirpManual(path, opts) - else - throw er - })) -} - -const mkdirpNativeSync = (path, opts) => { - opts.recursive = true - const parent = dirname(path) - if (parent === path) - return opts.mkdirSync(path, opts) - - const made = findMadeSync(opts, path) - try { - opts.mkdirSync(path, opts) - return made - } catch (er) { - if (er.code === 'ENOENT') - return mkdirpManualSync(path, opts) - else - throw er - } -} - -module.exports = {mkdirpNative, mkdirpNativeSync} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/opts-arg.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/opts-arg.js deleted file mode 100644 index 2fa4833f..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/lib/opts-arg.js +++ /dev/null @@ -1,23 +0,0 @@ -const { promisify } = require('util') -const fs = require('fs') -const optsArg = opts => { - if (!opts) - opts = { mode: 0o777, fs } - else if (typeof opts === 'object') - opts = { mode: 0o777, fs, ...opts } - else if (typeof opts === 'number') - opts = { mode: opts, fs } - else if (typeof opts === 'string') - opts = { mode: parseInt(opts, 8), fs } - else - throw new TypeError('invalid options argument') - - opts.mkdir = opts.mkdir || opts.fs.mkdir || fs.mkdir - opts.mkdirAsync = promisify(opts.mkdir) - opts.stat = opts.stat || opts.fs.stat || fs.stat - opts.statAsync = promisify(opts.stat) - opts.statSync = opts.statSync || opts.fs.statSync || fs.statSync - opts.mkdirSync = opts.mkdirSync || opts.fs.mkdirSync || fs.mkdirSync - return opts -} -module.exports = optsArg diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/path-arg.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/path-arg.js deleted file mode 100644 index cc07de5a..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/lib/path-arg.js +++ /dev/null @@ -1,29 +0,0 @@ -const platform = process.env.__TESTING_MKDIRP_PLATFORM__ || process.platform -const { resolve, parse } = require('path') -const pathArg = path => { - if (/\0/.test(path)) { - // simulate same failure that node raises - throw Object.assign( - new TypeError('path must be a string without null bytes'), - { - path, - code: 'ERR_INVALID_ARG_VALUE', - } - ) - } - - path = resolve(path) - if (platform === 'win32') { - const badWinChars = /[*|"<>?:]/ - const {root} = parse(path) - if (badWinChars.test(path.substr(root.length))) { - throw Object.assign(new Error('Illegal characters in path.'), { - path, - code: 'EINVAL', - }) - } - } - - return path -} -module.exports = pathArg diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/use-native.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/use-native.js deleted file mode 100644 index 079361de..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/lib/use-native.js +++ /dev/null @@ -1,10 +0,0 @@ -const fs = require('fs') - -const version = process.env.__TESTING_MKDIRP_NODE_VERSION__ || process.version -const versArr = version.replace(/^v/, '').split('.') -const hasNative = +versArr[0] > 10 || +versArr[0] === 10 && +versArr[1] >= 12 - -const useNative = !hasNative ? () => false : opts => opts.mkdir === fs.mkdir -const useNativeSync = !hasNative ? () => false : opts => opts.mkdirSync === fs.mkdirSync - -module.exports = {useNative, useNativeSync} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/package.json b/node_modules/lv_font_conv/node_modules/mkdirp/package.json deleted file mode 100644 index 1fb2e3d9..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/package.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "_args": [ - [ - "mkdirp@1.0.4", - "/home/vitaly/Dropbox/Coding/lv_font_conv" - ] - ], - "_from": "mkdirp@1.0.4", - "_id": "mkdirp@1.0.4", - "_inBundle": false, - "_integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "_location": "/mkdirp", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "mkdirp@1.0.4", - "name": "mkdirp", - "escapedName": "mkdirp", - "rawSpec": "1.0.4", - "saveSpec": null, - "fetchSpec": "1.0.4" - }, - "_requiredBy": [ - "/" - ], - "_resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "_spec": "1.0.4", - "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "bugs": { - "url": "https://github.com/isaacs/node-mkdirp/issues" - }, - "description": "Recursively mkdir, like `mkdir -p`", - "devDependencies": { - "require-inject": "^1.4.4", - "tap": "^14.10.7" - }, - "engines": { - "node": ">=10" - }, - "files": [ - "bin", - "lib", - "index.js" - ], - "homepage": "https://github.com/isaacs/node-mkdirp#readme", - "keywords": [ - "mkdir", - "directory", - "make dir", - "make", - "dir", - "recursive", - "native" - ], - "license": "MIT", - "main": "index.js", - "name": "mkdirp", - "repository": { - "type": "git", - "url": "git+https://github.com/isaacs/node-mkdirp.git" - }, - "scripts": { - "postpublish": "git push origin --follow-tags", - "postversion": "npm publish", - "preversion": "npm test", - "snap": "tap", - "test": "tap" - }, - "tap": { - "check-coverage": true, - "coverage-map": "map.js" - }, - "version": "1.0.4" -} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/readme.markdown b/node_modules/lv_font_conv/node_modules/mkdirp/readme.markdown deleted file mode 100644 index 827de590..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/readme.markdown +++ /dev/null @@ -1,266 +0,0 @@ -# mkdirp - -Like `mkdir -p`, but in Node.js! - -Now with a modern API and no\* bugs! - -\* may contain some bugs - -# example - -## pow.js - -```js -const mkdirp = require('mkdirp') - -// return value is a Promise resolving to the first directory created -mkdirp('/tmp/foo/bar/baz').then(made => - console.log(`made directories, starting with ${made}`)) -``` - -Output (where `/tmp/foo` already exists) - -``` -made directories, starting with /tmp/foo/bar -``` - -Or, if you don't have time to wait around for promises: - -```js -const mkdirp = require('mkdirp') - -// return value is the first directory created -const made = mkdirp.sync('/tmp/foo/bar/baz') -console.log(`made directories, starting with ${made}`) -``` - -And now /tmp/foo/bar/baz exists, huzzah! - -# methods - -```js -const mkdirp = require('mkdirp') -``` - -## mkdirp(dir, [opts]) -> Promise - -Create a new directory and any necessary subdirectories at `dir` with octal -permission string `opts.mode`. If `opts` is a string or number, it will be -treated as the `opts.mode`. - -If `opts.mode` isn't specified, it defaults to `0o777 & -(~process.umask())`. - -Promise resolves to first directory `made` that had to be created, or -`undefined` if everything already exists. Promise rejects if any errors -are encountered. Note that, in the case of promise rejection, some -directories _may_ have been created, as recursive directory creation is not -an atomic operation. - -You can optionally pass in an alternate `fs` implementation by passing in -`opts.fs`. Your implementation should have `opts.fs.mkdir(path, opts, cb)` -and `opts.fs.stat(path, cb)`. - -You can also override just one or the other of `mkdir` and `stat` by -passing in `opts.stat` or `opts.mkdir`, or providing an `fs` option that -only overrides one of these. - -## mkdirp.sync(dir, opts) -> String|null - -Synchronously create a new directory and any necessary subdirectories at -`dir` with octal permission string `opts.mode`. If `opts` is a string or -number, it will be treated as the `opts.mode`. - -If `opts.mode` isn't specified, it defaults to `0o777 & -(~process.umask())`. - -Returns the first directory that had to be created, or undefined if -everything already exists. - -You can optionally pass in an alternate `fs` implementation by passing in -`opts.fs`. Your implementation should have `opts.fs.mkdirSync(path, mode)` -and `opts.fs.statSync(path)`. - -You can also override just one or the other of `mkdirSync` and `statSync` -by passing in `opts.statSync` or `opts.mkdirSync`, or providing an `fs` -option that only overrides one of these. - -## mkdirp.manual, mkdirp.manualSync - -Use the manual implementation (not the native one). This is the default -when the native implementation is not available or the stat/mkdir -implementation is overridden. - -## mkdirp.native, mkdirp.nativeSync - -Use the native implementation (not the manual one). This is the default -when the native implementation is available and stat/mkdir are not -overridden. - -# implementation - -On Node.js v10.12.0 and above, use the native `fs.mkdir(p, -{recursive:true})` option, unless `fs.mkdir`/`fs.mkdirSync` has been -overridden by an option. - -## native implementation - -- If the path is a root directory, then pass it to the underlying - implementation and return the result/error. (In this case, it'll either - succeed or fail, but we aren't actually creating any dirs.) -- Walk up the path statting each directory, to find the first path that - will be created, `made`. -- Call `fs.mkdir(path, { recursive: true })` (or `fs.mkdirSync`) -- If error, raise it to the caller. -- Return `made`. - -## manual implementation - -- Call underlying `fs.mkdir` implementation, with `recursive: false` -- If error: - - If path is a root directory, raise to the caller and do not handle it - - If ENOENT, mkdirp parent dir, store result as `made` - - stat(path) - - If error, raise original `mkdir` error - - If directory, return `made` - - Else, raise original `mkdir` error -- else - - return `undefined` if a root dir, or `made` if set, or `path` - -## windows vs unix caveat - -On Windows file systems, attempts to create a root directory (ie, a drive -letter or root UNC path) will fail. If the root directory exists, then it -will fail with `EPERM`. If the root directory does not exist, then it will -fail with `ENOENT`. - -On posix file systems, attempts to create a root directory (in recursive -mode) will succeed silently, as it is treated like just another directory -that already exists. (In non-recursive mode, of course, it fails with -`EEXIST`.) - -In order to preserve this system-specific behavior (and because it's not as -if we can create the parent of a root directory anyway), attempts to create -a root directory are passed directly to the `fs` implementation, and any -errors encountered are not handled. - -## native error caveat - -The native implementation (as of at least Node.js v13.4.0) does not provide -appropriate errors in some cases (see -[nodejs/node#31481](https://github.com/nodejs/node/issues/31481) and -[nodejs/node#28015](https://github.com/nodejs/node/issues/28015)). - -In order to work around this issue, the native implementation will fall -back to the manual implementation if an `ENOENT` error is encountered. - -# choosing a recursive mkdir implementation - -There are a few to choose from! Use the one that suits your needs best :D - -## use `fs.mkdir(path, {recursive: true}, cb)` if: - -- You wish to optimize performance even at the expense of other factors. -- You don't need to know the first dir created. -- You are ok with getting `ENOENT` as the error when some other problem is - the actual cause. -- You can limit your platforms to Node.js v10.12 and above. -- You're ok with using callbacks instead of promises. -- You don't need/want a CLI. -- You don't need to override the `fs` methods in use. - -## use this module (mkdirp 1.x) if: - -- You need to know the first directory that was created. -- You wish to use the native implementation if available, but fall back - when it's not. -- You prefer promise-returning APIs to callback-taking APIs. -- You want more useful error messages than the native recursive mkdir - provides (at least as of Node.js v13.4), and are ok with re-trying on - `ENOENT` to achieve this. -- You need (or at least, are ok with) a CLI. -- You need to override the `fs` methods in use. - -## use [`make-dir`](http://npm.im/make-dir) if: - -- You do not need to know the first dir created (and wish to save a few - `stat` calls when using the native implementation for this reason). -- You wish to use the native implementation if available, but fall back - when it's not. -- You prefer promise-returning APIs to callback-taking APIs. -- You are ok with occasionally getting `ENOENT` errors for failures that - are actually related to something other than a missing file system entry. -- You don't need/want a CLI. -- You need to override the `fs` methods in use. - -## use mkdirp 0.x if: - -- You need to know the first directory that was created. -- You need (or at least, are ok with) a CLI. -- You need to override the `fs` methods in use. -- You're ok with using callbacks instead of promises. -- You are not running on Windows, where the root-level ENOENT errors can - lead to infinite regress. -- You think vinyl just sounds warmer and richer for some weird reason. -- You are supporting truly ancient Node.js versions, before even the advent - of a `Promise` language primitive. (Please don't. You deserve better.) - -# cli - -This package also ships with a `mkdirp` command. - -``` -$ mkdirp -h - -usage: mkdirp [DIR1,DIR2..] {OPTIONS} - - Create each supplied directory including any necessary parent directories - that don't yet exist. - - If the directory already exists, do nothing. - -OPTIONS are: - - -m If a directory needs to be created, set the mode as an octal - --mode= permission string. - - -v --version Print the mkdirp version number - - -h --help Print this helpful banner - - -p --print Print the first directories created for each path provided - - --manual Use manual implementation, even if native is available -``` - -# install - -With [npm](http://npmjs.org) do: - -``` -npm install mkdirp -``` - -to get the library locally, or - -``` -npm install -g mkdirp -``` - -to get the command everywhere, or - -``` -npx mkdirp ... -``` - -to run the command without installing it globally. - -# platform support - -This module works on node v8, but only v10 and above are officially -supported, as Node v8 reached its LTS end of life 2020-01-01, which is in -the past, as of this writing. - -# license - -MIT diff --git a/node_modules/lv_font_conv/node_modules/ms/index.js b/node_modules/lv_font_conv/node_modules/ms/index.js deleted file mode 100644 index c4498bcc..00000000 --- a/node_modules/lv_font_conv/node_modules/ms/index.js +++ /dev/null @@ -1,162 +0,0 @@ -/** - * Helpers. - */ - -var s = 1000; -var m = s * 60; -var h = m * 60; -var d = h * 24; -var w = d * 7; -var y = d * 365.25; - -/** - * Parse or format the given `val`. - * - * Options: - * - * - `long` verbose formatting [false] - * - * @param {String|Number} val - * @param {Object} [options] - * @throws {Error} throw an error if val is not a non-empty string or a number - * @return {String|Number} - * @api public - */ - -module.exports = function(val, options) { - options = options || {}; - var type = typeof val; - if (type === 'string' && val.length > 0) { - return parse(val); - } else if (type === 'number' && isFinite(val)) { - return options.long ? fmtLong(val) : fmtShort(val); - } - throw new Error( - 'val is not a non-empty string or a valid number. val=' + - JSON.stringify(val) - ); -}; - -/** - * Parse the given `str` and return milliseconds. - * - * @param {String} str - * @return {Number} - * @api private - */ - -function parse(str) { - str = String(str); - if (str.length > 100) { - return; - } - var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec( - str - ); - if (!match) { - return; - } - var n = parseFloat(match[1]); - var type = (match[2] || 'ms').toLowerCase(); - switch (type) { - case 'years': - case 'year': - case 'yrs': - case 'yr': - case 'y': - return n * y; - case 'weeks': - case 'week': - case 'w': - return n * w; - case 'days': - case 'day': - case 'd': - return n * d; - case 'hours': - case 'hour': - case 'hrs': - case 'hr': - case 'h': - return n * h; - case 'minutes': - case 'minute': - case 'mins': - case 'min': - case 'm': - return n * m; - case 'seconds': - case 'second': - case 'secs': - case 'sec': - case 's': - return n * s; - case 'milliseconds': - case 'millisecond': - case 'msecs': - case 'msec': - case 'ms': - return n; - default: - return undefined; - } -} - -/** - * Short format for `ms`. - * - * @param {Number} ms - * @return {String} - * @api private - */ - -function fmtShort(ms) { - var msAbs = Math.abs(ms); - if (msAbs >= d) { - return Math.round(ms / d) + 'd'; - } - if (msAbs >= h) { - return Math.round(ms / h) + 'h'; - } - if (msAbs >= m) { - return Math.round(ms / m) + 'm'; - } - if (msAbs >= s) { - return Math.round(ms / s) + 's'; - } - return ms + 'ms'; -} - -/** - * Long format for `ms`. - * - * @param {Number} ms - * @return {String} - * @api private - */ - -function fmtLong(ms) { - var msAbs = Math.abs(ms); - if (msAbs >= d) { - return plural(ms, msAbs, d, 'day'); - } - if (msAbs >= h) { - return plural(ms, msAbs, h, 'hour'); - } - if (msAbs >= m) { - return plural(ms, msAbs, m, 'minute'); - } - if (msAbs >= s) { - return plural(ms, msAbs, s, 'second'); - } - return ms + ' ms'; -} - -/** - * Pluralization helper. - */ - -function plural(ms, msAbs, n, name) { - var isPlural = msAbs >= n * 1.5; - return Math.round(ms / n) + ' ' + name + (isPlural ? 's' : ''); -} diff --git a/node_modules/lv_font_conv/node_modules/ms/license.md b/node_modules/lv_font_conv/node_modules/ms/license.md deleted file mode 100644 index 69b61253..00000000 --- a/node_modules/lv_font_conv/node_modules/ms/license.md +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Zeit, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/ms/package.json b/node_modules/lv_font_conv/node_modules/ms/package.json deleted file mode 100644 index 6d514e71..00000000 --- a/node_modules/lv_font_conv/node_modules/ms/package.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "_args": [ - [ - "ms@2.1.2", - "/home/vitaly/Dropbox/Coding/lv_font_conv" - ] - ], - "_from": "ms@2.1.2", - "_id": "ms@2.1.2", - "_inBundle": false, - "_integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "_location": "/ms", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "ms@2.1.2", - "name": "ms", - "escapedName": "ms", - "rawSpec": "2.1.2", - "saveSpec": null, - "fetchSpec": "2.1.2" - }, - "_requiredBy": [ - "/debug" - ], - "_resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "_spec": "2.1.2", - "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", - "bugs": { - "url": "https://github.com/zeit/ms/issues" - }, - "description": "Tiny millisecond conversion utility", - "devDependencies": { - "eslint": "4.12.1", - "expect.js": "0.3.1", - "husky": "0.14.3", - "lint-staged": "5.0.0", - "mocha": "4.0.1" - }, - "eslintConfig": { - "extends": "eslint:recommended", - "env": { - "node": true, - "es6": true - } - }, - "files": [ - "index.js" - ], - "homepage": "https://github.com/zeit/ms#readme", - "license": "MIT", - "lint-staged": { - "*.js": [ - "npm run lint", - "prettier --single-quote --write", - "git add" - ] - }, - "main": "./index", - "name": "ms", - "repository": { - "type": "git", - "url": "git+https://github.com/zeit/ms.git" - }, - "scripts": { - "lint": "eslint lib/* bin/*", - "precommit": "lint-staged", - "test": "mocha tests.js" - }, - "version": "2.1.2" -} diff --git a/node_modules/lv_font_conv/node_modules/ms/readme.md b/node_modules/lv_font_conv/node_modules/ms/readme.md deleted file mode 100644 index 9a1996b1..00000000 --- a/node_modules/lv_font_conv/node_modules/ms/readme.md +++ /dev/null @@ -1,60 +0,0 @@ -# ms - -[![Build Status](https://travis-ci.org/zeit/ms.svg?branch=master)](https://travis-ci.org/zeit/ms) -[![Join the community on Spectrum](https://withspectrum.github.io/badge/badge.svg)](https://spectrum.chat/zeit) - -Use this package to easily convert various time formats to milliseconds. - -## Examples - -```js -ms('2 days') // 172800000 -ms('1d') // 86400000 -ms('10h') // 36000000 -ms('2.5 hrs') // 9000000 -ms('2h') // 7200000 -ms('1m') // 60000 -ms('5s') // 5000 -ms('1y') // 31557600000 -ms('100') // 100 -ms('-3 days') // -259200000 -ms('-1h') // -3600000 -ms('-200') // -200 -``` - -### Convert from Milliseconds - -```js -ms(60000) // "1m" -ms(2 * 60000) // "2m" -ms(-3 * 60000) // "-3m" -ms(ms('10 hours')) // "10h" -``` - -### Time Format Written-Out - -```js -ms(60000, { long: true }) // "1 minute" -ms(2 * 60000, { long: true }) // "2 minutes" -ms(-3 * 60000, { long: true }) // "-3 minutes" -ms(ms('10 hours'), { long: true }) // "10 hours" -``` - -## Features - -- Works both in [Node.js](https://nodejs.org) and in the browser -- If a number is supplied to `ms`, a string with a unit is returned -- If a string that contains the number is supplied, it returns it as a number (e.g.: it returns `100` for `'100'`) -- If you pass a string with a number and a valid unit, the number of equivalent milliseconds is returned - -## Related Packages - -- [ms.macro](https://github.com/knpwrs/ms.macro) - Run `ms` as a macro at build-time. - -## Caught a Bug? - -1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device -2. Link the package to the global module directory: `npm link` -3. Within the module you want to test your local development instance of ms, just link it to the dependencies: `npm link ms`. Instead of the default one from npm, Node.js will now use your clone of ms! - -As always, you can run the tests using: `npm test` diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/LICENSE b/node_modules/lv_font_conv/node_modules/opentype.js/LICENSE deleted file mode 100644 index c9b39953..00000000 --- a/node_modules/lv_font_conv/node_modules/opentype.js/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2017 Frederik De Bleser - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/README.md b/node_modules/lv_font_conv/node_modules/opentype.js/README.md deleted file mode 100644 index bcb557e8..00000000 --- a/node_modules/lv_font_conv/node_modules/opentype.js/README.md +++ /dev/null @@ -1,313 +0,0 @@ - -# opentype.js · [![Build Status](https://img.shields.io/travis/opentypejs/opentype.js.svg?style=flat-square)](https://travis-ci.org/opentypejs/opentype.js) [![npm](https://img.shields.io/npm/v/opentype.js.svg?style=flat-square)](https://www.npmjs.com/package/opentype.js) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://github.com/opentypejs/opentype.js/blob/master/LICENSE) [![david-dm](https://david-dm.org/opentypejs/opentype.js.svg)](https://david-dm.org/opentypejs/opentype.js) [![Gitter](https://badges.gitter.im/opentypejs/opentype.js.svg)](https://gitter.im/opentypejs/opentype.js) - -opentype.js is a JavaScript parser and writer for TrueType and OpenType fonts. - -It gives you access to the letterforms of text from the browser or Node.js. -See [https://opentype.js.org/](https://opentype.js.org/) for a live demo. - -Features -======== - -* Create a bézier path out of a piece of text. -* Support for composite glyphs (accented letters). -* Support for WOFF, OTF, TTF (both with TrueType `glyf` and PostScript `cff` outlines) -* Support for kerning (Using GPOS or the kern table). -* Support for ligatures. -* Support for TrueType font hinting. -* Support arabic text rendering (See issue #364 & PR #359 #361) -* A low memory mode is available as an option (see #329) -* Runs in the browser and Node.js. - -Installation -============ - -### Using [npm](http://npmjs.org/) package manager - - npm install opentype.js - - const opentype = require('opentype.js'); - - import opentype from 'opentype.js' - - import { load } from 'opentype.js' - -Using TypeScript? [See this example](examples/typescript) - -Note: OpenType.js uses ES6-style imports, so if you want to edit it and debug it in Node.js run `npm run build` first and use `npm run watch` to automatically rebuild when files change. - -### Directly - -[Download the latest ZIP](https://github.com/opentypejs/opentype.js/archive/master.zip) and grab the files in the `dist` -folder. These are compiled. - -### Using via a CDN - -To use via a CDN, include the following code in your html: - - - -### Using Bower (Deprecated [see official post](https://bower.io/blog/2017/how-to-migrate-away-from-bower/)) - -To install using [Bower](https://bower.io/), enter the following command in your project directory: - - bower install opentype.js - -You can then include them in your scripts using: - - - - -API -=== -### Loading a font -![OpenType.js example Hello World](https://raw.github.com/opentypejs/opentype.js/master/g/hello-world.png) - -Use `opentype.load(url, callback)` to load a font from a URL. Since this method goes out the network, it is asynchronous. -The callback gets `(err, font)` where `font` is a `Font` object. Check if the `err` is null before using the font. -```javascript -opentype.load('fonts/Roboto-Black.ttf', function(err, font) { - if (err) { - alert('Font could not be loaded: ' + err); - } else { - // Now let's display it on a canvas with id "canvas" - const ctx = document.getElementById('canvas').getContext('2d'); - - // Construct a Path object containing the letter shapes of the given text. - // The other parameters are x, y and fontSize. - // Note that y is the position of the baseline. - const path = font.getPath('Hello, World!', 0, 150, 72); - - // If you just want to draw the text you can also use font.draw(ctx, text, x, y, fontSize). - path.draw(ctx); - } -}); -``` - -You can also use `es6 async/await` syntax to load your fonts - -```javascript -async function make(){ - const font = await opentype.load('fonts/Roboto-Black.ttf'); - const path = font.getPath('Hello, World!', 0, 150, 72); - console.log(path); -} -``` - -If you already have an `ArrayBuffer`, you can use `opentype.parse(buffer)` to parse the buffer. This method always -returns a Font, but check `font.supported` to see if the font is in a supported format. (Fonts can be marked unsupported -if they have encoding tables we can't read). - - const font = opentype.parse(myBuffer); - -### Loading a font synchronously (Node.js) -Use `opentype.loadSync(url)` to load a font from a file and return a `Font` object. -Throws an error if the font could not be parsed. This only works in Node.js. - - const font = opentype.loadSync('fonts/Roboto-Black.ttf'); - -### Writing a font -Once you have a `Font` object (either by using `opentype.load` or by creating a new one from scratch) you can write it -back out as a binary file. - -In the browser, you can use `Font.download()` to instruct the browser to download a binary .OTF file. The name is based -on the font name. -```javascript -// Create the bézier paths for each of the glyphs. -// Note that the .notdef glyph is required. -const notdefGlyph = new opentype.Glyph({ - name: '.notdef', - unicode: 0, - advanceWidth: 650, - path: new opentype.Path() -}); - -const aPath = new opentype.Path(); -aPath.moveTo(100, 0); -aPath.lineTo(100, 700); -// more drawing instructions... -const aGlyph = new opentype.Glyph({ - name: 'A', - unicode: 65, - advanceWidth: 650, - path: aPath -}); - -const glyphs = [notdefGlyph, aGlyph]; -const font = new opentype.Font({ - familyName: 'OpenTypeSans', - styleName: 'Medium', - unitsPerEm: 1000, - ascender: 800, - descender: -200, - glyphs: glyphs}); -font.download(); -``` - -If you want to inspect the font, use `font.toTables()` to generate an object showing the data structures that map -directly to binary values. If you want to get an `ArrayBuffer`, use `font.toArrayBuffer()`. - - -### The Font object -A Font represents a loaded OpenType font file. It contains a set of glyphs and methods to draw text on a drawing context, or to get a path representing the text. - -* `glyphs`: an indexed list of Glyph objects. -* `unitsPerEm`: X/Y coordinates in fonts are stored as integers. This value determines the size of the grid. Common values are 2048 and 4096. -* `ascender`: Distance from baseline of highest ascender. In font units, not pixels. -* `descender`: Distance from baseline of lowest descender. In font units, not pixels. - -#### `Font.getPath(text, x, y, fontSize, options)` -Create a Path that represents the given text. -* `x`: Horizontal position of the beginning of the text. (default: 0) -* `y`: Vertical position of the *baseline* of the text. (default: 0) -* `fontSize`: Size of the text in pixels (default: 72). - -Options is an optional object containing: -* `kerning`: if true takes kerning information into account (default: true) -* `features`: an object with [OpenType feature tags](https://docs.microsoft.com/en-us/typography/opentype/spec/featuretags) as keys, and a boolean value to enable each feature. -Currently only ligature features "liga" and "rlig" are supported (default: true). -* `hinting`: if true uses TrueType font hinting if available (default: false). - -_Note: there is also `Font.getPaths` with the same arguments which returns a list of Paths._ - -#### `Font.draw(ctx, text, x, y, fontSize, options)` -Create a Path that represents the given text. -* `ctx`: A 2D drawing context, like Canvas. -* `x`: Horizontal position of the beginning of the text. (default: 0) -* `y`: Vertical position of the *baseline* of the text. (default: 0) -* `fontSize`: Size of the text in pixels (default: 72). - -Options is an optional object containing: -* `kerning`: if true takes kerning information into account (default: true) -* `features`: an object with [OpenType feature tags](https://docs.microsoft.com/en-us/typography/opentype/spec/featuretags) as keys, and a boolean value to enable each feature. -Currently only ligature features "liga" and "rlig" are supported (default: true). -* `hinting`: if true uses TrueType font hinting if available (default: false). - -#### `Font.drawPoints(ctx, text, x, y, fontSize, options)` -Draw the points of all glyphs in the text. On-curve points will be drawn in blue, off-curve points will be drawn in red. The arguments are the same as `Font.draw`. - -#### `Font.drawMetrics(ctx, text, x, y, fontSize, options)` -Draw lines indicating important font measurements for all glyphs in the text. -Black lines indicate the origin of the coordinate system (point 0,0). -Blue lines indicate the glyph bounding box. -Green line indicates the advance width of the glyph. - -#### `Font.stringToGlyphs(string)` -Convert the string to a list of glyph objects. -Note that there is no strict 1-to-1 correspondence between the string and glyph list due to -possible substitutions such as ligatures. The list of returned glyphs can be larger or smaller than the length of the given string. - -#### `Font.charToGlyph(char)` -Convert the character to a `Glyph` object. Returns null if the glyph could not be found. Note that this function assumes that there is a one-to-one mapping between the given character and a glyph; for complex scripts this might not be the case. - -#### `Font.getKerningValue(leftGlyph, rightGlyph)` -Retrieve the value of the [kerning pair](https://en.wikipedia.org/wiki/Kerning) between the left glyph (or its index) and the right glyph (or its index). If no kerning pair is found, return 0. The kerning value gets added to the advance width when calculating the spacing between glyphs. - -#### `Font.getAdvanceWidth(text, fontSize, options)` -Returns the advance width of a text. - -This is something different than Path.getBoundingBox() as for example a -suffixed whitespace increases the advancewidth but not the bounding box -or an overhanging letter like a calligraphic 'f' might have a quite larger -bounding box than its advance width. - -This corresponds to canvas2dContext.measureText(text).width -* `fontSize`: Size of the text in pixels (default: 72). -* `options`: See Font.getPath - -#### The Glyph object -A Glyph is an individual mark that often corresponds to a character. Some glyphs, such as ligatures, are a combination of many characters. Glyphs are the basic building blocks of a font. - -* `font`: A reference to the `Font` object. -* `name`: The glyph name (e.g. "Aring", "five") -* `unicode`: The primary unicode value of this glyph (can be `undefined`). -* `unicodes`: The list of unicode values for this glyph (most of the time this will be 1, can also be empty). -* `index`: The index number of the glyph. -* `advanceWidth`: The width to advance the pen when drawing this glyph. -* `xMin`, `yMin`, `xMax`, `yMax`: The bounding box of the glyph. -* `path`: The raw, unscaled path of the glyph. - -##### `Glyph.getPath(x, y, fontSize)` -Get a scaled glyph Path object we can draw on a drawing context. -* `x`: Horizontal position of the glyph. (default: 0) -* `y`: Vertical position of the *baseline* of the glyph. (default: 0) -* `fontSize`: Font size in pixels (default: 72). - -##### `Glyph.getBoundingBox()` -Calculate the minimum bounding box for the unscaled path of the given glyph. Returns an `opentype.BoundingBox` object that contains x1/y1/x2/y2. -If the glyph has no points (e.g. a space character), all coordinates will be zero. - -##### `Glyph.draw(ctx, x, y, fontSize)` -Draw the glyph on the given context. -* `ctx`: The drawing context. -* `x`: Horizontal position of the glyph. (default: 0) -* `y`: Vertical position of the *baseline* of the glyph. (default: 0) -* `fontSize`: Font size, in pixels (default: 72). - -##### `Glyph.drawPoints(ctx, x, y, fontSize)` -Draw the points of the glyph on the given context. -On-curve points will be drawn in blue, off-curve points will be drawn in red. -The arguments are the same as `Glyph.draw`. - -##### `Glyph.drawMetrics(ctx, x, y, fontSize)` -Draw lines indicating important font measurements for all glyphs in the text. -Black lines indicate the origin of the coordinate system (point 0,0). -Blue lines indicate the glyph bounding box. -Green line indicates the advance width of the glyph. -The arguments are the same as `Glyph.draw`. - -### The Path object -Once you have a path through `Font.getPath` or `Glyph.getPath`, you can use it. - -* `commands`: The path commands. Each command is a dictionary containing a type and coordinates. See below for examples. -* `fill`: The fill color of the `Path`. Color is a string representing a [CSS color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value). (default: 'black') -* `stroke`: The stroke color of the `Path`. Color is a string representing a [CSS color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value). (default: `null`: the path will not be stroked) -* `strokeWidth`: The line thickness of the `Path`. (default: 1, but since the `stroke` is null no stroke will be drawn) - -##### `Path.draw(ctx)` -Draw the path on the given 2D context. This uses the `fill`, `stroke` and `strokeWidth` properties of the `Path` object. -* `ctx`: The drawing context. - -##### `Path.getBoundingBox()` -Calculate the minimum bounding box for the given path. Returns an `opentype.BoundingBox` object that contains x1/y1/x2/y2. -If the path is empty (e.g. a space character), all coordinates will be zero. - -##### `Path.toPathData(decimalPlaces)` -Convert the Path to a string of path data instructions. -See https://www.w3.org/TR/SVG/paths.html#PathData -* `decimalPlaces`: The amount of decimal places for floating-point values. (default: 2) - -##### `Path.toSVG(decimalPlaces)` -Convert the path to a SVG <path> element, as a string. -* `decimalPlaces`: The amount of decimal places for floating-point values. (default: 2) - -#### Path commands -* **Move To**: Move to a new position. This creates a new contour. Example: `{type: 'M', x: 100, y: 200}` -* **Line To**: Draw a line from the previous position to the given coordinate. Example: `{type: 'L', x: 100, y: 200}` -* **Curve To**: Draw a bézier curve from the current position to the given coordinate. Example: `{type: 'C', x1: 0, y1: 50, x2: 100, y2: 200, x: 100, y: 200}` -* **Quad To**: Draw a quadratic bézier curve from the current position to the given coordinate. Example: `{type: 'Q', x1: 0, y1: 50, x: 100, y: 200}` -* **Close**: Close the path. If stroked, this will draw a line from the first to the last point of the contour. Example: `{type: 'Z'}` - - -## Versioning - -We use [SemVer](https://semver.org/) for versioning. - - -## License - -MIT - - -Thanks -====== -I would like to acknowledge the work of others without which opentype.js wouldn't be possible: - -* [pdf.js](https://mozilla.github.io/pdf.js/): for an awesome implementation of font parsing in the browser. -* [FreeType](https://www.freetype.org/): for the nitty-gritty details and filling in the gaps when the spec was incomplete. -* [ttf.js](https://ynakajima.github.io/ttf.js/demo/glyflist/): for hints about the TrueType parsing code. -* [CFF-glyphlet-fonts](https://pomax.github.io/CFF-glyphlet-fonts/): for a great explanation/implementation of CFF font writing. -* [tiny-inflate](https://github.com/foliojs/tiny-inflate): for WOFF decompression. -* [Microsoft Typography](https://docs.microsoft.com/en-us/typography/opentype/spec/otff): the go-to reference for all things OpenType. -* [Adobe Compact Font Format spec](http://download.microsoft.com/download/8/0/1/801a191c-029d-4af3-9642-555f6fe514ee/cff.pdf) and the [Adobe Type 2 Charstring spec](http://download.microsoft.com/download/8/0/1/801a191c-029d-4af3-9642-555f6fe514ee/type2.pdf): explains the data structures and commands for the CFF glyph format. -* All contributing authors mentioned in the [AUTHORS](https://github.com/opentypejs/opentype.js/blob/master/AUTHORS.md) file. diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/RELEASES.md b/node_modules/lv_font_conv/node_modules/opentype.js/RELEASES.md deleted file mode 100644 index e1c698b1..00000000 --- a/node_modules/lv_font_conv/node_modules/opentype.js/RELEASES.md +++ /dev/null @@ -1,267 +0,0 @@ -1.3.3 (April 20, 2020) -===================== -* fix GlyphOptions with falsy values (#430) - -1.3.2 (April 20, 2020) -===================== -* Re-export named exports with a default export and add a TypeScript import example - -* 1.3.1 (April 13, 2020) -===================== -* Revert Fix Path.toPathData and Path.toSVG - X Axis is flipped (#369) - -1.3.0 (April 13, 2020) -===================== -* Forward os2 Table attributs during font construction (#422) -* Add default export - -1.2.1 (April 13, 2020) -===================== -* Fix Path.toPathData and Path.toSVG - X Axis is flipped (#369) -* Fix use of Promise / async/await in the load function (#427) -* Fix a bug for unsupported SUBSTITUTIONS #403 - -1.2.0 (April 13, 2020) -===================== -* Fix issue #385, merge default options with user options (#386) -* Adds support for browser Async/Await for .load() (#389) -* Introduce ES6 module build (#391) -* Fix test in featureQuery -* Remove Node 4 from Travis (#392) -* Update dependencies & build dist files - -1.1.0 (May 1, 2019) -===================== -* Support reading GSUB Single substitution format 1 (PR #382) (thanks @solomancode!) - -1.0.1 (April 19, 2019) -===================== -* Fix error if defaultLangSys is undefined (Issue #378) - -1.0.0 (April 17, 2019) -===================== -* Render arabic rtl text properly (PR #361, partial fix of #364) (thanks @solomancode!) -* #361 introduced a breaking change to `Font.prototype.defaultRenderOptions` -Before -```js -Font.prototype.defaultRenderOptions = { - kerning: true, - features: { - liga: true, - rlig: true - } -}; -``` - -Now -```js -Font.prototype.defaultRenderOptions = { - kerning: true, - features: [ - /** - * these 4 features are required to render Arabic text properly - * and shouldn't be turned off when rendering arabic text. - */ - { script: 'arab', tags: ['init', 'medi', 'fina', 'rlig'] }, - { script: 'latn', tags: ['liga', 'rlig'] } - ] -}; -``` - -Also as this project is now using SemVer, the breaking change required a new major version, 1.0.0! - -0.12.0 (April 17, 2019) -===================== -* Fix Glyph.getPath() issue (PR #362, fixes #363) (thanks @solomancode!) -* Add lowMemory mode (PR #329) (thanks @debussy2k!) -* Update README (PR #377) (thanks @jolg42!) - -0.11.0 (October 22, 2018) -===================== -* Support Arabic text rendering (PR #359, fixes #46) (thanks @solomancode!) - -0.10.0 (August 14, 2018) -===================== -* font.download(): use window.URL instead of window.requestFileSystem, which works on a larger set of browsers : Chrome (32+), Opera (19+), Firefox (26+), Safari (7.1+), and all of Edge. - -0.9.0 (June 21, 2018) -===================== -* Update/Migrate rollup, update all dependencies, add package-lock.json and fix circular dependency (thanks @jolg42!) -* Parse cmap table with platform id 0 as well (PR #350, fixes #348) (thanks @moyogo!) -* Prevent auto-generated postScriptName from containing whitespace (#339) (thanks @mqudsi!) -* Support non-Basic-Multilingual-Plane (BMP) characters (#338) (thanks @antonytse!) -* GPOS: display correct error message in some cases of malformed data (#336) (thanks @fpirsch!) -* Restore simple GPOS kerning in font.getKerningValue (#335) (thanks @fpirsch!) -* Fix duplicated lineTo when using `getPath` (#328) (thanks @jolg42!) -* Change example generate-font-node.js to be compatible with any Node.js version (thanks @jolg42!) - -0.8.0 (March 6, 2018) -===================== -* Fix loading font file on Android devices (thanks @maoamid!). -* Fix loading fonts from a local source (file://data/... for Android for example (thanks @IntuilabGit!). -* Fixing 2 issues when hinting "mutlu.ttf" (thanks @axkibe!). -* Add some support for OpenType font variations (thanks @taylorb-monotype!). -* Make cmap table format 12 if needed (thanks @Jolg42!). -* Enable uglify's mangle and compress optimizations for a ~30% smaller minified file. (thanks @lojjic & @Jolg42!). -* Better parsing of NULL pointers (thanks @fpirsch!). -* Fix bad path init (empty glyphs) (thanks @fpirsch!). -* Rewrite GPOS parsing (thanks @fpirsch!). -* Roboto-Black.ttf updated (thanks @Jolg42!). - -0.7.3 (July 18, 2017) -===================== -* Fix "Object x already has key" error in Safari (thanks @neiltron!). -* Fixed a bug where Font.getPaths() didn't pass options (thanks @keeslinp!). - -0.7.2 (June 7, 2017) -==================== -* WOFF fonts with cvt tables now parse correctly. -* Migrated to ES6 modules and let/const. -* Use Rollup to bundle the JavaScript. - -0.7.1 (Apr 25, 2017) -==================== -* Auto-generated glyph IDs (CID-keyed fonts) are now prefixed with "gid", e.g. "gid42". -* Fix ligature substitution for fonts with coverage table format 2. -* Better error messages when no valid cmap is found. - -0.7.0 (Apr 25, 2017) -==================== -* Add font hinting (thanks @axkibe!) -* Add support for CID-keyed fonts, thanks to @tshinnic. -* TrueType fonts with signature 'true' or 'typ1' are also supported. -* Fixing rounding issues. -* Add GSUB and kern output in font-inspector. -* Add font loading error callback. -* Dev server turns browser caching off. -* Add encoding support for variation adjustment deltas (thanks @brawer!). - -0.6.9 (Jan 17, 2017) -==================== -* Add ligature rendering (thanks @fpirsch!) - -0.6.8 (Jan 9, 2017) -========================= -* Add a `getBoundingBox` method to the `Path` and `Glyph` objects. - -0.6.7 (Jan 5, 2017) -========================= -* Add basic support for Mac OS X format kern tables. - -0.6.6 (October 25, 2016) -========================= -* Add support for letter-spacing and tracking (thanks @lachmanski!). -* Fixed a bug in the nameToGlyph function. - -0.6.5 (September 9, 2016) -========================= -* GSUB reading and writing by @fpirsch. This is still missing a user-friendly API. -* Add support for cmap table format 12, which enables support for Unicode characters outside of the 0x0 - 0xFFFF range. -* Better API documentation using [JSDoc](http://usejsdoc.org/). -* Accessing xMin/... metrics works before path load.
 - -0.6.4 (June 30, 2016) -========================= -* Add X/Y scale options to compute a streched path of a glyph. -* Correct reading/writing of font timestamps. -* examples/generate-font-node.js now generates "full" Latin font. -* Add OS/2 value options for weight, width and fsSelection. - -0.6.3 (May 10, 2016) -========================= -* Wrapped parseBuffer in a try/catch so it doesn't throw exceptions. Thanks @rBurgett! -* Fix a leaking global variable. Thanks @cuixiping! - -0.6.2 (March 11, 2016) -========================= -* Improve table writing to support nested subtables. Thanks @fpirsch! - -0.6.1 (February 20, 2016) -========================= -* Left side bearing is now correctly reported. -* Simplified code for including ascender / descender values. - -0.6.0 (December 1, 2015) -======================== -* Improvements to font writing: generated fonts now work properly on OS X. -* When creating a new font, ascender and descender are now required. - -0.5.1 (October 26, 2015) -======================== -* Add `Font.getPaths()` which returns a list of paths. - -0.5.0 (October 6, 2015) -======================= -* Read support for WOFF. - -0.4.11 (September 27, 2015) -=========================== -* Fix issue with loading of TrueType composite glyphs. -* Fix issue with missing hmtx values. -* Sensible getMetrics() values for empty glyphs (e.g. space). - -0.4.10 (July 30, 2015) -====================== -* Add loadSync method for Node.js. -* Unit tests for basic types and tables. -* Implement MACSTRING codec. -* Support multilingual names. -* Handle names of font variation axes and instances. - -0.4.9 (June 23, 2015) -===================== -* Improve memory usage by deferring glyph / path loading. Thanks @Pomax! -* Put examples in the "examples" directory. Use the local web server to see them. - -0.4.8 (June 3, 2015) -==================== -* Fix an issue with writing out fonts that have an UPM != 1000. - -0.4.6 (March 26, 2015) -====================== -* Fix issues with exporting/subsetting TrueType fonts. -* Improve validness of exported fonts. -* Empty paths (think: space) no longer contain a single closePath command. -* Fix issues with exporting fonts with TrueType half-point values. -* Expose the internal byte parsing algorithms as opentype._parse. - -0.4.5 (March 10, 2015) -====================== -* Add support for writing quad curves. -* Add support for CFF flex operators. -* Close CFF subpaths. - -0.4.4 (Dec 8, 2014) -=================== -* Solve issues with Browserify. - -0.4.3 (Nov 26, 2014) -==================== -* Un-break node.js support. - -0.4.2 (Nov 24, 2014) -==================== -* 2x speedup when writing fonts, thanks @louisremi! - -0.4.1 (Nov 10, 2014) -==================== -* Fix bug that prevented `npm install`. - -0.4.0 (Nov 10, 2014) -==================== -* Add support for font writing. - -0.3.0 (Jun 10, 2014) -==================== -* Support for GPOS kerning, which works in both PostScript and OpenType. -* Big performance improvements. -* The font and glyph inspector can visually debug a font. - -0.2.0 (Feb 7, 2014) -=================== -* Support for reading PostScript fonts. - -0.1.0 (Sep 27, 2013) -==================== -* Initial release. -* Supports reading TrueType CFF fonts. diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/bin/ot b/node_modules/lv_font_conv/node_modules/opentype.js/bin/ot deleted file mode 100755 index af990cd2..00000000 --- a/node_modules/lv_font_conv/node_modules/opentype.js/bin/ot +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env node -/* eslint no-console: off */ - -import fs from 'fs'; -import path from 'path'; -import { load } from '../src/opentype'; - -// Print out information about the font on the console. -function printFontInfo(font) { - console.log(' glyphs:', font.glyphs.length); - console.log(' kerning pairs (kern table):', Object.keys(font.kerningPairs).length); - console.log(' kerning pairs (GPOS table):', (font.getGposKerningValue) ? 'yes' : 'no'); -} - -// Recursively walk a directory and execute the function for every file. -function walk(dir, fn) { - var files, i, file; - files = fs.readdirSync(dir); - for (i = 0; i < files.length; i += 1) { - file = files[i]; - var fullName = path.join(dir, file); - var stat = fs.statSync(fullName); - if (stat.isFile()) { - fn(fullName); - } else if (stat.isDirectory()) { - walk(fullName, fn); - } - } -} - -// Print out usage information. -function printUsage() { - console.log('Usage: ot command [dir|file]'); - console.log(); - console.log('Commands:'); - console.log(); - console.log(' info Get information of specified font or fonts in the specified directory.'); - console.log(); -} - -function fileInfo(file) { - load(file, function(err, font) { - console.log(path.basename(file)); - if (err) { - console.log(' (Error: ' + err + ')'); - } else if (!font.supported) { - console.log(' (Unsupported)'); - } else { - printFontInfo(font); - } - }); -} - -function recursiveInfo(fontDirectory) { - walk(fontDirectory, function(file) { - var ext = path.extname(file).toLowerCase(); - if (ext === '.ttf' || ext === '.otf') { - fileInfo(file); - } - }); -} - -if (process.argv.length < 3) { - printUsage(); -} else { - var command = process.argv[2]; - if (command === 'info') { - var fontpath = process.argv.length === 3 ? '.' : process.argv[3]; - if (fs.existsSync(fontpath)) { - var ext = path.extname(fontpath).toLowerCase(); - if (fs.statSync(fontpath).isDirectory()) { - recursiveInfo(fontpath); - } else if (ext === '.ttf' || ext === '.otf') { - fileInfo(fontpath); - } else { - printUsage(); - } - } else { - console.log('Path not found'); - } - } else { - printUsage(); - } -} diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/bin/server.js b/node_modules/lv_font_conv/node_modules/opentype.js/bin/server.js deleted file mode 100755 index f6f450a4..00000000 --- a/node_modules/lv_font_conv/node_modules/opentype.js/bin/server.js +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env node - -var fs = require('fs'); -var http = require('http'); -var path = require('path'); -var rollup = require('rollup'); -var rollupConfig = require('../rollup.config'); - -var CONTENT_TYPES = { - '.html': 'text/html', - '.css': 'text/css', - '.png': 'image/png', - '.js': 'text/javascript', - '.ttf': 'font/otf', - '.otf': 'font/otf', - '.woff': 'font/woff', - '.woff2': 'font/woff2', -}; - -http.createServer(function(req, res) { - var rewrite = ''; - var url = req.url.substring(1); - if (url.length === 0) { - url = 'index.html'; - rewrite = ' -> ' + url; - } - - console.log('HTTP', req.url, rewrite); - var filePath = './' + url; - fs.readFile(filePath, function(err, data) { - if (err) { - res.writeHead(404, {'Content-Type': 'text/plain'}); - res.end('Error: ' + err); - } else { - var contentType = CONTENT_TYPES[path.extname(filePath)] || 'text/plain'; - res.writeHead(200, { - 'Content-Type': contentType, - 'Cache-Control': 'max-age=0' - }); - res.end(data); - } - }); -}).listen(8080); -console.log('Server running at http://localhost:8080/'); - -// Watch changes and rebundle -var watcher = rollup.watch(rollupConfig); -watcher.on('event', e => { - // event.code can be one of: - // START — the watcher is (re)starting - // BUNDLE_START — building an individual bundle - // BUNDLE_END — finished building a bundle - // END — finished building all bundles - // ERROR — encountered an error while bundling - // FATAL — encountered an unrecoverable error - - if (e.code === 'BUNDLE_START') { - console.log('Bundling...'); - } else if (e.code === 'BUNDLE_END') { - console.log('Bundled in ' + e.duration + 'ms.'); - } else if (e.code === 'ERROR' || e.code === 'FATAL') { - console.error(e.error); - } -}); diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/bin/test-render b/node_modules/lv_font_conv/node_modules/opentype.js/bin/test-render deleted file mode 100755 index 4bfc31e6..00000000 --- a/node_modules/lv_font_conv/node_modules/opentype.js/bin/test-render +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env node -// This is a command to test the text rendering compliance of OpenType.js. -// It is designed to operate with https://github.com/unicode-org/text-rendering-tests. -// -// Call it like this: -// -// ./bin/test-render --font=fonts/FiraSansOT-Medium.otf --testcase=TEST-1 --render=BALL -// -// The output will look like this: -// -// -// -// -// -// -// -// -// -// -// -// -// When viewing the SVG, it will be upside-down (since glyphs are designed Y-up). - -var opentype = require('../dist/opentype.js'); - -const SVG_FOOTER = ``; - -function printUsage() { - console.log('Usage: test-render --font=filename.otf --testcase=TEST_NAME --render=TEXT_TO_RENDER'); - console.log('This commands output the text to render as an SVG file.'); - console.log(); -} - -let filename; -let testcase; -let textToRender; -for (let i = 0; i < process.argv.length; i++) { - const arg = process.argv[i]; - if (arg.startsWith('--font=')) { - filename = arg.substring('--font='.length); - } else if (arg.startsWith('--testcase=')) { - testcase = arg.substring('--testcase='.length); - } else if (arg.startsWith('--render=')) { - textToRender = arg.substring('--render='.length); - } -} - -if (filename === undefined || testcase === undefined || textToRender === undefined) { - printUsage(); - process.exit(1); -} - -function renderSVG() { - var font = opentype.loadSync(filename); - - let svgSymbols = []; - let svgBody = []; - - var glyphSet = new Set(); - let x = 0; - const glyphs = font.stringToGlyphs(textToRender); - for (let i = 0; i < glyphs.length; i++) { - const glyph = glyphs[i]; - const symbolId = testcase + '.' + glyph.name; - if (!glyphSet.has(glyph)) { - glyphSet.add(glyph); - const svgPath = glyph.path.toSVG(); - svgSymbols.push(` ${svgPath}`); - } - svgBody.push(` `); - x += glyph.advanceWidth; - } - - let minX = 0; - let minY = Math.round(font.descender); - let width = Math.round(x); - let height = Math.round(font.ascender - font.descender); - let svgHeader = ` -`; - - return svgHeader + svgSymbols.join('\n') + svgBody.join('\n') + SVG_FOOTER; -} - -try { - var svg = renderSVG(); - console.log(svg); -} catch(e) { - console.error(e.stack); - process.exit(1); -} diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/dist/opentype.js b/node_modules/lv_font_conv/node_modules/opentype.js/dist/opentype.js deleted file mode 100644 index 11b0a548..00000000 --- a/node_modules/lv_font_conv/node_modules/opentype.js/dist/opentype.js +++ /dev/null @@ -1,14254 +0,0 @@ -/** - * https://opentype.js.org v1.3.3 | (c) Frederik De Bleser and other contributors | MIT License | Uses tiny-inflate by Devon Govett and string.prototype.codepointat polyfill by Mathias Bynens - */ - -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : - typeof define === 'function' && define.amd ? define(['exports'], factory) : - (global = global || self, factory(global.opentype = {})); -}(this, (function (exports) { 'use strict'; - - /*! https://mths.be/codepointat v0.2.0 by @mathias */ - if (!String.prototype.codePointAt) { - (function() { - var defineProperty = (function() { - // IE 8 only supports `Object.defineProperty` on DOM elements - try { - var object = {}; - var $defineProperty = Object.defineProperty; - var result = $defineProperty(object, object, object) && $defineProperty; - } catch(error) {} - return result; - }()); - var codePointAt = function(position) { - if (this == null) { - throw TypeError(); - } - var string = String(this); - var size = string.length; - // `ToInteger` - var index = position ? Number(position) : 0; - if (index != index) { // better `isNaN` - index = 0; - } - // Account for out-of-bounds indices: - if (index < 0 || index >= size) { - return undefined; - } - // Get the first code unit - var first = string.charCodeAt(index); - var second; - if ( // check if it’s the start of a surrogate pair - first >= 0xD800 && first <= 0xDBFF && // high surrogate - size > index + 1 // there is a next code unit - ) { - second = string.charCodeAt(index + 1); - if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate - // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae - return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; - } - } - return first; - }; - if (defineProperty) { - defineProperty(String.prototype, 'codePointAt', { - 'value': codePointAt, - 'configurable': true, - 'writable': true - }); - } else { - String.prototype.codePointAt = codePointAt; - } - }()); - } - - var TINF_OK = 0; - var TINF_DATA_ERROR = -3; - - function Tree() { - this.table = new Uint16Array(16); /* table of code length counts */ - this.trans = new Uint16Array(288); /* code -> symbol translation table */ - } - - function Data(source, dest) { - this.source = source; - this.sourceIndex = 0; - this.tag = 0; - this.bitcount = 0; - - this.dest = dest; - this.destLen = 0; - - this.ltree = new Tree(); /* dynamic length/symbol tree */ - this.dtree = new Tree(); /* dynamic distance tree */ - } - - /* --------------------------------------------------- * - * -- uninitialized global data (static structures) -- * - * --------------------------------------------------- */ - - var sltree = new Tree(); - var sdtree = new Tree(); - - /* extra bits and base tables for length codes */ - var length_bits = new Uint8Array(30); - var length_base = new Uint16Array(30); - - /* extra bits and base tables for distance codes */ - var dist_bits = new Uint8Array(30); - var dist_base = new Uint16Array(30); - - /* special ordering of code length codes */ - var clcidx = new Uint8Array([ - 16, 17, 18, 0, 8, 7, 9, 6, - 10, 5, 11, 4, 12, 3, 13, 2, - 14, 1, 15 - ]); - - /* used by tinf_decode_trees, avoids allocations every call */ - var code_tree = new Tree(); - var lengths = new Uint8Array(288 + 32); - - /* ----------------------- * - * -- utility functions -- * - * ----------------------- */ - - /* build extra bits and base tables */ - function tinf_build_bits_base(bits, base, delta, first) { - var i, sum; - - /* build bits table */ - for (i = 0; i < delta; ++i) { bits[i] = 0; } - for (i = 0; i < 30 - delta; ++i) { bits[i + delta] = i / delta | 0; } - - /* build base table */ - for (sum = first, i = 0; i < 30; ++i) { - base[i] = sum; - sum += 1 << bits[i]; - } - } - - /* build the fixed huffman trees */ - function tinf_build_fixed_trees(lt, dt) { - var i; - - /* build fixed length tree */ - for (i = 0; i < 7; ++i) { lt.table[i] = 0; } - - lt.table[7] = 24; - lt.table[8] = 152; - lt.table[9] = 112; - - for (i = 0; i < 24; ++i) { lt.trans[i] = 256 + i; } - for (i = 0; i < 144; ++i) { lt.trans[24 + i] = i; } - for (i = 0; i < 8; ++i) { lt.trans[24 + 144 + i] = 280 + i; } - for (i = 0; i < 112; ++i) { lt.trans[24 + 144 + 8 + i] = 144 + i; } - - /* build fixed distance tree */ - for (i = 0; i < 5; ++i) { dt.table[i] = 0; } - - dt.table[5] = 32; - - for (i = 0; i < 32; ++i) { dt.trans[i] = i; } - } - - /* given an array of code lengths, build a tree */ - var offs = new Uint16Array(16); - - function tinf_build_tree(t, lengths, off, num) { - var i, sum; - - /* clear code length count table */ - for (i = 0; i < 16; ++i) { t.table[i] = 0; } - - /* scan symbol lengths, and sum code length counts */ - for (i = 0; i < num; ++i) { t.table[lengths[off + i]]++; } - - t.table[0] = 0; - - /* compute offset table for distribution sort */ - for (sum = 0, i = 0; i < 16; ++i) { - offs[i] = sum; - sum += t.table[i]; - } - - /* create code->symbol translation table (symbols sorted by code) */ - for (i = 0; i < num; ++i) { - if (lengths[off + i]) { t.trans[offs[lengths[off + i]]++] = i; } - } - } - - /* ---------------------- * - * -- decode functions -- * - * ---------------------- */ - - /* get one bit from source stream */ - function tinf_getbit(d) { - /* check if tag is empty */ - if (!d.bitcount--) { - /* load next tag */ - d.tag = d.source[d.sourceIndex++]; - d.bitcount = 7; - } - - /* shift bit out of tag */ - var bit = d.tag & 1; - d.tag >>>= 1; - - return bit; - } - - /* read a num bit value from a stream and add base */ - function tinf_read_bits(d, num, base) { - if (!num) - { return base; } - - while (d.bitcount < 24) { - d.tag |= d.source[d.sourceIndex++] << d.bitcount; - d.bitcount += 8; - } - - var val = d.tag & (0xffff >>> (16 - num)); - d.tag >>>= num; - d.bitcount -= num; - return val + base; - } - - /* given a data stream and a tree, decode a symbol */ - function tinf_decode_symbol(d, t) { - while (d.bitcount < 24) { - d.tag |= d.source[d.sourceIndex++] << d.bitcount; - d.bitcount += 8; - } - - var sum = 0, cur = 0, len = 0; - var tag = d.tag; - - /* get more bits while code value is above sum */ - do { - cur = 2 * cur + (tag & 1); - tag >>>= 1; - ++len; - - sum += t.table[len]; - cur -= t.table[len]; - } while (cur >= 0); - - d.tag = tag; - d.bitcount -= len; - - return t.trans[sum + cur]; - } - - /* given a data stream, decode dynamic trees from it */ - function tinf_decode_trees(d, lt, dt) { - var hlit, hdist, hclen; - var i, num, length; - - /* get 5 bits HLIT (257-286) */ - hlit = tinf_read_bits(d, 5, 257); - - /* get 5 bits HDIST (1-32) */ - hdist = tinf_read_bits(d, 5, 1); - - /* get 4 bits HCLEN (4-19) */ - hclen = tinf_read_bits(d, 4, 4); - - for (i = 0; i < 19; ++i) { lengths[i] = 0; } - - /* read code lengths for code length alphabet */ - for (i = 0; i < hclen; ++i) { - /* get 3 bits code length (0-7) */ - var clen = tinf_read_bits(d, 3, 0); - lengths[clcidx[i]] = clen; - } - - /* build code length tree */ - tinf_build_tree(code_tree, lengths, 0, 19); - - /* decode code lengths for the dynamic trees */ - for (num = 0; num < hlit + hdist;) { - var sym = tinf_decode_symbol(d, code_tree); - - switch (sym) { - case 16: - /* copy previous code length 3-6 times (read 2 bits) */ - var prev = lengths[num - 1]; - for (length = tinf_read_bits(d, 2, 3); length; --length) { - lengths[num++] = prev; - } - break; - case 17: - /* repeat code length 0 for 3-10 times (read 3 bits) */ - for (length = tinf_read_bits(d, 3, 3); length; --length) { - lengths[num++] = 0; - } - break; - case 18: - /* repeat code length 0 for 11-138 times (read 7 bits) */ - for (length = tinf_read_bits(d, 7, 11); length; --length) { - lengths[num++] = 0; - } - break; - default: - /* values 0-15 represent the actual code lengths */ - lengths[num++] = sym; - break; - } - } - - /* build dynamic trees */ - tinf_build_tree(lt, lengths, 0, hlit); - tinf_build_tree(dt, lengths, hlit, hdist); - } - - /* ----------------------------- * - * -- block inflate functions -- * - * ----------------------------- */ - - /* given a stream and two trees, inflate a block of data */ - function tinf_inflate_block_data(d, lt, dt) { - while (1) { - var sym = tinf_decode_symbol(d, lt); - - /* check for end of block */ - if (sym === 256) { - return TINF_OK; - } - - if (sym < 256) { - d.dest[d.destLen++] = sym; - } else { - var length, dist, offs; - var i; - - sym -= 257; - - /* possibly get more bits from length code */ - length = tinf_read_bits(d, length_bits[sym], length_base[sym]); - - dist = tinf_decode_symbol(d, dt); - - /* possibly get more bits from distance code */ - offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]); - - /* copy match */ - for (i = offs; i < offs + length; ++i) { - d.dest[d.destLen++] = d.dest[i]; - } - } - } - } - - /* inflate an uncompressed block of data */ - function tinf_inflate_uncompressed_block(d) { - var length, invlength; - var i; - - /* unread from bitbuffer */ - while (d.bitcount > 8) { - d.sourceIndex--; - d.bitcount -= 8; - } - - /* get length */ - length = d.source[d.sourceIndex + 1]; - length = 256 * length + d.source[d.sourceIndex]; - - /* get one's complement of length */ - invlength = d.source[d.sourceIndex + 3]; - invlength = 256 * invlength + d.source[d.sourceIndex + 2]; - - /* check length */ - if (length !== (~invlength & 0x0000ffff)) - { return TINF_DATA_ERROR; } - - d.sourceIndex += 4; - - /* copy block */ - for (i = length; i; --i) - { d.dest[d.destLen++] = d.source[d.sourceIndex++]; } - - /* make sure we start next block on a byte boundary */ - d.bitcount = 0; - - return TINF_OK; - } - - /* inflate stream from source to dest */ - function tinf_uncompress(source, dest) { - var d = new Data(source, dest); - var bfinal, btype, res; - - do { - /* read final block flag */ - bfinal = tinf_getbit(d); - - /* read block type (2 bits) */ - btype = tinf_read_bits(d, 2, 0); - - /* decompress block */ - switch (btype) { - case 0: - /* decompress uncompressed block */ - res = tinf_inflate_uncompressed_block(d); - break; - case 1: - /* decompress block with fixed huffman trees */ - res = tinf_inflate_block_data(d, sltree, sdtree); - break; - case 2: - /* decompress block with dynamic huffman trees */ - tinf_decode_trees(d, d.ltree, d.dtree); - res = tinf_inflate_block_data(d, d.ltree, d.dtree); - break; - default: - res = TINF_DATA_ERROR; - } - - if (res !== TINF_OK) - { throw new Error('Data error'); } - - } while (!bfinal); - - if (d.destLen < d.dest.length) { - if (typeof d.dest.slice === 'function') - { return d.dest.slice(0, d.destLen); } - else - { return d.dest.subarray(0, d.destLen); } - } - - return d.dest; - } - - /* -------------------- * - * -- initialization -- * - * -------------------- */ - - /* build fixed huffman trees */ - tinf_build_fixed_trees(sltree, sdtree); - - /* build extra bits and base tables */ - tinf_build_bits_base(length_bits, length_base, 4, 3); - tinf_build_bits_base(dist_bits, dist_base, 2, 1); - - /* fix a special case */ - length_bits[28] = 0; - length_base[28] = 258; - - var tinyInflate = tinf_uncompress; - - // The Bounding Box object - - function derive(v0, v1, v2, v3, t) { - return Math.pow(1 - t, 3) * v0 + - 3 * Math.pow(1 - t, 2) * t * v1 + - 3 * (1 - t) * Math.pow(t, 2) * v2 + - Math.pow(t, 3) * v3; - } - /** - * A bounding box is an enclosing box that describes the smallest measure within which all the points lie. - * It is used to calculate the bounding box of a glyph or text path. - * - * On initialization, x1/y1/x2/y2 will be NaN. Check if the bounding box is empty using `isEmpty()`. - * - * @exports opentype.BoundingBox - * @class - * @constructor - */ - function BoundingBox() { - this.x1 = Number.NaN; - this.y1 = Number.NaN; - this.x2 = Number.NaN; - this.y2 = Number.NaN; - } - - /** - * Returns true if the bounding box is empty, that is, no points have been added to the box yet. - */ - BoundingBox.prototype.isEmpty = function() { - return isNaN(this.x1) || isNaN(this.y1) || isNaN(this.x2) || isNaN(this.y2); - }; - - /** - * Add the point to the bounding box. - * The x1/y1/x2/y2 coordinates of the bounding box will now encompass the given point. - * @param {number} x - The X coordinate of the point. - * @param {number} y - The Y coordinate of the point. - */ - BoundingBox.prototype.addPoint = function(x, y) { - if (typeof x === 'number') { - if (isNaN(this.x1) || isNaN(this.x2)) { - this.x1 = x; - this.x2 = x; - } - if (x < this.x1) { - this.x1 = x; - } - if (x > this.x2) { - this.x2 = x; - } - } - if (typeof y === 'number') { - if (isNaN(this.y1) || isNaN(this.y2)) { - this.y1 = y; - this.y2 = y; - } - if (y < this.y1) { - this.y1 = y; - } - if (y > this.y2) { - this.y2 = y; - } - } - }; - - /** - * Add a X coordinate to the bounding box. - * This extends the bounding box to include the X coordinate. - * This function is used internally inside of addBezier. - * @param {number} x - The X coordinate of the point. - */ - BoundingBox.prototype.addX = function(x) { - this.addPoint(x, null); - }; - - /** - * Add a Y coordinate to the bounding box. - * This extends the bounding box to include the Y coordinate. - * This function is used internally inside of addBezier. - * @param {number} y - The Y coordinate of the point. - */ - BoundingBox.prototype.addY = function(y) { - this.addPoint(null, y); - }; - - /** - * Add a Bézier curve to the bounding box. - * This extends the bounding box to include the entire Bézier. - * @param {number} x0 - The starting X coordinate. - * @param {number} y0 - The starting Y coordinate. - * @param {number} x1 - The X coordinate of the first control point. - * @param {number} y1 - The Y coordinate of the first control point. - * @param {number} x2 - The X coordinate of the second control point. - * @param {number} y2 - The Y coordinate of the second control point. - * @param {number} x - The ending X coordinate. - * @param {number} y - The ending Y coordinate. - */ - BoundingBox.prototype.addBezier = function(x0, y0, x1, y1, x2, y2, x, y) { - // This code is based on http://nishiohirokazu.blogspot.com/2009/06/how-to-calculate-bezier-curves-bounding.html - // and https://github.com/icons8/svg-path-bounding-box - - var p0 = [x0, y0]; - var p1 = [x1, y1]; - var p2 = [x2, y2]; - var p3 = [x, y]; - - this.addPoint(x0, y0); - this.addPoint(x, y); - - for (var i = 0; i <= 1; i++) { - var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i]; - var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i]; - var c = 3 * p1[i] - 3 * p0[i]; - - if (a === 0) { - if (b === 0) { continue; } - var t = -c / b; - if (0 < t && t < 1) { - if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t)); } - if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t)); } - } - continue; - } - - var b2ac = Math.pow(b, 2) - 4 * c * a; - if (b2ac < 0) { continue; } - var t1 = (-b + Math.sqrt(b2ac)) / (2 * a); - if (0 < t1 && t1 < 1) { - if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t1)); } - if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t1)); } - } - var t2 = (-b - Math.sqrt(b2ac)) / (2 * a); - if (0 < t2 && t2 < 1) { - if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t2)); } - if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t2)); } - } - } - }; - - /** - * Add a quadratic curve to the bounding box. - * This extends the bounding box to include the entire quadratic curve. - * @param {number} x0 - The starting X coordinate. - * @param {number} y0 - The starting Y coordinate. - * @param {number} x1 - The X coordinate of the control point. - * @param {number} y1 - The Y coordinate of the control point. - * @param {number} x - The ending X coordinate. - * @param {number} y - The ending Y coordinate. - */ - BoundingBox.prototype.addQuad = function(x0, y0, x1, y1, x, y) { - var cp1x = x0 + 2 / 3 * (x1 - x0); - var cp1y = y0 + 2 / 3 * (y1 - y0); - var cp2x = cp1x + 1 / 3 * (x - x0); - var cp2y = cp1y + 1 / 3 * (y - y0); - this.addBezier(x0, y0, cp1x, cp1y, cp2x, cp2y, x, y); - }; - - // Geometric objects - - /** - * A bézier path containing a set of path commands similar to a SVG path. - * Paths can be drawn on a context using `draw`. - * @exports opentype.Path - * @class - * @constructor - */ - function Path() { - this.commands = []; - this.fill = 'black'; - this.stroke = null; - this.strokeWidth = 1; - } - - /** - * @param {number} x - * @param {number} y - */ - Path.prototype.moveTo = function(x, y) { - this.commands.push({ - type: 'M', - x: x, - y: y - }); - }; - - /** - * @param {number} x - * @param {number} y - */ - Path.prototype.lineTo = function(x, y) { - this.commands.push({ - type: 'L', - x: x, - y: y - }); - }; - - /** - * Draws cubic curve - * @function - * curveTo - * @memberof opentype.Path.prototype - * @param {number} x1 - x of control 1 - * @param {number} y1 - y of control 1 - * @param {number} x2 - x of control 2 - * @param {number} y2 - y of control 2 - * @param {number} x - x of path point - * @param {number} y - y of path point - */ - - /** - * Draws cubic curve - * @function - * bezierCurveTo - * @memberof opentype.Path.prototype - * @param {number} x1 - x of control 1 - * @param {number} y1 - y of control 1 - * @param {number} x2 - x of control 2 - * @param {number} y2 - y of control 2 - * @param {number} x - x of path point - * @param {number} y - y of path point - * @see curveTo - */ - Path.prototype.curveTo = Path.prototype.bezierCurveTo = function(x1, y1, x2, y2, x, y) { - this.commands.push({ - type: 'C', - x1: x1, - y1: y1, - x2: x2, - y2: y2, - x: x, - y: y - }); - }; - - /** - * Draws quadratic curve - * @function - * quadraticCurveTo - * @memberof opentype.Path.prototype - * @param {number} x1 - x of control - * @param {number} y1 - y of control - * @param {number} x - x of path point - * @param {number} y - y of path point - */ - - /** - * Draws quadratic curve - * @function - * quadTo - * @memberof opentype.Path.prototype - * @param {number} x1 - x of control - * @param {number} y1 - y of control - * @param {number} x - x of path point - * @param {number} y - y of path point - */ - Path.prototype.quadTo = Path.prototype.quadraticCurveTo = function(x1, y1, x, y) { - this.commands.push({ - type: 'Q', - x1: x1, - y1: y1, - x: x, - y: y - }); - }; - - /** - * Closes the path - * @function closePath - * @memberof opentype.Path.prototype - */ - - /** - * Close the path - * @function close - * @memberof opentype.Path.prototype - */ - Path.prototype.close = Path.prototype.closePath = function() { - this.commands.push({ - type: 'Z' - }); - }; - - /** - * Add the given path or list of commands to the commands of this path. - * @param {Array} pathOrCommands - another opentype.Path, an opentype.BoundingBox, or an array of commands. - */ - Path.prototype.extend = function(pathOrCommands) { - if (pathOrCommands.commands) { - pathOrCommands = pathOrCommands.commands; - } else if (pathOrCommands instanceof BoundingBox) { - var box = pathOrCommands; - this.moveTo(box.x1, box.y1); - this.lineTo(box.x2, box.y1); - this.lineTo(box.x2, box.y2); - this.lineTo(box.x1, box.y2); - this.close(); - return; - } - - Array.prototype.push.apply(this.commands, pathOrCommands); - }; - - /** - * Calculate the bounding box of the path. - * @returns {opentype.BoundingBox} - */ - Path.prototype.getBoundingBox = function() { - var box = new BoundingBox(); - - var startX = 0; - var startY = 0; - var prevX = 0; - var prevY = 0; - for (var i = 0; i < this.commands.length; i++) { - var cmd = this.commands[i]; - switch (cmd.type) { - case 'M': - box.addPoint(cmd.x, cmd.y); - startX = prevX = cmd.x; - startY = prevY = cmd.y; - break; - case 'L': - box.addPoint(cmd.x, cmd.y); - prevX = cmd.x; - prevY = cmd.y; - break; - case 'Q': - box.addQuad(prevX, prevY, cmd.x1, cmd.y1, cmd.x, cmd.y); - prevX = cmd.x; - prevY = cmd.y; - break; - case 'C': - box.addBezier(prevX, prevY, cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); - prevX = cmd.x; - prevY = cmd.y; - break; - case 'Z': - prevX = startX; - prevY = startY; - break; - default: - throw new Error('Unexpected path command ' + cmd.type); - } - } - if (box.isEmpty()) { - box.addPoint(0, 0); - } - return box; - }; - - /** - * Draw the path to a 2D context. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context. - */ - Path.prototype.draw = function(ctx) { - ctx.beginPath(); - for (var i = 0; i < this.commands.length; i += 1) { - var cmd = this.commands[i]; - if (cmd.type === 'M') { - ctx.moveTo(cmd.x, cmd.y); - } else if (cmd.type === 'L') { - ctx.lineTo(cmd.x, cmd.y); - } else if (cmd.type === 'C') { - ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); - } else if (cmd.type === 'Q') { - ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y); - } else if (cmd.type === 'Z') { - ctx.closePath(); - } - } - - if (this.fill) { - ctx.fillStyle = this.fill; - ctx.fill(); - } - - if (this.stroke) { - ctx.strokeStyle = this.stroke; - ctx.lineWidth = this.strokeWidth; - ctx.stroke(); - } - }; - - /** - * Convert the Path to a string of path data instructions - * See http://www.w3.org/TR/SVG/paths.html#PathData - * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values - * @return {string} - */ - Path.prototype.toPathData = function(decimalPlaces) { - decimalPlaces = decimalPlaces !== undefined ? decimalPlaces : 2; - - function floatToString(v) { - if (Math.round(v) === v) { - return '' + Math.round(v); - } else { - return v.toFixed(decimalPlaces); - } - } - - function packValues() { - var arguments$1 = arguments; - - var s = ''; - for (var i = 0; i < arguments.length; i += 1) { - var v = arguments$1[i]; - if (v >= 0 && i > 0) { - s += ' '; - } - - s += floatToString(v); - } - - return s; - } - - var d = ''; - for (var i = 0; i < this.commands.length; i += 1) { - var cmd = this.commands[i]; - if (cmd.type === 'M') { - d += 'M' + packValues(cmd.x, cmd.y); - } else if (cmd.type === 'L') { - d += 'L' + packValues(cmd.x, cmd.y); - } else if (cmd.type === 'C') { - d += 'C' + packValues(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); - } else if (cmd.type === 'Q') { - d += 'Q' + packValues(cmd.x1, cmd.y1, cmd.x, cmd.y); - } else if (cmd.type === 'Z') { - d += 'Z'; - } - } - - return d; - }; - - /** - * Convert the path to an SVG element, as a string. - * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values - * @return {string} - */ - Path.prototype.toSVG = function(decimalPlaces) { - var svg = '= 0 && v <= 255, 'Byte value should be between 0 and 255.'); - return [v]; - }; - /** - * @constant - * @type {number} - */ - sizeOf.BYTE = constant(1); - - /** - * Convert a 8-bit signed integer to a list of 1 byte. - * @param {string} - * @returns {Array} - */ - encode.CHAR = function(v) { - return [v.charCodeAt(0)]; - }; - - /** - * @constant - * @type {number} - */ - sizeOf.CHAR = constant(1); - - /** - * Convert an ASCII string to a list of bytes. - * @param {string} - * @returns {Array} - */ - encode.CHARARRAY = function(v) { - var b = []; - for (var i = 0; i < v.length; i += 1) { - b[i] = v.charCodeAt(i); - } - - return b; - }; - - /** - * @param {Array} - * @returns {number} - */ - sizeOf.CHARARRAY = function(v) { - return v.length; - }; - - /** - * Convert a 16-bit unsigned integer to a list of 2 bytes. - * @param {number} - * @returns {Array} - */ - encode.USHORT = function(v) { - return [(v >> 8) & 0xFF, v & 0xFF]; - }; - - /** - * @constant - * @type {number} - */ - sizeOf.USHORT = constant(2); - - /** - * Convert a 16-bit signed integer to a list of 2 bytes. - * @param {number} - * @returns {Array} - */ - encode.SHORT = function(v) { - // Two's complement - if (v >= LIMIT16) { - v = -(2 * LIMIT16 - v); - } - - return [(v >> 8) & 0xFF, v & 0xFF]; - }; - - /** - * @constant - * @type {number} - */ - sizeOf.SHORT = constant(2); - - /** - * Convert a 24-bit unsigned integer to a list of 3 bytes. - * @param {number} - * @returns {Array} - */ - encode.UINT24 = function(v) { - return [(v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; - }; - - /** - * @constant - * @type {number} - */ - sizeOf.UINT24 = constant(3); - - /** - * Convert a 32-bit unsigned integer to a list of 4 bytes. - * @param {number} - * @returns {Array} - */ - encode.ULONG = function(v) { - return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; - }; - - /** - * @constant - * @type {number} - */ - sizeOf.ULONG = constant(4); - - /** - * Convert a 32-bit unsigned integer to a list of 4 bytes. - * @param {number} - * @returns {Array} - */ - encode.LONG = function(v) { - // Two's complement - if (v >= LIMIT32) { - v = -(2 * LIMIT32 - v); - } - - return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; - }; - - /** - * @constant - * @type {number} - */ - sizeOf.LONG = constant(4); - - encode.FIXED = encode.ULONG; - sizeOf.FIXED = sizeOf.ULONG; - - encode.FWORD = encode.SHORT; - sizeOf.FWORD = sizeOf.SHORT; - - encode.UFWORD = encode.USHORT; - sizeOf.UFWORD = sizeOf.USHORT; - - /** - * Convert a 32-bit Apple Mac timestamp integer to a list of 8 bytes, 64-bit timestamp. - * @param {number} - * @returns {Array} - */ - encode.LONGDATETIME = function(v) { - return [0, 0, 0, 0, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; - }; - - /** - * @constant - * @type {number} - */ - sizeOf.LONGDATETIME = constant(8); - - /** - * Convert a 4-char tag to a list of 4 bytes. - * @param {string} - * @returns {Array} - */ - encode.TAG = function(v) { - check.argument(v.length === 4, 'Tag should be exactly 4 ASCII characters.'); - return [v.charCodeAt(0), - v.charCodeAt(1), - v.charCodeAt(2), - v.charCodeAt(3)]; - }; - - /** - * @constant - * @type {number} - */ - sizeOf.TAG = constant(4); - - // CFF data types /////////////////////////////////////////////////////////// - - encode.Card8 = encode.BYTE; - sizeOf.Card8 = sizeOf.BYTE; - - encode.Card16 = encode.USHORT; - sizeOf.Card16 = sizeOf.USHORT; - - encode.OffSize = encode.BYTE; - sizeOf.OffSize = sizeOf.BYTE; - - encode.SID = encode.USHORT; - sizeOf.SID = sizeOf.USHORT; - - // Convert a numeric operand or charstring number to a variable-size list of bytes. - /** - * Convert a numeric operand or charstring number to a variable-size list of bytes. - * @param {number} - * @returns {Array} - */ - encode.NUMBER = function(v) { - if (v >= -107 && v <= 107) { - return [v + 139]; - } else if (v >= 108 && v <= 1131) { - v = v - 108; - return [(v >> 8) + 247, v & 0xFF]; - } else if (v >= -1131 && v <= -108) { - v = -v - 108; - return [(v >> 8) + 251, v & 0xFF]; - } else if (v >= -32768 && v <= 32767) { - return encode.NUMBER16(v); - } else { - return encode.NUMBER32(v); - } - }; - - /** - * @param {number} - * @returns {number} - */ - sizeOf.NUMBER = function(v) { - return encode.NUMBER(v).length; - }; - - /** - * Convert a signed number between -32768 and +32767 to a three-byte value. - * This ensures we always use three bytes, but is not the most compact format. - * @param {number} - * @returns {Array} - */ - encode.NUMBER16 = function(v) { - return [28, (v >> 8) & 0xFF, v & 0xFF]; - }; - - /** - * @constant - * @type {number} - */ - sizeOf.NUMBER16 = constant(3); - - /** - * Convert a signed number between -(2^31) and +(2^31-1) to a five-byte value. - * This is useful if you want to be sure you always use four bytes, - * at the expense of wasting a few bytes for smaller numbers. - * @param {number} - * @returns {Array} - */ - encode.NUMBER32 = function(v) { - return [29, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; - }; - - /** - * @constant - * @type {number} - */ - sizeOf.NUMBER32 = constant(5); - - /** - * @param {number} - * @returns {Array} - */ - encode.REAL = function(v) { - var value = v.toString(); - - // Some numbers use an epsilon to encode the value. (e.g. JavaScript will store 0.0000001 as 1e-7) - // This code converts it back to a number without the epsilon. - var m = /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value); - if (m) { - var epsilon = parseFloat('1e' + ((m[2] ? +m[2] : 0) + m[1].length)); - value = (Math.round(v * epsilon) / epsilon).toString(); - } - - var nibbles = ''; - for (var i = 0, ii = value.length; i < ii; i += 1) { - var c = value[i]; - if (c === 'e') { - nibbles += value[++i] === '-' ? 'c' : 'b'; - } else if (c === '.') { - nibbles += 'a'; - } else if (c === '-') { - nibbles += 'e'; - } else { - nibbles += c; - } - } - - nibbles += (nibbles.length & 1) ? 'f' : 'ff'; - var out = [30]; - for (var i$1 = 0, ii$1 = nibbles.length; i$1 < ii$1; i$1 += 2) { - out.push(parseInt(nibbles.substr(i$1, 2), 16)); - } - - return out; - }; - - /** - * @param {number} - * @returns {number} - */ - sizeOf.REAL = function(v) { - return encode.REAL(v).length; - }; - - encode.NAME = encode.CHARARRAY; - sizeOf.NAME = sizeOf.CHARARRAY; - - encode.STRING = encode.CHARARRAY; - sizeOf.STRING = sizeOf.CHARARRAY; - - /** - * @param {DataView} data - * @param {number} offset - * @param {number} numBytes - * @returns {string} - */ - decode.UTF8 = function(data, offset, numBytes) { - var codePoints = []; - var numChars = numBytes; - for (var j = 0; j < numChars; j++, offset += 1) { - codePoints[j] = data.getUint8(offset); - } - - return String.fromCharCode.apply(null, codePoints); - }; - - /** - * @param {DataView} data - * @param {number} offset - * @param {number} numBytes - * @returns {string} - */ - decode.UTF16 = function(data, offset, numBytes) { - var codePoints = []; - var numChars = numBytes / 2; - for (var j = 0; j < numChars; j++, offset += 2) { - codePoints[j] = data.getUint16(offset); - } - - return String.fromCharCode.apply(null, codePoints); - }; - - /** - * Convert a JavaScript string to UTF16-BE. - * @param {string} - * @returns {Array} - */ - encode.UTF16 = function(v) { - var b = []; - for (var i = 0; i < v.length; i += 1) { - var codepoint = v.charCodeAt(i); - b[b.length] = (codepoint >> 8) & 0xFF; - b[b.length] = codepoint & 0xFF; - } - - return b; - }; - - /** - * @param {string} - * @returns {number} - */ - sizeOf.UTF16 = function(v) { - return v.length * 2; - }; - - // Data for converting old eight-bit Macintosh encodings to Unicode. - // This representation is optimized for decoding; encoding is slower - // and needs more memory. The assumption is that all opentype.js users - // want to open fonts, but saving a font will be comparatively rare - // so it can be more expensive. Keyed by IANA character set name. - // - // Python script for generating these strings: - // - // s = u''.join([chr(c).decode('mac_greek') for c in range(128, 256)]) - // print(s.encode('utf-8')) - /** - * @private - */ - var eightBitMacEncodings = { - 'x-mac-croatian': // Python: 'mac_croatian' - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø' + - '¿¡¬√ƒ≈Ć«Č… ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ', - 'x-mac-cyrillic': // Python: 'mac_cyrillic' - 'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ†°Ґ£§•¶І®©™Ђђ≠Ѓѓ∞±≤≥іµґЈЄєЇїЉљЊњ' + - 'јЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю', - 'x-mac-gaelic': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/GAELIC.TXT - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØḂ±≤≥ḃĊċḊḋḞḟĠġṀæø' + - 'ṁṖṗɼƒſṠ«»… ÀÃÕŒœ–—“”‘’ṡẛÿŸṪ€‹›Ŷŷṫ·Ỳỳ⁊ÂÊÁËÈÍÎÏÌÓÔ♣ÒÚÛÙıÝýŴŵẄẅẀẁẂẃ', - 'x-mac-greek': // Python: 'mac_greek' - 'Ĺ²É³ÖÜ΅àâä΄¨çéèê룙î‰ôö¦€ùûü†ΓΔΘΛΞΠß®©ΣΪ§≠°·Α±≤≥¥ΒΕΖΗΙΚΜΦΫΨΩ' + - 'άΝ¬ΟΡ≈Τ«»… ΥΧΆΈœ–―“”‘’÷ΉΊΌΎέήίόΏύαβψδεφγηιξκλμνοπώρστθωςχυζϊϋΐΰ\u00AD', - 'x-mac-icelandic': // Python: 'mac_iceland' - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûüÝ°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + - '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€ÐðÞþý·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', - 'x-mac-inuit': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/INUIT.TXT - 'ᐃᐄᐅᐆᐊᐋᐱᐲᐳᐴᐸᐹᑉᑎᑏᑐᑑᑕᑖᑦᑭᑮᑯᑰᑲᑳᒃᒋᒌᒍᒎᒐᒑ°ᒡᒥᒦ•¶ᒧ®©™ᒨᒪᒫᒻᓂᓃᓄᓅᓇᓈᓐᓯᓰᓱᓲᓴᓵᔅᓕᓖᓗ' + - 'ᓘᓚᓛᓪᔨᔩᔪᔫᔭ… ᔮᔾᕕᕖᕗ–—“”‘’ᕘᕙᕚᕝᕆᕇᕈᕉᕋᕌᕐᕿᖀᖁᖂᖃᖄᖅᖏᖐᖑᖒᖓᖔᖕᙱᙲᙳᙴᙵᙶᖖᖠᖡᖢᖣᖤᖥᖦᕼŁł', - 'x-mac-ce': // Python: 'mac_latin2' - 'ÄĀāÉĄÖÜáąČäčĆć鏟ĎíďĒēĖóėôöõúĚěü†°Ę£§•¶ß®©™ę¨≠ģĮįĪ≤≥īĶ∂∑łĻļĽľĹĺŅ' + - 'ņѬ√ńŇ∆«»… ňŐÕőŌ–—“”‘’÷◊ōŔŕŘ‹›řŖŗŠ‚„šŚśÁŤťÍŽžŪÓÔūŮÚůŰűŲųÝýķŻŁżĢˇ', - macintosh: // Python: 'mac_roman' - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + - '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', - 'x-mac-romanian': // Python: 'mac_romanian' - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ĂȘ∞±≤≥¥µ∂∑∏π∫ªºΩăș' + - '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›Țț‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', - 'x-mac-turkish': // Python: 'mac_turkish' - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + - '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸĞğİıŞş‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙˆ˜¯˘˙˚¸˝˛ˇ' - }; - - /** - * Decodes an old-style Macintosh string. Returns either a Unicode JavaScript - * string, or 'undefined' if the encoding is unsupported. For example, we do - * not support Chinese, Japanese or Korean because these would need large - * mapping tables. - * @param {DataView} dataView - * @param {number} offset - * @param {number} dataLength - * @param {string} encoding - * @returns {string} - */ - decode.MACSTRING = function(dataView, offset, dataLength, encoding) { - var table = eightBitMacEncodings[encoding]; - if (table === undefined) { - return undefined; - } - - var result = ''; - for (var i = 0; i < dataLength; i++) { - var c = dataView.getUint8(offset + i); - // In all eight-bit Mac encodings, the characters 0x00..0x7F are - // mapped to U+0000..U+007F; we only need to look up the others. - if (c <= 0x7F) { - result += String.fromCharCode(c); - } else { - result += table[c & 0x7F]; - } - } - - return result; - }; - - // Helper function for encode.MACSTRING. Returns a dictionary for mapping - // Unicode character codes to their 8-bit MacOS equivalent. This table - // is not exactly a super cheap data structure, but we do not care because - // encoding Macintosh strings is only rarely needed in typical applications. - var macEncodingTableCache = typeof WeakMap === 'function' && new WeakMap(); - var macEncodingCacheKeys; - var getMacEncodingTable = function (encoding) { - // Since we use encoding as a cache key for WeakMap, it has to be - // a String object and not a literal. And at least on NodeJS 2.10.1, - // WeakMap requires that the same String instance is passed for cache hits. - if (!macEncodingCacheKeys) { - macEncodingCacheKeys = {}; - for (var e in eightBitMacEncodings) { - /*jshint -W053 */ // Suppress "Do not use String as a constructor." - macEncodingCacheKeys[e] = new String(e); - } - } - - var cacheKey = macEncodingCacheKeys[encoding]; - if (cacheKey === undefined) { - return undefined; - } - - // We can't do "if (cache.has(key)) {return cache.get(key)}" here: - // since garbage collection may run at any time, it could also kick in - // between the calls to cache.has() and cache.get(). In that case, - // we would return 'undefined' even though we do support the encoding. - if (macEncodingTableCache) { - var cachedTable = macEncodingTableCache.get(cacheKey); - if (cachedTable !== undefined) { - return cachedTable; - } - } - - var decodingTable = eightBitMacEncodings[encoding]; - if (decodingTable === undefined) { - return undefined; - } - - var encodingTable = {}; - for (var i = 0; i < decodingTable.length; i++) { - encodingTable[decodingTable.charCodeAt(i)] = i + 0x80; - } - - if (macEncodingTableCache) { - macEncodingTableCache.set(cacheKey, encodingTable); - } - - return encodingTable; - }; - - /** - * Encodes an old-style Macintosh string. Returns a byte array upon success. - * If the requested encoding is unsupported, or if the input string contains - * a character that cannot be expressed in the encoding, the function returns - * 'undefined'. - * @param {string} str - * @param {string} encoding - * @returns {Array} - */ - encode.MACSTRING = function(str, encoding) { - var table = getMacEncodingTable(encoding); - if (table === undefined) { - return undefined; - } - - var result = []; - for (var i = 0; i < str.length; i++) { - var c = str.charCodeAt(i); - - // In all eight-bit Mac encodings, the characters 0x00..0x7F are - // mapped to U+0000..U+007F; we only need to look up the others. - if (c >= 0x80) { - c = table[c]; - if (c === undefined) { - // str contains a Unicode character that cannot be encoded - // in the requested encoding. - return undefined; - } - } - result[i] = c; - // result.push(c); - } - - return result; - }; - - /** - * @param {string} str - * @param {string} encoding - * @returns {number} - */ - sizeOf.MACSTRING = function(str, encoding) { - var b = encode.MACSTRING(str, encoding); - if (b !== undefined) { - return b.length; - } else { - return 0; - } - }; - - // Helper for encode.VARDELTAS - function isByteEncodable(value) { - return value >= -128 && value <= 127; - } - - // Helper for encode.VARDELTAS - function encodeVarDeltaRunAsZeroes(deltas, pos, result) { - var runLength = 0; - var numDeltas = deltas.length; - while (pos < numDeltas && runLength < 64 && deltas[pos] === 0) { - ++pos; - ++runLength; - } - result.push(0x80 | (runLength - 1)); - return pos; - } - - // Helper for encode.VARDELTAS - function encodeVarDeltaRunAsBytes(deltas, offset, result) { - var runLength = 0; - var numDeltas = deltas.length; - var pos = offset; - while (pos < numDeltas && runLength < 64) { - var value = deltas[pos]; - if (!isByteEncodable(value)) { - break; - } - - // Within a byte-encoded run of deltas, a single zero is best - // stored literally as 0x00 value. However, if we have two or - // more zeroes in a sequence, it is better to start a new run. - // Fore example, the sequence of deltas [15, 15, 0, 15, 15] - // becomes 6 bytes (04 0F 0F 00 0F 0F) when storing the zero - // within the current run, but 7 bytes (01 0F 0F 80 01 0F 0F) - // when starting a new run. - if (value === 0 && pos + 1 < numDeltas && deltas[pos + 1] === 0) { - break; - } - - ++pos; - ++runLength; - } - result.push(runLength - 1); - for (var i = offset; i < pos; ++i) { - result.push((deltas[i] + 256) & 0xff); - } - return pos; - } - - // Helper for encode.VARDELTAS - function encodeVarDeltaRunAsWords(deltas, offset, result) { - var runLength = 0; - var numDeltas = deltas.length; - var pos = offset; - while (pos < numDeltas && runLength < 64) { - var value = deltas[pos]; - - // Within a word-encoded run of deltas, it is easiest to start - // a new run (with a different encoding) whenever we encounter - // a zero value. For example, the sequence [0x6666, 0, 0x7777] - // needs 7 bytes when storing the zero inside the current run - // (42 66 66 00 00 77 77), and equally 7 bytes when starting a - // new run (40 66 66 80 40 77 77). - if (value === 0) { - break; - } - - // Within a word-encoded run of deltas, a single value in the - // range (-128..127) should be encoded within the current run - // because it is more compact. For example, the sequence - // [0x6666, 2, 0x7777] becomes 7 bytes when storing the value - // literally (42 66 66 00 02 77 77), but 8 bytes when starting - // a new run (40 66 66 00 02 40 77 77). - if (isByteEncodable(value) && pos + 1 < numDeltas && isByteEncodable(deltas[pos + 1])) { - break; - } - - ++pos; - ++runLength; - } - result.push(0x40 | (runLength - 1)); - for (var i = offset; i < pos; ++i) { - var val = deltas[i]; - result.push(((val + 0x10000) >> 8) & 0xff, (val + 0x100) & 0xff); - } - return pos; - } - - /** - * Encode a list of variation adjustment deltas. - * - * Variation adjustment deltas are used in ‘gvar’ and ‘cvar’ tables. - * They indicate how points (in ‘gvar’) or values (in ‘cvar’) get adjusted - * when generating instances of variation fonts. - * - * @see https://www.microsoft.com/typography/otspec/gvar.htm - * @see https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html - * @param {Array} - * @return {Array} - */ - encode.VARDELTAS = function(deltas) { - var pos = 0; - var result = []; - while (pos < deltas.length) { - var value = deltas[pos]; - if (value === 0) { - pos = encodeVarDeltaRunAsZeroes(deltas, pos, result); - } else if (value >= -128 && value <= 127) { - pos = encodeVarDeltaRunAsBytes(deltas, pos, result); - } else { - pos = encodeVarDeltaRunAsWords(deltas, pos, result); - } - } - return result; - }; - - // Convert a list of values to a CFF INDEX structure. - // The values should be objects containing name / type / value. - /** - * @param {Array} l - * @returns {Array} - */ - encode.INDEX = function(l) { - //var offset, offsets, offsetEncoder, encodedOffsets, encodedOffset, data, - // i, v; - // Because we have to know which data type to use to encode the offsets, - // we have to go through the values twice: once to encode the data and - // calculate the offsets, then again to encode the offsets using the fitting data type. - var offset = 1; // First offset is always 1. - var offsets = [offset]; - var data = []; - for (var i = 0; i < l.length; i += 1) { - var v = encode.OBJECT(l[i]); - Array.prototype.push.apply(data, v); - offset += v.length; - offsets.push(offset); - } - - if (data.length === 0) { - return [0, 0]; - } - - var encodedOffsets = []; - var offSize = (1 + Math.floor(Math.log(offset) / Math.log(2)) / 8) | 0; - var offsetEncoder = [undefined, encode.BYTE, encode.USHORT, encode.UINT24, encode.ULONG][offSize]; - for (var i$1 = 0; i$1 < offsets.length; i$1 += 1) { - var encodedOffset = offsetEncoder(offsets[i$1]); - Array.prototype.push.apply(encodedOffsets, encodedOffset); - } - - return Array.prototype.concat(encode.Card16(l.length), - encode.OffSize(offSize), - encodedOffsets, - data); - }; - - /** - * @param {Array} - * @returns {number} - */ - sizeOf.INDEX = function(v) { - return encode.INDEX(v).length; - }; - - /** - * Convert an object to a CFF DICT structure. - * The keys should be numeric. - * The values should be objects containing name / type / value. - * @param {Object} m - * @returns {Array} - */ - encode.DICT = function(m) { - var d = []; - var keys = Object.keys(m); - var length = keys.length; - - for (var i = 0; i < length; i += 1) { - // Object.keys() return string keys, but our keys are always numeric. - var k = parseInt(keys[i], 0); - var v = m[k]; - // Value comes before the key. - d = d.concat(encode.OPERAND(v.value, v.type)); - d = d.concat(encode.OPERATOR(k)); - } - - return d; - }; - - /** - * @param {Object} - * @returns {number} - */ - sizeOf.DICT = function(m) { - return encode.DICT(m).length; - }; - - /** - * @param {number} - * @returns {Array} - */ - encode.OPERATOR = function(v) { - if (v < 1200) { - return [v]; - } else { - return [12, v - 1200]; - } - }; - - /** - * @param {Array} v - * @param {string} - * @returns {Array} - */ - encode.OPERAND = function(v, type) { - var d = []; - if (Array.isArray(type)) { - for (var i = 0; i < type.length; i += 1) { - check.argument(v.length === type.length, 'Not enough arguments given for type' + type); - d = d.concat(encode.OPERAND(v[i], type[i])); - } - } else { - if (type === 'SID') { - d = d.concat(encode.NUMBER(v)); - } else if (type === 'offset') { - // We make it easy for ourselves and always encode offsets as - // 4 bytes. This makes offset calculation for the top dict easier. - d = d.concat(encode.NUMBER32(v)); - } else if (type === 'number') { - d = d.concat(encode.NUMBER(v)); - } else if (type === 'real') { - d = d.concat(encode.REAL(v)); - } else { - throw new Error('Unknown operand type ' + type); - // FIXME Add support for booleans - } - } - - return d; - }; - - encode.OP = encode.BYTE; - sizeOf.OP = sizeOf.BYTE; - - // memoize charstring encoding using WeakMap if available - var wmm = typeof WeakMap === 'function' && new WeakMap(); - - /** - * Convert a list of CharString operations to bytes. - * @param {Array} - * @returns {Array} - */ - encode.CHARSTRING = function(ops) { - // See encode.MACSTRING for why we don't do "if (wmm && wmm.has(ops))". - if (wmm) { - var cachedValue = wmm.get(ops); - if (cachedValue !== undefined) { - return cachedValue; - } - } - - var d = []; - var length = ops.length; - - for (var i = 0; i < length; i += 1) { - var op = ops[i]; - d = d.concat(encode[op.type](op.value)); - } - - if (wmm) { - wmm.set(ops, d); - } - - return d; - }; - - /** - * @param {Array} - * @returns {number} - */ - sizeOf.CHARSTRING = function(ops) { - return encode.CHARSTRING(ops).length; - }; - - // Utility functions //////////////////////////////////////////////////////// - - /** - * Convert an object containing name / type / value to bytes. - * @param {Object} - * @returns {Array} - */ - encode.OBJECT = function(v) { - var encodingFunction = encode[v.type]; - check.argument(encodingFunction !== undefined, 'No encoding function for type ' + v.type); - return encodingFunction(v.value); - }; - - /** - * @param {Object} - * @returns {number} - */ - sizeOf.OBJECT = function(v) { - var sizeOfFunction = sizeOf[v.type]; - check.argument(sizeOfFunction !== undefined, 'No sizeOf function for type ' + v.type); - return sizeOfFunction(v.value); - }; - - /** - * Convert a table object to bytes. - * A table contains a list of fields containing the metadata (name, type and default value). - * The table itself has the field values set as attributes. - * @param {opentype.Table} - * @returns {Array} - */ - encode.TABLE = function(table) { - var d = []; - var length = table.fields.length; - var subtables = []; - var subtableOffsets = []; - - for (var i = 0; i < length; i += 1) { - var field = table.fields[i]; - var encodingFunction = encode[field.type]; - check.argument(encodingFunction !== undefined, 'No encoding function for field type ' + field.type + ' (' + field.name + ')'); - var value = table[field.name]; - if (value === undefined) { - value = field.value; - } - - var bytes = encodingFunction(value); - - if (field.type === 'TABLE') { - subtableOffsets.push(d.length); - d = d.concat([0, 0]); - subtables.push(bytes); - } else { - d = d.concat(bytes); - } - } - - for (var i$1 = 0; i$1 < subtables.length; i$1 += 1) { - var o = subtableOffsets[i$1]; - var offset = d.length; - check.argument(offset < 65536, 'Table ' + table.tableName + ' too big.'); - d[o] = offset >> 8; - d[o + 1] = offset & 0xff; - d = d.concat(subtables[i$1]); - } - - return d; - }; - - /** - * @param {opentype.Table} - * @returns {number} - */ - sizeOf.TABLE = function(table) { - var numBytes = 0; - var length = table.fields.length; - - for (var i = 0; i < length; i += 1) { - var field = table.fields[i]; - var sizeOfFunction = sizeOf[field.type]; - check.argument(sizeOfFunction !== undefined, 'No sizeOf function for field type ' + field.type + ' (' + field.name + ')'); - var value = table[field.name]; - if (value === undefined) { - value = field.value; - } - - numBytes += sizeOfFunction(value); - - // Subtables take 2 more bytes for offsets. - if (field.type === 'TABLE') { - numBytes += 2; - } - } - - return numBytes; - }; - - encode.RECORD = encode.TABLE; - sizeOf.RECORD = sizeOf.TABLE; - - // Merge in a list of bytes. - encode.LITERAL = function(v) { - return v; - }; - - sizeOf.LITERAL = function(v) { - return v.length; - }; - - // Table metadata - - /** - * @exports opentype.Table - * @class - * @param {string} tableName - * @param {Array} fields - * @param {Object} options - * @constructor - */ - function Table(tableName, fields, options) { - for (var i = 0; i < fields.length; i += 1) { - var field = fields[i]; - this[field.name] = field.value; - } - - this.tableName = tableName; - this.fields = fields; - if (options) { - var optionKeys = Object.keys(options); - for (var i$1 = 0; i$1 < optionKeys.length; i$1 += 1) { - var k = optionKeys[i$1]; - var v = options[k]; - if (this[k] !== undefined) { - this[k] = v; - } - } - } - } - - /** - * Encodes the table and returns an array of bytes - * @return {Array} - */ - Table.prototype.encode = function() { - return encode.TABLE(this); - }; - - /** - * Get the size of the table. - * @return {number} - */ - Table.prototype.sizeOf = function() { - return sizeOf.TABLE(this); - }; - - /** - * @private - */ - function ushortList(itemName, list, count) { - if (count === undefined) { - count = list.length; - } - var fields = new Array(list.length + 1); - fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; - for (var i = 0; i < list.length; i++) { - fields[i + 1] = {name: itemName + i, type: 'USHORT', value: list[i]}; - } - return fields; - } - - /** - * @private - */ - function tableList(itemName, records, itemCallback) { - var count = records.length; - var fields = new Array(count + 1); - fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; - for (var i = 0; i < count; i++) { - fields[i + 1] = {name: itemName + i, type: 'TABLE', value: itemCallback(records[i], i)}; - } - return fields; - } - - /** - * @private - */ - function recordList(itemName, records, itemCallback) { - var count = records.length; - var fields = []; - fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; - for (var i = 0; i < count; i++) { - fields = fields.concat(itemCallback(records[i], i)); - } - return fields; - } - - // Common Layout Tables - - /** - * @exports opentype.Coverage - * @class - * @param {opentype.Table} - * @constructor - * @extends opentype.Table - */ - function Coverage(coverageTable) { - if (coverageTable.format === 1) { - Table.call(this, 'coverageTable', - [{name: 'coverageFormat', type: 'USHORT', value: 1}] - .concat(ushortList('glyph', coverageTable.glyphs)) - ); - } else { - check.assert(false, 'Can\'t create coverage table format 2 yet.'); - } - } - Coverage.prototype = Object.create(Table.prototype); - Coverage.prototype.constructor = Coverage; - - function ScriptList(scriptListTable) { - Table.call(this, 'scriptListTable', - recordList('scriptRecord', scriptListTable, function(scriptRecord, i) { - var script = scriptRecord.script; - var defaultLangSys = script.defaultLangSys; - check.assert(!!defaultLangSys, 'Unable to write GSUB: script ' + scriptRecord.tag + ' has no default language system.'); - return [ - {name: 'scriptTag' + i, type: 'TAG', value: scriptRecord.tag}, - {name: 'script' + i, type: 'TABLE', value: new Table('scriptTable', [ - {name: 'defaultLangSys', type: 'TABLE', value: new Table('defaultLangSys', [ - {name: 'lookupOrder', type: 'USHORT', value: 0}, - {name: 'reqFeatureIndex', type: 'USHORT', value: defaultLangSys.reqFeatureIndex}] - .concat(ushortList('featureIndex', defaultLangSys.featureIndexes)))} - ].concat(recordList('langSys', script.langSysRecords, function(langSysRecord, i) { - var langSys = langSysRecord.langSys; - return [ - {name: 'langSysTag' + i, type: 'TAG', value: langSysRecord.tag}, - {name: 'langSys' + i, type: 'TABLE', value: new Table('langSys', [ - {name: 'lookupOrder', type: 'USHORT', value: 0}, - {name: 'reqFeatureIndex', type: 'USHORT', value: langSys.reqFeatureIndex} - ].concat(ushortList('featureIndex', langSys.featureIndexes)))} - ]; - })))} - ]; - }) - ); - } - ScriptList.prototype = Object.create(Table.prototype); - ScriptList.prototype.constructor = ScriptList; - - /** - * @exports opentype.FeatureList - * @class - * @param {opentype.Table} - * @constructor - * @extends opentype.Table - */ - function FeatureList(featureListTable) { - Table.call(this, 'featureListTable', - recordList('featureRecord', featureListTable, function(featureRecord, i) { - var feature = featureRecord.feature; - return [ - {name: 'featureTag' + i, type: 'TAG', value: featureRecord.tag}, - {name: 'feature' + i, type: 'TABLE', value: new Table('featureTable', [ - {name: 'featureParams', type: 'USHORT', value: feature.featureParams} ].concat(ushortList('lookupListIndex', feature.lookupListIndexes)))} - ]; - }) - ); - } - FeatureList.prototype = Object.create(Table.prototype); - FeatureList.prototype.constructor = FeatureList; - - /** - * @exports opentype.LookupList - * @class - * @param {opentype.Table} - * @param {Object} - * @constructor - * @extends opentype.Table - */ - function LookupList(lookupListTable, subtableMakers) { - Table.call(this, 'lookupListTable', tableList('lookup', lookupListTable, function(lookupTable) { - var subtableCallback = subtableMakers[lookupTable.lookupType]; - check.assert(!!subtableCallback, 'Unable to write GSUB lookup type ' + lookupTable.lookupType + ' tables.'); - return new Table('lookupTable', [ - {name: 'lookupType', type: 'USHORT', value: lookupTable.lookupType}, - {name: 'lookupFlag', type: 'USHORT', value: lookupTable.lookupFlag} - ].concat(tableList('subtable', lookupTable.subtables, subtableCallback))); - })); - } - LookupList.prototype = Object.create(Table.prototype); - LookupList.prototype.constructor = LookupList; - - // Record = same as Table, but inlined (a Table has an offset and its data is further in the stream) - // Don't use offsets inside Records (probable bug), only in Tables. - var table = { - Table: Table, - Record: Table, - Coverage: Coverage, - ScriptList: ScriptList, - FeatureList: FeatureList, - LookupList: LookupList, - ushortList: ushortList, - tableList: tableList, - recordList: recordList, - }; - - // Parsing utility functions - - // Retrieve an unsigned byte from the DataView. - function getByte(dataView, offset) { - return dataView.getUint8(offset); - } - - // Retrieve an unsigned 16-bit short from the DataView. - // The value is stored in big endian. - function getUShort(dataView, offset) { - return dataView.getUint16(offset, false); - } - - // Retrieve a signed 16-bit short from the DataView. - // The value is stored in big endian. - function getShort(dataView, offset) { - return dataView.getInt16(offset, false); - } - - // Retrieve an unsigned 32-bit long from the DataView. - // The value is stored in big endian. - function getULong(dataView, offset) { - return dataView.getUint32(offset, false); - } - - // Retrieve a 32-bit signed fixed-point number (16.16) from the DataView. - // The value is stored in big endian. - function getFixed(dataView, offset) { - var decimal = dataView.getInt16(offset, false); - var fraction = dataView.getUint16(offset + 2, false); - return decimal + fraction / 65535; - } - - // Retrieve a 4-character tag from the DataView. - // Tags are used to identify tables. - function getTag(dataView, offset) { - var tag = ''; - for (var i = offset; i < offset + 4; i += 1) { - tag += String.fromCharCode(dataView.getInt8(i)); - } - - return tag; - } - - // Retrieve an offset from the DataView. - // Offsets are 1 to 4 bytes in length, depending on the offSize argument. - function getOffset(dataView, offset, offSize) { - var v = 0; - for (var i = 0; i < offSize; i += 1) { - v <<= 8; - v += dataView.getUint8(offset + i); - } - - return v; - } - - // Retrieve a number of bytes from start offset to the end offset from the DataView. - function getBytes(dataView, startOffset, endOffset) { - var bytes = []; - for (var i = startOffset; i < endOffset; i += 1) { - bytes.push(dataView.getUint8(i)); - } - - return bytes; - } - - // Convert the list of bytes to a string. - function bytesToString(bytes) { - var s = ''; - for (var i = 0; i < bytes.length; i += 1) { - s += String.fromCharCode(bytes[i]); - } - - return s; - } - - var typeOffsets = { - byte: 1, - uShort: 2, - short: 2, - uLong: 4, - fixed: 4, - longDateTime: 8, - tag: 4 - }; - - // A stateful parser that changes the offset whenever a value is retrieved. - // The data is a DataView. - function Parser(data, offset) { - this.data = data; - this.offset = offset; - this.relativeOffset = 0; - } - - Parser.prototype.parseByte = function() { - var v = this.data.getUint8(this.offset + this.relativeOffset); - this.relativeOffset += 1; - return v; - }; - - Parser.prototype.parseChar = function() { - var v = this.data.getInt8(this.offset + this.relativeOffset); - this.relativeOffset += 1; - return v; - }; - - Parser.prototype.parseCard8 = Parser.prototype.parseByte; - - Parser.prototype.parseUShort = function() { - var v = this.data.getUint16(this.offset + this.relativeOffset); - this.relativeOffset += 2; - return v; - }; - - Parser.prototype.parseCard16 = Parser.prototype.parseUShort; - Parser.prototype.parseSID = Parser.prototype.parseUShort; - Parser.prototype.parseOffset16 = Parser.prototype.parseUShort; - - Parser.prototype.parseShort = function() { - var v = this.data.getInt16(this.offset + this.relativeOffset); - this.relativeOffset += 2; - return v; - }; - - Parser.prototype.parseF2Dot14 = function() { - var v = this.data.getInt16(this.offset + this.relativeOffset) / 16384; - this.relativeOffset += 2; - return v; - }; - - Parser.prototype.parseULong = function() { - var v = getULong(this.data, this.offset + this.relativeOffset); - this.relativeOffset += 4; - return v; - }; - - Parser.prototype.parseOffset32 = Parser.prototype.parseULong; - - Parser.prototype.parseFixed = function() { - var v = getFixed(this.data, this.offset + this.relativeOffset); - this.relativeOffset += 4; - return v; - }; - - Parser.prototype.parseString = function(length) { - var dataView = this.data; - var offset = this.offset + this.relativeOffset; - var string = ''; - this.relativeOffset += length; - for (var i = 0; i < length; i++) { - string += String.fromCharCode(dataView.getUint8(offset + i)); - } - - return string; - }; - - Parser.prototype.parseTag = function() { - return this.parseString(4); - }; - - // LONGDATETIME is a 64-bit integer. - // JavaScript and unix timestamps traditionally use 32 bits, so we - // only take the last 32 bits. - // + Since until 2038 those bits will be filled by zeros we can ignore them. - Parser.prototype.parseLongDateTime = function() { - var v = getULong(this.data, this.offset + this.relativeOffset + 4); - // Subtract seconds between 01/01/1904 and 01/01/1970 - // to convert Apple Mac timestamp to Standard Unix timestamp - v -= 2082844800; - this.relativeOffset += 8; - return v; - }; - - Parser.prototype.parseVersion = function(minorBase) { - var major = getUShort(this.data, this.offset + this.relativeOffset); - - // How to interpret the minor version is very vague in the spec. 0x5000 is 5, 0x1000 is 1 - // Default returns the correct number if minor = 0xN000 where N is 0-9 - // Set minorBase to 1 for tables that use minor = N where N is 0-9 - var minor = getUShort(this.data, this.offset + this.relativeOffset + 2); - this.relativeOffset += 4; - if (minorBase === undefined) { minorBase = 0x1000; } - return major + minor / minorBase / 10; - }; - - Parser.prototype.skip = function(type, amount) { - if (amount === undefined) { - amount = 1; - } - - this.relativeOffset += typeOffsets[type] * amount; - }; - - ///// Parsing lists and records /////////////////////////////// - - // Parse a list of 32 bit unsigned integers. - Parser.prototype.parseULongList = function(count) { - if (count === undefined) { count = this.parseULong(); } - var offsets = new Array(count); - var dataView = this.data; - var offset = this.offset + this.relativeOffset; - for (var i = 0; i < count; i++) { - offsets[i] = dataView.getUint32(offset); - offset += 4; - } - - this.relativeOffset += count * 4; - return offsets; - }; - - // Parse a list of 16 bit unsigned integers. The length of the list can be read on the stream - // or provided as an argument. - Parser.prototype.parseOffset16List = - Parser.prototype.parseUShortList = function(count) { - if (count === undefined) { count = this.parseUShort(); } - var offsets = new Array(count); - var dataView = this.data; - var offset = this.offset + this.relativeOffset; - for (var i = 0; i < count; i++) { - offsets[i] = dataView.getUint16(offset); - offset += 2; - } - - this.relativeOffset += count * 2; - return offsets; - }; - - // Parses a list of 16 bit signed integers. - Parser.prototype.parseShortList = function(count) { - var list = new Array(count); - var dataView = this.data; - var offset = this.offset + this.relativeOffset; - for (var i = 0; i < count; i++) { - list[i] = dataView.getInt16(offset); - offset += 2; - } - - this.relativeOffset += count * 2; - return list; - }; - - // Parses a list of bytes. - Parser.prototype.parseByteList = function(count) { - var list = new Array(count); - var dataView = this.data; - var offset = this.offset + this.relativeOffset; - for (var i = 0; i < count; i++) { - list[i] = dataView.getUint8(offset++); - } - - this.relativeOffset += count; - return list; - }; - - /** - * Parse a list of items. - * Record count is optional, if omitted it is read from the stream. - * itemCallback is one of the Parser methods. - */ - Parser.prototype.parseList = function(count, itemCallback) { - if (!itemCallback) { - itemCallback = count; - count = this.parseUShort(); - } - var list = new Array(count); - for (var i = 0; i < count; i++) { - list[i] = itemCallback.call(this); - } - return list; - }; - - Parser.prototype.parseList32 = function(count, itemCallback) { - if (!itemCallback) { - itemCallback = count; - count = this.parseULong(); - } - var list = new Array(count); - for (var i = 0; i < count; i++) { - list[i] = itemCallback.call(this); - } - return list; - }; - - /** - * Parse a list of records. - * Record count is optional, if omitted it is read from the stream. - * Example of recordDescription: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } - */ - Parser.prototype.parseRecordList = function(count, recordDescription) { - // If the count argument is absent, read it in the stream. - if (!recordDescription) { - recordDescription = count; - count = this.parseUShort(); - } - var records = new Array(count); - var fields = Object.keys(recordDescription); - for (var i = 0; i < count; i++) { - var rec = {}; - for (var j = 0; j < fields.length; j++) { - var fieldName = fields[j]; - var fieldType = recordDescription[fieldName]; - rec[fieldName] = fieldType.call(this); - } - records[i] = rec; - } - return records; - }; - - Parser.prototype.parseRecordList32 = function(count, recordDescription) { - // If the count argument is absent, read it in the stream. - if (!recordDescription) { - recordDescription = count; - count = this.parseULong(); - } - var records = new Array(count); - var fields = Object.keys(recordDescription); - for (var i = 0; i < count; i++) { - var rec = {}; - for (var j = 0; j < fields.length; j++) { - var fieldName = fields[j]; - var fieldType = recordDescription[fieldName]; - rec[fieldName] = fieldType.call(this); - } - records[i] = rec; - } - return records; - }; - - // Parse a data structure into an object - // Example of description: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } - Parser.prototype.parseStruct = function(description) { - if (typeof description === 'function') { - return description.call(this); - } else { - var fields = Object.keys(description); - var struct = {}; - for (var j = 0; j < fields.length; j++) { - var fieldName = fields[j]; - var fieldType = description[fieldName]; - struct[fieldName] = fieldType.call(this); - } - return struct; - } - }; - - /** - * Parse a GPOS valueRecord - * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record - * valueFormat is optional, if omitted it is read from the stream. - */ - Parser.prototype.parseValueRecord = function(valueFormat) { - if (valueFormat === undefined) { - valueFormat = this.parseUShort(); - } - if (valueFormat === 0) { - // valueFormat2 in kerning pairs is most often 0 - // in this case return undefined instead of an empty object, to save space - return; - } - var valueRecord = {}; - - if (valueFormat & 0x0001) { valueRecord.xPlacement = this.parseShort(); } - if (valueFormat & 0x0002) { valueRecord.yPlacement = this.parseShort(); } - if (valueFormat & 0x0004) { valueRecord.xAdvance = this.parseShort(); } - if (valueFormat & 0x0008) { valueRecord.yAdvance = this.parseShort(); } - - // Device table (non-variable font) / VariationIndex table (variable font) not supported - // https://docs.microsoft.com/fr-fr/typography/opentype/spec/chapter2#devVarIdxTbls - if (valueFormat & 0x0010) { valueRecord.xPlaDevice = undefined; this.parseShort(); } - if (valueFormat & 0x0020) { valueRecord.yPlaDevice = undefined; this.parseShort(); } - if (valueFormat & 0x0040) { valueRecord.xAdvDevice = undefined; this.parseShort(); } - if (valueFormat & 0x0080) { valueRecord.yAdvDevice = undefined; this.parseShort(); } - - return valueRecord; - }; - - /** - * Parse a list of GPOS valueRecords - * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record - * valueFormat and valueCount are read from the stream. - */ - Parser.prototype.parseValueRecordList = function() { - var valueFormat = this.parseUShort(); - var valueCount = this.parseUShort(); - var values = new Array(valueCount); - for (var i = 0; i < valueCount; i++) { - values[i] = this.parseValueRecord(valueFormat); - } - return values; - }; - - Parser.prototype.parsePointer = function(description) { - var structOffset = this.parseOffset16(); - if (structOffset > 0) { - // NULL offset => return undefined - return new Parser(this.data, this.offset + structOffset).parseStruct(description); - } - return undefined; - }; - - Parser.prototype.parsePointer32 = function(description) { - var structOffset = this.parseOffset32(); - if (structOffset > 0) { - // NULL offset => return undefined - return new Parser(this.data, this.offset + structOffset).parseStruct(description); - } - return undefined; - }; - - /** - * Parse a list of offsets to lists of 16-bit integers, - * or a list of offsets to lists of offsets to any kind of items. - * If itemCallback is not provided, a list of list of UShort is assumed. - * If provided, itemCallback is called on each item and must parse the item. - * See examples in tables/gsub.js - */ - Parser.prototype.parseListOfLists = function(itemCallback) { - var offsets = this.parseOffset16List(); - var count = offsets.length; - var relativeOffset = this.relativeOffset; - var list = new Array(count); - for (var i = 0; i < count; i++) { - var start = offsets[i]; - if (start === 0) { - // NULL offset - // Add i as owned property to list. Convenient with assert. - list[i] = undefined; - continue; - } - this.relativeOffset = start; - if (itemCallback) { - var subOffsets = this.parseOffset16List(); - var subList = new Array(subOffsets.length); - for (var j = 0; j < subOffsets.length; j++) { - this.relativeOffset = start + subOffsets[j]; - subList[j] = itemCallback.call(this); - } - list[i] = subList; - } else { - list[i] = this.parseUShortList(); - } - } - this.relativeOffset = relativeOffset; - return list; - }; - - ///// Complex tables parsing ////////////////////////////////// - - // Parse a coverage table in a GSUB, GPOS or GDEF table. - // https://www.microsoft.com/typography/OTSPEC/chapter2.htm - // parser.offset must point to the start of the table containing the coverage. - Parser.prototype.parseCoverage = function() { - var startOffset = this.offset + this.relativeOffset; - var format = this.parseUShort(); - var count = this.parseUShort(); - if (format === 1) { - return { - format: 1, - glyphs: this.parseUShortList(count) - }; - } else if (format === 2) { - var ranges = new Array(count); - for (var i = 0; i < count; i++) { - ranges[i] = { - start: this.parseUShort(), - end: this.parseUShort(), - index: this.parseUShort() - }; - } - return { - format: 2, - ranges: ranges - }; - } - throw new Error('0x' + startOffset.toString(16) + ': Coverage format must be 1 or 2.'); - }; - - // Parse a Class Definition Table in a GSUB, GPOS or GDEF table. - // https://www.microsoft.com/typography/OTSPEC/chapter2.htm - Parser.prototype.parseClassDef = function() { - var startOffset = this.offset + this.relativeOffset; - var format = this.parseUShort(); - if (format === 1) { - return { - format: 1, - startGlyph: this.parseUShort(), - classes: this.parseUShortList() - }; - } else if (format === 2) { - return { - format: 2, - ranges: this.parseRecordList({ - start: Parser.uShort, - end: Parser.uShort, - classId: Parser.uShort - }) - }; - } - throw new Error('0x' + startOffset.toString(16) + ': ClassDef format must be 1 or 2.'); - }; - - ///// Static methods /////////////////////////////////// - // These convenience methods can be used as callbacks and should be called with "this" context set to a Parser instance. - - Parser.list = function(count, itemCallback) { - return function() { - return this.parseList(count, itemCallback); - }; - }; - - Parser.list32 = function(count, itemCallback) { - return function() { - return this.parseList32(count, itemCallback); - }; - }; - - Parser.recordList = function(count, recordDescription) { - return function() { - return this.parseRecordList(count, recordDescription); - }; - }; - - Parser.recordList32 = function(count, recordDescription) { - return function() { - return this.parseRecordList32(count, recordDescription); - }; - }; - - Parser.pointer = function(description) { - return function() { - return this.parsePointer(description); - }; - }; - - Parser.pointer32 = function(description) { - return function() { - return this.parsePointer32(description); - }; - }; - - Parser.tag = Parser.prototype.parseTag; - Parser.byte = Parser.prototype.parseByte; - Parser.uShort = Parser.offset16 = Parser.prototype.parseUShort; - Parser.uShortList = Parser.prototype.parseUShortList; - Parser.uLong = Parser.offset32 = Parser.prototype.parseULong; - Parser.uLongList = Parser.prototype.parseULongList; - Parser.struct = Parser.prototype.parseStruct; - Parser.coverage = Parser.prototype.parseCoverage; - Parser.classDef = Parser.prototype.parseClassDef; - - ///// Script, Feature, Lookup lists /////////////////////////////////////////////// - // https://www.microsoft.com/typography/OTSPEC/chapter2.htm - - var langSysTable = { - reserved: Parser.uShort, - reqFeatureIndex: Parser.uShort, - featureIndexes: Parser.uShortList - }; - - Parser.prototype.parseScriptList = function() { - return this.parsePointer(Parser.recordList({ - tag: Parser.tag, - script: Parser.pointer({ - defaultLangSys: Parser.pointer(langSysTable), - langSysRecords: Parser.recordList({ - tag: Parser.tag, - langSys: Parser.pointer(langSysTable) - }) - }) - })) || []; - }; - - Parser.prototype.parseFeatureList = function() { - return this.parsePointer(Parser.recordList({ - tag: Parser.tag, - feature: Parser.pointer({ - featureParams: Parser.offset16, - lookupListIndexes: Parser.uShortList - }) - })) || []; - }; - - Parser.prototype.parseLookupList = function(lookupTableParsers) { - return this.parsePointer(Parser.list(Parser.pointer(function() { - var lookupType = this.parseUShort(); - check.argument(1 <= lookupType && lookupType <= 9, 'GPOS/GSUB lookup type ' + lookupType + ' unknown.'); - var lookupFlag = this.parseUShort(); - var useMarkFilteringSet = lookupFlag & 0x10; - return { - lookupType: lookupType, - lookupFlag: lookupFlag, - subtables: this.parseList(Parser.pointer(lookupTableParsers[lookupType])), - markFilteringSet: useMarkFilteringSet ? this.parseUShort() : undefined - }; - }))) || []; - }; - - Parser.prototype.parseFeatureVariationsList = function() { - return this.parsePointer32(function() { - var majorVersion = this.parseUShort(); - var minorVersion = this.parseUShort(); - check.argument(majorVersion === 1 && minorVersion < 1, 'GPOS/GSUB feature variations table unknown.'); - var featureVariations = this.parseRecordList32({ - conditionSetOffset: Parser.offset32, - featureTableSubstitutionOffset: Parser.offset32 - }); - return featureVariations; - }) || []; - }; - - var parse = { - getByte: getByte, - getCard8: getByte, - getUShort: getUShort, - getCard16: getUShort, - getShort: getShort, - getULong: getULong, - getFixed: getFixed, - getTag: getTag, - getOffset: getOffset, - getBytes: getBytes, - bytesToString: bytesToString, - Parser: Parser, - }; - - // The `cmap` table stores the mappings from characters to glyphs. - - function parseCmapTableFormat12(cmap, p) { - //Skip reserved. - p.parseUShort(); - - // Length in bytes of the sub-tables. - cmap.length = p.parseULong(); - cmap.language = p.parseULong(); - - var groupCount; - cmap.groupCount = groupCount = p.parseULong(); - cmap.glyphIndexMap = {}; - - for (var i = 0; i < groupCount; i += 1) { - var startCharCode = p.parseULong(); - var endCharCode = p.parseULong(); - var startGlyphId = p.parseULong(); - - for (var c = startCharCode; c <= endCharCode; c += 1) { - cmap.glyphIndexMap[c] = startGlyphId; - startGlyphId++; - } - } - } - - function parseCmapTableFormat4(cmap, p, data, start, offset) { - // Length in bytes of the sub-tables. - cmap.length = p.parseUShort(); - cmap.language = p.parseUShort(); - - // segCount is stored x 2. - var segCount; - cmap.segCount = segCount = p.parseUShort() >> 1; - - // Skip searchRange, entrySelector, rangeShift. - p.skip('uShort', 3); - - // The "unrolled" mapping from character codes to glyph indices. - cmap.glyphIndexMap = {}; - var endCountParser = new parse.Parser(data, start + offset + 14); - var startCountParser = new parse.Parser(data, start + offset + 16 + segCount * 2); - var idDeltaParser = new parse.Parser(data, start + offset + 16 + segCount * 4); - var idRangeOffsetParser = new parse.Parser(data, start + offset + 16 + segCount * 6); - var glyphIndexOffset = start + offset + 16 + segCount * 8; - for (var i = 0; i < segCount - 1; i += 1) { - var glyphIndex = (void 0); - var endCount = endCountParser.parseUShort(); - var startCount = startCountParser.parseUShort(); - var idDelta = idDeltaParser.parseShort(); - var idRangeOffset = idRangeOffsetParser.parseUShort(); - for (var c = startCount; c <= endCount; c += 1) { - if (idRangeOffset !== 0) { - // The idRangeOffset is relative to the current position in the idRangeOffset array. - // Take the current offset in the idRangeOffset array. - glyphIndexOffset = (idRangeOffsetParser.offset + idRangeOffsetParser.relativeOffset - 2); - - // Add the value of the idRangeOffset, which will move us into the glyphIndex array. - glyphIndexOffset += idRangeOffset; - - // Then add the character index of the current segment, multiplied by 2 for USHORTs. - glyphIndexOffset += (c - startCount) * 2; - glyphIndex = parse.getUShort(data, glyphIndexOffset); - if (glyphIndex !== 0) { - glyphIndex = (glyphIndex + idDelta) & 0xFFFF; - } - } else { - glyphIndex = (c + idDelta) & 0xFFFF; - } - - cmap.glyphIndexMap[c] = glyphIndex; - } - } - } - - // Parse the `cmap` table. This table stores the mappings from characters to glyphs. - // There are many available formats, but we only support the Windows format 4 and 12. - // This function returns a `CmapEncoding` object or null if no supported format could be found. - function parseCmapTable(data, start) { - var cmap = {}; - cmap.version = parse.getUShort(data, start); - check.argument(cmap.version === 0, 'cmap table version should be 0.'); - - // The cmap table can contain many sub-tables, each with their own format. - // We're only interested in a "platform 0" (Unicode format) and "platform 3" (Windows format) table. - cmap.numTables = parse.getUShort(data, start + 2); - var offset = -1; - for (var i = cmap.numTables - 1; i >= 0; i -= 1) { - var platformId = parse.getUShort(data, start + 4 + (i * 8)); - var encodingId = parse.getUShort(data, start + 4 + (i * 8) + 2); - if ((platformId === 3 && (encodingId === 0 || encodingId === 1 || encodingId === 10)) || - (platformId === 0 && (encodingId === 0 || encodingId === 1 || encodingId === 2 || encodingId === 3 || encodingId === 4))) { - offset = parse.getULong(data, start + 4 + (i * 8) + 4); - break; - } - } - - if (offset === -1) { - // There is no cmap table in the font that we support. - throw new Error('No valid cmap sub-tables found.'); - } - - var p = new parse.Parser(data, start + offset); - cmap.format = p.parseUShort(); - - if (cmap.format === 12) { - parseCmapTableFormat12(cmap, p); - } else if (cmap.format === 4) { - parseCmapTableFormat4(cmap, p, data, start, offset); - } else { - throw new Error('Only format 4 and 12 cmap tables are supported (found format ' + cmap.format + ').'); - } - - return cmap; - } - - function addSegment(t, code, glyphIndex) { - t.segments.push({ - end: code, - start: code, - delta: -(code - glyphIndex), - offset: 0, - glyphIndex: glyphIndex - }); - } - - function addTerminatorSegment(t) { - t.segments.push({ - end: 0xFFFF, - start: 0xFFFF, - delta: 1, - offset: 0 - }); - } - - // Make cmap table, format 4 by default, 12 if needed only - function makeCmapTable(glyphs) { - // Plan 0 is the base Unicode Plan but emojis, for example are on another plan, and needs cmap 12 format (with 32bit) - var isPlan0Only = true; - var i; - - // Check if we need to add cmap format 12 or if format 4 only is fine - for (i = glyphs.length - 1; i > 0; i -= 1) { - var g = glyphs.get(i); - if (g.unicode > 65535) { - console.log('Adding CMAP format 12 (needed!)'); - isPlan0Only = false; - break; - } - } - - var cmapTable = [ - {name: 'version', type: 'USHORT', value: 0}, - {name: 'numTables', type: 'USHORT', value: isPlan0Only ? 1 : 2}, - - // CMAP 4 header - {name: 'platformID', type: 'USHORT', value: 3}, - {name: 'encodingID', type: 'USHORT', value: 1}, - {name: 'offset', type: 'ULONG', value: isPlan0Only ? 12 : (12 + 8)} - ]; - - if (!isPlan0Only) - { cmapTable = cmapTable.concat([ - // CMAP 12 header - {name: 'cmap12PlatformID', type: 'USHORT', value: 3}, // We encode only for PlatformID = 3 (Windows) because it is supported everywhere - {name: 'cmap12EncodingID', type: 'USHORT', value: 10}, - {name: 'cmap12Offset', type: 'ULONG', value: 0} - ]); } - - cmapTable = cmapTable.concat([ - // CMAP 4 Subtable - {name: 'format', type: 'USHORT', value: 4}, - {name: 'cmap4Length', type: 'USHORT', value: 0}, - {name: 'language', type: 'USHORT', value: 0}, - {name: 'segCountX2', type: 'USHORT', value: 0}, - {name: 'searchRange', type: 'USHORT', value: 0}, - {name: 'entrySelector', type: 'USHORT', value: 0}, - {name: 'rangeShift', type: 'USHORT', value: 0} - ]); - - var t = new table.Table('cmap', cmapTable); - - t.segments = []; - for (i = 0; i < glyphs.length; i += 1) { - var glyph = glyphs.get(i); - for (var j = 0; j < glyph.unicodes.length; j += 1) { - addSegment(t, glyph.unicodes[j], i); - } - - t.segments = t.segments.sort(function (a, b) { - return a.start - b.start; - }); - } - - addTerminatorSegment(t); - - var segCount = t.segments.length; - var segCountToRemove = 0; - - // CMAP 4 - // Set up parallel segment arrays. - var endCounts = []; - var startCounts = []; - var idDeltas = []; - var idRangeOffsets = []; - var glyphIds = []; - - // CMAP 12 - var cmap12Groups = []; - - // Reminder this loop is not following the specification at 100% - // The specification -> find suites of characters and make a group - // Here we're doing one group for each letter - // Doing as the spec can save 8 times (or more) space - for (i = 0; i < segCount; i += 1) { - var segment = t.segments[i]; - - // CMAP 4 - if (segment.end <= 65535 && segment.start <= 65535) { - endCounts = endCounts.concat({name: 'end_' + i, type: 'USHORT', value: segment.end}); - startCounts = startCounts.concat({name: 'start_' + i, type: 'USHORT', value: segment.start}); - idDeltas = idDeltas.concat({name: 'idDelta_' + i, type: 'SHORT', value: segment.delta}); - idRangeOffsets = idRangeOffsets.concat({name: 'idRangeOffset_' + i, type: 'USHORT', value: segment.offset}); - if (segment.glyphId !== undefined) { - glyphIds = glyphIds.concat({name: 'glyph_' + i, type: 'USHORT', value: segment.glyphId}); - } - } else { - // Skip Unicode > 65535 (16bit unsigned max) for CMAP 4, will be added in CMAP 12 - segCountToRemove += 1; - } - - // CMAP 12 - // Skip Terminator Segment - if (!isPlan0Only && segment.glyphIndex !== undefined) { - cmap12Groups = cmap12Groups.concat({name: 'cmap12Start_' + i, type: 'ULONG', value: segment.start}); - cmap12Groups = cmap12Groups.concat({name: 'cmap12End_' + i, type: 'ULONG', value: segment.end}); - cmap12Groups = cmap12Groups.concat({name: 'cmap12Glyph_' + i, type: 'ULONG', value: segment.glyphIndex}); - } - } - - // CMAP 4 Subtable - t.segCountX2 = (segCount - segCountToRemove) * 2; - t.searchRange = Math.pow(2, Math.floor(Math.log((segCount - segCountToRemove)) / Math.log(2))) * 2; - t.entrySelector = Math.log(t.searchRange / 2) / Math.log(2); - t.rangeShift = t.segCountX2 - t.searchRange; - - t.fields = t.fields.concat(endCounts); - t.fields.push({name: 'reservedPad', type: 'USHORT', value: 0}); - t.fields = t.fields.concat(startCounts); - t.fields = t.fields.concat(idDeltas); - t.fields = t.fields.concat(idRangeOffsets); - t.fields = t.fields.concat(glyphIds); - - t.cmap4Length = 14 + // Subtable header - endCounts.length * 2 + - 2 + // reservedPad - startCounts.length * 2 + - idDeltas.length * 2 + - idRangeOffsets.length * 2 + - glyphIds.length * 2; - - if (!isPlan0Only) { - // CMAP 12 Subtable - var cmap12Length = 16 + // Subtable header - cmap12Groups.length * 4; - - t.cmap12Offset = 12 + (2 * 2) + 4 + t.cmap4Length; - t.fields = t.fields.concat([ - {name: 'cmap12Format', type: 'USHORT', value: 12}, - {name: 'cmap12Reserved', type: 'USHORT', value: 0}, - {name: 'cmap12Length', type: 'ULONG', value: cmap12Length}, - {name: 'cmap12Language', type: 'ULONG', value: 0}, - {name: 'cmap12nGroups', type: 'ULONG', value: cmap12Groups.length / 3} - ]); - - t.fields = t.fields.concat(cmap12Groups); - } - - return t; - } - - var cmap = { parse: parseCmapTable, make: makeCmapTable }; - - // Glyph encoding - - var cffStandardStrings = [ - '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', - 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', - 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', - 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', - 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', - 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling', - 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', - 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph', - 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', - 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', - 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', - 'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu', - 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', - 'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright', - 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', - 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex', - 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', - 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute', - 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', - 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', - 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', - 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff', 'onedotenleader', - 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', - 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', - 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', - 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', - 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', - 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', - 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', - 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', - 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', - 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', - 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', - 'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', - 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', - 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', - 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', - 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', - 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', - 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', - 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', - '001.001', '001.002', '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold']; - - var cffStandardEncoding = [ - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', - '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', - 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', - 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', - 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', - 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', - 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', - 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', - 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', - 'daggerdbl', 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', - 'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', 'tilde', - 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', - 'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', 'ordfeminine', '', '', '', - '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', - 'lslash', 'oslash', 'oe', 'germandbls']; - - var cffExpertEncoding = [ - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', - '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle', 'dollarsuperior', - 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', - 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', - 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', - 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', '', 'asuperior', - 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', '', '', 'isuperior', '', '', 'lsuperior', 'msuperior', - 'nsuperior', 'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior', '', 'ff', 'fi', 'fl', 'ffi', 'ffl', - 'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', - 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', - 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', - 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', - 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', '', '', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', - 'Brevesmall', 'Caronsmall', '', 'Dotaccentsmall', '', '', 'Macronsmall', '', '', 'figuredash', 'hypheninferior', - '', '', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters', - 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', '', - '', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', - 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', - 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', - 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', - 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', - 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', - 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', - 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', - 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall']; - - var standardNames = [ - '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', - 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', - 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', - 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', - 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', - 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', - 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', 'Odieresis', 'Udieresis', 'aacute', 'agrave', - 'acircumflex', 'adieresis', 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis', - 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex', 'odieresis', - 'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', - 'bullet', 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute', 'dieresis', 'notequal', - 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal', 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', - 'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', 'questiondown', - 'exclamdown', 'logicalnot', 'radical', 'florin', 'approxequal', 'Delta', 'guillemotleft', 'guillemotright', - 'ellipsis', 'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft', - 'quotedblright', 'quoteleft', 'quoteright', 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', - 'currency', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered', 'quotesinglbase', - 'quotedblbase', 'perthousand', 'Acircumflex', 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', - 'Icircumflex', 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute', 'Ucircumflex', - 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', - 'ogonek', 'caron', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar', 'Eth', 'eth', - 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply', 'onesuperior', 'twosuperior', 'threesuperior', - 'onehalf', 'onequarter', 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', 'scedilla', - 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat']; - - /** - * This is the encoding used for fonts created from scratch. - * It loops through all glyphs and finds the appropriate unicode value. - * Since it's linear time, other encodings will be faster. - * @exports opentype.DefaultEncoding - * @class - * @constructor - * @param {opentype.Font} - */ - function DefaultEncoding(font) { - this.font = font; - } - - DefaultEncoding.prototype.charToGlyphIndex = function(c) { - var code = c.codePointAt(0); - var glyphs = this.font.glyphs; - if (glyphs) { - for (var i = 0; i < glyphs.length; i += 1) { - var glyph = glyphs.get(i); - for (var j = 0; j < glyph.unicodes.length; j += 1) { - if (glyph.unicodes[j] === code) { - return i; - } - } - } - } - return null; - }; - - /** - * @exports opentype.CmapEncoding - * @class - * @constructor - * @param {Object} cmap - a object with the cmap encoded data - */ - function CmapEncoding(cmap) { - this.cmap = cmap; - } - - /** - * @param {string} c - the character - * @return {number} The glyph index. - */ - CmapEncoding.prototype.charToGlyphIndex = function(c) { - return this.cmap.glyphIndexMap[c.codePointAt(0)] || 0; - }; - - /** - * @exports opentype.CffEncoding - * @class - * @constructor - * @param {string} encoding - The encoding - * @param {Array} charset - The character set. - */ - function CffEncoding(encoding, charset) { - this.encoding = encoding; - this.charset = charset; - } - - /** - * @param {string} s - The character - * @return {number} The index. - */ - CffEncoding.prototype.charToGlyphIndex = function(s) { - var code = s.codePointAt(0); - var charName = this.encoding[code]; - return this.charset.indexOf(charName); - }; - - /** - * @exports opentype.GlyphNames - * @class - * @constructor - * @param {Object} post - */ - function GlyphNames(post) { - switch (post.version) { - case 1: - this.names = standardNames.slice(); - break; - case 2: - this.names = new Array(post.numberOfGlyphs); - for (var i = 0; i < post.numberOfGlyphs; i++) { - if (post.glyphNameIndex[i] < standardNames.length) { - this.names[i] = standardNames[post.glyphNameIndex[i]]; - } else { - this.names[i] = post.names[post.glyphNameIndex[i] - standardNames.length]; - } - } - - break; - case 2.5: - this.names = new Array(post.numberOfGlyphs); - for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { - this.names[i$1] = standardNames[i$1 + post.glyphNameIndex[i$1]]; - } - - break; - case 3: - this.names = []; - break; - default: - this.names = []; - break; - } - } - - /** - * Gets the index of a glyph by name. - * @param {string} name - The glyph name - * @return {number} The index - */ - GlyphNames.prototype.nameToGlyphIndex = function(name) { - return this.names.indexOf(name); - }; - - /** - * @param {number} gid - * @return {string} - */ - GlyphNames.prototype.glyphIndexToName = function(gid) { - return this.names[gid]; - }; - - function addGlyphNamesAll(font) { - var glyph; - var glyphIndexMap = font.tables.cmap.glyphIndexMap; - var charCodes = Object.keys(glyphIndexMap); - - for (var i = 0; i < charCodes.length; i += 1) { - var c = charCodes[i]; - var glyphIndex = glyphIndexMap[c]; - glyph = font.glyphs.get(glyphIndex); - glyph.addUnicode(parseInt(c)); - } - - for (var i$1 = 0; i$1 < font.glyphs.length; i$1 += 1) { - glyph = font.glyphs.get(i$1); - if (font.cffEncoding) { - if (font.isCIDFont) { - glyph.name = 'gid' + i$1; - } else { - glyph.name = font.cffEncoding.charset[i$1]; - } - } else if (font.glyphNames.names) { - glyph.name = font.glyphNames.glyphIndexToName(i$1); - } - } - } - - function addGlyphNamesToUnicodeMap(font) { - font._IndexToUnicodeMap = {}; - - var glyphIndexMap = font.tables.cmap.glyphIndexMap; - var charCodes = Object.keys(glyphIndexMap); - - for (var i = 0; i < charCodes.length; i += 1) { - var c = charCodes[i]; - var glyphIndex = glyphIndexMap[c]; - if (font._IndexToUnicodeMap[glyphIndex] === undefined) { - font._IndexToUnicodeMap[glyphIndex] = { - unicodes: [parseInt(c)] - }; - } else { - font._IndexToUnicodeMap[glyphIndex].unicodes.push(parseInt(c)); - } - } - } - - /** - * @alias opentype.addGlyphNames - * @param {opentype.Font} - * @param {Object} - */ - function addGlyphNames(font, opt) { - if (opt.lowMemory) { - addGlyphNamesToUnicodeMap(font); - } else { - addGlyphNamesAll(font); - } - } - - // Drawing utility functions. - - // Draw a line on the given context from point `x1,y1` to point `x2,y2`. - function line(ctx, x1, y1, x2, y2) { - ctx.beginPath(); - ctx.moveTo(x1, y1); - ctx.lineTo(x2, y2); - ctx.stroke(); - } - - var draw = { line: line }; - - // The Glyph object - // import glyf from './tables/glyf' Can't be imported here, because it's a circular dependency - - function getPathDefinition(glyph, path) { - var _path = path || new Path(); - return { - configurable: true, - - get: function() { - if (typeof _path === 'function') { - _path = _path(); - } - - return _path; - }, - - set: function(p) { - _path = p; - } - }; - } - /** - * @typedef GlyphOptions - * @type Object - * @property {string} [name] - The glyph name - * @property {number} [unicode] - * @property {Array} [unicodes] - * @property {number} [xMin] - * @property {number} [yMin] - * @property {number} [xMax] - * @property {number} [yMax] - * @property {number} [advanceWidth] - */ - - // A Glyph is an individual mark that often corresponds to a character. - // Some glyphs, such as ligatures, are a combination of many characters. - // Glyphs are the basic building blocks of a font. - // - // The `Glyph` class contains utility methods for drawing the path and its points. - /** - * @exports opentype.Glyph - * @class - * @param {GlyphOptions} - * @constructor - */ - function Glyph(options) { - // By putting all the code on a prototype function (which is only declared once) - // we reduce the memory requirements for larger fonts by some 2% - this.bindConstructorValues(options); - } - - /** - * @param {GlyphOptions} - */ - Glyph.prototype.bindConstructorValues = function(options) { - this.index = options.index || 0; - - // These three values cannot be deferred for memory optimization: - this.name = options.name || null; - this.unicode = options.unicode || undefined; - this.unicodes = options.unicodes || options.unicode !== undefined ? [options.unicode] : []; - - // But by binding these values only when necessary, we reduce can - // the memory requirements by almost 3% for larger fonts. - if ('xMin' in options) { - this.xMin = options.xMin; - } - - if ('yMin' in options) { - this.yMin = options.yMin; - } - - if ('xMax' in options) { - this.xMax = options.xMax; - } - - if ('yMax' in options) { - this.yMax = options.yMax; - } - - if ('advanceWidth' in options) { - this.advanceWidth = options.advanceWidth; - } - - // The path for a glyph is the most memory intensive, and is bound as a value - // with a getter/setter to ensure we actually do path parsing only once the - // path is actually needed by anything. - Object.defineProperty(this, 'path', getPathDefinition(this, options.path)); - }; - - /** - * @param {number} - */ - Glyph.prototype.addUnicode = function(unicode) { - if (this.unicodes.length === 0) { - this.unicode = unicode; - } - - this.unicodes.push(unicode); - }; - - /** - * Calculate the minimum bounding box for this glyph. - * @return {opentype.BoundingBox} - */ - Glyph.prototype.getBoundingBox = function() { - return this.path.getBoundingBox(); - }; - - /** - * Convert the glyph to a Path we can draw on a drawing context. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {Object=} options - xScale, yScale to stretch the glyph. - * @param {opentype.Font} if hinting is to be used, the font - * @return {opentype.Path} - */ - Glyph.prototype.getPath = function(x, y, fontSize, options, font) { - x = x !== undefined ? x : 0; - y = y !== undefined ? y : 0; - fontSize = fontSize !== undefined ? fontSize : 72; - var commands; - var hPoints; - if (!options) { options = { }; } - var xScale = options.xScale; - var yScale = options.yScale; - - if (options.hinting && font && font.hinting) { - // in case of hinting, the hinting engine takes care - // of scaling the points (not the path) before hinting. - hPoints = this.path && font.hinting.exec(this, fontSize); - // in case the hinting engine failed hPoints is undefined - // and thus reverts to plain rending - } - - if (hPoints) { - // Call font.hinting.getCommands instead of `glyf.getPath(hPoints).commands` to avoid a circular dependency - commands = font.hinting.getCommands(hPoints); - x = Math.round(x); - y = Math.round(y); - // TODO in case of hinting xyScaling is not yet supported - xScale = yScale = 1; - } else { - commands = this.path.commands; - var scale = 1 / (this.path.unitsPerEm || 1000) * fontSize; - if (xScale === undefined) { xScale = scale; } - if (yScale === undefined) { yScale = scale; } - } - - var p = new Path(); - for (var i = 0; i < commands.length; i += 1) { - var cmd = commands[i]; - if (cmd.type === 'M') { - p.moveTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); - } else if (cmd.type === 'L') { - p.lineTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); - } else if (cmd.type === 'Q') { - p.quadraticCurveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), - x + (cmd.x * xScale), y + (-cmd.y * yScale)); - } else if (cmd.type === 'C') { - p.curveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), - x + (cmd.x2 * xScale), y + (-cmd.y2 * yScale), - x + (cmd.x * xScale), y + (-cmd.y * yScale)); - } else if (cmd.type === 'Z') { - p.closePath(); - } - } - - return p; - }; - - /** - * Split the glyph into contours. - * This function is here for backwards compatibility, and to - * provide raw access to the TrueType glyph outlines. - * @return {Array} - */ - Glyph.prototype.getContours = function() { - if (this.points === undefined) { - return []; - } - - var contours = []; - var currentContour = []; - for (var i = 0; i < this.points.length; i += 1) { - var pt = this.points[i]; - currentContour.push(pt); - if (pt.lastPointOfContour) { - contours.push(currentContour); - currentContour = []; - } - } - - check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); - return contours; - }; - - /** - * Calculate the xMin/yMin/xMax/yMax/lsb/rsb for a Glyph. - * @return {Object} - */ - Glyph.prototype.getMetrics = function() { - var commands = this.path.commands; - var xCoords = []; - var yCoords = []; - for (var i = 0; i < commands.length; i += 1) { - var cmd = commands[i]; - if (cmd.type !== 'Z') { - xCoords.push(cmd.x); - yCoords.push(cmd.y); - } - - if (cmd.type === 'Q' || cmd.type === 'C') { - xCoords.push(cmd.x1); - yCoords.push(cmd.y1); - } - - if (cmd.type === 'C') { - xCoords.push(cmd.x2); - yCoords.push(cmd.y2); - } - } - - var metrics = { - xMin: Math.min.apply(null, xCoords), - yMin: Math.min.apply(null, yCoords), - xMax: Math.max.apply(null, xCoords), - yMax: Math.max.apply(null, yCoords), - leftSideBearing: this.leftSideBearing - }; - - if (!isFinite(metrics.xMin)) { - metrics.xMin = 0; - } - - if (!isFinite(metrics.xMax)) { - metrics.xMax = this.advanceWidth; - } - - if (!isFinite(metrics.yMin)) { - metrics.yMin = 0; - } - - if (!isFinite(metrics.yMax)) { - metrics.yMax = 0; - } - - metrics.rightSideBearing = this.advanceWidth - metrics.leftSideBearing - (metrics.xMax - metrics.xMin); - return metrics; - }; - - /** - * Draw the glyph on the given context. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {Object=} options - xScale, yScale to stretch the glyph. - */ - Glyph.prototype.draw = function(ctx, x, y, fontSize, options) { - this.getPath(x, y, fontSize, options).draw(ctx); - }; - - /** - * Draw the points of the glyph. - * On-curve points will be drawn in blue, off-curve points will be drawn in red. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - */ - Glyph.prototype.drawPoints = function(ctx, x, y, fontSize) { - function drawCircles(l, x, y, scale) { - ctx.beginPath(); - for (var j = 0; j < l.length; j += 1) { - ctx.moveTo(x + (l[j].x * scale), y + (l[j].y * scale)); - ctx.arc(x + (l[j].x * scale), y + (l[j].y * scale), 2, 0, Math.PI * 2, false); - } - - ctx.closePath(); - ctx.fill(); - } - - x = x !== undefined ? x : 0; - y = y !== undefined ? y : 0; - fontSize = fontSize !== undefined ? fontSize : 24; - var scale = 1 / this.path.unitsPerEm * fontSize; - - var blueCircles = []; - var redCircles = []; - var path = this.path; - for (var i = 0; i < path.commands.length; i += 1) { - var cmd = path.commands[i]; - if (cmd.x !== undefined) { - blueCircles.push({x: cmd.x, y: -cmd.y}); - } - - if (cmd.x1 !== undefined) { - redCircles.push({x: cmd.x1, y: -cmd.y1}); - } - - if (cmd.x2 !== undefined) { - redCircles.push({x: cmd.x2, y: -cmd.y2}); - } - } - - ctx.fillStyle = 'blue'; - drawCircles(blueCircles, x, y, scale); - ctx.fillStyle = 'red'; - drawCircles(redCircles, x, y, scale); - }; - - /** - * Draw lines indicating important font measurements. - * Black lines indicate the origin of the coordinate system (point 0,0). - * Blue lines indicate the glyph bounding box. - * Green line indicates the advance width of the glyph. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - */ - Glyph.prototype.drawMetrics = function(ctx, x, y, fontSize) { - var scale; - x = x !== undefined ? x : 0; - y = y !== undefined ? y : 0; - fontSize = fontSize !== undefined ? fontSize : 24; - scale = 1 / this.path.unitsPerEm * fontSize; - ctx.lineWidth = 1; - - // Draw the origin - ctx.strokeStyle = 'black'; - draw.line(ctx, x, -10000, x, 10000); - draw.line(ctx, -10000, y, 10000, y); - - // This code is here due to memory optimization: by not using - // defaults in the constructor, we save a notable amount of memory. - var xMin = this.xMin || 0; - var yMin = this.yMin || 0; - var xMax = this.xMax || 0; - var yMax = this.yMax || 0; - var advanceWidth = this.advanceWidth || 0; - - // Draw the glyph box - ctx.strokeStyle = 'blue'; - draw.line(ctx, x + (xMin * scale), -10000, x + (xMin * scale), 10000); - draw.line(ctx, x + (xMax * scale), -10000, x + (xMax * scale), 10000); - draw.line(ctx, -10000, y + (-yMin * scale), 10000, y + (-yMin * scale)); - draw.line(ctx, -10000, y + (-yMax * scale), 10000, y + (-yMax * scale)); - - // Draw the advance width - ctx.strokeStyle = 'green'; - draw.line(ctx, x + (advanceWidth * scale), -10000, x + (advanceWidth * scale), 10000); - }; - - // The GlyphSet object - - // Define a property on the glyph that depends on the path being loaded. - function defineDependentProperty(glyph, externalName, internalName) { - Object.defineProperty(glyph, externalName, { - get: function() { - // Request the path property to make sure the path is loaded. - glyph.path; // jshint ignore:line - return glyph[internalName]; - }, - set: function(newValue) { - glyph[internalName] = newValue; - }, - enumerable: true, - configurable: true - }); - } - - /** - * A GlyphSet represents all glyphs available in the font, but modelled using - * a deferred glyph loader, for retrieving glyphs only once they are absolutely - * necessary, to keep the memory footprint down. - * @exports opentype.GlyphSet - * @class - * @param {opentype.Font} - * @param {Array} - */ - function GlyphSet(font, glyphs) { - this.font = font; - this.glyphs = {}; - if (Array.isArray(glyphs)) { - for (var i = 0; i < glyphs.length; i++) { - var glyph = glyphs[i]; - glyph.path.unitsPerEm = font.unitsPerEm; - this.glyphs[i] = glyph; - } - } - - this.length = (glyphs && glyphs.length) || 0; - } - - /** - * @param {number} index - * @return {opentype.Glyph} - */ - GlyphSet.prototype.get = function(index) { - // this.glyphs[index] is 'undefined' when low memory mode is on. glyph is pushed on request only. - if (this.glyphs[index] === undefined) { - this.font._push(index); - if (typeof this.glyphs[index] === 'function') { - this.glyphs[index] = this.glyphs[index](); - } - - var glyph = this.glyphs[index]; - var unicodeObj = this.font._IndexToUnicodeMap[index]; - - if (unicodeObj) { - for (var j = 0; j < unicodeObj.unicodes.length; j++) - { glyph.addUnicode(unicodeObj.unicodes[j]); } - } - - if (this.font.cffEncoding) { - if (this.font.isCIDFont) { - glyph.name = 'gid' + index; - } else { - glyph.name = this.font.cffEncoding.charset[index]; - } - } else if (this.font.glyphNames.names) { - glyph.name = this.font.glyphNames.glyphIndexToName(index); - } - - this.glyphs[index].advanceWidth = this.font._hmtxTableData[index].advanceWidth; - this.glyphs[index].leftSideBearing = this.font._hmtxTableData[index].leftSideBearing; - } else { - if (typeof this.glyphs[index] === 'function') { - this.glyphs[index] = this.glyphs[index](); - } - } - - return this.glyphs[index]; - }; - - /** - * @param {number} index - * @param {Object} - */ - GlyphSet.prototype.push = function(index, loader) { - this.glyphs[index] = loader; - this.length++; - }; - - /** - * @alias opentype.glyphLoader - * @param {opentype.Font} font - * @param {number} index - * @return {opentype.Glyph} - */ - function glyphLoader(font, index) { - return new Glyph({index: index, font: font}); - } - - /** - * Generate a stub glyph that can be filled with all metadata *except* - * the "points" and "path" properties, which must be loaded only once - * the glyph's path is actually requested for text shaping. - * @alias opentype.ttfGlyphLoader - * @param {opentype.Font} font - * @param {number} index - * @param {Function} parseGlyph - * @param {Object} data - * @param {number} position - * @param {Function} buildPath - * @return {opentype.Glyph} - */ - function ttfGlyphLoader(font, index, parseGlyph, data, position, buildPath) { - return function() { - var glyph = new Glyph({index: index, font: font}); - - glyph.path = function() { - parseGlyph(glyph, data, position); - var path = buildPath(font.glyphs, glyph); - path.unitsPerEm = font.unitsPerEm; - return path; - }; - - defineDependentProperty(glyph, 'xMin', '_xMin'); - defineDependentProperty(glyph, 'xMax', '_xMax'); - defineDependentProperty(glyph, 'yMin', '_yMin'); - defineDependentProperty(glyph, 'yMax', '_yMax'); - - return glyph; - }; - } - /** - * @alias opentype.cffGlyphLoader - * @param {opentype.Font} font - * @param {number} index - * @param {Function} parseCFFCharstring - * @param {string} charstring - * @return {opentype.Glyph} - */ - function cffGlyphLoader(font, index, parseCFFCharstring, charstring) { - return function() { - var glyph = new Glyph({index: index, font: font}); - - glyph.path = function() { - var path = parseCFFCharstring(font, glyph, charstring); - path.unitsPerEm = font.unitsPerEm; - return path; - }; - - return glyph; - }; - } - - var glyphset = { GlyphSet: GlyphSet, glyphLoader: glyphLoader, ttfGlyphLoader: ttfGlyphLoader, cffGlyphLoader: cffGlyphLoader }; - - // The `CFF` table contains the glyph outlines in PostScript format. - - // Custom equals function that can also check lists. - function equals(a, b) { - if (a === b) { - return true; - } else if (Array.isArray(a) && Array.isArray(b)) { - if (a.length !== b.length) { - return false; - } - - for (var i = 0; i < a.length; i += 1) { - if (!equals(a[i], b[i])) { - return false; - } - } - - return true; - } else { - return false; - } - } - - // Subroutines are encoded using the negative half of the number space. - // See type 2 chapter 4.7 "Subroutine operators". - function calcCFFSubroutineBias(subrs) { - var bias; - if (subrs.length < 1240) { - bias = 107; - } else if (subrs.length < 33900) { - bias = 1131; - } else { - bias = 32768; - } - - return bias; - } - - // Parse a `CFF` INDEX array. - // An index array consists of a list of offsets, then a list of objects at those offsets. - function parseCFFIndex(data, start, conversionFn) { - var offsets = []; - var objects = []; - var count = parse.getCard16(data, start); - var objectOffset; - var endOffset; - if (count !== 0) { - var offsetSize = parse.getByte(data, start + 2); - objectOffset = start + ((count + 1) * offsetSize) + 2; - var pos = start + 3; - for (var i = 0; i < count + 1; i += 1) { - offsets.push(parse.getOffset(data, pos, offsetSize)); - pos += offsetSize; - } - - // The total size of the index array is 4 header bytes + the value of the last offset. - endOffset = objectOffset + offsets[count]; - } else { - endOffset = start + 2; - } - - for (var i$1 = 0; i$1 < offsets.length - 1; i$1 += 1) { - var value = parse.getBytes(data, objectOffset + offsets[i$1], objectOffset + offsets[i$1 + 1]); - if (conversionFn) { - value = conversionFn(value); - } - - objects.push(value); - } - - return {objects: objects, startOffset: start, endOffset: endOffset}; - } - - function parseCFFIndexLowMemory(data, start) { - var offsets = []; - var count = parse.getCard16(data, start); - var objectOffset; - var endOffset; - if (count !== 0) { - var offsetSize = parse.getByte(data, start + 2); - objectOffset = start + ((count + 1) * offsetSize) + 2; - var pos = start + 3; - for (var i = 0; i < count + 1; i += 1) { - offsets.push(parse.getOffset(data, pos, offsetSize)); - pos += offsetSize; - } - - // The total size of the index array is 4 header bytes + the value of the last offset. - endOffset = objectOffset + offsets[count]; - } else { - endOffset = start + 2; - } - - return {offsets: offsets, startOffset: start, endOffset: endOffset}; - } - function getCffIndexObject(i, offsets, data, start, conversionFn) { - var count = parse.getCard16(data, start); - var objectOffset = 0; - if (count !== 0) { - var offsetSize = parse.getByte(data, start + 2); - objectOffset = start + ((count + 1) * offsetSize) + 2; - } - - var value = parse.getBytes(data, objectOffset + offsets[i], objectOffset + offsets[i + 1]); - if (conversionFn) { - value = conversionFn(value); - } - return value; - } - - // Parse a `CFF` DICT real value. - function parseFloatOperand(parser) { - var s = ''; - var eof = 15; - var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-']; - while (true) { - var b = parser.parseByte(); - var n1 = b >> 4; - var n2 = b & 15; - - if (n1 === eof) { - break; - } - - s += lookup[n1]; - - if (n2 === eof) { - break; - } - - s += lookup[n2]; - } - - return parseFloat(s); - } - - // Parse a `CFF` DICT operand. - function parseOperand(parser, b0) { - var b1; - var b2; - var b3; - var b4; - if (b0 === 28) { - b1 = parser.parseByte(); - b2 = parser.parseByte(); - return b1 << 8 | b2; - } - - if (b0 === 29) { - b1 = parser.parseByte(); - b2 = parser.parseByte(); - b3 = parser.parseByte(); - b4 = parser.parseByte(); - return b1 << 24 | b2 << 16 | b3 << 8 | b4; - } - - if (b0 === 30) { - return parseFloatOperand(parser); - } - - if (b0 >= 32 && b0 <= 246) { - return b0 - 139; - } - - if (b0 >= 247 && b0 <= 250) { - b1 = parser.parseByte(); - return (b0 - 247) * 256 + b1 + 108; - } - - if (b0 >= 251 && b0 <= 254) { - b1 = parser.parseByte(); - return -(b0 - 251) * 256 - b1 - 108; - } - - throw new Error('Invalid b0 ' + b0); - } - - // Convert the entries returned by `parseDict` to a proper dictionary. - // If a value is a list of one, it is unpacked. - function entriesToObject(entries) { - var o = {}; - for (var i = 0; i < entries.length; i += 1) { - var key = entries[i][0]; - var values = entries[i][1]; - var value = (void 0); - if (values.length === 1) { - value = values[0]; - } else { - value = values; - } - - if (o.hasOwnProperty(key) && !isNaN(o[key])) { - throw new Error('Object ' + o + ' already has key ' + key); - } - - o[key] = value; - } - - return o; - } - - // Parse a `CFF` DICT object. - // A dictionary contains key-value pairs in a compact tokenized format. - function parseCFFDict(data, start, size) { - start = start !== undefined ? start : 0; - var parser = new parse.Parser(data, start); - var entries = []; - var operands = []; - size = size !== undefined ? size : data.length; - - while (parser.relativeOffset < size) { - var op = parser.parseByte(); - - // The first byte for each dict item distinguishes between operator (key) and operand (value). - // Values <= 21 are operators. - if (op <= 21) { - // Two-byte operators have an initial escape byte of 12. - if (op === 12) { - op = 1200 + parser.parseByte(); - } - - entries.push([op, operands]); - operands = []; - } else { - // Since the operands (values) come before the operators (keys), we store all operands in a list - // until we encounter an operator. - operands.push(parseOperand(parser, op)); - } - } - - return entriesToObject(entries); - } - - // Given a String Index (SID), return the value of the string. - // Strings below index 392 are standard CFF strings and are not encoded in the font. - function getCFFString(strings, index) { - if (index <= 390) { - index = cffStandardStrings[index]; - } else { - index = strings[index - 391]; - } - - return index; - } - - // Interpret a dictionary and return a new dictionary with readable keys and values for missing entries. - // This function takes `meta` which is a list of objects containing `operand`, `name` and `default`. - function interpretDict(dict, meta, strings) { - var newDict = {}; - var value; - - // Because we also want to include missing values, we start out from the meta list - // and lookup values in the dict. - for (var i = 0; i < meta.length; i += 1) { - var m = meta[i]; - - if (Array.isArray(m.type)) { - var values = []; - values.length = m.type.length; - for (var j = 0; j < m.type.length; j++) { - value = dict[m.op] !== undefined ? dict[m.op][j] : undefined; - if (value === undefined) { - value = m.value !== undefined && m.value[j] !== undefined ? m.value[j] : null; - } - if (m.type[j] === 'SID') { - value = getCFFString(strings, value); - } - values[j] = value; - } - newDict[m.name] = values; - } else { - value = dict[m.op]; - if (value === undefined) { - value = m.value !== undefined ? m.value : null; - } - - if (m.type === 'SID') { - value = getCFFString(strings, value); - } - newDict[m.name] = value; - } - } - - return newDict; - } - - // Parse the CFF header. - function parseCFFHeader(data, start) { - var header = {}; - header.formatMajor = parse.getCard8(data, start); - header.formatMinor = parse.getCard8(data, start + 1); - header.size = parse.getCard8(data, start + 2); - header.offsetSize = parse.getCard8(data, start + 3); - header.startOffset = start; - header.endOffset = start + 4; - return header; - } - - var TOP_DICT_META = [ - {name: 'version', op: 0, type: 'SID'}, - {name: 'notice', op: 1, type: 'SID'}, - {name: 'copyright', op: 1200, type: 'SID'}, - {name: 'fullName', op: 2, type: 'SID'}, - {name: 'familyName', op: 3, type: 'SID'}, - {name: 'weight', op: 4, type: 'SID'}, - {name: 'isFixedPitch', op: 1201, type: 'number', value: 0}, - {name: 'italicAngle', op: 1202, type: 'number', value: 0}, - {name: 'underlinePosition', op: 1203, type: 'number', value: -100}, - {name: 'underlineThickness', op: 1204, type: 'number', value: 50}, - {name: 'paintType', op: 1205, type: 'number', value: 0}, - {name: 'charstringType', op: 1206, type: 'number', value: 2}, - { - name: 'fontMatrix', - op: 1207, - type: ['real', 'real', 'real', 'real', 'real', 'real'], - value: [0.001, 0, 0, 0.001, 0, 0] - }, - {name: 'uniqueId', op: 13, type: 'number'}, - {name: 'fontBBox', op: 5, type: ['number', 'number', 'number', 'number'], value: [0, 0, 0, 0]}, - {name: 'strokeWidth', op: 1208, type: 'number', value: 0}, - {name: 'xuid', op: 14, type: [], value: null}, - {name: 'charset', op: 15, type: 'offset', value: 0}, - {name: 'encoding', op: 16, type: 'offset', value: 0}, - {name: 'charStrings', op: 17, type: 'offset', value: 0}, - {name: 'private', op: 18, type: ['number', 'offset'], value: [0, 0]}, - {name: 'ros', op: 1230, type: ['SID', 'SID', 'number']}, - {name: 'cidFontVersion', op: 1231, type: 'number', value: 0}, - {name: 'cidFontRevision', op: 1232, type: 'number', value: 0}, - {name: 'cidFontType', op: 1233, type: 'number', value: 0}, - {name: 'cidCount', op: 1234, type: 'number', value: 8720}, - {name: 'uidBase', op: 1235, type: 'number'}, - {name: 'fdArray', op: 1236, type: 'offset'}, - {name: 'fdSelect', op: 1237, type: 'offset'}, - {name: 'fontName', op: 1238, type: 'SID'} - ]; - - var PRIVATE_DICT_META = [ - {name: 'subrs', op: 19, type: 'offset', value: 0}, - {name: 'defaultWidthX', op: 20, type: 'number', value: 0}, - {name: 'nominalWidthX', op: 21, type: 'number', value: 0} - ]; - - // Parse the CFF top dictionary. A CFF table can contain multiple fonts, each with their own top dictionary. - // The top dictionary contains the essential metadata for the font, together with the private dictionary. - function parseCFFTopDict(data, strings) { - var dict = parseCFFDict(data, 0, data.byteLength); - return interpretDict(dict, TOP_DICT_META, strings); - } - - // Parse the CFF private dictionary. We don't fully parse out all the values, only the ones we need. - function parseCFFPrivateDict(data, start, size, strings) { - var dict = parseCFFDict(data, start, size); - return interpretDict(dict, PRIVATE_DICT_META, strings); - } - - // Returns a list of "Top DICT"s found using an INDEX list. - // Used to read both the usual high-level Top DICTs and also the FDArray - // discovered inside CID-keyed fonts. When a Top DICT has a reference to - // a Private DICT that is read and saved into the Top DICT. - // - // In addition to the expected/optional values as outlined in TOP_DICT_META - // the following values might be saved into the Top DICT. - // - // _subrs [] array of local CFF subroutines from Private DICT - // _subrsBias bias value computed from number of subroutines - // (see calcCFFSubroutineBias() and parseCFFCharstring()) - // _defaultWidthX default widths for CFF characters - // _nominalWidthX bias added to width embedded within glyph description - // - // _privateDict saved copy of parsed Private DICT from Top DICT - function gatherCFFTopDicts(data, start, cffIndex, strings) { - var topDictArray = []; - for (var iTopDict = 0; iTopDict < cffIndex.length; iTopDict += 1) { - var topDictData = new DataView(new Uint8Array(cffIndex[iTopDict]).buffer); - var topDict = parseCFFTopDict(topDictData, strings); - topDict._subrs = []; - topDict._subrsBias = 0; - var privateSize = topDict.private[0]; - var privateOffset = topDict.private[1]; - if (privateSize !== 0 && privateOffset !== 0) { - var privateDict = parseCFFPrivateDict(data, privateOffset + start, privateSize, strings); - topDict._defaultWidthX = privateDict.defaultWidthX; - topDict._nominalWidthX = privateDict.nominalWidthX; - if (privateDict.subrs !== 0) { - var subrOffset = privateOffset + privateDict.subrs; - var subrIndex = parseCFFIndex(data, subrOffset + start); - topDict._subrs = subrIndex.objects; - topDict._subrsBias = calcCFFSubroutineBias(topDict._subrs); - } - topDict._privateDict = privateDict; - } - topDictArray.push(topDict); - } - return topDictArray; - } - - // Parse the CFF charset table, which contains internal names for all the glyphs. - // This function will return a list of glyph names. - // See Adobe TN #5176 chapter 13, "Charsets". - function parseCFFCharset(data, start, nGlyphs, strings) { - var sid; - var count; - var parser = new parse.Parser(data, start); - - // The .notdef glyph is not included, so subtract 1. - nGlyphs -= 1; - var charset = ['.notdef']; - - var format = parser.parseCard8(); - if (format === 0) { - for (var i = 0; i < nGlyphs; i += 1) { - sid = parser.parseSID(); - charset.push(getCFFString(strings, sid)); - } - } else if (format === 1) { - while (charset.length <= nGlyphs) { - sid = parser.parseSID(); - count = parser.parseCard8(); - for (var i$1 = 0; i$1 <= count; i$1 += 1) { - charset.push(getCFFString(strings, sid)); - sid += 1; - } - } - } else if (format === 2) { - while (charset.length <= nGlyphs) { - sid = parser.parseSID(); - count = parser.parseCard16(); - for (var i$2 = 0; i$2 <= count; i$2 += 1) { - charset.push(getCFFString(strings, sid)); - sid += 1; - } - } - } else { - throw new Error('Unknown charset format ' + format); - } - - return charset; - } - - // Parse the CFF encoding data. Only one encoding can be specified per font. - // See Adobe TN #5176 chapter 12, "Encodings". - function parseCFFEncoding(data, start, charset) { - var code; - var enc = {}; - var parser = new parse.Parser(data, start); - var format = parser.parseCard8(); - if (format === 0) { - var nCodes = parser.parseCard8(); - for (var i = 0; i < nCodes; i += 1) { - code = parser.parseCard8(); - enc[code] = i; - } - } else if (format === 1) { - var nRanges = parser.parseCard8(); - code = 1; - for (var i$1 = 0; i$1 < nRanges; i$1 += 1) { - var first = parser.parseCard8(); - var nLeft = parser.parseCard8(); - for (var j = first; j <= first + nLeft; j += 1) { - enc[j] = code; - code += 1; - } - } - } else { - throw new Error('Unknown encoding format ' + format); - } - - return new CffEncoding(enc, charset); - } - - // Take in charstring code and return a Glyph object. - // The encoding is described in the Type 2 Charstring Format - // https://www.microsoft.com/typography/OTSPEC/charstr2.htm - function parseCFFCharstring(font, glyph, code) { - var c1x; - var c1y; - var c2x; - var c2y; - var p = new Path(); - var stack = []; - var nStems = 0; - var haveWidth = false; - var open = false; - var x = 0; - var y = 0; - var subrs; - var subrsBias; - var defaultWidthX; - var nominalWidthX; - if (font.isCIDFont) { - var fdIndex = font.tables.cff.topDict._fdSelect[glyph.index]; - var fdDict = font.tables.cff.topDict._fdArray[fdIndex]; - subrs = fdDict._subrs; - subrsBias = fdDict._subrsBias; - defaultWidthX = fdDict._defaultWidthX; - nominalWidthX = fdDict._nominalWidthX; - } else { - subrs = font.tables.cff.topDict._subrs; - subrsBias = font.tables.cff.topDict._subrsBias; - defaultWidthX = font.tables.cff.topDict._defaultWidthX; - nominalWidthX = font.tables.cff.topDict._nominalWidthX; - } - var width = defaultWidthX; - - function newContour(x, y) { - if (open) { - p.closePath(); - } - - p.moveTo(x, y); - open = true; - } - - function parseStems() { - var hasWidthArg; - - // The number of stem operators on the stack is always even. - // If the value is uneven, that means a width is specified. - hasWidthArg = stack.length % 2 !== 0; - if (hasWidthArg && !haveWidth) { - width = stack.shift() + nominalWidthX; - } - - nStems += stack.length >> 1; - stack.length = 0; - haveWidth = true; - } - - function parse(code) { - var b1; - var b2; - var b3; - var b4; - var codeIndex; - var subrCode; - var jpx; - var jpy; - var c3x; - var c3y; - var c4x; - var c4y; - - var i = 0; - while (i < code.length) { - var v = code[i]; - i += 1; - switch (v) { - case 1: // hstem - parseStems(); - break; - case 3: // vstem - parseStems(); - break; - case 4: // vmoveto - if (stack.length > 1 && !haveWidth) { - width = stack.shift() + nominalWidthX; - haveWidth = true; - } - - y += stack.pop(); - newContour(x, y); - break; - case 5: // rlineto - while (stack.length > 0) { - x += stack.shift(); - y += stack.shift(); - p.lineTo(x, y); - } - - break; - case 6: // hlineto - while (stack.length > 0) { - x += stack.shift(); - p.lineTo(x, y); - if (stack.length === 0) { - break; - } - - y += stack.shift(); - p.lineTo(x, y); - } - - break; - case 7: // vlineto - while (stack.length > 0) { - y += stack.shift(); - p.lineTo(x, y); - if (stack.length === 0) { - break; - } - - x += stack.shift(); - p.lineTo(x, y); - } - - break; - case 8: // rrcurveto - while (stack.length > 0) { - c1x = x + stack.shift(); - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y + stack.shift(); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } - - break; - case 10: // callsubr - codeIndex = stack.pop() + subrsBias; - subrCode = subrs[codeIndex]; - if (subrCode) { - parse(subrCode); - } - - break; - case 11: // return - return; - case 12: // flex operators - v = code[i]; - i += 1; - switch (v) { - case 35: // flex - // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |- - c1x = x + stack.shift(); // dx1 - c1y = y + stack.shift(); // dy1 - c2x = c1x + stack.shift(); // dx2 - c2y = c1y + stack.shift(); // dy2 - jpx = c2x + stack.shift(); // dx3 - jpy = c2y + stack.shift(); // dy3 - c3x = jpx + stack.shift(); // dx4 - c3y = jpy + stack.shift(); // dy4 - c4x = c3x + stack.shift(); // dx5 - c4y = c3y + stack.shift(); // dy5 - x = c4x + stack.shift(); // dx6 - y = c4y + stack.shift(); // dy6 - stack.shift(); // flex depth - p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); - p.curveTo(c3x, c3y, c4x, c4y, x, y); - break; - case 34: // hflex - // |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |- - c1x = x + stack.shift(); // dx1 - c1y = y; // dy1 - c2x = c1x + stack.shift(); // dx2 - c2y = c1y + stack.shift(); // dy2 - jpx = c2x + stack.shift(); // dx3 - jpy = c2y; // dy3 - c3x = jpx + stack.shift(); // dx4 - c3y = c2y; // dy4 - c4x = c3x + stack.shift(); // dx5 - c4y = y; // dy5 - x = c4x + stack.shift(); // dx6 - p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); - p.curveTo(c3x, c3y, c4x, c4y, x, y); - break; - case 36: // hflex1 - // |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |- - c1x = x + stack.shift(); // dx1 - c1y = y + stack.shift(); // dy1 - c2x = c1x + stack.shift(); // dx2 - c2y = c1y + stack.shift(); // dy2 - jpx = c2x + stack.shift(); // dx3 - jpy = c2y; // dy3 - c3x = jpx + stack.shift(); // dx4 - c3y = c2y; // dy4 - c4x = c3x + stack.shift(); // dx5 - c4y = c3y + stack.shift(); // dy5 - x = c4x + stack.shift(); // dx6 - p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); - p.curveTo(c3x, c3y, c4x, c4y, x, y); - break; - case 37: // flex1 - // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |- - c1x = x + stack.shift(); // dx1 - c1y = y + stack.shift(); // dy1 - c2x = c1x + stack.shift(); // dx2 - c2y = c1y + stack.shift(); // dy2 - jpx = c2x + stack.shift(); // dx3 - jpy = c2y + stack.shift(); // dy3 - c3x = jpx + stack.shift(); // dx4 - c3y = jpy + stack.shift(); // dy4 - c4x = c3x + stack.shift(); // dx5 - c4y = c3y + stack.shift(); // dy5 - if (Math.abs(c4x - x) > Math.abs(c4y - y)) { - x = c4x + stack.shift(); - } else { - y = c4y + stack.shift(); - } - - p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); - p.curveTo(c3x, c3y, c4x, c4y, x, y); - break; - default: - console.log('Glyph ' + glyph.index + ': unknown operator ' + 1200 + v); - stack.length = 0; - } - break; - case 14: // endchar - if (stack.length > 0 && !haveWidth) { - width = stack.shift() + nominalWidthX; - haveWidth = true; - } - - if (open) { - p.closePath(); - open = false; - } - - break; - case 18: // hstemhm - parseStems(); - break; - case 19: // hintmask - case 20: // cntrmask - parseStems(); - i += (nStems + 7) >> 3; - break; - case 21: // rmoveto - if (stack.length > 2 && !haveWidth) { - width = stack.shift() + nominalWidthX; - haveWidth = true; - } - - y += stack.pop(); - x += stack.pop(); - newContour(x, y); - break; - case 22: // hmoveto - if (stack.length > 1 && !haveWidth) { - width = stack.shift() + nominalWidthX; - haveWidth = true; - } - - x += stack.pop(); - newContour(x, y); - break; - case 23: // vstemhm - parseStems(); - break; - case 24: // rcurveline - while (stack.length > 2) { - c1x = x + stack.shift(); - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y + stack.shift(); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } - - x += stack.shift(); - y += stack.shift(); - p.lineTo(x, y); - break; - case 25: // rlinecurve - while (stack.length > 6) { - x += stack.shift(); - y += stack.shift(); - p.lineTo(x, y); - } - - c1x = x + stack.shift(); - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y + stack.shift(); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - break; - case 26: // vvcurveto - if (stack.length % 2) { - x += stack.shift(); - } - - while (stack.length > 0) { - c1x = x; - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x; - y = c2y + stack.shift(); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } - - break; - case 27: // hhcurveto - if (stack.length % 2) { - y += stack.shift(); - } - - while (stack.length > 0) { - c1x = x + stack.shift(); - c1y = y; - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y; - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } - - break; - case 28: // shortint - b1 = code[i]; - b2 = code[i + 1]; - stack.push(((b1 << 24) | (b2 << 16)) >> 16); - i += 2; - break; - case 29: // callgsubr - codeIndex = stack.pop() + font.gsubrsBias; - subrCode = font.gsubrs[codeIndex]; - if (subrCode) { - parse(subrCode); - } - - break; - case 30: // vhcurveto - while (stack.length > 0) { - c1x = x; - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y + (stack.length === 1 ? stack.shift() : 0); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - if (stack.length === 0) { - break; - } - - c1x = x + stack.shift(); - c1y = y; - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - y = c2y + stack.shift(); - x = c2x + (stack.length === 1 ? stack.shift() : 0); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } - - break; - case 31: // hvcurveto - while (stack.length > 0) { - c1x = x + stack.shift(); - c1y = y; - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - y = c2y + stack.shift(); - x = c2x + (stack.length === 1 ? stack.shift() : 0); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - if (stack.length === 0) { - break; - } - - c1x = x; - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y + (stack.length === 1 ? stack.shift() : 0); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } - - break; - default: - if (v < 32) { - console.log('Glyph ' + glyph.index + ': unknown operator ' + v); - } else if (v < 247) { - stack.push(v - 139); - } else if (v < 251) { - b1 = code[i]; - i += 1; - stack.push((v - 247) * 256 + b1 + 108); - } else if (v < 255) { - b1 = code[i]; - i += 1; - stack.push(-(v - 251) * 256 - b1 - 108); - } else { - b1 = code[i]; - b2 = code[i + 1]; - b3 = code[i + 2]; - b4 = code[i + 3]; - i += 4; - stack.push(((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) / 65536); - } - } - } - } - - parse(code); - - glyph.advanceWidth = width; - return p; - } - - function parseCFFFDSelect(data, start, nGlyphs, fdArrayCount) { - var fdSelect = []; - var fdIndex; - var parser = new parse.Parser(data, start); - var format = parser.parseCard8(); - if (format === 0) { - // Simple list of nGlyphs elements - for (var iGid = 0; iGid < nGlyphs; iGid++) { - fdIndex = parser.parseCard8(); - if (fdIndex >= fdArrayCount) { - throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); - } - fdSelect.push(fdIndex); - } - } else if (format === 3) { - // Ranges - var nRanges = parser.parseCard16(); - var first = parser.parseCard16(); - if (first !== 0) { - throw new Error('CFF Table CID Font FDSelect format 3 range has bad initial GID ' + first); - } - var next; - for (var iRange = 0; iRange < nRanges; iRange++) { - fdIndex = parser.parseCard8(); - next = parser.parseCard16(); - if (fdIndex >= fdArrayCount) { - throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); - } - if (next > nGlyphs) { - throw new Error('CFF Table CID Font FDSelect format 3 range has bad GID ' + next); - } - for (; first < next; first++) { - fdSelect.push(fdIndex); - } - first = next; - } - if (next !== nGlyphs) { - throw new Error('CFF Table CID Font FDSelect format 3 range has bad final GID ' + next); - } - } else { - throw new Error('CFF Table CID Font FDSelect table has unsupported format ' + format); - } - return fdSelect; - } - - // Parse the `CFF` table, which contains the glyph outlines in PostScript format. - function parseCFFTable(data, start, font, opt) { - font.tables.cff = {}; - var header = parseCFFHeader(data, start); - var nameIndex = parseCFFIndex(data, header.endOffset, parse.bytesToString); - var topDictIndex = parseCFFIndex(data, nameIndex.endOffset); - var stringIndex = parseCFFIndex(data, topDictIndex.endOffset, parse.bytesToString); - var globalSubrIndex = parseCFFIndex(data, stringIndex.endOffset); - font.gsubrs = globalSubrIndex.objects; - font.gsubrsBias = calcCFFSubroutineBias(font.gsubrs); - - var topDictArray = gatherCFFTopDicts(data, start, topDictIndex.objects, stringIndex.objects); - if (topDictArray.length !== 1) { - throw new Error('CFF table has too many fonts in \'FontSet\' - count of fonts NameIndex.length = ' + topDictArray.length); - } - - var topDict = topDictArray[0]; - font.tables.cff.topDict = topDict; - - if (topDict._privateDict) { - font.defaultWidthX = topDict._privateDict.defaultWidthX; - font.nominalWidthX = topDict._privateDict.nominalWidthX; - } - - if (topDict.ros[0] !== undefined && topDict.ros[1] !== undefined) { - font.isCIDFont = true; - } - - if (font.isCIDFont) { - var fdArrayOffset = topDict.fdArray; - var fdSelectOffset = topDict.fdSelect; - if (fdArrayOffset === 0 || fdSelectOffset === 0) { - throw new Error('Font is marked as a CID font, but FDArray and/or FDSelect information is missing'); - } - fdArrayOffset += start; - var fdArrayIndex = parseCFFIndex(data, fdArrayOffset); - var fdArray = gatherCFFTopDicts(data, start, fdArrayIndex.objects, stringIndex.objects); - topDict._fdArray = fdArray; - fdSelectOffset += start; - topDict._fdSelect = parseCFFFDSelect(data, fdSelectOffset, font.numGlyphs, fdArray.length); - } - - var privateDictOffset = start + topDict.private[1]; - var privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict.private[0], stringIndex.objects); - font.defaultWidthX = privateDict.defaultWidthX; - font.nominalWidthX = privateDict.nominalWidthX; - - if (privateDict.subrs !== 0) { - var subrOffset = privateDictOffset + privateDict.subrs; - var subrIndex = parseCFFIndex(data, subrOffset); - font.subrs = subrIndex.objects; - font.subrsBias = calcCFFSubroutineBias(font.subrs); - } else { - font.subrs = []; - font.subrsBias = 0; - } - - // Offsets in the top dict are relative to the beginning of the CFF data, so add the CFF start offset. - var charStringsIndex; - if (opt.lowMemory) { - charStringsIndex = parseCFFIndexLowMemory(data, start + topDict.charStrings); - font.nGlyphs = charStringsIndex.offsets.length; - } else { - charStringsIndex = parseCFFIndex(data, start + topDict.charStrings); - font.nGlyphs = charStringsIndex.objects.length; - } - - var charset = parseCFFCharset(data, start + topDict.charset, font.nGlyphs, stringIndex.objects); - if (topDict.encoding === 0) { - // Standard encoding - font.cffEncoding = new CffEncoding(cffStandardEncoding, charset); - } else if (topDict.encoding === 1) { - // Expert encoding - font.cffEncoding = new CffEncoding(cffExpertEncoding, charset); - } else { - font.cffEncoding = parseCFFEncoding(data, start + topDict.encoding, charset); - } - - // Prefer the CMAP encoding to the CFF encoding. - font.encoding = font.encoding || font.cffEncoding; - - font.glyphs = new glyphset.GlyphSet(font); - if (opt.lowMemory) { - font._push = function(i) { - var charString = getCffIndexObject(i, charStringsIndex.offsets, data, start + topDict.charStrings); - font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); - }; - } else { - for (var i = 0; i < font.nGlyphs; i += 1) { - var charString = charStringsIndex.objects[i]; - font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); - } - } - } - - // Convert a string to a String ID (SID). - // The list of strings is modified in place. - function encodeString(s, strings) { - var sid; - - // Is the string in the CFF standard strings? - var i = cffStandardStrings.indexOf(s); - if (i >= 0) { - sid = i; - } - - // Is the string already in the string index? - i = strings.indexOf(s); - if (i >= 0) { - sid = i + cffStandardStrings.length; - } else { - sid = cffStandardStrings.length + strings.length; - strings.push(s); - } - - return sid; - } - - function makeHeader() { - return new table.Record('Header', [ - {name: 'major', type: 'Card8', value: 1}, - {name: 'minor', type: 'Card8', value: 0}, - {name: 'hdrSize', type: 'Card8', value: 4}, - {name: 'major', type: 'Card8', value: 1} - ]); - } - - function makeNameIndex(fontNames) { - var t = new table.Record('Name INDEX', [ - {name: 'names', type: 'INDEX', value: []} - ]); - t.names = []; - for (var i = 0; i < fontNames.length; i += 1) { - t.names.push({name: 'name_' + i, type: 'NAME', value: fontNames[i]}); - } - - return t; - } - - // Given a dictionary's metadata, create a DICT structure. - function makeDict(meta, attrs, strings) { - var m = {}; - for (var i = 0; i < meta.length; i += 1) { - var entry = meta[i]; - var value = attrs[entry.name]; - if (value !== undefined && !equals(value, entry.value)) { - if (entry.type === 'SID') { - value = encodeString(value, strings); - } - - m[entry.op] = {name: entry.name, type: entry.type, value: value}; - } - } - - return m; - } - - // The Top DICT houses the global font attributes. - function makeTopDict(attrs, strings) { - var t = new table.Record('Top DICT', [ - {name: 'dict', type: 'DICT', value: {}} - ]); - t.dict = makeDict(TOP_DICT_META, attrs, strings); - return t; - } - - function makeTopDictIndex(topDict) { - var t = new table.Record('Top DICT INDEX', [ - {name: 'topDicts', type: 'INDEX', value: []} - ]); - t.topDicts = [{name: 'topDict_0', type: 'TABLE', value: topDict}]; - return t; - } - - function makeStringIndex(strings) { - var t = new table.Record('String INDEX', [ - {name: 'strings', type: 'INDEX', value: []} - ]); - t.strings = []; - for (var i = 0; i < strings.length; i += 1) { - t.strings.push({name: 'string_' + i, type: 'STRING', value: strings[i]}); - } - - return t; - } - - function makeGlobalSubrIndex() { - // Currently we don't use subroutines. - return new table.Record('Global Subr INDEX', [ - {name: 'subrs', type: 'INDEX', value: []} - ]); - } - - function makeCharsets(glyphNames, strings) { - var t = new table.Record('Charsets', [ - {name: 'format', type: 'Card8', value: 0} - ]); - for (var i = 0; i < glyphNames.length; i += 1) { - var glyphName = glyphNames[i]; - var glyphSID = encodeString(glyphName, strings); - t.fields.push({name: 'glyph_' + i, type: 'SID', value: glyphSID}); - } - - return t; - } - - function glyphToOps(glyph) { - var ops = []; - var path = glyph.path; - ops.push({name: 'width', type: 'NUMBER', value: glyph.advanceWidth}); - var x = 0; - var y = 0; - for (var i = 0; i < path.commands.length; i += 1) { - var dx = (void 0); - var dy = (void 0); - var cmd = path.commands[i]; - if (cmd.type === 'Q') { - // CFF only supports bézier curves, so convert the quad to a bézier. - var _13 = 1 / 3; - var _23 = 2 / 3; - - // We're going to create a new command so we don't change the original path. - cmd = { - type: 'C', - x: cmd.x, - y: cmd.y, - x1: _13 * x + _23 * cmd.x1, - y1: _13 * y + _23 * cmd.y1, - x2: _13 * cmd.x + _23 * cmd.x1, - y2: _13 * cmd.y + _23 * cmd.y1 - }; - } - - if (cmd.type === 'M') { - dx = Math.round(cmd.x - x); - dy = Math.round(cmd.y - y); - ops.push({name: 'dx', type: 'NUMBER', value: dx}); - ops.push({name: 'dy', type: 'NUMBER', value: dy}); - ops.push({name: 'rmoveto', type: 'OP', value: 21}); - x = Math.round(cmd.x); - y = Math.round(cmd.y); - } else if (cmd.type === 'L') { - dx = Math.round(cmd.x - x); - dy = Math.round(cmd.y - y); - ops.push({name: 'dx', type: 'NUMBER', value: dx}); - ops.push({name: 'dy', type: 'NUMBER', value: dy}); - ops.push({name: 'rlineto', type: 'OP', value: 5}); - x = Math.round(cmd.x); - y = Math.round(cmd.y); - } else if (cmd.type === 'C') { - var dx1 = Math.round(cmd.x1 - x); - var dy1 = Math.round(cmd.y1 - y); - var dx2 = Math.round(cmd.x2 - cmd.x1); - var dy2 = Math.round(cmd.y2 - cmd.y1); - dx = Math.round(cmd.x - cmd.x2); - dy = Math.round(cmd.y - cmd.y2); - ops.push({name: 'dx1', type: 'NUMBER', value: dx1}); - ops.push({name: 'dy1', type: 'NUMBER', value: dy1}); - ops.push({name: 'dx2', type: 'NUMBER', value: dx2}); - ops.push({name: 'dy2', type: 'NUMBER', value: dy2}); - ops.push({name: 'dx', type: 'NUMBER', value: dx}); - ops.push({name: 'dy', type: 'NUMBER', value: dy}); - ops.push({name: 'rrcurveto', type: 'OP', value: 8}); - x = Math.round(cmd.x); - y = Math.round(cmd.y); - } - - // Contours are closed automatically. - } - - ops.push({name: 'endchar', type: 'OP', value: 14}); - return ops; - } - - function makeCharStringsIndex(glyphs) { - var t = new table.Record('CharStrings INDEX', [ - {name: 'charStrings', type: 'INDEX', value: []} - ]); - - for (var i = 0; i < glyphs.length; i += 1) { - var glyph = glyphs.get(i); - var ops = glyphToOps(glyph); - t.charStrings.push({name: glyph.name, type: 'CHARSTRING', value: ops}); - } - - return t; - } - - function makePrivateDict(attrs, strings) { - var t = new table.Record('Private DICT', [ - {name: 'dict', type: 'DICT', value: {}} - ]); - t.dict = makeDict(PRIVATE_DICT_META, attrs, strings); - return t; - } - - function makeCFFTable(glyphs, options) { - var t = new table.Table('CFF ', [ - {name: 'header', type: 'RECORD'}, - {name: 'nameIndex', type: 'RECORD'}, - {name: 'topDictIndex', type: 'RECORD'}, - {name: 'stringIndex', type: 'RECORD'}, - {name: 'globalSubrIndex', type: 'RECORD'}, - {name: 'charsets', type: 'RECORD'}, - {name: 'charStringsIndex', type: 'RECORD'}, - {name: 'privateDict', type: 'RECORD'} - ]); - - var fontScale = 1 / options.unitsPerEm; - // We use non-zero values for the offsets so that the DICT encodes them. - // This is important because the size of the Top DICT plays a role in offset calculation, - // and the size shouldn't change after we've written correct offsets. - var attrs = { - version: options.version, - fullName: options.fullName, - familyName: options.familyName, - weight: options.weightName, - fontBBox: options.fontBBox || [0, 0, 0, 0], - fontMatrix: [fontScale, 0, 0, fontScale, 0, 0], - charset: 999, - encoding: 0, - charStrings: 999, - private: [0, 999] - }; - - var privateAttrs = {}; - - var glyphNames = []; - var glyph; - - // Skip first glyph (.notdef) - for (var i = 1; i < glyphs.length; i += 1) { - glyph = glyphs.get(i); - glyphNames.push(glyph.name); - } - - var strings = []; - - t.header = makeHeader(); - t.nameIndex = makeNameIndex([options.postScriptName]); - var topDict = makeTopDict(attrs, strings); - t.topDictIndex = makeTopDictIndex(topDict); - t.globalSubrIndex = makeGlobalSubrIndex(); - t.charsets = makeCharsets(glyphNames, strings); - t.charStringsIndex = makeCharStringsIndex(glyphs); - t.privateDict = makePrivateDict(privateAttrs, strings); - - // Needs to come at the end, to encode all custom strings used in the font. - t.stringIndex = makeStringIndex(strings); - - var startOffset = t.header.sizeOf() + - t.nameIndex.sizeOf() + - t.topDictIndex.sizeOf() + - t.stringIndex.sizeOf() + - t.globalSubrIndex.sizeOf(); - attrs.charset = startOffset; - - // We use the CFF standard encoding; proper encoding will be handled in cmap. - attrs.encoding = 0; - attrs.charStrings = attrs.charset + t.charsets.sizeOf(); - attrs.private[1] = attrs.charStrings + t.charStringsIndex.sizeOf(); - - // Recreate the Top DICT INDEX with the correct offsets. - topDict = makeTopDict(attrs, strings); - t.topDictIndex = makeTopDictIndex(topDict); - - return t; - } - - var cff = { parse: parseCFFTable, make: makeCFFTable }; - - // The `head` table contains global information about the font. - - // Parse the header `head` table - function parseHeadTable(data, start) { - var head = {}; - var p = new parse.Parser(data, start); - head.version = p.parseVersion(); - head.fontRevision = Math.round(p.parseFixed() * 1000) / 1000; - head.checkSumAdjustment = p.parseULong(); - head.magicNumber = p.parseULong(); - check.argument(head.magicNumber === 0x5F0F3CF5, 'Font header has wrong magic number.'); - head.flags = p.parseUShort(); - head.unitsPerEm = p.parseUShort(); - head.created = p.parseLongDateTime(); - head.modified = p.parseLongDateTime(); - head.xMin = p.parseShort(); - head.yMin = p.parseShort(); - head.xMax = p.parseShort(); - head.yMax = p.parseShort(); - head.macStyle = p.parseUShort(); - head.lowestRecPPEM = p.parseUShort(); - head.fontDirectionHint = p.parseShort(); - head.indexToLocFormat = p.parseShort(); - head.glyphDataFormat = p.parseShort(); - return head; - } - - function makeHeadTable(options) { - // Apple Mac timestamp epoch is 01/01/1904 not 01/01/1970 - var timestamp = Math.round(new Date().getTime() / 1000) + 2082844800; - var createdTimestamp = timestamp; - - if (options.createdTimestamp) { - createdTimestamp = options.createdTimestamp + 2082844800; - } - - return new table.Table('head', [ - {name: 'version', type: 'FIXED', value: 0x00010000}, - {name: 'fontRevision', type: 'FIXED', value: 0x00010000}, - {name: 'checkSumAdjustment', type: 'ULONG', value: 0}, - {name: 'magicNumber', type: 'ULONG', value: 0x5F0F3CF5}, - {name: 'flags', type: 'USHORT', value: 0}, - {name: 'unitsPerEm', type: 'USHORT', value: 1000}, - {name: 'created', type: 'LONGDATETIME', value: createdTimestamp}, - {name: 'modified', type: 'LONGDATETIME', value: timestamp}, - {name: 'xMin', type: 'SHORT', value: 0}, - {name: 'yMin', type: 'SHORT', value: 0}, - {name: 'xMax', type: 'SHORT', value: 0}, - {name: 'yMax', type: 'SHORT', value: 0}, - {name: 'macStyle', type: 'USHORT', value: 0}, - {name: 'lowestRecPPEM', type: 'USHORT', value: 0}, - {name: 'fontDirectionHint', type: 'SHORT', value: 2}, - {name: 'indexToLocFormat', type: 'SHORT', value: 0}, - {name: 'glyphDataFormat', type: 'SHORT', value: 0} - ], options); - } - - var head = { parse: parseHeadTable, make: makeHeadTable }; - - // The `hhea` table contains information for horizontal layout. - - // Parse the horizontal header `hhea` table - function parseHheaTable(data, start) { - var hhea = {}; - var p = new parse.Parser(data, start); - hhea.version = p.parseVersion(); - hhea.ascender = p.parseShort(); - hhea.descender = p.parseShort(); - hhea.lineGap = p.parseShort(); - hhea.advanceWidthMax = p.parseUShort(); - hhea.minLeftSideBearing = p.parseShort(); - hhea.minRightSideBearing = p.parseShort(); - hhea.xMaxExtent = p.parseShort(); - hhea.caretSlopeRise = p.parseShort(); - hhea.caretSlopeRun = p.parseShort(); - hhea.caretOffset = p.parseShort(); - p.relativeOffset += 8; - hhea.metricDataFormat = p.parseShort(); - hhea.numberOfHMetrics = p.parseUShort(); - return hhea; - } - - function makeHheaTable(options) { - return new table.Table('hhea', [ - {name: 'version', type: 'FIXED', value: 0x00010000}, - {name: 'ascender', type: 'FWORD', value: 0}, - {name: 'descender', type: 'FWORD', value: 0}, - {name: 'lineGap', type: 'FWORD', value: 0}, - {name: 'advanceWidthMax', type: 'UFWORD', value: 0}, - {name: 'minLeftSideBearing', type: 'FWORD', value: 0}, - {name: 'minRightSideBearing', type: 'FWORD', value: 0}, - {name: 'xMaxExtent', type: 'FWORD', value: 0}, - {name: 'caretSlopeRise', type: 'SHORT', value: 1}, - {name: 'caretSlopeRun', type: 'SHORT', value: 0}, - {name: 'caretOffset', type: 'SHORT', value: 0}, - {name: 'reserved1', type: 'SHORT', value: 0}, - {name: 'reserved2', type: 'SHORT', value: 0}, - {name: 'reserved3', type: 'SHORT', value: 0}, - {name: 'reserved4', type: 'SHORT', value: 0}, - {name: 'metricDataFormat', type: 'SHORT', value: 0}, - {name: 'numberOfHMetrics', type: 'USHORT', value: 0} - ], options); - } - - var hhea = { parse: parseHheaTable, make: makeHheaTable }; - - // The `hmtx` table contains the horizontal metrics for all glyphs. - - function parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs) { - var advanceWidth; - var leftSideBearing; - var p = new parse.Parser(data, start); - for (var i = 0; i < numGlyphs; i += 1) { - // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. - if (i < numMetrics) { - advanceWidth = p.parseUShort(); - leftSideBearing = p.parseShort(); - } - - var glyph = glyphs.get(i); - glyph.advanceWidth = advanceWidth; - glyph.leftSideBearing = leftSideBearing; - } - } - - function parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs) { - font._hmtxTableData = {}; - - var advanceWidth; - var leftSideBearing; - var p = new parse.Parser(data, start); - for (var i = 0; i < numGlyphs; i += 1) { - // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. - if (i < numMetrics) { - advanceWidth = p.parseUShort(); - leftSideBearing = p.parseShort(); - } - - font._hmtxTableData[i] = { - advanceWidth: advanceWidth, - leftSideBearing: leftSideBearing - }; - } - } - - // Parse the `hmtx` table, which contains the horizontal metrics for all glyphs. - // This function augments the glyph array, adding the advanceWidth and leftSideBearing to each glyph. - function parseHmtxTable(font, data, start, numMetrics, numGlyphs, glyphs, opt) { - if (opt.lowMemory) - { parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs); } - else - { parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs); } - } - - function makeHmtxTable(glyphs) { - var t = new table.Table('hmtx', []); - for (var i = 0; i < glyphs.length; i += 1) { - var glyph = glyphs.get(i); - var advanceWidth = glyph.advanceWidth || 0; - var leftSideBearing = glyph.leftSideBearing || 0; - t.fields.push({name: 'advanceWidth_' + i, type: 'USHORT', value: advanceWidth}); - t.fields.push({name: 'leftSideBearing_' + i, type: 'SHORT', value: leftSideBearing}); - } - - return t; - } - - var hmtx = { parse: parseHmtxTable, make: makeHmtxTable }; - - // The `ltag` table stores IETF BCP-47 language tags. It allows supporting - - function makeLtagTable(tags) { - var result = new table.Table('ltag', [ - {name: 'version', type: 'ULONG', value: 1}, - {name: 'flags', type: 'ULONG', value: 0}, - {name: 'numTags', type: 'ULONG', value: tags.length} - ]); - - var stringPool = ''; - var stringPoolOffset = 12 + tags.length * 4; - for (var i = 0; i < tags.length; ++i) { - var pos = stringPool.indexOf(tags[i]); - if (pos < 0) { - pos = stringPool.length; - stringPool += tags[i]; - } - - result.fields.push({name: 'offset ' + i, type: 'USHORT', value: stringPoolOffset + pos}); - result.fields.push({name: 'length ' + i, type: 'USHORT', value: tags[i].length}); - } - - result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); - return result; - } - - function parseLtagTable(data, start) { - var p = new parse.Parser(data, start); - var tableVersion = p.parseULong(); - check.argument(tableVersion === 1, 'Unsupported ltag table version.'); - // The 'ltag' specification does not define any flags; skip the field. - p.skip('uLong', 1); - var numTags = p.parseULong(); - - var tags = []; - for (var i = 0; i < numTags; i++) { - var tag = ''; - var offset = start + p.parseUShort(); - var length = p.parseUShort(); - for (var j = offset; j < offset + length; ++j) { - tag += String.fromCharCode(data.getInt8(j)); - } - - tags.push(tag); - } - - return tags; - } - - var ltag = { make: makeLtagTable, parse: parseLtagTable }; - - // The `maxp` table establishes the memory requirements for the font. - - // Parse the maximum profile `maxp` table. - function parseMaxpTable(data, start) { - var maxp = {}; - var p = new parse.Parser(data, start); - maxp.version = p.parseVersion(); - maxp.numGlyphs = p.parseUShort(); - if (maxp.version === 1.0) { - maxp.maxPoints = p.parseUShort(); - maxp.maxContours = p.parseUShort(); - maxp.maxCompositePoints = p.parseUShort(); - maxp.maxCompositeContours = p.parseUShort(); - maxp.maxZones = p.parseUShort(); - maxp.maxTwilightPoints = p.parseUShort(); - maxp.maxStorage = p.parseUShort(); - maxp.maxFunctionDefs = p.parseUShort(); - maxp.maxInstructionDefs = p.parseUShort(); - maxp.maxStackElements = p.parseUShort(); - maxp.maxSizeOfInstructions = p.parseUShort(); - maxp.maxComponentElements = p.parseUShort(); - maxp.maxComponentDepth = p.parseUShort(); - } - - return maxp; - } - - function makeMaxpTable(numGlyphs) { - return new table.Table('maxp', [ - {name: 'version', type: 'FIXED', value: 0x00005000}, - {name: 'numGlyphs', type: 'USHORT', value: numGlyphs} - ]); - } - - var maxp = { parse: parseMaxpTable, make: makeMaxpTable }; - - // The `name` naming table. - - // NameIDs for the name table. - var nameTableNames = [ - 'copyright', // 0 - 'fontFamily', // 1 - 'fontSubfamily', // 2 - 'uniqueID', // 3 - 'fullName', // 4 - 'version', // 5 - 'postScriptName', // 6 - 'trademark', // 7 - 'manufacturer', // 8 - 'designer', // 9 - 'description', // 10 - 'manufacturerURL', // 11 - 'designerURL', // 12 - 'license', // 13 - 'licenseURL', // 14 - 'reserved', // 15 - 'preferredFamily', // 16 - 'preferredSubfamily', // 17 - 'compatibleFullName', // 18 - 'sampleText', // 19 - 'postScriptFindFontName', // 20 - 'wwsFamily', // 21 - 'wwsSubfamily' // 22 - ]; - - var macLanguages = { - 0: 'en', - 1: 'fr', - 2: 'de', - 3: 'it', - 4: 'nl', - 5: 'sv', - 6: 'es', - 7: 'da', - 8: 'pt', - 9: 'no', - 10: 'he', - 11: 'ja', - 12: 'ar', - 13: 'fi', - 14: 'el', - 15: 'is', - 16: 'mt', - 17: 'tr', - 18: 'hr', - 19: 'zh-Hant', - 20: 'ur', - 21: 'hi', - 22: 'th', - 23: 'ko', - 24: 'lt', - 25: 'pl', - 26: 'hu', - 27: 'es', - 28: 'lv', - 29: 'se', - 30: 'fo', - 31: 'fa', - 32: 'ru', - 33: 'zh', - 34: 'nl-BE', - 35: 'ga', - 36: 'sq', - 37: 'ro', - 38: 'cz', - 39: 'sk', - 40: 'si', - 41: 'yi', - 42: 'sr', - 43: 'mk', - 44: 'bg', - 45: 'uk', - 46: 'be', - 47: 'uz', - 48: 'kk', - 49: 'az-Cyrl', - 50: 'az-Arab', - 51: 'hy', - 52: 'ka', - 53: 'mo', - 54: 'ky', - 55: 'tg', - 56: 'tk', - 57: 'mn-CN', - 58: 'mn', - 59: 'ps', - 60: 'ks', - 61: 'ku', - 62: 'sd', - 63: 'bo', - 64: 'ne', - 65: 'sa', - 66: 'mr', - 67: 'bn', - 68: 'as', - 69: 'gu', - 70: 'pa', - 71: 'or', - 72: 'ml', - 73: 'kn', - 74: 'ta', - 75: 'te', - 76: 'si', - 77: 'my', - 78: 'km', - 79: 'lo', - 80: 'vi', - 81: 'id', - 82: 'tl', - 83: 'ms', - 84: 'ms-Arab', - 85: 'am', - 86: 'ti', - 87: 'om', - 88: 'so', - 89: 'sw', - 90: 'rw', - 91: 'rn', - 92: 'ny', - 93: 'mg', - 94: 'eo', - 128: 'cy', - 129: 'eu', - 130: 'ca', - 131: 'la', - 132: 'qu', - 133: 'gn', - 134: 'ay', - 135: 'tt', - 136: 'ug', - 137: 'dz', - 138: 'jv', - 139: 'su', - 140: 'gl', - 141: 'af', - 142: 'br', - 143: 'iu', - 144: 'gd', - 145: 'gv', - 146: 'ga', - 147: 'to', - 148: 'el-polyton', - 149: 'kl', - 150: 'az', - 151: 'nn' - }; - - // MacOS language ID → MacOS script ID - // - // Note that the script ID is not sufficient to determine what encoding - // to use in TrueType files. For some languages, MacOS used a modification - // of a mainstream script. For example, an Icelandic name would be stored - // with smRoman in the TrueType naming table, but the actual encoding - // is a special Icelandic version of the normal Macintosh Roman encoding. - // As another example, Inuktitut uses an 8-bit encoding for Canadian Aboriginal - // Syllables but MacOS had run out of available script codes, so this was - // done as a (pretty radical) "modification" of Ethiopic. - // - // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt - var macLanguageToScript = { - 0: 0, // langEnglish → smRoman - 1: 0, // langFrench → smRoman - 2: 0, // langGerman → smRoman - 3: 0, // langItalian → smRoman - 4: 0, // langDutch → smRoman - 5: 0, // langSwedish → smRoman - 6: 0, // langSpanish → smRoman - 7: 0, // langDanish → smRoman - 8: 0, // langPortuguese → smRoman - 9: 0, // langNorwegian → smRoman - 10: 5, // langHebrew → smHebrew - 11: 1, // langJapanese → smJapanese - 12: 4, // langArabic → smArabic - 13: 0, // langFinnish → smRoman - 14: 6, // langGreek → smGreek - 15: 0, // langIcelandic → smRoman (modified) - 16: 0, // langMaltese → smRoman - 17: 0, // langTurkish → smRoman (modified) - 18: 0, // langCroatian → smRoman (modified) - 19: 2, // langTradChinese → smTradChinese - 20: 4, // langUrdu → smArabic - 21: 9, // langHindi → smDevanagari - 22: 21, // langThai → smThai - 23: 3, // langKorean → smKorean - 24: 29, // langLithuanian → smCentralEuroRoman - 25: 29, // langPolish → smCentralEuroRoman - 26: 29, // langHungarian → smCentralEuroRoman - 27: 29, // langEstonian → smCentralEuroRoman - 28: 29, // langLatvian → smCentralEuroRoman - 29: 0, // langSami → smRoman - 30: 0, // langFaroese → smRoman (modified) - 31: 4, // langFarsi → smArabic (modified) - 32: 7, // langRussian → smCyrillic - 33: 25, // langSimpChinese → smSimpChinese - 34: 0, // langFlemish → smRoman - 35: 0, // langIrishGaelic → smRoman (modified) - 36: 0, // langAlbanian → smRoman - 37: 0, // langRomanian → smRoman (modified) - 38: 29, // langCzech → smCentralEuroRoman - 39: 29, // langSlovak → smCentralEuroRoman - 40: 0, // langSlovenian → smRoman (modified) - 41: 5, // langYiddish → smHebrew - 42: 7, // langSerbian → smCyrillic - 43: 7, // langMacedonian → smCyrillic - 44: 7, // langBulgarian → smCyrillic - 45: 7, // langUkrainian → smCyrillic (modified) - 46: 7, // langByelorussian → smCyrillic - 47: 7, // langUzbek → smCyrillic - 48: 7, // langKazakh → smCyrillic - 49: 7, // langAzerbaijani → smCyrillic - 50: 4, // langAzerbaijanAr → smArabic - 51: 24, // langArmenian → smArmenian - 52: 23, // langGeorgian → smGeorgian - 53: 7, // langMoldavian → smCyrillic - 54: 7, // langKirghiz → smCyrillic - 55: 7, // langTajiki → smCyrillic - 56: 7, // langTurkmen → smCyrillic - 57: 27, // langMongolian → smMongolian - 58: 7, // langMongolianCyr → smCyrillic - 59: 4, // langPashto → smArabic - 60: 4, // langKurdish → smArabic - 61: 4, // langKashmiri → smArabic - 62: 4, // langSindhi → smArabic - 63: 26, // langTibetan → smTibetan - 64: 9, // langNepali → smDevanagari - 65: 9, // langSanskrit → smDevanagari - 66: 9, // langMarathi → smDevanagari - 67: 13, // langBengali → smBengali - 68: 13, // langAssamese → smBengali - 69: 11, // langGujarati → smGujarati - 70: 10, // langPunjabi → smGurmukhi - 71: 12, // langOriya → smOriya - 72: 17, // langMalayalam → smMalayalam - 73: 16, // langKannada → smKannada - 74: 14, // langTamil → smTamil - 75: 15, // langTelugu → smTelugu - 76: 18, // langSinhalese → smSinhalese - 77: 19, // langBurmese → smBurmese - 78: 20, // langKhmer → smKhmer - 79: 22, // langLao → smLao - 80: 30, // langVietnamese → smVietnamese - 81: 0, // langIndonesian → smRoman - 82: 0, // langTagalog → smRoman - 83: 0, // langMalayRoman → smRoman - 84: 4, // langMalayArabic → smArabic - 85: 28, // langAmharic → smEthiopic - 86: 28, // langTigrinya → smEthiopic - 87: 28, // langOromo → smEthiopic - 88: 0, // langSomali → smRoman - 89: 0, // langSwahili → smRoman - 90: 0, // langKinyarwanda → smRoman - 91: 0, // langRundi → smRoman - 92: 0, // langNyanja → smRoman - 93: 0, // langMalagasy → smRoman - 94: 0, // langEsperanto → smRoman - 128: 0, // langWelsh → smRoman (modified) - 129: 0, // langBasque → smRoman - 130: 0, // langCatalan → smRoman - 131: 0, // langLatin → smRoman - 132: 0, // langQuechua → smRoman - 133: 0, // langGuarani → smRoman - 134: 0, // langAymara → smRoman - 135: 7, // langTatar → smCyrillic - 136: 4, // langUighur → smArabic - 137: 26, // langDzongkha → smTibetan - 138: 0, // langJavaneseRom → smRoman - 139: 0, // langSundaneseRom → smRoman - 140: 0, // langGalician → smRoman - 141: 0, // langAfrikaans → smRoman - 142: 0, // langBreton → smRoman (modified) - 143: 28, // langInuktitut → smEthiopic (modified) - 144: 0, // langScottishGaelic → smRoman (modified) - 145: 0, // langManxGaelic → smRoman (modified) - 146: 0, // langIrishGaelicScript → smRoman (modified) - 147: 0, // langTongan → smRoman - 148: 6, // langGreekAncient → smRoman - 149: 0, // langGreenlandic → smRoman - 150: 0, // langAzerbaijanRoman → smRoman - 151: 0 // langNynorsk → smRoman - }; - - // While Microsoft indicates a region/country for all its language - // IDs, we omit the region code if it's equal to the "most likely - // region subtag" according to Unicode CLDR. For scripts, we omit - // the subtag if it is equal to the Suppress-Script entry in the - // IANA language subtag registry for IETF BCP 47. - // - // For example, Microsoft states that its language code 0x041A is - // Croatian in Croatia. We transform this to the BCP 47 language code 'hr' - // and not 'hr-HR' because Croatia is the default country for Croatian, - // according to Unicode CLDR. As another example, Microsoft states - // that 0x101A is Croatian (Latin) in Bosnia-Herzegovina. We transform - // this to 'hr-BA' and not 'hr-Latn-BA' because Latin is the default script - // for the Croatian language, according to IANA. - // - // http://www.unicode.org/cldr/charts/latest/supplemental/likely_subtags.html - // http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry - var windowsLanguages = { - 0x0436: 'af', - 0x041C: 'sq', - 0x0484: 'gsw', - 0x045E: 'am', - 0x1401: 'ar-DZ', - 0x3C01: 'ar-BH', - 0x0C01: 'ar', - 0x0801: 'ar-IQ', - 0x2C01: 'ar-JO', - 0x3401: 'ar-KW', - 0x3001: 'ar-LB', - 0x1001: 'ar-LY', - 0x1801: 'ary', - 0x2001: 'ar-OM', - 0x4001: 'ar-QA', - 0x0401: 'ar-SA', - 0x2801: 'ar-SY', - 0x1C01: 'aeb', - 0x3801: 'ar-AE', - 0x2401: 'ar-YE', - 0x042B: 'hy', - 0x044D: 'as', - 0x082C: 'az-Cyrl', - 0x042C: 'az', - 0x046D: 'ba', - 0x042D: 'eu', - 0x0423: 'be', - 0x0845: 'bn', - 0x0445: 'bn-IN', - 0x201A: 'bs-Cyrl', - 0x141A: 'bs', - 0x047E: 'br', - 0x0402: 'bg', - 0x0403: 'ca', - 0x0C04: 'zh-HK', - 0x1404: 'zh-MO', - 0x0804: 'zh', - 0x1004: 'zh-SG', - 0x0404: 'zh-TW', - 0x0483: 'co', - 0x041A: 'hr', - 0x101A: 'hr-BA', - 0x0405: 'cs', - 0x0406: 'da', - 0x048C: 'prs', - 0x0465: 'dv', - 0x0813: 'nl-BE', - 0x0413: 'nl', - 0x0C09: 'en-AU', - 0x2809: 'en-BZ', - 0x1009: 'en-CA', - 0x2409: 'en-029', - 0x4009: 'en-IN', - 0x1809: 'en-IE', - 0x2009: 'en-JM', - 0x4409: 'en-MY', - 0x1409: 'en-NZ', - 0x3409: 'en-PH', - 0x4809: 'en-SG', - 0x1C09: 'en-ZA', - 0x2C09: 'en-TT', - 0x0809: 'en-GB', - 0x0409: 'en', - 0x3009: 'en-ZW', - 0x0425: 'et', - 0x0438: 'fo', - 0x0464: 'fil', - 0x040B: 'fi', - 0x080C: 'fr-BE', - 0x0C0C: 'fr-CA', - 0x040C: 'fr', - 0x140C: 'fr-LU', - 0x180C: 'fr-MC', - 0x100C: 'fr-CH', - 0x0462: 'fy', - 0x0456: 'gl', - 0x0437: 'ka', - 0x0C07: 'de-AT', - 0x0407: 'de', - 0x1407: 'de-LI', - 0x1007: 'de-LU', - 0x0807: 'de-CH', - 0x0408: 'el', - 0x046F: 'kl', - 0x0447: 'gu', - 0x0468: 'ha', - 0x040D: 'he', - 0x0439: 'hi', - 0x040E: 'hu', - 0x040F: 'is', - 0x0470: 'ig', - 0x0421: 'id', - 0x045D: 'iu', - 0x085D: 'iu-Latn', - 0x083C: 'ga', - 0x0434: 'xh', - 0x0435: 'zu', - 0x0410: 'it', - 0x0810: 'it-CH', - 0x0411: 'ja', - 0x044B: 'kn', - 0x043F: 'kk', - 0x0453: 'km', - 0x0486: 'quc', - 0x0487: 'rw', - 0x0441: 'sw', - 0x0457: 'kok', - 0x0412: 'ko', - 0x0440: 'ky', - 0x0454: 'lo', - 0x0426: 'lv', - 0x0427: 'lt', - 0x082E: 'dsb', - 0x046E: 'lb', - 0x042F: 'mk', - 0x083E: 'ms-BN', - 0x043E: 'ms', - 0x044C: 'ml', - 0x043A: 'mt', - 0x0481: 'mi', - 0x047A: 'arn', - 0x044E: 'mr', - 0x047C: 'moh', - 0x0450: 'mn', - 0x0850: 'mn-CN', - 0x0461: 'ne', - 0x0414: 'nb', - 0x0814: 'nn', - 0x0482: 'oc', - 0x0448: 'or', - 0x0463: 'ps', - 0x0415: 'pl', - 0x0416: 'pt', - 0x0816: 'pt-PT', - 0x0446: 'pa', - 0x046B: 'qu-BO', - 0x086B: 'qu-EC', - 0x0C6B: 'qu', - 0x0418: 'ro', - 0x0417: 'rm', - 0x0419: 'ru', - 0x243B: 'smn', - 0x103B: 'smj-NO', - 0x143B: 'smj', - 0x0C3B: 'se-FI', - 0x043B: 'se', - 0x083B: 'se-SE', - 0x203B: 'sms', - 0x183B: 'sma-NO', - 0x1C3B: 'sms', - 0x044F: 'sa', - 0x1C1A: 'sr-Cyrl-BA', - 0x0C1A: 'sr', - 0x181A: 'sr-Latn-BA', - 0x081A: 'sr-Latn', - 0x046C: 'nso', - 0x0432: 'tn', - 0x045B: 'si', - 0x041B: 'sk', - 0x0424: 'sl', - 0x2C0A: 'es-AR', - 0x400A: 'es-BO', - 0x340A: 'es-CL', - 0x240A: 'es-CO', - 0x140A: 'es-CR', - 0x1C0A: 'es-DO', - 0x300A: 'es-EC', - 0x440A: 'es-SV', - 0x100A: 'es-GT', - 0x480A: 'es-HN', - 0x080A: 'es-MX', - 0x4C0A: 'es-NI', - 0x180A: 'es-PA', - 0x3C0A: 'es-PY', - 0x280A: 'es-PE', - 0x500A: 'es-PR', - - // Microsoft has defined two different language codes for - // “Spanish with modern sorting” and “Spanish with traditional - // sorting”. This makes sense for collation APIs, and it would be - // possible to express this in BCP 47 language tags via Unicode - // extensions (eg., es-u-co-trad is Spanish with traditional - // sorting). However, for storing names in fonts, the distinction - // does not make sense, so we give “es” in both cases. - 0x0C0A: 'es', - 0x040A: 'es', - - 0x540A: 'es-US', - 0x380A: 'es-UY', - 0x200A: 'es-VE', - 0x081D: 'sv-FI', - 0x041D: 'sv', - 0x045A: 'syr', - 0x0428: 'tg', - 0x085F: 'tzm', - 0x0449: 'ta', - 0x0444: 'tt', - 0x044A: 'te', - 0x041E: 'th', - 0x0451: 'bo', - 0x041F: 'tr', - 0x0442: 'tk', - 0x0480: 'ug', - 0x0422: 'uk', - 0x042E: 'hsb', - 0x0420: 'ur', - 0x0843: 'uz-Cyrl', - 0x0443: 'uz', - 0x042A: 'vi', - 0x0452: 'cy', - 0x0488: 'wo', - 0x0485: 'sah', - 0x0478: 'ii', - 0x046A: 'yo' - }; - - // Returns a IETF BCP 47 language code, for example 'zh-Hant' - // for 'Chinese in the traditional script'. - function getLanguageCode(platformID, languageID, ltag) { - switch (platformID) { - case 0: // Unicode - if (languageID === 0xFFFF) { - return 'und'; - } else if (ltag) { - return ltag[languageID]; - } - - break; - - case 1: // Macintosh - return macLanguages[languageID]; - - case 3: // Windows - return windowsLanguages[languageID]; - } - - return undefined; - } - - var utf16 = 'utf-16'; - - // MacOS script ID → encoding. This table stores the default case, - // which can be overridden by macLanguageEncodings. - var macScriptEncodings = { - 0: 'macintosh', // smRoman - 1: 'x-mac-japanese', // smJapanese - 2: 'x-mac-chinesetrad', // smTradChinese - 3: 'x-mac-korean', // smKorean - 6: 'x-mac-greek', // smGreek - 7: 'x-mac-cyrillic', // smCyrillic - 9: 'x-mac-devanagai', // smDevanagari - 10: 'x-mac-gurmukhi', // smGurmukhi - 11: 'x-mac-gujarati', // smGujarati - 12: 'x-mac-oriya', // smOriya - 13: 'x-mac-bengali', // smBengali - 14: 'x-mac-tamil', // smTamil - 15: 'x-mac-telugu', // smTelugu - 16: 'x-mac-kannada', // smKannada - 17: 'x-mac-malayalam', // smMalayalam - 18: 'x-mac-sinhalese', // smSinhalese - 19: 'x-mac-burmese', // smBurmese - 20: 'x-mac-khmer', // smKhmer - 21: 'x-mac-thai', // smThai - 22: 'x-mac-lao', // smLao - 23: 'x-mac-georgian', // smGeorgian - 24: 'x-mac-armenian', // smArmenian - 25: 'x-mac-chinesesimp', // smSimpChinese - 26: 'x-mac-tibetan', // smTibetan - 27: 'x-mac-mongolian', // smMongolian - 28: 'x-mac-ethiopic', // smEthiopic - 29: 'x-mac-ce', // smCentralEuroRoman - 30: 'x-mac-vietnamese', // smVietnamese - 31: 'x-mac-extarabic' // smExtArabic - }; - - // MacOS language ID → encoding. This table stores the exceptional - // cases, which override macScriptEncodings. For writing MacOS naming - // tables, we need to emit a MacOS script ID. Therefore, we cannot - // merge macScriptEncodings into macLanguageEncodings. - // - // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt - var macLanguageEncodings = { - 15: 'x-mac-icelandic', // langIcelandic - 17: 'x-mac-turkish', // langTurkish - 18: 'x-mac-croatian', // langCroatian - 24: 'x-mac-ce', // langLithuanian - 25: 'x-mac-ce', // langPolish - 26: 'x-mac-ce', // langHungarian - 27: 'x-mac-ce', // langEstonian - 28: 'x-mac-ce', // langLatvian - 30: 'x-mac-icelandic', // langFaroese - 37: 'x-mac-romanian', // langRomanian - 38: 'x-mac-ce', // langCzech - 39: 'x-mac-ce', // langSlovak - 40: 'x-mac-ce', // langSlovenian - 143: 'x-mac-inuit', // langInuktitut - 146: 'x-mac-gaelic' // langIrishGaelicScript - }; - - function getEncoding(platformID, encodingID, languageID) { - switch (platformID) { - case 0: // Unicode - return utf16; - - case 1: // Apple Macintosh - return macLanguageEncodings[languageID] || macScriptEncodings[encodingID]; - - case 3: // Microsoft Windows - if (encodingID === 1 || encodingID === 10) { - return utf16; - } - - break; - } - - return undefined; - } - - // Parse the naming `name` table. - // FIXME: Format 1 additional fields are not supported yet. - // ltag is the content of the `ltag' table, such as ['en', 'zh-Hans', 'de-CH-1904']. - function parseNameTable(data, start, ltag) { - var name = {}; - var p = new parse.Parser(data, start); - var format = p.parseUShort(); - var count = p.parseUShort(); - var stringOffset = p.offset + p.parseUShort(); - for (var i = 0; i < count; i++) { - var platformID = p.parseUShort(); - var encodingID = p.parseUShort(); - var languageID = p.parseUShort(); - var nameID = p.parseUShort(); - var property = nameTableNames[nameID] || nameID; - var byteLength = p.parseUShort(); - var offset = p.parseUShort(); - var language = getLanguageCode(platformID, languageID, ltag); - var encoding = getEncoding(platformID, encodingID, languageID); - if (encoding !== undefined && language !== undefined) { - var text = (void 0); - if (encoding === utf16) { - text = decode.UTF16(data, stringOffset + offset, byteLength); - } else { - text = decode.MACSTRING(data, stringOffset + offset, byteLength, encoding); - } - - if (text) { - var translations = name[property]; - if (translations === undefined) { - translations = name[property] = {}; - } - - translations[language] = text; - } - } - } - - var langTagCount = 0; - if (format === 1) { - // FIXME: Also handle Microsoft's 'name' table 1. - langTagCount = p.parseUShort(); - } - - return name; - } - - // {23: 'foo'} → {'foo': 23} - // ['bar', 'baz'] → {'bar': 0, 'baz': 1} - function reverseDict(dict) { - var result = {}; - for (var key in dict) { - result[dict[key]] = parseInt(key); - } - - return result; - } - - function makeNameRecord(platformID, encodingID, languageID, nameID, length, offset) { - return new table.Record('NameRecord', [ - {name: 'platformID', type: 'USHORT', value: platformID}, - {name: 'encodingID', type: 'USHORT', value: encodingID}, - {name: 'languageID', type: 'USHORT', value: languageID}, - {name: 'nameID', type: 'USHORT', value: nameID}, - {name: 'length', type: 'USHORT', value: length}, - {name: 'offset', type: 'USHORT', value: offset} - ]); - } - - // Finds the position of needle in haystack, or -1 if not there. - // Like String.indexOf(), but for arrays. - function findSubArray(needle, haystack) { - var needleLength = needle.length; - var limit = haystack.length - needleLength + 1; - - loop: - for (var pos = 0; pos < limit; pos++) { - for (; pos < limit; pos++) { - for (var k = 0; k < needleLength; k++) { - if (haystack[pos + k] !== needle[k]) { - continue loop; - } - } - - return pos; - } - } - - return -1; - } - - function addStringToPool(s, pool) { - var offset = findSubArray(s, pool); - if (offset < 0) { - offset = pool.length; - var i = 0; - var len = s.length; - for (; i < len; ++i) { - pool.push(s[i]); - } - - } - - return offset; - } - - function makeNameTable(names, ltag) { - var nameID; - var nameIDs = []; - - var namesWithNumericKeys = {}; - var nameTableIds = reverseDict(nameTableNames); - for (var key in names) { - var id = nameTableIds[key]; - if (id === undefined) { - id = key; - } - - nameID = parseInt(id); - - if (isNaN(nameID)) { - throw new Error('Name table entry "' + key + '" does not exist, see nameTableNames for complete list.'); - } - - namesWithNumericKeys[nameID] = names[key]; - nameIDs.push(nameID); - } - - var macLanguageIds = reverseDict(macLanguages); - var windowsLanguageIds = reverseDict(windowsLanguages); - - var nameRecords = []; - var stringPool = []; - - for (var i = 0; i < nameIDs.length; i++) { - nameID = nameIDs[i]; - var translations = namesWithNumericKeys[nameID]; - for (var lang in translations) { - var text = translations[lang]; - - // For MacOS, we try to emit the name in the form that was introduced - // in the initial version of the TrueType spec (in the late 1980s). - // However, this can fail for various reasons: the requested BCP 47 - // language code might not have an old-style Mac equivalent; - // we might not have a codec for the needed character encoding; - // or the name might contain characters that cannot be expressed - // in the old-style Macintosh encoding. In case of failure, we emit - // the name in a more modern fashion (Unicode encoding with BCP 47 - // language tags) that is recognized by MacOS 10.5, released in 2009. - // If fonts were only read by operating systems, we could simply - // emit all names in the modern form; this would be much easier. - // However, there are many applications and libraries that read - // 'name' tables directly, and these will usually only recognize - // the ancient form (silently skipping the unrecognized names). - var macPlatform = 1; // Macintosh - var macLanguage = macLanguageIds[lang]; - var macScript = macLanguageToScript[macLanguage]; - var macEncoding = getEncoding(macPlatform, macScript, macLanguage); - var macName = encode.MACSTRING(text, macEncoding); - if (macName === undefined) { - macPlatform = 0; // Unicode - macLanguage = ltag.indexOf(lang); - if (macLanguage < 0) { - macLanguage = ltag.length; - ltag.push(lang); - } - - macScript = 4; // Unicode 2.0 and later - macName = encode.UTF16(text); - } - - var macNameOffset = addStringToPool(macName, stringPool); - nameRecords.push(makeNameRecord(macPlatform, macScript, macLanguage, - nameID, macName.length, macNameOffset)); - - var winLanguage = windowsLanguageIds[lang]; - if (winLanguage !== undefined) { - var winName = encode.UTF16(text); - var winNameOffset = addStringToPool(winName, stringPool); - nameRecords.push(makeNameRecord(3, 1, winLanguage, - nameID, winName.length, winNameOffset)); - } - } - } - - nameRecords.sort(function(a, b) { - return ((a.platformID - b.platformID) || - (a.encodingID - b.encodingID) || - (a.languageID - b.languageID) || - (a.nameID - b.nameID)); - }); - - var t = new table.Table('name', [ - {name: 'format', type: 'USHORT', value: 0}, - {name: 'count', type: 'USHORT', value: nameRecords.length}, - {name: 'stringOffset', type: 'USHORT', value: 6 + nameRecords.length * 12} - ]); - - for (var r = 0; r < nameRecords.length; r++) { - t.fields.push({name: 'record_' + r, type: 'RECORD', value: nameRecords[r]}); - } - - t.fields.push({name: 'strings', type: 'LITERAL', value: stringPool}); - return t; - } - - var _name = { parse: parseNameTable, make: makeNameTable }; - - // The `OS/2` table contains metrics required in OpenType fonts. - - var unicodeRanges = [ - {begin: 0x0000, end: 0x007F}, // Basic Latin - {begin: 0x0080, end: 0x00FF}, // Latin-1 Supplement - {begin: 0x0100, end: 0x017F}, // Latin Extended-A - {begin: 0x0180, end: 0x024F}, // Latin Extended-B - {begin: 0x0250, end: 0x02AF}, // IPA Extensions - {begin: 0x02B0, end: 0x02FF}, // Spacing Modifier Letters - {begin: 0x0300, end: 0x036F}, // Combining Diacritical Marks - {begin: 0x0370, end: 0x03FF}, // Greek and Coptic - {begin: 0x2C80, end: 0x2CFF}, // Coptic - {begin: 0x0400, end: 0x04FF}, // Cyrillic - {begin: 0x0530, end: 0x058F}, // Armenian - {begin: 0x0590, end: 0x05FF}, // Hebrew - {begin: 0xA500, end: 0xA63F}, // Vai - {begin: 0x0600, end: 0x06FF}, // Arabic - {begin: 0x07C0, end: 0x07FF}, // NKo - {begin: 0x0900, end: 0x097F}, // Devanagari - {begin: 0x0980, end: 0x09FF}, // Bengali - {begin: 0x0A00, end: 0x0A7F}, // Gurmukhi - {begin: 0x0A80, end: 0x0AFF}, // Gujarati - {begin: 0x0B00, end: 0x0B7F}, // Oriya - {begin: 0x0B80, end: 0x0BFF}, // Tamil - {begin: 0x0C00, end: 0x0C7F}, // Telugu - {begin: 0x0C80, end: 0x0CFF}, // Kannada - {begin: 0x0D00, end: 0x0D7F}, // Malayalam - {begin: 0x0E00, end: 0x0E7F}, // Thai - {begin: 0x0E80, end: 0x0EFF}, // Lao - {begin: 0x10A0, end: 0x10FF}, // Georgian - {begin: 0x1B00, end: 0x1B7F}, // Balinese - {begin: 0x1100, end: 0x11FF}, // Hangul Jamo - {begin: 0x1E00, end: 0x1EFF}, // Latin Extended Additional - {begin: 0x1F00, end: 0x1FFF}, // Greek Extended - {begin: 0x2000, end: 0x206F}, // General Punctuation - {begin: 0x2070, end: 0x209F}, // Superscripts And Subscripts - {begin: 0x20A0, end: 0x20CF}, // Currency Symbol - {begin: 0x20D0, end: 0x20FF}, // Combining Diacritical Marks For Symbols - {begin: 0x2100, end: 0x214F}, // Letterlike Symbols - {begin: 0x2150, end: 0x218F}, // Number Forms - {begin: 0x2190, end: 0x21FF}, // Arrows - {begin: 0x2200, end: 0x22FF}, // Mathematical Operators - {begin: 0x2300, end: 0x23FF}, // Miscellaneous Technical - {begin: 0x2400, end: 0x243F}, // Control Pictures - {begin: 0x2440, end: 0x245F}, // Optical Character Recognition - {begin: 0x2460, end: 0x24FF}, // Enclosed Alphanumerics - {begin: 0x2500, end: 0x257F}, // Box Drawing - {begin: 0x2580, end: 0x259F}, // Block Elements - {begin: 0x25A0, end: 0x25FF}, // Geometric Shapes - {begin: 0x2600, end: 0x26FF}, // Miscellaneous Symbols - {begin: 0x2700, end: 0x27BF}, // Dingbats - {begin: 0x3000, end: 0x303F}, // CJK Symbols And Punctuation - {begin: 0x3040, end: 0x309F}, // Hiragana - {begin: 0x30A0, end: 0x30FF}, // Katakana - {begin: 0x3100, end: 0x312F}, // Bopomofo - {begin: 0x3130, end: 0x318F}, // Hangul Compatibility Jamo - {begin: 0xA840, end: 0xA87F}, // Phags-pa - {begin: 0x3200, end: 0x32FF}, // Enclosed CJK Letters And Months - {begin: 0x3300, end: 0x33FF}, // CJK Compatibility - {begin: 0xAC00, end: 0xD7AF}, // Hangul Syllables - {begin: 0xD800, end: 0xDFFF}, // Non-Plane 0 * - {begin: 0x10900, end: 0x1091F}, // Phoenicia - {begin: 0x4E00, end: 0x9FFF}, // CJK Unified Ideographs - {begin: 0xE000, end: 0xF8FF}, // Private Use Area (plane 0) - {begin: 0x31C0, end: 0x31EF}, // CJK Strokes - {begin: 0xFB00, end: 0xFB4F}, // Alphabetic Presentation Forms - {begin: 0xFB50, end: 0xFDFF}, // Arabic Presentation Forms-A - {begin: 0xFE20, end: 0xFE2F}, // Combining Half Marks - {begin: 0xFE10, end: 0xFE1F}, // Vertical Forms - {begin: 0xFE50, end: 0xFE6F}, // Small Form Variants - {begin: 0xFE70, end: 0xFEFF}, // Arabic Presentation Forms-B - {begin: 0xFF00, end: 0xFFEF}, // Halfwidth And Fullwidth Forms - {begin: 0xFFF0, end: 0xFFFF}, // Specials - {begin: 0x0F00, end: 0x0FFF}, // Tibetan - {begin: 0x0700, end: 0x074F}, // Syriac - {begin: 0x0780, end: 0x07BF}, // Thaana - {begin: 0x0D80, end: 0x0DFF}, // Sinhala - {begin: 0x1000, end: 0x109F}, // Myanmar - {begin: 0x1200, end: 0x137F}, // Ethiopic - {begin: 0x13A0, end: 0x13FF}, // Cherokee - {begin: 0x1400, end: 0x167F}, // Unified Canadian Aboriginal Syllabics - {begin: 0x1680, end: 0x169F}, // Ogham - {begin: 0x16A0, end: 0x16FF}, // Runic - {begin: 0x1780, end: 0x17FF}, // Khmer - {begin: 0x1800, end: 0x18AF}, // Mongolian - {begin: 0x2800, end: 0x28FF}, // Braille Patterns - {begin: 0xA000, end: 0xA48F}, // Yi Syllables - {begin: 0x1700, end: 0x171F}, // Tagalog - {begin: 0x10300, end: 0x1032F}, // Old Italic - {begin: 0x10330, end: 0x1034F}, // Gothic - {begin: 0x10400, end: 0x1044F}, // Deseret - {begin: 0x1D000, end: 0x1D0FF}, // Byzantine Musical Symbols - {begin: 0x1D400, end: 0x1D7FF}, // Mathematical Alphanumeric Symbols - {begin: 0xFF000, end: 0xFFFFD}, // Private Use (plane 15) - {begin: 0xFE00, end: 0xFE0F}, // Variation Selectors - {begin: 0xE0000, end: 0xE007F}, // Tags - {begin: 0x1900, end: 0x194F}, // Limbu - {begin: 0x1950, end: 0x197F}, // Tai Le - {begin: 0x1980, end: 0x19DF}, // New Tai Lue - {begin: 0x1A00, end: 0x1A1F}, // Buginese - {begin: 0x2C00, end: 0x2C5F}, // Glagolitic - {begin: 0x2D30, end: 0x2D7F}, // Tifinagh - {begin: 0x4DC0, end: 0x4DFF}, // Yijing Hexagram Symbols - {begin: 0xA800, end: 0xA82F}, // Syloti Nagri - {begin: 0x10000, end: 0x1007F}, // Linear B Syllabary - {begin: 0x10140, end: 0x1018F}, // Ancient Greek Numbers - {begin: 0x10380, end: 0x1039F}, // Ugaritic - {begin: 0x103A0, end: 0x103DF}, // Old Persian - {begin: 0x10450, end: 0x1047F}, // Shavian - {begin: 0x10480, end: 0x104AF}, // Osmanya - {begin: 0x10800, end: 0x1083F}, // Cypriot Syllabary - {begin: 0x10A00, end: 0x10A5F}, // Kharoshthi - {begin: 0x1D300, end: 0x1D35F}, // Tai Xuan Jing Symbols - {begin: 0x12000, end: 0x123FF}, // Cuneiform - {begin: 0x1D360, end: 0x1D37F}, // Counting Rod Numerals - {begin: 0x1B80, end: 0x1BBF}, // Sundanese - {begin: 0x1C00, end: 0x1C4F}, // Lepcha - {begin: 0x1C50, end: 0x1C7F}, // Ol Chiki - {begin: 0xA880, end: 0xA8DF}, // Saurashtra - {begin: 0xA900, end: 0xA92F}, // Kayah Li - {begin: 0xA930, end: 0xA95F}, // Rejang - {begin: 0xAA00, end: 0xAA5F}, // Cham - {begin: 0x10190, end: 0x101CF}, // Ancient Symbols - {begin: 0x101D0, end: 0x101FF}, // Phaistos Disc - {begin: 0x102A0, end: 0x102DF}, // Carian - {begin: 0x1F030, end: 0x1F09F} // Domino Tiles - ]; - - function getUnicodeRange(unicode) { - for (var i = 0; i < unicodeRanges.length; i += 1) { - var range = unicodeRanges[i]; - if (unicode >= range.begin && unicode < range.end) { - return i; - } - } - - return -1; - } - - // Parse the OS/2 and Windows metrics `OS/2` table - function parseOS2Table(data, start) { - var os2 = {}; - var p = new parse.Parser(data, start); - os2.version = p.parseUShort(); - os2.xAvgCharWidth = p.parseShort(); - os2.usWeightClass = p.parseUShort(); - os2.usWidthClass = p.parseUShort(); - os2.fsType = p.parseUShort(); - os2.ySubscriptXSize = p.parseShort(); - os2.ySubscriptYSize = p.parseShort(); - os2.ySubscriptXOffset = p.parseShort(); - os2.ySubscriptYOffset = p.parseShort(); - os2.ySuperscriptXSize = p.parseShort(); - os2.ySuperscriptYSize = p.parseShort(); - os2.ySuperscriptXOffset = p.parseShort(); - os2.ySuperscriptYOffset = p.parseShort(); - os2.yStrikeoutSize = p.parseShort(); - os2.yStrikeoutPosition = p.parseShort(); - os2.sFamilyClass = p.parseShort(); - os2.panose = []; - for (var i = 0; i < 10; i++) { - os2.panose[i] = p.parseByte(); - } - - os2.ulUnicodeRange1 = p.parseULong(); - os2.ulUnicodeRange2 = p.parseULong(); - os2.ulUnicodeRange3 = p.parseULong(); - os2.ulUnicodeRange4 = p.parseULong(); - os2.achVendID = String.fromCharCode(p.parseByte(), p.parseByte(), p.parseByte(), p.parseByte()); - os2.fsSelection = p.parseUShort(); - os2.usFirstCharIndex = p.parseUShort(); - os2.usLastCharIndex = p.parseUShort(); - os2.sTypoAscender = p.parseShort(); - os2.sTypoDescender = p.parseShort(); - os2.sTypoLineGap = p.parseShort(); - os2.usWinAscent = p.parseUShort(); - os2.usWinDescent = p.parseUShort(); - if (os2.version >= 1) { - os2.ulCodePageRange1 = p.parseULong(); - os2.ulCodePageRange2 = p.parseULong(); - } - - if (os2.version >= 2) { - os2.sxHeight = p.parseShort(); - os2.sCapHeight = p.parseShort(); - os2.usDefaultChar = p.parseUShort(); - os2.usBreakChar = p.parseUShort(); - os2.usMaxContent = p.parseUShort(); - } - - return os2; - } - - function makeOS2Table(options) { - return new table.Table('OS/2', [ - {name: 'version', type: 'USHORT', value: 0x0003}, - {name: 'xAvgCharWidth', type: 'SHORT', value: 0}, - {name: 'usWeightClass', type: 'USHORT', value: 0}, - {name: 'usWidthClass', type: 'USHORT', value: 0}, - {name: 'fsType', type: 'USHORT', value: 0}, - {name: 'ySubscriptXSize', type: 'SHORT', value: 650}, - {name: 'ySubscriptYSize', type: 'SHORT', value: 699}, - {name: 'ySubscriptXOffset', type: 'SHORT', value: 0}, - {name: 'ySubscriptYOffset', type: 'SHORT', value: 140}, - {name: 'ySuperscriptXSize', type: 'SHORT', value: 650}, - {name: 'ySuperscriptYSize', type: 'SHORT', value: 699}, - {name: 'ySuperscriptXOffset', type: 'SHORT', value: 0}, - {name: 'ySuperscriptYOffset', type: 'SHORT', value: 479}, - {name: 'yStrikeoutSize', type: 'SHORT', value: 49}, - {name: 'yStrikeoutPosition', type: 'SHORT', value: 258}, - {name: 'sFamilyClass', type: 'SHORT', value: 0}, - {name: 'bFamilyType', type: 'BYTE', value: 0}, - {name: 'bSerifStyle', type: 'BYTE', value: 0}, - {name: 'bWeight', type: 'BYTE', value: 0}, - {name: 'bProportion', type: 'BYTE', value: 0}, - {name: 'bContrast', type: 'BYTE', value: 0}, - {name: 'bStrokeVariation', type: 'BYTE', value: 0}, - {name: 'bArmStyle', type: 'BYTE', value: 0}, - {name: 'bLetterform', type: 'BYTE', value: 0}, - {name: 'bMidline', type: 'BYTE', value: 0}, - {name: 'bXHeight', type: 'BYTE', value: 0}, - {name: 'ulUnicodeRange1', type: 'ULONG', value: 0}, - {name: 'ulUnicodeRange2', type: 'ULONG', value: 0}, - {name: 'ulUnicodeRange3', type: 'ULONG', value: 0}, - {name: 'ulUnicodeRange4', type: 'ULONG', value: 0}, - {name: 'achVendID', type: 'CHARARRAY', value: 'XXXX'}, - {name: 'fsSelection', type: 'USHORT', value: 0}, - {name: 'usFirstCharIndex', type: 'USHORT', value: 0}, - {name: 'usLastCharIndex', type: 'USHORT', value: 0}, - {name: 'sTypoAscender', type: 'SHORT', value: 0}, - {name: 'sTypoDescender', type: 'SHORT', value: 0}, - {name: 'sTypoLineGap', type: 'SHORT', value: 0}, - {name: 'usWinAscent', type: 'USHORT', value: 0}, - {name: 'usWinDescent', type: 'USHORT', value: 0}, - {name: 'ulCodePageRange1', type: 'ULONG', value: 0}, - {name: 'ulCodePageRange2', type: 'ULONG', value: 0}, - {name: 'sxHeight', type: 'SHORT', value: 0}, - {name: 'sCapHeight', type: 'SHORT', value: 0}, - {name: 'usDefaultChar', type: 'USHORT', value: 0}, - {name: 'usBreakChar', type: 'USHORT', value: 0}, - {name: 'usMaxContext', type: 'USHORT', value: 0} - ], options); - } - - var os2 = { parse: parseOS2Table, make: makeOS2Table, unicodeRanges: unicodeRanges, getUnicodeRange: getUnicodeRange }; - - // The `post` table stores additional PostScript information, such as glyph names. - - // Parse the PostScript `post` table - function parsePostTable(data, start) { - var post = {}; - var p = new parse.Parser(data, start); - post.version = p.parseVersion(); - post.italicAngle = p.parseFixed(); - post.underlinePosition = p.parseShort(); - post.underlineThickness = p.parseShort(); - post.isFixedPitch = p.parseULong(); - post.minMemType42 = p.parseULong(); - post.maxMemType42 = p.parseULong(); - post.minMemType1 = p.parseULong(); - post.maxMemType1 = p.parseULong(); - switch (post.version) { - case 1: - post.names = standardNames.slice(); - break; - case 2: - post.numberOfGlyphs = p.parseUShort(); - post.glyphNameIndex = new Array(post.numberOfGlyphs); - for (var i = 0; i < post.numberOfGlyphs; i++) { - post.glyphNameIndex[i] = p.parseUShort(); - } - - post.names = []; - for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { - if (post.glyphNameIndex[i$1] >= standardNames.length) { - var nameLength = p.parseChar(); - post.names.push(p.parseString(nameLength)); - } - } - - break; - case 2.5: - post.numberOfGlyphs = p.parseUShort(); - post.offset = new Array(post.numberOfGlyphs); - for (var i$2 = 0; i$2 < post.numberOfGlyphs; i$2++) { - post.offset[i$2] = p.parseChar(); - } - - break; - } - return post; - } - - function makePostTable() { - return new table.Table('post', [ - {name: 'version', type: 'FIXED', value: 0x00030000}, - {name: 'italicAngle', type: 'FIXED', value: 0}, - {name: 'underlinePosition', type: 'FWORD', value: 0}, - {name: 'underlineThickness', type: 'FWORD', value: 0}, - {name: 'isFixedPitch', type: 'ULONG', value: 0}, - {name: 'minMemType42', type: 'ULONG', value: 0}, - {name: 'maxMemType42', type: 'ULONG', value: 0}, - {name: 'minMemType1', type: 'ULONG', value: 0}, - {name: 'maxMemType1', type: 'ULONG', value: 0} - ]); - } - - var post = { parse: parsePostTable, make: makePostTable }; - - // The `GSUB` table contains ligatures, among other things. - - var subtableParsers = new Array(9); // subtableParsers[0] is unused - - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#SS - subtableParsers[1] = function parseLookup1() { - var start = this.offset + this.relativeOffset; - var substFormat = this.parseUShort(); - if (substFormat === 1) { - return { - substFormat: 1, - coverage: this.parsePointer(Parser.coverage), - deltaGlyphId: this.parseUShort() - }; - } else if (substFormat === 2) { - return { - substFormat: 2, - coverage: this.parsePointer(Parser.coverage), - substitute: this.parseOffset16List() - }; - } - check.assert(false, '0x' + start.toString(16) + ': lookup type 1 format must be 1 or 2.'); - }; - - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#MS - subtableParsers[2] = function parseLookup2() { - var substFormat = this.parseUShort(); - check.argument(substFormat === 1, 'GSUB Multiple Substitution Subtable identifier-format must be 1'); - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - sequences: this.parseListOfLists() - }; - }; - - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#AS - subtableParsers[3] = function parseLookup3() { - var substFormat = this.parseUShort(); - check.argument(substFormat === 1, 'GSUB Alternate Substitution Subtable identifier-format must be 1'); - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - alternateSets: this.parseListOfLists() - }; - }; - - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#LS - subtableParsers[4] = function parseLookup4() { - var substFormat = this.parseUShort(); - check.argument(substFormat === 1, 'GSUB ligature table identifier-format must be 1'); - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - ligatureSets: this.parseListOfLists(function() { - return { - ligGlyph: this.parseUShort(), - components: this.parseUShortList(this.parseUShort() - 1) - }; - }) - }; - }; - - var lookupRecordDesc = { - sequenceIndex: Parser.uShort, - lookupListIndex: Parser.uShort - }; - - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CSF - subtableParsers[5] = function parseLookup5() { - var start = this.offset + this.relativeOffset; - var substFormat = this.parseUShort(); - - if (substFormat === 1) { - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - ruleSets: this.parseListOfLists(function() { - var glyphCount = this.parseUShort(); - var substCount = this.parseUShort(); - return { - input: this.parseUShortList(glyphCount - 1), - lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) - }; - }) - }; - } else if (substFormat === 2) { - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - classDef: this.parsePointer(Parser.classDef), - classSets: this.parseListOfLists(function() { - var glyphCount = this.parseUShort(); - var substCount = this.parseUShort(); - return { - classes: this.parseUShortList(glyphCount - 1), - lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) - }; - }) - }; - } else if (substFormat === 3) { - var glyphCount = this.parseUShort(); - var substCount = this.parseUShort(); - return { - substFormat: substFormat, - coverages: this.parseList(glyphCount, Parser.pointer(Parser.coverage)), - lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) - }; - } - check.assert(false, '0x' + start.toString(16) + ': lookup type 5 format must be 1, 2 or 3.'); - }; - - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CC - subtableParsers[6] = function parseLookup6() { - var start = this.offset + this.relativeOffset; - var substFormat = this.parseUShort(); - if (substFormat === 1) { - return { - substFormat: 1, - coverage: this.parsePointer(Parser.coverage), - chainRuleSets: this.parseListOfLists(function() { - return { - backtrack: this.parseUShortList(), - input: this.parseUShortList(this.parseShort() - 1), - lookahead: this.parseUShortList(), - lookupRecords: this.parseRecordList(lookupRecordDesc) - }; - }) - }; - } else if (substFormat === 2) { - return { - substFormat: 2, - coverage: this.parsePointer(Parser.coverage), - backtrackClassDef: this.parsePointer(Parser.classDef), - inputClassDef: this.parsePointer(Parser.classDef), - lookaheadClassDef: this.parsePointer(Parser.classDef), - chainClassSet: this.parseListOfLists(function() { - return { - backtrack: this.parseUShortList(), - input: this.parseUShortList(this.parseShort() - 1), - lookahead: this.parseUShortList(), - lookupRecords: this.parseRecordList(lookupRecordDesc) - }; - }) - }; - } else if (substFormat === 3) { - return { - substFormat: 3, - backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), - inputCoverage: this.parseList(Parser.pointer(Parser.coverage)), - lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), - lookupRecords: this.parseRecordList(lookupRecordDesc) - }; - } - check.assert(false, '0x' + start.toString(16) + ': lookup type 6 format must be 1, 2 or 3.'); - }; - - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#ES - subtableParsers[7] = function parseLookup7() { - // Extension Substitution subtable - var substFormat = this.parseUShort(); - check.argument(substFormat === 1, 'GSUB Extension Substitution subtable identifier-format must be 1'); - var extensionLookupType = this.parseUShort(); - var extensionParser = new Parser(this.data, this.offset + this.parseULong()); - return { - substFormat: 1, - lookupType: extensionLookupType, - extension: subtableParsers[extensionLookupType].call(extensionParser) - }; - }; - - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#RCCS - subtableParsers[8] = function parseLookup8() { - var substFormat = this.parseUShort(); - check.argument(substFormat === 1, 'GSUB Reverse Chaining Contextual Single Substitution Subtable identifier-format must be 1'); - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), - lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), - substitutes: this.parseUShortList() - }; - }; - - // https://www.microsoft.com/typography/OTSPEC/gsub.htm - function parseGsubTable(data, start) { - start = start || 0; - var p = new Parser(data, start); - var tableVersion = p.parseVersion(1); - check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GSUB table version.'); - if (tableVersion === 1) { - return { - version: tableVersion, - scripts: p.parseScriptList(), - features: p.parseFeatureList(), - lookups: p.parseLookupList(subtableParsers) - }; - } else { - return { - version: tableVersion, - scripts: p.parseScriptList(), - features: p.parseFeatureList(), - lookups: p.parseLookupList(subtableParsers), - variations: p.parseFeatureVariationsList() - }; - } - - } - - // GSUB Writing ////////////////////////////////////////////// - var subtableMakers = new Array(9); - - subtableMakers[1] = function makeLookup1(subtable) { - if (subtable.substFormat === 1) { - return new table.Table('substitutionTable', [ - {name: 'substFormat', type: 'USHORT', value: 1}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}, - {name: 'deltaGlyphID', type: 'USHORT', value: subtable.deltaGlyphId} - ]); - } else { - return new table.Table('substitutionTable', [ - {name: 'substFormat', type: 'USHORT', value: 2}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.ushortList('substitute', subtable.substitute))); - } - }; - - subtableMakers[3] = function makeLookup3(subtable) { - check.assert(subtable.substFormat === 1, 'Lookup type 3 substFormat must be 1.'); - return new table.Table('substitutionTable', [ - {name: 'substFormat', type: 'USHORT', value: 1}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.tableList('altSet', subtable.alternateSets, function(alternateSet) { - return new table.Table('alternateSetTable', table.ushortList('alternate', alternateSet)); - }))); - }; - - subtableMakers[4] = function makeLookup4(subtable) { - check.assert(subtable.substFormat === 1, 'Lookup type 4 substFormat must be 1.'); - return new table.Table('substitutionTable', [ - {name: 'substFormat', type: 'USHORT', value: 1}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.tableList('ligSet', subtable.ligatureSets, function(ligatureSet) { - return new table.Table('ligatureSetTable', table.tableList('ligature', ligatureSet, function(ligature) { - return new table.Table('ligatureTable', - [{name: 'ligGlyph', type: 'USHORT', value: ligature.ligGlyph}] - .concat(table.ushortList('component', ligature.components, ligature.components.length + 1)) - ); - })); - }))); - }; - - function makeGsubTable(gsub) { - return new table.Table('GSUB', [ - {name: 'version', type: 'ULONG', value: 0x10000}, - {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gsub.scripts)}, - {name: 'features', type: 'TABLE', value: new table.FeatureList(gsub.features)}, - {name: 'lookups', type: 'TABLE', value: new table.LookupList(gsub.lookups, subtableMakers)} - ]); - } - - var gsub = { parse: parseGsubTable, make: makeGsubTable }; - - // The `GPOS` table contains kerning pairs, among other things. - - // Parse the metadata `meta` table. - // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6meta.html - function parseMetaTable(data, start) { - var p = new parse.Parser(data, start); - var tableVersion = p.parseULong(); - check.argument(tableVersion === 1, 'Unsupported META table version.'); - p.parseULong(); // flags - currently unused and set to 0 - p.parseULong(); // tableOffset - var numDataMaps = p.parseULong(); - - var tags = {}; - for (var i = 0; i < numDataMaps; i++) { - var tag = p.parseTag(); - var dataOffset = p.parseULong(); - var dataLength = p.parseULong(); - var text = decode.UTF8(data, start + dataOffset, dataLength); - - tags[tag] = text; - } - return tags; - } - - function makeMetaTable(tags) { - var numTags = Object.keys(tags).length; - var stringPool = ''; - var stringPoolOffset = 16 + numTags * 12; - - var result = new table.Table('meta', [ - {name: 'version', type: 'ULONG', value: 1}, - {name: 'flags', type: 'ULONG', value: 0}, - {name: 'offset', type: 'ULONG', value: stringPoolOffset}, - {name: 'numTags', type: 'ULONG', value: numTags} - ]); - - for (var tag in tags) { - var pos = stringPool.length; - stringPool += tags[tag]; - - result.fields.push({name: 'tag ' + tag, type: 'TAG', value: tag}); - result.fields.push({name: 'offset ' + tag, type: 'ULONG', value: stringPoolOffset + pos}); - result.fields.push({name: 'length ' + tag, type: 'ULONG', value: tags[tag].length}); - } - - result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); - - return result; - } - - var meta = { parse: parseMetaTable, make: makeMetaTable }; - - // The `sfnt` wrapper provides organization for the tables in the font. - - function log2(v) { - return Math.log(v) / Math.log(2) | 0; - } - - function computeCheckSum(bytes) { - while (bytes.length % 4 !== 0) { - bytes.push(0); - } - - var sum = 0; - for (var i = 0; i < bytes.length; i += 4) { - sum += (bytes[i] << 24) + - (bytes[i + 1] << 16) + - (bytes[i + 2] << 8) + - (bytes[i + 3]); - } - - sum %= Math.pow(2, 32); - return sum; - } - - function makeTableRecord(tag, checkSum, offset, length) { - return new table.Record('Table Record', [ - {name: 'tag', type: 'TAG', value: tag !== undefined ? tag : ''}, - {name: 'checkSum', type: 'ULONG', value: checkSum !== undefined ? checkSum : 0}, - {name: 'offset', type: 'ULONG', value: offset !== undefined ? offset : 0}, - {name: 'length', type: 'ULONG', value: length !== undefined ? length : 0} - ]); - } - - function makeSfntTable(tables) { - var sfnt = new table.Table('sfnt', [ - {name: 'version', type: 'TAG', value: 'OTTO'}, - {name: 'numTables', type: 'USHORT', value: 0}, - {name: 'searchRange', type: 'USHORT', value: 0}, - {name: 'entrySelector', type: 'USHORT', value: 0}, - {name: 'rangeShift', type: 'USHORT', value: 0} - ]); - sfnt.tables = tables; - sfnt.numTables = tables.length; - var highestPowerOf2 = Math.pow(2, log2(sfnt.numTables)); - sfnt.searchRange = 16 * highestPowerOf2; - sfnt.entrySelector = log2(highestPowerOf2); - sfnt.rangeShift = sfnt.numTables * 16 - sfnt.searchRange; - - var recordFields = []; - var tableFields = []; - - var offset = sfnt.sizeOf() + (makeTableRecord().sizeOf() * sfnt.numTables); - while (offset % 4 !== 0) { - offset += 1; - tableFields.push({name: 'padding', type: 'BYTE', value: 0}); - } - - for (var i = 0; i < tables.length; i += 1) { - var t = tables[i]; - check.argument(t.tableName.length === 4, 'Table name' + t.tableName + ' is invalid.'); - var tableLength = t.sizeOf(); - var tableRecord = makeTableRecord(t.tableName, computeCheckSum(t.encode()), offset, tableLength); - recordFields.push({name: tableRecord.tag + ' Table Record', type: 'RECORD', value: tableRecord}); - tableFields.push({name: t.tableName + ' table', type: 'RECORD', value: t}); - offset += tableLength; - check.argument(!isNaN(offset), 'Something went wrong calculating the offset.'); - while (offset % 4 !== 0) { - offset += 1; - tableFields.push({name: 'padding', type: 'BYTE', value: 0}); - } - } - - // Table records need to be sorted alphabetically. - recordFields.sort(function(r1, r2) { - if (r1.value.tag > r2.value.tag) { - return 1; - } else { - return -1; - } - }); - - sfnt.fields = sfnt.fields.concat(recordFields); - sfnt.fields = sfnt.fields.concat(tableFields); - return sfnt; - } - - // Get the metrics for a character. If the string has more than one character - // this function returns metrics for the first available character. - // You can provide optional fallback metrics if no characters are available. - function metricsForChar(font, chars, notFoundMetrics) { - for (var i = 0; i < chars.length; i += 1) { - var glyphIndex = font.charToGlyphIndex(chars[i]); - if (glyphIndex > 0) { - var glyph = font.glyphs.get(glyphIndex); - return glyph.getMetrics(); - } - } - - return notFoundMetrics; - } - - function average(vs) { - var sum = 0; - for (var i = 0; i < vs.length; i += 1) { - sum += vs[i]; - } - - return sum / vs.length; - } - - // Convert the font object to a SFNT data structure. - // This structure contains all the necessary tables and metadata to create a binary OTF file. - function fontToSfntTable(font) { - var xMins = []; - var yMins = []; - var xMaxs = []; - var yMaxs = []; - var advanceWidths = []; - var leftSideBearings = []; - var rightSideBearings = []; - var firstCharIndex; - var lastCharIndex = 0; - var ulUnicodeRange1 = 0; - var ulUnicodeRange2 = 0; - var ulUnicodeRange3 = 0; - var ulUnicodeRange4 = 0; - - for (var i = 0; i < font.glyphs.length; i += 1) { - var glyph = font.glyphs.get(i); - var unicode = glyph.unicode | 0; - - if (isNaN(glyph.advanceWidth)) { - throw new Error('Glyph ' + glyph.name + ' (' + i + '): advanceWidth is not a number.'); - } - - if (firstCharIndex > unicode || firstCharIndex === undefined) { - // ignore .notdef char - if (unicode > 0) { - firstCharIndex = unicode; - } - } - - if (lastCharIndex < unicode) { - lastCharIndex = unicode; - } - - var position = os2.getUnicodeRange(unicode); - if (position < 32) { - ulUnicodeRange1 |= 1 << position; - } else if (position < 64) { - ulUnicodeRange2 |= 1 << position - 32; - } else if (position < 96) { - ulUnicodeRange3 |= 1 << position - 64; - } else if (position < 123) { - ulUnicodeRange4 |= 1 << position - 96; - } else { - throw new Error('Unicode ranges bits > 123 are reserved for internal usage'); - } - // Skip non-important characters. - if (glyph.name === '.notdef') { continue; } - var metrics = glyph.getMetrics(); - xMins.push(metrics.xMin); - yMins.push(metrics.yMin); - xMaxs.push(metrics.xMax); - yMaxs.push(metrics.yMax); - leftSideBearings.push(metrics.leftSideBearing); - rightSideBearings.push(metrics.rightSideBearing); - advanceWidths.push(glyph.advanceWidth); - } - - var globals = { - xMin: Math.min.apply(null, xMins), - yMin: Math.min.apply(null, yMins), - xMax: Math.max.apply(null, xMaxs), - yMax: Math.max.apply(null, yMaxs), - advanceWidthMax: Math.max.apply(null, advanceWidths), - advanceWidthAvg: average(advanceWidths), - minLeftSideBearing: Math.min.apply(null, leftSideBearings), - maxLeftSideBearing: Math.max.apply(null, leftSideBearings), - minRightSideBearing: Math.min.apply(null, rightSideBearings) - }; - globals.ascender = font.ascender; - globals.descender = font.descender; - - var headTable = head.make({ - flags: 3, // 00000011 (baseline for font at y=0; left sidebearing point at x=0) - unitsPerEm: font.unitsPerEm, - xMin: globals.xMin, - yMin: globals.yMin, - xMax: globals.xMax, - yMax: globals.yMax, - lowestRecPPEM: 3, - createdTimestamp: font.createdTimestamp - }); - - var hheaTable = hhea.make({ - ascender: globals.ascender, - descender: globals.descender, - advanceWidthMax: globals.advanceWidthMax, - minLeftSideBearing: globals.minLeftSideBearing, - minRightSideBearing: globals.minRightSideBearing, - xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin), - numberOfHMetrics: font.glyphs.length - }); - - var maxpTable = maxp.make(font.glyphs.length); - - var os2Table = os2.make(Object.assign({ - xAvgCharWidth: Math.round(globals.advanceWidthAvg), - usFirstCharIndex: firstCharIndex, - usLastCharIndex: lastCharIndex, - ulUnicodeRange1: ulUnicodeRange1, - ulUnicodeRange2: ulUnicodeRange2, - ulUnicodeRange3: ulUnicodeRange3, - ulUnicodeRange4: ulUnicodeRange4, - // See http://typophile.com/node/13081 for more info on vertical metrics. - // We get metrics for typical characters (such as "x" for xHeight). - // We provide some fallback characters if characters are unavailable: their - // ordering was chosen experimentally. - sTypoAscender: globals.ascender, - sTypoDescender: globals.descender, - sTypoLineGap: 0, - usWinAscent: globals.yMax, - usWinDescent: Math.abs(globals.yMin), - ulCodePageRange1: 1, // FIXME: hard-code Latin 1 support for now - sxHeight: metricsForChar(font, 'xyvw', {yMax: Math.round(globals.ascender / 2)}).yMax, - sCapHeight: metricsForChar(font, 'HIKLEFJMNTZBDPRAGOQSUVWXY', globals).yMax, - usDefaultChar: font.hasChar(' ') ? 32 : 0, // Use space as the default character, if available. - usBreakChar: font.hasChar(' ') ? 32 : 0, // Use space as the break character, if available. - }, font.tables.os2)); - - var hmtxTable = hmtx.make(font.glyphs); - var cmapTable = cmap.make(font.glyphs); - - var englishFamilyName = font.getEnglishName('fontFamily'); - var englishStyleName = font.getEnglishName('fontSubfamily'); - var englishFullName = englishFamilyName + ' ' + englishStyleName; - var postScriptName = font.getEnglishName('postScriptName'); - if (!postScriptName) { - postScriptName = englishFamilyName.replace(/\s/g, '') + '-' + englishStyleName; - } - - var names = {}; - for (var n in font.names) { - names[n] = font.names[n]; - } - - if (!names.uniqueID) { - names.uniqueID = {en: font.getEnglishName('manufacturer') + ':' + englishFullName}; - } - - if (!names.postScriptName) { - names.postScriptName = {en: postScriptName}; - } - - if (!names.preferredFamily) { - names.preferredFamily = font.names.fontFamily; - } - - if (!names.preferredSubfamily) { - names.preferredSubfamily = font.names.fontSubfamily; - } - - var languageTags = []; - var nameTable = _name.make(names, languageTags); - var ltagTable = (languageTags.length > 0 ? ltag.make(languageTags) : undefined); - - var postTable = post.make(); - var cffTable = cff.make(font.glyphs, { - version: font.getEnglishName('version'), - fullName: englishFullName, - familyName: englishFamilyName, - weightName: englishStyleName, - postScriptName: postScriptName, - unitsPerEm: font.unitsPerEm, - fontBBox: [0, globals.yMin, globals.ascender, globals.advanceWidthMax] - }); - - var metaTable = (font.metas && Object.keys(font.metas).length > 0) ? meta.make(font.metas) : undefined; - - // The order does not matter because makeSfntTable() will sort them. - var tables = [headTable, hheaTable, maxpTable, os2Table, nameTable, cmapTable, postTable, cffTable, hmtxTable]; - if (ltagTable) { - tables.push(ltagTable); - } - // Optional tables - if (font.tables.gsub) { - tables.push(gsub.make(font.tables.gsub)); - } - if (metaTable) { - tables.push(metaTable); - } - - var sfntTable = makeSfntTable(tables); - - // Compute the font's checkSum and store it in head.checkSumAdjustment. - var bytes = sfntTable.encode(); - var checkSum = computeCheckSum(bytes); - var tableFields = sfntTable.fields; - var checkSumAdjusted = false; - for (var i$1 = 0; i$1 < tableFields.length; i$1 += 1) { - if (tableFields[i$1].name === 'head table') { - tableFields[i$1].value.checkSumAdjustment = 0xB1B0AFBA - checkSum; - checkSumAdjusted = true; - break; - } - } - - if (!checkSumAdjusted) { - throw new Error('Could not find head table with checkSum to adjust.'); - } - - return sfntTable; - } - - var sfnt = { make: makeSfntTable, fontToTable: fontToSfntTable, computeCheckSum: computeCheckSum }; - - // The Layout object is the prototype of Substitution objects, and provides - - function searchTag(arr, tag) { - /* jshint bitwise: false */ - var imin = 0; - var imax = arr.length - 1; - while (imin <= imax) { - var imid = (imin + imax) >>> 1; - var val = arr[imid].tag; - if (val === tag) { - return imid; - } else if (val < tag) { - imin = imid + 1; - } else { imax = imid - 1; } - } - // Not found: return -1-insertion point - return -imin - 1; - } - - function binSearch(arr, value) { - /* jshint bitwise: false */ - var imin = 0; - var imax = arr.length - 1; - while (imin <= imax) { - var imid = (imin + imax) >>> 1; - var val = arr[imid]; - if (val === value) { - return imid; - } else if (val < value) { - imin = imid + 1; - } else { imax = imid - 1; } - } - // Not found: return -1-insertion point - return -imin - 1; - } - - // binary search in a list of ranges (coverage, class definition) - function searchRange(ranges, value) { - // jshint bitwise: false - var range; - var imin = 0; - var imax = ranges.length - 1; - while (imin <= imax) { - var imid = (imin + imax) >>> 1; - range = ranges[imid]; - var start = range.start; - if (start === value) { - return range; - } else if (start < value) { - imin = imid + 1; - } else { imax = imid - 1; } - } - if (imin > 0) { - range = ranges[imin - 1]; - if (value > range.end) { return 0; } - return range; - } - } - - /** - * @exports opentype.Layout - * @class - */ - function Layout(font, tableName) { - this.font = font; - this.tableName = tableName; - } - - Layout.prototype = { - - /** - * Binary search an object by "tag" property - * @instance - * @function searchTag - * @memberof opentype.Layout - * @param {Array} arr - * @param {string} tag - * @return {number} - */ - searchTag: searchTag, - - /** - * Binary search in a list of numbers - * @instance - * @function binSearch - * @memberof opentype.Layout - * @param {Array} arr - * @param {number} value - * @return {number} - */ - binSearch: binSearch, - - /** - * Get or create the Layout table (GSUB, GPOS etc). - * @param {boolean} create - Whether to create a new one. - * @return {Object} The GSUB or GPOS table. - */ - getTable: function(create) { - var layout = this.font.tables[this.tableName]; - if (!layout && create) { - layout = this.font.tables[this.tableName] = this.createDefaultTable(); - } - return layout; - }, - - /** - * Returns all scripts in the substitution table. - * @instance - * @return {Array} - */ - getScriptNames: function() { - var layout = this.getTable(); - if (!layout) { return []; } - return layout.scripts.map(function(script) { - return script.tag; - }); - }, - - /** - * Returns the best bet for a script name. - * Returns 'DFLT' if it exists. - * If not, returns 'latn' if it exists. - * If neither exist, returns undefined. - */ - getDefaultScriptName: function() { - var layout = this.getTable(); - if (!layout) { return; } - var hasLatn = false; - for (var i = 0; i < layout.scripts.length; i++) { - var name = layout.scripts[i].tag; - if (name === 'DFLT') { return name; } - if (name === 'latn') { hasLatn = true; } - } - if (hasLatn) { return 'latn'; } - }, - - /** - * Returns all LangSysRecords in the given script. - * @instance - * @param {string} [script='DFLT'] - * @param {boolean} create - forces the creation of this script table if it doesn't exist. - * @return {Object} An object with tag and script properties. - */ - getScriptTable: function(script, create) { - var layout = this.getTable(create); - if (layout) { - script = script || 'DFLT'; - var scripts = layout.scripts; - var pos = searchTag(layout.scripts, script); - if (pos >= 0) { - return scripts[pos].script; - } else if (create) { - var scr = { - tag: script, - script: { - defaultLangSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []}, - langSysRecords: [] - } - }; - scripts.splice(-1 - pos, 0, scr); - return scr.script; - } - } - }, - - /** - * Returns a language system table - * @instance - * @param {string} [script='DFLT'] - * @param {string} [language='dlft'] - * @param {boolean} create - forces the creation of this langSysTable if it doesn't exist. - * @return {Object} - */ - getLangSysTable: function(script, language, create) { - var scriptTable = this.getScriptTable(script, create); - if (scriptTable) { - if (!language || language === 'dflt' || language === 'DFLT') { - return scriptTable.defaultLangSys; - } - var pos = searchTag(scriptTable.langSysRecords, language); - if (pos >= 0) { - return scriptTable.langSysRecords[pos].langSys; - } else if (create) { - var langSysRecord = { - tag: language, - langSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []} - }; - scriptTable.langSysRecords.splice(-1 - pos, 0, langSysRecord); - return langSysRecord.langSys; - } - } - }, - - /** - * Get a specific feature table. - * @instance - * @param {string} [script='DFLT'] - * @param {string} [language='dlft'] - * @param {string} feature - One of the codes listed at https://www.microsoft.com/typography/OTSPEC/featurelist.htm - * @param {boolean} create - forces the creation of the feature table if it doesn't exist. - * @return {Object} - */ - getFeatureTable: function(script, language, feature, create) { - var langSysTable = this.getLangSysTable(script, language, create); - if (langSysTable) { - var featureRecord; - var featIndexes = langSysTable.featureIndexes; - var allFeatures = this.font.tables[this.tableName].features; - // The FeatureIndex array of indices is in arbitrary order, - // even if allFeatures is sorted alphabetically by feature tag. - for (var i = 0; i < featIndexes.length; i++) { - featureRecord = allFeatures[featIndexes[i]]; - if (featureRecord.tag === feature) { - return featureRecord.feature; - } - } - if (create) { - var index = allFeatures.length; - // Automatic ordering of features would require to shift feature indexes in the script list. - check.assert(index === 0 || feature >= allFeatures[index - 1].tag, 'Features must be added in alphabetical order.'); - featureRecord = { - tag: feature, - feature: { params: 0, lookupListIndexes: [] } - }; - allFeatures.push(featureRecord); - featIndexes.push(index); - return featureRecord.feature; - } - } - }, - - /** - * Get the lookup tables of a given type for a script/language/feature. - * @instance - * @param {string} [script='DFLT'] - * @param {string} [language='dlft'] - * @param {string} feature - 4-letter feature code - * @param {number} lookupType - 1 to 9 - * @param {boolean} create - forces the creation of the lookup table if it doesn't exist, with no subtables. - * @return {Object[]} - */ - getLookupTables: function(script, language, feature, lookupType, create) { - var featureTable = this.getFeatureTable(script, language, feature, create); - var tables = []; - if (featureTable) { - var lookupTable; - var lookupListIndexes = featureTable.lookupListIndexes; - var allLookups = this.font.tables[this.tableName].lookups; - // lookupListIndexes are in no particular order, so use naive search. - for (var i = 0; i < lookupListIndexes.length; i++) { - lookupTable = allLookups[lookupListIndexes[i]]; - if (lookupTable.lookupType === lookupType) { - tables.push(lookupTable); - } - } - if (tables.length === 0 && create) { - lookupTable = { - lookupType: lookupType, - lookupFlag: 0, - subtables: [], - markFilteringSet: undefined - }; - var index = allLookups.length; - allLookups.push(lookupTable); - lookupListIndexes.push(index); - return [lookupTable]; - } - } - return tables; - }, - - /** - * Find a glyph in a class definition table - * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table - * @param {object} classDefTable - an OpenType Layout class definition table - * @param {number} glyphIndex - the index of the glyph to find - * @returns {number} -1 if not found - */ - getGlyphClass: function(classDefTable, glyphIndex) { - switch (classDefTable.format) { - case 1: - if (classDefTable.startGlyph <= glyphIndex && glyphIndex < classDefTable.startGlyph + classDefTable.classes.length) { - return classDefTable.classes[glyphIndex - classDefTable.startGlyph]; - } - return 0; - case 2: - var range = searchRange(classDefTable.ranges, glyphIndex); - return range ? range.classId : 0; - } - }, - - /** - * Find a glyph in a coverage table - * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-table - * @param {object} coverageTable - an OpenType Layout coverage table - * @param {number} glyphIndex - the index of the glyph to find - * @returns {number} -1 if not found - */ - getCoverageIndex: function(coverageTable, glyphIndex) { - switch (coverageTable.format) { - case 1: - var index = binSearch(coverageTable.glyphs, glyphIndex); - return index >= 0 ? index : -1; - case 2: - var range = searchRange(coverageTable.ranges, glyphIndex); - return range ? range.index + glyphIndex - range.start : -1; - } - }, - - /** - * Returns the list of glyph indexes of a coverage table. - * Format 1: the list is stored raw - * Format 2: compact list as range records. - * @instance - * @param {Object} coverageTable - * @return {Array} - */ - expandCoverage: function(coverageTable) { - if (coverageTable.format === 1) { - return coverageTable.glyphs; - } else { - var glyphs = []; - var ranges = coverageTable.ranges; - for (var i = 0; i < ranges.length; i++) { - var range = ranges[i]; - var start = range.start; - var end = range.end; - for (var j = start; j <= end; j++) { - glyphs.push(j); - } - } - return glyphs; - } - } - - }; - - // The Position object provides utility methods to manipulate - - /** - * @exports opentype.Position - * @class - * @extends opentype.Layout - * @param {opentype.Font} - * @constructor - */ - function Position(font) { - Layout.call(this, font, 'gpos'); - } - - Position.prototype = Layout.prototype; - - /** - * Init some data for faster and easier access later. - */ - Position.prototype.init = function() { - var script = this.getDefaultScriptName(); - this.defaultKerningTables = this.getKerningTables(script); - }; - - /** - * Find a glyph pair in a list of lookup tables of type 2 and retrieve the xAdvance kerning value. - * - * @param {integer} leftIndex - left glyph index - * @param {integer} rightIndex - right glyph index - * @returns {integer} - */ - Position.prototype.getKerningValue = function(kerningLookups, leftIndex, rightIndex) { - for (var i = 0; i < kerningLookups.length; i++) { - var subtables = kerningLookups[i].subtables; - for (var j = 0; j < subtables.length; j++) { - var subtable = subtables[j]; - var covIndex = this.getCoverageIndex(subtable.coverage, leftIndex); - if (covIndex < 0) { continue; } - switch (subtable.posFormat) { - case 1: - // Search Pair Adjustment Positioning Format 1 - var pairSet = subtable.pairSets[covIndex]; - for (var k = 0; k < pairSet.length; k++) { - var pair = pairSet[k]; - if (pair.secondGlyph === rightIndex) { - return pair.value1 && pair.value1.xAdvance || 0; - } - } - break; // left glyph found, not right glyph - try next subtable - case 2: - // Search Pair Adjustment Positioning Format 2 - var class1 = this.getGlyphClass(subtable.classDef1, leftIndex); - var class2 = this.getGlyphClass(subtable.classDef2, rightIndex); - var pair$1 = subtable.classRecords[class1][class2]; - return pair$1.value1 && pair$1.value1.xAdvance || 0; - } - } - } - return 0; - }; - - /** - * List all kerning lookup tables. - * - * @param {string} [script='DFLT'] - use font.position.getDefaultScriptName() for a better default value - * @param {string} [language='dflt'] - * @return {object[]} The list of kerning lookup tables (may be empty), or undefined if there is no GPOS table (and we should use the kern table) - */ - Position.prototype.getKerningTables = function(script, language) { - if (this.font.tables.gpos) { - return this.getLookupTables(script, language, 'kern', 2); - } - }; - - // The Substitution object provides utility methods to manipulate - - /** - * @exports opentype.Substitution - * @class - * @extends opentype.Layout - * @param {opentype.Font} - * @constructor - */ - function Substitution(font) { - Layout.call(this, font, 'gsub'); - } - - // Check if 2 arrays of primitives are equal. - function arraysEqual(ar1, ar2) { - var n = ar1.length; - if (n !== ar2.length) { return false; } - for (var i = 0; i < n; i++) { - if (ar1[i] !== ar2[i]) { return false; } - } - return true; - } - - // Find the first subtable of a lookup table in a particular format. - function getSubstFormat(lookupTable, format, defaultSubtable) { - var subtables = lookupTable.subtables; - for (var i = 0; i < subtables.length; i++) { - var subtable = subtables[i]; - if (subtable.substFormat === format) { - return subtable; - } - } - if (defaultSubtable) { - subtables.push(defaultSubtable); - return defaultSubtable; - } - return undefined; - } - - Substitution.prototype = Layout.prototype; - - /** - * Create a default GSUB table. - * @return {Object} gsub - The GSUB table. - */ - Substitution.prototype.createDefaultTable = function() { - // Generate a default empty GSUB table with just a DFLT script and dflt lang sys. - return { - version: 1, - scripts: [{ - tag: 'DFLT', - script: { - defaultLangSys: { reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: [] }, - langSysRecords: [] - } - }], - features: [], - lookups: [] - }; - }; - - /** - * List all single substitutions (lookup type 1) for a given script, language, and feature. - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - * @param {string} feature - 4-character feature name ('aalt', 'salt', 'ss01'...) - * @return {Array} substitutions - The list of substitutions. - */ - Substitution.prototype.getSingle = function(feature, script, language) { - var substitutions = []; - var lookupTables = this.getLookupTables(script, language, feature, 1); - for (var idx = 0; idx < lookupTables.length; idx++) { - var subtables = lookupTables[idx].subtables; - for (var i = 0; i < subtables.length; i++) { - var subtable = subtables[i]; - var glyphs = this.expandCoverage(subtable.coverage); - var j = (void 0); - if (subtable.substFormat === 1) { - var delta = subtable.deltaGlyphId; - for (j = 0; j < glyphs.length; j++) { - var glyph = glyphs[j]; - substitutions.push({ sub: glyph, by: glyph + delta }); - } - } else { - var substitute = subtable.substitute; - for (j = 0; j < glyphs.length; j++) { - substitutions.push({ sub: glyphs[j], by: substitute[j] }); - } - } - } - } - return substitutions; - }; - - /** - * List all alternates (lookup type 3) for a given script, language, and feature. - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - * @param {string} feature - 4-character feature name ('aalt', 'salt'...) - * @return {Array} alternates - The list of alternates - */ - Substitution.prototype.getAlternates = function(feature, script, language) { - var alternates = []; - var lookupTables = this.getLookupTables(script, language, feature, 3); - for (var idx = 0; idx < lookupTables.length; idx++) { - var subtables = lookupTables[idx].subtables; - for (var i = 0; i < subtables.length; i++) { - var subtable = subtables[i]; - var glyphs = this.expandCoverage(subtable.coverage); - var alternateSets = subtable.alternateSets; - for (var j = 0; j < glyphs.length; j++) { - alternates.push({ sub: glyphs[j], by: alternateSets[j] }); - } - } - } - return alternates; - }; - - /** - * List all ligatures (lookup type 4) for a given script, language, and feature. - * The result is an array of ligature objects like { sub: [ids], by: id } - * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - * @return {Array} ligatures - The list of ligatures. - */ - Substitution.prototype.getLigatures = function(feature, script, language) { - var ligatures = []; - var lookupTables = this.getLookupTables(script, language, feature, 4); - for (var idx = 0; idx < lookupTables.length; idx++) { - var subtables = lookupTables[idx].subtables; - for (var i = 0; i < subtables.length; i++) { - var subtable = subtables[i]; - var glyphs = this.expandCoverage(subtable.coverage); - var ligatureSets = subtable.ligatureSets; - for (var j = 0; j < glyphs.length; j++) { - var startGlyph = glyphs[j]; - var ligSet = ligatureSets[j]; - for (var k = 0; k < ligSet.length; k++) { - var lig = ligSet[k]; - ligatures.push({ - sub: [startGlyph].concat(lig.components), - by: lig.ligGlyph - }); - } - } - } - } - return ligatures; - }; - - /** - * Add or modify a single substitution (lookup type 1) - * Format 2, more flexible, is always used. - * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) - * @param {Object} substitution - { sub: id, delta: number } for format 1 or { sub: id, by: id } for format 2. - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - */ - Substitution.prototype.addSingle = function(feature, substitution, script, language) { - var lookupTable = this.getLookupTables(script, language, feature, 1, true)[0]; - var subtable = getSubstFormat(lookupTable, 2, { // lookup type 1 subtable, format 2, coverage format 1 - substFormat: 2, - coverage: {format: 1, glyphs: []}, - substitute: [] - }); - check.assert(subtable.coverage.format === 1, 'Ligature: unable to modify coverage table format ' + subtable.coverage.format); - var coverageGlyph = substitution.sub; - var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); - if (pos < 0) { - pos = -1 - pos; - subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); - subtable.substitute.splice(pos, 0, 0); - } - subtable.substitute[pos] = substitution.by; - }; - - /** - * Add or modify an alternate substitution (lookup type 1) - * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) - * @param {Object} substitution - { sub: id, by: [ids] } - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - */ - Substitution.prototype.addAlternate = function(feature, substitution, script, language) { - var lookupTable = this.getLookupTables(script, language, feature, 3, true)[0]; - var subtable = getSubstFormat(lookupTable, 1, { // lookup type 3 subtable, format 1, coverage format 1 - substFormat: 1, - coverage: {format: 1, glyphs: []}, - alternateSets: [] - }); - check.assert(subtable.coverage.format === 1, 'Ligature: unable to modify coverage table format ' + subtable.coverage.format); - var coverageGlyph = substitution.sub; - var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); - if (pos < 0) { - pos = -1 - pos; - subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); - subtable.alternateSets.splice(pos, 0, 0); - } - subtable.alternateSets[pos] = substitution.by; - }; - - /** - * Add a ligature (lookup type 4) - * Ligatures with more components must be stored ahead of those with fewer components in order to be found - * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) - * @param {Object} ligature - { sub: [ids], by: id } - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - */ - Substitution.prototype.addLigature = function(feature, ligature, script, language) { - var lookupTable = this.getLookupTables(script, language, feature, 4, true)[0]; - var subtable = lookupTable.subtables[0]; - if (!subtable) { - subtable = { // lookup type 4 subtable, format 1, coverage format 1 - substFormat: 1, - coverage: { format: 1, glyphs: [] }, - ligatureSets: [] - }; - lookupTable.subtables[0] = subtable; - } - check.assert(subtable.coverage.format === 1, 'Ligature: unable to modify coverage table format ' + subtable.coverage.format); - var coverageGlyph = ligature.sub[0]; - var ligComponents = ligature.sub.slice(1); - var ligatureTable = { - ligGlyph: ligature.by, - components: ligComponents - }; - var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); - if (pos >= 0) { - // ligatureSet already exists - var ligatureSet = subtable.ligatureSets[pos]; - for (var i = 0; i < ligatureSet.length; i++) { - // If ligature already exists, return. - if (arraysEqual(ligatureSet[i].components, ligComponents)) { - return; - } - } - // ligature does not exist: add it. - ligatureSet.push(ligatureTable); - } else { - // Create a new ligatureSet and add coverage for the first glyph. - pos = -1 - pos; - subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); - subtable.ligatureSets.splice(pos, 0, [ligatureTable]); - } - }; - - /** - * List all feature data for a given script and language. - * @param {string} feature - 4-letter feature name - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - * @return {Array} substitutions - The list of substitutions. - */ - Substitution.prototype.getFeature = function(feature, script, language) { - if (/ss\d\d/.test(feature)) { - // ss01 - ss20 - return this.getSingle(feature, script, language); - } - switch (feature) { - case 'aalt': - case 'salt': - return this.getSingle(feature, script, language) - .concat(this.getAlternates(feature, script, language)); - case 'dlig': - case 'liga': - case 'rlig': return this.getLigatures(feature, script, language); - } - return undefined; - }; - - /** - * Add a substitution to a feature for a given script and language. - * @param {string} feature - 4-letter feature name - * @param {Object} sub - the substitution to add (an object like { sub: id or [ids], by: id or [ids] }) - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - */ - Substitution.prototype.add = function(feature, sub, script, language) { - if (/ss\d\d/.test(feature)) { - // ss01 - ss20 - return this.addSingle(feature, sub, script, language); - } - switch (feature) { - case 'aalt': - case 'salt': - if (typeof sub.by === 'number') { - return this.addSingle(feature, sub, script, language); - } - return this.addAlternate(feature, sub, script, language); - case 'dlig': - case 'liga': - case 'rlig': - return this.addLigature(feature, sub, script, language); - } - return undefined; - }; - - function isBrowser() { - return typeof window !== 'undefined'; - } - - function nodeBufferToArrayBuffer(buffer) { - var ab = new ArrayBuffer(buffer.length); - var view = new Uint8Array(ab); - for (var i = 0; i < buffer.length; ++i) { - view[i] = buffer[i]; - } - - return ab; - } - - function arrayBufferToNodeBuffer(ab) { - var buffer = new Buffer(ab.byteLength); - var view = new Uint8Array(ab); - for (var i = 0; i < buffer.length; ++i) { - buffer[i] = view[i]; - } - - return buffer; - } - - function checkArgument(expression, message) { - if (!expression) { - throw message; - } - } - - // The `glyf` table describes the glyphs in TrueType outline format. - - // Parse the coordinate data for a glyph. - function parseGlyphCoordinate(p, flag, previousValue, shortVectorBitMask, sameBitMask) { - var v; - if ((flag & shortVectorBitMask) > 0) { - // The coordinate is 1 byte long. - v = p.parseByte(); - // The `same` bit is re-used for short values to signify the sign of the value. - if ((flag & sameBitMask) === 0) { - v = -v; - } - - v = previousValue + v; - } else { - // The coordinate is 2 bytes long. - // If the `same` bit is set, the coordinate is the same as the previous coordinate. - if ((flag & sameBitMask) > 0) { - v = previousValue; - } else { - // Parse the coordinate as a signed 16-bit delta value. - v = previousValue + p.parseShort(); - } - } - - return v; - } - - // Parse a TrueType glyph. - function parseGlyph(glyph, data, start) { - var p = new parse.Parser(data, start); - glyph.numberOfContours = p.parseShort(); - glyph._xMin = p.parseShort(); - glyph._yMin = p.parseShort(); - glyph._xMax = p.parseShort(); - glyph._yMax = p.parseShort(); - var flags; - var flag; - - if (glyph.numberOfContours > 0) { - // This glyph is not a composite. - var endPointIndices = glyph.endPointIndices = []; - for (var i = 0; i < glyph.numberOfContours; i += 1) { - endPointIndices.push(p.parseUShort()); - } - - glyph.instructionLength = p.parseUShort(); - glyph.instructions = []; - for (var i$1 = 0; i$1 < glyph.instructionLength; i$1 += 1) { - glyph.instructions.push(p.parseByte()); - } - - var numberOfCoordinates = endPointIndices[endPointIndices.length - 1] + 1; - flags = []; - for (var i$2 = 0; i$2 < numberOfCoordinates; i$2 += 1) { - flag = p.parseByte(); - flags.push(flag); - // If bit 3 is set, we repeat this flag n times, where n is the next byte. - if ((flag & 8) > 0) { - var repeatCount = p.parseByte(); - for (var j = 0; j < repeatCount; j += 1) { - flags.push(flag); - i$2 += 1; - } - } - } - - check.argument(flags.length === numberOfCoordinates, 'Bad flags.'); - - if (endPointIndices.length > 0) { - var points = []; - var point; - // X/Y coordinates are relative to the previous point, except for the first point which is relative to 0,0. - if (numberOfCoordinates > 0) { - for (var i$3 = 0; i$3 < numberOfCoordinates; i$3 += 1) { - flag = flags[i$3]; - point = {}; - point.onCurve = !!(flag & 1); - point.lastPointOfContour = endPointIndices.indexOf(i$3) >= 0; - points.push(point); - } - - var px = 0; - for (var i$4 = 0; i$4 < numberOfCoordinates; i$4 += 1) { - flag = flags[i$4]; - point = points[i$4]; - point.x = parseGlyphCoordinate(p, flag, px, 2, 16); - px = point.x; - } - - var py = 0; - for (var i$5 = 0; i$5 < numberOfCoordinates; i$5 += 1) { - flag = flags[i$5]; - point = points[i$5]; - point.y = parseGlyphCoordinate(p, flag, py, 4, 32); - py = point.y; - } - } - - glyph.points = points; - } else { - glyph.points = []; - } - } else if (glyph.numberOfContours === 0) { - glyph.points = []; - } else { - glyph.isComposite = true; - glyph.points = []; - glyph.components = []; - var moreComponents = true; - while (moreComponents) { - flags = p.parseUShort(); - var component = { - glyphIndex: p.parseUShort(), - xScale: 1, - scale01: 0, - scale10: 0, - yScale: 1, - dx: 0, - dy: 0 - }; - if ((flags & 1) > 0) { - // The arguments are words - if ((flags & 2) > 0) { - // values are offset - component.dx = p.parseShort(); - component.dy = p.parseShort(); - } else { - // values are matched points - component.matchedPoints = [p.parseUShort(), p.parseUShort()]; - } - - } else { - // The arguments are bytes - if ((flags & 2) > 0) { - // values are offset - component.dx = p.parseChar(); - component.dy = p.parseChar(); - } else { - // values are matched points - component.matchedPoints = [p.parseByte(), p.parseByte()]; - } - } - - if ((flags & 8) > 0) { - // We have a scale - component.xScale = component.yScale = p.parseF2Dot14(); - } else if ((flags & 64) > 0) { - // We have an X / Y scale - component.xScale = p.parseF2Dot14(); - component.yScale = p.parseF2Dot14(); - } else if ((flags & 128) > 0) { - // We have a 2x2 transformation - component.xScale = p.parseF2Dot14(); - component.scale01 = p.parseF2Dot14(); - component.scale10 = p.parseF2Dot14(); - component.yScale = p.parseF2Dot14(); - } - - glyph.components.push(component); - moreComponents = !!(flags & 32); - } - if (flags & 0x100) { - // We have instructions - glyph.instructionLength = p.parseUShort(); - glyph.instructions = []; - for (var i$6 = 0; i$6 < glyph.instructionLength; i$6 += 1) { - glyph.instructions.push(p.parseByte()); - } - } - } - } - - // Transform an array of points and return a new array. - function transformPoints(points, transform) { - var newPoints = []; - for (var i = 0; i < points.length; i += 1) { - var pt = points[i]; - var newPt = { - x: transform.xScale * pt.x + transform.scale01 * pt.y + transform.dx, - y: transform.scale10 * pt.x + transform.yScale * pt.y + transform.dy, - onCurve: pt.onCurve, - lastPointOfContour: pt.lastPointOfContour - }; - newPoints.push(newPt); - } - - return newPoints; - } - - function getContours(points) { - var contours = []; - var currentContour = []; - for (var i = 0; i < points.length; i += 1) { - var pt = points[i]; - currentContour.push(pt); - if (pt.lastPointOfContour) { - contours.push(currentContour); - currentContour = []; - } - } - - check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); - return contours; - } - - // Convert the TrueType glyph outline to a Path. - function getPath(points) { - var p = new Path(); - if (!points) { - return p; - } - - var contours = getContours(points); - - for (var contourIndex = 0; contourIndex < contours.length; ++contourIndex) { - var contour = contours[contourIndex]; - - var prev = null; - var curr = contour[contour.length - 1]; - var next = contour[0]; - - if (curr.onCurve) { - p.moveTo(curr.x, curr.y); - } else { - if (next.onCurve) { - p.moveTo(next.x, next.y); - } else { - // If both first and last points are off-curve, start at their middle. - var start = {x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5}; - p.moveTo(start.x, start.y); - } - } - - for (var i = 0; i < contour.length; ++i) { - prev = curr; - curr = next; - next = contour[(i + 1) % contour.length]; - - if (curr.onCurve) { - // This is a straight line. - p.lineTo(curr.x, curr.y); - } else { - var prev2 = prev; - var next2 = next; - - if (!prev.onCurve) { - prev2 = { x: (curr.x + prev.x) * 0.5, y: (curr.y + prev.y) * 0.5 }; - } - - if (!next.onCurve) { - next2 = { x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5 }; - } - - p.quadraticCurveTo(curr.x, curr.y, next2.x, next2.y); - } - } - - p.closePath(); - } - return p; - } - - function buildPath(glyphs, glyph) { - if (glyph.isComposite) { - for (var j = 0; j < glyph.components.length; j += 1) { - var component = glyph.components[j]; - var componentGlyph = glyphs.get(component.glyphIndex); - // Force the ttfGlyphLoader to parse the glyph. - componentGlyph.getPath(); - if (componentGlyph.points) { - var transformedPoints = (void 0); - if (component.matchedPoints === undefined) { - // component positioned by offset - transformedPoints = transformPoints(componentGlyph.points, component); - } else { - // component positioned by matched points - if ((component.matchedPoints[0] > glyph.points.length - 1) || - (component.matchedPoints[1] > componentGlyph.points.length - 1)) { - throw Error('Matched points out of range in ' + glyph.name); - } - var firstPt = glyph.points[component.matchedPoints[0]]; - var secondPt = componentGlyph.points[component.matchedPoints[1]]; - var transform = { - xScale: component.xScale, scale01: component.scale01, - scale10: component.scale10, yScale: component.yScale, - dx: 0, dy: 0 - }; - secondPt = transformPoints([secondPt], transform)[0]; - transform.dx = firstPt.x - secondPt.x; - transform.dy = firstPt.y - secondPt.y; - transformedPoints = transformPoints(componentGlyph.points, transform); - } - glyph.points = glyph.points.concat(transformedPoints); - } - } - } - - return getPath(glyph.points); - } - - function parseGlyfTableAll(data, start, loca, font) { - var glyphs = new glyphset.GlyphSet(font); - - // The last element of the loca table is invalid. - for (var i = 0; i < loca.length - 1; i += 1) { - var offset = loca[i]; - var nextOffset = loca[i + 1]; - if (offset !== nextOffset) { - glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); - } else { - glyphs.push(i, glyphset.glyphLoader(font, i)); - } - } - - return glyphs; - } - - function parseGlyfTableOnLowMemory(data, start, loca, font) { - var glyphs = new glyphset.GlyphSet(font); - - font._push = function(i) { - var offset = loca[i]; - var nextOffset = loca[i + 1]; - if (offset !== nextOffset) { - glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); - } else { - glyphs.push(i, glyphset.glyphLoader(font, i)); - } - }; - - return glyphs; - } - - // Parse all the glyphs according to the offsets from the `loca` table. - function parseGlyfTable(data, start, loca, font, opt) { - if (opt.lowMemory) - { return parseGlyfTableOnLowMemory(data, start, loca, font); } - else - { return parseGlyfTableAll(data, start, loca, font); } - } - - var glyf = { getPath: getPath, parse: parseGlyfTable}; - - /* A TrueType font hinting interpreter. - * - * (c) 2017 Axel Kittenberger - * - * This interpreter has been implemented according to this documentation: - * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM05/Chap5.html - * - * According to the documentation F24DOT6 values are used for pixels. - * That means calculation is 1/64 pixel accurate and uses integer operations. - * However, Javascript has floating point operations by default and only - * those are available. One could make a case to simulate the 1/64 accuracy - * exactly by truncating after every division operation - * (for example with << 0) to get pixel exactly results as other TrueType - * implementations. It may make sense since some fonts are pixel optimized - * by hand using DELTAP instructions. The current implementation doesn't - * and rather uses full floating point precision. - * - * xScale, yScale and rotation is currently ignored. - * - * A few non-trivial instructions are missing as I didn't encounter yet - * a font that used them to test a possible implementation. - * - * Some fonts seem to use undocumented features regarding the twilight zone. - * Only some of them are implemented as they were encountered. - * - * The exports.DEBUG statements are removed on the minified distribution file. - */ - - var instructionTable; - var exec; - var execGlyph; - var execComponent; - - /* - * Creates a hinting object. - * - * There ought to be exactly one - * for each truetype font that is used for hinting. - */ - function Hinting(font) { - // the font this hinting object is for - this.font = font; - - this.getCommands = function (hPoints) { - return glyf.getPath(hPoints).commands; - }; - - // cached states - this._fpgmState = - this._prepState = - undefined; - - // errorState - // 0 ... all okay - // 1 ... had an error in a glyf, - // continue working but stop spamming - // the console - // 2 ... error at prep, stop hinting at this ppem - // 3 ... error at fpeg, stop hinting for this font at all - this._errorState = 0; - } - - /* - * Not rounding. - */ - function roundOff(v) { - return v; - } - - /* - * Rounding to grid. - */ - function roundToGrid(v) { - //Rounding in TT is supposed to "symmetrical around zero" - return Math.sign(v) * Math.round(Math.abs(v)); - } - - /* - * Rounding to double grid. - */ - function roundToDoubleGrid(v) { - return Math.sign(v) * Math.round(Math.abs(v * 2)) / 2; - } - - /* - * Rounding to half grid. - */ - function roundToHalfGrid(v) { - return Math.sign(v) * (Math.round(Math.abs(v) + 0.5) - 0.5); - } - - /* - * Rounding to up to grid. - */ - function roundUpToGrid(v) { - return Math.sign(v) * Math.ceil(Math.abs(v)); - } - - /* - * Rounding to down to grid. - */ - function roundDownToGrid(v) { - return Math.sign(v) * Math.floor(Math.abs(v)); - } - - /* - * Super rounding. - */ - var roundSuper = function (v) { - var period = this.srPeriod; - var phase = this.srPhase; - var threshold = this.srThreshold; - var sign = 1; - - if (v < 0) { - v = -v; - sign = -1; - } - - v += threshold - phase; - - v = Math.trunc(v / period) * period; - - v += phase; - - // according to http://xgridfit.sourceforge.net/round.html - if (v < 0) { return phase * sign; } - - return v * sign; - }; - - /* - * Unit vector of x-axis. - */ - var xUnitVector = { - x: 1, - - y: 0, - - axis: 'x', - - // Gets the projected distance between two points. - // o1/o2 ... if true, respective original position is used. - distance: function (p1, p2, o1, o2) { - return (o1 ? p1.xo : p1.x) - (o2 ? p2.xo : p2.x); - }, - - // Moves point p so the moved position has the same relative - // position to the moved positions of rp1 and rp2 than the - // original positions had. - // - // See APPENDIX on INTERPOLATE at the bottom of this file. - interpolate: function (p, rp1, rp2, pv) { - var do1; - var do2; - var doa1; - var doa2; - var dm1; - var dm2; - var dt; - - if (!pv || pv === this) { - do1 = p.xo - rp1.xo; - do2 = p.xo - rp2.xo; - dm1 = rp1.x - rp1.xo; - dm2 = rp2.x - rp2.xo; - doa1 = Math.abs(do1); - doa2 = Math.abs(do2); - dt = doa1 + doa2; - - if (dt === 0) { - p.x = p.xo + (dm1 + dm2) / 2; - return; - } - - p.x = p.xo + (dm1 * doa2 + dm2 * doa1) / dt; - return; - } - - do1 = pv.distance(p, rp1, true, true); - do2 = pv.distance(p, rp2, true, true); - dm1 = pv.distance(rp1, rp1, false, true); - dm2 = pv.distance(rp2, rp2, false, true); - doa1 = Math.abs(do1); - doa2 = Math.abs(do2); - dt = doa1 + doa2; - - if (dt === 0) { - xUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); - return; - } - - xUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); - }, - - // Slope of line normal to this - normalSlope: Number.NEGATIVE_INFINITY, - - // Sets the point 'p' relative to point 'rp' - // by the distance 'd'. - // - // See APPENDIX on SETRELATIVE at the bottom of this file. - // - // p ... point to set - // rp ... reference point - // d ... distance on projection vector - // pv ... projection vector (undefined = this) - // org ... if true, uses the original position of rp as reference. - setRelative: function (p, rp, d, pv, org) { - if (!pv || pv === this) { - p.x = (org ? rp.xo : rp.x) + d; - return; - } - - var rpx = org ? rp.xo : rp.x; - var rpy = org ? rp.yo : rp.y; - var rpdx = rpx + d * pv.x; - var rpdy = rpy + d * pv.y; - - p.x = rpdx + (p.y - rpdy) / pv.normalSlope; - }, - - // Slope of vector line. - slope: 0, - - // Touches the point p. - touch: function (p) { - p.xTouched = true; - }, - - // Tests if a point p is touched. - touched: function (p) { - return p.xTouched; - }, - - // Untouches the point p. - untouch: function (p) { - p.xTouched = false; - } - }; - - /* - * Unit vector of y-axis. - */ - var yUnitVector = { - x: 0, - - y: 1, - - axis: 'y', - - // Gets the projected distance between two points. - // o1/o2 ... if true, respective original position is used. - distance: function (p1, p2, o1, o2) { - return (o1 ? p1.yo : p1.y) - (o2 ? p2.yo : p2.y); - }, - - // Moves point p so the moved position has the same relative - // position to the moved positions of rp1 and rp2 than the - // original positions had. - // - // See APPENDIX on INTERPOLATE at the bottom of this file. - interpolate: function (p, rp1, rp2, pv) { - var do1; - var do2; - var doa1; - var doa2; - var dm1; - var dm2; - var dt; - - if (!pv || pv === this) { - do1 = p.yo - rp1.yo; - do2 = p.yo - rp2.yo; - dm1 = rp1.y - rp1.yo; - dm2 = rp2.y - rp2.yo; - doa1 = Math.abs(do1); - doa2 = Math.abs(do2); - dt = doa1 + doa2; - - if (dt === 0) { - p.y = p.yo + (dm1 + dm2) / 2; - return; - } - - p.y = p.yo + (dm1 * doa2 + dm2 * doa1) / dt; - return; - } - - do1 = pv.distance(p, rp1, true, true); - do2 = pv.distance(p, rp2, true, true); - dm1 = pv.distance(rp1, rp1, false, true); - dm2 = pv.distance(rp2, rp2, false, true); - doa1 = Math.abs(do1); - doa2 = Math.abs(do2); - dt = doa1 + doa2; - - if (dt === 0) { - yUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); - return; - } - - yUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); - }, - - // Slope of line normal to this. - normalSlope: 0, - - // Sets the point 'p' relative to point 'rp' - // by the distance 'd' - // - // See APPENDIX on SETRELATIVE at the bottom of this file. - // - // p ... point to set - // rp ... reference point - // d ... distance on projection vector - // pv ... projection vector (undefined = this) - // org ... if true, uses the original position of rp as reference. - setRelative: function (p, rp, d, pv, org) { - if (!pv || pv === this) { - p.y = (org ? rp.yo : rp.y) + d; - return; - } - - var rpx = org ? rp.xo : rp.x; - var rpy = org ? rp.yo : rp.y; - var rpdx = rpx + d * pv.x; - var rpdy = rpy + d * pv.y; - - p.y = rpdy + pv.normalSlope * (p.x - rpdx); - }, - - // Slope of vector line. - slope: Number.POSITIVE_INFINITY, - - // Touches the point p. - touch: function (p) { - p.yTouched = true; - }, - - // Tests if a point p is touched. - touched: function (p) { - return p.yTouched; - }, - - // Untouches the point p. - untouch: function (p) { - p.yTouched = false; - } - }; - - Object.freeze(xUnitVector); - Object.freeze(yUnitVector); - - /* - * Creates a unit vector that is not x- or y-axis. - */ - function UnitVector(x, y) { - this.x = x; - this.y = y; - this.axis = undefined; - this.slope = y / x; - this.normalSlope = -x / y; - Object.freeze(this); - } - - /* - * Gets the projected distance between two points. - * o1/o2 ... if true, respective original position is used. - */ - UnitVector.prototype.distance = function(p1, p2, o1, o2) { - return ( - this.x * xUnitVector.distance(p1, p2, o1, o2) + - this.y * yUnitVector.distance(p1, p2, o1, o2) - ); - }; - - /* - * Moves point p so the moved position has the same relative - * position to the moved positions of rp1 and rp2 than the - * original positions had. - * - * See APPENDIX on INTERPOLATE at the bottom of this file. - */ - UnitVector.prototype.interpolate = function(p, rp1, rp2, pv) { - var dm1; - var dm2; - var do1; - var do2; - var doa1; - var doa2; - var dt; - - do1 = pv.distance(p, rp1, true, true); - do2 = pv.distance(p, rp2, true, true); - dm1 = pv.distance(rp1, rp1, false, true); - dm2 = pv.distance(rp2, rp2, false, true); - doa1 = Math.abs(do1); - doa2 = Math.abs(do2); - dt = doa1 + doa2; - - if (dt === 0) { - this.setRelative(p, p, (dm1 + dm2) / 2, pv, true); - return; - } - - this.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); - }; - - /* - * Sets the point 'p' relative to point 'rp' - * by the distance 'd' - * - * See APPENDIX on SETRELATIVE at the bottom of this file. - * - * p ... point to set - * rp ... reference point - * d ... distance on projection vector - * pv ... projection vector (undefined = this) - * org ... if true, uses the original position of rp as reference. - */ - UnitVector.prototype.setRelative = function(p, rp, d, pv, org) { - pv = pv || this; - - var rpx = org ? rp.xo : rp.x; - var rpy = org ? rp.yo : rp.y; - var rpdx = rpx + d * pv.x; - var rpdy = rpy + d * pv.y; - - var pvns = pv.normalSlope; - var fvs = this.slope; - - var px = p.x; - var py = p.y; - - p.x = (fvs * px - pvns * rpdx + rpdy - py) / (fvs - pvns); - p.y = fvs * (p.x - px) + py; - }; - - /* - * Touches the point p. - */ - UnitVector.prototype.touch = function(p) { - p.xTouched = true; - p.yTouched = true; - }; - - /* - * Returns a unit vector with x/y coordinates. - */ - function getUnitVector(x, y) { - var d = Math.sqrt(x * x + y * y); - - x /= d; - y /= d; - - if (x === 1 && y === 0) { return xUnitVector; } - else if (x === 0 && y === 1) { return yUnitVector; } - else { return new UnitVector(x, y); } - } - - /* - * Creates a point in the hinting engine. - */ - function HPoint( - x, - y, - lastPointOfContour, - onCurve - ) { - this.x = this.xo = Math.round(x * 64) / 64; // hinted x value and original x-value - this.y = this.yo = Math.round(y * 64) / 64; // hinted y value and original y-value - - this.lastPointOfContour = lastPointOfContour; - this.onCurve = onCurve; - this.prevPointOnContour = undefined; - this.nextPointOnContour = undefined; - this.xTouched = false; - this.yTouched = false; - - Object.preventExtensions(this); - } - - /* - * Returns the next touched point on the contour. - * - * v ... unit vector to test touch axis. - */ - HPoint.prototype.nextTouched = function(v) { - var p = this.nextPointOnContour; - - while (!v.touched(p) && p !== this) { p = p.nextPointOnContour; } - - return p; - }; - - /* - * Returns the previous touched point on the contour - * - * v ... unit vector to test touch axis. - */ - HPoint.prototype.prevTouched = function(v) { - var p = this.prevPointOnContour; - - while (!v.touched(p) && p !== this) { p = p.prevPointOnContour; } - - return p; - }; - - /* - * The zero point. - */ - var HPZero = Object.freeze(new HPoint(0, 0)); - - /* - * The default state of the interpreter. - * - * Note: Freezing the defaultState and then deriving from it - * makes the V8 Javascript engine going awkward, - * so this is avoided, albeit the defaultState shouldn't - * ever change. - */ - var defaultState = { - cvCutIn: 17 / 16, // control value cut in - deltaBase: 9, - deltaShift: 0.125, - loop: 1, // loops some instructions - minDis: 1, // minimum distance - autoFlip: true - }; - - /* - * The current state of the interpreter. - * - * env ... 'fpgm' or 'prep' or 'glyf' - * prog ... the program - */ - function State(env, prog) { - this.env = env; - this.stack = []; - this.prog = prog; - - switch (env) { - case 'glyf' : - this.zp0 = this.zp1 = this.zp2 = 1; - this.rp0 = this.rp1 = this.rp2 = 0; - /* fall through */ - case 'prep' : - this.fv = this.pv = this.dpv = xUnitVector; - this.round = roundToGrid; - } - } - - /* - * Executes a glyph program. - * - * This does the hinting for each glyph. - * - * Returns an array of moved points. - * - * glyph: the glyph to hint - * ppem: the size the glyph is rendered for - */ - Hinting.prototype.exec = function(glyph, ppem) { - if (typeof ppem !== 'number') { - throw new Error('Point size is not a number!'); - } - - // Received a fatal error, don't do any hinting anymore. - if (this._errorState > 2) { return; } - - var font = this.font; - var prepState = this._prepState; - - if (!prepState || prepState.ppem !== ppem) { - var fpgmState = this._fpgmState; - - if (!fpgmState) { - // Executes the fpgm state. - // This is used by fonts to define functions. - State.prototype = defaultState; - - fpgmState = - this._fpgmState = - new State('fpgm', font.tables.fpgm); - - fpgmState.funcs = [ ]; - fpgmState.font = font; - - if (exports.DEBUG) { - console.log('---EXEC FPGM---'); - fpgmState.step = -1; - } - - try { - exec(fpgmState); - } catch (e) { - console.log('Hinting error in FPGM:' + e); - this._errorState = 3; - return; - } - } - - // Executes the prep program for this ppem setting. - // This is used by fonts to set cvt values - // depending on to be rendered font size. - - State.prototype = fpgmState; - prepState = - this._prepState = - new State('prep', font.tables.prep); - - prepState.ppem = ppem; - - // Creates a copy of the cvt table - // and scales it to the current ppem setting. - var oCvt = font.tables.cvt; - if (oCvt) { - var cvt = prepState.cvt = new Array(oCvt.length); - var scale = ppem / font.unitsPerEm; - for (var c = 0; c < oCvt.length; c++) { - cvt[c] = oCvt[c] * scale; - } - } else { - prepState.cvt = []; - } - - if (exports.DEBUG) { - console.log('---EXEC PREP---'); - prepState.step = -1; - } - - try { - exec(prepState); - } catch (e) { - if (this._errorState < 2) { - console.log('Hinting error in PREP:' + e); - } - this._errorState = 2; - } - } - - if (this._errorState > 1) { return; } - - try { - return execGlyph(glyph, prepState); - } catch (e) { - if (this._errorState < 1) { - console.log('Hinting error:' + e); - console.log('Note: further hinting errors are silenced'); - } - this._errorState = 1; - return undefined; - } - }; - - /* - * Executes the hinting program for a glyph. - */ - execGlyph = function(glyph, prepState) { - // original point positions - var xScale = prepState.ppem / prepState.font.unitsPerEm; - var yScale = xScale; - var components = glyph.components; - var contours; - var gZone; - var state; - - State.prototype = prepState; - if (!components) { - state = new State('glyf', glyph.instructions); - if (exports.DEBUG) { - console.log('---EXEC GLYPH---'); - state.step = -1; - } - execComponent(glyph, state, xScale, yScale); - gZone = state.gZone; - } else { - var font = prepState.font; - gZone = []; - contours = []; - for (var i = 0; i < components.length; i++) { - var c = components[i]; - var cg = font.glyphs.get(c.glyphIndex); - - state = new State('glyf', cg.instructions); - - if (exports.DEBUG) { - console.log('---EXEC COMP ' + i + '---'); - state.step = -1; - } - - execComponent(cg, state, xScale, yScale); - // appends the computed points to the result array - // post processes the component points - var dx = Math.round(c.dx * xScale); - var dy = Math.round(c.dy * yScale); - var gz = state.gZone; - var cc = state.contours; - for (var pi = 0; pi < gz.length; pi++) { - var p = gz[pi]; - p.xTouched = p.yTouched = false; - p.xo = p.x = p.x + dx; - p.yo = p.y = p.y + dy; - } - - var gLen = gZone.length; - gZone.push.apply(gZone, gz); - for (var j = 0; j < cc.length; j++) { - contours.push(cc[j] + gLen); - } - } - - if (glyph.instructions && !state.inhibitGridFit) { - // the composite has instructions on its own - state = new State('glyf', glyph.instructions); - - state.gZone = state.z0 = state.z1 = state.z2 = gZone; - - state.contours = contours; - - // note: HPZero cannot be used here, since - // the point might be modified - gZone.push( - new HPoint(0, 0), - new HPoint(Math.round(glyph.advanceWidth * xScale), 0) - ); - - if (exports.DEBUG) { - console.log('---EXEC COMPOSITE---'); - state.step = -1; - } - - exec(state); - - gZone.length -= 2; - } - } - - return gZone; - }; - - /* - * Executes the hinting program for a component of a multi-component glyph - * or of the glyph itself for a non-component glyph. - */ - execComponent = function(glyph, state, xScale, yScale) - { - var points = glyph.points || []; - var pLen = points.length; - var gZone = state.gZone = state.z0 = state.z1 = state.z2 = []; - var contours = state.contours = []; - - // Scales the original points and - // makes copies for the hinted points. - var cp; // current point - for (var i = 0; i < pLen; i++) { - cp = points[i]; - - gZone[i] = new HPoint( - cp.x * xScale, - cp.y * yScale, - cp.lastPointOfContour, - cp.onCurve - ); - } - - // Chain links the contours. - var sp; // start point - var np; // next point - - for (var i$1 = 0; i$1 < pLen; i$1++) { - cp = gZone[i$1]; - - if (!sp) { - sp = cp; - contours.push(i$1); - } - - if (cp.lastPointOfContour) { - cp.nextPointOnContour = sp; - sp.prevPointOnContour = cp; - sp = undefined; - } else { - np = gZone[i$1 + 1]; - cp.nextPointOnContour = np; - np.prevPointOnContour = cp; - } - } - - if (state.inhibitGridFit) { return; } - - if (exports.DEBUG) { - console.log('PROCESSING GLYPH', state.stack); - for (var i$2 = 0; i$2 < pLen; i$2++) { - console.log(i$2, gZone[i$2].x, gZone[i$2].y); - } - } - - gZone.push( - new HPoint(0, 0), - new HPoint(Math.round(glyph.advanceWidth * xScale), 0) - ); - - exec(state); - - // Removes the extra points. - gZone.length -= 2; - - if (exports.DEBUG) { - console.log('FINISHED GLYPH', state.stack); - for (var i$3 = 0; i$3 < pLen; i$3++) { - console.log(i$3, gZone[i$3].x, gZone[i$3].y); - } - } - }; - - /* - * Executes the program loaded in state. - */ - exec = function(state) { - var prog = state.prog; - - if (!prog) { return; } - - var pLen = prog.length; - var ins; - - for (state.ip = 0; state.ip < pLen; state.ip++) { - if (exports.DEBUG) { state.step++; } - ins = instructionTable[prog[state.ip]]; - - if (!ins) { - throw new Error( - 'unknown instruction: 0x' + - Number(prog[state.ip]).toString(16) - ); - } - - ins(state); - - // very extensive debugging for each step - /* - if (exports.DEBUG) { - var da; - if (state.gZone) { - da = []; - for (let i = 0; i < state.gZone.length; i++) - { - da.push(i + ' ' + - state.gZone[i].x * 64 + ' ' + - state.gZone[i].y * 64 + ' ' + - (state.gZone[i].xTouched ? 'x' : '') + - (state.gZone[i].yTouched ? 'y' : '') - ); - } - console.log('GZ', da); - } - - if (state.tZone) { - da = []; - for (let i = 0; i < state.tZone.length; i++) { - da.push(i + ' ' + - state.tZone[i].x * 64 + ' ' + - state.tZone[i].y * 64 + ' ' + - (state.tZone[i].xTouched ? 'x' : '') + - (state.tZone[i].yTouched ? 'y' : '') - ); - } - console.log('TZ', da); - } - - if (state.stack.length > 10) { - console.log( - state.stack.length, - '...', state.stack.slice(state.stack.length - 10) - ); - } else { - console.log(state.stack.length, state.stack); - } - } - */ - } - }; - - /* - * Initializes the twilight zone. - * - * This is only done if a SZPx instruction - * refers to the twilight zone. - */ - function initTZone(state) - { - var tZone = state.tZone = new Array(state.gZone.length); - - // no idea if this is actually correct... - for (var i = 0; i < tZone.length; i++) - { - tZone[i] = new HPoint(0, 0); - } - } - - /* - * Skips the instruction pointer ahead over an IF/ELSE block. - * handleElse .. if true breaks on matching ELSE - */ - function skip(state, handleElse) - { - var prog = state.prog; - var ip = state.ip; - var nesting = 1; - var ins; - - do { - ins = prog[++ip]; - if (ins === 0x58) // IF - { nesting++; } - else if (ins === 0x59) // EIF - { nesting--; } - else if (ins === 0x40) // NPUSHB - { ip += prog[ip + 1] + 1; } - else if (ins === 0x41) // NPUSHW - { ip += 2 * prog[ip + 1] + 1; } - else if (ins >= 0xB0 && ins <= 0xB7) // PUSHB - { ip += ins - 0xB0 + 1; } - else if (ins >= 0xB8 && ins <= 0xBF) // PUSHW - { ip += (ins - 0xB8 + 1) * 2; } - else if (handleElse && nesting === 1 && ins === 0x1B) // ELSE - { break; } - } while (nesting > 0); - - state.ip = ip; - } - - /*----------------------------------------------------------* - * And then a lot of instructions... * - *----------------------------------------------------------*/ - - // SVTCA[a] Set freedom and projection Vectors To Coordinate Axis - // 0x00-0x01 - function SVTCA(v, state) { - if (exports.DEBUG) { console.log(state.step, 'SVTCA[' + v.axis + ']'); } - - state.fv = state.pv = state.dpv = v; - } - - // SPVTCA[a] Set Projection Vector to Coordinate Axis - // 0x02-0x03 - function SPVTCA(v, state) { - if (exports.DEBUG) { console.log(state.step, 'SPVTCA[' + v.axis + ']'); } - - state.pv = state.dpv = v; - } - - // SFVTCA[a] Set Freedom Vector to Coordinate Axis - // 0x04-0x05 - function SFVTCA(v, state) { - if (exports.DEBUG) { console.log(state.step, 'SFVTCA[' + v.axis + ']'); } - - state.fv = v; - } - - // SPVTL[a] Set Projection Vector To Line - // 0x06-0x07 - function SPVTL(a, state) { - var stack = state.stack; - var p2i = stack.pop(); - var p1i = stack.pop(); - var p2 = state.z2[p2i]; - var p1 = state.z1[p1i]; - - if (exports.DEBUG) { console.log('SPVTL[' + a + ']', p2i, p1i); } - - var dx; - var dy; - - if (!a) { - dx = p1.x - p2.x; - dy = p1.y - p2.y; - } else { - dx = p2.y - p1.y; - dy = p1.x - p2.x; - } - - state.pv = state.dpv = getUnitVector(dx, dy); - } - - // SFVTL[a] Set Freedom Vector To Line - // 0x08-0x09 - function SFVTL(a, state) { - var stack = state.stack; - var p2i = stack.pop(); - var p1i = stack.pop(); - var p2 = state.z2[p2i]; - var p1 = state.z1[p1i]; - - if (exports.DEBUG) { console.log('SFVTL[' + a + ']', p2i, p1i); } - - var dx; - var dy; - - if (!a) { - dx = p1.x - p2.x; - dy = p1.y - p2.y; - } else { - dx = p2.y - p1.y; - dy = p1.x - p2.x; - } - - state.fv = getUnitVector(dx, dy); - } - - // SPVFS[] Set Projection Vector From Stack - // 0x0A - function SPVFS(state) { - var stack = state.stack; - var y = stack.pop(); - var x = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } - - state.pv = state.dpv = getUnitVector(x, y); - } - - // SFVFS[] Set Freedom Vector From Stack - // 0x0B - function SFVFS(state) { - var stack = state.stack; - var y = stack.pop(); - var x = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } - - state.fv = getUnitVector(x, y); - } - - // GPV[] Get Projection Vector - // 0x0C - function GPV(state) { - var stack = state.stack; - var pv = state.pv; - - if (exports.DEBUG) { console.log(state.step, 'GPV[]'); } - - stack.push(pv.x * 0x4000); - stack.push(pv.y * 0x4000); - } - - // GFV[] Get Freedom Vector - // 0x0C - function GFV(state) { - var stack = state.stack; - var fv = state.fv; - - if (exports.DEBUG) { console.log(state.step, 'GFV[]'); } - - stack.push(fv.x * 0x4000); - stack.push(fv.y * 0x4000); - } - - // SFVTPV[] Set Freedom Vector To Projection Vector - // 0x0E - function SFVTPV(state) { - state.fv = state.pv; - - if (exports.DEBUG) { console.log(state.step, 'SFVTPV[]'); } - } - - // ISECT[] moves point p to the InterSECTion of two lines - // 0x0F - function ISECT(state) - { - var stack = state.stack; - var pa0i = stack.pop(); - var pa1i = stack.pop(); - var pb0i = stack.pop(); - var pb1i = stack.pop(); - var pi = stack.pop(); - var z0 = state.z0; - var z1 = state.z1; - var pa0 = z0[pa0i]; - var pa1 = z0[pa1i]; - var pb0 = z1[pb0i]; - var pb1 = z1[pb1i]; - var p = state.z2[pi]; - - if (exports.DEBUG) { console.log('ISECT[], ', pa0i, pa1i, pb0i, pb1i, pi); } - - // math from - // en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line - - var x1 = pa0.x; - var y1 = pa0.y; - var x2 = pa1.x; - var y2 = pa1.y; - var x3 = pb0.x; - var y3 = pb0.y; - var x4 = pb1.x; - var y4 = pb1.y; - - var div = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); - var f1 = x1 * y2 - y1 * x2; - var f2 = x3 * y4 - y3 * x4; - - p.x = (f1 * (x3 - x4) - f2 * (x1 - x2)) / div; - p.y = (f1 * (y3 - y4) - f2 * (y1 - y2)) / div; - } - - // SRP0[] Set Reference Point 0 - // 0x10 - function SRP0(state) { - state.rp0 = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SRP0[]', state.rp0); } - } - - // SRP1[] Set Reference Point 1 - // 0x11 - function SRP1(state) { - state.rp1 = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SRP1[]', state.rp1); } - } - - // SRP1[] Set Reference Point 2 - // 0x12 - function SRP2(state) { - state.rp2 = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SRP2[]', state.rp2); } - } - - // SZP0[] Set Zone Pointer 0 - // 0x13 - function SZP0(state) { - var n = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SZP0[]', n); } - - state.zp0 = n; - - switch (n) { - case 0: - if (!state.tZone) { initTZone(state); } - state.z0 = state.tZone; - break; - case 1 : - state.z0 = state.gZone; - break; - default : - throw new Error('Invalid zone pointer'); - } - } - - // SZP1[] Set Zone Pointer 1 - // 0x14 - function SZP1(state) { - var n = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SZP1[]', n); } - - state.zp1 = n; - - switch (n) { - case 0: - if (!state.tZone) { initTZone(state); } - state.z1 = state.tZone; - break; - case 1 : - state.z1 = state.gZone; - break; - default : - throw new Error('Invalid zone pointer'); - } - } - - // SZP2[] Set Zone Pointer 2 - // 0x15 - function SZP2(state) { - var n = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SZP2[]', n); } - - state.zp2 = n; - - switch (n) { - case 0: - if (!state.tZone) { initTZone(state); } - state.z2 = state.tZone; - break; - case 1 : - state.z2 = state.gZone; - break; - default : - throw new Error('Invalid zone pointer'); - } - } - - // SZPS[] Set Zone PointerS - // 0x16 - function SZPS(state) { - var n = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SZPS[]', n); } - - state.zp0 = state.zp1 = state.zp2 = n; - - switch (n) { - case 0: - if (!state.tZone) { initTZone(state); } - state.z0 = state.z1 = state.z2 = state.tZone; - break; - case 1 : - state.z0 = state.z1 = state.z2 = state.gZone; - break; - default : - throw new Error('Invalid zone pointer'); - } - } - - // SLOOP[] Set LOOP variable - // 0x17 - function SLOOP(state) { - state.loop = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SLOOP[]', state.loop); } - } - - // RTG[] Round To Grid - // 0x18 - function RTG(state) { - if (exports.DEBUG) { console.log(state.step, 'RTG[]'); } - - state.round = roundToGrid; - } - - // RTHG[] Round To Half Grid - // 0x19 - function RTHG(state) { - if (exports.DEBUG) { console.log(state.step, 'RTHG[]'); } - - state.round = roundToHalfGrid; - } - - // SMD[] Set Minimum Distance - // 0x1A - function SMD(state) { - var d = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SMD[]', d); } - - state.minDis = d / 0x40; - } - - // ELSE[] ELSE clause - // 0x1B - function ELSE(state) { - // This instruction has been reached by executing a then branch - // so it just skips ahead until matching EIF. - // - // In case the IF was negative the IF[] instruction already - // skipped forward over the ELSE[] - - if (exports.DEBUG) { console.log(state.step, 'ELSE[]'); } - - skip(state, false); - } - - // JMPR[] JuMP Relative - // 0x1C - function JMPR(state) { - var o = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'JMPR[]', o); } - - // A jump by 1 would do nothing. - state.ip += o - 1; - } - - // SCVTCI[] Set Control Value Table Cut-In - // 0x1D - function SCVTCI(state) { - var n = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SCVTCI[]', n); } - - state.cvCutIn = n / 0x40; - } - - // DUP[] DUPlicate top stack element - // 0x20 - function DUP(state) { - var stack = state.stack; - - if (exports.DEBUG) { console.log(state.step, 'DUP[]'); } - - stack.push(stack[stack.length - 1]); - } - - // POP[] POP top stack element - // 0x21 - function POP(state) { - if (exports.DEBUG) { console.log(state.step, 'POP[]'); } - - state.stack.pop(); - } - - // CLEAR[] CLEAR the stack - // 0x22 - function CLEAR(state) { - if (exports.DEBUG) { console.log(state.step, 'CLEAR[]'); } - - state.stack.length = 0; - } - - // SWAP[] SWAP the top two elements on the stack - // 0x23 - function SWAP(state) { - var stack = state.stack; - - var a = stack.pop(); - var b = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SWAP[]'); } - - stack.push(a); - stack.push(b); - } - - // DEPTH[] DEPTH of the stack - // 0x24 - function DEPTH(state) { - var stack = state.stack; - - if (exports.DEBUG) { console.log(state.step, 'DEPTH[]'); } - - stack.push(stack.length); - } - - // LOOPCALL[] LOOPCALL function - // 0x2A - function LOOPCALL(state) { - var stack = state.stack; - var fn = stack.pop(); - var c = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'LOOPCALL[]', fn, c); } - - // saves callers program - var cip = state.ip; - var cprog = state.prog; - - state.prog = state.funcs[fn]; - - // executes the function - for (var i = 0; i < c; i++) { - exec(state); - - if (exports.DEBUG) { console.log( - ++state.step, - i + 1 < c ? 'next loopcall' : 'done loopcall', - i - ); } - } - - // restores the callers program - state.ip = cip; - state.prog = cprog; - } - - // CALL[] CALL function - // 0x2B - function CALL(state) { - var fn = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'CALL[]', fn); } - - // saves callers program - var cip = state.ip; - var cprog = state.prog; - - state.prog = state.funcs[fn]; - - // executes the function - exec(state); - - // restores the callers program - state.ip = cip; - state.prog = cprog; - - if (exports.DEBUG) { console.log(++state.step, 'returning from', fn); } - } - - // CINDEX[] Copy the INDEXed element to the top of the stack - // 0x25 - function CINDEX(state) { - var stack = state.stack; - var k = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'CINDEX[]', k); } - - // In case of k == 1, it copies the last element after popping - // thus stack.length - k. - stack.push(stack[stack.length - k]); - } - - // MINDEX[] Move the INDEXed element to the top of the stack - // 0x26 - function MINDEX(state) { - var stack = state.stack; - var k = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'MINDEX[]', k); } - - stack.push(stack.splice(stack.length - k, 1)[0]); - } - - // FDEF[] Function DEFinition - // 0x2C - function FDEF(state) { - if (state.env !== 'fpgm') { throw new Error('FDEF not allowed here'); } - var stack = state.stack; - var prog = state.prog; - var ip = state.ip; - - var fn = stack.pop(); - var ipBegin = ip; - - if (exports.DEBUG) { console.log(state.step, 'FDEF[]', fn); } - - while (prog[++ip] !== 0x2D){ } - - state.ip = ip; - state.funcs[fn] = prog.slice(ipBegin + 1, ip); - } - - // MDAP[a] Move Direct Absolute Point - // 0x2E-0x2F - function MDAP(round, state) { - var pi = state.stack.pop(); - var p = state.z0[pi]; - var fv = state.fv; - var pv = state.pv; - - if (exports.DEBUG) { console.log(state.step, 'MDAP[' + round + ']', pi); } - - var d = pv.distance(p, HPZero); - - if (round) { d = state.round(d); } - - fv.setRelative(p, HPZero, d, pv); - fv.touch(p); - - state.rp0 = state.rp1 = pi; - } - - // IUP[a] Interpolate Untouched Points through the outline - // 0x30 - function IUP(v, state) { - var z2 = state.z2; - var pLen = z2.length - 2; - var cp; - var pp; - var np; - - if (exports.DEBUG) { console.log(state.step, 'IUP[' + v.axis + ']'); } - - for (var i = 0; i < pLen; i++) { - cp = z2[i]; // current point - - // if this point has been touched go on - if (v.touched(cp)) { continue; } - - pp = cp.prevTouched(v); - - // no point on the contour has been touched? - if (pp === cp) { continue; } - - np = cp.nextTouched(v); - - if (pp === np) { - // only one point on the contour has been touched - // so simply moves the point like that - - v.setRelative(cp, cp, v.distance(pp, pp, false, true), v, true); - } - - v.interpolate(cp, pp, np, v); - } - } - - // SHP[] SHift Point using reference point - // 0x32-0x33 - function SHP(a, state) { - var stack = state.stack; - var rpi = a ? state.rp1 : state.rp2; - var rp = (a ? state.z0 : state.z1)[rpi]; - var fv = state.fv; - var pv = state.pv; - var loop = state.loop; - var z2 = state.z2; - - while (loop--) - { - var pi = stack.pop(); - var p = z2[pi]; - - var d = pv.distance(rp, rp, false, true); - fv.setRelative(p, p, d, pv); - fv.touch(p); - - if (exports.DEBUG) { - console.log( - state.step, - (state.loop > 1 ? - 'loop ' + (state.loop - loop) + ': ' : - '' - ) + - 'SHP[' + (a ? 'rp1' : 'rp2') + ']', pi - ); - } - } - - state.loop = 1; - } - - // SHC[] SHift Contour using reference point - // 0x36-0x37 - function SHC(a, state) { - var stack = state.stack; - var rpi = a ? state.rp1 : state.rp2; - var rp = (a ? state.z0 : state.z1)[rpi]; - var fv = state.fv; - var pv = state.pv; - var ci = stack.pop(); - var sp = state.z2[state.contours[ci]]; - var p = sp; - - if (exports.DEBUG) { console.log(state.step, 'SHC[' + a + ']', ci); } - - var d = pv.distance(rp, rp, false, true); - - do { - if (p !== rp) { fv.setRelative(p, p, d, pv); } - p = p.nextPointOnContour; - } while (p !== sp); - } - - // SHZ[] SHift Zone using reference point - // 0x36-0x37 - function SHZ(a, state) { - var stack = state.stack; - var rpi = a ? state.rp1 : state.rp2; - var rp = (a ? state.z0 : state.z1)[rpi]; - var fv = state.fv; - var pv = state.pv; - - var e = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SHZ[' + a + ']', e); } - - var z; - switch (e) { - case 0 : z = state.tZone; break; - case 1 : z = state.gZone; break; - default : throw new Error('Invalid zone'); - } - - var p; - var d = pv.distance(rp, rp, false, true); - var pLen = z.length - 2; - for (var i = 0; i < pLen; i++) - { - p = z[i]; - fv.setRelative(p, p, d, pv); - //if (p !== rp) fv.setRelative(p, p, d, pv); - } - } - - // SHPIX[] SHift point by a PIXel amount - // 0x38 - function SHPIX(state) { - var stack = state.stack; - var loop = state.loop; - var fv = state.fv; - var d = stack.pop() / 0x40; - var z2 = state.z2; - - while (loop--) { - var pi = stack.pop(); - var p = z2[pi]; - - if (exports.DEBUG) { - console.log( - state.step, - (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + - 'SHPIX[]', pi, d - ); - } - - fv.setRelative(p, p, d); - fv.touch(p); - } - - state.loop = 1; - } - - // IP[] Interpolate Point - // 0x39 - function IP(state) { - var stack = state.stack; - var rp1i = state.rp1; - var rp2i = state.rp2; - var loop = state.loop; - var rp1 = state.z0[rp1i]; - var rp2 = state.z1[rp2i]; - var fv = state.fv; - var pv = state.dpv; - var z2 = state.z2; - - while (loop--) { - var pi = stack.pop(); - var p = z2[pi]; - - if (exports.DEBUG) { - console.log( - state.step, - (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + - 'IP[]', pi, rp1i, '<->', rp2i - ); - } - - fv.interpolate(p, rp1, rp2, pv); - - fv.touch(p); - } - - state.loop = 1; - } - - // MSIRP[a] Move Stack Indirect Relative Point - // 0x3A-0x3B - function MSIRP(a, state) { - var stack = state.stack; - var d = stack.pop() / 64; - var pi = stack.pop(); - var p = state.z1[pi]; - var rp0 = state.z0[state.rp0]; - var fv = state.fv; - var pv = state.pv; - - fv.setRelative(p, rp0, d, pv); - fv.touch(p); - - if (exports.DEBUG) { console.log(state.step, 'MSIRP[' + a + ']', d, pi); } - - state.rp1 = state.rp0; - state.rp2 = pi; - if (a) { state.rp0 = pi; } - } - - // ALIGNRP[] Align to reference point. - // 0x3C - function ALIGNRP(state) { - var stack = state.stack; - var rp0i = state.rp0; - var rp0 = state.z0[rp0i]; - var loop = state.loop; - var fv = state.fv; - var pv = state.pv; - var z1 = state.z1; - - while (loop--) { - var pi = stack.pop(); - var p = z1[pi]; - - if (exports.DEBUG) { - console.log( - state.step, - (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + - 'ALIGNRP[]', pi - ); - } - - fv.setRelative(p, rp0, 0, pv); - fv.touch(p); - } - - state.loop = 1; - } - - // RTG[] Round To Double Grid - // 0x3D - function RTDG(state) { - if (exports.DEBUG) { console.log(state.step, 'RTDG[]'); } - - state.round = roundToDoubleGrid; - } - - // MIAP[a] Move Indirect Absolute Point - // 0x3E-0x3F - function MIAP(round, state) { - var stack = state.stack; - var n = stack.pop(); - var pi = stack.pop(); - var p = state.z0[pi]; - var fv = state.fv; - var pv = state.pv; - var cv = state.cvt[n]; - - if (exports.DEBUG) { - console.log( - state.step, - 'MIAP[' + round + ']', - n, '(', cv, ')', pi - ); - } - - var d = pv.distance(p, HPZero); - - if (round) { - if (Math.abs(d - cv) < state.cvCutIn) { d = cv; } - - d = state.round(d); - } - - fv.setRelative(p, HPZero, d, pv); - - if (state.zp0 === 0) { - p.xo = p.x; - p.yo = p.y; - } - - fv.touch(p); - - state.rp0 = state.rp1 = pi; - } - - // NPUSB[] PUSH N Bytes - // 0x40 - function NPUSHB(state) { - var prog = state.prog; - var ip = state.ip; - var stack = state.stack; - - var n = prog[++ip]; - - if (exports.DEBUG) { console.log(state.step, 'NPUSHB[]', n); } - - for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } - - state.ip = ip; - } - - // NPUSHW[] PUSH N Words - // 0x41 - function NPUSHW(state) { - var ip = state.ip; - var prog = state.prog; - var stack = state.stack; - var n = prog[++ip]; - - if (exports.DEBUG) { console.log(state.step, 'NPUSHW[]', n); } - - for (var i = 0; i < n; i++) { - var w = (prog[++ip] << 8) | prog[++ip]; - if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } - stack.push(w); - } - - state.ip = ip; - } - - // WS[] Write Store - // 0x42 - function WS(state) { - var stack = state.stack; - var store = state.store; - - if (!store) { store = state.store = []; } - - var v = stack.pop(); - var l = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'WS', v, l); } - - store[l] = v; - } - - // RS[] Read Store - // 0x43 - function RS(state) { - var stack = state.stack; - var store = state.store; - - var l = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'RS', l); } - - var v = (store && store[l]) || 0; - - stack.push(v); - } - - // WCVTP[] Write Control Value Table in Pixel units - // 0x44 - function WCVTP(state) { - var stack = state.stack; - - var v = stack.pop(); - var l = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'WCVTP', v, l); } - - state.cvt[l] = v / 0x40; - } - - // RCVT[] Read Control Value Table entry - // 0x45 - function RCVT(state) { - var stack = state.stack; - var cvte = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'RCVT', cvte); } - - stack.push(state.cvt[cvte] * 0x40); - } - - // GC[] Get Coordinate projected onto the projection vector - // 0x46-0x47 - function GC(a, state) { - var stack = state.stack; - var pi = stack.pop(); - var p = state.z2[pi]; - - if (exports.DEBUG) { console.log(state.step, 'GC[' + a + ']', pi); } - - stack.push(state.dpv.distance(p, HPZero, a, false) * 0x40); - } - - // MD[a] Measure Distance - // 0x49-0x4A - function MD(a, state) { - var stack = state.stack; - var pi2 = stack.pop(); - var pi1 = stack.pop(); - var p2 = state.z1[pi2]; - var p1 = state.z0[pi1]; - var d = state.dpv.distance(p1, p2, a, a); - - if (exports.DEBUG) { console.log(state.step, 'MD[' + a + ']', pi2, pi1, '->', d); } - - state.stack.push(Math.round(d * 64)); - } - - // MPPEM[] Measure Pixels Per EM - // 0x4B - function MPPEM(state) { - if (exports.DEBUG) { console.log(state.step, 'MPPEM[]'); } - state.stack.push(state.ppem); - } - - // FLIPON[] set the auto FLIP Boolean to ON - // 0x4D - function FLIPON(state) { - if (exports.DEBUG) { console.log(state.step, 'FLIPON[]'); } - state.autoFlip = true; - } - - // LT[] Less Than - // 0x50 - function LT(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'LT[]', e2, e1); } - - stack.push(e1 < e2 ? 1 : 0); - } - - // LTEQ[] Less Than or EQual - // 0x53 - function LTEQ(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'LTEQ[]', e2, e1); } - - stack.push(e1 <= e2 ? 1 : 0); - } - - // GTEQ[] Greater Than - // 0x52 - function GT(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'GT[]', e2, e1); } - - stack.push(e1 > e2 ? 1 : 0); - } - - // GTEQ[] Greater Than or EQual - // 0x53 - function GTEQ(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'GTEQ[]', e2, e1); } - - stack.push(e1 >= e2 ? 1 : 0); - } - - // EQ[] EQual - // 0x54 - function EQ(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'EQ[]', e2, e1); } - - stack.push(e2 === e1 ? 1 : 0); - } - - // NEQ[] Not EQual - // 0x55 - function NEQ(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'NEQ[]', e2, e1); } - - stack.push(e2 !== e1 ? 1 : 0); - } - - // ODD[] ODD - // 0x56 - function ODD(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'ODD[]', n); } - - stack.push(Math.trunc(n) % 2 ? 1 : 0); - } - - // EVEN[] EVEN - // 0x57 - function EVEN(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'EVEN[]', n); } - - stack.push(Math.trunc(n) % 2 ? 0 : 1); - } - - // IF[] IF test - // 0x58 - function IF(state) { - var test = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'IF[]', test); } - - // if test is true it just continues - // if not the ip is skipped until matching ELSE or EIF - if (!test) { - skip(state, true); - - if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } - } - } - - // EIF[] End IF - // 0x59 - function EIF(state) { - // this can be reached normally when - // executing an else branch. - // -> just ignore it - - if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } - } - - // AND[] logical AND - // 0x5A - function AND(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'AND[]', e2, e1); } - - stack.push(e2 && e1 ? 1 : 0); - } - - // OR[] logical OR - // 0x5B - function OR(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'OR[]', e2, e1); } - - stack.push(e2 || e1 ? 1 : 0); - } - - // NOT[] logical NOT - // 0x5C - function NOT(state) { - var stack = state.stack; - var e = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'NOT[]', e); } - - stack.push(e ? 0 : 1); - } - - // DELTAP1[] DELTA exception P1 - // DELTAP2[] DELTA exception P2 - // DELTAP3[] DELTA exception P3 - // 0x5D, 0x71, 0x72 - function DELTAP123(b, state) { - var stack = state.stack; - var n = stack.pop(); - var fv = state.fv; - var pv = state.pv; - var ppem = state.ppem; - var base = state.deltaBase + (b - 1) * 16; - var ds = state.deltaShift; - var z0 = state.z0; - - if (exports.DEBUG) { console.log(state.step, 'DELTAP[' + b + ']', n, stack); } - - for (var i = 0; i < n; i++) { - var pi = stack.pop(); - var arg = stack.pop(); - var appem = base + ((arg & 0xF0) >> 4); - if (appem !== ppem) { continue; } - - var mag = (arg & 0x0F) - 8; - if (mag >= 0) { mag++; } - if (exports.DEBUG) { console.log(state.step, 'DELTAPFIX', pi, 'by', mag * ds); } - - var p = z0[pi]; - fv.setRelative(p, p, mag * ds, pv); - } - } - - // SDB[] Set Delta Base in the graphics state - // 0x5E - function SDB(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SDB[]', n); } - - state.deltaBase = n; - } - - // SDS[] Set Delta Shift in the graphics state - // 0x5F - function SDS(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SDS[]', n); } - - state.deltaShift = Math.pow(0.5, n); - } - - // ADD[] ADD - // 0x60 - function ADD(state) { - var stack = state.stack; - var n2 = stack.pop(); - var n1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'ADD[]', n2, n1); } - - stack.push(n1 + n2); - } - - // SUB[] SUB - // 0x61 - function SUB(state) { - var stack = state.stack; - var n2 = stack.pop(); - var n1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SUB[]', n2, n1); } - - stack.push(n1 - n2); - } - - // DIV[] DIV - // 0x62 - function DIV(state) { - var stack = state.stack; - var n2 = stack.pop(); - var n1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'DIV[]', n2, n1); } - - stack.push(n1 * 64 / n2); - } - - // MUL[] MUL - // 0x63 - function MUL(state) { - var stack = state.stack; - var n2 = stack.pop(); - var n1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'MUL[]', n2, n1); } - - stack.push(n1 * n2 / 64); - } - - // ABS[] ABSolute value - // 0x64 - function ABS(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'ABS[]', n); } - - stack.push(Math.abs(n)); - } - - // NEG[] NEGate - // 0x65 - function NEG(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'NEG[]', n); } - - stack.push(-n); - } - - // FLOOR[] FLOOR - // 0x66 - function FLOOR(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'FLOOR[]', n); } - - stack.push(Math.floor(n / 0x40) * 0x40); - } - - // CEILING[] CEILING - // 0x67 - function CEILING(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'CEILING[]', n); } - - stack.push(Math.ceil(n / 0x40) * 0x40); - } - - // ROUND[ab] ROUND value - // 0x68-0x6B - function ROUND(dt, state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'ROUND[]'); } - - stack.push(state.round(n / 0x40) * 0x40); - } - - // WCVTF[] Write Control Value Table in Funits - // 0x70 - function WCVTF(state) { - var stack = state.stack; - var v = stack.pop(); - var l = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'WCVTF[]', v, l); } - - state.cvt[l] = v * state.ppem / state.font.unitsPerEm; - } - - // DELTAC1[] DELTA exception C1 - // DELTAC2[] DELTA exception C2 - // DELTAC3[] DELTA exception C3 - // 0x73, 0x74, 0x75 - function DELTAC123(b, state) { - var stack = state.stack; - var n = stack.pop(); - var ppem = state.ppem; - var base = state.deltaBase + (b - 1) * 16; - var ds = state.deltaShift; - - if (exports.DEBUG) { console.log(state.step, 'DELTAC[' + b + ']', n, stack); } - - for (var i = 0; i < n; i++) { - var c = stack.pop(); - var arg = stack.pop(); - var appem = base + ((arg & 0xF0) >> 4); - if (appem !== ppem) { continue; } - - var mag = (arg & 0x0F) - 8; - if (mag >= 0) { mag++; } - - var delta = mag * ds; - - if (exports.DEBUG) { console.log(state.step, 'DELTACFIX', c, 'by', delta); } - - state.cvt[c] += delta; - } - } - - // SROUND[] Super ROUND - // 0x76 - function SROUND(state) { - var n = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SROUND[]', n); } - - state.round = roundSuper; - - var period; - - switch (n & 0xC0) { - case 0x00: - period = 0.5; - break; - case 0x40: - period = 1; - break; - case 0x80: - period = 2; - break; - default: - throw new Error('invalid SROUND value'); - } - - state.srPeriod = period; - - switch (n & 0x30) { - case 0x00: - state.srPhase = 0; - break; - case 0x10: - state.srPhase = 0.25 * period; - break; - case 0x20: - state.srPhase = 0.5 * period; - break; - case 0x30: - state.srPhase = 0.75 * period; - break; - default: throw new Error('invalid SROUND value'); - } - - n &= 0x0F; - - if (n === 0) { state.srThreshold = 0; } - else { state.srThreshold = (n / 8 - 0.5) * period; } - } - - // S45ROUND[] Super ROUND 45 degrees - // 0x77 - function S45ROUND(state) { - var n = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'S45ROUND[]', n); } - - state.round = roundSuper; - - var period; - - switch (n & 0xC0) { - case 0x00: - period = Math.sqrt(2) / 2; - break; - case 0x40: - period = Math.sqrt(2); - break; - case 0x80: - period = 2 * Math.sqrt(2); - break; - default: - throw new Error('invalid S45ROUND value'); - } - - state.srPeriod = period; - - switch (n & 0x30) { - case 0x00: - state.srPhase = 0; - break; - case 0x10: - state.srPhase = 0.25 * period; - break; - case 0x20: - state.srPhase = 0.5 * period; - break; - case 0x30: - state.srPhase = 0.75 * period; - break; - default: - throw new Error('invalid S45ROUND value'); - } - - n &= 0x0F; - - if (n === 0) { state.srThreshold = 0; } - else { state.srThreshold = (n / 8 - 0.5) * period; } - } - - // ROFF[] Round Off - // 0x7A - function ROFF(state) { - if (exports.DEBUG) { console.log(state.step, 'ROFF[]'); } - - state.round = roundOff; - } - - // RUTG[] Round Up To Grid - // 0x7C - function RUTG(state) { - if (exports.DEBUG) { console.log(state.step, 'RUTG[]'); } - - state.round = roundUpToGrid; - } - - // RDTG[] Round Down To Grid - // 0x7D - function RDTG(state) { - if (exports.DEBUG) { console.log(state.step, 'RDTG[]'); } - - state.round = roundDownToGrid; - } - - // SCANCTRL[] SCAN conversion ConTRoL - // 0x85 - function SCANCTRL(state) { - var n = state.stack.pop(); - - // ignored by opentype.js - - if (exports.DEBUG) { console.log(state.step, 'SCANCTRL[]', n); } - } - - // SDPVTL[a] Set Dual Projection Vector To Line - // 0x86-0x87 - function SDPVTL(a, state) { - var stack = state.stack; - var p2i = stack.pop(); - var p1i = stack.pop(); - var p2 = state.z2[p2i]; - var p1 = state.z1[p1i]; - - if (exports.DEBUG) { console.log(state.step, 'SDPVTL[' + a + ']', p2i, p1i); } - - var dx; - var dy; - - if (!a) { - dx = p1.x - p2.x; - dy = p1.y - p2.y; - } else { - dx = p2.y - p1.y; - dy = p1.x - p2.x; - } - - state.dpv = getUnitVector(dx, dy); - } - - // GETINFO[] GET INFOrmation - // 0x88 - function GETINFO(state) { - var stack = state.stack; - var sel = stack.pop(); - var r = 0; - - if (exports.DEBUG) { console.log(state.step, 'GETINFO[]', sel); } - - // v35 as in no subpixel hinting - if (sel & 0x01) { r = 35; } - - // TODO rotation and stretch currently not supported - // and thus those GETINFO are always 0. - - // opentype.js is always gray scaling - if (sel & 0x20) { r |= 0x1000; } - - stack.push(r); - } - - // ROLL[] ROLL the top three stack elements - // 0x8A - function ROLL(state) { - var stack = state.stack; - var a = stack.pop(); - var b = stack.pop(); - var c = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'ROLL[]'); } - - stack.push(b); - stack.push(a); - stack.push(c); - } - - // MAX[] MAXimum of top two stack elements - // 0x8B - function MAX(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'MAX[]', e2, e1); } - - stack.push(Math.max(e1, e2)); - } - - // MIN[] MINimum of top two stack elements - // 0x8C - function MIN(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'MIN[]', e2, e1); } - - stack.push(Math.min(e1, e2)); - } - - // SCANTYPE[] SCANTYPE - // 0x8D - function SCANTYPE(state) { - var n = state.stack.pop(); - // ignored by opentype.js - if (exports.DEBUG) { console.log(state.step, 'SCANTYPE[]', n); } - } - - // INSTCTRL[] INSTCTRL - // 0x8D - function INSTCTRL(state) { - var s = state.stack.pop(); - var v = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'INSTCTRL[]', s, v); } - - switch (s) { - case 1 : state.inhibitGridFit = !!v; return; - case 2 : state.ignoreCvt = !!v; return; - default: throw new Error('invalid INSTCTRL[] selector'); - } - } - - // PUSHB[abc] PUSH Bytes - // 0xB0-0xB7 - function PUSHB(n, state) { - var stack = state.stack; - var prog = state.prog; - var ip = state.ip; - - if (exports.DEBUG) { console.log(state.step, 'PUSHB[' + n + ']'); } - - for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } - - state.ip = ip; - } - - // PUSHW[abc] PUSH Words - // 0xB8-0xBF - function PUSHW(n, state) { - var ip = state.ip; - var prog = state.prog; - var stack = state.stack; - - if (exports.DEBUG) { console.log(state.ip, 'PUSHW[' + n + ']'); } - - for (var i = 0; i < n; i++) { - var w = (prog[++ip] << 8) | prog[++ip]; - if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } - stack.push(w); - } - - state.ip = ip; - } - - // MDRP[abcde] Move Direct Relative Point - // 0xD0-0xEF - // (if indirect is 0) - // - // and - // - // MIRP[abcde] Move Indirect Relative Point - // 0xE0-0xFF - // (if indirect is 1) - - function MDRP_MIRP(indirect, setRp0, keepD, ro, dt, state) { - var stack = state.stack; - var cvte = indirect && stack.pop(); - var pi = stack.pop(); - var rp0i = state.rp0; - var rp = state.z0[rp0i]; - var p = state.z1[pi]; - - var md = state.minDis; - var fv = state.fv; - var pv = state.dpv; - var od; // original distance - var d; // moving distance - var sign; // sign of distance - var cv; - - d = od = pv.distance(p, rp, true, true); - sign = d >= 0 ? 1 : -1; // Math.sign would be 0 in case of 0 - - // TODO consider autoFlip - d = Math.abs(d); - - if (indirect) { - cv = state.cvt[cvte]; - - if (ro && Math.abs(d - cv) < state.cvCutIn) { d = cv; } - } - - if (keepD && d < md) { d = md; } - - if (ro) { d = state.round(d); } - - fv.setRelative(p, rp, sign * d, pv); - fv.touch(p); - - if (exports.DEBUG) { - console.log( - state.step, - (indirect ? 'MIRP[' : 'MDRP[') + - (setRp0 ? 'M' : 'm') + - (keepD ? '>' : '_') + - (ro ? 'R' : '_') + - (dt === 0 ? 'Gr' : (dt === 1 ? 'Bl' : (dt === 2 ? 'Wh' : ''))) + - ']', - indirect ? - cvte + '(' + state.cvt[cvte] + ',' + cv + ')' : - '', - pi, - '(d =', od, '->', sign * d, ')' - ); - } - - state.rp1 = state.rp0; - state.rp2 = pi; - if (setRp0) { state.rp0 = pi; } - } - - /* - * The instruction table. - */ - instructionTable = [ - /* 0x00 */ SVTCA.bind(undefined, yUnitVector), - /* 0x01 */ SVTCA.bind(undefined, xUnitVector), - /* 0x02 */ SPVTCA.bind(undefined, yUnitVector), - /* 0x03 */ SPVTCA.bind(undefined, xUnitVector), - /* 0x04 */ SFVTCA.bind(undefined, yUnitVector), - /* 0x05 */ SFVTCA.bind(undefined, xUnitVector), - /* 0x06 */ SPVTL.bind(undefined, 0), - /* 0x07 */ SPVTL.bind(undefined, 1), - /* 0x08 */ SFVTL.bind(undefined, 0), - /* 0x09 */ SFVTL.bind(undefined, 1), - /* 0x0A */ SPVFS, - /* 0x0B */ SFVFS, - /* 0x0C */ GPV, - /* 0x0D */ GFV, - /* 0x0E */ SFVTPV, - /* 0x0F */ ISECT, - /* 0x10 */ SRP0, - /* 0x11 */ SRP1, - /* 0x12 */ SRP2, - /* 0x13 */ SZP0, - /* 0x14 */ SZP1, - /* 0x15 */ SZP2, - /* 0x16 */ SZPS, - /* 0x17 */ SLOOP, - /* 0x18 */ RTG, - /* 0x19 */ RTHG, - /* 0x1A */ SMD, - /* 0x1B */ ELSE, - /* 0x1C */ JMPR, - /* 0x1D */ SCVTCI, - /* 0x1E */ undefined, // TODO SSWCI - /* 0x1F */ undefined, // TODO SSW - /* 0x20 */ DUP, - /* 0x21 */ POP, - /* 0x22 */ CLEAR, - /* 0x23 */ SWAP, - /* 0x24 */ DEPTH, - /* 0x25 */ CINDEX, - /* 0x26 */ MINDEX, - /* 0x27 */ undefined, // TODO ALIGNPTS - /* 0x28 */ undefined, - /* 0x29 */ undefined, // TODO UTP - /* 0x2A */ LOOPCALL, - /* 0x2B */ CALL, - /* 0x2C */ FDEF, - /* 0x2D */ undefined, // ENDF (eaten by FDEF) - /* 0x2E */ MDAP.bind(undefined, 0), - /* 0x2F */ MDAP.bind(undefined, 1), - /* 0x30 */ IUP.bind(undefined, yUnitVector), - /* 0x31 */ IUP.bind(undefined, xUnitVector), - /* 0x32 */ SHP.bind(undefined, 0), - /* 0x33 */ SHP.bind(undefined, 1), - /* 0x34 */ SHC.bind(undefined, 0), - /* 0x35 */ SHC.bind(undefined, 1), - /* 0x36 */ SHZ.bind(undefined, 0), - /* 0x37 */ SHZ.bind(undefined, 1), - /* 0x38 */ SHPIX, - /* 0x39 */ IP, - /* 0x3A */ MSIRP.bind(undefined, 0), - /* 0x3B */ MSIRP.bind(undefined, 1), - /* 0x3C */ ALIGNRP, - /* 0x3D */ RTDG, - /* 0x3E */ MIAP.bind(undefined, 0), - /* 0x3F */ MIAP.bind(undefined, 1), - /* 0x40 */ NPUSHB, - /* 0x41 */ NPUSHW, - /* 0x42 */ WS, - /* 0x43 */ RS, - /* 0x44 */ WCVTP, - /* 0x45 */ RCVT, - /* 0x46 */ GC.bind(undefined, 0), - /* 0x47 */ GC.bind(undefined, 1), - /* 0x48 */ undefined, // TODO SCFS - /* 0x49 */ MD.bind(undefined, 0), - /* 0x4A */ MD.bind(undefined, 1), - /* 0x4B */ MPPEM, - /* 0x4C */ undefined, // TODO MPS - /* 0x4D */ FLIPON, - /* 0x4E */ undefined, // TODO FLIPOFF - /* 0x4F */ undefined, // TODO DEBUG - /* 0x50 */ LT, - /* 0x51 */ LTEQ, - /* 0x52 */ GT, - /* 0x53 */ GTEQ, - /* 0x54 */ EQ, - /* 0x55 */ NEQ, - /* 0x56 */ ODD, - /* 0x57 */ EVEN, - /* 0x58 */ IF, - /* 0x59 */ EIF, - /* 0x5A */ AND, - /* 0x5B */ OR, - /* 0x5C */ NOT, - /* 0x5D */ DELTAP123.bind(undefined, 1), - /* 0x5E */ SDB, - /* 0x5F */ SDS, - /* 0x60 */ ADD, - /* 0x61 */ SUB, - /* 0x62 */ DIV, - /* 0x63 */ MUL, - /* 0x64 */ ABS, - /* 0x65 */ NEG, - /* 0x66 */ FLOOR, - /* 0x67 */ CEILING, - /* 0x68 */ ROUND.bind(undefined, 0), - /* 0x69 */ ROUND.bind(undefined, 1), - /* 0x6A */ ROUND.bind(undefined, 2), - /* 0x6B */ ROUND.bind(undefined, 3), - /* 0x6C */ undefined, // TODO NROUND[ab] - /* 0x6D */ undefined, // TODO NROUND[ab] - /* 0x6E */ undefined, // TODO NROUND[ab] - /* 0x6F */ undefined, // TODO NROUND[ab] - /* 0x70 */ WCVTF, - /* 0x71 */ DELTAP123.bind(undefined, 2), - /* 0x72 */ DELTAP123.bind(undefined, 3), - /* 0x73 */ DELTAC123.bind(undefined, 1), - /* 0x74 */ DELTAC123.bind(undefined, 2), - /* 0x75 */ DELTAC123.bind(undefined, 3), - /* 0x76 */ SROUND, - /* 0x77 */ S45ROUND, - /* 0x78 */ undefined, // TODO JROT[] - /* 0x79 */ undefined, // TODO JROF[] - /* 0x7A */ ROFF, - /* 0x7B */ undefined, - /* 0x7C */ RUTG, - /* 0x7D */ RDTG, - /* 0x7E */ POP, // actually SANGW, supposed to do only a pop though - /* 0x7F */ POP, // actually AA, supposed to do only a pop though - /* 0x80 */ undefined, // TODO FLIPPT - /* 0x81 */ undefined, // TODO FLIPRGON - /* 0x82 */ undefined, // TODO FLIPRGOFF - /* 0x83 */ undefined, - /* 0x84 */ undefined, - /* 0x85 */ SCANCTRL, - /* 0x86 */ SDPVTL.bind(undefined, 0), - /* 0x87 */ SDPVTL.bind(undefined, 1), - /* 0x88 */ GETINFO, - /* 0x89 */ undefined, // TODO IDEF - /* 0x8A */ ROLL, - /* 0x8B */ MAX, - /* 0x8C */ MIN, - /* 0x8D */ SCANTYPE, - /* 0x8E */ INSTCTRL, - /* 0x8F */ undefined, - /* 0x90 */ undefined, - /* 0x91 */ undefined, - /* 0x92 */ undefined, - /* 0x93 */ undefined, - /* 0x94 */ undefined, - /* 0x95 */ undefined, - /* 0x96 */ undefined, - /* 0x97 */ undefined, - /* 0x98 */ undefined, - /* 0x99 */ undefined, - /* 0x9A */ undefined, - /* 0x9B */ undefined, - /* 0x9C */ undefined, - /* 0x9D */ undefined, - /* 0x9E */ undefined, - /* 0x9F */ undefined, - /* 0xA0 */ undefined, - /* 0xA1 */ undefined, - /* 0xA2 */ undefined, - /* 0xA3 */ undefined, - /* 0xA4 */ undefined, - /* 0xA5 */ undefined, - /* 0xA6 */ undefined, - /* 0xA7 */ undefined, - /* 0xA8 */ undefined, - /* 0xA9 */ undefined, - /* 0xAA */ undefined, - /* 0xAB */ undefined, - /* 0xAC */ undefined, - /* 0xAD */ undefined, - /* 0xAE */ undefined, - /* 0xAF */ undefined, - /* 0xB0 */ PUSHB.bind(undefined, 1), - /* 0xB1 */ PUSHB.bind(undefined, 2), - /* 0xB2 */ PUSHB.bind(undefined, 3), - /* 0xB3 */ PUSHB.bind(undefined, 4), - /* 0xB4 */ PUSHB.bind(undefined, 5), - /* 0xB5 */ PUSHB.bind(undefined, 6), - /* 0xB6 */ PUSHB.bind(undefined, 7), - /* 0xB7 */ PUSHB.bind(undefined, 8), - /* 0xB8 */ PUSHW.bind(undefined, 1), - /* 0xB9 */ PUSHW.bind(undefined, 2), - /* 0xBA */ PUSHW.bind(undefined, 3), - /* 0xBB */ PUSHW.bind(undefined, 4), - /* 0xBC */ PUSHW.bind(undefined, 5), - /* 0xBD */ PUSHW.bind(undefined, 6), - /* 0xBE */ PUSHW.bind(undefined, 7), - /* 0xBF */ PUSHW.bind(undefined, 8), - /* 0xC0 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 0), - /* 0xC1 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 1), - /* 0xC2 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 2), - /* 0xC3 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 3), - /* 0xC4 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 0), - /* 0xC5 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 1), - /* 0xC6 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 2), - /* 0xC7 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 3), - /* 0xC8 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 0), - /* 0xC9 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 1), - /* 0xCA */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 2), - /* 0xCB */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 3), - /* 0xCC */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 0), - /* 0xCD */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 1), - /* 0xCE */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 2), - /* 0xCF */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 3), - /* 0xD0 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 0), - /* 0xD1 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 1), - /* 0xD2 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 2), - /* 0xD3 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 3), - /* 0xD4 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 0), - /* 0xD5 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 1), - /* 0xD6 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 2), - /* 0xD7 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 3), - /* 0xD8 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 0), - /* 0xD9 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 1), - /* 0xDA */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 2), - /* 0xDB */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 3), - /* 0xDC */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 0), - /* 0xDD */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 1), - /* 0xDE */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 2), - /* 0xDF */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 3), - /* 0xE0 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 0), - /* 0xE1 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 1), - /* 0xE2 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 2), - /* 0xE3 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 3), - /* 0xE4 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 0), - /* 0xE5 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 1), - /* 0xE6 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 2), - /* 0xE7 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 3), - /* 0xE8 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 0), - /* 0xE9 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 1), - /* 0xEA */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 2), - /* 0xEB */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 3), - /* 0xEC */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 0), - /* 0xED */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 1), - /* 0xEE */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 2), - /* 0xEF */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 3), - /* 0xF0 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 0), - /* 0xF1 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 1), - /* 0xF2 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 2), - /* 0xF3 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 3), - /* 0xF4 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 0), - /* 0xF5 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 1), - /* 0xF6 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 2), - /* 0xF7 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 3), - /* 0xF8 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 0), - /* 0xF9 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 1), - /* 0xFA */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 2), - /* 0xFB */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 3), - /* 0xFC */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 0), - /* 0xFD */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 1), - /* 0xFE */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 2), - /* 0xFF */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 3) - ]; - - /***************************** - Mathematical Considerations - ****************************** - - fv ... refers to freedom vector - pv ... refers to projection vector - rp ... refers to reference point - p ... refers to to point being operated on - d ... refers to distance - - SETRELATIVE: - ============ - - case freedom vector == x-axis: - ------------------------------ - - (pv) - .-' - rpd .-' - .-* - d .-'90°' - .-' ' - .-' ' - *-' ' b - rp ' - ' - ' - p *----------*-------------- (fv) - pm - - rpdx = rpx + d * pv.x - rpdy = rpy + d * pv.y - - equation of line b - - y - rpdy = pvns * (x- rpdx) - - y = p.y - - x = rpdx + ( p.y - rpdy ) / pvns - - - case freedom vector == y-axis: - ------------------------------ - - * pm - |\ - | \ - | \ - | \ - | \ - | \ - | \ - | \ - | \ - | \ b - | \ - | \ - | \ .-' (pv) - | 90° \.-' - | .-'* rpd - | .-' - * *-' d - p rp - - rpdx = rpx + d * pv.x - rpdy = rpy + d * pv.y - - equation of line b: - pvns ... normal slope to pv - - y - rpdy = pvns * (x - rpdx) - - x = p.x - - y = rpdy + pvns * (p.x - rpdx) - - - - generic case: - ------------- - - - .'(fv) - .' - .* pm - .' ! - .' . - .' ! - .' . b - .' ! - * . - p ! - 90° . ... (pv) - ...-*-''' - ...---''' rpd - ...---''' d - *--''' - rp - - rpdx = rpx + d * pv.x - rpdy = rpy + d * pv.y - - equation of line b: - pvns... normal slope to pv - - y - rpdy = pvns * (x - rpdx) - - equation of freedom vector line: - fvs ... slope of freedom vector (=fy/fx) - - y - py = fvs * (x - px) - - - on pm both equations are true for same x/y - - y - rpdy = pvns * (x - rpdx) - - y - py = fvs * (x - px) - - form to y and set equal: - - pvns * (x - rpdx) + rpdy = fvs * (x - px) + py - - expand: - - pvns * x - pvns * rpdx + rpdy = fvs * x - fvs * px + py - - switch: - - fvs * x - fvs * px + py = pvns * x - pvns * rpdx + rpdy - - solve for x: - - fvs * x - pvns * x = fvs * px - pvns * rpdx - py + rpdy - - - - fvs * px - pvns * rpdx + rpdy - py - x = ----------------------------------- - fvs - pvns - - and: - - y = fvs * (x - px) + py - - - - INTERPOLATE: - ============ - - Examples of point interpolation. - - The weight of the movement of the reference point gets bigger - the further the other reference point is away, thus the safest - option (that is avoiding 0/0 divisions) is to weight the - original distance of the other point by the sum of both distances. - - If the sum of both distances is 0, then move the point by the - arithmetic average of the movement of both reference points. - - - - - (+6) - rp1o *---->*rp1 - . . (+12) - . . rp2o *---------->* rp2 - . . . . - . . . . - . 10 20 . . - |.........|...................| . - . . . - . . (+8) . - po *------>*p . - . . . - . 12 . 24 . - |...........|.......................| - 36 - - - ------- - - - - (+10) - rp1o *-------->*rp1 - . . (-10) - . . rp2 *<---------* rpo2 - . . . . - . . . . - . 10 . 30 . . - |.........|.............................| - . . - . (+5) . - po *--->* p . - . . . - . . 20 . - |....|..............| - 5 15 - - - ------- - - - (+10) - rp1o *-------->*rp1 - . . - . . - rp2o *-------->*rp2 - - - (+10) - po *-------->* p - - ------- - - - (+10) - rp1o *-------->*rp1 - . . - . .(+30) - rp2o *---------------------------->*rp2 - - - (+25) - po *----------------------->* p - - - - vim: set ts=4 sw=4 expandtab: - *****/ - - /** - * Converts a string into a list of tokens. - */ - - /** - * Create a new token - * @param {string} char a single char - */ - function Token(char) { - this.char = char; - this.state = {}; - this.activeState = null; - } - - /** - * Create a new context range - * @param {number} startIndex range start index - * @param {number} endOffset range end index offset - * @param {string} contextName owner context name - */ - function ContextRange(startIndex, endOffset, contextName) { - this.contextName = contextName; - this.startIndex = startIndex; - this.endOffset = endOffset; - } - - /** - * Check context start and end - * @param {string} contextName a unique context name - * @param {function} checkStart a predicate function the indicates a context's start - * @param {function} checkEnd a predicate function the indicates a context's end - */ - function ContextChecker(contextName, checkStart, checkEnd) { - this.contextName = contextName; - this.openRange = null; - this.ranges = []; - this.checkStart = checkStart; - this.checkEnd = checkEnd; - } - - /** - * @typedef ContextParams - * @type Object - * @property {array} context context items - * @property {number} currentIndex current item index - */ - - /** - * Create a context params - * @param {array} context a list of items - * @param {number} currentIndex current item index - */ - function ContextParams(context, currentIndex) { - this.context = context; - this.index = currentIndex; - this.length = context.length; - this.current = context[currentIndex]; - this.backtrack = context.slice(0, currentIndex); - this.lookahead = context.slice(currentIndex + 1); - } - - /** - * Create an event instance - * @param {string} eventId event unique id - */ - function Event(eventId) { - this.eventId = eventId; - this.subscribers = []; - } - - /** - * Initialize a core events and auto subscribe required event handlers - * @param {any} events an object that enlists core events handlers - */ - function initializeCoreEvents(events) { - var this$1 = this; - - var coreEvents = [ - 'start', 'end', 'next', 'newToken', 'contextStart', - 'contextEnd', 'insertToken', 'removeToken', 'removeRange', - 'replaceToken', 'replaceRange', 'composeRUD', 'updateContextsRanges' - ]; - - coreEvents.forEach(function (eventId) { - Object.defineProperty(this$1.events, eventId, { - value: new Event(eventId) - }); - }); - - if (!!events) { - coreEvents.forEach(function (eventId) { - var event = events[eventId]; - if (typeof event === 'function') { - this$1.events[eventId].subscribe(event); - } - }); - } - var requiresContextUpdate = [ - 'insertToken', 'removeToken', 'removeRange', - 'replaceToken', 'replaceRange', 'composeRUD' - ]; - requiresContextUpdate.forEach(function (eventId) { - this$1.events[eventId].subscribe( - this$1.updateContextsRanges - ); - }); - } - - /** - * Converts a string into a list of tokens - * @param {any} events tokenizer core events - */ - function Tokenizer(events) { - this.tokens = []; - this.registeredContexts = {}; - this.contextCheckers = []; - this.events = {}; - this.registeredModifiers = []; - - initializeCoreEvents.call(this, events); - } - - /** - * Sets the state of a token, usually called by a state modifier. - * @param {string} key state item key - * @param {any} value state item value - */ - Token.prototype.setState = function(key, value) { - this.state[key] = value; - this.activeState = { key: key, value: this.state[key] }; - return this.activeState; - }; - - Token.prototype.getState = function (stateId) { - return this.state[stateId] || null; - }; - - /** - * Checks if an index exists in the tokens list. - * @param {number} index token index - */ - Tokenizer.prototype.inboundIndex = function(index) { - return index >= 0 && index < this.tokens.length; - }; - - /** - * Compose and apply a list of operations (replace, update, delete) - * @param {array} RUDs replace, update and delete operations - * TODO: Perf. Optimization (lengthBefore === lengthAfter ? dispatch once) - */ - Tokenizer.prototype.composeRUD = function (RUDs) { - var this$1 = this; - - var silent = true; - var state = RUDs.map(function (RUD) { return ( - this$1[RUD[0]].apply(this$1, RUD.slice(1).concat(silent)) - ); }); - var hasFAILObject = function (obj) { return ( - typeof obj === 'object' && - obj.hasOwnProperty('FAIL') - ); }; - if (state.every(hasFAILObject)) { - return { - FAIL: "composeRUD: one or more operations hasn't completed successfully", - report: state.filter(hasFAILObject) - }; - } - this.dispatch('composeRUD', [state.filter(function (op) { return !hasFAILObject(op); })]); - }; - - /** - * Replace a range of tokens with a list of tokens - * @param {number} startIndex range start index - * @param {number} offset range offset - * @param {token} tokens a list of tokens to replace - * @param {boolean} silent dispatch events and update context ranges - */ - Tokenizer.prototype.replaceRange = function (startIndex, offset, tokens, silent) { - offset = offset !== null ? offset : this.tokens.length; - var isTokenType = tokens.every(function (token) { return token instanceof Token; }); - if (!isNaN(startIndex) && this.inboundIndex(startIndex) && isTokenType) { - var replaced = this.tokens.splice.apply( - this.tokens, [startIndex, offset].concat(tokens) - ); - if (!silent) { this.dispatch('replaceToken', [startIndex, offset, tokens]); } - return [replaced, tokens]; - } else { - return { FAIL: 'replaceRange: invalid tokens or startIndex.' }; - } - }; - - /** - * Replace a token with another token - * @param {number} index token index - * @param {token} token a token to replace - * @param {boolean} silent dispatch events and update context ranges - */ - Tokenizer.prototype.replaceToken = function (index, token, silent) { - if (!isNaN(index) && this.inboundIndex(index) && token instanceof Token) { - var replaced = this.tokens.splice(index, 1, token); - if (!silent) { this.dispatch('replaceToken', [index, token]); } - return [replaced[0], token]; - } else { - return { FAIL: 'replaceToken: invalid token or index.' }; - } - }; - - /** - * Removes a range of tokens - * @param {number} startIndex range start index - * @param {number} offset range offset - * @param {boolean} silent dispatch events and update context ranges - */ - Tokenizer.prototype.removeRange = function(startIndex, offset, silent) { - offset = !isNaN(offset) ? offset : this.tokens.length; - var tokens = this.tokens.splice(startIndex, offset); - if (!silent) { this.dispatch('removeRange', [tokens, startIndex, offset]); } - return tokens; - }; - - /** - * Remove a token at a certain index - * @param {number} index token index - * @param {boolean} silent dispatch events and update context ranges - */ - Tokenizer.prototype.removeToken = function(index, silent) { - if (!isNaN(index) && this.inboundIndex(index)) { - var token = this.tokens.splice(index, 1); - if (!silent) { this.dispatch('removeToken', [token, index]); } - return token; - } else { - return { FAIL: 'removeToken: invalid token index.' }; - } - }; - - /** - * Insert a list of tokens at a certain index - * @param {array} tokens a list of tokens to insert - * @param {number} index insert the list of tokens at index - * @param {boolean} silent dispatch events and update context ranges - */ - Tokenizer.prototype.insertToken = function (tokens, index, silent) { - var tokenType = tokens.every( - function (token) { return token instanceof Token; } - ); - if (tokenType) { - this.tokens.splice.apply( - this.tokens, [index, 0].concat(tokens) - ); - if (!silent) { this.dispatch('insertToken', [tokens, index]); } - return tokens; - } else { - return { FAIL: 'insertToken: invalid token(s).' }; - } - }; - - /** - * A state modifier that is called on 'newToken' event - * @param {string} modifierId state modifier id - * @param {function} condition a predicate function that returns true or false - * @param {function} modifier a function to update token state - */ - Tokenizer.prototype.registerModifier = function(modifierId, condition, modifier) { - this.events.newToken.subscribe(function(token, contextParams) { - var conditionParams = [token, contextParams]; - var canApplyModifier = ( - condition === null || - condition.apply(this, conditionParams) === true - ); - var modifierParams = [token, contextParams]; - if (canApplyModifier) { - var newStateValue = modifier.apply(this, modifierParams); - token.setState(modifierId, newStateValue); - } - }); - this.registeredModifiers.push(modifierId); - }; - - /** - * Subscribe a handler to an event - * @param {function} eventHandler an event handler function - */ - Event.prototype.subscribe = function (eventHandler) { - if (typeof eventHandler === 'function') { - return ((this.subscribers.push(eventHandler)) - 1); - } else { - return { FAIL: ("invalid '" + (this.eventId) + "' event handler")}; - } - }; - - /** - * Unsubscribe an event handler - * @param {string} subsId subscription id - */ - Event.prototype.unsubscribe = function (subsId) { - this.subscribers.splice(subsId, 1); - }; - - /** - * Sets context params current value index - * @param {number} index context params current value index - */ - ContextParams.prototype.setCurrentIndex = function(index) { - this.index = index; - this.current = this.context[index]; - this.backtrack = this.context.slice(0, index); - this.lookahead = this.context.slice(index + 1); - }; - - /** - * Get an item at an offset from the current value - * example (current value is 3): - * 1 2 [3] 4 5 | items values - * -2 -1 0 1 2 | offset values - * @param {number} offset an offset from current value index - */ - ContextParams.prototype.get = function (offset) { - switch (true) { - case (offset === 0): - return this.current; - case (offset < 0 && Math.abs(offset) <= this.backtrack.length): - return this.backtrack.slice(offset)[0]; - case (offset > 0 && offset <= this.lookahead.length): - return this.lookahead[offset - 1]; - default: - return null; - } - }; - - /** - * Converts a context range into a string value - * @param {contextRange} range a context range - */ - Tokenizer.prototype.rangeToText = function (range) { - if (range instanceof ContextRange) { - return ( - this.getRangeTokens(range) - .map(function (token) { return token.char; }).join('') - ); - } - }; - - /** - * Converts all tokens into a string - */ - Tokenizer.prototype.getText = function () { - return this.tokens.map(function (token) { return token.char; }).join(''); - }; - - /** - * Get a context by name - * @param {string} contextName context name to get - */ - Tokenizer.prototype.getContext = function (contextName) { - var context = this.registeredContexts[contextName]; - return !!context ? context : null; - }; - - /** - * Subscribes a new event handler to an event - * @param {string} eventName event name to subscribe to - * @param {function} eventHandler a function to be invoked on event - */ - Tokenizer.prototype.on = function(eventName, eventHandler) { - var event = this.events[eventName]; - if (!!event) { - return event.subscribe(eventHandler); - } else { - return null; - } - }; - - /** - * Dispatches an event - * @param {string} eventName event name - * @param {any} args event handler arguments - */ - Tokenizer.prototype.dispatch = function(eventName, args) { - var this$1 = this; - - var event = this.events[eventName]; - if (event instanceof Event) { - event.subscribers.forEach(function (subscriber) { - subscriber.apply(this$1, args || []); - }); - } - }; - - /** - * Register a new context checker - * @param {string} contextName a unique context name - * @param {function} contextStartCheck a predicate function that returns true on context start - * @param {function} contextEndCheck a predicate function that returns true on context end - * TODO: call tokenize on registration to update context ranges with the new context. - */ - Tokenizer.prototype.registerContextChecker = function(contextName, contextStartCheck, contextEndCheck) { - if (!!this.getContext(contextName)) { return { - FAIL: - ("context name '" + contextName + "' is already registered.") - }; } - if (typeof contextStartCheck !== 'function') { return { - FAIL: - "missing context start check." - }; } - if (typeof contextEndCheck !== 'function') { return { - FAIL: - "missing context end check." - }; } - var contextCheckers = new ContextChecker( - contextName, contextStartCheck, contextEndCheck - ); - this.registeredContexts[contextName] = contextCheckers; - this.contextCheckers.push(contextCheckers); - return contextCheckers; - }; - - /** - * Gets a context range tokens - * @param {contextRange} range a context range - */ - Tokenizer.prototype.getRangeTokens = function(range) { - var endIndex = range.startIndex + range.endOffset; - return [].concat( - this.tokens - .slice(range.startIndex, endIndex) - ); - }; - - /** - * Gets the ranges of a context - * @param {string} contextName context name - */ - Tokenizer.prototype.getContextRanges = function(contextName) { - var context = this.getContext(contextName); - if (!!context) { - return context.ranges; - } else { - return { FAIL: ("context checker '" + contextName + "' is not registered.") }; - } - }; - - /** - * Resets context ranges to run context update - */ - Tokenizer.prototype.resetContextsRanges = function () { - var registeredContexts = this.registeredContexts; - for (var contextName in registeredContexts) { - if (registeredContexts.hasOwnProperty(contextName)) { - var context = registeredContexts[contextName]; - context.ranges = []; - } - } - }; - - /** - * Updates context ranges - */ - Tokenizer.prototype.updateContextsRanges = function () { - this.resetContextsRanges(); - var chars = this.tokens.map(function (token) { return token.char; }); - for (var i = 0; i < chars.length; i++) { - var contextParams = new ContextParams(chars, i); - this.runContextCheck(contextParams); - } - this.dispatch('updateContextsRanges', [this.registeredContexts]); - }; - - /** - * Sets the end offset of an open range - * @param {number} offset range end offset - * @param {string} contextName context name - */ - Tokenizer.prototype.setEndOffset = function (offset, contextName) { - var startIndex = this.getContext(contextName).openRange.startIndex; - var range = new ContextRange(startIndex, offset, contextName); - var ranges = this.getContext(contextName).ranges; - range.rangeId = contextName + "." + (ranges.length); - ranges.push(range); - this.getContext(contextName).openRange = null; - return range; - }; - - /** - * Runs a context check on the current context - * @param {contextParams} contextParams current context params - */ - Tokenizer.prototype.runContextCheck = function(contextParams) { - var this$1 = this; - - var index = contextParams.index; - this.contextCheckers.forEach(function (contextChecker) { - var contextName = contextChecker.contextName; - var openRange = this$1.getContext(contextName).openRange; - if (!openRange && contextChecker.checkStart(contextParams)) { - openRange = new ContextRange(index, null, contextName); - this$1.getContext(contextName).openRange = openRange; - this$1.dispatch('contextStart', [contextName, index]); - } - if (!!openRange && contextChecker.checkEnd(contextParams)) { - var offset = (index - openRange.startIndex) + 1; - var range = this$1.setEndOffset(offset, contextName); - this$1.dispatch('contextEnd', [contextName, range]); - } - }); - }; - - /** - * Converts a text into a list of tokens - * @param {string} text a text to tokenize - */ - Tokenizer.prototype.tokenize = function (text) { - this.tokens = []; - this.resetContextsRanges(); - var chars = Array.from(text); - this.dispatch('start'); - for (var i = 0; i < chars.length; i++) { - var char = chars[i]; - var contextParams = new ContextParams(chars, i); - this.dispatch('next', [contextParams]); - this.runContextCheck(contextParams); - var token = new Token(char); - this.tokens.push(token); - this.dispatch('newToken', [token, contextParams]); - } - this.dispatch('end', [this.tokens]); - return this.tokens; - }; - - // ╭─┄┄┄────────────────────────┄─────────────────────────────────────────────╮ - // ┊ Character Class Assertions ┊ Checks if a char belongs to a certain class ┊ - // ╰─╾──────────────────────────┄─────────────────────────────────────────────╯ - // jscs:disable maximumLineLength - /** - * Check if a char is Arabic - * @param {string} c a single char - */ - function isArabicChar(c) { - return /[\u0600-\u065F\u066A-\u06D2\u06FA-\u06FF]/.test(c); - } - - /** - * Check if a char is an isolated arabic char - * @param {string} c a single char - */ - function isIsolatedArabicChar(char) { - return /[\u0630\u0690\u0621\u0631\u0661\u0671\u0622\u0632\u0672\u0692\u06C2\u0623\u0673\u0693\u06C3\u0624\u0694\u06C4\u0625\u0675\u0695\u06C5\u06E5\u0676\u0696\u06C6\u0627\u0677\u0697\u06C7\u0648\u0688\u0698\u06C8\u0689\u0699\u06C9\u068A\u06CA\u066B\u068B\u06CB\u068C\u068D\u06CD\u06FD\u068E\u06EE\u06FE\u062F\u068F\u06CF\u06EF]/.test(char); - } - - /** - * Check if a char is an Arabic Tashkeel char - * @param {string} c a single char - */ - function isTashkeelArabicChar(char) { - return /[\u0600-\u0605\u060C-\u060E\u0610-\u061B\u061E\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED]/.test(char); - } - - /** - * Check if a char is Latin - * @param {string} c a single char - */ - function isLatinChar(c) { - return /[A-z]/.test(c); - } - - /** - * Check if a char is whitespace char - * @param {string} c a single char - */ - function isWhiteSpace(c) { - return /\s/.test(c); - } - - /** - * Query a feature by some of it's properties to lookup a glyph substitution. - */ - - /** - * Create feature query instance - * @param {Font} font opentype font instance - */ - function FeatureQuery(font) { - this.font = font; - this.features = {}; - } - - /** - * @typedef SubstitutionAction - * @type Object - * @property {number} id substitution type - * @property {string} tag feature tag - * @property {any} substitution substitution value(s) - */ - - /** - * Create a substitution action instance - * @param {SubstitutionAction} action - */ - function SubstitutionAction(action) { - this.id = action.id; - this.tag = action.tag; - this.substitution = action.substitution; - } - - /** - * Lookup a coverage table - * @param {number} glyphIndex glyph index - * @param {CoverageTable} coverage coverage table - */ - function lookupCoverage(glyphIndex, coverage) { - if (!glyphIndex) { return -1; } - switch (coverage.format) { - case 1: - return coverage.glyphs.indexOf(glyphIndex); - - case 2: - var ranges = coverage.ranges; - for (var i = 0; i < ranges.length; i++) { - var range = ranges[i]; - if (glyphIndex >= range.start && glyphIndex <= range.end) { - var offset = glyphIndex - range.start; - return range.index + offset; - } - } - break; - default: - return -1; // not found - } - return -1; - } - - /** - * Handle a single substitution - format 1 - * @param {ContextParams} contextParams context params to lookup - */ - function singleSubstitutionFormat1(glyphIndex, subtable) { - var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); - if (substituteIndex === -1) { return null; } - return glyphIndex + subtable.deltaGlyphId; - } - - /** - * Handle a single substitution - format 2 - * @param {ContextParams} contextParams context params to lookup - */ - function singleSubstitutionFormat2(glyphIndex, subtable) { - var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); - if (substituteIndex === -1) { return null; } - return subtable.substitute[substituteIndex]; - } - - /** - * Lookup a list of coverage tables - * @param {any} coverageList a list of coverage tables - * @param {ContextParams} contextParams context params to lookup - */ - function lookupCoverageList(coverageList, contextParams) { - var lookupList = []; - for (var i = 0; i < coverageList.length; i++) { - var coverage = coverageList[i]; - var glyphIndex = contextParams.current; - glyphIndex = Array.isArray(glyphIndex) ? glyphIndex[0] : glyphIndex; - var lookupIndex = lookupCoverage(glyphIndex, coverage); - if (lookupIndex !== -1) { - lookupList.push(lookupIndex); - } - } - if (lookupList.length !== coverageList.length) { return -1; } - return lookupList; - } - - /** - * Handle chaining context substitution - format 3 - * @param {ContextParams} contextParams context params to lookup - */ - function chainingSubstitutionFormat3(contextParams, subtable) { - var lookupsCount = ( - subtable.inputCoverage.length + - subtable.lookaheadCoverage.length + - subtable.backtrackCoverage.length - ); - if (contextParams.context.length < lookupsCount) { return []; } - // INPUT LOOKUP // - var inputLookups = lookupCoverageList( - subtable.inputCoverage, contextParams - ); - if (inputLookups === -1) { return []; } - // LOOKAHEAD LOOKUP // - var lookaheadOffset = subtable.inputCoverage.length - 1; - if (contextParams.lookahead.length < subtable.lookaheadCoverage.length) { return []; } - var lookaheadContext = contextParams.lookahead.slice(lookaheadOffset); - while (lookaheadContext.length && isTashkeelArabicChar(lookaheadContext[0].char)) { - lookaheadContext.shift(); - } - var lookaheadParams = new ContextParams(lookaheadContext, 0); - var lookaheadLookups = lookupCoverageList( - subtable.lookaheadCoverage, lookaheadParams - ); - // BACKTRACK LOOKUP // - var backtrackContext = [].concat(contextParams.backtrack); - backtrackContext.reverse(); - while (backtrackContext.length && isTashkeelArabicChar(backtrackContext[0].char)) { - backtrackContext.shift(); - } - if (backtrackContext.length < subtable.backtrackCoverage.length) { return []; } - var backtrackParams = new ContextParams(backtrackContext, 0); - var backtrackLookups = lookupCoverageList( - subtable.backtrackCoverage, backtrackParams - ); - var contextRulesMatch = ( - inputLookups.length === subtable.inputCoverage.length && - lookaheadLookups.length === subtable.lookaheadCoverage.length && - backtrackLookups.length === subtable.backtrackCoverage.length - ); - var substitutions = []; - if (contextRulesMatch) { - for (var i = 0; i < subtable.lookupRecords.length; i++) { - var lookupRecord = subtable.lookupRecords[i]; - var lookupListIndex = lookupRecord.lookupListIndex; - var lookupTable = this.getLookupByIndex(lookupListIndex); - for (var s = 0; s < lookupTable.subtables.length; s++) { - var subtable$1 = lookupTable.subtables[s]; - var lookup = this.getLookupMethod(lookupTable, subtable$1); - var substitutionType = this.getSubstitutionType(lookupTable, subtable$1); - if (substitutionType === '12') { - for (var n = 0; n < inputLookups.length; n++) { - var glyphIndex = contextParams.get(n); - var substitution = lookup(glyphIndex); - if (substitution) { substitutions.push(substitution); } - } - } - } - } - } - return substitutions; - } - - /** - * Handle ligature substitution - format 1 - * @param {ContextParams} contextParams context params to lookup - */ - function ligatureSubstitutionFormat1(contextParams, subtable) { - // COVERAGE LOOKUP // - var glyphIndex = contextParams.current; - var ligSetIndex = lookupCoverage(glyphIndex, subtable.coverage); - if (ligSetIndex === -1) { return null; } - // COMPONENTS LOOKUP - // (!) note, components are ordered in the written direction. - var ligature; - var ligatureSet = subtable.ligatureSets[ligSetIndex]; - for (var s = 0; s < ligatureSet.length; s++) { - ligature = ligatureSet[s]; - for (var l = 0; l < ligature.components.length; l++) { - var lookaheadItem = contextParams.lookahead[l]; - var component = ligature.components[l]; - if (lookaheadItem !== component) { break; } - if (l === ligature.components.length - 1) { return ligature; } - } - } - return null; - } - - /** - * Handle decomposition substitution - format 1 - * @param {number} glyphIndex glyph index - * @param {any} subtable subtable - */ - function decompositionSubstitutionFormat1(glyphIndex, subtable) { - var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); - if (substituteIndex === -1) { return null; } - return subtable.sequences[substituteIndex]; - } - - /** - * Get default script features indexes - */ - FeatureQuery.prototype.getDefaultScriptFeaturesIndexes = function () { - var scripts = this.font.tables.gsub.scripts; - for (var s = 0; s < scripts.length; s++) { - var script = scripts[s]; - if (script.tag === 'DFLT') { return ( - script.script.defaultLangSys.featureIndexes - ); } - } - return []; - }; - - /** - * Get feature indexes of a specific script - * @param {string} scriptTag script tag - */ - FeatureQuery.prototype.getScriptFeaturesIndexes = function(scriptTag) { - var tables = this.font.tables; - if (!tables.gsub) { return []; } - if (!scriptTag) { return this.getDefaultScriptFeaturesIndexes(); } - var scripts = this.font.tables.gsub.scripts; - for (var i = 0; i < scripts.length; i++) { - var script = scripts[i]; - if (script.tag === scriptTag && script.script.defaultLangSys) { - return script.script.defaultLangSys.featureIndexes; - } else { - var langSysRecords = script.langSysRecords; - if (!!langSysRecords) { - for (var j = 0; j < langSysRecords.length; j++) { - var langSysRecord = langSysRecords[j]; - if (langSysRecord.tag === scriptTag) { - var langSys = langSysRecord.langSys; - return langSys.featureIndexes; - } - } - } - } - } - return this.getDefaultScriptFeaturesIndexes(); - }; - - /** - * Map a feature tag to a gsub feature - * @param {any} features gsub features - * @param {string} scriptTag script tag - */ - FeatureQuery.prototype.mapTagsToFeatures = function (features, scriptTag) { - var tags = {}; - for (var i = 0; i < features.length; i++) { - var tag = features[i].tag; - var feature = features[i].feature; - tags[tag] = feature; - } - this.features[scriptTag].tags = tags; - }; - - /** - * Get features of a specific script - * @param {string} scriptTag script tag - */ - FeatureQuery.prototype.getScriptFeatures = function (scriptTag) { - var features = this.features[scriptTag]; - if (this.features.hasOwnProperty(scriptTag)) { return features; } - var featuresIndexes = this.getScriptFeaturesIndexes(scriptTag); - if (!featuresIndexes) { return null; } - var gsub = this.font.tables.gsub; - features = featuresIndexes.map(function (index) { return gsub.features[index]; }); - this.features[scriptTag] = features; - this.mapTagsToFeatures(features, scriptTag); - return features; - }; - - /** - * Get substitution type - * @param {any} lookupTable lookup table - * @param {any} subtable subtable - */ - FeatureQuery.prototype.getSubstitutionType = function(lookupTable, subtable) { - var lookupType = lookupTable.lookupType.toString(); - var substFormat = subtable.substFormat.toString(); - return lookupType + substFormat; - }; - - /** - * Get lookup method - * @param {any} lookupTable lookup table - * @param {any} subtable subtable - */ - FeatureQuery.prototype.getLookupMethod = function(lookupTable, subtable) { - var this$1 = this; - - var substitutionType = this.getSubstitutionType(lookupTable, subtable); - switch (substitutionType) { - case '11': - return function (glyphIndex) { return singleSubstitutionFormat1.apply( - this$1, [glyphIndex, subtable] - ); }; - case '12': - return function (glyphIndex) { return singleSubstitutionFormat2.apply( - this$1, [glyphIndex, subtable] - ); }; - case '63': - return function (contextParams) { return chainingSubstitutionFormat3.apply( - this$1, [contextParams, subtable] - ); }; - case '41': - return function (contextParams) { return ligatureSubstitutionFormat1.apply( - this$1, [contextParams, subtable] - ); }; - case '21': - return function (glyphIndex) { return decompositionSubstitutionFormat1.apply( - this$1, [glyphIndex, subtable] - ); }; - default: - throw new Error( - "lookupType: " + (lookupTable.lookupType) + " - " + - "substFormat: " + (subtable.substFormat) + " " + - "is not yet supported" - ); - } - }; - - /** - * [ LOOKUP TYPES ] - * ------------------------------- - * Single 1; - * Multiple 2; - * Alternate 3; - * Ligature 4; - * Context 5; - * ChainingContext 6; - * ExtensionSubstitution 7; - * ReverseChainingContext 8; - * ------------------------------- - * - */ - - /** - * @typedef FQuery - * @type Object - * @param {string} tag feature tag - * @param {string} script feature script - * @param {ContextParams} contextParams context params - */ - - /** - * Lookup a feature using a query parameters - * @param {FQuery} query feature query - */ - FeatureQuery.prototype.lookupFeature = function (query) { - var contextParams = query.contextParams; - var currentIndex = contextParams.index; - var feature = this.getFeature({ - tag: query.tag, script: query.script - }); - if (!feature) { return new Error( - "font '" + (this.font.names.fullName.en) + "' " + - "doesn't support feature '" + (query.tag) + "' " + - "for script '" + (query.script) + "'." - ); } - var lookups = this.getFeatureLookups(feature); - var substitutions = [].concat(contextParams.context); - for (var l = 0; l < lookups.length; l++) { - var lookupTable = lookups[l]; - var subtables = this.getLookupSubtables(lookupTable); - for (var s = 0; s < subtables.length; s++) { - var subtable = subtables[s]; - var substType = this.getSubstitutionType(lookupTable, subtable); - var lookup = this.getLookupMethod(lookupTable, subtable); - var substitution = (void 0); - switch (substType) { - case '11': - substitution = lookup(contextParams.current); - if (substitution) { - substitutions.splice(currentIndex, 1, new SubstitutionAction({ - id: 11, tag: query.tag, substitution: substitution - })); - } - break; - case '12': - substitution = lookup(contextParams.current); - if (substitution) { - substitutions.splice(currentIndex, 1, new SubstitutionAction({ - id: 12, tag: query.tag, substitution: substitution - })); - } - break; - case '63': - substitution = lookup(contextParams); - if (Array.isArray(substitution) && substitution.length) { - substitutions.splice(currentIndex, 1, new SubstitutionAction({ - id: 63, tag: query.tag, substitution: substitution - })); - } - break; - case '41': - substitution = lookup(contextParams); - if (substitution) { - substitutions.splice(currentIndex, 1, new SubstitutionAction({ - id: 41, tag: query.tag, substitution: substitution - })); - } - break; - case '21': - substitution = lookup(contextParams.current); - if (substitution) { - substitutions.splice(currentIndex, 1, new SubstitutionAction({ - id: 21, tag: query.tag, substitution: substitution - })); - } - break; - } - contextParams = new ContextParams(substitutions, currentIndex); - if (Array.isArray(substitution) && !substitution.length) { continue; } - substitution = null; - } - } - return substitutions.length ? substitutions : null; - }; - - /** - * Checks if a font supports a specific features - * @param {FQuery} query feature query object - */ - FeatureQuery.prototype.supports = function (query) { - if (!query.script) { return false; } - this.getScriptFeatures(query.script); - var supportedScript = this.features.hasOwnProperty(query.script); - if (!query.tag) { return supportedScript; } - var supportedFeature = ( - this.features[query.script].some(function (feature) { return feature.tag === query.tag; }) - ); - return supportedScript && supportedFeature; - }; - - /** - * Get lookup table subtables - * @param {any} lookupTable lookup table - */ - FeatureQuery.prototype.getLookupSubtables = function (lookupTable) { - return lookupTable.subtables || null; - }; - - /** - * Get lookup table by index - * @param {number} index lookup table index - */ - FeatureQuery.prototype.getLookupByIndex = function (index) { - var lookups = this.font.tables.gsub.lookups; - return lookups[index] || null; - }; - - /** - * Get lookup tables for a feature - * @param {string} feature - */ - FeatureQuery.prototype.getFeatureLookups = function (feature) { - // TODO: memoize - return feature.lookupListIndexes.map(this.getLookupByIndex.bind(this)); - }; - - /** - * Query a feature by it's properties - * @param {any} query an object that describes the properties of a query - */ - FeatureQuery.prototype.getFeature = function getFeature(query) { - if (!this.font) { return { FAIL: "No font was found"}; } - if (!this.features.hasOwnProperty(query.script)) { - this.getScriptFeatures(query.script); - } - var scriptFeatures = this.features[query.script]; - if (!scriptFeatures) { return ( - { FAIL: ("No feature for script " + (query.script))} - ); } - if (!scriptFeatures.tags[query.tag]) { return null; } - return this.features[query.script].tags[query.tag]; - }; - - /** - * Arabic word context checkers - */ - - function arabicWordStartCheck(contextParams) { - var char = contextParams.current; - var prevChar = contextParams.get(-1); - return ( - // ? arabic first char - (prevChar === null && isArabicChar(char)) || - // ? arabic char preceded with a non arabic char - (!isArabicChar(prevChar) && isArabicChar(char)) - ); - } - - function arabicWordEndCheck(contextParams) { - var nextChar = contextParams.get(1); - return ( - // ? last arabic char - (nextChar === null) || - // ? next char is not arabic - (!isArabicChar(nextChar)) - ); - } - - var arabicWordCheck = { - startCheck: arabicWordStartCheck, - endCheck: arabicWordEndCheck - }; - - /** - * Arabic sentence context checkers - */ - - function arabicSentenceStartCheck(contextParams) { - var char = contextParams.current; - var prevChar = contextParams.get(-1); - return ( - // ? an arabic char preceded with a non arabic char - (isArabicChar(char) || isTashkeelArabicChar(char)) && - !isArabicChar(prevChar) - ); - } - - function arabicSentenceEndCheck(contextParams) { - var nextChar = contextParams.get(1); - switch (true) { - case nextChar === null: - return true; - case (!isArabicChar(nextChar) && !isTashkeelArabicChar(nextChar)): - var nextIsWhitespace = isWhiteSpace(nextChar); - if (!nextIsWhitespace) { return true; } - if (nextIsWhitespace) { - var arabicCharAhead = false; - arabicCharAhead = ( - contextParams.lookahead.some( - function (c) { return isArabicChar(c) || isTashkeelArabicChar(c); } - ) - ); - if (!arabicCharAhead) { return true; } - } - break; - default: - return false; - } - } - - var arabicSentenceCheck = { - startCheck: arabicSentenceStartCheck, - endCheck: arabicSentenceEndCheck - }; - - /** - * Apply single substitution format 1 - * @param {Array} substitutions substitutions - * @param {any} tokens a list of tokens - * @param {number} index token index - */ - function singleSubstitutionFormat1$1(action, tokens, index) { - tokens[index].setState(action.tag, action.substitution); - } - - /** - * Apply single substitution format 2 - * @param {Array} substitutions substitutions - * @param {any} tokens a list of tokens - * @param {number} index token index - */ - function singleSubstitutionFormat2$1(action, tokens, index) { - tokens[index].setState(action.tag, action.substitution); - } - - /** - * Apply chaining context substitution format 3 - * @param {Array} substitutions substitutions - * @param {any} tokens a list of tokens - * @param {number} index token index - */ - function chainingSubstitutionFormat3$1(action, tokens, index) { - action.substitution.forEach(function (subst, offset) { - var token = tokens[index + offset]; - token.setState(action.tag, subst); - }); - } - - /** - * Apply ligature substitution format 1 - * @param {Array} substitutions substitutions - * @param {any} tokens a list of tokens - * @param {number} index token index - */ - function ligatureSubstitutionFormat1$1(action, tokens, index) { - var token = tokens[index]; - token.setState(action.tag, action.substitution.ligGlyph); - var compsCount = action.substitution.components.length; - for (var i = 0; i < compsCount; i++) { - token = tokens[index + i + 1]; - token.setState('deleted', true); - } - } - - /** - * Supported substitutions - */ - var SUBSTITUTIONS = { - 11: singleSubstitutionFormat1$1, - 12: singleSubstitutionFormat2$1, - 63: chainingSubstitutionFormat3$1, - 41: ligatureSubstitutionFormat1$1 - }; - - /** - * Apply substitutions to a list of tokens - * @param {Array} substitutions substitutions - * @param {any} tokens a list of tokens - * @param {number} index token index - */ - function applySubstitution(action, tokens, index) { - if (action instanceof SubstitutionAction && SUBSTITUTIONS[action.id]) { - SUBSTITUTIONS[action.id](action, tokens, index); - } - } - - /** - * Apply Arabic presentation forms to a range of tokens - */ - - /** - * Check if a char can be connected to it's preceding char - * @param {ContextParams} charContextParams context params of a char - */ - function willConnectPrev(charContextParams) { - var backtrack = [].concat(charContextParams.backtrack); - for (var i = backtrack.length - 1; i >= 0; i--) { - var prevChar = backtrack[i]; - var isolated = isIsolatedArabicChar(prevChar); - var tashkeel = isTashkeelArabicChar(prevChar); - if (!isolated && !tashkeel) { return true; } - if (isolated) { return false; } - } - return false; - } - - /** - * Check if a char can be connected to it's proceeding char - * @param {ContextParams} charContextParams context params of a char - */ - function willConnectNext(charContextParams) { - if (isIsolatedArabicChar(charContextParams.current)) { return false; } - for (var i = 0; i < charContextParams.lookahead.length; i++) { - var nextChar = charContextParams.lookahead[i]; - var tashkeel = isTashkeelArabicChar(nextChar); - if (!tashkeel) { return true; } - } - return false; - } - - /** - * Apply arabic presentation forms to a list of tokens - * @param {ContextRange} range a range of tokens - */ - function arabicPresentationForms(range) { - var this$1 = this; - - var script = 'arab'; - var tags = this.featuresTags[script]; - var tokens = this.tokenizer.getRangeTokens(range); - if (tokens.length === 1) { return; } - var contextParams = new ContextParams( - tokens.map(function (token) { return token.getState('glyphIndex'); } - ), 0); - var charContextParams = new ContextParams( - tokens.map(function (token) { return token.char; } - ), 0); - tokens.forEach(function (token, index) { - if (isTashkeelArabicChar(token.char)) { return; } - contextParams.setCurrentIndex(index); - charContextParams.setCurrentIndex(index); - var CONNECT = 0; // 2 bits 00 (10: can connect next) (01: can connect prev) - if (willConnectPrev(charContextParams)) { CONNECT |= 1; } - if (willConnectNext(charContextParams)) { CONNECT |= 2; } - var tag; - switch (CONNECT) { - case 1: (tag = 'fina'); break; - case 2: (tag = 'init'); break; - case 3: (tag = 'medi'); break; - } - if (tags.indexOf(tag) === -1) { return; } - var substitutions = this$1.query.lookupFeature({ - tag: tag, script: script, contextParams: contextParams - }); - if (substitutions instanceof Error) { return console.info(substitutions.message); } - substitutions.forEach(function (action, index) { - if (action instanceof SubstitutionAction) { - applySubstitution(action, tokens, index); - contextParams.context[index] = action.substitution; - } - }); - }); - } - - /** - * Apply Arabic required ligatures feature to a range of tokens - */ - - /** - * Update context params - * @param {any} tokens a list of tokens - * @param {number} index current item index - */ - function getContextParams(tokens, index) { - var context = tokens.map(function (token) { return token.activeState.value; }); - return new ContextParams(context, index || 0); - } - - /** - * Apply Arabic required ligatures to a context range - * @param {ContextRange} range a range of tokens - */ - function arabicRequiredLigatures(range) { - var this$1 = this; - - var script = 'arab'; - var tokens = this.tokenizer.getRangeTokens(range); - var contextParams = getContextParams(tokens); - contextParams.context.forEach(function (glyphIndex, index) { - contextParams.setCurrentIndex(index); - var substitutions = this$1.query.lookupFeature({ - tag: 'rlig', script: script, contextParams: contextParams - }); - if (substitutions.length) { - substitutions.forEach( - function (action) { return applySubstitution(action, tokens, index); } - ); - contextParams = getContextParams(tokens); - } - }); - } - - /** - * Latin word context checkers - */ - - function latinWordStartCheck(contextParams) { - var char = contextParams.current; - var prevChar = contextParams.get(-1); - return ( - // ? latin first char - (prevChar === null && isLatinChar(char)) || - // ? latin char preceded with a non latin char - (!isLatinChar(prevChar) && isLatinChar(char)) - ); - } - - function latinWordEndCheck(contextParams) { - var nextChar = contextParams.get(1); - return ( - // ? last latin char - (nextChar === null) || - // ? next char is not latin - (!isLatinChar(nextChar)) - ); - } - - var latinWordCheck = { - startCheck: latinWordStartCheck, - endCheck: latinWordEndCheck - }; - - /** - * Apply Latin ligature feature to a range of tokens - */ - - /** - * Update context params - * @param {any} tokens a list of tokens - * @param {number} index current item index - */ - function getContextParams$1(tokens, index) { - var context = tokens.map(function (token) { return token.activeState.value; }); - return new ContextParams(context, index || 0); - } - - /** - * Apply Arabic required ligatures to a context range - * @param {ContextRange} range a range of tokens - */ - function latinLigature(range) { - var this$1 = this; - - var script = 'latn'; - var tokens = this.tokenizer.getRangeTokens(range); - var contextParams = getContextParams$1(tokens); - contextParams.context.forEach(function (glyphIndex, index) { - contextParams.setCurrentIndex(index); - var substitutions = this$1.query.lookupFeature({ - tag: 'liga', script: script, contextParams: contextParams - }); - if (substitutions.length) { - substitutions.forEach( - function (action) { return applySubstitution(action, tokens, index); } - ); - contextParams = getContextParams$1(tokens); - } - }); - } - - /** - * Infer bidirectional properties for a given text and apply - * the corresponding layout rules. - */ - - /** - * Create Bidi. features - * @param {string} baseDir text base direction. value either 'ltr' or 'rtl' - */ - function Bidi(baseDir) { - this.baseDir = baseDir || 'ltr'; - this.tokenizer = new Tokenizer(); - this.featuresTags = {}; - } - - /** - * Sets Bidi text - * @param {string} text a text input - */ - Bidi.prototype.setText = function (text) { - this.text = text; - }; - - /** - * Store essential context checks: - * arabic word check for applying gsub features - * arabic sentence check for adjusting arabic layout - */ - Bidi.prototype.contextChecks = ({ - latinWordCheck: latinWordCheck, - arabicWordCheck: arabicWordCheck, - arabicSentenceCheck: arabicSentenceCheck - }); - - /** - * Register arabic word check - */ - function registerContextChecker(checkId) { - var check = this.contextChecks[(checkId + "Check")]; - return this.tokenizer.registerContextChecker( - checkId, check.startCheck, check.endCheck - ); - } - - /** - * Perform pre tokenization procedure then - * tokenize text input - */ - function tokenizeText() { - registerContextChecker.call(this, 'latinWord'); - registerContextChecker.call(this, 'arabicWord'); - registerContextChecker.call(this, 'arabicSentence'); - return this.tokenizer.tokenize(this.text); - } - - /** - * Reverse arabic sentence layout - * TODO: check base dir before applying adjustments - priority low - */ - function reverseArabicSentences() { - var this$1 = this; - - var ranges = this.tokenizer.getContextRanges('arabicSentence'); - ranges.forEach(function (range) { - var rangeTokens = this$1.tokenizer.getRangeTokens(range); - this$1.tokenizer.replaceRange( - range.startIndex, - range.endOffset, - rangeTokens.reverse() - ); - }); - } - - /** - * Register supported features tags - * @param {script} script script tag - * @param {Array} tags features tags list - */ - Bidi.prototype.registerFeatures = function (script, tags) { - var this$1 = this; - - var supportedTags = tags.filter( - function (tag) { return this$1.query.supports({script: script, tag: tag}); } - ); - if (!this.featuresTags.hasOwnProperty(script)) { - this.featuresTags[script] = supportedTags; - } else { - this.featuresTags[script] = - this.featuresTags[script].concat(supportedTags); - } - }; - - /** - * Apply GSUB features - * @param {Array} tagsList a list of features tags - * @param {string} script a script tag - * @param {Font} font opentype font instance - */ - Bidi.prototype.applyFeatures = function (font, features) { - if (!font) { throw new Error( - 'No valid font was provided to apply features' - ); } - if (!this.query) { this.query = new FeatureQuery(font); } - for (var f = 0; f < features.length; f++) { - var feature = features[f]; - if (!this.query.supports({script: feature.script})) { continue; } - this.registerFeatures(feature.script, feature.tags); - } - }; - - /** - * Register a state modifier - * @param {string} modifierId state modifier id - * @param {function} condition a predicate function that returns true or false - * @param {function} modifier a modifier function to set token state - */ - Bidi.prototype.registerModifier = function (modifierId, condition, modifier) { - this.tokenizer.registerModifier(modifierId, condition, modifier); - }; - - /** - * Check if 'glyphIndex' is registered - */ - function checkGlyphIndexStatus() { - if (this.tokenizer.registeredModifiers.indexOf('glyphIndex') === -1) { - throw new Error( - 'glyphIndex modifier is required to apply ' + - 'arabic presentation features.' - ); - } - } - - /** - * Apply arabic presentation forms features - */ - function applyArabicPresentationForms() { - var this$1 = this; - - var script = 'arab'; - if (!this.featuresTags.hasOwnProperty(script)) { return; } - checkGlyphIndexStatus.call(this); - var ranges = this.tokenizer.getContextRanges('arabicWord'); - ranges.forEach(function (range) { - arabicPresentationForms.call(this$1, range); - }); - } - - /** - * Apply required arabic ligatures - */ - function applyArabicRequireLigatures() { - var this$1 = this; - - var script = 'arab'; - if (!this.featuresTags.hasOwnProperty(script)) { return; } - var tags = this.featuresTags[script]; - if (tags.indexOf('rlig') === -1) { return; } - checkGlyphIndexStatus.call(this); - var ranges = this.tokenizer.getContextRanges('arabicWord'); - ranges.forEach(function (range) { - arabicRequiredLigatures.call(this$1, range); - }); - } - - /** - * Apply required arabic ligatures - */ - function applyLatinLigatures() { - var this$1 = this; - - var script = 'latn'; - if (!this.featuresTags.hasOwnProperty(script)) { return; } - var tags = this.featuresTags[script]; - if (tags.indexOf('liga') === -1) { return; } - checkGlyphIndexStatus.call(this); - var ranges = this.tokenizer.getContextRanges('latinWord'); - ranges.forEach(function (range) { - latinLigature.call(this$1, range); - }); - } - - /** - * Check if a context is registered - * @param {string} contextId context id - */ - Bidi.prototype.checkContextReady = function (contextId) { - return !!this.tokenizer.getContext(contextId); - }; - - /** - * Apply features to registered contexts - */ - Bidi.prototype.applyFeaturesToContexts = function () { - if (this.checkContextReady('arabicWord')) { - applyArabicPresentationForms.call(this); - applyArabicRequireLigatures.call(this); - } - if (this.checkContextReady('latinWord')) { - applyLatinLigatures.call(this); - } - if (this.checkContextReady('arabicSentence')) { - reverseArabicSentences.call(this); - } - }; - - /** - * process text input - * @param {string} text an input text - */ - Bidi.prototype.processText = function(text) { - if (!this.text || this.text !== text) { - this.setText(text); - tokenizeText.call(this); - this.applyFeaturesToContexts(); - } - }; - - /** - * Process a string of text to identify and adjust - * bidirectional text entities. - * @param {string} text input text - */ - Bidi.prototype.getBidiText = function (text) { - this.processText(text); - return this.tokenizer.getText(); - }; - - /** - * Get the current state index of each token - * @param {text} text an input text - */ - Bidi.prototype.getTextGlyphs = function (text) { - this.processText(text); - var indexes = []; - for (var i = 0; i < this.tokenizer.tokens.length; i++) { - var token = this.tokenizer.tokens[i]; - if (token.state.deleted) { continue; } - var index = token.activeState.value; - indexes.push(Array.isArray(index) ? index[0] : index); - } - return indexes; - }; - - // The Font object - - /** - * @typedef FontOptions - * @type Object - * @property {Boolean} empty - whether to create a new empty font - * @property {string} familyName - * @property {string} styleName - * @property {string=} fullName - * @property {string=} postScriptName - * @property {string=} designer - * @property {string=} designerURL - * @property {string=} manufacturer - * @property {string=} manufacturerURL - * @property {string=} license - * @property {string=} licenseURL - * @property {string=} version - * @property {string=} description - * @property {string=} copyright - * @property {string=} trademark - * @property {Number} unitsPerEm - * @property {Number} ascender - * @property {Number} descender - * @property {Number} createdTimestamp - * @property {string=} weightClass - * @property {string=} widthClass - * @property {string=} fsSelection - */ - - /** - * A Font represents a loaded OpenType font file. - * It contains a set of glyphs and methods to draw text on a drawing context, - * or to get a path representing the text. - * @exports opentype.Font - * @class - * @param {FontOptions} - * @constructor - */ - function Font(options) { - options = options || {}; - options.tables = options.tables || {}; - - if (!options.empty) { - // Check that we've provided the minimum set of names. - checkArgument(options.familyName, 'When creating a new Font object, familyName is required.'); - checkArgument(options.styleName, 'When creating a new Font object, styleName is required.'); - checkArgument(options.unitsPerEm, 'When creating a new Font object, unitsPerEm is required.'); - checkArgument(options.ascender, 'When creating a new Font object, ascender is required.'); - checkArgument(options.descender <= 0, 'When creating a new Font object, negative descender value is required.'); - - // OS X will complain if the names are empty, so we put a single space everywhere by default. - this.names = { - fontFamily: {en: options.familyName || ' '}, - fontSubfamily: {en: options.styleName || ' '}, - fullName: {en: options.fullName || options.familyName + ' ' + options.styleName}, - // postScriptName may not contain any whitespace - postScriptName: {en: options.postScriptName || (options.familyName + options.styleName).replace(/\s/g, '')}, - designer: {en: options.designer || ' '}, - designerURL: {en: options.designerURL || ' '}, - manufacturer: {en: options.manufacturer || ' '}, - manufacturerURL: {en: options.manufacturerURL || ' '}, - license: {en: options.license || ' '}, - licenseURL: {en: options.licenseURL || ' '}, - version: {en: options.version || 'Version 0.1'}, - description: {en: options.description || ' '}, - copyright: {en: options.copyright || ' '}, - trademark: {en: options.trademark || ' '} - }; - this.unitsPerEm = options.unitsPerEm || 1000; - this.ascender = options.ascender; - this.descender = options.descender; - this.createdTimestamp = options.createdTimestamp; - this.tables = Object.assign(options.tables, { - os2: Object.assign({ - usWeightClass: options.weightClass || this.usWeightClasses.MEDIUM, - usWidthClass: options.widthClass || this.usWidthClasses.MEDIUM, - fsSelection: options.fsSelection || this.fsSelectionValues.REGULAR, - }, options.tables.os2) - }); - } - - this.supported = true; // Deprecated: parseBuffer will throw an error if font is not supported. - this.glyphs = new glyphset.GlyphSet(this, options.glyphs || []); - this.encoding = new DefaultEncoding(this); - this.position = new Position(this); - this.substitution = new Substitution(this); - this.tables = this.tables || {}; - - // needed for low memory mode only. - this._push = null; - this._hmtxTableData = {}; - - Object.defineProperty(this, 'hinting', { - get: function() { - if (this._hinting) { return this._hinting; } - if (this.outlinesFormat === 'truetype') { - return (this._hinting = new Hinting(this)); - } - } - }); - } - - /** - * Check if the font has a glyph for the given character. - * @param {string} - * @return {Boolean} - */ - Font.prototype.hasChar = function(c) { - return this.encoding.charToGlyphIndex(c) !== null; - }; - - /** - * Convert the given character to a single glyph index. - * Note that this function assumes that there is a one-to-one mapping between - * the given character and a glyph; for complex scripts this might not be the case. - * @param {string} - * @return {Number} - */ - Font.prototype.charToGlyphIndex = function(s) { - return this.encoding.charToGlyphIndex(s); - }; - - /** - * Convert the given character to a single Glyph object. - * Note that this function assumes that there is a one-to-one mapping between - * the given character and a glyph; for complex scripts this might not be the case. - * @param {string} - * @return {opentype.Glyph} - */ - Font.prototype.charToGlyph = function(c) { - var glyphIndex = this.charToGlyphIndex(c); - var glyph = this.glyphs.get(glyphIndex); - if (!glyph) { - // .notdef - glyph = this.glyphs.get(0); - } - - return glyph; - }; - - /** - * Update features - * @param {any} options features options - */ - Font.prototype.updateFeatures = function (options) { - // TODO: update all features options not only 'latn'. - return this.defaultRenderOptions.features.map(function (feature) { - if (feature.script === 'latn') { - return { - script: 'latn', - tags: feature.tags.filter(function (tag) { return options[tag]; }) - }; - } else { - return feature; - } - }); - }; - - /** - * Convert the given text to a list of Glyph objects. - * Note that there is no strict one-to-one mapping between characters and - * glyphs, so the list of returned glyphs can be larger or smaller than the - * length of the given string. - * @param {string} - * @param {GlyphRenderOptions} [options] - * @return {opentype.Glyph[]} - */ - Font.prototype.stringToGlyphs = function(s, options) { - var this$1 = this; - - - var bidi = new Bidi(); - - // Create and register 'glyphIndex' state modifier - var charToGlyphIndexMod = function (token) { return this$1.charToGlyphIndex(token.char); }; - bidi.registerModifier('glyphIndex', null, charToGlyphIndexMod); - - // roll-back to default features - var features = options ? - this.updateFeatures(options.features) : - this.defaultRenderOptions.features; - - bidi.applyFeatures(this, features); - - var indexes = bidi.getTextGlyphs(s); - - var length = indexes.length; - - // convert glyph indexes to glyph objects - var glyphs = new Array(length); - var notdef = this.glyphs.get(0); - for (var i = 0; i < length; i += 1) { - glyphs[i] = this.glyphs.get(indexes[i]) || notdef; - } - return glyphs; - }; - - /** - * @param {string} - * @return {Number} - */ - Font.prototype.nameToGlyphIndex = function(name) { - return this.glyphNames.nameToGlyphIndex(name); - }; - - /** - * @param {string} - * @return {opentype.Glyph} - */ - Font.prototype.nameToGlyph = function(name) { - var glyphIndex = this.nameToGlyphIndex(name); - var glyph = this.glyphs.get(glyphIndex); - if (!glyph) { - // .notdef - glyph = this.glyphs.get(0); - } - - return glyph; - }; - - /** - * @param {Number} - * @return {String} - */ - Font.prototype.glyphIndexToName = function(gid) { - if (!this.glyphNames.glyphIndexToName) { - return ''; - } - - return this.glyphNames.glyphIndexToName(gid); - }; - - /** - * Retrieve the value of the kerning pair between the left glyph (or its index) - * and the right glyph (or its index). If no kerning pair is found, return 0. - * The kerning value gets added to the advance width when calculating the spacing - * between glyphs. - * For GPOS kerning, this method uses the default script and language, which covers - * most use cases. To have greater control, use font.position.getKerningValue . - * @param {opentype.Glyph} leftGlyph - * @param {opentype.Glyph} rightGlyph - * @return {Number} - */ - Font.prototype.getKerningValue = function(leftGlyph, rightGlyph) { - leftGlyph = leftGlyph.index || leftGlyph; - rightGlyph = rightGlyph.index || rightGlyph; - var gposKerning = this.position.defaultKerningTables; - if (gposKerning) { - return this.position.getKerningValue(gposKerning, leftGlyph, rightGlyph); - } - // "kern" table - return this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0; - }; - - /** - * @typedef GlyphRenderOptions - * @type Object - * @property {string} [script] - script used to determine which features to apply. By default, 'DFLT' or 'latn' is used. - * See https://www.microsoft.com/typography/otspec/scripttags.htm - * @property {string} [language='dflt'] - language system used to determine which features to apply. - * See https://www.microsoft.com/typography/developers/opentype/languagetags.aspx - * @property {boolean} [kerning=true] - whether to include kerning values - * @property {object} [features] - OpenType Layout feature tags. Used to enable or disable the features of the given script/language system. - * See https://www.microsoft.com/typography/otspec/featuretags.htm - */ - Font.prototype.defaultRenderOptions = { - kerning: true, - features: [ - /** - * these 4 features are required to render Arabic text properly - * and shouldn't be turned off when rendering arabic text. - */ - { script: 'arab', tags: ['init', 'medi', 'fina', 'rlig'] }, - { script: 'latn', tags: ['liga', 'rlig'] } - ] - }; - - /** - * Helper function that invokes the given callback for each glyph in the given text. - * The callback gets `(glyph, x, y, fontSize, options)`.* @param {string} text - * @param {string} text - The text to apply. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - * @param {Function} callback - */ - Font.prototype.forEachGlyph = function(text, x, y, fontSize, options, callback) { - x = x !== undefined ? x : 0; - y = y !== undefined ? y : 0; - fontSize = fontSize !== undefined ? fontSize : 72; - options = Object.assign({}, this.defaultRenderOptions, options); - var fontScale = 1 / this.unitsPerEm * fontSize; - var glyphs = this.stringToGlyphs(text, options); - var kerningLookups; - if (options.kerning) { - var script = options.script || this.position.getDefaultScriptName(); - kerningLookups = this.position.getKerningTables(script, options.language); - } - for (var i = 0; i < glyphs.length; i += 1) { - var glyph = glyphs[i]; - callback.call(this, glyph, x, y, fontSize, options); - if (glyph.advanceWidth) { - x += glyph.advanceWidth * fontScale; - } - - if (options.kerning && i < glyphs.length - 1) { - // We should apply position adjustment lookups in a more generic way. - // Here we only use the xAdvance value. - var kerningValue = kerningLookups ? - this.position.getKerningValue(kerningLookups, glyph.index, glyphs[i + 1].index) : - this.getKerningValue(glyph, glyphs[i + 1]); - x += kerningValue * fontScale; - } - - if (options.letterSpacing) { - x += options.letterSpacing * fontSize; - } else if (options.tracking) { - x += (options.tracking / 1000) * fontSize; - } - } - return x; - }; - - /** - * Create a Path object that represents the given text. - * @param {string} text - The text to create. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - * @return {opentype.Path} - */ - Font.prototype.getPath = function(text, x, y, fontSize, options) { - var fullPath = new Path(); - this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { - var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); - fullPath.extend(glyphPath); - }); - return fullPath; - }; - - /** - * Create an array of Path objects that represent the glyphs of a given text. - * @param {string} text - The text to create. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - * @return {opentype.Path[]} - */ - Font.prototype.getPaths = function(text, x, y, fontSize, options) { - var glyphPaths = []; - this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { - var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); - glyphPaths.push(glyphPath); - }); - - return glyphPaths; - }; - - /** - * Returns the advance width of a text. - * - * This is something different than Path.getBoundingBox() as for example a - * suffixed whitespace increases the advanceWidth but not the bounding box - * or an overhanging letter like a calligraphic 'f' might have a quite larger - * bounding box than its advance width. - * - * This corresponds to canvas2dContext.measureText(text).width - * - * @param {string} text - The text to create. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - * @return advance width - */ - Font.prototype.getAdvanceWidth = function(text, fontSize, options) { - return this.forEachGlyph(text, 0, 0, fontSize, options, function() {}); - }; - - /** - * Draw the text on the given drawing context. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {string} text - The text to create. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - */ - Font.prototype.draw = function(ctx, text, x, y, fontSize, options) { - this.getPath(text, x, y, fontSize, options).draw(ctx); - }; - - /** - * Draw the points of all glyphs in the text. - * On-curve points will be drawn in blue, off-curve points will be drawn in red. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {string} text - The text to create. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - */ - Font.prototype.drawPoints = function(ctx, text, x, y, fontSize, options) { - this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { - glyph.drawPoints(ctx, gX, gY, gFontSize); - }); - }; - - /** - * Draw lines indicating important font measurements for all glyphs in the text. - * Black lines indicate the origin of the coordinate system (point 0,0). - * Blue lines indicate the glyph bounding box. - * Green line indicates the advance width of the glyph. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {string} text - The text to create. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - */ - Font.prototype.drawMetrics = function(ctx, text, x, y, fontSize, options) { - this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { - glyph.drawMetrics(ctx, gX, gY, gFontSize); - }); - }; - - /** - * @param {string} - * @return {string} - */ - Font.prototype.getEnglishName = function(name) { - var translations = this.names[name]; - if (translations) { - return translations.en; - } - }; - - /** - * Validate - */ - Font.prototype.validate = function() { - var _this = this; - - function assert(predicate, message) { - } - - function assertNamePresent(name) { - var englishName = _this.getEnglishName(name); - assert(englishName && englishName.trim().length > 0); - } - - // Identification information - assertNamePresent('fontFamily'); - assertNamePresent('weightName'); - assertNamePresent('manufacturer'); - assertNamePresent('copyright'); - assertNamePresent('version'); - - // Dimension information - assert(this.unitsPerEm > 0); - }; - - /** - * Convert the font object to a SFNT data structure. - * This structure contains all the necessary tables and metadata to create a binary OTF file. - * @return {opentype.Table} - */ - Font.prototype.toTables = function() { - return sfnt.fontToTable(this); - }; - /** - * @deprecated Font.toBuffer is deprecated. Use Font.toArrayBuffer instead. - */ - Font.prototype.toBuffer = function() { - console.warn('Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.'); - return this.toArrayBuffer(); - }; - /** - * Converts a `opentype.Font` into an `ArrayBuffer` - * @return {ArrayBuffer} - */ - Font.prototype.toArrayBuffer = function() { - var sfntTable = this.toTables(); - var bytes = sfntTable.encode(); - var buffer = new ArrayBuffer(bytes.length); - var intArray = new Uint8Array(buffer); - for (var i = 0; i < bytes.length; i++) { - intArray[i] = bytes[i]; - } - - return buffer; - }; - - /** - * Initiate a download of the OpenType font. - */ - Font.prototype.download = function(fileName) { - var familyName = this.getEnglishName('fontFamily'); - var styleName = this.getEnglishName('fontSubfamily'); - fileName = fileName || familyName.replace(/\s/g, '') + '-' + styleName + '.otf'; - var arrayBuffer = this.toArrayBuffer(); - - if (isBrowser()) { - window.URL = window.URL || window.webkitURL; - - if (window.URL) { - var dataView = new DataView(arrayBuffer); - var blob = new Blob([dataView], {type: 'font/opentype'}); - - var link = document.createElement('a'); - link.href = window.URL.createObjectURL(blob); - link.download = fileName; - - var event = document.createEvent('MouseEvents'); - event.initEvent('click', true, false); - link.dispatchEvent(event); - } else { - console.warn('Font file could not be downloaded. Try using a different browser.'); - } - } else { - var fs = require('fs'); - var buffer = arrayBufferToNodeBuffer(arrayBuffer); - fs.writeFileSync(fileName, buffer); - } - }; - /** - * @private - */ - Font.prototype.fsSelectionValues = { - ITALIC: 0x001, //1 - UNDERSCORE: 0x002, //2 - NEGATIVE: 0x004, //4 - OUTLINED: 0x008, //8 - STRIKEOUT: 0x010, //16 - BOLD: 0x020, //32 - REGULAR: 0x040, //64 - USER_TYPO_METRICS: 0x080, //128 - WWS: 0x100, //256 - OBLIQUE: 0x200 //512 - }; - - /** - * @private - */ - Font.prototype.usWidthClasses = { - ULTRA_CONDENSED: 1, - EXTRA_CONDENSED: 2, - CONDENSED: 3, - SEMI_CONDENSED: 4, - MEDIUM: 5, - SEMI_EXPANDED: 6, - EXPANDED: 7, - EXTRA_EXPANDED: 8, - ULTRA_EXPANDED: 9 - }; - - /** - * @private - */ - Font.prototype.usWeightClasses = { - THIN: 100, - EXTRA_LIGHT: 200, - LIGHT: 300, - NORMAL: 400, - MEDIUM: 500, - SEMI_BOLD: 600, - BOLD: 700, - EXTRA_BOLD: 800, - BLACK: 900 - }; - - // The `fvar` table stores font variation axes and instances. - - function addName(name, names) { - var nameString = JSON.stringify(name); - var nameID = 256; - for (var nameKey in names) { - var n = parseInt(nameKey); - if (!n || n < 256) { - continue; - } - - if (JSON.stringify(names[nameKey]) === nameString) { - return n; - } - - if (nameID <= n) { - nameID = n + 1; - } - } - - names[nameID] = name; - return nameID; - } - - function makeFvarAxis(n, axis, names) { - var nameID = addName(axis.name, names); - return [ - {name: 'tag_' + n, type: 'TAG', value: axis.tag}, - {name: 'minValue_' + n, type: 'FIXED', value: axis.minValue << 16}, - {name: 'defaultValue_' + n, type: 'FIXED', value: axis.defaultValue << 16}, - {name: 'maxValue_' + n, type: 'FIXED', value: axis.maxValue << 16}, - {name: 'flags_' + n, type: 'USHORT', value: 0}, - {name: 'nameID_' + n, type: 'USHORT', value: nameID} - ]; - } - - function parseFvarAxis(data, start, names) { - var axis = {}; - var p = new parse.Parser(data, start); - axis.tag = p.parseTag(); - axis.minValue = p.parseFixed(); - axis.defaultValue = p.parseFixed(); - axis.maxValue = p.parseFixed(); - p.skip('uShort', 1); // reserved for flags; no values defined - axis.name = names[p.parseUShort()] || {}; - return axis; - } - - function makeFvarInstance(n, inst, axes, names) { - var nameID = addName(inst.name, names); - var fields = [ - {name: 'nameID_' + n, type: 'USHORT', value: nameID}, - {name: 'flags_' + n, type: 'USHORT', value: 0} - ]; - - for (var i = 0; i < axes.length; ++i) { - var axisTag = axes[i].tag; - fields.push({ - name: 'axis_' + n + ' ' + axisTag, - type: 'FIXED', - value: inst.coordinates[axisTag] << 16 - }); - } - - return fields; - } - - function parseFvarInstance(data, start, axes, names) { - var inst = {}; - var p = new parse.Parser(data, start); - inst.name = names[p.parseUShort()] || {}; - p.skip('uShort', 1); // reserved for flags; no values defined - - inst.coordinates = {}; - for (var i = 0; i < axes.length; ++i) { - inst.coordinates[axes[i].tag] = p.parseFixed(); - } - - return inst; - } - - function makeFvarTable(fvar, names) { - var result = new table.Table('fvar', [ - {name: 'version', type: 'ULONG', value: 0x10000}, - {name: 'offsetToData', type: 'USHORT', value: 0}, - {name: 'countSizePairs', type: 'USHORT', value: 2}, - {name: 'axisCount', type: 'USHORT', value: fvar.axes.length}, - {name: 'axisSize', type: 'USHORT', value: 20}, - {name: 'instanceCount', type: 'USHORT', value: fvar.instances.length}, - {name: 'instanceSize', type: 'USHORT', value: 4 + fvar.axes.length * 4} - ]); - result.offsetToData = result.sizeOf(); - - for (var i = 0; i < fvar.axes.length; i++) { - result.fields = result.fields.concat(makeFvarAxis(i, fvar.axes[i], names)); - } - - for (var j = 0; j < fvar.instances.length; j++) { - result.fields = result.fields.concat(makeFvarInstance(j, fvar.instances[j], fvar.axes, names)); - } - - return result; - } - - function parseFvarTable(data, start, names) { - var p = new parse.Parser(data, start); - var tableVersion = p.parseULong(); - check.argument(tableVersion === 0x00010000, 'Unsupported fvar table version.'); - var offsetToData = p.parseOffset16(); - // Skip countSizePairs. - p.skip('uShort', 1); - var axisCount = p.parseUShort(); - var axisSize = p.parseUShort(); - var instanceCount = p.parseUShort(); - var instanceSize = p.parseUShort(); - - var axes = []; - for (var i = 0; i < axisCount; i++) { - axes.push(parseFvarAxis(data, start + offsetToData + i * axisSize, names)); - } - - var instances = []; - var instanceStart = start + offsetToData + axisCount * axisSize; - for (var j = 0; j < instanceCount; j++) { - instances.push(parseFvarInstance(data, instanceStart + j * instanceSize, axes, names)); - } - - return {axes: axes, instances: instances}; - } - - var fvar = { make: makeFvarTable, parse: parseFvarTable }; - - // The `GPOS` table contains kerning pairs, among other things. - - var subtableParsers$1 = new Array(10); // subtableParsers[0] is unused - - // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-1-single-adjustment-positioning-subtable - // this = Parser instance - subtableParsers$1[1] = function parseLookup1() { - var start = this.offset + this.relativeOffset; - var posformat = this.parseUShort(); - if (posformat === 1) { - return { - posFormat: 1, - coverage: this.parsePointer(Parser.coverage), - value: this.parseValueRecord() - }; - } else if (posformat === 2) { - return { - posFormat: 2, - coverage: this.parsePointer(Parser.coverage), - values: this.parseValueRecordList() - }; - } - check.assert(false, '0x' + start.toString(16) + ': GPOS lookup type 1 format must be 1 or 2.'); - }; - - // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-2-pair-adjustment-positioning-subtable - subtableParsers$1[2] = function parseLookup2() { - var start = this.offset + this.relativeOffset; - var posFormat = this.parseUShort(); - check.assert(posFormat === 1 || posFormat === 2, '0x' + start.toString(16) + ': GPOS lookup type 2 format must be 1 or 2.'); - var coverage = this.parsePointer(Parser.coverage); - var valueFormat1 = this.parseUShort(); - var valueFormat2 = this.parseUShort(); - if (posFormat === 1) { - // Adjustments for Glyph Pairs - return { - posFormat: posFormat, - coverage: coverage, - valueFormat1: valueFormat1, - valueFormat2: valueFormat2, - pairSets: this.parseList(Parser.pointer(Parser.list(function() { - return { // pairValueRecord - secondGlyph: this.parseUShort(), - value1: this.parseValueRecord(valueFormat1), - value2: this.parseValueRecord(valueFormat2) - }; - }))) - }; - } else if (posFormat === 2) { - var classDef1 = this.parsePointer(Parser.classDef); - var classDef2 = this.parsePointer(Parser.classDef); - var class1Count = this.parseUShort(); - var class2Count = this.parseUShort(); - return { - // Class Pair Adjustment - posFormat: posFormat, - coverage: coverage, - valueFormat1: valueFormat1, - valueFormat2: valueFormat2, - classDef1: classDef1, - classDef2: classDef2, - class1Count: class1Count, - class2Count: class2Count, - classRecords: this.parseList(class1Count, Parser.list(class2Count, function() { - return { - value1: this.parseValueRecord(valueFormat1), - value2: this.parseValueRecord(valueFormat2) - }; - })) - }; - } - }; - - subtableParsers$1[3] = function parseLookup3() { return { error: 'GPOS Lookup 3 not supported' }; }; - subtableParsers$1[4] = function parseLookup4() { return { error: 'GPOS Lookup 4 not supported' }; }; - subtableParsers$1[5] = function parseLookup5() { return { error: 'GPOS Lookup 5 not supported' }; }; - subtableParsers$1[6] = function parseLookup6() { return { error: 'GPOS Lookup 6 not supported' }; }; - subtableParsers$1[7] = function parseLookup7() { return { error: 'GPOS Lookup 7 not supported' }; }; - subtableParsers$1[8] = function parseLookup8() { return { error: 'GPOS Lookup 8 not supported' }; }; - subtableParsers$1[9] = function parseLookup9() { return { error: 'GPOS Lookup 9 not supported' }; }; - - // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos - function parseGposTable(data, start) { - start = start || 0; - var p = new Parser(data, start); - var tableVersion = p.parseVersion(1); - check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GPOS table version ' + tableVersion); - - if (tableVersion === 1) { - return { - version: tableVersion, - scripts: p.parseScriptList(), - features: p.parseFeatureList(), - lookups: p.parseLookupList(subtableParsers$1) - }; - } else { - return { - version: tableVersion, - scripts: p.parseScriptList(), - features: p.parseFeatureList(), - lookups: p.parseLookupList(subtableParsers$1), - variations: p.parseFeatureVariationsList() - }; - } - - } - - // GPOS Writing ////////////////////////////////////////////// - // NOT SUPPORTED - var subtableMakers$1 = new Array(10); - - function makeGposTable(gpos) { - return new table.Table('GPOS', [ - {name: 'version', type: 'ULONG', value: 0x10000}, - {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gpos.scripts)}, - {name: 'features', type: 'TABLE', value: new table.FeatureList(gpos.features)}, - {name: 'lookups', type: 'TABLE', value: new table.LookupList(gpos.lookups, subtableMakers$1)} - ]); - } - - var gpos = { parse: parseGposTable, make: makeGposTable }; - - // The `kern` table contains kerning pairs. - - function parseWindowsKernTable(p) { - var pairs = {}; - // Skip nTables. - p.skip('uShort'); - var subtableVersion = p.parseUShort(); - check.argument(subtableVersion === 0, 'Unsupported kern sub-table version.'); - // Skip subtableLength, subtableCoverage - p.skip('uShort', 2); - var nPairs = p.parseUShort(); - // Skip searchRange, entrySelector, rangeShift. - p.skip('uShort', 3); - for (var i = 0; i < nPairs; i += 1) { - var leftIndex = p.parseUShort(); - var rightIndex = p.parseUShort(); - var value = p.parseShort(); - pairs[leftIndex + ',' + rightIndex] = value; - } - return pairs; - } - - function parseMacKernTable(p) { - var pairs = {}; - // The Mac kern table stores the version as a fixed (32 bits) but we only loaded the first 16 bits. - // Skip the rest. - p.skip('uShort'); - var nTables = p.parseULong(); - //check.argument(nTables === 1, 'Only 1 subtable is supported (got ' + nTables + ').'); - if (nTables > 1) { - console.warn('Only the first kern subtable is supported.'); - } - p.skip('uLong'); - var coverage = p.parseUShort(); - var subtableVersion = coverage & 0xFF; - p.skip('uShort'); - if (subtableVersion === 0) { - var nPairs = p.parseUShort(); - // Skip searchRange, entrySelector, rangeShift. - p.skip('uShort', 3); - for (var i = 0; i < nPairs; i += 1) { - var leftIndex = p.parseUShort(); - var rightIndex = p.parseUShort(); - var value = p.parseShort(); - pairs[leftIndex + ',' + rightIndex] = value; - } - } - return pairs; - } - - // Parse the `kern` table which contains kerning pairs. - function parseKernTable(data, start) { - var p = new parse.Parser(data, start); - var tableVersion = p.parseUShort(); - if (tableVersion === 0) { - return parseWindowsKernTable(p); - } else if (tableVersion === 1) { - return parseMacKernTable(p); - } else { - throw new Error('Unsupported kern table version (' + tableVersion + ').'); - } - } - - var kern = { parse: parseKernTable }; - - // The `loca` table stores the offsets to the locations of the glyphs in the font. - - // Parse the `loca` table. This table stores the offsets to the locations of the glyphs in the font, - // relative to the beginning of the glyphData table. - // The number of glyphs stored in the `loca` table is specified in the `maxp` table (under numGlyphs) - // The loca table has two versions: a short version where offsets are stored as uShorts, and a long - // version where offsets are stored as uLongs. The `head` table specifies which version to use - // (under indexToLocFormat). - function parseLocaTable(data, start, numGlyphs, shortVersion) { - var p = new parse.Parser(data, start); - var parseFn = shortVersion ? p.parseUShort : p.parseULong; - // There is an extra entry after the last index element to compute the length of the last glyph. - // That's why we use numGlyphs + 1. - var glyphOffsets = []; - for (var i = 0; i < numGlyphs + 1; i += 1) { - var glyphOffset = parseFn.call(p); - if (shortVersion) { - // The short table version stores the actual offset divided by 2. - glyphOffset *= 2; - } - - glyphOffsets.push(glyphOffset); - } - - return glyphOffsets; - } - - var loca = { parse: parseLocaTable }; - - // opentype.js - - /** - * The opentype library. - * @namespace opentype - */ - - // File loaders ///////////////////////////////////////////////////////// - /** - * Loads a font from a file. The callback throws an error message as the first parameter if it fails - * and the font as an ArrayBuffer in the second parameter if it succeeds. - * @param {string} path - The path of the file - * @param {Function} callback - The function to call when the font load completes - */ - function loadFromFile(path, callback) { - var fs = require('fs'); - fs.readFile(path, function(err, buffer) { - if (err) { - return callback(err.message); - } - - callback(null, nodeBufferToArrayBuffer(buffer)); - }); - } - /** - * Loads a font from a URL. The callback throws an error message as the first parameter if it fails - * and the font as an ArrayBuffer in the second parameter if it succeeds. - * @param {string} url - The URL of the font file. - * @param {Function} callback - The function to call when the font load completes - */ - function loadFromUrl(url, callback) { - var request = new XMLHttpRequest(); - request.open('get', url, true); - request.responseType = 'arraybuffer'; - request.onload = function() { - if (request.response) { - return callback(null, request.response); - } else { - return callback('Font could not be loaded: ' + request.statusText); - } - }; - - request.onerror = function () { - callback('Font could not be loaded'); - }; - - request.send(); - } - - // Table Directory Entries ////////////////////////////////////////////// - /** - * Parses OpenType table entries. - * @param {DataView} - * @param {Number} - * @return {Object[]} - */ - function parseOpenTypeTableEntries(data, numTables) { - var tableEntries = []; - var p = 12; - for (var i = 0; i < numTables; i += 1) { - var tag = parse.getTag(data, p); - var checksum = parse.getULong(data, p + 4); - var offset = parse.getULong(data, p + 8); - var length = parse.getULong(data, p + 12); - tableEntries.push({tag: tag, checksum: checksum, offset: offset, length: length, compression: false}); - p += 16; - } - - return tableEntries; - } - - /** - * Parses WOFF table entries. - * @param {DataView} - * @param {Number} - * @return {Object[]} - */ - function parseWOFFTableEntries(data, numTables) { - var tableEntries = []; - var p = 44; // offset to the first table directory entry. - for (var i = 0; i < numTables; i += 1) { - var tag = parse.getTag(data, p); - var offset = parse.getULong(data, p + 4); - var compLength = parse.getULong(data, p + 8); - var origLength = parse.getULong(data, p + 12); - var compression = (void 0); - if (compLength < origLength) { - compression = 'WOFF'; - } else { - compression = false; - } - - tableEntries.push({tag: tag, offset: offset, compression: compression, - compressedLength: compLength, length: origLength}); - p += 20; - } - - return tableEntries; - } - - /** - * @typedef TableData - * @type Object - * @property {DataView} data - The DataView - * @property {number} offset - The data offset. - */ - - /** - * @param {DataView} - * @param {Object} - * @return {TableData} - */ - function uncompressTable(data, tableEntry) { - if (tableEntry.compression === 'WOFF') { - var inBuffer = new Uint8Array(data.buffer, tableEntry.offset + 2, tableEntry.compressedLength - 2); - var outBuffer = new Uint8Array(tableEntry.length); - tinyInflate(inBuffer, outBuffer); - if (outBuffer.byteLength !== tableEntry.length) { - throw new Error('Decompression error: ' + tableEntry.tag + ' decompressed length doesn\'t match recorded length'); - } - - var view = new DataView(outBuffer.buffer, 0); - return {data: view, offset: 0}; - } else { - return {data: data, offset: tableEntry.offset}; - } - } - - // Public API /////////////////////////////////////////////////////////// - - /** - * Parse the OpenType file data (as an ArrayBuffer) and return a Font object. - * Throws an error if the font could not be parsed. - * @param {ArrayBuffer} - * @param {Object} opt - options for parsing - * @return {opentype.Font} - */ - function parseBuffer(buffer, opt) { - opt = (opt === undefined || opt === null) ? {} : opt; - - var indexToLocFormat; - var ltagTable; - - // Since the constructor can also be called to create new fonts from scratch, we indicate this - // should be an empty font that we'll fill with our own data. - var font = new Font({empty: true}); - - // OpenType fonts use big endian byte ordering. - // We can't rely on typed array view types, because they operate with the endianness of the host computer. - // Instead we use DataViews where we can specify endianness. - var data = new DataView(buffer, 0); - var numTables; - var tableEntries = []; - var signature = parse.getTag(data, 0); - if (signature === String.fromCharCode(0, 1, 0, 0) || signature === 'true' || signature === 'typ1') { - font.outlinesFormat = 'truetype'; - numTables = parse.getUShort(data, 4); - tableEntries = parseOpenTypeTableEntries(data, numTables); - } else if (signature === 'OTTO') { - font.outlinesFormat = 'cff'; - numTables = parse.getUShort(data, 4); - tableEntries = parseOpenTypeTableEntries(data, numTables); - } else if (signature === 'wOFF') { - var flavor = parse.getTag(data, 4); - if (flavor === String.fromCharCode(0, 1, 0, 0)) { - font.outlinesFormat = 'truetype'; - } else if (flavor === 'OTTO') { - font.outlinesFormat = 'cff'; - } else { - throw new Error('Unsupported OpenType flavor ' + signature); - } - - numTables = parse.getUShort(data, 12); - tableEntries = parseWOFFTableEntries(data, numTables); - } else { - throw new Error('Unsupported OpenType signature ' + signature); - } - - var cffTableEntry; - var fvarTableEntry; - var glyfTableEntry; - var gposTableEntry; - var gsubTableEntry; - var hmtxTableEntry; - var kernTableEntry; - var locaTableEntry; - var nameTableEntry; - var metaTableEntry; - var p; - - for (var i = 0; i < numTables; i += 1) { - var tableEntry = tableEntries[i]; - var table = (void 0); - switch (tableEntry.tag) { - case 'cmap': - table = uncompressTable(data, tableEntry); - font.tables.cmap = cmap.parse(table.data, table.offset); - font.encoding = new CmapEncoding(font.tables.cmap); - break; - case 'cvt ' : - table = uncompressTable(data, tableEntry); - p = new parse.Parser(table.data, table.offset); - font.tables.cvt = p.parseShortList(tableEntry.length / 2); - break; - case 'fvar': - fvarTableEntry = tableEntry; - break; - case 'fpgm' : - table = uncompressTable(data, tableEntry); - p = new parse.Parser(table.data, table.offset); - font.tables.fpgm = p.parseByteList(tableEntry.length); - break; - case 'head': - table = uncompressTable(data, tableEntry); - font.tables.head = head.parse(table.data, table.offset); - font.unitsPerEm = font.tables.head.unitsPerEm; - indexToLocFormat = font.tables.head.indexToLocFormat; - break; - case 'hhea': - table = uncompressTable(data, tableEntry); - font.tables.hhea = hhea.parse(table.data, table.offset); - font.ascender = font.tables.hhea.ascender; - font.descender = font.tables.hhea.descender; - font.numberOfHMetrics = font.tables.hhea.numberOfHMetrics; - break; - case 'hmtx': - hmtxTableEntry = tableEntry; - break; - case 'ltag': - table = uncompressTable(data, tableEntry); - ltagTable = ltag.parse(table.data, table.offset); - break; - case 'maxp': - table = uncompressTable(data, tableEntry); - font.tables.maxp = maxp.parse(table.data, table.offset); - font.numGlyphs = font.tables.maxp.numGlyphs; - break; - case 'name': - nameTableEntry = tableEntry; - break; - case 'OS/2': - table = uncompressTable(data, tableEntry); - font.tables.os2 = os2.parse(table.data, table.offset); - break; - case 'post': - table = uncompressTable(data, tableEntry); - font.tables.post = post.parse(table.data, table.offset); - font.glyphNames = new GlyphNames(font.tables.post); - break; - case 'prep' : - table = uncompressTable(data, tableEntry); - p = new parse.Parser(table.data, table.offset); - font.tables.prep = p.parseByteList(tableEntry.length); - break; - case 'glyf': - glyfTableEntry = tableEntry; - break; - case 'loca': - locaTableEntry = tableEntry; - break; - case 'CFF ': - cffTableEntry = tableEntry; - break; - case 'kern': - kernTableEntry = tableEntry; - break; - case 'GPOS': - gposTableEntry = tableEntry; - break; - case 'GSUB': - gsubTableEntry = tableEntry; - break; - case 'meta': - metaTableEntry = tableEntry; - break; - } - } - - var nameTable = uncompressTable(data, nameTableEntry); - font.tables.name = _name.parse(nameTable.data, nameTable.offset, ltagTable); - font.names = font.tables.name; - - if (glyfTableEntry && locaTableEntry) { - var shortVersion = indexToLocFormat === 0; - var locaTable = uncompressTable(data, locaTableEntry); - var locaOffsets = loca.parse(locaTable.data, locaTable.offset, font.numGlyphs, shortVersion); - var glyfTable = uncompressTable(data, glyfTableEntry); - font.glyphs = glyf.parse(glyfTable.data, glyfTable.offset, locaOffsets, font, opt); - } else if (cffTableEntry) { - var cffTable = uncompressTable(data, cffTableEntry); - cff.parse(cffTable.data, cffTable.offset, font, opt); - } else { - throw new Error('Font doesn\'t contain TrueType or CFF outlines.'); - } - - var hmtxTable = uncompressTable(data, hmtxTableEntry); - hmtx.parse(font, hmtxTable.data, hmtxTable.offset, font.numberOfHMetrics, font.numGlyphs, font.glyphs, opt); - addGlyphNames(font, opt); - - if (kernTableEntry) { - var kernTable = uncompressTable(data, kernTableEntry); - font.kerningPairs = kern.parse(kernTable.data, kernTable.offset); - } else { - font.kerningPairs = {}; - } - - if (gposTableEntry) { - var gposTable = uncompressTable(data, gposTableEntry); - font.tables.gpos = gpos.parse(gposTable.data, gposTable.offset); - font.position.init(); - } - - if (gsubTableEntry) { - var gsubTable = uncompressTable(data, gsubTableEntry); - font.tables.gsub = gsub.parse(gsubTable.data, gsubTable.offset); - } - - if (fvarTableEntry) { - var fvarTable = uncompressTable(data, fvarTableEntry); - font.tables.fvar = fvar.parse(fvarTable.data, fvarTable.offset, font.names); - } - - if (metaTableEntry) { - var metaTable = uncompressTable(data, metaTableEntry); - font.tables.meta = meta.parse(metaTable.data, metaTable.offset); - font.metas = font.tables.meta; - } - - return font; - } - - /** - * Asynchronously load the font from a URL or a filesystem. When done, call the callback - * with two arguments `(err, font)`. The `err` will be null on success, - * the `font` is a Font object. - * We use the node.js callback convention so that - * opentype.js can integrate with frameworks like async.js. - * @alias opentype.load - * @param {string} url - The URL of the font to load. - * @param {Function} callback - The callback. - */ - function load(url, callback, opt) { - var isNode = typeof window === 'undefined'; - var loadFn = isNode ? loadFromFile : loadFromUrl; - - return new Promise(function (resolve, reject) { - loadFn(url, function(err, arrayBuffer) { - if (err) { - if (callback) { - return callback(err); - } else { - reject(err); - } - } - var font; - try { - font = parseBuffer(arrayBuffer, opt); - } catch (e) { - if (callback) { - return callback(e, null); - } else { - reject(e); - } - } - if (callback) { - return callback(null, font); - } else { - resolve(font); - } - }); - }); - } - - /** - * Synchronously load the font from a URL or file. - * When done, returns the font object or throws an error. - * @alias opentype.loadSync - * @param {string} url - The URL of the font to load. - * @param {Object} opt - opt.lowMemory - * @return {opentype.Font} - */ - function loadSync(url, opt) { - var fs = require('fs'); - var buffer = fs.readFileSync(url); - return parseBuffer(nodeBufferToArrayBuffer(buffer), opt); - } - - var opentype = /*#__PURE__*/Object.freeze({ - __proto__: null, - Font: Font, - Glyph: Glyph, - Path: Path, - BoundingBox: BoundingBox, - _parse: parse, - parse: parseBuffer, - load: load, - loadSync: loadSync - }); - - exports.BoundingBox = BoundingBox; - exports.Font = Font; - exports.Glyph = Glyph; - exports.Path = Path; - exports._parse = parse; - exports.default = opentype; - exports.load = load; - exports.loadSync = loadSync; - exports.parse = parseBuffer; - - Object.defineProperty(exports, '__esModule', { value: true }); - -}))); -//# sourceMappingURL=opentype.js.map diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/package.json b/node_modules/lv_font_conv/node_modules/opentype.js/package.json deleted file mode 100644 index 8c75ccad..00000000 --- a/node_modules/lv_font_conv/node_modules/opentype.js/package.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "_args": [ - [ - "opentype.js@1.3.3", - "/home/vitaly/Dropbox/Coding/lv_font_conv" - ] - ], - "_from": "opentype.js@1.3.3", - "_id": "opentype.js@1.3.3", - "_inBundle": false, - "_integrity": "sha512-/qIY/+WnKGlPIIPhbeNjynfD2PO15G9lA/xqlX2bDH+4lc3Xz5GCQ68mqxj3DdUv6AJqCeaPvuAoH8mVL0zcuA==", - "_location": "/opentype.js", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "opentype.js@1.3.3", - "name": "opentype.js", - "escapedName": "opentype.js", - "rawSpec": "1.3.3", - "saveSpec": null, - "fetchSpec": "1.3.3" - }, - "_requiredBy": [ - "/" - ], - "_resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.3.tgz", - "_spec": "1.3.3", - "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", - "author": { - "name": "Frederik De Bleser", - "email": "frederik@debleser.be" - }, - "bin": { - "ot": "bin/ot" - }, - "browser": { - "fs": false - }, - "bugs": { - "url": "https://github.com/opentypejs/opentype.js/issues" - }, - "dependencies": { - "string.prototype.codepointat": "^0.2.1", - "tiny-inflate": "^1.0.3" - }, - "description": "OpenType font parser", - "devDependencies": { - "@babel/preset-env": "^7.9.5", - "buble": "^0.20.0", - "cross-env": "^7.0.2", - "jscs": "^3.0.7", - "jshint": "^2.11.0", - "mocha": "^7.1.1", - "reify": "^0.20.12", - "rollup": "^1.32.1", - "rollup-plugin-buble": "^0.19.8", - "rollup-plugin-commonjs": "^10.1.0", - "rollup-plugin-license": "^0.9.0", - "rollup-plugin-node-resolve": "^5.2.0", - "uglify-js": "^3.8.1" - }, - "engines": { - "node": ">= 8.0.0" - }, - "files": [ - "LICENSE", - "RELEASES.md", - "README.md", - "bin", - "dist", - "src" - ], - "homepage": "https://github.com/opentypejs/opentype.js#readme", - "keywords": [ - "graphics", - "fonts", - "font", - "opentype", - "otf", - "ttf", - "woff", - "type" - ], - "license": "MIT", - "main": "dist/opentype.js", - "module": "dist/opentype.module.js", - "name": "opentype.js", - "repository": { - "type": "git", - "url": "git://github.com/opentypejs/opentype.js.git" - }, - "scripts": { - "build": "rollup -c", - "dist": "npm run test && npm run build && npm run minify", - "minify": "uglifyjs --source-map \"url='opentype.min.js.map'\" --compress --mangle --output ./dist/opentype.min.js -- ./dist/opentype.js", - "start": "node ./bin/server.js", - "test": "mocha --require reify --recursive && jshint . && jscs .", - "watch": "rollup -c -w" - }, - "version": "1.3.3" -} diff --git a/node_modules/lv_font_conv/node_modules/pngjs/LICENSE b/node_modules/lv_font_conv/node_modules/pngjs/LICENSE deleted file mode 100644 index 6942e254..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -pngjs2 original work Copyright (c) 2015 Luke Page & Original Contributors -pngjs derived work Copyright (c) 2012 Kuba Niegowski - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/pngjs/README.md b/node_modules/lv_font_conv/node_modules/pngjs/README.md deleted file mode 100644 index 2aef03d9..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/README.md +++ /dev/null @@ -1,433 +0,0 @@ -[![Build Status](https://travis-ci.com/lukeapage/pngjs.svg?branch=master)](https://travis-ci.com/lukeapage/pngjs) [![Build status](https://ci.appveyor.com/api/projects/status/qo5x8ayutr028108/branch/master?svg=true)](https://ci.appveyor.com/project/lukeapage/pngjs/branch/master) [![codecov](https://codecov.io/gh/lukeapage/pngjs/branch/master/graph/badge.svg)](https://codecov.io/gh/lukeapage/pngjs) [![npm version](https://badge.fury.io/js/pngjs.svg)](http://badge.fury.io/js/pngjs) - -# pngjs - -Simple PNG encoder/decoder for Node.js with no dependencies. - -Based on the original [pngjs](https://github.com/niegowski/node-pngjs) with the follow enhancements. - -- Support for reading 1,2,4 & 16 bit files -- Support for reading interlace files -- Support for reading `tTRNS` transparent colours -- Support for writing colortype 0 (grayscale), colortype 2 (RGB), colortype 4 (grayscale alpha) and colortype 6 (RGBA) -- Sync interface as well as async -- API compatible with pngjs and node-pngjs - -Known lack of support for: - -- Extended PNG e.g. Animation -- Writing in colortype 3 (indexed color) - -# Table of Contents - -- [Requirements](#requirements) -- [Comparison Table](#comparison-table) -- [Tests](#tests) -- [Installation](#installation) -- [Browser](#browser) -- [Example](#example) -- [Async API](#async-api) -- [Sync API](#sync-api) -- [Changelog](#changelog) - -# Comparison Table - -| Name | Forked From | Sync | Async | 16 Bit | 1/2/4 Bit | Interlace | Gamma | Encodes | Tested | -| ------------- | ----------- | ---- | ----- | ------ | --------- | --------- | ------ | ------- | ------ | -| pngjs | | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | -| node-png | pngjs | No | Yes | No | No | No | Hidden | Yes | Manual | -| png-coder | pngjs | No | Yes | Yes | No | No | Hidden | Yes | Manual | -| pngparse | | No | Yes | No | Yes | No | No | No | Yes | -| pngparse-sync | pngparse | Yes | No | No | Yes | No | No | No | Yes | -| png-async | | No | Yes | No | No | No | No | Yes | Yes | -| png-js | | No | Yes | No | No | No | No | No | No | - -Native C++ node decoders: - -- png -- png-sync (sync version of above) -- pixel-png -- png-img - -# Tests - -Tested using [PNG Suite](http://www.schaik.com/pngsuite/). We read every file into pngjs, output it in standard 8bit colour, synchronously and asynchronously, then compare the original with the newly saved images. - -To run the tests, fetch the repo (tests are not distributed via npm) and install with `npm i`, run `npm test`. - -The only thing not converted is gamma correction - this is because multiple vendors will do gamma correction differently, so the tests will have different results on different browsers. - -# Installation - -``` -$ npm install pngjs --save -``` - -# Browser - -The package has been build with a [Browserify](browserify.org) version (`npm run browserify`) and you can use the browser version by including in your code: - -``` -import { PNG } from 'pngjs/browser'; -``` - -# Example - -```js -var fs = require("fs"), - PNG = require("pngjs").PNG; - -fs.createReadStream("in.png") - .pipe( - new PNG({ - filterType: 4, - }) - ) - .on("parsed", function () { - for (var y = 0; y < this.height; y++) { - for (var x = 0; x < this.width; x++) { - var idx = (this.width * y + x) << 2; - - // invert color - this.data[idx] = 255 - this.data[idx]; - this.data[idx + 1] = 255 - this.data[idx + 1]; - this.data[idx + 2] = 255 - this.data[idx + 2]; - - // and reduce opacity - this.data[idx + 3] = this.data[idx + 3] >> 1; - } - } - - this.pack().pipe(fs.createWriteStream("out.png")); - }); -``` - -For more examples see `examples` folder. - -# Async API - -As input any color type is accepted (grayscale, rgb, palette, grayscale with alpha, rgb with alpha) but 8 bit per sample (channel) is the only supported bit depth. Interlaced mode is not supported. - -## Class: PNG - -`PNG` is readable and writable `Stream`. - -### Options - -- `width` - use this with `height` if you want to create png from scratch -- `height` - as above -- `checkCRC` - whether parser should be strict about checksums in source stream (default: `true`) -- `deflateChunkSize` - chunk size used for deflating data chunks, this should be power of 2 and must not be less than 256 and more than 32\*1024 (default: 32 kB) -- `deflateLevel` - compression level for deflate (default: 9) -- `deflateStrategy` - compression strategy for deflate (default: 3) -- `deflateFactory` - deflate stream factory (default: `zlib.createDeflate`) -- `filterType` - png filtering method for scanlines (default: -1 => auto, accepts array of numbers 0-4) -- `colorType` - the output colorType - see constants. 0 = grayscale, no alpha, 2 = color, no alpha, 4 = grayscale & alpha, 6 = color & alpha. Default currently 6, but in the future may calculate best mode. -- `inputColorType` - the input colorType - see constants. Default is 6 (RGBA) -- `bitDepth` - the bitDepth of the output, 8 or 16 bits. Input data is expected to have this bit depth. - 16 bit data is expected in the system endianness (Default: 8) -- `inputHasAlpha` - whether the input bitmap has 4 bytes per pixel (rgb and alpha) or 3 (rgb - no alpha). -- `bgColor` - an object containing red, green, and blue values between 0 and 255 - that is used when packing a PNG if alpha is not to be included (default: 255,255,255) - -### Event "metadata" - -`function(metadata) { }` -Image's header has been parsed, metadata contains this information: - -- `width` image size in pixels -- `height` image size in pixels -- `palette` image is paletted -- `color` image is not grayscale -- `alpha` image contains alpha channel -- `interlace` image is interlaced - -### Event: "parsed" - -`function(data) { }` -Input image has been completely parsed, `data` is complete and ready for modification. - -### Event: "error" - -`function(error) { }` - -### png.parse(data, [callback]) - -Parses PNG file data. Can be `String` or `Buffer`. Alternatively you can stream data to instance of PNG. - -Optional `callback` is once called on `error` or `parsed`. The callback gets -two arguments `(err, data)`. - -Returns `this` for method chaining. - -#### Example - -```js -new PNG({ filterType: 4 }).parse(imageData, function (error, data) { - console.log(error, data); -}); -``` - -### png.pack() - -Starts converting data to PNG file Stream. - -Returns `this` for method chaining. - -### png.bitblt(dst, sx, sy, w, h, dx, dy) - -Helper for image manipulation, copies a rectangle of pixels from current (i.e. the source) image (`sx`, `sy`, `w`, `h`) to `dst` image (at `dx`, `dy`). - -Returns `this` for method chaining. - -For example, the following code copies the top-left 100x50 px of `in.png` into dst and writes it to `out.png`: - -```js -var dst = new PNG({ width: 100, height: 50 }); -fs.createReadStream("in.png") - .pipe(new PNG()) - .on("parsed", function () { - this.bitblt(dst, 0, 0, 100, 50, 0, 0); - dst.pack().pipe(fs.createWriteStream("out.png")); - }); -``` - -### Property: adjustGamma() - -Helper that takes data and adjusts it to be gamma corrected. Note that it is not 100% reliable with transparent colours because that requires knowing the background colour the bitmap is rendered on to. - -In tests against PNG suite it compared 100% with chrome on all 8 bit and below images. On IE there were some differences. - -The following example reads a file, adjusts the gamma (which sets the gamma to 0) and writes it out again, effectively removing any gamma correction from the image. - -```js -fs.createReadStream("in.png") - .pipe(new PNG()) - .on("parsed", function () { - this.adjustGamma(); - this.pack().pipe(fs.createWriteStream("out.png")); - }); -``` - -### Property: width - -Width of image in pixels - -### Property: height - -Height of image in pixels - -### Property: data - -Buffer of image pixel data. Every pixel consists 4 bytes: R, G, B, A (opacity). - -### Property: gamma - -Gamma of image (0 if not specified) - -## Packing a PNG and removing alpha (RGBA to RGB) - -When removing the alpha channel from an image, there needs to be a background color to correctly -convert each pixel's transparency to the appropriate RGB value. By default, pngjs will flatten -the image against a white background. You can override this in the options: - -```js -var fs = require("fs"), - PNG = require("pngjs").PNG; - -fs.createReadStream("in.png") - .pipe( - new PNG({ - colorType: 2, - bgColor: { - red: 0, - green: 255, - blue: 0, - }, - }) - ) - .on("parsed", function () { - this.pack().pipe(fs.createWriteStream("out.png")); - }); -``` - -# Sync API - -## PNG.sync - -### PNG.sync.read(buffer) - -Take a buffer and returns a PNG image. The properties on the image include the meta data and `data` as per the async API above. - -``` -var data = fs.readFileSync('in.png'); -var png = PNG.sync.read(data); -``` - -### PNG.sync.write(png) - -Take a PNG image and returns a buffer. The properties on the image include the meta data and `data` as per the async API above. - -``` -var data = fs.readFileSync('in.png'); -var png = PNG.sync.read(data); -var options = { colorType: 6 }; -var buffer = PNG.sync.write(png, options); -fs.writeFileSync('out.png', buffer); -``` - -### PNG.adjustGamma(src) - -Adjusts the gamma of a sync image. See the async adjustGamma. - -``` -var data = fs.readFileSync('in.png'); -var png = PNG.sync.read(data); -PNG.adjustGamma(png); -``` - -# Changelog - -### 6.0.0 - 24/10/2020 - -- BREAKING - Sync version now throws if there is unexpected content at the end of the stream. -- BREAKING - Drop support for node 10 (Though nothing incompatible in this release yet) -- Reduce the number of files included in the package - -### 5.1.0 - 13/09/2020 - -- Add option to skip rescaling - -### 5.0.0 - 15/04/2020 - -- Drop support for Node 8 -- Browserified bundle may now contain ES20(15-20) code if the supported node version supports it. Please run the browserified version through babel if you need to support older browsers. - -### 4.0.1 - 15/04/2020 - -- Fix to possible null reference in nextTick of async method - -### 4.0.0 - 09/04/2020 - -- Fix issue in newer nodes with using Buffer -- Fix async issue with some png files -- Drop support for Node 4 & 6 - -### 3.4.0 - 09/03/2019 - -- Include whether the png has alpha in the meta data -- emit an error if the image is truncated instead of hanging -- Add a browserified version -- speed up some mapping functions - -### 3.3.3 - 19/04/2018 - -- Real fix for node 9 - -### 3.3.2 - 16/02/2018 - -- Fix for node 9 - -### 3.3.1 - 15/11/2017 - -- Bugfixes and removal of es6 - -### 3.3.0 - -- Add writing 16 bit channels and support for grayscale input - -### 3.2.0 - 30/04/2017 - -- Support for encoding 8-bit grayscale images - -### 3.1.0 - 30/04/2017 - -- Support for pngs with zlib chunks that are malformed after valid data - -### 3.0.1 - 16/02/2017 - -- Fix single pixel pngs - -### 3.0.0 - 03/08/2016 - -- Drop support for node below v4 and iojs. Pin to 2.3.0 to use with old, unsupported or patched node versions. - -### 2.3.0 - 22/04/2016 - -- Support for sync in node 0.10 - -### 2.2.0 - 04/12/2015 - -- Add sync write api -- Fix newfile example -- Correct comparison table - -### 2.1.0 - 28/10/2015 - -- rename package to pngjs -- added 'bgColor' option - -### 2.0.0 - 08/10/2015 - -- fixes to readme -- _breaking change_ - bitblt on the png prototype now doesn't take a unused, unnecessary src first argument - -### 1.2.0 - 13/09/2015 - -- support passing colorType to write PNG's and writing bitmaps without alpha information - -### 1.1.0 - 07/09/2015 - -- support passing a deflate factory for controlled compression - -### 1.0.2 - 22/08/2015 - -- Expose all PNG creation info - -### 1.0.1 - 21/08/2015 - -- Fix non square interlaced files - -### 1.0.0 - 08/08/2015 - -- More tests -- source linted -- maintainability refactorings -- async API - exceptions in reading now emit warnings -- documentation improvement - sync api now documented, adjustGamma documented -- breaking change - gamma chunk is now written. previously a read then write would destroy gamma information, now it is persisted. - -### 0.0.3 - 03/08/2015 - -- Error handling fixes -- ignore files for smaller npm footprint - -### 0.0.2 - 02/08/2015 - -- Bugfixes to interlacing, support for transparent colours - -### 0.0.1 - 02/08/2015 - -- Initial release, see pngjs for older changelog. - -# License - -(The MIT License) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/bitmapper.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/bitmapper.js deleted file mode 100644 index 18378a02..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/bitmapper.js +++ /dev/null @@ -1,267 +0,0 @@ -"use strict"; - -let interlaceUtils = require("./interlace"); - -let pixelBppMapper = [ - // 0 - dummy entry - function () {}, - - // 1 - L - // 0: 0, 1: 0, 2: 0, 3: 0xff - function (pxData, data, pxPos, rawPos) { - if (rawPos === data.length) { - throw new Error("Ran out of data"); - } - - let pixel = data[rawPos]; - pxData[pxPos] = pixel; - pxData[pxPos + 1] = pixel; - pxData[pxPos + 2] = pixel; - pxData[pxPos + 3] = 0xff; - }, - - // 2 - LA - // 0: 0, 1: 0, 2: 0, 3: 1 - function (pxData, data, pxPos, rawPos) { - if (rawPos + 1 >= data.length) { - throw new Error("Ran out of data"); - } - - let pixel = data[rawPos]; - pxData[pxPos] = pixel; - pxData[pxPos + 1] = pixel; - pxData[pxPos + 2] = pixel; - pxData[pxPos + 3] = data[rawPos + 1]; - }, - - // 3 - RGB - // 0: 0, 1: 1, 2: 2, 3: 0xff - function (pxData, data, pxPos, rawPos) { - if (rawPos + 2 >= data.length) { - throw new Error("Ran out of data"); - } - - pxData[pxPos] = data[rawPos]; - pxData[pxPos + 1] = data[rawPos + 1]; - pxData[pxPos + 2] = data[rawPos + 2]; - pxData[pxPos + 3] = 0xff; - }, - - // 4 - RGBA - // 0: 0, 1: 1, 2: 2, 3: 3 - function (pxData, data, pxPos, rawPos) { - if (rawPos + 3 >= data.length) { - throw new Error("Ran out of data"); - } - - pxData[pxPos] = data[rawPos]; - pxData[pxPos + 1] = data[rawPos + 1]; - pxData[pxPos + 2] = data[rawPos + 2]; - pxData[pxPos + 3] = data[rawPos + 3]; - }, -]; - -let pixelBppCustomMapper = [ - // 0 - dummy entry - function () {}, - - // 1 - L - // 0: 0, 1: 0, 2: 0, 3: 0xff - function (pxData, pixelData, pxPos, maxBit) { - let pixel = pixelData[0]; - pxData[pxPos] = pixel; - pxData[pxPos + 1] = pixel; - pxData[pxPos + 2] = pixel; - pxData[pxPos + 3] = maxBit; - }, - - // 2 - LA - // 0: 0, 1: 0, 2: 0, 3: 1 - function (pxData, pixelData, pxPos) { - let pixel = pixelData[0]; - pxData[pxPos] = pixel; - pxData[pxPos + 1] = pixel; - pxData[pxPos + 2] = pixel; - pxData[pxPos + 3] = pixelData[1]; - }, - - // 3 - RGB - // 0: 0, 1: 1, 2: 2, 3: 0xff - function (pxData, pixelData, pxPos, maxBit) { - pxData[pxPos] = pixelData[0]; - pxData[pxPos + 1] = pixelData[1]; - pxData[pxPos + 2] = pixelData[2]; - pxData[pxPos + 3] = maxBit; - }, - - // 4 - RGBA - // 0: 0, 1: 1, 2: 2, 3: 3 - function (pxData, pixelData, pxPos) { - pxData[pxPos] = pixelData[0]; - pxData[pxPos + 1] = pixelData[1]; - pxData[pxPos + 2] = pixelData[2]; - pxData[pxPos + 3] = pixelData[3]; - }, -]; - -function bitRetriever(data, depth) { - let leftOver = []; - let i = 0; - - function split() { - if (i === data.length) { - throw new Error("Ran out of data"); - } - let byte = data[i]; - i++; - let byte8, byte7, byte6, byte5, byte4, byte3, byte2, byte1; - switch (depth) { - default: - throw new Error("unrecognised depth"); - case 16: - byte2 = data[i]; - i++; - leftOver.push((byte << 8) + byte2); - break; - case 4: - byte2 = byte & 0x0f; - byte1 = byte >> 4; - leftOver.push(byte1, byte2); - break; - case 2: - byte4 = byte & 3; - byte3 = (byte >> 2) & 3; - byte2 = (byte >> 4) & 3; - byte1 = (byte >> 6) & 3; - leftOver.push(byte1, byte2, byte3, byte4); - break; - case 1: - byte8 = byte & 1; - byte7 = (byte >> 1) & 1; - byte6 = (byte >> 2) & 1; - byte5 = (byte >> 3) & 1; - byte4 = (byte >> 4) & 1; - byte3 = (byte >> 5) & 1; - byte2 = (byte >> 6) & 1; - byte1 = (byte >> 7) & 1; - leftOver.push(byte1, byte2, byte3, byte4, byte5, byte6, byte7, byte8); - break; - } - } - - return { - get: function (count) { - while (leftOver.length < count) { - split(); - } - let returner = leftOver.slice(0, count); - leftOver = leftOver.slice(count); - return returner; - }, - resetAfterLine: function () { - leftOver.length = 0; - }, - end: function () { - if (i !== data.length) { - throw new Error("extra data found"); - } - }, - }; -} - -function mapImage8Bit(image, pxData, getPxPos, bpp, data, rawPos) { - // eslint-disable-line max-params - let imageWidth = image.width; - let imageHeight = image.height; - let imagePass = image.index; - for (let y = 0; y < imageHeight; y++) { - for (let x = 0; x < imageWidth; x++) { - let pxPos = getPxPos(x, y, imagePass); - pixelBppMapper[bpp](pxData, data, pxPos, rawPos); - rawPos += bpp; //eslint-disable-line no-param-reassign - } - } - return rawPos; -} - -function mapImageCustomBit(image, pxData, getPxPos, bpp, bits, maxBit) { - // eslint-disable-line max-params - let imageWidth = image.width; - let imageHeight = image.height; - let imagePass = image.index; - for (let y = 0; y < imageHeight; y++) { - for (let x = 0; x < imageWidth; x++) { - let pixelData = bits.get(bpp); - let pxPos = getPxPos(x, y, imagePass); - pixelBppCustomMapper[bpp](pxData, pixelData, pxPos, maxBit); - } - bits.resetAfterLine(); - } -} - -exports.dataToBitMap = function (data, bitmapInfo) { - let width = bitmapInfo.width; - let height = bitmapInfo.height; - let depth = bitmapInfo.depth; - let bpp = bitmapInfo.bpp; - let interlace = bitmapInfo.interlace; - let bits; - - if (depth !== 8) { - bits = bitRetriever(data, depth); - } - let pxData; - if (depth <= 8) { - pxData = Buffer.alloc(width * height * 4); - } else { - pxData = new Uint16Array(width * height * 4); - } - let maxBit = Math.pow(2, depth) - 1; - let rawPos = 0; - let images; - let getPxPos; - - if (interlace) { - images = interlaceUtils.getImagePasses(width, height); - getPxPos = interlaceUtils.getInterlaceIterator(width, height); - } else { - let nonInterlacedPxPos = 0; - getPxPos = function () { - let returner = nonInterlacedPxPos; - nonInterlacedPxPos += 4; - return returner; - }; - images = [{ width: width, height: height }]; - } - - for (let imageIndex = 0; imageIndex < images.length; imageIndex++) { - if (depth === 8) { - rawPos = mapImage8Bit( - images[imageIndex], - pxData, - getPxPos, - bpp, - data, - rawPos - ); - } else { - mapImageCustomBit( - images[imageIndex], - pxData, - getPxPos, - bpp, - bits, - maxBit - ); - } - } - if (depth === 8) { - if (rawPos !== data.length) { - throw new Error("extra data found"); - } - } else { - bits.end(); - } - - return pxData; -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/bitpacker.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/bitpacker.js deleted file mode 100644 index d7a4e656..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/bitpacker.js +++ /dev/null @@ -1,158 +0,0 @@ -"use strict"; - -let constants = require("./constants"); - -module.exports = function (dataIn, width, height, options) { - let outHasAlpha = - [constants.COLORTYPE_COLOR_ALPHA, constants.COLORTYPE_ALPHA].indexOf( - options.colorType - ) !== -1; - if (options.colorType === options.inputColorType) { - let bigEndian = (function () { - let buffer = new ArrayBuffer(2); - new DataView(buffer).setInt16(0, 256, true /* littleEndian */); - // Int16Array uses the platform's endianness. - return new Int16Array(buffer)[0] !== 256; - })(); - // If no need to convert to grayscale and alpha is present/absent in both, take a fast route - if (options.bitDepth === 8 || (options.bitDepth === 16 && bigEndian)) { - return dataIn; - } - } - - // map to a UInt16 array if data is 16bit, fix endianness below - let data = options.bitDepth !== 16 ? dataIn : new Uint16Array(dataIn.buffer); - - let maxValue = 255; - let inBpp = constants.COLORTYPE_TO_BPP_MAP[options.inputColorType]; - if (inBpp === 4 && !options.inputHasAlpha) { - inBpp = 3; - } - let outBpp = constants.COLORTYPE_TO_BPP_MAP[options.colorType]; - if (options.bitDepth === 16) { - maxValue = 65535; - outBpp *= 2; - } - let outData = Buffer.alloc(width * height * outBpp); - - let inIndex = 0; - let outIndex = 0; - - let bgColor = options.bgColor || {}; - if (bgColor.red === undefined) { - bgColor.red = maxValue; - } - if (bgColor.green === undefined) { - bgColor.green = maxValue; - } - if (bgColor.blue === undefined) { - bgColor.blue = maxValue; - } - - function getRGBA() { - let red; - let green; - let blue; - let alpha = maxValue; - switch (options.inputColorType) { - case constants.COLORTYPE_COLOR_ALPHA: - alpha = data[inIndex + 3]; - red = data[inIndex]; - green = data[inIndex + 1]; - blue = data[inIndex + 2]; - break; - case constants.COLORTYPE_COLOR: - red = data[inIndex]; - green = data[inIndex + 1]; - blue = data[inIndex + 2]; - break; - case constants.COLORTYPE_ALPHA: - alpha = data[inIndex + 1]; - red = data[inIndex]; - green = red; - blue = red; - break; - case constants.COLORTYPE_GRAYSCALE: - red = data[inIndex]; - green = red; - blue = red; - break; - default: - throw new Error( - "input color type:" + - options.inputColorType + - " is not supported at present" - ); - } - - if (options.inputHasAlpha) { - if (!outHasAlpha) { - alpha /= maxValue; - red = Math.min( - Math.max(Math.round((1 - alpha) * bgColor.red + alpha * red), 0), - maxValue - ); - green = Math.min( - Math.max(Math.round((1 - alpha) * bgColor.green + alpha * green), 0), - maxValue - ); - blue = Math.min( - Math.max(Math.round((1 - alpha) * bgColor.blue + alpha * blue), 0), - maxValue - ); - } - } - return { red: red, green: green, blue: blue, alpha: alpha }; - } - - for (let y = 0; y < height; y++) { - for (let x = 0; x < width; x++) { - let rgba = getRGBA(data, inIndex); - - switch (options.colorType) { - case constants.COLORTYPE_COLOR_ALPHA: - case constants.COLORTYPE_COLOR: - if (options.bitDepth === 8) { - outData[outIndex] = rgba.red; - outData[outIndex + 1] = rgba.green; - outData[outIndex + 2] = rgba.blue; - if (outHasAlpha) { - outData[outIndex + 3] = rgba.alpha; - } - } else { - outData.writeUInt16BE(rgba.red, outIndex); - outData.writeUInt16BE(rgba.green, outIndex + 2); - outData.writeUInt16BE(rgba.blue, outIndex + 4); - if (outHasAlpha) { - outData.writeUInt16BE(rgba.alpha, outIndex + 6); - } - } - break; - case constants.COLORTYPE_ALPHA: - case constants.COLORTYPE_GRAYSCALE: { - // Convert to grayscale and alpha - let grayscale = (rgba.red + rgba.green + rgba.blue) / 3; - if (options.bitDepth === 8) { - outData[outIndex] = grayscale; - if (outHasAlpha) { - outData[outIndex + 1] = rgba.alpha; - } - } else { - outData.writeUInt16BE(grayscale, outIndex); - if (outHasAlpha) { - outData.writeUInt16BE(rgba.alpha, outIndex + 2); - } - } - break; - } - default: - throw new Error("unrecognised color Type " + options.colorType); - } - - inIndex += inBpp; - outIndex += outBpp; - } - } - - return outData; -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/chunkstream.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/chunkstream.js deleted file mode 100644 index 95b46d48..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/chunkstream.js +++ /dev/null @@ -1,189 +0,0 @@ -"use strict"; - -let util = require("util"); -let Stream = require("stream"); - -let ChunkStream = (module.exports = function () { - Stream.call(this); - - this._buffers = []; - this._buffered = 0; - - this._reads = []; - this._paused = false; - - this._encoding = "utf8"; - this.writable = true; -}); -util.inherits(ChunkStream, Stream); - -ChunkStream.prototype.read = function (length, callback) { - this._reads.push({ - length: Math.abs(length), // if length < 0 then at most this length - allowLess: length < 0, - func: callback, - }); - - process.nextTick( - function () { - this._process(); - - // its paused and there is not enought data then ask for more - if (this._paused && this._reads && this._reads.length > 0) { - this._paused = false; - - this.emit("drain"); - } - }.bind(this) - ); -}; - -ChunkStream.prototype.write = function (data, encoding) { - if (!this.writable) { - this.emit("error", new Error("Stream not writable")); - return false; - } - - let dataBuffer; - if (Buffer.isBuffer(data)) { - dataBuffer = data; - } else { - dataBuffer = Buffer.from(data, encoding || this._encoding); - } - - this._buffers.push(dataBuffer); - this._buffered += dataBuffer.length; - - this._process(); - - // ok if there are no more read requests - if (this._reads && this._reads.length === 0) { - this._paused = true; - } - - return this.writable && !this._paused; -}; - -ChunkStream.prototype.end = function (data, encoding) { - if (data) { - this.write(data, encoding); - } - - this.writable = false; - - // already destroyed - if (!this._buffers) { - return; - } - - // enqueue or handle end - if (this._buffers.length === 0) { - this._end(); - } else { - this._buffers.push(null); - this._process(); - } -}; - -ChunkStream.prototype.destroySoon = ChunkStream.prototype.end; - -ChunkStream.prototype._end = function () { - if (this._reads.length > 0) { - this.emit("error", new Error("Unexpected end of input")); - } - - this.destroy(); -}; - -ChunkStream.prototype.destroy = function () { - if (!this._buffers) { - return; - } - - this.writable = false; - this._reads = null; - this._buffers = null; - - this.emit("close"); -}; - -ChunkStream.prototype._processReadAllowingLess = function (read) { - // ok there is any data so that we can satisfy this request - this._reads.shift(); // == read - - // first we need to peek into first buffer - let smallerBuf = this._buffers[0]; - - // ok there is more data than we need - if (smallerBuf.length > read.length) { - this._buffered -= read.length; - this._buffers[0] = smallerBuf.slice(read.length); - - read.func.call(this, smallerBuf.slice(0, read.length)); - } else { - // ok this is less than maximum length so use it all - this._buffered -= smallerBuf.length; - this._buffers.shift(); // == smallerBuf - - read.func.call(this, smallerBuf); - } -}; - -ChunkStream.prototype._processRead = function (read) { - this._reads.shift(); // == read - - let pos = 0; - let count = 0; - let data = Buffer.alloc(read.length); - - // create buffer for all data - while (pos < read.length) { - let buf = this._buffers[count++]; - let len = Math.min(buf.length, read.length - pos); - - buf.copy(data, pos, 0, len); - pos += len; - - // last buffer wasn't used all so just slice it and leave - if (len !== buf.length) { - this._buffers[--count] = buf.slice(len); - } - } - - // remove all used buffers - if (count > 0) { - this._buffers.splice(0, count); - } - - this._buffered -= read.length; - - read.func.call(this, data); -}; - -ChunkStream.prototype._process = function () { - try { - // as long as there is any data and read requests - while (this._buffered > 0 && this._reads && this._reads.length > 0) { - let read = this._reads[0]; - - // read any data (but no more than length) - if (read.allowLess) { - this._processReadAllowingLess(read); - } else if (this._buffered >= read.length) { - // ok we can meet some expectations - - this._processRead(read); - } else { - // not enought data to satisfy first request in queue - // so we need to wait for more - break; - } - } - - if (this._buffers && !this.writable) { - this._end(); - } - } catch (ex) { - this.emit("error", ex); - } -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/constants.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/constants.js deleted file mode 100644 index 21fdad68..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/constants.js +++ /dev/null @@ -1,32 +0,0 @@ -"use strict"; - -module.exports = { - PNG_SIGNATURE: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], - - TYPE_IHDR: 0x49484452, - TYPE_IEND: 0x49454e44, - TYPE_IDAT: 0x49444154, - TYPE_PLTE: 0x504c5445, - TYPE_tRNS: 0x74524e53, // eslint-disable-line camelcase - TYPE_gAMA: 0x67414d41, // eslint-disable-line camelcase - - // color-type bits - COLORTYPE_GRAYSCALE: 0, - COLORTYPE_PALETTE: 1, - COLORTYPE_COLOR: 2, - COLORTYPE_ALPHA: 4, // e.g. grayscale and alpha - - // color-type combinations - COLORTYPE_PALETTE_COLOR: 3, - COLORTYPE_COLOR_ALPHA: 6, - - COLORTYPE_TO_BPP_MAP: { - 0: 1, - 2: 3, - 3: 1, - 4: 2, - 6: 4, - }, - - GAMMA_DIVISION: 100000, -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/crc.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/crc.js deleted file mode 100644 index 950ec8ae..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/crc.js +++ /dev/null @@ -1,40 +0,0 @@ -"use strict"; - -let crcTable = []; - -(function () { - for (let i = 0; i < 256; i++) { - let currentCrc = i; - for (let j = 0; j < 8; j++) { - if (currentCrc & 1) { - currentCrc = 0xedb88320 ^ (currentCrc >>> 1); - } else { - currentCrc = currentCrc >>> 1; - } - } - crcTable[i] = currentCrc; - } -})(); - -let CrcCalculator = (module.exports = function () { - this._crc = -1; -}); - -CrcCalculator.prototype.write = function (data) { - for (let i = 0; i < data.length; i++) { - this._crc = crcTable[(this._crc ^ data[i]) & 0xff] ^ (this._crc >>> 8); - } - return true; -}; - -CrcCalculator.prototype.crc32 = function () { - return this._crc ^ -1; -}; - -CrcCalculator.crc32 = function (buf) { - let crc = -1; - for (let i = 0; i < buf.length; i++) { - crc = crcTable[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8); - } - return crc ^ -1; -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-pack.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-pack.js deleted file mode 100644 index 32c85c40..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-pack.js +++ /dev/null @@ -1,171 +0,0 @@ -"use strict"; - -let paethPredictor = require("./paeth-predictor"); - -function filterNone(pxData, pxPos, byteWidth, rawData, rawPos) { - for (let x = 0; x < byteWidth; x++) { - rawData[rawPos + x] = pxData[pxPos + x]; - } -} - -function filterSumNone(pxData, pxPos, byteWidth) { - let sum = 0; - let length = pxPos + byteWidth; - - for (let i = pxPos; i < length; i++) { - sum += Math.abs(pxData[i]); - } - return sum; -} - -function filterSub(pxData, pxPos, byteWidth, rawData, rawPos, bpp) { - for (let x = 0; x < byteWidth; x++) { - let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; - let val = pxData[pxPos + x] - left; - - rawData[rawPos + x] = val; - } -} - -function filterSumSub(pxData, pxPos, byteWidth, bpp) { - let sum = 0; - for (let x = 0; x < byteWidth; x++) { - let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; - let val = pxData[pxPos + x] - left; - - sum += Math.abs(val); - } - - return sum; -} - -function filterUp(pxData, pxPos, byteWidth, rawData, rawPos) { - for (let x = 0; x < byteWidth; x++) { - let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; - let val = pxData[pxPos + x] - up; - - rawData[rawPos + x] = val; - } -} - -function filterSumUp(pxData, pxPos, byteWidth) { - let sum = 0; - let length = pxPos + byteWidth; - for (let x = pxPos; x < length; x++) { - let up = pxPos > 0 ? pxData[x - byteWidth] : 0; - let val = pxData[x] - up; - - sum += Math.abs(val); - } - - return sum; -} - -function filterAvg(pxData, pxPos, byteWidth, rawData, rawPos, bpp) { - for (let x = 0; x < byteWidth; x++) { - let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; - let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; - let val = pxData[pxPos + x] - ((left + up) >> 1); - - rawData[rawPos + x] = val; - } -} - -function filterSumAvg(pxData, pxPos, byteWidth, bpp) { - let sum = 0; - for (let x = 0; x < byteWidth; x++) { - let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; - let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; - let val = pxData[pxPos + x] - ((left + up) >> 1); - - sum += Math.abs(val); - } - - return sum; -} - -function filterPaeth(pxData, pxPos, byteWidth, rawData, rawPos, bpp) { - for (let x = 0; x < byteWidth; x++) { - let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; - let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; - let upleft = - pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0; - let val = pxData[pxPos + x] - paethPredictor(left, up, upleft); - - rawData[rawPos + x] = val; - } -} - -function filterSumPaeth(pxData, pxPos, byteWidth, bpp) { - let sum = 0; - for (let x = 0; x < byteWidth; x++) { - let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; - let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; - let upleft = - pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0; - let val = pxData[pxPos + x] - paethPredictor(left, up, upleft); - - sum += Math.abs(val); - } - - return sum; -} - -let filters = { - 0: filterNone, - 1: filterSub, - 2: filterUp, - 3: filterAvg, - 4: filterPaeth, -}; - -let filterSums = { - 0: filterSumNone, - 1: filterSumSub, - 2: filterSumUp, - 3: filterSumAvg, - 4: filterSumPaeth, -}; - -module.exports = function (pxData, width, height, options, bpp) { - let filterTypes; - if (!("filterType" in options) || options.filterType === -1) { - filterTypes = [0, 1, 2, 3, 4]; - } else if (typeof options.filterType === "number") { - filterTypes = [options.filterType]; - } else { - throw new Error("unrecognised filter types"); - } - - if (options.bitDepth === 16) { - bpp *= 2; - } - let byteWidth = width * bpp; - let rawPos = 0; - let pxPos = 0; - let rawData = Buffer.alloc((byteWidth + 1) * height); - - let sel = filterTypes[0]; - - for (let y = 0; y < height; y++) { - if (filterTypes.length > 1) { - // find best filter for this line (with lowest sum of values) - let min = Infinity; - - for (let i = 0; i < filterTypes.length; i++) { - let sum = filterSums[filterTypes[i]](pxData, pxPos, byteWidth, bpp); - if (sum < min) { - sel = filterTypes[i]; - min = sum; - } - } - } - - rawData[rawPos] = sel; - rawPos++; - filters[sel](pxData, pxPos, byteWidth, rawData, rawPos, bpp); - rawPos += byteWidth; - pxPos += byteWidth; - } - return rawData; -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-async.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-async.js deleted file mode 100644 index 832b86cd..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-async.js +++ /dev/null @@ -1,24 +0,0 @@ -"use strict"; - -let util = require("util"); -let ChunkStream = require("./chunkstream"); -let Filter = require("./filter-parse"); - -let FilterAsync = (module.exports = function (bitmapInfo) { - ChunkStream.call(this); - - let buffers = []; - let that = this; - this._filter = new Filter(bitmapInfo, { - read: this.read.bind(this), - write: function (buffer) { - buffers.push(buffer); - }, - complete: function () { - that.emit("complete", Buffer.concat(buffers)); - }, - }); - - this._filter.start(); -}); -util.inherits(FilterAsync, ChunkStream); diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-sync.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-sync.js deleted file mode 100644 index 6924d161..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-sync.js +++ /dev/null @@ -1,21 +0,0 @@ -"use strict"; - -let SyncReader = require("./sync-reader"); -let Filter = require("./filter-parse"); - -exports.process = function (inBuffer, bitmapInfo) { - let outBuffers = []; - let reader = new SyncReader(inBuffer); - let filter = new Filter(bitmapInfo, { - read: reader.read.bind(reader), - write: function (bufferPart) { - outBuffers.push(bufferPart); - }, - complete: function () {}, - }); - - filter.start(); - reader.process(); - - return Buffer.concat(outBuffers); -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse.js deleted file mode 100644 index 3a32e5ee..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse.js +++ /dev/null @@ -1,177 +0,0 @@ -"use strict"; - -let interlaceUtils = require("./interlace"); -let paethPredictor = require("./paeth-predictor"); - -function getByteWidth(width, bpp, depth) { - let byteWidth = width * bpp; - if (depth !== 8) { - byteWidth = Math.ceil(byteWidth / (8 / depth)); - } - return byteWidth; -} - -let Filter = (module.exports = function (bitmapInfo, dependencies) { - let width = bitmapInfo.width; - let height = bitmapInfo.height; - let interlace = bitmapInfo.interlace; - let bpp = bitmapInfo.bpp; - let depth = bitmapInfo.depth; - - this.read = dependencies.read; - this.write = dependencies.write; - this.complete = dependencies.complete; - - this._imageIndex = 0; - this._images = []; - if (interlace) { - let passes = interlaceUtils.getImagePasses(width, height); - for (let i = 0; i < passes.length; i++) { - this._images.push({ - byteWidth: getByteWidth(passes[i].width, bpp, depth), - height: passes[i].height, - lineIndex: 0, - }); - } - } else { - this._images.push({ - byteWidth: getByteWidth(width, bpp, depth), - height: height, - lineIndex: 0, - }); - } - - // when filtering the line we look at the pixel to the left - // the spec also says it is done on a byte level regardless of the number of pixels - // so if the depth is byte compatible (8 or 16) we subtract the bpp in order to compare back - // a pixel rather than just a different byte part. However if we are sub byte, we ignore. - if (depth === 8) { - this._xComparison = bpp; - } else if (depth === 16) { - this._xComparison = bpp * 2; - } else { - this._xComparison = 1; - } -}); - -Filter.prototype.start = function () { - this.read( - this._images[this._imageIndex].byteWidth + 1, - this._reverseFilterLine.bind(this) - ); -}; - -Filter.prototype._unFilterType1 = function ( - rawData, - unfilteredLine, - byteWidth -) { - let xComparison = this._xComparison; - let xBiggerThan = xComparison - 1; - - for (let x = 0; x < byteWidth; x++) { - let rawByte = rawData[1 + x]; - let f1Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0; - unfilteredLine[x] = rawByte + f1Left; - } -}; - -Filter.prototype._unFilterType2 = function ( - rawData, - unfilteredLine, - byteWidth -) { - let lastLine = this._lastLine; - - for (let x = 0; x < byteWidth; x++) { - let rawByte = rawData[1 + x]; - let f2Up = lastLine ? lastLine[x] : 0; - unfilteredLine[x] = rawByte + f2Up; - } -}; - -Filter.prototype._unFilterType3 = function ( - rawData, - unfilteredLine, - byteWidth -) { - let xComparison = this._xComparison; - let xBiggerThan = xComparison - 1; - let lastLine = this._lastLine; - - for (let x = 0; x < byteWidth; x++) { - let rawByte = rawData[1 + x]; - let f3Up = lastLine ? lastLine[x] : 0; - let f3Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0; - let f3Add = Math.floor((f3Left + f3Up) / 2); - unfilteredLine[x] = rawByte + f3Add; - } -}; - -Filter.prototype._unFilterType4 = function ( - rawData, - unfilteredLine, - byteWidth -) { - let xComparison = this._xComparison; - let xBiggerThan = xComparison - 1; - let lastLine = this._lastLine; - - for (let x = 0; x < byteWidth; x++) { - let rawByte = rawData[1 + x]; - let f4Up = lastLine ? lastLine[x] : 0; - let f4Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0; - let f4UpLeft = x > xBiggerThan && lastLine ? lastLine[x - xComparison] : 0; - let f4Add = paethPredictor(f4Left, f4Up, f4UpLeft); - unfilteredLine[x] = rawByte + f4Add; - } -}; - -Filter.prototype._reverseFilterLine = function (rawData) { - let filter = rawData[0]; - let unfilteredLine; - let currentImage = this._images[this._imageIndex]; - let byteWidth = currentImage.byteWidth; - - if (filter === 0) { - unfilteredLine = rawData.slice(1, byteWidth + 1); - } else { - unfilteredLine = Buffer.alloc(byteWidth); - - switch (filter) { - case 1: - this._unFilterType1(rawData, unfilteredLine, byteWidth); - break; - case 2: - this._unFilterType2(rawData, unfilteredLine, byteWidth); - break; - case 3: - this._unFilterType3(rawData, unfilteredLine, byteWidth); - break; - case 4: - this._unFilterType4(rawData, unfilteredLine, byteWidth); - break; - default: - throw new Error("Unrecognised filter type - " + filter); - } - } - - this.write(unfilteredLine); - - currentImage.lineIndex++; - if (currentImage.lineIndex >= currentImage.height) { - this._lastLine = null; - this._imageIndex++; - currentImage = this._images[this._imageIndex]; - } else { - this._lastLine = unfilteredLine; - } - - if (currentImage) { - // read, using the byte width that may be from the new current image - this.read(currentImage.byteWidth + 1, this._reverseFilterLine.bind(this)); - } else { - this._lastLine = null; - this.complete(); - } -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/format-normaliser.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/format-normaliser.js deleted file mode 100644 index 209b66bb..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/format-normaliser.js +++ /dev/null @@ -1,93 +0,0 @@ -"use strict"; - -function dePalette(indata, outdata, width, height, palette) { - let pxPos = 0; - // use values from palette - for (let y = 0; y < height; y++) { - for (let x = 0; x < width; x++) { - let color = palette[indata[pxPos]]; - - if (!color) { - throw new Error("index " + indata[pxPos] + " not in palette"); - } - - for (let i = 0; i < 4; i++) { - outdata[pxPos + i] = color[i]; - } - pxPos += 4; - } - } -} - -function replaceTransparentColor(indata, outdata, width, height, transColor) { - let pxPos = 0; - for (let y = 0; y < height; y++) { - for (let x = 0; x < width; x++) { - let makeTrans = false; - - if (transColor.length === 1) { - if (transColor[0] === indata[pxPos]) { - makeTrans = true; - } - } else if ( - transColor[0] === indata[pxPos] && - transColor[1] === indata[pxPos + 1] && - transColor[2] === indata[pxPos + 2] - ) { - makeTrans = true; - } - if (makeTrans) { - for (let i = 0; i < 4; i++) { - outdata[pxPos + i] = 0; - } - } - pxPos += 4; - } - } -} - -function scaleDepth(indata, outdata, width, height, depth) { - let maxOutSample = 255; - let maxInSample = Math.pow(2, depth) - 1; - let pxPos = 0; - - for (let y = 0; y < height; y++) { - for (let x = 0; x < width; x++) { - for (let i = 0; i < 4; i++) { - outdata[pxPos + i] = Math.floor( - (indata[pxPos + i] * maxOutSample) / maxInSample + 0.5 - ); - } - pxPos += 4; - } - } -} - -module.exports = function (indata, imageData, skipRescale = false) { - let depth = imageData.depth; - let width = imageData.width; - let height = imageData.height; - let colorType = imageData.colorType; - let transColor = imageData.transColor; - let palette = imageData.palette; - - let outdata = indata; // only different for 16 bits - - if (colorType === 3) { - // paletted - dePalette(indata, outdata, width, height, palette); - } else { - if (transColor) { - replaceTransparentColor(indata, outdata, width, height, transColor); - } - // if it needs scaling - if (depth !== 8 && !skipRescale) { - // if we need to change the buffer size - if (depth === 16) { - outdata = Buffer.alloc(width * height * 4); - } - scaleDepth(indata, outdata, width, height, depth); - } - } - return outdata; -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/interlace.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/interlace.js deleted file mode 100644 index a035cb15..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/interlace.js +++ /dev/null @@ -1,95 +0,0 @@ -"use strict"; - -// Adam 7 -// 0 1 2 3 4 5 6 7 -// 0 x 6 4 6 x 6 4 6 -// 1 7 7 7 7 7 7 7 7 -// 2 5 6 5 6 5 6 5 6 -// 3 7 7 7 7 7 7 7 7 -// 4 3 6 4 6 3 6 4 6 -// 5 7 7 7 7 7 7 7 7 -// 6 5 6 5 6 5 6 5 6 -// 7 7 7 7 7 7 7 7 7 - -let imagePasses = [ - { - // pass 1 - 1px - x: [0], - y: [0], - }, - { - // pass 2 - 1px - x: [4], - y: [0], - }, - { - // pass 3 - 2px - x: [0, 4], - y: [4], - }, - { - // pass 4 - 4px - x: [2, 6], - y: [0, 4], - }, - { - // pass 5 - 8px - x: [0, 2, 4, 6], - y: [2, 6], - }, - { - // pass 6 - 16px - x: [1, 3, 5, 7], - y: [0, 2, 4, 6], - }, - { - // pass 7 - 32px - x: [0, 1, 2, 3, 4, 5, 6, 7], - y: [1, 3, 5, 7], - }, -]; - -exports.getImagePasses = function (width, height) { - let images = []; - let xLeftOver = width % 8; - let yLeftOver = height % 8; - let xRepeats = (width - xLeftOver) / 8; - let yRepeats = (height - yLeftOver) / 8; - for (let i = 0; i < imagePasses.length; i++) { - let pass = imagePasses[i]; - let passWidth = xRepeats * pass.x.length; - let passHeight = yRepeats * pass.y.length; - for (let j = 0; j < pass.x.length; j++) { - if (pass.x[j] < xLeftOver) { - passWidth++; - } else { - break; - } - } - for (let j = 0; j < pass.y.length; j++) { - if (pass.y[j] < yLeftOver) { - passHeight++; - } else { - break; - } - } - if (passWidth > 0 && passHeight > 0) { - images.push({ width: passWidth, height: passHeight, index: i }); - } - } - return images; -}; - -exports.getInterlaceIterator = function (width) { - return function (x, y, pass) { - let outerXLeftOver = x % imagePasses[pass].x.length; - let outerX = - ((x - outerXLeftOver) / imagePasses[pass].x.length) * 8 + - imagePasses[pass].x[outerXLeftOver]; - let outerYLeftOver = y % imagePasses[pass].y.length; - let outerY = - ((y - outerYLeftOver) / imagePasses[pass].y.length) * 8 + - imagePasses[pass].y[outerYLeftOver]; - return outerX * 4 + outerY * width * 4; - }; -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-async.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-async.js deleted file mode 100644 index f3df73aa..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-async.js +++ /dev/null @@ -1,50 +0,0 @@ -"use strict"; - -let util = require("util"); -let Stream = require("stream"); -let constants = require("./constants"); -let Packer = require("./packer"); - -let PackerAsync = (module.exports = function (opt) { - Stream.call(this); - - let options = opt || {}; - - this._packer = new Packer(options); - this._deflate = this._packer.createDeflate(); - - this.readable = true; -}); -util.inherits(PackerAsync, Stream); - -PackerAsync.prototype.pack = function (data, width, height, gamma) { - // Signature - this.emit("data", Buffer.from(constants.PNG_SIGNATURE)); - this.emit("data", this._packer.packIHDR(width, height)); - - if (gamma) { - this.emit("data", this._packer.packGAMA(gamma)); - } - - let filteredData = this._packer.filterData(data, width, height); - - // compress it - this._deflate.on("error", this.emit.bind(this, "error")); - - this._deflate.on( - "data", - function (compressedData) { - this.emit("data", this._packer.packIDAT(compressedData)); - }.bind(this) - ); - - this._deflate.on( - "end", - function () { - this.emit("data", this._packer.packIEND()); - this.emit("end"); - }.bind(this) - ); - - this._deflate.end(filteredData); -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-sync.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-sync.js deleted file mode 100644 index f5ab0b3d..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-sync.js +++ /dev/null @@ -1,56 +0,0 @@ -"use strict"; - -let hasSyncZlib = true; -let zlib = require("zlib"); -if (!zlib.deflateSync) { - hasSyncZlib = false; -} -let constants = require("./constants"); -let Packer = require("./packer"); - -module.exports = function (metaData, opt) { - if (!hasSyncZlib) { - throw new Error( - "To use the sync capability of this library in old node versions, please pin pngjs to v2.3.0" - ); - } - - let options = opt || {}; - - let packer = new Packer(options); - - let chunks = []; - - // Signature - chunks.push(Buffer.from(constants.PNG_SIGNATURE)); - - // Header - chunks.push(packer.packIHDR(metaData.width, metaData.height)); - - if (metaData.gamma) { - chunks.push(packer.packGAMA(metaData.gamma)); - } - - let filteredData = packer.filterData( - metaData.data, - metaData.width, - metaData.height - ); - - // compress it - let compressedData = zlib.deflateSync( - filteredData, - packer.getDeflateOptions() - ); - filteredData = null; - - if (!compressedData || !compressedData.length) { - throw new Error("bad png - invalid compressed data response"); - } - chunks.push(packer.packIDAT(compressedData)); - - // End - chunks.push(packer.packIEND()); - - return Buffer.concat(chunks); -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/packer.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/packer.js deleted file mode 100644 index 4aba12c8..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/packer.js +++ /dev/null @@ -1,129 +0,0 @@ -"use strict"; - -let constants = require("./constants"); -let CrcStream = require("./crc"); -let bitPacker = require("./bitpacker"); -let filter = require("./filter-pack"); -let zlib = require("zlib"); - -let Packer = (module.exports = function (options) { - this._options = options; - - options.deflateChunkSize = options.deflateChunkSize || 32 * 1024; - options.deflateLevel = - options.deflateLevel != null ? options.deflateLevel : 9; - options.deflateStrategy = - options.deflateStrategy != null ? options.deflateStrategy : 3; - options.inputHasAlpha = - options.inputHasAlpha != null ? options.inputHasAlpha : true; - options.deflateFactory = options.deflateFactory || zlib.createDeflate; - options.bitDepth = options.bitDepth || 8; - // This is outputColorType - options.colorType = - typeof options.colorType === "number" - ? options.colorType - : constants.COLORTYPE_COLOR_ALPHA; - options.inputColorType = - typeof options.inputColorType === "number" - ? options.inputColorType - : constants.COLORTYPE_COLOR_ALPHA; - - if ( - [ - constants.COLORTYPE_GRAYSCALE, - constants.COLORTYPE_COLOR, - constants.COLORTYPE_COLOR_ALPHA, - constants.COLORTYPE_ALPHA, - ].indexOf(options.colorType) === -1 - ) { - throw new Error( - "option color type:" + options.colorType + " is not supported at present" - ); - } - if ( - [ - constants.COLORTYPE_GRAYSCALE, - constants.COLORTYPE_COLOR, - constants.COLORTYPE_COLOR_ALPHA, - constants.COLORTYPE_ALPHA, - ].indexOf(options.inputColorType) === -1 - ) { - throw new Error( - "option input color type:" + - options.inputColorType + - " is not supported at present" - ); - } - if (options.bitDepth !== 8 && options.bitDepth !== 16) { - throw new Error( - "option bit depth:" + options.bitDepth + " is not supported at present" - ); - } -}); - -Packer.prototype.getDeflateOptions = function () { - return { - chunkSize: this._options.deflateChunkSize, - level: this._options.deflateLevel, - strategy: this._options.deflateStrategy, - }; -}; - -Packer.prototype.createDeflate = function () { - return this._options.deflateFactory(this.getDeflateOptions()); -}; - -Packer.prototype.filterData = function (data, width, height) { - // convert to correct format for filtering (e.g. right bpp and bit depth) - let packedData = bitPacker(data, width, height, this._options); - - // filter pixel data - let bpp = constants.COLORTYPE_TO_BPP_MAP[this._options.colorType]; - let filteredData = filter(packedData, width, height, this._options, bpp); - return filteredData; -}; - -Packer.prototype._packChunk = function (type, data) { - let len = data ? data.length : 0; - let buf = Buffer.alloc(len + 12); - - buf.writeUInt32BE(len, 0); - buf.writeUInt32BE(type, 4); - - if (data) { - data.copy(buf, 8); - } - - buf.writeInt32BE( - CrcStream.crc32(buf.slice(4, buf.length - 4)), - buf.length - 4 - ); - return buf; -}; - -Packer.prototype.packGAMA = function (gamma) { - let buf = Buffer.alloc(4); - buf.writeUInt32BE(Math.floor(gamma * constants.GAMMA_DIVISION), 0); - return this._packChunk(constants.TYPE_gAMA, buf); -}; - -Packer.prototype.packIHDR = function (width, height) { - let buf = Buffer.alloc(13); - buf.writeUInt32BE(width, 0); - buf.writeUInt32BE(height, 4); - buf[8] = this._options.bitDepth; // Bit depth - buf[9] = this._options.colorType; // colorType - buf[10] = 0; // compression - buf[11] = 0; // filter - buf[12] = 0; // interlace - - return this._packChunk(constants.TYPE_IHDR, buf); -}; - -Packer.prototype.packIDAT = function (data) { - return this._packChunk(constants.TYPE_IDAT, data); -}; - -Packer.prototype.packIEND = function () { - return this._packChunk(constants.TYPE_IEND, null); -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/paeth-predictor.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/paeth-predictor.js deleted file mode 100644 index 9634497d..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/paeth-predictor.js +++ /dev/null @@ -1,16 +0,0 @@ -"use strict"; - -module.exports = function paethPredictor(left, above, upLeft) { - let paeth = left + above - upLeft; - let pLeft = Math.abs(paeth - left); - let pAbove = Math.abs(paeth - above); - let pUpLeft = Math.abs(paeth - upLeft); - - if (pLeft <= pAbove && pLeft <= pUpLeft) { - return left; - } - if (pAbove <= pUpLeft) { - return above; - } - return upLeft; -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-async.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-async.js deleted file mode 100644 index 1aacde34..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-async.js +++ /dev/null @@ -1,169 +0,0 @@ -"use strict"; - -let util = require("util"); -let zlib = require("zlib"); -let ChunkStream = require("./chunkstream"); -let FilterAsync = require("./filter-parse-async"); -let Parser = require("./parser"); -let bitmapper = require("./bitmapper"); -let formatNormaliser = require("./format-normaliser"); - -let ParserAsync = (module.exports = function (options) { - ChunkStream.call(this); - - this._parser = new Parser(options, { - read: this.read.bind(this), - error: this._handleError.bind(this), - metadata: this._handleMetaData.bind(this), - gamma: this.emit.bind(this, "gamma"), - palette: this._handlePalette.bind(this), - transColor: this._handleTransColor.bind(this), - finished: this._finished.bind(this), - inflateData: this._inflateData.bind(this), - simpleTransparency: this._simpleTransparency.bind(this), - headersFinished: this._headersFinished.bind(this), - }); - this._options = options; - this.writable = true; - - this._parser.start(); -}); -util.inherits(ParserAsync, ChunkStream); - -ParserAsync.prototype._handleError = function (err) { - this.emit("error", err); - - this.writable = false; - - this.destroy(); - - if (this._inflate && this._inflate.destroy) { - this._inflate.destroy(); - } - - if (this._filter) { - this._filter.destroy(); - // For backward compatibility with Node 7 and below. - // Suppress errors due to _inflate calling write() even after - // it's destroy()'ed. - this._filter.on("error", function () {}); - } - - this.errord = true; -}; - -ParserAsync.prototype._inflateData = function (data) { - if (!this._inflate) { - if (this._bitmapInfo.interlace) { - this._inflate = zlib.createInflate(); - - this._inflate.on("error", this.emit.bind(this, "error")); - this._filter.on("complete", this._complete.bind(this)); - - this._inflate.pipe(this._filter); - } else { - let rowSize = - ((this._bitmapInfo.width * - this._bitmapInfo.bpp * - this._bitmapInfo.depth + - 7) >> - 3) + - 1; - let imageSize = rowSize * this._bitmapInfo.height; - let chunkSize = Math.max(imageSize, zlib.Z_MIN_CHUNK); - - this._inflate = zlib.createInflate({ chunkSize: chunkSize }); - let leftToInflate = imageSize; - - let emitError = this.emit.bind(this, "error"); - this._inflate.on("error", function (err) { - if (!leftToInflate) { - return; - } - - emitError(err); - }); - this._filter.on("complete", this._complete.bind(this)); - - let filterWrite = this._filter.write.bind(this._filter); - this._inflate.on("data", function (chunk) { - if (!leftToInflate) { - return; - } - - if (chunk.length > leftToInflate) { - chunk = chunk.slice(0, leftToInflate); - } - - leftToInflate -= chunk.length; - - filterWrite(chunk); - }); - - this._inflate.on("end", this._filter.end.bind(this._filter)); - } - } - this._inflate.write(data); -}; - -ParserAsync.prototype._handleMetaData = function (metaData) { - this._metaData = metaData; - this._bitmapInfo = Object.create(metaData); - - this._filter = new FilterAsync(this._bitmapInfo); -}; - -ParserAsync.prototype._handleTransColor = function (transColor) { - this._bitmapInfo.transColor = transColor; -}; - -ParserAsync.prototype._handlePalette = function (palette) { - this._bitmapInfo.palette = palette; -}; - -ParserAsync.prototype._simpleTransparency = function () { - this._metaData.alpha = true; -}; - -ParserAsync.prototype._headersFinished = function () { - // Up until this point, we don't know if we have a tRNS chunk (alpha) - // so we can't emit metadata any earlier - this.emit("metadata", this._metaData); -}; - -ParserAsync.prototype._finished = function () { - if (this.errord) { - return; - } - - if (!this._inflate) { - this.emit("error", "No Inflate block"); - } else { - // no more data to inflate - this._inflate.end(); - } -}; - -ParserAsync.prototype._complete = function (filteredData) { - if (this.errord) { - return; - } - - let normalisedBitmapData; - - try { - let bitmapData = bitmapper.dataToBitMap(filteredData, this._bitmapInfo); - - normalisedBitmapData = formatNormaliser( - bitmapData, - this._bitmapInfo, - this._options.skipRescale - ); - bitmapData = null; - } catch (ex) { - this._handleError(ex); - return; - } - - this.emit("parsed", normalisedBitmapData); -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-sync.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-sync.js deleted file mode 100644 index 76cb134b..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-sync.js +++ /dev/null @@ -1,112 +0,0 @@ -"use strict"; - -let hasSyncZlib = true; -let zlib = require("zlib"); -let inflateSync = require("./sync-inflate"); -if (!zlib.deflateSync) { - hasSyncZlib = false; -} -let SyncReader = require("./sync-reader"); -let FilterSync = require("./filter-parse-sync"); -let Parser = require("./parser"); -let bitmapper = require("./bitmapper"); -let formatNormaliser = require("./format-normaliser"); - -module.exports = function (buffer, options) { - if (!hasSyncZlib) { - throw new Error( - "To use the sync capability of this library in old node versions, please pin pngjs to v2.3.0" - ); - } - - let err; - function handleError(_err_) { - err = _err_; - } - - let metaData; - function handleMetaData(_metaData_) { - metaData = _metaData_; - } - - function handleTransColor(transColor) { - metaData.transColor = transColor; - } - - function handlePalette(palette) { - metaData.palette = palette; - } - - function handleSimpleTransparency() { - metaData.alpha = true; - } - - let gamma; - function handleGamma(_gamma_) { - gamma = _gamma_; - } - - let inflateDataList = []; - function handleInflateData(inflatedData) { - inflateDataList.push(inflatedData); - } - - let reader = new SyncReader(buffer); - - let parser = new Parser(options, { - read: reader.read.bind(reader), - error: handleError, - metadata: handleMetaData, - gamma: handleGamma, - palette: handlePalette, - transColor: handleTransColor, - inflateData: handleInflateData, - simpleTransparency: handleSimpleTransparency, - }); - - parser.start(); - reader.process(); - - if (err) { - throw err; - } - - //join together the inflate datas - let inflateData = Buffer.concat(inflateDataList); - inflateDataList.length = 0; - - let inflatedData; - if (metaData.interlace) { - inflatedData = zlib.inflateSync(inflateData); - } else { - let rowSize = - ((metaData.width * metaData.bpp * metaData.depth + 7) >> 3) + 1; - let imageSize = rowSize * metaData.height; - inflatedData = inflateSync(inflateData, { - chunkSize: imageSize, - maxLength: imageSize, - }); - } - inflateData = null; - - if (!inflatedData || !inflatedData.length) { - throw new Error("bad png - invalid inflate data response"); - } - - let unfilteredData = FilterSync.process(inflatedData, metaData); - inflateData = null; - - let bitmapData = bitmapper.dataToBitMap(unfilteredData, metaData); - unfilteredData = null; - - let normalisedBitmapData = formatNormaliser( - bitmapData, - metaData, - options.skipRescale - ); - - metaData.data = normalisedBitmapData; - metaData.gamma = gamma || 0; - - return metaData; -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/parser.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/parser.js deleted file mode 100644 index 51a8f2a5..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/parser.js +++ /dev/null @@ -1,290 +0,0 @@ -"use strict"; - -let constants = require("./constants"); -let CrcCalculator = require("./crc"); - -let Parser = (module.exports = function (options, dependencies) { - this._options = options; - options.checkCRC = options.checkCRC !== false; - - this._hasIHDR = false; - this._hasIEND = false; - this._emittedHeadersFinished = false; - - // input flags/metadata - this._palette = []; - this._colorType = 0; - - this._chunks = {}; - this._chunks[constants.TYPE_IHDR] = this._handleIHDR.bind(this); - this._chunks[constants.TYPE_IEND] = this._handleIEND.bind(this); - this._chunks[constants.TYPE_IDAT] = this._handleIDAT.bind(this); - this._chunks[constants.TYPE_PLTE] = this._handlePLTE.bind(this); - this._chunks[constants.TYPE_tRNS] = this._handleTRNS.bind(this); - this._chunks[constants.TYPE_gAMA] = this._handleGAMA.bind(this); - - this.read = dependencies.read; - this.error = dependencies.error; - this.metadata = dependencies.metadata; - this.gamma = dependencies.gamma; - this.transColor = dependencies.transColor; - this.palette = dependencies.palette; - this.parsed = dependencies.parsed; - this.inflateData = dependencies.inflateData; - this.finished = dependencies.finished; - this.simpleTransparency = dependencies.simpleTransparency; - this.headersFinished = dependencies.headersFinished || function () {}; -}); - -Parser.prototype.start = function () { - this.read(constants.PNG_SIGNATURE.length, this._parseSignature.bind(this)); -}; - -Parser.prototype._parseSignature = function (data) { - let signature = constants.PNG_SIGNATURE; - - for (let i = 0; i < signature.length; i++) { - if (data[i] !== signature[i]) { - this.error(new Error("Invalid file signature")); - return; - } - } - this.read(8, this._parseChunkBegin.bind(this)); -}; - -Parser.prototype._parseChunkBegin = function (data) { - // chunk content length - let length = data.readUInt32BE(0); - - // chunk type - let type = data.readUInt32BE(4); - let name = ""; - for (let i = 4; i < 8; i++) { - name += String.fromCharCode(data[i]); - } - - //console.log('chunk ', name, length); - - // chunk flags - let ancillary = Boolean(data[4] & 0x20); // or critical - // priv = Boolean(data[5] & 0x20), // or public - // safeToCopy = Boolean(data[7] & 0x20); // or unsafe - - if (!this._hasIHDR && type !== constants.TYPE_IHDR) { - this.error(new Error("Expected IHDR on beggining")); - return; - } - - this._crc = new CrcCalculator(); - this._crc.write(Buffer.from(name)); - - if (this._chunks[type]) { - return this._chunks[type](length); - } - - if (!ancillary) { - this.error(new Error("Unsupported critical chunk type " + name)); - return; - } - - this.read(length + 4, this._skipChunk.bind(this)); -}; - -Parser.prototype._skipChunk = function (/*data*/) { - this.read(8, this._parseChunkBegin.bind(this)); -}; - -Parser.prototype._handleChunkEnd = function () { - this.read(4, this._parseChunkEnd.bind(this)); -}; - -Parser.prototype._parseChunkEnd = function (data) { - let fileCrc = data.readInt32BE(0); - let calcCrc = this._crc.crc32(); - - // check CRC - if (this._options.checkCRC && calcCrc !== fileCrc) { - this.error(new Error("Crc error - " + fileCrc + " - " + calcCrc)); - return; - } - - if (!this._hasIEND) { - this.read(8, this._parseChunkBegin.bind(this)); - } -}; - -Parser.prototype._handleIHDR = function (length) { - this.read(length, this._parseIHDR.bind(this)); -}; -Parser.prototype._parseIHDR = function (data) { - this._crc.write(data); - - let width = data.readUInt32BE(0); - let height = data.readUInt32BE(4); - let depth = data[8]; - let colorType = data[9]; // bits: 1 palette, 2 color, 4 alpha - let compr = data[10]; - let filter = data[11]; - let interlace = data[12]; - - // console.log(' width', width, 'height', height, - // 'depth', depth, 'colorType', colorType, - // 'compr', compr, 'filter', filter, 'interlace', interlace - // ); - - if ( - depth !== 8 && - depth !== 4 && - depth !== 2 && - depth !== 1 && - depth !== 16 - ) { - this.error(new Error("Unsupported bit depth " + depth)); - return; - } - if (!(colorType in constants.COLORTYPE_TO_BPP_MAP)) { - this.error(new Error("Unsupported color type")); - return; - } - if (compr !== 0) { - this.error(new Error("Unsupported compression method")); - return; - } - if (filter !== 0) { - this.error(new Error("Unsupported filter method")); - return; - } - if (interlace !== 0 && interlace !== 1) { - this.error(new Error("Unsupported interlace method")); - return; - } - - this._colorType = colorType; - - let bpp = constants.COLORTYPE_TO_BPP_MAP[this._colorType]; - - this._hasIHDR = true; - - this.metadata({ - width: width, - height: height, - depth: depth, - interlace: Boolean(interlace), - palette: Boolean(colorType & constants.COLORTYPE_PALETTE), - color: Boolean(colorType & constants.COLORTYPE_COLOR), - alpha: Boolean(colorType & constants.COLORTYPE_ALPHA), - bpp: bpp, - colorType: colorType, - }); - - this._handleChunkEnd(); -}; - -Parser.prototype._handlePLTE = function (length) { - this.read(length, this._parsePLTE.bind(this)); -}; -Parser.prototype._parsePLTE = function (data) { - this._crc.write(data); - - let entries = Math.floor(data.length / 3); - // console.log('Palette:', entries); - - for (let i = 0; i < entries; i++) { - this._palette.push([data[i * 3], data[i * 3 + 1], data[i * 3 + 2], 0xff]); - } - - this.palette(this._palette); - - this._handleChunkEnd(); -}; - -Parser.prototype._handleTRNS = function (length) { - this.simpleTransparency(); - this.read(length, this._parseTRNS.bind(this)); -}; -Parser.prototype._parseTRNS = function (data) { - this._crc.write(data); - - // palette - if (this._colorType === constants.COLORTYPE_PALETTE_COLOR) { - if (this._palette.length === 0) { - this.error(new Error("Transparency chunk must be after palette")); - return; - } - if (data.length > this._palette.length) { - this.error(new Error("More transparent colors than palette size")); - return; - } - for (let i = 0; i < data.length; i++) { - this._palette[i][3] = data[i]; - } - this.palette(this._palette); - } - - // for colorType 0 (grayscale) and 2 (rgb) - // there might be one gray/color defined as transparent - if (this._colorType === constants.COLORTYPE_GRAYSCALE) { - // grey, 2 bytes - this.transColor([data.readUInt16BE(0)]); - } - if (this._colorType === constants.COLORTYPE_COLOR) { - this.transColor([ - data.readUInt16BE(0), - data.readUInt16BE(2), - data.readUInt16BE(4), - ]); - } - - this._handleChunkEnd(); -}; - -Parser.prototype._handleGAMA = function (length) { - this.read(length, this._parseGAMA.bind(this)); -}; -Parser.prototype._parseGAMA = function (data) { - this._crc.write(data); - this.gamma(data.readUInt32BE(0) / constants.GAMMA_DIVISION); - - this._handleChunkEnd(); -}; - -Parser.prototype._handleIDAT = function (length) { - if (!this._emittedHeadersFinished) { - this._emittedHeadersFinished = true; - this.headersFinished(); - } - this.read(-length, this._parseIDAT.bind(this, length)); -}; -Parser.prototype._parseIDAT = function (length, data) { - this._crc.write(data); - - if ( - this._colorType === constants.COLORTYPE_PALETTE_COLOR && - this._palette.length === 0 - ) { - throw new Error("Expected palette not found"); - } - - this.inflateData(data); - let leftOverLength = length - data.length; - - if (leftOverLength > 0) { - this._handleIDAT(leftOverLength); - } else { - this._handleChunkEnd(); - } -}; - -Parser.prototype._handleIEND = function (length) { - this.read(length, this._parseIEND.bind(this)); -}; -Parser.prototype._parseIEND = function (data) { - this._crc.write(data); - - this._hasIEND = true; - this._handleChunkEnd(); - - if (this.finished) { - this.finished(); - } -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/png-sync.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/png-sync.js deleted file mode 100644 index 68cac9bc..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/png-sync.js +++ /dev/null @@ -1,12 +0,0 @@ -"use strict"; - -let parse = require("./parser-sync"); -let pack = require("./packer-sync"); - -exports.read = function (buffer, options) { - return parse(buffer, options || {}); -}; - -exports.write = function (png, options) { - return pack(png, options); -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/png.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/png.js deleted file mode 100644 index 0b8af3f7..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/png.js +++ /dev/null @@ -1,194 +0,0 @@ -"use strict"; - -let util = require("util"); -let Stream = require("stream"); -let Parser = require("./parser-async"); -let Packer = require("./packer-async"); -let PNGSync = require("./png-sync"); - -let PNG = (exports.PNG = function (options) { - Stream.call(this); - - options = options || {}; // eslint-disable-line no-param-reassign - - // coerce pixel dimensions to integers (also coerces undefined -> 0): - this.width = options.width | 0; - this.height = options.height | 0; - - this.data = - this.width > 0 && this.height > 0 - ? Buffer.alloc(4 * this.width * this.height) - : null; - - if (options.fill && this.data) { - this.data.fill(0); - } - - this.gamma = 0; - this.readable = this.writable = true; - - this._parser = new Parser(options); - - this._parser.on("error", this.emit.bind(this, "error")); - this._parser.on("close", this._handleClose.bind(this)); - this._parser.on("metadata", this._metadata.bind(this)); - this._parser.on("gamma", this._gamma.bind(this)); - this._parser.on( - "parsed", - function (data) { - this.data = data; - this.emit("parsed", data); - }.bind(this) - ); - - this._packer = new Packer(options); - this._packer.on("data", this.emit.bind(this, "data")); - this._packer.on("end", this.emit.bind(this, "end")); - this._parser.on("close", this._handleClose.bind(this)); - this._packer.on("error", this.emit.bind(this, "error")); -}); -util.inherits(PNG, Stream); - -PNG.sync = PNGSync; - -PNG.prototype.pack = function () { - if (!this.data || !this.data.length) { - this.emit("error", "No data provided"); - return this; - } - - process.nextTick( - function () { - this._packer.pack(this.data, this.width, this.height, this.gamma); - }.bind(this) - ); - - return this; -}; - -PNG.prototype.parse = function (data, callback) { - if (callback) { - let onParsed, onError; - - onParsed = function (parsedData) { - this.removeListener("error", onError); - - this.data = parsedData; - callback(null, this); - }.bind(this); - - onError = function (err) { - this.removeListener("parsed", onParsed); - - callback(err, null); - }.bind(this); - - this.once("parsed", onParsed); - this.once("error", onError); - } - - this.end(data); - return this; -}; - -PNG.prototype.write = function (data) { - this._parser.write(data); - return true; -}; - -PNG.prototype.end = function (data) { - this._parser.end(data); -}; - -PNG.prototype._metadata = function (metadata) { - this.width = metadata.width; - this.height = metadata.height; - - this.emit("metadata", metadata); -}; - -PNG.prototype._gamma = function (gamma) { - this.gamma = gamma; -}; - -PNG.prototype._handleClose = function () { - if (!this._parser.writable && !this._packer.readable) { - this.emit("close"); - } -}; - -PNG.bitblt = function (src, dst, srcX, srcY, width, height, deltaX, deltaY) { - // eslint-disable-line max-params - // coerce pixel dimensions to integers (also coerces undefined -> 0): - /* eslint-disable no-param-reassign */ - srcX |= 0; - srcY |= 0; - width |= 0; - height |= 0; - deltaX |= 0; - deltaY |= 0; - /* eslint-enable no-param-reassign */ - - if ( - srcX > src.width || - srcY > src.height || - srcX + width > src.width || - srcY + height > src.height - ) { - throw new Error("bitblt reading outside image"); - } - - if ( - deltaX > dst.width || - deltaY > dst.height || - deltaX + width > dst.width || - deltaY + height > dst.height - ) { - throw new Error("bitblt writing outside image"); - } - - for (let y = 0; y < height; y++) { - src.data.copy( - dst.data, - ((deltaY + y) * dst.width + deltaX) << 2, - ((srcY + y) * src.width + srcX) << 2, - ((srcY + y) * src.width + srcX + width) << 2 - ); - } -}; - -PNG.prototype.bitblt = function ( - dst, - srcX, - srcY, - width, - height, - deltaX, - deltaY -) { - // eslint-disable-line max-params - - PNG.bitblt(this, dst, srcX, srcY, width, height, deltaX, deltaY); - return this; -}; - -PNG.adjustGamma = function (src) { - if (src.gamma) { - for (let y = 0; y < src.height; y++) { - for (let x = 0; x < src.width; x++) { - let idx = (src.width * y + x) << 2; - - for (let i = 0; i < 3; i++) { - let sample = src.data[idx + i] / 255; - sample = Math.pow(sample, 1 / 2.2 / src.gamma); - src.data[idx + i] = Math.round(sample * 255); - } - } - } - src.gamma = 0; - } -}; - -PNG.prototype.adjustGamma = function () { - PNG.adjustGamma(this); -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-inflate.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-inflate.js deleted file mode 100644 index 4da0d5f0..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-inflate.js +++ /dev/null @@ -1,168 +0,0 @@ -"use strict"; - -let assert = require("assert").ok; -let zlib = require("zlib"); -let util = require("util"); - -let kMaxLength = require("buffer").kMaxLength; - -function Inflate(opts) { - if (!(this instanceof Inflate)) { - return new Inflate(opts); - } - - if (opts && opts.chunkSize < zlib.Z_MIN_CHUNK) { - opts.chunkSize = zlib.Z_MIN_CHUNK; - } - - zlib.Inflate.call(this, opts); - - // Node 8 --> 9 compatibility check - this._offset = this._offset === undefined ? this._outOffset : this._offset; - this._buffer = this._buffer || this._outBuffer; - - if (opts && opts.maxLength != null) { - this._maxLength = opts.maxLength; - } -} - -function createInflate(opts) { - return new Inflate(opts); -} - -function _close(engine, callback) { - if (callback) { - process.nextTick(callback); - } - - // Caller may invoke .close after a zlib error (which will null _handle). - if (!engine._handle) { - return; - } - - engine._handle.close(); - engine._handle = null; -} - -Inflate.prototype._processChunk = function (chunk, flushFlag, asyncCb) { - if (typeof asyncCb === "function") { - return zlib.Inflate._processChunk.call(this, chunk, flushFlag, asyncCb); - } - - let self = this; - - let availInBefore = chunk && chunk.length; - let availOutBefore = this._chunkSize - this._offset; - let leftToInflate = this._maxLength; - let inOff = 0; - - let buffers = []; - let nread = 0; - - let error; - this.on("error", function (err) { - error = err; - }); - - function handleChunk(availInAfter, availOutAfter) { - if (self._hadError) { - return; - } - - let have = availOutBefore - availOutAfter; - assert(have >= 0, "have should not go down"); - - if (have > 0) { - let out = self._buffer.slice(self._offset, self._offset + have); - self._offset += have; - - if (out.length > leftToInflate) { - out = out.slice(0, leftToInflate); - } - - buffers.push(out); - nread += out.length; - leftToInflate -= out.length; - - if (leftToInflate === 0) { - return false; - } - } - - if (availOutAfter === 0 || self._offset >= self._chunkSize) { - availOutBefore = self._chunkSize; - self._offset = 0; - self._buffer = Buffer.allocUnsafe(self._chunkSize); - } - - if (availOutAfter === 0) { - inOff += availInBefore - availInAfter; - availInBefore = availInAfter; - - return true; - } - - return false; - } - - assert(this._handle, "zlib binding closed"); - let res; - do { - res = this._handle.writeSync( - flushFlag, - chunk, // in - inOff, // in_off - availInBefore, // in_len - this._buffer, // out - this._offset, //out_off - availOutBefore - ); // out_len - // Node 8 --> 9 compatibility check - res = res || this._writeState; - } while (!this._hadError && handleChunk(res[0], res[1])); - - if (this._hadError) { - throw error; - } - - if (nread >= kMaxLength) { - _close(this); - throw new RangeError( - "Cannot create final Buffer. It would be larger than 0x" + - kMaxLength.toString(16) + - " bytes" - ); - } - - let buf = Buffer.concat(buffers, nread); - _close(this); - - return buf; -}; - -util.inherits(Inflate, zlib.Inflate); - -function zlibBufferSync(engine, buffer) { - if (typeof buffer === "string") { - buffer = Buffer.from(buffer); - } - if (!(buffer instanceof Buffer)) { - throw new TypeError("Not a string or buffer"); - } - - let flushFlag = engine._finishFlushFlag; - if (flushFlag == null) { - flushFlag = zlib.Z_FINISH; - } - - return engine._processChunk(buffer, flushFlag); -} - -function inflateSync(buffer, opts) { - return zlibBufferSync(new Inflate(opts), buffer); -} - -module.exports = exports = inflateSync; -exports.Inflate = Inflate; -exports.createInflate = createInflate; -exports.inflateSync = inflateSync; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-reader.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-reader.js deleted file mode 100644 index 213d1a75..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-reader.js +++ /dev/null @@ -1,45 +0,0 @@ -"use strict"; - -let SyncReader = (module.exports = function (buffer) { - this._buffer = buffer; - this._reads = []; -}); - -SyncReader.prototype.read = function (length, callback) { - this._reads.push({ - length: Math.abs(length), // if length < 0 then at most this length - allowLess: length < 0, - func: callback, - }); -}; - -SyncReader.prototype.process = function () { - // as long as there is any data and read requests - while (this._reads.length > 0 && this._buffer.length) { - let read = this._reads[0]; - - if ( - this._buffer.length && - (this._buffer.length >= read.length || read.allowLess) - ) { - // ok there is any data so that we can satisfy this request - this._reads.shift(); // == read - - let buf = this._buffer; - - this._buffer = buf.slice(read.length); - - read.func.call(this, buf.slice(0, read.length)); - } else { - break; - } - } - - if (this._reads.length > 0) { - throw new Error("There are some read requests waitng on finished stream"); - } - - if (this._buffer.length > 0) { - throw new Error("unrecognised content at end of stream"); - } -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/package.json b/node_modules/lv_font_conv/node_modules/pngjs/package.json deleted file mode 100644 index 1f696a7e..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/package.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "_args": [ - [ - "pngjs@6.0.0", - "/home/vitaly/Dropbox/Coding/lv_font_conv" - ] - ], - "_from": "pngjs@6.0.0", - "_id": "pngjs@6.0.0", - "_inBundle": false, - "_integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", - "_location": "/pngjs", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "pngjs@6.0.0", - "name": "pngjs", - "escapedName": "pngjs", - "rawSpec": "6.0.0", - "saveSpec": null, - "fetchSpec": "6.0.0" - }, - "_requiredBy": [ - "/" - ], - "_resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", - "_spec": "6.0.0", - "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", - "bugs": { - "url": "https://github.com/lukeapage/pngjs/issues" - }, - "contributors": [ - { - "name": "Alexandre Paré" - }, - { - "name": "Gaurav Mali" - }, - { - "name": "Gusts Kaksis" - }, - { - "name": "Kuba Niegowski" - }, - { - "name": "Luke Page" - }, - { - "name": "Pietajan De Potter" - }, - { - "name": "Steven Sojka" - }, - { - "name": "liangzeng" - }, - { - "name": "Michael Vogt" - }, - { - "name": "Xin-Xin Wang" - }, - { - "name": "toriningen" - }, - { - "name": "Eugene Kulabuhov" - } - ], - "description": "PNG encoder/decoder in pure JS, supporting any bit size & interlace, async & sync with full test suite.", - "devDependencies": { - "browserify": "17.0.0", - "buffer-equal": "1.0.0", - "codecov": "3.7.1", - "connect": "3.7.0", - "eslint": "7.8.1", - "eslint-config-prettier": "6.14.0", - "nyc": "15.1.0", - "prettier": "2.1.1", - "puppeteer": "5.4.0", - "serve-static": "1.14.1", - "tap-dot": "2.0.0", - "tape": "5.0.1" - }, - "directories": { - "lib": "lib", - "example": "examples", - "test": "test" - }, - "engines": { - "node": ">=12.13.0" - }, - "files": [ - "browser.js", - "lib/" - ], - "homepage": "https://github.com/lukeapage/pngjs", - "keywords": [ - "PNG", - "decoder", - "encoder", - "js-png", - "node-png", - "parser", - "png", - "png-js", - "png-parse", - "pngjs" - ], - "license": "MIT", - "main": "./lib/png.js", - "name": "pngjs", - "repository": { - "type": "git", - "url": "git://github.com/lukeapage/pngjs.git" - }, - "scripts": { - "browserify": "browserify lib/png.js --standalone png > browser.js", - "build": "yarn prepublish", - "coverage": "nyc --reporter=lcov --reporter=text-summary tape test/*-spec.js nolarge", - "lint": "eslint .", - "prepublish": "yarn browserify", - "prettier:check": "prettier --check .", - "prettier:write": "prettier --write .", - "test": "yarn lint && yarn prettier:check && tape test/*-spec.js | tap-dot && node test/run-compare" - }, - "version": "6.0.0" -} diff --git a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/LICENSE-MIT.txt b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/LICENSE-MIT.txt deleted file mode 100644 index a41e0a7e..00000000 --- a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/LICENSE-MIT.txt +++ /dev/null @@ -1,20 +0,0 @@ -Copyright Mathias Bynens - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/README.md b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/README.md deleted file mode 100644 index d648b879..00000000 --- a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# ES6 `String.prototype.codePointAt` polyfill [![Build status](https://travis-ci.org/mathiasbynens/String.prototype.codePointAt.svg?branch=master)](https://travis-ci.org/mathiasbynens/String.prototype.codePointAt) - -A robust & optimized ES3-compatible polyfill for [the `String.prototype.codePointAt` method in ECMAScript 6](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-string.prototype.codepointat). - -Other polyfills for `String.prototype.codePointAt` are available: - -* by [Norbert Lindenberg](http://norbertlindenberg.com/) (fails some tests) -* by [Steven Levithan](http://stevenlevithan.com/) (fails some tests) -* by [Paul Miller](http://paulmillr.com/) (~~[fails some tests](https://github.com/paulmillr/es6-shim/issues/166)~~ passes all tests) - -## Installation - -In a browser: - -```html - -``` - -Via [npm](http://npmjs.org/): - -```bash -npm install string.prototype.codepointat -``` - -Then, in [Node.js](http://nodejs.org/): - -```js -require('string.prototype.codepointat'); - -// On Windows and on Mac systems with default settings, case doesn’t matter, -// which allows you to do this instead: -require('String.prototype.codePointAt'); -``` - -## Notes - -[A polyfill + test suite for `String.fromCodePoint`](https://mths.be/fromcodepoint) is available, too. - -## Author - -| [![twitter/mathias](https://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") | -|---| -| [Mathias Bynens](https://mathiasbynens.be/) | - -## License - -This polyfill is available under the [MIT](https://mths.be/mit) license. diff --git a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/codepointat.js b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/codepointat.js deleted file mode 100644 index f724c892..00000000 --- a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/codepointat.js +++ /dev/null @@ -1,54 +0,0 @@ -/*! https://mths.be/codepointat v0.2.0 by @mathias */ -if (!String.prototype.codePointAt) { - (function() { - 'use strict'; // needed to support `apply`/`call` with `undefined`/`null` - var defineProperty = (function() { - // IE 8 only supports `Object.defineProperty` on DOM elements - try { - var object = {}; - var $defineProperty = Object.defineProperty; - var result = $defineProperty(object, object, object) && $defineProperty; - } catch(error) {} - return result; - }()); - var codePointAt = function(position) { - if (this == null) { - throw TypeError(); - } - var string = String(this); - var size = string.length; - // `ToInteger` - var index = position ? Number(position) : 0; - if (index != index) { // better `isNaN` - index = 0; - } - // Account for out-of-bounds indices: - if (index < 0 || index >= size) { - return undefined; - } - // Get the first code unit - var first = string.charCodeAt(index); - var second; - if ( // check if it’s the start of a surrogate pair - first >= 0xD800 && first <= 0xDBFF && // high surrogate - size > index + 1 // there is a next code unit - ) { - second = string.charCodeAt(index + 1); - if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate - // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae - return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; - } - } - return first; - }; - if (defineProperty) { - defineProperty(String.prototype, 'codePointAt', { - 'value': codePointAt, - 'configurable': true, - 'writable': true - }); - } else { - String.prototype.codePointAt = codePointAt; - } - }()); -} diff --git a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/package.json b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/package.json deleted file mode 100644 index cee4e79d..00000000 --- a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/package.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "_args": [ - [ - "string.prototype.codepointat@0.2.1", - "/home/vitaly/Dropbox/Coding/lv_font_conv" - ] - ], - "_from": "string.prototype.codepointat@0.2.1", - "_id": "string.prototype.codepointat@0.2.1", - "_inBundle": false, - "_integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==", - "_location": "/string.prototype.codepointat", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "string.prototype.codepointat@0.2.1", - "name": "string.prototype.codepointat", - "escapedName": "string.prototype.codepointat", - "rawSpec": "0.2.1", - "saveSpec": null, - "fetchSpec": "0.2.1" - }, - "_requiredBy": [ - "/opentype.js" - ], - "_resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", - "_spec": "0.2.1", - "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", - "author": { - "name": "Mathias Bynens", - "url": "https://mathiasbynens.be/" - }, - "bugs": { - "url": "https://github.com/mathiasbynens/String.prototype.codePointAt/issues" - }, - "description": "A robust & optimized `String.prototype.codePointAt` polyfill, based on the ECMAScript 6 specification.", - "files": [ - "LICENSE-MIT.txt", - "codepointat.js" - ], - "homepage": "https://mths.be/codepointat", - "keywords": [ - "string", - "unicode", - "es6", - "ecmascript", - "polyfill" - ], - "license": "MIT", - "main": "codepointat.js", - "name": "string.prototype.codepointat", - "repository": { - "type": "git", - "url": "git+https://github.com/mathiasbynens/String.prototype.codePointAt.git" - }, - "scripts": { - "cover": "istanbul cover --report html --verbose --dir coverage tests/tests.js", - "test": "node tests/tests.js" - }, - "version": "0.2.1" -} diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/LICENSE b/node_modules/lv_font_conv/node_modules/tiny-inflate/LICENSE deleted file mode 100644 index 62914548..00000000 --- a/node_modules/lv_font_conv/node_modules/tiny-inflate/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2015-present Devon Govett - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/index.js b/node_modules/lv_font_conv/node_modules/tiny-inflate/index.js deleted file mode 100644 index 44d1151b..00000000 --- a/node_modules/lv_font_conv/node_modules/tiny-inflate/index.js +++ /dev/null @@ -1,375 +0,0 @@ -var TINF_OK = 0; -var TINF_DATA_ERROR = -3; - -function Tree() { - this.table = new Uint16Array(16); /* table of code length counts */ - this.trans = new Uint16Array(288); /* code -> symbol translation table */ -} - -function Data(source, dest) { - this.source = source; - this.sourceIndex = 0; - this.tag = 0; - this.bitcount = 0; - - this.dest = dest; - this.destLen = 0; - - this.ltree = new Tree(); /* dynamic length/symbol tree */ - this.dtree = new Tree(); /* dynamic distance tree */ -} - -/* --------------------------------------------------- * - * -- uninitialized global data (static structures) -- * - * --------------------------------------------------- */ - -var sltree = new Tree(); -var sdtree = new Tree(); - -/* extra bits and base tables for length codes */ -var length_bits = new Uint8Array(30); -var length_base = new Uint16Array(30); - -/* extra bits and base tables for distance codes */ -var dist_bits = new Uint8Array(30); -var dist_base = new Uint16Array(30); - -/* special ordering of code length codes */ -var clcidx = new Uint8Array([ - 16, 17, 18, 0, 8, 7, 9, 6, - 10, 5, 11, 4, 12, 3, 13, 2, - 14, 1, 15 -]); - -/* used by tinf_decode_trees, avoids allocations every call */ -var code_tree = new Tree(); -var lengths = new Uint8Array(288 + 32); - -/* ----------------------- * - * -- utility functions -- * - * ----------------------- */ - -/* build extra bits and base tables */ -function tinf_build_bits_base(bits, base, delta, first) { - var i, sum; - - /* build bits table */ - for (i = 0; i < delta; ++i) bits[i] = 0; - for (i = 0; i < 30 - delta; ++i) bits[i + delta] = i / delta | 0; - - /* build base table */ - for (sum = first, i = 0; i < 30; ++i) { - base[i] = sum; - sum += 1 << bits[i]; - } -} - -/* build the fixed huffman trees */ -function tinf_build_fixed_trees(lt, dt) { - var i; - - /* build fixed length tree */ - for (i = 0; i < 7; ++i) lt.table[i] = 0; - - lt.table[7] = 24; - lt.table[8] = 152; - lt.table[9] = 112; - - for (i = 0; i < 24; ++i) lt.trans[i] = 256 + i; - for (i = 0; i < 144; ++i) lt.trans[24 + i] = i; - for (i = 0; i < 8; ++i) lt.trans[24 + 144 + i] = 280 + i; - for (i = 0; i < 112; ++i) lt.trans[24 + 144 + 8 + i] = 144 + i; - - /* build fixed distance tree */ - for (i = 0; i < 5; ++i) dt.table[i] = 0; - - dt.table[5] = 32; - - for (i = 0; i < 32; ++i) dt.trans[i] = i; -} - -/* given an array of code lengths, build a tree */ -var offs = new Uint16Array(16); - -function tinf_build_tree(t, lengths, off, num) { - var i, sum; - - /* clear code length count table */ - for (i = 0; i < 16; ++i) t.table[i] = 0; - - /* scan symbol lengths, and sum code length counts */ - for (i = 0; i < num; ++i) t.table[lengths[off + i]]++; - - t.table[0] = 0; - - /* compute offset table for distribution sort */ - for (sum = 0, i = 0; i < 16; ++i) { - offs[i] = sum; - sum += t.table[i]; - } - - /* create code->symbol translation table (symbols sorted by code) */ - for (i = 0; i < num; ++i) { - if (lengths[off + i]) t.trans[offs[lengths[off + i]]++] = i; - } -} - -/* ---------------------- * - * -- decode functions -- * - * ---------------------- */ - -/* get one bit from source stream */ -function tinf_getbit(d) { - /* check if tag is empty */ - if (!d.bitcount--) { - /* load next tag */ - d.tag = d.source[d.sourceIndex++]; - d.bitcount = 7; - } - - /* shift bit out of tag */ - var bit = d.tag & 1; - d.tag >>>= 1; - - return bit; -} - -/* read a num bit value from a stream and add base */ -function tinf_read_bits(d, num, base) { - if (!num) - return base; - - while (d.bitcount < 24) { - d.tag |= d.source[d.sourceIndex++] << d.bitcount; - d.bitcount += 8; - } - - var val = d.tag & (0xffff >>> (16 - num)); - d.tag >>>= num; - d.bitcount -= num; - return val + base; -} - -/* given a data stream and a tree, decode a symbol */ -function tinf_decode_symbol(d, t) { - while (d.bitcount < 24) { - d.tag |= d.source[d.sourceIndex++] << d.bitcount; - d.bitcount += 8; - } - - var sum = 0, cur = 0, len = 0; - var tag = d.tag; - - /* get more bits while code value is above sum */ - do { - cur = 2 * cur + (tag & 1); - tag >>>= 1; - ++len; - - sum += t.table[len]; - cur -= t.table[len]; - } while (cur >= 0); - - d.tag = tag; - d.bitcount -= len; - - return t.trans[sum + cur]; -} - -/* given a data stream, decode dynamic trees from it */ -function tinf_decode_trees(d, lt, dt) { - var hlit, hdist, hclen; - var i, num, length; - - /* get 5 bits HLIT (257-286) */ - hlit = tinf_read_bits(d, 5, 257); - - /* get 5 bits HDIST (1-32) */ - hdist = tinf_read_bits(d, 5, 1); - - /* get 4 bits HCLEN (4-19) */ - hclen = tinf_read_bits(d, 4, 4); - - for (i = 0; i < 19; ++i) lengths[i] = 0; - - /* read code lengths for code length alphabet */ - for (i = 0; i < hclen; ++i) { - /* get 3 bits code length (0-7) */ - var clen = tinf_read_bits(d, 3, 0); - lengths[clcidx[i]] = clen; - } - - /* build code length tree */ - tinf_build_tree(code_tree, lengths, 0, 19); - - /* decode code lengths for the dynamic trees */ - for (num = 0; num < hlit + hdist;) { - var sym = tinf_decode_symbol(d, code_tree); - - switch (sym) { - case 16: - /* copy previous code length 3-6 times (read 2 bits) */ - var prev = lengths[num - 1]; - for (length = tinf_read_bits(d, 2, 3); length; --length) { - lengths[num++] = prev; - } - break; - case 17: - /* repeat code length 0 for 3-10 times (read 3 bits) */ - for (length = tinf_read_bits(d, 3, 3); length; --length) { - lengths[num++] = 0; - } - break; - case 18: - /* repeat code length 0 for 11-138 times (read 7 bits) */ - for (length = tinf_read_bits(d, 7, 11); length; --length) { - lengths[num++] = 0; - } - break; - default: - /* values 0-15 represent the actual code lengths */ - lengths[num++] = sym; - break; - } - } - - /* build dynamic trees */ - tinf_build_tree(lt, lengths, 0, hlit); - tinf_build_tree(dt, lengths, hlit, hdist); -} - -/* ----------------------------- * - * -- block inflate functions -- * - * ----------------------------- */ - -/* given a stream and two trees, inflate a block of data */ -function tinf_inflate_block_data(d, lt, dt) { - while (1) { - var sym = tinf_decode_symbol(d, lt); - - /* check for end of block */ - if (sym === 256) { - return TINF_OK; - } - - if (sym < 256) { - d.dest[d.destLen++] = sym; - } else { - var length, dist, offs; - var i; - - sym -= 257; - - /* possibly get more bits from length code */ - length = tinf_read_bits(d, length_bits[sym], length_base[sym]); - - dist = tinf_decode_symbol(d, dt); - - /* possibly get more bits from distance code */ - offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]); - - /* copy match */ - for (i = offs; i < offs + length; ++i) { - d.dest[d.destLen++] = d.dest[i]; - } - } - } -} - -/* inflate an uncompressed block of data */ -function tinf_inflate_uncompressed_block(d) { - var length, invlength; - var i; - - /* unread from bitbuffer */ - while (d.bitcount > 8) { - d.sourceIndex--; - d.bitcount -= 8; - } - - /* get length */ - length = d.source[d.sourceIndex + 1]; - length = 256 * length + d.source[d.sourceIndex]; - - /* get one's complement of length */ - invlength = d.source[d.sourceIndex + 3]; - invlength = 256 * invlength + d.source[d.sourceIndex + 2]; - - /* check length */ - if (length !== (~invlength & 0x0000ffff)) - return TINF_DATA_ERROR; - - d.sourceIndex += 4; - - /* copy block */ - for (i = length; i; --i) - d.dest[d.destLen++] = d.source[d.sourceIndex++]; - - /* make sure we start next block on a byte boundary */ - d.bitcount = 0; - - return TINF_OK; -} - -/* inflate stream from source to dest */ -function tinf_uncompress(source, dest) { - var d = new Data(source, dest); - var bfinal, btype, res; - - do { - /* read final block flag */ - bfinal = tinf_getbit(d); - - /* read block type (2 bits) */ - btype = tinf_read_bits(d, 2, 0); - - /* decompress block */ - switch (btype) { - case 0: - /* decompress uncompressed block */ - res = tinf_inflate_uncompressed_block(d); - break; - case 1: - /* decompress block with fixed huffman trees */ - res = tinf_inflate_block_data(d, sltree, sdtree); - break; - case 2: - /* decompress block with dynamic huffman trees */ - tinf_decode_trees(d, d.ltree, d.dtree); - res = tinf_inflate_block_data(d, d.ltree, d.dtree); - break; - default: - res = TINF_DATA_ERROR; - } - - if (res !== TINF_OK) - throw new Error('Data error'); - - } while (!bfinal); - - if (d.destLen < d.dest.length) { - if (typeof d.dest.slice === 'function') - return d.dest.slice(0, d.destLen); - else - return d.dest.subarray(0, d.destLen); - } - - return d.dest; -} - -/* -------------------- * - * -- initialization -- * - * -------------------- */ - -/* build fixed huffman trees */ -tinf_build_fixed_trees(sltree, sdtree); - -/* build extra bits and base tables */ -tinf_build_bits_base(length_bits, length_base, 4, 3); -tinf_build_bits_base(dist_bits, dist_base, 2, 1); - -/* fix a special case */ -length_bits[28] = 0; -length_base[28] = 258; - -module.exports = tinf_uncompress; diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/package.json b/node_modules/lv_font_conv/node_modules/tiny-inflate/package.json deleted file mode 100644 index 53399e20..00000000 --- a/node_modules/lv_font_conv/node_modules/tiny-inflate/package.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "_args": [ - [ - "tiny-inflate@1.0.3", - "/home/vitaly/Dropbox/Coding/lv_font_conv" - ] - ], - "_from": "tiny-inflate@1.0.3", - "_id": "tiny-inflate@1.0.3", - "_inBundle": false, - "_integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", - "_location": "/tiny-inflate", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "tiny-inflate@1.0.3", - "name": "tiny-inflate", - "escapedName": "tiny-inflate", - "rawSpec": "1.0.3", - "saveSpec": null, - "fetchSpec": "1.0.3" - }, - "_requiredBy": [ - "/opentype.js", - "/unicode-trie" - ], - "_resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", - "_spec": "1.0.3", - "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", - "author": { - "name": "Devon Govett", - "email": "devongovett@gmail.com" - }, - "bugs": { - "url": "https://github.com/devongovett/tiny-inflate/issues" - }, - "description": "A tiny inflate implementation", - "devDependencies": { - "mocha": "^2.1.0" - }, - "homepage": "https://github.com/devongovett/tiny-inflate", - "keywords": [ - "inflate", - "zlib", - "gzip", - "zip" - ], - "license": "MIT", - "main": "index.js", - "name": "tiny-inflate", - "repository": { - "type": "git", - "url": "git://github.com/devongovett/tiny-inflate.git" - }, - "scripts": { - "test": "mocha" - }, - "version": "1.0.3" -} diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/readme.md b/node_modules/lv_font_conv/node_modules/tiny-inflate/readme.md deleted file mode 100644 index dd8c408c..00000000 --- a/node_modules/lv_font_conv/node_modules/tiny-inflate/readme.md +++ /dev/null @@ -1,31 +0,0 @@ -# tiny-inflate - -This is a port of Joergen Ibsen's [tiny inflate](https://bitbucket.org/jibsen/tinf) to JavaScript. -Minified it is about 3KB, or 1.3KB gzipped. While being very small, it is also reasonably fast -(about 30% - 50% slower than [pako](https://github.com/nodeca/pako) on average), and should be -good enough for many applications. If you need the absolute best performance, however, you'll -need to use a larger library such as pako that contains additional optimizations. - -## Installation - - npm install tiny-inflate - -## Example - -To use tiny-inflate, you need two things: a buffer of data compressed with deflate, -and the decompressed size (often stored in a file header) to allocate your output buffer. -Input and output buffers can be either node `Buffer`s, or `Uint8Array`s. - -```javascript -var inflate = require('tiny-inflate'); - -var compressedBuffer = new Bufer([ ... ]); -var decompressedSize = ...; -var outputBuffer = new Buffer(decompressedSize); - -inflate(compressedBuffer, outputBuffer); -``` - -## License - -MIT diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/test/index.js b/node_modules/lv_font_conv/node_modules/tiny-inflate/test/index.js deleted file mode 100644 index f4e8c88f..00000000 --- a/node_modules/lv_font_conv/node_modules/tiny-inflate/test/index.js +++ /dev/null @@ -1,75 +0,0 @@ -var inflate = require('../'); -var zlib = require('zlib'); -var fs = require('fs'); -var assert = require('assert'); -var uncompressed = fs.readFileSync(__dirname + '/lorem.txt'); - -describe('tiny-inflate', function() { - var compressed, noCompression, fixed; - - function deflate(buf, options, fn) { - var chunks = []; - zlib.createDeflateRaw(options) - .on('data', function(chunk) { - chunks.push(chunk); - }) - .on('error', fn) - .on('end', function() { - fn(null, Buffer.concat(chunks)); - }) - .end(buf); - } - - before(function(done) { - zlib.deflateRaw(uncompressed, function(err, data) { - compressed = data; - done(); - }); - }); - - before(function(done) { - deflate(uncompressed, { level: zlib.Z_NO_COMPRESSION }, function(err, data) { - noCompression = data; - done(); - }); - }); - - before(function(done) { - deflate(uncompressed, { strategy: zlib.Z_FIXED }, function(err, data) { - fixed = data; - done(); - }); - }); - - it('should inflate some data', function() { - var out = Buffer.alloc(uncompressed.length); - inflate(compressed, out); - assert.deepEqual(out, uncompressed); - }); - - it('should slice output buffer', function() { - var out = Buffer.alloc(uncompressed.length + 1024); - var res = inflate(compressed, out); - assert.deepEqual(res, uncompressed); - assert.equal(res.length, uncompressed.length); - }); - - it('should handle uncompressed blocks', function() { - var out = Buffer.alloc(uncompressed.length); - inflate(noCompression, out); - assert.deepEqual(out, uncompressed); - }); - - it('should handle fixed huffman blocks', function() { - var out = Buffer.alloc(uncompressed.length); - inflate(fixed, out); - assert.deepEqual(out, uncompressed); - }); - - it('should handle typed arrays', function() { - var input = new Uint8Array(compressed); - var out = new Uint8Array(uncompressed.length); - inflate(input, out); - assert.deepEqual(out, new Uint8Array(uncompressed)); - }); -}); diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/test/lorem.txt b/node_modules/lv_font_conv/node_modules/tiny-inflate/test/lorem.txt deleted file mode 100644 index c37b0a59..00000000 --- a/node_modules/lv_font_conv/node_modules/tiny-inflate/test/lorem.txt +++ /dev/null @@ -1,199 +0,0 @@ -Lorem ipsum dolor sit amet, sea pertinax pertinacia appellantur in, est ad esse assentior mediocritatem, magna populo menandri cum te. Vel augue menandri eu, at integre appareat splendide duo. Est ne tollit ullamcorper, eu pro falli diceret perpetua, sea ferri numquam legendos ut. Diceret suscipiantur at nec, his ei nulla mentitum efficiantur. Errem saepe ei vis. - - Per melius aperiri eu. Et interesset philosophia vim, graece denique intellegam duo at, te vix quot apeirian dignissim. Ei essent percipitur nam, natum possit interpretaris sea ea. Cum assum adipisci cotidieque ut, ut veri tollit duo. Erat idque volutpat mea ut, mel nominati splendide vulputate ea. - - No ferri partem ceteros pro. Everti volumus menandri at pro. Cum illud euripidis cu, mazim deterruisset ei eum. Ex alia dolorem insolens per, malis clita laboramus duo ut, ridens appareat philosophia ea quo. - - Vix elit tantas phaedrum et, ea quo vide facete scriptorem. Ut facer laboramus definitiones has, viris dictas regione at eos. Noluisse constituto vix at, nec malorum rationibus te. Nec numquam definiebas id, vim id liber munere. Simul discere reprimique qui eu. Et aeterno aperiri disputando vix. - - Usu ne denique albucius gloriatur. Pri in novum electram, ei amet electram quo. Vix summo recusabo dissentiunt no. Natum mediocrem maiestatis ut eum, vocibus nominavi has in, affert civibus te nam. Cibo minim ex nec. - - Ipsum tibique deseruisse vel ut. Ad laudem iracundia eam. Eum id legere scripta nominavi, vim melius ceteros et, ea tale enim nec. Aeque facete signiferumque ne est, vis ei sint persequeris. Ei magna veritus nec, enim aliquando ex pro. - - Verear noluisse qui eu, id mutat possit nec. Ad est melius placerat, soluta facilisi et vel. Id his simul consul praesent, no nam nihil perfecto, quo in mucius corrumpit. Assum ceteros cotidieque et nam, illum quaeque cum et. Numquam laoreet his at, in vis suas quando suscipit. - - Ne graece efficiendi interpretaris cum. In vide dictas cotidieque sed. Ea est augue vidisse. Eu epicurei salutandi est, etiam oratio imperdiet ex his. Te pri novum meliore sensibus. An nec eius dicant, iracundia consectetuer per ex. An iuvaret meliore constituto eam, latine detraxit mea eu, mel eu aperiri moderatius. - - Per commodo virtute eu. Eius euripidis nam eu, cum ne praesent vituperata, vix at integre verterem posidonium. Eu adhuc labores eam, temporibus reformidans eos id. Feugiat labores nam ne, eu sed nostro veritus, mea in consul evertitur repudiandae. Cu eos detracto voluptaria consetetur. - - Sit vidit mundi offendit ad. Usu semper vivendo eloquentiam eu. Nonumes deleniti ex vim, fabulas forensibus ad eam. Mazim admodum petentium sed et, has eu eirmod eruditi laoreet. An eam hinc erant. Et semper accumsan similique cum. Sea at inani error. - - Te commodo delicata abhorreant cum, iusto lucilius ut sed, ea his evertitur scripserit. Te eligendi scriptorem sit. Mel quodsi meliore dissentias ei, cum deserunt moderatius ex, error sanctus adversarium eam et. Eam et appareat placerat tincidunt, ius an quidam putant delenit, utinam partem quo ea. Mei legendos constituto scribentur eu, usu ea consul facilisis. Purto veritus lobortis te sea. - - Ne sea ludus solet decore, in cum erat dicta labitur. Doctus maluisset scripserit qui in. Qui at postea audiam, has ut aperiam dissentiet, vix ut harum nemore integre. Sit ad lucilius deseruisse, iuvaret percipit in pro. Sed referrentur voluptatibus ne, his ei nullam omnesque aliquando. - - Ius te purto augue fierent, mea et decore feugiat definiebas. Dolor abhorreant deseruisse at sit, corpora tacimates duo ut. Dicta equidem ne has. At pri elit magna vocibus, ipsum vidisse definiebas id cum. Ex pri laudem assentior. - - Aliquip referrentur id vim, sea labores nominati recteque id. Torquatos adversarium no mel, pri eu impedit fastidii, in usu tollit electram. Ad esse facer nec, mel id laudem adipisci, pri eu vocent efficiantur. Cu est posse possit. Vis definiebas neglegentur ad, pro facer sanctus propriae id. Sit epicurei comprehensam signiferumque ea, ad eam vero admodum scaevola. - - Nec an vivendo ocurreret, qui id nihil fastidii ocurreret. Vis ei mazim zril, dictas aperiri aliquando per ad, sit ne harum postea appellantur. No munere periculis reprimique duo, ut nam unum animal repudiandae. Cibo vocent dissentias te mea. Sea te elit denique volutpat, nec ne epicuri mentitum delicata. - - Ius eros aeterno torquatos ex, et per nisl accusata. Ne nam nonumy discere, in vim nemore commune. Cu pri nibh pertinacia assueverit, duo percipit definitionem ne, delectus voluptaria et vix. Mutat appellantur mea ad, ad pericula suavitate nam. Primis rationibus mei eu. Ad dolorem verterem deserunt eam, in decore probatus consulatu sed. Quo ut ullum epicuri. - - Quod contentiones ea quo. Et nihil conclusionemque mel, offendit perfecto eam eu. Suas case nam et. Ex elit doctus civibus mei, eam facilis scaevola ne. His elit scripta constituto eu, mea tantas petentium ut. - - Eos dicam intellegam id. Cum omnes concludaturque ea. Qui in semper legendos intellegebat. Option mediocrem eam id. Doctus principes deterruisset pro ad, qui ea fugit populo repudiare. - - Sed ne summo percipitur. Eu porro persius vix, an vix esse fastidii delicatissimi, elit rationibus dissentias cu eam. Te dolorem scriptorem mei, no mei aeque tibique. Iisque molestie ei ius. - - Ius eu vide salutatus. Ut modus errem nam, latine vocibus referrentur an has, his laudem docendi at. Mei in enim aeterno, sea id dolores placerat signiferumque. Mea modo semper maluisset at, vel te magna eruditi. Eam in verear tacimates concludaturque. Nam cu porro mediocritatem, platonem splendide in has, dicit dolor populo mei et. Melius saperet tacimates nam an, mentitum fabellas assueverit duo id, sea an nihil tritani inciderint. - - At vim option nominati quaerendum. Dolor vituperata dissentiunt eu nec, an modo appareat suscipiantur sea. In vim suas eligendi sadipscing. Ne nihil molestie ius, sumo aperiri argumentum sit ea, eius principes te sit. Eam no fugit mazim alterum. Sed ignota sententiae in. At pro illud reprimique. - - At nec vivendo luptatum, vel congue oratio cu. Audire sententiae ius an. Antiopam scripserit et duo, illud commune te his. Posse disputationi vix in, quod oporteat id eam. Legimus admodum docendi cum at, cu postea deserunt periculis per. - - Sonet clita ponderum sea ei, est ex semper fabellas. Te vix elitr congue phaedrum, ea eum argumentum eloquentiam. Te elitr suavitate definitionem eum. Est inciderint comprehensam cu, ei ius dolor dignissim assueverit. - - Eos ut dolorum albucius placerat, mel erat mentitum cu. Nec copiosae periculis in, ex vix everti consectetuer. Pro at dicat utroque, iracundia sententiae in usu, sit no cibo natum alienum. Duo audiam placerat ei. Cu mel philosophia disputationi. - - Eam fierent maiestatis instructior no, cu sea iusto iriure voluptatum. Vide numquam vivendo ex est, an vel nulla similique posidonium. No mea tritani molestiae, pro te placerat sensibus. Mel ad posse mucius vocent, te populo timeam democritum mel. - - Pri suscipit luptatum eu, et persius accumsan argumentum sea. In paulo tempor possit sea. Vidisse viderer ut vix, vix ex hinc solet. Altera principes qui in, est utroque scaevola eu. - - Lobortis gubergren mediocritatem ne has, te eum adhuc pericula vulputate, cu antiopam posidonium percipitur mei. Mandamus sapientem et sed. Ea dicta quodsi aperiri sea, ad putent posidonium sea. Duo et molestie erroribus, facete gloriatur eam te, ne eam accusam interesset instructior. Eam posidonium reformidans at. Duo menandri pericula te, qui accumsan facilisi expetendis ut. Et eum dicunt persius pertinacia, vis recteque eloquentiam te. - - Habemus blandit at pri, ad quando consequat per. Mea id adhuc dolores definitionem, in platonem mediocrem abhorreant per, vis cu hinc harum qualisque. Et purto lorem intellegam vel, summo apeirian cum no. At vel nonumy volutpat quaerendum. Ludus fastidii fabellas ut eum, no nec assum homero sanctus. - - His at doming forensibus honestatis, et nostrum praesent eum. At dolores patrioque sententiae eos, quaeque corpora qui no. Nec postea senserit persecuti ei, ad sea invenire voluptatibus, ne veniam lobortis repudiandae mea. Et nam integre democritum. Ex populo menandri qui. Ei duo aeterno accommodare, nibh ponderum iudicabit id vel, cu eos platonem postulant omittantur. - - Mollis molestie nam an, duo an option patrioque posidonium. Putant luptatum aliquando mea ei. Ut quas semper perfecto mei, ei ius justo possit epicurei. Cum odio omnium eu, gubergren definitionem eum ex. Tempor definitionem pri at, ad sit nihil postea moderatius. - - An cum verear scribentur, duo et purto tempor euripidis. Aeterno postulant ut eam, no diam inermis argumentum eos, posse blandit incorrupte ne pri. Est commodo laoreet conclusionemque ei. Graeci tacimates ei eum. Ad nec forensibus voluptaria, mea in alii putent, sed facer ceteros ad. - - Vidisse vivendo placerat duo ex, pri malorum definitionem ex. Decore virtute accumsan cum ad, ex eum postea putant euismod. Mei id alia populo democritum, mei ea adhuc posse. Et primis hendrerit signiferumque nam, ei vis modo lobortis recteque. Saepe persius aliquid et sit, vel everti feugait ne. - - Qui ex laoreet argumentum temporibus, agam tacimates intellegam ad qui. At wisi perfecto has, an sit viris definitionem. No quo sint tincidunt, vim adhuc expetenda ut, nam ad everti expetendis. Adhuc soleat doming nec eu, dicta summo sapientem mei te, nostro mediocrem dignissim ex eos. Id alienum mediocritatem mea, te error iriure placerat pri, feugiat platonem vituperatoribus mea te. - - Quo elit legere consequuntur in, in fabulas ancillae mea, sed scripta mentitum tacimates ei. Mazim phaedrum interpretaris et per, te patrioque assueverit vim. Populo commodo imperdiet vix ut. Mea modo bonorum ut, cu pri enim posse efficiantur. - - Sea veri ancillae adipisci te, quot tota modus ad sea. Suas malorum per ex, mazim rationibus ad vix. Ius ullum deserunt id. Quas corrumpit constituam no mei. Ut quo deleniti atomorum omittantur. Clita feugait docendi ei cum, no mea modo menandri, nam mundi doming an. No altera commodo pri. - - Vel fastidii convenire ex. Vim soleat maluisset te, et expetendis sadipscing liberavisse has. Per sale facilisis accommodare in. Cu option incorrupte nec. - - An eam vocibus intellegat, possit aliquid ex eum, tale mentitum oportere at duo. Ad eam audiam consectetuer, eu meliore verterem mediocritatem has, nam etiam reprimique ut. Nullam adipisci mei in, ad duo quem simul veniam. Vis at tempor sententiae. Te essent iisque aperiam vis. - - Te vix etiam quando ullamcorper, mel ei offendit iudicabit necessitatibus, usu assum facilisi sensibus ex. Alterum adversarium vis ad. Odio fierent deleniti ex cum. Quo an bonorum inciderint, harum simul maiorum in mel, ex legimus alienum corrumpit has. Pri soluta lobortis adipiscing te, eos clita ponderum mandamus at, elit meis assum mea in. Tale intellegebat cu vim, facilis expetenda democritum duo te, cu his causae dissentiet liberavisse. - - Primis latine epicurei no mea. Nam ad quis putant everti, no fugit minimum disputando vix. Laudem neglegentur te qui. Vel splendide efficiendi at, sed liber urbanitas no. Id his atqui inermis scriptorem, vituperata adversarium eos cu, pro in movet accommodare. - - Munere indoctum eu duo. Id eam duis voluptua expetenda, et prodesset inciderint ius. An nibh elitr deseruisse usu. Idque copiosae nam ea, ne tempor omittantur definitionem vis, et cum sumo principes. Quo rebum viderer minimum ex, est at melius blandit. - - Ut adolescens definitiones sed. Est ut erant legendos, in quo facilis salutatus. Agam expetenda salutatus ut sit, quo ex fuisset repudiandae, tale probo aliquip mea id. No vix diceret scaevola. Posidonium conclusionemque ut est. Quo porro menandri assentior ei. - - Vocent neglegentur intellegebat sit id. Et ullum accusam sea, ex nam aeque ubique ocurreret, ea cum quidam euismod. Eam ne zril alienum. Mea id veritus alienum. Mei simul appareat nominati no. Cum option accumsan ea, ne eos legimus dissentiunt. - - Everti prodesset scripserit ea cum, eam nostrud adolescens deterruisset an. Deseruisse definiebas eos ne, mel ex nisl meliore consulatu, per te scripta gubergren. Vix cu novum admodum recusabo, te omnes similique efficiantur nec. Suas novum semper duo ex. Vocibus cotidieque cu qui, at sale malorum intellegam mel. Quo errem accumsan ullamcorper cu, eu quo liber quidam conceptam. - - Scripta habemus quaestio id usu. Id vis utroque forensibus, cu simul fabulas efficiantur vis, ad mel cibo quas feugait. Eros expetendis in cum. No cum aeterno menandri consetetur, quo ex alterum probatus. Eu audire tritani ius, ei labore commune detraxit est. Unum sapientem cotidieque ei vel, ullum prompta per ex. Recteque persequeris quo ei, volutpat quaerendum ex sea. - - Qui phaedrum dissentiunt ne, sea debet fuisset ut. Ridens virtute pro an. Eos at stet modus iisque. Usu ea esse sententiae, deleniti salutandi ne sit, semper graecis sensibus vis ne. Ea corrumpit assueverit mel, in ignota quodsi nominati vix. - - Sea equidem vivendo ut, sed cibo nusquam id, nostro integre te mei. Etiam tractatos et duo, ut ludus dolore pri. Eius hendrerit cu vel, quo ei quodsi causae ullamcorper, per cibo nihil at. Ea voluptatum incorrupte duo, dolore debitis no usu. Ei clita concludaturque cum. Quo quas quando persecuti cu, eruditi scripserit cum eu. - - Ex eleifend philosophia has. Sint repudiandae in sea, et prima latine persecuti eum, pro ea everti expetendis. At his stet facete minimum, sed in delenit maiestatis. Cum ei omittam contentiones, suas sale melius ei cum, mel id vocent propriae necessitatibus. Vix amet nibh ea, autem movet ne vel. - - Accusata percipitur ut vim. Decore tritani scriptorem vis eu, vis volutpat reprehendunt ea. Postea inciderint vix an, no dicunt tamquam mel, usu id illud sensibus expetendis. Ex his aliquip blandit appellantur. - - Officiis evertitur ut mea, cu illud omnesque scripserit eam. Simul vituperatoribus an eum, mel quidam disputando cu, nibh probatus consequat ei mei. Eam populo appareat inimicus ei. Ne officiis definitiones his, vis vero simul similique ei. Iudico oratio elaboraret vim et, id posse nemore eirmod has. - - Pro in veniam consul expetenda, an est movet consequuntur, ex ius admodum recusabo ullamcorper. Mei an utroque ceteros singulis, id eum iudico latine. At est reque lorem intellegat. Quando corpora qui ea. Quo vocent salutatus id. - - Wisi ignota concludaturque est ex. Mea ad inermis vituperatoribus. Dolor persius inimicus ad nec, etiam dignissim qui ex. Ridens quodsi sed ex. Pro in etiam antiopam, eos graece eripuit ad. Affert soluta mei te, pri illud graecis id, vel sint vivendo at. Mea eu veri dicta offendit, cu has sint copiosae. - - Duo libris salutatus ad, porro principes mel ex. Nec iudico consectetuer no. Ut pri causae qualisque democritum. Habeo homero iuvaret at mei. Atqui aliquam eu mea. Gubergren delicatissimi vim ad, amet quodsi efficiendi te ius. Everti latine vulputate pro te, in nam falli definiebas, duo harum graeco nusquam no. - - Per ne aeterno appareat, melius verear tamquam eam id, mei ei invidunt atomorum. Eu doctus viderer eam, sea eu possit dolorem appetere, efficiantur necessitatibus mel cu. Ad est suas officiis, assum erant eum cu, eum erat vitae te. Habemus scaevola no per, nominati adipiscing et eam. - - Ei stet quidam scaevola quo, ius cu noster officiis. Has dolorum vulputate voluptaria ea, ei ius audiam liberavisse. Duo in accumsan constituam, sea esse deseruisse ad. Ne eam eligendi sensibus. Ea rebum porro interesset sed, te alii tritani singulis vis, vel enim liber ne. Movet numquam salutatus eu vim. Nam ad deleniti interpretaris conclusionemque. - - Ea offendit apeirian reprimique ius, accusam incorrupte voluptatibus ei duo. Hinc ponderum detraxit vel te, has no labore regione. Sint impetus duo ex, cu has liber soluta fierent. Vix ut cibo mollis deseruisse. - - Cum id tale disputationi, usu adhuc tritani ea. Id vim volumus quaerendum delicatissimi, no vix dolorum legimus corpora, justo dicit id duo. Ancillae concludaturque at usu. Vim diceret singulis incorrupte eu, oratio nullam quo et. Et sea mediocrem vituperatoribus, fugit tacimates deterruisset cum et. Mel ne sale soleat, vim ad labitur equidem, eos ea justo noluisse. - - An nam consectetuer necessitatibus, eos ei mazim persecuti. Libris explicari dissentiunt te vis, te per veniam sadipscing. Ut diceret euismod vix, duo discere inermis ea. No sit veri sensibus cotidieque, inermis sadipscing reprehendunt qui ad, elitr referrentur repudiandae eu est. Vis quem probo postulant no. Ad viris tollit ullamcorper pri, congue discere ad usu. - - Eam te cetero reprehendunt. His ad ferri feugiat invenire, oratio indoctum id pro. Errem omittam sed et, est no quando omnesque platonem, sed quis philosophia ei. Vix ut porro aeque habemus, eu eam consul nominati omittantur. - - At pri everti indoctum, ullum adipiscing instructior qui ad. Usu ignota omittam ex. At audire vocibus pericula vix, usu ex reque feugait. Ne sea electram salutandi moderatius, te qui verterem scripserit adversarium, an sed tantas lobortis intellegat. - - Ad quas suscipit atomorum duo, quo saepe maiestatis eu. Nostro expetenda ea usu, in atqui doming eam. Vis utroque consulatu ne. Zril noster scripta in eum, vim ad dicam facete legendos, et sit civibus consequat. Eum autem periculis ex. Sed ne nemore eligendi, his legimus verterem ad. - - Est ad amet possit latine. Sed ne legere populo, has pericula scribentur voluptatibus eu. Usu in nonumy vituperata. Aeque oratio gubergren mea ad, quo eu debet dolorum contentiones. Veri expetenda ex mel, eu veniam apeirian vis, aeterno debitis id his. - - Ei ius consul nonumes. Id qui porro periculis, quando dolore iisque qui id. Quo ex simul convenire, vix ei erat petentium, mea cu clita causae. Tale facilisis ex pri, vim ne vide laudem mnesarchum. Duo option blandit ex. Eu est modus vitae, nam in latine maiorum. - - Est an quis quaeque disputando, sit an postulant expetenda, dolor erroribus consequuntur ad vel. Ius phaedrum cotidieque ei, omnis persius copiosae eu vim. Pri facer consequat eu, esse copiosae facilisis mei ne. Ubique convenire no sit. - - Nec regione prompta no. Et quo recteque concludaturque, et ius nostro mollis regione. Ad aliquid lucilius scriptorem eam. Sit at summo eligendi omittantur, ius ea paulo option referrentur. Rationibus inciderint mediocritatem sea id, in dicant assentior sea, quidam copiosae reprehendunt in usu. Cu appetere scripserit vix. Mea mollis audiam aliquam ea, te qui adhuc nonumes deserunt. - - Duo ad facilis consequuntur, vis vide mutat in. At inani ludus eam, an sit quod primis, ea est integre consetetur. Has dolorem salutandi te, tota doctus sit ne, cum ex minimum convenire. Ne legere deterruisset vel, partem phaedrum ne pro, vim modo facete fabellas ex. Cu vix possim eleifend posidonium. Ne est sumo impetus. No quo ubique neglegentur, in usu aliquando scripserit reformidans. - - Vix et numquam expetendis. Ei quas senserit vel, ne has placerat conclusionemque. Noster perpetua euripidis ex sit. Ex mel vidit nonumy vituperata, duo ad nostrum liberavisse. Primis signiferumque duo te, ius te hinc aeque laoreet, ne eius voluptua pri. Dico eros copiosae sed ei, duo cu laudem propriae gubergren. - - Unum erant oratio duo cu, qui no audiam fabulas ornatus. Nihil omnium offendit ad cum, ea ius inermis appetere nominati. Ei nam vero oratio corrumpit. Atqui voluptatibus mei id, at duo assum nostrud aliquando. At nec laudem ridens phaedrum. Dicunt qualisque eum an, ex natum persecuti adipiscing vix, tritani consulatu persecuti nam id. - - In pro mundi percipit, eum tibique eloquentiam in, mea illud ullum altera ex. Veniam epicuri ex mea, quot eruditi definiebas eu duo. Vel augue regione consectetuer ei, appetere moderatius eos in. Laoreet lucilius vim eu. At oratio eirmod qui. - - Altera labitur qui ei, in eam libris primis. Eirmod audiam te vel, eu mei case vide ponderum, an principes persecuti neglegentur mei. Ferri vulputate instructior no vix, in est vidisse detraxit molestiae. Te sit quot choro adipisci, no labore indoctum deterruisset cum. Elit ancillae appetere usu at, mundi dissentias te quo. Mentitum erroribus ad pri. - - Usu ei vero possit appetere. Id erroribus constituam quo. Sit id quidam pertinacia, epicuri delicata eu has, debet melius evertitur ut sed. Id est alienum voluptua. Sit eu dico discere accusata, cu mazim viderer numquam usu, has an solum pertinax. Vel natum summo te, mel integre perfecto consetetur et. - - Postea luptatum menandri cu has, no nam neglegentur necessitatibus. Deseruisse reprehendunt ne mea, lorem tollit nonumes ne vim. Eu pro amet populo, omnesque ponderum sadipscing et ius, ad debet consequat dissentiet vix. Ius nulla aliquip complectitur et, id sed repudiare necessitatibus, et erant legimus invidunt vel. Magna labore democritum vis ei. - - Ne has consulatu reprehendunt, ad nam nulla integre admodum, no has everti impedit perpetua. Tempor antiopam dissentias pro et, ex per legere electram. Ut ius brute omnesque consequat, an his dissentias persequeris. Dicta ludus tritani eam ei. - - Minim rationibus et usu, eam in elit senserit. Stet harum qualisque eu has. Cu decore nostrud sit, mea magna iracundia te, vix tritani convenire imperdiet at. Te pri dictas appetere, brute velit ius ad. Veri dicit legere pri at, sea tation fierent molestie eu. Eum et paulo consul. - - Veri iusto mei id, reque invidunt ne his, te agam dolore electram nam. Est ullum oporteat facilisi eu, dicunt officiis ad eos. Vis ut populo similique. Et minim platonem percipitur usu. Et usu saperet alienum consequuntur, per ad luptatum concludaturque, cibo duis definitionem vix an. - - Ancillae iracundia eu vix, mazim conceptam no qui. Sit nihil epicuri voluptatibus in, an sale debet vis. Porro congue senserit quo an. Facer constituam vel no, facete pertinacia adolescens pro ut, errem nullam menandri ius an. Ex tractatos periculis interpretaris eum, vim ornatus patrioque an. Ius magna iudicabit reprehendunt at, ea viris ornatus sit, eum in vidisse percipitur. - - Vel agam interpretaris ex, zril deserunt electram in duo. Eu eam vero atqui maiorum. Ne pri alia meis decore, vis ea expetenda dissentiet. Cu quis evertitur intellegat cum, ea sed posse expetendis liberavisse, sed ne quis deseruisse. Solum ignota causae ad quo, ferri erant est id, vim ut choro liberavisse. Ne nam causae reformidans, possit percipitur id has. Case tota id has, pri tation ancillae sensibus te. - - Posse aperiam sit id, est in dicam iracundia. Eum mollis dolores te, at vis laoreet habemus fuisset. Vim nibh civibus signiferumque eu. Fierent reformidans an quo. Exerci dissentiunt ex pri, per illum debitis et. - - Vel tota exerci facilis eu. Est primis atomorum id. Hinc insolens dissentiet duo et, iusto inermis salutatus vel ne. Corpora propriae vituperata ex est, sit quod ignota deterruisset no, elitr tempor suscipit ex nam. Eripuit repudiandae his cu, fabulas inermis accumsan ei eum. Nisl molestie ex pri, sea veniam graecis expetenda eu. - - Vel possim moderatius cu, sea no dicit aliquip erroribus, eam te debet partem. Sea no graeco vocent probatus, qui ut fugit delectus definiebas. Id soluta viderer per, tritani disputando necessitatibus ad vix. Eleifend facilisis percipitur at pri. Sit dicat adipisci ex, mundi solet doming nec te, ne maiorum tractatos evertitur sit. At velit propriae eos, mel alii invenire id. - - Ei per minim voluptaria, ea mel scaevola mediocrem euripidis. Et quo wisi sonet eirmod, consul tritani delenit vis id, per id mutat facer. Accusam patrioque eu cum, te cum quot saepe scribentur. Eros summo mnesarchum sed ut, ad possit nostrum comprehensam ius. - - In elitr nullam eam, cu mea mentitum omnesque. Ex has wisi consequat. Sed simul offendit argumentum no, solet corpora lucilius cu vis. Nec ut legimus suscipit, ne usu mucius latine, nusquam mentitum perpetua ad vel. Vidisse feugait facilisis an sea. - - Mel ne oblique menandri, nam graecis antiopam id. Id sanctus referrentur contentiones eam, et eam purto consequat, te vix quod tantas bonorum. Fabellas sapientem consulatu eu per. Iudico possim per ei, eu vide adversarium per. Usu vero essent albucius et, nec veritus ancillae prodesset eu, ipsum probatus cu sea. In quod tantas iudicabit eam, his et odio augue iuvaret. - - Eum an iisque oportere dignissim, est convenire molestiae interesset at, eam id dico audiam quaerendum. Est prompta ocurreret adversarium id, sumo legendos iracundia pri ea, quo ei agam aeque. Ei vix omnes conclusionemque, nec ut novum urbanitas honestatis. Sumo moderatius eos no, ea saperet impedit petentium vel. Vim in harum blandit, posse gubergren deterruisset ex ius. Ei error menandri platonem duo, partiendo qualisque nam te. - - Erat nobis maluisset at sed. Ius no dolor soluta. Has ne sumo brute suavitate, his ne viris aeterno omittantur. Te usu delenit philosophia. Mei wisi libris deseruisse eu. - - Tantas constituam per ei, doctus timeam ea vim. Quo ei putent delicata, eu quo aeterno labores. Qui liber eripuit singulis te, qui diam appareat similique ex, aliquip feugait noluisse cu vis. Dicant recteque definitiones ex mea. Cu exerci dictas eleifend duo, id nonumes denique vix, pri magna facer saperet ei. - - Aeque quodsi partiendo in est, partem malorum intellegebat et per, mea no porro ipsum oratio. Mel sale meliore fuisset ad, per aeque aperiam nominavi ex. Ad tantas meliore placerat mel, mel in prompta torquatos, nulla mollis tamquam pri cu. An quo noster intellegat. Utroque antiopam similique ius eu, at duo ullum apeirian reprimique, vide quaeque assueverit eam ea. - - Mel no quod viris latine, no platonem dissentiunt ius, quis amet gloriatur no eos. Ei mea ancillae probatus. Ad molestiae moderatius vim. Ex soluta meliore molestiae has, ne sea quas natum. Movet verterem vis no, vis amet homero an. Ius dicta tantas id. - - Pri ut modus disputationi, iudico ignota commune eam ut. Vim integre eripuit appareat in, malorum inermis perpetua his cu. Altera alienum mediocrem eu vis. Omnis honestatis repudiandae in qui, his quis duis te, novum rationibus cu est. Eos ut dicant molestie, pri argumentum quaerendum adversarium te. Ea cetero deserunt conceptam pri. - - Augue utroque iudicabit ei vim, ea per dico debet. Cu per regione feugait, sensibus necessitatibus cu cum, at mundi aliquando per. In cum latine evertitur definitionem. Sit movet aperiri liberavisse ad, pro paulo veniam eu. Vel ut cotidieque definitionem, adhuc movet intellegebat quo ne. - - Eum deleniti gubergren an, dolor utamur omittam ea ius. Eam movet possim accommodare et. Ius cu malorum tibique, ludus eligendi id pro. Solum vulputate efficiendi ex quo. - - Regione fierent eu per. No amet iuvaret efficiantur usu. Sit minim sensibus no, his principes gloriatur adversarium no. Ubique definitionem ne vis, vis propriae intellegebat te. Tibique suscipiantur his no. - - Eu quo elit cetero scripserit, maiestatis scripserit pri in, vim cu tibique eligendi. Ne tritani gubergren vituperatoribus quo, melius facilisi ne has. Id pro vivendo fuisset, ex causae utroque deleniti mea. Ad eam percipit perfecto, mutat justo essent at vim. Eros novum duo et, in pri melius blandit. Affert albucius eos ad. Singulis mnesarchum ea mel, eos dictas nominavi reprimique no. - - Ei qui congue voluptatibus. Nam no tritani elaboraret. Conceptam rationibus expetendis duo no. Vivendo reprimique cum te, qui timeam copiosae id, modo delicata ius ad. Nam cu dico aliquip, eu pro legimus officiis delicata, fugit quando ea per. - - In eos equidem accommodare. Electram principes ad usu. Nec adversarium disputationi in, sed feugait lucilius ut. Ludus hendrerit cu ius. - - Cum suscipit gloriatur ea. Id aeterno principes euripidis nam, mea id probo graeco verterem, vulputate ullamcorper definitionem in pri. Mel cu detraxit assueverit. Quo et posse fastidii, ei ponderum delicata sed, has brute forensibus ut. Quo option pericula ea, nam gubergren assueverit te. - - Graece doming intellegebat duo in, ullum clita expetenda nam at. Diam alienum menandri sit id. Unum clita consulatu duo id. Per in mucius legendos scribentur, sea quod phaedrum ut, facete animal dissentias no usu. Quo te dico suavitate. Est te erant congue vivendum, oporteat forensibus in his. - - Pro no eleifend reprehendunt, ut meis consetetur argumentum mei. Id vis harum ornatus cotidieque. Inani libris volumus ea qui. Ius at suas percipit voluptatum, pro solet invidunt honestatis ei, et nam delectus reprimique instructior. Nam id tacimates argumentum dissentiet, mei an sint adipiscing. - - Quando cotidieque sit at, ei sed tantas ancillae verterem, cum nibh omittam ut. Erant laboramus moderatius te eum. Civibus adipiscing sed ne, vix eu erant euripidis. Illud qualisque at nec, id tale sint facete per. Vero autem democritum eam an. - - Mel mazim prodesset ad. An vis alii suas congue, vim veri illum iisque et, in modus perfecto deseruisse vel. Sed summo fuisset fierent an. Vel eu bonorum ornatus, alii decore nec ad. Eu dicta constituto mea. - - Id sint stet graece usu. Cu sit essent reformidans, eos eius ridens et. Usu voluptaria posidonium cu. \ No newline at end of file diff --git a/node_modules/lv_font_conv/package.json b/node_modules/lv_font_conv/package.json deleted file mode 100644 index e0f1464f..00000000 --- a/node_modules/lv_font_conv/package.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "name": "lv_font_conv", - "version": "1.5.2", - "description": "Rasterize vector fonts for embedded use. Supports subsettings & merge.", - "keywords": [ - "font", - "convertor", - "embedded" - ], - "repository": "lvgl/lv_font_conv", - "license": "MIT", - "files": [ - "lv_font_conv.js", - "lib/" - ], - "bin": { - "lv_font_conv": "lv_font_conv.js" - }, - "scripts": { - "start": "parcel ./web/index.html --open", - "build": "parcel build ./web/index.html ./web/content.html --public-url ./", - "build:dockerimage": "docker build -t lv_font_conv_freetype ./support", - "build:freetype": "docker run --rm -v $(pwd):/src/lv_font_conv -it lv_font_conv_freetype ./lv_font_conv/support/build.sh", - "lint": "eslint .", - "test": "npm run lint && nyc mocha --recursive", - "coverage": "npm run test && nyc report --reporter html", - "shrink-deps": "shx rm -rf node_modules/opentype.js/src node_modules/opentype.js/dist/opentype.{m,js.m}* node_modules/pngjs/browser.js", - "prepublishOnly": "npm run shrink-deps" - }, - "dependencies": { - "argparse": "^2.0.0", - "bit-buffer": "^0.2.5", - "debug": "^4.1.1", - "make-error": "^1.3.5", - "mkdirp": "^1.0.4", - "opentype.js": "^1.1.0", - "pngjs": "^6.0.0" - }, - "bundledDependencies": [ - "argparse", - "bit-buffer", - "debug", - "make-error", - "mkdirp", - "opentype.js", - "pngjs" - ], - "devDependencies": { - "eslint": "^7.21.0", - "file-saver": "^2.0.2", - "mocha": "^8.3.0", - "nyc": "^15.1.0", - "parcel-bundler": "^1.12.4", - "posthtml-include": "^1.6.2", - "roboto-fontface": "^0.10.0", - "shx": "^0.3.2" - }, - "browserslist": [ - "last 1 Chrome version" - ] -} diff --git a/src/displayapp/screens/WatchFaceMeow.cpp b/src/displayapp/screens/WatchFaceMeow.cpp index a4abadaa..656cebf2 100644 --- a/src/displayapp/screens/WatchFaceMeow.cpp +++ b/src/displayapp/screens/WatchFaceMeow.cpp @@ -170,16 +170,19 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, } // Side Cover - static constexpr lv_point_t linePoints[nLines][2] = {{{30, 25}, {68, -8}}, - {{26, 167}, {43, 216}}, - {{27, 40}, {27, 196}}, - {{12, 182}, {65, 249}}, - {{17, 99}, {17, 144}}, - {{14, 81}, {40, 127}}, - {{14, 163}, {40, 118}}, - {{-20, 124}, {25, -11}}, - {{-29, 89}, {27, 254}}}; + //paires de points pour chaque ligne + // les lignes sont pas les contours des triangles, elles sont des grosses bandes superposées + static constexpr lv_point_t linePoints[nLines][2] = {{{30, 25}, {68, -8}}, //1 small triangle on top + {{26, 167}, {43, 216}}, //2 purple, smalltriangle in the bottom half part on the clock display side + {{27, 40}, {27, 196}},// 3 small triangles up above and below battery + {{12, 182}, {65, 249}}, //4 most bottom right triangle + {{17, 97}, {17, 147}}, // 5 left part of battery zone, overlapped after by the large triangles + {{16, 81}, {42, 127}}, //6 upper part of battery zone + {{16, 163}, {42, 118}}, //7 lower part of battery zone + {{-20, 124}, {25, -11}}, //8 large upper triangle + {{-29, 89}, {27, 254}}}; //9 large lower triangle + //largeur des bandes static constexpr lv_style_int_t lineWidths[nLines] = {18, 15, 14, 22, 20, 18, 18, 52, 48}; const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); @@ -193,15 +196,16 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, //Battery indicator logoPine = lv_img_create(lv_scr_act(), nullptr); - lv_img_set_src(logoPine, "F:/images/pine_small.bin"); - lv_obj_set_pos(logoPine, 15, 106); + lv_img_set_src(logoPine, "F:/images/cat_small.bin"); + + lv_obj_set_pos(logoPine, 12, 108); lineBattery = lv_line_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_line_width(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 24); + lv_obj_set_style_local_line_width(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 30); lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); lv_obj_set_style_local_line_opa(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 190); - lineBatteryPoints[0] = {27, 105}; - lineBatteryPoints[1] = {27, 106}; + lineBatteryPoints[0] = {27, 107};//27 = image x offset + image width / 2 + lineBatteryPoints[1] = {27, 108};// the line covering the image is initialized as 1 px high lv_line_set_points(lineBattery, lineBatteryPoints, 2); lv_obj_move_foreground(lineBattery); @@ -533,8 +537,9 @@ void WatchFaceMeow::Refresh() { } void WatchFaceMeow::SetBatteryLevel(uint8_t batteryPercent) { - // starting point (y) + Pine64 logo height * (100 - batteryPercent) / 100 - lineBatteryPoints[1] = {27, static_cast(105 + 32 * (100 - batteryPercent) / 100)}; + // starting point (y) + Pine64 logo height * (100 - batteryPercent) / 100^ + //the ligne grows, it covers the icon starting from the top + lineBatteryPoints[1] = {27, static_cast(107 + 31 * (100 - batteryPercent) / 100)}; lv_line_set_points(lineBattery, lineBatteryPoints, 2); } @@ -565,7 +570,7 @@ bool WatchFaceMeow::IsAvailable(Pinetime::Controllers::FS& filesystem) { } filesystem.FileClose(&file); - if (filesystem.FileOpen(&file, "/images/pine_small.bin", LFS_O_RDONLY) < 0) { + if (filesystem.FileOpen(&file, "/images/cat_small.bin", LFS_O_RDONLY) < 0) { return false; } diff --git a/src/resources/images/cat_clean.png b/src/resources/images/cat_clean.png index 6f8da93a9c060c3405fff35442a8c96114417fdb..e4d98e961fc817f7a566f6a14c3f172962351cbd 100644 GIT binary patch delta 1735 zcmV;&1~~b^3i1shiBL{Q4GJ0x0000DNk~Le0000U0000V2nGNE0Pt*m2azEle^X0E zDh_rK70FPYEQpFYY88r5A=C=3I+$GgAv9@7Qd}Gb*Mfr|i&X~~XI&j!1wrrw#M!|~ z(M3x9Us7lhNc_lk#p5^5 zMVAGh88tKMIpQd>SZrgZjakXmh$o3-s-{!Eko8#Qyv127S6TC({DqOczP!YBnuADU z0gI3zLO~T9D8ojaR-F_JY1&Wt`1@VIL@tF~8(`#^M+F*W*AM;&zq_>xe-q3c&jreNztTy#=~fz22JpIDG&z)K%&RI5-4Gij=+P^X|^}-u^w)?C%FY_HvR1 zq;zKh000JJOGiWi4*(AUlfD5Ze;x-M7{0Mj9RL6X?@2^KR7l6Q)_aUyRTT#C-`u$! zuOf47E7M}Awup@{su=Ns22em-Boz#L6-6*aNmNvP#Rq~!d=NEJ!YfJusUj&`dICV_;Wu~#euy{s_gbNODE$94)PEXZN%W5w^cA=T zFG9g?R88Q%^F>&PJvqoHe{gA4RZo2!phw|mJTI?myt8T3Zu}m?t8fQinddWbM^#l5 zn2Cd8`}|(7_uVB+mK=kZ;1T>Kdk-gHX61Po9>BAw1BTb*n*qKXKf;$WXTWaV=;-Jw zjNn5J*7`tK;#j;Gt8rXo|Ilu?`;<2RjXx(s*n+QOUg#bk0PW$%f2padlVh_sAkAPt zh!u(SSNLLMe*~>Ievg$HYXFbo?bBT0ouPMqBDn%b4@52Be=7$&ukpTMXB&${&zafy z7ycB3YA?~Q#OLshfE|rBxu!we4au9&H{M-HeG!XUL92|a`glpmHImj}+JKrDK!oe{<9REm^Q_<87m`t?S zC82ih1^D*l#Om0t$zl48Y>wYU^KRa7r_=dJ1RP!%fNgkfe+1*_IlyuG@Csf(PT^-cWd9XgWWRewJ^}ZK%}o zxG18t8k=G_Gkz9^`h(i|dS0|KKOpM^I+WylU*m3v68$=y-T;OB>$+Z*eCX@;30M%| zRq22eE&Li?e|$3ZEKO^iS4!DaN_hxhi_q+AZNbL}n#GB!gbv<>PeuR#(83J-BozMu zCnwrv8H@<>#yFQMbu-HuK_w;yE=B8qfgL+9O%^mdDe>qiEon2Mc8eEQt;;^|U@zFGb zNAY*OH~G?E+Zzqy!sh66Q+}U~%aY~rw2Vc^U?V;g+rKuPUKb76nCE|Dbl_lD;w4>dflDcqDT7O}1mDg_!^!fkX&8%A0XKx14H1+%cw^M-;ZWW0%~wUm-jy=cj94k< zu~N$Ue|TGz>-)LOUXpKdYH$jtkBp3rM?B7m&1=#u?#lrskjJpNuIur-u6xr>AWA8B zS58?chrCi&lCg8Nd0pEr7RaLzuPR|VO zz0kM0lyYBP*Sg*A;f+RcYSe2XzEIcoj_GP|f1l%PDP?;pWg-dIZ*8-&VsvzLcDLI- zHd96?z=uSo&e|8~v=Q%R^~+;U{%@H(ckU^f9D8XJyHlsP)^)w}e>oi9AJoao$*E4K z^W)gxg+;Sw&HC`z*w_)NfIIO=e5|hPsr~V}zY!~?JXlrL#VMP6dcEEP+>`S8ZC%$Z d8UCN4|33nqieR2(vn2ok002ovPDHLkV1lCfRb~JH delta 1297 zcmV+s1@8Lt4ZsQ^iBL{Q4GJ0x0000DNk~Le0000M0000N2nGNE01xQDo{=FSe_M-E zDjn<~q7b1vSr8R*)G8FALZ}s5buhW~3z{?}DK3tJYr(;f#j1mgv#t)Vf*|+-;_Tq0 z=prTlFDbN$@!+^0@9sVB-U0qbg{fxOIG}2lkxnLrY;INPenk*Nh+r5YiJAJGD5l^! zzV6}U>s_2@d7t}p^eTCi0X~6vf0pTnMZ7^gy=m#3_lcvdBq_w_#A60skob}7vdeFr ziw^sFX4J@}=ZT}lLa~G84rV1oC7vRVDXK>K{;bOi=Pk}^rN&zKh(v6QksO{!alsy=?t4Xb?PYO*o+Py!kdBrDAE9GUCwXA(jZ%o zRT#j2?CR<18IQ-YHf(GD9p!ruxtN%yc)O>*ojRw+=pe6%ke?py_SSyI4z>y z+qM5`aNLOdF%r{j;pqx|7m;3vZvs>WNwbS+8WzNR2ipSw7ru@gfArOmFcItH;MMpx zfb*kEx>pB3M3GnGRshPS8jDwOQ4GaF+!PL1g-SkI6ve({eOnBQ2A$G|RFLtyb$G{ti`J z9M{jHlU^%|;$LTt?uwLUxdU@kN*iNz7ll?V8XO#46C`(sk{!k#yigRy)}cCGKc0A3 zRkb_M^9-}1V@IdtdA_@^ukV4-gEK;K9w~}q;CLMM6MY$~s@j+5`MPNMy}iA?lbX$D zib!|isiG)$o(Nw%*=MIL%N2MAyRs~s7@aa7Gm4_ Date: Fri, 15 Mar 2024 09:20:19 +0100 Subject: [PATCH 039/101] Emit the message BleRadioEnableToggle to DisplayApp only if the enable state of the radio has actually changed. (#2037) This fixes an issue where the BLE connected logo would disappear when opening and closing the BLE setting (without changing it) while InfiniTime was already connected to a companion app. Co-authored-by: JustScott --- src/displayapp/screens/settings/SettingBluetooth.cpp | 6 +++--- src/displayapp/screens/settings/SettingBluetooth.h | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/displayapp/screens/settings/SettingBluetooth.cpp b/src/displayapp/screens/settings/SettingBluetooth.cpp index 82c3dee1..e4dc695c 100644 --- a/src/displayapp/screens/settings/SettingBluetooth.cpp +++ b/src/displayapp/screens/settings/SettingBluetooth.cpp @@ -36,17 +36,19 @@ namespace { SettingBluetooth::SettingBluetooth(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController) : app {app}, + settings {settingsController}, checkboxList( 0, 1, "Bluetooth", Symbols::bluetooth, settingsController.GetBleRadioEnabled() ? 0 : 1, - [&settings = settingsController](uint32_t index) { + [this](uint32_t index) { const bool priorMode = settings.GetBleRadioEnabled(); const bool newMode = options[index].radioEnabled; if (newMode != priorMode) { settings.SetBleRadioEnabled(newMode); + this->app->PushMessage(Pinetime::Applications::Display::Messages::BleRadioEnableToggle); } }, CreateOptionArray()) { @@ -54,6 +56,4 @@ SettingBluetooth::SettingBluetooth(Pinetime::Applications::DisplayApp* app, Pine SettingBluetooth::~SettingBluetooth() { lv_obj_clean(lv_scr_act()); - // Pushing the message in the OnValueChanged function causes a freeze? - app->PushMessage(Pinetime::Applications::Display::Messages::BleRadioEnableToggle); } diff --git a/src/displayapp/screens/settings/SettingBluetooth.h b/src/displayapp/screens/settings/SettingBluetooth.h index 1e3f9b81..0cf014f5 100644 --- a/src/displayapp/screens/settings/SettingBluetooth.h +++ b/src/displayapp/screens/settings/SettingBluetooth.h @@ -20,6 +20,7 @@ namespace Pinetime { private: DisplayApp* app; + Pinetime::Controllers::Settings& settings; CheckboxList checkboxList; }; } From db947fa97c4219928660e1ff8e26dd1081e8cbe1 Mon Sep 17 00:00:00 2001 From: Victor Kareh Date: Mon, 18 Mar 2024 12:35:22 -0400 Subject: [PATCH 040/101] WatchFaceDigital: Remove unused variables --- src/displayapp/screens/WatchFaceDigital.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/displayapp/screens/WatchFaceDigital.h b/src/displayapp/screens/WatchFaceDigital.h index 78232c1e..7bb713cb 100644 --- a/src/displayapp/screens/WatchFaceDigital.h +++ b/src/displayapp/screens/WatchFaceDigital.h @@ -43,10 +43,6 @@ namespace Pinetime { uint8_t displayedHour = -1; uint8_t displayedMinute = -1; - Utility::DirtyValue batteryPercentRemaining {}; - Utility::DirtyValue powerPresent {}; - Utility::DirtyValue bleState {}; - Utility::DirtyValue bleRadioEnabled {}; Utility::DirtyValue> currentDateTime {}; Utility::DirtyValue stepCount {}; Utility::DirtyValue heartbeat {}; From c7a74adb13b22dd016495483dfb6dc9feeaf5354 Mon Sep 17 00:00:00 2001 From: BloodStainedCrow Date: Sat, 23 Mar 2024 10:45:45 +0100 Subject: [PATCH 041/101] Unify docker devcontainer with dockerfile used for CI (#1587) * Only use one Dockerfile and build.sh script for both docker and devcontainer * Remove all now unneccessary tasks and scripts * Update to clang-format-14 * Move devcontainer.json into root folder * Fix conditional statements in Dockerfile * Move .devcontainer/README into doc/usingDevcontainers * Remove obsolete VSCode Task * Change standard compiler path to the correct compiler * Set GDB Path for debugging * Hide broken buttons from CMake Extension * Refactor .devcontainer * Remove unneccessary postBuildCommand * Add devcontainer dependencies to all docker images * Add Devcontainer Debug launch config * Add an additional c_cpp_properties config as a fallback for devcontainer * Remove obsolete Docker Argument * Fix wrong C/Cpp versions * Fix silent fail of gdb, add libncurses5 --- .devcontainer.json | 32 +++++++ .devcontainer/Dockerfile | 66 -------------- .devcontainer/build.sh | 87 ------------------- .devcontainer/build_app.sh | 2 - .devcontainer/create_build_openocd.sh | 3 - .devcontainer/devcontainer.json | 38 -------- .devcontainer/make_build_dir.sh | 2 - .vscode/c_cpp_properties.json | 22 ++++- .vscode/cmake-kits.json | 6 ++ .vscode/launch.json | 45 +++++++--- .vscode/settings.json | 15 +++- .vscode/tasks.json | 22 ----- doc/buildWithVScode.md | 2 +- .../README.md => doc/usingDevcontainers.md | 0 docker/Dockerfile | 10 +++ 15 files changed, 115 insertions(+), 237 deletions(-) create mode 100644 .devcontainer.json delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .devcontainer/build.sh delete mode 100644 .devcontainer/build_app.sh delete mode 100644 .devcontainer/create_build_openocd.sh delete mode 100644 .devcontainer/devcontainer.json delete mode 100644 .devcontainer/make_build_dir.sh create mode 100644 .vscode/cmake-kits.json rename .devcontainer/README.md => doc/usingDevcontainers.md (100%) diff --git a/.devcontainer.json b/.devcontainer.json new file mode 100644 index 00000000..95a27dac --- /dev/null +++ b/.devcontainer.json @@ -0,0 +1,32 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.154.2/containers/cpp +{ + "build": { + "dockerfile": "docker/Dockerfile" + }, + "customizations": { + "vscode": { + "settings": { + // Set *default* container specific settings.json values on container create. + "terminal.integrated.profiles.linux": { + "bash": { + "path": "/bin/bash" + } + }, + "terminal.integrated.defaultProfile.linux": "bash", + "editor.formatOnSave": true, + // FIXME: This and the Dockerfile might get out of sync + "clang-format.executable": "clang-format-14" + }, + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-vscode.cpptools", + "ms-vscode.cmake-tools", + "marus25.cortex-debug", + "notskm.clang-tidy", + "mjohns.clang-format" + ] + } + }, + "remoteUser": "infinitime" +} \ No newline at end of file diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index e4ad5c4f..00000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,66 +0,0 @@ -FROM ubuntu:latest - -ARG DEBIAN_FRONTEND=noninteractive -RUN apt-get update -qq \ - && apt-get install -y \ -# x86_64 / generic packages - bash \ - build-essential \ - cmake \ - git \ - make \ - python3 \ - python3-pip \ - python3-pil \ - tar \ - unzip \ - wget \ - curl \ - dos2unix \ - clang-format-12 \ - clang-tidy \ - locales \ - libncurses5 \ -# aarch64 packages - libffi-dev \ - libssl-dev \ - python3-dev \ - rustc \ - && rm -rf /var/cache/apt/* /var/lib/apt/lists/*; - -#SET LOCALE -RUN locale-gen en_US.UTF-8 -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US:en -ENV LC_ALL en_US.UTF-8 - -RUN pip3 install adafruit-nrfutil -# required for McuBoot -RUN pip3 install setuptools_rust - -WORKDIR /opt/ -# build.sh knows how to compile but it problimatic on Win10 -COPY build.sh . -RUN chmod +x build.sh -# create_build_openocd.sh uses cmake to crate to build directory -COPY create_build_openocd.sh . -RUN chmod +x create_build_openocd.sh -# Lets get each in a separate docker layer for better downloads -# GCC -# RUN bash -c "source /opt/build.sh; GetGcc;" -RUN wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2020q2/gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2 -O - | tar -xj -C /opt -# NrfSdk -# RUN bash -c "source /opt/build.sh; GetNrfSdk;" -RUN wget -q "https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v15.x.x/nRF5_SDK_15.3.0_59ac345.zip" -O /tmp/nRF5_SDK_15.3.0_59ac345 -RUN unzip -q /tmp/nRF5_SDK_15.3.0_59ac345 -d /opt -RUN rm /tmp/nRF5_SDK_15.3.0_59ac345 -# McuBoot -# RUN bash -c "source /opt/build.sh; GetMcuBoot;" -RUN git clone https://github.com/mcu-tools/mcuboot.git -RUN pip3 install -r ./mcuboot/scripts/requirements.txt - -RUN adduser infinitime - -ENV NRF5_SDK_PATH /opt/nRF5_SDK_15.3.0_59ac345 -ENV ARM_NONE_EABI_TOOLCHAIN_PATH /opt/gcc-arm-none-eabi-9-2020-q2-update -ENV SOURCES_DIR /workspaces/InfiniTime diff --git a/.devcontainer/build.sh b/.devcontainer/build.sh deleted file mode 100644 index b4f080dd..00000000 --- a/.devcontainer/build.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/bin/bash -(return 0 2>/dev/null) && SOURCED="true" || SOURCED="false" -export LC_ALL=C.UTF-8 -export LANG=C.UTF-8 -set -x -set -e - -# Default locations if the var isn't already set -export TOOLS_DIR="${TOOLS_DIR:=/opt}" -export SOURCES_DIR="${SOURCES_DIR:=/sources}" -export BUILD_DIR="${BUILD_DIR:=$SOURCES_DIR/build}" -export OUTPUT_DIR="${OUTPUT_DIR:=$BUILD_DIR/output}" - -export BUILD_TYPE=${BUILD_TYPE:=Release} -export GCC_ARM_VER=${GCC_ARM_VER:="gcc-arm-none-eabi-9-2020-q2-update"} -export NRF_SDK_VER=${NRF_SDK_VER:="nRF5_SDK_15.3.0_59ac345"} - -MACHINE="$(uname -m)" -[[ "$MACHINE" == "arm64" ]] && MACHINE="aarch64" - -main() { - local target="$1" - - mkdir -p "$TOOLS_DIR" - - [[ ! -d "$TOOLS_DIR/$GCC_ARM_VER" ]] && GetGcc - [[ ! -d "$TOOLS_DIR/$NRF_SDK_VER" ]] && GetNrfSdk - [[ ! -d "$TOOLS_DIR/mcuboot" ]] && GetMcuBoot - - mkdir -p "$BUILD_DIR" - - CmakeGenerate - CmakeBuild $target - BUILD_RESULT=$? - if [ "$DISABLE_POSTBUILD" != "true" -a "$BUILD_RESULT" == 0 ]; then - source "$BUILD_DIR/post_build.sh" - fi - # assuming post_build.sh will never fail on a successful build - return $BUILD_RESULT -} - -GetGcc() { - GCC_SRC="$GCC_ARM_VER-$MACHINE-linux.tar.bz" - wget -q https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2020q2/$GCC_SRC -O - | tar -xj -C $TOOLS_DIR/ -} - -GetMcuBoot() { - git clone https://github.com/mcu-tools/mcuboot.git "$TOOLS_DIR/mcuboot" - pip3 install -r "$TOOLS_DIR/mcuboot/scripts/requirements.txt" -} - -GetNrfSdk() { - wget -q "https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v15.x.x/$NRF_SDK_VER.zip" -O /tmp/$NRF_SDK_VER - unzip -q /tmp/$NRF_SDK_VER -d "$TOOLS_DIR/" - rm /tmp/$NRF_SDK_VER -} - -CmakeGenerate() { - # We can swap the CD and trailing SOURCES_DIR for -B and -S respectively - # once we go to newer CMake (Ubuntu 18.10 gives us CMake 3.10) - cd "$BUILD_DIR" - - cmake -G "Unix Makefiles" \ - -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ - -DARM_NONE_EABI_TOOLCHAIN_PATH="$TOOLS_DIR/$GCC_ARM_VER" \ - -DNRF5_SDK_PATH="$TOOLS_DIR/$NRF_SDK_VER" \ - "$SOURCES_DIR" - cmake -L -N . -} - -CmakeBuild() { - local target="$1" - [[ -n "$target" ]] && target="--target $target" - if cmake --build "$BUILD_DIR" --config $BUILD_TYPE $target -- -j$(nproc) - then return 0; else return 1; - fi -} - -if [[ $SOURCED == "false" ]]; then - # It is important to return exit code of main - # To be future-proof, this is handled explicitely - main "$@" - BUILD_RESULT=$? - exit $BUILD_RESULT -else - echo "Sourced!" -fi diff --git a/.devcontainer/build_app.sh b/.devcontainer/build_app.sh deleted file mode 100644 index 0f578cc6..00000000 --- a/.devcontainer/build_app.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -cmake --build /workspaces/Pinetime/build --config Release -- -j6 pinetime-app \ No newline at end of file diff --git a/.devcontainer/create_build_openocd.sh b/.devcontainer/create_build_openocd.sh deleted file mode 100644 index c5bff5c8..00000000 --- a/.devcontainer/create_build_openocd.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -rm -rf build/ -cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Release -DUSE_OPENOCD=1 -DARM_NONE_EABI_TOOLCHAIN_PATH=/opt/gcc-arm-none-eabi-9-2020-q2-update -DNRF5_SDK_PATH=/opt/nRF5_SDK_15.3.0_59ac345 -S . -Bbuild \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 1bb315f7..00000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,38 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: -// https://github.com/microsoft/vscode-dev-containers/tree/v0.154.2/containers/cpp -{ - // "name": "Pinetime", - // "image": "feabhas/pinetime-dev" - "build": { - "dockerfile": "Dockerfile", - // Update 'VARIANT' to pick an Debian / Ubuntu OS version: debian-10, debian-9, ubuntu-20.04, ubuntu-18.04 - // "args": { "VARIANT": "ubuntu-20.04" } - }, - "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined"], - - // Set *default* container specific settings.json values on container create. - "settings": { - "terminal.integrated.shell.linux": "/bin/bash", - "editor.formatOnSave": true, - "clang-format.executable": "clang-format-12" - }, - - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "ms-vscode.cpptools", - "ms-vscode.cmake-tools", - "marus25.cortex-debug", - "notskm.clang-tidy", - "mjohns.clang-format" - ], - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], - - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "bash /opt/create_build_openocd.sh", - - // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. - // "remoteUser": "vscode" - "remoteUser": "infinitime" -} diff --git a/.devcontainer/make_build_dir.sh b/.devcontainer/make_build_dir.sh deleted file mode 100644 index 76240037..00000000 --- a/.devcontainer/make_build_dir.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Release -DUSE_OPENOCD=1 -DARM_NONE_EABI_TOOLCHAIN_PATH=/opt/gcc-arm-none-eabi-9-2020-q2-update -DNRF5_SDK_PATH=/opt/nRF5_SDK_15.3.0_59ac345 ${SOURCES_DIR} diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 392f4151..c5f88a82 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -1,4 +1,9 @@ { + "env": { + // TODO: This is a duplication of the configuration set in /docker/build.sh! + "TOOLS_DIR": "/opt", + "GCC_ARM_PATH": "gcc-arm-none-eabi-10.3-2021.10" + }, "configurations": [ { "name": "nrfCC", @@ -14,7 +19,22 @@ "intelliSenseMode": "linux-gcc-arm", "configurationProvider": "ms-vscode.cpp-tools", "compileCommands": "${workspaceFolder}/build/compile_commands.json" + }, + { + "name": "nrfCC Devcontainer", + "includePath": [ + "${workspaceFolder}/**", + "${workspaceFolder}/src/**", + "${workspaceFolder}/src" + ], + "defines": [], + "compilerPath": "${TOOLS_DIR}/${GCC_ARM_PATH}/bin/arm-none-eabi-gcc", + "cStandard": "c99", + "cppStandard": "c++20", + "intelliSenseMode": "linux-gcc-arm", + "configurationProvider": "ms-vscode.cpp-tools", + "compileCommands": "${workspaceFolder}/build/compile_commands.json" } ], "version": 4 -} \ No newline at end of file +} diff --git a/.vscode/cmake-kits.json b/.vscode/cmake-kits.json new file mode 100644 index 00000000..95bb600b --- /dev/null +++ b/.vscode/cmake-kits.json @@ -0,0 +1,6 @@ +[ + { + "name": "InfiniTime Compiler", + "environmentSetupScript": "${workspaceFolder}/docker/build.sh" + } +] diff --git a/.vscode/launch.json b/.vscode/launch.json index a50270d2..7d3f17a1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,20 +1,18 @@ - { +{ "version": "0.1.0", "configurations": [ { "name": "Debug - Openocd docker Remote", - "type":"cortex-debug", - "cortex-debug.armToolchainPath":"${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin", + "type": "cortex-debug", "cwd": "${workspaceRoot}", "executable": "${command:cmake.launchTargetPath}", "request": "launch", "servertype": "external", - // This may need to be arm-none-eabi-gdb depending on your system - "gdbPath" : "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb", + "gdbPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb", // Connect to an already running OpenOCD instance "gdbTarget": "host.docker.internal:3333", "svdFile": "${workspaceRoot}/nrf52.svd", - "runToMain": true, + "runToEntryPoint": "main", // Work around for stopping at main on restart "postRestartCommands": [ "break main", @@ -23,18 +21,16 @@ }, { "name": "Debug - Openocd Local", - "type":"cortex-debug", - "cortex-debug.armToolchainPath":"${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin", + "type": "cortex-debug", "cwd": "${workspaceRoot}", "executable": "${command:cmake.launchTargetPath}", "request": "launch", "servertype": "openocd", - // This may need to be arm-none-eabi-gdb depending on your system - "gdbPath" : "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb", + "gdbPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb", // Connect to an already running OpenOCD instance "gdbTarget": "localhost:3333", "svdFile": "${workspaceRoot}/nrf52.svd", - "runToMain": true, + "runToEntryPoint": "main", // Work around for stopping at main on restart "postRestartCommands": [ "break main", @@ -51,6 +47,11 @@ "showDevDebugOutput": false, "servertype": "openocd", "runToMain": true, + // Work around for stopping at main on restart + "postRestartCommands": [ + "break main", + "continue" + ], // Only use armToolchainPath if your arm-none-eabi-gdb is not in your path (some GCC packages does not contain arm-none-eabi-gdb) "armToolchainPath": "${workspaceRoot}/../gcc-arm-none-eabi-10.3-2021.10/bin", "svdFile": "${workspaceRoot}/nrf52.svd", @@ -58,7 +59,25 @@ "interface/stlink.cfg", "target/nrf52.cfg" ], - } - + }, + { + "name": "Debug - Openocd Devcontainer", + "type": "cortex-debug", + "cwd": "${workspaceRoot}", + "executable": "${command:cmake.launchTargetPath}", + "request": "launch", + "servertype": "external", + // FIXME: This is hardcoded. I have no idea how to use the values set in build.sh here + "gdbPath": "/opt/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-gdb", + // Connect to an already running OpenOCD instance + "gdbTarget": "host.docker.internal:3333", + "svdFile": "${workspaceRoot}/nrf52.svd", + "runToEntryPoint": "main", + // Work around for stopping at main on restart + "postRestartCommands": [ + "break main", + "continue" + ] + }, ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index f1cc3a81..a7b04eea 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,20 @@ { "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", "cmake.configureArgs": [ - "-DARM_NONE_EABI_TOOLCHAIN_PATH=${env:ARM_NONE_EABI_TOOLCHAIN_PATH}", - "-DNRF5_SDK_PATH=${env:NRF5_SDK_PATH}", + "-DARM_NONE_EABI_TOOLCHAIN_PATH=${env:TOOLS_DIR}/${env:GCC_ARM_PATH}", + "-DNRF5_SDK_PATH=${env:TOOLS_DIR}/${env:NRF_SDK_VER}", ], + "cmake.statusbar.advanced": { + "launch": { + "visibility": "hidden" + }, + "launchTarget": { + "visibility": "hidden" + }, + "debug": { + "visibility": "hidden" + } + }, "cmake.generator": "Unix Makefiles", "clang-tidy.buildPath": "build/compile_commands.json", "files.associations": { diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 17f51f5e..06a08bfc 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,20 +1,6 @@ { "version": "2.0.0", "tasks": [ - { - "label": "create openocd build", - "type": "shell", - "command": "/opt/create_build_openocd.sh", - "group": { - "kind": "build", - "isDefault": true - }, - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, { "label": "update submodules", "type": "shell", @@ -31,14 +17,6 @@ "panel": "shared" }, "problemMatcher": [] - }, - { - "label": "BuildInit", - "dependsOn": [ - "update submodules", - "create openocd build" - ], - "problemMatcher": [] } ] } \ No newline at end of file diff --git a/doc/buildWithVScode.md b/doc/buildWithVScode.md index 9d0a5bdf..5f872482 100644 --- a/doc/buildWithVScode.md +++ b/doc/buildWithVScode.md @@ -32,7 +32,7 @@ The .devcontainer folder contains the configuration and scripts for using a Dock Using the [Remote-Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension is recommended. It will handle configuring the Docker virtual machine and setting everything up. -More documentation is available in the [readme in .devcontainer](../.devcontainer/README.md) +More documentation is available in the [readme in .devcontainer](usingDevcontainers.md) ### DevContainer on Ubuntu diff --git a/.devcontainer/README.md b/doc/usingDevcontainers.md similarity index 100% rename from .devcontainer/README.md rename to doc/usingDevcontainers.md diff --git a/docker/Dockerfile b/docker/Dockerfile index 22bf7bd7..bb5d5f65 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -37,6 +37,13 @@ RUN apt-get update -qq \ libpangocairo-1.0-0 \ && rm -rf /var/cache/apt/* /var/lib/apt/lists/*; +# Add the necessary apt-gets for the devcontainer +RUN apt-get update -qq \ + && apt-get install -y \ + clang-format-14 \ + clang-tidy \ + libncurses5 + # Git needed for PROJECT_GIT_COMMIT_HASH variable setting RUN pip3 install adafruit-nrfutil @@ -55,5 +62,8 @@ RUN bash -c "source /opt/build.sh; GetNrfSdk;" # McuBoot RUN bash -c "source /opt/build.sh; GetMcuBoot;" +# Add the infinitime user for connecting devcontainer +RUN adduser infinitime + ENV SOURCES_DIR /sources CMD ["/opt/build.sh"] From 80b4da819716ebf5f208f9653ce6f87eeb1d3d48 Mon Sep 17 00:00:00 2001 From: John Crawford <61567332+KaffeinatedKat@users.noreply.github.com> Date: Fri, 12 Apr 2024 06:50:33 -0600 Subject: [PATCH 042/101] fix: heartrate app displays --- instead of 000 (#1887) --- src/displayapp/screens/HeartRate.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/displayapp/screens/HeartRate.cpp b/src/displayapp/screens/HeartRate.cpp index f611fa26..9677be3b 100644 --- a/src/displayapp/screens/HeartRate.cpp +++ b/src/displayapp/screens/HeartRate.cpp @@ -41,7 +41,7 @@ HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, Syst lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); } - lv_label_set_text_static(label_hr, "000"); + lv_label_set_text_static(label_hr, "---"); lv_obj_align(label_hr, nullptr, LV_ALIGN_CENTER, 0, -40); label_bpm = lv_label_create(lv_scr_act(), nullptr); @@ -82,10 +82,14 @@ void HeartRate::Refresh() { case Controllers::HeartRateController::States::NoTouch: case Controllers::HeartRateController::States::NotEnoughData: // case Controllers::HeartRateController::States::Stopped: - lv_label_set_text_static(label_hr, "000"); + lv_label_set_text_static(label_hr, "---"); break; default: - lv_label_set_text_fmt(label_hr, "%03d", heartRateController.HeartRate()); + if (heartRateController.HeartRate() == 0) { + lv_label_set_text_static(label_hr, "---"); + } else { + lv_label_set_text_fmt(label_hr, "%03d", heartRateController.HeartRate()); + } } lv_label_set_text_static(label_status, ToString(state)); From 2c9444d67aa9c0bf230b1b27e70876db860b6a2d Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Sun, 11 Feb 2024 00:44:06 +0000 Subject: [PATCH 043/101] SPI transaction hooks --- src/drivers/Spi.cpp | 4 ++-- src/drivers/Spi.h | 2 +- src/drivers/SpiMaster.cpp | 16 ++++++++++++-- src/drivers/SpiMaster.h | 3 ++- src/drivers/SpiNorFlash.cpp | 2 +- src/drivers/St7789.cpp | 42 +++++++++++++++++++++++-------------- src/drivers/St7789.h | 8 +++---- src/main.cpp | 2 +- src/recoveryLoader.cpp | 2 +- 9 files changed, 52 insertions(+), 29 deletions(-) diff --git a/src/drivers/Spi.cpp b/src/drivers/Spi.cpp index c85b90c1..e0b716fa 100644 --- a/src/drivers/Spi.cpp +++ b/src/drivers/Spi.cpp @@ -9,8 +9,8 @@ Spi::Spi(SpiMaster& spiMaster, uint8_t pinCsn) : spiMaster {spiMaster}, pinCsn { nrf_gpio_pin_set(pinCsn); } -bool Spi::Write(const uint8_t* data, size_t size) { - return spiMaster.Write(pinCsn, data, size); +bool Spi::Write(const uint8_t* data, size_t size, void (*TransactionHook)(bool)) { + return spiMaster.Write(pinCsn, data, size, TransactionHook); } bool Spi::Read(uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize) { diff --git a/src/drivers/Spi.h b/src/drivers/Spi.h index 9b6a30f4..55eef05c 100644 --- a/src/drivers/Spi.h +++ b/src/drivers/Spi.h @@ -14,7 +14,7 @@ namespace Pinetime { Spi& operator=(Spi&&) = delete; bool Init(); - bool Write(const uint8_t* data, size_t size); + bool Write(const uint8_t* data, size_t size, void (*TransactionHook)(bool)); bool Read(uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize); bool WriteCmdAndBuffer(const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize); void Sleep(); diff --git a/src/drivers/SpiMaster.cpp b/src/drivers/SpiMaster.cpp index 3446d639..4c2bc940 100644 --- a/src/drivers/SpiMaster.cpp +++ b/src/drivers/SpiMaster.cpp @@ -143,6 +143,9 @@ void SpiMaster::OnEndEvent() { } nrf_gpio_pin_set(this->pinCsn); + if (this->TransactionHook != nullptr) { + this->TransactionHook(false); + } currentBufferAddr = 0; BaseType_t xHigherPriorityTaskWoken2 = pdFALSE; xSemaphoreGiveFromISR(mutex, &xHigherPriorityTaskWoken2); @@ -173,13 +176,14 @@ void SpiMaster::PrepareRx(const uint32_t bufferAddress, const size_t size) { spiBaseAddress->EVENTS_END = 0; } -bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size) { +bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, void (*TransactionHook)(bool)) { if (data == nullptr) return false; auto ok = xSemaphoreTake(mutex, portMAX_DELAY); ASSERT(ok == true); taskToNotify = xTaskGetCurrentTaskHandle(); + this->TransactionHook = TransactionHook; this->pinCsn = pinCsn; if (size == 1) { @@ -188,6 +192,9 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size) { DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0); } + if (this->TransactionHook != nullptr) { + this->TransactionHook(true); + } nrf_gpio_pin_clear(this->pinCsn); currentBufferAddr = (uint32_t) data; @@ -203,6 +210,9 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size) { while (spiBaseAddress->EVENTS_END == 0) ; nrf_gpio_pin_set(this->pinCsn); + if (this->TransactionHook != nullptr) { + this->TransactionHook(false); + } currentBufferAddr = 0; DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0); @@ -217,7 +227,7 @@ bool SpiMaster::Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t* data xSemaphoreTake(mutex, portMAX_DELAY); taskToNotify = nullptr; - + this->TransactionHook = nullptr; this->pinCsn = pinCsn; DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0); spiBaseAddress->INTENCLR = (1 << 6); @@ -267,6 +277,8 @@ bool SpiMaster::WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmd taskToNotify = nullptr; + this->TransactionHook = nullptr; + this->pinCsn = pinCsn; DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0); spiBaseAddress->INTENCLR = (1 << 6); diff --git a/src/drivers/SpiMaster.h b/src/drivers/SpiMaster.h index 8b698c57..9014061e 100644 --- a/src/drivers/SpiMaster.h +++ b/src/drivers/SpiMaster.h @@ -31,7 +31,7 @@ namespace Pinetime { SpiMaster& operator=(SpiMaster&&) = delete; bool Init(); - bool Write(uint8_t pinCsn, const uint8_t* data, size_t size); + bool Write(uint8_t pinCsn, const uint8_t* data, size_t size, void (*TransactionHook)(bool)); bool Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize); bool WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize); @@ -50,6 +50,7 @@ namespace Pinetime { NRF_SPIM_Type* spiBaseAddress; uint8_t pinCsn; + void (*TransactionHook)(bool); SpiMaster::SpiModule spi; SpiMaster::Parameters params; diff --git a/src/drivers/SpiNorFlash.cpp b/src/drivers/SpiNorFlash.cpp index 28f82fe6..56a8aabd 100644 --- a/src/drivers/SpiNorFlash.cpp +++ b/src/drivers/SpiNorFlash.cpp @@ -22,7 +22,7 @@ void SpiNorFlash::Uninit() { void SpiNorFlash::Sleep() { auto cmd = static_cast(Commands::DeepPowerDown); - spi.Write(&cmd, sizeof(uint8_t)); + spi.Write(&cmd, sizeof(uint8_t), nullptr); NRF_LOG_INFO("[SpiNorFlash] Sleep") } diff --git a/src/drivers/St7789.cpp b/src/drivers/St7789.cpp index e583aac8..6824acd8 100644 --- a/src/drivers/St7789.cpp +++ b/src/drivers/St7789.cpp @@ -3,16 +3,17 @@ #include #include #include "drivers/Spi.h" +#include "drivers/PinMap.h" using namespace Pinetime::Drivers; -St7789::St7789(Spi& spi, uint8_t pinDataCommand, uint8_t pinReset) : spi {spi}, pinDataCommand {pinDataCommand}, pinReset {pinReset} { +St7789::St7789(Spi& spi) : spi {spi} { } void St7789::Init() { - nrf_gpio_cfg_output(pinDataCommand); - nrf_gpio_cfg_output(pinReset); - nrf_gpio_pin_set(pinReset); + nrf_gpio_cfg_output(PinMap::LcdDataCommand); + nrf_gpio_cfg_output(PinMap::LcdReset); + nrf_gpio_pin_set(PinMap::LcdReset); HardwareReset(); SoftwareReset(); SleepOut(); @@ -29,18 +30,28 @@ void St7789::Init() { DisplayOn(); } +void St7789::EnableDataMode(bool isStart) { + if (isStart) { + nrf_gpio_pin_set(PinMap::LcdDataCommand); + } +} + +void St7789::EnableCommandMode(bool isStart) { + if (isStart) { + nrf_gpio_pin_clear(PinMap::LcdDataCommand); + } +} + void St7789::WriteCommand(uint8_t cmd) { - nrf_gpio_pin_clear(pinDataCommand); - WriteSpi(&cmd, 1); + WriteSpi(&cmd, 1, EnableCommandMode); } void St7789::WriteData(uint8_t data) { - nrf_gpio_pin_set(pinDataCommand); - WriteSpi(&data, 1); + WriteSpi(&data, 1, EnableDataMode); } -void St7789::WriteSpi(const uint8_t* data, size_t size) { - spi.Write(data, size); +void St7789::WriteSpi(const uint8_t* data, size_t size, void (*TransactionHook)(bool)) { + spi.Write(data, size, TransactionHook); } void St7789::SoftwareReset() { @@ -152,24 +163,23 @@ void St7789::Uninit() { void St7789::DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, size_t size) { SetAddrWindow(x, y, x + width - 1, y + height - 1); - nrf_gpio_pin_set(pinDataCommand); - WriteSpi(data, size); + WriteSpi(data, size, EnableDataMode); } void St7789::HardwareReset() { - nrf_gpio_pin_clear(pinReset); + nrf_gpio_pin_clear(PinMap::LcdReset); nrf_delay_ms(10); - nrf_gpio_pin_set(pinReset); + nrf_gpio_pin_set(PinMap::LcdReset); } void St7789::Sleep() { SleepIn(); - nrf_gpio_cfg_default(pinDataCommand); + nrf_gpio_cfg_default(PinMap::LcdDataCommand); NRF_LOG_INFO("[LCD] Sleep"); } void St7789::Wakeup() { - nrf_gpio_cfg_output(pinDataCommand); + nrf_gpio_cfg_output(PinMap::LcdDataCommand); SleepOut(); VerticalScrollStartAddress(verticalScrollingStartAddress); DisplayOn(); diff --git a/src/drivers/St7789.h b/src/drivers/St7789.h index b00bee03..185c44a0 100644 --- a/src/drivers/St7789.h +++ b/src/drivers/St7789.h @@ -8,7 +8,7 @@ namespace Pinetime { class St7789 { public: - explicit St7789(Spi& spi, uint8_t pinDataCommand, uint8_t pinReset); + explicit St7789(Spi& spi); St7789(const St7789&) = delete; St7789& operator=(const St7789&) = delete; St7789(St7789&&) = delete; @@ -26,8 +26,6 @@ namespace Pinetime { private: Spi& spi; - uint8_t pinDataCommand; - uint8_t pinReset; uint8_t verticalScrollingStartAddress = 0; void HardwareReset(); @@ -45,7 +43,9 @@ namespace Pinetime { void SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1); void SetVdv(); void WriteCommand(uint8_t cmd); - void WriteSpi(const uint8_t* data, size_t size); + void WriteSpi(const uint8_t* data, size_t size, void (*TransactionHook)(bool)); + static void EnableDataMode(bool isStart); + static void EnableCommandMode(bool isStart); enum class Commands : uint8_t { SoftwareReset = 0x01, diff --git a/src/main.cpp b/src/main.cpp index ee6a6d3d..723c2e63 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -68,7 +68,7 @@ Pinetime::Drivers::SpiMaster spi {Pinetime::Drivers::SpiMaster::SpiModule::SPI0, Pinetime::PinMap::SpiMiso}}; Pinetime::Drivers::Spi lcdSpi {spi, Pinetime::PinMap::SpiLcdCsn}; -Pinetime::Drivers::St7789 lcd {lcdSpi, Pinetime::PinMap::LcdDataCommand, Pinetime::PinMap::LcdReset}; +Pinetime::Drivers::St7789 lcd {lcdSpi}; Pinetime::Drivers::Spi flashSpi {spi, Pinetime::PinMap::SpiFlashCsn}; Pinetime::Drivers::SpiNorFlash spiNorFlash {flashSpi}; diff --git a/src/recoveryLoader.cpp b/src/recoveryLoader.cpp index a0b4d784..56cb965f 100644 --- a/src/recoveryLoader.cpp +++ b/src/recoveryLoader.cpp @@ -45,7 +45,7 @@ Pinetime::Drivers::Spi flashSpi {spi, Pinetime::PinMap::SpiFlashCsn}; Pinetime::Drivers::SpiNorFlash spiNorFlash {flashSpi}; Pinetime::Drivers::Spi lcdSpi {spi, Pinetime::PinMap::SpiLcdCsn}; -Pinetime::Drivers::St7789 lcd {lcdSpi, Pinetime::PinMap::LcdDataCommand, Pinetime::PinMap::LcdReset}; +Pinetime::Drivers::St7789 lcd {lcdSpi}; Pinetime::Controllers::BrightnessController brightnessController; From e579b8320167fa9e9efc6495ea27dae93ef640f9 Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Sun, 11 Feb 2024 14:35:11 +0000 Subject: [PATCH 044/101] Remove task to notify --- src/FreeRTOSConfig.h | 1 + src/displayapp/DisplayApp.cpp | 3 --- src/displayapp/DisplayAppRecovery.cpp | 5 ----- src/displayapp/LittleVgl.cpp | 5 ----- src/drivers/SpiMaster.cpp | 16 +++------------- src/drivers/SpiMaster.h | 1 - src/recoveryLoader.cpp | 2 -- 7 files changed, 4 insertions(+), 29 deletions(-) diff --git a/src/FreeRTOSConfig.h b/src/FreeRTOSConfig.h index cf18f418..67c33a34 100644 --- a/src/FreeRTOSConfig.h +++ b/src/FreeRTOSConfig.h @@ -75,6 +75,7 @@ #define configUSE_TIME_SLICING 0 #define configUSE_NEWLIB_REENTRANT 0 #define configENABLE_BACKWARD_COMPATIBILITY 1 +#define configUSE_TASK_NOTIFICATIONS 0 /* Hook function related definitions. */ #define configUSE_IDLE_HOOK 0 diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index adfa6171..014da83c 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -143,9 +143,6 @@ void DisplayApp::Process(void* instance) { NRF_LOG_INFO("displayapp task started!"); app->InitHw(); - // Send a dummy notification to unlock the lvgl display driver for the first iteration - xTaskNotifyGive(xTaskGetCurrentTaskHandle()); - while (true) { app->Refresh(); } diff --git a/src/displayapp/DisplayAppRecovery.cpp b/src/displayapp/DisplayAppRecovery.cpp index 002ee3bd..28892723 100644 --- a/src/displayapp/DisplayAppRecovery.cpp +++ b/src/displayapp/DisplayAppRecovery.cpp @@ -38,9 +38,6 @@ void DisplayApp::Process(void* instance) { auto* app = static_cast(instance); NRF_LOG_INFO("displayapp task started!"); - // Send a dummy notification to unlock the lvgl display driver for the first iteration - xTaskNotifyGive(xTaskGetCurrentTaskHandle()); - app->InitHw(); while (true) { app->Refresh(); @@ -94,7 +91,6 @@ void DisplayApp::DisplayLogo(uint16_t color) { Pinetime::Tools::RleDecoder rleDecoder(infinitime_nb, sizeof(infinitime_nb), color, colorBlack); for (int i = 0; i < displayWidth; i++) { rleDecoder.DecodeNext(displayBuffer, displayWidth * bytesPerPixel); - ulTaskNotifyTake(pdTRUE, 500); lcd.DrawBuffer(0, i, displayWidth, 1, reinterpret_cast(displayBuffer), displayWidth * bytesPerPixel); } } @@ -103,7 +99,6 @@ void DisplayApp::DisplayOtaProgress(uint8_t percent, uint16_t color) { const uint8_t barHeight = 20; std::fill(displayBuffer, displayBuffer + (displayWidth * bytesPerPixel), color); for (int i = 0; i < barHeight; i++) { - ulTaskNotifyTake(pdTRUE, 500); uint16_t barWidth = std::min(static_cast(percent) * 2.4f, static_cast(displayWidth)); lcd.DrawBuffer(0, displayWidth - barHeight + i, barWidth, 1, reinterpret_cast(displayBuffer), barWidth * bytesPerPixel); } diff --git a/src/displayapp/LittleVgl.cpp b/src/displayapp/LittleVgl.cpp index 89893cf7..c70a0856 100644 --- a/src/displayapp/LittleVgl.cpp +++ b/src/displayapp/LittleVgl.cpp @@ -152,10 +152,6 @@ void LittleVgl::SetFullRefresh(FullRefreshDirections direction) { void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) { uint16_t y1, y2, width, height = 0; - ulTaskNotifyTake(pdTRUE, 200); - // Notification is still needed (even if there is a mutex on SPI) because of the DataCommand pin - // which cannot be set/clear during a transfer. - if ((scrollDirection == LittleVgl::FullRefreshDirections::Down) && (area->y2 == visibleNbLines - 1)) { writeOffset = ((writeOffset + totalNbLines) - visibleNbLines) % totalNbLines; } else if ((scrollDirection == FullRefreshDirections::Up) && (area->y1 == 0)) { @@ -219,7 +215,6 @@ void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) { if (height > 0) { lcd.DrawBuffer(area->x1, y1, width, height, reinterpret_cast(color_p), width * height * 2); - ulTaskNotifyTake(pdTRUE, 100); } uint16_t pixOffset = width * height; diff --git a/src/drivers/SpiMaster.cpp b/src/drivers/SpiMaster.cpp index 4c2bc940..f878c7d5 100644 --- a/src/drivers/SpiMaster.cpp +++ b/src/drivers/SpiMaster.cpp @@ -136,20 +136,14 @@ void SpiMaster::OnEndEvent() { spiBaseAddress->TASKS_START = 1; } else { - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - if (taskToNotify != nullptr) { - vTaskNotifyGiveFromISR(taskToNotify, &xHigherPriorityTaskWoken); - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); - } - nrf_gpio_pin_set(this->pinCsn); if (this->TransactionHook != nullptr) { this->TransactionHook(false); } currentBufferAddr = 0; - BaseType_t xHigherPriorityTaskWoken2 = pdFALSE; - xSemaphoreGiveFromISR(mutex, &xHigherPriorityTaskWoken2); - portYIELD_FROM_ISR(xHigherPriorityTaskWoken | xHigherPriorityTaskWoken2); + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xSemaphoreGiveFromISR(mutex, &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } } @@ -181,7 +175,6 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, void (*T return false; auto ok = xSemaphoreTake(mutex, portMAX_DELAY); ASSERT(ok == true); - taskToNotify = xTaskGetCurrentTaskHandle(); this->TransactionHook = TransactionHook; this->pinCsn = pinCsn; @@ -226,7 +219,6 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, void (*T bool SpiMaster::Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize) { xSemaphoreTake(mutex, portMAX_DELAY); - taskToNotify = nullptr; this->TransactionHook = nullptr; this->pinCsn = pinCsn; DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0); @@ -275,8 +267,6 @@ void SpiMaster::Wakeup() { bool SpiMaster::WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize) { xSemaphoreTake(mutex, portMAX_DELAY); - taskToNotify = nullptr; - this->TransactionHook = nullptr; this->pinCsn = pinCsn; diff --git a/src/drivers/SpiMaster.h b/src/drivers/SpiMaster.h index 9014061e..131965e1 100644 --- a/src/drivers/SpiMaster.h +++ b/src/drivers/SpiMaster.h @@ -57,7 +57,6 @@ namespace Pinetime { volatile uint32_t currentBufferAddr = 0; volatile size_t currentBufferSize = 0; - volatile TaskHandle_t taskToNotify; SemaphoreHandle_t mutex = nullptr; }; } diff --git a/src/recoveryLoader.cpp b/src/recoveryLoader.cpp index 56cb965f..55f85123 100644 --- a/src/recoveryLoader.cpp +++ b/src/recoveryLoader.cpp @@ -121,7 +121,6 @@ void DisplayLogo() { Pinetime::Tools::RleDecoder rleDecoder(infinitime_nb, sizeof(infinitime_nb)); for (int i = 0; i < displayWidth; i++) { rleDecoder.DecodeNext(displayBuffer, displayWidth * bytesPerPixel); - ulTaskNotifyTake(pdTRUE, 500); lcd.DrawBuffer(0, i, displayWidth, 1, reinterpret_cast(displayBuffer), displayWidth * bytesPerPixel); } } @@ -130,7 +129,6 @@ void DisplayProgressBar(uint8_t percent, uint16_t color) { static constexpr uint8_t barHeight = 20; std::fill(displayBuffer, displayBuffer + (displayWidth * bytesPerPixel), color); for (int i = 0; i < barHeight; i++) { - ulTaskNotifyTake(pdTRUE, 500); uint16_t barWidth = std::min(static_cast(percent) * 2.4f, static_cast(displayWidth)); lcd.DrawBuffer(0, displayWidth - barHeight + i, barWidth, 1, reinterpret_cast(displayBuffer), barWidth * bytesPerPixel); } From 7739ecf900c64b3a88360b8005a925932bd922cb Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Fri, 9 Feb 2024 00:11:22 +0000 Subject: [PATCH 045/101] Refactor display WriteToRam --- src/drivers/St7789.cpp | 7 +++---- src/drivers/St7789.h | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/drivers/St7789.cpp b/src/drivers/St7789.cpp index 6824acd8..78ad3a66 100644 --- a/src/drivers/St7789.cpp +++ b/src/drivers/St7789.cpp @@ -131,12 +131,11 @@ void St7789::SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { WriteData(y0 & 0xff); WriteData(y1 >> 8); WriteData(y1 & 0xff); - - WriteToRam(); } -void St7789::WriteToRam() { +void St7789::WriteToRam(const uint8_t* data, size_t size) { WriteCommand(static_cast(Commands::WriteToRam)); + WriteSpi(data, size, EnableDataMode); } void St7789::SetVdv() { @@ -163,7 +162,7 @@ void St7789::Uninit() { void St7789::DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, size_t size) { SetAddrWindow(x, y, x + width - 1, y + height - 1); - WriteSpi(data, size, EnableDataMode); + WriteToRam(data, size); } void St7789::HardwareReset() { diff --git a/src/drivers/St7789.h b/src/drivers/St7789.h index 185c44a0..339776ae 100644 --- a/src/drivers/St7789.h +++ b/src/drivers/St7789.h @@ -36,7 +36,7 @@ namespace Pinetime { void MemoryDataAccessControl(); void DisplayInversionOn(); void NormalModeOn(); - void WriteToRam(); + void WriteToRam(const uint8_t* data, size_t size); void DisplayOn(); void DisplayOff(); From 0d77cf9189884d8879ee7d324859cddf337119fb Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Mon, 1 Apr 2024 17:14:33 +0100 Subject: [PATCH 046/101] Use functional abstraction for hooks --- src/drivers/Spi.cpp | 2 +- src/drivers/Spi.h | 3 ++- src/drivers/SpiMaster.cpp | 2 +- src/drivers/SpiMaster.h | 5 +++-- src/drivers/St7789.cpp | 41 ++++++++++++++++++++++----------------- src/drivers/St7789.h | 11 +++++++---- src/main.cpp | 2 +- src/recoveryLoader.cpp | 2 +- 8 files changed, 39 insertions(+), 29 deletions(-) diff --git a/src/drivers/Spi.cpp b/src/drivers/Spi.cpp index e0b716fa..df018775 100644 --- a/src/drivers/Spi.cpp +++ b/src/drivers/Spi.cpp @@ -9,7 +9,7 @@ Spi::Spi(SpiMaster& spiMaster, uint8_t pinCsn) : spiMaster {spiMaster}, pinCsn { nrf_gpio_pin_set(pinCsn); } -bool Spi::Write(const uint8_t* data, size_t size, void (*TransactionHook)(bool)) { +bool Spi::Write(const uint8_t* data, size_t size, std::function TransactionHook) { return spiMaster.Write(pinCsn, data, size, TransactionHook); } diff --git a/src/drivers/Spi.h b/src/drivers/Spi.h index 55eef05c..4f48fafe 100644 --- a/src/drivers/Spi.h +++ b/src/drivers/Spi.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include "drivers/SpiMaster.h" namespace Pinetime { @@ -14,7 +15,7 @@ namespace Pinetime { Spi& operator=(Spi&&) = delete; bool Init(); - bool Write(const uint8_t* data, size_t size, void (*TransactionHook)(bool)); + bool Write(const uint8_t* data, size_t size, std::function TransactionHook); bool Read(uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize); bool WriteCmdAndBuffer(const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize); void Sleep(); diff --git a/src/drivers/SpiMaster.cpp b/src/drivers/SpiMaster.cpp index f878c7d5..5bfbf7b4 100644 --- a/src/drivers/SpiMaster.cpp +++ b/src/drivers/SpiMaster.cpp @@ -170,7 +170,7 @@ void SpiMaster::PrepareRx(const uint32_t bufferAddress, const size_t size) { spiBaseAddress->EVENTS_END = 0; } -bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, void (*TransactionHook)(bool)) { +bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, std::function TransactionHook) { if (data == nullptr) return false; auto ok = xSemaphoreTake(mutex, portMAX_DELAY); diff --git a/src/drivers/SpiMaster.h b/src/drivers/SpiMaster.h index 131965e1..21980f4f 100644 --- a/src/drivers/SpiMaster.h +++ b/src/drivers/SpiMaster.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include @@ -31,7 +32,7 @@ namespace Pinetime { SpiMaster& operator=(SpiMaster&&) = delete; bool Init(); - bool Write(uint8_t pinCsn, const uint8_t* data, size_t size, void (*TransactionHook)(bool)); + bool Write(uint8_t pinCsn, const uint8_t* data, size_t size, std::function TransactionHook); bool Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize); bool WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize); @@ -50,7 +51,7 @@ namespace Pinetime { NRF_SPIM_Type* spiBaseAddress; uint8_t pinCsn; - void (*TransactionHook)(bool); + std::function TransactionHook; SpiMaster::SpiModule spi; SpiMaster::Parameters params; diff --git a/src/drivers/St7789.cpp b/src/drivers/St7789.cpp index 78ad3a66..6e5d13b1 100644 --- a/src/drivers/St7789.cpp +++ b/src/drivers/St7789.cpp @@ -3,17 +3,16 @@ #include #include #include "drivers/Spi.h" -#include "drivers/PinMap.h" using namespace Pinetime::Drivers; -St7789::St7789(Spi& spi) : spi {spi} { +St7789::St7789(Spi& spi, uint8_t pinDataCommand, uint8_t pinReset) : spi {spi}, pinDataCommand {pinDataCommand}, pinReset {pinReset} { } void St7789::Init() { - nrf_gpio_cfg_output(PinMap::LcdDataCommand); - nrf_gpio_cfg_output(PinMap::LcdReset); - nrf_gpio_pin_set(PinMap::LcdReset); + nrf_gpio_cfg_output(pinDataCommand); + nrf_gpio_cfg_output(pinReset); + nrf_gpio_pin_set(pinReset); HardwareReset(); SoftwareReset(); SleepOut(); @@ -32,25 +31,29 @@ void St7789::Init() { void St7789::EnableDataMode(bool isStart) { if (isStart) { - nrf_gpio_pin_set(PinMap::LcdDataCommand); + nrf_gpio_pin_set(pinDataCommand); } } void St7789::EnableCommandMode(bool isStart) { if (isStart) { - nrf_gpio_pin_clear(PinMap::LcdDataCommand); + nrf_gpio_pin_clear(pinDataCommand); } } -void St7789::WriteCommand(uint8_t cmd) { - WriteSpi(&cmd, 1, EnableCommandMode); -} - void St7789::WriteData(uint8_t data) { - WriteSpi(&data, 1, EnableDataMode); + WriteSpi(&data, 1, [this](bool isStart) { + EnableDataMode(isStart); + }); } -void St7789::WriteSpi(const uint8_t* data, size_t size, void (*TransactionHook)(bool)) { +void St7789::WriteCommand(uint8_t cmd) { + WriteSpi(&cmd, 1, [this](bool isStart) { + EnableCommandMode(isStart); + }); +} + +void St7789::WriteSpi(const uint8_t* data, size_t size, std::function TransactionHook) { spi.Write(data, size, TransactionHook); } @@ -135,7 +138,9 @@ void St7789::SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { void St7789::WriteToRam(const uint8_t* data, size_t size) { WriteCommand(static_cast(Commands::WriteToRam)); - WriteSpi(data, size, EnableDataMode); + WriteSpi(data, size, [this](bool isStart) { + EnableDataMode(isStart); + }); } void St7789::SetVdv() { @@ -166,19 +171,19 @@ void St7789::DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, } void St7789::HardwareReset() { - nrf_gpio_pin_clear(PinMap::LcdReset); + nrf_gpio_pin_clear(pinReset); nrf_delay_ms(10); - nrf_gpio_pin_set(PinMap::LcdReset); + nrf_gpio_pin_set(pinReset); } void St7789::Sleep() { SleepIn(); - nrf_gpio_cfg_default(PinMap::LcdDataCommand); + nrf_gpio_cfg_default(pinDataCommand); NRF_LOG_INFO("[LCD] Sleep"); } void St7789::Wakeup() { - nrf_gpio_cfg_output(PinMap::LcdDataCommand); + nrf_gpio_cfg_output(pinDataCommand); SleepOut(); VerticalScrollStartAddress(verticalScrollingStartAddress); DisplayOn(); diff --git a/src/drivers/St7789.h b/src/drivers/St7789.h index 339776ae..0c73f77e 100644 --- a/src/drivers/St7789.h +++ b/src/drivers/St7789.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include namespace Pinetime { namespace Drivers { @@ -8,7 +9,7 @@ namespace Pinetime { class St7789 { public: - explicit St7789(Spi& spi); + explicit St7789(Spi& spi, uint8_t pinDataCommand, uint8_t pinReset); St7789(const St7789&) = delete; St7789& operator=(const St7789&) = delete; St7789(St7789&&) = delete; @@ -26,6 +27,8 @@ namespace Pinetime { private: Spi& spi; + uint8_t pinDataCommand; + uint8_t pinReset; uint8_t verticalScrollingStartAddress = 0; void HardwareReset(); @@ -43,9 +46,9 @@ namespace Pinetime { void SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1); void SetVdv(); void WriteCommand(uint8_t cmd); - void WriteSpi(const uint8_t* data, size_t size, void (*TransactionHook)(bool)); - static void EnableDataMode(bool isStart); - static void EnableCommandMode(bool isStart); + void WriteSpi(const uint8_t* data, size_t size, std::function TransactionHook); + void EnableDataMode(bool isStart); + void EnableCommandMode(bool isStart); enum class Commands : uint8_t { SoftwareReset = 0x01, diff --git a/src/main.cpp b/src/main.cpp index 723c2e63..ee6a6d3d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -68,7 +68,7 @@ Pinetime::Drivers::SpiMaster spi {Pinetime::Drivers::SpiMaster::SpiModule::SPI0, Pinetime::PinMap::SpiMiso}}; Pinetime::Drivers::Spi lcdSpi {spi, Pinetime::PinMap::SpiLcdCsn}; -Pinetime::Drivers::St7789 lcd {lcdSpi}; +Pinetime::Drivers::St7789 lcd {lcdSpi, Pinetime::PinMap::LcdDataCommand, Pinetime::PinMap::LcdReset}; Pinetime::Drivers::Spi flashSpi {spi, Pinetime::PinMap::SpiFlashCsn}; Pinetime::Drivers::SpiNorFlash spiNorFlash {flashSpi}; diff --git a/src/recoveryLoader.cpp b/src/recoveryLoader.cpp index 55f85123..fc9ab76c 100644 --- a/src/recoveryLoader.cpp +++ b/src/recoveryLoader.cpp @@ -45,7 +45,7 @@ Pinetime::Drivers::Spi flashSpi {spi, Pinetime::PinMap::SpiFlashCsn}; Pinetime::Drivers::SpiNorFlash spiNorFlash {flashSpi}; Pinetime::Drivers::Spi lcdSpi {spi, Pinetime::PinMap::SpiLcdCsn}; -Pinetime::Drivers::St7789 lcd {lcdSpi}; +Pinetime::Drivers::St7789 lcd {lcdSpi, Pinetime::PinMap::LcdDataCommand, Pinetime::PinMap::LcdReset}; Pinetime::Controllers::BrightnessController brightnessController; From 1a379c80bf5277ac181977e5af775ea93d09dffc Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Tue, 2 Apr 2024 11:14:45 +0100 Subject: [PATCH 047/101] Refactor lambdas --- src/drivers/St7789.cpp | 38 ++++++++++++++++++-------------------- src/drivers/St7789.h | 4 ++-- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/drivers/St7789.cpp b/src/drivers/St7789.cpp index 6e5d13b1..e42592e6 100644 --- a/src/drivers/St7789.cpp +++ b/src/drivers/St7789.cpp @@ -29,27 +29,27 @@ void St7789::Init() { DisplayOn(); } -void St7789::EnableDataMode(bool isStart) { - if (isStart) { - nrf_gpio_pin_set(pinDataCommand); - } -} - -void St7789::EnableCommandMode(bool isStart) { - if (isStart) { - nrf_gpio_pin_clear(pinDataCommand); - } -} - void St7789::WriteData(uint8_t data) { - WriteSpi(&data, 1, [this](bool isStart) { - EnableDataMode(isStart); + WriteData(&data, 1); +} + +void St7789::WriteData(const uint8_t* data, size_t size) { + WriteSpi(data, size, [pinDataCommand = pinDataCommand](bool isStart) { + if (isStart) { + nrf_gpio_pin_set(pinDataCommand); + } }); } -void St7789::WriteCommand(uint8_t cmd) { - WriteSpi(&cmd, 1, [this](bool isStart) { - EnableCommandMode(isStart); +void St7789::WriteCommand(uint8_t data) { + WriteCommand(&data, 1); +} + +void St7789::WriteCommand(const uint8_t* data, size_t size) { + WriteSpi(data, size, [pinDataCommand = pinDataCommand](bool isStart) { + if (isStart) { + nrf_gpio_pin_clear(pinDataCommand); + } }); } @@ -138,9 +138,7 @@ void St7789::SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { void St7789::WriteToRam(const uint8_t* data, size_t size) { WriteCommand(static_cast(Commands::WriteToRam)); - WriteSpi(data, size, [this](bool isStart) { - EnableDataMode(isStart); - }); + WriteData(data, size); } void St7789::SetVdv() { diff --git a/src/drivers/St7789.h b/src/drivers/St7789.h index 0c73f77e..5eb60cfd 100644 --- a/src/drivers/St7789.h +++ b/src/drivers/St7789.h @@ -46,9 +46,8 @@ namespace Pinetime { void SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1); void SetVdv(); void WriteCommand(uint8_t cmd); + void WriteCommand(const uint8_t* data, size_t size); void WriteSpi(const uint8_t* data, size_t size, std::function TransactionHook); - void EnableDataMode(bool isStart); - void EnableCommandMode(bool isStart); enum class Commands : uint8_t { SoftwareReset = 0x01, @@ -68,6 +67,7 @@ namespace Pinetime { VdvSet = 0xc4, }; void WriteData(uint8_t data); + void WriteData(const uint8_t* data, size_t size); void ColumnAddressSet(); static constexpr uint16_t Width = 240; From 7e8a4d32ee4e79db7834aa4ef9ffffe7b4a2a101 Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Thu, 11 Apr 2024 00:03:36 +0100 Subject: [PATCH 048/101] Avoid storing lambda --- src/drivers/Spi.cpp | 4 ++-- src/drivers/Spi.h | 2 +- src/drivers/SpiMaster.cpp | 16 +++------------- src/drivers/SpiMaster.h | 3 +-- src/drivers/St7789.cpp | 16 ++++++---------- src/drivers/St7789.h | 2 +- 6 files changed, 14 insertions(+), 29 deletions(-) diff --git a/src/drivers/Spi.cpp b/src/drivers/Spi.cpp index df018775..a03ea3c0 100644 --- a/src/drivers/Spi.cpp +++ b/src/drivers/Spi.cpp @@ -9,8 +9,8 @@ Spi::Spi(SpiMaster& spiMaster, uint8_t pinCsn) : spiMaster {spiMaster}, pinCsn { nrf_gpio_pin_set(pinCsn); } -bool Spi::Write(const uint8_t* data, size_t size, std::function TransactionHook) { - return spiMaster.Write(pinCsn, data, size, TransactionHook); +bool Spi::Write(const uint8_t* data, size_t size, const std::function& transactionHook) { + return spiMaster.Write(pinCsn, data, size, transactionHook); } bool Spi::Read(uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize) { diff --git a/src/drivers/Spi.h b/src/drivers/Spi.h index 4f48fafe..e30620cc 100644 --- a/src/drivers/Spi.h +++ b/src/drivers/Spi.h @@ -15,7 +15,7 @@ namespace Pinetime { Spi& operator=(Spi&&) = delete; bool Init(); - bool Write(const uint8_t* data, size_t size, std::function TransactionHook); + bool Write(const uint8_t* data, size_t size, const std::function& transactionHook); bool Read(uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize); bool WriteCmdAndBuffer(const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize); void Sleep(); diff --git a/src/drivers/SpiMaster.cpp b/src/drivers/SpiMaster.cpp index 5bfbf7b4..eec62cb7 100644 --- a/src/drivers/SpiMaster.cpp +++ b/src/drivers/SpiMaster.cpp @@ -137,9 +137,6 @@ void SpiMaster::OnEndEvent() { spiBaseAddress->TASKS_START = 1; } else { nrf_gpio_pin_set(this->pinCsn); - if (this->TransactionHook != nullptr) { - this->TransactionHook(false); - } currentBufferAddr = 0; BaseType_t xHigherPriorityTaskWoken = pdFALSE; xSemaphoreGiveFromISR(mutex, &xHigherPriorityTaskWoken); @@ -170,13 +167,12 @@ void SpiMaster::PrepareRx(const uint32_t bufferAddress, const size_t size) { spiBaseAddress->EVENTS_END = 0; } -bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, std::function TransactionHook) { +bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, const std::function& transactionHook) { if (data == nullptr) return false; auto ok = xSemaphoreTake(mutex, portMAX_DELAY); ASSERT(ok == true); - this->TransactionHook = TransactionHook; this->pinCsn = pinCsn; if (size == 1) { @@ -185,8 +181,8 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, std::fun DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0); } - if (this->TransactionHook != nullptr) { - this->TransactionHook(true); + if (transactionHook != nullptr) { + transactionHook(); } nrf_gpio_pin_clear(this->pinCsn); @@ -203,9 +199,6 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, std::fun while (spiBaseAddress->EVENTS_END == 0) ; nrf_gpio_pin_set(this->pinCsn); - if (this->TransactionHook != nullptr) { - this->TransactionHook(false); - } currentBufferAddr = 0; DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0); @@ -219,7 +212,6 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, std::fun bool SpiMaster::Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize) { xSemaphoreTake(mutex, portMAX_DELAY); - this->TransactionHook = nullptr; this->pinCsn = pinCsn; DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0); spiBaseAddress->INTENCLR = (1 << 6); @@ -267,8 +259,6 @@ void SpiMaster::Wakeup() { bool SpiMaster::WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize) { xSemaphoreTake(mutex, portMAX_DELAY); - this->TransactionHook = nullptr; - this->pinCsn = pinCsn; DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0); spiBaseAddress->INTENCLR = (1 << 6); diff --git a/src/drivers/SpiMaster.h b/src/drivers/SpiMaster.h index 21980f4f..2f39a1b2 100644 --- a/src/drivers/SpiMaster.h +++ b/src/drivers/SpiMaster.h @@ -32,7 +32,7 @@ namespace Pinetime { SpiMaster& operator=(SpiMaster&&) = delete; bool Init(); - bool Write(uint8_t pinCsn, const uint8_t* data, size_t size, std::function TransactionHook); + bool Write(uint8_t pinCsn, const uint8_t* data, size_t size, const std::function& transactionHook); bool Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize); bool WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize); @@ -51,7 +51,6 @@ namespace Pinetime { NRF_SPIM_Type* spiBaseAddress; uint8_t pinCsn; - std::function TransactionHook; SpiMaster::SpiModule spi; SpiMaster::Parameters params; diff --git a/src/drivers/St7789.cpp b/src/drivers/St7789.cpp index e42592e6..e933c374 100644 --- a/src/drivers/St7789.cpp +++ b/src/drivers/St7789.cpp @@ -34,10 +34,8 @@ void St7789::WriteData(uint8_t data) { } void St7789::WriteData(const uint8_t* data, size_t size) { - WriteSpi(data, size, [pinDataCommand = pinDataCommand](bool isStart) { - if (isStart) { - nrf_gpio_pin_set(pinDataCommand); - } + WriteSpi(data, size, [pinDataCommand = pinDataCommand]() { + nrf_gpio_pin_set(pinDataCommand); }); } @@ -46,15 +44,13 @@ void St7789::WriteCommand(uint8_t data) { } void St7789::WriteCommand(const uint8_t* data, size_t size) { - WriteSpi(data, size, [pinDataCommand = pinDataCommand](bool isStart) { - if (isStart) { - nrf_gpio_pin_clear(pinDataCommand); - } + WriteSpi(data, size, [pinDataCommand = pinDataCommand]() { + nrf_gpio_pin_clear(pinDataCommand); }); } -void St7789::WriteSpi(const uint8_t* data, size_t size, std::function TransactionHook) { - spi.Write(data, size, TransactionHook); +void St7789::WriteSpi(const uint8_t* data, size_t size, const std::function& transactionHook) { + spi.Write(data, size, transactionHook); } void St7789::SoftwareReset() { diff --git a/src/drivers/St7789.h b/src/drivers/St7789.h index 5eb60cfd..f49ed511 100644 --- a/src/drivers/St7789.h +++ b/src/drivers/St7789.h @@ -47,7 +47,7 @@ namespace Pinetime { void SetVdv(); void WriteCommand(uint8_t cmd); void WriteCommand(const uint8_t* data, size_t size); - void WriteSpi(const uint8_t* data, size_t size, std::function TransactionHook); + void WriteSpi(const uint8_t* data, size_t size, const std::function& transactionHook); enum class Commands : uint8_t { SoftwareReset = 0x01, From 7c8e5c214429b6e001abc5cb8e0b40ed0447fb4c Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Thu, 18 Apr 2024 18:54:07 +0100 Subject: [PATCH 049/101] Rename to pre-transaction hook --- src/drivers/Spi.cpp | 4 ++-- src/drivers/Spi.h | 2 +- src/drivers/SpiMaster.cpp | 6 +++--- src/drivers/SpiMaster.h | 2 +- src/drivers/St7789.cpp | 4 ++-- src/drivers/St7789.h | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/drivers/Spi.cpp b/src/drivers/Spi.cpp index a03ea3c0..a95a7eae 100644 --- a/src/drivers/Spi.cpp +++ b/src/drivers/Spi.cpp @@ -9,8 +9,8 @@ Spi::Spi(SpiMaster& spiMaster, uint8_t pinCsn) : spiMaster {spiMaster}, pinCsn { nrf_gpio_pin_set(pinCsn); } -bool Spi::Write(const uint8_t* data, size_t size, const std::function& transactionHook) { - return spiMaster.Write(pinCsn, data, size, transactionHook); +bool Spi::Write(const uint8_t* data, size_t size, const std::function& preTransactionHook) { + return spiMaster.Write(pinCsn, data, size, preTransactionHook); } bool Spi::Read(uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize) { diff --git a/src/drivers/Spi.h b/src/drivers/Spi.h index e30620cc..0c5edf08 100644 --- a/src/drivers/Spi.h +++ b/src/drivers/Spi.h @@ -15,7 +15,7 @@ namespace Pinetime { Spi& operator=(Spi&&) = delete; bool Init(); - bool Write(const uint8_t* data, size_t size, const std::function& transactionHook); + bool Write(const uint8_t* data, size_t size, const std::function& preTransactionHook); bool Read(uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize); bool WriteCmdAndBuffer(const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize); void Sleep(); diff --git a/src/drivers/SpiMaster.cpp b/src/drivers/SpiMaster.cpp index eec62cb7..690a3226 100644 --- a/src/drivers/SpiMaster.cpp +++ b/src/drivers/SpiMaster.cpp @@ -167,7 +167,7 @@ void SpiMaster::PrepareRx(const uint32_t bufferAddress, const size_t size) { spiBaseAddress->EVENTS_END = 0; } -bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, const std::function& transactionHook) { +bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, const std::function& preTransactionHook) { if (data == nullptr) return false; auto ok = xSemaphoreTake(mutex, portMAX_DELAY); @@ -181,8 +181,8 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, const st DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0); } - if (transactionHook != nullptr) { - transactionHook(); + if (preTransactionHook != nullptr) { + preTransactionHook(); } nrf_gpio_pin_clear(this->pinCsn); diff --git a/src/drivers/SpiMaster.h b/src/drivers/SpiMaster.h index 2f39a1b2..af38e87b 100644 --- a/src/drivers/SpiMaster.h +++ b/src/drivers/SpiMaster.h @@ -32,7 +32,7 @@ namespace Pinetime { SpiMaster& operator=(SpiMaster&&) = delete; bool Init(); - bool Write(uint8_t pinCsn, const uint8_t* data, size_t size, const std::function& transactionHook); + bool Write(uint8_t pinCsn, const uint8_t* data, size_t size, const std::function& preTransactionHook); bool Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize); bool WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize); diff --git a/src/drivers/St7789.cpp b/src/drivers/St7789.cpp index e933c374..d1747c23 100644 --- a/src/drivers/St7789.cpp +++ b/src/drivers/St7789.cpp @@ -49,8 +49,8 @@ void St7789::WriteCommand(const uint8_t* data, size_t size) { }); } -void St7789::WriteSpi(const uint8_t* data, size_t size, const std::function& transactionHook) { - spi.Write(data, size, transactionHook); +void St7789::WriteSpi(const uint8_t* data, size_t size, const std::function& preTransactionHook) { + spi.Write(data, size, preTransactionHook); } void St7789::SoftwareReset() { diff --git a/src/drivers/St7789.h b/src/drivers/St7789.h index f49ed511..715bd1bd 100644 --- a/src/drivers/St7789.h +++ b/src/drivers/St7789.h @@ -47,7 +47,7 @@ namespace Pinetime { void SetVdv(); void WriteCommand(uint8_t cmd); void WriteCommand(const uint8_t* data, size_t size); - void WriteSpi(const uint8_t* data, size_t size, const std::function& transactionHook); + void WriteSpi(const uint8_t* data, size_t size, const std::function& preTransactionHook); enum class Commands : uint8_t { SoftwareReset = 0x01, From cad3de46b04f1df03e992de6bdf8dc4eb783f858 Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Fri, 27 Oct 2023 00:26:57 +0100 Subject: [PATCH 050/101] Use FreeRTOS delay instead of spinning the CPU --- src/drivers/St7789.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/drivers/St7789.cpp b/src/drivers/St7789.cpp index d1747c23..82cefc43 100644 --- a/src/drivers/St7789.cpp +++ b/src/drivers/St7789.cpp @@ -1,6 +1,5 @@ #include "drivers/St7789.h" #include -#include #include #include "drivers/Spi.h" @@ -55,7 +54,7 @@ void St7789::WriteSpi(const uint8_t* data, size_t size, const std::function(Commands::SoftwareReset)); - nrf_delay_ms(150); + vTaskDelay(pdMS_TO_TICKS(150)); } void St7789::SleepOut() { @@ -69,7 +68,7 @@ void St7789::SleepIn() { void St7789::ColMod() { WriteCommand(static_cast(Commands::ColMod)); WriteData(0x55); - nrf_delay_ms(10); + vTaskDelay(pdMS_TO_TICKS(10)); } void St7789::MemoryDataAccessControl() { @@ -106,12 +105,12 @@ void St7789::RowAddressSet() { void St7789::DisplayInversionOn() { WriteCommand(static_cast(Commands::DisplayInversionOn)); - nrf_delay_ms(10); + vTaskDelay(pdMS_TO_TICKS(10)); } void St7789::NormalModeOn() { WriteCommand(static_cast(Commands::NormalModeOn)); - nrf_delay_ms(10); + vTaskDelay(pdMS_TO_TICKS(10)); } void St7789::DisplayOn() { @@ -146,7 +145,7 @@ void St7789::SetVdv() { void St7789::DisplayOff() { WriteCommand(static_cast(Commands::DisplayOff)); - nrf_delay_ms(500); + vTaskDelay(pdMS_TO_TICKS(500)); } void St7789::VerticalScrollStartAddress(uint16_t line) { @@ -166,7 +165,7 @@ void St7789::DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, void St7789::HardwareReset() { nrf_gpio_pin_clear(pinReset); - nrf_delay_ms(10); + vTaskDelay(pdMS_TO_TICKS(10)); nrf_gpio_pin_set(pinReset); } From 32dfaa82d6ef2e886c6439bf4b1d2a014c22c044 Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Tue, 30 Jan 2024 23:29:16 +0000 Subject: [PATCH 051/101] Apply display driver datasheet delays --- src/drivers/St7789.cpp | 47 ++++++++++++++++++++++++++++++++++-------- src/drivers/St7789.h | 7 +++++++ 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/drivers/St7789.cpp b/src/drivers/St7789.cpp index 82cefc43..f1917a77 100644 --- a/src/drivers/St7789.cpp +++ b/src/drivers/St7789.cpp @@ -1,7 +1,4 @@ #include "drivers/St7789.h" -#include -#include -#include "drivers/Spi.h" using namespace Pinetime::Drivers; @@ -53,22 +50,52 @@ void St7789::WriteSpi(const uint8_t* data, size_t size, const std::function(Commands::SoftwareReset)); - vTaskDelay(pdMS_TO_TICKS(150)); + // If sleep in: must wait 120ms before sleep out can sent (see driver datasheet) + // Unconditionally wait as software reset doesn't need to be performant + sleepIn = true; + lastSleepExit = xTaskGetTickCount(); + vTaskDelay(pdMS_TO_TICKS(125)); } void St7789::SleepOut() { + if (!sleepIn) { + return; + } WriteCommand(static_cast(Commands::SleepOut)); + // Wait 5ms for clocks to stabilise + // pdMS rounds down => 6 used here + vTaskDelay(pdMS_TO_TICKS(6)); + // Cannot send sleep in or software reset for 120ms + lastSleepExit = xTaskGetTickCount(); + sleepIn = false; +} + +void St7789::EnsureSleepOutPostDelay() { + TickType_t delta = xTaskGetTickCount() - lastSleepExit; + // Due to timer wraparound, there is a chance of delaying when not necessary + // It is very low (pdMS_TO_TICKS(125)/2^32) and waiting an extra 125ms isn't too bad + if (delta < pdMS_TO_TICKS(125)) { + vTaskDelay(pdMS_TO_TICKS(125) - delta); + } } void St7789::SleepIn() { + if (sleepIn) { + return; + } + EnsureSleepOutPostDelay(); WriteCommand(static_cast(Commands::SleepIn)); + // Wait 5ms for clocks to stabilise + // pdMS rounds down => 6 used here + vTaskDelay(pdMS_TO_TICKS(6)); + sleepIn = true; } void St7789::ColMod() { WriteCommand(static_cast(Commands::ColMod)); WriteData(0x55); - vTaskDelay(pdMS_TO_TICKS(10)); } void St7789::MemoryDataAccessControl() { @@ -105,12 +132,10 @@ void St7789::RowAddressSet() { void St7789::DisplayInversionOn() { WriteCommand(static_cast(Commands::DisplayInversionOn)); - vTaskDelay(pdMS_TO_TICKS(10)); } void St7789::NormalModeOn() { WriteCommand(static_cast(Commands::NormalModeOn)); - vTaskDelay(pdMS_TO_TICKS(10)); } void St7789::DisplayOn() { @@ -145,7 +170,6 @@ void St7789::SetVdv() { void St7789::DisplayOff() { WriteCommand(static_cast(Commands::DisplayOff)); - vTaskDelay(pdMS_TO_TICKS(500)); } void St7789::VerticalScrollStartAddress(uint16_t line) { @@ -165,8 +189,13 @@ void St7789::DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, void St7789::HardwareReset() { nrf_gpio_pin_clear(pinReset); - vTaskDelay(pdMS_TO_TICKS(10)); + vTaskDelay(pdMS_TO_TICKS(1)); nrf_gpio_pin_set(pinReset); + // If hardware reset started while sleep out, reset time may be up to 120ms + // Unconditionally wait as hardware reset doesn't need to be performant + sleepIn = true; + lastSleepExit = xTaskGetTickCount(); + vTaskDelay(pdMS_TO_TICKS(125)); } void St7789::Sleep() { diff --git a/src/drivers/St7789.h b/src/drivers/St7789.h index 715bd1bd..fcb6f944 100644 --- a/src/drivers/St7789.h +++ b/src/drivers/St7789.h @@ -3,6 +3,10 @@ #include #include +#include +#include +#include "drivers/Spi.h" + namespace Pinetime { namespace Drivers { class Spi; @@ -30,10 +34,13 @@ namespace Pinetime { uint8_t pinDataCommand; uint8_t pinReset; uint8_t verticalScrollingStartAddress = 0; + bool sleepIn; + TickType_t lastSleepExit; void HardwareReset(); void SoftwareReset(); void SleepOut(); + void EnsureSleepOutPostDelay(); void SleepIn(); void ColMod(); void MemoryDataAccessControl(); From b8fe20b71a2893efdada05c795bef8376a88e939 Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Mon, 25 Mar 2024 23:05:01 +0000 Subject: [PATCH 052/101] Move includes back --- src/drivers/St7789.cpp | 3 +++ src/drivers/St7789.h | 4 +--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/drivers/St7789.cpp b/src/drivers/St7789.cpp index f1917a77..c81f3b58 100644 --- a/src/drivers/St7789.cpp +++ b/src/drivers/St7789.cpp @@ -1,4 +1,7 @@ #include "drivers/St7789.h" +#include +#include +#include "drivers/Spi.h" using namespace Pinetime::Drivers; diff --git a/src/drivers/St7789.h b/src/drivers/St7789.h index fcb6f944..45d4b56d 100644 --- a/src/drivers/St7789.h +++ b/src/drivers/St7789.h @@ -3,9 +3,7 @@ #include #include -#include -#include -#include "drivers/Spi.h" +#include namespace Pinetime { namespace Drivers { From 5c93cbd55252343738efb58dfa0f774cc038e97b Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Fri, 29 Mar 2024 16:29:07 +0000 Subject: [PATCH 053/101] Include task header (Fixes sim) --- src/drivers/St7789.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/drivers/St7789.cpp b/src/drivers/St7789.cpp index c81f3b58..12e95a41 100644 --- a/src/drivers/St7789.cpp +++ b/src/drivers/St7789.cpp @@ -2,6 +2,7 @@ #include #include #include "drivers/Spi.h" +#include "task.h" using namespace Pinetime::Drivers; From 1246d7778134e827620295c231aab8c92ef2df47 Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Wed, 24 Jan 2024 23:30:48 +0000 Subject: [PATCH 054/101] Fix erratum 58 workaround --- src/drivers/SpiMaster.cpp | 61 ++++++++++++++++++++++++--------------- src/drivers/SpiMaster.h | 8 +++-- 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/src/drivers/SpiMaster.cpp b/src/drivers/SpiMaster.cpp index 690a3226..19422ef3 100644 --- a/src/drivers/SpiMaster.cpp +++ b/src/drivers/SpiMaster.cpp @@ -94,32 +94,45 @@ bool SpiMaster::Init() { return true; } -void SpiMaster::SetupWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel) { - // Create an event when SCK toggles. - NRF_GPIOTE->CONFIG[gpiote_channel] = (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos) | (spim->PSEL.SCK << GPIOTE_CONFIG_PSEL_Pos) | - (GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos); +void SpiMaster::SetupWorkaroundForErratum58() { + nrfx_gpiote_pin_t pin = spiBaseAddress->PSEL.SCK; + nrfx_gpiote_in_config_t gpioteCfg = {.sense = NRF_GPIOTE_POLARITY_TOGGLE, + .pull = NRF_GPIO_PIN_NOPULL, + .is_watcher = false, + .hi_accuracy = true, + .skip_gpio_setup = true}; + if (!workaroundActive) { + // Create an event when SCK toggles. + APP_ERROR_CHECK(nrfx_gpiote_in_init(pin, &gpioteCfg, NULL)); + nrfx_gpiote_in_event_enable(pin, false); + + // Stop the spim instance when SCK toggles. + nrf_ppi_channel_endpoint_setup(workaroundPpi, nrfx_gpiote_in_event_addr_get(pin), spiBaseAddress->TASKS_STOP); + nrf_ppi_channel_enable(workaroundPpi); + } - // Stop the spim instance when SCK toggles. - NRF_PPI->CH[ppi_channel].EEP = (uint32_t) &NRF_GPIOTE->EVENTS_IN[gpiote_channel]; - NRF_PPI->CH[ppi_channel].TEP = (uint32_t) &spim->TASKS_STOP; - NRF_PPI->CHENSET = 1U << ppi_channel; spiBaseAddress->EVENTS_END = 0; // Disable IRQ - spim->INTENCLR = (1 << 6); - spim->INTENCLR = (1 << 1); - spim->INTENCLR = (1 << 19); + spiBaseAddress->INTENCLR = (1 << 6); + spiBaseAddress->INTENCLR = (1 << 1); + spiBaseAddress->INTENCLR = (1 << 19); + workaroundActive = true; } -void SpiMaster::DisableWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel) { - NRF_GPIOTE->CONFIG[gpiote_channel] = 0; - NRF_PPI->CH[ppi_channel].EEP = 0; - NRF_PPI->CH[ppi_channel].TEP = 0; - NRF_PPI->CHENSET = ppi_channel; +void SpiMaster::DisableWorkaroundForErratum58() { + nrfx_gpiote_pin_t pin = spiBaseAddress->PSEL.SCK; + if (workaroundActive) { + nrfx_gpiote_in_uninit(pin); + nrf_ppi_channel_disable(workaroundPpi); + } spiBaseAddress->EVENTS_END = 0; - spim->INTENSET = (1 << 6); - spim->INTENSET = (1 << 1); - spim->INTENSET = (1 << 19); + + // Enable IRQ + spiBaseAddress->INTENSET = (1 << 6); + spiBaseAddress->INTENSET = (1 << 1); + spiBaseAddress->INTENSET = (1 << 19); + workaroundActive = false; } void SpiMaster::OnEndEvent() { @@ -176,9 +189,9 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, const st this->pinCsn = pinCsn; if (size == 1) { - SetupWorkaroundForFtpan58(spiBaseAddress, 0, 0); + SetupWorkaroundForErratum58(); } else { - DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0); + DisableWorkaroundForErratum58(); } if (preTransactionHook != nullptr) { @@ -201,7 +214,7 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, const st nrf_gpio_pin_set(this->pinCsn); currentBufferAddr = 0; - DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0); + DisableWorkaroundForErratum58(); xSemaphoreGive(mutex); } @@ -213,7 +226,7 @@ bool SpiMaster::Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t* data xSemaphoreTake(mutex, portMAX_DELAY); this->pinCsn = pinCsn; - DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0); + DisableWorkaroundForErratum58(); spiBaseAddress->INTENCLR = (1 << 6); spiBaseAddress->INTENCLR = (1 << 1); spiBaseAddress->INTENCLR = (1 << 19); @@ -260,7 +273,7 @@ bool SpiMaster::WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmd xSemaphoreTake(mutex, portMAX_DELAY); this->pinCsn = pinCsn; - DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0); + DisableWorkaroundForErratum58(); spiBaseAddress->INTENCLR = (1 << 6); spiBaseAddress->INTENCLR = (1 << 1); spiBaseAddress->INTENCLR = (1 << 19); diff --git a/src/drivers/SpiMaster.h b/src/drivers/SpiMaster.h index af38e87b..be6e5351 100644 --- a/src/drivers/SpiMaster.h +++ b/src/drivers/SpiMaster.h @@ -6,6 +6,8 @@ #include #include #include +#include "nrfx_gpiote.h" +#include "nrf_ppi.h" namespace Pinetime { namespace Drivers { @@ -44,8 +46,8 @@ namespace Pinetime { void Wakeup(); private: - void SetupWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel); - void DisableWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel); + void SetupWorkaroundForErratum58(); + void DisableWorkaroundForErratum58(); void PrepareTx(const volatile uint32_t bufferAddress, const volatile size_t size); void PrepareRx(const volatile uint32_t bufferAddress, const volatile size_t size); @@ -58,6 +60,8 @@ namespace Pinetime { volatile uint32_t currentBufferAddr = 0; volatile size_t currentBufferSize = 0; SemaphoreHandle_t mutex = nullptr; + static constexpr nrf_ppi_channel_t workaroundPpi = NRF_PPI_CHANNEL0; + bool workaroundActive = false; }; } } From b55c9d256cbed128c84391aaf0acc34ff8c04ffa Mon Sep 17 00:00:00 2001 From: ecarlett Date: Tue, 28 May 2024 10:39:51 +0200 Subject: [PATCH 055/101] initial commit, with already alarm added. TODO : add possibility to disable alarm status, and make it work on 12hrs format pick 0dcfb2ed Fix erratum 58 workaround pick 1162fdbf initial commit, with already alarm added. TODO : add possibility to disable alarm status, and make it work on 12hrs format Next commands to do (30 remaining commands): pick ab901b89 added option to show or hide alarm status pick 1af813a2 fixed alarm status reappearing when alarm changed, even if not set to be displayed. TODO : clean comments related to this, and add am/pm format for alarm You are currently rebasing branch 'testcalendar' on '577dfd5b'. --- node_modules/.package-lock.json | 113 + node_modules/lv_font_conv/CHANGELOG.md | 164 + node_modules/lv_font_conv/LICENSE | 22 + node_modules/lv_font_conv/README.md | 150 + node_modules/lv_font_conv/lib/app_error.js | 9 + node_modules/lv_font_conv/lib/cli.js | 318 + .../lv_font_conv/lib/collect_font_data.js | 173 + node_modules/lv_font_conv/lib/convert.js | 30 + .../lib/font/cmap_build_subtables.js | 105 + .../lv_font_conv/lib/font/compress.js | 107 + node_modules/lv_font_conv/lib/font/font.js | 131 + .../lv_font_conv/lib/font/table_cmap.js | 201 + .../lv_font_conv/lib/font/table_glyf.js | 147 + .../lv_font_conv/lib/font/table_head.js | 99 + .../lv_font_conv/lib/font/table_kern.js | 256 + .../lv_font_conv/lib/font/table_loca.js | 42 + .../lv_font_conv/lib/freetype/index.js | 317 + .../lv_font_conv/lib/freetype/render.c | 83 + node_modules/lv_font_conv/lib/ranger.js | 51 + node_modules/lv_font_conv/lib/utils.js | 131 + node_modules/lv_font_conv/lib/writers/bin.js | 17 + node_modules/lv_font_conv/lib/writers/dump.js | 68 + .../lv_font_conv/lib/writers/lvgl/index.js | 17 + .../lv_font_conv/lib/writers/lvgl/lv_font.js | 98 + .../lib/writers/lvgl/lv_table_cmap.js | 125 + .../lib/writers/lvgl/lv_table_glyf.js | 121 + .../lib/writers/lvgl/lv_table_head.js | 99 + .../lib/writers/lvgl/lv_table_kern.js | 121 + node_modules/lv_font_conv/lv_font_conv.js | 17 + .../node_modules/argparse/CHANGELOG.md | 216 + .../node_modules/argparse/LICENSE | 254 + .../node_modules/argparse/README.md | 84 + .../node_modules/argparse/argparse.js | 3707 ++++ .../node_modules/argparse/lib/sub.js | 67 + .../node_modules/argparse/lib/textwrap.js | 440 + .../node_modules/argparse/package.json | 67 + .../bit-buffer/.github/workflows/ci.yml | 36 + .../node_modules/bit-buffer/LICENSE | 21 + .../node_modules/bit-buffer/README.md | 148 + .../node_modules/bit-buffer/bit-buffer.d.ts | 115 + .../node_modules/bit-buffer/bit-buffer.js | 502 + .../node_modules/bit-buffer/package.json | 71 + .../node_modules/bit-buffer/test.js | 628 + .../lv_font_conv/node_modules/debug/LICENSE | 19 + .../lv_font_conv/node_modules/debug/README.md | 455 + .../node_modules/debug/package.json | 111 + .../node_modules/debug/src/browser.js | 269 + .../node_modules/debug/src/common.js | 261 + .../node_modules/debug/src/index.js | 10 + .../node_modules/debug/src/node.js | 263 + .../node_modules/make-error/LICENSE | 5 + .../node_modules/make-error/README.md | 112 + .../make-error/dist/make-error.js | 1 + .../node_modules/make-error/index.d.ts | 47 + .../node_modules/make-error/index.js | 151 + .../node_modules/make-error/package.json | 95 + .../node_modules/mkdirp/CHANGELOG.md | 15 + .../lv_font_conv/node_modules/mkdirp/LICENSE | 21 + .../node_modules/mkdirp/bin/cmd.js | 68 + .../lv_font_conv/node_modules/mkdirp/index.js | 31 + .../node_modules/mkdirp/lib/find-made.js | 29 + .../node_modules/mkdirp/lib/mkdirp-manual.js | 64 + .../node_modules/mkdirp/lib/mkdirp-native.js | 39 + .../node_modules/mkdirp/lib/opts-arg.js | 23 + .../node_modules/mkdirp/lib/path-arg.js | 29 + .../node_modules/mkdirp/lib/use-native.js | 10 + .../node_modules/mkdirp/package.json | 78 + .../node_modules/mkdirp/readme.markdown | 266 + .../lv_font_conv/node_modules/ms/index.js | 162 + .../lv_font_conv/node_modules/ms/license.md | 21 + .../lv_font_conv/node_modules/ms/package.json | 72 + .../lv_font_conv/node_modules/ms/readme.md | 60 + .../node_modules/opentype.js/LICENSE | 20 + .../node_modules/opentype.js/README.md | 313 + .../node_modules/opentype.js/RELEASES.md | 267 + .../node_modules/opentype.js/bin/ot | 84 + .../node_modules/opentype.js/bin/server.js | 64 + .../node_modules/opentype.js/bin/test-render | 96 + .../node_modules/opentype.js/dist/opentype.js | 14254 ++++++++++++++++ .../node_modules/opentype.js/package.json | 102 + .../lv_font_conv/node_modules/pngjs/LICENSE | 20 + .../lv_font_conv/node_modules/pngjs/README.md | 433 + .../node_modules/pngjs/lib/bitmapper.js | 267 + .../node_modules/pngjs/lib/bitpacker.js | 158 + .../node_modules/pngjs/lib/chunkstream.js | 189 + .../node_modules/pngjs/lib/constants.js | 32 + .../node_modules/pngjs/lib/crc.js | 40 + .../node_modules/pngjs/lib/filter-pack.js | 171 + .../pngjs/lib/filter-parse-async.js | 24 + .../pngjs/lib/filter-parse-sync.js | 21 + .../node_modules/pngjs/lib/filter-parse.js | 177 + .../pngjs/lib/format-normaliser.js | 93 + .../node_modules/pngjs/lib/interlace.js | 95 + .../node_modules/pngjs/lib/packer-async.js | 50 + .../node_modules/pngjs/lib/packer-sync.js | 56 + .../node_modules/pngjs/lib/packer.js | 129 + .../node_modules/pngjs/lib/paeth-predictor.js | 16 + .../node_modules/pngjs/lib/parser-async.js | 169 + .../node_modules/pngjs/lib/parser-sync.js | 112 + .../node_modules/pngjs/lib/parser.js | 290 + .../node_modules/pngjs/lib/png-sync.js | 12 + .../node_modules/pngjs/lib/png.js | 194 + .../node_modules/pngjs/lib/sync-inflate.js | 168 + .../node_modules/pngjs/lib/sync-reader.js | 45 + .../node_modules/pngjs/package.json | 129 + .../LICENSE-MIT.txt | 20 + .../string.prototype.codepointat/README.md | 47 + .../codepointat.js | 54 + .../string.prototype.codepointat/package.json | 62 + .../node_modules/tiny-inflate/LICENSE | 21 + .../node_modules/tiny-inflate/index.js | 375 + .../node_modules/tiny-inflate/package.json | 60 + .../node_modules/tiny-inflate/readme.md | 31 + .../node_modules/tiny-inflate/test/index.js | 75 + .../node_modules/tiny-inflate/test/lorem.txt | 199 + node_modules/lv_font_conv/package.json | 61 + package-lock.json | 183 + package.json | 5 + src/CMakeLists.txt | 1 + src/displayapp/fonts/fonts.json | 2 +- src/displayapp/screens/AlarmIcon.cpp | 11 + src/displayapp/screens/AlarmIcon.h | 14 + src/displayapp/screens/Symbols.h | 1 + src/displayapp/screens/WatchFaceInfineat.cpp | 34 +- src/displayapp/screens/WatchFaceInfineat.h | 6 + 125 files changed, 32022 insertions(+), 5 deletions(-) create mode 100644 node_modules/.package-lock.json create mode 100644 node_modules/lv_font_conv/CHANGELOG.md create mode 100644 node_modules/lv_font_conv/LICENSE create mode 100644 node_modules/lv_font_conv/README.md create mode 100644 node_modules/lv_font_conv/lib/app_error.js create mode 100644 node_modules/lv_font_conv/lib/cli.js create mode 100644 node_modules/lv_font_conv/lib/collect_font_data.js create mode 100644 node_modules/lv_font_conv/lib/convert.js create mode 100644 node_modules/lv_font_conv/lib/font/cmap_build_subtables.js create mode 100644 node_modules/lv_font_conv/lib/font/compress.js create mode 100644 node_modules/lv_font_conv/lib/font/font.js create mode 100644 node_modules/lv_font_conv/lib/font/table_cmap.js create mode 100644 node_modules/lv_font_conv/lib/font/table_glyf.js create mode 100644 node_modules/lv_font_conv/lib/font/table_head.js create mode 100644 node_modules/lv_font_conv/lib/font/table_kern.js create mode 100644 node_modules/lv_font_conv/lib/font/table_loca.js create mode 100644 node_modules/lv_font_conv/lib/freetype/index.js create mode 100644 node_modules/lv_font_conv/lib/freetype/render.c create mode 100644 node_modules/lv_font_conv/lib/ranger.js create mode 100644 node_modules/lv_font_conv/lib/utils.js create mode 100644 node_modules/lv_font_conv/lib/writers/bin.js create mode 100644 node_modules/lv_font_conv/lib/writers/dump.js create mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/index.js create mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/lv_font.js create mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/lv_table_cmap.js create mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/lv_table_glyf.js create mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/lv_table_head.js create mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/lv_table_kern.js create mode 100755 node_modules/lv_font_conv/lv_font_conv.js create mode 100644 node_modules/lv_font_conv/node_modules/argparse/CHANGELOG.md create mode 100644 node_modules/lv_font_conv/node_modules/argparse/LICENSE create mode 100644 node_modules/lv_font_conv/node_modules/argparse/README.md create mode 100644 node_modules/lv_font_conv/node_modules/argparse/argparse.js create mode 100644 node_modules/lv_font_conv/node_modules/argparse/lib/sub.js create mode 100644 node_modules/lv_font_conv/node_modules/argparse/lib/textwrap.js create mode 100644 node_modules/lv_font_conv/node_modules/argparse/package.json create mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/.github/workflows/ci.yml create mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/LICENSE create mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/README.md create mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.d.ts create mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.js create mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/package.json create mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/test.js create mode 100644 node_modules/lv_font_conv/node_modules/debug/LICENSE create mode 100644 node_modules/lv_font_conv/node_modules/debug/README.md create mode 100644 node_modules/lv_font_conv/node_modules/debug/package.json create mode 100644 node_modules/lv_font_conv/node_modules/debug/src/browser.js create mode 100644 node_modules/lv_font_conv/node_modules/debug/src/common.js create mode 100644 node_modules/lv_font_conv/node_modules/debug/src/index.js create mode 100644 node_modules/lv_font_conv/node_modules/debug/src/node.js create mode 100644 node_modules/lv_font_conv/node_modules/make-error/LICENSE create mode 100644 node_modules/lv_font_conv/node_modules/make-error/README.md create mode 100644 node_modules/lv_font_conv/node_modules/make-error/dist/make-error.js create mode 100644 node_modules/lv_font_conv/node_modules/make-error/index.d.ts create mode 100644 node_modules/lv_font_conv/node_modules/make-error/index.js create mode 100644 node_modules/lv_font_conv/node_modules/make-error/package.json create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/CHANGELOG.md create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/LICENSE create mode 100755 node_modules/lv_font_conv/node_modules/mkdirp/bin/cmd.js create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/index.js create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/find-made.js create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-manual.js create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-native.js create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/opts-arg.js create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/path-arg.js create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/use-native.js create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/package.json create mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/readme.markdown create mode 100644 node_modules/lv_font_conv/node_modules/ms/index.js create mode 100644 node_modules/lv_font_conv/node_modules/ms/license.md create mode 100644 node_modules/lv_font_conv/node_modules/ms/package.json create mode 100644 node_modules/lv_font_conv/node_modules/ms/readme.md create mode 100644 node_modules/lv_font_conv/node_modules/opentype.js/LICENSE create mode 100644 node_modules/lv_font_conv/node_modules/opentype.js/README.md create mode 100644 node_modules/lv_font_conv/node_modules/opentype.js/RELEASES.md create mode 100755 node_modules/lv_font_conv/node_modules/opentype.js/bin/ot create mode 100755 node_modules/lv_font_conv/node_modules/opentype.js/bin/server.js create mode 100755 node_modules/lv_font_conv/node_modules/opentype.js/bin/test-render create mode 100644 node_modules/lv_font_conv/node_modules/opentype.js/dist/opentype.js create mode 100644 node_modules/lv_font_conv/node_modules/opentype.js/package.json create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/LICENSE create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/README.md create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/bitmapper.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/bitpacker.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/chunkstream.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/constants.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/crc.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/filter-pack.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-async.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-sync.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/format-normaliser.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/interlace.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/packer-async.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/packer-sync.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/packer.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/paeth-predictor.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/parser-async.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/parser-sync.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/parser.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/png-sync.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/png.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/sync-inflate.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/sync-reader.js create mode 100644 node_modules/lv_font_conv/node_modules/pngjs/package.json create mode 100644 node_modules/lv_font_conv/node_modules/string.prototype.codepointat/LICENSE-MIT.txt create mode 100644 node_modules/lv_font_conv/node_modules/string.prototype.codepointat/README.md create mode 100644 node_modules/lv_font_conv/node_modules/string.prototype.codepointat/codepointat.js create mode 100644 node_modules/lv_font_conv/node_modules/string.prototype.codepointat/package.json create mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/LICENSE create mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/index.js create mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/package.json create mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/readme.md create mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/test/index.js create mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/test/lorem.txt create mode 100644 node_modules/lv_font_conv/package.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/displayapp/screens/AlarmIcon.cpp create mode 100644 src/displayapp/screens/AlarmIcon.h diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json new file mode 100644 index 00000000..a519c455 --- /dev/null +++ b/node_modules/.package-lock.json @@ -0,0 +1,113 @@ +{ + "name": "InfiniTime", + "lockfileVersion": 2, + "requires": true, + "packages": { + "node_modules/lv_font_conv": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/lv_font_conv/-/lv_font_conv-1.5.2.tgz", + "integrity": "sha512-0UapRSTkVP/pnB8Z4r2HDHx5p2dJx/xUG1+14u/WXo59mwuC7BahR+Bnx/66jKoDrG1wFQwn9ZzoyMxRHOD9bg==", + "bundleDependencies": [ + "argparse", + "bit-buffer", + "debug", + "make-error", + "mkdirp", + "opentype.js", + "pngjs" + ], + "dependencies": { + "argparse": "^2.0.0", + "bit-buffer": "^0.2.5", + "debug": "^4.1.1", + "make-error": "^1.3.5", + "mkdirp": "^1.0.4", + "opentype.js": "^1.1.0", + "pngjs": "^6.0.0" + }, + "bin": { + "lv_font_conv": "lv_font_conv.js" + } + }, + "node_modules/lv_font_conv/node_modules/argparse": { + "version": "2.0.1", + "inBundle": true, + "license": "Python-2.0" + }, + "node_modules/lv_font_conv/node_modules/bit-buffer": { + "version": "0.2.5", + "inBundle": true, + "license": "MIT" + }, + "node_modules/lv_font_conv/node_modules/debug": { + "version": "4.3.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/lv_font_conv/node_modules/make-error": { + "version": "1.3.6", + "inBundle": true, + "license": "ISC" + }, + "node_modules/lv_font_conv/node_modules/mkdirp": { + "version": "1.0.4", + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lv_font_conv/node_modules/ms": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/lv_font_conv/node_modules/opentype.js": { + "version": "1.3.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "string.prototype.codepointat": "^0.2.1", + "tiny-inflate": "^1.0.3" + }, + "bin": { + "ot": "bin/ot" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/lv_font_conv/node_modules/pngjs": { + "version": "6.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/lv_font_conv/node_modules/string.prototype.codepointat": { + "version": "0.2.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/lv_font_conv/node_modules/tiny-inflate": { + "version": "1.0.3", + "inBundle": true, + "license": "MIT" + } + } +} diff --git a/node_modules/lv_font_conv/CHANGELOG.md b/node_modules/lv_font_conv/CHANGELOG.md new file mode 100644 index 00000000..94512228 --- /dev/null +++ b/node_modules/lv_font_conv/CHANGELOG.md @@ -0,0 +1,164 @@ +1.5.2 / 2021-07-18 +------------------ + +- Fixed lvgl version check for v8+, #64. + + +1.5.1 / 2021-04-06 +------------------ + +- Fixed fail of CMAP generation for edge cases, #62. +- Dev deps bump. + + +1.5.0 / 2021-03-08 +------------------ + +- More `const` in generated font (for v8+), #59. + + +1.4.1 / 2021-01-26 +------------------ + +- Fix charcodes padding in comments, #54. + + +1.4.0 / 2021-01-03 +------------------ + +- Added OTF fonts support. +- Added `--use-color-info` for limited multi-tone glyphs support. + + +1.3.1 / 2020-12-28 +------------------ + +- Unify `lvgl.h` include. +- Updated repo refs (littlevgl => lvgl). +- Deps bump. +- Moved CI to github actions. + + +1.3.0 / 2020-10-25 +------------------ + +- Drop `lodash` use. +- Deps bump. + + +1.2.1 / 2020-10-24 +------------------ + +- Reduced npm package size (drop unneeded files before publish). + + +1.2.0 / 2020-10-24 +------------------ + +- Bump FreeType to 2.10.4. +- Bundle dependencies to npm package. + + +1.1.3 / 2020-09-22 +------------------ + +- lvgl: added `LV_FONT_FMT_TXT_LARGE` check or very large fonts. + + +1.1.2 / 2020-08-23 +------------------ + +- Fix: skip `glyph.advanceWidth` for monospace fonts, #43. +- Spec fix: version size should be 4 bytes, #44. +- Spec fix: bbox x/y bits => unsigned, #45. +- Bump argparse. +- Cleanup help formatter. + + +1.1.1 / 2020-08-01 +------------------ + +- `--version` should show number from `package.json`. + + +1.1.0 / 2020-07-27 +------------------ + +- Added `post.underlinePosition` & `post.underlineThickness` info to font header. + + +1.0.0 / 2020-06-26 +------------------ + +- Maintenance release. +- Set package version 1.x, to label package as stable. +- Deps bump. + + +0.4.3 / 2020-03-05 +------------------ + +- Enabled `--bpp 8` mode. + + +0.4.2 / 2020-01-05 +------------------ + +- Added `--lv_include` option to set alternate `lvgl.h` path. +- Added guards to hide `.subpx` property for lvgl 6.0 (supported from 6.1 only), #32. +- Dev deps bump + + +0.4.1 / 2019-12-09 +------------------ + +- Allow memory growth for FreeType build, #29. +- Dev deps bump. +- Web build update. + + +0.4.0 / 2019-11-29 +------------------ + +- Note, this release is for lvgl 6.1 and has potentially breaking changes + (see below). If you have compatibility issues with lvgl 6.0 - use previous + versions or update your code. +- Spec change: added subpixels info field to font header (header size increased). +- Updated `bin` & `lvgl` writers to match new spec. +- lvgl: fixed data type for kerning values (needs appropriate update + in LittlevGL 6.1+). +- Fix errors display (disable emscripten error catcher). + + +0.3.1 / 2019-10-24 +------------------ + +- Fixed "out of range" error for big `--size`. + + +0.3.0 / 2019-10-12 +------------------ + +- Added beta options `--lcd` & `--lcd-v` for subpixel rendering (still need + header info update). +- Added FreeType data properties to dump info. +- Fixed glyph width (missed fractional part after switch to FreeType). +- Fixed missed sigh for negative X/Y bitmap offsets. +- Deps bump. + + +0.2.0 / 2019-09-26 +------------------ + +- Use FreeType renderer. Should solve all regressions, reported in 0.1.0. +- Enforced light autohinting (horizontal lines only). +- Use special hinter for monochrome output (improve quality). +- API changed to async. +- Fix: added missed `.bitmap_format` field to lvgl writer. +- Fix: changed struct fields init order to match declaration, #25. + + +0.1.0 / 2019-09-03 +------------------ + +- First release. diff --git a/node_modules/lv_font_conv/LICENSE b/node_modules/lv_font_conv/LICENSE new file mode 100644 index 00000000..4dc31bfa --- /dev/null +++ b/node_modules/lv_font_conv/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2018 authors + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/lv_font_conv/README.md b/node_modules/lv_font_conv/README.md new file mode 100644 index 00000000..6716ebd9 --- /dev/null +++ b/node_modules/lv_font_conv/README.md @@ -0,0 +1,150 @@ +lv_font_conv - font convertor to compact bitmap format +====================================================== + +[![CI](https://github.com/lvgl/lv_font_conv/workflows/CI/badge.svg?branch=master)](https://github.com/lvgl/lv_font_conv/actions) +[![NPM version](https://img.shields.io/npm/v/lv_font_conv.svg?style=flat)](https://www.npmjs.org/package/lv_font_conv) + +Converts TTF/WOFF/OTF fonts to __[compact format](https://github.com/lvgl/lv_font_conv/blob/master/doc/font_spec.md)__, suitable for small embedded systems. Main features are: + +- Allows bitonal and anti-aliased glyphs (1-4 bits per pixel). +- Preserves kerning info. +- Compression. +- Users can select required glyphs only (subsetting). +- Multiple font sources can be merged. +- Simple CLI interface, easy to integrate into external build systems. + + +## Install the script + +[node.js](https://nodejs.org/en/download/) v10+ required. + +Global install of the last version, execute as "lv_font_conv" + +```sh +# install release from npm registry +npm i lv_font_conv -g +# install from github's repo, master branch +npm i lvgl/lv_font_conv -g +``` + +**run via [npx](https://www.npmjs.com/package/npx) without install** + +```sh +# run from npm registry +npx lv_font_conv -h +# run from github master +npx github:lvgl/lv_font_conv -h +``` + +Note, runing via `npx` may take some time until modules installed, be patient. + + +## CLI params + +Common: + +- `--bpp` - bits per pixel (antialiasing). +- `--size` - output font size (pixels). +- `-o`, `--output` - output path (file or directory, depends on format). +- `--format` - output format. + - `--format dump` - dump glyph images and font info, useful for debug. + - `--format bin` - dump font in binary form (as described in [spec](https://github.com/lvgl/lv_font_conv/blob/master/doc/font_spec.md)). + - `--format lvgl` - dump font in [LittlevGL](https://github.com/lvgl/lvgl) format. +- `--force-fast-kern-format` - always use more fast kering storage format, + at cost of some size. If size difference appears, it will be displayed. +- `--lcd` - generate bitmaps with 3x horizontal resolution, for subpixel + smoothing. +- `--lcd-v` - generate bitmaps with 3x vertical resolution, for subpixel + smoothing. +- `--use-color-info` - try to use glyph color info from font to create + grayscale icons. Since gray tones are emulated via transparency, result + will be good on contrast background only. +- `--lv-include` - only with `--format lvgl`, set alternate path for `lvgl.h`. + +Per font: + +- `--font` - path to font file (ttf/woff/woff2/otf). May be used multiple time for + merge. +- `-r`, `--range` - single glyph or range + optional mapping, belongs to + previously declared `--font`. Can be used multiple times. Examples: + - `-r 0x1F450` - single value, dec or hex format. + - `-r 0x1F450-0x1F470` - range. + - `-r '0x1F450=>0xF005'` - single glyph with mapping. + - `-r '0x1F450-0x1F470=>0xF005'` - range with mapping. + - `-r 0x1F450 -r 0x1F451-0x1F470` - 2 ranges. + - `-r 0x1F450,0x1F451-0x1F470` - the same as above, but defined with single `-r`. +- `--symbols` - list of characters to copy (instead of numeric format in `-r`). + - `--symbols 0123456789.,` - extract chars to display numbers. +- `--autohint-off` - do not force autohinting ("light" is on by default). +- `--autohint-strong` - use more strong autohinting (will break kerning). + +Additional debug options: + +- `--no-compress` - disable built-in RLE compression. +- `--no-prefilter` - disable bitmap lines filter (XOR), used to improve + compression ratio. +- `--no-kerning` - drop kerning info to reduce size (not recommended). +- `--full-info` - don't shorten 'font_info.json' (include pixels data). + + +## Examples + +Merge english from Roboto Regular and icons from Font Awesome, and show debug +info: + +`env DEBUG=* lv_font_conv --font Roboto-Regular.ttf -r 0x20-0x7F --font FontAwesome.ttf -r 0xFE00=>0x81 --size 16 --format bin --bpp 3 --no-compress -o output.font` + +Merge english & russian from Roboto Regular, and show debug info: + +`env DEBUG=* lv_font_conv --font Roboto-Regular.ttf -r 0x20-0x7F -r 0x401,0x410-0x44F,0x451 --size 16 --format bin --bpp 3 --no-compress -o output.font` + +Dump all Roboto glyphs to inspect icons and font details: + +`lv_font_conv --font Roboto-Regular.ttf -r 0x20-0x7F --size 16 --format dump --bpp 3 -o ./dump` + +**Note**. Option `--no-compress` exists temporary, to avoid confusion until LVGL +adds compression support. + + +## Technical notes + +### Supported output formats + +1. **bin** - universal binary format, as described in https://github.com/lvgl/lv_font_conv/tree/master/doc. +2. **lvgl** - format for LittlevGL, C file. Has minor limitations and a bit + bigger size, because C does not allow to effectively define relative offsets + in data blocks. +3. **dump** - create folder with each glyph in separate image, and other font + data as `json`. Useful for debug. + +### Merged font metrics + +When multiple fonts merged into one, sources can have different metrics. Result +will follow principles below: + +1. No scaling. Glyphs will have exactly the same size, as intended by font authors. +2. The same baseline. +3. `OS/2` metrics (`sTypoAscender`, `sTypoDescender`, `sTypoLineGap`) will be + used from the first font in list. +4. `hhea` metrics (`ascender`, `descender`), defined as max/min point of all + font glyphs, are recalculated, according to new glyphs set. + + +## Development + +Current package includes WebAssembly build of FreeType with some helper +functions. Everything is wrapped into Docker and requires zero knowledge about +additional tools install. See `package.json` for additional commands. You may +need those if decide to upgrade FreeType or update helpers. + +This builds image with emscripten & freetype, usually should be done only once: + +``` +npm run build:dockerimage +``` + +This compiles helpers and creates WebAssembly files: + +``` +npm run build:freetype +``` diff --git a/node_modules/lv_font_conv/lib/app_error.js b/node_modules/lv_font_conv/lib/app_error.js new file mode 100644 index 00000000..98e9052e --- /dev/null +++ b/node_modules/lv_font_conv/lib/app_error.js @@ -0,0 +1,9 @@ +// Custom Error type to simplify error messaging +// +'use strict'; + + +//const ExtendableError = require('es6-error'); +//module.exports = class AppError extends ExtendableError {}; + +module.exports = require('make-error')('AppError'); diff --git a/node_modules/lv_font_conv/lib/cli.js b/node_modules/lv_font_conv/lib/cli.js new file mode 100644 index 00000000..a73a9b22 --- /dev/null +++ b/node_modules/lv_font_conv/lib/cli.js @@ -0,0 +1,318 @@ +// Parse input arguments and execute convertor + +'use strict'; + + +const argparse = require('argparse'); +const fs = require('fs'); +const mkdirp = require('mkdirp'); +const path = require('path'); +const convert = require('./convert'); + + +class ActionFontAdd extends argparse.Action { + call(parser, namespace, value/*, option_string*/) { + let items = (namespace[this.dest] || []).slice(); + items.push({ source_path: value, ranges: [] }); + namespace[this.dest] = items; + } +} + + +// add range or symbols to font; +// need to merge them into one array here so overrides work correctly +class ActionFontRangeAdd extends argparse.Action { + call(parser, namespace, value, option_string) { + let fonts = namespace.font || []; + + if (fonts.length === 0) { + parser.error(`argument ${option_string}: Only allowed after --font`); + } + + let lastFont = fonts[fonts.length - 1]; + + // { symbols: 'ABC' }, or { range: [ 65, 67, 65 ] } + lastFont.ranges.push({ [this.dest]: value }); + } +} + + +// add hinting option to font; +class ActionFontStoreTrue extends argparse.Action { + constructor(options) { + options = options || {}; + options.const = true; + options.default = options.default !== null ? options.default : false; + options.nargs = 0; + super(options); + } + + call(parser, namespace, value, option_string) { + let fonts = namespace.font || []; + + if (fonts.length === 0) { + parser.error(`argument ${option_string}: Only allowed after --font`); + } + + let lastFont = fonts[fonts.length - 1]; + + lastFont[this.dest] = this.const; + } +} + + +// Formatter with support of `\n` in Help texts. +class RawTextHelpFormatter2 extends argparse.RawDescriptionHelpFormatter { + // executes parent _split_lines for each line of the help, then flattens the result + _split_lines(text, width) { + return [].concat(...text.split('\n').map(line => super._split_lines(line, width))); + } +} + + +// parse decimal or hex code in unicode range +function unicode_point(str) { + let m = /^(?:(?:0x([0-9a-f]+))|([0-9]+))$/i.exec(str.trim()); + + if (!m) throw new TypeError(`${str} is not a number`); + + let [ , hex, dec ] = m; + + let value = hex ? parseInt(hex, 16) : parseInt(dec, 10); + + if (value > 0x10FFFF) throw new TypeError(`${str} is out of unicode range`); + + return value; +} + + +// parse range +function range(str) { + let result = []; + + for (let s of str.split(',')) { + let m = /^(.+?)(?:-(.+?))?(?:=>(.+?))?$/i.exec(s); + + let [ , start, end, mapped_start ] = m; + + if (!end) end = start; + if (!mapped_start) mapped_start = start; + + start = unicode_point(start); + end = unicode_point(end); + + if (start > end) throw new TypeError(`Invalid range: ${s}`); + + mapped_start = unicode_point(mapped_start); + + result.push(start, end, mapped_start); + } + + return result; +} + + +// exclude negative numbers and non-numbers +function positive_int(str) { + if (!/^\d+$/.test(str)) throw new TypeError(`${str} is not a valid number`); + + let n = parseInt(str, 10); + + if (n <= 0) throw new TypeError(`${str} is not a valid number`); + + return n; +} + + +module.exports.run = async function (argv, debug = false) { + + // + // Configure CLI + // + + let parser = new argparse.ArgumentParser({ + add_help: true, + formatter_class: RawTextHelpFormatter2 + }); + + if (debug) { + parser.exit = function (status, message) { + throw new Error(message); + }; + } + + parser.add_argument('-v', '--version', { + action: 'version', + version: require('../package.json').version + }); + + parser.add_argument('--size', { + metavar: 'PIXELS', + type: positive_int, + required: true, + help: 'Output font size, pixels.' + }); + + parser.add_argument('-o', '--output', { + metavar: '', + help: 'Output path.' + }); + + parser.add_argument('--bpp', { + choices: [ 1, 2, 3, 4, 8 ], + type: positive_int, + required: true, + help: 'Bits per pixel, for antialiasing.' + }); + + let lcd_group = parser.add_mutually_exclusive_group(); + + lcd_group.add_argument('--lcd', { + action: 'store_true', + default: false, + help: 'Enable subpixel rendering (horizontal pixel layout).' + }); + + lcd_group.add_argument('--lcd-v', { + action: 'store_true', + default: false, + help: 'Enable subpixel rendering (vertical pixel layout).' + }); + + parser.add_argument('--use-color-info', { + dest: 'use_color_info', + action: 'store_true', + default: false, + help: 'Try to use glyph color info from font to create grayscale icons. ' + + 'Since gray tones are emulated via transparency, result will be good on contrast background only.' + }); + + parser.add_argument('--format', { + choices: convert.formats, + required: true, + help: 'Output format.' + }); + + parser.add_argument('--font', { + metavar: '', + action: ActionFontAdd, + required: true, + help: 'Source font path. Can be used multiple times to merge glyphs from different fonts.' + }); + + parser.add_argument('-r', '--range', { + type: range, + action: ActionFontRangeAdd, + help: ` +Range of glyphs to copy. Can be used multiple times, belongs to previously declared "--font". Examples: + -r 0x1F450 + -r 0x20-0x7F + -r 32-127 + -r 32-127,0x1F450 + -r '0x1F450=>0xF005' + -r '0x1F450-0x1F470=>0xF005' +` + }); + + parser.add_argument('--symbols', { + action: ActionFontRangeAdd, + help: ` +List of characters to copy, belongs to previously declared "--font". Examples: + --symbols ,.0123456789 + --symbols abcdefghigklmnopqrstuvwxyz +` + }); + + parser.add_argument('--autohint-off', { + type: range, + action: ActionFontStoreTrue, + help: 'Disable autohinting for previously declared "--font"' + }); + + parser.add_argument('--autohint-strong', { + type: range, + action: ActionFontStoreTrue, + help: 'Use more strong autohinting for previously declared "--font" (will break kerning)' + }); + + parser.add_argument('--force-fast-kern-format', { + dest: 'fast_kerning', + action: 'store_true', + default: false, + help: 'Always use kern classes instead of pairs (might be larger but faster).' + }); + + parser.add_argument('--no-compress', { + dest: 'no_compress', + action: 'store_true', + default: false, + help: 'Disable built-in RLE compression.' + }); + + parser.add_argument('--no-prefilter', { + dest: 'no_prefilter', + action: 'store_true', + default: false, + help: 'Disable bitmap lines filter (XOR), used to improve compression ratio.' + }); + + parser.add_argument('--no-kerning', { + dest: 'no_kerning', + action: 'store_true', + default: false, + help: 'Drop kerning info to reduce size (not recommended).' + }); + + parser.add_argument('--lv-include', { + metavar: '', + help: 'Set alternate "lvgl.h" path (for --format lvgl).' + }); + + parser.add_argument('--full-info', { + dest: 'full_info', + action: 'store_true', + default: false, + help: 'Don\'t shorten "font_info.json" (include pixels data).' + }); + + // + // Process CLI options + // + + let args = parser.parse_args(argv.length ? argv : [ '-h' ]); + + for (let font of args.font) { + if (font.ranges.length === 0) { + parser.error(`You need to specify either "--range" or "--symbols" for font "${font.source_path}"`); + } + + try { + font.source_bin = fs.readFileSync(font.source_path); + } catch (err) { + parser.error(`Cannot read file "${font.source_path}": ${err.message}`); + } + } + + // + // Convert + // + + let files = await convert(args); + + // + // Store files + // + + for (let [ filename, data ] of Object.entries(files)) { + let dir = path.dirname(filename); + + mkdirp.sync(dir); + + fs.writeFileSync(filename, data); + } + +}; + + +// export for tests +module.exports._range = range; diff --git a/node_modules/lv_font_conv/lib/collect_font_data.js b/node_modules/lv_font_conv/lib/collect_font_data.js new file mode 100644 index 00000000..227c5835 --- /dev/null +++ b/node_modules/lv_font_conv/lib/collect_font_data.js @@ -0,0 +1,173 @@ +// Read fonts + +'use strict'; + + +const opentype = require('opentype.js'); +const ft_render = require('./freetype'); +const AppError = require('./app_error'); +const Ranger = require('./ranger'); + + +module.exports = async function collect_font_data(args) { + await ft_render.init(); + + // Duplicate font options as k/v for quick access + let fonts_options = {}; + args.font.forEach(f => { fonts_options[f.source_path] = f; }); + + // read fonts + let fonts_opentype = {}; + let fonts_freetype = {}; + + for (let { source_path, source_bin } of args.font) { + // don't load font again if it's specified multiple times in args + if (fonts_opentype[source_path]) continue; + + try { + let b = source_bin; + + if (Buffer.isBuffer(b)) { + // node.js Buffer -> ArrayBuffer + b = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength); + } + + fonts_opentype[source_path] = opentype.parse(b); + } catch (err) { + throw new AppError(`Cannot load font "${source_path}": ${err.message}`); + } + + fonts_freetype[source_path] = ft_render.fontface_create(source_bin, args.size); + } + + // merge all ranges + let ranger = new Ranger(); + + for (let { source_path, ranges } of args.font) { + let font = fonts_freetype[source_path]; + + for (let item of ranges) { + /* eslint-disable max-depth */ + if (item.range) { + for (let i = 0; i < item.range.length; i += 3) { + let range = item.range.slice(i, i + 3); + let chars = ranger.add_range(source_path, ...range); + let is_empty = true; + + for (let code of chars) { + if (ft_render.glyph_exists(font, code)) { + is_empty = false; + break; + } + } + + if (is_empty) { + let a = '0x' + range[0].toString(16); + let b = '0x' + range[1].toString(16); + throw new AppError(`Font "${source_path}" doesn't have any characters included in range ${a}-${b}`); + } + } + } + + if (item.symbols) { + let chars = ranger.add_symbols(source_path, item.symbols); + let is_empty = true; + + for (let code of chars) { + if (ft_render.glyph_exists(font, code)) { + is_empty = false; + break; + } + } + + if (is_empty) { + throw new AppError(`Font "${source_path}" doesn't have any characters included in "${item.symbols}"`); + } + } + } + } + + let mapping = ranger.get(); + let glyphs = []; + let all_dst_charcodes = Object.keys(mapping).sort((a, b) => a - b).map(Number); + + for (let dst_code of all_dst_charcodes) { + let src_code = mapping[dst_code].code; + let src_font = mapping[dst_code].font; + + if (!ft_render.glyph_exists(fonts_freetype[src_font], src_code)) continue; + + let ft_result = ft_render.glyph_render( + fonts_freetype[src_font], + src_code, + { + autohint_off: fonts_options[src_font].autohint_off, + autohint_strong: fonts_options[src_font].autohint_strong, + lcd: args.lcd, + lcd_v: args.lcd_v, + mono: !args.lcd && !args.lcd_v && args.bpp === 1, + use_color_info: args.use_color_info + } + ); + + glyphs.push({ + code: dst_code, + advanceWidth: ft_result.advance_x, + bbox: { + x: ft_result.x, + y: ft_result.y - ft_result.height, + width: ft_result.width, + height: ft_result.height + }, + kerning: {}, + freetype: ft_result.freetype, + pixels: ft_result.pixels + }); + } + + if (!args.no_kerning) { + let existing_dst_charcodes = glyphs.map(g => g.code); + + for (let { code, kerning } of glyphs) { + let src_code = mapping[code].code; + let src_font = mapping[code].font; + let font = fonts_opentype[src_font]; + let glyph = font.charToGlyph(String.fromCodePoint(src_code)); + + for (let dst_code2 of existing_dst_charcodes) { + // can't merge kerning values from 2 different fonts + if (mapping[dst_code2].font !== src_font) continue; + + let src_code2 = mapping[dst_code2].code; + let glyph2 = font.charToGlyph(String.fromCodePoint(src_code2)); + let krn_value = font.getKerningValue(glyph, glyph2); + + if (krn_value) kerning[dst_code2] = krn_value * args.size / font.unitsPerEm; + + //let krn_value = ft_render.get_kerning(font, src_code, src_code2).x; + //if (krn_value) kerning[dst_code2] = krn_value; + } + } + } + + let first_font = fonts_freetype[args.font[0].source_path]; + let first_font_scale = args.size / first_font.units_per_em; + let os2_metrics = ft_render.fontface_os2_table(first_font); + let post_table = fonts_opentype[args.font[0].source_path].tables.post; + + for (let font of Object.values(fonts_freetype)) ft_render.fontface_destroy(font); + + ft_render.destroy(); + + return { + ascent: Math.max(...glyphs.map(g => g.bbox.y + g.bbox.height)), + descent: Math.min(...glyphs.map(g => g.bbox.y)), + typoAscent: Math.round(os2_metrics.typoAscent * first_font_scale), + typoDescent: Math.round(os2_metrics.typoDescent * first_font_scale), + typoLineGap: Math.round(os2_metrics.typoLineGap * first_font_scale), + size: args.size, + glyphs, + underlinePosition: Math.round(post_table.underlinePosition * first_font_scale), + underlineThickness: Math.round(post_table.underlineThickness * first_font_scale) + }; +}; diff --git a/node_modules/lv_font_conv/lib/convert.js b/node_modules/lv_font_conv/lib/convert.js new file mode 100644 index 00000000..0cf088a0 --- /dev/null +++ b/node_modules/lv_font_conv/lib/convert.js @@ -0,0 +1,30 @@ +// Internal API to convert input data into output font data +// Used by both CLI and Web wrappers. +'use strict'; + +const collect_font_data = require('./collect_font_data'); + +let writers = { + dump: require('./writers/dump'), + bin: require('./writers/bin'), + lvgl: require('./writers/lvgl') +}; + + +// +// Input: +// - args like from CLI (optionally extended with binary content of files) +// +// Output: +// - { name1: bin_data1, name2: bin_data2, ... } +// +// returns hash with files to write +// +module.exports = async function convert(args) { + let font_data = await collect_font_data(args); + let files = writers[args.format](args, font_data); + + return files; +}; + +module.exports.formats = Object.keys(writers); diff --git a/node_modules/lv_font_conv/lib/font/cmap_build_subtables.js b/node_modules/lv_font_conv/lib/font/cmap_build_subtables.js new file mode 100644 index 00000000..c80a219e --- /dev/null +++ b/node_modules/lv_font_conv/lib/font/cmap_build_subtables.js @@ -0,0 +1,105 @@ +// Find an optimal configuration of cmap tables representing set of codepoints, +// using simple breadth-first algorithm +// +// Assume that: +// - codepoints have one-to-one correspondence to glyph ids +// - glyph ids are always bigger for bigger codepoints +// - glyph ids are always consecutive (1..N without gaps) +// +// This way we can omit glyph ids from all calculations entirely: if codepoints +// fit in format0, then glyph ids also will. +// +// format6 is not considered, because if glyph ids can be delta-coded, +// multiple format0 tables are guaranteed to be smaller than a single format6. +// +// sparse format is not used because as long as glyph ids are consecutive, +// sparse_tiny will always be preferred. +// + +'use strict'; + + +function estimate_format0_tiny_size(/*start_code, end_code*/) { + return 16; +} + +function estimate_format0_size(start_code, end_code) { + return 16 + (end_code - start_code + 1); +} + +//function estimate_sparse_size(count) { +// return 16 + count * 4; +//} + +function estimate_sparse_tiny_size(count) { + return 16 + count * 2; +} + +module.exports = function cmap_split(all_codepoints) { + all_codepoints = all_codepoints.sort((a, b) => a - b); + + let min_paths = []; + + for (let i = 0; i < all_codepoints.length; i++) { + let min = { dist: Infinity }; + + for (let j = 0; j <= i; j++) { + let prev_dist = (j - 1 >= 0) ? min_paths[j - 1].dist : 0; + let s; + + if (all_codepoints[i] - all_codepoints[j] < 256) { + s = estimate_format0_size(all_codepoints[j], all_codepoints[i]); + + /* eslint-disable max-depth */ + if (prev_dist + s < min.dist) { + min = { + dist: prev_dist + s, + start: j, + end: i, + format: 'format0' + }; + } + } + + if (all_codepoints[i] - all_codepoints[j] < 256 && all_codepoints[i] - i === all_codepoints[j] - j) { + s = estimate_format0_tiny_size(all_codepoints[j], all_codepoints[i]); + + /* eslint-disable max-depth */ + if (prev_dist + s < min.dist) { + min = { + dist: prev_dist + s, + start: j, + end: i, + format: 'format0_tiny' + }; + } + } + + // tiny sparse will always be preferred over full sparse because glyph ids are consecutive + if (all_codepoints[i] - all_codepoints[j] < 65536) { + s = estimate_sparse_tiny_size(i - j + 1); + + if (prev_dist + s < min.dist) { + min = { + dist: prev_dist + s, + start: j, + end: i, + format: 'sparse_tiny' + }; + } + } + } + + min_paths[i] = min; + } + + let result = []; + + for (let i = all_codepoints.length - 1; i >= 0;) { + let path = min_paths[i]; + result.unshift([ path.format, all_codepoints.slice(path.start, path.end + 1) ]); + i = path.start - 1; + } + + return result; +}; diff --git a/node_modules/lv_font_conv/lib/font/compress.js b/node_modules/lv_font_conv/lib/font/compress.js new file mode 100644 index 00000000..aa30a3a1 --- /dev/null +++ b/node_modules/lv_font_conv/lib/font/compress.js @@ -0,0 +1,107 @@ +'use strict'; + +//const debug = require('debug')('compress'); + +function count_same(arr, offset) { + let same = 1; + let val = arr[offset]; + + for (let i = offset + 1; i < arr.length; i++) { + if (arr[i] !== val) break; + same++; + } + + return same; +} + +// +// Compress pixels with RLE-like algorythm (modified I3BN) +// +// 1. Require minimal repeat count (1) to enter I3BN mode +// 2. Increased 1-bit-replaced repeat limit (2 => 10) +// 3. Length of direct repetition counter reduced (8 => 6 bits). +// +// pixels - flat array of pixels (one per entry) +// options.bpp - bits per pixels +// +module.exports = function compress(bitStream, pixels, options) { + const opts = Object.assign({}, { repeat: 1 }, options); + + // Minimal repetitions count to enable RLE mode. + const RLE_SKIP_COUNT = 1; + // Number of repeats, when `1` used to replace data + // If more - write as number + const RLE_BIT_COLLAPSED_COUNT = 10; + + const RLE_COUNTER_BITS = 6; // (2^bits - 1) - max value + const RLE_COUNTER_MAX = (1 << RLE_COUNTER_BITS) - 1; + // Force flush if counter dencity exceeded. + const RLE_MAX_REPEATS = RLE_COUNTER_MAX + RLE_BIT_COLLAPSED_COUNT + 1; + + //let bits_start_offset = bitStream.index; + + let offset = 0; + + while (offset < pixels.length) { + const p = pixels[offset]; + + let same = count_same(pixels, offset); + + // Clamp value because RLE counter density is limited + if (same > RLE_MAX_REPEATS + RLE_SKIP_COUNT) { + same = RLE_MAX_REPEATS + RLE_SKIP_COUNT; + } + + //debug(`offset: ${offset}, count: ${same}, pixel: ${p}`); + + offset += same; + + // If not enough for RLE - write as is. + if (same <= RLE_SKIP_COUNT) { + for (let i = 0; i < same; i++) { + bitStream.writeBits(p, opts.bpp); + //debug(`==> ${opts.bpp} bits`); + } + continue; + } + + // First, write "skipped" head as is. + for (let i = 0; i < RLE_SKIP_COUNT; i++) { + bitStream.writeBits(p, opts.bpp); + //debug(`==> ${opts.bpp} bits`); + } + + same -= RLE_SKIP_COUNT; + + // Not reached state to use counter => dump bit-extended + if (same <= RLE_BIT_COLLAPSED_COUNT) { + bitStream.writeBits(p, opts.bpp); + //debug(`==> ${opts.bpp} bits (val)`); + for (let i = 0; i < same; i++) { + /*eslint-disable max-depth*/ + if (i < same - 1) { + bitStream.writeBits(1, 1); + //debug('==> 1 bit (rle repeat)'); + } else { + bitStream.writeBits(0, 1); + //debug('==> 1 bit (rle repeat last)'); + } + } + continue; + } + + same -= RLE_BIT_COLLAPSED_COUNT + 1; + + bitStream.writeBits(p, opts.bpp); + //debug(`==> ${opts.bpp} bits (val)`); + + for (let i = 0; i < RLE_BIT_COLLAPSED_COUNT + 1; i++) { + bitStream.writeBits(1, 1); + //debug('==> 1 bit (rle repeat)'); + } + bitStream.writeBits(same, RLE_COUNTER_BITS); + //debug(`==> 4 bits (rle repeat count ${same})`); + } + + //debug(`output bits: ${bitStream.index - bits_start_offset}`); +}; diff --git a/node_modules/lv_font_conv/lib/font/font.js b/node_modules/lv_font_conv/lib/font/font.js new file mode 100644 index 00000000..e5743629 --- /dev/null +++ b/node_modules/lv_font_conv/lib/font/font.js @@ -0,0 +1,131 @@ +// Font class to generate tables +'use strict'; + +const u = require('../utils'); +const debug = require('debug')('font'); +const Head = require('./table_head'); +const Cmap = require('./table_cmap'); +const Glyf = require('./table_glyf'); +const Loca = require('./table_loca'); +const Kern = require('./table_kern'); + +class Font { + constructor(fontData, options) { + this.src = fontData; + + this.opts = options; + + // Map chars to IDs (zero is reserved) + this.glyph_id = { 0: 0 }; + + this.last_id = 1; + this.createIDs(); + debug(`last_id: ${this.last_id}`); + + this.init_tables(); + + this.minY = Math.min(...this.src.glyphs.map(g => g.bbox.y)); + debug(`minY: ${this.minY}`); + this.maxY = Math.max(...this.src.glyphs.map(g => g.bbox.y + g.bbox.height)); + debug(`maxY: ${this.maxY}`); + + // 0 => 1 byte, 1 => 2 bytes + this.glyphIdFormat = Math.max(...Object.values(this.glyph_id)) > 255 ? 1 : 0; + debug(`glyphIdFormat: ${this.glyphIdFormat}`); + + // 1.0 by default, will be stored in font as FP12.4 + this.kerningScale = 1.0; + let kerningMax = Math.max(...this.src.glyphs.map(g => Object.values(g.kerning).map(Math.abs)).flat()); + if (kerningMax >= 7.5) this.kerningScale = Math.ceil(kerningMax / 7.5 * 16) / 16; + debug(`kerningScale: ${this.kerningScale}`); + + // 0 => int, 1 => FP4 + this.advanceWidthFormat = this.hasKerning() ? 1 : 0; + debug(`advanceWidthFormat: ${this.advanceWidthFormat}`); + + this.xy_bits = Math.max(...this.src.glyphs.map(g => Math.max( + u.signed_bits(g.bbox.x), u.signed_bits(g.bbox.y) + ))); + debug(`xy_bits: ${this.xy_bits}`); + + this.wh_bits = Math.max(...this.src.glyphs.map(g => Math.max( + u.unsigned_bits(g.bbox.width), u.unsigned_bits(g.bbox.height) + ))); + debug(`wh_bits: ${this.wh_bits}`); + + this.advanceWidthBits = Math.max(...this.src.glyphs.map( + g => u.signed_bits(this.widthToInt(g.advanceWidth)) + )); + debug(`advanceWidthBits: ${this.advanceWidthBits}`); + + let glyphs = this.src.glyphs; + + this.monospaced = glyphs.every((v, i, arr) => v.advanceWidth === arr[0].advanceWidth); + debug(`monospaced: ${this.monospaced}`); + + // This should stay in the end, because depends on previous variables + // 0 => 2 bytes, 1 => 4 bytes + this.indexToLocFormat = this.glyf.getSize() > 65535 ? 1 : 0; + debug(`indexToLocFormat: ${this.indexToLocFormat}`); + + this.subpixels_mode = options.lcd ? 1 : (options.lcd_v ? 2 : 0); + debug(`subpixels_mode: ${this.subpixels_mode}`); + } + + init_tables() { + this.head = new Head(this); + this.glyf = new Glyf(this); + this.cmap = new Cmap(this); + this.loca = new Loca(this); + this.kern = new Kern(this); + } + + createIDs() { + // Simplified, don't check dupes + this.last_id = 1; + + for (let i = 0; i < this.src.glyphs.length; i++) { + // reserve zero for special cases + this.glyph_id[this.src.glyphs[i].code] = this.last_id; + this.last_id++; + } + } + + hasKerning() { + if (this.opts.no_kerning) return false; + + for (let glyph of this.src.glyphs) { + if (glyph.kerning && Object.keys(glyph.kerning).length) return true; + } + return false; + } + + // Returns integer width, depending on format + widthToInt(val) { + if (this.advanceWidthFormat === 0) return Math.round(val); + + return Math.round(val * 16); + } + + // Convert kerning to FP4.4, useable for writer. Apply `kerningScale`. + kernToFP(val) { + return Math.round(val / this.kerningScale * 16); + } + + toBin() { + const result = Buffer.concat([ + this.head.toBin(), + this.cmap.toBin(), + this.loca.toBin(), + this.glyf.toBin(), + this.kern.toBin() + ]); + + debug(`font size: ${result.length}`); + + return result; + } +} + + +module.exports = Font; diff --git a/node_modules/lv_font_conv/lib/font/table_cmap.js b/node_modules/lv_font_conv/lib/font/table_cmap.js new file mode 100644 index 00000000..8e94bde1 --- /dev/null +++ b/node_modules/lv_font_conv/lib/font/table_cmap.js @@ -0,0 +1,201 @@ +'use strict'; + + +const build_subtables = require('./cmap_build_subtables'); +const u = require('../utils'); +const debug = require('debug')('font.table.cmap'); + + +const O_SIZE = 0; +const O_LABEL = O_SIZE + 4; +const O_COUNT = O_LABEL + 4; + +const HEAD_LENGTH = O_COUNT + 4; + +const SUB_FORMAT_0 = 0; +const SUB_FORMAT_0_TINY = 2; +const SUB_FORMAT_SPARSE = 1; +const SUB_FORMAT_SPARSE_TINY = 3; + + +class Cmap { + constructor(font) { + this.font = font; + this.label = 'cmap'; + + this.sub_heads = []; + this.sub_data = []; + + this.compiled = false; + } + + compile() { + if (this.compiled) return; + this.compiled = true; + + const f = this.font; + + let subtables_plan = build_subtables(f.src.glyphs.map(g => g.code)); + + const count_format0 = subtables_plan.filter(s => s[0] === 'format0').length; + const count_sparse = subtables_plan.length - count_format0; + debug(`${subtables_plan.length} subtable(s): ${count_format0} "format 0", ${count_sparse} "sparse"`); + + for (let [ format, codepoints ] of subtables_plan) { + let g = this.glyphByCode(codepoints[0]); + let start_glyph_id = f.glyph_id[g.code]; + let min_code = codepoints[0]; + let max_code = codepoints[codepoints.length - 1]; + let entries_count = max_code - min_code + 1; + let format_code = 0; + + if (format === 'format0_tiny') { + format_code = SUB_FORMAT_0_TINY; + this.sub_data.push(Buffer.alloc(0)); + } else if (format === 'format0') { + format_code = SUB_FORMAT_0; + this.sub_data.push(this.create_format0_data(min_code, max_code, start_glyph_id)); + } else if (format === 'sparse_tiny') { + entries_count = codepoints.length; + format_code = SUB_FORMAT_SPARSE_TINY; + this.sub_data.push(this.create_sparse_tiny_data(codepoints, start_glyph_id)); + } else { // assume format === 'sparse' + entries_count = codepoints.length; + format_code = SUB_FORMAT_SPARSE; + this.sub_data.push(this.create_sparse_data(codepoints, start_glyph_id)); + } + + this.sub_heads.push(this.createSubHeader( + min_code, + max_code - min_code + 1, + start_glyph_id, + entries_count, + format_code + )); + } + + this.subHeaderUpdateAllOffsets(); + } + + createSubHeader(rangeStart, rangeLen, glyphIdOffset, total, type) { + const buf = Buffer.alloc(16); + + // buf.writeUInt32LE(offset, 0); offset unknown at this moment + buf.writeUInt32LE(rangeStart, 4); + buf.writeUInt16LE(rangeLen, 8); + buf.writeUInt16LE(glyphIdOffset, 10); + buf.writeUInt16LE(total, 12); + buf.writeUInt8(type, 14); + + return buf; + } + + subHeaderUpdateOffset(header, val) { + header.writeUInt32LE(val, 0); + } + + subHeaderUpdateAllOffsets() { + for (let i = 0; i < this.sub_heads.length; i++) { + const offset = HEAD_LENGTH + + u.sum(this.sub_heads.map(h => h.length)) + + u.sum(this.sub_data.slice(0, i).map(d => d.length)); + + this.subHeaderUpdateOffset(this.sub_heads[i], offset); + } + } + + glyphByCode(code) { + for (let g of this.font.src.glyphs) { + if (g.code === code) return g; + } + + return null; + } + + + collect_format0_data(min_code, max_code, start_glyph_id) { + let data = []; + + for (let i = min_code; i <= max_code; i++) { + const g = this.glyphByCode(i); + + if (!g) { + data.push(0); + continue; + } + + const id_delta = this.font.glyph_id[g.code] - start_glyph_id; + + if (id_delta < 0 || id_delta > 255) throw new Error('Glyph ID delta out of Format 0 range'); + + data.push(id_delta); + } + + return data; + } + + create_format0_data(min_code, max_code, start_glyph_id) { + const data = this.collect_format0_data(min_code, max_code, start_glyph_id); + + return u.balign4(Buffer.from(data)); + } + + collect_sparse_data(codepoints, start_glyph_id) { + let codepoints_list = []; + let ids_list = []; + + for (let code of codepoints) { + let g = this.glyphByCode(code); + let id = this.font.glyph_id[g.code]; + + let code_delta = code - codepoints[0]; + let id_delta = id - start_glyph_id; + + if (code_delta < 0 || code_delta > 65535) throw new Error('Codepoint delta out of range'); + if (id_delta < 0 || id_delta > 65535) throw new Error('Glyph ID delta out of range'); + + codepoints_list.push(code_delta); + ids_list.push(id_delta); + } + + return { + codes: codepoints_list, + ids: ids_list + }; + } + + create_sparse_data(codepoints, start_glyph_id) { + const data = this.collect_sparse_data(codepoints, start_glyph_id); + + return u.balign4(Buffer.concat([ + u.bFromA16(data.codes), + u.bFromA16(data.ids) + ])); + } + + create_sparse_tiny_data(codepoints, start_glyph_id) { + const data = this.collect_sparse_data(codepoints, start_glyph_id); + + return u.balign4(u.bFromA16(data.codes)); + } + + toBin() { + if (!this.compiled) this.compile(); + + const buf = Buffer.concat([ + Buffer.alloc(HEAD_LENGTH), + Buffer.concat(this.sub_heads), + Buffer.concat(this.sub_data) + ]); + debug(`table size = ${buf.length}`); + + buf.writeUInt32LE(buf.length, O_SIZE); + buf.write(this.label, O_LABEL); + buf.writeUInt32LE(this.sub_heads.length, O_COUNT); + + return buf; + } +} + + +module.exports = Cmap; diff --git a/node_modules/lv_font_conv/lib/font/table_glyf.js b/node_modules/lv_font_conv/lib/font/table_glyf.js new file mode 100644 index 00000000..e7a62999 --- /dev/null +++ b/node_modules/lv_font_conv/lib/font/table_glyf.js @@ -0,0 +1,147 @@ +'use strict'; + +const u = require('../utils'); +const { BitStream } = require('bit-buffer'); +const debug = require('debug')('font.table.glyf'); +const compress = require('./compress'); + + +const O_SIZE = 0; +const O_LABEL = O_SIZE + 4; + +const HEAD_LENGTH = O_LABEL + 4; + + +class Glyf { + constructor(font) { + this.font = font; + this.label = 'glyf'; + + this.compiled = false; + + this.binData = []; + } + + // convert 8-bit opacity to bpp-bit + pixelsToBpp(pixels) { + const bpp = this.font.opts.bpp; + return pixels.map(line => line.map(p => (p >>> (8 - bpp)))); + } + + // Returns "binary stream" (Buffer) of compiled glyph data + compileGlyph(glyph) { + // Allocate memory, enough for eny storage formats + const buf = Buffer.alloc(100 + glyph.bbox.width * glyph.bbox.height * 4); + const bs = new BitStream(buf); + bs.bigEndian = true; + const f = this.font; + + // Store Width + if (!f.monospaced) { + let w = f.widthToInt(glyph.advanceWidth); + bs.writeBits(w, f.advanceWidthBits); + } + + // Store X, Y + bs.writeBits(glyph.bbox.x, f.xy_bits); + bs.writeBits(glyph.bbox.y, f.xy_bits); + bs.writeBits(glyph.bbox.width, f.wh_bits); + bs.writeBits(glyph.bbox.height, f.wh_bits); + + const pixels = this.pixelsToBpp(glyph.pixels); + + this.storePixels(bs, pixels); + + // Shrink size + const result = Buffer.alloc(bs.byteIndex); + buf.copy(result, 0, 0, bs.byteIndex); + + return result; + } + + storePixels(bitStream, pixels) { + if (this.getCompressionCode() === 0) this.storePixelsRaw(bitStream, pixels); + else this.storePixelsCompressed(bitStream, pixels); + } + + storePixelsRaw(bitStream, pixels) { + const bpp = this.font.opts.bpp; + + for (let y = 0; y < pixels.length; y++) { + const line = pixels[y]; + for (let x = 0; x < line.length; x++) { + bitStream.writeBits(line[x], bpp); + } + } + } + + storePixelsCompressed(bitStream, pixels) { + let p; + + if (this.font.opts.no_prefilter) p = pixels.flat(); + else p = u.prefilter(pixels).flat(); + + compress(bitStream, p, this.font.opts); + } + + // Create internal struct with binary data for each glyph + // Needed to calculate offsets & build final result + compile() { + this.compiled = true; + + this.binData = [ + Buffer.alloc(0) // Reserve id 0 + ]; + + const f = this.font; + + f.src.glyphs.forEach(g => { + const id = f.glyph_id[g.code]; + + this.binData[id] = this.compileGlyph(g); + }); + } + + toBin() { + if (!this.compiled) this.compile(); + + const buf = u.balign4(Buffer.concat([ + Buffer.alloc(HEAD_LENGTH), + Buffer.concat(this.binData) + ])); + + buf.writeUInt32LE(buf.length, O_SIZE); + buf.write(this.label, O_LABEL); + + debug(`table size = ${buf.length}`); + + return buf; + } + + getSize() { + if (!this.compiled) this.compile(); + + return u.align4(HEAD_LENGTH + u.sum(this.binData.map(b => b.length))); + } + + getOffset(id) { + if (!this.compiled) this.compile(); + + let offset = HEAD_LENGTH; + + for (let i = 0; i < id; i++) offset += this.binData[i].length; + + return offset; + } + + getCompressionCode() { + if (this.font.opts.no_compress) return 0; + if (this.font.opts.bpp === 1) return 0; + + if (this.font.opts.no_prefilter) return 2; + return 1; + } +} + + +module.exports = Glyf; diff --git a/node_modules/lv_font_conv/lib/font/table_head.js b/node_modules/lv_font_conv/lib/font/table_head.js new file mode 100644 index 00000000..4cb9f676 --- /dev/null +++ b/node_modules/lv_font_conv/lib/font/table_head.js @@ -0,0 +1,99 @@ +'use strict'; + + +const u = require('../utils'); +const debug = require('debug')('font.table.head'); + +const O_SIZE = 0; +const O_LABEL = O_SIZE + 4; +const O_VERSION = O_LABEL + 4; +const O_TABLES = O_VERSION + 4; +const O_FONT_SIZE = O_TABLES + 2; +const O_ASCENT = O_FONT_SIZE + 2; +const O_DESCENT = O_ASCENT + 2; +const O_TYPO_ASCENT = O_DESCENT + 2; +const O_TYPO_DESCENT = O_TYPO_ASCENT + 2; +const O_TYPO_LINE_GAP = O_TYPO_DESCENT + 2; +const O_MIN_Y = O_TYPO_LINE_GAP + 2; +const O_MAX_Y = O_MIN_Y + 2; +const O_DEF_ADVANCE_WIDTH = O_MAX_Y + 2; +const O_KERNING_SCALE = O_DEF_ADVANCE_WIDTH + 2; +const O_INDEX_TO_LOC_FORMAT = O_KERNING_SCALE + 2; +const O_GLYPH_ID_FORMAT = O_INDEX_TO_LOC_FORMAT + 1; +const O_ADVANCE_WIDTH_FORMAT = O_GLYPH_ID_FORMAT + 1; +const O_BITS_PER_PIXEL = O_ADVANCE_WIDTH_FORMAT + 1; +const O_XY_BITS = O_BITS_PER_PIXEL + 1; +const O_WH_BITS = O_XY_BITS + 1; +const O_ADVANCE_WIDTH_BITS = O_WH_BITS + 1; +const O_COMPRESSION_ID = O_ADVANCE_WIDTH_BITS + 1; +const O_SUBPIXELS_MODE = O_COMPRESSION_ID + 1; +const O_TMP_RESERVED1 = O_SUBPIXELS_MODE + 1; +const O_UNDERLINE_POSITION = O_TMP_RESERVED1 + 1; +const O_UNDERLINE_THICKNESS = O_UNDERLINE_POSITION + 2; +const HEAD_LENGTH = u.align4(O_UNDERLINE_THICKNESS + 2); + + +class Head { + constructor(font) { + this.font = font; + this.label = 'head'; + this.version = 1; + } + + toBin() { + const buf = Buffer.alloc(HEAD_LENGTH); + debug(`table size = ${buf.length}`); + + buf.writeUInt32LE(HEAD_LENGTH, O_SIZE); + buf.write(this.label, O_LABEL); + buf.writeUInt32LE(this.version, O_VERSION); + + const f = this.font; + + const tables_count = f.hasKerning() ? 4 : 3; + + buf.writeUInt16LE(tables_count, O_TABLES); + + buf.writeUInt16LE(f.src.size, O_FONT_SIZE); + buf.writeUInt16LE(f.src.ascent, O_ASCENT); + buf.writeInt16LE(f.src.descent, O_DESCENT); + + buf.writeUInt16LE(f.src.typoAscent, O_TYPO_ASCENT); + buf.writeInt16LE(f.src.typoDescent, O_TYPO_DESCENT); + buf.writeUInt16LE(f.src.typoLineGap, O_TYPO_LINE_GAP); + + buf.writeInt16LE(f.minY, O_MIN_Y); + buf.writeInt16LE(f.maxY, O_MAX_Y); + + if (f.monospaced) { + buf.writeUInt16LE(f.widthToInt(f.src.glyphs[0].advanceWidth), O_DEF_ADVANCE_WIDTH); + } else { + buf.writeUInt16LE(0, O_DEF_ADVANCE_WIDTH); + } + + buf.writeUInt16LE(Math.round(f.kerningScale * 16), O_KERNING_SCALE); // FP12.4 + + buf.writeUInt8(f.indexToLocFormat, O_INDEX_TO_LOC_FORMAT); + buf.writeUInt8(f.glyphIdFormat, O_GLYPH_ID_FORMAT); + buf.writeUInt8(f.advanceWidthFormat, O_ADVANCE_WIDTH_FORMAT); + + buf.writeUInt8(f.opts.bpp, O_BITS_PER_PIXEL); + buf.writeUInt8(f.xy_bits, O_XY_BITS); + buf.writeUInt8(f.wh_bits, O_WH_BITS); + + if (f.monospaced) buf.writeUInt8(0, O_ADVANCE_WIDTH_BITS); + else buf.writeUInt8(f.advanceWidthBits, O_ADVANCE_WIDTH_BITS); + + buf.writeUInt8(f.glyf.getCompressionCode(), O_COMPRESSION_ID); + + buf.writeUInt8(f.subpixels_mode, O_SUBPIXELS_MODE); + + buf.writeInt16LE(f.src.underlinePosition, O_UNDERLINE_POSITION); + buf.writeUInt16LE(f.src.underlineThickness, O_UNDERLINE_POSITION); + + return buf; + } +} + + +module.exports = Head; diff --git a/node_modules/lv_font_conv/lib/font/table_kern.js b/node_modules/lv_font_conv/lib/font/table_kern.js new file mode 100644 index 00000000..e879b3c7 --- /dev/null +++ b/node_modules/lv_font_conv/lib/font/table_kern.js @@ -0,0 +1,256 @@ +'use strict'; + +const u = require('../utils'); +const debug = require('debug')('font.table.kern'); + + +const O_SIZE = 0; +const O_LABEL = O_SIZE + 4; +const O_FORMAT = O_LABEL + 4; + +const HEAD_LENGTH = u.align4(O_FORMAT + 1); + + +class Kern { + constructor(font) { + this.font = font; + this.label = 'kern'; + this.format3_forced = false; + } + + collect_format0_data() { + const f = this.font; + const glyphs = u.sort_by(this.font.src.glyphs, g => f.glyph_id[g.code]); + const kernSorted = []; + + for (let g of glyphs) { + if (!g.kerning || !Object.keys(g.kerning).length) continue; + + const glyph_id = f.glyph_id[g.code]; + const paired = u.sort_by(Object.keys(g.kerning), code => f.glyph_id[code]); + + for (let code of paired) { + const glyph_id2 = f.glyph_id[code]; + kernSorted.push([ glyph_id, glyph_id2, g.kerning[code] ]); + } + } + + return kernSorted; + } + + create_format0_data() { + const f = this.font; + const glyphs = this.font.src.glyphs; + const kernSorted = this.collect_format0_data(); + + const count = kernSorted.length; + + const kerned_glyphs = glyphs.filter(g => Object.keys(g.kerning).length).length; + const kerning_list_max = Math.max(...glyphs.map(g => Object.keys(g.kerning).length)); + debug(`${kerned_glyphs} kerned glyphs of ${glyphs.length}, ${kerning_list_max} max list, ${count} total pairs`); + + const subheader = Buffer.alloc(4); + + subheader.writeUInt32LE(count, 0); + + const pairs_buf = Buffer.alloc((f.glyphIdFormat ? 4 : 2) * count); + + // Write kerning pairs + for (let i = 0; i < count; i++) { + if (f.glyphIdFormat === 0) { + pairs_buf.writeUInt8(kernSorted[i][0], 2 * i); + pairs_buf.writeUInt8(kernSorted[i][1], 2 * i + 1); + } else { + pairs_buf.writeUInt16LE(kernSorted[i][0], 4 * i); + pairs_buf.writeUInt16LE(kernSorted[i][1], 4 * i + 2); + } + } + + const values_buf = Buffer.alloc(count); + + // Write kerning values + for (let i = 0; i < count; i++) { + values_buf.writeInt8(f.kernToFP(kernSorted[i][2]), i); // FP4.4 + } + + let buf = Buffer.concat([ + subheader, + pairs_buf, + values_buf + ]); + + let buf_aligned = u.balign4(buf); + + debug(`table format0 size = ${buf_aligned.length}`); + return buf_aligned; + } + + collect_format3_data() { + const f = this.font; + const glyphs = u.sort_by(this.font.src.glyphs, g => f.glyph_id[g.code]); + + // extract kerning pairs for each character; + // left kernings are kerning values based on left char (already there), + // right kernings are kerning values based on right char (extracted from left) + const left_kernings = {}; + const right_kernings = {}; + + for (let g of glyphs) { + if (!g.kerning || !Object.keys(g.kerning).length) continue; + + const paired = Object.keys(g.kerning); + + left_kernings[g.code] = g.kerning; + + for (let code of paired) { + right_kernings[code] = right_kernings[code] || {}; + right_kernings[code][g.code] = g.kerning[code]; + } + } + + // input: + // - kernings, char => { hash: String, [char1]: Number, [char2]: Number, ... } + // + // returns: + // - array of [ char1, char2, ... ] + // + function build_classes(kernings) { + const classes = []; + + for (let code of Object.keys(kernings)) { + // for each kerning table calculate unique value representing it; + // keys needs to be sorted for this (but we're using numeric keys, so + // sorting happens automatically and can't be changed) + const hash = JSON.stringify(kernings[code]); + + classes[hash] = classes[hash] || []; + classes[hash].push(Number(code)); + } + + return Object.values(classes); + } + + const left_classes = build_classes(left_kernings); + debug(`unique left classes: ${left_classes.length}`); + + const right_classes = build_classes(right_kernings); + debug(`unique right classes: ${right_classes.length}`); + + if (left_classes.length >= 255 || right_classes.length >= 255) { + debug('too many classes for format3 subtable'); + return null; + } + + function kern_class_mapping(classes) { + const arr = Array(f.last_id).fill(0); + + classes.forEach((members, idx) => { + for (let code of members) { + arr[f.glyph_id[code]] = idx + 1; + } + }); + + return arr; + } + + function kern_class_values() { + const arr = []; + + for (let left_class of left_classes) { + for (let right_class of right_classes) { + let code1 = left_class[0]; + let code2 = right_class[0]; + arr.push(left_kernings[code1][code2] || 0); + } + } + + return arr; + } + + return { + left_classes: left_classes.length, + right_classes: right_classes.length, + left_mapping: kern_class_mapping(left_classes), + right_mapping: kern_class_mapping(right_classes), + values: kern_class_values() + }; + } + + create_format3_data() { + const f = this.font; + const { + left_classes, + right_classes, + left_mapping, + right_mapping, + values + } = this.collect_format3_data(); + + const subheader = Buffer.alloc(4); + subheader.writeUInt16LE(f.last_id); + subheader.writeUInt8(left_classes, 2); + subheader.writeUInt8(right_classes, 3); + + let buf = Buffer.concat([ + subheader, + Buffer.from(left_mapping), + Buffer.from(right_mapping), + Buffer.from(values.map(v => f.kernToFP(v))) + ]); + + let buf_aligned = u.balign4(buf); + + debug(`table format3 size = ${buf_aligned.length}`); + return buf_aligned; + } + + should_use_format3() { + if (!this.font.hasKerning()) return false; + + const format0_data = this.create_format0_data(); + const format3_data = this.create_format3_data(); + + if (format3_data && format3_data.length <= format0_data.length) return true; + + if (this.font.opts.fast_kerning && format3_data) { + this.format3_forced = true; + return true; + } + + return false; + } + + toBin() { + if (!this.font.hasKerning()) return Buffer.alloc(0); + + const format0_data = this.create_format0_data(); + const format3_data = this.create_format3_data(); + + let header = Buffer.alloc(HEAD_LENGTH); + + let data = format0_data; + header.writeUInt8(0, O_FORMAT); + + /* eslint-disable no-console */ + + if (this.should_use_format3()) { + data = format3_data; + header.writeUInt8(3, O_FORMAT); + + if (this.format3_forced) { + let diff = format3_data.length - format0_data.length; + console.log(`Forced faster kerning format (via classes). Size increase is ${diff} bytes.`); + } + } else if (this.font.opts.fast_kerning) { + console.log('Forced faster kerning format (via classes), but data exceeds it\'s limits. Continue use pairs.'); + } + + header.writeUInt32LE(header.length + data.length, O_SIZE); + header.write(this.label, O_LABEL); + + return Buffer.concat([ header, data ]); + } +} + + +module.exports = Kern; diff --git a/node_modules/lv_font_conv/lib/font/table_loca.js b/node_modules/lv_font_conv/lib/font/table_loca.js new file mode 100644 index 00000000..4aa50d22 --- /dev/null +++ b/node_modules/lv_font_conv/lib/font/table_loca.js @@ -0,0 +1,42 @@ +'use strict'; + + +const u = require('../utils'); +const debug = require('debug')('font.table.loca'); + + +const O_SIZE = 0; +const O_LABEL = O_SIZE + 4; +const O_COUNT = O_LABEL + 4; + +const HEAD_LENGTH = O_COUNT + 4; + + +class Loca { + constructor(font) { + this.font = font; + this.label = 'loca'; + } + + toBin() { + const f = this.font; + + const offsets = [ ...Array(f.last_id).keys() ].map(i => f.glyf.getOffset(i)); + + const buf = u.balign4(Buffer.concat([ + Buffer.alloc(HEAD_LENGTH), + f.indexToLocFormat ? u.bFromA32(offsets) : u.bFromA16(offsets) + ])); + + buf.writeUInt32LE(buf.length, O_SIZE); + buf.write(this.label, O_LABEL); + buf.writeUInt32LE(f.last_id, O_COUNT); + + debug(`table size = ${buf.length}`); + + return buf; + } +} + + +module.exports = Loca; diff --git a/node_modules/lv_font_conv/lib/freetype/index.js b/node_modules/lv_font_conv/lib/freetype/index.js new file mode 100644 index 00000000..d05f68c3 --- /dev/null +++ b/node_modules/lv_font_conv/lib/freetype/index.js @@ -0,0 +1,317 @@ +'use strict'; + + +const ft_render_fabric = require('./build/ft_render'); + +let m = null; // compiled module instance +let library = 0; // pointer to library struct in wasm memory + + +// workaround because of bug in emscripten: +// https://github.com/emscripten-core/emscripten/issues/5820 +const runtime_initialized = new Promise(resolve => { + ft_render_fabric().then(module_instance => { + m = module_instance; + resolve(); + }); +}); + +function from_16_16(fixed_point) { + return fixed_point / (1 << 16); +} + +function from_26_6(fixed_point) { + return fixed_point / (1 << 6); +} + +function int8_to_uint8(value) { + return value >= 0 ? value : value + 0x100; +} + +let FT_New_Memory_Face, + FT_Set_Char_Size, + FT_Set_Pixel_Sizes, + FT_Get_Char_Index, + FT_Load_Glyph, + FT_Get_Sfnt_Table, + FT_Get_Kerning, + FT_Done_Face; + +module.exports.init = async function () { + await runtime_initialized; + m._init_constants(); + + FT_New_Memory_Face = module.exports.FT_New_Memory_Face = + m.cwrap('FT_New_Memory_Face', 'number', [ 'number', 'number', 'number', 'number', 'number' ]); + + FT_Set_Char_Size = module.exports.FT_Set_Char_Size = + m.cwrap('FT_Set_Char_Size', 'number', [ 'number', 'number', 'number', 'number', 'number' ]); + + FT_Set_Pixel_Sizes = module.exports.FT_Set_Pixel_Sizes = + m.cwrap('FT_Set_Pixel_Sizes', 'number', [ 'number', 'number', 'number' ]); + + FT_Get_Char_Index = module.exports.FT_Get_Char_Index = + m.cwrap('FT_Get_Char_Index', 'number', [ 'number', 'number' ]); + + FT_Load_Glyph = module.exports.FT_Load_Glyph = + m.cwrap('FT_Load_Glyph', 'number', [ 'number', 'number', 'number' ]); + + FT_Get_Sfnt_Table = module.exports.FT_Get_Sfnt_Table = + m.cwrap('FT_Get_Sfnt_Table', 'number', [ 'number', 'number' ]); + + FT_Get_Kerning = module.exports.FT_Get_Kerning = + m.cwrap('FT_Get_Kerning', 'number', [ 'number', 'number', 'number', 'number', 'number' ]); + + FT_Done_Face = module.exports.FT_Done_Face = + m.cwrap('FT_Done_Face', 'number', [ 'number' ]); + + if (!library) { + let ptr = m._malloc(4); + + try { + let error = m.ccall('FT_Init_FreeType', 'number', [ 'number' ], [ ptr ]); + + if (error) throw new Error(`error in FT_Init_FreeType: ${error}`); + + library = m.getValue(ptr, 'i32'); + } finally { + m._free(ptr); + } + } +}; + + +module.exports.fontface_create = function (source, size) { + let error; + let face = { + ptr: 0, + font: m._malloc(source.length) + }; + + m.writeArrayToMemory(source, face.font); + + let ptr = m._malloc(4); + + try { + error = FT_New_Memory_Face(library, face.font, source.length, 0, ptr); + + if (error) throw new Error(`error in FT_New_Memory_Face: ${error}`); + + face.ptr = m.getValue(ptr, 'i32'); + } finally { + m._free(ptr); + } + + error = FT_Set_Char_Size(face.ptr, 0, size * 64, 300, 300); + + if (error) throw new Error(`error in FT_Set_Char_Size: ${error}`); + + error = FT_Set_Pixel_Sizes(face.ptr, 0, size); + + if (error) throw new Error(`error in FT_Set_Pixel_Sizes: ${error}`); + + let units_per_em = m.getValue(face.ptr + m.OFFSET_FACE_UNITS_PER_EM, 'i16'); + let ascender = m.getValue(face.ptr + m.OFFSET_FACE_ASCENDER, 'i16'); + let descender = m.getValue(face.ptr + m.OFFSET_FACE_DESCENDER, 'i16'); + let height = m.getValue(face.ptr + m.OFFSET_FACE_HEIGHT, 'i16'); + + return Object.assign(face, { + units_per_em, + ascender, + descender, + height + }); +}; + + +module.exports.fontface_os2_table = function (face) { + let sfnt_ptr = FT_Get_Sfnt_Table(face.ptr, m.FT_SFNT_OS2); + + if (!sfnt_ptr) throw new Error('os/2 table not found for this font'); + + let typoAscent = m.getValue(sfnt_ptr + m.OFFSET_TT_OS2_ASCENDER, 'i16'); + let typoDescent = m.getValue(sfnt_ptr + m.OFFSET_TT_OS2_DESCENDER, 'i16'); + let typoLineGap = m.getValue(sfnt_ptr + m.OFFSET_TT_OS2_LINEGAP, 'i16'); + + return { + typoAscent, + typoDescent, + typoLineGap + }; +}; + + +module.exports.get_kerning = function (face, code1, code2) { + let glyph1 = FT_Get_Char_Index(face.ptr, code1); + let glyph2 = FT_Get_Char_Index(face.ptr, code2); + let ptr = m._malloc(4 * 2); + + try { + let error = FT_Get_Kerning(face.ptr, glyph1, glyph2, m.FT_KERNING_DEFAULT, ptr); + + if (error) throw new Error(`error in FT_Get_Kerning: ${error}`); + } finally { + m._free(ptr); + } + + return { + x: from_26_6(m.getValue(ptr, 'i32')), + y: from_26_6(m.getValue(ptr + 4, 'i32')) + }; +}; + + +module.exports.glyph_exists = function (face, code) { + let glyph_index = FT_Get_Char_Index(face.ptr, code); + + return glyph_index !== 0; +}; + + +module.exports.glyph_render = function (face, code, opts = {}) { + let glyph_index = FT_Get_Char_Index(face.ptr, code); + + if (glyph_index === 0) throw new Error(`glyph does not exist for codepoint ${code}`); + + let load_flags = m.FT_LOAD_RENDER; + + if (opts.mono) { + load_flags |= m.FT_LOAD_TARGET_MONO; + + } else if (opts.lcd) { + load_flags |= m.FT_LOAD_TARGET_LCD; + + } else if (opts.lcd_v) { + load_flags |= m.FT_LOAD_TARGET_LCD_V; + + } else { + /* eslint-disable no-lonely-if */ + + // Use "light" by default, it changes horizontal lines only. + // "normal" is more strong (with vertical lines), but will break kerning, if + // no additional care taken. More advanced rendering requires upper level + // layout support (via Harfbuzz, for example). + if (!opts.autohint_strong) load_flags |= m.FT_LOAD_TARGET_LIGHT; + else load_flags |= m.FT_LOAD_TARGET_NORMAL; + } + + if (opts.autohint_off) load_flags |= m.FT_LOAD_NO_AUTOHINT; + else load_flags |= m.FT_LOAD_FORCE_AUTOHINT; + + if (opts.use_color_info) load_flags |= m.FT_LOAD_COLOR; + + let error = FT_Load_Glyph(face.ptr, glyph_index, load_flags); + + if (error) throw new Error(`error in FT_Load_Glyph: ${error}`); + + let glyph = m.getValue(face.ptr + m.OFFSET_FACE_GLYPH, 'i32'); + + let glyph_data = { + glyph_index: m.getValue(glyph + m.OFFSET_GLYPH_INDEX, 'i32'), + metrics: { + width: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_WIDTH, 'i32')), + height: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_HEIGHT, 'i32')), + horiBearingX: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_HORI_BEARING_X, 'i32')), + horiBearingY: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_HORI_BEARING_Y, 'i32')), + horiAdvance: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_HORI_ADVANCE, 'i32')), + vertBearingX: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_VERT_BEARING_X, 'i32')), + vertBearingY: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_VERT_BEARING_Y, 'i32')), + vertAdvance: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_VERT_ADVANCE, 'i32')) + }, + linearHoriAdvance: from_16_16(m.getValue(glyph + m.OFFSET_GLYPH_LINEAR_HORI_ADVANCE, 'i32')), + linearVertAdvance: from_16_16(m.getValue(glyph + m.OFFSET_GLYPH_LINEAR_VERT_ADVANCE, 'i32')), + advance: { + x: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_ADVANCE_X, 'i32')), + y: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_ADVANCE_Y, 'i32')) + }, + bitmap: { + width: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_WIDTH, 'i32'), + rows: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_ROWS, 'i32'), + pitch: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_PITCH, 'i32'), + num_grays: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_NUM_GRAYS, 'i16'), + pixel_mode: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_PIXEL_MODE, 'i8'), + palette_mode: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_PALETTE_MODE, 'i8') + }, + bitmap_left: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_LEFT, 'i32'), + bitmap_top: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_TOP, 'i32'), + lsb_delta: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_LSB_DELTA, 'i32')), + rsb_delta: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_RSB_DELTA, 'i32')) + }; + + let g_w = glyph_data.bitmap.width; + let g_h = glyph_data.bitmap.rows; + let g_x = glyph_data.bitmap_left; + let g_y = glyph_data.bitmap_top; + + let buffer = m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_BUFFER, 'i32'); + let pitch = Math.abs(glyph_data.bitmap.pitch); + + let advance_x = glyph_data.linearHoriAdvance; + let advance_y = glyph_data.linearVertAdvance; + + let pixel_mode = glyph_data.bitmap.pixel_mode; + + let output = []; + + for (let y = 0; y < g_h; y++) { + let row_start = buffer + y * pitch; + let line = []; + + for (let x = 0; x < g_w; x++) { + if (pixel_mode === m.FT_PIXEL_MODE_MONO) { + let value = m.getValue(row_start + ~~(x / 8), 'i8'); + line.push(value & (1 << (7 - (x % 8))) ? 255 : 0); + } else if (pixel_mode === m.FT_PIXEL_MODE_BGRA) { + let blue = int8_to_uint8(m.getValue(row_start + (x * 4) + 0, 'i8')); + let green = int8_to_uint8(m.getValue(row_start + (x * 4) + 1, 'i8')); + let red = int8_to_uint8(m.getValue(row_start + (x * 4) + 2, 'i8')); + let alpha = int8_to_uint8(m.getValue(row_start + (x * 4) + 3, 'i8')); + // convert RGBA to grayscale + let grayscale = Math.round(0.299 * red + 0.587 * green + 0.114 * blue); + if (grayscale > 255) grayscale = 255; + // meld grayscale into alpha channel + alpha = ((255 - grayscale) * alpha) / 255; + line.push(alpha); + } else { + let value = m.getValue(row_start + x, 'i8'); + line.push(int8_to_uint8(value)); + } + } + + output.push(line); + } + + return { + x: g_x, + y: g_y, + width: g_w, + height: g_h, + advance_x, + advance_y, + pixels: output, + freetype: glyph_data + }; +}; + + +module.exports.fontface_destroy = function (face) { + let error = FT_Done_Face(face.ptr); + + if (error) throw new Error(`error in FT_Done_Face: ${error}`); + + m._free(face.font); + face.ptr = 0; + face.font = 0; +}; + + +module.exports.destroy = function () { + let error = m.ccall('FT_Done_FreeType', 'number', [ 'number' ], [ library ]); + + if (error) throw new Error(`error in FT_Done_FreeType: ${error}`); + + library = 0; + + // don't unload wasm - slows down tests too much + //m = null; +}; diff --git a/node_modules/lv_font_conv/lib/freetype/render.c b/node_modules/lv_font_conv/lib/freetype/render.c new file mode 100644 index 00000000..901d4974 --- /dev/null +++ b/node_modules/lv_font_conv/lib/freetype/render.c @@ -0,0 +1,83 @@ +#include +#include +#include FT_FREETYPE_H +#include FT_TRUETYPE_TABLES_H + +static void set_js_variable(char* name, int value) { + char buffer[strlen(name) + 32]; + sprintf(buffer, "Module.%s = %d;", name, value); + emscripten_run_script(buffer); +} + +// Expose constants, used in calls from js +void init_constants() +{ + set_js_variable("FT_LOAD_DEFAULT", FT_LOAD_DEFAULT); + set_js_variable("FT_LOAD_NO_HINTING", FT_LOAD_NO_HINTING); + set_js_variable("FT_LOAD_RENDER", FT_LOAD_RENDER); + set_js_variable("FT_LOAD_FORCE_AUTOHINT", FT_LOAD_FORCE_AUTOHINT); + set_js_variable("FT_LOAD_PEDANTIC", FT_LOAD_PEDANTIC); + set_js_variable("FT_LOAD_MONOCHROME", FT_LOAD_MONOCHROME); + set_js_variable("FT_LOAD_NO_AUTOHINT", FT_LOAD_NO_AUTOHINT); + set_js_variable("FT_LOAD_COLOR", FT_LOAD_COLOR); + + set_js_variable("FT_LOAD_TARGET_NORMAL", FT_LOAD_TARGET_NORMAL); + set_js_variable("FT_LOAD_TARGET_LIGHT", FT_LOAD_TARGET_LIGHT); + set_js_variable("FT_LOAD_TARGET_MONO", FT_LOAD_TARGET_MONO); + set_js_variable("FT_LOAD_TARGET_LCD", FT_LOAD_TARGET_LCD); + set_js_variable("FT_LOAD_TARGET_LCD_V", FT_LOAD_TARGET_LCD_V); + + set_js_variable("FT_RENDER_MODE_NORMAL", FT_RENDER_MODE_NORMAL); + set_js_variable("FT_RENDER_MODE_MONO", FT_RENDER_MODE_MONO); + set_js_variable("FT_RENDER_MODE_LCD", FT_RENDER_MODE_LCD); + set_js_variable("FT_RENDER_MODE_LCD_V", FT_RENDER_MODE_LCD_V); + + set_js_variable("FT_KERNING_DEFAULT", FT_KERNING_DEFAULT); + set_js_variable("FT_KERNING_UNFITTED", FT_KERNING_UNFITTED); + set_js_variable("FT_KERNING_UNSCALED", FT_KERNING_UNSCALED); + + set_js_variable("FT_SFNT_OS2", FT_SFNT_OS2); + + set_js_variable("FT_FACE_FLAG_COLOR", FT_FACE_FLAG_COLOR); + + set_js_variable("FT_PIXEL_MODE_MONO", FT_PIXEL_MODE_MONO); + set_js_variable("FT_PIXEL_MODE_BGRA", FT_PIXEL_MODE_BGRA); + + set_js_variable("OFFSET_FACE_GLYPH", offsetof(FT_FaceRec, glyph)); + set_js_variable("OFFSET_FACE_UNITS_PER_EM", offsetof(FT_FaceRec, units_per_EM)); + set_js_variable("OFFSET_FACE_ASCENDER", offsetof(FT_FaceRec, ascender)); + set_js_variable("OFFSET_FACE_DESCENDER", offsetof(FT_FaceRec, descender)); + set_js_variable("OFFSET_FACE_HEIGHT", offsetof(FT_FaceRec, height)); + set_js_variable("OFFSET_FACE_FACE_FLAGS", offsetof(FT_FaceRec, face_flags)); + + set_js_variable("OFFSET_GLYPH_BITMAP_WIDTH", offsetof(FT_GlyphSlotRec, bitmap.width)); + set_js_variable("OFFSET_GLYPH_BITMAP_ROWS", offsetof(FT_GlyphSlotRec, bitmap.rows)); + set_js_variable("OFFSET_GLYPH_BITMAP_PITCH", offsetof(FT_GlyphSlotRec, bitmap.pitch)); + set_js_variable("OFFSET_GLYPH_BITMAP_BUFFER", offsetof(FT_GlyphSlotRec, bitmap.buffer)); + set_js_variable("OFFSET_GLYPH_BITMAP_NUM_GRAYS", offsetof(FT_GlyphSlotRec, bitmap.num_grays)); + set_js_variable("OFFSET_GLYPH_BITMAP_PIXEL_MODE", offsetof(FT_GlyphSlotRec, bitmap.pixel_mode)); + set_js_variable("OFFSET_GLYPH_BITMAP_PALETTE_MODE", offsetof(FT_GlyphSlotRec, bitmap.palette_mode)); + + set_js_variable("OFFSET_GLYPH_METRICS_WIDTH", offsetof(FT_GlyphSlotRec, metrics.width)); + set_js_variable("OFFSET_GLYPH_METRICS_HEIGHT", offsetof(FT_GlyphSlotRec, metrics.height)); + set_js_variable("OFFSET_GLYPH_METRICS_HORI_BEARING_X", offsetof(FT_GlyphSlotRec, metrics.horiBearingX)); + set_js_variable("OFFSET_GLYPH_METRICS_HORI_BEARING_Y", offsetof(FT_GlyphSlotRec, metrics.horiBearingY)); + set_js_variable("OFFSET_GLYPH_METRICS_HORI_ADVANCE", offsetof(FT_GlyphSlotRec, metrics.horiAdvance)); + set_js_variable("OFFSET_GLYPH_METRICS_VERT_BEARING_X", offsetof(FT_GlyphSlotRec, metrics.vertBearingX)); + set_js_variable("OFFSET_GLYPH_METRICS_VERT_BEARING_Y", offsetof(FT_GlyphSlotRec, metrics.vertBearingY)); + set_js_variable("OFFSET_GLYPH_METRICS_VERT_ADVANCE", offsetof(FT_GlyphSlotRec, metrics.vertAdvance)); + + set_js_variable("OFFSET_GLYPH_BITMAP_LEFT", offsetof(FT_GlyphSlotRec, bitmap_left)); + set_js_variable("OFFSET_GLYPH_BITMAP_TOP", offsetof(FT_GlyphSlotRec, bitmap_top)); + set_js_variable("OFFSET_GLYPH_INDEX", offsetof(FT_GlyphSlotRec, glyph_index)); + set_js_variable("OFFSET_GLYPH_LINEAR_HORI_ADVANCE", offsetof(FT_GlyphSlotRec, linearHoriAdvance)); + set_js_variable("OFFSET_GLYPH_LINEAR_VERT_ADVANCE", offsetof(FT_GlyphSlotRec, linearVertAdvance)); + set_js_variable("OFFSET_GLYPH_ADVANCE_X", offsetof(FT_GlyphSlotRec, advance.x)); + set_js_variable("OFFSET_GLYPH_ADVANCE_Y", offsetof(FT_GlyphSlotRec, advance.y)); + set_js_variable("OFFSET_GLYPH_LSB_DELTA", offsetof(FT_GlyphSlotRec, lsb_delta)); + set_js_variable("OFFSET_GLYPH_RSB_DELTA", offsetof(FT_GlyphSlotRec, rsb_delta)); + + set_js_variable("OFFSET_TT_OS2_ASCENDER", offsetof(TT_OS2, sTypoAscender)); + set_js_variable("OFFSET_TT_OS2_DESCENDER", offsetof(TT_OS2, sTypoDescender)); + set_js_variable("OFFSET_TT_OS2_LINEGAP", offsetof(TT_OS2, sTypoLineGap)); +} diff --git a/node_modules/lv_font_conv/lib/ranger.js b/node_modules/lv_font_conv/lib/ranger.js new file mode 100644 index 00000000..34372d75 --- /dev/null +++ b/node_modules/lv_font_conv/lib/ranger.js @@ -0,0 +1,51 @@ +// Merge ranges into single object + +'use strict'; + + +class Ranger { + constructor() { + this.data = {}; + } + + // input: + // -r 0x1F450 - single value, dec or hex format + // -r 0x1F450-0x1F470 - range + // -r 0x1F450=>0xF005 - single glyph with mapping + // -r 0x1F450-0x1F470=>0xF005 - range with mapping + add_range(font, start, end, mapped_start) { + let offset = mapped_start - start; + let output = []; + + for (let i = start; i <= end; i++) { + this._set_char(font, i, i + offset); + output.push(i); + } + + return output; + } + + // input: characters to copy, e.g. '1234567890abcdef' + add_symbols(font, str) { + let output = []; + + for (let chr of str) { + let code = chr.codePointAt(0); + this._set_char(font, code, code); + output.push(code); + } + + return output; + } + + _set_char(font, code, mapped_to) { + this.data[mapped_to] = { font, code }; + } + + get() { + return this.data; + } +} + + +module.exports = Ranger; diff --git a/node_modules/lv_font_conv/lib/utils.js b/node_modules/lv_font_conv/lib/utils.js new file mode 100644 index 00000000..0ca79322 --- /dev/null +++ b/node_modules/lv_font_conv/lib/utils.js @@ -0,0 +1,131 @@ +'use strict'; + + +function set_byte_depth(depth) { + return function (byte) { + // calculate significant bits, e.g. for depth=2 it's 0, 1, 2 or 3 + let value = ~~(byte / (256 >> depth)); + + // spread those bits around 0..255 range, e.g. for depth=2 it's 0, 85, 170 or 255 + let scale = (2 << (depth - 1)) - 1; + + return (value * 0xFFFF / scale) >> 8; + }; +} + + +module.exports.set_depth = function set_depth(glyph, depth) { + let pixels = []; + let fn = set_byte_depth(depth); + + for (let y = 0; y < glyph.bbox.height; y++) { + pixels.push(glyph.pixels[y].map(fn)); + } + + return Object.assign({}, glyph, { pixels }); +}; + + +function count_bits(val) { + let count = 0; + val = ~~val; + + while (val) { + count++; + val >>= 1; + } + + return count; +} + +// Minimal number of bits to store unsigned value +module.exports.unsigned_bits = count_bits; + +// Minimal number of bits to store signed value +module.exports.signed_bits = function signed_bits(val) { + if (val >= 0) return count_bits(val) + 1; + + return count_bits(Math.abs(val) - 1) + 1; +}; + +// Align value to 4x - useful to create word-aligned arrays +function align4(size) { + if (size % 4 === 0) return size; + return size + 4 - (size % 4); +} +module.exports.align4 = align4; + +// Align buffer length to 4x (returns copy with zero-filled tail) +module.exports.balign4 = function balign4(buf) { + let buf_aligned = Buffer.alloc(align4(buf.length)); + buf.copy(buf_aligned); + return buf_aligned; +}; + +// Pre-filter image to improve compression ratio +// In this case - XOR lines, because it's very effective +// in decompressor and does not depend on bpp. +module.exports.prefilter = function prefilter(pixels) { + return pixels.map((line, l_idx, arr) => { + if (l_idx === 0) return line.slice(); + + return line.map((p, idx) => p ^ arr[l_idx - 1][idx]); + }); +}; + + +// Convert array with uint16 data to buffer +module.exports.bFromA16 = function bFromA16(arr) { + const buf = Buffer.alloc(arr.length * 2); + + for (let i = 0; i < arr.length; i++) buf.writeUInt16LE(arr[i], i * 2); + + return buf; +}; + +// Convert array with uint32 data to buffer +module.exports.bFromA32 = function bFromA32(arr) { + const buf = Buffer.alloc(arr.length * 4); + + for (let i = 0; i < arr.length; i++) buf.writeUInt32LE(arr[i], i * 4); + + return buf; +}; + + +function chunk(arr, size) { + const result = []; + for (let i = 0; i < arr.length; i += size) { + result.push(arr.slice(i, i + size)); + } + return result; +} + +// Dump long array to multiline format with X columns and Y indent +module.exports.long_dump = function long_dump(arr, options = {}) { + const defaults = { + col: 8, + indent: 4, + hex: false + }; + + let opts = Object.assign({}, defaults, options); + let indent = ' '.repeat(opts.indent); + + return chunk(Array.from(arr), opts.col) + .map(l => l.map(v => (opts.hex ? `0x${v.toString(16)}` : v.toString()))) + .map(l => `${indent}${l.join(', ')}`) + .join(',\n'); +}; + +// stable sort by pick() result +module.exports.sort_by = function sort_by(arr, pick) { + return arr + .map((el, idx) => ({ el, idx })) + .sort((a, b) => (pick(a.el) - pick(b.el)) || (a.idx - b.idx)) + .map(({ el }) => el); +}; + +module.exports.sum = function sum(arr) { + return arr.reduce((a, v) => a + v, 0); +}; diff --git a/node_modules/lv_font_conv/lib/writers/bin.js b/node_modules/lv_font_conv/lib/writers/bin.js new file mode 100644 index 00000000..bb482080 --- /dev/null +++ b/node_modules/lv_font_conv/lib/writers/bin.js @@ -0,0 +1,17 @@ +// Write font in binary format +'use strict'; + + +const AppError = require('../app_error'); +const Font = require('../font/font'); + + +module.exports = function write_images(args, fontData) { + if (!args.output) throw new AppError('Output is required for "bin" writer'); + + const font = new Font(fontData, args); + + return { + [args.output]: font.toBin() + }; +}; diff --git a/node_modules/lv_font_conv/lib/writers/dump.js b/node_modules/lv_font_conv/lib/writers/dump.js new file mode 100644 index 00000000..150d3b99 --- /dev/null +++ b/node_modules/lv_font_conv/lib/writers/dump.js @@ -0,0 +1,68 @@ +// Write font data into png images + +'use strict'; + + +const path = require('path'); +const { PNG } = require('pngjs'); +const AppError = require('../app_error'); +const utils = require('../utils'); + +const normal_color = [ 255, 255, 255 ]; +const outside_color = [ 255, 127, 184 ]; + + +module.exports = function write_images(args, font) { + if (!args.output) throw new AppError('Output is required for "dump" writer'); + + let files = {}; + + let glyphs = font.glyphs.map(glyph => utils.set_depth(glyph, args.bpp)); + + for (let glyph of glyphs) { + let { code, advanceWidth, bbox, pixels } = glyph; + + advanceWidth = Math.round(advanceWidth); + + let minX = bbox.x; + let maxX = Math.max(bbox.x + bbox.width - 1, bbox.x); + let minY = Math.min(bbox.y, font.typoDescent); + let maxY = Math.max(bbox.y + bbox.height - 1, font.typoAscent); + + let png = new PNG({ width: maxX - minX + 1, height: maxY - minY + 1 }); + + /* eslint-disable max-depth */ + for (let pos = 0, y = maxY; y >= minY; y--) { + for (let x = minX; x <= maxX; x++) { + let value = 0; + + if (x >= bbox.x && x < bbox.x + bbox.width && y >= bbox.y && y < bbox.y + bbox.height) { + value = pixels[bbox.height - (y - bbox.y) - 1][x - bbox.x]; + } + + let r, g, b; + + if (x < 0 || x >= advanceWidth || y < font.typoDescent || y > font.typoAscent) { + [ r, g, b ] = outside_color; + } else { + [ r, g, b ] = normal_color; + } + + png.data[pos++] = (255 - value) * r / 255; + png.data[pos++] = (255 - value) * g / 255; + png.data[pos++] = (255 - value) * b / 255; + png.data[pos++] = 255; + } + } + + + files[path.join(args.output, `${code.toString(16)}.png`)] = PNG.sync.write(png); + } + + files[path.join(args.output, 'font_info.json')] = JSON.stringify( + font, + (k, v) => (k === 'pixels' && !args.full_info ? undefined : v), + 2); + + return files; +}; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/index.js b/node_modules/lv_font_conv/lib/writers/lvgl/index.js new file mode 100644 index 00000000..b592104f --- /dev/null +++ b/node_modules/lv_font_conv/lib/writers/lvgl/index.js @@ -0,0 +1,17 @@ +// Write font in lvgl format +'use strict'; + + +const AppError = require('../../app_error'); +const Font = require('./lv_font'); + + +module.exports = function write_images(args, fontData) { + if (!args.output) throw new AppError('Output is required for "lvgl" writer'); + + const font = new Font(fontData, args); + + return { + [args.output]: font.toLVGL() + }; +}; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/lv_font.js b/node_modules/lv_font_conv/lib/writers/lvgl/lv_font.js new file mode 100644 index 00000000..e3ad9c72 --- /dev/null +++ b/node_modules/lv_font_conv/lib/writers/lvgl/lv_font.js @@ -0,0 +1,98 @@ +'use strict'; + + +const path = require('path'); + +const Font = require('../../font/font'); +const Head = require('./lv_table_head'); +const Cmap = require('./lv_table_cmap'); +const Glyf = require('./lv_table_glyf'); +const Kern = require('./lv_table_kern'); +const AppError = require('../../app_error'); + + +class LvFont extends Font { + constructor(fontData, options) { + super(fontData, options); + + const ext = path.extname(options.output); + this.font_name = path.basename(options.output, ext); + + if (options.bpp === 3 & options.no_compress) { + throw new AppError('LittlevGL supports "--bpp 3" with compression only'); + } + } + + init_tables() { + this.head = new Head(this); + this.glyf = new Glyf(this); + this.cmap = new Cmap(this); + this.kern = new Kern(this); + } + + large_format_guard() { + let guard_required = false; + let glyphs_bin_size = 0; + + this.glyf.lv_data.forEach(d => { + glyphs_bin_size += d.bin.length; + + if (d.glyph.bbox.width > 255 || + d.glyph.bbox.height > 255 || + Math.abs(d.glyph.bbox.x) > 127 || + Math.abs(d.glyph.bbox.y) > 127 || + Math.round(d.glyph.advanceWidth * 16) > 4096) { + guard_required = true; + } + }); + + if (glyphs_bin_size > 1024 * 1024) guard_required = true; + + if (!guard_required) return ''; + + return ` +#if (LV_FONT_FMT_TXT_LARGE == 0) +# error "Too large font or glyphs in ${this.font_name.toUpperCase()}. Enable LV_FONT_FMT_TXT_LARGE in lv_conf.h") +#endif +`.trimLeft(); + } + + toLVGL() { + let guard_name = this.font_name.toUpperCase(); + + return `/******************************************************************************* + * Size: ${this.src.size} px + * Bpp: ${this.opts.bpp} + * Opts: ${process.argv.slice(2).join(' ')} + ******************************************************************************/ + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "${this.opts.lv_include || 'lvgl/lvgl.h'}" +#endif + +#ifndef ${guard_name} +#define ${guard_name} 1 +#endif + +#if ${guard_name} + +${this.glyf.toLVGL()} + +${this.cmap.toLVGL()} + +${this.kern.toLVGL()} + +${this.head.toLVGL()} + +${this.large_format_guard()} + +#endif /*#if ${guard_name}*/ + +`; + } +} + + +module.exports = LvFont; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_cmap.js b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_cmap.js new file mode 100644 index 00000000..56c1e6aa --- /dev/null +++ b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_cmap.js @@ -0,0 +1,125 @@ +'use strict'; + + +const u = require('../../utils'); +const build_subtables = require('../../font/cmap_build_subtables'); +const Cmap = require('../../font/table_cmap'); + + +class LvCmap extends Cmap { + constructor(font) { + super(font); + + this.lv_compiled = false; + this.lv_subtables = []; + } + + lv_format2enum(name) { + switch (name) { + case 'format0_tiny': return 'LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY'; + case 'format0': return 'LV_FONT_FMT_TXT_CMAP_FORMAT0_FULL'; + case 'sparse_tiny': return 'LV_FONT_FMT_TXT_CMAP_SPARSE_TINY'; + case 'sparse': return 'LV_FONT_FMT_TXT_CMAP_SPARSE_FULL'; + default: throw new Error('Unknown subtable format'); + } + } + + lv_compile() { + if (this.lv_compiled) return; + this.lv_compiled = true; + + const f = this.font; + + let subtables_plan = build_subtables(f.src.glyphs.map(g => g.code)); + let idx = 0; + + for (let [ format, codepoints ] of subtables_plan) { + let g = this.glyphByCode(codepoints[0]); + let start_glyph_id = f.glyph_id[g.code]; + let min_code = codepoints[0]; + let max_code = codepoints[codepoints.length - 1]; + + let has_charcodes = false; + let has_ids = false; + let defs = ''; + let entries_count = 0; + + if (format === 'format0_tiny') { + // use default empty values + } else if (format === 'format0') { + has_ids = true; + let d = this.collect_format0_data(min_code, max_code, start_glyph_id); + entries_count = d.length; + + defs = ` +static const uint8_t glyph_id_ofs_list_${idx}[] = { +${u.long_dump(d)} +}; +`.trim(); + + } else if (format === 'sparse_tiny') { + has_charcodes = true; + let d = this.collect_sparse_data(codepoints, start_glyph_id); + entries_count = d.codes.length; + + defs = ` +static const uint16_t unicode_list_${idx}[] = { +${u.long_dump(d.codes, { hex: true })} +}; +`.trim(); + + } else { // assume format === 'sparse' + has_charcodes = true; + has_ids = true; + let d = this.collect_sparse_data(codepoints, start_glyph_id); + entries_count = d.codes.length; + + defs = ` +static const uint16_t unicode_list_${idx}[] = { +${u.long_dump(d.codes, { hex: true })} +}; +static const uint16_t glyph_id_ofs_list_${idx}[] = { +${u.long_dump(d.ids)} +}; +`.trim(); + } + + const u_list = has_charcodes ? `unicode_list_${idx}` : 'NULL'; + const id_list = has_ids ? `glyph_id_ofs_list_${idx}` : 'NULL'; + + /* eslint-disable max-len */ + const head = ` { + .range_start = ${min_code}, .range_length = ${max_code - min_code + 1}, .glyph_id_start = ${start_glyph_id}, + .unicode_list = ${u_list}, .glyph_id_ofs_list = ${id_list}, .list_length = ${entries_count}, .type = ${this.lv_format2enum(format)} + }`; + + this.lv_subtables.push({ + defs, + head + }); + + idx++; + } + } + + toLVGL() { + this.lv_compile(); + + return ` +/*--------------------- + * CHARACTER MAPPING + *--------------------*/ + +${this.lv_subtables.map(d => d.defs).filter(Boolean).join('\n\n')} + +/*Collect the unicode lists and glyph_id offsets*/ +static const lv_font_fmt_txt_cmap_t cmaps[] = +{ +${this.lv_subtables.map(d => d.head).join(',\n')} +}; + `.trim(); + } +} + + +module.exports = LvCmap; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_glyf.js b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_glyf.js new file mode 100644 index 00000000..3f14851f --- /dev/null +++ b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_glyf.js @@ -0,0 +1,121 @@ +'use strict'; + + +const { BitStream } = require('bit-buffer'); +const u = require('../../utils'); +const Glyf = require('../../font/table_glyf'); + + +class LvGlyf extends Glyf { + constructor(font) { + super(font); + + this.lv_data = []; + this.lv_compiled = false; + } + + lv_bitmap(glyph) { + const buf = Buffer.alloc(100 + glyph.bbox.width * glyph.bbox.height * 4); + const bs = new BitStream(buf); + bs.bigEndian = true; + + const pixels = this.font.glyf.pixelsToBpp(glyph.pixels); + + this.font.glyf.storePixels(bs, pixels); + + const glyph_bitmap = Buffer.alloc(bs.byteIndex); + buf.copy(glyph_bitmap, 0, 0, bs.byteIndex); + + return glyph_bitmap; + } + + lv_compile() { + if (this.lv_compiled) return; + + this.lv_compiled = true; + + const f = this.font; + this.lv_data = []; + let offset = 0; + + f.src.glyphs.forEach(g => { + const id = f.glyph_id[g.code]; + const bin = this.lv_bitmap(g); + this.lv_data[id] = { + bin, + offset, + glyph: g + }; + offset += bin.length; + }); + } + + to_lv_bitmaps() { + this.lv_compile(); + + let result = []; + this.lv_data.forEach((d, idx) => { + if (idx === 0) return; + const code_hex = d.glyph.code.toString(16).toUpperCase(); + const code_str = JSON.stringify(String.fromCodePoint(d.glyph.code)); + + let txt = ` /* U+${code_hex.padStart(4, '0')} ${code_str} */ +${u.long_dump(d.bin, { hex: true })}`; + + if (idx < this.lv_data.length - 1) { + // skip comma for zero data + txt += d.bin.length ? ',\n\n' : '\n'; + } + + result.push(txt); + }); + + return result.join(''); + } + + to_lv_glyph_dsc() { + this.lv_compile(); + + /* eslint-disable max-len */ + + let result = [ ' {.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */' ]; + + this.lv_data.forEach(d => { + const idx = d.offset, + adv_w = Math.round(d.glyph.advanceWidth * 16), + h = d.glyph.bbox.height, + w = d.glyph.bbox.width, + x = d.glyph.bbox.x, + y = d.glyph.bbox.y; + result.push(` {.bitmap_index = ${idx}, .adv_w = ${adv_w}, .box_w = ${w}, .box_h = ${h}, .ofs_x = ${x}, .ofs_y = ${y}}`); + }); + + return result.join(',\n'); + } + + + toLVGL() { + return ` +/*----------------- + * BITMAPS + *----------------*/ + +/*Store the image of the glyphs*/ +static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = { +${this.to_lv_bitmaps()} +}; + + +/*--------------------- + * GLYPH DESCRIPTION + *--------------------*/ + +static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = { +${this.to_lv_glyph_dsc()} +}; +`.trim(); + } +} + + +module.exports = LvGlyf; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_head.js b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_head.js new file mode 100644 index 00000000..f5c0173e --- /dev/null +++ b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_head.js @@ -0,0 +1,99 @@ +'use strict'; + + +const Head = require('../../font/table_head'); + + +class LvHead extends Head { + constructor(font) { + super(font); + } + + kern_ref() { + const f = this.font; + + if (!f.hasKerning()) { + return { + scale: '0', + dsc: 'NULL', + classes: '0' + }; + } + + if (!f.kern.should_use_format3()) { + return { + scale: `${Math.round(f.kerningScale * 16)}`, + dsc: '&kern_pairs', + classes: '0' + }; + } + + return { + scale: `${Math.round(f.kerningScale * 16)}`, + dsc: '&kern_classes', + classes: '1' + }; + } + + toLVGL() { + const f = this.font; + const kern = this.kern_ref(); + const subpixels = (f.subpixels_mode === 0) ? 'LV_FONT_SUBPX_NONE' : + (f.subpixels_mode === 1) ? 'LV_FONT_SUBPX_HOR' : 'LV_FONT_SUBPX_VER'; + + return ` +/*-------------------- + * ALL CUSTOM DATA + *--------------------*/ + +#if LV_VERSION_CHECK(8, 0, 0) +/*Store all the custom data of the font*/ +static lv_font_fmt_txt_glyph_cache_t cache; +static const lv_font_fmt_txt_dsc_t font_dsc = { +#else +static lv_font_fmt_txt_dsc_t font_dsc = { +#endif + .glyph_bitmap = glyph_bitmap, + .glyph_dsc = glyph_dsc, + .cmaps = cmaps, + .kern_dsc = ${kern.dsc}, + .kern_scale = ${kern.scale}, + .cmap_num = ${f.cmap.toBin().readUInt32LE(8)}, + .bpp = ${f.opts.bpp}, + .kern_classes = ${kern.classes}, + .bitmap_format = ${f.glyf.getCompressionCode()}, +#if LV_VERSION_CHECK(8, 0, 0) + .cache = &cache +#endif +}; + + +/*----------------- + * PUBLIC FONT + *----------------*/ + +/*Initialize a public general font descriptor*/ +#if LV_VERSION_CHECK(8, 0, 0) +const lv_font_t ${f.font_name} = { +#else +lv_font_t ${f.font_name} = { +#endif + .get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/ + .get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/ + .line_height = ${f.src.ascent - f.src.descent}, /*The maximum line height required by the font*/ + .base_line = ${-f.src.descent}, /*Baseline measured from the bottom of the line*/ +#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0) + .subpx = ${subpixels}, +#endif +#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8 + .underline_position = ${f.src.underlinePosition}, + .underline_thickness = ${f.src.underlineThickness}, +#endif + .dsc = &font_dsc /*The custom font data. Will be accessed by \`get_glyph_bitmap/dsc\` */ +}; +`.trim(); + } +} + + +module.exports = LvHead; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_kern.js b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_kern.js new file mode 100644 index 00000000..e50ba427 --- /dev/null +++ b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_kern.js @@ -0,0 +1,121 @@ +'use strict'; + + +const u = require('../../utils'); +const Kern = require('../../font/table_kern'); + + +class LvKern extends Kern { + constructor(font) { + super(font); + } + + to_lv_format0() { + const f = this.font; + let kern_pairs = this.collect_format0_data(); + + return ` +/*----------------- + * KERNING + *----------------*/ + + +/*Pair left and right glyphs for kerning*/ +static const ${f.glyphIdFormat ? 'uint16_t' : 'uint8_t'} kern_pair_glyph_ids[] = +{ +${kern_pairs.map(pair => ` ${pair[0]}, ${pair[1]}`).join(',\n')} +}; + +/* Kerning between the respective left and right glyphs + * 4.4 format which needs to scaled with \`kern_scale\`*/ +static const int8_t kern_pair_values[] = +{ +${u.long_dump(kern_pairs.map(pair => f.kernToFP(pair[2])))} +}; + +/*Collect the kern pair's data in one place*/ +static const lv_font_fmt_txt_kern_pair_t kern_pairs = +{ + .glyph_ids = kern_pair_glyph_ids, + .values = kern_pair_values, + .pair_cnt = ${kern_pairs.length}, + .glyph_ids_size = ${f.glyphIdFormat} +}; + + +`.trim(); + } + + to_lv_format3() { + const f = this.font; + const { + left_classes, + right_classes, + left_mapping, + right_mapping, + values + } = this.collect_format3_data(); + + return ` +/*----------------- + * KERNING + *----------------*/ + + +/*Map glyph_ids to kern left classes*/ +static const uint8_t kern_left_class_mapping[] = +{ +${u.long_dump(left_mapping)} +}; + +/*Map glyph_ids to kern right classes*/ +static const uint8_t kern_right_class_mapping[] = +{ +${u.long_dump(right_mapping)} +}; + +/*Kern values between classes*/ +static const int8_t kern_class_values[] = +{ +${u.long_dump(values.map(v => f.kernToFP(v)))} +}; + + +/*Collect the kern class' data in one place*/ +static const lv_font_fmt_txt_kern_classes_t kern_classes = +{ + .class_pair_values = kern_class_values, + .left_class_mapping = kern_left_class_mapping, + .right_class_mapping = kern_right_class_mapping, + .left_class_cnt = ${left_classes}, + .right_class_cnt = ${right_classes}, +}; + + +`.trim(); + } + + toLVGL() { + const f = this.font; + + if (!f.hasKerning()) return ''; + + /* eslint-disable no-console */ + + if (f.kern.should_use_format3()) { + if (f.kern.format3_forced) { + let diff = this.create_format3_data().length - this.create_format0_data().length; + console.log(`Forced faster kerning format (via classes). Size increase is ${diff} bytes.`); + } + return this.to_lv_format3(); + } + + if (this.font.opts.fast_kerning) { + console.log('Forced faster kerning format (via classes), but data exceeds it\'s limits. Continue use pairs.'); + } + return this.to_lv_format0(); + } +} + + +module.exports = LvKern; diff --git a/node_modules/lv_font_conv/lv_font_conv.js b/node_modules/lv_font_conv/lv_font_conv.js new file mode 100755 index 00000000..f76cbde4 --- /dev/null +++ b/node_modules/lv_font_conv/lv_font_conv.js @@ -0,0 +1,17 @@ +#!/usr/bin/env node + +'use strict'; + +const AppError = require('./lib/app_error'); + +require('./lib/cli').run(process.argv.slice(2)).catch(err => { + /*eslint-disable no-console*/ + if (err instanceof AppError) { + // Try to beautify normal errors + console.error(err.message.trim()); + } else { + // Print crashes + console.error(err.stack); + } + process.exit(1); +}); diff --git a/node_modules/lv_font_conv/node_modules/argparse/CHANGELOG.md b/node_modules/lv_font_conv/node_modules/argparse/CHANGELOG.md new file mode 100644 index 00000000..dc39ed69 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/argparse/CHANGELOG.md @@ -0,0 +1,216 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +## [2.0.1] - 2020-08-29 +### Fixed +- Fix issue with `process.argv` when used with interpreters (`coffee`, `ts-node`, etc.), #150. + + +## [2.0.0] - 2020-08-14 +### Changed +- Full rewrite. Now port from python 3.9.0 & more precise following. + See [doc](./doc) for difference and migration info. +- node.js 10+ required +- Removed most of local docs in favour of original ones. + + +## [1.0.10] - 2018-02-15 +### Fixed +- Use .concat instead of + for arrays, #122. + + +## [1.0.9] - 2016-09-29 +### Changed +- Rerelease after 1.0.8 - deps cleanup. + + +## [1.0.8] - 2016-09-29 +### Changed +- Maintenance (deps bump, fix node 6.5+ tests, coverage report). + + +## [1.0.7] - 2016-03-17 +### Changed +- Teach `addArgument` to accept string arg names. #97, @tomxtobin. + + +## [1.0.6] - 2016-02-06 +### Changed +- Maintenance: moved to eslint & updated CS. + + +## [1.0.5] - 2016-02-05 +### Changed +- Removed lodash dependency to significantly reduce install size. + Thanks to @mourner. + + +## [1.0.4] - 2016-01-17 +### Changed +- Maintenance: lodash update to 4.0.0. + + +## [1.0.3] - 2015-10-27 +### Fixed +- Fix parse `=` in args: `--examplepath="C:\myfolder\env=x64"`. #84, @CatWithApple. + + +## [1.0.2] - 2015-03-22 +### Changed +- Relaxed lodash version dependency. + + +## [1.0.1] - 2015-02-20 +### Changed +- Changed dependencies to be compatible with ancient nodejs. + + +## [1.0.0] - 2015-02-19 +### Changed +- Maintenance release. +- Replaced `underscore` with `lodash`. +- Bumped version to 1.0.0 to better reflect semver meaning. +- HISTORY.md -> CHANGELOG.md + + +## [0.1.16] - 2013-12-01 +### Changed +- Maintenance release. Updated dependencies and docs. + + +## [0.1.15] - 2013-05-13 +### Fixed +- Fixed #55, @trebor89 + + +## [0.1.14] - 2013-05-12 +### Fixed +- Fixed #62, @maxtaco + + +## [0.1.13] - 2013-04-08 +### Changed +- Added `.npmignore` to reduce package size + + +## [0.1.12] - 2013-02-10 +### Fixed +- Fixed conflictHandler (#46), @hpaulj + + +## [0.1.11] - 2013-02-07 +### Added +- Added 70+ tests (ported from python), @hpaulj +- Added conflictHandler, @applepicke +- Added fromfilePrefixChar, @hpaulj + +### Fixed +- Multiple bugfixes, @hpaulj + + +## [0.1.10] - 2012-12-30 +### Added +- Added [mutual exclusion](http://docs.python.org/dev/library/argparse.html#mutual-exclusion) + support, thanks to @hpaulj + +### Fixed +- Fixed options check for `storeConst` & `appendConst` actions, thanks to @hpaulj + + +## [0.1.9] - 2012-12-27 +### Fixed +- Fixed option dest interferens with other options (issue #23), thanks to @hpaulj +- Fixed default value behavior with `*` positionals, thanks to @hpaulj +- Improve `getDefault()` behavior, thanks to @hpaulj +- Improve negative argument parsing, thanks to @hpaulj + + +## [0.1.8] - 2012-12-01 +### Fixed +- Fixed parser parents (issue #19), thanks to @hpaulj +- Fixed negative argument parse (issue #20), thanks to @hpaulj + + +## [0.1.7] - 2012-10-14 +### Fixed +- Fixed 'choices' argument parse (issue #16) +- Fixed stderr output (issue #15) + + +## [0.1.6] - 2012-09-09 +### Fixed +- Fixed check for conflict of options (thanks to @tomxtobin) + + +## [0.1.5] - 2012-09-03 +### Fixed +- Fix parser #setDefaults method (thanks to @tomxtobin) + + +## [0.1.4] - 2012-07-30 +### Fixed +- Fixed pseudo-argument support (thanks to @CGamesPlay) +- Fixed addHelp default (should be true), if not set (thanks to @benblank) + + +## [0.1.3] - 2012-06-27 +### Fixed +- Fixed formatter api name: Formatter -> HelpFormatter + + +## [0.1.2] - 2012-05-29 +### Fixed +- Removed excess whitespace in help +- Fixed error reporting, when parcer with subcommands + called with empty arguments + +### Added +- Added basic tests + + +## [0.1.1] - 2012-05-23 +### Fixed +- Fixed line wrapping in help formatter +- Added better error reporting on invalid arguments + + +## [0.1.0] - 2012-05-16 +### Added +- First release. + + +[2.0.1]: https://github.com/nodeca/argparse/compare/2.0.0...2.0.1 +[2.0.0]: https://github.com/nodeca/argparse/compare/1.0.10...2.0.0 +[1.0.10]: https://github.com/nodeca/argparse/compare/1.0.9...1.0.10 +[1.0.9]: https://github.com/nodeca/argparse/compare/1.0.8...1.0.9 +[1.0.8]: https://github.com/nodeca/argparse/compare/1.0.7...1.0.8 +[1.0.7]: https://github.com/nodeca/argparse/compare/1.0.6...1.0.7 +[1.0.6]: https://github.com/nodeca/argparse/compare/1.0.5...1.0.6 +[1.0.5]: https://github.com/nodeca/argparse/compare/1.0.4...1.0.5 +[1.0.4]: https://github.com/nodeca/argparse/compare/1.0.3...1.0.4 +[1.0.3]: https://github.com/nodeca/argparse/compare/1.0.2...1.0.3 +[1.0.2]: https://github.com/nodeca/argparse/compare/1.0.1...1.0.2 +[1.0.1]: https://github.com/nodeca/argparse/compare/1.0.0...1.0.1 +[1.0.0]: https://github.com/nodeca/argparse/compare/0.1.16...1.0.0 +[0.1.16]: https://github.com/nodeca/argparse/compare/0.1.15...0.1.16 +[0.1.15]: https://github.com/nodeca/argparse/compare/0.1.14...0.1.15 +[0.1.14]: https://github.com/nodeca/argparse/compare/0.1.13...0.1.14 +[0.1.13]: https://github.com/nodeca/argparse/compare/0.1.12...0.1.13 +[0.1.12]: https://github.com/nodeca/argparse/compare/0.1.11...0.1.12 +[0.1.11]: https://github.com/nodeca/argparse/compare/0.1.10...0.1.11 +[0.1.10]: https://github.com/nodeca/argparse/compare/0.1.9...0.1.10 +[0.1.9]: https://github.com/nodeca/argparse/compare/0.1.8...0.1.9 +[0.1.8]: https://github.com/nodeca/argparse/compare/0.1.7...0.1.8 +[0.1.7]: https://github.com/nodeca/argparse/compare/0.1.6...0.1.7 +[0.1.6]: https://github.com/nodeca/argparse/compare/0.1.5...0.1.6 +[0.1.5]: https://github.com/nodeca/argparse/compare/0.1.4...0.1.5 +[0.1.4]: https://github.com/nodeca/argparse/compare/0.1.3...0.1.4 +[0.1.3]: https://github.com/nodeca/argparse/compare/0.1.2...0.1.3 +[0.1.2]: https://github.com/nodeca/argparse/compare/0.1.1...0.1.2 +[0.1.1]: https://github.com/nodeca/argparse/compare/0.1.0...0.1.1 +[0.1.0]: https://github.com/nodeca/argparse/releases/tag/0.1.0 diff --git a/node_modules/lv_font_conv/node_modules/argparse/LICENSE b/node_modules/lv_font_conv/node_modules/argparse/LICENSE new file mode 100644 index 00000000..66a3ac80 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/argparse/LICENSE @@ -0,0 +1,254 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. + +All Python releases are Open Source (see http://www.opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the Internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the Internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/argparse/README.md b/node_modules/lv_font_conv/node_modules/argparse/README.md new file mode 100644 index 00000000..550b5c9b --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/argparse/README.md @@ -0,0 +1,84 @@ +argparse +======== + +[![Build Status](https://secure.travis-ci.org/nodeca/argparse.svg?branch=master)](http://travis-ci.org/nodeca/argparse) +[![NPM version](https://img.shields.io/npm/v/argparse.svg)](https://www.npmjs.org/package/argparse) + +CLI arguments parser for node.js, with [sub-commands](https://docs.python.org/3.9/library/argparse.html#sub-commands) support. Port of python's [argparse](http://docs.python.org/dev/library/argparse.html) (version [3.9.0](https://github.com/python/cpython/blob/v3.9.0rc1/Lib/argparse.py)). + +**Difference with original.** + +- JS has no keyword arguments support. + - Pass options instead: `new ArgumentParser({ description: 'example', add_help: true })`. +- JS has no python's types `int`, `float`, ... + - Use string-typed names: `.add_argument('-b', { type: 'int', help: 'help' })`. +- `%r` format specifier uses `require('util').inspect()`. + +More details in [doc](./doc). + + +Example +------- + +`test.js` file: + +```javascript +#!/usr/bin/env node +'use strict'; + +const { ArgumentParser } = require('argparse'); +const { version } = require('./package.json'); + +const parser = new ArgumentParser({ + description: 'Argparse example' +}); + +parser.add_argument('-v', '--version', { action: 'version', version }); +parser.add_argument('-f', '--foo', { help: 'foo bar' }); +parser.add_argument('-b', '--bar', { help: 'bar foo' }); +parser.add_argument('--baz', { help: 'baz bar' }); + +console.dir(parser.parse_args()); +``` + +Display help: + +``` +$ ./test.js -h +usage: test.js [-h] [-v] [-f FOO] [-b BAR] [--baz BAZ] + +Argparse example + +optional arguments: + -h, --help show this help message and exit + -v, --version show program's version number and exit + -f FOO, --foo FOO foo bar + -b BAR, --bar BAR bar foo + --baz BAZ baz bar +``` + +Parse arguments: + +``` +$ ./test.js -f=3 --bar=4 --baz 5 +{ foo: '3', bar: '4', baz: '5' } +``` + + +API docs +-------- + +Since this is a port with minimal divergence, there's no separate documentation. +Use original one instead, with notes about difference. + +1. [Original doc](https://docs.python.org/3.9/library/argparse.html). +2. [Original tutorial](https://docs.python.org/3.9/howto/argparse.html). +3. [Difference with python](./doc). + + +argparse for enterprise +----------------------- + +Available as part of the Tidelift Subscription + +The maintainers of argparse and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-argparse?utm_source=npm-argparse&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/node_modules/lv_font_conv/node_modules/argparse/argparse.js b/node_modules/lv_font_conv/node_modules/argparse/argparse.js new file mode 100644 index 00000000..2b8c8c63 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/argparse/argparse.js @@ -0,0 +1,3707 @@ +// Port of python's argparse module, version 3.9.0: +// https://github.com/python/cpython/blob/v3.9.0rc1/Lib/argparse.py + +'use strict' + +// Copyright (C) 2010-2020 Python Software Foundation. +// Copyright (C) 2020 argparse.js authors + +/* + * Command-line parsing library + * + * This module is an optparse-inspired command-line parsing library that: + * + * - handles both optional and positional arguments + * - produces highly informative usage messages + * - supports parsers that dispatch to sub-parsers + * + * The following is a simple usage example that sums integers from the + * command-line and writes the result to a file:: + * + * parser = argparse.ArgumentParser( + * description='sum the integers at the command line') + * parser.add_argument( + * 'integers', metavar='int', nargs='+', type=int, + * help='an integer to be summed') + * parser.add_argument( + * '--log', default=sys.stdout, type=argparse.FileType('w'), + * help='the file where the sum should be written') + * args = parser.parse_args() + * args.log.write('%s' % sum(args.integers)) + * args.log.close() + * + * The module contains the following public classes: + * + * - ArgumentParser -- The main entry point for command-line parsing. As the + * example above shows, the add_argument() method is used to populate + * the parser with actions for optional and positional arguments. Then + * the parse_args() method is invoked to convert the args at the + * command-line into an object with attributes. + * + * - ArgumentError -- The exception raised by ArgumentParser objects when + * there are errors with the parser's actions. Errors raised while + * parsing the command-line are caught by ArgumentParser and emitted + * as command-line messages. + * + * - FileType -- A factory for defining types of files to be created. As the + * example above shows, instances of FileType are typically passed as + * the type= argument of add_argument() calls. + * + * - Action -- The base class for parser actions. Typically actions are + * selected by passing strings like 'store_true' or 'append_const' to + * the action= argument of add_argument(). However, for greater + * customization of ArgumentParser actions, subclasses of Action may + * be defined and passed as the action= argument. + * + * - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter, + * ArgumentDefaultsHelpFormatter -- Formatter classes which + * may be passed as the formatter_class= argument to the + * ArgumentParser constructor. HelpFormatter is the default, + * RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser + * not to change the formatting for help text, and + * ArgumentDefaultsHelpFormatter adds information about argument defaults + * to the help. + * + * All other classes in this module are considered implementation details. + * (Also note that HelpFormatter and RawDescriptionHelpFormatter are only + * considered public as object names -- the API of the formatter objects is + * still considered an implementation detail.) + */ + +const SUPPRESS = '==SUPPRESS==' + +const OPTIONAL = '?' +const ZERO_OR_MORE = '*' +const ONE_OR_MORE = '+' +const PARSER = 'A...' +const REMAINDER = '...' +const _UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args' + + +// ================================== +// Utility functions used for porting +// ================================== +const assert = require('assert') +const util = require('util') +const fs = require('fs') +const sub = require('./lib/sub') +const path = require('path') +const repr = util.inspect + +function get_argv() { + // omit first argument (which is assumed to be interpreter - `node`, `coffee`, `ts-node`, etc.) + return process.argv.slice(1) +} + +function get_terminal_size() { + return { + columns: +process.env.COLUMNS || process.stdout.columns || 80 + } +} + +function hasattr(object, name) { + return Object.prototype.hasOwnProperty.call(object, name) +} + +function getattr(object, name, value) { + return hasattr(object, name) ? object[name] : value +} + +function setattr(object, name, value) { + object[name] = value +} + +function setdefault(object, name, value) { + if (!hasattr(object, name)) object[name] = value + return object[name] +} + +function delattr(object, name) { + delete object[name] +} + +function range(from, to, step=1) { + // range(10) is equivalent to range(0, 10) + if (arguments.length === 1) [ to, from ] = [ from, 0 ] + if (typeof from !== 'number' || typeof to !== 'number' || typeof step !== 'number') { + throw new TypeError('argument cannot be interpreted as an integer') + } + if (step === 0) throw new TypeError('range() arg 3 must not be zero') + + let result = [] + if (step > 0) { + for (let i = from; i < to; i += step) result.push(i) + } else { + for (let i = from; i > to; i += step) result.push(i) + } + return result +} + +function splitlines(str, keepends = false) { + let result + if (!keepends) { + result = str.split(/\r\n|[\n\r\v\f\x1c\x1d\x1e\x85\u2028\u2029]/) + } else { + result = [] + let parts = str.split(/(\r\n|[\n\r\v\f\x1c\x1d\x1e\x85\u2028\u2029])/) + for (let i = 0; i < parts.length; i += 2) { + result.push(parts[i] + (i + 1 < parts.length ? parts[i + 1] : '')) + } + } + if (!result[result.length - 1]) result.pop() + return result +} + +function _string_lstrip(string, prefix_chars) { + let idx = 0 + while (idx < string.length && prefix_chars.includes(string[idx])) idx++ + return idx ? string.slice(idx) : string +} + +function _string_split(string, sep, maxsplit) { + let result = string.split(sep) + if (result.length > maxsplit) { + result = result.slice(0, maxsplit).concat([ result.slice(maxsplit).join(sep) ]) + } + return result +} + +function _array_equal(array1, array2) { + if (array1.length !== array2.length) return false + for (let i = 0; i < array1.length; i++) { + if (array1[i] !== array2[i]) return false + } + return true +} + +function _array_remove(array, item) { + let idx = array.indexOf(item) + if (idx === -1) throw new TypeError(sub('%r not in list', item)) + array.splice(idx, 1) +} + +// normalize choices to array; +// this isn't required in python because `in` and `map` operators work with anything, +// but in js dealing with multiple types here is too clunky +function _choices_to_array(choices) { + if (choices === undefined) { + return [] + } else if (Array.isArray(choices)) { + return choices + } else if (choices !== null && typeof choices[Symbol.iterator] === 'function') { + return Array.from(choices) + } else if (typeof choices === 'object' && choices !== null) { + return Object.keys(choices) + } else { + throw new Error(sub('invalid choices value: %r', choices)) + } +} + +// decorator that allows a class to be called without new +function _callable(cls) { + let result = { // object is needed for inferred class name + [cls.name]: function (...args) { + let this_class = new.target === result || !new.target + return Reflect.construct(cls, args, this_class ? cls : new.target) + } + } + result[cls.name].prototype = cls.prototype + // fix default tag for toString, e.g. [object Action] instead of [object Object] + cls.prototype[Symbol.toStringTag] = cls.name + return result[cls.name] +} + +function _alias(object, from, to) { + try { + let name = object.constructor.name + Object.defineProperty(object, from, { + value: util.deprecate(object[to], sub('%s.%s() is renamed to %s.%s()', + name, from, name, to)), + enumerable: false + }) + } catch {} +} + +// decorator that allows snake_case class methods to be called with camelCase and vice versa +function _camelcase_alias(_class) { + for (let name of Object.getOwnPropertyNames(_class.prototype)) { + let camelcase = name.replace(/\w_[a-z]/g, s => s[0] + s[2].toUpperCase()) + if (camelcase !== name) _alias(_class.prototype, camelcase, name) + } + return _class +} + +function _to_legacy_name(key) { + key = key.replace(/\w_[a-z]/g, s => s[0] + s[2].toUpperCase()) + if (key === 'default') key = 'defaultValue' + if (key === 'const') key = 'constant' + return key +} + +function _to_new_name(key) { + if (key === 'defaultValue') key = 'default' + if (key === 'constant') key = 'const' + key = key.replace(/[A-Z]/g, c => '_' + c.toLowerCase()) + return key +} + +// parse options +let no_default = Symbol('no_default_value') +function _parse_opts(args, descriptor) { + function get_name() { + let stack = new Error().stack.split('\n') + .map(x => x.match(/^ at (.*) \(.*\)$/)) + .filter(Boolean) + .map(m => m[1]) + .map(fn => fn.match(/[^ .]*$/)[0]) + + if (stack.length && stack[0] === get_name.name) stack.shift() + if (stack.length && stack[0] === _parse_opts.name) stack.shift() + return stack.length ? stack[0] : '' + } + + args = Array.from(args) + let kwargs = {} + let result = [] + let last_opt = args.length && args[args.length - 1] + + if (typeof last_opt === 'object' && last_opt !== null && !Array.isArray(last_opt) && + (!last_opt.constructor || last_opt.constructor.name === 'Object')) { + kwargs = Object.assign({}, args.pop()) + } + + // LEGACY (v1 compatibility): camelcase + let renames = [] + for (let key of Object.keys(descriptor)) { + let old_name = _to_legacy_name(key) + if (old_name !== key && (old_name in kwargs)) { + if (key in kwargs) { + // default and defaultValue specified at the same time, happens often in old tests + //throw new TypeError(sub('%s() got multiple values for argument %r', get_name(), key)) + } else { + kwargs[key] = kwargs[old_name] + } + renames.push([ old_name, key ]) + delete kwargs[old_name] + } + } + if (renames.length) { + let name = get_name() + deprecate('camelcase_' + name, sub('%s(): following options are renamed: %s', + name, renames.map(([ a, b ]) => sub('%r -> %r', a, b)))) + } + // end + + let missing_positionals = [] + let positional_count = args.length + + for (let [ key, def ] of Object.entries(descriptor)) { + if (key[0] === '*') { + if (key.length > 0 && key[1] === '*') { + // LEGACY (v1 compatibility): camelcase + let renames = [] + for (let key of Object.keys(kwargs)) { + let new_name = _to_new_name(key) + if (new_name !== key && (key in kwargs)) { + if (new_name in kwargs) { + // default and defaultValue specified at the same time, happens often in old tests + //throw new TypeError(sub('%s() got multiple values for argument %r', get_name(), new_name)) + } else { + kwargs[new_name] = kwargs[key] + } + renames.push([ key, new_name ]) + delete kwargs[key] + } + } + if (renames.length) { + let name = get_name() + deprecate('camelcase_' + name, sub('%s(): following options are renamed: %s', + name, renames.map(([ a, b ]) => sub('%r -> %r', a, b)))) + } + // end + result.push(kwargs) + kwargs = {} + } else { + result.push(args) + args = [] + } + } else if (key in kwargs && args.length > 0) { + throw new TypeError(sub('%s() got multiple values for argument %r', get_name(), key)) + } else if (key in kwargs) { + result.push(kwargs[key]) + delete kwargs[key] + } else if (args.length > 0) { + result.push(args.shift()) + } else if (def !== no_default) { + result.push(def) + } else { + missing_positionals.push(key) + } + } + + if (Object.keys(kwargs).length) { + throw new TypeError(sub('%s() got an unexpected keyword argument %r', + get_name(), Object.keys(kwargs)[0])) + } + + if (args.length) { + let from = Object.entries(descriptor).filter(([ k, v ]) => k[0] !== '*' && v !== no_default).length + let to = Object.entries(descriptor).filter(([ k ]) => k[0] !== '*').length + throw new TypeError(sub('%s() takes %s positional argument%s but %s %s given', + get_name(), + from === to ? sub('from %s to %s', from, to) : to, + from === to && to === 1 ? '' : 's', + positional_count, + positional_count === 1 ? 'was' : 'were')) + } + + if (missing_positionals.length) { + let strs = missing_positionals.map(repr) + if (strs.length > 1) strs[strs.length - 1] = 'and ' + strs[strs.length - 1] + let str_joined = strs.join(strs.length === 2 ? '' : ', ') + throw new TypeError(sub('%s() missing %i required positional argument%s: %s', + get_name(), strs.length, strs.length === 1 ? '' : 's', str_joined)) + } + + return result +} + +let _deprecations = {} +function deprecate(id, string) { + _deprecations[id] = _deprecations[id] || util.deprecate(() => {}, string) + _deprecations[id]() +} + + +// ============================= +// Utility functions and classes +// ============================= +function _AttributeHolder(cls = Object) { + /* + * Abstract base class that provides __repr__. + * + * The __repr__ method returns a string in the format:: + * ClassName(attr=name, attr=name, ...) + * The attributes are determined either by a class-level attribute, + * '_kwarg_names', or by inspecting the instance __dict__. + */ + + return class _AttributeHolder extends cls { + [util.inspect.custom]() { + let type_name = this.constructor.name + let arg_strings = [] + let star_args = {} + for (let arg of this._get_args()) { + arg_strings.push(repr(arg)) + } + for (let [ name, value ] of this._get_kwargs()) { + if (/^[a-z_][a-z0-9_$]*$/i.test(name)) { + arg_strings.push(sub('%s=%r', name, value)) + } else { + star_args[name] = value + } + } + if (Object.keys(star_args).length) { + arg_strings.push(sub('**%s', repr(star_args))) + } + return sub('%s(%s)', type_name, arg_strings.join(', ')) + } + + toString() { + return this[util.inspect.custom]() + } + + _get_kwargs() { + return Object.entries(this) + } + + _get_args() { + return [] + } + } +} + + +function _copy_items(items) { + if (items === undefined) { + return [] + } + return items.slice(0) +} + + +// =============== +// Formatting Help +// =============== +const HelpFormatter = _camelcase_alias(_callable(class HelpFormatter { + /* + * Formatter for generating usage messages and argument help strings. + * + * Only the name of this class is considered a public API. All the methods + * provided by the class are considered an implementation detail. + */ + + constructor() { + let [ + prog, + indent_increment, + max_help_position, + width + ] = _parse_opts(arguments, { + prog: no_default, + indent_increment: 2, + max_help_position: 24, + width: undefined + }) + + // default setting for width + if (width === undefined) { + width = get_terminal_size().columns + width -= 2 + } + + this._prog = prog + this._indent_increment = indent_increment + this._max_help_position = Math.min(max_help_position, + Math.max(width - 20, indent_increment * 2)) + this._width = width + + this._current_indent = 0 + this._level = 0 + this._action_max_length = 0 + + this._root_section = this._Section(this, undefined) + this._current_section = this._root_section + + this._whitespace_matcher = /[ \t\n\r\f\v]+/g // equivalent to python /\s+/ with ASCII flag + this._long_break_matcher = /\n\n\n+/g + } + + // =============================== + // Section and indentation methods + // =============================== + _indent() { + this._current_indent += this._indent_increment + this._level += 1 + } + + _dedent() { + this._current_indent -= this._indent_increment + assert(this._current_indent >= 0, 'Indent decreased below 0.') + this._level -= 1 + } + + _add_item(func, args) { + this._current_section.items.push([ func, args ]) + } + + // ======================== + // Message building methods + // ======================== + start_section(heading) { + this._indent() + let section = this._Section(this, this._current_section, heading) + this._add_item(section.format_help.bind(section), []) + this._current_section = section + } + + end_section() { + this._current_section = this._current_section.parent + this._dedent() + } + + add_text(text) { + if (text !== SUPPRESS && text !== undefined) { + this._add_item(this._format_text.bind(this), [text]) + } + } + + add_usage(usage, actions, groups, prefix = undefined) { + if (usage !== SUPPRESS) { + let args = [ usage, actions, groups, prefix ] + this._add_item(this._format_usage.bind(this), args) + } + } + + add_argument(action) { + if (action.help !== SUPPRESS) { + + // find all invocations + let invocations = [this._format_action_invocation(action)] + for (let subaction of this._iter_indented_subactions(action)) { + invocations.push(this._format_action_invocation(subaction)) + } + + // update the maximum item length + let invocation_length = Math.max(...invocations.map(invocation => invocation.length)) + let action_length = invocation_length + this._current_indent + this._action_max_length = Math.max(this._action_max_length, + action_length) + + // add the item to the list + this._add_item(this._format_action.bind(this), [action]) + } + } + + add_arguments(actions) { + for (let action of actions) { + this.add_argument(action) + } + } + + // ======================= + // Help-formatting methods + // ======================= + format_help() { + let help = this._root_section.format_help() + if (help) { + help = help.replace(this._long_break_matcher, '\n\n') + help = help.replace(/^\n+|\n+$/g, '') + '\n' + } + return help + } + + _join_parts(part_strings) { + return part_strings.filter(part => part && part !== SUPPRESS).join('') + } + + _format_usage(usage, actions, groups, prefix) { + if (prefix === undefined) { + prefix = 'usage: ' + } + + // if usage is specified, use that + if (usage !== undefined) { + usage = sub(usage, { prog: this._prog }) + + // if no optionals or positionals are available, usage is just prog + } else if (usage === undefined && !actions.length) { + usage = sub('%(prog)s', { prog: this._prog }) + + // if optionals and positionals are available, calculate usage + } else if (usage === undefined) { + let prog = sub('%(prog)s', { prog: this._prog }) + + // split optionals from positionals + let optionals = [] + let positionals = [] + for (let action of actions) { + if (action.option_strings.length) { + optionals.push(action) + } else { + positionals.push(action) + } + } + + // build full usage string + let action_usage = this._format_actions_usage([].concat(optionals).concat(positionals), groups) + usage = [ prog, action_usage ].map(String).join(' ') + + // wrap the usage parts if it's too long + let text_width = this._width - this._current_indent + if (prefix.length + usage.length > text_width) { + + // break usage into wrappable parts + let part_regexp = /\(.*?\)+(?=\s|$)|\[.*?\]+(?=\s|$)|\S+/g + let opt_usage = this._format_actions_usage(optionals, groups) + let pos_usage = this._format_actions_usage(positionals, groups) + let opt_parts = opt_usage.match(part_regexp) || [] + let pos_parts = pos_usage.match(part_regexp) || [] + assert(opt_parts.join(' ') === opt_usage) + assert(pos_parts.join(' ') === pos_usage) + + // helper for wrapping lines + let get_lines = (parts, indent, prefix = undefined) => { + let lines = [] + let line = [] + let line_len + if (prefix !== undefined) { + line_len = prefix.length - 1 + } else { + line_len = indent.length - 1 + } + for (let part of parts) { + if (line_len + 1 + part.length > text_width && line) { + lines.push(indent + line.join(' ')) + line = [] + line_len = indent.length - 1 + } + line.push(part) + line_len += part.length + 1 + } + if (line.length) { + lines.push(indent + line.join(' ')) + } + if (prefix !== undefined) { + lines[0] = lines[0].slice(indent.length) + } + return lines + } + + let lines + + // if prog is short, follow it with optionals or positionals + if (prefix.length + prog.length <= 0.75 * text_width) { + let indent = ' '.repeat(prefix.length + prog.length + 1) + if (opt_parts.length) { + lines = get_lines([prog].concat(opt_parts), indent, prefix) + lines = lines.concat(get_lines(pos_parts, indent)) + } else if (pos_parts.length) { + lines = get_lines([prog].concat(pos_parts), indent, prefix) + } else { + lines = [prog] + } + + // if prog is long, put it on its own line + } else { + let indent = ' '.repeat(prefix.length) + let parts = [].concat(opt_parts).concat(pos_parts) + lines = get_lines(parts, indent) + if (lines.length > 1) { + lines = [] + lines = lines.concat(get_lines(opt_parts, indent)) + lines = lines.concat(get_lines(pos_parts, indent)) + } + lines = [prog].concat(lines) + } + + // join lines into usage + usage = lines.join('\n') + } + } + + // prefix with 'usage:' + return sub('%s%s\n\n', prefix, usage) + } + + _format_actions_usage(actions, groups) { + // find group indices and identify actions in groups + let group_actions = new Set() + let inserts = {} + for (let group of groups) { + let start = actions.indexOf(group._group_actions[0]) + if (start === -1) { + continue + } else { + let end = start + group._group_actions.length + if (_array_equal(actions.slice(start, end), group._group_actions)) { + for (let action of group._group_actions) { + group_actions.add(action) + } + if (!group.required) { + if (start in inserts) { + inserts[start] += ' [' + } else { + inserts[start] = '[' + } + if (end in inserts) { + inserts[end] += ']' + } else { + inserts[end] = ']' + } + } else { + if (start in inserts) { + inserts[start] += ' (' + } else { + inserts[start] = '(' + } + if (end in inserts) { + inserts[end] += ')' + } else { + inserts[end] = ')' + } + } + for (let i of range(start + 1, end)) { + inserts[i] = '|' + } + } + } + } + + // collect all actions format strings + let parts = [] + for (let [ i, action ] of Object.entries(actions)) { + + // suppressed arguments are marked with None + // remove | separators for suppressed arguments + if (action.help === SUPPRESS) { + parts.push(undefined) + if (inserts[+i] === '|') { + delete inserts[+i] + } else if (inserts[+i + 1] === '|') { + delete inserts[+i + 1] + } + + // produce all arg strings + } else if (!action.option_strings.length) { + let default_value = this._get_default_metavar_for_positional(action) + let part = this._format_args(action, default_value) + + // if it's in a group, strip the outer [] + if (group_actions.has(action)) { + if (part[0] === '[' && part[part.length - 1] === ']') { + part = part.slice(1, -1) + } + } + + // add the action string to the list + parts.push(part) + + // produce the first way to invoke the option in brackets + } else { + let option_string = action.option_strings[0] + let part + + // if the Optional doesn't take a value, format is: + // -s or --long + if (action.nargs === 0) { + part = action.format_usage() + + // if the Optional takes a value, format is: + // -s ARGS or --long ARGS + } else { + let default_value = this._get_default_metavar_for_optional(action) + let args_string = this._format_args(action, default_value) + part = sub('%s %s', option_string, args_string) + } + + // make it look optional if it's not required or in a group + if (!action.required && !group_actions.has(action)) { + part = sub('[%s]', part) + } + + // add the action string to the list + parts.push(part) + } + } + + // insert things at the necessary indices + for (let i of Object.keys(inserts).map(Number).sort((a, b) => b - a)) { + parts.splice(+i, 0, inserts[+i]) + } + + // join all the action items with spaces + let text = parts.filter(Boolean).join(' ') + + // clean up separators for mutually exclusive groups + text = text.replace(/([\[(]) /g, '$1') + text = text.replace(/ ([\])])/g, '$1') + text = text.replace(/[\[(] *[\])]/g, '') + text = text.replace(/\(([^|]*)\)/g, '$1', text) + text = text.trim() + + // return the text + return text + } + + _format_text(text) { + if (text.includes('%(prog)')) { + text = sub(text, { prog: this._prog }) + } + let text_width = Math.max(this._width - this._current_indent, 11) + let indent = ' '.repeat(this._current_indent) + return this._fill_text(text, text_width, indent) + '\n\n' + } + + _format_action(action) { + // determine the required width and the entry label + let help_position = Math.min(this._action_max_length + 2, + this._max_help_position) + let help_width = Math.max(this._width - help_position, 11) + let action_width = help_position - this._current_indent - 2 + let action_header = this._format_action_invocation(action) + let indent_first + + // no help; start on same line and add a final newline + if (!action.help) { + let tup = [ this._current_indent, '', action_header ] + action_header = sub('%*s%s\n', ...tup) + + // short action name; start on the same line and pad two spaces + } else if (action_header.length <= action_width) { + let tup = [ this._current_indent, '', action_width, action_header ] + action_header = sub('%*s%-*s ', ...tup) + indent_first = 0 + + // long action name; start on the next line + } else { + let tup = [ this._current_indent, '', action_header ] + action_header = sub('%*s%s\n', ...tup) + indent_first = help_position + } + + // collect the pieces of the action help + let parts = [action_header] + + // if there was help for the action, add lines of help text + if (action.help) { + let help_text = this._expand_help(action) + let help_lines = this._split_lines(help_text, help_width) + parts.push(sub('%*s%s\n', indent_first, '', help_lines[0])) + for (let line of help_lines.slice(1)) { + parts.push(sub('%*s%s\n', help_position, '', line)) + } + + // or add a newline if the description doesn't end with one + } else if (!action_header.endsWith('\n')) { + parts.push('\n') + } + + // if there are any sub-actions, add their help as well + for (let subaction of this._iter_indented_subactions(action)) { + parts.push(this._format_action(subaction)) + } + + // return a single string + return this._join_parts(parts) + } + + _format_action_invocation(action) { + if (!action.option_strings.length) { + let default_value = this._get_default_metavar_for_positional(action) + let metavar = this._metavar_formatter(action, default_value)(1)[0] + return metavar + + } else { + let parts = [] + + // if the Optional doesn't take a value, format is: + // -s, --long + if (action.nargs === 0) { + parts = parts.concat(action.option_strings) + + // if the Optional takes a value, format is: + // -s ARGS, --long ARGS + } else { + let default_value = this._get_default_metavar_for_optional(action) + let args_string = this._format_args(action, default_value) + for (let option_string of action.option_strings) { + parts.push(sub('%s %s', option_string, args_string)) + } + } + + return parts.join(', ') + } + } + + _metavar_formatter(action, default_metavar) { + let result + if (action.metavar !== undefined) { + result = action.metavar + } else if (action.choices !== undefined) { + let choice_strs = _choices_to_array(action.choices).map(String) + result = sub('{%s}', choice_strs.join(',')) + } else { + result = default_metavar + } + + function format(tuple_size) { + if (Array.isArray(result)) { + return result + } else { + return Array(tuple_size).fill(result) + } + } + return format + } + + _format_args(action, default_metavar) { + let get_metavar = this._metavar_formatter(action, default_metavar) + let result + if (action.nargs === undefined) { + result = sub('%s', ...get_metavar(1)) + } else if (action.nargs === OPTIONAL) { + result = sub('[%s]', ...get_metavar(1)) + } else if (action.nargs === ZERO_OR_MORE) { + let metavar = get_metavar(1) + if (metavar.length === 2) { + result = sub('[%s [%s ...]]', ...metavar) + } else { + result = sub('[%s ...]', ...metavar) + } + } else if (action.nargs === ONE_OR_MORE) { + result = sub('%s [%s ...]', ...get_metavar(2)) + } else if (action.nargs === REMAINDER) { + result = '...' + } else if (action.nargs === PARSER) { + result = sub('%s ...', ...get_metavar(1)) + } else if (action.nargs === SUPPRESS) { + result = '' + } else { + let formats + try { + formats = range(action.nargs).map(() => '%s') + } catch (err) { + throw new TypeError('invalid nargs value') + } + result = sub(formats.join(' '), ...get_metavar(action.nargs)) + } + return result + } + + _expand_help(action) { + let params = Object.assign({ prog: this._prog }, action) + for (let name of Object.keys(params)) { + if (params[name] === SUPPRESS) { + delete params[name] + } + } + for (let name of Object.keys(params)) { + if (params[name] && params[name].name) { + params[name] = params[name].name + } + } + if (params.choices !== undefined) { + let choices_str = _choices_to_array(params.choices).map(String).join(', ') + params.choices = choices_str + } + // LEGACY (v1 compatibility): camelcase + for (let key of Object.keys(params)) { + let old_name = _to_legacy_name(key) + if (old_name !== key) { + params[old_name] = params[key] + } + } + // end + return sub(this._get_help_string(action), params) + } + + * _iter_indented_subactions(action) { + if (typeof action._get_subactions === 'function') { + this._indent() + yield* action._get_subactions() + this._dedent() + } + } + + _split_lines(text, width) { + text = text.replace(this._whitespace_matcher, ' ').trim() + // The textwrap module is used only for formatting help. + // Delay its import for speeding up the common usage of argparse. + let textwrap = require('./lib/textwrap') + return textwrap.wrap(text, { width }) + } + + _fill_text(text, width, indent) { + text = text.replace(this._whitespace_matcher, ' ').trim() + let textwrap = require('./lib/textwrap') + return textwrap.fill(text, { width, + initial_indent: indent, + subsequent_indent: indent }) + } + + _get_help_string(action) { + return action.help + } + + _get_default_metavar_for_optional(action) { + return action.dest.toUpperCase() + } + + _get_default_metavar_for_positional(action) { + return action.dest + } +})) + +HelpFormatter.prototype._Section = _callable(class _Section { + + constructor(formatter, parent, heading = undefined) { + this.formatter = formatter + this.parent = parent + this.heading = heading + this.items = [] + } + + format_help() { + // format the indented section + if (this.parent !== undefined) { + this.formatter._indent() + } + let item_help = this.formatter._join_parts(this.items.map(([ func, args ]) => func.apply(null, args))) + if (this.parent !== undefined) { + this.formatter._dedent() + } + + // return nothing if the section was empty + if (!item_help) { + return '' + } + + // add the heading if the section was non-empty + let heading + if (this.heading !== SUPPRESS && this.heading !== undefined) { + let current_indent = this.formatter._current_indent + heading = sub('%*s%s:\n', current_indent, '', this.heading) + } else { + heading = '' + } + + // join the section-initial newline, the heading and the help + return this.formatter._join_parts(['\n', heading, item_help, '\n']) + } +}) + + +const RawDescriptionHelpFormatter = _camelcase_alias(_callable(class RawDescriptionHelpFormatter extends HelpFormatter { + /* + * Help message formatter which retains any formatting in descriptions. + * + * Only the name of this class is considered a public API. All the methods + * provided by the class are considered an implementation detail. + */ + + _fill_text(text, width, indent) { + return splitlines(text, true).map(line => indent + line).join('') + } +})) + + +const RawTextHelpFormatter = _camelcase_alias(_callable(class RawTextHelpFormatter extends RawDescriptionHelpFormatter { + /* + * Help message formatter which retains formatting of all help text. + * + * Only the name of this class is considered a public API. All the methods + * provided by the class are considered an implementation detail. + */ + + _split_lines(text/*, width*/) { + return splitlines(text) + } +})) + + +const ArgumentDefaultsHelpFormatter = _camelcase_alias(_callable(class ArgumentDefaultsHelpFormatter extends HelpFormatter { + /* + * Help message formatter which adds default values to argument help. + * + * Only the name of this class is considered a public API. All the methods + * provided by the class are considered an implementation detail. + */ + + _get_help_string(action) { + let help = action.help + // LEGACY (v1 compatibility): additional check for defaultValue needed + if (!action.help.includes('%(default)') && !action.help.includes('%(defaultValue)')) { + if (action.default !== SUPPRESS) { + let defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] + if (action.option_strings.length || defaulting_nargs.includes(action.nargs)) { + help += ' (default: %(default)s)' + } + } + } + return help + } +})) + + +const MetavarTypeHelpFormatter = _camelcase_alias(_callable(class MetavarTypeHelpFormatter extends HelpFormatter { + /* + * Help message formatter which uses the argument 'type' as the default + * metavar value (instead of the argument 'dest') + * + * Only the name of this class is considered a public API. All the methods + * provided by the class are considered an implementation detail. + */ + + _get_default_metavar_for_optional(action) { + return typeof action.type === 'function' ? action.type.name : action.type + } + + _get_default_metavar_for_positional(action) { + return typeof action.type === 'function' ? action.type.name : action.type + } +})) + + +// ===================== +// Options and Arguments +// ===================== +function _get_action_name(argument) { + if (argument === undefined) { + return undefined + } else if (argument.option_strings.length) { + return argument.option_strings.join('/') + } else if (![ undefined, SUPPRESS ].includes(argument.metavar)) { + return argument.metavar + } else if (![ undefined, SUPPRESS ].includes(argument.dest)) { + return argument.dest + } else { + return undefined + } +} + + +const ArgumentError = _callable(class ArgumentError extends Error { + /* + * An error from creating or using an argument (optional or positional). + * + * The string value of this exception is the message, augmented with + * information about the argument that caused it. + */ + + constructor(argument, message) { + super() + this.name = 'ArgumentError' + this._argument_name = _get_action_name(argument) + this._message = message + this.message = this.str() + } + + str() { + let format + if (this._argument_name === undefined) { + format = '%(message)s' + } else { + format = 'argument %(argument_name)s: %(message)s' + } + return sub(format, { message: this._message, + argument_name: this._argument_name }) + } +}) + + +const ArgumentTypeError = _callable(class ArgumentTypeError extends Error { + /* + * An error from trying to convert a command line string to a type. + */ + + constructor(message) { + super(message) + this.name = 'ArgumentTypeError' + } +}) + + +// ============== +// Action classes +// ============== +const Action = _camelcase_alias(_callable(class Action extends _AttributeHolder(Function) { + /* + * Information about how to convert command line strings to Python objects. + * + * Action objects are used by an ArgumentParser to represent the information + * needed to parse a single argument from one or more strings from the + * command line. The keyword arguments to the Action constructor are also + * all attributes of Action instances. + * + * Keyword Arguments: + * + * - option_strings -- A list of command-line option strings which + * should be associated with this action. + * + * - dest -- The name of the attribute to hold the created object(s) + * + * - nargs -- The number of command-line arguments that should be + * consumed. By default, one argument will be consumed and a single + * value will be produced. Other values include: + * - N (an integer) consumes N arguments (and produces a list) + * - '?' consumes zero or one arguments + * - '*' consumes zero or more arguments (and produces a list) + * - '+' consumes one or more arguments (and produces a list) + * Note that the difference between the default and nargs=1 is that + * with the default, a single value will be produced, while with + * nargs=1, a list containing a single value will be produced. + * + * - const -- The value to be produced if the option is specified and the + * option uses an action that takes no values. + * + * - default -- The value to be produced if the option is not specified. + * + * - type -- A callable that accepts a single string argument, and + * returns the converted value. The standard Python types str, int, + * float, and complex are useful examples of such callables. If None, + * str is used. + * + * - choices -- A container of values that should be allowed. If not None, + * after a command-line argument has been converted to the appropriate + * type, an exception will be raised if it is not a member of this + * collection. + * + * - required -- True if the action must always be specified at the + * command line. This is only meaningful for optional command-line + * arguments. + * + * - help -- The help string describing the argument. + * + * - metavar -- The name to be used for the option's argument with the + * help string. If None, the 'dest' value will be used as the name. + */ + + constructor() { + let [ + option_strings, + dest, + nargs, + const_value, + default_value, + type, + choices, + required, + help, + metavar + ] = _parse_opts(arguments, { + option_strings: no_default, + dest: no_default, + nargs: undefined, + const: undefined, + default: undefined, + type: undefined, + choices: undefined, + required: false, + help: undefined, + metavar: undefined + }) + + // when this class is called as a function, redirect it to .call() method of itself + super('return arguments.callee.call.apply(arguments.callee, arguments)') + + this.option_strings = option_strings + this.dest = dest + this.nargs = nargs + this.const = const_value + this.default = default_value + this.type = type + this.choices = choices + this.required = required + this.help = help + this.metavar = metavar + } + + _get_kwargs() { + let names = [ + 'option_strings', + 'dest', + 'nargs', + 'const', + 'default', + 'type', + 'choices', + 'help', + 'metavar' + ] + return names.map(name => [ name, getattr(this, name) ]) + } + + format_usage() { + return this.option_strings[0] + } + + call(/*parser, namespace, values, option_string = undefined*/) { + throw new Error('.call() not defined') + } +})) + + +const BooleanOptionalAction = _camelcase_alias(_callable(class BooleanOptionalAction extends Action { + + constructor() { + let [ + option_strings, + dest, + default_value, + type, + choices, + required, + help, + metavar + ] = _parse_opts(arguments, { + option_strings: no_default, + dest: no_default, + default: undefined, + type: undefined, + choices: undefined, + required: false, + help: undefined, + metavar: undefined + }) + + let _option_strings = [] + for (let option_string of option_strings) { + _option_strings.push(option_string) + + if (option_string.startsWith('--')) { + option_string = '--no-' + option_string.slice(2) + _option_strings.push(option_string) + } + } + + if (help !== undefined && default_value !== undefined) { + help += ` (default: ${default_value})` + } + + super({ + option_strings: _option_strings, + dest, + nargs: 0, + default: default_value, + type, + choices, + required, + help, + metavar + }) + } + + call(parser, namespace, values, option_string = undefined) { + if (this.option_strings.includes(option_string)) { + setattr(namespace, this.dest, !option_string.startsWith('--no-')) + } + } + + format_usage() { + return this.option_strings.join(' | ') + } +})) + + +const _StoreAction = _callable(class _StoreAction extends Action { + + constructor() { + let [ + option_strings, + dest, + nargs, + const_value, + default_value, + type, + choices, + required, + help, + metavar + ] = _parse_opts(arguments, { + option_strings: no_default, + dest: no_default, + nargs: undefined, + const: undefined, + default: undefined, + type: undefined, + choices: undefined, + required: false, + help: undefined, + metavar: undefined + }) + + if (nargs === 0) { + throw new TypeError('nargs for store actions must be != 0; if you ' + + 'have nothing to store, actions such as store ' + + 'true or store const may be more appropriate') + } + if (const_value !== undefined && nargs !== OPTIONAL) { + throw new TypeError(sub('nargs must be %r to supply const', OPTIONAL)) + } + super({ + option_strings, + dest, + nargs, + const: const_value, + default: default_value, + type, + choices, + required, + help, + metavar + }) + } + + call(parser, namespace, values/*, option_string = undefined*/) { + setattr(namespace, this.dest, values) + } +}) + + +const _StoreConstAction = _callable(class _StoreConstAction extends Action { + + constructor() { + let [ + option_strings, + dest, + const_value, + default_value, + required, + help + //, metavar + ] = _parse_opts(arguments, { + option_strings: no_default, + dest: no_default, + const: no_default, + default: undefined, + required: false, + help: undefined, + metavar: undefined + }) + + super({ + option_strings, + dest, + nargs: 0, + const: const_value, + default: default_value, + required, + help + }) + } + + call(parser, namespace/*, values, option_string = undefined*/) { + setattr(namespace, this.dest, this.const) + } +}) + + +const _StoreTrueAction = _callable(class _StoreTrueAction extends _StoreConstAction { + + constructor() { + let [ + option_strings, + dest, + default_value, + required, + help + ] = _parse_opts(arguments, { + option_strings: no_default, + dest: no_default, + default: false, + required: false, + help: undefined + }) + + super({ + option_strings, + dest, + const: true, + default: default_value, + required, + help + }) + } +}) + + +const _StoreFalseAction = _callable(class _StoreFalseAction extends _StoreConstAction { + + constructor() { + let [ + option_strings, + dest, + default_value, + required, + help + ] = _parse_opts(arguments, { + option_strings: no_default, + dest: no_default, + default: true, + required: false, + help: undefined + }) + + super({ + option_strings, + dest, + const: false, + default: default_value, + required, + help + }) + } +}) + + +const _AppendAction = _callable(class _AppendAction extends Action { + + constructor() { + let [ + option_strings, + dest, + nargs, + const_value, + default_value, + type, + choices, + required, + help, + metavar + ] = _parse_opts(arguments, { + option_strings: no_default, + dest: no_default, + nargs: undefined, + const: undefined, + default: undefined, + type: undefined, + choices: undefined, + required: false, + help: undefined, + metavar: undefined + }) + + if (nargs === 0) { + throw new TypeError('nargs for append actions must be != 0; if arg ' + + 'strings are not supplying the value to append, ' + + 'the append const action may be more appropriate') + } + if (const_value !== undefined && nargs !== OPTIONAL) { + throw new TypeError(sub('nargs must be %r to supply const', OPTIONAL)) + } + super({ + option_strings, + dest, + nargs, + const: const_value, + default: default_value, + type, + choices, + required, + help, + metavar + }) + } + + call(parser, namespace, values/*, option_string = undefined*/) { + let items = getattr(namespace, this.dest, undefined) + items = _copy_items(items) + items.push(values) + setattr(namespace, this.dest, items) + } +}) + + +const _AppendConstAction = _callable(class _AppendConstAction extends Action { + + constructor() { + let [ + option_strings, + dest, + const_value, + default_value, + required, + help, + metavar + ] = _parse_opts(arguments, { + option_strings: no_default, + dest: no_default, + const: no_default, + default: undefined, + required: false, + help: undefined, + metavar: undefined + }) + + super({ + option_strings, + dest, + nargs: 0, + const: const_value, + default: default_value, + required, + help, + metavar + }) + } + + call(parser, namespace/*, values, option_string = undefined*/) { + let items = getattr(namespace, this.dest, undefined) + items = _copy_items(items) + items.push(this.const) + setattr(namespace, this.dest, items) + } +}) + + +const _CountAction = _callable(class _CountAction extends Action { + + constructor() { + let [ + option_strings, + dest, + default_value, + required, + help + ] = _parse_opts(arguments, { + option_strings: no_default, + dest: no_default, + default: undefined, + required: false, + help: undefined + }) + + super({ + option_strings, + dest, + nargs: 0, + default: default_value, + required, + help + }) + } + + call(parser, namespace/*, values, option_string = undefined*/) { + let count = getattr(namespace, this.dest, undefined) + if (count === undefined) { + count = 0 + } + setattr(namespace, this.dest, count + 1) + } +}) + + +const _HelpAction = _callable(class _HelpAction extends Action { + + constructor() { + let [ + option_strings, + dest, + default_value, + help + ] = _parse_opts(arguments, { + option_strings: no_default, + dest: SUPPRESS, + default: SUPPRESS, + help: undefined + }) + + super({ + option_strings, + dest, + default: default_value, + nargs: 0, + help + }) + } + + call(parser/*, namespace, values, option_string = undefined*/) { + parser.print_help() + parser.exit() + } +}) + + +const _VersionAction = _callable(class _VersionAction extends Action { + + constructor() { + let [ + option_strings, + version, + dest, + default_value, + help + ] = _parse_opts(arguments, { + option_strings: no_default, + version: undefined, + dest: SUPPRESS, + default: SUPPRESS, + help: "show program's version number and exit" + }) + + super({ + option_strings, + dest, + default: default_value, + nargs: 0, + help + }) + this.version = version + } + + call(parser/*, namespace, values, option_string = undefined*/) { + let version = this.version + if (version === undefined) { + version = parser.version + } + let formatter = parser._get_formatter() + formatter.add_text(version) + parser._print_message(formatter.format_help(), process.stdout) + parser.exit() + } +}) + + +const _SubParsersAction = _camelcase_alias(_callable(class _SubParsersAction extends Action { + + constructor() { + let [ + option_strings, + prog, + parser_class, + dest, + required, + help, + metavar + ] = _parse_opts(arguments, { + option_strings: no_default, + prog: no_default, + parser_class: no_default, + dest: SUPPRESS, + required: false, + help: undefined, + metavar: undefined + }) + + let name_parser_map = {} + + super({ + option_strings, + dest, + nargs: PARSER, + choices: name_parser_map, + required, + help, + metavar + }) + + this._prog_prefix = prog + this._parser_class = parser_class + this._name_parser_map = name_parser_map + this._choices_actions = [] + } + + add_parser() { + let [ + name, + kwargs + ] = _parse_opts(arguments, { + name: no_default, + '**kwargs': no_default + }) + + // set prog from the existing prefix + if (kwargs.prog === undefined) { + kwargs.prog = sub('%s %s', this._prog_prefix, name) + } + + let aliases = getattr(kwargs, 'aliases', []) + delete kwargs.aliases + + // create a pseudo-action to hold the choice help + if ('help' in kwargs) { + let help = kwargs.help + delete kwargs.help + let choice_action = this._ChoicesPseudoAction(name, aliases, help) + this._choices_actions.push(choice_action) + } + + // create the parser and add it to the map + let parser = new this._parser_class(kwargs) + this._name_parser_map[name] = parser + + // make parser available under aliases also + for (let alias of aliases) { + this._name_parser_map[alias] = parser + } + + return parser + } + + _get_subactions() { + return this._choices_actions + } + + call(parser, namespace, values/*, option_string = undefined*/) { + let parser_name = values[0] + let arg_strings = values.slice(1) + + // set the parser name if requested + if (this.dest !== SUPPRESS) { + setattr(namespace, this.dest, parser_name) + } + + // select the parser + if (hasattr(this._name_parser_map, parser_name)) { + parser = this._name_parser_map[parser_name] + } else { + let args = {parser_name, + choices: this._name_parser_map.join(', ')} + let msg = sub('unknown parser %(parser_name)r (choices: %(choices)s)', args) + throw new ArgumentError(this, msg) + } + + // parse all the remaining options into the namespace + // store any unrecognized options on the object, so that the top + // level parser can decide what to do with them + + // In case this subparser defines new defaults, we parse them + // in a new namespace object and then update the original + // namespace for the relevant parts. + let subnamespace + [ subnamespace, arg_strings ] = parser.parse_known_args(arg_strings, undefined) + for (let [ key, value ] of Object.entries(subnamespace)) { + setattr(namespace, key, value) + } + + if (arg_strings.length) { + setdefault(namespace, _UNRECOGNIZED_ARGS_ATTR, []) + getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).push(...arg_strings) + } + } +})) + + +_SubParsersAction.prototype._ChoicesPseudoAction = _callable(class _ChoicesPseudoAction extends Action { + constructor(name, aliases, help) { + let metavar = name, dest = name + if (aliases.length) { + metavar += sub(' (%s)', aliases.join(', ')) + } + super({ option_strings: [], dest, help, metavar }) + } +}) + + +const _ExtendAction = _callable(class _ExtendAction extends _AppendAction { + call(parser, namespace, values/*, option_string = undefined*/) { + let items = getattr(namespace, this.dest, undefined) + items = _copy_items(items) + items = items.concat(values) + setattr(namespace, this.dest, items) + } +}) + + +// ============== +// Type classes +// ============== +const FileType = _callable(class FileType extends Function { + /* + * Factory for creating file object types + * + * Instances of FileType are typically passed as type= arguments to the + * ArgumentParser add_argument() method. + * + * Keyword Arguments: + * - mode -- A string indicating how the file is to be opened. Accepts the + * same values as the builtin open() function. + * - bufsize -- The file's desired buffer size. Accepts the same values as + * the builtin open() function. + * - encoding -- The file's encoding. Accepts the same values as the + * builtin open() function. + * - errors -- A string indicating how encoding and decoding errors are to + * be handled. Accepts the same value as the builtin open() function. + */ + + constructor() { + let [ + flags, + encoding, + mode, + autoClose, + emitClose, + start, + end, + highWaterMark, + fs + ] = _parse_opts(arguments, { + flags: 'r', + encoding: undefined, + mode: undefined, // 0o666 + autoClose: undefined, // true + emitClose: undefined, // false + start: undefined, // 0 + end: undefined, // Infinity + highWaterMark: undefined, // 64 * 1024 + fs: undefined + }) + + // when this class is called as a function, redirect it to .call() method of itself + super('return arguments.callee.call.apply(arguments.callee, arguments)') + + Object.defineProperty(this, 'name', { + get() { + return sub('FileType(%r)', flags) + } + }) + this._flags = flags + this._options = {} + if (encoding !== undefined) this._options.encoding = encoding + if (mode !== undefined) this._options.mode = mode + if (autoClose !== undefined) this._options.autoClose = autoClose + if (emitClose !== undefined) this._options.emitClose = emitClose + if (start !== undefined) this._options.start = start + if (end !== undefined) this._options.end = end + if (highWaterMark !== undefined) this._options.highWaterMark = highWaterMark + if (fs !== undefined) this._options.fs = fs + } + + call(string) { + // the special argument "-" means sys.std{in,out} + if (string === '-') { + if (this._flags.includes('r')) { + return process.stdin + } else if (this._flags.includes('w')) { + return process.stdout + } else { + let msg = sub('argument "-" with mode %r', this._flags) + throw new TypeError(msg) + } + } + + // all other arguments are used as file names + let fd + try { + fd = fs.openSync(string, this._flags, this._options.mode) + } catch (e) { + let args = { filename: string, error: e.message } + let message = "can't open '%(filename)s': %(error)s" + throw new ArgumentTypeError(sub(message, args)) + } + + let options = Object.assign({ fd, flags: this._flags }, this._options) + if (this._flags.includes('r')) { + return fs.createReadStream(undefined, options) + } else if (this._flags.includes('w')) { + return fs.createWriteStream(undefined, options) + } else { + let msg = sub('argument "%s" with mode %r', string, this._flags) + throw new TypeError(msg) + } + } + + [util.inspect.custom]() { + let args = [ this._flags ] + let kwargs = Object.entries(this._options).map(([ k, v ]) => { + if (k === 'mode') v = { value: v, [util.inspect.custom]() { return '0o' + this.value.toString(8) } } + return [ k, v ] + }) + let args_str = [] + .concat(args.filter(arg => arg !== -1).map(repr)) + .concat(kwargs.filter(([/*kw*/, arg]) => arg !== undefined) + .map(([kw, arg]) => sub('%s=%r', kw, arg))) + .join(', ') + return sub('%s(%s)', this.constructor.name, args_str) + } + + toString() { + return this[util.inspect.custom]() + } +}) + +// =========================== +// Optional and Positional Parsing +// =========================== +const Namespace = _callable(class Namespace extends _AttributeHolder() { + /* + * Simple object for storing attributes. + * + * Implements equality by attribute names and values, and provides a simple + * string representation. + */ + + constructor(options = {}) { + super() + Object.assign(this, options) + } +}) + +// unset string tag to mimic plain object +Namespace.prototype[Symbol.toStringTag] = undefined + + +const _ActionsContainer = _camelcase_alias(_callable(class _ActionsContainer { + + constructor() { + let [ + description, + prefix_chars, + argument_default, + conflict_handler + ] = _parse_opts(arguments, { + description: no_default, + prefix_chars: no_default, + argument_default: no_default, + conflict_handler: no_default + }) + + this.description = description + this.argument_default = argument_default + this.prefix_chars = prefix_chars + this.conflict_handler = conflict_handler + + // set up registries + this._registries = {} + + // register actions + this.register('action', undefined, _StoreAction) + this.register('action', 'store', _StoreAction) + this.register('action', 'store_const', _StoreConstAction) + this.register('action', 'store_true', _StoreTrueAction) + this.register('action', 'store_false', _StoreFalseAction) + this.register('action', 'append', _AppendAction) + this.register('action', 'append_const', _AppendConstAction) + this.register('action', 'count', _CountAction) + this.register('action', 'help', _HelpAction) + this.register('action', 'version', _VersionAction) + this.register('action', 'parsers', _SubParsersAction) + this.register('action', 'extend', _ExtendAction) + // LEGACY (v1 compatibility): camelcase variants + ;[ 'storeConst', 'storeTrue', 'storeFalse', 'appendConst' ].forEach(old_name => { + let new_name = _to_new_name(old_name) + this.register('action', old_name, util.deprecate(this._registry_get('action', new_name), + sub('{action: "%s"} is renamed to {action: "%s"}', old_name, new_name))) + }) + // end + + // raise an exception if the conflict handler is invalid + this._get_handler() + + // action storage + this._actions = [] + this._option_string_actions = {} + + // groups + this._action_groups = [] + this._mutually_exclusive_groups = [] + + // defaults storage + this._defaults = {} + + // determines whether an "option" looks like a negative number + this._negative_number_matcher = /^-\d+$|^-\d*\.\d+$/ + + // whether or not there are any optionals that look like negative + // numbers -- uses a list so it can be shared and edited + this._has_negative_number_optionals = [] + } + + // ==================== + // Registration methods + // ==================== + register(registry_name, value, object) { + let registry = setdefault(this._registries, registry_name, {}) + registry[value] = object + } + + _registry_get(registry_name, value, default_value = undefined) { + return getattr(this._registries[registry_name], value, default_value) + } + + // ================================== + // Namespace default accessor methods + // ================================== + set_defaults(kwargs) { + Object.assign(this._defaults, kwargs) + + // if these defaults match any existing arguments, replace + // the previous default on the object with the new one + for (let action of this._actions) { + if (action.dest in kwargs) { + action.default = kwargs[action.dest] + } + } + } + + get_default(dest) { + for (let action of this._actions) { + if (action.dest === dest && action.default !== undefined) { + return action.default + } + } + return this._defaults[dest] + } + + + // ======================= + // Adding argument actions + // ======================= + add_argument() { + /* + * add_argument(dest, ..., name=value, ...) + * add_argument(option_string, option_string, ..., name=value, ...) + */ + let [ + args, + kwargs + ] = _parse_opts(arguments, { + '*args': no_default, + '**kwargs': no_default + }) + // LEGACY (v1 compatibility), old-style add_argument([ args ], { options }) + if (args.length === 1 && Array.isArray(args[0])) { + args = args[0] + deprecate('argument-array', + sub('use add_argument(%(args)s, {...}) instead of add_argument([ %(args)s ], { ... })', { + args: args.map(repr).join(', ') + })) + } + // end + + // if no positional args are supplied or only one is supplied and + // it doesn't look like an option string, parse a positional + // argument + let chars = this.prefix_chars + if (!args.length || args.length === 1 && !chars.includes(args[0][0])) { + if (args.length && 'dest' in kwargs) { + throw new TypeError('dest supplied twice for positional argument') + } + kwargs = this._get_positional_kwargs(...args, kwargs) + + // otherwise, we're adding an optional argument + } else { + kwargs = this._get_optional_kwargs(...args, kwargs) + } + + // if no default was supplied, use the parser-level default + if (!('default' in kwargs)) { + let dest = kwargs.dest + if (dest in this._defaults) { + kwargs.default = this._defaults[dest] + } else if (this.argument_default !== undefined) { + kwargs.default = this.argument_default + } + } + + // create the action object, and add it to the parser + let action_class = this._pop_action_class(kwargs) + if (typeof action_class !== 'function') { + throw new TypeError(sub('unknown action "%s"', action_class)) + } + // eslint-disable-next-line new-cap + let action = new action_class(kwargs) + + // raise an error if the action type is not callable + let type_func = this._registry_get('type', action.type, action.type) + if (typeof type_func !== 'function') { + throw new TypeError(sub('%r is not callable', type_func)) + } + + if (type_func === FileType) { + throw new TypeError(sub('%r is a FileType class object, instance of it' + + ' must be passed', type_func)) + } + + // raise an error if the metavar does not match the type + if ('_get_formatter' in this) { + try { + this._get_formatter()._format_args(action, undefined) + } catch (err) { + // check for 'invalid nargs value' is an artifact of TypeError and ValueError in js being the same + if (err instanceof TypeError && err.message !== 'invalid nargs value') { + throw new TypeError('length of metavar tuple does not match nargs') + } else { + throw err + } + } + } + + return this._add_action(action) + } + + add_argument_group() { + let group = _ArgumentGroup(this, ...arguments) + this._action_groups.push(group) + return group + } + + add_mutually_exclusive_group() { + // eslint-disable-next-line no-use-before-define + let group = _MutuallyExclusiveGroup(this, ...arguments) + this._mutually_exclusive_groups.push(group) + return group + } + + _add_action(action) { + // resolve any conflicts + this._check_conflict(action) + + // add to actions list + this._actions.push(action) + action.container = this + + // index the action by any option strings it has + for (let option_string of action.option_strings) { + this._option_string_actions[option_string] = action + } + + // set the flag if any option strings look like negative numbers + for (let option_string of action.option_strings) { + if (this._negative_number_matcher.test(option_string)) { + if (!this._has_negative_number_optionals.length) { + this._has_negative_number_optionals.push(true) + } + } + } + + // return the created action + return action + } + + _remove_action(action) { + _array_remove(this._actions, action) + } + + _add_container_actions(container) { + // collect groups by titles + let title_group_map = {} + for (let group of this._action_groups) { + if (group.title in title_group_map) { + let msg = 'cannot merge actions - two groups are named %r' + throw new TypeError(sub(msg, group.title)) + } + title_group_map[group.title] = group + } + + // map each action to its group + let group_map = new Map() + for (let group of container._action_groups) { + + // if a group with the title exists, use that, otherwise + // create a new group matching the container's group + if (!(group.title in title_group_map)) { + title_group_map[group.title] = this.add_argument_group({ + title: group.title, + description: group.description, + conflict_handler: group.conflict_handler + }) + } + + // map the actions to their new group + for (let action of group._group_actions) { + group_map.set(action, title_group_map[group.title]) + } + } + + // add container's mutually exclusive groups + // NOTE: if add_mutually_exclusive_group ever gains title= and + // description= then this code will need to be expanded as above + for (let group of container._mutually_exclusive_groups) { + let mutex_group = this.add_mutually_exclusive_group({ + required: group.required + }) + + // map the actions to their new mutex group + for (let action of group._group_actions) { + group_map.set(action, mutex_group) + } + } + + // add all actions to this container or their group + for (let action of container._actions) { + group_map.get(action)._add_action(action) + } + } + + _get_positional_kwargs() { + let [ + dest, + kwargs + ] = _parse_opts(arguments, { + dest: no_default, + '**kwargs': no_default + }) + + // make sure required is not specified + if ('required' in kwargs) { + let msg = "'required' is an invalid argument for positionals" + throw new TypeError(msg) + } + + // mark positional arguments as required if at least one is + // always required + if (![OPTIONAL, ZERO_OR_MORE].includes(kwargs.nargs)) { + kwargs.required = true + } + if (kwargs.nargs === ZERO_OR_MORE && !('default' in kwargs)) { + kwargs.required = true + } + + // return the keyword arguments with no option strings + return Object.assign(kwargs, { dest, option_strings: [] }) + } + + _get_optional_kwargs() { + let [ + args, + kwargs + ] = _parse_opts(arguments, { + '*args': no_default, + '**kwargs': no_default + }) + + // determine short and long option strings + let option_strings = [] + let long_option_strings = [] + let option_string + for (option_string of args) { + // error on strings that don't start with an appropriate prefix + if (!this.prefix_chars.includes(option_string[0])) { + let args = {option: option_string, + prefix_chars: this.prefix_chars} + let msg = 'invalid option string %(option)r: ' + + 'must start with a character %(prefix_chars)r' + throw new TypeError(sub(msg, args)) + } + + // strings starting with two prefix characters are long options + option_strings.push(option_string) + if (option_string.length > 1 && this.prefix_chars.includes(option_string[1])) { + long_option_strings.push(option_string) + } + } + + // infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x' + let dest = kwargs.dest + delete kwargs.dest + if (dest === undefined) { + let dest_option_string + if (long_option_strings.length) { + dest_option_string = long_option_strings[0] + } else { + dest_option_string = option_strings[0] + } + dest = _string_lstrip(dest_option_string, this.prefix_chars) + if (!dest) { + let msg = 'dest= is required for options like %r' + throw new TypeError(sub(msg, option_string)) + } + dest = dest.replace(/-/g, '_') + } + + // return the updated keyword arguments + return Object.assign(kwargs, { dest, option_strings }) + } + + _pop_action_class(kwargs, default_value = undefined) { + let action = getattr(kwargs, 'action', default_value) + delete kwargs.action + return this._registry_get('action', action, action) + } + + _get_handler() { + // determine function from conflict handler string + let handler_func_name = sub('_handle_conflict_%s', this.conflict_handler) + if (typeof this[handler_func_name] === 'function') { + return this[handler_func_name] + } else { + let msg = 'invalid conflict_resolution value: %r' + throw new TypeError(sub(msg, this.conflict_handler)) + } + } + + _check_conflict(action) { + + // find all options that conflict with this option + let confl_optionals = [] + for (let option_string of action.option_strings) { + if (hasattr(this._option_string_actions, option_string)) { + let confl_optional = this._option_string_actions[option_string] + confl_optionals.push([ option_string, confl_optional ]) + } + } + + // resolve any conflicts + if (confl_optionals.length) { + let conflict_handler = this._get_handler() + conflict_handler.call(this, action, confl_optionals) + } + } + + _handle_conflict_error(action, conflicting_actions) { + let message = conflicting_actions.length === 1 ? + 'conflicting option string: %s' : + 'conflicting option strings: %s' + let conflict_string = conflicting_actions.map(([ option_string/*, action*/ ]) => option_string).join(', ') + throw new ArgumentError(action, sub(message, conflict_string)) + } + + _handle_conflict_resolve(action, conflicting_actions) { + + // remove all conflicting options + for (let [ option_string, action ] of conflicting_actions) { + + // remove the conflicting option + _array_remove(action.option_strings, option_string) + delete this._option_string_actions[option_string] + + // if the option now has no option string, remove it from the + // container holding it + if (!action.option_strings.length) { + action.container._remove_action(action) + } + } + } +})) + + +const _ArgumentGroup = _callable(class _ArgumentGroup extends _ActionsContainer { + + constructor() { + let [ + container, + title, + description, + kwargs + ] = _parse_opts(arguments, { + container: no_default, + title: undefined, + description: undefined, + '**kwargs': no_default + }) + + // add any missing keyword arguments by checking the container + setdefault(kwargs, 'conflict_handler', container.conflict_handler) + setdefault(kwargs, 'prefix_chars', container.prefix_chars) + setdefault(kwargs, 'argument_default', container.argument_default) + super(Object.assign({ description }, kwargs)) + + // group attributes + this.title = title + this._group_actions = [] + + // share most attributes with the container + this._registries = container._registries + this._actions = container._actions + this._option_string_actions = container._option_string_actions + this._defaults = container._defaults + this._has_negative_number_optionals = + container._has_negative_number_optionals + this._mutually_exclusive_groups = container._mutually_exclusive_groups + } + + _add_action(action) { + action = super._add_action(action) + this._group_actions.push(action) + return action + } + + _remove_action(action) { + super._remove_action(action) + _array_remove(this._group_actions, action) + } +}) + + +const _MutuallyExclusiveGroup = _callable(class _MutuallyExclusiveGroup extends _ArgumentGroup { + + constructor() { + let [ + container, + required + ] = _parse_opts(arguments, { + container: no_default, + required: false + }) + + super(container) + this.required = required + this._container = container + } + + _add_action(action) { + if (action.required) { + let msg = 'mutually exclusive arguments must be optional' + throw new TypeError(msg) + } + action = this._container._add_action(action) + this._group_actions.push(action) + return action + } + + _remove_action(action) { + this._container._remove_action(action) + _array_remove(this._group_actions, action) + } +}) + + +const ArgumentParser = _camelcase_alias(_callable(class ArgumentParser extends _AttributeHolder(_ActionsContainer) { + /* + * Object for parsing command line strings into Python objects. + * + * Keyword Arguments: + * - prog -- The name of the program (default: sys.argv[0]) + * - usage -- A usage message (default: auto-generated from arguments) + * - description -- A description of what the program does + * - epilog -- Text following the argument descriptions + * - parents -- Parsers whose arguments should be copied into this one + * - formatter_class -- HelpFormatter class for printing help messages + * - prefix_chars -- Characters that prefix optional arguments + * - fromfile_prefix_chars -- Characters that prefix files containing + * additional arguments + * - argument_default -- The default value for all arguments + * - conflict_handler -- String indicating how to handle conflicts + * - add_help -- Add a -h/-help option + * - allow_abbrev -- Allow long options to be abbreviated unambiguously + * - exit_on_error -- Determines whether or not ArgumentParser exits with + * error info when an error occurs + */ + + constructor() { + let [ + prog, + usage, + description, + epilog, + parents, + formatter_class, + prefix_chars, + fromfile_prefix_chars, + argument_default, + conflict_handler, + add_help, + allow_abbrev, + exit_on_error, + debug, // LEGACY (v1 compatibility), debug mode + version // LEGACY (v1 compatibility), version + ] = _parse_opts(arguments, { + prog: undefined, + usage: undefined, + description: undefined, + epilog: undefined, + parents: [], + formatter_class: HelpFormatter, + prefix_chars: '-', + fromfile_prefix_chars: undefined, + argument_default: undefined, + conflict_handler: 'error', + add_help: true, + allow_abbrev: true, + exit_on_error: true, + debug: undefined, // LEGACY (v1 compatibility), debug mode + version: undefined // LEGACY (v1 compatibility), version + }) + + // LEGACY (v1 compatibility) + if (debug !== undefined) { + deprecate('debug', + 'The "debug" argument to ArgumentParser is deprecated. Please ' + + 'override ArgumentParser.exit function instead.' + ) + } + + if (version !== undefined) { + deprecate('version', + 'The "version" argument to ArgumentParser is deprecated. Please use ' + + "add_argument(..., { action: 'version', version: 'N', ... }) instead." + ) + } + // end + + super({ + description, + prefix_chars, + argument_default, + conflict_handler + }) + + // default setting for prog + if (prog === undefined) { + prog = path.basename(get_argv()[0] || '') + } + + this.prog = prog + this.usage = usage + this.epilog = epilog + this.formatter_class = formatter_class + this.fromfile_prefix_chars = fromfile_prefix_chars + this.add_help = add_help + this.allow_abbrev = allow_abbrev + this.exit_on_error = exit_on_error + // LEGACY (v1 compatibility), debug mode + this.debug = debug + // end + + this._positionals = this.add_argument_group('positional arguments') + this._optionals = this.add_argument_group('optional arguments') + this._subparsers = undefined + + // register types + function identity(string) { + return string + } + this.register('type', undefined, identity) + this.register('type', null, identity) + this.register('type', 'auto', identity) + this.register('type', 'int', function (x) { + let result = Number(x) + if (!Number.isInteger(result)) { + throw new TypeError(sub('could not convert string to int: %r', x)) + } + return result + }) + this.register('type', 'float', function (x) { + let result = Number(x) + if (isNaN(result)) { + throw new TypeError(sub('could not convert string to float: %r', x)) + } + return result + }) + this.register('type', 'str', String) + // LEGACY (v1 compatibility): custom types + this.register('type', 'string', + util.deprecate(String, 'use {type:"str"} or {type:String} instead of {type:"string"}')) + // end + + // add help argument if necessary + // (using explicit default to override global argument_default) + let default_prefix = prefix_chars.includes('-') ? '-' : prefix_chars[0] + if (this.add_help) { + this.add_argument( + default_prefix + 'h', + default_prefix.repeat(2) + 'help', + { + action: 'help', + default: SUPPRESS, + help: 'show this help message and exit' + } + ) + } + // LEGACY (v1 compatibility), version + if (version) { + this.add_argument( + default_prefix + 'v', + default_prefix.repeat(2) + 'version', + { + action: 'version', + default: SUPPRESS, + version: this.version, + help: "show program's version number and exit" + } + ) + } + // end + + // add parent arguments and defaults + for (let parent of parents) { + this._add_container_actions(parent) + Object.assign(this._defaults, parent._defaults) + } + } + + // ======================= + // Pretty __repr__ methods + // ======================= + _get_kwargs() { + let names = [ + 'prog', + 'usage', + 'description', + 'formatter_class', + 'conflict_handler', + 'add_help' + ] + return names.map(name => [ name, getattr(this, name) ]) + } + + // ================================== + // Optional/Positional adding methods + // ================================== + add_subparsers() { + let [ + kwargs + ] = _parse_opts(arguments, { + '**kwargs': no_default + }) + + if (this._subparsers !== undefined) { + this.error('cannot have multiple subparser arguments') + } + + // add the parser class to the arguments if it's not present + setdefault(kwargs, 'parser_class', this.constructor) + + if ('title' in kwargs || 'description' in kwargs) { + let title = getattr(kwargs, 'title', 'subcommands') + let description = getattr(kwargs, 'description', undefined) + delete kwargs.title + delete kwargs.description + this._subparsers = this.add_argument_group(title, description) + } else { + this._subparsers = this._positionals + } + + // prog defaults to the usage message of this parser, skipping + // optional arguments and with no "usage:" prefix + if (kwargs.prog === undefined) { + let formatter = this._get_formatter() + let positionals = this._get_positional_actions() + let groups = this._mutually_exclusive_groups + formatter.add_usage(this.usage, positionals, groups, '') + kwargs.prog = formatter.format_help().trim() + } + + // create the parsers action and add it to the positionals list + let parsers_class = this._pop_action_class(kwargs, 'parsers') + // eslint-disable-next-line new-cap + let action = new parsers_class(Object.assign({ option_strings: [] }, kwargs)) + this._subparsers._add_action(action) + + // return the created parsers action + return action + } + + _add_action(action) { + if (action.option_strings.length) { + this._optionals._add_action(action) + } else { + this._positionals._add_action(action) + } + return action + } + + _get_optional_actions() { + return this._actions.filter(action => action.option_strings.length) + } + + _get_positional_actions() { + return this._actions.filter(action => !action.option_strings.length) + } + + // ===================================== + // Command line argument parsing methods + // ===================================== + parse_args(args = undefined, namespace = undefined) { + let argv + [ args, argv ] = this.parse_known_args(args, namespace) + if (argv && argv.length > 0) { + let msg = 'unrecognized arguments: %s' + this.error(sub(msg, argv.join(' '))) + } + return args + } + + parse_known_args(args = undefined, namespace = undefined) { + if (args === undefined) { + args = get_argv().slice(1) + } + + // default Namespace built from parser defaults + if (namespace === undefined) { + namespace = new Namespace() + } + + // add any action defaults that aren't present + for (let action of this._actions) { + if (action.dest !== SUPPRESS) { + if (!hasattr(namespace, action.dest)) { + if (action.default !== SUPPRESS) { + setattr(namespace, action.dest, action.default) + } + } + } + } + + // add any parser defaults that aren't present + for (let dest of Object.keys(this._defaults)) { + if (!hasattr(namespace, dest)) { + setattr(namespace, dest, this._defaults[dest]) + } + } + + // parse the arguments and exit if there are any errors + if (this.exit_on_error) { + try { + [ namespace, args ] = this._parse_known_args(args, namespace) + } catch (err) { + if (err instanceof ArgumentError) { + this.error(err.message) + } else { + throw err + } + } + } else { + [ namespace, args ] = this._parse_known_args(args, namespace) + } + + if (hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) { + args = args.concat(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) + delattr(namespace, _UNRECOGNIZED_ARGS_ATTR) + } + + return [ namespace, args ] + } + + _parse_known_args(arg_strings, namespace) { + // replace arg strings that are file references + if (this.fromfile_prefix_chars !== undefined) { + arg_strings = this._read_args_from_files(arg_strings) + } + + // map all mutually exclusive arguments to the other arguments + // they can't occur with + let action_conflicts = new Map() + for (let mutex_group of this._mutually_exclusive_groups) { + let group_actions = mutex_group._group_actions + for (let [ i, mutex_action ] of Object.entries(mutex_group._group_actions)) { + let conflicts = action_conflicts.get(mutex_action) || [] + conflicts = conflicts.concat(group_actions.slice(0, +i)) + conflicts = conflicts.concat(group_actions.slice(+i + 1)) + action_conflicts.set(mutex_action, conflicts) + } + } + + // find all option indices, and determine the arg_string_pattern + // which has an 'O' if there is an option at an index, + // an 'A' if there is an argument, or a '-' if there is a '--' + let option_string_indices = {} + let arg_string_pattern_parts = [] + let arg_strings_iter = Object.entries(arg_strings)[Symbol.iterator]() + for (let [ i, arg_string ] of arg_strings_iter) { + + // all args after -- are non-options + if (arg_string === '--') { + arg_string_pattern_parts.push('-') + for ([ i, arg_string ] of arg_strings_iter) { + arg_string_pattern_parts.push('A') + } + + // otherwise, add the arg to the arg strings + // and note the index if it was an option + } else { + let option_tuple = this._parse_optional(arg_string) + let pattern + if (option_tuple === undefined) { + pattern = 'A' + } else { + option_string_indices[i] = option_tuple + pattern = 'O' + } + arg_string_pattern_parts.push(pattern) + } + } + + // join the pieces together to form the pattern + let arg_strings_pattern = arg_string_pattern_parts.join('') + + // converts arg strings to the appropriate and then takes the action + let seen_actions = new Set() + let seen_non_default_actions = new Set() + let extras + + let take_action = (action, argument_strings, option_string = undefined) => { + seen_actions.add(action) + let argument_values = this._get_values(action, argument_strings) + + // error if this argument is not allowed with other previously + // seen arguments, assuming that actions that use the default + // value don't really count as "present" + if (argument_values !== action.default) { + seen_non_default_actions.add(action) + for (let conflict_action of action_conflicts.get(action) || []) { + if (seen_non_default_actions.has(conflict_action)) { + let msg = 'not allowed with argument %s' + let action_name = _get_action_name(conflict_action) + throw new ArgumentError(action, sub(msg, action_name)) + } + } + } + + // take the action if we didn't receive a SUPPRESS value + // (e.g. from a default) + if (argument_values !== SUPPRESS) { + action(this, namespace, argument_values, option_string) + } + } + + // function to convert arg_strings into an optional action + let consume_optional = start_index => { + + // get the optional identified at this index + let option_tuple = option_string_indices[start_index] + let [ action, option_string, explicit_arg ] = option_tuple + + // identify additional optionals in the same arg string + // (e.g. -xyz is the same as -x -y -z if no args are required) + let action_tuples = [] + let stop + for (;;) { + + // if we found no optional action, skip it + if (action === undefined) { + extras.push(arg_strings[start_index]) + return start_index + 1 + } + + // if there is an explicit argument, try to match the + // optional's string arguments to only this + if (explicit_arg !== undefined) { + let arg_count = this._match_argument(action, 'A') + + // if the action is a single-dash option and takes no + // arguments, try to parse more single-dash options out + // of the tail of the option string + let chars = this.prefix_chars + if (arg_count === 0 && !chars.includes(option_string[1])) { + action_tuples.push([ action, [], option_string ]) + let char = option_string[0] + option_string = char + explicit_arg[0] + let new_explicit_arg = explicit_arg.slice(1) || undefined + let optionals_map = this._option_string_actions + if (hasattr(optionals_map, option_string)) { + action = optionals_map[option_string] + explicit_arg = new_explicit_arg + } else { + let msg = 'ignored explicit argument %r' + throw new ArgumentError(action, sub(msg, explicit_arg)) + } + + // if the action expect exactly one argument, we've + // successfully matched the option; exit the loop + } else if (arg_count === 1) { + stop = start_index + 1 + let args = [ explicit_arg ] + action_tuples.push([ action, args, option_string ]) + break + + // error if a double-dash option did not use the + // explicit argument + } else { + let msg = 'ignored explicit argument %r' + throw new ArgumentError(action, sub(msg, explicit_arg)) + } + + // if there is no explicit argument, try to match the + // optional's string arguments with the following strings + // if successful, exit the loop + } else { + let start = start_index + 1 + let selected_patterns = arg_strings_pattern.slice(start) + let arg_count = this._match_argument(action, selected_patterns) + stop = start + arg_count + let args = arg_strings.slice(start, stop) + action_tuples.push([ action, args, option_string ]) + break + } + } + + // add the Optional to the list and return the index at which + // the Optional's string args stopped + assert(action_tuples.length) + for (let [ action, args, option_string ] of action_tuples) { + take_action(action, args, option_string) + } + return stop + } + + // the list of Positionals left to be parsed; this is modified + // by consume_positionals() + let positionals = this._get_positional_actions() + + // function to convert arg_strings into positional actions + let consume_positionals = start_index => { + // match as many Positionals as possible + let selected_pattern = arg_strings_pattern.slice(start_index) + let arg_counts = this._match_arguments_partial(positionals, selected_pattern) + + // slice off the appropriate arg strings for each Positional + // and add the Positional and its args to the list + for (let i = 0; i < positionals.length && i < arg_counts.length; i++) { + let action = positionals[i] + let arg_count = arg_counts[i] + let args = arg_strings.slice(start_index, start_index + arg_count) + start_index += arg_count + take_action(action, args) + } + + // slice off the Positionals that we just parsed and return the + // index at which the Positionals' string args stopped + positionals = positionals.slice(arg_counts.length) + return start_index + } + + // consume Positionals and Optionals alternately, until we have + // passed the last option string + extras = [] + let start_index = 0 + let max_option_string_index = Math.max(-1, ...Object.keys(option_string_indices).map(Number)) + while (start_index <= max_option_string_index) { + + // consume any Positionals preceding the next option + let next_option_string_index = Math.min( + // eslint-disable-next-line no-loop-func + ...Object.keys(option_string_indices).map(Number).filter(index => index >= start_index) + ) + if (start_index !== next_option_string_index) { + let positionals_end_index = consume_positionals(start_index) + + // only try to parse the next optional if we didn't consume + // the option string during the positionals parsing + if (positionals_end_index > start_index) { + start_index = positionals_end_index + continue + } else { + start_index = positionals_end_index + } + } + + // if we consumed all the positionals we could and we're not + // at the index of an option string, there were extra arguments + if (!(start_index in option_string_indices)) { + let strings = arg_strings.slice(start_index, next_option_string_index) + extras = extras.concat(strings) + start_index = next_option_string_index + } + + // consume the next optional and any arguments for it + start_index = consume_optional(start_index) + } + + // consume any positionals following the last Optional + let stop_index = consume_positionals(start_index) + + // if we didn't consume all the argument strings, there were extras + extras = extras.concat(arg_strings.slice(stop_index)) + + // make sure all required actions were present and also convert + // action defaults which were not given as arguments + let required_actions = [] + for (let action of this._actions) { + if (!seen_actions.has(action)) { + if (action.required) { + required_actions.push(_get_action_name(action)) + } else { + // Convert action default now instead of doing it before + // parsing arguments to avoid calling convert functions + // twice (which may fail) if the argument was given, but + // only if it was defined already in the namespace + if (action.default !== undefined && + typeof action.default === 'string' && + hasattr(namespace, action.dest) && + action.default === getattr(namespace, action.dest)) { + setattr(namespace, action.dest, + this._get_value(action, action.default)) + } + } + } + } + + if (required_actions.length) { + this.error(sub('the following arguments are required: %s', + required_actions.join(', '))) + } + + // make sure all required groups had one option present + for (let group of this._mutually_exclusive_groups) { + if (group.required) { + let no_actions_used = true + for (let action of group._group_actions) { + if (seen_non_default_actions.has(action)) { + no_actions_used = false + break + } + } + + // if no actions were used, report the error + if (no_actions_used) { + let names = group._group_actions + .filter(action => action.help !== SUPPRESS) + .map(action => _get_action_name(action)) + let msg = 'one of the arguments %s is required' + this.error(sub(msg, names.join(' '))) + } + } + } + + // return the updated namespace and the extra arguments + return [ namespace, extras ] + } + + _read_args_from_files(arg_strings) { + // expand arguments referencing files + let new_arg_strings = [] + for (let arg_string of arg_strings) { + + // for regular arguments, just add them back into the list + if (!arg_string || !this.fromfile_prefix_chars.includes(arg_string[0])) { + new_arg_strings.push(arg_string) + + // replace arguments referencing files with the file content + } else { + try { + let args_file = fs.readFileSync(arg_string.slice(1), 'utf8') + let arg_strings = [] + for (let arg_line of splitlines(args_file)) { + for (let arg of this.convert_arg_line_to_args(arg_line)) { + arg_strings.push(arg) + } + } + arg_strings = this._read_args_from_files(arg_strings) + new_arg_strings = new_arg_strings.concat(arg_strings) + } catch (err) { + this.error(err.message) + } + } + } + + // return the modified argument list + return new_arg_strings + } + + convert_arg_line_to_args(arg_line) { + return [arg_line] + } + + _match_argument(action, arg_strings_pattern) { + // match the pattern for this action to the arg strings + let nargs_pattern = this._get_nargs_pattern(action) + let match = arg_strings_pattern.match(new RegExp('^' + nargs_pattern)) + + // raise an exception if we weren't able to find a match + if (match === null) { + let nargs_errors = { + undefined: 'expected one argument', + [OPTIONAL]: 'expected at most one argument', + [ONE_OR_MORE]: 'expected at least one argument' + } + let msg = nargs_errors[action.nargs] + if (msg === undefined) { + msg = sub(action.nargs === 1 ? 'expected %s argument' : 'expected %s arguments', action.nargs) + } + throw new ArgumentError(action, msg) + } + + // return the number of arguments matched + return match[1].length + } + + _match_arguments_partial(actions, arg_strings_pattern) { + // progressively shorten the actions list by slicing off the + // final actions until we find a match + let result = [] + for (let i of range(actions.length, 0, -1)) { + let actions_slice = actions.slice(0, i) + let pattern = actions_slice.map(action => this._get_nargs_pattern(action)).join('') + let match = arg_strings_pattern.match(new RegExp('^' + pattern)) + if (match !== null) { + result = result.concat(match.slice(1).map(string => string.length)) + break + } + } + + // return the list of arg string counts + return result + } + + _parse_optional(arg_string) { + // if it's an empty string, it was meant to be a positional + if (!arg_string) { + return undefined + } + + // if it doesn't start with a prefix, it was meant to be positional + if (!this.prefix_chars.includes(arg_string[0])) { + return undefined + } + + // if the option string is present in the parser, return the action + if (arg_string in this._option_string_actions) { + let action = this._option_string_actions[arg_string] + return [ action, arg_string, undefined ] + } + + // if it's just a single character, it was meant to be positional + if (arg_string.length === 1) { + return undefined + } + + // if the option string before the "=" is present, return the action + if (arg_string.includes('=')) { + let [ option_string, explicit_arg ] = _string_split(arg_string, '=', 1) + if (option_string in this._option_string_actions) { + let action = this._option_string_actions[option_string] + return [ action, option_string, explicit_arg ] + } + } + + // search through all possible prefixes of the option string + // and all actions in the parser for possible interpretations + let option_tuples = this._get_option_tuples(arg_string) + + // if multiple actions match, the option string was ambiguous + if (option_tuples.length > 1) { + let options = option_tuples.map(([ /*action*/, option_string/*, explicit_arg*/ ]) => option_string).join(', ') + let args = {option: arg_string, matches: options} + let msg = 'ambiguous option: %(option)s could match %(matches)s' + this.error(sub(msg, args)) + + // if exactly one action matched, this segmentation is good, + // so return the parsed action + } else if (option_tuples.length === 1) { + let [ option_tuple ] = option_tuples + return option_tuple + } + + // if it was not found as an option, but it looks like a negative + // number, it was meant to be positional + // unless there are negative-number-like options + if (this._negative_number_matcher.test(arg_string)) { + if (!this._has_negative_number_optionals.length) { + return undefined + } + } + + // if it contains a space, it was meant to be a positional + if (arg_string.includes(' ')) { + return undefined + } + + // it was meant to be an optional but there is no such option + // in this parser (though it might be a valid option in a subparser) + return [ undefined, arg_string, undefined ] + } + + _get_option_tuples(option_string) { + let result = [] + + // option strings starting with two prefix characters are only + // split at the '=' + let chars = this.prefix_chars + if (chars.includes(option_string[0]) && chars.includes(option_string[1])) { + if (this.allow_abbrev) { + let option_prefix, explicit_arg + if (option_string.includes('=')) { + [ option_prefix, explicit_arg ] = _string_split(option_string, '=', 1) + } else { + option_prefix = option_string + explicit_arg = undefined + } + for (let option_string of Object.keys(this._option_string_actions)) { + if (option_string.startsWith(option_prefix)) { + let action = this._option_string_actions[option_string] + let tup = [ action, option_string, explicit_arg ] + result.push(tup) + } + } + } + + // single character options can be concatenated with their arguments + // but multiple character options always have to have their argument + // separate + } else if (chars.includes(option_string[0]) && !chars.includes(option_string[1])) { + let option_prefix = option_string + let explicit_arg = undefined + let short_option_prefix = option_string.slice(0, 2) + let short_explicit_arg = option_string.slice(2) + + for (let option_string of Object.keys(this._option_string_actions)) { + if (option_string === short_option_prefix) { + let action = this._option_string_actions[option_string] + let tup = [ action, option_string, short_explicit_arg ] + result.push(tup) + } else if (option_string.startsWith(option_prefix)) { + let action = this._option_string_actions[option_string] + let tup = [ action, option_string, explicit_arg ] + result.push(tup) + } + } + + // shouldn't ever get here + } else { + this.error(sub('unexpected option string: %s', option_string)) + } + + // return the collected option tuples + return result + } + + _get_nargs_pattern(action) { + // in all examples below, we have to allow for '--' args + // which are represented as '-' in the pattern + let nargs = action.nargs + let nargs_pattern + + // the default (None) is assumed to be a single argument + if (nargs === undefined) { + nargs_pattern = '(-*A-*)' + + // allow zero or one arguments + } else if (nargs === OPTIONAL) { + nargs_pattern = '(-*A?-*)' + + // allow zero or more arguments + } else if (nargs === ZERO_OR_MORE) { + nargs_pattern = '(-*[A-]*)' + + // allow one or more arguments + } else if (nargs === ONE_OR_MORE) { + nargs_pattern = '(-*A[A-]*)' + + // allow any number of options or arguments + } else if (nargs === REMAINDER) { + nargs_pattern = '([-AO]*)' + + // allow one argument followed by any number of options or arguments + } else if (nargs === PARSER) { + nargs_pattern = '(-*A[-AO]*)' + + // suppress action, like nargs=0 + } else if (nargs === SUPPRESS) { + nargs_pattern = '(-*-*)' + + // all others should be integers + } else { + nargs_pattern = sub('(-*%s-*)', 'A'.repeat(nargs).split('').join('-*')) + } + + // if this is an optional action, -- is not allowed + if (action.option_strings.length) { + nargs_pattern = nargs_pattern.replace(/-\*/g, '') + nargs_pattern = nargs_pattern.replace(/-/g, '') + } + + // return the pattern + return nargs_pattern + } + + // ======================== + // Alt command line argument parsing, allowing free intermix + // ======================== + + parse_intermixed_args(args = undefined, namespace = undefined) { + let argv + [ args, argv ] = this.parse_known_intermixed_args(args, namespace) + if (argv.length) { + let msg = 'unrecognized arguments: %s' + this.error(sub(msg, argv.join(' '))) + } + return args + } + + parse_known_intermixed_args(args = undefined, namespace = undefined) { + // returns a namespace and list of extras + // + // positional can be freely intermixed with optionals. optionals are + // first parsed with all positional arguments deactivated. The 'extras' + // are then parsed. If the parser definition is incompatible with the + // intermixed assumptions (e.g. use of REMAINDER, subparsers) a + // TypeError is raised. + // + // positionals are 'deactivated' by setting nargs and default to + // SUPPRESS. This blocks the addition of that positional to the + // namespace + + let extras + let positionals = this._get_positional_actions() + let a = positionals.filter(action => [ PARSER, REMAINDER ].includes(action.nargs)) + if (a.length) { + throw new TypeError(sub('parse_intermixed_args: positional arg' + + ' with nargs=%s', a[0].nargs)) + } + + for (let group of this._mutually_exclusive_groups) { + for (let action of group._group_actions) { + if (positionals.includes(action)) { + throw new TypeError('parse_intermixed_args: positional in' + + ' mutuallyExclusiveGroup') + } + } + } + + let save_usage + try { + save_usage = this.usage + let remaining_args + try { + if (this.usage === undefined) { + // capture the full usage for use in error messages + this.usage = this.format_usage().slice(7) + } + for (let action of positionals) { + // deactivate positionals + action.save_nargs = action.nargs + // action.nargs = 0 + action.nargs = SUPPRESS + action.save_default = action.default + action.default = SUPPRESS + } + [ namespace, remaining_args ] = this.parse_known_args(args, + namespace) + for (let action of positionals) { + // remove the empty positional values from namespace + let attr = getattr(namespace, action.dest) + if (Array.isArray(attr) && attr.length === 0) { + // eslint-disable-next-line no-console + console.warn(sub('Do not expect %s in %s', action.dest, namespace)) + delattr(namespace, action.dest) + } + } + } finally { + // restore nargs and usage before exiting + for (let action of positionals) { + action.nargs = action.save_nargs + action.default = action.save_default + } + } + let optionals = this._get_optional_actions() + try { + // parse positionals. optionals aren't normally required, but + // they could be, so make sure they aren't. + for (let action of optionals) { + action.save_required = action.required + action.required = false + } + for (let group of this._mutually_exclusive_groups) { + group.save_required = group.required + group.required = false + } + [ namespace, extras ] = this.parse_known_args(remaining_args, + namespace) + } finally { + // restore parser values before exiting + for (let action of optionals) { + action.required = action.save_required + } + for (let group of this._mutually_exclusive_groups) { + group.required = group.save_required + } + } + } finally { + this.usage = save_usage + } + return [ namespace, extras ] + } + + // ======================== + // Value conversion methods + // ======================== + _get_values(action, arg_strings) { + // for everything but PARSER, REMAINDER args, strip out first '--' + if (![PARSER, REMAINDER].includes(action.nargs)) { + try { + _array_remove(arg_strings, '--') + } catch (err) {} + } + + let value + // optional argument produces a default when not present + if (!arg_strings.length && action.nargs === OPTIONAL) { + if (action.option_strings.length) { + value = action.const + } else { + value = action.default + } + if (typeof value === 'string') { + value = this._get_value(action, value) + this._check_value(action, value) + } + + // when nargs='*' on a positional, if there were no command-line + // args, use the default if it is anything other than None + } else if (!arg_strings.length && action.nargs === ZERO_OR_MORE && + !action.option_strings.length) { + if (action.default !== undefined) { + value = action.default + } else { + value = arg_strings + } + this._check_value(action, value) + + // single argument or optional argument produces a single value + } else if (arg_strings.length === 1 && [undefined, OPTIONAL].includes(action.nargs)) { + let arg_string = arg_strings[0] + value = this._get_value(action, arg_string) + this._check_value(action, value) + + // REMAINDER arguments convert all values, checking none + } else if (action.nargs === REMAINDER) { + value = arg_strings.map(v => this._get_value(action, v)) + + // PARSER arguments convert all values, but check only the first + } else if (action.nargs === PARSER) { + value = arg_strings.map(v => this._get_value(action, v)) + this._check_value(action, value[0]) + + // SUPPRESS argument does not put anything in the namespace + } else if (action.nargs === SUPPRESS) { + value = SUPPRESS + + // all other types of nargs produce a list + } else { + value = arg_strings.map(v => this._get_value(action, v)) + for (let v of value) { + this._check_value(action, v) + } + } + + // return the converted value + return value + } + + _get_value(action, arg_string) { + let type_func = this._registry_get('type', action.type, action.type) + if (typeof type_func !== 'function') { + let msg = '%r is not callable' + throw new ArgumentError(action, sub(msg, type_func)) + } + + // convert the value to the appropriate type + let result + try { + try { + result = type_func(arg_string) + } catch (err) { + // Dear TC39, why would you ever consider making es6 classes not callable? + // We had one universal interface, [[Call]], which worked for anything + // (with familiar this-instanceof guard for classes). Now we have two. + if (err instanceof TypeError && + /Class constructor .* cannot be invoked without 'new'/.test(err.message)) { + // eslint-disable-next-line new-cap + result = new type_func(arg_string) + } else { + throw err + } + } + + } catch (err) { + // ArgumentTypeErrors indicate errors + if (err instanceof ArgumentTypeError) { + //let name = getattr(action.type, 'name', repr(action.type)) + let msg = err.message + throw new ArgumentError(action, msg) + + // TypeErrors or ValueErrors also indicate errors + } else if (err instanceof TypeError) { + let name = getattr(action.type, 'name', repr(action.type)) + let args = {type: name, value: arg_string} + let msg = 'invalid %(type)s value: %(value)r' + throw new ArgumentError(action, sub(msg, args)) + } else { + throw err + } + } + + // return the converted value + return result + } + + _check_value(action, value) { + // converted value must be one of the choices (if specified) + if (action.choices !== undefined && !_choices_to_array(action.choices).includes(value)) { + let args = {value, + choices: _choices_to_array(action.choices).map(repr).join(', ')} + let msg = 'invalid choice: %(value)r (choose from %(choices)s)' + throw new ArgumentError(action, sub(msg, args)) + } + } + + // ======================= + // Help-formatting methods + // ======================= + format_usage() { + let formatter = this._get_formatter() + formatter.add_usage(this.usage, this._actions, + this._mutually_exclusive_groups) + return formatter.format_help() + } + + format_help() { + let formatter = this._get_formatter() + + // usage + formatter.add_usage(this.usage, this._actions, + this._mutually_exclusive_groups) + + // description + formatter.add_text(this.description) + + // positionals, optionals and user-defined groups + for (let action_group of this._action_groups) { + formatter.start_section(action_group.title) + formatter.add_text(action_group.description) + formatter.add_arguments(action_group._group_actions) + formatter.end_section() + } + + // epilog + formatter.add_text(this.epilog) + + // determine help from format above + return formatter.format_help() + } + + _get_formatter() { + // eslint-disable-next-line new-cap + return new this.formatter_class({ prog: this.prog }) + } + + // ===================== + // Help-printing methods + // ===================== + print_usage(file = undefined) { + if (file === undefined) file = process.stdout + this._print_message(this.format_usage(), file) + } + + print_help(file = undefined) { + if (file === undefined) file = process.stdout + this._print_message(this.format_help(), file) + } + + _print_message(message, file = undefined) { + if (message) { + if (file === undefined) file = process.stderr + file.write(message) + } + } + + // =============== + // Exiting methods + // =============== + exit(status = 0, message = undefined) { + if (message) { + this._print_message(message, process.stderr) + } + process.exit(status) + } + + error(message) { + /* + * error(message: string) + * + * Prints a usage message incorporating the message to stderr and + * exits. + * + * If you override this in a subclass, it should not return -- it + * should either exit or raise an exception. + */ + + // LEGACY (v1 compatibility), debug mode + if (this.debug === true) throw new Error(message) + // end + this.print_usage(process.stderr) + let args = {prog: this.prog, message: message} + this.exit(2, sub('%(prog)s: error: %(message)s\n', args)) + } +})) + + +module.exports = { + ArgumentParser, + ArgumentError, + ArgumentTypeError, + BooleanOptionalAction, + FileType, + HelpFormatter, + ArgumentDefaultsHelpFormatter, + RawDescriptionHelpFormatter, + RawTextHelpFormatter, + MetavarTypeHelpFormatter, + Namespace, + Action, + ONE_OR_MORE, + OPTIONAL, + PARSER, + REMAINDER, + SUPPRESS, + ZERO_OR_MORE +} + +// LEGACY (v1 compatibility), Const alias +Object.defineProperty(module.exports, 'Const', { + get() { + let result = {} + Object.entries({ ONE_OR_MORE, OPTIONAL, PARSER, REMAINDER, SUPPRESS, ZERO_OR_MORE }).forEach(([ n, v ]) => { + Object.defineProperty(result, n, { + get() { + deprecate(n, sub('use argparse.%s instead of argparse.Const.%s', n, n)) + return v + } + }) + }) + Object.entries({ _UNRECOGNIZED_ARGS_ATTR }).forEach(([ n, v ]) => { + Object.defineProperty(result, n, { + get() { + deprecate(n, sub('argparse.Const.%s is an internal symbol and will no longer be available', n)) + return v + } + }) + }) + return result + }, + enumerable: false +}) +// end diff --git a/node_modules/lv_font_conv/node_modules/argparse/lib/sub.js b/node_modules/lv_font_conv/node_modules/argparse/lib/sub.js new file mode 100644 index 00000000..e3eb3215 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/argparse/lib/sub.js @@ -0,0 +1,67 @@ +// Limited implementation of python % string operator, supports only %s and %r for now +// (other formats are not used here, but may appear in custom templates) + +'use strict' + +const { inspect } = require('util') + + +module.exports = function sub(pattern, ...values) { + let regex = /%(?:(%)|(-)?(\*)?(?:\((\w+)\))?([A-Za-z]))/g + + let result = pattern.replace(regex, function (_, is_literal, is_left_align, is_padded, name, format) { + if (is_literal) return '%' + + let padded_count = 0 + if (is_padded) { + if (values.length === 0) throw new TypeError('not enough arguments for format string') + padded_count = values.shift() + if (!Number.isInteger(padded_count)) throw new TypeError('* wants int') + } + + let str + if (name !== undefined) { + let dict = values[0] + if (typeof dict !== 'object' || dict === null) throw new TypeError('format requires a mapping') + if (!(name in dict)) throw new TypeError(`no such key: '${name}'`) + str = dict[name] + } else { + if (values.length === 0) throw new TypeError('not enough arguments for format string') + str = values.shift() + } + + switch (format) { + case 's': + str = String(str) + break + case 'r': + str = inspect(str) + break + case 'd': + case 'i': + if (typeof str !== 'number') { + throw new TypeError(`%${format} format: a number is required, not ${typeof str}`) + } + str = String(str.toFixed(0)) + break + default: + throw new TypeError(`unsupported format character '${format}'`) + } + + if (padded_count > 0) { + return is_left_align ? str.padEnd(padded_count) : str.padStart(padded_count) + } else { + return str + } + }) + + if (values.length) { + if (values.length === 1 && typeof values[0] === 'object' && values[0] !== null) { + // mapping + } else { + throw new TypeError('not all arguments converted during string formatting') + } + } + + return result +} diff --git a/node_modules/lv_font_conv/node_modules/argparse/lib/textwrap.js b/node_modules/lv_font_conv/node_modules/argparse/lib/textwrap.js new file mode 100644 index 00000000..23d51cdb --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/argparse/lib/textwrap.js @@ -0,0 +1,440 @@ +// Partial port of python's argparse module, version 3.9.0 (only wrap and fill functions): +// https://github.com/python/cpython/blob/v3.9.0b4/Lib/textwrap.py + +'use strict' + +/* + * Text wrapping and filling. + */ + +// Copyright (C) 1999-2001 Gregory P. Ward. +// Copyright (C) 2002, 2003 Python Software Foundation. +// Copyright (C) 2020 argparse.js authors +// Originally written by Greg Ward + +// Hardcode the recognized whitespace characters to the US-ASCII +// whitespace characters. The main reason for doing this is that +// some Unicode spaces (like \u00a0) are non-breaking whitespaces. +// +// This less funky little regex just split on recognized spaces. E.g. +// "Hello there -- you goof-ball, use the -b option!" +// splits into +// Hello/ /there/ /--/ /you/ /goof-ball,/ /use/ /the/ /-b/ /option!/ +const wordsep_simple_re = /([\t\n\x0b\x0c\r ]+)/ + +class TextWrapper { + /* + * Object for wrapping/filling text. The public interface consists of + * the wrap() and fill() methods; the other methods are just there for + * subclasses to override in order to tweak the default behaviour. + * If you want to completely replace the main wrapping algorithm, + * you'll probably have to override _wrap_chunks(). + * + * Several instance attributes control various aspects of wrapping: + * width (default: 70) + * the maximum width of wrapped lines (unless break_long_words + * is false) + * initial_indent (default: "") + * string that will be prepended to the first line of wrapped + * output. Counts towards the line's width. + * subsequent_indent (default: "") + * string that will be prepended to all lines save the first + * of wrapped output; also counts towards each line's width. + * expand_tabs (default: true) + * Expand tabs in input text to spaces before further processing. + * Each tab will become 0 .. 'tabsize' spaces, depending on its position + * in its line. If false, each tab is treated as a single character. + * tabsize (default: 8) + * Expand tabs in input text to 0 .. 'tabsize' spaces, unless + * 'expand_tabs' is false. + * replace_whitespace (default: true) + * Replace all whitespace characters in the input text by spaces + * after tab expansion. Note that if expand_tabs is false and + * replace_whitespace is true, every tab will be converted to a + * single space! + * fix_sentence_endings (default: false) + * Ensure that sentence-ending punctuation is always followed + * by two spaces. Off by default because the algorithm is + * (unavoidably) imperfect. + * break_long_words (default: true) + * Break words longer than 'width'. If false, those words will not + * be broken, and some lines might be longer than 'width'. + * break_on_hyphens (default: true) + * Allow breaking hyphenated words. If true, wrapping will occur + * preferably on whitespaces and right after hyphens part of + * compound words. + * drop_whitespace (default: true) + * Drop leading and trailing whitespace from lines. + * max_lines (default: None) + * Truncate wrapped lines. + * placeholder (default: ' [...]') + * Append to the last line of truncated text. + */ + + constructor(options = {}) { + let { + width = 70, + initial_indent = '', + subsequent_indent = '', + expand_tabs = true, + replace_whitespace = true, + fix_sentence_endings = false, + break_long_words = true, + drop_whitespace = true, + break_on_hyphens = true, + tabsize = 8, + max_lines = undefined, + placeholder=' [...]' + } = options + + this.width = width + this.initial_indent = initial_indent + this.subsequent_indent = subsequent_indent + this.expand_tabs = expand_tabs + this.replace_whitespace = replace_whitespace + this.fix_sentence_endings = fix_sentence_endings + this.break_long_words = break_long_words + this.drop_whitespace = drop_whitespace + this.break_on_hyphens = break_on_hyphens + this.tabsize = tabsize + this.max_lines = max_lines + this.placeholder = placeholder + } + + + // -- Private methods ----------------------------------------------- + // (possibly useful for subclasses to override) + + _munge_whitespace(text) { + /* + * _munge_whitespace(text : string) -> string + * + * Munge whitespace in text: expand tabs and convert all other + * whitespace characters to spaces. Eg. " foo\\tbar\\n\\nbaz" + * becomes " foo bar baz". + */ + if (this.expand_tabs) { + text = text.replace(/\t/g, ' '.repeat(this.tabsize)) // not strictly correct in js + } + if (this.replace_whitespace) { + text = text.replace(/[\t\n\x0b\x0c\r]/g, ' ') + } + return text + } + + _split(text) { + /* + * _split(text : string) -> [string] + * + * Split the text to wrap into indivisible chunks. Chunks are + * not quite the same as words; see _wrap_chunks() for full + * details. As an example, the text + * Look, goof-ball -- use the -b option! + * breaks into the following chunks: + * 'Look,', ' ', 'goof-', 'ball', ' ', '--', ' ', + * 'use', ' ', 'the', ' ', '-b', ' ', 'option!' + * if break_on_hyphens is True, or in: + * 'Look,', ' ', 'goof-ball', ' ', '--', ' ', + * 'use', ' ', 'the', ' ', '-b', ' ', option!' + * otherwise. + */ + let chunks = text.split(wordsep_simple_re) + chunks = chunks.filter(Boolean) + return chunks + } + + _handle_long_word(reversed_chunks, cur_line, cur_len, width) { + /* + * _handle_long_word(chunks : [string], + * cur_line : [string], + * cur_len : int, width : int) + * + * Handle a chunk of text (most likely a word, not whitespace) that + * is too long to fit in any line. + */ + // Figure out when indent is larger than the specified width, and make + // sure at least one character is stripped off on every pass + let space_left + if (width < 1) { + space_left = 1 + } else { + space_left = width - cur_len + } + + // If we're allowed to break long words, then do so: put as much + // of the next chunk onto the current line as will fit. + if (this.break_long_words) { + cur_line.push(reversed_chunks[reversed_chunks.length - 1].slice(0, space_left)) + reversed_chunks[reversed_chunks.length - 1] = reversed_chunks[reversed_chunks.length - 1].slice(space_left) + + // Otherwise, we have to preserve the long word intact. Only add + // it to the current line if there's nothing already there -- + // that minimizes how much we violate the width constraint. + } else if (!cur_line) { + cur_line.push(...reversed_chunks.pop()) + } + + // If we're not allowed to break long words, and there's already + // text on the current line, do nothing. Next time through the + // main loop of _wrap_chunks(), we'll wind up here again, but + // cur_len will be zero, so the next line will be entirely + // devoted to the long word that we can't handle right now. + } + + _wrap_chunks(chunks) { + /* + * _wrap_chunks(chunks : [string]) -> [string] + * + * Wrap a sequence of text chunks and return a list of lines of + * length 'self.width' or less. (If 'break_long_words' is false, + * some lines may be longer than this.) Chunks correspond roughly + * to words and the whitespace between them: each chunk is + * indivisible (modulo 'break_long_words'), but a line break can + * come between any two chunks. Chunks should not have internal + * whitespace; ie. a chunk is either all whitespace or a "word". + * Whitespace chunks will be removed from the beginning and end of + * lines, but apart from that whitespace is preserved. + */ + let lines = [] + let indent + if (this.width <= 0) { + throw Error(`invalid width ${this.width} (must be > 0)`) + } + if (this.max_lines !== undefined) { + if (this.max_lines > 1) { + indent = this.subsequent_indent + } else { + indent = this.initial_indent + } + if (indent.length + this.placeholder.trimStart().length > this.width) { + throw Error('placeholder too large for max width') + } + } + + // Arrange in reverse order so items can be efficiently popped + // from a stack of chucks. + chunks = chunks.reverse() + + while (chunks.length > 0) { + + // Start the list of chunks that will make up the current line. + // cur_len is just the length of all the chunks in cur_line. + let cur_line = [] + let cur_len = 0 + + // Figure out which static string will prefix this line. + let indent + if (lines) { + indent = this.subsequent_indent + } else { + indent = this.initial_indent + } + + // Maximum width for this line. + let width = this.width - indent.length + + // First chunk on line is whitespace -- drop it, unless this + // is the very beginning of the text (ie. no lines started yet). + if (this.drop_whitespace && chunks[chunks.length - 1].trim() === '' && lines.length > 0) { + chunks.pop() + } + + while (chunks.length > 0) { + let l = chunks[chunks.length - 1].length + + // Can at least squeeze this chunk onto the current line. + if (cur_len + l <= width) { + cur_line.push(chunks.pop()) + cur_len += l + + // Nope, this line is full. + } else { + break + } + } + + // The current line is full, and the next chunk is too big to + // fit on *any* line (not just this one). + if (chunks.length && chunks[chunks.length - 1].length > width) { + this._handle_long_word(chunks, cur_line, cur_len, width) + cur_len = cur_line.map(l => l.length).reduce((a, b) => a + b, 0) + } + + // If the last chunk on this line is all whitespace, drop it. + if (this.drop_whitespace && cur_line.length > 0 && cur_line[cur_line.length - 1].trim() === '') { + cur_len -= cur_line[cur_line.length - 1].length + cur_line.pop() + } + + if (cur_line) { + if (this.max_lines === undefined || + lines.length + 1 < this.max_lines || + (chunks.length === 0 || + this.drop_whitespace && + chunks.length === 1 && + !chunks[0].trim()) && cur_len <= width) { + // Convert current line back to a string and store it in + // list of all lines (return value). + lines.push(indent + cur_line.join('')) + } else { + let had_break = false + while (cur_line) { + if (cur_line[cur_line.length - 1].trim() && + cur_len + this.placeholder.length <= width) { + cur_line.push(this.placeholder) + lines.push(indent + cur_line.join('')) + had_break = true + break + } + cur_len -= cur_line[-1].length + cur_line.pop() + } + if (!had_break) { + if (lines) { + let prev_line = lines[lines.length - 1].trimEnd() + if (prev_line.length + this.placeholder.length <= + this.width) { + lines[lines.length - 1] = prev_line + this.placeholder + break + } + } + lines.push(indent + this.placeholder.lstrip()) + } + break + } + } + } + + return lines + } + + _split_chunks(text) { + text = this._munge_whitespace(text) + return this._split(text) + } + + // -- Public interface ---------------------------------------------- + + wrap(text) { + /* + * wrap(text : string) -> [string] + * + * Reformat the single paragraph in 'text' so it fits in lines of + * no more than 'self.width' columns, and return a list of wrapped + * lines. Tabs in 'text' are expanded with string.expandtabs(), + * and all other whitespace characters (including newline) are + * converted to space. + */ + let chunks = this._split_chunks(text) + // not implemented in js + //if (this.fix_sentence_endings) { + // this._fix_sentence_endings(chunks) + //} + return this._wrap_chunks(chunks) + } + + fill(text) { + /* + * fill(text : string) -> string + * + * Reformat the single paragraph in 'text' to fit in lines of no + * more than 'self.width' columns, and return a new string + * containing the entire wrapped paragraph. + */ + return this.wrap(text).join('\n') + } +} + + +// -- Convenience interface --------------------------------------------- + +function wrap(text, options = {}) { + /* + * Wrap a single paragraph of text, returning a list of wrapped lines. + * + * Reformat the single paragraph in 'text' so it fits in lines of no + * more than 'width' columns, and return a list of wrapped lines. By + * default, tabs in 'text' are expanded with string.expandtabs(), and + * all other whitespace characters (including newline) are converted to + * space. See TextWrapper class for available keyword args to customize + * wrapping behaviour. + */ + let { width = 70, ...kwargs } = options + let w = new TextWrapper(Object.assign({ width }, kwargs)) + return w.wrap(text) +} + +function fill(text, options = {}) { + /* + * Fill a single paragraph of text, returning a new string. + * + * Reformat the single paragraph in 'text' to fit in lines of no more + * than 'width' columns, and return a new string containing the entire + * wrapped paragraph. As with wrap(), tabs are expanded and other + * whitespace characters converted to space. See TextWrapper class for + * available keyword args to customize wrapping behaviour. + */ + let { width = 70, ...kwargs } = options + let w = new TextWrapper(Object.assign({ width }, kwargs)) + return w.fill(text) +} + +// -- Loosely related functionality ------------------------------------- + +let _whitespace_only_re = /^[ \t]+$/mg +let _leading_whitespace_re = /(^[ \t]*)(?:[^ \t\n])/mg + +function dedent(text) { + /* + * Remove any common leading whitespace from every line in `text`. + * + * This can be used to make triple-quoted strings line up with the left + * edge of the display, while still presenting them in the source code + * in indented form. + * + * Note that tabs and spaces are both treated as whitespace, but they + * are not equal: the lines " hello" and "\\thello" are + * considered to have no common leading whitespace. + * + * Entirely blank lines are normalized to a newline character. + */ + // Look for the longest leading string of spaces and tabs common to + // all lines. + let margin = undefined + text = text.replace(_whitespace_only_re, '') + let indents = text.match(_leading_whitespace_re) || [] + for (let indent of indents) { + indent = indent.slice(0, -1) + + if (margin === undefined) { + margin = indent + + // Current line more deeply indented than previous winner: + // no change (previous winner is still on top). + } else if (indent.startsWith(margin)) { + // pass + + // Current line consistent with and no deeper than previous winner: + // it's the new winner. + } else if (margin.startsWith(indent)) { + margin = indent + + // Find the largest common whitespace between current line and previous + // winner. + } else { + for (let i = 0; i < margin.length && i < indent.length; i++) { + if (margin[i] !== indent[i]) { + margin = margin.slice(0, i) + break + } + } + } + } + + if (margin) { + text = text.replace(new RegExp('^' + margin, 'mg'), '') + } + return text +} + +module.exports = { wrap, fill, dedent } diff --git a/node_modules/lv_font_conv/node_modules/argparse/package.json b/node_modules/lv_font_conv/node_modules/argparse/package.json new file mode 100644 index 00000000..22fb0a3e --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/argparse/package.json @@ -0,0 +1,67 @@ +{ + "_args": [ + [ + "argparse@2.0.1", + "/home/vitaly/Dropbox/Coding/lv_font_conv" + ] + ], + "_from": "argparse@2.0.1", + "_id": "argparse@2.0.1", + "_inBundle": false, + "_integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "_location": "/argparse", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "argparse@2.0.1", + "name": "argparse", + "escapedName": "argparse", + "rawSpec": "2.0.1", + "saveSpec": null, + "fetchSpec": "2.0.1" + }, + "_requiredBy": [ + "/", + "/mocha/js-yaml" + ], + "_resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "_spec": "2.0.1", + "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", + "bugs": { + "url": "https://github.com/nodeca/argparse/issues" + }, + "description": "CLI arguments parser. Native port of python's argparse.", + "devDependencies": { + "@babel/eslint-parser": "^7.11.0", + "@babel/plugin-syntax-class-properties": "^7.10.4", + "eslint": "^7.5.0", + "mocha": "^8.0.1", + "nyc": "^15.1.0" + }, + "files": [ + "argparse.js", + "lib/" + ], + "homepage": "https://github.com/nodeca/argparse#readme", + "keywords": [ + "cli", + "parser", + "argparse", + "option", + "args" + ], + "license": "Python-2.0", + "main": "argparse.js", + "name": "argparse", + "repository": { + "type": "git", + "url": "git+https://github.com/nodeca/argparse.git" + }, + "scripts": { + "coverage": "npm run test && nyc report --reporter html", + "lint": "eslint .", + "test": "npm run lint && nyc mocha" + }, + "version": "2.0.1" +} diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/.github/workflows/ci.yml b/node_modules/lv_font_conv/node_modules/bit-buffer/.github/workflows/ci.yml new file mode 100644 index 00000000..8f0f168b --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/bit-buffer/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: Node.js CI + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [10.x, 12.x, 14.x, 15.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci + - run: npm test + + lint: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [10.x, 12.x, 14.x, 15.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci + - run: npm run lint diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/LICENSE b/node_modules/lv_font_conv/node_modules/bit-buffer/LICENSE new file mode 100644 index 00000000..9194ecce --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/bit-buffer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 bit-buffer developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/README.md b/node_modules/lv_font_conv/node_modules/bit-buffer/README.md new file mode 100644 index 00000000..3ecf4ab7 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/bit-buffer/README.md @@ -0,0 +1,148 @@ +# BitBuffer + +![Node.js CI](https://github.com/inolen/bit-buffer/workflows/Node.js%20CI/badge.svg) + +BitBuffer provides two objects, `BitView` and `BitStream`. `BitView` is a wrapper for ArrayBuffers, similar to JavaScript's [DataView](https://developer.mozilla.org/en-US/docs/JavaScript/Typed_arrays/DataView), but with support for bit-level reads and writes. `BitStream` is a wrapper for a `BitView` used to help maintain your current buffer position, as well as to provide higher-level read / write operations such as for ASCII strings. + +## BitView + +### Attributes + +```javascript +bb.buffer // Underlying ArrayBuffer. +``` + +```javascript +bb.bigEndian = true; // Switch to big endian (default is little) +``` + +### Methods + +#### BitView(buffer, optional byteOffset, optional byteLength) + +Default constructor, takes in a single argument of an ArrayBuffer. Optional are the `byteOffset` and `byteLength` arguments to offset and truncate the view's representation of the buffer. + +### getBits(offset, bits, signed) + +Reads `bits` number of bits starting at `offset`, twiddling the bits appropriately to return a proper 32-bit signed or unsigned value. NOTE: While JavaScript numbers are 64-bit floating-point values, we don't bother with anything other than the first 32 bits. + +### getInt8, getUint8, getInt16, getUint16, getInt32, getUint32(offset) + +Shortcuts for getBits, setting the correct `bits` / `signed` values. + +### getFloat32(offset) + +Gets 32 bits from `offset`, and coerces and returns as a proper float32 value. + +### getFloat64(offset) + +Gets 64 bits from `offset`, and coerces and returns as a proper float64 value. + +### setBits(offset, value, bits) + +Sets `bits` number of bits at `offset`. + +### setInt8, setUint8, setInt16, setUint16, setInt32, setUint32(offset) + +Shortcuts for setBits, setting the correct `bits` count. + +### setFloat32(offset) + +Coerces a float32 to uint32 and sets at `offset`. + +### setFloat64(offset) + +Coerces a float64 to two uint32s and sets at `offset`. + + +## BitStream + +### Attributes + +```javascript +bb.byteIndex; // Get current index in bytes. +bb.byteIndex = 0; // Set current index in bytes. +``` + +```javascript +bb.view; // Underlying BitView +``` + +```javascript +bb.length; // Get the length of the stream in bits +``` + +```javascript +bb.bitsLeft; // The number of bits left in the stream +``` + +```javascript +bb.index; // Get the current index in bits +bb.index = 0// Set the current index in bits +``` + +```javascript +bb.bigEndian = true; // Switch to big endian (default is little) +``` + +### Methods + +#### BitStream(view) + +Default constructor, takes in a single argument of a `BitView`, `ArrayBuffer` or node `Buffer`. + +#### BitSteam(buffer, optional byteOffset, optional byteLength) + +Shortcut constructor that initializes a new `BitView(buffer, byteOffset, byteLength)` for the stream to use. + +#### readBits(bits, signed) + +Returns `bits` numbers of bits from the view at the current index, updating the index. + +#### writeBits(value, bits) + +Sets `bits` numbers of bits from `value` in the view at the current index, updating the index. + +#### readUint8(), readUint16(), readUint32(), readInt8(), readInt16(), readInt32() + +Read a 8, 16 or 32 bits (unsigned) integer at the current index, updating the index. + +#### writeUint8(value), writeUint16(value), writeUint32(value), writeInt8(value), writeInt16(value), writeInt32(value) + +Write 8, 16 or 32 bits from `value` as (unsigned) integer at the current index, updating the index. + +#### readFloat32(), readFloat64() + +Read a 32 or 64 bit floating point number at the current index, updating the index. + +#### writeFloat32(value), writeFloat64() + +Set 32 or 64 bits from `value` as floating point value at the current index, updating the index. + +#### readBoolean() + +Read a single bit from the view at the current index, updating the index. + +#### writeBoolean(value) + +Write a single bit to the view at the current index, updating the index. + +#### readASCIIString(optional bytes), readUTF8String(optional bytes) + +Reads bytes from the underlying view at the current index until either `bytes` count is reached or a 0x00 terminator is reached. + +#### writeASCIIString(string, optional bytes), writeUTF8String(string, optional bytes) + +Writes a string followed by a NULL character to the underlying view starting at the current index. If the string is longer than `bytes` it will be truncated, and if it is shorter 0x00 will be written in its place. + +#### readBitStream(length) + +Create a new `BitStream` from the underlying view starting the the current index and a length of `length` bits. Updating the index of the existing `BitStream` + +#### readArrayBuffer(byteLength) + +Read `byteLength` bytes of data from the underlying view as `ArrayBuffer`, updating the index. + +## license + +MIT diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.d.ts b/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.d.ts new file mode 100644 index 00000000..72c4b13e --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.d.ts @@ -0,0 +1,115 @@ +declare module 'bit-buffer' { + import {Buffer} from 'buffer'; + + export class BitView { + constructor(buffer: ArrayBuffer | Buffer, byteLength?: number); + + readonly buffer: Buffer; + readonly byteLength: number; + bigEndian: boolean; + + getBits(offset: number, bits: number, signed?: boolean): number; + + getInt8(offset: number): number; + + getInt16(offset: number): number; + + getInt32(offset: number): number; + + getUint8(offset: number): number; + + getUint16(offset: number): number; + + getUint32(offset: number): number; + + getFloat32(offset: number): number; + + getFloat64(offset: number): number; + + setBits(offset: number, value: number, bits: number); + + setInt8(offset: number); + + setInt16(offset: number); + + setInt32(offset: number); + + setUint8(offset: number); + + setUint16(offset: number); + + setUint32(offset: number); + + setFloat32(offset: number, value: number); + + setFloat64(offset: number, value: number); + } + + export class BitStream { + constructor(source: ArrayBuffer | Buffer | BitView, byteOffset?: number, byteLength?: number) + + readonly length: number; + readonly bitsLeft: number; + readonly buffer: Buffer; + readonly view: BitView; + byteIndex: number; + index: number; + bigEndian: boolean; + + readBits(bits: number, signed?: boolean): number; + + writeBits(value: number, bits: number); + + readBoolean(): boolean; + + readInt8(): number; + + readUint8(): number; + + readInt16(): number; + + readUint16(): number; + + readInt32(): number; + + readUint32(): number; + + readFloat32(): number; + + readFloat64(): number; + + writeBoolean(value: boolean); + + writeInt8(value: number); + + writeUint8(value: number); + + writeInt16(value: number); + + writeUint16(value: number); + + writeInt32(value: number); + + writeUint32(value: number); + + writeFloat32(value: number); + + writeFloat64(value: number); + + readASCIIString(length?: number): string; + + readUTF8String(length?: number): string; + + writeASCIIString(data: string, length?: number); + + writeUTF8String(data: string, length?: number); + + readBitStream(length: number): BitStream; + + readArrayBuffer(byteLength: number): Uint8Array; + + writeBitStream(stream: BitStream, length?: number); + + writeArrayBuffer(buffer: BitStream, length?: number); + } +} diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.js b/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.js new file mode 100644 index 00000000..60c7f795 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.js @@ -0,0 +1,502 @@ +(function (root) { + +/********************************************************** + * + * BitView + * + * BitView provides a similar interface to the standard + * DataView, but with support for bit-level reads / writes. + * + **********************************************************/ +var BitView = function (source, byteOffset, byteLength) { + var isBuffer = source instanceof ArrayBuffer || + (typeof Buffer !== 'undefined' && source instanceof Buffer); + + if (!isBuffer) { + throw new Error('Must specify a valid ArrayBuffer or Buffer.'); + } + + byteOffset = byteOffset || 0; + byteLength = byteLength || source.byteLength /* ArrayBuffer */ || source.length /* Buffer */; + + this._view = new Uint8Array(source.buffer || source, byteOffset, byteLength); + + this.bigEndian = false; +}; + +// Used to massage fp values so we can operate on them +// at the bit level. +BitView._scratch = new DataView(new ArrayBuffer(8)); + +Object.defineProperty(BitView.prototype, 'buffer', { + get: function () { return typeof Buffer !== 'undefined' ? Buffer.from(this._view.buffer) : this._view.buffer; }, + enumerable: true, + configurable: false +}); + +Object.defineProperty(BitView.prototype, 'byteLength', { + get: function () { return this._view.length; }, + enumerable: true, + configurable: false +}); + +BitView.prototype._setBit = function (offset, on) { + if (on) { + this._view[offset >> 3] |= 1 << (offset & 7); + } else { + this._view[offset >> 3] &= ~(1 << (offset & 7)); + } +}; + +BitView.prototype.getBits = function (offset, bits, signed) { + var available = (this._view.length * 8 - offset); + + if (bits > available) { + throw new Error('Cannot get ' + bits + ' bit(s) from offset ' + offset + ', ' + available + ' available'); + } + + var value = 0; + for (var i = 0; i < bits;) { + var remaining = bits - i; + var bitOffset = offset & 7; + var currentByte = this._view[offset >> 3]; + + // the max number of bits we can read from the current byte + var read = Math.min(remaining, 8 - bitOffset); + + var mask, readBits; + if (this.bigEndian) { + // create a mask with the correct bit width + mask = ~(0xFF << read); + // shift the bits we want to the start of the byte and mask of the rest + readBits = (currentByte >> (8 - read - bitOffset)) & mask; + + value <<= read; + value |= readBits; + } else { + // create a mask with the correct bit width + mask = ~(0xFF << read); + // shift the bits we want to the start of the byte and mask off the rest + readBits = (currentByte >> bitOffset) & mask; + + value |= readBits << i; + } + + offset += read; + i += read; + } + + if (signed) { + // If we're not working with a full 32 bits, check the + // imaginary MSB for this bit count and convert to a + // valid 32-bit signed value if set. + if (bits !== 32 && value & (1 << (bits - 1))) { + value |= -1 ^ ((1 << bits) - 1); + } + + return value; + } + + return value >>> 0; +}; + +BitView.prototype.setBits = function (offset, value, bits) { + var available = (this._view.length * 8 - offset); + + if (bits > available) { + throw new Error('Cannot set ' + bits + ' bit(s) from offset ' + offset + ', ' + available + ' available'); + } + + for (var i = 0; i < bits;) { + var remaining = bits - i; + var bitOffset = offset & 7; + var byteOffset = offset >> 3; + var wrote = Math.min(remaining, 8 - bitOffset); + + var mask, writeBits, destMask; + if (this.bigEndian) { + // create a mask with the correct bit width + mask = ~(~0 << wrote); + // shift the bits we want to the start of the byte and mask of the rest + writeBits = (value >> (bits - i - wrote)) & mask; + + var destShift = 8 - bitOffset - wrote; + // destination mask to zero all the bits we're changing first + destMask = ~(mask << destShift); + + this._view[byteOffset] = + (this._view[byteOffset] & destMask) + | (writeBits << destShift); + + } else { + // create a mask with the correct bit width + mask = ~(0xFF << wrote); + // shift the bits we want to the start of the byte and mask of the rest + writeBits = value & mask; + value >>= wrote; + + // destination mask to zero all the bits we're changing first + destMask = ~(mask << bitOffset); + + this._view[byteOffset] = + (this._view[byteOffset] & destMask) + | (writeBits << bitOffset); + } + + offset += wrote; + i += wrote; + } +}; + +BitView.prototype.getBoolean = function (offset) { + return this.getBits(offset, 1, false) !== 0; +}; +BitView.prototype.getInt8 = function (offset) { + return this.getBits(offset, 8, true); +}; +BitView.prototype.getUint8 = function (offset) { + return this.getBits(offset, 8, false); +}; +BitView.prototype.getInt16 = function (offset) { + return this.getBits(offset, 16, true); +}; +BitView.prototype.getUint16 = function (offset) { + return this.getBits(offset, 16, false); +}; +BitView.prototype.getInt32 = function (offset) { + return this.getBits(offset, 32, true); +}; +BitView.prototype.getUint32 = function (offset) { + return this.getBits(offset, 32, false); +}; +BitView.prototype.getFloat32 = function (offset) { + BitView._scratch.setUint32(0, this.getUint32(offset)); + return BitView._scratch.getFloat32(0); +}; +BitView.prototype.getFloat64 = function (offset) { + BitView._scratch.setUint32(0, this.getUint32(offset)); + // DataView offset is in bytes. + BitView._scratch.setUint32(4, this.getUint32(offset+32)); + return BitView._scratch.getFloat64(0); +}; + +BitView.prototype.setBoolean = function (offset, value) { + this.setBits(offset, value ? 1 : 0, 1); +}; +BitView.prototype.setInt8 = +BitView.prototype.setUint8 = function (offset, value) { + this.setBits(offset, value, 8); +}; +BitView.prototype.setInt16 = +BitView.prototype.setUint16 = function (offset, value) { + this.setBits(offset, value, 16); +}; +BitView.prototype.setInt32 = +BitView.prototype.setUint32 = function (offset, value) { + this.setBits(offset, value, 32); +}; +BitView.prototype.setFloat32 = function (offset, value) { + BitView._scratch.setFloat32(0, value); + this.setBits(offset, BitView._scratch.getUint32(0), 32); +}; +BitView.prototype.setFloat64 = function (offset, value) { + BitView._scratch.setFloat64(0, value); + this.setBits(offset, BitView._scratch.getUint32(0), 32); + this.setBits(offset+32, BitView._scratch.getUint32(4), 32); +}; +BitView.prototype.getArrayBuffer = function (offset, byteLength) { + var buffer = new Uint8Array(byteLength); + for (var i = 0; i < byteLength; i++) { + buffer[i] = this.getUint8(offset + (i * 8)); + } + return buffer; +}; + +/********************************************************** + * + * BitStream + * + * Small wrapper for a BitView to maintain your position, + * as well as to handle reading / writing of string data + * to the underlying buffer. + * + **********************************************************/ +var reader = function (name, size) { + return function () { + if (this._index + size > this._length) { + throw new Error('Trying to read past the end of the stream'); + } + var val = this._view[name](this._index); + this._index += size; + return val; + }; +}; + +var writer = function (name, size) { + return function (value) { + this._view[name](this._index, value); + this._index += size; + }; +}; + +function readASCIIString(stream, bytes) { + return readString(stream, bytes, false); +} + +function readUTF8String(stream, bytes) { + return readString(stream, bytes, true); +} + +function readString(stream, bytes, utf8) { + if (bytes === 0) { + return ''; + } + var i = 0; + var chars = []; + var append = true; + var fixedLength = !!bytes; + if (!bytes) { + bytes = Math.floor((stream._length - stream._index) / 8); + } + + // Read while we still have space available, or until we've + // hit the fixed byte length passed in. + while (i < bytes) { + var c = stream.readUint8(); + + // Stop appending chars once we hit 0x00 + if (c === 0x00) { + append = false; + + // If we don't have a fixed length to read, break out now. + if (!fixedLength) { + break; + } + } + if (append) { + chars.push(c); + } + + i++; + } + + var string = String.fromCharCode.apply(null, chars); + if (utf8) { + try { + return decodeURIComponent(escape(string)); // https://stackoverflow.com/a/17192845 + } catch (e) { + return string; + } + } else { + return string; + } +} + +function writeASCIIString(stream, string, bytes) { + var length = bytes || string.length + 1; // + 1 for NULL + + for (var i = 0; i < length; i++) { + stream.writeUint8(i < string.length ? string.charCodeAt(i) : 0x00); + } +} + +function writeUTF8String(stream, string, bytes) { + var byteArray = stringToByteArray(string); + + var length = bytes || byteArray.length + 1; // + 1 for NULL + for (var i = 0; i < length; i++) { + stream.writeUint8(i < byteArray.length ? byteArray[i] : 0x00); + } +} + +function stringToByteArray(str) { // https://gist.github.com/volodymyr-mykhailyk/2923227 + var b = [], i, unicode; + for (i = 0; i < str.length; i++) { + unicode = str.charCodeAt(i); + // 0x00000000 - 0x0000007f -> 0xxxxxxx + if (unicode <= 0x7f) { + b.push(unicode); + // 0x00000080 - 0x000007ff -> 110xxxxx 10xxxxxx + } else if (unicode <= 0x7ff) { + b.push((unicode >> 6) | 0xc0); + b.push((unicode & 0x3F) | 0x80); + // 0x00000800 - 0x0000ffff -> 1110xxxx 10xxxxxx 10xxxxxx + } else if (unicode <= 0xffff) { + b.push((unicode >> 12) | 0xe0); + b.push(((unicode >> 6) & 0x3f) | 0x80); + b.push((unicode & 0x3f) | 0x80); + // 0x00010000 - 0x001fffff -> 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + } else { + b.push((unicode >> 18) | 0xf0); + b.push(((unicode >> 12) & 0x3f) | 0x80); + b.push(((unicode >> 6) & 0x3f) | 0x80); + b.push((unicode & 0x3f) | 0x80); + } + } + + return b; +} + +var BitStream = function (source, byteOffset, byteLength) { + var isBuffer = source instanceof ArrayBuffer || + (typeof Buffer !== 'undefined' && source instanceof Buffer); + + if (!(source instanceof BitView) && !isBuffer) { + throw new Error('Must specify a valid BitView, ArrayBuffer or Buffer'); + } + + if (isBuffer) { + this._view = new BitView(source, byteOffset, byteLength); + } else { + this._view = source; + } + + this._index = 0; + this._startIndex = 0; + this._length = this._view.byteLength * 8; +}; + +Object.defineProperty(BitStream.prototype, 'index', { + get: function () { return this._index - this._startIndex; }, + set: function (val) { this._index = val + this._startIndex; }, + enumerable: true, + configurable: true +}); + +Object.defineProperty(BitStream.prototype, 'length', { + get: function () { return this._length - this._startIndex; }, + set: function (val) { this._length = val + this._startIndex; }, + enumerable : true, + configurable: true +}); + +Object.defineProperty(BitStream.prototype, 'bitsLeft', { + get: function () { return this._length - this._index; }, + enumerable : true, + configurable: true +}); + +Object.defineProperty(BitStream.prototype, 'byteIndex', { + // Ceil the returned value, over compensating for the amount of + // bits written to the stream. + get: function () { return Math.ceil(this._index / 8); }, + set: function (val) { this._index = val * 8; }, + enumerable: true, + configurable: true +}); + +Object.defineProperty(BitStream.prototype, 'buffer', { + get: function () { return this._view.buffer; }, + enumerable: true, + configurable: false +}); + +Object.defineProperty(BitStream.prototype, 'view', { + get: function () { return this._view; }, + enumerable: true, + configurable: false +}); + +Object.defineProperty(BitStream.prototype, 'bigEndian', { + get: function () { return this._view.bigEndian; }, + set: function (val) { this._view.bigEndian = val; }, + enumerable: true, + configurable: false +}); + +BitStream.prototype.readBits = function (bits, signed) { + var val = this._view.getBits(this._index, bits, signed); + this._index += bits; + return val; +}; + +BitStream.prototype.writeBits = function (value, bits) { + this._view.setBits(this._index, value, bits); + this._index += bits; +}; + +BitStream.prototype.readBoolean = reader('getBoolean', 1); +BitStream.prototype.readInt8 = reader('getInt8', 8); +BitStream.prototype.readUint8 = reader('getUint8', 8); +BitStream.prototype.readInt16 = reader('getInt16', 16); +BitStream.prototype.readUint16 = reader('getUint16', 16); +BitStream.prototype.readInt32 = reader('getInt32', 32); +BitStream.prototype.readUint32 = reader('getUint32', 32); +BitStream.prototype.readFloat32 = reader('getFloat32', 32); +BitStream.prototype.readFloat64 = reader('getFloat64', 64); + +BitStream.prototype.writeBoolean = writer('setBoolean', 1); +BitStream.prototype.writeInt8 = writer('setInt8', 8); +BitStream.prototype.writeUint8 = writer('setUint8', 8); +BitStream.prototype.writeInt16 = writer('setInt16', 16); +BitStream.prototype.writeUint16 = writer('setUint16', 16); +BitStream.prototype.writeInt32 = writer('setInt32', 32); +BitStream.prototype.writeUint32 = writer('setUint32', 32); +BitStream.prototype.writeFloat32 = writer('setFloat32', 32); +BitStream.prototype.writeFloat64 = writer('setFloat64', 64); + +BitStream.prototype.readASCIIString = function (bytes) { + return readASCIIString(this, bytes); +}; + +BitStream.prototype.readUTF8String = function (bytes) { + return readUTF8String(this, bytes); +}; + +BitStream.prototype.writeASCIIString = function (string, bytes) { + writeASCIIString(this, string, bytes); +}; + +BitStream.prototype.writeUTF8String = function (string, bytes) { + writeUTF8String(this, string, bytes); +}; +BitStream.prototype.readBitStream = function(bitLength) { + var slice = new BitStream(this._view); + slice._startIndex = this._index; + slice._index = this._index; + slice.length = bitLength; + this._index += bitLength; + return slice; +}; + +BitStream.prototype.writeBitStream = function(stream, length) { + if (!length) { + length = stream.bitsLeft; + } + + var bitsToWrite; + while (length > 0) { + bitsToWrite = Math.min(length, 32); + this.writeBits(stream.readBits(bitsToWrite), bitsToWrite); + length -= bitsToWrite; + } +}; + +BitStream.prototype.readArrayBuffer = function(byteLength) { + var buffer = this._view.getArrayBuffer(this._index, byteLength); + this._index += (byteLength * 8); + return buffer; +}; + +BitStream.prototype.writeArrayBuffer = function(buffer, byteLength) { + this.writeBitStream(new BitStream(buffer), byteLength * 8); +}; + +// AMD / RequireJS +if (typeof define !== 'undefined' && define.amd) { + define(function () { + return { + BitView: BitView, + BitStream: BitStream + }; + }); +} +// Node.js +else if (typeof module !== 'undefined' && module.exports) { + module.exports = { + BitView: BitView, + BitStream: BitStream + }; +} + +}(this)); diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/package.json b/node_modules/lv_font_conv/node_modules/bit-buffer/package.json new file mode 100644 index 00000000..f2e2d2ce --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/bit-buffer/package.json @@ -0,0 +1,71 @@ +{ + "_args": [ + [ + "bit-buffer@0.2.5", + "/home/vitaly/Dropbox/Coding/lv_font_conv" + ] + ], + "_from": "bit-buffer@0.2.5", + "_id": "bit-buffer@0.2.5", + "_inBundle": false, + "_integrity": "sha512-x1yGnmXvFg6e3DiyRztElbcn1bsCTFSoM/ncAzY62uE0JdTl5xlKJd0ooqLYoPbhdsnpehSIQrdIvclcZJYwiA==", + "_location": "/bit-buffer", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "bit-buffer@0.2.5", + "name": "bit-buffer", + "escapedName": "bit-buffer", + "rawSpec": "0.2.5", + "saveSpec": null, + "fetchSpec": "0.2.5" + }, + "_requiredBy": [ + "/" + ], + "_resolved": "https://registry.npmjs.org/bit-buffer/-/bit-buffer-0.2.5.tgz", + "_spec": "0.2.5", + "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", + "author": { + "name": "Anthony Pesch" + }, + "bugs": { + "url": "https://github.com/inolen/bit-buffer/issues" + }, + "contributors": [ + { + "name": "Robin Appelman" + } + ], + "description": "Bit-level reads and writes for ArrayBuffers", + "devDependencies": { + "@types/node": "^14.14.22", + "jshint": "^2.12.0", + "mocha": "^8.2.1" + }, + "directories": { + "test": "test" + }, + "gitHead": "cd4417237bed1f22dd5adfd8a6b961ea7234d9c9", + "homepage": "https://github.com/inolen/bit-buffer#readme", + "keywords": [ + "dataview", + "arraybuffer", + "bit", + "bits" + ], + "license": "MIT", + "main": "bit-buffer.js", + "name": "bit-buffer", + "repository": { + "type": "git", + "url": "git://github.com/inolen/bit-buffer.git" + }, + "scripts": { + "lint": "jshint bit-buffer.js", + "test": "mocha --ui tdd" + }, + "types": "./bit-buffer.d.ts", + "version": "0.2.5" +} diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/test.js b/node_modules/lv_font_conv/node_modules/bit-buffer/test.js new file mode 100644 index 00000000..a53fc87b --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/bit-buffer/test.js @@ -0,0 +1,628 @@ +var assert = require('assert'), + BitView = require('./bit-buffer').BitView, + BitStream = require('./bit-buffer').BitStream; + +suite('BitBuffer', function () { + var array, bv, bsw, bsr; + + setup(function () { + array = new ArrayBuffer(64); + bv = new BitView(array); + bsw = new BitStream(bv); + // Test initializing straight from the array. + bsr = new BitStream(array); + }); + + test('Min / max signed 5 bits', function () { + var signed_max = (1 << 4) - 1; + + bsw.writeBits(signed_max, 5); + bsw.writeBits(-signed_max - 1, 5); + assert(bsr.readBits(5, true) === signed_max); + assert(bsr.readBits(5, true) === -signed_max - 1); + }); + + test('Min / max unsigned 5 bits', function () { + var unsigned_max = (1 << 5) - 1; + + bsw.writeBits(unsigned_max, 5); + bsw.writeBits(-unsigned_max, 5); + assert.equal(bsr.readBits(5), unsigned_max); + assert.equal(bsr.readBits(5), 1); + }); + + test('Min / max int8', function () { + var signed_max = 0x7F; + + bsw.writeInt8(signed_max); + bsw.writeInt8(-signed_max - 1); + assert.equal(bsr.readInt8(), signed_max); + assert.equal(bsr.readInt8(), -signed_max - 1); + }); + + test('Min / max uint8', function () { + var unsigned_max = 0xFF; + + bsw.writeUint8(unsigned_max); + bsw.writeUint8(-unsigned_max); + assert.equal(bsr.readUint8(), unsigned_max); + assert.equal(bsr.readUint8(), 1); + }); + + test('Min / max int16', function () { + var signed_max = 0x7FFF; + + bsw.writeInt16(signed_max); + bsw.writeInt16(-signed_max - 1); + assert.equal(bsr.readInt16(), signed_max); + assert.equal(bsr.readInt16(), -signed_max - 1); + }); + + test('Min / max uint16', function () { + var unsigned_max = 0xFFFF; + + bsw.writeUint16(unsigned_max); + bsw.writeUint16(-unsigned_max); + assert.equal(bsr.readUint16(), unsigned_max); + assert.equal(bsr.readUint16(), 1); + }); + + test('Min / max int32', function () { + var signed_max = 0x7FFFFFFF; + + bsw.writeInt32(signed_max); + bsw.writeInt32(-signed_max - 1); + assert.equal(bsr.readInt32(), signed_max); + assert.equal(bsr.readInt32(), -signed_max - 1); + }); + + test('Min / max uint32', function () { + var unsigned_max = 0xFFFFFFFF; + + bsw.writeUint32(unsigned_max); + bsw.writeUint32(-unsigned_max); + assert.equal(bsr.readUint32(), unsigned_max); + assert.equal(bsr.readUint32(), 1); + }); + + test('Unaligned reads', function () { + bsw.writeBits(13, 5); + bsw.writeUint8(0xFF); + bsw.writeBits(14, 5); + + assert.equal(bsr.readBits(5), 13); + assert.equal(bsr.readUint8(), 0xFF); + assert.equal(bsr.readBits(5), 14); + }); + + test('Min / max float32 (normal values)', function () { + var scratch = new DataView(new ArrayBuffer(8)); + + scratch.setUint32(0, 0x00800000); + scratch.setUint32(4, 0x7f7fffff); + + var min = scratch.getFloat32(0); + var max = scratch.getFloat32(4); + + bsw.writeFloat32(min); + bsw.writeFloat32(max); + + assert.equal(bsr.readFloat32(), min); + assert.equal(bsr.readFloat32(), max); + }); + + test('Min / max float64 (normal values)', function () { + var scratch = new DataView(new ArrayBuffer(16)); + + scratch.setUint32(0, 0x00100000); + scratch.setUint32(4, 0x00000000); + scratch.setUint32(8, 0x7fefffff); + scratch.setUint32(12, 0xffffffff); + + var min = scratch.getFloat64(0); + var max = scratch.getFloat64(8); + + bsw.writeFloat64(min); + bsw.writeFloat64(max); + + assert.equal(bsr.readFloat64(), min); + assert.equal(bsr.readFloat64(), max); + }); + + test('Overwrite previous value with 0', function () { + bv.setUint8(0, 13); + bv.setUint8(0, 0); + + assert.equal(bv.getUint8(0), 0); + }); + + test('Read / write ASCII string, fixed length', function () { + var str = 'foobar'; + var len = 16; + + bsw.writeASCIIString(str, len); + assert.equal(bsw.byteIndex, len); + + assert.equal(bsr.readASCIIString(len), str); + assert.equal(bsr.byteIndex, len); + }); + + test('Read / write ASCII string, unknown length', function () { + var str = 'foobar'; + + bsw.writeASCIIString(str); + assert.equal(bsw.byteIndex, str.length + 1); // +1 for 0x00 + + assert.equal(bsr.readASCIIString(), str); + assert.equal(bsr.byteIndex, str.length + 1); + }); + + test('Read ASCII string, 0 length', function () { + var str = 'foobar'; + + bsw.writeASCIIString(str); + assert.equal(bsw.byteIndex, str.length + 1); // +1 for 0x00 + + assert.equal(bsr.readASCIIString(0), ''); + assert.equal(bsr.byteIndex, 0); + }); + + test('Read overflow', function () { + var exception = false; + + try { + bsr.readASCIIString(128); + } catch (e) { + exception = true; + } + + assert(exception); + }); + + test('Write overflow', function () { + var exception = false; + + try { + bsw.writeASCIIString('foobar', 128); + } catch (e) { + exception = true; + } + + assert(exception); + }); + + test('Get boolean', function () { + bv.setUint8(0, 1); + + assert(bv.getBoolean(0)); + + bv.setUint8(0, 0); + assert(!bv.getBoolean(0)); + }); + + test('Set boolean', function () { + bv.setBoolean(0, true); + + assert(bv.getBoolean(0)); + + bv.setBoolean(0, false); + + assert(!bv.getBoolean(0)); + }); + + test('Read boolean', function () { + bv.setBits(0, 1, 1); + bv.setBits(1, 0, 1); + + assert(bsr.readBoolean()); + assert(!bsr.readBoolean()); + }); + + test('Write boolean', function () { + bsr.writeBoolean(true); + assert.equal(bv.getBits(0, 1, false), 1); + bsr.writeBoolean(false); + assert.equal(bv.getBits(1, 1, false), 0); + }); + + test('Read / write UTF8 string, only ASCII characters', function () { + var str = 'foobar'; + + bsw.writeUTF8String(str); + assert(bsw.byteIndex === str.length + 1); // +1 for 0x00 + + assert.equal(bsr.readUTF8String(), str); + assert.equal(bsr.byteIndex, str.length + 1); + }); + + test('Read / write UTF8 string, non ASCII characters', function () { + var str = '日本語'; + + var bytes = [ + 0xE6, + 0x97, + 0xA5, + 0xE6, + 0x9C, + 0xAC, + 0xE8, + 0xAA, + 0x9E + ]; + + bsw.writeUTF8String(str); + + for (var i = 0; i < bytes.length; i++) { + assert.equal(bytes[i], bv.getBits(i * 8, 8)); + } + + assert.equal(bsw.byteIndex, bytes.length + 1); // +1 for 0x00 + + assert.equal(str, bsr.readUTF8String()); + assert.equal(bsr.byteIndex, bytes.length + 1); + }); + + test('readBitStream', function () { + bsw.writeBits(0xF0, 8); //0b11110000 + bsw.writeBits(0xF1, 8); //0b11110001 + bsr.readBits(3); //offset + var slice = bsr.readBitStream(8); + assert.equal(slice.readBits(6), 0x3E); //0b111110 + assert.equal(9, slice._index); + assert.equal(6, slice.index); + assert.equal(8, slice.length); + assert.equal(2, slice.bitsLeft); + + assert.equal(bsr._index, 11); + assert.equal((64 * 8) - 11, bsr.bitsLeft); + }); + + test('readBitStream overflow', function () { + bsw.writeBits(0xF0, 8); //0b11110000 + bsw.writeBits(0xF1, 8); //0b11110001 + bsr.readBits(3); //offset + var slice = bsr.readBitStream(4); + + var exception = false; + + try { + slice.readUint8(); + } catch (e) { + exception = true; + } + + assert(exception); + }); + + test('writeBitStream', function () { + var buf = new ArrayBuffer(64); + var sourceStream = new BitStream(buf); + + sourceStream.writeBits(0xF0, 8); //0b11110000 + sourceStream.writeBits(0xF1, 8); //0b11110001 + sourceStream.index = 0; + sourceStream.readBits(3); //offset + bsr.writeBitStream(sourceStream, 8); + assert.equal(8, bsr.index); + bsr.index = 0; + assert.equal(bsr.readBits(6), 0x3E); //0b00111110 + assert.equal(11, sourceStream.index); + + var bin = new Uint8Array(buf); + assert.equal(bin[0], 0xF0); + assert.equal(bin[1], 0xF1); + }); + + test('writeBitStream Buffer', function () { + var buf = Buffer.alloc(64); + var sourceStream = new BitStream(buf); + + sourceStream.writeBits(0xF0, 8); //0b11110000 + sourceStream.writeBits(0xF1, 8); //0b11110001 + sourceStream.index = 0; + sourceStream.readBits(3); //offset + bsr.writeBitStream(sourceStream, 8); + assert.equal(8, bsr.index); + bsr.index = 0; + assert.equal(bsr.readBits(6), 0x3E); //0b00111110 + assert.equal(11, sourceStream.index); + + var bin = new Uint8Array(buf.buffer); + assert.equal(bin[0], 0xF0); + assert.equal(bin[1], 0xF1); + }); + + test('writeBitStream long', function () { + var sourceStream = new BitStream(new ArrayBuffer(64)); + + sourceStream.writeBits(0xF0, 8); + sourceStream.writeBits(0xF1, 8); + sourceStream.writeBits(0xF1, 8); + sourceStream.writeBits(0xF1, 8); + sourceStream.writeBits(0xF1, 8); + sourceStream.index = 0; + sourceStream.readBits(3); //offset + bsr.index = 3; + bsr.writeBitStream(sourceStream, 35); + assert.equal(38, bsr.index); + bsr.index = 3; + assert.equal(bsr.readBits(35), 1044266558); + assert.equal(38, sourceStream.index); + }); + + test('readArrayBuffer', function () { + bsw.writeBits(0xF0, 8); //0b11110000 + bsw.writeBits(0xF1, 8); //0b11110001 + bsw.writeBits(0xF0, 8); //0b11110000 + bsr.readBits(3); //offset + + var buffer = bsr.readArrayBuffer(2); + + assert.equal(0x3E, buffer[0]); //0b00111110 + assert.equal(0x1E, buffer[1]); //0b00011110 + + assert.equal(3 + (2 * 8), bsr._index); + }); + + test('writeArrayBuffer', function () { + var source = new Uint8Array(4); + source[0] = 0xF0; + source[1] = 0xF1; + source[2] = 0xF1; + bsr.readBits(3); //offset + + bsr.writeArrayBuffer(source.buffer, 2); + assert.equal(19, bsr.index); + + bsr.index = 0; + + assert.equal(bsr.readBits(8), 128); + }); + + test('Get buffer from view', function () { + bv.setBits(0, 0xFFFFFFFF, 32); + var buffer = bv.buffer; + + assert.equal(64, buffer.length); + assert.equal(0xFFFF, buffer.readUInt16LE(0)); + }); + + test('Get buffer from stream', function () { + bsw.writeBits(0xFFFFFFFF, 32); + var buffer = bsr.buffer; + + assert.equal(64, buffer.length); + assert.equal(0xFFFF, buffer.readUInt16LE(0)); + }); +}); + +suite('Reading big/little endian', function () { + var array, u8, bv, bsw, bsr; + + setup(function () { + array = new ArrayBuffer(64); + u8 = new Uint8Array(array); + u8[0] = 0x01; + u8[1] = 0x02; + // Test initializing straight from the array. + bsr = new BitStream(array); + }); + + test('4b, little-endian', function () { + assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); + + var result = []; + result.push(bsr.readBits(4)); + result.push(bsr.readBits(4)); + result.push(bsr.readBits(4)); + result.push(bsr.readBits(4)); + + // 0000 0001 0000 0010 [01 02] + // [#2] [#1] [#4] [#3] + assert.deepEqual(result, [1, 0, 2, 0]); + }); + + test('8b, little-endian', function () { + assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); + + var result = []; + result.push(bsr.readBits(8)); + result.push(bsr.readBits(8)); + + // 0000 0001 0000 0010 [01 02] + // [ #1] [ #2] + assert.deepEqual(result, [1, 2]); + }); + + test('10b, little-endian', function () { + assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); + + var result = []; + result.push(bsr.readBits(10)); + + // 0000 0001 0000 0010 [01 02] + // ... #1] [ #2][#1... + assert.deepEqual(result, [513]); + }); + + test('16b, little-endian', function () { + assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); + + var result = []; + result.push(bsr.readBits(16)); + + // 0000 0001 0000 0010 [01 02] + // [ #1] + assert.deepEqual(result, [0x201]); + }); + + test('24b, little-endian', function () { + u8[2] = 0x03; + assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); + + var result = []; + result.push(bsr.readBits(24)); + + // 0000 0001 0000 0010 0000 0011 [01 02 03] + // [ #1] + assert.deepEqual(result, [0x30201]); + }); + + test('4b, big-endian', function () { + bsr.bigEndian = true; + assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); + + var result = []; + result.push(bsr.readBits(4)); + result.push(bsr.readBits(4)); + result.push(bsr.readBits(4)); + result.push(bsr.readBits(4)); + + // 0000 0001 0000 0010 [01 02] + // [#1] [#2] [#3] [#4] + assert.deepEqual(result, [0, 1, 0, 2]); + }); + + test('8b, big-endian', function () { + bsr.bigEndian = true; + assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); + + var result = []; + result.push(bsr.readBits(8)); + result.push(bsr.readBits(8)); + + // 0000 0001 0000 0010 [01 02] + // [ #1] [ #2] + assert.deepEqual(result, [1, 2]); + }); + + test('10b, big-endian', function () { + bsr.bigEndian = true; + assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); + + var result = []; + result.push(bsr.readBits(10)); + result.push(bsr.readBits(6)); + + // 0000 0001 0000 0010 [01 02] + // [ #1][ #2] + assert.deepEqual(result, [4, 2]); + }); + + test('16b, big-endian', function () { + bsr.bigEndian = true; + assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); + + var result = []; + result.push(bsr.readBits(16)); + + // 0000 0001 0000 0010 [01 02] + // [ #1] + assert.deepEqual(result, [0x102]); + }); + + test('24b, big-endian', function () { + u8[2] = 0x03; + bsr.bigEndian = true; + assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); + + var result = []; + result.push(bsr.readBits(24)); + + // 0000 0001 0000 0010 0000 0011 [01 02 03] + // [ #1] + assert.deepEqual(result, [0x10203]); + }); +}); + +suite('Writing big/little endian', function () { + var array, u8, bv, bsw, bsr; + + setup(function () { + array = new ArrayBuffer(2); + u8 = new Uint8Array(array); + bv = new BitView(array); + bsw = new BitStream(bv); + }); + + test('4b, little-endian', function () { + // 0000 0001 0000 0010 [01 02] + // [#2] [#1] [#4] [#3] + bsw.writeBits(1, 4); + bsw.writeBits(0, 4); + bsw.writeBits(2, 4); + bsw.writeBits(0, 4); + + assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); + }); + + test('8b, little-endian', function () { + // 0000 0001 0000 0010 [01 02] + // [ #1] [ #2] + bsw.writeBits(1, 8); + bsw.writeBits(2, 8); + + assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); + }); + + test('10b, little-endian', function () { + // 0000 0001 0000 0010 [01 02] + // ... #1] [ #2][#1... + bsw.writeBits(513, 10); + + assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); + }); + + test('16b, little-endian', function () { + // 0000 0001 0000 0010 [01 02] + // [ #1] + bsw.writeBits(0x201, 16); + + assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); + }); + + test('4b, big-endian', function () { + bsw.bigEndian = true; + + // 0000 0001 0000 0010 [01 02] + // [#1] [#2] [#3] [#4] + bsw.writeBits(0, 4); + bsw.writeBits(1, 4); + bsw.writeBits(0, 4); + bsw.writeBits(2, 4); + + assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); + }); + + test('8b, big-endian', function () { + bsw.bigEndian = true; + + // 0000 0001 0000 0010 [01 02] + // [ #1] [ #2] + bsw.writeBits(1, 8); + bsw.writeBits(2, 8); + + assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); + }); + + test('10b, big-endian', function () { + bsw.bigEndian = true; + + // 0000 0001 0000 0010 [01 02] + // [ #1][ #2] + bsw.writeBits(4, 10); + bsw.writeBits(2, 6); + + assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); + }); + + test('16b, big-endian', function () { + bsw.bigEndian = true; + + // 0000 0001 0000 0010 [01 02] + // [ #1] + bsw.writeBits(0x102, 16); + + assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); + }); +}); diff --git a/node_modules/lv_font_conv/node_modules/debug/LICENSE b/node_modules/lv_font_conv/node_modules/debug/LICENSE new file mode 100644 index 00000000..658c933d --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/debug/LICENSE @@ -0,0 +1,19 @@ +(The MIT License) + +Copyright (c) 2014 TJ Holowaychuk + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the 'Software'), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/node_modules/lv_font_conv/node_modules/debug/README.md b/node_modules/lv_font_conv/node_modules/debug/README.md new file mode 100644 index 00000000..88dae35d --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/debug/README.md @@ -0,0 +1,455 @@ +# debug +[![Build Status](https://travis-ci.org/visionmedia/debug.svg?branch=master)](https://travis-ci.org/visionmedia/debug) [![Coverage Status](https://coveralls.io/repos/github/visionmedia/debug/badge.svg?branch=master)](https://coveralls.io/github/visionmedia/debug?branch=master) [![Slack](https://visionmedia-community-slackin.now.sh/badge.svg)](https://visionmedia-community-slackin.now.sh/) [![OpenCollective](https://opencollective.com/debug/backers/badge.svg)](#backers) +[![OpenCollective](https://opencollective.com/debug/sponsors/badge.svg)](#sponsors) + + + +A tiny JavaScript debugging utility modelled after Node.js core's debugging +technique. Works in Node.js and web browsers. + +## Installation + +```bash +$ npm install debug +``` + +## Usage + +`debug` exposes a function; simply pass this function the name of your module, and it will return a decorated version of `console.error` for you to pass debug statements to. This will allow you to toggle the debug output for different parts of your module as well as the module as a whole. + +Example [_app.js_](./examples/node/app.js): + +```js +var debug = require('debug')('http') + , http = require('http') + , name = 'My App'; + +// fake app + +debug('booting %o', name); + +http.createServer(function(req, res){ + debug(req.method + ' ' + req.url); + res.end('hello\n'); +}).listen(3000, function(){ + debug('listening'); +}); + +// fake worker of some kind + +require('./worker'); +``` + +Example [_worker.js_](./examples/node/worker.js): + +```js +var a = require('debug')('worker:a') + , b = require('debug')('worker:b'); + +function work() { + a('doing lots of uninteresting work'); + setTimeout(work, Math.random() * 1000); +} + +work(); + +function workb() { + b('doing some work'); + setTimeout(workb, Math.random() * 2000); +} + +workb(); +``` + +The `DEBUG` environment variable is then used to enable these based on space or +comma-delimited names. + +Here are some examples: + +screen shot 2017-08-08 at 12 53 04 pm +screen shot 2017-08-08 at 12 53 38 pm +screen shot 2017-08-08 at 12 53 25 pm + +#### Windows command prompt notes + +##### CMD + +On Windows the environment variable is set using the `set` command. + +```cmd +set DEBUG=*,-not_this +``` + +Example: + +```cmd +set DEBUG=* & node app.js +``` + +##### PowerShell (VS Code default) + +PowerShell uses different syntax to set environment variables. + +```cmd +$env:DEBUG = "*,-not_this" +``` + +Example: + +```cmd +$env:DEBUG='app';node app.js +``` + +Then, run the program to be debugged as usual. + +npm script example: +```js + "windowsDebug": "@powershell -Command $env:DEBUG='*';node app.js", +``` + +## Namespace Colors + +Every debug instance has a color generated for it based on its namespace name. +This helps when visually parsing the debug output to identify which debug instance +a debug line belongs to. + +#### Node.js + +In Node.js, colors are enabled when stderr is a TTY. You also _should_ install +the [`supports-color`](https://npmjs.org/supports-color) module alongside debug, +otherwise debug will only use a small handful of basic colors. + + + +#### Web Browser + +Colors are also enabled on "Web Inspectors" that understand the `%c` formatting +option. These are WebKit web inspectors, Firefox ([since version +31](https://hacks.mozilla.org/2014/05/editable-box-model-multiple-selection-sublime-text-keys-much-more-firefox-developer-tools-episode-31/)) +and the Firebug plugin for Firefox (any version). + + + + +## Millisecond diff + +When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the "+NNNms" will show you how much time was spent between calls. + + + +When stdout is not a TTY, `Date#toISOString()` is used, making it more useful for logging the debug information as shown below: + + + + +## Conventions + +If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use ":" to separate features. For example "bodyParser" from Connect would then be "connect:bodyParser". If you append a "*" to the end of your name, it will always be enabled regardless of the setting of the DEBUG environment variable. You can then use it for normal output as well as debug output. + +## Wildcards + +The `*` character may be used as a wildcard. Suppose for example your library has +debuggers named "connect:bodyParser", "connect:compress", "connect:session", +instead of listing all three with +`DEBUG=connect:bodyParser,connect:compress,connect:session`, you may simply do +`DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`. + +You can also exclude specific debuggers by prefixing them with a "-" character. +For example, `DEBUG=*,-connect:*` would include all debuggers except those +starting with "connect:". + +## Environment Variables + +When running through Node.js, you can set a few environment variables that will +change the behavior of the debug logging: + +| Name | Purpose | +|-----------|-------------------------------------------------| +| `DEBUG` | Enables/disables specific debugging namespaces. | +| `DEBUG_HIDE_DATE` | Hide date from debug output (non-TTY). | +| `DEBUG_COLORS`| Whether or not to use colors in the debug output. | +| `DEBUG_DEPTH` | Object inspection depth. | +| `DEBUG_SHOW_HIDDEN` | Shows hidden properties on inspected objects. | + + +__Note:__ The environment variables beginning with `DEBUG_` end up being +converted into an Options object that gets used with `%o`/`%O` formatters. +See the Node.js documentation for +[`util.inspect()`](https://nodejs.org/api/util.html#util_util_inspect_object_options) +for the complete list. + +## Formatters + +Debug uses [printf-style](https://wikipedia.org/wiki/Printf_format_string) formatting. +Below are the officially supported formatters: + +| Formatter | Representation | +|-----------|----------------| +| `%O` | Pretty-print an Object on multiple lines. | +| `%o` | Pretty-print an Object all on a single line. | +| `%s` | String. | +| `%d` | Number (both integer and float). | +| `%j` | JSON. Replaced with the string '[Circular]' if the argument contains circular references. | +| `%%` | Single percent sign ('%'). This does not consume an argument. | + + +### Custom formatters + +You can add custom formatters by extending the `debug.formatters` object. +For example, if you wanted to add support for rendering a Buffer as hex with +`%h`, you could do something like: + +```js +const createDebug = require('debug') +createDebug.formatters.h = (v) => { + return v.toString('hex') +} + +// …elsewhere +const debug = createDebug('foo') +debug('this is hex: %h', new Buffer('hello world')) +// foo this is hex: 68656c6c6f20776f726c6421 +0ms +``` + + +## Browser Support + +You can build a browser-ready script using [browserify](https://github.com/substack/node-browserify), +or just use the [browserify-as-a-service](https://wzrd.in/) [build](https://wzrd.in/standalone/debug@latest), +if you don't want to build it yourself. + +Debug's enable state is currently persisted by `localStorage`. +Consider the situation shown below where you have `worker:a` and `worker:b`, +and wish to debug both. You can enable this using `localStorage.debug`: + +```js +localStorage.debug = 'worker:*' +``` + +And then refresh the page. + +```js +a = debug('worker:a'); +b = debug('worker:b'); + +setInterval(function(){ + a('doing some work'); +}, 1000); + +setInterval(function(){ + b('doing some work'); +}, 1200); +``` + + +## Output streams + + By default `debug` will log to stderr, however this can be configured per-namespace by overriding the `log` method: + +Example [_stdout.js_](./examples/node/stdout.js): + +```js +var debug = require('debug'); +var error = debug('app:error'); + +// by default stderr is used +error('goes to stderr!'); + +var log = debug('app:log'); +// set this namespace to log via console.log +log.log = console.log.bind(console); // don't forget to bind to console! +log('goes to stdout'); +error('still goes to stderr!'); + +// set all output to go via console.info +// overrides all per-namespace log settings +debug.log = console.info.bind(console); +error('now goes to stdout via console.info'); +log('still goes to stdout, but via console.info now'); +``` + +## Extend +You can simply extend debugger +```js +const log = require('debug')('auth'); + +//creates new debug instance with extended namespace +const logSign = log.extend('sign'); +const logLogin = log.extend('login'); + +log('hello'); // auth hello +logSign('hello'); //auth:sign hello +logLogin('hello'); //auth:login hello +``` + +## Set dynamically + +You can also enable debug dynamically by calling the `enable()` method : + +```js +let debug = require('debug'); + +console.log(1, debug.enabled('test')); + +debug.enable('test'); +console.log(2, debug.enabled('test')); + +debug.disable(); +console.log(3, debug.enabled('test')); + +``` + +print : +``` +1 false +2 true +3 false +``` + +Usage : +`enable(namespaces)` +`namespaces` can include modes separated by a colon and wildcards. + +Note that calling `enable()` completely overrides previously set DEBUG variable : + +``` +$ DEBUG=foo node -e 'var dbg = require("debug"); dbg.enable("bar"); console.log(dbg.enabled("foo"))' +=> false +``` + +`disable()` + +Will disable all namespaces. The functions returns the namespaces currently +enabled (and skipped). This can be useful if you want to disable debugging +temporarily without knowing what was enabled to begin with. + +For example: + +```js +let debug = require('debug'); +debug.enable('foo:*,-foo:bar'); +let namespaces = debug.disable(); +debug.enable(namespaces); +``` + +Note: There is no guarantee that the string will be identical to the initial +enable string, but semantically they will be identical. + +## Checking whether a debug target is enabled + +After you've created a debug instance, you can determine whether or not it is +enabled by checking the `enabled` property: + +```javascript +const debug = require('debug')('http'); + +if (debug.enabled) { + // do stuff... +} +``` + +You can also manually toggle this property to force the debug instance to be +enabled or disabled. + + +## Authors + + - TJ Holowaychuk + - Nathan Rajlich + - Andrew Rhyne + +## Backers + +Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/debug#backer)] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +## Sponsors + +Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/debug#sponsor)] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +## License + +(The MIT License) + +Copyright (c) 2014-2017 TJ Holowaychuk <tj@vision-media.ca> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/debug/package.json b/node_modules/lv_font_conv/node_modules/debug/package.json new file mode 100644 index 00000000..c9f15f8c --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/debug/package.json @@ -0,0 +1,111 @@ +{ + "_args": [ + [ + "debug@4.3.1", + "/home/vitaly/Dropbox/Coding/lv_font_conv" + ] + ], + "_from": "debug@4.3.1", + "_id": "debug@4.3.1", + "_inBundle": false, + "_integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "_location": "/debug", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "debug@4.3.1", + "name": "debug", + "escapedName": "debug", + "rawSpec": "4.3.1", + "saveSpec": null, + "fetchSpec": "4.3.1" + }, + "_requiredBy": [ + "/", + "/@babel/core", + "/@babel/helper-define-polyfill-provider", + "/@babel/traverse", + "/@eslint/eslintrc", + "/eslint", + "/istanbul-lib-source-maps", + "/mocha" + ], + "_resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "_spec": "4.3.1", + "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", + "author": { + "name": "TJ Holowaychuk", + "email": "tj@vision-media.ca" + }, + "browser": "./src/browser.js", + "bugs": { + "url": "https://github.com/visionmedia/debug/issues" + }, + "contributors": [ + { + "name": "Nathan Rajlich", + "email": "nathan@tootallnate.net", + "url": "http://n8.io" + }, + { + "name": "Andrew Rhyne", + "email": "rhyneandrew@gmail.com" + }, + { + "name": "Josh Junon", + "email": "josh@junon.me" + } + ], + "dependencies": { + "ms": "2.1.2" + }, + "description": "small debugging utility", + "devDependencies": { + "brfs": "^2.0.1", + "browserify": "^16.2.3", + "coveralls": "^3.0.2", + "istanbul": "^0.4.5", + "karma": "^3.1.4", + "karma-browserify": "^6.0.0", + "karma-chrome-launcher": "^2.2.0", + "karma-mocha": "^1.3.0", + "mocha": "^5.2.0", + "mocha-lcov-reporter": "^1.2.0", + "xo": "^0.23.0" + }, + "engines": { + "node": ">=6.0" + }, + "files": [ + "src", + "LICENSE", + "README.md" + ], + "homepage": "https://github.com/visionmedia/debug#readme", + "keywords": [ + "debug", + "log", + "debugger" + ], + "license": "MIT", + "main": "./src/index.js", + "name": "debug", + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + }, + "repository": { + "type": "git", + "url": "git://github.com/visionmedia/debug.git" + }, + "scripts": { + "lint": "xo", + "test": "npm run test:node && npm run test:browser && npm run lint", + "test:browser": "karma start --single-run", + "test:coverage": "cat ./coverage/lcov.info | coveralls", + "test:node": "istanbul cover _mocha -- test.js" + }, + "version": "4.3.1" +} diff --git a/node_modules/lv_font_conv/node_modules/debug/src/browser.js b/node_modules/lv_font_conv/node_modules/debug/src/browser.js new file mode 100644 index 00000000..cd0fc35d --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/debug/src/browser.js @@ -0,0 +1,269 @@ +/* eslint-env browser */ + +/** + * This is the web browser implementation of `debug()`. + */ + +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +exports.storage = localstorage(); +exports.destroy = (() => { + let warned = false; + + return () => { + if (!warned) { + warned = true; + console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); + } + }; +})(); + +/** + * Colors. + */ + +exports.colors = [ + '#0000CC', + '#0000FF', + '#0033CC', + '#0033FF', + '#0066CC', + '#0066FF', + '#0099CC', + '#0099FF', + '#00CC00', + '#00CC33', + '#00CC66', + '#00CC99', + '#00CCCC', + '#00CCFF', + '#3300CC', + '#3300FF', + '#3333CC', + '#3333FF', + '#3366CC', + '#3366FF', + '#3399CC', + '#3399FF', + '#33CC00', + '#33CC33', + '#33CC66', + '#33CC99', + '#33CCCC', + '#33CCFF', + '#6600CC', + '#6600FF', + '#6633CC', + '#6633FF', + '#66CC00', + '#66CC33', + '#9900CC', + '#9900FF', + '#9933CC', + '#9933FF', + '#99CC00', + '#99CC33', + '#CC0000', + '#CC0033', + '#CC0066', + '#CC0099', + '#CC00CC', + '#CC00FF', + '#CC3300', + '#CC3333', + '#CC3366', + '#CC3399', + '#CC33CC', + '#CC33FF', + '#CC6600', + '#CC6633', + '#CC9900', + '#CC9933', + '#CCCC00', + '#CCCC33', + '#FF0000', + '#FF0033', + '#FF0066', + '#FF0099', + '#FF00CC', + '#FF00FF', + '#FF3300', + '#FF3333', + '#FF3366', + '#FF3399', + '#FF33CC', + '#FF33FF', + '#FF6600', + '#FF6633', + '#FF9900', + '#FF9933', + '#FFCC00', + '#FFCC33' +]; + +/** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ + +// eslint-disable-next-line complexity +function useColors() { + // NB: In an Electron preload script, document will be defined but not fully + // initialized. Since we know we're in Chrome, we'll just detect this case + // explicitly + if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) { + return true; + } + + // Internet Explorer and Edge do not support colors. + if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) { + return false; + } + + // Is webkit? http://stackoverflow.com/a/16459606/376773 + // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 + return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || + // Is firebug? http://stackoverflow.com/a/398120/376773 + (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || + // Is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || + // Double check webkit in userAgent just in case we are in a worker + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); +} + +/** + * Colorize log arguments if enabled. + * + * @api public + */ + +function formatArgs(args) { + args[0] = (this.useColors ? '%c' : '') + + this.namespace + + (this.useColors ? ' %c' : ' ') + + args[0] + + (this.useColors ? '%c ' : ' ') + + '+' + module.exports.humanize(this.diff); + + if (!this.useColors) { + return; + } + + const c = 'color: ' + this.color; + args.splice(1, 0, c, 'color: inherit'); + + // The final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + let index = 0; + let lastC = 0; + args[0].replace(/%[a-zA-Z%]/g, match => { + if (match === '%%') { + return; + } + index++; + if (match === '%c') { + // We only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; + } + }); + + args.splice(lastC, 0, c); +} + +/** + * Invokes `console.debug()` when available. + * No-op when `console.debug` is not a "function". + * If `console.debug` is not available, falls back + * to `console.log`. + * + * @api public + */ +exports.log = console.debug || console.log || (() => {}); + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ +function save(namespaces) { + try { + if (namespaces) { + exports.storage.setItem('debug', namespaces); + } else { + exports.storage.removeItem('debug'); + } + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ +function load() { + let r; + try { + r = exports.storage.getItem('debug'); + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } + + // If debug isn't set in LS, and we're in Electron, try to load $DEBUG + if (!r && typeof process !== 'undefined' && 'env' in process) { + r = process.env.DEBUG; + } + + return r; +} + +/** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + +function localstorage() { + try { + // TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context + // The Browser also has localStorage in the global context. + return localStorage; + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } +} + +module.exports = require('./common')(exports); + +const {formatters} = module.exports; + +/** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ + +formatters.j = function (v) { + try { + return JSON.stringify(v); + } catch (error) { + return '[UnexpectedJSONParseError]: ' + error.message; + } +}; diff --git a/node_modules/lv_font_conv/node_modules/debug/src/common.js b/node_modules/lv_font_conv/node_modules/debug/src/common.js new file mode 100644 index 00000000..392a8e00 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/debug/src/common.js @@ -0,0 +1,261 @@ + +/** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + */ + +function setup(env) { + createDebug.debug = createDebug; + createDebug.default = createDebug; + createDebug.coerce = coerce; + createDebug.disable = disable; + createDebug.enable = enable; + createDebug.enabled = enabled; + createDebug.humanize = require('ms'); + createDebug.destroy = destroy; + + Object.keys(env).forEach(key => { + createDebug[key] = env[key]; + }); + + /** + * The currently active debug mode names, and names to skip. + */ + + createDebug.names = []; + createDebug.skips = []; + + /** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". + */ + createDebug.formatters = {}; + + /** + * Selects a color for a debug namespace + * @param {String} namespace The namespace string for the for the debug instance to be colored + * @return {Number|String} An ANSI color code for the given namespace + * @api private + */ + function selectColor(namespace) { + let hash = 0; + + for (let i = 0; i < namespace.length; i++) { + hash = ((hash << 5) - hash) + namespace.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + + return createDebug.colors[Math.abs(hash) % createDebug.colors.length]; + } + createDebug.selectColor = selectColor; + + /** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + function createDebug(namespace) { + let prevTime; + let enableOverride = null; + + function debug(...args) { + // Disabled? + if (!debug.enabled) { + return; + } + + const self = debug; + + // Set `diff` timestamp + const curr = Number(new Date()); + const ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; + + args[0] = createDebug.coerce(args[0]); + + if (typeof args[0] !== 'string') { + // Anything else let's inspect with %O + args.unshift('%O'); + } + + // Apply any `formatters` transformations + let index = 0; + args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => { + // If we encounter an escaped % then don't increase the array index + if (match === '%%') { + return '%'; + } + index++; + const formatter = createDebug.formatters[format]; + if (typeof formatter === 'function') { + const val = args[index]; + match = formatter.call(self, val); + + // Now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); + + // Apply env-specific formatting (colors, etc.) + createDebug.formatArgs.call(self, args); + + const logFn = self.log || createDebug.log; + logFn.apply(self, args); + } + + debug.namespace = namespace; + debug.useColors = createDebug.useColors(); + debug.color = createDebug.selectColor(namespace); + debug.extend = extend; + debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release. + + Object.defineProperty(debug, 'enabled', { + enumerable: true, + configurable: false, + get: () => enableOverride === null ? createDebug.enabled(namespace) : enableOverride, + set: v => { + enableOverride = v; + } + }); + + // Env-specific initialization logic for debug instances + if (typeof createDebug.init === 'function') { + createDebug.init(debug); + } + + return debug; + } + + function extend(namespace, delimiter) { + const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace); + newDebug.log = this.log; + return newDebug; + } + + /** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + function enable(namespaces) { + createDebug.save(namespaces); + + createDebug.names = []; + createDebug.skips = []; + + let i; + const split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); + const len = split.length; + + for (i = 0; i < len; i++) { + if (!split[i]) { + // ignore empty strings + continue; + } + + namespaces = split[i].replace(/\*/g, '.*?'); + + if (namespaces[0] === '-') { + createDebug.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); + } else { + createDebug.names.push(new RegExp('^' + namespaces + '$')); + } + } + } + + /** + * Disable debug output. + * + * @return {String} namespaces + * @api public + */ + function disable() { + const namespaces = [ + ...createDebug.names.map(toNamespace), + ...createDebug.skips.map(toNamespace).map(namespace => '-' + namespace) + ].join(','); + createDebug.enable(''); + return namespaces; + } + + /** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + function enabled(name) { + if (name[name.length - 1] === '*') { + return true; + } + + let i; + let len; + + for (i = 0, len = createDebug.skips.length; i < len; i++) { + if (createDebug.skips[i].test(name)) { + return false; + } + } + + for (i = 0, len = createDebug.names.length; i < len; i++) { + if (createDebug.names[i].test(name)) { + return true; + } + } + + return false; + } + + /** + * Convert regexp to namespace + * + * @param {RegExp} regxep + * @return {String} namespace + * @api private + */ + function toNamespace(regexp) { + return regexp.toString() + .substring(2, regexp.toString().length - 2) + .replace(/\.\*\?$/, '*'); + } + + /** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + function coerce(val) { + if (val instanceof Error) { + return val.stack || val.message; + } + return val; + } + + /** + * XXX DO NOT USE. This is a temporary stub function. + * XXX It WILL be removed in the next major release. + */ + function destroy() { + console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); + } + + createDebug.enable(createDebug.load()); + + return createDebug; +} + +module.exports = setup; diff --git a/node_modules/lv_font_conv/node_modules/debug/src/index.js b/node_modules/lv_font_conv/node_modules/debug/src/index.js new file mode 100644 index 00000000..bf4c57f2 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/debug/src/index.js @@ -0,0 +1,10 @@ +/** + * Detect Electron renderer / nwjs process, which is node, but we should + * treat as a browser. + */ + +if (typeof process === 'undefined' || process.type === 'renderer' || process.browser === true || process.__nwjs) { + module.exports = require('./browser.js'); +} else { + module.exports = require('./node.js'); +} diff --git a/node_modules/lv_font_conv/node_modules/debug/src/node.js b/node_modules/lv_font_conv/node_modules/debug/src/node.js new file mode 100644 index 00000000..79bc085c --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/debug/src/node.js @@ -0,0 +1,263 @@ +/** + * Module dependencies. + */ + +const tty = require('tty'); +const util = require('util'); + +/** + * This is the Node.js implementation of `debug()`. + */ + +exports.init = init; +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +exports.destroy = util.deprecate( + () => {}, + 'Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.' +); + +/** + * Colors. + */ + +exports.colors = [6, 2, 3, 4, 5, 1]; + +try { + // Optional dependency (as in, doesn't need to be installed, NOT like optionalDependencies in package.json) + // eslint-disable-next-line import/no-extraneous-dependencies + const supportsColor = require('supports-color'); + + if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) { + exports.colors = [ + 20, + 21, + 26, + 27, + 32, + 33, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 56, + 57, + 62, + 63, + 68, + 69, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 92, + 93, + 98, + 99, + 112, + 113, + 128, + 129, + 134, + 135, + 148, + 149, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 178, + 179, + 184, + 185, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 214, + 215, + 220, + 221 + ]; + } +} catch (error) { + // Swallow - we only care if `supports-color` is available; it doesn't have to be. +} + +/** + * Build up the default `inspectOpts` object from the environment variables. + * + * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js + */ + +exports.inspectOpts = Object.keys(process.env).filter(key => { + return /^debug_/i.test(key); +}).reduce((obj, key) => { + // Camel-case + const prop = key + .substring(6) + .toLowerCase() + .replace(/_([a-z])/g, (_, k) => { + return k.toUpperCase(); + }); + + // Coerce string value into JS value + let val = process.env[key]; + if (/^(yes|on|true|enabled)$/i.test(val)) { + val = true; + } else if (/^(no|off|false|disabled)$/i.test(val)) { + val = false; + } else if (val === 'null') { + val = null; + } else { + val = Number(val); + } + + obj[prop] = val; + return obj; +}, {}); + +/** + * Is stdout a TTY? Colored output is enabled when `true`. + */ + +function useColors() { + return 'colors' in exports.inspectOpts ? + Boolean(exports.inspectOpts.colors) : + tty.isatty(process.stderr.fd); +} + +/** + * Adds ANSI color escape codes if enabled. + * + * @api public + */ + +function formatArgs(args) { + const {namespace: name, useColors} = this; + + if (useColors) { + const c = this.color; + const colorCode = '\u001B[3' + (c < 8 ? c : '8;5;' + c); + const prefix = ` ${colorCode};1m${name} \u001B[0m`; + + args[0] = prefix + args[0].split('\n').join('\n' + prefix); + args.push(colorCode + 'm+' + module.exports.humanize(this.diff) + '\u001B[0m'); + } else { + args[0] = getDate() + name + ' ' + args[0]; + } +} + +function getDate() { + if (exports.inspectOpts.hideDate) { + return ''; + } + return new Date().toISOString() + ' '; +} + +/** + * Invokes `util.format()` with the specified arguments and writes to stderr. + */ + +function log(...args) { + return process.stderr.write(util.format(...args) + '\n'); +} + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ +function save(namespaces) { + if (namespaces) { + process.env.DEBUG = namespaces; + } else { + // If you set a process.env field to null or undefined, it gets cast to the + // string 'null' or 'undefined'. Just delete instead. + delete process.env.DEBUG; + } +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + +function load() { + return process.env.DEBUG; +} + +/** + * Init logic for `debug` instances. + * + * Create a new `inspectOpts` object in case `useColors` is set + * differently for a particular `debug` instance. + */ + +function init(debug) { + debug.inspectOpts = {}; + + const keys = Object.keys(exports.inspectOpts); + for (let i = 0; i < keys.length; i++) { + debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]]; + } +} + +module.exports = require('./common')(exports); + +const {formatters} = module.exports; + +/** + * Map %o to `util.inspect()`, all on a single line. + */ + +formatters.o = function (v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts) + .split('\n') + .map(str => str.trim()) + .join(' '); +}; + +/** + * Map %O to `util.inspect()`, allowing multiple lines if needed. + */ + +formatters.O = function (v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts); +}; diff --git a/node_modules/lv_font_conv/node_modules/make-error/LICENSE b/node_modules/lv_font_conv/node_modules/make-error/LICENSE new file mode 100644 index 00000000..9dcf797e --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/make-error/LICENSE @@ -0,0 +1,5 @@ +Copyright 2014 Julien Fontanet + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/make-error/README.md b/node_modules/lv_font_conv/node_modules/make-error/README.md new file mode 100644 index 00000000..5c089a26 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/make-error/README.md @@ -0,0 +1,112 @@ +# make-error + +[![Package Version](https://badgen.net/npm/v/make-error)](https://npmjs.org/package/make-error) [![Build Status](https://travis-ci.org/JsCommunity/make-error.png?branch=master)](https://travis-ci.org/JsCommunity/make-error) [![PackagePhobia](https://badgen.net/packagephobia/install/make-error)](https://packagephobia.now.sh/result?p=make-error) [![Latest Commit](https://badgen.net/github/last-commit/JsCommunity/make-error)](https://github.com/JsCommunity/make-error/commits/master) + +> Make your own error types! + +## Features + +- Compatible Node & browsers +- `instanceof` support +- `error.name` & `error.stack` support +- compatible with [CSP](https://en.wikipedia.org/wiki/Content_Security_Policy) (i.e. no `eval()`) + +## Installation + +### Node & [Browserify](http://browserify.org/)/[Webpack](https://webpack.js.org/) + +Installation of the [npm package](https://npmjs.org/package/make-error): + +``` +> npm install --save make-error +``` + +Then require the package: + +```javascript +var makeError = require("make-error"); +``` + +### Browser + +You can directly use the build provided at [unpkg.com](https://unpkg.com): + +```html + +``` + +## Usage + +### Basic named error + +```javascript +var CustomError = makeError("CustomError"); + +// Parameters are forwarded to the super class (here Error). +throw new CustomError("a message"); +``` + +### Advanced error class + +```javascript +function CustomError(customValue) { + CustomError.super.call(this, "custom error message"); + + this.customValue = customValue; +} +makeError(CustomError); + +// Feel free to extend the prototype. +CustomError.prototype.myMethod = function CustomError$myMethod() { + console.log("CustomError.myMethod (%s, %s)", this.code, this.message); +}; + +//----- + +try { + throw new CustomError(42); +} catch (error) { + error.myMethod(); +} +``` + +### Specialized error + +```javascript +var SpecializedError = makeError("SpecializedError", CustomError); + +throw new SpecializedError(42); +``` + +### Inheritance + +> Best for ES2015+. + +```javascript +import { BaseError } from "make-error"; + +class CustomError extends BaseError { + constructor() { + super("custom error message"); + } +} +``` + +## Related + +- [make-error-cause](https://www.npmjs.com/package/make-error-cause): Make your own error types, with a cause! + +## Contributions + +Contributions are _very_ welcomed, either on the documentation or on +the code. + +You may: + +- report any [issue](https://github.com/JsCommunity/make-error/issues) + you've encountered; +- fork and create a pull request. + +## License + +ISC © [Julien Fontanet](http://julien.isonoe.net) diff --git a/node_modules/lv_font_conv/node_modules/make-error/dist/make-error.js b/node_modules/lv_font_conv/node_modules/make-error/dist/make-error.js new file mode 100644 index 00000000..32444c69 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/make-error/dist/make-error.js @@ -0,0 +1 @@ +!function(f){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=f();else if("function"==typeof define&&define.amd)define([],f);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).makeError=f()}}(function(){return function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){return o(e[i][1][r]||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i; + +/** + * Set the constructor prototype to `BaseError`. + */ +declare function makeError(super_: { + new (...args: any[]): T; +}): makeError.Constructor; + +/** + * Create a specialized error instance. + */ +declare function makeError( + name: string | Function, + super_: K +): K & makeError.SpecializedConstructor; + +declare namespace makeError { + /** + * Use with ES2015+ inheritance. + */ + export class BaseError extends Error { + message: string; + name: string; + stack: string; + + constructor(message?: string); + } + + export interface Constructor { + new (message?: string): T; + super_: any; + prototype: T; + } + + export interface SpecializedConstructor { + super_: any; + prototype: T; + } +} + +export = makeError; diff --git a/node_modules/lv_font_conv/node_modules/make-error/index.js b/node_modules/lv_font_conv/node_modules/make-error/index.js new file mode 100644 index 00000000..fab60407 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/make-error/index.js @@ -0,0 +1,151 @@ +// ISC @ Julien Fontanet + +"use strict"; + +// =================================================================== + +var construct = typeof Reflect !== "undefined" ? Reflect.construct : undefined; +var defineProperty = Object.defineProperty; + +// ------------------------------------------------------------------- + +var captureStackTrace = Error.captureStackTrace; +if (captureStackTrace === undefined) { + captureStackTrace = function captureStackTrace(error) { + var container = new Error(); + + defineProperty(error, "stack", { + configurable: true, + get: function getStack() { + var stack = container.stack; + + // Replace property with value for faster future accesses. + defineProperty(this, "stack", { + configurable: true, + value: stack, + writable: true, + }); + + return stack; + }, + set: function setStack(stack) { + defineProperty(error, "stack", { + configurable: true, + value: stack, + writable: true, + }); + }, + }); + }; +} + +// ------------------------------------------------------------------- + +function BaseError(message) { + if (message !== undefined) { + defineProperty(this, "message", { + configurable: true, + value: message, + writable: true, + }); + } + + var cname = this.constructor.name; + if (cname !== undefined && cname !== this.name) { + defineProperty(this, "name", { + configurable: true, + value: cname, + writable: true, + }); + } + + captureStackTrace(this, this.constructor); +} + +BaseError.prototype = Object.create(Error.prototype, { + // See: https://github.com/JsCommunity/make-error/issues/4 + constructor: { + configurable: true, + value: BaseError, + writable: true, + }, +}); + +// ------------------------------------------------------------------- + +// Sets the name of a function if possible (depends of the JS engine). +var setFunctionName = (function() { + function setFunctionName(fn, name) { + return defineProperty(fn, "name", { + configurable: true, + value: name, + }); + } + try { + var f = function() {}; + setFunctionName(f, "foo"); + if (f.name === "foo") { + return setFunctionName; + } + } catch (_) {} +})(); + +// ------------------------------------------------------------------- + +function makeError(constructor, super_) { + if (super_ == null || super_ === Error) { + super_ = BaseError; + } else if (typeof super_ !== "function") { + throw new TypeError("super_ should be a function"); + } + + var name; + if (typeof constructor === "string") { + name = constructor; + constructor = + construct !== undefined + ? function() { + return construct(super_, arguments, this.constructor); + } + : function() { + super_.apply(this, arguments); + }; + + // If the name can be set, do it once and for all. + if (setFunctionName !== undefined) { + setFunctionName(constructor, name); + name = undefined; + } + } else if (typeof constructor !== "function") { + throw new TypeError("constructor should be either a string or a function"); + } + + // Also register the super constructor also as `constructor.super_` just + // like Node's `util.inherits()`. + // + // eslint-disable-next-line dot-notation + constructor.super_ = constructor["super"] = super_; + + var properties = { + constructor: { + configurable: true, + value: constructor, + writable: true, + }, + }; + + // If the name could not be set on the constructor, set it on the + // prototype. + if (name !== undefined) { + properties.name = { + configurable: true, + value: name, + writable: true, + }; + } + constructor.prototype = Object.create(super_.prototype, properties); + + return constructor; +} +exports = module.exports = makeError; +exports.BaseError = BaseError; diff --git a/node_modules/lv_font_conv/node_modules/make-error/package.json b/node_modules/lv_font_conv/node_modules/make-error/package.json new file mode 100644 index 00000000..e5f69904 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/make-error/package.json @@ -0,0 +1,95 @@ +{ + "_args": [ + [ + "make-error@1.3.6", + "/home/vitaly/Dropbox/Coding/lv_font_conv" + ] + ], + "_from": "make-error@1.3.6", + "_id": "make-error@1.3.6", + "_inBundle": false, + "_integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "_location": "/make-error", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "make-error@1.3.6", + "name": "make-error", + "escapedName": "make-error", + "rawSpec": "1.3.6", + "saveSpec": null, + "fetchSpec": "1.3.6" + }, + "_requiredBy": [ + "/" + ], + "_resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "_spec": "1.3.6", + "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", + "author": { + "name": "Julien Fontanet", + "email": "julien.fontanet@isonoe.net" + }, + "bugs": { + "url": "https://github.com/JsCommunity/make-error/issues" + }, + "description": "Make your own error types!", + "devDependencies": { + "browserify": "^16.2.3", + "eslint": "^6.5.1", + "eslint-config-prettier": "^6.4.0", + "eslint-config-standard": "^14.1.0", + "eslint-plugin-import": "^2.14.0", + "eslint-plugin-node": "^10.0.0", + "eslint-plugin-promise": "^4.0.1", + "eslint-plugin-standard": "^4.0.0", + "husky": "^3.0.9", + "jest": "^24", + "prettier": "^1.14.3", + "uglify-js": "^3.3.2" + }, + "files": [ + "dist/", + "index.js", + "index.d.ts" + ], + "homepage": "https://github.com/JsCommunity/make-error", + "husky": { + "hooks": { + "commit-msg": "npm run test" + } + }, + "jest": { + "testEnvironment": "node" + }, + "keywords": [ + "create", + "custom", + "derive", + "error", + "errors", + "extend", + "extending", + "extension", + "factory", + "inherit", + "make", + "subclass" + ], + "license": "ISC", + "main": "index.js", + "name": "make-error", + "repository": { + "type": "git", + "url": "git://github.com/JsCommunity/make-error.git" + }, + "scripts": { + "dev-test": "jest --watch", + "format": "prettier --write '**'", + "prepublishOnly": "mkdir -p dist && browserify -s makeError index.js | uglifyjs -c > dist/make-error.js", + "pretest": "eslint --ignore-path .gitignore .", + "test": "jest" + }, + "version": "1.3.6" +} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/CHANGELOG.md b/node_modules/lv_font_conv/node_modules/mkdirp/CHANGELOG.md new file mode 100644 index 00000000..81458380 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changers Lorgs! + +## 1.0 + +Full rewrite. Essentially a brand new module. + +- Return a promise instead of taking a callback. +- Use native `fs.mkdir(path, { recursive: true })` when available. +- Drop support for outdated Node.js versions. (Technically still works on + Node.js v8, but only 10 and above are officially supported.) + +## 0.x + +Original and most widely used recursive directory creation implementation +in JavaScript, dating back to 2010. diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/LICENSE b/node_modules/lv_font_conv/node_modules/mkdirp/LICENSE new file mode 100644 index 00000000..13fcd15f --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/LICENSE @@ -0,0 +1,21 @@ +Copyright James Halliday (mail@substack.net) and Isaac Z. Schlueter (i@izs.me) + +This project is free software released under the MIT license: + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/bin/cmd.js b/node_modules/lv_font_conv/node_modules/mkdirp/bin/cmd.js new file mode 100755 index 00000000..6e0aa8dc --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/bin/cmd.js @@ -0,0 +1,68 @@ +#!/usr/bin/env node + +const usage = () => ` +usage: mkdirp [DIR1,DIR2..] {OPTIONS} + + Create each supplied directory including any necessary parent directories + that don't yet exist. + + If the directory already exists, do nothing. + +OPTIONS are: + + -m If a directory needs to be created, set the mode as an octal + --mode= permission string. + + -v --version Print the mkdirp version number + + -h --help Print this helpful banner + + -p --print Print the first directories created for each path provided + + --manual Use manual implementation, even if native is available +` + +const dirs = [] +const opts = {} +let print = false +let dashdash = false +let manual = false +for (const arg of process.argv.slice(2)) { + if (dashdash) + dirs.push(arg) + else if (arg === '--') + dashdash = true + else if (arg === '--manual') + manual = true + else if (/^-h/.test(arg) || /^--help/.test(arg)) { + console.log(usage()) + process.exit(0) + } else if (arg === '-v' || arg === '--version') { + console.log(require('../package.json').version) + process.exit(0) + } else if (arg === '-p' || arg === '--print') { + print = true + } else if (/^-m/.test(arg) || /^--mode=/.test(arg)) { + const mode = parseInt(arg.replace(/^(-m|--mode=)/, ''), 8) + if (isNaN(mode)) { + console.error(`invalid mode argument: ${arg}\nMust be an octal number.`) + process.exit(1) + } + opts.mode = mode + } else + dirs.push(arg) +} + +const mkdirp = require('../') +const impl = manual ? mkdirp.manual : mkdirp +if (dirs.length === 0) + console.error(usage()) + +Promise.all(dirs.map(dir => impl(dir, opts))) + .then(made => print ? made.forEach(m => m && console.log(m)) : null) + .catch(er => { + console.error(er.message) + if (er.code) + console.error(' code: ' + er.code) + process.exit(1) + }) diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/index.js b/node_modules/lv_font_conv/node_modules/mkdirp/index.js new file mode 100644 index 00000000..ad7a16c9 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/index.js @@ -0,0 +1,31 @@ +const optsArg = require('./lib/opts-arg.js') +const pathArg = require('./lib/path-arg.js') + +const {mkdirpNative, mkdirpNativeSync} = require('./lib/mkdirp-native.js') +const {mkdirpManual, mkdirpManualSync} = require('./lib/mkdirp-manual.js') +const {useNative, useNativeSync} = require('./lib/use-native.js') + + +const mkdirp = (path, opts) => { + path = pathArg(path) + opts = optsArg(opts) + return useNative(opts) + ? mkdirpNative(path, opts) + : mkdirpManual(path, opts) +} + +const mkdirpSync = (path, opts) => { + path = pathArg(path) + opts = optsArg(opts) + return useNativeSync(opts) + ? mkdirpNativeSync(path, opts) + : mkdirpManualSync(path, opts) +} + +mkdirp.sync = mkdirpSync +mkdirp.native = (path, opts) => mkdirpNative(pathArg(path), optsArg(opts)) +mkdirp.manual = (path, opts) => mkdirpManual(pathArg(path), optsArg(opts)) +mkdirp.nativeSync = (path, opts) => mkdirpNativeSync(pathArg(path), optsArg(opts)) +mkdirp.manualSync = (path, opts) => mkdirpManualSync(pathArg(path), optsArg(opts)) + +module.exports = mkdirp diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/find-made.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/find-made.js new file mode 100644 index 00000000..022e492c --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/lib/find-made.js @@ -0,0 +1,29 @@ +const {dirname} = require('path') + +const findMade = (opts, parent, path = undefined) => { + // we never want the 'made' return value to be a root directory + if (path === parent) + return Promise.resolve() + + return opts.statAsync(parent).then( + st => st.isDirectory() ? path : undefined, // will fail later + er => er.code === 'ENOENT' + ? findMade(opts, dirname(parent), parent) + : undefined + ) +} + +const findMadeSync = (opts, parent, path = undefined) => { + if (path === parent) + return undefined + + try { + return opts.statSync(parent).isDirectory() ? path : undefined + } catch (er) { + return er.code === 'ENOENT' + ? findMadeSync(opts, dirname(parent), parent) + : undefined + } +} + +module.exports = {findMade, findMadeSync} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-manual.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-manual.js new file mode 100644 index 00000000..2eb18cd6 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-manual.js @@ -0,0 +1,64 @@ +const {dirname} = require('path') + +const mkdirpManual = (path, opts, made) => { + opts.recursive = false + const parent = dirname(path) + if (parent === path) { + return opts.mkdirAsync(path, opts).catch(er => { + // swallowed by recursive implementation on posix systems + // any other error is a failure + if (er.code !== 'EISDIR') + throw er + }) + } + + return opts.mkdirAsync(path, opts).then(() => made || path, er => { + if (er.code === 'ENOENT') + return mkdirpManual(parent, opts) + .then(made => mkdirpManual(path, opts, made)) + if (er.code !== 'EEXIST' && er.code !== 'EROFS') + throw er + return opts.statAsync(path).then(st => { + if (st.isDirectory()) + return made + else + throw er + }, () => { throw er }) + }) +} + +const mkdirpManualSync = (path, opts, made) => { + const parent = dirname(path) + opts.recursive = false + + if (parent === path) { + try { + return opts.mkdirSync(path, opts) + } catch (er) { + // swallowed by recursive implementation on posix systems + // any other error is a failure + if (er.code !== 'EISDIR') + throw er + else + return + } + } + + try { + opts.mkdirSync(path, opts) + return made || path + } catch (er) { + if (er.code === 'ENOENT') + return mkdirpManualSync(path, opts, mkdirpManualSync(parent, opts, made)) + if (er.code !== 'EEXIST' && er.code !== 'EROFS') + throw er + try { + if (!opts.statSync(path).isDirectory()) + throw er + } catch (_) { + throw er + } + } +} + +module.exports = {mkdirpManual, mkdirpManualSync} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-native.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-native.js new file mode 100644 index 00000000..c7a6b698 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-native.js @@ -0,0 +1,39 @@ +const {dirname} = require('path') +const {findMade, findMadeSync} = require('./find-made.js') +const {mkdirpManual, mkdirpManualSync} = require('./mkdirp-manual.js') + +const mkdirpNative = (path, opts) => { + opts.recursive = true + const parent = dirname(path) + if (parent === path) + return opts.mkdirAsync(path, opts) + + return findMade(opts, path).then(made => + opts.mkdirAsync(path, opts).then(() => made) + .catch(er => { + if (er.code === 'ENOENT') + return mkdirpManual(path, opts) + else + throw er + })) +} + +const mkdirpNativeSync = (path, opts) => { + opts.recursive = true + const parent = dirname(path) + if (parent === path) + return opts.mkdirSync(path, opts) + + const made = findMadeSync(opts, path) + try { + opts.mkdirSync(path, opts) + return made + } catch (er) { + if (er.code === 'ENOENT') + return mkdirpManualSync(path, opts) + else + throw er + } +} + +module.exports = {mkdirpNative, mkdirpNativeSync} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/opts-arg.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/opts-arg.js new file mode 100644 index 00000000..2fa4833f --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/lib/opts-arg.js @@ -0,0 +1,23 @@ +const { promisify } = require('util') +const fs = require('fs') +const optsArg = opts => { + if (!opts) + opts = { mode: 0o777, fs } + else if (typeof opts === 'object') + opts = { mode: 0o777, fs, ...opts } + else if (typeof opts === 'number') + opts = { mode: opts, fs } + else if (typeof opts === 'string') + opts = { mode: parseInt(opts, 8), fs } + else + throw new TypeError('invalid options argument') + + opts.mkdir = opts.mkdir || opts.fs.mkdir || fs.mkdir + opts.mkdirAsync = promisify(opts.mkdir) + opts.stat = opts.stat || opts.fs.stat || fs.stat + opts.statAsync = promisify(opts.stat) + opts.statSync = opts.statSync || opts.fs.statSync || fs.statSync + opts.mkdirSync = opts.mkdirSync || opts.fs.mkdirSync || fs.mkdirSync + return opts +} +module.exports = optsArg diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/path-arg.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/path-arg.js new file mode 100644 index 00000000..cc07de5a --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/lib/path-arg.js @@ -0,0 +1,29 @@ +const platform = process.env.__TESTING_MKDIRP_PLATFORM__ || process.platform +const { resolve, parse } = require('path') +const pathArg = path => { + if (/\0/.test(path)) { + // simulate same failure that node raises + throw Object.assign( + new TypeError('path must be a string without null bytes'), + { + path, + code: 'ERR_INVALID_ARG_VALUE', + } + ) + } + + path = resolve(path) + if (platform === 'win32') { + const badWinChars = /[*|"<>?:]/ + const {root} = parse(path) + if (badWinChars.test(path.substr(root.length))) { + throw Object.assign(new Error('Illegal characters in path.'), { + path, + code: 'EINVAL', + }) + } + } + + return path +} +module.exports = pathArg diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/use-native.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/use-native.js new file mode 100644 index 00000000..079361de --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/lib/use-native.js @@ -0,0 +1,10 @@ +const fs = require('fs') + +const version = process.env.__TESTING_MKDIRP_NODE_VERSION__ || process.version +const versArr = version.replace(/^v/, '').split('.') +const hasNative = +versArr[0] > 10 || +versArr[0] === 10 && +versArr[1] >= 12 + +const useNative = !hasNative ? () => false : opts => opts.mkdir === fs.mkdir +const useNativeSync = !hasNative ? () => false : opts => opts.mkdirSync === fs.mkdirSync + +module.exports = {useNative, useNativeSync} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/package.json b/node_modules/lv_font_conv/node_modules/mkdirp/package.json new file mode 100644 index 00000000..1fb2e3d9 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/package.json @@ -0,0 +1,78 @@ +{ + "_args": [ + [ + "mkdirp@1.0.4", + "/home/vitaly/Dropbox/Coding/lv_font_conv" + ] + ], + "_from": "mkdirp@1.0.4", + "_id": "mkdirp@1.0.4", + "_inBundle": false, + "_integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "_location": "/mkdirp", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "mkdirp@1.0.4", + "name": "mkdirp", + "escapedName": "mkdirp", + "rawSpec": "1.0.4", + "saveSpec": null, + "fetchSpec": "1.0.4" + }, + "_requiredBy": [ + "/" + ], + "_resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "_spec": "1.0.4", + "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "bugs": { + "url": "https://github.com/isaacs/node-mkdirp/issues" + }, + "description": "Recursively mkdir, like `mkdir -p`", + "devDependencies": { + "require-inject": "^1.4.4", + "tap": "^14.10.7" + }, + "engines": { + "node": ">=10" + }, + "files": [ + "bin", + "lib", + "index.js" + ], + "homepage": "https://github.com/isaacs/node-mkdirp#readme", + "keywords": [ + "mkdir", + "directory", + "make dir", + "make", + "dir", + "recursive", + "native" + ], + "license": "MIT", + "main": "index.js", + "name": "mkdirp", + "repository": { + "type": "git", + "url": "git+https://github.com/isaacs/node-mkdirp.git" + }, + "scripts": { + "postpublish": "git push origin --follow-tags", + "postversion": "npm publish", + "preversion": "npm test", + "snap": "tap", + "test": "tap" + }, + "tap": { + "check-coverage": true, + "coverage-map": "map.js" + }, + "version": "1.0.4" +} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/readme.markdown b/node_modules/lv_font_conv/node_modules/mkdirp/readme.markdown new file mode 100644 index 00000000..827de590 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/mkdirp/readme.markdown @@ -0,0 +1,266 @@ +# mkdirp + +Like `mkdir -p`, but in Node.js! + +Now with a modern API and no\* bugs! + +\* may contain some bugs + +# example + +## pow.js + +```js +const mkdirp = require('mkdirp') + +// return value is a Promise resolving to the first directory created +mkdirp('/tmp/foo/bar/baz').then(made => + console.log(`made directories, starting with ${made}`)) +``` + +Output (where `/tmp/foo` already exists) + +``` +made directories, starting with /tmp/foo/bar +``` + +Or, if you don't have time to wait around for promises: + +```js +const mkdirp = require('mkdirp') + +// return value is the first directory created +const made = mkdirp.sync('/tmp/foo/bar/baz') +console.log(`made directories, starting with ${made}`) +``` + +And now /tmp/foo/bar/baz exists, huzzah! + +# methods + +```js +const mkdirp = require('mkdirp') +``` + +## mkdirp(dir, [opts]) -> Promise + +Create a new directory and any necessary subdirectories at `dir` with octal +permission string `opts.mode`. If `opts` is a string or number, it will be +treated as the `opts.mode`. + +If `opts.mode` isn't specified, it defaults to `0o777 & +(~process.umask())`. + +Promise resolves to first directory `made` that had to be created, or +`undefined` if everything already exists. Promise rejects if any errors +are encountered. Note that, in the case of promise rejection, some +directories _may_ have been created, as recursive directory creation is not +an atomic operation. + +You can optionally pass in an alternate `fs` implementation by passing in +`opts.fs`. Your implementation should have `opts.fs.mkdir(path, opts, cb)` +and `opts.fs.stat(path, cb)`. + +You can also override just one or the other of `mkdir` and `stat` by +passing in `opts.stat` or `opts.mkdir`, or providing an `fs` option that +only overrides one of these. + +## mkdirp.sync(dir, opts) -> String|null + +Synchronously create a new directory and any necessary subdirectories at +`dir` with octal permission string `opts.mode`. If `opts` is a string or +number, it will be treated as the `opts.mode`. + +If `opts.mode` isn't specified, it defaults to `0o777 & +(~process.umask())`. + +Returns the first directory that had to be created, or undefined if +everything already exists. + +You can optionally pass in an alternate `fs` implementation by passing in +`opts.fs`. Your implementation should have `opts.fs.mkdirSync(path, mode)` +and `opts.fs.statSync(path)`. + +You can also override just one or the other of `mkdirSync` and `statSync` +by passing in `opts.statSync` or `opts.mkdirSync`, or providing an `fs` +option that only overrides one of these. + +## mkdirp.manual, mkdirp.manualSync + +Use the manual implementation (not the native one). This is the default +when the native implementation is not available or the stat/mkdir +implementation is overridden. + +## mkdirp.native, mkdirp.nativeSync + +Use the native implementation (not the manual one). This is the default +when the native implementation is available and stat/mkdir are not +overridden. + +# implementation + +On Node.js v10.12.0 and above, use the native `fs.mkdir(p, +{recursive:true})` option, unless `fs.mkdir`/`fs.mkdirSync` has been +overridden by an option. + +## native implementation + +- If the path is a root directory, then pass it to the underlying + implementation and return the result/error. (In this case, it'll either + succeed or fail, but we aren't actually creating any dirs.) +- Walk up the path statting each directory, to find the first path that + will be created, `made`. +- Call `fs.mkdir(path, { recursive: true })` (or `fs.mkdirSync`) +- If error, raise it to the caller. +- Return `made`. + +## manual implementation + +- Call underlying `fs.mkdir` implementation, with `recursive: false` +- If error: + - If path is a root directory, raise to the caller and do not handle it + - If ENOENT, mkdirp parent dir, store result as `made` + - stat(path) + - If error, raise original `mkdir` error + - If directory, return `made` + - Else, raise original `mkdir` error +- else + - return `undefined` if a root dir, or `made` if set, or `path` + +## windows vs unix caveat + +On Windows file systems, attempts to create a root directory (ie, a drive +letter or root UNC path) will fail. If the root directory exists, then it +will fail with `EPERM`. If the root directory does not exist, then it will +fail with `ENOENT`. + +On posix file systems, attempts to create a root directory (in recursive +mode) will succeed silently, as it is treated like just another directory +that already exists. (In non-recursive mode, of course, it fails with +`EEXIST`.) + +In order to preserve this system-specific behavior (and because it's not as +if we can create the parent of a root directory anyway), attempts to create +a root directory are passed directly to the `fs` implementation, and any +errors encountered are not handled. + +## native error caveat + +The native implementation (as of at least Node.js v13.4.0) does not provide +appropriate errors in some cases (see +[nodejs/node#31481](https://github.com/nodejs/node/issues/31481) and +[nodejs/node#28015](https://github.com/nodejs/node/issues/28015)). + +In order to work around this issue, the native implementation will fall +back to the manual implementation if an `ENOENT` error is encountered. + +# choosing a recursive mkdir implementation + +There are a few to choose from! Use the one that suits your needs best :D + +## use `fs.mkdir(path, {recursive: true}, cb)` if: + +- You wish to optimize performance even at the expense of other factors. +- You don't need to know the first dir created. +- You are ok with getting `ENOENT` as the error when some other problem is + the actual cause. +- You can limit your platforms to Node.js v10.12 and above. +- You're ok with using callbacks instead of promises. +- You don't need/want a CLI. +- You don't need to override the `fs` methods in use. + +## use this module (mkdirp 1.x) if: + +- You need to know the first directory that was created. +- You wish to use the native implementation if available, but fall back + when it's not. +- You prefer promise-returning APIs to callback-taking APIs. +- You want more useful error messages than the native recursive mkdir + provides (at least as of Node.js v13.4), and are ok with re-trying on + `ENOENT` to achieve this. +- You need (or at least, are ok with) a CLI. +- You need to override the `fs` methods in use. + +## use [`make-dir`](http://npm.im/make-dir) if: + +- You do not need to know the first dir created (and wish to save a few + `stat` calls when using the native implementation for this reason). +- You wish to use the native implementation if available, but fall back + when it's not. +- You prefer promise-returning APIs to callback-taking APIs. +- You are ok with occasionally getting `ENOENT` errors for failures that + are actually related to something other than a missing file system entry. +- You don't need/want a CLI. +- You need to override the `fs` methods in use. + +## use mkdirp 0.x if: + +- You need to know the first directory that was created. +- You need (or at least, are ok with) a CLI. +- You need to override the `fs` methods in use. +- You're ok with using callbacks instead of promises. +- You are not running on Windows, where the root-level ENOENT errors can + lead to infinite regress. +- You think vinyl just sounds warmer and richer for some weird reason. +- You are supporting truly ancient Node.js versions, before even the advent + of a `Promise` language primitive. (Please don't. You deserve better.) + +# cli + +This package also ships with a `mkdirp` command. + +``` +$ mkdirp -h + +usage: mkdirp [DIR1,DIR2..] {OPTIONS} + + Create each supplied directory including any necessary parent directories + that don't yet exist. + + If the directory already exists, do nothing. + +OPTIONS are: + + -m If a directory needs to be created, set the mode as an octal + --mode= permission string. + + -v --version Print the mkdirp version number + + -h --help Print this helpful banner + + -p --print Print the first directories created for each path provided + + --manual Use manual implementation, even if native is available +``` + +# install + +With [npm](http://npmjs.org) do: + +``` +npm install mkdirp +``` + +to get the library locally, or + +``` +npm install -g mkdirp +``` + +to get the command everywhere, or + +``` +npx mkdirp ... +``` + +to run the command without installing it globally. + +# platform support + +This module works on node v8, but only v10 and above are officially +supported, as Node v8 reached its LTS end of life 2020-01-01, which is in +the past, as of this writing. + +# license + +MIT diff --git a/node_modules/lv_font_conv/node_modules/ms/index.js b/node_modules/lv_font_conv/node_modules/ms/index.js new file mode 100644 index 00000000..c4498bcc --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/ms/index.js @@ -0,0 +1,162 @@ +/** + * Helpers. + */ + +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; +var w = d * 7; +var y = d * 365.25; + +/** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} [options] + * @throws {Error} throw an error if val is not a non-empty string or a number + * @return {String|Number} + * @api public + */ + +module.exports = function(val, options) { + options = options || {}; + var type = typeof val; + if (type === 'string' && val.length > 0) { + return parse(val); + } else if (type === 'number' && isFinite(val)) { + return options.long ? fmtLong(val) : fmtShort(val); + } + throw new Error( + 'val is not a non-empty string or a valid number. val=' + + JSON.stringify(val) + ); +}; + +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + str = String(str); + if (str.length > 100) { + return; + } + var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec( + str + ); + if (!match) { + return; + } + var n = parseFloat(match[1]); + var type = (match[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'yrs': + case 'yr': + case 'y': + return n * y; + case 'weeks': + case 'week': + case 'w': + return n * w; + case 'days': + case 'day': + case 'd': + return n * d; + case 'hours': + case 'hour': + case 'hrs': + case 'hr': + case 'h': + return n * h; + case 'minutes': + case 'minute': + case 'mins': + case 'min': + case 'm': + return n * m; + case 'seconds': + case 'second': + case 'secs': + case 'sec': + case 's': + return n * s; + case 'milliseconds': + case 'millisecond': + case 'msecs': + case 'msec': + case 'ms': + return n; + default: + return undefined; + } +} + +/** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtShort(ms) { + var msAbs = Math.abs(ms); + if (msAbs >= d) { + return Math.round(ms / d) + 'd'; + } + if (msAbs >= h) { + return Math.round(ms / h) + 'h'; + } + if (msAbs >= m) { + return Math.round(ms / m) + 'm'; + } + if (msAbs >= s) { + return Math.round(ms / s) + 's'; + } + return ms + 'ms'; +} + +/** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtLong(ms) { + var msAbs = Math.abs(ms); + if (msAbs >= d) { + return plural(ms, msAbs, d, 'day'); + } + if (msAbs >= h) { + return plural(ms, msAbs, h, 'hour'); + } + if (msAbs >= m) { + return plural(ms, msAbs, m, 'minute'); + } + if (msAbs >= s) { + return plural(ms, msAbs, s, 'second'); + } + return ms + ' ms'; +} + +/** + * Pluralization helper. + */ + +function plural(ms, msAbs, n, name) { + var isPlural = msAbs >= n * 1.5; + return Math.round(ms / n) + ' ' + name + (isPlural ? 's' : ''); +} diff --git a/node_modules/lv_font_conv/node_modules/ms/license.md b/node_modules/lv_font_conv/node_modules/ms/license.md new file mode 100644 index 00000000..69b61253 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/ms/license.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Zeit, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/ms/package.json b/node_modules/lv_font_conv/node_modules/ms/package.json new file mode 100644 index 00000000..6d514e71 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/ms/package.json @@ -0,0 +1,72 @@ +{ + "_args": [ + [ + "ms@2.1.2", + "/home/vitaly/Dropbox/Coding/lv_font_conv" + ] + ], + "_from": "ms@2.1.2", + "_id": "ms@2.1.2", + "_inBundle": false, + "_integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "_location": "/ms", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "ms@2.1.2", + "name": "ms", + "escapedName": "ms", + "rawSpec": "2.1.2", + "saveSpec": null, + "fetchSpec": "2.1.2" + }, + "_requiredBy": [ + "/debug" + ], + "_resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "_spec": "2.1.2", + "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", + "bugs": { + "url": "https://github.com/zeit/ms/issues" + }, + "description": "Tiny millisecond conversion utility", + "devDependencies": { + "eslint": "4.12.1", + "expect.js": "0.3.1", + "husky": "0.14.3", + "lint-staged": "5.0.0", + "mocha": "4.0.1" + }, + "eslintConfig": { + "extends": "eslint:recommended", + "env": { + "node": true, + "es6": true + } + }, + "files": [ + "index.js" + ], + "homepage": "https://github.com/zeit/ms#readme", + "license": "MIT", + "lint-staged": { + "*.js": [ + "npm run lint", + "prettier --single-quote --write", + "git add" + ] + }, + "main": "./index", + "name": "ms", + "repository": { + "type": "git", + "url": "git+https://github.com/zeit/ms.git" + }, + "scripts": { + "lint": "eslint lib/* bin/*", + "precommit": "lint-staged", + "test": "mocha tests.js" + }, + "version": "2.1.2" +} diff --git a/node_modules/lv_font_conv/node_modules/ms/readme.md b/node_modules/lv_font_conv/node_modules/ms/readme.md new file mode 100644 index 00000000..9a1996b1 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/ms/readme.md @@ -0,0 +1,60 @@ +# ms + +[![Build Status](https://travis-ci.org/zeit/ms.svg?branch=master)](https://travis-ci.org/zeit/ms) +[![Join the community on Spectrum](https://withspectrum.github.io/badge/badge.svg)](https://spectrum.chat/zeit) + +Use this package to easily convert various time formats to milliseconds. + +## Examples + +```js +ms('2 days') // 172800000 +ms('1d') // 86400000 +ms('10h') // 36000000 +ms('2.5 hrs') // 9000000 +ms('2h') // 7200000 +ms('1m') // 60000 +ms('5s') // 5000 +ms('1y') // 31557600000 +ms('100') // 100 +ms('-3 days') // -259200000 +ms('-1h') // -3600000 +ms('-200') // -200 +``` + +### Convert from Milliseconds + +```js +ms(60000) // "1m" +ms(2 * 60000) // "2m" +ms(-3 * 60000) // "-3m" +ms(ms('10 hours')) // "10h" +``` + +### Time Format Written-Out + +```js +ms(60000, { long: true }) // "1 minute" +ms(2 * 60000, { long: true }) // "2 minutes" +ms(-3 * 60000, { long: true }) // "-3 minutes" +ms(ms('10 hours'), { long: true }) // "10 hours" +``` + +## Features + +- Works both in [Node.js](https://nodejs.org) and in the browser +- If a number is supplied to `ms`, a string with a unit is returned +- If a string that contains the number is supplied, it returns it as a number (e.g.: it returns `100` for `'100'`) +- If you pass a string with a number and a valid unit, the number of equivalent milliseconds is returned + +## Related Packages + +- [ms.macro](https://github.com/knpwrs/ms.macro) - Run `ms` as a macro at build-time. + +## Caught a Bug? + +1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device +2. Link the package to the global module directory: `npm link` +3. Within the module you want to test your local development instance of ms, just link it to the dependencies: `npm link ms`. Instead of the default one from npm, Node.js will now use your clone of ms! + +As always, you can run the tests using: `npm test` diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/LICENSE b/node_modules/lv_font_conv/node_modules/opentype.js/LICENSE new file mode 100644 index 00000000..c9b39953 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/opentype.js/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2017 Frederik De Bleser + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/README.md b/node_modules/lv_font_conv/node_modules/opentype.js/README.md new file mode 100644 index 00000000..bcb557e8 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/opentype.js/README.md @@ -0,0 +1,313 @@ + +# opentype.js · [![Build Status](https://img.shields.io/travis/opentypejs/opentype.js.svg?style=flat-square)](https://travis-ci.org/opentypejs/opentype.js) [![npm](https://img.shields.io/npm/v/opentype.js.svg?style=flat-square)](https://www.npmjs.com/package/opentype.js) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://github.com/opentypejs/opentype.js/blob/master/LICENSE) [![david-dm](https://david-dm.org/opentypejs/opentype.js.svg)](https://david-dm.org/opentypejs/opentype.js) [![Gitter](https://badges.gitter.im/opentypejs/opentype.js.svg)](https://gitter.im/opentypejs/opentype.js) + +opentype.js is a JavaScript parser and writer for TrueType and OpenType fonts. + +It gives you access to the letterforms of text from the browser or Node.js. +See [https://opentype.js.org/](https://opentype.js.org/) for a live demo. + +Features +======== + +* Create a bézier path out of a piece of text. +* Support for composite glyphs (accented letters). +* Support for WOFF, OTF, TTF (both with TrueType `glyf` and PostScript `cff` outlines) +* Support for kerning (Using GPOS or the kern table). +* Support for ligatures. +* Support for TrueType font hinting. +* Support arabic text rendering (See issue #364 & PR #359 #361) +* A low memory mode is available as an option (see #329) +* Runs in the browser and Node.js. + +Installation +============ + +### Using [npm](http://npmjs.org/) package manager + + npm install opentype.js + + const opentype = require('opentype.js'); + + import opentype from 'opentype.js' + + import { load } from 'opentype.js' + +Using TypeScript? [See this example](examples/typescript) + +Note: OpenType.js uses ES6-style imports, so if you want to edit it and debug it in Node.js run `npm run build` first and use `npm run watch` to automatically rebuild when files change. + +### Directly + +[Download the latest ZIP](https://github.com/opentypejs/opentype.js/archive/master.zip) and grab the files in the `dist` +folder. These are compiled. + +### Using via a CDN + +To use via a CDN, include the following code in your html: + + + +### Using Bower (Deprecated [see official post](https://bower.io/blog/2017/how-to-migrate-away-from-bower/)) + +To install using [Bower](https://bower.io/), enter the following command in your project directory: + + bower install opentype.js + +You can then include them in your scripts using: + + + + +API +=== +### Loading a font +![OpenType.js example Hello World](https://raw.github.com/opentypejs/opentype.js/master/g/hello-world.png) + +Use `opentype.load(url, callback)` to load a font from a URL. Since this method goes out the network, it is asynchronous. +The callback gets `(err, font)` where `font` is a `Font` object. Check if the `err` is null before using the font. +```javascript +opentype.load('fonts/Roboto-Black.ttf', function(err, font) { + if (err) { + alert('Font could not be loaded: ' + err); + } else { + // Now let's display it on a canvas with id "canvas" + const ctx = document.getElementById('canvas').getContext('2d'); + + // Construct a Path object containing the letter shapes of the given text. + // The other parameters are x, y and fontSize. + // Note that y is the position of the baseline. + const path = font.getPath('Hello, World!', 0, 150, 72); + + // If you just want to draw the text you can also use font.draw(ctx, text, x, y, fontSize). + path.draw(ctx); + } +}); +``` + +You can also use `es6 async/await` syntax to load your fonts + +```javascript +async function make(){ + const font = await opentype.load('fonts/Roboto-Black.ttf'); + const path = font.getPath('Hello, World!', 0, 150, 72); + console.log(path); +} +``` + +If you already have an `ArrayBuffer`, you can use `opentype.parse(buffer)` to parse the buffer. This method always +returns a Font, but check `font.supported` to see if the font is in a supported format. (Fonts can be marked unsupported +if they have encoding tables we can't read). + + const font = opentype.parse(myBuffer); + +### Loading a font synchronously (Node.js) +Use `opentype.loadSync(url)` to load a font from a file and return a `Font` object. +Throws an error if the font could not be parsed. This only works in Node.js. + + const font = opentype.loadSync('fonts/Roboto-Black.ttf'); + +### Writing a font +Once you have a `Font` object (either by using `opentype.load` or by creating a new one from scratch) you can write it +back out as a binary file. + +In the browser, you can use `Font.download()` to instruct the browser to download a binary .OTF file. The name is based +on the font name. +```javascript +// Create the bézier paths for each of the glyphs. +// Note that the .notdef glyph is required. +const notdefGlyph = new opentype.Glyph({ + name: '.notdef', + unicode: 0, + advanceWidth: 650, + path: new opentype.Path() +}); + +const aPath = new opentype.Path(); +aPath.moveTo(100, 0); +aPath.lineTo(100, 700); +// more drawing instructions... +const aGlyph = new opentype.Glyph({ + name: 'A', + unicode: 65, + advanceWidth: 650, + path: aPath +}); + +const glyphs = [notdefGlyph, aGlyph]; +const font = new opentype.Font({ + familyName: 'OpenTypeSans', + styleName: 'Medium', + unitsPerEm: 1000, + ascender: 800, + descender: -200, + glyphs: glyphs}); +font.download(); +``` + +If you want to inspect the font, use `font.toTables()` to generate an object showing the data structures that map +directly to binary values. If you want to get an `ArrayBuffer`, use `font.toArrayBuffer()`. + + +### The Font object +A Font represents a loaded OpenType font file. It contains a set of glyphs and methods to draw text on a drawing context, or to get a path representing the text. + +* `glyphs`: an indexed list of Glyph objects. +* `unitsPerEm`: X/Y coordinates in fonts are stored as integers. This value determines the size of the grid. Common values are 2048 and 4096. +* `ascender`: Distance from baseline of highest ascender. In font units, not pixels. +* `descender`: Distance from baseline of lowest descender. In font units, not pixels. + +#### `Font.getPath(text, x, y, fontSize, options)` +Create a Path that represents the given text. +* `x`: Horizontal position of the beginning of the text. (default: 0) +* `y`: Vertical position of the *baseline* of the text. (default: 0) +* `fontSize`: Size of the text in pixels (default: 72). + +Options is an optional object containing: +* `kerning`: if true takes kerning information into account (default: true) +* `features`: an object with [OpenType feature tags](https://docs.microsoft.com/en-us/typography/opentype/spec/featuretags) as keys, and a boolean value to enable each feature. +Currently only ligature features "liga" and "rlig" are supported (default: true). +* `hinting`: if true uses TrueType font hinting if available (default: false). + +_Note: there is also `Font.getPaths` with the same arguments which returns a list of Paths._ + +#### `Font.draw(ctx, text, x, y, fontSize, options)` +Create a Path that represents the given text. +* `ctx`: A 2D drawing context, like Canvas. +* `x`: Horizontal position of the beginning of the text. (default: 0) +* `y`: Vertical position of the *baseline* of the text. (default: 0) +* `fontSize`: Size of the text in pixels (default: 72). + +Options is an optional object containing: +* `kerning`: if true takes kerning information into account (default: true) +* `features`: an object with [OpenType feature tags](https://docs.microsoft.com/en-us/typography/opentype/spec/featuretags) as keys, and a boolean value to enable each feature. +Currently only ligature features "liga" and "rlig" are supported (default: true). +* `hinting`: if true uses TrueType font hinting if available (default: false). + +#### `Font.drawPoints(ctx, text, x, y, fontSize, options)` +Draw the points of all glyphs in the text. On-curve points will be drawn in blue, off-curve points will be drawn in red. The arguments are the same as `Font.draw`. + +#### `Font.drawMetrics(ctx, text, x, y, fontSize, options)` +Draw lines indicating important font measurements for all glyphs in the text. +Black lines indicate the origin of the coordinate system (point 0,0). +Blue lines indicate the glyph bounding box. +Green line indicates the advance width of the glyph. + +#### `Font.stringToGlyphs(string)` +Convert the string to a list of glyph objects. +Note that there is no strict 1-to-1 correspondence between the string and glyph list due to +possible substitutions such as ligatures. The list of returned glyphs can be larger or smaller than the length of the given string. + +#### `Font.charToGlyph(char)` +Convert the character to a `Glyph` object. Returns null if the glyph could not be found. Note that this function assumes that there is a one-to-one mapping between the given character and a glyph; for complex scripts this might not be the case. + +#### `Font.getKerningValue(leftGlyph, rightGlyph)` +Retrieve the value of the [kerning pair](https://en.wikipedia.org/wiki/Kerning) between the left glyph (or its index) and the right glyph (or its index). If no kerning pair is found, return 0. The kerning value gets added to the advance width when calculating the spacing between glyphs. + +#### `Font.getAdvanceWidth(text, fontSize, options)` +Returns the advance width of a text. + +This is something different than Path.getBoundingBox() as for example a +suffixed whitespace increases the advancewidth but not the bounding box +or an overhanging letter like a calligraphic 'f' might have a quite larger +bounding box than its advance width. + +This corresponds to canvas2dContext.measureText(text).width +* `fontSize`: Size of the text in pixels (default: 72). +* `options`: See Font.getPath + +#### The Glyph object +A Glyph is an individual mark that often corresponds to a character. Some glyphs, such as ligatures, are a combination of many characters. Glyphs are the basic building blocks of a font. + +* `font`: A reference to the `Font` object. +* `name`: The glyph name (e.g. "Aring", "five") +* `unicode`: The primary unicode value of this glyph (can be `undefined`). +* `unicodes`: The list of unicode values for this glyph (most of the time this will be 1, can also be empty). +* `index`: The index number of the glyph. +* `advanceWidth`: The width to advance the pen when drawing this glyph. +* `xMin`, `yMin`, `xMax`, `yMax`: The bounding box of the glyph. +* `path`: The raw, unscaled path of the glyph. + +##### `Glyph.getPath(x, y, fontSize)` +Get a scaled glyph Path object we can draw on a drawing context. +* `x`: Horizontal position of the glyph. (default: 0) +* `y`: Vertical position of the *baseline* of the glyph. (default: 0) +* `fontSize`: Font size in pixels (default: 72). + +##### `Glyph.getBoundingBox()` +Calculate the minimum bounding box for the unscaled path of the given glyph. Returns an `opentype.BoundingBox` object that contains x1/y1/x2/y2. +If the glyph has no points (e.g. a space character), all coordinates will be zero. + +##### `Glyph.draw(ctx, x, y, fontSize)` +Draw the glyph on the given context. +* `ctx`: The drawing context. +* `x`: Horizontal position of the glyph. (default: 0) +* `y`: Vertical position of the *baseline* of the glyph. (default: 0) +* `fontSize`: Font size, in pixels (default: 72). + +##### `Glyph.drawPoints(ctx, x, y, fontSize)` +Draw the points of the glyph on the given context. +On-curve points will be drawn in blue, off-curve points will be drawn in red. +The arguments are the same as `Glyph.draw`. + +##### `Glyph.drawMetrics(ctx, x, y, fontSize)` +Draw lines indicating important font measurements for all glyphs in the text. +Black lines indicate the origin of the coordinate system (point 0,0). +Blue lines indicate the glyph bounding box. +Green line indicates the advance width of the glyph. +The arguments are the same as `Glyph.draw`. + +### The Path object +Once you have a path through `Font.getPath` or `Glyph.getPath`, you can use it. + +* `commands`: The path commands. Each command is a dictionary containing a type and coordinates. See below for examples. +* `fill`: The fill color of the `Path`. Color is a string representing a [CSS color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value). (default: 'black') +* `stroke`: The stroke color of the `Path`. Color is a string representing a [CSS color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value). (default: `null`: the path will not be stroked) +* `strokeWidth`: The line thickness of the `Path`. (default: 1, but since the `stroke` is null no stroke will be drawn) + +##### `Path.draw(ctx)` +Draw the path on the given 2D context. This uses the `fill`, `stroke` and `strokeWidth` properties of the `Path` object. +* `ctx`: The drawing context. + +##### `Path.getBoundingBox()` +Calculate the minimum bounding box for the given path. Returns an `opentype.BoundingBox` object that contains x1/y1/x2/y2. +If the path is empty (e.g. a space character), all coordinates will be zero. + +##### `Path.toPathData(decimalPlaces)` +Convert the Path to a string of path data instructions. +See https://www.w3.org/TR/SVG/paths.html#PathData +* `decimalPlaces`: The amount of decimal places for floating-point values. (default: 2) + +##### `Path.toSVG(decimalPlaces)` +Convert the path to a SVG <path> element, as a string. +* `decimalPlaces`: The amount of decimal places for floating-point values. (default: 2) + +#### Path commands +* **Move To**: Move to a new position. This creates a new contour. Example: `{type: 'M', x: 100, y: 200}` +* **Line To**: Draw a line from the previous position to the given coordinate. Example: `{type: 'L', x: 100, y: 200}` +* **Curve To**: Draw a bézier curve from the current position to the given coordinate. Example: `{type: 'C', x1: 0, y1: 50, x2: 100, y2: 200, x: 100, y: 200}` +* **Quad To**: Draw a quadratic bézier curve from the current position to the given coordinate. Example: `{type: 'Q', x1: 0, y1: 50, x: 100, y: 200}` +* **Close**: Close the path. If stroked, this will draw a line from the first to the last point of the contour. Example: `{type: 'Z'}` + + +## Versioning + +We use [SemVer](https://semver.org/) for versioning. + + +## License + +MIT + + +Thanks +====== +I would like to acknowledge the work of others without which opentype.js wouldn't be possible: + +* [pdf.js](https://mozilla.github.io/pdf.js/): for an awesome implementation of font parsing in the browser. +* [FreeType](https://www.freetype.org/): for the nitty-gritty details and filling in the gaps when the spec was incomplete. +* [ttf.js](https://ynakajima.github.io/ttf.js/demo/glyflist/): for hints about the TrueType parsing code. +* [CFF-glyphlet-fonts](https://pomax.github.io/CFF-glyphlet-fonts/): for a great explanation/implementation of CFF font writing. +* [tiny-inflate](https://github.com/foliojs/tiny-inflate): for WOFF decompression. +* [Microsoft Typography](https://docs.microsoft.com/en-us/typography/opentype/spec/otff): the go-to reference for all things OpenType. +* [Adobe Compact Font Format spec](http://download.microsoft.com/download/8/0/1/801a191c-029d-4af3-9642-555f6fe514ee/cff.pdf) and the [Adobe Type 2 Charstring spec](http://download.microsoft.com/download/8/0/1/801a191c-029d-4af3-9642-555f6fe514ee/type2.pdf): explains the data structures and commands for the CFF glyph format. +* All contributing authors mentioned in the [AUTHORS](https://github.com/opentypejs/opentype.js/blob/master/AUTHORS.md) file. diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/RELEASES.md b/node_modules/lv_font_conv/node_modules/opentype.js/RELEASES.md new file mode 100644 index 00000000..e1c698b1 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/opentype.js/RELEASES.md @@ -0,0 +1,267 @@ +1.3.3 (April 20, 2020) +===================== +* fix GlyphOptions with falsy values (#430) + +1.3.2 (April 20, 2020) +===================== +* Re-export named exports with a default export and add a TypeScript import example + +* 1.3.1 (April 13, 2020) +===================== +* Revert Fix Path.toPathData and Path.toSVG - X Axis is flipped (#369) + +1.3.0 (April 13, 2020) +===================== +* Forward os2 Table attributs during font construction (#422) +* Add default export + +1.2.1 (April 13, 2020) +===================== +* Fix Path.toPathData and Path.toSVG - X Axis is flipped (#369) +* Fix use of Promise / async/await in the load function (#427) +* Fix a bug for unsupported SUBSTITUTIONS #403 + +1.2.0 (April 13, 2020) +===================== +* Fix issue #385, merge default options with user options (#386) +* Adds support for browser Async/Await for .load() (#389) +* Introduce ES6 module build (#391) +* Fix test in featureQuery +* Remove Node 4 from Travis (#392) +* Update dependencies & build dist files + +1.1.0 (May 1, 2019) +===================== +* Support reading GSUB Single substitution format 1 (PR #382) (thanks @solomancode!) + +1.0.1 (April 19, 2019) +===================== +* Fix error if defaultLangSys is undefined (Issue #378) + +1.0.0 (April 17, 2019) +===================== +* Render arabic rtl text properly (PR #361, partial fix of #364) (thanks @solomancode!) +* #361 introduced a breaking change to `Font.prototype.defaultRenderOptions` +Before +```js +Font.prototype.defaultRenderOptions = { + kerning: true, + features: { + liga: true, + rlig: true + } +}; +``` + +Now +```js +Font.prototype.defaultRenderOptions = { + kerning: true, + features: [ + /** + * these 4 features are required to render Arabic text properly + * and shouldn't be turned off when rendering arabic text. + */ + { script: 'arab', tags: ['init', 'medi', 'fina', 'rlig'] }, + { script: 'latn', tags: ['liga', 'rlig'] } + ] +}; +``` + +Also as this project is now using SemVer, the breaking change required a new major version, 1.0.0! + +0.12.0 (April 17, 2019) +===================== +* Fix Glyph.getPath() issue (PR #362, fixes #363) (thanks @solomancode!) +* Add lowMemory mode (PR #329) (thanks @debussy2k!) +* Update README (PR #377) (thanks @jolg42!) + +0.11.0 (October 22, 2018) +===================== +* Support Arabic text rendering (PR #359, fixes #46) (thanks @solomancode!) + +0.10.0 (August 14, 2018) +===================== +* font.download(): use window.URL instead of window.requestFileSystem, which works on a larger set of browsers : Chrome (32+), Opera (19+), Firefox (26+), Safari (7.1+), and all of Edge. + +0.9.0 (June 21, 2018) +===================== +* Update/Migrate rollup, update all dependencies, add package-lock.json and fix circular dependency (thanks @jolg42!) +* Parse cmap table with platform id 0 as well (PR #350, fixes #348) (thanks @moyogo!) +* Prevent auto-generated postScriptName from containing whitespace (#339) (thanks @mqudsi!) +* Support non-Basic-Multilingual-Plane (BMP) characters (#338) (thanks @antonytse!) +* GPOS: display correct error message in some cases of malformed data (#336) (thanks @fpirsch!) +* Restore simple GPOS kerning in font.getKerningValue (#335) (thanks @fpirsch!) +* Fix duplicated lineTo when using `getPath` (#328) (thanks @jolg42!) +* Change example generate-font-node.js to be compatible with any Node.js version (thanks @jolg42!) + +0.8.0 (March 6, 2018) +===================== +* Fix loading font file on Android devices (thanks @maoamid!). +* Fix loading fonts from a local source (file://data/... for Android for example (thanks @IntuilabGit!). +* Fixing 2 issues when hinting "mutlu.ttf" (thanks @axkibe!). +* Add some support for OpenType font variations (thanks @taylorb-monotype!). +* Make cmap table format 12 if needed (thanks @Jolg42!). +* Enable uglify's mangle and compress optimizations for a ~30% smaller minified file. (thanks @lojjic & @Jolg42!). +* Better parsing of NULL pointers (thanks @fpirsch!). +* Fix bad path init (empty glyphs) (thanks @fpirsch!). +* Rewrite GPOS parsing (thanks @fpirsch!). +* Roboto-Black.ttf updated (thanks @Jolg42!). + +0.7.3 (July 18, 2017) +===================== +* Fix "Object x already has key" error in Safari (thanks @neiltron!). +* Fixed a bug where Font.getPaths() didn't pass options (thanks @keeslinp!). + +0.7.2 (June 7, 2017) +==================== +* WOFF fonts with cvt tables now parse correctly. +* Migrated to ES6 modules and let/const. +* Use Rollup to bundle the JavaScript. + +0.7.1 (Apr 25, 2017) +==================== +* Auto-generated glyph IDs (CID-keyed fonts) are now prefixed with "gid", e.g. "gid42". +* Fix ligature substitution for fonts with coverage table format 2. +* Better error messages when no valid cmap is found. + +0.7.0 (Apr 25, 2017) +==================== +* Add font hinting (thanks @axkibe!) +* Add support for CID-keyed fonts, thanks to @tshinnic. +* TrueType fonts with signature 'true' or 'typ1' are also supported. +* Fixing rounding issues. +* Add GSUB and kern output in font-inspector. +* Add font loading error callback. +* Dev server turns browser caching off. +* Add encoding support for variation adjustment deltas (thanks @brawer!). + +0.6.9 (Jan 17, 2017) +==================== +* Add ligature rendering (thanks @fpirsch!) + +0.6.8 (Jan 9, 2017) +========================= +* Add a `getBoundingBox` method to the `Path` and `Glyph` objects. + +0.6.7 (Jan 5, 2017) +========================= +* Add basic support for Mac OS X format kern tables. + +0.6.6 (October 25, 2016) +========================= +* Add support for letter-spacing and tracking (thanks @lachmanski!). +* Fixed a bug in the nameToGlyph function. + +0.6.5 (September 9, 2016) +========================= +* GSUB reading and writing by @fpirsch. This is still missing a user-friendly API. +* Add support for cmap table format 12, which enables support for Unicode characters outside of the 0x0 - 0xFFFF range. +* Better API documentation using [JSDoc](http://usejsdoc.org/). +* Accessing xMin/... metrics works before path load.
 + +0.6.4 (June 30, 2016) +========================= +* Add X/Y scale options to compute a streched path of a glyph. +* Correct reading/writing of font timestamps. +* examples/generate-font-node.js now generates "full" Latin font. +* Add OS/2 value options for weight, width and fsSelection. + +0.6.3 (May 10, 2016) +========================= +* Wrapped parseBuffer in a try/catch so it doesn't throw exceptions. Thanks @rBurgett! +* Fix a leaking global variable. Thanks @cuixiping! + +0.6.2 (March 11, 2016) +========================= +* Improve table writing to support nested subtables. Thanks @fpirsch! + +0.6.1 (February 20, 2016) +========================= +* Left side bearing is now correctly reported. +* Simplified code for including ascender / descender values. + +0.6.0 (December 1, 2015) +======================== +* Improvements to font writing: generated fonts now work properly on OS X. +* When creating a new font, ascender and descender are now required. + +0.5.1 (October 26, 2015) +======================== +* Add `Font.getPaths()` which returns a list of paths. + +0.5.0 (October 6, 2015) +======================= +* Read support for WOFF. + +0.4.11 (September 27, 2015) +=========================== +* Fix issue with loading of TrueType composite glyphs. +* Fix issue with missing hmtx values. +* Sensible getMetrics() values for empty glyphs (e.g. space). + +0.4.10 (July 30, 2015) +====================== +* Add loadSync method for Node.js. +* Unit tests for basic types and tables. +* Implement MACSTRING codec. +* Support multilingual names. +* Handle names of font variation axes and instances. + +0.4.9 (June 23, 2015) +===================== +* Improve memory usage by deferring glyph / path loading. Thanks @Pomax! +* Put examples in the "examples" directory. Use the local web server to see them. + +0.4.8 (June 3, 2015) +==================== +* Fix an issue with writing out fonts that have an UPM != 1000. + +0.4.6 (March 26, 2015) +====================== +* Fix issues with exporting/subsetting TrueType fonts. +* Improve validness of exported fonts. +* Empty paths (think: space) no longer contain a single closePath command. +* Fix issues with exporting fonts with TrueType half-point values. +* Expose the internal byte parsing algorithms as opentype._parse. + +0.4.5 (March 10, 2015) +====================== +* Add support for writing quad curves. +* Add support for CFF flex operators. +* Close CFF subpaths. + +0.4.4 (Dec 8, 2014) +=================== +* Solve issues with Browserify. + +0.4.3 (Nov 26, 2014) +==================== +* Un-break node.js support. + +0.4.2 (Nov 24, 2014) +==================== +* 2x speedup when writing fonts, thanks @louisremi! + +0.4.1 (Nov 10, 2014) +==================== +* Fix bug that prevented `npm install`. + +0.4.0 (Nov 10, 2014) +==================== +* Add support for font writing. + +0.3.0 (Jun 10, 2014) +==================== +* Support for GPOS kerning, which works in both PostScript and OpenType. +* Big performance improvements. +* The font and glyph inspector can visually debug a font. + +0.2.0 (Feb 7, 2014) +=================== +* Support for reading PostScript fonts. + +0.1.0 (Sep 27, 2013) +==================== +* Initial release. +* Supports reading TrueType CFF fonts. diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/bin/ot b/node_modules/lv_font_conv/node_modules/opentype.js/bin/ot new file mode 100755 index 00000000..af990cd2 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/opentype.js/bin/ot @@ -0,0 +1,84 @@ +#!/usr/bin/env node +/* eslint no-console: off */ + +import fs from 'fs'; +import path from 'path'; +import { load } from '../src/opentype'; + +// Print out information about the font on the console. +function printFontInfo(font) { + console.log(' glyphs:', font.glyphs.length); + console.log(' kerning pairs (kern table):', Object.keys(font.kerningPairs).length); + console.log(' kerning pairs (GPOS table):', (font.getGposKerningValue) ? 'yes' : 'no'); +} + +// Recursively walk a directory and execute the function for every file. +function walk(dir, fn) { + var files, i, file; + files = fs.readdirSync(dir); + for (i = 0; i < files.length; i += 1) { + file = files[i]; + var fullName = path.join(dir, file); + var stat = fs.statSync(fullName); + if (stat.isFile()) { + fn(fullName); + } else if (stat.isDirectory()) { + walk(fullName, fn); + } + } +} + +// Print out usage information. +function printUsage() { + console.log('Usage: ot command [dir|file]'); + console.log(); + console.log('Commands:'); + console.log(); + console.log(' info Get information of specified font or fonts in the specified directory.'); + console.log(); +} + +function fileInfo(file) { + load(file, function(err, font) { + console.log(path.basename(file)); + if (err) { + console.log(' (Error: ' + err + ')'); + } else if (!font.supported) { + console.log(' (Unsupported)'); + } else { + printFontInfo(font); + } + }); +} + +function recursiveInfo(fontDirectory) { + walk(fontDirectory, function(file) { + var ext = path.extname(file).toLowerCase(); + if (ext === '.ttf' || ext === '.otf') { + fileInfo(file); + } + }); +} + +if (process.argv.length < 3) { + printUsage(); +} else { + var command = process.argv[2]; + if (command === 'info') { + var fontpath = process.argv.length === 3 ? '.' : process.argv[3]; + if (fs.existsSync(fontpath)) { + var ext = path.extname(fontpath).toLowerCase(); + if (fs.statSync(fontpath).isDirectory()) { + recursiveInfo(fontpath); + } else if (ext === '.ttf' || ext === '.otf') { + fileInfo(fontpath); + } else { + printUsage(); + } + } else { + console.log('Path not found'); + } + } else { + printUsage(); + } +} diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/bin/server.js b/node_modules/lv_font_conv/node_modules/opentype.js/bin/server.js new file mode 100755 index 00000000..f6f450a4 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/opentype.js/bin/server.js @@ -0,0 +1,64 @@ +#!/usr/bin/env node + +var fs = require('fs'); +var http = require('http'); +var path = require('path'); +var rollup = require('rollup'); +var rollupConfig = require('../rollup.config'); + +var CONTENT_TYPES = { + '.html': 'text/html', + '.css': 'text/css', + '.png': 'image/png', + '.js': 'text/javascript', + '.ttf': 'font/otf', + '.otf': 'font/otf', + '.woff': 'font/woff', + '.woff2': 'font/woff2', +}; + +http.createServer(function(req, res) { + var rewrite = ''; + var url = req.url.substring(1); + if (url.length === 0) { + url = 'index.html'; + rewrite = ' -> ' + url; + } + + console.log('HTTP', req.url, rewrite); + var filePath = './' + url; + fs.readFile(filePath, function(err, data) { + if (err) { + res.writeHead(404, {'Content-Type': 'text/plain'}); + res.end('Error: ' + err); + } else { + var contentType = CONTENT_TYPES[path.extname(filePath)] || 'text/plain'; + res.writeHead(200, { + 'Content-Type': contentType, + 'Cache-Control': 'max-age=0' + }); + res.end(data); + } + }); +}).listen(8080); +console.log('Server running at http://localhost:8080/'); + +// Watch changes and rebundle +var watcher = rollup.watch(rollupConfig); +watcher.on('event', e => { + // event.code can be one of: + // START — the watcher is (re)starting + // BUNDLE_START — building an individual bundle + // BUNDLE_END — finished building a bundle + // END — finished building all bundles + // ERROR — encountered an error while bundling + // FATAL — encountered an unrecoverable error + + if (e.code === 'BUNDLE_START') { + console.log('Bundling...'); + } else if (e.code === 'BUNDLE_END') { + console.log('Bundled in ' + e.duration + 'ms.'); + } else if (e.code === 'ERROR' || e.code === 'FATAL') { + console.error(e.error); + } +}); diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/bin/test-render b/node_modules/lv_font_conv/node_modules/opentype.js/bin/test-render new file mode 100755 index 00000000..4bfc31e6 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/opentype.js/bin/test-render @@ -0,0 +1,96 @@ +#!/usr/bin/env node +// This is a command to test the text rendering compliance of OpenType.js. +// It is designed to operate with https://github.com/unicode-org/text-rendering-tests. +// +// Call it like this: +// +// ./bin/test-render --font=fonts/FiraSansOT-Medium.otf --testcase=TEST-1 --render=BALL +// +// The output will look like this: +// +// +// +// +// +// +// +// +// +// +// +// +// When viewing the SVG, it will be upside-down (since glyphs are designed Y-up). + +var opentype = require('../dist/opentype.js'); + +const SVG_FOOTER = ``; + +function printUsage() { + console.log('Usage: test-render --font=filename.otf --testcase=TEST_NAME --render=TEXT_TO_RENDER'); + console.log('This commands output the text to render as an SVG file.'); + console.log(); +} + +let filename; +let testcase; +let textToRender; +for (let i = 0; i < process.argv.length; i++) { + const arg = process.argv[i]; + if (arg.startsWith('--font=')) { + filename = arg.substring('--font='.length); + } else if (arg.startsWith('--testcase=')) { + testcase = arg.substring('--testcase='.length); + } else if (arg.startsWith('--render=')) { + textToRender = arg.substring('--render='.length); + } +} + +if (filename === undefined || testcase === undefined || textToRender === undefined) { + printUsage(); + process.exit(1); +} + +function renderSVG() { + var font = opentype.loadSync(filename); + + let svgSymbols = []; + let svgBody = []; + + var glyphSet = new Set(); + let x = 0; + const glyphs = font.stringToGlyphs(textToRender); + for (let i = 0; i < glyphs.length; i++) { + const glyph = glyphs[i]; + const symbolId = testcase + '.' + glyph.name; + if (!glyphSet.has(glyph)) { + glyphSet.add(glyph); + const svgPath = glyph.path.toSVG(); + svgSymbols.push(` ${svgPath}`); + } + svgBody.push(` `); + x += glyph.advanceWidth; + } + + let minX = 0; + let minY = Math.round(font.descender); + let width = Math.round(x); + let height = Math.round(font.ascender - font.descender); + let svgHeader = ` +`; + + return svgHeader + svgSymbols.join('\n') + svgBody.join('\n') + SVG_FOOTER; +} + +try { + var svg = renderSVG(); + console.log(svg); +} catch(e) { + console.error(e.stack); + process.exit(1); +} diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/dist/opentype.js b/node_modules/lv_font_conv/node_modules/opentype.js/dist/opentype.js new file mode 100644 index 00000000..11b0a548 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/opentype.js/dist/opentype.js @@ -0,0 +1,14254 @@ +/** + * https://opentype.js.org v1.3.3 | (c) Frederik De Bleser and other contributors | MIT License | Uses tiny-inflate by Devon Govett and string.prototype.codepointat polyfill by Mathias Bynens + */ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (global = global || self, factory(global.opentype = {})); +}(this, (function (exports) { 'use strict'; + + /*! https://mths.be/codepointat v0.2.0 by @mathias */ + if (!String.prototype.codePointAt) { + (function() { + var defineProperty = (function() { + // IE 8 only supports `Object.defineProperty` on DOM elements + try { + var object = {}; + var $defineProperty = Object.defineProperty; + var result = $defineProperty(object, object, object) && $defineProperty; + } catch(error) {} + return result; + }()); + var codePointAt = function(position) { + if (this == null) { + throw TypeError(); + } + var string = String(this); + var size = string.length; + // `ToInteger` + var index = position ? Number(position) : 0; + if (index != index) { // better `isNaN` + index = 0; + } + // Account for out-of-bounds indices: + if (index < 0 || index >= size) { + return undefined; + } + // Get the first code unit + var first = string.charCodeAt(index); + var second; + if ( // check if it’s the start of a surrogate pair + first >= 0xD800 && first <= 0xDBFF && // high surrogate + size > index + 1 // there is a next code unit + ) { + second = string.charCodeAt(index + 1); + if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate + // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; + } + } + return first; + }; + if (defineProperty) { + defineProperty(String.prototype, 'codePointAt', { + 'value': codePointAt, + 'configurable': true, + 'writable': true + }); + } else { + String.prototype.codePointAt = codePointAt; + } + }()); + } + + var TINF_OK = 0; + var TINF_DATA_ERROR = -3; + + function Tree() { + this.table = new Uint16Array(16); /* table of code length counts */ + this.trans = new Uint16Array(288); /* code -> symbol translation table */ + } + + function Data(source, dest) { + this.source = source; + this.sourceIndex = 0; + this.tag = 0; + this.bitcount = 0; + + this.dest = dest; + this.destLen = 0; + + this.ltree = new Tree(); /* dynamic length/symbol tree */ + this.dtree = new Tree(); /* dynamic distance tree */ + } + + /* --------------------------------------------------- * + * -- uninitialized global data (static structures) -- * + * --------------------------------------------------- */ + + var sltree = new Tree(); + var sdtree = new Tree(); + + /* extra bits and base tables for length codes */ + var length_bits = new Uint8Array(30); + var length_base = new Uint16Array(30); + + /* extra bits and base tables for distance codes */ + var dist_bits = new Uint8Array(30); + var dist_base = new Uint16Array(30); + + /* special ordering of code length codes */ + var clcidx = new Uint8Array([ + 16, 17, 18, 0, 8, 7, 9, 6, + 10, 5, 11, 4, 12, 3, 13, 2, + 14, 1, 15 + ]); + + /* used by tinf_decode_trees, avoids allocations every call */ + var code_tree = new Tree(); + var lengths = new Uint8Array(288 + 32); + + /* ----------------------- * + * -- utility functions -- * + * ----------------------- */ + + /* build extra bits and base tables */ + function tinf_build_bits_base(bits, base, delta, first) { + var i, sum; + + /* build bits table */ + for (i = 0; i < delta; ++i) { bits[i] = 0; } + for (i = 0; i < 30 - delta; ++i) { bits[i + delta] = i / delta | 0; } + + /* build base table */ + for (sum = first, i = 0; i < 30; ++i) { + base[i] = sum; + sum += 1 << bits[i]; + } + } + + /* build the fixed huffman trees */ + function tinf_build_fixed_trees(lt, dt) { + var i; + + /* build fixed length tree */ + for (i = 0; i < 7; ++i) { lt.table[i] = 0; } + + lt.table[7] = 24; + lt.table[8] = 152; + lt.table[9] = 112; + + for (i = 0; i < 24; ++i) { lt.trans[i] = 256 + i; } + for (i = 0; i < 144; ++i) { lt.trans[24 + i] = i; } + for (i = 0; i < 8; ++i) { lt.trans[24 + 144 + i] = 280 + i; } + for (i = 0; i < 112; ++i) { lt.trans[24 + 144 + 8 + i] = 144 + i; } + + /* build fixed distance tree */ + for (i = 0; i < 5; ++i) { dt.table[i] = 0; } + + dt.table[5] = 32; + + for (i = 0; i < 32; ++i) { dt.trans[i] = i; } + } + + /* given an array of code lengths, build a tree */ + var offs = new Uint16Array(16); + + function tinf_build_tree(t, lengths, off, num) { + var i, sum; + + /* clear code length count table */ + for (i = 0; i < 16; ++i) { t.table[i] = 0; } + + /* scan symbol lengths, and sum code length counts */ + for (i = 0; i < num; ++i) { t.table[lengths[off + i]]++; } + + t.table[0] = 0; + + /* compute offset table for distribution sort */ + for (sum = 0, i = 0; i < 16; ++i) { + offs[i] = sum; + sum += t.table[i]; + } + + /* create code->symbol translation table (symbols sorted by code) */ + for (i = 0; i < num; ++i) { + if (lengths[off + i]) { t.trans[offs[lengths[off + i]]++] = i; } + } + } + + /* ---------------------- * + * -- decode functions -- * + * ---------------------- */ + + /* get one bit from source stream */ + function tinf_getbit(d) { + /* check if tag is empty */ + if (!d.bitcount--) { + /* load next tag */ + d.tag = d.source[d.sourceIndex++]; + d.bitcount = 7; + } + + /* shift bit out of tag */ + var bit = d.tag & 1; + d.tag >>>= 1; + + return bit; + } + + /* read a num bit value from a stream and add base */ + function tinf_read_bits(d, num, base) { + if (!num) + { return base; } + + while (d.bitcount < 24) { + d.tag |= d.source[d.sourceIndex++] << d.bitcount; + d.bitcount += 8; + } + + var val = d.tag & (0xffff >>> (16 - num)); + d.tag >>>= num; + d.bitcount -= num; + return val + base; + } + + /* given a data stream and a tree, decode a symbol */ + function tinf_decode_symbol(d, t) { + while (d.bitcount < 24) { + d.tag |= d.source[d.sourceIndex++] << d.bitcount; + d.bitcount += 8; + } + + var sum = 0, cur = 0, len = 0; + var tag = d.tag; + + /* get more bits while code value is above sum */ + do { + cur = 2 * cur + (tag & 1); + tag >>>= 1; + ++len; + + sum += t.table[len]; + cur -= t.table[len]; + } while (cur >= 0); + + d.tag = tag; + d.bitcount -= len; + + return t.trans[sum + cur]; + } + + /* given a data stream, decode dynamic trees from it */ + function tinf_decode_trees(d, lt, dt) { + var hlit, hdist, hclen; + var i, num, length; + + /* get 5 bits HLIT (257-286) */ + hlit = tinf_read_bits(d, 5, 257); + + /* get 5 bits HDIST (1-32) */ + hdist = tinf_read_bits(d, 5, 1); + + /* get 4 bits HCLEN (4-19) */ + hclen = tinf_read_bits(d, 4, 4); + + for (i = 0; i < 19; ++i) { lengths[i] = 0; } + + /* read code lengths for code length alphabet */ + for (i = 0; i < hclen; ++i) { + /* get 3 bits code length (0-7) */ + var clen = tinf_read_bits(d, 3, 0); + lengths[clcidx[i]] = clen; + } + + /* build code length tree */ + tinf_build_tree(code_tree, lengths, 0, 19); + + /* decode code lengths for the dynamic trees */ + for (num = 0; num < hlit + hdist;) { + var sym = tinf_decode_symbol(d, code_tree); + + switch (sym) { + case 16: + /* copy previous code length 3-6 times (read 2 bits) */ + var prev = lengths[num - 1]; + for (length = tinf_read_bits(d, 2, 3); length; --length) { + lengths[num++] = prev; + } + break; + case 17: + /* repeat code length 0 for 3-10 times (read 3 bits) */ + for (length = tinf_read_bits(d, 3, 3); length; --length) { + lengths[num++] = 0; + } + break; + case 18: + /* repeat code length 0 for 11-138 times (read 7 bits) */ + for (length = tinf_read_bits(d, 7, 11); length; --length) { + lengths[num++] = 0; + } + break; + default: + /* values 0-15 represent the actual code lengths */ + lengths[num++] = sym; + break; + } + } + + /* build dynamic trees */ + tinf_build_tree(lt, lengths, 0, hlit); + tinf_build_tree(dt, lengths, hlit, hdist); + } + + /* ----------------------------- * + * -- block inflate functions -- * + * ----------------------------- */ + + /* given a stream and two trees, inflate a block of data */ + function tinf_inflate_block_data(d, lt, dt) { + while (1) { + var sym = tinf_decode_symbol(d, lt); + + /* check for end of block */ + if (sym === 256) { + return TINF_OK; + } + + if (sym < 256) { + d.dest[d.destLen++] = sym; + } else { + var length, dist, offs; + var i; + + sym -= 257; + + /* possibly get more bits from length code */ + length = tinf_read_bits(d, length_bits[sym], length_base[sym]); + + dist = tinf_decode_symbol(d, dt); + + /* possibly get more bits from distance code */ + offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]); + + /* copy match */ + for (i = offs; i < offs + length; ++i) { + d.dest[d.destLen++] = d.dest[i]; + } + } + } + } + + /* inflate an uncompressed block of data */ + function tinf_inflate_uncompressed_block(d) { + var length, invlength; + var i; + + /* unread from bitbuffer */ + while (d.bitcount > 8) { + d.sourceIndex--; + d.bitcount -= 8; + } + + /* get length */ + length = d.source[d.sourceIndex + 1]; + length = 256 * length + d.source[d.sourceIndex]; + + /* get one's complement of length */ + invlength = d.source[d.sourceIndex + 3]; + invlength = 256 * invlength + d.source[d.sourceIndex + 2]; + + /* check length */ + if (length !== (~invlength & 0x0000ffff)) + { return TINF_DATA_ERROR; } + + d.sourceIndex += 4; + + /* copy block */ + for (i = length; i; --i) + { d.dest[d.destLen++] = d.source[d.sourceIndex++]; } + + /* make sure we start next block on a byte boundary */ + d.bitcount = 0; + + return TINF_OK; + } + + /* inflate stream from source to dest */ + function tinf_uncompress(source, dest) { + var d = new Data(source, dest); + var bfinal, btype, res; + + do { + /* read final block flag */ + bfinal = tinf_getbit(d); + + /* read block type (2 bits) */ + btype = tinf_read_bits(d, 2, 0); + + /* decompress block */ + switch (btype) { + case 0: + /* decompress uncompressed block */ + res = tinf_inflate_uncompressed_block(d); + break; + case 1: + /* decompress block with fixed huffman trees */ + res = tinf_inflate_block_data(d, sltree, sdtree); + break; + case 2: + /* decompress block with dynamic huffman trees */ + tinf_decode_trees(d, d.ltree, d.dtree); + res = tinf_inflate_block_data(d, d.ltree, d.dtree); + break; + default: + res = TINF_DATA_ERROR; + } + + if (res !== TINF_OK) + { throw new Error('Data error'); } + + } while (!bfinal); + + if (d.destLen < d.dest.length) { + if (typeof d.dest.slice === 'function') + { return d.dest.slice(0, d.destLen); } + else + { return d.dest.subarray(0, d.destLen); } + } + + return d.dest; + } + + /* -------------------- * + * -- initialization -- * + * -------------------- */ + + /* build fixed huffman trees */ + tinf_build_fixed_trees(sltree, sdtree); + + /* build extra bits and base tables */ + tinf_build_bits_base(length_bits, length_base, 4, 3); + tinf_build_bits_base(dist_bits, dist_base, 2, 1); + + /* fix a special case */ + length_bits[28] = 0; + length_base[28] = 258; + + var tinyInflate = tinf_uncompress; + + // The Bounding Box object + + function derive(v0, v1, v2, v3, t) { + return Math.pow(1 - t, 3) * v0 + + 3 * Math.pow(1 - t, 2) * t * v1 + + 3 * (1 - t) * Math.pow(t, 2) * v2 + + Math.pow(t, 3) * v3; + } + /** + * A bounding box is an enclosing box that describes the smallest measure within which all the points lie. + * It is used to calculate the bounding box of a glyph or text path. + * + * On initialization, x1/y1/x2/y2 will be NaN. Check if the bounding box is empty using `isEmpty()`. + * + * @exports opentype.BoundingBox + * @class + * @constructor + */ + function BoundingBox() { + this.x1 = Number.NaN; + this.y1 = Number.NaN; + this.x2 = Number.NaN; + this.y2 = Number.NaN; + } + + /** + * Returns true if the bounding box is empty, that is, no points have been added to the box yet. + */ + BoundingBox.prototype.isEmpty = function() { + return isNaN(this.x1) || isNaN(this.y1) || isNaN(this.x2) || isNaN(this.y2); + }; + + /** + * Add the point to the bounding box. + * The x1/y1/x2/y2 coordinates of the bounding box will now encompass the given point. + * @param {number} x - The X coordinate of the point. + * @param {number} y - The Y coordinate of the point. + */ + BoundingBox.prototype.addPoint = function(x, y) { + if (typeof x === 'number') { + if (isNaN(this.x1) || isNaN(this.x2)) { + this.x1 = x; + this.x2 = x; + } + if (x < this.x1) { + this.x1 = x; + } + if (x > this.x2) { + this.x2 = x; + } + } + if (typeof y === 'number') { + if (isNaN(this.y1) || isNaN(this.y2)) { + this.y1 = y; + this.y2 = y; + } + if (y < this.y1) { + this.y1 = y; + } + if (y > this.y2) { + this.y2 = y; + } + } + }; + + /** + * Add a X coordinate to the bounding box. + * This extends the bounding box to include the X coordinate. + * This function is used internally inside of addBezier. + * @param {number} x - The X coordinate of the point. + */ + BoundingBox.prototype.addX = function(x) { + this.addPoint(x, null); + }; + + /** + * Add a Y coordinate to the bounding box. + * This extends the bounding box to include the Y coordinate. + * This function is used internally inside of addBezier. + * @param {number} y - The Y coordinate of the point. + */ + BoundingBox.prototype.addY = function(y) { + this.addPoint(null, y); + }; + + /** + * Add a Bézier curve to the bounding box. + * This extends the bounding box to include the entire Bézier. + * @param {number} x0 - The starting X coordinate. + * @param {number} y0 - The starting Y coordinate. + * @param {number} x1 - The X coordinate of the first control point. + * @param {number} y1 - The Y coordinate of the first control point. + * @param {number} x2 - The X coordinate of the second control point. + * @param {number} y2 - The Y coordinate of the second control point. + * @param {number} x - The ending X coordinate. + * @param {number} y - The ending Y coordinate. + */ + BoundingBox.prototype.addBezier = function(x0, y0, x1, y1, x2, y2, x, y) { + // This code is based on http://nishiohirokazu.blogspot.com/2009/06/how-to-calculate-bezier-curves-bounding.html + // and https://github.com/icons8/svg-path-bounding-box + + var p0 = [x0, y0]; + var p1 = [x1, y1]; + var p2 = [x2, y2]; + var p3 = [x, y]; + + this.addPoint(x0, y0); + this.addPoint(x, y); + + for (var i = 0; i <= 1; i++) { + var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i]; + var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i]; + var c = 3 * p1[i] - 3 * p0[i]; + + if (a === 0) { + if (b === 0) { continue; } + var t = -c / b; + if (0 < t && t < 1) { + if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t)); } + if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t)); } + } + continue; + } + + var b2ac = Math.pow(b, 2) - 4 * c * a; + if (b2ac < 0) { continue; } + var t1 = (-b + Math.sqrt(b2ac)) / (2 * a); + if (0 < t1 && t1 < 1) { + if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t1)); } + if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t1)); } + } + var t2 = (-b - Math.sqrt(b2ac)) / (2 * a); + if (0 < t2 && t2 < 1) { + if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t2)); } + if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t2)); } + } + } + }; + + /** + * Add a quadratic curve to the bounding box. + * This extends the bounding box to include the entire quadratic curve. + * @param {number} x0 - The starting X coordinate. + * @param {number} y0 - The starting Y coordinate. + * @param {number} x1 - The X coordinate of the control point. + * @param {number} y1 - The Y coordinate of the control point. + * @param {number} x - The ending X coordinate. + * @param {number} y - The ending Y coordinate. + */ + BoundingBox.prototype.addQuad = function(x0, y0, x1, y1, x, y) { + var cp1x = x0 + 2 / 3 * (x1 - x0); + var cp1y = y0 + 2 / 3 * (y1 - y0); + var cp2x = cp1x + 1 / 3 * (x - x0); + var cp2y = cp1y + 1 / 3 * (y - y0); + this.addBezier(x0, y0, cp1x, cp1y, cp2x, cp2y, x, y); + }; + + // Geometric objects + + /** + * A bézier path containing a set of path commands similar to a SVG path. + * Paths can be drawn on a context using `draw`. + * @exports opentype.Path + * @class + * @constructor + */ + function Path() { + this.commands = []; + this.fill = 'black'; + this.stroke = null; + this.strokeWidth = 1; + } + + /** + * @param {number} x + * @param {number} y + */ + Path.prototype.moveTo = function(x, y) { + this.commands.push({ + type: 'M', + x: x, + y: y + }); + }; + + /** + * @param {number} x + * @param {number} y + */ + Path.prototype.lineTo = function(x, y) { + this.commands.push({ + type: 'L', + x: x, + y: y + }); + }; + + /** + * Draws cubic curve + * @function + * curveTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control 1 + * @param {number} y1 - y of control 1 + * @param {number} x2 - x of control 2 + * @param {number} y2 - y of control 2 + * @param {number} x - x of path point + * @param {number} y - y of path point + */ + + /** + * Draws cubic curve + * @function + * bezierCurveTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control 1 + * @param {number} y1 - y of control 1 + * @param {number} x2 - x of control 2 + * @param {number} y2 - y of control 2 + * @param {number} x - x of path point + * @param {number} y - y of path point + * @see curveTo + */ + Path.prototype.curveTo = Path.prototype.bezierCurveTo = function(x1, y1, x2, y2, x, y) { + this.commands.push({ + type: 'C', + x1: x1, + y1: y1, + x2: x2, + y2: y2, + x: x, + y: y + }); + }; + + /** + * Draws quadratic curve + * @function + * quadraticCurveTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control + * @param {number} y1 - y of control + * @param {number} x - x of path point + * @param {number} y - y of path point + */ + + /** + * Draws quadratic curve + * @function + * quadTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control + * @param {number} y1 - y of control + * @param {number} x - x of path point + * @param {number} y - y of path point + */ + Path.prototype.quadTo = Path.prototype.quadraticCurveTo = function(x1, y1, x, y) { + this.commands.push({ + type: 'Q', + x1: x1, + y1: y1, + x: x, + y: y + }); + }; + + /** + * Closes the path + * @function closePath + * @memberof opentype.Path.prototype + */ + + /** + * Close the path + * @function close + * @memberof opentype.Path.prototype + */ + Path.prototype.close = Path.prototype.closePath = function() { + this.commands.push({ + type: 'Z' + }); + }; + + /** + * Add the given path or list of commands to the commands of this path. + * @param {Array} pathOrCommands - another opentype.Path, an opentype.BoundingBox, or an array of commands. + */ + Path.prototype.extend = function(pathOrCommands) { + if (pathOrCommands.commands) { + pathOrCommands = pathOrCommands.commands; + } else if (pathOrCommands instanceof BoundingBox) { + var box = pathOrCommands; + this.moveTo(box.x1, box.y1); + this.lineTo(box.x2, box.y1); + this.lineTo(box.x2, box.y2); + this.lineTo(box.x1, box.y2); + this.close(); + return; + } + + Array.prototype.push.apply(this.commands, pathOrCommands); + }; + + /** + * Calculate the bounding box of the path. + * @returns {opentype.BoundingBox} + */ + Path.prototype.getBoundingBox = function() { + var box = new BoundingBox(); + + var startX = 0; + var startY = 0; + var prevX = 0; + var prevY = 0; + for (var i = 0; i < this.commands.length; i++) { + var cmd = this.commands[i]; + switch (cmd.type) { + case 'M': + box.addPoint(cmd.x, cmd.y); + startX = prevX = cmd.x; + startY = prevY = cmd.y; + break; + case 'L': + box.addPoint(cmd.x, cmd.y); + prevX = cmd.x; + prevY = cmd.y; + break; + case 'Q': + box.addQuad(prevX, prevY, cmd.x1, cmd.y1, cmd.x, cmd.y); + prevX = cmd.x; + prevY = cmd.y; + break; + case 'C': + box.addBezier(prevX, prevY, cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + prevX = cmd.x; + prevY = cmd.y; + break; + case 'Z': + prevX = startX; + prevY = startY; + break; + default: + throw new Error('Unexpected path command ' + cmd.type); + } + } + if (box.isEmpty()) { + box.addPoint(0, 0); + } + return box; + }; + + /** + * Draw the path to a 2D context. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context. + */ + Path.prototype.draw = function(ctx) { + ctx.beginPath(); + for (var i = 0; i < this.commands.length; i += 1) { + var cmd = this.commands[i]; + if (cmd.type === 'M') { + ctx.moveTo(cmd.x, cmd.y); + } else if (cmd.type === 'L') { + ctx.lineTo(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y); + } else if (cmd.type === 'Z') { + ctx.closePath(); + } + } + + if (this.fill) { + ctx.fillStyle = this.fill; + ctx.fill(); + } + + if (this.stroke) { + ctx.strokeStyle = this.stroke; + ctx.lineWidth = this.strokeWidth; + ctx.stroke(); + } + }; + + /** + * Convert the Path to a string of path data instructions + * See http://www.w3.org/TR/SVG/paths.html#PathData + * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values + * @return {string} + */ + Path.prototype.toPathData = function(decimalPlaces) { + decimalPlaces = decimalPlaces !== undefined ? decimalPlaces : 2; + + function floatToString(v) { + if (Math.round(v) === v) { + return '' + Math.round(v); + } else { + return v.toFixed(decimalPlaces); + } + } + + function packValues() { + var arguments$1 = arguments; + + var s = ''; + for (var i = 0; i < arguments.length; i += 1) { + var v = arguments$1[i]; + if (v >= 0 && i > 0) { + s += ' '; + } + + s += floatToString(v); + } + + return s; + } + + var d = ''; + for (var i = 0; i < this.commands.length; i += 1) { + var cmd = this.commands[i]; + if (cmd.type === 'M') { + d += 'M' + packValues(cmd.x, cmd.y); + } else if (cmd.type === 'L') { + d += 'L' + packValues(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + d += 'C' + packValues(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + d += 'Q' + packValues(cmd.x1, cmd.y1, cmd.x, cmd.y); + } else if (cmd.type === 'Z') { + d += 'Z'; + } + } + + return d; + }; + + /** + * Convert the path to an SVG element, as a string. + * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values + * @return {string} + */ + Path.prototype.toSVG = function(decimalPlaces) { + var svg = '= 0 && v <= 255, 'Byte value should be between 0 and 255.'); + return [v]; + }; + /** + * @constant + * @type {number} + */ + sizeOf.BYTE = constant(1); + + /** + * Convert a 8-bit signed integer to a list of 1 byte. + * @param {string} + * @returns {Array} + */ + encode.CHAR = function(v) { + return [v.charCodeAt(0)]; + }; + + /** + * @constant + * @type {number} + */ + sizeOf.CHAR = constant(1); + + /** + * Convert an ASCII string to a list of bytes. + * @param {string} + * @returns {Array} + */ + encode.CHARARRAY = function(v) { + var b = []; + for (var i = 0; i < v.length; i += 1) { + b[i] = v.charCodeAt(i); + } + + return b; + }; + + /** + * @param {Array} + * @returns {number} + */ + sizeOf.CHARARRAY = function(v) { + return v.length; + }; + + /** + * Convert a 16-bit unsigned integer to a list of 2 bytes. + * @param {number} + * @returns {Array} + */ + encode.USHORT = function(v) { + return [(v >> 8) & 0xFF, v & 0xFF]; + }; + + /** + * @constant + * @type {number} + */ + sizeOf.USHORT = constant(2); + + /** + * Convert a 16-bit signed integer to a list of 2 bytes. + * @param {number} + * @returns {Array} + */ + encode.SHORT = function(v) { + // Two's complement + if (v >= LIMIT16) { + v = -(2 * LIMIT16 - v); + } + + return [(v >> 8) & 0xFF, v & 0xFF]; + }; + + /** + * @constant + * @type {number} + */ + sizeOf.SHORT = constant(2); + + /** + * Convert a 24-bit unsigned integer to a list of 3 bytes. + * @param {number} + * @returns {Array} + */ + encode.UINT24 = function(v) { + return [(v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; + }; + + /** + * @constant + * @type {number} + */ + sizeOf.UINT24 = constant(3); + + /** + * Convert a 32-bit unsigned integer to a list of 4 bytes. + * @param {number} + * @returns {Array} + */ + encode.ULONG = function(v) { + return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; + }; + + /** + * @constant + * @type {number} + */ + sizeOf.ULONG = constant(4); + + /** + * Convert a 32-bit unsigned integer to a list of 4 bytes. + * @param {number} + * @returns {Array} + */ + encode.LONG = function(v) { + // Two's complement + if (v >= LIMIT32) { + v = -(2 * LIMIT32 - v); + } + + return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; + }; + + /** + * @constant + * @type {number} + */ + sizeOf.LONG = constant(4); + + encode.FIXED = encode.ULONG; + sizeOf.FIXED = sizeOf.ULONG; + + encode.FWORD = encode.SHORT; + sizeOf.FWORD = sizeOf.SHORT; + + encode.UFWORD = encode.USHORT; + sizeOf.UFWORD = sizeOf.USHORT; + + /** + * Convert a 32-bit Apple Mac timestamp integer to a list of 8 bytes, 64-bit timestamp. + * @param {number} + * @returns {Array} + */ + encode.LONGDATETIME = function(v) { + return [0, 0, 0, 0, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; + }; + + /** + * @constant + * @type {number} + */ + sizeOf.LONGDATETIME = constant(8); + + /** + * Convert a 4-char tag to a list of 4 bytes. + * @param {string} + * @returns {Array} + */ + encode.TAG = function(v) { + check.argument(v.length === 4, 'Tag should be exactly 4 ASCII characters.'); + return [v.charCodeAt(0), + v.charCodeAt(1), + v.charCodeAt(2), + v.charCodeAt(3)]; + }; + + /** + * @constant + * @type {number} + */ + sizeOf.TAG = constant(4); + + // CFF data types /////////////////////////////////////////////////////////// + + encode.Card8 = encode.BYTE; + sizeOf.Card8 = sizeOf.BYTE; + + encode.Card16 = encode.USHORT; + sizeOf.Card16 = sizeOf.USHORT; + + encode.OffSize = encode.BYTE; + sizeOf.OffSize = sizeOf.BYTE; + + encode.SID = encode.USHORT; + sizeOf.SID = sizeOf.USHORT; + + // Convert a numeric operand or charstring number to a variable-size list of bytes. + /** + * Convert a numeric operand or charstring number to a variable-size list of bytes. + * @param {number} + * @returns {Array} + */ + encode.NUMBER = function(v) { + if (v >= -107 && v <= 107) { + return [v + 139]; + } else if (v >= 108 && v <= 1131) { + v = v - 108; + return [(v >> 8) + 247, v & 0xFF]; + } else if (v >= -1131 && v <= -108) { + v = -v - 108; + return [(v >> 8) + 251, v & 0xFF]; + } else if (v >= -32768 && v <= 32767) { + return encode.NUMBER16(v); + } else { + return encode.NUMBER32(v); + } + }; + + /** + * @param {number} + * @returns {number} + */ + sizeOf.NUMBER = function(v) { + return encode.NUMBER(v).length; + }; + + /** + * Convert a signed number between -32768 and +32767 to a three-byte value. + * This ensures we always use three bytes, but is not the most compact format. + * @param {number} + * @returns {Array} + */ + encode.NUMBER16 = function(v) { + return [28, (v >> 8) & 0xFF, v & 0xFF]; + }; + + /** + * @constant + * @type {number} + */ + sizeOf.NUMBER16 = constant(3); + + /** + * Convert a signed number between -(2^31) and +(2^31-1) to a five-byte value. + * This is useful if you want to be sure you always use four bytes, + * at the expense of wasting a few bytes for smaller numbers. + * @param {number} + * @returns {Array} + */ + encode.NUMBER32 = function(v) { + return [29, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; + }; + + /** + * @constant + * @type {number} + */ + sizeOf.NUMBER32 = constant(5); + + /** + * @param {number} + * @returns {Array} + */ + encode.REAL = function(v) { + var value = v.toString(); + + // Some numbers use an epsilon to encode the value. (e.g. JavaScript will store 0.0000001 as 1e-7) + // This code converts it back to a number without the epsilon. + var m = /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value); + if (m) { + var epsilon = parseFloat('1e' + ((m[2] ? +m[2] : 0) + m[1].length)); + value = (Math.round(v * epsilon) / epsilon).toString(); + } + + var nibbles = ''; + for (var i = 0, ii = value.length; i < ii; i += 1) { + var c = value[i]; + if (c === 'e') { + nibbles += value[++i] === '-' ? 'c' : 'b'; + } else if (c === '.') { + nibbles += 'a'; + } else if (c === '-') { + nibbles += 'e'; + } else { + nibbles += c; + } + } + + nibbles += (nibbles.length & 1) ? 'f' : 'ff'; + var out = [30]; + for (var i$1 = 0, ii$1 = nibbles.length; i$1 < ii$1; i$1 += 2) { + out.push(parseInt(nibbles.substr(i$1, 2), 16)); + } + + return out; + }; + + /** + * @param {number} + * @returns {number} + */ + sizeOf.REAL = function(v) { + return encode.REAL(v).length; + }; + + encode.NAME = encode.CHARARRAY; + sizeOf.NAME = sizeOf.CHARARRAY; + + encode.STRING = encode.CHARARRAY; + sizeOf.STRING = sizeOf.CHARARRAY; + + /** + * @param {DataView} data + * @param {number} offset + * @param {number} numBytes + * @returns {string} + */ + decode.UTF8 = function(data, offset, numBytes) { + var codePoints = []; + var numChars = numBytes; + for (var j = 0; j < numChars; j++, offset += 1) { + codePoints[j] = data.getUint8(offset); + } + + return String.fromCharCode.apply(null, codePoints); + }; + + /** + * @param {DataView} data + * @param {number} offset + * @param {number} numBytes + * @returns {string} + */ + decode.UTF16 = function(data, offset, numBytes) { + var codePoints = []; + var numChars = numBytes / 2; + for (var j = 0; j < numChars; j++, offset += 2) { + codePoints[j] = data.getUint16(offset); + } + + return String.fromCharCode.apply(null, codePoints); + }; + + /** + * Convert a JavaScript string to UTF16-BE. + * @param {string} + * @returns {Array} + */ + encode.UTF16 = function(v) { + var b = []; + for (var i = 0; i < v.length; i += 1) { + var codepoint = v.charCodeAt(i); + b[b.length] = (codepoint >> 8) & 0xFF; + b[b.length] = codepoint & 0xFF; + } + + return b; + }; + + /** + * @param {string} + * @returns {number} + */ + sizeOf.UTF16 = function(v) { + return v.length * 2; + }; + + // Data for converting old eight-bit Macintosh encodings to Unicode. + // This representation is optimized for decoding; encoding is slower + // and needs more memory. The assumption is that all opentype.js users + // want to open fonts, but saving a font will be comparatively rare + // so it can be more expensive. Keyed by IANA character set name. + // + // Python script for generating these strings: + // + // s = u''.join([chr(c).decode('mac_greek') for c in range(128, 256)]) + // print(s.encode('utf-8')) + /** + * @private + */ + var eightBitMacEncodings = { + 'x-mac-croatian': // Python: 'mac_croatian' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø' + + '¿¡¬√ƒ≈Ć«Č… ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ', + 'x-mac-cyrillic': // Python: 'mac_cyrillic' + 'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ†°Ґ£§•¶І®©™Ђђ≠Ѓѓ∞±≤≥іµґЈЄєЇїЉљЊњ' + + 'јЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю', + 'x-mac-gaelic': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/GAELIC.TXT + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØḂ±≤≥ḃĊċḊḋḞḟĠġṀæø' + + 'ṁṖṗɼƒſṠ«»… ÀÃÕŒœ–—“”‘’ṡẛÿŸṪ€‹›Ŷŷṫ·Ỳỳ⁊ÂÊÁËÈÍÎÏÌÓÔ♣ÒÚÛÙıÝýŴŵẄẅẀẁẂẃ', + 'x-mac-greek': // Python: 'mac_greek' + 'Ĺ²É³ÖÜ΅àâä΄¨çéèê룙î‰ôö¦€ùûü†ΓΔΘΛΞΠß®©ΣΪ§≠°·Α±≤≥¥ΒΕΖΗΙΚΜΦΫΨΩ' + + 'άΝ¬ΟΡ≈Τ«»… ΥΧΆΈœ–―“”‘’÷ΉΊΌΎέήίόΏύαβψδεφγηιξκλμνοπώρστθωςχυζϊϋΐΰ\u00AD', + 'x-mac-icelandic': // Python: 'mac_iceland' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûüÝ°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€ÐðÞþý·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + 'x-mac-inuit': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/INUIT.TXT + 'ᐃᐄᐅᐆᐊᐋᐱᐲᐳᐴᐸᐹᑉᑎᑏᑐᑑᑕᑖᑦᑭᑮᑯᑰᑲᑳᒃᒋᒌᒍᒎᒐᒑ°ᒡᒥᒦ•¶ᒧ®©™ᒨᒪᒫᒻᓂᓃᓄᓅᓇᓈᓐᓯᓰᓱᓲᓴᓵᔅᓕᓖᓗ' + + 'ᓘᓚᓛᓪᔨᔩᔪᔫᔭ… ᔮᔾᕕᕖᕗ–—“”‘’ᕘᕙᕚᕝᕆᕇᕈᕉᕋᕌᕐᕿᖀᖁᖂᖃᖄᖅᖏᖐᖑᖒᖓᖔᖕᙱᙲᙳᙴᙵᙶᖖᖠᖡᖢᖣᖤᖥᖦᕼŁł', + 'x-mac-ce': // Python: 'mac_latin2' + 'ÄĀāÉĄÖÜáąČäčĆć鏟ĎíďĒēĖóėôöõúĚěü†°Ę£§•¶ß®©™ę¨≠ģĮįĪ≤≥īĶ∂∑łĻļĽľĹĺŅ' + + 'ņѬ√ńŇ∆«»… ňŐÕőŌ–—“”‘’÷◊ōŔŕŘ‹›řŖŗŠ‚„šŚśÁŤťÍŽžŪÓÔūŮÚůŰűŲųÝýķŻŁżĢˇ', + macintosh: // Python: 'mac_roman' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + 'x-mac-romanian': // Python: 'mac_romanian' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ĂȘ∞±≤≥¥µ∂∑∏π∫ªºΩăș' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›Țț‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + 'x-mac-turkish': // Python: 'mac_turkish' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸĞğİıŞş‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙˆ˜¯˘˙˚¸˝˛ˇ' + }; + + /** + * Decodes an old-style Macintosh string. Returns either a Unicode JavaScript + * string, or 'undefined' if the encoding is unsupported. For example, we do + * not support Chinese, Japanese or Korean because these would need large + * mapping tables. + * @param {DataView} dataView + * @param {number} offset + * @param {number} dataLength + * @param {string} encoding + * @returns {string} + */ + decode.MACSTRING = function(dataView, offset, dataLength, encoding) { + var table = eightBitMacEncodings[encoding]; + if (table === undefined) { + return undefined; + } + + var result = ''; + for (var i = 0; i < dataLength; i++) { + var c = dataView.getUint8(offset + i); + // In all eight-bit Mac encodings, the characters 0x00..0x7F are + // mapped to U+0000..U+007F; we only need to look up the others. + if (c <= 0x7F) { + result += String.fromCharCode(c); + } else { + result += table[c & 0x7F]; + } + } + + return result; + }; + + // Helper function for encode.MACSTRING. Returns a dictionary for mapping + // Unicode character codes to their 8-bit MacOS equivalent. This table + // is not exactly a super cheap data structure, but we do not care because + // encoding Macintosh strings is only rarely needed in typical applications. + var macEncodingTableCache = typeof WeakMap === 'function' && new WeakMap(); + var macEncodingCacheKeys; + var getMacEncodingTable = function (encoding) { + // Since we use encoding as a cache key for WeakMap, it has to be + // a String object and not a literal. And at least on NodeJS 2.10.1, + // WeakMap requires that the same String instance is passed for cache hits. + if (!macEncodingCacheKeys) { + macEncodingCacheKeys = {}; + for (var e in eightBitMacEncodings) { + /*jshint -W053 */ // Suppress "Do not use String as a constructor." + macEncodingCacheKeys[e] = new String(e); + } + } + + var cacheKey = macEncodingCacheKeys[encoding]; + if (cacheKey === undefined) { + return undefined; + } + + // We can't do "if (cache.has(key)) {return cache.get(key)}" here: + // since garbage collection may run at any time, it could also kick in + // between the calls to cache.has() and cache.get(). In that case, + // we would return 'undefined' even though we do support the encoding. + if (macEncodingTableCache) { + var cachedTable = macEncodingTableCache.get(cacheKey); + if (cachedTable !== undefined) { + return cachedTable; + } + } + + var decodingTable = eightBitMacEncodings[encoding]; + if (decodingTable === undefined) { + return undefined; + } + + var encodingTable = {}; + for (var i = 0; i < decodingTable.length; i++) { + encodingTable[decodingTable.charCodeAt(i)] = i + 0x80; + } + + if (macEncodingTableCache) { + macEncodingTableCache.set(cacheKey, encodingTable); + } + + return encodingTable; + }; + + /** + * Encodes an old-style Macintosh string. Returns a byte array upon success. + * If the requested encoding is unsupported, or if the input string contains + * a character that cannot be expressed in the encoding, the function returns + * 'undefined'. + * @param {string} str + * @param {string} encoding + * @returns {Array} + */ + encode.MACSTRING = function(str, encoding) { + var table = getMacEncodingTable(encoding); + if (table === undefined) { + return undefined; + } + + var result = []; + for (var i = 0; i < str.length; i++) { + var c = str.charCodeAt(i); + + // In all eight-bit Mac encodings, the characters 0x00..0x7F are + // mapped to U+0000..U+007F; we only need to look up the others. + if (c >= 0x80) { + c = table[c]; + if (c === undefined) { + // str contains a Unicode character that cannot be encoded + // in the requested encoding. + return undefined; + } + } + result[i] = c; + // result.push(c); + } + + return result; + }; + + /** + * @param {string} str + * @param {string} encoding + * @returns {number} + */ + sizeOf.MACSTRING = function(str, encoding) { + var b = encode.MACSTRING(str, encoding); + if (b !== undefined) { + return b.length; + } else { + return 0; + } + }; + + // Helper for encode.VARDELTAS + function isByteEncodable(value) { + return value >= -128 && value <= 127; + } + + // Helper for encode.VARDELTAS + function encodeVarDeltaRunAsZeroes(deltas, pos, result) { + var runLength = 0; + var numDeltas = deltas.length; + while (pos < numDeltas && runLength < 64 && deltas[pos] === 0) { + ++pos; + ++runLength; + } + result.push(0x80 | (runLength - 1)); + return pos; + } + + // Helper for encode.VARDELTAS + function encodeVarDeltaRunAsBytes(deltas, offset, result) { + var runLength = 0; + var numDeltas = deltas.length; + var pos = offset; + while (pos < numDeltas && runLength < 64) { + var value = deltas[pos]; + if (!isByteEncodable(value)) { + break; + } + + // Within a byte-encoded run of deltas, a single zero is best + // stored literally as 0x00 value. However, if we have two or + // more zeroes in a sequence, it is better to start a new run. + // Fore example, the sequence of deltas [15, 15, 0, 15, 15] + // becomes 6 bytes (04 0F 0F 00 0F 0F) when storing the zero + // within the current run, but 7 bytes (01 0F 0F 80 01 0F 0F) + // when starting a new run. + if (value === 0 && pos + 1 < numDeltas && deltas[pos + 1] === 0) { + break; + } + + ++pos; + ++runLength; + } + result.push(runLength - 1); + for (var i = offset; i < pos; ++i) { + result.push((deltas[i] + 256) & 0xff); + } + return pos; + } + + // Helper for encode.VARDELTAS + function encodeVarDeltaRunAsWords(deltas, offset, result) { + var runLength = 0; + var numDeltas = deltas.length; + var pos = offset; + while (pos < numDeltas && runLength < 64) { + var value = deltas[pos]; + + // Within a word-encoded run of deltas, it is easiest to start + // a new run (with a different encoding) whenever we encounter + // a zero value. For example, the sequence [0x6666, 0, 0x7777] + // needs 7 bytes when storing the zero inside the current run + // (42 66 66 00 00 77 77), and equally 7 bytes when starting a + // new run (40 66 66 80 40 77 77). + if (value === 0) { + break; + } + + // Within a word-encoded run of deltas, a single value in the + // range (-128..127) should be encoded within the current run + // because it is more compact. For example, the sequence + // [0x6666, 2, 0x7777] becomes 7 bytes when storing the value + // literally (42 66 66 00 02 77 77), but 8 bytes when starting + // a new run (40 66 66 00 02 40 77 77). + if (isByteEncodable(value) && pos + 1 < numDeltas && isByteEncodable(deltas[pos + 1])) { + break; + } + + ++pos; + ++runLength; + } + result.push(0x40 | (runLength - 1)); + for (var i = offset; i < pos; ++i) { + var val = deltas[i]; + result.push(((val + 0x10000) >> 8) & 0xff, (val + 0x100) & 0xff); + } + return pos; + } + + /** + * Encode a list of variation adjustment deltas. + * + * Variation adjustment deltas are used in ‘gvar’ and ‘cvar’ tables. + * They indicate how points (in ‘gvar’) or values (in ‘cvar’) get adjusted + * when generating instances of variation fonts. + * + * @see https://www.microsoft.com/typography/otspec/gvar.htm + * @see https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html + * @param {Array} + * @return {Array} + */ + encode.VARDELTAS = function(deltas) { + var pos = 0; + var result = []; + while (pos < deltas.length) { + var value = deltas[pos]; + if (value === 0) { + pos = encodeVarDeltaRunAsZeroes(deltas, pos, result); + } else if (value >= -128 && value <= 127) { + pos = encodeVarDeltaRunAsBytes(deltas, pos, result); + } else { + pos = encodeVarDeltaRunAsWords(deltas, pos, result); + } + } + return result; + }; + + // Convert a list of values to a CFF INDEX structure. + // The values should be objects containing name / type / value. + /** + * @param {Array} l + * @returns {Array} + */ + encode.INDEX = function(l) { + //var offset, offsets, offsetEncoder, encodedOffsets, encodedOffset, data, + // i, v; + // Because we have to know which data type to use to encode the offsets, + // we have to go through the values twice: once to encode the data and + // calculate the offsets, then again to encode the offsets using the fitting data type. + var offset = 1; // First offset is always 1. + var offsets = [offset]; + var data = []; + for (var i = 0; i < l.length; i += 1) { + var v = encode.OBJECT(l[i]); + Array.prototype.push.apply(data, v); + offset += v.length; + offsets.push(offset); + } + + if (data.length === 0) { + return [0, 0]; + } + + var encodedOffsets = []; + var offSize = (1 + Math.floor(Math.log(offset) / Math.log(2)) / 8) | 0; + var offsetEncoder = [undefined, encode.BYTE, encode.USHORT, encode.UINT24, encode.ULONG][offSize]; + for (var i$1 = 0; i$1 < offsets.length; i$1 += 1) { + var encodedOffset = offsetEncoder(offsets[i$1]); + Array.prototype.push.apply(encodedOffsets, encodedOffset); + } + + return Array.prototype.concat(encode.Card16(l.length), + encode.OffSize(offSize), + encodedOffsets, + data); + }; + + /** + * @param {Array} + * @returns {number} + */ + sizeOf.INDEX = function(v) { + return encode.INDEX(v).length; + }; + + /** + * Convert an object to a CFF DICT structure. + * The keys should be numeric. + * The values should be objects containing name / type / value. + * @param {Object} m + * @returns {Array} + */ + encode.DICT = function(m) { + var d = []; + var keys = Object.keys(m); + var length = keys.length; + + for (var i = 0; i < length; i += 1) { + // Object.keys() return string keys, but our keys are always numeric. + var k = parseInt(keys[i], 0); + var v = m[k]; + // Value comes before the key. + d = d.concat(encode.OPERAND(v.value, v.type)); + d = d.concat(encode.OPERATOR(k)); + } + + return d; + }; + + /** + * @param {Object} + * @returns {number} + */ + sizeOf.DICT = function(m) { + return encode.DICT(m).length; + }; + + /** + * @param {number} + * @returns {Array} + */ + encode.OPERATOR = function(v) { + if (v < 1200) { + return [v]; + } else { + return [12, v - 1200]; + } + }; + + /** + * @param {Array} v + * @param {string} + * @returns {Array} + */ + encode.OPERAND = function(v, type) { + var d = []; + if (Array.isArray(type)) { + for (var i = 0; i < type.length; i += 1) { + check.argument(v.length === type.length, 'Not enough arguments given for type' + type); + d = d.concat(encode.OPERAND(v[i], type[i])); + } + } else { + if (type === 'SID') { + d = d.concat(encode.NUMBER(v)); + } else if (type === 'offset') { + // We make it easy for ourselves and always encode offsets as + // 4 bytes. This makes offset calculation for the top dict easier. + d = d.concat(encode.NUMBER32(v)); + } else if (type === 'number') { + d = d.concat(encode.NUMBER(v)); + } else if (type === 'real') { + d = d.concat(encode.REAL(v)); + } else { + throw new Error('Unknown operand type ' + type); + // FIXME Add support for booleans + } + } + + return d; + }; + + encode.OP = encode.BYTE; + sizeOf.OP = sizeOf.BYTE; + + // memoize charstring encoding using WeakMap if available + var wmm = typeof WeakMap === 'function' && new WeakMap(); + + /** + * Convert a list of CharString operations to bytes. + * @param {Array} + * @returns {Array} + */ + encode.CHARSTRING = function(ops) { + // See encode.MACSTRING for why we don't do "if (wmm && wmm.has(ops))". + if (wmm) { + var cachedValue = wmm.get(ops); + if (cachedValue !== undefined) { + return cachedValue; + } + } + + var d = []; + var length = ops.length; + + for (var i = 0; i < length; i += 1) { + var op = ops[i]; + d = d.concat(encode[op.type](op.value)); + } + + if (wmm) { + wmm.set(ops, d); + } + + return d; + }; + + /** + * @param {Array} + * @returns {number} + */ + sizeOf.CHARSTRING = function(ops) { + return encode.CHARSTRING(ops).length; + }; + + // Utility functions //////////////////////////////////////////////////////// + + /** + * Convert an object containing name / type / value to bytes. + * @param {Object} + * @returns {Array} + */ + encode.OBJECT = function(v) { + var encodingFunction = encode[v.type]; + check.argument(encodingFunction !== undefined, 'No encoding function for type ' + v.type); + return encodingFunction(v.value); + }; + + /** + * @param {Object} + * @returns {number} + */ + sizeOf.OBJECT = function(v) { + var sizeOfFunction = sizeOf[v.type]; + check.argument(sizeOfFunction !== undefined, 'No sizeOf function for type ' + v.type); + return sizeOfFunction(v.value); + }; + + /** + * Convert a table object to bytes. + * A table contains a list of fields containing the metadata (name, type and default value). + * The table itself has the field values set as attributes. + * @param {opentype.Table} + * @returns {Array} + */ + encode.TABLE = function(table) { + var d = []; + var length = table.fields.length; + var subtables = []; + var subtableOffsets = []; + + for (var i = 0; i < length; i += 1) { + var field = table.fields[i]; + var encodingFunction = encode[field.type]; + check.argument(encodingFunction !== undefined, 'No encoding function for field type ' + field.type + ' (' + field.name + ')'); + var value = table[field.name]; + if (value === undefined) { + value = field.value; + } + + var bytes = encodingFunction(value); + + if (field.type === 'TABLE') { + subtableOffsets.push(d.length); + d = d.concat([0, 0]); + subtables.push(bytes); + } else { + d = d.concat(bytes); + } + } + + for (var i$1 = 0; i$1 < subtables.length; i$1 += 1) { + var o = subtableOffsets[i$1]; + var offset = d.length; + check.argument(offset < 65536, 'Table ' + table.tableName + ' too big.'); + d[o] = offset >> 8; + d[o + 1] = offset & 0xff; + d = d.concat(subtables[i$1]); + } + + return d; + }; + + /** + * @param {opentype.Table} + * @returns {number} + */ + sizeOf.TABLE = function(table) { + var numBytes = 0; + var length = table.fields.length; + + for (var i = 0; i < length; i += 1) { + var field = table.fields[i]; + var sizeOfFunction = sizeOf[field.type]; + check.argument(sizeOfFunction !== undefined, 'No sizeOf function for field type ' + field.type + ' (' + field.name + ')'); + var value = table[field.name]; + if (value === undefined) { + value = field.value; + } + + numBytes += sizeOfFunction(value); + + // Subtables take 2 more bytes for offsets. + if (field.type === 'TABLE') { + numBytes += 2; + } + } + + return numBytes; + }; + + encode.RECORD = encode.TABLE; + sizeOf.RECORD = sizeOf.TABLE; + + // Merge in a list of bytes. + encode.LITERAL = function(v) { + return v; + }; + + sizeOf.LITERAL = function(v) { + return v.length; + }; + + // Table metadata + + /** + * @exports opentype.Table + * @class + * @param {string} tableName + * @param {Array} fields + * @param {Object} options + * @constructor + */ + function Table(tableName, fields, options) { + for (var i = 0; i < fields.length; i += 1) { + var field = fields[i]; + this[field.name] = field.value; + } + + this.tableName = tableName; + this.fields = fields; + if (options) { + var optionKeys = Object.keys(options); + for (var i$1 = 0; i$1 < optionKeys.length; i$1 += 1) { + var k = optionKeys[i$1]; + var v = options[k]; + if (this[k] !== undefined) { + this[k] = v; + } + } + } + } + + /** + * Encodes the table and returns an array of bytes + * @return {Array} + */ + Table.prototype.encode = function() { + return encode.TABLE(this); + }; + + /** + * Get the size of the table. + * @return {number} + */ + Table.prototype.sizeOf = function() { + return sizeOf.TABLE(this); + }; + + /** + * @private + */ + function ushortList(itemName, list, count) { + if (count === undefined) { + count = list.length; + } + var fields = new Array(list.length + 1); + fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; + for (var i = 0; i < list.length; i++) { + fields[i + 1] = {name: itemName + i, type: 'USHORT', value: list[i]}; + } + return fields; + } + + /** + * @private + */ + function tableList(itemName, records, itemCallback) { + var count = records.length; + var fields = new Array(count + 1); + fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; + for (var i = 0; i < count; i++) { + fields[i + 1] = {name: itemName + i, type: 'TABLE', value: itemCallback(records[i], i)}; + } + return fields; + } + + /** + * @private + */ + function recordList(itemName, records, itemCallback) { + var count = records.length; + var fields = []; + fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; + for (var i = 0; i < count; i++) { + fields = fields.concat(itemCallback(records[i], i)); + } + return fields; + } + + // Common Layout Tables + + /** + * @exports opentype.Coverage + * @class + * @param {opentype.Table} + * @constructor + * @extends opentype.Table + */ + function Coverage(coverageTable) { + if (coverageTable.format === 1) { + Table.call(this, 'coverageTable', + [{name: 'coverageFormat', type: 'USHORT', value: 1}] + .concat(ushortList('glyph', coverageTable.glyphs)) + ); + } else { + check.assert(false, 'Can\'t create coverage table format 2 yet.'); + } + } + Coverage.prototype = Object.create(Table.prototype); + Coverage.prototype.constructor = Coverage; + + function ScriptList(scriptListTable) { + Table.call(this, 'scriptListTable', + recordList('scriptRecord', scriptListTable, function(scriptRecord, i) { + var script = scriptRecord.script; + var defaultLangSys = script.defaultLangSys; + check.assert(!!defaultLangSys, 'Unable to write GSUB: script ' + scriptRecord.tag + ' has no default language system.'); + return [ + {name: 'scriptTag' + i, type: 'TAG', value: scriptRecord.tag}, + {name: 'script' + i, type: 'TABLE', value: new Table('scriptTable', [ + {name: 'defaultLangSys', type: 'TABLE', value: new Table('defaultLangSys', [ + {name: 'lookupOrder', type: 'USHORT', value: 0}, + {name: 'reqFeatureIndex', type: 'USHORT', value: defaultLangSys.reqFeatureIndex}] + .concat(ushortList('featureIndex', defaultLangSys.featureIndexes)))} + ].concat(recordList('langSys', script.langSysRecords, function(langSysRecord, i) { + var langSys = langSysRecord.langSys; + return [ + {name: 'langSysTag' + i, type: 'TAG', value: langSysRecord.tag}, + {name: 'langSys' + i, type: 'TABLE', value: new Table('langSys', [ + {name: 'lookupOrder', type: 'USHORT', value: 0}, + {name: 'reqFeatureIndex', type: 'USHORT', value: langSys.reqFeatureIndex} + ].concat(ushortList('featureIndex', langSys.featureIndexes)))} + ]; + })))} + ]; + }) + ); + } + ScriptList.prototype = Object.create(Table.prototype); + ScriptList.prototype.constructor = ScriptList; + + /** + * @exports opentype.FeatureList + * @class + * @param {opentype.Table} + * @constructor + * @extends opentype.Table + */ + function FeatureList(featureListTable) { + Table.call(this, 'featureListTable', + recordList('featureRecord', featureListTable, function(featureRecord, i) { + var feature = featureRecord.feature; + return [ + {name: 'featureTag' + i, type: 'TAG', value: featureRecord.tag}, + {name: 'feature' + i, type: 'TABLE', value: new Table('featureTable', [ + {name: 'featureParams', type: 'USHORT', value: feature.featureParams} ].concat(ushortList('lookupListIndex', feature.lookupListIndexes)))} + ]; + }) + ); + } + FeatureList.prototype = Object.create(Table.prototype); + FeatureList.prototype.constructor = FeatureList; + + /** + * @exports opentype.LookupList + * @class + * @param {opentype.Table} + * @param {Object} + * @constructor + * @extends opentype.Table + */ + function LookupList(lookupListTable, subtableMakers) { + Table.call(this, 'lookupListTable', tableList('lookup', lookupListTable, function(lookupTable) { + var subtableCallback = subtableMakers[lookupTable.lookupType]; + check.assert(!!subtableCallback, 'Unable to write GSUB lookup type ' + lookupTable.lookupType + ' tables.'); + return new Table('lookupTable', [ + {name: 'lookupType', type: 'USHORT', value: lookupTable.lookupType}, + {name: 'lookupFlag', type: 'USHORT', value: lookupTable.lookupFlag} + ].concat(tableList('subtable', lookupTable.subtables, subtableCallback))); + })); + } + LookupList.prototype = Object.create(Table.prototype); + LookupList.prototype.constructor = LookupList; + + // Record = same as Table, but inlined (a Table has an offset and its data is further in the stream) + // Don't use offsets inside Records (probable bug), only in Tables. + var table = { + Table: Table, + Record: Table, + Coverage: Coverage, + ScriptList: ScriptList, + FeatureList: FeatureList, + LookupList: LookupList, + ushortList: ushortList, + tableList: tableList, + recordList: recordList, + }; + + // Parsing utility functions + + // Retrieve an unsigned byte from the DataView. + function getByte(dataView, offset) { + return dataView.getUint8(offset); + } + + // Retrieve an unsigned 16-bit short from the DataView. + // The value is stored in big endian. + function getUShort(dataView, offset) { + return dataView.getUint16(offset, false); + } + + // Retrieve a signed 16-bit short from the DataView. + // The value is stored in big endian. + function getShort(dataView, offset) { + return dataView.getInt16(offset, false); + } + + // Retrieve an unsigned 32-bit long from the DataView. + // The value is stored in big endian. + function getULong(dataView, offset) { + return dataView.getUint32(offset, false); + } + + // Retrieve a 32-bit signed fixed-point number (16.16) from the DataView. + // The value is stored in big endian. + function getFixed(dataView, offset) { + var decimal = dataView.getInt16(offset, false); + var fraction = dataView.getUint16(offset + 2, false); + return decimal + fraction / 65535; + } + + // Retrieve a 4-character tag from the DataView. + // Tags are used to identify tables. + function getTag(dataView, offset) { + var tag = ''; + for (var i = offset; i < offset + 4; i += 1) { + tag += String.fromCharCode(dataView.getInt8(i)); + } + + return tag; + } + + // Retrieve an offset from the DataView. + // Offsets are 1 to 4 bytes in length, depending on the offSize argument. + function getOffset(dataView, offset, offSize) { + var v = 0; + for (var i = 0; i < offSize; i += 1) { + v <<= 8; + v += dataView.getUint8(offset + i); + } + + return v; + } + + // Retrieve a number of bytes from start offset to the end offset from the DataView. + function getBytes(dataView, startOffset, endOffset) { + var bytes = []; + for (var i = startOffset; i < endOffset; i += 1) { + bytes.push(dataView.getUint8(i)); + } + + return bytes; + } + + // Convert the list of bytes to a string. + function bytesToString(bytes) { + var s = ''; + for (var i = 0; i < bytes.length; i += 1) { + s += String.fromCharCode(bytes[i]); + } + + return s; + } + + var typeOffsets = { + byte: 1, + uShort: 2, + short: 2, + uLong: 4, + fixed: 4, + longDateTime: 8, + tag: 4 + }; + + // A stateful parser that changes the offset whenever a value is retrieved. + // The data is a DataView. + function Parser(data, offset) { + this.data = data; + this.offset = offset; + this.relativeOffset = 0; + } + + Parser.prototype.parseByte = function() { + var v = this.data.getUint8(this.offset + this.relativeOffset); + this.relativeOffset += 1; + return v; + }; + + Parser.prototype.parseChar = function() { + var v = this.data.getInt8(this.offset + this.relativeOffset); + this.relativeOffset += 1; + return v; + }; + + Parser.prototype.parseCard8 = Parser.prototype.parseByte; + + Parser.prototype.parseUShort = function() { + var v = this.data.getUint16(this.offset + this.relativeOffset); + this.relativeOffset += 2; + return v; + }; + + Parser.prototype.parseCard16 = Parser.prototype.parseUShort; + Parser.prototype.parseSID = Parser.prototype.parseUShort; + Parser.prototype.parseOffset16 = Parser.prototype.parseUShort; + + Parser.prototype.parseShort = function() { + var v = this.data.getInt16(this.offset + this.relativeOffset); + this.relativeOffset += 2; + return v; + }; + + Parser.prototype.parseF2Dot14 = function() { + var v = this.data.getInt16(this.offset + this.relativeOffset) / 16384; + this.relativeOffset += 2; + return v; + }; + + Parser.prototype.parseULong = function() { + var v = getULong(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v; + }; + + Parser.prototype.parseOffset32 = Parser.prototype.parseULong; + + Parser.prototype.parseFixed = function() { + var v = getFixed(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v; + }; + + Parser.prototype.parseString = function(length) { + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + var string = ''; + this.relativeOffset += length; + for (var i = 0; i < length; i++) { + string += String.fromCharCode(dataView.getUint8(offset + i)); + } + + return string; + }; + + Parser.prototype.parseTag = function() { + return this.parseString(4); + }; + + // LONGDATETIME is a 64-bit integer. + // JavaScript and unix timestamps traditionally use 32 bits, so we + // only take the last 32 bits. + // + Since until 2038 those bits will be filled by zeros we can ignore them. + Parser.prototype.parseLongDateTime = function() { + var v = getULong(this.data, this.offset + this.relativeOffset + 4); + // Subtract seconds between 01/01/1904 and 01/01/1970 + // to convert Apple Mac timestamp to Standard Unix timestamp + v -= 2082844800; + this.relativeOffset += 8; + return v; + }; + + Parser.prototype.parseVersion = function(minorBase) { + var major = getUShort(this.data, this.offset + this.relativeOffset); + + // How to interpret the minor version is very vague in the spec. 0x5000 is 5, 0x1000 is 1 + // Default returns the correct number if minor = 0xN000 where N is 0-9 + // Set minorBase to 1 for tables that use minor = N where N is 0-9 + var minor = getUShort(this.data, this.offset + this.relativeOffset + 2); + this.relativeOffset += 4; + if (minorBase === undefined) { minorBase = 0x1000; } + return major + minor / minorBase / 10; + }; + + Parser.prototype.skip = function(type, amount) { + if (amount === undefined) { + amount = 1; + } + + this.relativeOffset += typeOffsets[type] * amount; + }; + + ///// Parsing lists and records /////////////////////////////// + + // Parse a list of 32 bit unsigned integers. + Parser.prototype.parseULongList = function(count) { + if (count === undefined) { count = this.parseULong(); } + var offsets = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + offsets[i] = dataView.getUint32(offset); + offset += 4; + } + + this.relativeOffset += count * 4; + return offsets; + }; + + // Parse a list of 16 bit unsigned integers. The length of the list can be read on the stream + // or provided as an argument. + Parser.prototype.parseOffset16List = + Parser.prototype.parseUShortList = function(count) { + if (count === undefined) { count = this.parseUShort(); } + var offsets = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + offsets[i] = dataView.getUint16(offset); + offset += 2; + } + + this.relativeOffset += count * 2; + return offsets; + }; + + // Parses a list of 16 bit signed integers. + Parser.prototype.parseShortList = function(count) { + var list = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + list[i] = dataView.getInt16(offset); + offset += 2; + } + + this.relativeOffset += count * 2; + return list; + }; + + // Parses a list of bytes. + Parser.prototype.parseByteList = function(count) { + var list = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + list[i] = dataView.getUint8(offset++); + } + + this.relativeOffset += count; + return list; + }; + + /** + * Parse a list of items. + * Record count is optional, if omitted it is read from the stream. + * itemCallback is one of the Parser methods. + */ + Parser.prototype.parseList = function(count, itemCallback) { + if (!itemCallback) { + itemCallback = count; + count = this.parseUShort(); + } + var list = new Array(count); + for (var i = 0; i < count; i++) { + list[i] = itemCallback.call(this); + } + return list; + }; + + Parser.prototype.parseList32 = function(count, itemCallback) { + if (!itemCallback) { + itemCallback = count; + count = this.parseULong(); + } + var list = new Array(count); + for (var i = 0; i < count; i++) { + list[i] = itemCallback.call(this); + } + return list; + }; + + /** + * Parse a list of records. + * Record count is optional, if omitted it is read from the stream. + * Example of recordDescription: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } + */ + Parser.prototype.parseRecordList = function(count, recordDescription) { + // If the count argument is absent, read it in the stream. + if (!recordDescription) { + recordDescription = count; + count = this.parseUShort(); + } + var records = new Array(count); + var fields = Object.keys(recordDescription); + for (var i = 0; i < count; i++) { + var rec = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = recordDescription[fieldName]; + rec[fieldName] = fieldType.call(this); + } + records[i] = rec; + } + return records; + }; + + Parser.prototype.parseRecordList32 = function(count, recordDescription) { + // If the count argument is absent, read it in the stream. + if (!recordDescription) { + recordDescription = count; + count = this.parseULong(); + } + var records = new Array(count); + var fields = Object.keys(recordDescription); + for (var i = 0; i < count; i++) { + var rec = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = recordDescription[fieldName]; + rec[fieldName] = fieldType.call(this); + } + records[i] = rec; + } + return records; + }; + + // Parse a data structure into an object + // Example of description: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } + Parser.prototype.parseStruct = function(description) { + if (typeof description === 'function') { + return description.call(this); + } else { + var fields = Object.keys(description); + var struct = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = description[fieldName]; + struct[fieldName] = fieldType.call(this); + } + return struct; + } + }; + + /** + * Parse a GPOS valueRecord + * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record + * valueFormat is optional, if omitted it is read from the stream. + */ + Parser.prototype.parseValueRecord = function(valueFormat) { + if (valueFormat === undefined) { + valueFormat = this.parseUShort(); + } + if (valueFormat === 0) { + // valueFormat2 in kerning pairs is most often 0 + // in this case return undefined instead of an empty object, to save space + return; + } + var valueRecord = {}; + + if (valueFormat & 0x0001) { valueRecord.xPlacement = this.parseShort(); } + if (valueFormat & 0x0002) { valueRecord.yPlacement = this.parseShort(); } + if (valueFormat & 0x0004) { valueRecord.xAdvance = this.parseShort(); } + if (valueFormat & 0x0008) { valueRecord.yAdvance = this.parseShort(); } + + // Device table (non-variable font) / VariationIndex table (variable font) not supported + // https://docs.microsoft.com/fr-fr/typography/opentype/spec/chapter2#devVarIdxTbls + if (valueFormat & 0x0010) { valueRecord.xPlaDevice = undefined; this.parseShort(); } + if (valueFormat & 0x0020) { valueRecord.yPlaDevice = undefined; this.parseShort(); } + if (valueFormat & 0x0040) { valueRecord.xAdvDevice = undefined; this.parseShort(); } + if (valueFormat & 0x0080) { valueRecord.yAdvDevice = undefined; this.parseShort(); } + + return valueRecord; + }; + + /** + * Parse a list of GPOS valueRecords + * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record + * valueFormat and valueCount are read from the stream. + */ + Parser.prototype.parseValueRecordList = function() { + var valueFormat = this.parseUShort(); + var valueCount = this.parseUShort(); + var values = new Array(valueCount); + for (var i = 0; i < valueCount; i++) { + values[i] = this.parseValueRecord(valueFormat); + } + return values; + }; + + Parser.prototype.parsePointer = function(description) { + var structOffset = this.parseOffset16(); + if (structOffset > 0) { + // NULL offset => return undefined + return new Parser(this.data, this.offset + structOffset).parseStruct(description); + } + return undefined; + }; + + Parser.prototype.parsePointer32 = function(description) { + var structOffset = this.parseOffset32(); + if (structOffset > 0) { + // NULL offset => return undefined + return new Parser(this.data, this.offset + structOffset).parseStruct(description); + } + return undefined; + }; + + /** + * Parse a list of offsets to lists of 16-bit integers, + * or a list of offsets to lists of offsets to any kind of items. + * If itemCallback is not provided, a list of list of UShort is assumed. + * If provided, itemCallback is called on each item and must parse the item. + * See examples in tables/gsub.js + */ + Parser.prototype.parseListOfLists = function(itemCallback) { + var offsets = this.parseOffset16List(); + var count = offsets.length; + var relativeOffset = this.relativeOffset; + var list = new Array(count); + for (var i = 0; i < count; i++) { + var start = offsets[i]; + if (start === 0) { + // NULL offset + // Add i as owned property to list. Convenient with assert. + list[i] = undefined; + continue; + } + this.relativeOffset = start; + if (itemCallback) { + var subOffsets = this.parseOffset16List(); + var subList = new Array(subOffsets.length); + for (var j = 0; j < subOffsets.length; j++) { + this.relativeOffset = start + subOffsets[j]; + subList[j] = itemCallback.call(this); + } + list[i] = subList; + } else { + list[i] = this.parseUShortList(); + } + } + this.relativeOffset = relativeOffset; + return list; + }; + + ///// Complex tables parsing ////////////////////////////////// + + // Parse a coverage table in a GSUB, GPOS or GDEF table. + // https://www.microsoft.com/typography/OTSPEC/chapter2.htm + // parser.offset must point to the start of the table containing the coverage. + Parser.prototype.parseCoverage = function() { + var startOffset = this.offset + this.relativeOffset; + var format = this.parseUShort(); + var count = this.parseUShort(); + if (format === 1) { + return { + format: 1, + glyphs: this.parseUShortList(count) + }; + } else if (format === 2) { + var ranges = new Array(count); + for (var i = 0; i < count; i++) { + ranges[i] = { + start: this.parseUShort(), + end: this.parseUShort(), + index: this.parseUShort() + }; + } + return { + format: 2, + ranges: ranges + }; + } + throw new Error('0x' + startOffset.toString(16) + ': Coverage format must be 1 or 2.'); + }; + + // Parse a Class Definition Table in a GSUB, GPOS or GDEF table. + // https://www.microsoft.com/typography/OTSPEC/chapter2.htm + Parser.prototype.parseClassDef = function() { + var startOffset = this.offset + this.relativeOffset; + var format = this.parseUShort(); + if (format === 1) { + return { + format: 1, + startGlyph: this.parseUShort(), + classes: this.parseUShortList() + }; + } else if (format === 2) { + return { + format: 2, + ranges: this.parseRecordList({ + start: Parser.uShort, + end: Parser.uShort, + classId: Parser.uShort + }) + }; + } + throw new Error('0x' + startOffset.toString(16) + ': ClassDef format must be 1 or 2.'); + }; + + ///// Static methods /////////////////////////////////// + // These convenience methods can be used as callbacks and should be called with "this" context set to a Parser instance. + + Parser.list = function(count, itemCallback) { + return function() { + return this.parseList(count, itemCallback); + }; + }; + + Parser.list32 = function(count, itemCallback) { + return function() { + return this.parseList32(count, itemCallback); + }; + }; + + Parser.recordList = function(count, recordDescription) { + return function() { + return this.parseRecordList(count, recordDescription); + }; + }; + + Parser.recordList32 = function(count, recordDescription) { + return function() { + return this.parseRecordList32(count, recordDescription); + }; + }; + + Parser.pointer = function(description) { + return function() { + return this.parsePointer(description); + }; + }; + + Parser.pointer32 = function(description) { + return function() { + return this.parsePointer32(description); + }; + }; + + Parser.tag = Parser.prototype.parseTag; + Parser.byte = Parser.prototype.parseByte; + Parser.uShort = Parser.offset16 = Parser.prototype.parseUShort; + Parser.uShortList = Parser.prototype.parseUShortList; + Parser.uLong = Parser.offset32 = Parser.prototype.parseULong; + Parser.uLongList = Parser.prototype.parseULongList; + Parser.struct = Parser.prototype.parseStruct; + Parser.coverage = Parser.prototype.parseCoverage; + Parser.classDef = Parser.prototype.parseClassDef; + + ///// Script, Feature, Lookup lists /////////////////////////////////////////////// + // https://www.microsoft.com/typography/OTSPEC/chapter2.htm + + var langSysTable = { + reserved: Parser.uShort, + reqFeatureIndex: Parser.uShort, + featureIndexes: Parser.uShortList + }; + + Parser.prototype.parseScriptList = function() { + return this.parsePointer(Parser.recordList({ + tag: Parser.tag, + script: Parser.pointer({ + defaultLangSys: Parser.pointer(langSysTable), + langSysRecords: Parser.recordList({ + tag: Parser.tag, + langSys: Parser.pointer(langSysTable) + }) + }) + })) || []; + }; + + Parser.prototype.parseFeatureList = function() { + return this.parsePointer(Parser.recordList({ + tag: Parser.tag, + feature: Parser.pointer({ + featureParams: Parser.offset16, + lookupListIndexes: Parser.uShortList + }) + })) || []; + }; + + Parser.prototype.parseLookupList = function(lookupTableParsers) { + return this.parsePointer(Parser.list(Parser.pointer(function() { + var lookupType = this.parseUShort(); + check.argument(1 <= lookupType && lookupType <= 9, 'GPOS/GSUB lookup type ' + lookupType + ' unknown.'); + var lookupFlag = this.parseUShort(); + var useMarkFilteringSet = lookupFlag & 0x10; + return { + lookupType: lookupType, + lookupFlag: lookupFlag, + subtables: this.parseList(Parser.pointer(lookupTableParsers[lookupType])), + markFilteringSet: useMarkFilteringSet ? this.parseUShort() : undefined + }; + }))) || []; + }; + + Parser.prototype.parseFeatureVariationsList = function() { + return this.parsePointer32(function() { + var majorVersion = this.parseUShort(); + var minorVersion = this.parseUShort(); + check.argument(majorVersion === 1 && minorVersion < 1, 'GPOS/GSUB feature variations table unknown.'); + var featureVariations = this.parseRecordList32({ + conditionSetOffset: Parser.offset32, + featureTableSubstitutionOffset: Parser.offset32 + }); + return featureVariations; + }) || []; + }; + + var parse = { + getByte: getByte, + getCard8: getByte, + getUShort: getUShort, + getCard16: getUShort, + getShort: getShort, + getULong: getULong, + getFixed: getFixed, + getTag: getTag, + getOffset: getOffset, + getBytes: getBytes, + bytesToString: bytesToString, + Parser: Parser, + }; + + // The `cmap` table stores the mappings from characters to glyphs. + + function parseCmapTableFormat12(cmap, p) { + //Skip reserved. + p.parseUShort(); + + // Length in bytes of the sub-tables. + cmap.length = p.parseULong(); + cmap.language = p.parseULong(); + + var groupCount; + cmap.groupCount = groupCount = p.parseULong(); + cmap.glyphIndexMap = {}; + + for (var i = 0; i < groupCount; i += 1) { + var startCharCode = p.parseULong(); + var endCharCode = p.parseULong(); + var startGlyphId = p.parseULong(); + + for (var c = startCharCode; c <= endCharCode; c += 1) { + cmap.glyphIndexMap[c] = startGlyphId; + startGlyphId++; + } + } + } + + function parseCmapTableFormat4(cmap, p, data, start, offset) { + // Length in bytes of the sub-tables. + cmap.length = p.parseUShort(); + cmap.language = p.parseUShort(); + + // segCount is stored x 2. + var segCount; + cmap.segCount = segCount = p.parseUShort() >> 1; + + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + + // The "unrolled" mapping from character codes to glyph indices. + cmap.glyphIndexMap = {}; + var endCountParser = new parse.Parser(data, start + offset + 14); + var startCountParser = new parse.Parser(data, start + offset + 16 + segCount * 2); + var idDeltaParser = new parse.Parser(data, start + offset + 16 + segCount * 4); + var idRangeOffsetParser = new parse.Parser(data, start + offset + 16 + segCount * 6); + var glyphIndexOffset = start + offset + 16 + segCount * 8; + for (var i = 0; i < segCount - 1; i += 1) { + var glyphIndex = (void 0); + var endCount = endCountParser.parseUShort(); + var startCount = startCountParser.parseUShort(); + var idDelta = idDeltaParser.parseShort(); + var idRangeOffset = idRangeOffsetParser.parseUShort(); + for (var c = startCount; c <= endCount; c += 1) { + if (idRangeOffset !== 0) { + // The idRangeOffset is relative to the current position in the idRangeOffset array. + // Take the current offset in the idRangeOffset array. + glyphIndexOffset = (idRangeOffsetParser.offset + idRangeOffsetParser.relativeOffset - 2); + + // Add the value of the idRangeOffset, which will move us into the glyphIndex array. + glyphIndexOffset += idRangeOffset; + + // Then add the character index of the current segment, multiplied by 2 for USHORTs. + glyphIndexOffset += (c - startCount) * 2; + glyphIndex = parse.getUShort(data, glyphIndexOffset); + if (glyphIndex !== 0) { + glyphIndex = (glyphIndex + idDelta) & 0xFFFF; + } + } else { + glyphIndex = (c + idDelta) & 0xFFFF; + } + + cmap.glyphIndexMap[c] = glyphIndex; + } + } + } + + // Parse the `cmap` table. This table stores the mappings from characters to glyphs. + // There are many available formats, but we only support the Windows format 4 and 12. + // This function returns a `CmapEncoding` object or null if no supported format could be found. + function parseCmapTable(data, start) { + var cmap = {}; + cmap.version = parse.getUShort(data, start); + check.argument(cmap.version === 0, 'cmap table version should be 0.'); + + // The cmap table can contain many sub-tables, each with their own format. + // We're only interested in a "platform 0" (Unicode format) and "platform 3" (Windows format) table. + cmap.numTables = parse.getUShort(data, start + 2); + var offset = -1; + for (var i = cmap.numTables - 1; i >= 0; i -= 1) { + var platformId = parse.getUShort(data, start + 4 + (i * 8)); + var encodingId = parse.getUShort(data, start + 4 + (i * 8) + 2); + if ((platformId === 3 && (encodingId === 0 || encodingId === 1 || encodingId === 10)) || + (platformId === 0 && (encodingId === 0 || encodingId === 1 || encodingId === 2 || encodingId === 3 || encodingId === 4))) { + offset = parse.getULong(data, start + 4 + (i * 8) + 4); + break; + } + } + + if (offset === -1) { + // There is no cmap table in the font that we support. + throw new Error('No valid cmap sub-tables found.'); + } + + var p = new parse.Parser(data, start + offset); + cmap.format = p.parseUShort(); + + if (cmap.format === 12) { + parseCmapTableFormat12(cmap, p); + } else if (cmap.format === 4) { + parseCmapTableFormat4(cmap, p, data, start, offset); + } else { + throw new Error('Only format 4 and 12 cmap tables are supported (found format ' + cmap.format + ').'); + } + + return cmap; + } + + function addSegment(t, code, glyphIndex) { + t.segments.push({ + end: code, + start: code, + delta: -(code - glyphIndex), + offset: 0, + glyphIndex: glyphIndex + }); + } + + function addTerminatorSegment(t) { + t.segments.push({ + end: 0xFFFF, + start: 0xFFFF, + delta: 1, + offset: 0 + }); + } + + // Make cmap table, format 4 by default, 12 if needed only + function makeCmapTable(glyphs) { + // Plan 0 is the base Unicode Plan but emojis, for example are on another plan, and needs cmap 12 format (with 32bit) + var isPlan0Only = true; + var i; + + // Check if we need to add cmap format 12 or if format 4 only is fine + for (i = glyphs.length - 1; i > 0; i -= 1) { + var g = glyphs.get(i); + if (g.unicode > 65535) { + console.log('Adding CMAP format 12 (needed!)'); + isPlan0Only = false; + break; + } + } + + var cmapTable = [ + {name: 'version', type: 'USHORT', value: 0}, + {name: 'numTables', type: 'USHORT', value: isPlan0Only ? 1 : 2}, + + // CMAP 4 header + {name: 'platformID', type: 'USHORT', value: 3}, + {name: 'encodingID', type: 'USHORT', value: 1}, + {name: 'offset', type: 'ULONG', value: isPlan0Only ? 12 : (12 + 8)} + ]; + + if (!isPlan0Only) + { cmapTable = cmapTable.concat([ + // CMAP 12 header + {name: 'cmap12PlatformID', type: 'USHORT', value: 3}, // We encode only for PlatformID = 3 (Windows) because it is supported everywhere + {name: 'cmap12EncodingID', type: 'USHORT', value: 10}, + {name: 'cmap12Offset', type: 'ULONG', value: 0} + ]); } + + cmapTable = cmapTable.concat([ + // CMAP 4 Subtable + {name: 'format', type: 'USHORT', value: 4}, + {name: 'cmap4Length', type: 'USHORT', value: 0}, + {name: 'language', type: 'USHORT', value: 0}, + {name: 'segCountX2', type: 'USHORT', value: 0}, + {name: 'searchRange', type: 'USHORT', value: 0}, + {name: 'entrySelector', type: 'USHORT', value: 0}, + {name: 'rangeShift', type: 'USHORT', value: 0} + ]); + + var t = new table.Table('cmap', cmapTable); + + t.segments = []; + for (i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + for (var j = 0; j < glyph.unicodes.length; j += 1) { + addSegment(t, glyph.unicodes[j], i); + } + + t.segments = t.segments.sort(function (a, b) { + return a.start - b.start; + }); + } + + addTerminatorSegment(t); + + var segCount = t.segments.length; + var segCountToRemove = 0; + + // CMAP 4 + // Set up parallel segment arrays. + var endCounts = []; + var startCounts = []; + var idDeltas = []; + var idRangeOffsets = []; + var glyphIds = []; + + // CMAP 12 + var cmap12Groups = []; + + // Reminder this loop is not following the specification at 100% + // The specification -> find suites of characters and make a group + // Here we're doing one group for each letter + // Doing as the spec can save 8 times (or more) space + for (i = 0; i < segCount; i += 1) { + var segment = t.segments[i]; + + // CMAP 4 + if (segment.end <= 65535 && segment.start <= 65535) { + endCounts = endCounts.concat({name: 'end_' + i, type: 'USHORT', value: segment.end}); + startCounts = startCounts.concat({name: 'start_' + i, type: 'USHORT', value: segment.start}); + idDeltas = idDeltas.concat({name: 'idDelta_' + i, type: 'SHORT', value: segment.delta}); + idRangeOffsets = idRangeOffsets.concat({name: 'idRangeOffset_' + i, type: 'USHORT', value: segment.offset}); + if (segment.glyphId !== undefined) { + glyphIds = glyphIds.concat({name: 'glyph_' + i, type: 'USHORT', value: segment.glyphId}); + } + } else { + // Skip Unicode > 65535 (16bit unsigned max) for CMAP 4, will be added in CMAP 12 + segCountToRemove += 1; + } + + // CMAP 12 + // Skip Terminator Segment + if (!isPlan0Only && segment.glyphIndex !== undefined) { + cmap12Groups = cmap12Groups.concat({name: 'cmap12Start_' + i, type: 'ULONG', value: segment.start}); + cmap12Groups = cmap12Groups.concat({name: 'cmap12End_' + i, type: 'ULONG', value: segment.end}); + cmap12Groups = cmap12Groups.concat({name: 'cmap12Glyph_' + i, type: 'ULONG', value: segment.glyphIndex}); + } + } + + // CMAP 4 Subtable + t.segCountX2 = (segCount - segCountToRemove) * 2; + t.searchRange = Math.pow(2, Math.floor(Math.log((segCount - segCountToRemove)) / Math.log(2))) * 2; + t.entrySelector = Math.log(t.searchRange / 2) / Math.log(2); + t.rangeShift = t.segCountX2 - t.searchRange; + + t.fields = t.fields.concat(endCounts); + t.fields.push({name: 'reservedPad', type: 'USHORT', value: 0}); + t.fields = t.fields.concat(startCounts); + t.fields = t.fields.concat(idDeltas); + t.fields = t.fields.concat(idRangeOffsets); + t.fields = t.fields.concat(glyphIds); + + t.cmap4Length = 14 + // Subtable header + endCounts.length * 2 + + 2 + // reservedPad + startCounts.length * 2 + + idDeltas.length * 2 + + idRangeOffsets.length * 2 + + glyphIds.length * 2; + + if (!isPlan0Only) { + // CMAP 12 Subtable + var cmap12Length = 16 + // Subtable header + cmap12Groups.length * 4; + + t.cmap12Offset = 12 + (2 * 2) + 4 + t.cmap4Length; + t.fields = t.fields.concat([ + {name: 'cmap12Format', type: 'USHORT', value: 12}, + {name: 'cmap12Reserved', type: 'USHORT', value: 0}, + {name: 'cmap12Length', type: 'ULONG', value: cmap12Length}, + {name: 'cmap12Language', type: 'ULONG', value: 0}, + {name: 'cmap12nGroups', type: 'ULONG', value: cmap12Groups.length / 3} + ]); + + t.fields = t.fields.concat(cmap12Groups); + } + + return t; + } + + var cmap = { parse: parseCmapTable, make: makeCmapTable }; + + // Glyph encoding + + var cffStandardStrings = [ + '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', + 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', + 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', + 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling', + 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', + 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph', + 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', + 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', + 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', + 'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu', + 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', + 'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright', + 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', + 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex', + 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', + 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute', + 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', + 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', + 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', + 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff', 'onedotenleader', + 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', + 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', + 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', + 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', + 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', + 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', + 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', + 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', + 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', + 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', + 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', + 'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', + 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', + 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', + 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', + 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', + 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', + 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', + 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', + '001.001', '001.002', '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold']; + + var cffStandardEncoding = [ + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', + 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', + 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', + 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', + 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', + 'daggerdbl', 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', + 'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', 'tilde', + 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', + 'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', 'ordfeminine', '', '', '', + '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', + 'lslash', 'oslash', 'oe', 'germandbls']; + + var cffExpertEncoding = [ + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle', 'dollarsuperior', + 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', + 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', + 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', + 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', '', 'asuperior', + 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', '', '', 'isuperior', '', '', 'lsuperior', 'msuperior', + 'nsuperior', 'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior', '', 'ff', 'fi', 'fl', 'ffi', 'ffl', + 'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', + 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', + 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', + 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', '', '', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', + 'Brevesmall', 'Caronsmall', '', 'Dotaccentsmall', '', '', 'Macronsmall', '', '', 'figuredash', 'hypheninferior', + '', '', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters', + 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', '', + '', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', + 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', + 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', + 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', + 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', + 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', + 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', + 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', + 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall']; + + var standardNames = [ + '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', + 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', + 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', + 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', + 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', + 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', 'Odieresis', 'Udieresis', 'aacute', 'agrave', + 'acircumflex', 'adieresis', 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis', + 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex', 'odieresis', + 'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', + 'bullet', 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute', 'dieresis', 'notequal', + 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal', 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', + 'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', 'questiondown', + 'exclamdown', 'logicalnot', 'radical', 'florin', 'approxequal', 'Delta', 'guillemotleft', 'guillemotright', + 'ellipsis', 'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft', + 'quotedblright', 'quoteleft', 'quoteright', 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', + 'currency', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered', 'quotesinglbase', + 'quotedblbase', 'perthousand', 'Acircumflex', 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', + 'Icircumflex', 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute', 'Ucircumflex', + 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', + 'ogonek', 'caron', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar', 'Eth', 'eth', + 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply', 'onesuperior', 'twosuperior', 'threesuperior', + 'onehalf', 'onequarter', 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', 'scedilla', + 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat']; + + /** + * This is the encoding used for fonts created from scratch. + * It loops through all glyphs and finds the appropriate unicode value. + * Since it's linear time, other encodings will be faster. + * @exports opentype.DefaultEncoding + * @class + * @constructor + * @param {opentype.Font} + */ + function DefaultEncoding(font) { + this.font = font; + } + + DefaultEncoding.prototype.charToGlyphIndex = function(c) { + var code = c.codePointAt(0); + var glyphs = this.font.glyphs; + if (glyphs) { + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + for (var j = 0; j < glyph.unicodes.length; j += 1) { + if (glyph.unicodes[j] === code) { + return i; + } + } + } + } + return null; + }; + + /** + * @exports opentype.CmapEncoding + * @class + * @constructor + * @param {Object} cmap - a object with the cmap encoded data + */ + function CmapEncoding(cmap) { + this.cmap = cmap; + } + + /** + * @param {string} c - the character + * @return {number} The glyph index. + */ + CmapEncoding.prototype.charToGlyphIndex = function(c) { + return this.cmap.glyphIndexMap[c.codePointAt(0)] || 0; + }; + + /** + * @exports opentype.CffEncoding + * @class + * @constructor + * @param {string} encoding - The encoding + * @param {Array} charset - The character set. + */ + function CffEncoding(encoding, charset) { + this.encoding = encoding; + this.charset = charset; + } + + /** + * @param {string} s - The character + * @return {number} The index. + */ + CffEncoding.prototype.charToGlyphIndex = function(s) { + var code = s.codePointAt(0); + var charName = this.encoding[code]; + return this.charset.indexOf(charName); + }; + + /** + * @exports opentype.GlyphNames + * @class + * @constructor + * @param {Object} post + */ + function GlyphNames(post) { + switch (post.version) { + case 1: + this.names = standardNames.slice(); + break; + case 2: + this.names = new Array(post.numberOfGlyphs); + for (var i = 0; i < post.numberOfGlyphs; i++) { + if (post.glyphNameIndex[i] < standardNames.length) { + this.names[i] = standardNames[post.glyphNameIndex[i]]; + } else { + this.names[i] = post.names[post.glyphNameIndex[i] - standardNames.length]; + } + } + + break; + case 2.5: + this.names = new Array(post.numberOfGlyphs); + for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { + this.names[i$1] = standardNames[i$1 + post.glyphNameIndex[i$1]]; + } + + break; + case 3: + this.names = []; + break; + default: + this.names = []; + break; + } + } + + /** + * Gets the index of a glyph by name. + * @param {string} name - The glyph name + * @return {number} The index + */ + GlyphNames.prototype.nameToGlyphIndex = function(name) { + return this.names.indexOf(name); + }; + + /** + * @param {number} gid + * @return {string} + */ + GlyphNames.prototype.glyphIndexToName = function(gid) { + return this.names[gid]; + }; + + function addGlyphNamesAll(font) { + var glyph; + var glyphIndexMap = font.tables.cmap.glyphIndexMap; + var charCodes = Object.keys(glyphIndexMap); + + for (var i = 0; i < charCodes.length; i += 1) { + var c = charCodes[i]; + var glyphIndex = glyphIndexMap[c]; + glyph = font.glyphs.get(glyphIndex); + glyph.addUnicode(parseInt(c)); + } + + for (var i$1 = 0; i$1 < font.glyphs.length; i$1 += 1) { + glyph = font.glyphs.get(i$1); + if (font.cffEncoding) { + if (font.isCIDFont) { + glyph.name = 'gid' + i$1; + } else { + glyph.name = font.cffEncoding.charset[i$1]; + } + } else if (font.glyphNames.names) { + glyph.name = font.glyphNames.glyphIndexToName(i$1); + } + } + } + + function addGlyphNamesToUnicodeMap(font) { + font._IndexToUnicodeMap = {}; + + var glyphIndexMap = font.tables.cmap.glyphIndexMap; + var charCodes = Object.keys(glyphIndexMap); + + for (var i = 0; i < charCodes.length; i += 1) { + var c = charCodes[i]; + var glyphIndex = glyphIndexMap[c]; + if (font._IndexToUnicodeMap[glyphIndex] === undefined) { + font._IndexToUnicodeMap[glyphIndex] = { + unicodes: [parseInt(c)] + }; + } else { + font._IndexToUnicodeMap[glyphIndex].unicodes.push(parseInt(c)); + } + } + } + + /** + * @alias opentype.addGlyphNames + * @param {opentype.Font} + * @param {Object} + */ + function addGlyphNames(font, opt) { + if (opt.lowMemory) { + addGlyphNamesToUnicodeMap(font); + } else { + addGlyphNamesAll(font); + } + } + + // Drawing utility functions. + + // Draw a line on the given context from point `x1,y1` to point `x2,y2`. + function line(ctx, x1, y1, x2, y2) { + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); + } + + var draw = { line: line }; + + // The Glyph object + // import glyf from './tables/glyf' Can't be imported here, because it's a circular dependency + + function getPathDefinition(glyph, path) { + var _path = path || new Path(); + return { + configurable: true, + + get: function() { + if (typeof _path === 'function') { + _path = _path(); + } + + return _path; + }, + + set: function(p) { + _path = p; + } + }; + } + /** + * @typedef GlyphOptions + * @type Object + * @property {string} [name] - The glyph name + * @property {number} [unicode] + * @property {Array} [unicodes] + * @property {number} [xMin] + * @property {number} [yMin] + * @property {number} [xMax] + * @property {number} [yMax] + * @property {number} [advanceWidth] + */ + + // A Glyph is an individual mark that often corresponds to a character. + // Some glyphs, such as ligatures, are a combination of many characters. + // Glyphs are the basic building blocks of a font. + // + // The `Glyph` class contains utility methods for drawing the path and its points. + /** + * @exports opentype.Glyph + * @class + * @param {GlyphOptions} + * @constructor + */ + function Glyph(options) { + // By putting all the code on a prototype function (which is only declared once) + // we reduce the memory requirements for larger fonts by some 2% + this.bindConstructorValues(options); + } + + /** + * @param {GlyphOptions} + */ + Glyph.prototype.bindConstructorValues = function(options) { + this.index = options.index || 0; + + // These three values cannot be deferred for memory optimization: + this.name = options.name || null; + this.unicode = options.unicode || undefined; + this.unicodes = options.unicodes || options.unicode !== undefined ? [options.unicode] : []; + + // But by binding these values only when necessary, we reduce can + // the memory requirements by almost 3% for larger fonts. + if ('xMin' in options) { + this.xMin = options.xMin; + } + + if ('yMin' in options) { + this.yMin = options.yMin; + } + + if ('xMax' in options) { + this.xMax = options.xMax; + } + + if ('yMax' in options) { + this.yMax = options.yMax; + } + + if ('advanceWidth' in options) { + this.advanceWidth = options.advanceWidth; + } + + // The path for a glyph is the most memory intensive, and is bound as a value + // with a getter/setter to ensure we actually do path parsing only once the + // path is actually needed by anything. + Object.defineProperty(this, 'path', getPathDefinition(this, options.path)); + }; + + /** + * @param {number} + */ + Glyph.prototype.addUnicode = function(unicode) { + if (this.unicodes.length === 0) { + this.unicode = unicode; + } + + this.unicodes.push(unicode); + }; + + /** + * Calculate the minimum bounding box for this glyph. + * @return {opentype.BoundingBox} + */ + Glyph.prototype.getBoundingBox = function() { + return this.path.getBoundingBox(); + }; + + /** + * Convert the glyph to a Path we can draw on a drawing context. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {Object=} options - xScale, yScale to stretch the glyph. + * @param {opentype.Font} if hinting is to be used, the font + * @return {opentype.Path} + */ + Glyph.prototype.getPath = function(x, y, fontSize, options, font) { + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 72; + var commands; + var hPoints; + if (!options) { options = { }; } + var xScale = options.xScale; + var yScale = options.yScale; + + if (options.hinting && font && font.hinting) { + // in case of hinting, the hinting engine takes care + // of scaling the points (not the path) before hinting. + hPoints = this.path && font.hinting.exec(this, fontSize); + // in case the hinting engine failed hPoints is undefined + // and thus reverts to plain rending + } + + if (hPoints) { + // Call font.hinting.getCommands instead of `glyf.getPath(hPoints).commands` to avoid a circular dependency + commands = font.hinting.getCommands(hPoints); + x = Math.round(x); + y = Math.round(y); + // TODO in case of hinting xyScaling is not yet supported + xScale = yScale = 1; + } else { + commands = this.path.commands; + var scale = 1 / (this.path.unitsPerEm || 1000) * fontSize; + if (xScale === undefined) { xScale = scale; } + if (yScale === undefined) { yScale = scale; } + } + + var p = new Path(); + for (var i = 0; i < commands.length; i += 1) { + var cmd = commands[i]; + if (cmd.type === 'M') { + p.moveTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'L') { + p.lineTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'Q') { + p.quadraticCurveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), + x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'C') { + p.curveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), + x + (cmd.x2 * xScale), y + (-cmd.y2 * yScale), + x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'Z') { + p.closePath(); + } + } + + return p; + }; + + /** + * Split the glyph into contours. + * This function is here for backwards compatibility, and to + * provide raw access to the TrueType glyph outlines. + * @return {Array} + */ + Glyph.prototype.getContours = function() { + if (this.points === undefined) { + return []; + } + + var contours = []; + var currentContour = []; + for (var i = 0; i < this.points.length; i += 1) { + var pt = this.points[i]; + currentContour.push(pt); + if (pt.lastPointOfContour) { + contours.push(currentContour); + currentContour = []; + } + } + + check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); + return contours; + }; + + /** + * Calculate the xMin/yMin/xMax/yMax/lsb/rsb for a Glyph. + * @return {Object} + */ + Glyph.prototype.getMetrics = function() { + var commands = this.path.commands; + var xCoords = []; + var yCoords = []; + for (var i = 0; i < commands.length; i += 1) { + var cmd = commands[i]; + if (cmd.type !== 'Z') { + xCoords.push(cmd.x); + yCoords.push(cmd.y); + } + + if (cmd.type === 'Q' || cmd.type === 'C') { + xCoords.push(cmd.x1); + yCoords.push(cmd.y1); + } + + if (cmd.type === 'C') { + xCoords.push(cmd.x2); + yCoords.push(cmd.y2); + } + } + + var metrics = { + xMin: Math.min.apply(null, xCoords), + yMin: Math.min.apply(null, yCoords), + xMax: Math.max.apply(null, xCoords), + yMax: Math.max.apply(null, yCoords), + leftSideBearing: this.leftSideBearing + }; + + if (!isFinite(metrics.xMin)) { + metrics.xMin = 0; + } + + if (!isFinite(metrics.xMax)) { + metrics.xMax = this.advanceWidth; + } + + if (!isFinite(metrics.yMin)) { + metrics.yMin = 0; + } + + if (!isFinite(metrics.yMax)) { + metrics.yMax = 0; + } + + metrics.rightSideBearing = this.advanceWidth - metrics.leftSideBearing - (metrics.xMax - metrics.xMin); + return metrics; + }; + + /** + * Draw the glyph on the given context. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {Object=} options - xScale, yScale to stretch the glyph. + */ + Glyph.prototype.draw = function(ctx, x, y, fontSize, options) { + this.getPath(x, y, fontSize, options).draw(ctx); + }; + + /** + * Draw the points of the glyph. + * On-curve points will be drawn in blue, off-curve points will be drawn in red. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + */ + Glyph.prototype.drawPoints = function(ctx, x, y, fontSize) { + function drawCircles(l, x, y, scale) { + ctx.beginPath(); + for (var j = 0; j < l.length; j += 1) { + ctx.moveTo(x + (l[j].x * scale), y + (l[j].y * scale)); + ctx.arc(x + (l[j].x * scale), y + (l[j].y * scale), 2, 0, Math.PI * 2, false); + } + + ctx.closePath(); + ctx.fill(); + } + + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 24; + var scale = 1 / this.path.unitsPerEm * fontSize; + + var blueCircles = []; + var redCircles = []; + var path = this.path; + for (var i = 0; i < path.commands.length; i += 1) { + var cmd = path.commands[i]; + if (cmd.x !== undefined) { + blueCircles.push({x: cmd.x, y: -cmd.y}); + } + + if (cmd.x1 !== undefined) { + redCircles.push({x: cmd.x1, y: -cmd.y1}); + } + + if (cmd.x2 !== undefined) { + redCircles.push({x: cmd.x2, y: -cmd.y2}); + } + } + + ctx.fillStyle = 'blue'; + drawCircles(blueCircles, x, y, scale); + ctx.fillStyle = 'red'; + drawCircles(redCircles, x, y, scale); + }; + + /** + * Draw lines indicating important font measurements. + * Black lines indicate the origin of the coordinate system (point 0,0). + * Blue lines indicate the glyph bounding box. + * Green line indicates the advance width of the glyph. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + */ + Glyph.prototype.drawMetrics = function(ctx, x, y, fontSize) { + var scale; + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 24; + scale = 1 / this.path.unitsPerEm * fontSize; + ctx.lineWidth = 1; + + // Draw the origin + ctx.strokeStyle = 'black'; + draw.line(ctx, x, -10000, x, 10000); + draw.line(ctx, -10000, y, 10000, y); + + // This code is here due to memory optimization: by not using + // defaults in the constructor, we save a notable amount of memory. + var xMin = this.xMin || 0; + var yMin = this.yMin || 0; + var xMax = this.xMax || 0; + var yMax = this.yMax || 0; + var advanceWidth = this.advanceWidth || 0; + + // Draw the glyph box + ctx.strokeStyle = 'blue'; + draw.line(ctx, x + (xMin * scale), -10000, x + (xMin * scale), 10000); + draw.line(ctx, x + (xMax * scale), -10000, x + (xMax * scale), 10000); + draw.line(ctx, -10000, y + (-yMin * scale), 10000, y + (-yMin * scale)); + draw.line(ctx, -10000, y + (-yMax * scale), 10000, y + (-yMax * scale)); + + // Draw the advance width + ctx.strokeStyle = 'green'; + draw.line(ctx, x + (advanceWidth * scale), -10000, x + (advanceWidth * scale), 10000); + }; + + // The GlyphSet object + + // Define a property on the glyph that depends on the path being loaded. + function defineDependentProperty(glyph, externalName, internalName) { + Object.defineProperty(glyph, externalName, { + get: function() { + // Request the path property to make sure the path is loaded. + glyph.path; // jshint ignore:line + return glyph[internalName]; + }, + set: function(newValue) { + glyph[internalName] = newValue; + }, + enumerable: true, + configurable: true + }); + } + + /** + * A GlyphSet represents all glyphs available in the font, but modelled using + * a deferred glyph loader, for retrieving glyphs only once they are absolutely + * necessary, to keep the memory footprint down. + * @exports opentype.GlyphSet + * @class + * @param {opentype.Font} + * @param {Array} + */ + function GlyphSet(font, glyphs) { + this.font = font; + this.glyphs = {}; + if (Array.isArray(glyphs)) { + for (var i = 0; i < glyphs.length; i++) { + var glyph = glyphs[i]; + glyph.path.unitsPerEm = font.unitsPerEm; + this.glyphs[i] = glyph; + } + } + + this.length = (glyphs && glyphs.length) || 0; + } + + /** + * @param {number} index + * @return {opentype.Glyph} + */ + GlyphSet.prototype.get = function(index) { + // this.glyphs[index] is 'undefined' when low memory mode is on. glyph is pushed on request only. + if (this.glyphs[index] === undefined) { + this.font._push(index); + if (typeof this.glyphs[index] === 'function') { + this.glyphs[index] = this.glyphs[index](); + } + + var glyph = this.glyphs[index]; + var unicodeObj = this.font._IndexToUnicodeMap[index]; + + if (unicodeObj) { + for (var j = 0; j < unicodeObj.unicodes.length; j++) + { glyph.addUnicode(unicodeObj.unicodes[j]); } + } + + if (this.font.cffEncoding) { + if (this.font.isCIDFont) { + glyph.name = 'gid' + index; + } else { + glyph.name = this.font.cffEncoding.charset[index]; + } + } else if (this.font.glyphNames.names) { + glyph.name = this.font.glyphNames.glyphIndexToName(index); + } + + this.glyphs[index].advanceWidth = this.font._hmtxTableData[index].advanceWidth; + this.glyphs[index].leftSideBearing = this.font._hmtxTableData[index].leftSideBearing; + } else { + if (typeof this.glyphs[index] === 'function') { + this.glyphs[index] = this.glyphs[index](); + } + } + + return this.glyphs[index]; + }; + + /** + * @param {number} index + * @param {Object} + */ + GlyphSet.prototype.push = function(index, loader) { + this.glyphs[index] = loader; + this.length++; + }; + + /** + * @alias opentype.glyphLoader + * @param {opentype.Font} font + * @param {number} index + * @return {opentype.Glyph} + */ + function glyphLoader(font, index) { + return new Glyph({index: index, font: font}); + } + + /** + * Generate a stub glyph that can be filled with all metadata *except* + * the "points" and "path" properties, which must be loaded only once + * the glyph's path is actually requested for text shaping. + * @alias opentype.ttfGlyphLoader + * @param {opentype.Font} font + * @param {number} index + * @param {Function} parseGlyph + * @param {Object} data + * @param {number} position + * @param {Function} buildPath + * @return {opentype.Glyph} + */ + function ttfGlyphLoader(font, index, parseGlyph, data, position, buildPath) { + return function() { + var glyph = new Glyph({index: index, font: font}); + + glyph.path = function() { + parseGlyph(glyph, data, position); + var path = buildPath(font.glyphs, glyph); + path.unitsPerEm = font.unitsPerEm; + return path; + }; + + defineDependentProperty(glyph, 'xMin', '_xMin'); + defineDependentProperty(glyph, 'xMax', '_xMax'); + defineDependentProperty(glyph, 'yMin', '_yMin'); + defineDependentProperty(glyph, 'yMax', '_yMax'); + + return glyph; + }; + } + /** + * @alias opentype.cffGlyphLoader + * @param {opentype.Font} font + * @param {number} index + * @param {Function} parseCFFCharstring + * @param {string} charstring + * @return {opentype.Glyph} + */ + function cffGlyphLoader(font, index, parseCFFCharstring, charstring) { + return function() { + var glyph = new Glyph({index: index, font: font}); + + glyph.path = function() { + var path = parseCFFCharstring(font, glyph, charstring); + path.unitsPerEm = font.unitsPerEm; + return path; + }; + + return glyph; + }; + } + + var glyphset = { GlyphSet: GlyphSet, glyphLoader: glyphLoader, ttfGlyphLoader: ttfGlyphLoader, cffGlyphLoader: cffGlyphLoader }; + + // The `CFF` table contains the glyph outlines in PostScript format. + + // Custom equals function that can also check lists. + function equals(a, b) { + if (a === b) { + return true; + } else if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) { + return false; + } + + for (var i = 0; i < a.length; i += 1) { + if (!equals(a[i], b[i])) { + return false; + } + } + + return true; + } else { + return false; + } + } + + // Subroutines are encoded using the negative half of the number space. + // See type 2 chapter 4.7 "Subroutine operators". + function calcCFFSubroutineBias(subrs) { + var bias; + if (subrs.length < 1240) { + bias = 107; + } else if (subrs.length < 33900) { + bias = 1131; + } else { + bias = 32768; + } + + return bias; + } + + // Parse a `CFF` INDEX array. + // An index array consists of a list of offsets, then a list of objects at those offsets. + function parseCFFIndex(data, start, conversionFn) { + var offsets = []; + var objects = []; + var count = parse.getCard16(data, start); + var objectOffset; + var endOffset; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + var pos = start + 3; + for (var i = 0; i < count + 1; i += 1) { + offsets.push(parse.getOffset(data, pos, offsetSize)); + pos += offsetSize; + } + + // The total size of the index array is 4 header bytes + the value of the last offset. + endOffset = objectOffset + offsets[count]; + } else { + endOffset = start + 2; + } + + for (var i$1 = 0; i$1 < offsets.length - 1; i$1 += 1) { + var value = parse.getBytes(data, objectOffset + offsets[i$1], objectOffset + offsets[i$1 + 1]); + if (conversionFn) { + value = conversionFn(value); + } + + objects.push(value); + } + + return {objects: objects, startOffset: start, endOffset: endOffset}; + } + + function parseCFFIndexLowMemory(data, start) { + var offsets = []; + var count = parse.getCard16(data, start); + var objectOffset; + var endOffset; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + var pos = start + 3; + for (var i = 0; i < count + 1; i += 1) { + offsets.push(parse.getOffset(data, pos, offsetSize)); + pos += offsetSize; + } + + // The total size of the index array is 4 header bytes + the value of the last offset. + endOffset = objectOffset + offsets[count]; + } else { + endOffset = start + 2; + } + + return {offsets: offsets, startOffset: start, endOffset: endOffset}; + } + function getCffIndexObject(i, offsets, data, start, conversionFn) { + var count = parse.getCard16(data, start); + var objectOffset = 0; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + } + + var value = parse.getBytes(data, objectOffset + offsets[i], objectOffset + offsets[i + 1]); + if (conversionFn) { + value = conversionFn(value); + } + return value; + } + + // Parse a `CFF` DICT real value. + function parseFloatOperand(parser) { + var s = ''; + var eof = 15; + var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-']; + while (true) { + var b = parser.parseByte(); + var n1 = b >> 4; + var n2 = b & 15; + + if (n1 === eof) { + break; + } + + s += lookup[n1]; + + if (n2 === eof) { + break; + } + + s += lookup[n2]; + } + + return parseFloat(s); + } + + // Parse a `CFF` DICT operand. + function parseOperand(parser, b0) { + var b1; + var b2; + var b3; + var b4; + if (b0 === 28) { + b1 = parser.parseByte(); + b2 = parser.parseByte(); + return b1 << 8 | b2; + } + + if (b0 === 29) { + b1 = parser.parseByte(); + b2 = parser.parseByte(); + b3 = parser.parseByte(); + b4 = parser.parseByte(); + return b1 << 24 | b2 << 16 | b3 << 8 | b4; + } + + if (b0 === 30) { + return parseFloatOperand(parser); + } + + if (b0 >= 32 && b0 <= 246) { + return b0 - 139; + } + + if (b0 >= 247 && b0 <= 250) { + b1 = parser.parseByte(); + return (b0 - 247) * 256 + b1 + 108; + } + + if (b0 >= 251 && b0 <= 254) { + b1 = parser.parseByte(); + return -(b0 - 251) * 256 - b1 - 108; + } + + throw new Error('Invalid b0 ' + b0); + } + + // Convert the entries returned by `parseDict` to a proper dictionary. + // If a value is a list of one, it is unpacked. + function entriesToObject(entries) { + var o = {}; + for (var i = 0; i < entries.length; i += 1) { + var key = entries[i][0]; + var values = entries[i][1]; + var value = (void 0); + if (values.length === 1) { + value = values[0]; + } else { + value = values; + } + + if (o.hasOwnProperty(key) && !isNaN(o[key])) { + throw new Error('Object ' + o + ' already has key ' + key); + } + + o[key] = value; + } + + return o; + } + + // Parse a `CFF` DICT object. + // A dictionary contains key-value pairs in a compact tokenized format. + function parseCFFDict(data, start, size) { + start = start !== undefined ? start : 0; + var parser = new parse.Parser(data, start); + var entries = []; + var operands = []; + size = size !== undefined ? size : data.length; + + while (parser.relativeOffset < size) { + var op = parser.parseByte(); + + // The first byte for each dict item distinguishes between operator (key) and operand (value). + // Values <= 21 are operators. + if (op <= 21) { + // Two-byte operators have an initial escape byte of 12. + if (op === 12) { + op = 1200 + parser.parseByte(); + } + + entries.push([op, operands]); + operands = []; + } else { + // Since the operands (values) come before the operators (keys), we store all operands in a list + // until we encounter an operator. + operands.push(parseOperand(parser, op)); + } + } + + return entriesToObject(entries); + } + + // Given a String Index (SID), return the value of the string. + // Strings below index 392 are standard CFF strings and are not encoded in the font. + function getCFFString(strings, index) { + if (index <= 390) { + index = cffStandardStrings[index]; + } else { + index = strings[index - 391]; + } + + return index; + } + + // Interpret a dictionary and return a new dictionary with readable keys and values for missing entries. + // This function takes `meta` which is a list of objects containing `operand`, `name` and `default`. + function interpretDict(dict, meta, strings) { + var newDict = {}; + var value; + + // Because we also want to include missing values, we start out from the meta list + // and lookup values in the dict. + for (var i = 0; i < meta.length; i += 1) { + var m = meta[i]; + + if (Array.isArray(m.type)) { + var values = []; + values.length = m.type.length; + for (var j = 0; j < m.type.length; j++) { + value = dict[m.op] !== undefined ? dict[m.op][j] : undefined; + if (value === undefined) { + value = m.value !== undefined && m.value[j] !== undefined ? m.value[j] : null; + } + if (m.type[j] === 'SID') { + value = getCFFString(strings, value); + } + values[j] = value; + } + newDict[m.name] = values; + } else { + value = dict[m.op]; + if (value === undefined) { + value = m.value !== undefined ? m.value : null; + } + + if (m.type === 'SID') { + value = getCFFString(strings, value); + } + newDict[m.name] = value; + } + } + + return newDict; + } + + // Parse the CFF header. + function parseCFFHeader(data, start) { + var header = {}; + header.formatMajor = parse.getCard8(data, start); + header.formatMinor = parse.getCard8(data, start + 1); + header.size = parse.getCard8(data, start + 2); + header.offsetSize = parse.getCard8(data, start + 3); + header.startOffset = start; + header.endOffset = start + 4; + return header; + } + + var TOP_DICT_META = [ + {name: 'version', op: 0, type: 'SID'}, + {name: 'notice', op: 1, type: 'SID'}, + {name: 'copyright', op: 1200, type: 'SID'}, + {name: 'fullName', op: 2, type: 'SID'}, + {name: 'familyName', op: 3, type: 'SID'}, + {name: 'weight', op: 4, type: 'SID'}, + {name: 'isFixedPitch', op: 1201, type: 'number', value: 0}, + {name: 'italicAngle', op: 1202, type: 'number', value: 0}, + {name: 'underlinePosition', op: 1203, type: 'number', value: -100}, + {name: 'underlineThickness', op: 1204, type: 'number', value: 50}, + {name: 'paintType', op: 1205, type: 'number', value: 0}, + {name: 'charstringType', op: 1206, type: 'number', value: 2}, + { + name: 'fontMatrix', + op: 1207, + type: ['real', 'real', 'real', 'real', 'real', 'real'], + value: [0.001, 0, 0, 0.001, 0, 0] + }, + {name: 'uniqueId', op: 13, type: 'number'}, + {name: 'fontBBox', op: 5, type: ['number', 'number', 'number', 'number'], value: [0, 0, 0, 0]}, + {name: 'strokeWidth', op: 1208, type: 'number', value: 0}, + {name: 'xuid', op: 14, type: [], value: null}, + {name: 'charset', op: 15, type: 'offset', value: 0}, + {name: 'encoding', op: 16, type: 'offset', value: 0}, + {name: 'charStrings', op: 17, type: 'offset', value: 0}, + {name: 'private', op: 18, type: ['number', 'offset'], value: [0, 0]}, + {name: 'ros', op: 1230, type: ['SID', 'SID', 'number']}, + {name: 'cidFontVersion', op: 1231, type: 'number', value: 0}, + {name: 'cidFontRevision', op: 1232, type: 'number', value: 0}, + {name: 'cidFontType', op: 1233, type: 'number', value: 0}, + {name: 'cidCount', op: 1234, type: 'number', value: 8720}, + {name: 'uidBase', op: 1235, type: 'number'}, + {name: 'fdArray', op: 1236, type: 'offset'}, + {name: 'fdSelect', op: 1237, type: 'offset'}, + {name: 'fontName', op: 1238, type: 'SID'} + ]; + + var PRIVATE_DICT_META = [ + {name: 'subrs', op: 19, type: 'offset', value: 0}, + {name: 'defaultWidthX', op: 20, type: 'number', value: 0}, + {name: 'nominalWidthX', op: 21, type: 'number', value: 0} + ]; + + // Parse the CFF top dictionary. A CFF table can contain multiple fonts, each with their own top dictionary. + // The top dictionary contains the essential metadata for the font, together with the private dictionary. + function parseCFFTopDict(data, strings) { + var dict = parseCFFDict(data, 0, data.byteLength); + return interpretDict(dict, TOP_DICT_META, strings); + } + + // Parse the CFF private dictionary. We don't fully parse out all the values, only the ones we need. + function parseCFFPrivateDict(data, start, size, strings) { + var dict = parseCFFDict(data, start, size); + return interpretDict(dict, PRIVATE_DICT_META, strings); + } + + // Returns a list of "Top DICT"s found using an INDEX list. + // Used to read both the usual high-level Top DICTs and also the FDArray + // discovered inside CID-keyed fonts. When a Top DICT has a reference to + // a Private DICT that is read and saved into the Top DICT. + // + // In addition to the expected/optional values as outlined in TOP_DICT_META + // the following values might be saved into the Top DICT. + // + // _subrs [] array of local CFF subroutines from Private DICT + // _subrsBias bias value computed from number of subroutines + // (see calcCFFSubroutineBias() and parseCFFCharstring()) + // _defaultWidthX default widths for CFF characters + // _nominalWidthX bias added to width embedded within glyph description + // + // _privateDict saved copy of parsed Private DICT from Top DICT + function gatherCFFTopDicts(data, start, cffIndex, strings) { + var topDictArray = []; + for (var iTopDict = 0; iTopDict < cffIndex.length; iTopDict += 1) { + var topDictData = new DataView(new Uint8Array(cffIndex[iTopDict]).buffer); + var topDict = parseCFFTopDict(topDictData, strings); + topDict._subrs = []; + topDict._subrsBias = 0; + var privateSize = topDict.private[0]; + var privateOffset = topDict.private[1]; + if (privateSize !== 0 && privateOffset !== 0) { + var privateDict = parseCFFPrivateDict(data, privateOffset + start, privateSize, strings); + topDict._defaultWidthX = privateDict.defaultWidthX; + topDict._nominalWidthX = privateDict.nominalWidthX; + if (privateDict.subrs !== 0) { + var subrOffset = privateOffset + privateDict.subrs; + var subrIndex = parseCFFIndex(data, subrOffset + start); + topDict._subrs = subrIndex.objects; + topDict._subrsBias = calcCFFSubroutineBias(topDict._subrs); + } + topDict._privateDict = privateDict; + } + topDictArray.push(topDict); + } + return topDictArray; + } + + // Parse the CFF charset table, which contains internal names for all the glyphs. + // This function will return a list of glyph names. + // See Adobe TN #5176 chapter 13, "Charsets". + function parseCFFCharset(data, start, nGlyphs, strings) { + var sid; + var count; + var parser = new parse.Parser(data, start); + + // The .notdef glyph is not included, so subtract 1. + nGlyphs -= 1; + var charset = ['.notdef']; + + var format = parser.parseCard8(); + if (format === 0) { + for (var i = 0; i < nGlyphs; i += 1) { + sid = parser.parseSID(); + charset.push(getCFFString(strings, sid)); + } + } else if (format === 1) { + while (charset.length <= nGlyphs) { + sid = parser.parseSID(); + count = parser.parseCard8(); + for (var i$1 = 0; i$1 <= count; i$1 += 1) { + charset.push(getCFFString(strings, sid)); + sid += 1; + } + } + } else if (format === 2) { + while (charset.length <= nGlyphs) { + sid = parser.parseSID(); + count = parser.parseCard16(); + for (var i$2 = 0; i$2 <= count; i$2 += 1) { + charset.push(getCFFString(strings, sid)); + sid += 1; + } + } + } else { + throw new Error('Unknown charset format ' + format); + } + + return charset; + } + + // Parse the CFF encoding data. Only one encoding can be specified per font. + // See Adobe TN #5176 chapter 12, "Encodings". + function parseCFFEncoding(data, start, charset) { + var code; + var enc = {}; + var parser = new parse.Parser(data, start); + var format = parser.parseCard8(); + if (format === 0) { + var nCodes = parser.parseCard8(); + for (var i = 0; i < nCodes; i += 1) { + code = parser.parseCard8(); + enc[code] = i; + } + } else if (format === 1) { + var nRanges = parser.parseCard8(); + code = 1; + for (var i$1 = 0; i$1 < nRanges; i$1 += 1) { + var first = parser.parseCard8(); + var nLeft = parser.parseCard8(); + for (var j = first; j <= first + nLeft; j += 1) { + enc[j] = code; + code += 1; + } + } + } else { + throw new Error('Unknown encoding format ' + format); + } + + return new CffEncoding(enc, charset); + } + + // Take in charstring code and return a Glyph object. + // The encoding is described in the Type 2 Charstring Format + // https://www.microsoft.com/typography/OTSPEC/charstr2.htm + function parseCFFCharstring(font, glyph, code) { + var c1x; + var c1y; + var c2x; + var c2y; + var p = new Path(); + var stack = []; + var nStems = 0; + var haveWidth = false; + var open = false; + var x = 0; + var y = 0; + var subrs; + var subrsBias; + var defaultWidthX; + var nominalWidthX; + if (font.isCIDFont) { + var fdIndex = font.tables.cff.topDict._fdSelect[glyph.index]; + var fdDict = font.tables.cff.topDict._fdArray[fdIndex]; + subrs = fdDict._subrs; + subrsBias = fdDict._subrsBias; + defaultWidthX = fdDict._defaultWidthX; + nominalWidthX = fdDict._nominalWidthX; + } else { + subrs = font.tables.cff.topDict._subrs; + subrsBias = font.tables.cff.topDict._subrsBias; + defaultWidthX = font.tables.cff.topDict._defaultWidthX; + nominalWidthX = font.tables.cff.topDict._nominalWidthX; + } + var width = defaultWidthX; + + function newContour(x, y) { + if (open) { + p.closePath(); + } + + p.moveTo(x, y); + open = true; + } + + function parseStems() { + var hasWidthArg; + + // The number of stem operators on the stack is always even. + // If the value is uneven, that means a width is specified. + hasWidthArg = stack.length % 2 !== 0; + if (hasWidthArg && !haveWidth) { + width = stack.shift() + nominalWidthX; + } + + nStems += stack.length >> 1; + stack.length = 0; + haveWidth = true; + } + + function parse(code) { + var b1; + var b2; + var b3; + var b4; + var codeIndex; + var subrCode; + var jpx; + var jpy; + var c3x; + var c3y; + var c4x; + var c4y; + + var i = 0; + while (i < code.length) { + var v = code[i]; + i += 1; + switch (v) { + case 1: // hstem + parseStems(); + break; + case 3: // vstem + parseStems(); + break; + case 4: // vmoveto + if (stack.length > 1 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + + y += stack.pop(); + newContour(x, y); + break; + case 5: // rlineto + while (stack.length > 0) { + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + } + + break; + case 6: // hlineto + while (stack.length > 0) { + x += stack.shift(); + p.lineTo(x, y); + if (stack.length === 0) { + break; + } + + y += stack.shift(); + p.lineTo(x, y); + } + + break; + case 7: // vlineto + while (stack.length > 0) { + y += stack.shift(); + p.lineTo(x, y); + if (stack.length === 0) { + break; + } + + x += stack.shift(); + p.lineTo(x, y); + } + + break; + case 8: // rrcurveto + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 10: // callsubr + codeIndex = stack.pop() + subrsBias; + subrCode = subrs[codeIndex]; + if (subrCode) { + parse(subrCode); + } + + break; + case 11: // return + return; + case 12: // flex operators + v = code[i]; + i += 1; + switch (v) { + case 35: // flex + // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y + stack.shift(); // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = jpy + stack.shift(); // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + x = c4x + stack.shift(); // dx6 + y = c4y + stack.shift(); // dy6 + stack.shift(); // flex depth + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 34: // hflex + // |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |- + c1x = x + stack.shift(); // dx1 + c1y = y; // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y; // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = c2y; // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = y; // dy5 + x = c4x + stack.shift(); // dx6 + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 36: // hflex1 + // |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y; // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = c2y; // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + x = c4x + stack.shift(); // dx6 + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 37: // flex1 + // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y + stack.shift(); // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = jpy + stack.shift(); // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + if (Math.abs(c4x - x) > Math.abs(c4y - y)) { + x = c4x + stack.shift(); + } else { + y = c4y + stack.shift(); + } + + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + default: + console.log('Glyph ' + glyph.index + ': unknown operator ' + 1200 + v); + stack.length = 0; + } + break; + case 14: // endchar + if (stack.length > 0 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + + if (open) { + p.closePath(); + open = false; + } + + break; + case 18: // hstemhm + parseStems(); + break; + case 19: // hintmask + case 20: // cntrmask + parseStems(); + i += (nStems + 7) >> 3; + break; + case 21: // rmoveto + if (stack.length > 2 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + + y += stack.pop(); + x += stack.pop(); + newContour(x, y); + break; + case 22: // hmoveto + if (stack.length > 1 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + + x += stack.pop(); + newContour(x, y); + break; + case 23: // vstemhm + parseStems(); + break; + case 24: // rcurveline + while (stack.length > 2) { + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + break; + case 25: // rlinecurve + while (stack.length > 6) { + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + } + + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + break; + case 26: // vvcurveto + if (stack.length % 2) { + x += stack.shift(); + } + + while (stack.length > 0) { + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x; + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 27: // hhcurveto + if (stack.length % 2) { + y += stack.shift(); + } + + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y; + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 28: // shortint + b1 = code[i]; + b2 = code[i + 1]; + stack.push(((b1 << 24) | (b2 << 16)) >> 16); + i += 2; + break; + case 29: // callgsubr + codeIndex = stack.pop() + font.gsubrsBias; + subrCode = font.gsubrs[codeIndex]; + if (subrCode) { + parse(subrCode); + } + + break; + case 30: // vhcurveto + while (stack.length > 0) { + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + if (stack.length === 0) { + break; + } + + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + y = c2y + stack.shift(); + x = c2x + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 31: // hvcurveto + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + y = c2y + stack.shift(); + x = c2x + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + if (stack.length === 0) { + break; + } + + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + default: + if (v < 32) { + console.log('Glyph ' + glyph.index + ': unknown operator ' + v); + } else if (v < 247) { + stack.push(v - 139); + } else if (v < 251) { + b1 = code[i]; + i += 1; + stack.push((v - 247) * 256 + b1 + 108); + } else if (v < 255) { + b1 = code[i]; + i += 1; + stack.push(-(v - 251) * 256 - b1 - 108); + } else { + b1 = code[i]; + b2 = code[i + 1]; + b3 = code[i + 2]; + b4 = code[i + 3]; + i += 4; + stack.push(((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) / 65536); + } + } + } + } + + parse(code); + + glyph.advanceWidth = width; + return p; + } + + function parseCFFFDSelect(data, start, nGlyphs, fdArrayCount) { + var fdSelect = []; + var fdIndex; + var parser = new parse.Parser(data, start); + var format = parser.parseCard8(); + if (format === 0) { + // Simple list of nGlyphs elements + for (var iGid = 0; iGid < nGlyphs; iGid++) { + fdIndex = parser.parseCard8(); + if (fdIndex >= fdArrayCount) { + throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); + } + fdSelect.push(fdIndex); + } + } else if (format === 3) { + // Ranges + var nRanges = parser.parseCard16(); + var first = parser.parseCard16(); + if (first !== 0) { + throw new Error('CFF Table CID Font FDSelect format 3 range has bad initial GID ' + first); + } + var next; + for (var iRange = 0; iRange < nRanges; iRange++) { + fdIndex = parser.parseCard8(); + next = parser.parseCard16(); + if (fdIndex >= fdArrayCount) { + throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); + } + if (next > nGlyphs) { + throw new Error('CFF Table CID Font FDSelect format 3 range has bad GID ' + next); + } + for (; first < next; first++) { + fdSelect.push(fdIndex); + } + first = next; + } + if (next !== nGlyphs) { + throw new Error('CFF Table CID Font FDSelect format 3 range has bad final GID ' + next); + } + } else { + throw new Error('CFF Table CID Font FDSelect table has unsupported format ' + format); + } + return fdSelect; + } + + // Parse the `CFF` table, which contains the glyph outlines in PostScript format. + function parseCFFTable(data, start, font, opt) { + font.tables.cff = {}; + var header = parseCFFHeader(data, start); + var nameIndex = parseCFFIndex(data, header.endOffset, parse.bytesToString); + var topDictIndex = parseCFFIndex(data, nameIndex.endOffset); + var stringIndex = parseCFFIndex(data, topDictIndex.endOffset, parse.bytesToString); + var globalSubrIndex = parseCFFIndex(data, stringIndex.endOffset); + font.gsubrs = globalSubrIndex.objects; + font.gsubrsBias = calcCFFSubroutineBias(font.gsubrs); + + var topDictArray = gatherCFFTopDicts(data, start, topDictIndex.objects, stringIndex.objects); + if (topDictArray.length !== 1) { + throw new Error('CFF table has too many fonts in \'FontSet\' - count of fonts NameIndex.length = ' + topDictArray.length); + } + + var topDict = topDictArray[0]; + font.tables.cff.topDict = topDict; + + if (topDict._privateDict) { + font.defaultWidthX = topDict._privateDict.defaultWidthX; + font.nominalWidthX = topDict._privateDict.nominalWidthX; + } + + if (topDict.ros[0] !== undefined && topDict.ros[1] !== undefined) { + font.isCIDFont = true; + } + + if (font.isCIDFont) { + var fdArrayOffset = topDict.fdArray; + var fdSelectOffset = topDict.fdSelect; + if (fdArrayOffset === 0 || fdSelectOffset === 0) { + throw new Error('Font is marked as a CID font, but FDArray and/or FDSelect information is missing'); + } + fdArrayOffset += start; + var fdArrayIndex = parseCFFIndex(data, fdArrayOffset); + var fdArray = gatherCFFTopDicts(data, start, fdArrayIndex.objects, stringIndex.objects); + topDict._fdArray = fdArray; + fdSelectOffset += start; + topDict._fdSelect = parseCFFFDSelect(data, fdSelectOffset, font.numGlyphs, fdArray.length); + } + + var privateDictOffset = start + topDict.private[1]; + var privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict.private[0], stringIndex.objects); + font.defaultWidthX = privateDict.defaultWidthX; + font.nominalWidthX = privateDict.nominalWidthX; + + if (privateDict.subrs !== 0) { + var subrOffset = privateDictOffset + privateDict.subrs; + var subrIndex = parseCFFIndex(data, subrOffset); + font.subrs = subrIndex.objects; + font.subrsBias = calcCFFSubroutineBias(font.subrs); + } else { + font.subrs = []; + font.subrsBias = 0; + } + + // Offsets in the top dict are relative to the beginning of the CFF data, so add the CFF start offset. + var charStringsIndex; + if (opt.lowMemory) { + charStringsIndex = parseCFFIndexLowMemory(data, start + topDict.charStrings); + font.nGlyphs = charStringsIndex.offsets.length; + } else { + charStringsIndex = parseCFFIndex(data, start + topDict.charStrings); + font.nGlyphs = charStringsIndex.objects.length; + } + + var charset = parseCFFCharset(data, start + topDict.charset, font.nGlyphs, stringIndex.objects); + if (topDict.encoding === 0) { + // Standard encoding + font.cffEncoding = new CffEncoding(cffStandardEncoding, charset); + } else if (topDict.encoding === 1) { + // Expert encoding + font.cffEncoding = new CffEncoding(cffExpertEncoding, charset); + } else { + font.cffEncoding = parseCFFEncoding(data, start + topDict.encoding, charset); + } + + // Prefer the CMAP encoding to the CFF encoding. + font.encoding = font.encoding || font.cffEncoding; + + font.glyphs = new glyphset.GlyphSet(font); + if (opt.lowMemory) { + font._push = function(i) { + var charString = getCffIndexObject(i, charStringsIndex.offsets, data, start + topDict.charStrings); + font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); + }; + } else { + for (var i = 0; i < font.nGlyphs; i += 1) { + var charString = charStringsIndex.objects[i]; + font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); + } + } + } + + // Convert a string to a String ID (SID). + // The list of strings is modified in place. + function encodeString(s, strings) { + var sid; + + // Is the string in the CFF standard strings? + var i = cffStandardStrings.indexOf(s); + if (i >= 0) { + sid = i; + } + + // Is the string already in the string index? + i = strings.indexOf(s); + if (i >= 0) { + sid = i + cffStandardStrings.length; + } else { + sid = cffStandardStrings.length + strings.length; + strings.push(s); + } + + return sid; + } + + function makeHeader() { + return new table.Record('Header', [ + {name: 'major', type: 'Card8', value: 1}, + {name: 'minor', type: 'Card8', value: 0}, + {name: 'hdrSize', type: 'Card8', value: 4}, + {name: 'major', type: 'Card8', value: 1} + ]); + } + + function makeNameIndex(fontNames) { + var t = new table.Record('Name INDEX', [ + {name: 'names', type: 'INDEX', value: []} + ]); + t.names = []; + for (var i = 0; i < fontNames.length; i += 1) { + t.names.push({name: 'name_' + i, type: 'NAME', value: fontNames[i]}); + } + + return t; + } + + // Given a dictionary's metadata, create a DICT structure. + function makeDict(meta, attrs, strings) { + var m = {}; + for (var i = 0; i < meta.length; i += 1) { + var entry = meta[i]; + var value = attrs[entry.name]; + if (value !== undefined && !equals(value, entry.value)) { + if (entry.type === 'SID') { + value = encodeString(value, strings); + } + + m[entry.op] = {name: entry.name, type: entry.type, value: value}; + } + } + + return m; + } + + // The Top DICT houses the global font attributes. + function makeTopDict(attrs, strings) { + var t = new table.Record('Top DICT', [ + {name: 'dict', type: 'DICT', value: {}} + ]); + t.dict = makeDict(TOP_DICT_META, attrs, strings); + return t; + } + + function makeTopDictIndex(topDict) { + var t = new table.Record('Top DICT INDEX', [ + {name: 'topDicts', type: 'INDEX', value: []} + ]); + t.topDicts = [{name: 'topDict_0', type: 'TABLE', value: topDict}]; + return t; + } + + function makeStringIndex(strings) { + var t = new table.Record('String INDEX', [ + {name: 'strings', type: 'INDEX', value: []} + ]); + t.strings = []; + for (var i = 0; i < strings.length; i += 1) { + t.strings.push({name: 'string_' + i, type: 'STRING', value: strings[i]}); + } + + return t; + } + + function makeGlobalSubrIndex() { + // Currently we don't use subroutines. + return new table.Record('Global Subr INDEX', [ + {name: 'subrs', type: 'INDEX', value: []} + ]); + } + + function makeCharsets(glyphNames, strings) { + var t = new table.Record('Charsets', [ + {name: 'format', type: 'Card8', value: 0} + ]); + for (var i = 0; i < glyphNames.length; i += 1) { + var glyphName = glyphNames[i]; + var glyphSID = encodeString(glyphName, strings); + t.fields.push({name: 'glyph_' + i, type: 'SID', value: glyphSID}); + } + + return t; + } + + function glyphToOps(glyph) { + var ops = []; + var path = glyph.path; + ops.push({name: 'width', type: 'NUMBER', value: glyph.advanceWidth}); + var x = 0; + var y = 0; + for (var i = 0; i < path.commands.length; i += 1) { + var dx = (void 0); + var dy = (void 0); + var cmd = path.commands[i]; + if (cmd.type === 'Q') { + // CFF only supports bézier curves, so convert the quad to a bézier. + var _13 = 1 / 3; + var _23 = 2 / 3; + + // We're going to create a new command so we don't change the original path. + cmd = { + type: 'C', + x: cmd.x, + y: cmd.y, + x1: _13 * x + _23 * cmd.x1, + y1: _13 * y + _23 * cmd.y1, + x2: _13 * cmd.x + _23 * cmd.x1, + y2: _13 * cmd.y + _23 * cmd.y1 + }; + } + + if (cmd.type === 'M') { + dx = Math.round(cmd.x - x); + dy = Math.round(cmd.y - y); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rmoveto', type: 'OP', value: 21}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } else if (cmd.type === 'L') { + dx = Math.round(cmd.x - x); + dy = Math.round(cmd.y - y); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rlineto', type: 'OP', value: 5}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } else if (cmd.type === 'C') { + var dx1 = Math.round(cmd.x1 - x); + var dy1 = Math.round(cmd.y1 - y); + var dx2 = Math.round(cmd.x2 - cmd.x1); + var dy2 = Math.round(cmd.y2 - cmd.y1); + dx = Math.round(cmd.x - cmd.x2); + dy = Math.round(cmd.y - cmd.y2); + ops.push({name: 'dx1', type: 'NUMBER', value: dx1}); + ops.push({name: 'dy1', type: 'NUMBER', value: dy1}); + ops.push({name: 'dx2', type: 'NUMBER', value: dx2}); + ops.push({name: 'dy2', type: 'NUMBER', value: dy2}); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rrcurveto', type: 'OP', value: 8}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } + + // Contours are closed automatically. + } + + ops.push({name: 'endchar', type: 'OP', value: 14}); + return ops; + } + + function makeCharStringsIndex(glyphs) { + var t = new table.Record('CharStrings INDEX', [ + {name: 'charStrings', type: 'INDEX', value: []} + ]); + + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + var ops = glyphToOps(glyph); + t.charStrings.push({name: glyph.name, type: 'CHARSTRING', value: ops}); + } + + return t; + } + + function makePrivateDict(attrs, strings) { + var t = new table.Record('Private DICT', [ + {name: 'dict', type: 'DICT', value: {}} + ]); + t.dict = makeDict(PRIVATE_DICT_META, attrs, strings); + return t; + } + + function makeCFFTable(glyphs, options) { + var t = new table.Table('CFF ', [ + {name: 'header', type: 'RECORD'}, + {name: 'nameIndex', type: 'RECORD'}, + {name: 'topDictIndex', type: 'RECORD'}, + {name: 'stringIndex', type: 'RECORD'}, + {name: 'globalSubrIndex', type: 'RECORD'}, + {name: 'charsets', type: 'RECORD'}, + {name: 'charStringsIndex', type: 'RECORD'}, + {name: 'privateDict', type: 'RECORD'} + ]); + + var fontScale = 1 / options.unitsPerEm; + // We use non-zero values for the offsets so that the DICT encodes them. + // This is important because the size of the Top DICT plays a role in offset calculation, + // and the size shouldn't change after we've written correct offsets. + var attrs = { + version: options.version, + fullName: options.fullName, + familyName: options.familyName, + weight: options.weightName, + fontBBox: options.fontBBox || [0, 0, 0, 0], + fontMatrix: [fontScale, 0, 0, fontScale, 0, 0], + charset: 999, + encoding: 0, + charStrings: 999, + private: [0, 999] + }; + + var privateAttrs = {}; + + var glyphNames = []; + var glyph; + + // Skip first glyph (.notdef) + for (var i = 1; i < glyphs.length; i += 1) { + glyph = glyphs.get(i); + glyphNames.push(glyph.name); + } + + var strings = []; + + t.header = makeHeader(); + t.nameIndex = makeNameIndex([options.postScriptName]); + var topDict = makeTopDict(attrs, strings); + t.topDictIndex = makeTopDictIndex(topDict); + t.globalSubrIndex = makeGlobalSubrIndex(); + t.charsets = makeCharsets(glyphNames, strings); + t.charStringsIndex = makeCharStringsIndex(glyphs); + t.privateDict = makePrivateDict(privateAttrs, strings); + + // Needs to come at the end, to encode all custom strings used in the font. + t.stringIndex = makeStringIndex(strings); + + var startOffset = t.header.sizeOf() + + t.nameIndex.sizeOf() + + t.topDictIndex.sizeOf() + + t.stringIndex.sizeOf() + + t.globalSubrIndex.sizeOf(); + attrs.charset = startOffset; + + // We use the CFF standard encoding; proper encoding will be handled in cmap. + attrs.encoding = 0; + attrs.charStrings = attrs.charset + t.charsets.sizeOf(); + attrs.private[1] = attrs.charStrings + t.charStringsIndex.sizeOf(); + + // Recreate the Top DICT INDEX with the correct offsets. + topDict = makeTopDict(attrs, strings); + t.topDictIndex = makeTopDictIndex(topDict); + + return t; + } + + var cff = { parse: parseCFFTable, make: makeCFFTable }; + + // The `head` table contains global information about the font. + + // Parse the header `head` table + function parseHeadTable(data, start) { + var head = {}; + var p = new parse.Parser(data, start); + head.version = p.parseVersion(); + head.fontRevision = Math.round(p.parseFixed() * 1000) / 1000; + head.checkSumAdjustment = p.parseULong(); + head.magicNumber = p.parseULong(); + check.argument(head.magicNumber === 0x5F0F3CF5, 'Font header has wrong magic number.'); + head.flags = p.parseUShort(); + head.unitsPerEm = p.parseUShort(); + head.created = p.parseLongDateTime(); + head.modified = p.parseLongDateTime(); + head.xMin = p.parseShort(); + head.yMin = p.parseShort(); + head.xMax = p.parseShort(); + head.yMax = p.parseShort(); + head.macStyle = p.parseUShort(); + head.lowestRecPPEM = p.parseUShort(); + head.fontDirectionHint = p.parseShort(); + head.indexToLocFormat = p.parseShort(); + head.glyphDataFormat = p.parseShort(); + return head; + } + + function makeHeadTable(options) { + // Apple Mac timestamp epoch is 01/01/1904 not 01/01/1970 + var timestamp = Math.round(new Date().getTime() / 1000) + 2082844800; + var createdTimestamp = timestamp; + + if (options.createdTimestamp) { + createdTimestamp = options.createdTimestamp + 2082844800; + } + + return new table.Table('head', [ + {name: 'version', type: 'FIXED', value: 0x00010000}, + {name: 'fontRevision', type: 'FIXED', value: 0x00010000}, + {name: 'checkSumAdjustment', type: 'ULONG', value: 0}, + {name: 'magicNumber', type: 'ULONG', value: 0x5F0F3CF5}, + {name: 'flags', type: 'USHORT', value: 0}, + {name: 'unitsPerEm', type: 'USHORT', value: 1000}, + {name: 'created', type: 'LONGDATETIME', value: createdTimestamp}, + {name: 'modified', type: 'LONGDATETIME', value: timestamp}, + {name: 'xMin', type: 'SHORT', value: 0}, + {name: 'yMin', type: 'SHORT', value: 0}, + {name: 'xMax', type: 'SHORT', value: 0}, + {name: 'yMax', type: 'SHORT', value: 0}, + {name: 'macStyle', type: 'USHORT', value: 0}, + {name: 'lowestRecPPEM', type: 'USHORT', value: 0}, + {name: 'fontDirectionHint', type: 'SHORT', value: 2}, + {name: 'indexToLocFormat', type: 'SHORT', value: 0}, + {name: 'glyphDataFormat', type: 'SHORT', value: 0} + ], options); + } + + var head = { parse: parseHeadTable, make: makeHeadTable }; + + // The `hhea` table contains information for horizontal layout. + + // Parse the horizontal header `hhea` table + function parseHheaTable(data, start) { + var hhea = {}; + var p = new parse.Parser(data, start); + hhea.version = p.parseVersion(); + hhea.ascender = p.parseShort(); + hhea.descender = p.parseShort(); + hhea.lineGap = p.parseShort(); + hhea.advanceWidthMax = p.parseUShort(); + hhea.minLeftSideBearing = p.parseShort(); + hhea.minRightSideBearing = p.parseShort(); + hhea.xMaxExtent = p.parseShort(); + hhea.caretSlopeRise = p.parseShort(); + hhea.caretSlopeRun = p.parseShort(); + hhea.caretOffset = p.parseShort(); + p.relativeOffset += 8; + hhea.metricDataFormat = p.parseShort(); + hhea.numberOfHMetrics = p.parseUShort(); + return hhea; + } + + function makeHheaTable(options) { + return new table.Table('hhea', [ + {name: 'version', type: 'FIXED', value: 0x00010000}, + {name: 'ascender', type: 'FWORD', value: 0}, + {name: 'descender', type: 'FWORD', value: 0}, + {name: 'lineGap', type: 'FWORD', value: 0}, + {name: 'advanceWidthMax', type: 'UFWORD', value: 0}, + {name: 'minLeftSideBearing', type: 'FWORD', value: 0}, + {name: 'minRightSideBearing', type: 'FWORD', value: 0}, + {name: 'xMaxExtent', type: 'FWORD', value: 0}, + {name: 'caretSlopeRise', type: 'SHORT', value: 1}, + {name: 'caretSlopeRun', type: 'SHORT', value: 0}, + {name: 'caretOffset', type: 'SHORT', value: 0}, + {name: 'reserved1', type: 'SHORT', value: 0}, + {name: 'reserved2', type: 'SHORT', value: 0}, + {name: 'reserved3', type: 'SHORT', value: 0}, + {name: 'reserved4', type: 'SHORT', value: 0}, + {name: 'metricDataFormat', type: 'SHORT', value: 0}, + {name: 'numberOfHMetrics', type: 'USHORT', value: 0} + ], options); + } + + var hhea = { parse: parseHheaTable, make: makeHheaTable }; + + // The `hmtx` table contains the horizontal metrics for all glyphs. + + function parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs) { + var advanceWidth; + var leftSideBearing; + var p = new parse.Parser(data, start); + for (var i = 0; i < numGlyphs; i += 1) { + // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. + if (i < numMetrics) { + advanceWidth = p.parseUShort(); + leftSideBearing = p.parseShort(); + } + + var glyph = glyphs.get(i); + glyph.advanceWidth = advanceWidth; + glyph.leftSideBearing = leftSideBearing; + } + } + + function parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs) { + font._hmtxTableData = {}; + + var advanceWidth; + var leftSideBearing; + var p = new parse.Parser(data, start); + for (var i = 0; i < numGlyphs; i += 1) { + // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. + if (i < numMetrics) { + advanceWidth = p.parseUShort(); + leftSideBearing = p.parseShort(); + } + + font._hmtxTableData[i] = { + advanceWidth: advanceWidth, + leftSideBearing: leftSideBearing + }; + } + } + + // Parse the `hmtx` table, which contains the horizontal metrics for all glyphs. + // This function augments the glyph array, adding the advanceWidth and leftSideBearing to each glyph. + function parseHmtxTable(font, data, start, numMetrics, numGlyphs, glyphs, opt) { + if (opt.lowMemory) + { parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs); } + else + { parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs); } + } + + function makeHmtxTable(glyphs) { + var t = new table.Table('hmtx', []); + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + var advanceWidth = glyph.advanceWidth || 0; + var leftSideBearing = glyph.leftSideBearing || 0; + t.fields.push({name: 'advanceWidth_' + i, type: 'USHORT', value: advanceWidth}); + t.fields.push({name: 'leftSideBearing_' + i, type: 'SHORT', value: leftSideBearing}); + } + + return t; + } + + var hmtx = { parse: parseHmtxTable, make: makeHmtxTable }; + + // The `ltag` table stores IETF BCP-47 language tags. It allows supporting + + function makeLtagTable(tags) { + var result = new table.Table('ltag', [ + {name: 'version', type: 'ULONG', value: 1}, + {name: 'flags', type: 'ULONG', value: 0}, + {name: 'numTags', type: 'ULONG', value: tags.length} + ]); + + var stringPool = ''; + var stringPoolOffset = 12 + tags.length * 4; + for (var i = 0; i < tags.length; ++i) { + var pos = stringPool.indexOf(tags[i]); + if (pos < 0) { + pos = stringPool.length; + stringPool += tags[i]; + } + + result.fields.push({name: 'offset ' + i, type: 'USHORT', value: stringPoolOffset + pos}); + result.fields.push({name: 'length ' + i, type: 'USHORT', value: tags[i].length}); + } + + result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); + return result; + } + + function parseLtagTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 1, 'Unsupported ltag table version.'); + // The 'ltag' specification does not define any flags; skip the field. + p.skip('uLong', 1); + var numTags = p.parseULong(); + + var tags = []; + for (var i = 0; i < numTags; i++) { + var tag = ''; + var offset = start + p.parseUShort(); + var length = p.parseUShort(); + for (var j = offset; j < offset + length; ++j) { + tag += String.fromCharCode(data.getInt8(j)); + } + + tags.push(tag); + } + + return tags; + } + + var ltag = { make: makeLtagTable, parse: parseLtagTable }; + + // The `maxp` table establishes the memory requirements for the font. + + // Parse the maximum profile `maxp` table. + function parseMaxpTable(data, start) { + var maxp = {}; + var p = new parse.Parser(data, start); + maxp.version = p.parseVersion(); + maxp.numGlyphs = p.parseUShort(); + if (maxp.version === 1.0) { + maxp.maxPoints = p.parseUShort(); + maxp.maxContours = p.parseUShort(); + maxp.maxCompositePoints = p.parseUShort(); + maxp.maxCompositeContours = p.parseUShort(); + maxp.maxZones = p.parseUShort(); + maxp.maxTwilightPoints = p.parseUShort(); + maxp.maxStorage = p.parseUShort(); + maxp.maxFunctionDefs = p.parseUShort(); + maxp.maxInstructionDefs = p.parseUShort(); + maxp.maxStackElements = p.parseUShort(); + maxp.maxSizeOfInstructions = p.parseUShort(); + maxp.maxComponentElements = p.parseUShort(); + maxp.maxComponentDepth = p.parseUShort(); + } + + return maxp; + } + + function makeMaxpTable(numGlyphs) { + return new table.Table('maxp', [ + {name: 'version', type: 'FIXED', value: 0x00005000}, + {name: 'numGlyphs', type: 'USHORT', value: numGlyphs} + ]); + } + + var maxp = { parse: parseMaxpTable, make: makeMaxpTable }; + + // The `name` naming table. + + // NameIDs for the name table. + var nameTableNames = [ + 'copyright', // 0 + 'fontFamily', // 1 + 'fontSubfamily', // 2 + 'uniqueID', // 3 + 'fullName', // 4 + 'version', // 5 + 'postScriptName', // 6 + 'trademark', // 7 + 'manufacturer', // 8 + 'designer', // 9 + 'description', // 10 + 'manufacturerURL', // 11 + 'designerURL', // 12 + 'license', // 13 + 'licenseURL', // 14 + 'reserved', // 15 + 'preferredFamily', // 16 + 'preferredSubfamily', // 17 + 'compatibleFullName', // 18 + 'sampleText', // 19 + 'postScriptFindFontName', // 20 + 'wwsFamily', // 21 + 'wwsSubfamily' // 22 + ]; + + var macLanguages = { + 0: 'en', + 1: 'fr', + 2: 'de', + 3: 'it', + 4: 'nl', + 5: 'sv', + 6: 'es', + 7: 'da', + 8: 'pt', + 9: 'no', + 10: 'he', + 11: 'ja', + 12: 'ar', + 13: 'fi', + 14: 'el', + 15: 'is', + 16: 'mt', + 17: 'tr', + 18: 'hr', + 19: 'zh-Hant', + 20: 'ur', + 21: 'hi', + 22: 'th', + 23: 'ko', + 24: 'lt', + 25: 'pl', + 26: 'hu', + 27: 'es', + 28: 'lv', + 29: 'se', + 30: 'fo', + 31: 'fa', + 32: 'ru', + 33: 'zh', + 34: 'nl-BE', + 35: 'ga', + 36: 'sq', + 37: 'ro', + 38: 'cz', + 39: 'sk', + 40: 'si', + 41: 'yi', + 42: 'sr', + 43: 'mk', + 44: 'bg', + 45: 'uk', + 46: 'be', + 47: 'uz', + 48: 'kk', + 49: 'az-Cyrl', + 50: 'az-Arab', + 51: 'hy', + 52: 'ka', + 53: 'mo', + 54: 'ky', + 55: 'tg', + 56: 'tk', + 57: 'mn-CN', + 58: 'mn', + 59: 'ps', + 60: 'ks', + 61: 'ku', + 62: 'sd', + 63: 'bo', + 64: 'ne', + 65: 'sa', + 66: 'mr', + 67: 'bn', + 68: 'as', + 69: 'gu', + 70: 'pa', + 71: 'or', + 72: 'ml', + 73: 'kn', + 74: 'ta', + 75: 'te', + 76: 'si', + 77: 'my', + 78: 'km', + 79: 'lo', + 80: 'vi', + 81: 'id', + 82: 'tl', + 83: 'ms', + 84: 'ms-Arab', + 85: 'am', + 86: 'ti', + 87: 'om', + 88: 'so', + 89: 'sw', + 90: 'rw', + 91: 'rn', + 92: 'ny', + 93: 'mg', + 94: 'eo', + 128: 'cy', + 129: 'eu', + 130: 'ca', + 131: 'la', + 132: 'qu', + 133: 'gn', + 134: 'ay', + 135: 'tt', + 136: 'ug', + 137: 'dz', + 138: 'jv', + 139: 'su', + 140: 'gl', + 141: 'af', + 142: 'br', + 143: 'iu', + 144: 'gd', + 145: 'gv', + 146: 'ga', + 147: 'to', + 148: 'el-polyton', + 149: 'kl', + 150: 'az', + 151: 'nn' + }; + + // MacOS language ID → MacOS script ID + // + // Note that the script ID is not sufficient to determine what encoding + // to use in TrueType files. For some languages, MacOS used a modification + // of a mainstream script. For example, an Icelandic name would be stored + // with smRoman in the TrueType naming table, but the actual encoding + // is a special Icelandic version of the normal Macintosh Roman encoding. + // As another example, Inuktitut uses an 8-bit encoding for Canadian Aboriginal + // Syllables but MacOS had run out of available script codes, so this was + // done as a (pretty radical) "modification" of Ethiopic. + // + // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt + var macLanguageToScript = { + 0: 0, // langEnglish → smRoman + 1: 0, // langFrench → smRoman + 2: 0, // langGerman → smRoman + 3: 0, // langItalian → smRoman + 4: 0, // langDutch → smRoman + 5: 0, // langSwedish → smRoman + 6: 0, // langSpanish → smRoman + 7: 0, // langDanish → smRoman + 8: 0, // langPortuguese → smRoman + 9: 0, // langNorwegian → smRoman + 10: 5, // langHebrew → smHebrew + 11: 1, // langJapanese → smJapanese + 12: 4, // langArabic → smArabic + 13: 0, // langFinnish → smRoman + 14: 6, // langGreek → smGreek + 15: 0, // langIcelandic → smRoman (modified) + 16: 0, // langMaltese → smRoman + 17: 0, // langTurkish → smRoman (modified) + 18: 0, // langCroatian → smRoman (modified) + 19: 2, // langTradChinese → smTradChinese + 20: 4, // langUrdu → smArabic + 21: 9, // langHindi → smDevanagari + 22: 21, // langThai → smThai + 23: 3, // langKorean → smKorean + 24: 29, // langLithuanian → smCentralEuroRoman + 25: 29, // langPolish → smCentralEuroRoman + 26: 29, // langHungarian → smCentralEuroRoman + 27: 29, // langEstonian → smCentralEuroRoman + 28: 29, // langLatvian → smCentralEuroRoman + 29: 0, // langSami → smRoman + 30: 0, // langFaroese → smRoman (modified) + 31: 4, // langFarsi → smArabic (modified) + 32: 7, // langRussian → smCyrillic + 33: 25, // langSimpChinese → smSimpChinese + 34: 0, // langFlemish → smRoman + 35: 0, // langIrishGaelic → smRoman (modified) + 36: 0, // langAlbanian → smRoman + 37: 0, // langRomanian → smRoman (modified) + 38: 29, // langCzech → smCentralEuroRoman + 39: 29, // langSlovak → smCentralEuroRoman + 40: 0, // langSlovenian → smRoman (modified) + 41: 5, // langYiddish → smHebrew + 42: 7, // langSerbian → smCyrillic + 43: 7, // langMacedonian → smCyrillic + 44: 7, // langBulgarian → smCyrillic + 45: 7, // langUkrainian → smCyrillic (modified) + 46: 7, // langByelorussian → smCyrillic + 47: 7, // langUzbek → smCyrillic + 48: 7, // langKazakh → smCyrillic + 49: 7, // langAzerbaijani → smCyrillic + 50: 4, // langAzerbaijanAr → smArabic + 51: 24, // langArmenian → smArmenian + 52: 23, // langGeorgian → smGeorgian + 53: 7, // langMoldavian → smCyrillic + 54: 7, // langKirghiz → smCyrillic + 55: 7, // langTajiki → smCyrillic + 56: 7, // langTurkmen → smCyrillic + 57: 27, // langMongolian → smMongolian + 58: 7, // langMongolianCyr → smCyrillic + 59: 4, // langPashto → smArabic + 60: 4, // langKurdish → smArabic + 61: 4, // langKashmiri → smArabic + 62: 4, // langSindhi → smArabic + 63: 26, // langTibetan → smTibetan + 64: 9, // langNepali → smDevanagari + 65: 9, // langSanskrit → smDevanagari + 66: 9, // langMarathi → smDevanagari + 67: 13, // langBengali → smBengali + 68: 13, // langAssamese → smBengali + 69: 11, // langGujarati → smGujarati + 70: 10, // langPunjabi → smGurmukhi + 71: 12, // langOriya → smOriya + 72: 17, // langMalayalam → smMalayalam + 73: 16, // langKannada → smKannada + 74: 14, // langTamil → smTamil + 75: 15, // langTelugu → smTelugu + 76: 18, // langSinhalese → smSinhalese + 77: 19, // langBurmese → smBurmese + 78: 20, // langKhmer → smKhmer + 79: 22, // langLao → smLao + 80: 30, // langVietnamese → smVietnamese + 81: 0, // langIndonesian → smRoman + 82: 0, // langTagalog → smRoman + 83: 0, // langMalayRoman → smRoman + 84: 4, // langMalayArabic → smArabic + 85: 28, // langAmharic → smEthiopic + 86: 28, // langTigrinya → smEthiopic + 87: 28, // langOromo → smEthiopic + 88: 0, // langSomali → smRoman + 89: 0, // langSwahili → smRoman + 90: 0, // langKinyarwanda → smRoman + 91: 0, // langRundi → smRoman + 92: 0, // langNyanja → smRoman + 93: 0, // langMalagasy → smRoman + 94: 0, // langEsperanto → smRoman + 128: 0, // langWelsh → smRoman (modified) + 129: 0, // langBasque → smRoman + 130: 0, // langCatalan → smRoman + 131: 0, // langLatin → smRoman + 132: 0, // langQuechua → smRoman + 133: 0, // langGuarani → smRoman + 134: 0, // langAymara → smRoman + 135: 7, // langTatar → smCyrillic + 136: 4, // langUighur → smArabic + 137: 26, // langDzongkha → smTibetan + 138: 0, // langJavaneseRom → smRoman + 139: 0, // langSundaneseRom → smRoman + 140: 0, // langGalician → smRoman + 141: 0, // langAfrikaans → smRoman + 142: 0, // langBreton → smRoman (modified) + 143: 28, // langInuktitut → smEthiopic (modified) + 144: 0, // langScottishGaelic → smRoman (modified) + 145: 0, // langManxGaelic → smRoman (modified) + 146: 0, // langIrishGaelicScript → smRoman (modified) + 147: 0, // langTongan → smRoman + 148: 6, // langGreekAncient → smRoman + 149: 0, // langGreenlandic → smRoman + 150: 0, // langAzerbaijanRoman → smRoman + 151: 0 // langNynorsk → smRoman + }; + + // While Microsoft indicates a region/country for all its language + // IDs, we omit the region code if it's equal to the "most likely + // region subtag" according to Unicode CLDR. For scripts, we omit + // the subtag if it is equal to the Suppress-Script entry in the + // IANA language subtag registry for IETF BCP 47. + // + // For example, Microsoft states that its language code 0x041A is + // Croatian in Croatia. We transform this to the BCP 47 language code 'hr' + // and not 'hr-HR' because Croatia is the default country for Croatian, + // according to Unicode CLDR. As another example, Microsoft states + // that 0x101A is Croatian (Latin) in Bosnia-Herzegovina. We transform + // this to 'hr-BA' and not 'hr-Latn-BA' because Latin is the default script + // for the Croatian language, according to IANA. + // + // http://www.unicode.org/cldr/charts/latest/supplemental/likely_subtags.html + // http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry + var windowsLanguages = { + 0x0436: 'af', + 0x041C: 'sq', + 0x0484: 'gsw', + 0x045E: 'am', + 0x1401: 'ar-DZ', + 0x3C01: 'ar-BH', + 0x0C01: 'ar', + 0x0801: 'ar-IQ', + 0x2C01: 'ar-JO', + 0x3401: 'ar-KW', + 0x3001: 'ar-LB', + 0x1001: 'ar-LY', + 0x1801: 'ary', + 0x2001: 'ar-OM', + 0x4001: 'ar-QA', + 0x0401: 'ar-SA', + 0x2801: 'ar-SY', + 0x1C01: 'aeb', + 0x3801: 'ar-AE', + 0x2401: 'ar-YE', + 0x042B: 'hy', + 0x044D: 'as', + 0x082C: 'az-Cyrl', + 0x042C: 'az', + 0x046D: 'ba', + 0x042D: 'eu', + 0x0423: 'be', + 0x0845: 'bn', + 0x0445: 'bn-IN', + 0x201A: 'bs-Cyrl', + 0x141A: 'bs', + 0x047E: 'br', + 0x0402: 'bg', + 0x0403: 'ca', + 0x0C04: 'zh-HK', + 0x1404: 'zh-MO', + 0x0804: 'zh', + 0x1004: 'zh-SG', + 0x0404: 'zh-TW', + 0x0483: 'co', + 0x041A: 'hr', + 0x101A: 'hr-BA', + 0x0405: 'cs', + 0x0406: 'da', + 0x048C: 'prs', + 0x0465: 'dv', + 0x0813: 'nl-BE', + 0x0413: 'nl', + 0x0C09: 'en-AU', + 0x2809: 'en-BZ', + 0x1009: 'en-CA', + 0x2409: 'en-029', + 0x4009: 'en-IN', + 0x1809: 'en-IE', + 0x2009: 'en-JM', + 0x4409: 'en-MY', + 0x1409: 'en-NZ', + 0x3409: 'en-PH', + 0x4809: 'en-SG', + 0x1C09: 'en-ZA', + 0x2C09: 'en-TT', + 0x0809: 'en-GB', + 0x0409: 'en', + 0x3009: 'en-ZW', + 0x0425: 'et', + 0x0438: 'fo', + 0x0464: 'fil', + 0x040B: 'fi', + 0x080C: 'fr-BE', + 0x0C0C: 'fr-CA', + 0x040C: 'fr', + 0x140C: 'fr-LU', + 0x180C: 'fr-MC', + 0x100C: 'fr-CH', + 0x0462: 'fy', + 0x0456: 'gl', + 0x0437: 'ka', + 0x0C07: 'de-AT', + 0x0407: 'de', + 0x1407: 'de-LI', + 0x1007: 'de-LU', + 0x0807: 'de-CH', + 0x0408: 'el', + 0x046F: 'kl', + 0x0447: 'gu', + 0x0468: 'ha', + 0x040D: 'he', + 0x0439: 'hi', + 0x040E: 'hu', + 0x040F: 'is', + 0x0470: 'ig', + 0x0421: 'id', + 0x045D: 'iu', + 0x085D: 'iu-Latn', + 0x083C: 'ga', + 0x0434: 'xh', + 0x0435: 'zu', + 0x0410: 'it', + 0x0810: 'it-CH', + 0x0411: 'ja', + 0x044B: 'kn', + 0x043F: 'kk', + 0x0453: 'km', + 0x0486: 'quc', + 0x0487: 'rw', + 0x0441: 'sw', + 0x0457: 'kok', + 0x0412: 'ko', + 0x0440: 'ky', + 0x0454: 'lo', + 0x0426: 'lv', + 0x0427: 'lt', + 0x082E: 'dsb', + 0x046E: 'lb', + 0x042F: 'mk', + 0x083E: 'ms-BN', + 0x043E: 'ms', + 0x044C: 'ml', + 0x043A: 'mt', + 0x0481: 'mi', + 0x047A: 'arn', + 0x044E: 'mr', + 0x047C: 'moh', + 0x0450: 'mn', + 0x0850: 'mn-CN', + 0x0461: 'ne', + 0x0414: 'nb', + 0x0814: 'nn', + 0x0482: 'oc', + 0x0448: 'or', + 0x0463: 'ps', + 0x0415: 'pl', + 0x0416: 'pt', + 0x0816: 'pt-PT', + 0x0446: 'pa', + 0x046B: 'qu-BO', + 0x086B: 'qu-EC', + 0x0C6B: 'qu', + 0x0418: 'ro', + 0x0417: 'rm', + 0x0419: 'ru', + 0x243B: 'smn', + 0x103B: 'smj-NO', + 0x143B: 'smj', + 0x0C3B: 'se-FI', + 0x043B: 'se', + 0x083B: 'se-SE', + 0x203B: 'sms', + 0x183B: 'sma-NO', + 0x1C3B: 'sms', + 0x044F: 'sa', + 0x1C1A: 'sr-Cyrl-BA', + 0x0C1A: 'sr', + 0x181A: 'sr-Latn-BA', + 0x081A: 'sr-Latn', + 0x046C: 'nso', + 0x0432: 'tn', + 0x045B: 'si', + 0x041B: 'sk', + 0x0424: 'sl', + 0x2C0A: 'es-AR', + 0x400A: 'es-BO', + 0x340A: 'es-CL', + 0x240A: 'es-CO', + 0x140A: 'es-CR', + 0x1C0A: 'es-DO', + 0x300A: 'es-EC', + 0x440A: 'es-SV', + 0x100A: 'es-GT', + 0x480A: 'es-HN', + 0x080A: 'es-MX', + 0x4C0A: 'es-NI', + 0x180A: 'es-PA', + 0x3C0A: 'es-PY', + 0x280A: 'es-PE', + 0x500A: 'es-PR', + + // Microsoft has defined two different language codes for + // “Spanish with modern sorting” and “Spanish with traditional + // sorting”. This makes sense for collation APIs, and it would be + // possible to express this in BCP 47 language tags via Unicode + // extensions (eg., es-u-co-trad is Spanish with traditional + // sorting). However, for storing names in fonts, the distinction + // does not make sense, so we give “es” in both cases. + 0x0C0A: 'es', + 0x040A: 'es', + + 0x540A: 'es-US', + 0x380A: 'es-UY', + 0x200A: 'es-VE', + 0x081D: 'sv-FI', + 0x041D: 'sv', + 0x045A: 'syr', + 0x0428: 'tg', + 0x085F: 'tzm', + 0x0449: 'ta', + 0x0444: 'tt', + 0x044A: 'te', + 0x041E: 'th', + 0x0451: 'bo', + 0x041F: 'tr', + 0x0442: 'tk', + 0x0480: 'ug', + 0x0422: 'uk', + 0x042E: 'hsb', + 0x0420: 'ur', + 0x0843: 'uz-Cyrl', + 0x0443: 'uz', + 0x042A: 'vi', + 0x0452: 'cy', + 0x0488: 'wo', + 0x0485: 'sah', + 0x0478: 'ii', + 0x046A: 'yo' + }; + + // Returns a IETF BCP 47 language code, for example 'zh-Hant' + // for 'Chinese in the traditional script'. + function getLanguageCode(platformID, languageID, ltag) { + switch (platformID) { + case 0: // Unicode + if (languageID === 0xFFFF) { + return 'und'; + } else if (ltag) { + return ltag[languageID]; + } + + break; + + case 1: // Macintosh + return macLanguages[languageID]; + + case 3: // Windows + return windowsLanguages[languageID]; + } + + return undefined; + } + + var utf16 = 'utf-16'; + + // MacOS script ID → encoding. This table stores the default case, + // which can be overridden by macLanguageEncodings. + var macScriptEncodings = { + 0: 'macintosh', // smRoman + 1: 'x-mac-japanese', // smJapanese + 2: 'x-mac-chinesetrad', // smTradChinese + 3: 'x-mac-korean', // smKorean + 6: 'x-mac-greek', // smGreek + 7: 'x-mac-cyrillic', // smCyrillic + 9: 'x-mac-devanagai', // smDevanagari + 10: 'x-mac-gurmukhi', // smGurmukhi + 11: 'x-mac-gujarati', // smGujarati + 12: 'x-mac-oriya', // smOriya + 13: 'x-mac-bengali', // smBengali + 14: 'x-mac-tamil', // smTamil + 15: 'x-mac-telugu', // smTelugu + 16: 'x-mac-kannada', // smKannada + 17: 'x-mac-malayalam', // smMalayalam + 18: 'x-mac-sinhalese', // smSinhalese + 19: 'x-mac-burmese', // smBurmese + 20: 'x-mac-khmer', // smKhmer + 21: 'x-mac-thai', // smThai + 22: 'x-mac-lao', // smLao + 23: 'x-mac-georgian', // smGeorgian + 24: 'x-mac-armenian', // smArmenian + 25: 'x-mac-chinesesimp', // smSimpChinese + 26: 'x-mac-tibetan', // smTibetan + 27: 'x-mac-mongolian', // smMongolian + 28: 'x-mac-ethiopic', // smEthiopic + 29: 'x-mac-ce', // smCentralEuroRoman + 30: 'x-mac-vietnamese', // smVietnamese + 31: 'x-mac-extarabic' // smExtArabic + }; + + // MacOS language ID → encoding. This table stores the exceptional + // cases, which override macScriptEncodings. For writing MacOS naming + // tables, we need to emit a MacOS script ID. Therefore, we cannot + // merge macScriptEncodings into macLanguageEncodings. + // + // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt + var macLanguageEncodings = { + 15: 'x-mac-icelandic', // langIcelandic + 17: 'x-mac-turkish', // langTurkish + 18: 'x-mac-croatian', // langCroatian + 24: 'x-mac-ce', // langLithuanian + 25: 'x-mac-ce', // langPolish + 26: 'x-mac-ce', // langHungarian + 27: 'x-mac-ce', // langEstonian + 28: 'x-mac-ce', // langLatvian + 30: 'x-mac-icelandic', // langFaroese + 37: 'x-mac-romanian', // langRomanian + 38: 'x-mac-ce', // langCzech + 39: 'x-mac-ce', // langSlovak + 40: 'x-mac-ce', // langSlovenian + 143: 'x-mac-inuit', // langInuktitut + 146: 'x-mac-gaelic' // langIrishGaelicScript + }; + + function getEncoding(platformID, encodingID, languageID) { + switch (platformID) { + case 0: // Unicode + return utf16; + + case 1: // Apple Macintosh + return macLanguageEncodings[languageID] || macScriptEncodings[encodingID]; + + case 3: // Microsoft Windows + if (encodingID === 1 || encodingID === 10) { + return utf16; + } + + break; + } + + return undefined; + } + + // Parse the naming `name` table. + // FIXME: Format 1 additional fields are not supported yet. + // ltag is the content of the `ltag' table, such as ['en', 'zh-Hans', 'de-CH-1904']. + function parseNameTable(data, start, ltag) { + var name = {}; + var p = new parse.Parser(data, start); + var format = p.parseUShort(); + var count = p.parseUShort(); + var stringOffset = p.offset + p.parseUShort(); + for (var i = 0; i < count; i++) { + var platformID = p.parseUShort(); + var encodingID = p.parseUShort(); + var languageID = p.parseUShort(); + var nameID = p.parseUShort(); + var property = nameTableNames[nameID] || nameID; + var byteLength = p.parseUShort(); + var offset = p.parseUShort(); + var language = getLanguageCode(platformID, languageID, ltag); + var encoding = getEncoding(platformID, encodingID, languageID); + if (encoding !== undefined && language !== undefined) { + var text = (void 0); + if (encoding === utf16) { + text = decode.UTF16(data, stringOffset + offset, byteLength); + } else { + text = decode.MACSTRING(data, stringOffset + offset, byteLength, encoding); + } + + if (text) { + var translations = name[property]; + if (translations === undefined) { + translations = name[property] = {}; + } + + translations[language] = text; + } + } + } + + var langTagCount = 0; + if (format === 1) { + // FIXME: Also handle Microsoft's 'name' table 1. + langTagCount = p.parseUShort(); + } + + return name; + } + + // {23: 'foo'} → {'foo': 23} + // ['bar', 'baz'] → {'bar': 0, 'baz': 1} + function reverseDict(dict) { + var result = {}; + for (var key in dict) { + result[dict[key]] = parseInt(key); + } + + return result; + } + + function makeNameRecord(platformID, encodingID, languageID, nameID, length, offset) { + return new table.Record('NameRecord', [ + {name: 'platformID', type: 'USHORT', value: platformID}, + {name: 'encodingID', type: 'USHORT', value: encodingID}, + {name: 'languageID', type: 'USHORT', value: languageID}, + {name: 'nameID', type: 'USHORT', value: nameID}, + {name: 'length', type: 'USHORT', value: length}, + {name: 'offset', type: 'USHORT', value: offset} + ]); + } + + // Finds the position of needle in haystack, or -1 if not there. + // Like String.indexOf(), but for arrays. + function findSubArray(needle, haystack) { + var needleLength = needle.length; + var limit = haystack.length - needleLength + 1; + + loop: + for (var pos = 0; pos < limit; pos++) { + for (; pos < limit; pos++) { + for (var k = 0; k < needleLength; k++) { + if (haystack[pos + k] !== needle[k]) { + continue loop; + } + } + + return pos; + } + } + + return -1; + } + + function addStringToPool(s, pool) { + var offset = findSubArray(s, pool); + if (offset < 0) { + offset = pool.length; + var i = 0; + var len = s.length; + for (; i < len; ++i) { + pool.push(s[i]); + } + + } + + return offset; + } + + function makeNameTable(names, ltag) { + var nameID; + var nameIDs = []; + + var namesWithNumericKeys = {}; + var nameTableIds = reverseDict(nameTableNames); + for (var key in names) { + var id = nameTableIds[key]; + if (id === undefined) { + id = key; + } + + nameID = parseInt(id); + + if (isNaN(nameID)) { + throw new Error('Name table entry "' + key + '" does not exist, see nameTableNames for complete list.'); + } + + namesWithNumericKeys[nameID] = names[key]; + nameIDs.push(nameID); + } + + var macLanguageIds = reverseDict(macLanguages); + var windowsLanguageIds = reverseDict(windowsLanguages); + + var nameRecords = []; + var stringPool = []; + + for (var i = 0; i < nameIDs.length; i++) { + nameID = nameIDs[i]; + var translations = namesWithNumericKeys[nameID]; + for (var lang in translations) { + var text = translations[lang]; + + // For MacOS, we try to emit the name in the form that was introduced + // in the initial version of the TrueType spec (in the late 1980s). + // However, this can fail for various reasons: the requested BCP 47 + // language code might not have an old-style Mac equivalent; + // we might not have a codec for the needed character encoding; + // or the name might contain characters that cannot be expressed + // in the old-style Macintosh encoding. In case of failure, we emit + // the name in a more modern fashion (Unicode encoding with BCP 47 + // language tags) that is recognized by MacOS 10.5, released in 2009. + // If fonts were only read by operating systems, we could simply + // emit all names in the modern form; this would be much easier. + // However, there are many applications and libraries that read + // 'name' tables directly, and these will usually only recognize + // the ancient form (silently skipping the unrecognized names). + var macPlatform = 1; // Macintosh + var macLanguage = macLanguageIds[lang]; + var macScript = macLanguageToScript[macLanguage]; + var macEncoding = getEncoding(macPlatform, macScript, macLanguage); + var macName = encode.MACSTRING(text, macEncoding); + if (macName === undefined) { + macPlatform = 0; // Unicode + macLanguage = ltag.indexOf(lang); + if (macLanguage < 0) { + macLanguage = ltag.length; + ltag.push(lang); + } + + macScript = 4; // Unicode 2.0 and later + macName = encode.UTF16(text); + } + + var macNameOffset = addStringToPool(macName, stringPool); + nameRecords.push(makeNameRecord(macPlatform, macScript, macLanguage, + nameID, macName.length, macNameOffset)); + + var winLanguage = windowsLanguageIds[lang]; + if (winLanguage !== undefined) { + var winName = encode.UTF16(text); + var winNameOffset = addStringToPool(winName, stringPool); + nameRecords.push(makeNameRecord(3, 1, winLanguage, + nameID, winName.length, winNameOffset)); + } + } + } + + nameRecords.sort(function(a, b) { + return ((a.platformID - b.platformID) || + (a.encodingID - b.encodingID) || + (a.languageID - b.languageID) || + (a.nameID - b.nameID)); + }); + + var t = new table.Table('name', [ + {name: 'format', type: 'USHORT', value: 0}, + {name: 'count', type: 'USHORT', value: nameRecords.length}, + {name: 'stringOffset', type: 'USHORT', value: 6 + nameRecords.length * 12} + ]); + + for (var r = 0; r < nameRecords.length; r++) { + t.fields.push({name: 'record_' + r, type: 'RECORD', value: nameRecords[r]}); + } + + t.fields.push({name: 'strings', type: 'LITERAL', value: stringPool}); + return t; + } + + var _name = { parse: parseNameTable, make: makeNameTable }; + + // The `OS/2` table contains metrics required in OpenType fonts. + + var unicodeRanges = [ + {begin: 0x0000, end: 0x007F}, // Basic Latin + {begin: 0x0080, end: 0x00FF}, // Latin-1 Supplement + {begin: 0x0100, end: 0x017F}, // Latin Extended-A + {begin: 0x0180, end: 0x024F}, // Latin Extended-B + {begin: 0x0250, end: 0x02AF}, // IPA Extensions + {begin: 0x02B0, end: 0x02FF}, // Spacing Modifier Letters + {begin: 0x0300, end: 0x036F}, // Combining Diacritical Marks + {begin: 0x0370, end: 0x03FF}, // Greek and Coptic + {begin: 0x2C80, end: 0x2CFF}, // Coptic + {begin: 0x0400, end: 0x04FF}, // Cyrillic + {begin: 0x0530, end: 0x058F}, // Armenian + {begin: 0x0590, end: 0x05FF}, // Hebrew + {begin: 0xA500, end: 0xA63F}, // Vai + {begin: 0x0600, end: 0x06FF}, // Arabic + {begin: 0x07C0, end: 0x07FF}, // NKo + {begin: 0x0900, end: 0x097F}, // Devanagari + {begin: 0x0980, end: 0x09FF}, // Bengali + {begin: 0x0A00, end: 0x0A7F}, // Gurmukhi + {begin: 0x0A80, end: 0x0AFF}, // Gujarati + {begin: 0x0B00, end: 0x0B7F}, // Oriya + {begin: 0x0B80, end: 0x0BFF}, // Tamil + {begin: 0x0C00, end: 0x0C7F}, // Telugu + {begin: 0x0C80, end: 0x0CFF}, // Kannada + {begin: 0x0D00, end: 0x0D7F}, // Malayalam + {begin: 0x0E00, end: 0x0E7F}, // Thai + {begin: 0x0E80, end: 0x0EFF}, // Lao + {begin: 0x10A0, end: 0x10FF}, // Georgian + {begin: 0x1B00, end: 0x1B7F}, // Balinese + {begin: 0x1100, end: 0x11FF}, // Hangul Jamo + {begin: 0x1E00, end: 0x1EFF}, // Latin Extended Additional + {begin: 0x1F00, end: 0x1FFF}, // Greek Extended + {begin: 0x2000, end: 0x206F}, // General Punctuation + {begin: 0x2070, end: 0x209F}, // Superscripts And Subscripts + {begin: 0x20A0, end: 0x20CF}, // Currency Symbol + {begin: 0x20D0, end: 0x20FF}, // Combining Diacritical Marks For Symbols + {begin: 0x2100, end: 0x214F}, // Letterlike Symbols + {begin: 0x2150, end: 0x218F}, // Number Forms + {begin: 0x2190, end: 0x21FF}, // Arrows + {begin: 0x2200, end: 0x22FF}, // Mathematical Operators + {begin: 0x2300, end: 0x23FF}, // Miscellaneous Technical + {begin: 0x2400, end: 0x243F}, // Control Pictures + {begin: 0x2440, end: 0x245F}, // Optical Character Recognition + {begin: 0x2460, end: 0x24FF}, // Enclosed Alphanumerics + {begin: 0x2500, end: 0x257F}, // Box Drawing + {begin: 0x2580, end: 0x259F}, // Block Elements + {begin: 0x25A0, end: 0x25FF}, // Geometric Shapes + {begin: 0x2600, end: 0x26FF}, // Miscellaneous Symbols + {begin: 0x2700, end: 0x27BF}, // Dingbats + {begin: 0x3000, end: 0x303F}, // CJK Symbols And Punctuation + {begin: 0x3040, end: 0x309F}, // Hiragana + {begin: 0x30A0, end: 0x30FF}, // Katakana + {begin: 0x3100, end: 0x312F}, // Bopomofo + {begin: 0x3130, end: 0x318F}, // Hangul Compatibility Jamo + {begin: 0xA840, end: 0xA87F}, // Phags-pa + {begin: 0x3200, end: 0x32FF}, // Enclosed CJK Letters And Months + {begin: 0x3300, end: 0x33FF}, // CJK Compatibility + {begin: 0xAC00, end: 0xD7AF}, // Hangul Syllables + {begin: 0xD800, end: 0xDFFF}, // Non-Plane 0 * + {begin: 0x10900, end: 0x1091F}, // Phoenicia + {begin: 0x4E00, end: 0x9FFF}, // CJK Unified Ideographs + {begin: 0xE000, end: 0xF8FF}, // Private Use Area (plane 0) + {begin: 0x31C0, end: 0x31EF}, // CJK Strokes + {begin: 0xFB00, end: 0xFB4F}, // Alphabetic Presentation Forms + {begin: 0xFB50, end: 0xFDFF}, // Arabic Presentation Forms-A + {begin: 0xFE20, end: 0xFE2F}, // Combining Half Marks + {begin: 0xFE10, end: 0xFE1F}, // Vertical Forms + {begin: 0xFE50, end: 0xFE6F}, // Small Form Variants + {begin: 0xFE70, end: 0xFEFF}, // Arabic Presentation Forms-B + {begin: 0xFF00, end: 0xFFEF}, // Halfwidth And Fullwidth Forms + {begin: 0xFFF0, end: 0xFFFF}, // Specials + {begin: 0x0F00, end: 0x0FFF}, // Tibetan + {begin: 0x0700, end: 0x074F}, // Syriac + {begin: 0x0780, end: 0x07BF}, // Thaana + {begin: 0x0D80, end: 0x0DFF}, // Sinhala + {begin: 0x1000, end: 0x109F}, // Myanmar + {begin: 0x1200, end: 0x137F}, // Ethiopic + {begin: 0x13A0, end: 0x13FF}, // Cherokee + {begin: 0x1400, end: 0x167F}, // Unified Canadian Aboriginal Syllabics + {begin: 0x1680, end: 0x169F}, // Ogham + {begin: 0x16A0, end: 0x16FF}, // Runic + {begin: 0x1780, end: 0x17FF}, // Khmer + {begin: 0x1800, end: 0x18AF}, // Mongolian + {begin: 0x2800, end: 0x28FF}, // Braille Patterns + {begin: 0xA000, end: 0xA48F}, // Yi Syllables + {begin: 0x1700, end: 0x171F}, // Tagalog + {begin: 0x10300, end: 0x1032F}, // Old Italic + {begin: 0x10330, end: 0x1034F}, // Gothic + {begin: 0x10400, end: 0x1044F}, // Deseret + {begin: 0x1D000, end: 0x1D0FF}, // Byzantine Musical Symbols + {begin: 0x1D400, end: 0x1D7FF}, // Mathematical Alphanumeric Symbols + {begin: 0xFF000, end: 0xFFFFD}, // Private Use (plane 15) + {begin: 0xFE00, end: 0xFE0F}, // Variation Selectors + {begin: 0xE0000, end: 0xE007F}, // Tags + {begin: 0x1900, end: 0x194F}, // Limbu + {begin: 0x1950, end: 0x197F}, // Tai Le + {begin: 0x1980, end: 0x19DF}, // New Tai Lue + {begin: 0x1A00, end: 0x1A1F}, // Buginese + {begin: 0x2C00, end: 0x2C5F}, // Glagolitic + {begin: 0x2D30, end: 0x2D7F}, // Tifinagh + {begin: 0x4DC0, end: 0x4DFF}, // Yijing Hexagram Symbols + {begin: 0xA800, end: 0xA82F}, // Syloti Nagri + {begin: 0x10000, end: 0x1007F}, // Linear B Syllabary + {begin: 0x10140, end: 0x1018F}, // Ancient Greek Numbers + {begin: 0x10380, end: 0x1039F}, // Ugaritic + {begin: 0x103A0, end: 0x103DF}, // Old Persian + {begin: 0x10450, end: 0x1047F}, // Shavian + {begin: 0x10480, end: 0x104AF}, // Osmanya + {begin: 0x10800, end: 0x1083F}, // Cypriot Syllabary + {begin: 0x10A00, end: 0x10A5F}, // Kharoshthi + {begin: 0x1D300, end: 0x1D35F}, // Tai Xuan Jing Symbols + {begin: 0x12000, end: 0x123FF}, // Cuneiform + {begin: 0x1D360, end: 0x1D37F}, // Counting Rod Numerals + {begin: 0x1B80, end: 0x1BBF}, // Sundanese + {begin: 0x1C00, end: 0x1C4F}, // Lepcha + {begin: 0x1C50, end: 0x1C7F}, // Ol Chiki + {begin: 0xA880, end: 0xA8DF}, // Saurashtra + {begin: 0xA900, end: 0xA92F}, // Kayah Li + {begin: 0xA930, end: 0xA95F}, // Rejang + {begin: 0xAA00, end: 0xAA5F}, // Cham + {begin: 0x10190, end: 0x101CF}, // Ancient Symbols + {begin: 0x101D0, end: 0x101FF}, // Phaistos Disc + {begin: 0x102A0, end: 0x102DF}, // Carian + {begin: 0x1F030, end: 0x1F09F} // Domino Tiles + ]; + + function getUnicodeRange(unicode) { + for (var i = 0; i < unicodeRanges.length; i += 1) { + var range = unicodeRanges[i]; + if (unicode >= range.begin && unicode < range.end) { + return i; + } + } + + return -1; + } + + // Parse the OS/2 and Windows metrics `OS/2` table + function parseOS2Table(data, start) { + var os2 = {}; + var p = new parse.Parser(data, start); + os2.version = p.parseUShort(); + os2.xAvgCharWidth = p.parseShort(); + os2.usWeightClass = p.parseUShort(); + os2.usWidthClass = p.parseUShort(); + os2.fsType = p.parseUShort(); + os2.ySubscriptXSize = p.parseShort(); + os2.ySubscriptYSize = p.parseShort(); + os2.ySubscriptXOffset = p.parseShort(); + os2.ySubscriptYOffset = p.parseShort(); + os2.ySuperscriptXSize = p.parseShort(); + os2.ySuperscriptYSize = p.parseShort(); + os2.ySuperscriptXOffset = p.parseShort(); + os2.ySuperscriptYOffset = p.parseShort(); + os2.yStrikeoutSize = p.parseShort(); + os2.yStrikeoutPosition = p.parseShort(); + os2.sFamilyClass = p.parseShort(); + os2.panose = []; + for (var i = 0; i < 10; i++) { + os2.panose[i] = p.parseByte(); + } + + os2.ulUnicodeRange1 = p.parseULong(); + os2.ulUnicodeRange2 = p.parseULong(); + os2.ulUnicodeRange3 = p.parseULong(); + os2.ulUnicodeRange4 = p.parseULong(); + os2.achVendID = String.fromCharCode(p.parseByte(), p.parseByte(), p.parseByte(), p.parseByte()); + os2.fsSelection = p.parseUShort(); + os2.usFirstCharIndex = p.parseUShort(); + os2.usLastCharIndex = p.parseUShort(); + os2.sTypoAscender = p.parseShort(); + os2.sTypoDescender = p.parseShort(); + os2.sTypoLineGap = p.parseShort(); + os2.usWinAscent = p.parseUShort(); + os2.usWinDescent = p.parseUShort(); + if (os2.version >= 1) { + os2.ulCodePageRange1 = p.parseULong(); + os2.ulCodePageRange2 = p.parseULong(); + } + + if (os2.version >= 2) { + os2.sxHeight = p.parseShort(); + os2.sCapHeight = p.parseShort(); + os2.usDefaultChar = p.parseUShort(); + os2.usBreakChar = p.parseUShort(); + os2.usMaxContent = p.parseUShort(); + } + + return os2; + } + + function makeOS2Table(options) { + return new table.Table('OS/2', [ + {name: 'version', type: 'USHORT', value: 0x0003}, + {name: 'xAvgCharWidth', type: 'SHORT', value: 0}, + {name: 'usWeightClass', type: 'USHORT', value: 0}, + {name: 'usWidthClass', type: 'USHORT', value: 0}, + {name: 'fsType', type: 'USHORT', value: 0}, + {name: 'ySubscriptXSize', type: 'SHORT', value: 650}, + {name: 'ySubscriptYSize', type: 'SHORT', value: 699}, + {name: 'ySubscriptXOffset', type: 'SHORT', value: 0}, + {name: 'ySubscriptYOffset', type: 'SHORT', value: 140}, + {name: 'ySuperscriptXSize', type: 'SHORT', value: 650}, + {name: 'ySuperscriptYSize', type: 'SHORT', value: 699}, + {name: 'ySuperscriptXOffset', type: 'SHORT', value: 0}, + {name: 'ySuperscriptYOffset', type: 'SHORT', value: 479}, + {name: 'yStrikeoutSize', type: 'SHORT', value: 49}, + {name: 'yStrikeoutPosition', type: 'SHORT', value: 258}, + {name: 'sFamilyClass', type: 'SHORT', value: 0}, + {name: 'bFamilyType', type: 'BYTE', value: 0}, + {name: 'bSerifStyle', type: 'BYTE', value: 0}, + {name: 'bWeight', type: 'BYTE', value: 0}, + {name: 'bProportion', type: 'BYTE', value: 0}, + {name: 'bContrast', type: 'BYTE', value: 0}, + {name: 'bStrokeVariation', type: 'BYTE', value: 0}, + {name: 'bArmStyle', type: 'BYTE', value: 0}, + {name: 'bLetterform', type: 'BYTE', value: 0}, + {name: 'bMidline', type: 'BYTE', value: 0}, + {name: 'bXHeight', type: 'BYTE', value: 0}, + {name: 'ulUnicodeRange1', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange2', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange3', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange4', type: 'ULONG', value: 0}, + {name: 'achVendID', type: 'CHARARRAY', value: 'XXXX'}, + {name: 'fsSelection', type: 'USHORT', value: 0}, + {name: 'usFirstCharIndex', type: 'USHORT', value: 0}, + {name: 'usLastCharIndex', type: 'USHORT', value: 0}, + {name: 'sTypoAscender', type: 'SHORT', value: 0}, + {name: 'sTypoDescender', type: 'SHORT', value: 0}, + {name: 'sTypoLineGap', type: 'SHORT', value: 0}, + {name: 'usWinAscent', type: 'USHORT', value: 0}, + {name: 'usWinDescent', type: 'USHORT', value: 0}, + {name: 'ulCodePageRange1', type: 'ULONG', value: 0}, + {name: 'ulCodePageRange2', type: 'ULONG', value: 0}, + {name: 'sxHeight', type: 'SHORT', value: 0}, + {name: 'sCapHeight', type: 'SHORT', value: 0}, + {name: 'usDefaultChar', type: 'USHORT', value: 0}, + {name: 'usBreakChar', type: 'USHORT', value: 0}, + {name: 'usMaxContext', type: 'USHORT', value: 0} + ], options); + } + + var os2 = { parse: parseOS2Table, make: makeOS2Table, unicodeRanges: unicodeRanges, getUnicodeRange: getUnicodeRange }; + + // The `post` table stores additional PostScript information, such as glyph names. + + // Parse the PostScript `post` table + function parsePostTable(data, start) { + var post = {}; + var p = new parse.Parser(data, start); + post.version = p.parseVersion(); + post.italicAngle = p.parseFixed(); + post.underlinePosition = p.parseShort(); + post.underlineThickness = p.parseShort(); + post.isFixedPitch = p.parseULong(); + post.minMemType42 = p.parseULong(); + post.maxMemType42 = p.parseULong(); + post.minMemType1 = p.parseULong(); + post.maxMemType1 = p.parseULong(); + switch (post.version) { + case 1: + post.names = standardNames.slice(); + break; + case 2: + post.numberOfGlyphs = p.parseUShort(); + post.glyphNameIndex = new Array(post.numberOfGlyphs); + for (var i = 0; i < post.numberOfGlyphs; i++) { + post.glyphNameIndex[i] = p.parseUShort(); + } + + post.names = []; + for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { + if (post.glyphNameIndex[i$1] >= standardNames.length) { + var nameLength = p.parseChar(); + post.names.push(p.parseString(nameLength)); + } + } + + break; + case 2.5: + post.numberOfGlyphs = p.parseUShort(); + post.offset = new Array(post.numberOfGlyphs); + for (var i$2 = 0; i$2 < post.numberOfGlyphs; i$2++) { + post.offset[i$2] = p.parseChar(); + } + + break; + } + return post; + } + + function makePostTable() { + return new table.Table('post', [ + {name: 'version', type: 'FIXED', value: 0x00030000}, + {name: 'italicAngle', type: 'FIXED', value: 0}, + {name: 'underlinePosition', type: 'FWORD', value: 0}, + {name: 'underlineThickness', type: 'FWORD', value: 0}, + {name: 'isFixedPitch', type: 'ULONG', value: 0}, + {name: 'minMemType42', type: 'ULONG', value: 0}, + {name: 'maxMemType42', type: 'ULONG', value: 0}, + {name: 'minMemType1', type: 'ULONG', value: 0}, + {name: 'maxMemType1', type: 'ULONG', value: 0} + ]); + } + + var post = { parse: parsePostTable, make: makePostTable }; + + // The `GSUB` table contains ligatures, among other things. + + var subtableParsers = new Array(9); // subtableParsers[0] is unused + + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#SS + subtableParsers[1] = function parseLookup1() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + if (substFormat === 1) { + return { + substFormat: 1, + coverage: this.parsePointer(Parser.coverage), + deltaGlyphId: this.parseUShort() + }; + } else if (substFormat === 2) { + return { + substFormat: 2, + coverage: this.parsePointer(Parser.coverage), + substitute: this.parseOffset16List() + }; + } + check.assert(false, '0x' + start.toString(16) + ': lookup type 1 format must be 1 or 2.'); + }; + + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#MS + subtableParsers[2] = function parseLookup2() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Multiple Substitution Subtable identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + sequences: this.parseListOfLists() + }; + }; + + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#AS + subtableParsers[3] = function parseLookup3() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Alternate Substitution Subtable identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + alternateSets: this.parseListOfLists() + }; + }; + + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#LS + subtableParsers[4] = function parseLookup4() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB ligature table identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + ligatureSets: this.parseListOfLists(function() { + return { + ligGlyph: this.parseUShort(), + components: this.parseUShortList(this.parseUShort() - 1) + }; + }) + }; + }; + + var lookupRecordDesc = { + sequenceIndex: Parser.uShort, + lookupListIndex: Parser.uShort + }; + + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CSF + subtableParsers[5] = function parseLookup5() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + + if (substFormat === 1) { + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + ruleSets: this.parseListOfLists(function() { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + input: this.parseUShortList(glyphCount - 1), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 2) { + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + classDef: this.parsePointer(Parser.classDef), + classSets: this.parseListOfLists(function() { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + classes: this.parseUShortList(glyphCount - 1), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 3) { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + substFormat: substFormat, + coverages: this.parseList(glyphCount, Parser.pointer(Parser.coverage)), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; + } + check.assert(false, '0x' + start.toString(16) + ': lookup type 5 format must be 1, 2 or 3.'); + }; + + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CC + subtableParsers[6] = function parseLookup6() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + if (substFormat === 1) { + return { + substFormat: 1, + coverage: this.parsePointer(Parser.coverage), + chainRuleSets: this.parseListOfLists(function() { + return { + backtrack: this.parseUShortList(), + input: this.parseUShortList(this.parseShort() - 1), + lookahead: this.parseUShortList(), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 2) { + return { + substFormat: 2, + coverage: this.parsePointer(Parser.coverage), + backtrackClassDef: this.parsePointer(Parser.classDef), + inputClassDef: this.parsePointer(Parser.classDef), + lookaheadClassDef: this.parsePointer(Parser.classDef), + chainClassSet: this.parseListOfLists(function() { + return { + backtrack: this.parseUShortList(), + input: this.parseUShortList(this.parseShort() - 1), + lookahead: this.parseUShortList(), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 3) { + return { + substFormat: 3, + backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), + inputCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + } + check.assert(false, '0x' + start.toString(16) + ': lookup type 6 format must be 1, 2 or 3.'); + }; + + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#ES + subtableParsers[7] = function parseLookup7() { + // Extension Substitution subtable + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Extension Substitution subtable identifier-format must be 1'); + var extensionLookupType = this.parseUShort(); + var extensionParser = new Parser(this.data, this.offset + this.parseULong()); + return { + substFormat: 1, + lookupType: extensionLookupType, + extension: subtableParsers[extensionLookupType].call(extensionParser) + }; + }; + + // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#RCCS + subtableParsers[8] = function parseLookup8() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Reverse Chaining Contextual Single Substitution Subtable identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), + substitutes: this.parseUShortList() + }; + }; + + // https://www.microsoft.com/typography/OTSPEC/gsub.htm + function parseGsubTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GSUB table version.'); + if (tableVersion === 1) { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers) + }; + } else { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers), + variations: p.parseFeatureVariationsList() + }; + } + + } + + // GSUB Writing ////////////////////////////////////////////// + var subtableMakers = new Array(9); + + subtableMakers[1] = function makeLookup1(subtable) { + if (subtable.substFormat === 1) { + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}, + {name: 'deltaGlyphID', type: 'USHORT', value: subtable.deltaGlyphId} + ]); + } else { + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 2}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.ushortList('substitute', subtable.substitute))); + } + }; + + subtableMakers[3] = function makeLookup3(subtable) { + check.assert(subtable.substFormat === 1, 'Lookup type 3 substFormat must be 1.'); + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('altSet', subtable.alternateSets, function(alternateSet) { + return new table.Table('alternateSetTable', table.ushortList('alternate', alternateSet)); + }))); + }; + + subtableMakers[4] = function makeLookup4(subtable) { + check.assert(subtable.substFormat === 1, 'Lookup type 4 substFormat must be 1.'); + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('ligSet', subtable.ligatureSets, function(ligatureSet) { + return new table.Table('ligatureSetTable', table.tableList('ligature', ligatureSet, function(ligature) { + return new table.Table('ligatureTable', + [{name: 'ligGlyph', type: 'USHORT', value: ligature.ligGlyph}] + .concat(table.ushortList('component', ligature.components, ligature.components.length + 1)) + ); + })); + }))); + }; + + function makeGsubTable(gsub) { + return new table.Table('GSUB', [ + {name: 'version', type: 'ULONG', value: 0x10000}, + {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gsub.scripts)}, + {name: 'features', type: 'TABLE', value: new table.FeatureList(gsub.features)}, + {name: 'lookups', type: 'TABLE', value: new table.LookupList(gsub.lookups, subtableMakers)} + ]); + } + + var gsub = { parse: parseGsubTable, make: makeGsubTable }; + + // The `GPOS` table contains kerning pairs, among other things. + + // Parse the metadata `meta` table. + // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6meta.html + function parseMetaTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 1, 'Unsupported META table version.'); + p.parseULong(); // flags - currently unused and set to 0 + p.parseULong(); // tableOffset + var numDataMaps = p.parseULong(); + + var tags = {}; + for (var i = 0; i < numDataMaps; i++) { + var tag = p.parseTag(); + var dataOffset = p.parseULong(); + var dataLength = p.parseULong(); + var text = decode.UTF8(data, start + dataOffset, dataLength); + + tags[tag] = text; + } + return tags; + } + + function makeMetaTable(tags) { + var numTags = Object.keys(tags).length; + var stringPool = ''; + var stringPoolOffset = 16 + numTags * 12; + + var result = new table.Table('meta', [ + {name: 'version', type: 'ULONG', value: 1}, + {name: 'flags', type: 'ULONG', value: 0}, + {name: 'offset', type: 'ULONG', value: stringPoolOffset}, + {name: 'numTags', type: 'ULONG', value: numTags} + ]); + + for (var tag in tags) { + var pos = stringPool.length; + stringPool += tags[tag]; + + result.fields.push({name: 'tag ' + tag, type: 'TAG', value: tag}); + result.fields.push({name: 'offset ' + tag, type: 'ULONG', value: stringPoolOffset + pos}); + result.fields.push({name: 'length ' + tag, type: 'ULONG', value: tags[tag].length}); + } + + result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); + + return result; + } + + var meta = { parse: parseMetaTable, make: makeMetaTable }; + + // The `sfnt` wrapper provides organization for the tables in the font. + + function log2(v) { + return Math.log(v) / Math.log(2) | 0; + } + + function computeCheckSum(bytes) { + while (bytes.length % 4 !== 0) { + bytes.push(0); + } + + var sum = 0; + for (var i = 0; i < bytes.length; i += 4) { + sum += (bytes[i] << 24) + + (bytes[i + 1] << 16) + + (bytes[i + 2] << 8) + + (bytes[i + 3]); + } + + sum %= Math.pow(2, 32); + return sum; + } + + function makeTableRecord(tag, checkSum, offset, length) { + return new table.Record('Table Record', [ + {name: 'tag', type: 'TAG', value: tag !== undefined ? tag : ''}, + {name: 'checkSum', type: 'ULONG', value: checkSum !== undefined ? checkSum : 0}, + {name: 'offset', type: 'ULONG', value: offset !== undefined ? offset : 0}, + {name: 'length', type: 'ULONG', value: length !== undefined ? length : 0} + ]); + } + + function makeSfntTable(tables) { + var sfnt = new table.Table('sfnt', [ + {name: 'version', type: 'TAG', value: 'OTTO'}, + {name: 'numTables', type: 'USHORT', value: 0}, + {name: 'searchRange', type: 'USHORT', value: 0}, + {name: 'entrySelector', type: 'USHORT', value: 0}, + {name: 'rangeShift', type: 'USHORT', value: 0} + ]); + sfnt.tables = tables; + sfnt.numTables = tables.length; + var highestPowerOf2 = Math.pow(2, log2(sfnt.numTables)); + sfnt.searchRange = 16 * highestPowerOf2; + sfnt.entrySelector = log2(highestPowerOf2); + sfnt.rangeShift = sfnt.numTables * 16 - sfnt.searchRange; + + var recordFields = []; + var tableFields = []; + + var offset = sfnt.sizeOf() + (makeTableRecord().sizeOf() * sfnt.numTables); + while (offset % 4 !== 0) { + offset += 1; + tableFields.push({name: 'padding', type: 'BYTE', value: 0}); + } + + for (var i = 0; i < tables.length; i += 1) { + var t = tables[i]; + check.argument(t.tableName.length === 4, 'Table name' + t.tableName + ' is invalid.'); + var tableLength = t.sizeOf(); + var tableRecord = makeTableRecord(t.tableName, computeCheckSum(t.encode()), offset, tableLength); + recordFields.push({name: tableRecord.tag + ' Table Record', type: 'RECORD', value: tableRecord}); + tableFields.push({name: t.tableName + ' table', type: 'RECORD', value: t}); + offset += tableLength; + check.argument(!isNaN(offset), 'Something went wrong calculating the offset.'); + while (offset % 4 !== 0) { + offset += 1; + tableFields.push({name: 'padding', type: 'BYTE', value: 0}); + } + } + + // Table records need to be sorted alphabetically. + recordFields.sort(function(r1, r2) { + if (r1.value.tag > r2.value.tag) { + return 1; + } else { + return -1; + } + }); + + sfnt.fields = sfnt.fields.concat(recordFields); + sfnt.fields = sfnt.fields.concat(tableFields); + return sfnt; + } + + // Get the metrics for a character. If the string has more than one character + // this function returns metrics for the first available character. + // You can provide optional fallback metrics if no characters are available. + function metricsForChar(font, chars, notFoundMetrics) { + for (var i = 0; i < chars.length; i += 1) { + var glyphIndex = font.charToGlyphIndex(chars[i]); + if (glyphIndex > 0) { + var glyph = font.glyphs.get(glyphIndex); + return glyph.getMetrics(); + } + } + + return notFoundMetrics; + } + + function average(vs) { + var sum = 0; + for (var i = 0; i < vs.length; i += 1) { + sum += vs[i]; + } + + return sum / vs.length; + } + + // Convert the font object to a SFNT data structure. + // This structure contains all the necessary tables and metadata to create a binary OTF file. + function fontToSfntTable(font) { + var xMins = []; + var yMins = []; + var xMaxs = []; + var yMaxs = []; + var advanceWidths = []; + var leftSideBearings = []; + var rightSideBearings = []; + var firstCharIndex; + var lastCharIndex = 0; + var ulUnicodeRange1 = 0; + var ulUnicodeRange2 = 0; + var ulUnicodeRange3 = 0; + var ulUnicodeRange4 = 0; + + for (var i = 0; i < font.glyphs.length; i += 1) { + var glyph = font.glyphs.get(i); + var unicode = glyph.unicode | 0; + + if (isNaN(glyph.advanceWidth)) { + throw new Error('Glyph ' + glyph.name + ' (' + i + '): advanceWidth is not a number.'); + } + + if (firstCharIndex > unicode || firstCharIndex === undefined) { + // ignore .notdef char + if (unicode > 0) { + firstCharIndex = unicode; + } + } + + if (lastCharIndex < unicode) { + lastCharIndex = unicode; + } + + var position = os2.getUnicodeRange(unicode); + if (position < 32) { + ulUnicodeRange1 |= 1 << position; + } else if (position < 64) { + ulUnicodeRange2 |= 1 << position - 32; + } else if (position < 96) { + ulUnicodeRange3 |= 1 << position - 64; + } else if (position < 123) { + ulUnicodeRange4 |= 1 << position - 96; + } else { + throw new Error('Unicode ranges bits > 123 are reserved for internal usage'); + } + // Skip non-important characters. + if (glyph.name === '.notdef') { continue; } + var metrics = glyph.getMetrics(); + xMins.push(metrics.xMin); + yMins.push(metrics.yMin); + xMaxs.push(metrics.xMax); + yMaxs.push(metrics.yMax); + leftSideBearings.push(metrics.leftSideBearing); + rightSideBearings.push(metrics.rightSideBearing); + advanceWidths.push(glyph.advanceWidth); + } + + var globals = { + xMin: Math.min.apply(null, xMins), + yMin: Math.min.apply(null, yMins), + xMax: Math.max.apply(null, xMaxs), + yMax: Math.max.apply(null, yMaxs), + advanceWidthMax: Math.max.apply(null, advanceWidths), + advanceWidthAvg: average(advanceWidths), + minLeftSideBearing: Math.min.apply(null, leftSideBearings), + maxLeftSideBearing: Math.max.apply(null, leftSideBearings), + minRightSideBearing: Math.min.apply(null, rightSideBearings) + }; + globals.ascender = font.ascender; + globals.descender = font.descender; + + var headTable = head.make({ + flags: 3, // 00000011 (baseline for font at y=0; left sidebearing point at x=0) + unitsPerEm: font.unitsPerEm, + xMin: globals.xMin, + yMin: globals.yMin, + xMax: globals.xMax, + yMax: globals.yMax, + lowestRecPPEM: 3, + createdTimestamp: font.createdTimestamp + }); + + var hheaTable = hhea.make({ + ascender: globals.ascender, + descender: globals.descender, + advanceWidthMax: globals.advanceWidthMax, + minLeftSideBearing: globals.minLeftSideBearing, + minRightSideBearing: globals.minRightSideBearing, + xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin), + numberOfHMetrics: font.glyphs.length + }); + + var maxpTable = maxp.make(font.glyphs.length); + + var os2Table = os2.make(Object.assign({ + xAvgCharWidth: Math.round(globals.advanceWidthAvg), + usFirstCharIndex: firstCharIndex, + usLastCharIndex: lastCharIndex, + ulUnicodeRange1: ulUnicodeRange1, + ulUnicodeRange2: ulUnicodeRange2, + ulUnicodeRange3: ulUnicodeRange3, + ulUnicodeRange4: ulUnicodeRange4, + // See http://typophile.com/node/13081 for more info on vertical metrics. + // We get metrics for typical characters (such as "x" for xHeight). + // We provide some fallback characters if characters are unavailable: their + // ordering was chosen experimentally. + sTypoAscender: globals.ascender, + sTypoDescender: globals.descender, + sTypoLineGap: 0, + usWinAscent: globals.yMax, + usWinDescent: Math.abs(globals.yMin), + ulCodePageRange1: 1, // FIXME: hard-code Latin 1 support for now + sxHeight: metricsForChar(font, 'xyvw', {yMax: Math.round(globals.ascender / 2)}).yMax, + sCapHeight: metricsForChar(font, 'HIKLEFJMNTZBDPRAGOQSUVWXY', globals).yMax, + usDefaultChar: font.hasChar(' ') ? 32 : 0, // Use space as the default character, if available. + usBreakChar: font.hasChar(' ') ? 32 : 0, // Use space as the break character, if available. + }, font.tables.os2)); + + var hmtxTable = hmtx.make(font.glyphs); + var cmapTable = cmap.make(font.glyphs); + + var englishFamilyName = font.getEnglishName('fontFamily'); + var englishStyleName = font.getEnglishName('fontSubfamily'); + var englishFullName = englishFamilyName + ' ' + englishStyleName; + var postScriptName = font.getEnglishName('postScriptName'); + if (!postScriptName) { + postScriptName = englishFamilyName.replace(/\s/g, '') + '-' + englishStyleName; + } + + var names = {}; + for (var n in font.names) { + names[n] = font.names[n]; + } + + if (!names.uniqueID) { + names.uniqueID = {en: font.getEnglishName('manufacturer') + ':' + englishFullName}; + } + + if (!names.postScriptName) { + names.postScriptName = {en: postScriptName}; + } + + if (!names.preferredFamily) { + names.preferredFamily = font.names.fontFamily; + } + + if (!names.preferredSubfamily) { + names.preferredSubfamily = font.names.fontSubfamily; + } + + var languageTags = []; + var nameTable = _name.make(names, languageTags); + var ltagTable = (languageTags.length > 0 ? ltag.make(languageTags) : undefined); + + var postTable = post.make(); + var cffTable = cff.make(font.glyphs, { + version: font.getEnglishName('version'), + fullName: englishFullName, + familyName: englishFamilyName, + weightName: englishStyleName, + postScriptName: postScriptName, + unitsPerEm: font.unitsPerEm, + fontBBox: [0, globals.yMin, globals.ascender, globals.advanceWidthMax] + }); + + var metaTable = (font.metas && Object.keys(font.metas).length > 0) ? meta.make(font.metas) : undefined; + + // The order does not matter because makeSfntTable() will sort them. + var tables = [headTable, hheaTable, maxpTable, os2Table, nameTable, cmapTable, postTable, cffTable, hmtxTable]; + if (ltagTable) { + tables.push(ltagTable); + } + // Optional tables + if (font.tables.gsub) { + tables.push(gsub.make(font.tables.gsub)); + } + if (metaTable) { + tables.push(metaTable); + } + + var sfntTable = makeSfntTable(tables); + + // Compute the font's checkSum and store it in head.checkSumAdjustment. + var bytes = sfntTable.encode(); + var checkSum = computeCheckSum(bytes); + var tableFields = sfntTable.fields; + var checkSumAdjusted = false; + for (var i$1 = 0; i$1 < tableFields.length; i$1 += 1) { + if (tableFields[i$1].name === 'head table') { + tableFields[i$1].value.checkSumAdjustment = 0xB1B0AFBA - checkSum; + checkSumAdjusted = true; + break; + } + } + + if (!checkSumAdjusted) { + throw new Error('Could not find head table with checkSum to adjust.'); + } + + return sfntTable; + } + + var sfnt = { make: makeSfntTable, fontToTable: fontToSfntTable, computeCheckSum: computeCheckSum }; + + // The Layout object is the prototype of Substitution objects, and provides + + function searchTag(arr, tag) { + /* jshint bitwise: false */ + var imin = 0; + var imax = arr.length - 1; + while (imin <= imax) { + var imid = (imin + imax) >>> 1; + var val = arr[imid].tag; + if (val === tag) { + return imid; + } else if (val < tag) { + imin = imid + 1; + } else { imax = imid - 1; } + } + // Not found: return -1-insertion point + return -imin - 1; + } + + function binSearch(arr, value) { + /* jshint bitwise: false */ + var imin = 0; + var imax = arr.length - 1; + while (imin <= imax) { + var imid = (imin + imax) >>> 1; + var val = arr[imid]; + if (val === value) { + return imid; + } else if (val < value) { + imin = imid + 1; + } else { imax = imid - 1; } + } + // Not found: return -1-insertion point + return -imin - 1; + } + + // binary search in a list of ranges (coverage, class definition) + function searchRange(ranges, value) { + // jshint bitwise: false + var range; + var imin = 0; + var imax = ranges.length - 1; + while (imin <= imax) { + var imid = (imin + imax) >>> 1; + range = ranges[imid]; + var start = range.start; + if (start === value) { + return range; + } else if (start < value) { + imin = imid + 1; + } else { imax = imid - 1; } + } + if (imin > 0) { + range = ranges[imin - 1]; + if (value > range.end) { return 0; } + return range; + } + } + + /** + * @exports opentype.Layout + * @class + */ + function Layout(font, tableName) { + this.font = font; + this.tableName = tableName; + } + + Layout.prototype = { + + /** + * Binary search an object by "tag" property + * @instance + * @function searchTag + * @memberof opentype.Layout + * @param {Array} arr + * @param {string} tag + * @return {number} + */ + searchTag: searchTag, + + /** + * Binary search in a list of numbers + * @instance + * @function binSearch + * @memberof opentype.Layout + * @param {Array} arr + * @param {number} value + * @return {number} + */ + binSearch: binSearch, + + /** + * Get or create the Layout table (GSUB, GPOS etc). + * @param {boolean} create - Whether to create a new one. + * @return {Object} The GSUB or GPOS table. + */ + getTable: function(create) { + var layout = this.font.tables[this.tableName]; + if (!layout && create) { + layout = this.font.tables[this.tableName] = this.createDefaultTable(); + } + return layout; + }, + + /** + * Returns all scripts in the substitution table. + * @instance + * @return {Array} + */ + getScriptNames: function() { + var layout = this.getTable(); + if (!layout) { return []; } + return layout.scripts.map(function(script) { + return script.tag; + }); + }, + + /** + * Returns the best bet for a script name. + * Returns 'DFLT' if it exists. + * If not, returns 'latn' if it exists. + * If neither exist, returns undefined. + */ + getDefaultScriptName: function() { + var layout = this.getTable(); + if (!layout) { return; } + var hasLatn = false; + for (var i = 0; i < layout.scripts.length; i++) { + var name = layout.scripts[i].tag; + if (name === 'DFLT') { return name; } + if (name === 'latn') { hasLatn = true; } + } + if (hasLatn) { return 'latn'; } + }, + + /** + * Returns all LangSysRecords in the given script. + * @instance + * @param {string} [script='DFLT'] + * @param {boolean} create - forces the creation of this script table if it doesn't exist. + * @return {Object} An object with tag and script properties. + */ + getScriptTable: function(script, create) { + var layout = this.getTable(create); + if (layout) { + script = script || 'DFLT'; + var scripts = layout.scripts; + var pos = searchTag(layout.scripts, script); + if (pos >= 0) { + return scripts[pos].script; + } else if (create) { + var scr = { + tag: script, + script: { + defaultLangSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []}, + langSysRecords: [] + } + }; + scripts.splice(-1 - pos, 0, scr); + return scr.script; + } + } + }, + + /** + * Returns a language system table + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {boolean} create - forces the creation of this langSysTable if it doesn't exist. + * @return {Object} + */ + getLangSysTable: function(script, language, create) { + var scriptTable = this.getScriptTable(script, create); + if (scriptTable) { + if (!language || language === 'dflt' || language === 'DFLT') { + return scriptTable.defaultLangSys; + } + var pos = searchTag(scriptTable.langSysRecords, language); + if (pos >= 0) { + return scriptTable.langSysRecords[pos].langSys; + } else if (create) { + var langSysRecord = { + tag: language, + langSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []} + }; + scriptTable.langSysRecords.splice(-1 - pos, 0, langSysRecord); + return langSysRecord.langSys; + } + } + }, + + /** + * Get a specific feature table. + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {string} feature - One of the codes listed at https://www.microsoft.com/typography/OTSPEC/featurelist.htm + * @param {boolean} create - forces the creation of the feature table if it doesn't exist. + * @return {Object} + */ + getFeatureTable: function(script, language, feature, create) { + var langSysTable = this.getLangSysTable(script, language, create); + if (langSysTable) { + var featureRecord; + var featIndexes = langSysTable.featureIndexes; + var allFeatures = this.font.tables[this.tableName].features; + // The FeatureIndex array of indices is in arbitrary order, + // even if allFeatures is sorted alphabetically by feature tag. + for (var i = 0; i < featIndexes.length; i++) { + featureRecord = allFeatures[featIndexes[i]]; + if (featureRecord.tag === feature) { + return featureRecord.feature; + } + } + if (create) { + var index = allFeatures.length; + // Automatic ordering of features would require to shift feature indexes in the script list. + check.assert(index === 0 || feature >= allFeatures[index - 1].tag, 'Features must be added in alphabetical order.'); + featureRecord = { + tag: feature, + feature: { params: 0, lookupListIndexes: [] } + }; + allFeatures.push(featureRecord); + featIndexes.push(index); + return featureRecord.feature; + } + } + }, + + /** + * Get the lookup tables of a given type for a script/language/feature. + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {string} feature - 4-letter feature code + * @param {number} lookupType - 1 to 9 + * @param {boolean} create - forces the creation of the lookup table if it doesn't exist, with no subtables. + * @return {Object[]} + */ + getLookupTables: function(script, language, feature, lookupType, create) { + var featureTable = this.getFeatureTable(script, language, feature, create); + var tables = []; + if (featureTable) { + var lookupTable; + var lookupListIndexes = featureTable.lookupListIndexes; + var allLookups = this.font.tables[this.tableName].lookups; + // lookupListIndexes are in no particular order, so use naive search. + for (var i = 0; i < lookupListIndexes.length; i++) { + lookupTable = allLookups[lookupListIndexes[i]]; + if (lookupTable.lookupType === lookupType) { + tables.push(lookupTable); + } + } + if (tables.length === 0 && create) { + lookupTable = { + lookupType: lookupType, + lookupFlag: 0, + subtables: [], + markFilteringSet: undefined + }; + var index = allLookups.length; + allLookups.push(lookupTable); + lookupListIndexes.push(index); + return [lookupTable]; + } + } + return tables; + }, + + /** + * Find a glyph in a class definition table + * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table + * @param {object} classDefTable - an OpenType Layout class definition table + * @param {number} glyphIndex - the index of the glyph to find + * @returns {number} -1 if not found + */ + getGlyphClass: function(classDefTable, glyphIndex) { + switch (classDefTable.format) { + case 1: + if (classDefTable.startGlyph <= glyphIndex && glyphIndex < classDefTable.startGlyph + classDefTable.classes.length) { + return classDefTable.classes[glyphIndex - classDefTable.startGlyph]; + } + return 0; + case 2: + var range = searchRange(classDefTable.ranges, glyphIndex); + return range ? range.classId : 0; + } + }, + + /** + * Find a glyph in a coverage table + * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-table + * @param {object} coverageTable - an OpenType Layout coverage table + * @param {number} glyphIndex - the index of the glyph to find + * @returns {number} -1 if not found + */ + getCoverageIndex: function(coverageTable, glyphIndex) { + switch (coverageTable.format) { + case 1: + var index = binSearch(coverageTable.glyphs, glyphIndex); + return index >= 0 ? index : -1; + case 2: + var range = searchRange(coverageTable.ranges, glyphIndex); + return range ? range.index + glyphIndex - range.start : -1; + } + }, + + /** + * Returns the list of glyph indexes of a coverage table. + * Format 1: the list is stored raw + * Format 2: compact list as range records. + * @instance + * @param {Object} coverageTable + * @return {Array} + */ + expandCoverage: function(coverageTable) { + if (coverageTable.format === 1) { + return coverageTable.glyphs; + } else { + var glyphs = []; + var ranges = coverageTable.ranges; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + var start = range.start; + var end = range.end; + for (var j = start; j <= end; j++) { + glyphs.push(j); + } + } + return glyphs; + } + } + + }; + + // The Position object provides utility methods to manipulate + + /** + * @exports opentype.Position + * @class + * @extends opentype.Layout + * @param {opentype.Font} + * @constructor + */ + function Position(font) { + Layout.call(this, font, 'gpos'); + } + + Position.prototype = Layout.prototype; + + /** + * Init some data for faster and easier access later. + */ + Position.prototype.init = function() { + var script = this.getDefaultScriptName(); + this.defaultKerningTables = this.getKerningTables(script); + }; + + /** + * Find a glyph pair in a list of lookup tables of type 2 and retrieve the xAdvance kerning value. + * + * @param {integer} leftIndex - left glyph index + * @param {integer} rightIndex - right glyph index + * @returns {integer} + */ + Position.prototype.getKerningValue = function(kerningLookups, leftIndex, rightIndex) { + for (var i = 0; i < kerningLookups.length; i++) { + var subtables = kerningLookups[i].subtables; + for (var j = 0; j < subtables.length; j++) { + var subtable = subtables[j]; + var covIndex = this.getCoverageIndex(subtable.coverage, leftIndex); + if (covIndex < 0) { continue; } + switch (subtable.posFormat) { + case 1: + // Search Pair Adjustment Positioning Format 1 + var pairSet = subtable.pairSets[covIndex]; + for (var k = 0; k < pairSet.length; k++) { + var pair = pairSet[k]; + if (pair.secondGlyph === rightIndex) { + return pair.value1 && pair.value1.xAdvance || 0; + } + } + break; // left glyph found, not right glyph - try next subtable + case 2: + // Search Pair Adjustment Positioning Format 2 + var class1 = this.getGlyphClass(subtable.classDef1, leftIndex); + var class2 = this.getGlyphClass(subtable.classDef2, rightIndex); + var pair$1 = subtable.classRecords[class1][class2]; + return pair$1.value1 && pair$1.value1.xAdvance || 0; + } + } + } + return 0; + }; + + /** + * List all kerning lookup tables. + * + * @param {string} [script='DFLT'] - use font.position.getDefaultScriptName() for a better default value + * @param {string} [language='dflt'] + * @return {object[]} The list of kerning lookup tables (may be empty), or undefined if there is no GPOS table (and we should use the kern table) + */ + Position.prototype.getKerningTables = function(script, language) { + if (this.font.tables.gpos) { + return this.getLookupTables(script, language, 'kern', 2); + } + }; + + // The Substitution object provides utility methods to manipulate + + /** + * @exports opentype.Substitution + * @class + * @extends opentype.Layout + * @param {opentype.Font} + * @constructor + */ + function Substitution(font) { + Layout.call(this, font, 'gsub'); + } + + // Check if 2 arrays of primitives are equal. + function arraysEqual(ar1, ar2) { + var n = ar1.length; + if (n !== ar2.length) { return false; } + for (var i = 0; i < n; i++) { + if (ar1[i] !== ar2[i]) { return false; } + } + return true; + } + + // Find the first subtable of a lookup table in a particular format. + function getSubstFormat(lookupTable, format, defaultSubtable) { + var subtables = lookupTable.subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + if (subtable.substFormat === format) { + return subtable; + } + } + if (defaultSubtable) { + subtables.push(defaultSubtable); + return defaultSubtable; + } + return undefined; + } + + Substitution.prototype = Layout.prototype; + + /** + * Create a default GSUB table. + * @return {Object} gsub - The GSUB table. + */ + Substitution.prototype.createDefaultTable = function() { + // Generate a default empty GSUB table with just a DFLT script and dflt lang sys. + return { + version: 1, + scripts: [{ + tag: 'DFLT', + script: { + defaultLangSys: { reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: [] }, + langSysRecords: [] + } + }], + features: [], + lookups: [] + }; + }; + + /** + * List all single substitutions (lookup type 1) for a given script, language, and feature. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @param {string} feature - 4-character feature name ('aalt', 'salt', 'ss01'...) + * @return {Array} substitutions - The list of substitutions. + */ + Substitution.prototype.getSingle = function(feature, script, language) { + var substitutions = []; + var lookupTables = this.getLookupTables(script, language, feature, 1); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var j = (void 0); + if (subtable.substFormat === 1) { + var delta = subtable.deltaGlyphId; + for (j = 0; j < glyphs.length; j++) { + var glyph = glyphs[j]; + substitutions.push({ sub: glyph, by: glyph + delta }); + } + } else { + var substitute = subtable.substitute; + for (j = 0; j < glyphs.length; j++) { + substitutions.push({ sub: glyphs[j], by: substitute[j] }); + } + } + } + } + return substitutions; + }; + + /** + * List all alternates (lookup type 3) for a given script, language, and feature. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @param {string} feature - 4-character feature name ('aalt', 'salt'...) + * @return {Array} alternates - The list of alternates + */ + Substitution.prototype.getAlternates = function(feature, script, language) { + var alternates = []; + var lookupTables = this.getLookupTables(script, language, feature, 3); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var alternateSets = subtable.alternateSets; + for (var j = 0; j < glyphs.length; j++) { + alternates.push({ sub: glyphs[j], by: alternateSets[j] }); + } + } + } + return alternates; + }; + + /** + * List all ligatures (lookup type 4) for a given script, language, and feature. + * The result is an array of ligature objects like { sub: [ids], by: id } + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @return {Array} ligatures - The list of ligatures. + */ + Substitution.prototype.getLigatures = function(feature, script, language) { + var ligatures = []; + var lookupTables = this.getLookupTables(script, language, feature, 4); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var ligatureSets = subtable.ligatureSets; + for (var j = 0; j < glyphs.length; j++) { + var startGlyph = glyphs[j]; + var ligSet = ligatureSets[j]; + for (var k = 0; k < ligSet.length; k++) { + var lig = ligSet[k]; + ligatures.push({ + sub: [startGlyph].concat(lig.components), + by: lig.ligGlyph + }); + } + } + } + } + return ligatures; + }; + + /** + * Add or modify a single substitution (lookup type 1) + * Format 2, more flexible, is always used. + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {Object} substitution - { sub: id, delta: number } for format 1 or { sub: id, by: id } for format 2. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ + Substitution.prototype.addSingle = function(feature, substitution, script, language) { + var lookupTable = this.getLookupTables(script, language, feature, 1, true)[0]; + var subtable = getSubstFormat(lookupTable, 2, { // lookup type 1 subtable, format 2, coverage format 1 + substFormat: 2, + coverage: {format: 1, glyphs: []}, + substitute: [] + }); + check.assert(subtable.coverage.format === 1, 'Ligature: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.substitute.splice(pos, 0, 0); + } + subtable.substitute[pos] = substitution.by; + }; + + /** + * Add or modify an alternate substitution (lookup type 1) + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {Object} substitution - { sub: id, by: [ids] } + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ + Substitution.prototype.addAlternate = function(feature, substitution, script, language) { + var lookupTable = this.getLookupTables(script, language, feature, 3, true)[0]; + var subtable = getSubstFormat(lookupTable, 1, { // lookup type 3 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: {format: 1, glyphs: []}, + alternateSets: [] + }); + check.assert(subtable.coverage.format === 1, 'Ligature: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.alternateSets.splice(pos, 0, 0); + } + subtable.alternateSets[pos] = substitution.by; + }; + + /** + * Add a ligature (lookup type 4) + * Ligatures with more components must be stored ahead of those with fewer components in order to be found + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {Object} ligature - { sub: [ids], by: id } + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ + Substitution.prototype.addLigature = function(feature, ligature, script, language) { + var lookupTable = this.getLookupTables(script, language, feature, 4, true)[0]; + var subtable = lookupTable.subtables[0]; + if (!subtable) { + subtable = { // lookup type 4 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: { format: 1, glyphs: [] }, + ligatureSets: [] + }; + lookupTable.subtables[0] = subtable; + } + check.assert(subtable.coverage.format === 1, 'Ligature: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = ligature.sub[0]; + var ligComponents = ligature.sub.slice(1); + var ligatureTable = { + ligGlyph: ligature.by, + components: ligComponents + }; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos >= 0) { + // ligatureSet already exists + var ligatureSet = subtable.ligatureSets[pos]; + for (var i = 0; i < ligatureSet.length; i++) { + // If ligature already exists, return. + if (arraysEqual(ligatureSet[i].components, ligComponents)) { + return; + } + } + // ligature does not exist: add it. + ligatureSet.push(ligatureTable); + } else { + // Create a new ligatureSet and add coverage for the first glyph. + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.ligatureSets.splice(pos, 0, [ligatureTable]); + } + }; + + /** + * List all feature data for a given script and language. + * @param {string} feature - 4-letter feature name + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @return {Array} substitutions - The list of substitutions. + */ + Substitution.prototype.getFeature = function(feature, script, language) { + if (/ss\d\d/.test(feature)) { + // ss01 - ss20 + return this.getSingle(feature, script, language); + } + switch (feature) { + case 'aalt': + case 'salt': + return this.getSingle(feature, script, language) + .concat(this.getAlternates(feature, script, language)); + case 'dlig': + case 'liga': + case 'rlig': return this.getLigatures(feature, script, language); + } + return undefined; + }; + + /** + * Add a substitution to a feature for a given script and language. + * @param {string} feature - 4-letter feature name + * @param {Object} sub - the substitution to add (an object like { sub: id or [ids], by: id or [ids] }) + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ + Substitution.prototype.add = function(feature, sub, script, language) { + if (/ss\d\d/.test(feature)) { + // ss01 - ss20 + return this.addSingle(feature, sub, script, language); + } + switch (feature) { + case 'aalt': + case 'salt': + if (typeof sub.by === 'number') { + return this.addSingle(feature, sub, script, language); + } + return this.addAlternate(feature, sub, script, language); + case 'dlig': + case 'liga': + case 'rlig': + return this.addLigature(feature, sub, script, language); + } + return undefined; + }; + + function isBrowser() { + return typeof window !== 'undefined'; + } + + function nodeBufferToArrayBuffer(buffer) { + var ab = new ArrayBuffer(buffer.length); + var view = new Uint8Array(ab); + for (var i = 0; i < buffer.length; ++i) { + view[i] = buffer[i]; + } + + return ab; + } + + function arrayBufferToNodeBuffer(ab) { + var buffer = new Buffer(ab.byteLength); + var view = new Uint8Array(ab); + for (var i = 0; i < buffer.length; ++i) { + buffer[i] = view[i]; + } + + return buffer; + } + + function checkArgument(expression, message) { + if (!expression) { + throw message; + } + } + + // The `glyf` table describes the glyphs in TrueType outline format. + + // Parse the coordinate data for a glyph. + function parseGlyphCoordinate(p, flag, previousValue, shortVectorBitMask, sameBitMask) { + var v; + if ((flag & shortVectorBitMask) > 0) { + // The coordinate is 1 byte long. + v = p.parseByte(); + // The `same` bit is re-used for short values to signify the sign of the value. + if ((flag & sameBitMask) === 0) { + v = -v; + } + + v = previousValue + v; + } else { + // The coordinate is 2 bytes long. + // If the `same` bit is set, the coordinate is the same as the previous coordinate. + if ((flag & sameBitMask) > 0) { + v = previousValue; + } else { + // Parse the coordinate as a signed 16-bit delta value. + v = previousValue + p.parseShort(); + } + } + + return v; + } + + // Parse a TrueType glyph. + function parseGlyph(glyph, data, start) { + var p = new parse.Parser(data, start); + glyph.numberOfContours = p.parseShort(); + glyph._xMin = p.parseShort(); + glyph._yMin = p.parseShort(); + glyph._xMax = p.parseShort(); + glyph._yMax = p.parseShort(); + var flags; + var flag; + + if (glyph.numberOfContours > 0) { + // This glyph is not a composite. + var endPointIndices = glyph.endPointIndices = []; + for (var i = 0; i < glyph.numberOfContours; i += 1) { + endPointIndices.push(p.parseUShort()); + } + + glyph.instructionLength = p.parseUShort(); + glyph.instructions = []; + for (var i$1 = 0; i$1 < glyph.instructionLength; i$1 += 1) { + glyph.instructions.push(p.parseByte()); + } + + var numberOfCoordinates = endPointIndices[endPointIndices.length - 1] + 1; + flags = []; + for (var i$2 = 0; i$2 < numberOfCoordinates; i$2 += 1) { + flag = p.parseByte(); + flags.push(flag); + // If bit 3 is set, we repeat this flag n times, where n is the next byte. + if ((flag & 8) > 0) { + var repeatCount = p.parseByte(); + for (var j = 0; j < repeatCount; j += 1) { + flags.push(flag); + i$2 += 1; + } + } + } + + check.argument(flags.length === numberOfCoordinates, 'Bad flags.'); + + if (endPointIndices.length > 0) { + var points = []; + var point; + // X/Y coordinates are relative to the previous point, except for the first point which is relative to 0,0. + if (numberOfCoordinates > 0) { + for (var i$3 = 0; i$3 < numberOfCoordinates; i$3 += 1) { + flag = flags[i$3]; + point = {}; + point.onCurve = !!(flag & 1); + point.lastPointOfContour = endPointIndices.indexOf(i$3) >= 0; + points.push(point); + } + + var px = 0; + for (var i$4 = 0; i$4 < numberOfCoordinates; i$4 += 1) { + flag = flags[i$4]; + point = points[i$4]; + point.x = parseGlyphCoordinate(p, flag, px, 2, 16); + px = point.x; + } + + var py = 0; + for (var i$5 = 0; i$5 < numberOfCoordinates; i$5 += 1) { + flag = flags[i$5]; + point = points[i$5]; + point.y = parseGlyphCoordinate(p, flag, py, 4, 32); + py = point.y; + } + } + + glyph.points = points; + } else { + glyph.points = []; + } + } else if (glyph.numberOfContours === 0) { + glyph.points = []; + } else { + glyph.isComposite = true; + glyph.points = []; + glyph.components = []; + var moreComponents = true; + while (moreComponents) { + flags = p.parseUShort(); + var component = { + glyphIndex: p.parseUShort(), + xScale: 1, + scale01: 0, + scale10: 0, + yScale: 1, + dx: 0, + dy: 0 + }; + if ((flags & 1) > 0) { + // The arguments are words + if ((flags & 2) > 0) { + // values are offset + component.dx = p.parseShort(); + component.dy = p.parseShort(); + } else { + // values are matched points + component.matchedPoints = [p.parseUShort(), p.parseUShort()]; + } + + } else { + // The arguments are bytes + if ((flags & 2) > 0) { + // values are offset + component.dx = p.parseChar(); + component.dy = p.parseChar(); + } else { + // values are matched points + component.matchedPoints = [p.parseByte(), p.parseByte()]; + } + } + + if ((flags & 8) > 0) { + // We have a scale + component.xScale = component.yScale = p.parseF2Dot14(); + } else if ((flags & 64) > 0) { + // We have an X / Y scale + component.xScale = p.parseF2Dot14(); + component.yScale = p.parseF2Dot14(); + } else if ((flags & 128) > 0) { + // We have a 2x2 transformation + component.xScale = p.parseF2Dot14(); + component.scale01 = p.parseF2Dot14(); + component.scale10 = p.parseF2Dot14(); + component.yScale = p.parseF2Dot14(); + } + + glyph.components.push(component); + moreComponents = !!(flags & 32); + } + if (flags & 0x100) { + // We have instructions + glyph.instructionLength = p.parseUShort(); + glyph.instructions = []; + for (var i$6 = 0; i$6 < glyph.instructionLength; i$6 += 1) { + glyph.instructions.push(p.parseByte()); + } + } + } + } + + // Transform an array of points and return a new array. + function transformPoints(points, transform) { + var newPoints = []; + for (var i = 0; i < points.length; i += 1) { + var pt = points[i]; + var newPt = { + x: transform.xScale * pt.x + transform.scale01 * pt.y + transform.dx, + y: transform.scale10 * pt.x + transform.yScale * pt.y + transform.dy, + onCurve: pt.onCurve, + lastPointOfContour: pt.lastPointOfContour + }; + newPoints.push(newPt); + } + + return newPoints; + } + + function getContours(points) { + var contours = []; + var currentContour = []; + for (var i = 0; i < points.length; i += 1) { + var pt = points[i]; + currentContour.push(pt); + if (pt.lastPointOfContour) { + contours.push(currentContour); + currentContour = []; + } + } + + check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); + return contours; + } + + // Convert the TrueType glyph outline to a Path. + function getPath(points) { + var p = new Path(); + if (!points) { + return p; + } + + var contours = getContours(points); + + for (var contourIndex = 0; contourIndex < contours.length; ++contourIndex) { + var contour = contours[contourIndex]; + + var prev = null; + var curr = contour[contour.length - 1]; + var next = contour[0]; + + if (curr.onCurve) { + p.moveTo(curr.x, curr.y); + } else { + if (next.onCurve) { + p.moveTo(next.x, next.y); + } else { + // If both first and last points are off-curve, start at their middle. + var start = {x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5}; + p.moveTo(start.x, start.y); + } + } + + for (var i = 0; i < contour.length; ++i) { + prev = curr; + curr = next; + next = contour[(i + 1) % contour.length]; + + if (curr.onCurve) { + // This is a straight line. + p.lineTo(curr.x, curr.y); + } else { + var prev2 = prev; + var next2 = next; + + if (!prev.onCurve) { + prev2 = { x: (curr.x + prev.x) * 0.5, y: (curr.y + prev.y) * 0.5 }; + } + + if (!next.onCurve) { + next2 = { x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5 }; + } + + p.quadraticCurveTo(curr.x, curr.y, next2.x, next2.y); + } + } + + p.closePath(); + } + return p; + } + + function buildPath(glyphs, glyph) { + if (glyph.isComposite) { + for (var j = 0; j < glyph.components.length; j += 1) { + var component = glyph.components[j]; + var componentGlyph = glyphs.get(component.glyphIndex); + // Force the ttfGlyphLoader to parse the glyph. + componentGlyph.getPath(); + if (componentGlyph.points) { + var transformedPoints = (void 0); + if (component.matchedPoints === undefined) { + // component positioned by offset + transformedPoints = transformPoints(componentGlyph.points, component); + } else { + // component positioned by matched points + if ((component.matchedPoints[0] > glyph.points.length - 1) || + (component.matchedPoints[1] > componentGlyph.points.length - 1)) { + throw Error('Matched points out of range in ' + glyph.name); + } + var firstPt = glyph.points[component.matchedPoints[0]]; + var secondPt = componentGlyph.points[component.matchedPoints[1]]; + var transform = { + xScale: component.xScale, scale01: component.scale01, + scale10: component.scale10, yScale: component.yScale, + dx: 0, dy: 0 + }; + secondPt = transformPoints([secondPt], transform)[0]; + transform.dx = firstPt.x - secondPt.x; + transform.dy = firstPt.y - secondPt.y; + transformedPoints = transformPoints(componentGlyph.points, transform); + } + glyph.points = glyph.points.concat(transformedPoints); + } + } + } + + return getPath(glyph.points); + } + + function parseGlyfTableAll(data, start, loca, font) { + var glyphs = new glyphset.GlyphSet(font); + + // The last element of the loca table is invalid. + for (var i = 0; i < loca.length - 1; i += 1) { + var offset = loca[i]; + var nextOffset = loca[i + 1]; + if (offset !== nextOffset) { + glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); + } else { + glyphs.push(i, glyphset.glyphLoader(font, i)); + } + } + + return glyphs; + } + + function parseGlyfTableOnLowMemory(data, start, loca, font) { + var glyphs = new glyphset.GlyphSet(font); + + font._push = function(i) { + var offset = loca[i]; + var nextOffset = loca[i + 1]; + if (offset !== nextOffset) { + glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); + } else { + glyphs.push(i, glyphset.glyphLoader(font, i)); + } + }; + + return glyphs; + } + + // Parse all the glyphs according to the offsets from the `loca` table. + function parseGlyfTable(data, start, loca, font, opt) { + if (opt.lowMemory) + { return parseGlyfTableOnLowMemory(data, start, loca, font); } + else + { return parseGlyfTableAll(data, start, loca, font); } + } + + var glyf = { getPath: getPath, parse: parseGlyfTable}; + + /* A TrueType font hinting interpreter. + * + * (c) 2017 Axel Kittenberger + * + * This interpreter has been implemented according to this documentation: + * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM05/Chap5.html + * + * According to the documentation F24DOT6 values are used for pixels. + * That means calculation is 1/64 pixel accurate and uses integer operations. + * However, Javascript has floating point operations by default and only + * those are available. One could make a case to simulate the 1/64 accuracy + * exactly by truncating after every division operation + * (for example with << 0) to get pixel exactly results as other TrueType + * implementations. It may make sense since some fonts are pixel optimized + * by hand using DELTAP instructions. The current implementation doesn't + * and rather uses full floating point precision. + * + * xScale, yScale and rotation is currently ignored. + * + * A few non-trivial instructions are missing as I didn't encounter yet + * a font that used them to test a possible implementation. + * + * Some fonts seem to use undocumented features regarding the twilight zone. + * Only some of them are implemented as they were encountered. + * + * The exports.DEBUG statements are removed on the minified distribution file. + */ + + var instructionTable; + var exec; + var execGlyph; + var execComponent; + + /* + * Creates a hinting object. + * + * There ought to be exactly one + * for each truetype font that is used for hinting. + */ + function Hinting(font) { + // the font this hinting object is for + this.font = font; + + this.getCommands = function (hPoints) { + return glyf.getPath(hPoints).commands; + }; + + // cached states + this._fpgmState = + this._prepState = + undefined; + + // errorState + // 0 ... all okay + // 1 ... had an error in a glyf, + // continue working but stop spamming + // the console + // 2 ... error at prep, stop hinting at this ppem + // 3 ... error at fpeg, stop hinting for this font at all + this._errorState = 0; + } + + /* + * Not rounding. + */ + function roundOff(v) { + return v; + } + + /* + * Rounding to grid. + */ + function roundToGrid(v) { + //Rounding in TT is supposed to "symmetrical around zero" + return Math.sign(v) * Math.round(Math.abs(v)); + } + + /* + * Rounding to double grid. + */ + function roundToDoubleGrid(v) { + return Math.sign(v) * Math.round(Math.abs(v * 2)) / 2; + } + + /* + * Rounding to half grid. + */ + function roundToHalfGrid(v) { + return Math.sign(v) * (Math.round(Math.abs(v) + 0.5) - 0.5); + } + + /* + * Rounding to up to grid. + */ + function roundUpToGrid(v) { + return Math.sign(v) * Math.ceil(Math.abs(v)); + } + + /* + * Rounding to down to grid. + */ + function roundDownToGrid(v) { + return Math.sign(v) * Math.floor(Math.abs(v)); + } + + /* + * Super rounding. + */ + var roundSuper = function (v) { + var period = this.srPeriod; + var phase = this.srPhase; + var threshold = this.srThreshold; + var sign = 1; + + if (v < 0) { + v = -v; + sign = -1; + } + + v += threshold - phase; + + v = Math.trunc(v / period) * period; + + v += phase; + + // according to http://xgridfit.sourceforge.net/round.html + if (v < 0) { return phase * sign; } + + return v * sign; + }; + + /* + * Unit vector of x-axis. + */ + var xUnitVector = { + x: 1, + + y: 0, + + axis: 'x', + + // Gets the projected distance between two points. + // o1/o2 ... if true, respective original position is used. + distance: function (p1, p2, o1, o2) { + return (o1 ? p1.xo : p1.x) - (o2 ? p2.xo : p2.x); + }, + + // Moves point p so the moved position has the same relative + // position to the moved positions of rp1 and rp2 than the + // original positions had. + // + // See APPENDIX on INTERPOLATE at the bottom of this file. + interpolate: function (p, rp1, rp2, pv) { + var do1; + var do2; + var doa1; + var doa2; + var dm1; + var dm2; + var dt; + + if (!pv || pv === this) { + do1 = p.xo - rp1.xo; + do2 = p.xo - rp2.xo; + dm1 = rp1.x - rp1.xo; + dm2 = rp2.x - rp2.xo; + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + p.x = p.xo + (dm1 + dm2) / 2; + return; + } + + p.x = p.xo + (dm1 * doa2 + dm2 * doa1) / dt; + return; + } + + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + xUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } + + xUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); + }, + + // Slope of line normal to this + normalSlope: Number.NEGATIVE_INFINITY, + + // Sets the point 'p' relative to point 'rp' + // by the distance 'd'. + // + // See APPENDIX on SETRELATIVE at the bottom of this file. + // + // p ... point to set + // rp ... reference point + // d ... distance on projection vector + // pv ... projection vector (undefined = this) + // org ... if true, uses the original position of rp as reference. + setRelative: function (p, rp, d, pv, org) { + if (!pv || pv === this) { + p.x = (org ? rp.xo : rp.x) + d; + return; + } + + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d * pv.x; + var rpdy = rpy + d * pv.y; + + p.x = rpdx + (p.y - rpdy) / pv.normalSlope; + }, + + // Slope of vector line. + slope: 0, + + // Touches the point p. + touch: function (p) { + p.xTouched = true; + }, + + // Tests if a point p is touched. + touched: function (p) { + return p.xTouched; + }, + + // Untouches the point p. + untouch: function (p) { + p.xTouched = false; + } + }; + + /* + * Unit vector of y-axis. + */ + var yUnitVector = { + x: 0, + + y: 1, + + axis: 'y', + + // Gets the projected distance between two points. + // o1/o2 ... if true, respective original position is used. + distance: function (p1, p2, o1, o2) { + return (o1 ? p1.yo : p1.y) - (o2 ? p2.yo : p2.y); + }, + + // Moves point p so the moved position has the same relative + // position to the moved positions of rp1 and rp2 than the + // original positions had. + // + // See APPENDIX on INTERPOLATE at the bottom of this file. + interpolate: function (p, rp1, rp2, pv) { + var do1; + var do2; + var doa1; + var doa2; + var dm1; + var dm2; + var dt; + + if (!pv || pv === this) { + do1 = p.yo - rp1.yo; + do2 = p.yo - rp2.yo; + dm1 = rp1.y - rp1.yo; + dm2 = rp2.y - rp2.yo; + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + p.y = p.yo + (dm1 + dm2) / 2; + return; + } + + p.y = p.yo + (dm1 * doa2 + dm2 * doa1) / dt; + return; + } + + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + yUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } + + yUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); + }, + + // Slope of line normal to this. + normalSlope: 0, + + // Sets the point 'p' relative to point 'rp' + // by the distance 'd' + // + // See APPENDIX on SETRELATIVE at the bottom of this file. + // + // p ... point to set + // rp ... reference point + // d ... distance on projection vector + // pv ... projection vector (undefined = this) + // org ... if true, uses the original position of rp as reference. + setRelative: function (p, rp, d, pv, org) { + if (!pv || pv === this) { + p.y = (org ? rp.yo : rp.y) + d; + return; + } + + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d * pv.x; + var rpdy = rpy + d * pv.y; + + p.y = rpdy + pv.normalSlope * (p.x - rpdx); + }, + + // Slope of vector line. + slope: Number.POSITIVE_INFINITY, + + // Touches the point p. + touch: function (p) { + p.yTouched = true; + }, + + // Tests if a point p is touched. + touched: function (p) { + return p.yTouched; + }, + + // Untouches the point p. + untouch: function (p) { + p.yTouched = false; + } + }; + + Object.freeze(xUnitVector); + Object.freeze(yUnitVector); + + /* + * Creates a unit vector that is not x- or y-axis. + */ + function UnitVector(x, y) { + this.x = x; + this.y = y; + this.axis = undefined; + this.slope = y / x; + this.normalSlope = -x / y; + Object.freeze(this); + } + + /* + * Gets the projected distance between two points. + * o1/o2 ... if true, respective original position is used. + */ + UnitVector.prototype.distance = function(p1, p2, o1, o2) { + return ( + this.x * xUnitVector.distance(p1, p2, o1, o2) + + this.y * yUnitVector.distance(p1, p2, o1, o2) + ); + }; + + /* + * Moves point p so the moved position has the same relative + * position to the moved positions of rp1 and rp2 than the + * original positions had. + * + * See APPENDIX on INTERPOLATE at the bottom of this file. + */ + UnitVector.prototype.interpolate = function(p, rp1, rp2, pv) { + var dm1; + var dm2; + var do1; + var do2; + var doa1; + var doa2; + var dt; + + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + this.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } + + this.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); + }; + + /* + * Sets the point 'p' relative to point 'rp' + * by the distance 'd' + * + * See APPENDIX on SETRELATIVE at the bottom of this file. + * + * p ... point to set + * rp ... reference point + * d ... distance on projection vector + * pv ... projection vector (undefined = this) + * org ... if true, uses the original position of rp as reference. + */ + UnitVector.prototype.setRelative = function(p, rp, d, pv, org) { + pv = pv || this; + + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d * pv.x; + var rpdy = rpy + d * pv.y; + + var pvns = pv.normalSlope; + var fvs = this.slope; + + var px = p.x; + var py = p.y; + + p.x = (fvs * px - pvns * rpdx + rpdy - py) / (fvs - pvns); + p.y = fvs * (p.x - px) + py; + }; + + /* + * Touches the point p. + */ + UnitVector.prototype.touch = function(p) { + p.xTouched = true; + p.yTouched = true; + }; + + /* + * Returns a unit vector with x/y coordinates. + */ + function getUnitVector(x, y) { + var d = Math.sqrt(x * x + y * y); + + x /= d; + y /= d; + + if (x === 1 && y === 0) { return xUnitVector; } + else if (x === 0 && y === 1) { return yUnitVector; } + else { return new UnitVector(x, y); } + } + + /* + * Creates a point in the hinting engine. + */ + function HPoint( + x, + y, + lastPointOfContour, + onCurve + ) { + this.x = this.xo = Math.round(x * 64) / 64; // hinted x value and original x-value + this.y = this.yo = Math.round(y * 64) / 64; // hinted y value and original y-value + + this.lastPointOfContour = lastPointOfContour; + this.onCurve = onCurve; + this.prevPointOnContour = undefined; + this.nextPointOnContour = undefined; + this.xTouched = false; + this.yTouched = false; + + Object.preventExtensions(this); + } + + /* + * Returns the next touched point on the contour. + * + * v ... unit vector to test touch axis. + */ + HPoint.prototype.nextTouched = function(v) { + var p = this.nextPointOnContour; + + while (!v.touched(p) && p !== this) { p = p.nextPointOnContour; } + + return p; + }; + + /* + * Returns the previous touched point on the contour + * + * v ... unit vector to test touch axis. + */ + HPoint.prototype.prevTouched = function(v) { + var p = this.prevPointOnContour; + + while (!v.touched(p) && p !== this) { p = p.prevPointOnContour; } + + return p; + }; + + /* + * The zero point. + */ + var HPZero = Object.freeze(new HPoint(0, 0)); + + /* + * The default state of the interpreter. + * + * Note: Freezing the defaultState and then deriving from it + * makes the V8 Javascript engine going awkward, + * so this is avoided, albeit the defaultState shouldn't + * ever change. + */ + var defaultState = { + cvCutIn: 17 / 16, // control value cut in + deltaBase: 9, + deltaShift: 0.125, + loop: 1, // loops some instructions + minDis: 1, // minimum distance + autoFlip: true + }; + + /* + * The current state of the interpreter. + * + * env ... 'fpgm' or 'prep' or 'glyf' + * prog ... the program + */ + function State(env, prog) { + this.env = env; + this.stack = []; + this.prog = prog; + + switch (env) { + case 'glyf' : + this.zp0 = this.zp1 = this.zp2 = 1; + this.rp0 = this.rp1 = this.rp2 = 0; + /* fall through */ + case 'prep' : + this.fv = this.pv = this.dpv = xUnitVector; + this.round = roundToGrid; + } + } + + /* + * Executes a glyph program. + * + * This does the hinting for each glyph. + * + * Returns an array of moved points. + * + * glyph: the glyph to hint + * ppem: the size the glyph is rendered for + */ + Hinting.prototype.exec = function(glyph, ppem) { + if (typeof ppem !== 'number') { + throw new Error('Point size is not a number!'); + } + + // Received a fatal error, don't do any hinting anymore. + if (this._errorState > 2) { return; } + + var font = this.font; + var prepState = this._prepState; + + if (!prepState || prepState.ppem !== ppem) { + var fpgmState = this._fpgmState; + + if (!fpgmState) { + // Executes the fpgm state. + // This is used by fonts to define functions. + State.prototype = defaultState; + + fpgmState = + this._fpgmState = + new State('fpgm', font.tables.fpgm); + + fpgmState.funcs = [ ]; + fpgmState.font = font; + + if (exports.DEBUG) { + console.log('---EXEC FPGM---'); + fpgmState.step = -1; + } + + try { + exec(fpgmState); + } catch (e) { + console.log('Hinting error in FPGM:' + e); + this._errorState = 3; + return; + } + } + + // Executes the prep program for this ppem setting. + // This is used by fonts to set cvt values + // depending on to be rendered font size. + + State.prototype = fpgmState; + prepState = + this._prepState = + new State('prep', font.tables.prep); + + prepState.ppem = ppem; + + // Creates a copy of the cvt table + // and scales it to the current ppem setting. + var oCvt = font.tables.cvt; + if (oCvt) { + var cvt = prepState.cvt = new Array(oCvt.length); + var scale = ppem / font.unitsPerEm; + for (var c = 0; c < oCvt.length; c++) { + cvt[c] = oCvt[c] * scale; + } + } else { + prepState.cvt = []; + } + + if (exports.DEBUG) { + console.log('---EXEC PREP---'); + prepState.step = -1; + } + + try { + exec(prepState); + } catch (e) { + if (this._errorState < 2) { + console.log('Hinting error in PREP:' + e); + } + this._errorState = 2; + } + } + + if (this._errorState > 1) { return; } + + try { + return execGlyph(glyph, prepState); + } catch (e) { + if (this._errorState < 1) { + console.log('Hinting error:' + e); + console.log('Note: further hinting errors are silenced'); + } + this._errorState = 1; + return undefined; + } + }; + + /* + * Executes the hinting program for a glyph. + */ + execGlyph = function(glyph, prepState) { + // original point positions + var xScale = prepState.ppem / prepState.font.unitsPerEm; + var yScale = xScale; + var components = glyph.components; + var contours; + var gZone; + var state; + + State.prototype = prepState; + if (!components) { + state = new State('glyf', glyph.instructions); + if (exports.DEBUG) { + console.log('---EXEC GLYPH---'); + state.step = -1; + } + execComponent(glyph, state, xScale, yScale); + gZone = state.gZone; + } else { + var font = prepState.font; + gZone = []; + contours = []; + for (var i = 0; i < components.length; i++) { + var c = components[i]; + var cg = font.glyphs.get(c.glyphIndex); + + state = new State('glyf', cg.instructions); + + if (exports.DEBUG) { + console.log('---EXEC COMP ' + i + '---'); + state.step = -1; + } + + execComponent(cg, state, xScale, yScale); + // appends the computed points to the result array + // post processes the component points + var dx = Math.round(c.dx * xScale); + var dy = Math.round(c.dy * yScale); + var gz = state.gZone; + var cc = state.contours; + for (var pi = 0; pi < gz.length; pi++) { + var p = gz[pi]; + p.xTouched = p.yTouched = false; + p.xo = p.x = p.x + dx; + p.yo = p.y = p.y + dy; + } + + var gLen = gZone.length; + gZone.push.apply(gZone, gz); + for (var j = 0; j < cc.length; j++) { + contours.push(cc[j] + gLen); + } + } + + if (glyph.instructions && !state.inhibitGridFit) { + // the composite has instructions on its own + state = new State('glyf', glyph.instructions); + + state.gZone = state.z0 = state.z1 = state.z2 = gZone; + + state.contours = contours; + + // note: HPZero cannot be used here, since + // the point might be modified + gZone.push( + new HPoint(0, 0), + new HPoint(Math.round(glyph.advanceWidth * xScale), 0) + ); + + if (exports.DEBUG) { + console.log('---EXEC COMPOSITE---'); + state.step = -1; + } + + exec(state); + + gZone.length -= 2; + } + } + + return gZone; + }; + + /* + * Executes the hinting program for a component of a multi-component glyph + * or of the glyph itself for a non-component glyph. + */ + execComponent = function(glyph, state, xScale, yScale) + { + var points = glyph.points || []; + var pLen = points.length; + var gZone = state.gZone = state.z0 = state.z1 = state.z2 = []; + var contours = state.contours = []; + + // Scales the original points and + // makes copies for the hinted points. + var cp; // current point + for (var i = 0; i < pLen; i++) { + cp = points[i]; + + gZone[i] = new HPoint( + cp.x * xScale, + cp.y * yScale, + cp.lastPointOfContour, + cp.onCurve + ); + } + + // Chain links the contours. + var sp; // start point + var np; // next point + + for (var i$1 = 0; i$1 < pLen; i$1++) { + cp = gZone[i$1]; + + if (!sp) { + sp = cp; + contours.push(i$1); + } + + if (cp.lastPointOfContour) { + cp.nextPointOnContour = sp; + sp.prevPointOnContour = cp; + sp = undefined; + } else { + np = gZone[i$1 + 1]; + cp.nextPointOnContour = np; + np.prevPointOnContour = cp; + } + } + + if (state.inhibitGridFit) { return; } + + if (exports.DEBUG) { + console.log('PROCESSING GLYPH', state.stack); + for (var i$2 = 0; i$2 < pLen; i$2++) { + console.log(i$2, gZone[i$2].x, gZone[i$2].y); + } + } + + gZone.push( + new HPoint(0, 0), + new HPoint(Math.round(glyph.advanceWidth * xScale), 0) + ); + + exec(state); + + // Removes the extra points. + gZone.length -= 2; + + if (exports.DEBUG) { + console.log('FINISHED GLYPH', state.stack); + for (var i$3 = 0; i$3 < pLen; i$3++) { + console.log(i$3, gZone[i$3].x, gZone[i$3].y); + } + } + }; + + /* + * Executes the program loaded in state. + */ + exec = function(state) { + var prog = state.prog; + + if (!prog) { return; } + + var pLen = prog.length; + var ins; + + for (state.ip = 0; state.ip < pLen; state.ip++) { + if (exports.DEBUG) { state.step++; } + ins = instructionTable[prog[state.ip]]; + + if (!ins) { + throw new Error( + 'unknown instruction: 0x' + + Number(prog[state.ip]).toString(16) + ); + } + + ins(state); + + // very extensive debugging for each step + /* + if (exports.DEBUG) { + var da; + if (state.gZone) { + da = []; + for (let i = 0; i < state.gZone.length; i++) + { + da.push(i + ' ' + + state.gZone[i].x * 64 + ' ' + + state.gZone[i].y * 64 + ' ' + + (state.gZone[i].xTouched ? 'x' : '') + + (state.gZone[i].yTouched ? 'y' : '') + ); + } + console.log('GZ', da); + } + + if (state.tZone) { + da = []; + for (let i = 0; i < state.tZone.length; i++) { + da.push(i + ' ' + + state.tZone[i].x * 64 + ' ' + + state.tZone[i].y * 64 + ' ' + + (state.tZone[i].xTouched ? 'x' : '') + + (state.tZone[i].yTouched ? 'y' : '') + ); + } + console.log('TZ', da); + } + + if (state.stack.length > 10) { + console.log( + state.stack.length, + '...', state.stack.slice(state.stack.length - 10) + ); + } else { + console.log(state.stack.length, state.stack); + } + } + */ + } + }; + + /* + * Initializes the twilight zone. + * + * This is only done if a SZPx instruction + * refers to the twilight zone. + */ + function initTZone(state) + { + var tZone = state.tZone = new Array(state.gZone.length); + + // no idea if this is actually correct... + for (var i = 0; i < tZone.length; i++) + { + tZone[i] = new HPoint(0, 0); + } + } + + /* + * Skips the instruction pointer ahead over an IF/ELSE block. + * handleElse .. if true breaks on matching ELSE + */ + function skip(state, handleElse) + { + var prog = state.prog; + var ip = state.ip; + var nesting = 1; + var ins; + + do { + ins = prog[++ip]; + if (ins === 0x58) // IF + { nesting++; } + else if (ins === 0x59) // EIF + { nesting--; } + else if (ins === 0x40) // NPUSHB + { ip += prog[ip + 1] + 1; } + else if (ins === 0x41) // NPUSHW + { ip += 2 * prog[ip + 1] + 1; } + else if (ins >= 0xB0 && ins <= 0xB7) // PUSHB + { ip += ins - 0xB0 + 1; } + else if (ins >= 0xB8 && ins <= 0xBF) // PUSHW + { ip += (ins - 0xB8 + 1) * 2; } + else if (handleElse && nesting === 1 && ins === 0x1B) // ELSE + { break; } + } while (nesting > 0); + + state.ip = ip; + } + + /*----------------------------------------------------------* + * And then a lot of instructions... * + *----------------------------------------------------------*/ + + // SVTCA[a] Set freedom and projection Vectors To Coordinate Axis + // 0x00-0x01 + function SVTCA(v, state) { + if (exports.DEBUG) { console.log(state.step, 'SVTCA[' + v.axis + ']'); } + + state.fv = state.pv = state.dpv = v; + } + + // SPVTCA[a] Set Projection Vector to Coordinate Axis + // 0x02-0x03 + function SPVTCA(v, state) { + if (exports.DEBUG) { console.log(state.step, 'SPVTCA[' + v.axis + ']'); } + + state.pv = state.dpv = v; + } + + // SFVTCA[a] Set Freedom Vector to Coordinate Axis + // 0x04-0x05 + function SFVTCA(v, state) { + if (exports.DEBUG) { console.log(state.step, 'SFVTCA[' + v.axis + ']'); } + + state.fv = v; + } + + // SPVTL[a] Set Projection Vector To Line + // 0x06-0x07 + function SPVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; + + if (exports.DEBUG) { console.log('SPVTL[' + a + ']', p2i, p1i); } + + var dx; + var dy; + + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } + + state.pv = state.dpv = getUnitVector(dx, dy); + } + + // SFVTL[a] Set Freedom Vector To Line + // 0x08-0x09 + function SFVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; + + if (exports.DEBUG) { console.log('SFVTL[' + a + ']', p2i, p1i); } + + var dx; + var dy; + + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } + + state.fv = getUnitVector(dx, dy); + } + + // SPVFS[] Set Projection Vector From Stack + // 0x0A + function SPVFS(state) { + var stack = state.stack; + var y = stack.pop(); + var x = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } + + state.pv = state.dpv = getUnitVector(x, y); + } + + // SFVFS[] Set Freedom Vector From Stack + // 0x0B + function SFVFS(state) { + var stack = state.stack; + var y = stack.pop(); + var x = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } + + state.fv = getUnitVector(x, y); + } + + // GPV[] Get Projection Vector + // 0x0C + function GPV(state) { + var stack = state.stack; + var pv = state.pv; + + if (exports.DEBUG) { console.log(state.step, 'GPV[]'); } + + stack.push(pv.x * 0x4000); + stack.push(pv.y * 0x4000); + } + + // GFV[] Get Freedom Vector + // 0x0C + function GFV(state) { + var stack = state.stack; + var fv = state.fv; + + if (exports.DEBUG) { console.log(state.step, 'GFV[]'); } + + stack.push(fv.x * 0x4000); + stack.push(fv.y * 0x4000); + } + + // SFVTPV[] Set Freedom Vector To Projection Vector + // 0x0E + function SFVTPV(state) { + state.fv = state.pv; + + if (exports.DEBUG) { console.log(state.step, 'SFVTPV[]'); } + } + + // ISECT[] moves point p to the InterSECTion of two lines + // 0x0F + function ISECT(state) + { + var stack = state.stack; + var pa0i = stack.pop(); + var pa1i = stack.pop(); + var pb0i = stack.pop(); + var pb1i = stack.pop(); + var pi = stack.pop(); + var z0 = state.z0; + var z1 = state.z1; + var pa0 = z0[pa0i]; + var pa1 = z0[pa1i]; + var pb0 = z1[pb0i]; + var pb1 = z1[pb1i]; + var p = state.z2[pi]; + + if (exports.DEBUG) { console.log('ISECT[], ', pa0i, pa1i, pb0i, pb1i, pi); } + + // math from + // en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line + + var x1 = pa0.x; + var y1 = pa0.y; + var x2 = pa1.x; + var y2 = pa1.y; + var x3 = pb0.x; + var y3 = pb0.y; + var x4 = pb1.x; + var y4 = pb1.y; + + var div = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); + var f1 = x1 * y2 - y1 * x2; + var f2 = x3 * y4 - y3 * x4; + + p.x = (f1 * (x3 - x4) - f2 * (x1 - x2)) / div; + p.y = (f1 * (y3 - y4) - f2 * (y1 - y2)) / div; + } + + // SRP0[] Set Reference Point 0 + // 0x10 + function SRP0(state) { + state.rp0 = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SRP0[]', state.rp0); } + } + + // SRP1[] Set Reference Point 1 + // 0x11 + function SRP1(state) { + state.rp1 = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SRP1[]', state.rp1); } + } + + // SRP1[] Set Reference Point 2 + // 0x12 + function SRP2(state) { + state.rp2 = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SRP2[]', state.rp2); } + } + + // SZP0[] Set Zone Pointer 0 + // 0x13 + function SZP0(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SZP0[]', n); } + + state.zp0 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z0 = state.tZone; + break; + case 1 : + state.z0 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } + } + + // SZP1[] Set Zone Pointer 1 + // 0x14 + function SZP1(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SZP1[]', n); } + + state.zp1 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z1 = state.tZone; + break; + case 1 : + state.z1 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } + } + + // SZP2[] Set Zone Pointer 2 + // 0x15 + function SZP2(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SZP2[]', n); } + + state.zp2 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z2 = state.tZone; + break; + case 1 : + state.z2 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } + } + + // SZPS[] Set Zone PointerS + // 0x16 + function SZPS(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SZPS[]', n); } + + state.zp0 = state.zp1 = state.zp2 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z0 = state.z1 = state.z2 = state.tZone; + break; + case 1 : + state.z0 = state.z1 = state.z2 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } + } + + // SLOOP[] Set LOOP variable + // 0x17 + function SLOOP(state) { + state.loop = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SLOOP[]', state.loop); } + } + + // RTG[] Round To Grid + // 0x18 + function RTG(state) { + if (exports.DEBUG) { console.log(state.step, 'RTG[]'); } + + state.round = roundToGrid; + } + + // RTHG[] Round To Half Grid + // 0x19 + function RTHG(state) { + if (exports.DEBUG) { console.log(state.step, 'RTHG[]'); } + + state.round = roundToHalfGrid; + } + + // SMD[] Set Minimum Distance + // 0x1A + function SMD(state) { + var d = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SMD[]', d); } + + state.minDis = d / 0x40; + } + + // ELSE[] ELSE clause + // 0x1B + function ELSE(state) { + // This instruction has been reached by executing a then branch + // so it just skips ahead until matching EIF. + // + // In case the IF was negative the IF[] instruction already + // skipped forward over the ELSE[] + + if (exports.DEBUG) { console.log(state.step, 'ELSE[]'); } + + skip(state, false); + } + + // JMPR[] JuMP Relative + // 0x1C + function JMPR(state) { + var o = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'JMPR[]', o); } + + // A jump by 1 would do nothing. + state.ip += o - 1; + } + + // SCVTCI[] Set Control Value Table Cut-In + // 0x1D + function SCVTCI(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SCVTCI[]', n); } + + state.cvCutIn = n / 0x40; + } + + // DUP[] DUPlicate top stack element + // 0x20 + function DUP(state) { + var stack = state.stack; + + if (exports.DEBUG) { console.log(state.step, 'DUP[]'); } + + stack.push(stack[stack.length - 1]); + } + + // POP[] POP top stack element + // 0x21 + function POP(state) { + if (exports.DEBUG) { console.log(state.step, 'POP[]'); } + + state.stack.pop(); + } + + // CLEAR[] CLEAR the stack + // 0x22 + function CLEAR(state) { + if (exports.DEBUG) { console.log(state.step, 'CLEAR[]'); } + + state.stack.length = 0; + } + + // SWAP[] SWAP the top two elements on the stack + // 0x23 + function SWAP(state) { + var stack = state.stack; + + var a = stack.pop(); + var b = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SWAP[]'); } + + stack.push(a); + stack.push(b); + } + + // DEPTH[] DEPTH of the stack + // 0x24 + function DEPTH(state) { + var stack = state.stack; + + if (exports.DEBUG) { console.log(state.step, 'DEPTH[]'); } + + stack.push(stack.length); + } + + // LOOPCALL[] LOOPCALL function + // 0x2A + function LOOPCALL(state) { + var stack = state.stack; + var fn = stack.pop(); + var c = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'LOOPCALL[]', fn, c); } + + // saves callers program + var cip = state.ip; + var cprog = state.prog; + + state.prog = state.funcs[fn]; + + // executes the function + for (var i = 0; i < c; i++) { + exec(state); + + if (exports.DEBUG) { console.log( + ++state.step, + i + 1 < c ? 'next loopcall' : 'done loopcall', + i + ); } + } + + // restores the callers program + state.ip = cip; + state.prog = cprog; + } + + // CALL[] CALL function + // 0x2B + function CALL(state) { + var fn = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'CALL[]', fn); } + + // saves callers program + var cip = state.ip; + var cprog = state.prog; + + state.prog = state.funcs[fn]; + + // executes the function + exec(state); + + // restores the callers program + state.ip = cip; + state.prog = cprog; + + if (exports.DEBUG) { console.log(++state.step, 'returning from', fn); } + } + + // CINDEX[] Copy the INDEXed element to the top of the stack + // 0x25 + function CINDEX(state) { + var stack = state.stack; + var k = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'CINDEX[]', k); } + + // In case of k == 1, it copies the last element after popping + // thus stack.length - k. + stack.push(stack[stack.length - k]); + } + + // MINDEX[] Move the INDEXed element to the top of the stack + // 0x26 + function MINDEX(state) { + var stack = state.stack; + var k = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MINDEX[]', k); } + + stack.push(stack.splice(stack.length - k, 1)[0]); + } + + // FDEF[] Function DEFinition + // 0x2C + function FDEF(state) { + if (state.env !== 'fpgm') { throw new Error('FDEF not allowed here'); } + var stack = state.stack; + var prog = state.prog; + var ip = state.ip; + + var fn = stack.pop(); + var ipBegin = ip; + + if (exports.DEBUG) { console.log(state.step, 'FDEF[]', fn); } + + while (prog[++ip] !== 0x2D){ } + + state.ip = ip; + state.funcs[fn] = prog.slice(ipBegin + 1, ip); + } + + // MDAP[a] Move Direct Absolute Point + // 0x2E-0x2F + function MDAP(round, state) { + var pi = state.stack.pop(); + var p = state.z0[pi]; + var fv = state.fv; + var pv = state.pv; + + if (exports.DEBUG) { console.log(state.step, 'MDAP[' + round + ']', pi); } + + var d = pv.distance(p, HPZero); + + if (round) { d = state.round(d); } + + fv.setRelative(p, HPZero, d, pv); + fv.touch(p); + + state.rp0 = state.rp1 = pi; + } + + // IUP[a] Interpolate Untouched Points through the outline + // 0x30 + function IUP(v, state) { + var z2 = state.z2; + var pLen = z2.length - 2; + var cp; + var pp; + var np; + + if (exports.DEBUG) { console.log(state.step, 'IUP[' + v.axis + ']'); } + + for (var i = 0; i < pLen; i++) { + cp = z2[i]; // current point + + // if this point has been touched go on + if (v.touched(cp)) { continue; } + + pp = cp.prevTouched(v); + + // no point on the contour has been touched? + if (pp === cp) { continue; } + + np = cp.nextTouched(v); + + if (pp === np) { + // only one point on the contour has been touched + // so simply moves the point like that + + v.setRelative(cp, cp, v.distance(pp, pp, false, true), v, true); + } + + v.interpolate(cp, pp, np, v); + } + } + + // SHP[] SHift Point using reference point + // 0x32-0x33 + function SHP(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + var loop = state.loop; + var z2 = state.z2; + + while (loop--) + { + var pi = stack.pop(); + var p = z2[pi]; + + var d = pv.distance(rp, rp, false, true); + fv.setRelative(p, p, d, pv); + fv.touch(p); + + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? + 'loop ' + (state.loop - loop) + ': ' : + '' + ) + + 'SHP[' + (a ? 'rp1' : 'rp2') + ']', pi + ); + } + } + + state.loop = 1; + } + + // SHC[] SHift Contour using reference point + // 0x36-0x37 + function SHC(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + var ci = stack.pop(); + var sp = state.z2[state.contours[ci]]; + var p = sp; + + if (exports.DEBUG) { console.log(state.step, 'SHC[' + a + ']', ci); } + + var d = pv.distance(rp, rp, false, true); + + do { + if (p !== rp) { fv.setRelative(p, p, d, pv); } + p = p.nextPointOnContour; + } while (p !== sp); + } + + // SHZ[] SHift Zone using reference point + // 0x36-0x37 + function SHZ(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + + var e = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SHZ[' + a + ']', e); } + + var z; + switch (e) { + case 0 : z = state.tZone; break; + case 1 : z = state.gZone; break; + default : throw new Error('Invalid zone'); + } + + var p; + var d = pv.distance(rp, rp, false, true); + var pLen = z.length - 2; + for (var i = 0; i < pLen; i++) + { + p = z[i]; + fv.setRelative(p, p, d, pv); + //if (p !== rp) fv.setRelative(p, p, d, pv); + } + } + + // SHPIX[] SHift point by a PIXel amount + // 0x38 + function SHPIX(state) { + var stack = state.stack; + var loop = state.loop; + var fv = state.fv; + var d = stack.pop() / 0x40; + var z2 = state.z2; + + while (loop--) { + var pi = stack.pop(); + var p = z2[pi]; + + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + + 'SHPIX[]', pi, d + ); + } + + fv.setRelative(p, p, d); + fv.touch(p); + } + + state.loop = 1; + } + + // IP[] Interpolate Point + // 0x39 + function IP(state) { + var stack = state.stack; + var rp1i = state.rp1; + var rp2i = state.rp2; + var loop = state.loop; + var rp1 = state.z0[rp1i]; + var rp2 = state.z1[rp2i]; + var fv = state.fv; + var pv = state.dpv; + var z2 = state.z2; + + while (loop--) { + var pi = stack.pop(); + var p = z2[pi]; + + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + + 'IP[]', pi, rp1i, '<->', rp2i + ); + } + + fv.interpolate(p, rp1, rp2, pv); + + fv.touch(p); + } + + state.loop = 1; + } + + // MSIRP[a] Move Stack Indirect Relative Point + // 0x3A-0x3B + function MSIRP(a, state) { + var stack = state.stack; + var d = stack.pop() / 64; + var pi = stack.pop(); + var p = state.z1[pi]; + var rp0 = state.z0[state.rp0]; + var fv = state.fv; + var pv = state.pv; + + fv.setRelative(p, rp0, d, pv); + fv.touch(p); + + if (exports.DEBUG) { console.log(state.step, 'MSIRP[' + a + ']', d, pi); } + + state.rp1 = state.rp0; + state.rp2 = pi; + if (a) { state.rp0 = pi; } + } + + // ALIGNRP[] Align to reference point. + // 0x3C + function ALIGNRP(state) { + var stack = state.stack; + var rp0i = state.rp0; + var rp0 = state.z0[rp0i]; + var loop = state.loop; + var fv = state.fv; + var pv = state.pv; + var z1 = state.z1; + + while (loop--) { + var pi = stack.pop(); + var p = z1[pi]; + + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + + 'ALIGNRP[]', pi + ); + } + + fv.setRelative(p, rp0, 0, pv); + fv.touch(p); + } + + state.loop = 1; + } + + // RTG[] Round To Double Grid + // 0x3D + function RTDG(state) { + if (exports.DEBUG) { console.log(state.step, 'RTDG[]'); } + + state.round = roundToDoubleGrid; + } + + // MIAP[a] Move Indirect Absolute Point + // 0x3E-0x3F + function MIAP(round, state) { + var stack = state.stack; + var n = stack.pop(); + var pi = stack.pop(); + var p = state.z0[pi]; + var fv = state.fv; + var pv = state.pv; + var cv = state.cvt[n]; + + if (exports.DEBUG) { + console.log( + state.step, + 'MIAP[' + round + ']', + n, '(', cv, ')', pi + ); + } + + var d = pv.distance(p, HPZero); + + if (round) { + if (Math.abs(d - cv) < state.cvCutIn) { d = cv; } + + d = state.round(d); + } + + fv.setRelative(p, HPZero, d, pv); + + if (state.zp0 === 0) { + p.xo = p.x; + p.yo = p.y; + } + + fv.touch(p); + + state.rp0 = state.rp1 = pi; + } + + // NPUSB[] PUSH N Bytes + // 0x40 + function NPUSHB(state) { + var prog = state.prog; + var ip = state.ip; + var stack = state.stack; + + var n = prog[++ip]; + + if (exports.DEBUG) { console.log(state.step, 'NPUSHB[]', n); } + + for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } + + state.ip = ip; + } + + // NPUSHW[] PUSH N Words + // 0x41 + function NPUSHW(state) { + var ip = state.ip; + var prog = state.prog; + var stack = state.stack; + var n = prog[++ip]; + + if (exports.DEBUG) { console.log(state.step, 'NPUSHW[]', n); } + + for (var i = 0; i < n; i++) { + var w = (prog[++ip] << 8) | prog[++ip]; + if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } + stack.push(w); + } + + state.ip = ip; + } + + // WS[] Write Store + // 0x42 + function WS(state) { + var stack = state.stack; + var store = state.store; + + if (!store) { store = state.store = []; } + + var v = stack.pop(); + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'WS', v, l); } + + store[l] = v; + } + + // RS[] Read Store + // 0x43 + function RS(state) { + var stack = state.stack; + var store = state.store; + + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'RS', l); } + + var v = (store && store[l]) || 0; + + stack.push(v); + } + + // WCVTP[] Write Control Value Table in Pixel units + // 0x44 + function WCVTP(state) { + var stack = state.stack; + + var v = stack.pop(); + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'WCVTP', v, l); } + + state.cvt[l] = v / 0x40; + } + + // RCVT[] Read Control Value Table entry + // 0x45 + function RCVT(state) { + var stack = state.stack; + var cvte = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'RCVT', cvte); } + + stack.push(state.cvt[cvte] * 0x40); + } + + // GC[] Get Coordinate projected onto the projection vector + // 0x46-0x47 + function GC(a, state) { + var stack = state.stack; + var pi = stack.pop(); + var p = state.z2[pi]; + + if (exports.DEBUG) { console.log(state.step, 'GC[' + a + ']', pi); } + + stack.push(state.dpv.distance(p, HPZero, a, false) * 0x40); + } + + // MD[a] Measure Distance + // 0x49-0x4A + function MD(a, state) { + var stack = state.stack; + var pi2 = stack.pop(); + var pi1 = stack.pop(); + var p2 = state.z1[pi2]; + var p1 = state.z0[pi1]; + var d = state.dpv.distance(p1, p2, a, a); + + if (exports.DEBUG) { console.log(state.step, 'MD[' + a + ']', pi2, pi1, '->', d); } + + state.stack.push(Math.round(d * 64)); + } + + // MPPEM[] Measure Pixels Per EM + // 0x4B + function MPPEM(state) { + if (exports.DEBUG) { console.log(state.step, 'MPPEM[]'); } + state.stack.push(state.ppem); + } + + // FLIPON[] set the auto FLIP Boolean to ON + // 0x4D + function FLIPON(state) { + if (exports.DEBUG) { console.log(state.step, 'FLIPON[]'); } + state.autoFlip = true; + } + + // LT[] Less Than + // 0x50 + function LT(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'LT[]', e2, e1); } + + stack.push(e1 < e2 ? 1 : 0); + } + + // LTEQ[] Less Than or EQual + // 0x53 + function LTEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'LTEQ[]', e2, e1); } + + stack.push(e1 <= e2 ? 1 : 0); + } + + // GTEQ[] Greater Than + // 0x52 + function GT(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'GT[]', e2, e1); } + + stack.push(e1 > e2 ? 1 : 0); + } + + // GTEQ[] Greater Than or EQual + // 0x53 + function GTEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'GTEQ[]', e2, e1); } + + stack.push(e1 >= e2 ? 1 : 0); + } + + // EQ[] EQual + // 0x54 + function EQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'EQ[]', e2, e1); } + + stack.push(e2 === e1 ? 1 : 0); + } + + // NEQ[] Not EQual + // 0x55 + function NEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'NEQ[]', e2, e1); } + + stack.push(e2 !== e1 ? 1 : 0); + } + + // ODD[] ODD + // 0x56 + function ODD(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ODD[]', n); } + + stack.push(Math.trunc(n) % 2 ? 1 : 0); + } + + // EVEN[] EVEN + // 0x57 + function EVEN(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'EVEN[]', n); } + + stack.push(Math.trunc(n) % 2 ? 0 : 1); + } + + // IF[] IF test + // 0x58 + function IF(state) { + var test = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'IF[]', test); } + + // if test is true it just continues + // if not the ip is skipped until matching ELSE or EIF + if (!test) { + skip(state, true); + + if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } + } + } + + // EIF[] End IF + // 0x59 + function EIF(state) { + // this can be reached normally when + // executing an else branch. + // -> just ignore it + + if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } + } + + // AND[] logical AND + // 0x5A + function AND(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'AND[]', e2, e1); } + + stack.push(e2 && e1 ? 1 : 0); + } + + // OR[] logical OR + // 0x5B + function OR(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'OR[]', e2, e1); } + + stack.push(e2 || e1 ? 1 : 0); + } + + // NOT[] logical NOT + // 0x5C + function NOT(state) { + var stack = state.stack; + var e = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'NOT[]', e); } + + stack.push(e ? 0 : 1); + } + + // DELTAP1[] DELTA exception P1 + // DELTAP2[] DELTA exception P2 + // DELTAP3[] DELTA exception P3 + // 0x5D, 0x71, 0x72 + function DELTAP123(b, state) { + var stack = state.stack; + var n = stack.pop(); + var fv = state.fv; + var pv = state.pv; + var ppem = state.ppem; + var base = state.deltaBase + (b - 1) * 16; + var ds = state.deltaShift; + var z0 = state.z0; + + if (exports.DEBUG) { console.log(state.step, 'DELTAP[' + b + ']', n, stack); } + + for (var i = 0; i < n; i++) { + var pi = stack.pop(); + var arg = stack.pop(); + var appem = base + ((arg & 0xF0) >> 4); + if (appem !== ppem) { continue; } + + var mag = (arg & 0x0F) - 8; + if (mag >= 0) { mag++; } + if (exports.DEBUG) { console.log(state.step, 'DELTAPFIX', pi, 'by', mag * ds); } + + var p = z0[pi]; + fv.setRelative(p, p, mag * ds, pv); + } + } + + // SDB[] Set Delta Base in the graphics state + // 0x5E + function SDB(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SDB[]', n); } + + state.deltaBase = n; + } + + // SDS[] Set Delta Shift in the graphics state + // 0x5F + function SDS(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SDS[]', n); } + + state.deltaShift = Math.pow(0.5, n); + } + + // ADD[] ADD + // 0x60 + function ADD(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ADD[]', n2, n1); } + + stack.push(n1 + n2); + } + + // SUB[] SUB + // 0x61 + function SUB(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SUB[]', n2, n1); } + + stack.push(n1 - n2); + } + + // DIV[] DIV + // 0x62 + function DIV(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'DIV[]', n2, n1); } + + stack.push(n1 * 64 / n2); + } + + // MUL[] MUL + // 0x63 + function MUL(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MUL[]', n2, n1); } + + stack.push(n1 * n2 / 64); + } + + // ABS[] ABSolute value + // 0x64 + function ABS(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ABS[]', n); } + + stack.push(Math.abs(n)); + } + + // NEG[] NEGate + // 0x65 + function NEG(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'NEG[]', n); } + + stack.push(-n); + } + + // FLOOR[] FLOOR + // 0x66 + function FLOOR(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'FLOOR[]', n); } + + stack.push(Math.floor(n / 0x40) * 0x40); + } + + // CEILING[] CEILING + // 0x67 + function CEILING(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'CEILING[]', n); } + + stack.push(Math.ceil(n / 0x40) * 0x40); + } + + // ROUND[ab] ROUND value + // 0x68-0x6B + function ROUND(dt, state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ROUND[]'); } + + stack.push(state.round(n / 0x40) * 0x40); + } + + // WCVTF[] Write Control Value Table in Funits + // 0x70 + function WCVTF(state) { + var stack = state.stack; + var v = stack.pop(); + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'WCVTF[]', v, l); } + + state.cvt[l] = v * state.ppem / state.font.unitsPerEm; + } + + // DELTAC1[] DELTA exception C1 + // DELTAC2[] DELTA exception C2 + // DELTAC3[] DELTA exception C3 + // 0x73, 0x74, 0x75 + function DELTAC123(b, state) { + var stack = state.stack; + var n = stack.pop(); + var ppem = state.ppem; + var base = state.deltaBase + (b - 1) * 16; + var ds = state.deltaShift; + + if (exports.DEBUG) { console.log(state.step, 'DELTAC[' + b + ']', n, stack); } + + for (var i = 0; i < n; i++) { + var c = stack.pop(); + var arg = stack.pop(); + var appem = base + ((arg & 0xF0) >> 4); + if (appem !== ppem) { continue; } + + var mag = (arg & 0x0F) - 8; + if (mag >= 0) { mag++; } + + var delta = mag * ds; + + if (exports.DEBUG) { console.log(state.step, 'DELTACFIX', c, 'by', delta); } + + state.cvt[c] += delta; + } + } + + // SROUND[] Super ROUND + // 0x76 + function SROUND(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SROUND[]', n); } + + state.round = roundSuper; + + var period; + + switch (n & 0xC0) { + case 0x00: + period = 0.5; + break; + case 0x40: + period = 1; + break; + case 0x80: + period = 2; + break; + default: + throw new Error('invalid SROUND value'); + } + + state.srPeriod = period; + + switch (n & 0x30) { + case 0x00: + state.srPhase = 0; + break; + case 0x10: + state.srPhase = 0.25 * period; + break; + case 0x20: + state.srPhase = 0.5 * period; + break; + case 0x30: + state.srPhase = 0.75 * period; + break; + default: throw new Error('invalid SROUND value'); + } + + n &= 0x0F; + + if (n === 0) { state.srThreshold = 0; } + else { state.srThreshold = (n / 8 - 0.5) * period; } + } + + // S45ROUND[] Super ROUND 45 degrees + // 0x77 + function S45ROUND(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'S45ROUND[]', n); } + + state.round = roundSuper; + + var period; + + switch (n & 0xC0) { + case 0x00: + period = Math.sqrt(2) / 2; + break; + case 0x40: + period = Math.sqrt(2); + break; + case 0x80: + period = 2 * Math.sqrt(2); + break; + default: + throw new Error('invalid S45ROUND value'); + } + + state.srPeriod = period; + + switch (n & 0x30) { + case 0x00: + state.srPhase = 0; + break; + case 0x10: + state.srPhase = 0.25 * period; + break; + case 0x20: + state.srPhase = 0.5 * period; + break; + case 0x30: + state.srPhase = 0.75 * period; + break; + default: + throw new Error('invalid S45ROUND value'); + } + + n &= 0x0F; + + if (n === 0) { state.srThreshold = 0; } + else { state.srThreshold = (n / 8 - 0.5) * period; } + } + + // ROFF[] Round Off + // 0x7A + function ROFF(state) { + if (exports.DEBUG) { console.log(state.step, 'ROFF[]'); } + + state.round = roundOff; + } + + // RUTG[] Round Up To Grid + // 0x7C + function RUTG(state) { + if (exports.DEBUG) { console.log(state.step, 'RUTG[]'); } + + state.round = roundUpToGrid; + } + + // RDTG[] Round Down To Grid + // 0x7D + function RDTG(state) { + if (exports.DEBUG) { console.log(state.step, 'RDTG[]'); } + + state.round = roundDownToGrid; + } + + // SCANCTRL[] SCAN conversion ConTRoL + // 0x85 + function SCANCTRL(state) { + var n = state.stack.pop(); + + // ignored by opentype.js + + if (exports.DEBUG) { console.log(state.step, 'SCANCTRL[]', n); } + } + + // SDPVTL[a] Set Dual Projection Vector To Line + // 0x86-0x87 + function SDPVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; + + if (exports.DEBUG) { console.log(state.step, 'SDPVTL[' + a + ']', p2i, p1i); } + + var dx; + var dy; + + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } + + state.dpv = getUnitVector(dx, dy); + } + + // GETINFO[] GET INFOrmation + // 0x88 + function GETINFO(state) { + var stack = state.stack; + var sel = stack.pop(); + var r = 0; + + if (exports.DEBUG) { console.log(state.step, 'GETINFO[]', sel); } + + // v35 as in no subpixel hinting + if (sel & 0x01) { r = 35; } + + // TODO rotation and stretch currently not supported + // and thus those GETINFO are always 0. + + // opentype.js is always gray scaling + if (sel & 0x20) { r |= 0x1000; } + + stack.push(r); + } + + // ROLL[] ROLL the top three stack elements + // 0x8A + function ROLL(state) { + var stack = state.stack; + var a = stack.pop(); + var b = stack.pop(); + var c = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ROLL[]'); } + + stack.push(b); + stack.push(a); + stack.push(c); + } + + // MAX[] MAXimum of top two stack elements + // 0x8B + function MAX(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MAX[]', e2, e1); } + + stack.push(Math.max(e1, e2)); + } + + // MIN[] MINimum of top two stack elements + // 0x8C + function MIN(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MIN[]', e2, e1); } + + stack.push(Math.min(e1, e2)); + } + + // SCANTYPE[] SCANTYPE + // 0x8D + function SCANTYPE(state) { + var n = state.stack.pop(); + // ignored by opentype.js + if (exports.DEBUG) { console.log(state.step, 'SCANTYPE[]', n); } + } + + // INSTCTRL[] INSTCTRL + // 0x8D + function INSTCTRL(state) { + var s = state.stack.pop(); + var v = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'INSTCTRL[]', s, v); } + + switch (s) { + case 1 : state.inhibitGridFit = !!v; return; + case 2 : state.ignoreCvt = !!v; return; + default: throw new Error('invalid INSTCTRL[] selector'); + } + } + + // PUSHB[abc] PUSH Bytes + // 0xB0-0xB7 + function PUSHB(n, state) { + var stack = state.stack; + var prog = state.prog; + var ip = state.ip; + + if (exports.DEBUG) { console.log(state.step, 'PUSHB[' + n + ']'); } + + for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } + + state.ip = ip; + } + + // PUSHW[abc] PUSH Words + // 0xB8-0xBF + function PUSHW(n, state) { + var ip = state.ip; + var prog = state.prog; + var stack = state.stack; + + if (exports.DEBUG) { console.log(state.ip, 'PUSHW[' + n + ']'); } + + for (var i = 0; i < n; i++) { + var w = (prog[++ip] << 8) | prog[++ip]; + if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } + stack.push(w); + } + + state.ip = ip; + } + + // MDRP[abcde] Move Direct Relative Point + // 0xD0-0xEF + // (if indirect is 0) + // + // and + // + // MIRP[abcde] Move Indirect Relative Point + // 0xE0-0xFF + // (if indirect is 1) + + function MDRP_MIRP(indirect, setRp0, keepD, ro, dt, state) { + var stack = state.stack; + var cvte = indirect && stack.pop(); + var pi = stack.pop(); + var rp0i = state.rp0; + var rp = state.z0[rp0i]; + var p = state.z1[pi]; + + var md = state.minDis; + var fv = state.fv; + var pv = state.dpv; + var od; // original distance + var d; // moving distance + var sign; // sign of distance + var cv; + + d = od = pv.distance(p, rp, true, true); + sign = d >= 0 ? 1 : -1; // Math.sign would be 0 in case of 0 + + // TODO consider autoFlip + d = Math.abs(d); + + if (indirect) { + cv = state.cvt[cvte]; + + if (ro && Math.abs(d - cv) < state.cvCutIn) { d = cv; } + } + + if (keepD && d < md) { d = md; } + + if (ro) { d = state.round(d); } + + fv.setRelative(p, rp, sign * d, pv); + fv.touch(p); + + if (exports.DEBUG) { + console.log( + state.step, + (indirect ? 'MIRP[' : 'MDRP[') + + (setRp0 ? 'M' : 'm') + + (keepD ? '>' : '_') + + (ro ? 'R' : '_') + + (dt === 0 ? 'Gr' : (dt === 1 ? 'Bl' : (dt === 2 ? 'Wh' : ''))) + + ']', + indirect ? + cvte + '(' + state.cvt[cvte] + ',' + cv + ')' : + '', + pi, + '(d =', od, '->', sign * d, ')' + ); + } + + state.rp1 = state.rp0; + state.rp2 = pi; + if (setRp0) { state.rp0 = pi; } + } + + /* + * The instruction table. + */ + instructionTable = [ + /* 0x00 */ SVTCA.bind(undefined, yUnitVector), + /* 0x01 */ SVTCA.bind(undefined, xUnitVector), + /* 0x02 */ SPVTCA.bind(undefined, yUnitVector), + /* 0x03 */ SPVTCA.bind(undefined, xUnitVector), + /* 0x04 */ SFVTCA.bind(undefined, yUnitVector), + /* 0x05 */ SFVTCA.bind(undefined, xUnitVector), + /* 0x06 */ SPVTL.bind(undefined, 0), + /* 0x07 */ SPVTL.bind(undefined, 1), + /* 0x08 */ SFVTL.bind(undefined, 0), + /* 0x09 */ SFVTL.bind(undefined, 1), + /* 0x0A */ SPVFS, + /* 0x0B */ SFVFS, + /* 0x0C */ GPV, + /* 0x0D */ GFV, + /* 0x0E */ SFVTPV, + /* 0x0F */ ISECT, + /* 0x10 */ SRP0, + /* 0x11 */ SRP1, + /* 0x12 */ SRP2, + /* 0x13 */ SZP0, + /* 0x14 */ SZP1, + /* 0x15 */ SZP2, + /* 0x16 */ SZPS, + /* 0x17 */ SLOOP, + /* 0x18 */ RTG, + /* 0x19 */ RTHG, + /* 0x1A */ SMD, + /* 0x1B */ ELSE, + /* 0x1C */ JMPR, + /* 0x1D */ SCVTCI, + /* 0x1E */ undefined, // TODO SSWCI + /* 0x1F */ undefined, // TODO SSW + /* 0x20 */ DUP, + /* 0x21 */ POP, + /* 0x22 */ CLEAR, + /* 0x23 */ SWAP, + /* 0x24 */ DEPTH, + /* 0x25 */ CINDEX, + /* 0x26 */ MINDEX, + /* 0x27 */ undefined, // TODO ALIGNPTS + /* 0x28 */ undefined, + /* 0x29 */ undefined, // TODO UTP + /* 0x2A */ LOOPCALL, + /* 0x2B */ CALL, + /* 0x2C */ FDEF, + /* 0x2D */ undefined, // ENDF (eaten by FDEF) + /* 0x2E */ MDAP.bind(undefined, 0), + /* 0x2F */ MDAP.bind(undefined, 1), + /* 0x30 */ IUP.bind(undefined, yUnitVector), + /* 0x31 */ IUP.bind(undefined, xUnitVector), + /* 0x32 */ SHP.bind(undefined, 0), + /* 0x33 */ SHP.bind(undefined, 1), + /* 0x34 */ SHC.bind(undefined, 0), + /* 0x35 */ SHC.bind(undefined, 1), + /* 0x36 */ SHZ.bind(undefined, 0), + /* 0x37 */ SHZ.bind(undefined, 1), + /* 0x38 */ SHPIX, + /* 0x39 */ IP, + /* 0x3A */ MSIRP.bind(undefined, 0), + /* 0x3B */ MSIRP.bind(undefined, 1), + /* 0x3C */ ALIGNRP, + /* 0x3D */ RTDG, + /* 0x3E */ MIAP.bind(undefined, 0), + /* 0x3F */ MIAP.bind(undefined, 1), + /* 0x40 */ NPUSHB, + /* 0x41 */ NPUSHW, + /* 0x42 */ WS, + /* 0x43 */ RS, + /* 0x44 */ WCVTP, + /* 0x45 */ RCVT, + /* 0x46 */ GC.bind(undefined, 0), + /* 0x47 */ GC.bind(undefined, 1), + /* 0x48 */ undefined, // TODO SCFS + /* 0x49 */ MD.bind(undefined, 0), + /* 0x4A */ MD.bind(undefined, 1), + /* 0x4B */ MPPEM, + /* 0x4C */ undefined, // TODO MPS + /* 0x4D */ FLIPON, + /* 0x4E */ undefined, // TODO FLIPOFF + /* 0x4F */ undefined, // TODO DEBUG + /* 0x50 */ LT, + /* 0x51 */ LTEQ, + /* 0x52 */ GT, + /* 0x53 */ GTEQ, + /* 0x54 */ EQ, + /* 0x55 */ NEQ, + /* 0x56 */ ODD, + /* 0x57 */ EVEN, + /* 0x58 */ IF, + /* 0x59 */ EIF, + /* 0x5A */ AND, + /* 0x5B */ OR, + /* 0x5C */ NOT, + /* 0x5D */ DELTAP123.bind(undefined, 1), + /* 0x5E */ SDB, + /* 0x5F */ SDS, + /* 0x60 */ ADD, + /* 0x61 */ SUB, + /* 0x62 */ DIV, + /* 0x63 */ MUL, + /* 0x64 */ ABS, + /* 0x65 */ NEG, + /* 0x66 */ FLOOR, + /* 0x67 */ CEILING, + /* 0x68 */ ROUND.bind(undefined, 0), + /* 0x69 */ ROUND.bind(undefined, 1), + /* 0x6A */ ROUND.bind(undefined, 2), + /* 0x6B */ ROUND.bind(undefined, 3), + /* 0x6C */ undefined, // TODO NROUND[ab] + /* 0x6D */ undefined, // TODO NROUND[ab] + /* 0x6E */ undefined, // TODO NROUND[ab] + /* 0x6F */ undefined, // TODO NROUND[ab] + /* 0x70 */ WCVTF, + /* 0x71 */ DELTAP123.bind(undefined, 2), + /* 0x72 */ DELTAP123.bind(undefined, 3), + /* 0x73 */ DELTAC123.bind(undefined, 1), + /* 0x74 */ DELTAC123.bind(undefined, 2), + /* 0x75 */ DELTAC123.bind(undefined, 3), + /* 0x76 */ SROUND, + /* 0x77 */ S45ROUND, + /* 0x78 */ undefined, // TODO JROT[] + /* 0x79 */ undefined, // TODO JROF[] + /* 0x7A */ ROFF, + /* 0x7B */ undefined, + /* 0x7C */ RUTG, + /* 0x7D */ RDTG, + /* 0x7E */ POP, // actually SANGW, supposed to do only a pop though + /* 0x7F */ POP, // actually AA, supposed to do only a pop though + /* 0x80 */ undefined, // TODO FLIPPT + /* 0x81 */ undefined, // TODO FLIPRGON + /* 0x82 */ undefined, // TODO FLIPRGOFF + /* 0x83 */ undefined, + /* 0x84 */ undefined, + /* 0x85 */ SCANCTRL, + /* 0x86 */ SDPVTL.bind(undefined, 0), + /* 0x87 */ SDPVTL.bind(undefined, 1), + /* 0x88 */ GETINFO, + /* 0x89 */ undefined, // TODO IDEF + /* 0x8A */ ROLL, + /* 0x8B */ MAX, + /* 0x8C */ MIN, + /* 0x8D */ SCANTYPE, + /* 0x8E */ INSTCTRL, + /* 0x8F */ undefined, + /* 0x90 */ undefined, + /* 0x91 */ undefined, + /* 0x92 */ undefined, + /* 0x93 */ undefined, + /* 0x94 */ undefined, + /* 0x95 */ undefined, + /* 0x96 */ undefined, + /* 0x97 */ undefined, + /* 0x98 */ undefined, + /* 0x99 */ undefined, + /* 0x9A */ undefined, + /* 0x9B */ undefined, + /* 0x9C */ undefined, + /* 0x9D */ undefined, + /* 0x9E */ undefined, + /* 0x9F */ undefined, + /* 0xA0 */ undefined, + /* 0xA1 */ undefined, + /* 0xA2 */ undefined, + /* 0xA3 */ undefined, + /* 0xA4 */ undefined, + /* 0xA5 */ undefined, + /* 0xA6 */ undefined, + /* 0xA7 */ undefined, + /* 0xA8 */ undefined, + /* 0xA9 */ undefined, + /* 0xAA */ undefined, + /* 0xAB */ undefined, + /* 0xAC */ undefined, + /* 0xAD */ undefined, + /* 0xAE */ undefined, + /* 0xAF */ undefined, + /* 0xB0 */ PUSHB.bind(undefined, 1), + /* 0xB1 */ PUSHB.bind(undefined, 2), + /* 0xB2 */ PUSHB.bind(undefined, 3), + /* 0xB3 */ PUSHB.bind(undefined, 4), + /* 0xB4 */ PUSHB.bind(undefined, 5), + /* 0xB5 */ PUSHB.bind(undefined, 6), + /* 0xB6 */ PUSHB.bind(undefined, 7), + /* 0xB7 */ PUSHB.bind(undefined, 8), + /* 0xB8 */ PUSHW.bind(undefined, 1), + /* 0xB9 */ PUSHW.bind(undefined, 2), + /* 0xBA */ PUSHW.bind(undefined, 3), + /* 0xBB */ PUSHW.bind(undefined, 4), + /* 0xBC */ PUSHW.bind(undefined, 5), + /* 0xBD */ PUSHW.bind(undefined, 6), + /* 0xBE */ PUSHW.bind(undefined, 7), + /* 0xBF */ PUSHW.bind(undefined, 8), + /* 0xC0 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 0), + /* 0xC1 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 1), + /* 0xC2 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 2), + /* 0xC3 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 3), + /* 0xC4 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 0), + /* 0xC5 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 1), + /* 0xC6 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 2), + /* 0xC7 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 3), + /* 0xC8 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 0), + /* 0xC9 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 1), + /* 0xCA */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 2), + /* 0xCB */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 3), + /* 0xCC */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 0), + /* 0xCD */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 1), + /* 0xCE */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 2), + /* 0xCF */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 3), + /* 0xD0 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 0), + /* 0xD1 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 1), + /* 0xD2 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 2), + /* 0xD3 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 3), + /* 0xD4 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 0), + /* 0xD5 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 1), + /* 0xD6 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 2), + /* 0xD7 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 3), + /* 0xD8 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 0), + /* 0xD9 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 1), + /* 0xDA */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 2), + /* 0xDB */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 3), + /* 0xDC */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 0), + /* 0xDD */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 1), + /* 0xDE */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 2), + /* 0xDF */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 3), + /* 0xE0 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 0), + /* 0xE1 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 1), + /* 0xE2 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 2), + /* 0xE3 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 3), + /* 0xE4 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 0), + /* 0xE5 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 1), + /* 0xE6 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 2), + /* 0xE7 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 3), + /* 0xE8 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 0), + /* 0xE9 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 1), + /* 0xEA */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 2), + /* 0xEB */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 3), + /* 0xEC */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 0), + /* 0xED */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 1), + /* 0xEE */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 2), + /* 0xEF */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 3), + /* 0xF0 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 0), + /* 0xF1 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 1), + /* 0xF2 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 2), + /* 0xF3 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 3), + /* 0xF4 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 0), + /* 0xF5 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 1), + /* 0xF6 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 2), + /* 0xF7 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 3), + /* 0xF8 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 0), + /* 0xF9 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 1), + /* 0xFA */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 2), + /* 0xFB */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 3), + /* 0xFC */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 0), + /* 0xFD */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 1), + /* 0xFE */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 2), + /* 0xFF */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 3) + ]; + + /***************************** + Mathematical Considerations + ****************************** + + fv ... refers to freedom vector + pv ... refers to projection vector + rp ... refers to reference point + p ... refers to to point being operated on + d ... refers to distance + + SETRELATIVE: + ============ + + case freedom vector == x-axis: + ------------------------------ + + (pv) + .-' + rpd .-' + .-* + d .-'90°' + .-' ' + .-' ' + *-' ' b + rp ' + ' + ' + p *----------*-------------- (fv) + pm + + rpdx = rpx + d * pv.x + rpdy = rpy + d * pv.y + + equation of line b + + y - rpdy = pvns * (x- rpdx) + + y = p.y + + x = rpdx + ( p.y - rpdy ) / pvns + + + case freedom vector == y-axis: + ------------------------------ + + * pm + |\ + | \ + | \ + | \ + | \ + | \ + | \ + | \ + | \ + | \ b + | \ + | \ + | \ .-' (pv) + | 90° \.-' + | .-'* rpd + | .-' + * *-' d + p rp + + rpdx = rpx + d * pv.x + rpdy = rpy + d * pv.y + + equation of line b: + pvns ... normal slope to pv + + y - rpdy = pvns * (x - rpdx) + + x = p.x + + y = rpdy + pvns * (p.x - rpdx) + + + + generic case: + ------------- + + + .'(fv) + .' + .* pm + .' ! + .' . + .' ! + .' . b + .' ! + * . + p ! + 90° . ... (pv) + ...-*-''' + ...---''' rpd + ...---''' d + *--''' + rp + + rpdx = rpx + d * pv.x + rpdy = rpy + d * pv.y + + equation of line b: + pvns... normal slope to pv + + y - rpdy = pvns * (x - rpdx) + + equation of freedom vector line: + fvs ... slope of freedom vector (=fy/fx) + + y - py = fvs * (x - px) + + + on pm both equations are true for same x/y + + y - rpdy = pvns * (x - rpdx) + + y - py = fvs * (x - px) + + form to y and set equal: + + pvns * (x - rpdx) + rpdy = fvs * (x - px) + py + + expand: + + pvns * x - pvns * rpdx + rpdy = fvs * x - fvs * px + py + + switch: + + fvs * x - fvs * px + py = pvns * x - pvns * rpdx + rpdy + + solve for x: + + fvs * x - pvns * x = fvs * px - pvns * rpdx - py + rpdy + + + + fvs * px - pvns * rpdx + rpdy - py + x = ----------------------------------- + fvs - pvns + + and: + + y = fvs * (x - px) + py + + + + INTERPOLATE: + ============ + + Examples of point interpolation. + + The weight of the movement of the reference point gets bigger + the further the other reference point is away, thus the safest + option (that is avoiding 0/0 divisions) is to weight the + original distance of the other point by the sum of both distances. + + If the sum of both distances is 0, then move the point by the + arithmetic average of the movement of both reference points. + + + + + (+6) + rp1o *---->*rp1 + . . (+12) + . . rp2o *---------->* rp2 + . . . . + . . . . + . 10 20 . . + |.........|...................| . + . . . + . . (+8) . + po *------>*p . + . . . + . 12 . 24 . + |...........|.......................| + 36 + + + ------- + + + + (+10) + rp1o *-------->*rp1 + . . (-10) + . . rp2 *<---------* rpo2 + . . . . + . . . . + . 10 . 30 . . + |.........|.............................| + . . + . (+5) . + po *--->* p . + . . . + . . 20 . + |....|..............| + 5 15 + + + ------- + + + (+10) + rp1o *-------->*rp1 + . . + . . + rp2o *-------->*rp2 + + + (+10) + po *-------->* p + + ------- + + + (+10) + rp1o *-------->*rp1 + . . + . .(+30) + rp2o *---------------------------->*rp2 + + + (+25) + po *----------------------->* p + + + + vim: set ts=4 sw=4 expandtab: + *****/ + + /** + * Converts a string into a list of tokens. + */ + + /** + * Create a new token + * @param {string} char a single char + */ + function Token(char) { + this.char = char; + this.state = {}; + this.activeState = null; + } + + /** + * Create a new context range + * @param {number} startIndex range start index + * @param {number} endOffset range end index offset + * @param {string} contextName owner context name + */ + function ContextRange(startIndex, endOffset, contextName) { + this.contextName = contextName; + this.startIndex = startIndex; + this.endOffset = endOffset; + } + + /** + * Check context start and end + * @param {string} contextName a unique context name + * @param {function} checkStart a predicate function the indicates a context's start + * @param {function} checkEnd a predicate function the indicates a context's end + */ + function ContextChecker(contextName, checkStart, checkEnd) { + this.contextName = contextName; + this.openRange = null; + this.ranges = []; + this.checkStart = checkStart; + this.checkEnd = checkEnd; + } + + /** + * @typedef ContextParams + * @type Object + * @property {array} context context items + * @property {number} currentIndex current item index + */ + + /** + * Create a context params + * @param {array} context a list of items + * @param {number} currentIndex current item index + */ + function ContextParams(context, currentIndex) { + this.context = context; + this.index = currentIndex; + this.length = context.length; + this.current = context[currentIndex]; + this.backtrack = context.slice(0, currentIndex); + this.lookahead = context.slice(currentIndex + 1); + } + + /** + * Create an event instance + * @param {string} eventId event unique id + */ + function Event(eventId) { + this.eventId = eventId; + this.subscribers = []; + } + + /** + * Initialize a core events and auto subscribe required event handlers + * @param {any} events an object that enlists core events handlers + */ + function initializeCoreEvents(events) { + var this$1 = this; + + var coreEvents = [ + 'start', 'end', 'next', 'newToken', 'contextStart', + 'contextEnd', 'insertToken', 'removeToken', 'removeRange', + 'replaceToken', 'replaceRange', 'composeRUD', 'updateContextsRanges' + ]; + + coreEvents.forEach(function (eventId) { + Object.defineProperty(this$1.events, eventId, { + value: new Event(eventId) + }); + }); + + if (!!events) { + coreEvents.forEach(function (eventId) { + var event = events[eventId]; + if (typeof event === 'function') { + this$1.events[eventId].subscribe(event); + } + }); + } + var requiresContextUpdate = [ + 'insertToken', 'removeToken', 'removeRange', + 'replaceToken', 'replaceRange', 'composeRUD' + ]; + requiresContextUpdate.forEach(function (eventId) { + this$1.events[eventId].subscribe( + this$1.updateContextsRanges + ); + }); + } + + /** + * Converts a string into a list of tokens + * @param {any} events tokenizer core events + */ + function Tokenizer(events) { + this.tokens = []; + this.registeredContexts = {}; + this.contextCheckers = []; + this.events = {}; + this.registeredModifiers = []; + + initializeCoreEvents.call(this, events); + } + + /** + * Sets the state of a token, usually called by a state modifier. + * @param {string} key state item key + * @param {any} value state item value + */ + Token.prototype.setState = function(key, value) { + this.state[key] = value; + this.activeState = { key: key, value: this.state[key] }; + return this.activeState; + }; + + Token.prototype.getState = function (stateId) { + return this.state[stateId] || null; + }; + + /** + * Checks if an index exists in the tokens list. + * @param {number} index token index + */ + Tokenizer.prototype.inboundIndex = function(index) { + return index >= 0 && index < this.tokens.length; + }; + + /** + * Compose and apply a list of operations (replace, update, delete) + * @param {array} RUDs replace, update and delete operations + * TODO: Perf. Optimization (lengthBefore === lengthAfter ? dispatch once) + */ + Tokenizer.prototype.composeRUD = function (RUDs) { + var this$1 = this; + + var silent = true; + var state = RUDs.map(function (RUD) { return ( + this$1[RUD[0]].apply(this$1, RUD.slice(1).concat(silent)) + ); }); + var hasFAILObject = function (obj) { return ( + typeof obj === 'object' && + obj.hasOwnProperty('FAIL') + ); }; + if (state.every(hasFAILObject)) { + return { + FAIL: "composeRUD: one or more operations hasn't completed successfully", + report: state.filter(hasFAILObject) + }; + } + this.dispatch('composeRUD', [state.filter(function (op) { return !hasFAILObject(op); })]); + }; + + /** + * Replace a range of tokens with a list of tokens + * @param {number} startIndex range start index + * @param {number} offset range offset + * @param {token} tokens a list of tokens to replace + * @param {boolean} silent dispatch events and update context ranges + */ + Tokenizer.prototype.replaceRange = function (startIndex, offset, tokens, silent) { + offset = offset !== null ? offset : this.tokens.length; + var isTokenType = tokens.every(function (token) { return token instanceof Token; }); + if (!isNaN(startIndex) && this.inboundIndex(startIndex) && isTokenType) { + var replaced = this.tokens.splice.apply( + this.tokens, [startIndex, offset].concat(tokens) + ); + if (!silent) { this.dispatch('replaceToken', [startIndex, offset, tokens]); } + return [replaced, tokens]; + } else { + return { FAIL: 'replaceRange: invalid tokens or startIndex.' }; + } + }; + + /** + * Replace a token with another token + * @param {number} index token index + * @param {token} token a token to replace + * @param {boolean} silent dispatch events and update context ranges + */ + Tokenizer.prototype.replaceToken = function (index, token, silent) { + if (!isNaN(index) && this.inboundIndex(index) && token instanceof Token) { + var replaced = this.tokens.splice(index, 1, token); + if (!silent) { this.dispatch('replaceToken', [index, token]); } + return [replaced[0], token]; + } else { + return { FAIL: 'replaceToken: invalid token or index.' }; + } + }; + + /** + * Removes a range of tokens + * @param {number} startIndex range start index + * @param {number} offset range offset + * @param {boolean} silent dispatch events and update context ranges + */ + Tokenizer.prototype.removeRange = function(startIndex, offset, silent) { + offset = !isNaN(offset) ? offset : this.tokens.length; + var tokens = this.tokens.splice(startIndex, offset); + if (!silent) { this.dispatch('removeRange', [tokens, startIndex, offset]); } + return tokens; + }; + + /** + * Remove a token at a certain index + * @param {number} index token index + * @param {boolean} silent dispatch events and update context ranges + */ + Tokenizer.prototype.removeToken = function(index, silent) { + if (!isNaN(index) && this.inboundIndex(index)) { + var token = this.tokens.splice(index, 1); + if (!silent) { this.dispatch('removeToken', [token, index]); } + return token; + } else { + return { FAIL: 'removeToken: invalid token index.' }; + } + }; + + /** + * Insert a list of tokens at a certain index + * @param {array} tokens a list of tokens to insert + * @param {number} index insert the list of tokens at index + * @param {boolean} silent dispatch events and update context ranges + */ + Tokenizer.prototype.insertToken = function (tokens, index, silent) { + var tokenType = tokens.every( + function (token) { return token instanceof Token; } + ); + if (tokenType) { + this.tokens.splice.apply( + this.tokens, [index, 0].concat(tokens) + ); + if (!silent) { this.dispatch('insertToken', [tokens, index]); } + return tokens; + } else { + return { FAIL: 'insertToken: invalid token(s).' }; + } + }; + + /** + * A state modifier that is called on 'newToken' event + * @param {string} modifierId state modifier id + * @param {function} condition a predicate function that returns true or false + * @param {function} modifier a function to update token state + */ + Tokenizer.prototype.registerModifier = function(modifierId, condition, modifier) { + this.events.newToken.subscribe(function(token, contextParams) { + var conditionParams = [token, contextParams]; + var canApplyModifier = ( + condition === null || + condition.apply(this, conditionParams) === true + ); + var modifierParams = [token, contextParams]; + if (canApplyModifier) { + var newStateValue = modifier.apply(this, modifierParams); + token.setState(modifierId, newStateValue); + } + }); + this.registeredModifiers.push(modifierId); + }; + + /** + * Subscribe a handler to an event + * @param {function} eventHandler an event handler function + */ + Event.prototype.subscribe = function (eventHandler) { + if (typeof eventHandler === 'function') { + return ((this.subscribers.push(eventHandler)) - 1); + } else { + return { FAIL: ("invalid '" + (this.eventId) + "' event handler")}; + } + }; + + /** + * Unsubscribe an event handler + * @param {string} subsId subscription id + */ + Event.prototype.unsubscribe = function (subsId) { + this.subscribers.splice(subsId, 1); + }; + + /** + * Sets context params current value index + * @param {number} index context params current value index + */ + ContextParams.prototype.setCurrentIndex = function(index) { + this.index = index; + this.current = this.context[index]; + this.backtrack = this.context.slice(0, index); + this.lookahead = this.context.slice(index + 1); + }; + + /** + * Get an item at an offset from the current value + * example (current value is 3): + * 1 2 [3] 4 5 | items values + * -2 -1 0 1 2 | offset values + * @param {number} offset an offset from current value index + */ + ContextParams.prototype.get = function (offset) { + switch (true) { + case (offset === 0): + return this.current; + case (offset < 0 && Math.abs(offset) <= this.backtrack.length): + return this.backtrack.slice(offset)[0]; + case (offset > 0 && offset <= this.lookahead.length): + return this.lookahead[offset - 1]; + default: + return null; + } + }; + + /** + * Converts a context range into a string value + * @param {contextRange} range a context range + */ + Tokenizer.prototype.rangeToText = function (range) { + if (range instanceof ContextRange) { + return ( + this.getRangeTokens(range) + .map(function (token) { return token.char; }).join('') + ); + } + }; + + /** + * Converts all tokens into a string + */ + Tokenizer.prototype.getText = function () { + return this.tokens.map(function (token) { return token.char; }).join(''); + }; + + /** + * Get a context by name + * @param {string} contextName context name to get + */ + Tokenizer.prototype.getContext = function (contextName) { + var context = this.registeredContexts[contextName]; + return !!context ? context : null; + }; + + /** + * Subscribes a new event handler to an event + * @param {string} eventName event name to subscribe to + * @param {function} eventHandler a function to be invoked on event + */ + Tokenizer.prototype.on = function(eventName, eventHandler) { + var event = this.events[eventName]; + if (!!event) { + return event.subscribe(eventHandler); + } else { + return null; + } + }; + + /** + * Dispatches an event + * @param {string} eventName event name + * @param {any} args event handler arguments + */ + Tokenizer.prototype.dispatch = function(eventName, args) { + var this$1 = this; + + var event = this.events[eventName]; + if (event instanceof Event) { + event.subscribers.forEach(function (subscriber) { + subscriber.apply(this$1, args || []); + }); + } + }; + + /** + * Register a new context checker + * @param {string} contextName a unique context name + * @param {function} contextStartCheck a predicate function that returns true on context start + * @param {function} contextEndCheck a predicate function that returns true on context end + * TODO: call tokenize on registration to update context ranges with the new context. + */ + Tokenizer.prototype.registerContextChecker = function(contextName, contextStartCheck, contextEndCheck) { + if (!!this.getContext(contextName)) { return { + FAIL: + ("context name '" + contextName + "' is already registered.") + }; } + if (typeof contextStartCheck !== 'function') { return { + FAIL: + "missing context start check." + }; } + if (typeof contextEndCheck !== 'function') { return { + FAIL: + "missing context end check." + }; } + var contextCheckers = new ContextChecker( + contextName, contextStartCheck, contextEndCheck + ); + this.registeredContexts[contextName] = contextCheckers; + this.contextCheckers.push(contextCheckers); + return contextCheckers; + }; + + /** + * Gets a context range tokens + * @param {contextRange} range a context range + */ + Tokenizer.prototype.getRangeTokens = function(range) { + var endIndex = range.startIndex + range.endOffset; + return [].concat( + this.tokens + .slice(range.startIndex, endIndex) + ); + }; + + /** + * Gets the ranges of a context + * @param {string} contextName context name + */ + Tokenizer.prototype.getContextRanges = function(contextName) { + var context = this.getContext(contextName); + if (!!context) { + return context.ranges; + } else { + return { FAIL: ("context checker '" + contextName + "' is not registered.") }; + } + }; + + /** + * Resets context ranges to run context update + */ + Tokenizer.prototype.resetContextsRanges = function () { + var registeredContexts = this.registeredContexts; + for (var contextName in registeredContexts) { + if (registeredContexts.hasOwnProperty(contextName)) { + var context = registeredContexts[contextName]; + context.ranges = []; + } + } + }; + + /** + * Updates context ranges + */ + Tokenizer.prototype.updateContextsRanges = function () { + this.resetContextsRanges(); + var chars = this.tokens.map(function (token) { return token.char; }); + for (var i = 0; i < chars.length; i++) { + var contextParams = new ContextParams(chars, i); + this.runContextCheck(contextParams); + } + this.dispatch('updateContextsRanges', [this.registeredContexts]); + }; + + /** + * Sets the end offset of an open range + * @param {number} offset range end offset + * @param {string} contextName context name + */ + Tokenizer.prototype.setEndOffset = function (offset, contextName) { + var startIndex = this.getContext(contextName).openRange.startIndex; + var range = new ContextRange(startIndex, offset, contextName); + var ranges = this.getContext(contextName).ranges; + range.rangeId = contextName + "." + (ranges.length); + ranges.push(range); + this.getContext(contextName).openRange = null; + return range; + }; + + /** + * Runs a context check on the current context + * @param {contextParams} contextParams current context params + */ + Tokenizer.prototype.runContextCheck = function(contextParams) { + var this$1 = this; + + var index = contextParams.index; + this.contextCheckers.forEach(function (contextChecker) { + var contextName = contextChecker.contextName; + var openRange = this$1.getContext(contextName).openRange; + if (!openRange && contextChecker.checkStart(contextParams)) { + openRange = new ContextRange(index, null, contextName); + this$1.getContext(contextName).openRange = openRange; + this$1.dispatch('contextStart', [contextName, index]); + } + if (!!openRange && contextChecker.checkEnd(contextParams)) { + var offset = (index - openRange.startIndex) + 1; + var range = this$1.setEndOffset(offset, contextName); + this$1.dispatch('contextEnd', [contextName, range]); + } + }); + }; + + /** + * Converts a text into a list of tokens + * @param {string} text a text to tokenize + */ + Tokenizer.prototype.tokenize = function (text) { + this.tokens = []; + this.resetContextsRanges(); + var chars = Array.from(text); + this.dispatch('start'); + for (var i = 0; i < chars.length; i++) { + var char = chars[i]; + var contextParams = new ContextParams(chars, i); + this.dispatch('next', [contextParams]); + this.runContextCheck(contextParams); + var token = new Token(char); + this.tokens.push(token); + this.dispatch('newToken', [token, contextParams]); + } + this.dispatch('end', [this.tokens]); + return this.tokens; + }; + + // ╭─┄┄┄────────────────────────┄─────────────────────────────────────────────╮ + // ┊ Character Class Assertions ┊ Checks if a char belongs to a certain class ┊ + // ╰─╾──────────────────────────┄─────────────────────────────────────────────╯ + // jscs:disable maximumLineLength + /** + * Check if a char is Arabic + * @param {string} c a single char + */ + function isArabicChar(c) { + return /[\u0600-\u065F\u066A-\u06D2\u06FA-\u06FF]/.test(c); + } + + /** + * Check if a char is an isolated arabic char + * @param {string} c a single char + */ + function isIsolatedArabicChar(char) { + return /[\u0630\u0690\u0621\u0631\u0661\u0671\u0622\u0632\u0672\u0692\u06C2\u0623\u0673\u0693\u06C3\u0624\u0694\u06C4\u0625\u0675\u0695\u06C5\u06E5\u0676\u0696\u06C6\u0627\u0677\u0697\u06C7\u0648\u0688\u0698\u06C8\u0689\u0699\u06C9\u068A\u06CA\u066B\u068B\u06CB\u068C\u068D\u06CD\u06FD\u068E\u06EE\u06FE\u062F\u068F\u06CF\u06EF]/.test(char); + } + + /** + * Check if a char is an Arabic Tashkeel char + * @param {string} c a single char + */ + function isTashkeelArabicChar(char) { + return /[\u0600-\u0605\u060C-\u060E\u0610-\u061B\u061E\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED]/.test(char); + } + + /** + * Check if a char is Latin + * @param {string} c a single char + */ + function isLatinChar(c) { + return /[A-z]/.test(c); + } + + /** + * Check if a char is whitespace char + * @param {string} c a single char + */ + function isWhiteSpace(c) { + return /\s/.test(c); + } + + /** + * Query a feature by some of it's properties to lookup a glyph substitution. + */ + + /** + * Create feature query instance + * @param {Font} font opentype font instance + */ + function FeatureQuery(font) { + this.font = font; + this.features = {}; + } + + /** + * @typedef SubstitutionAction + * @type Object + * @property {number} id substitution type + * @property {string} tag feature tag + * @property {any} substitution substitution value(s) + */ + + /** + * Create a substitution action instance + * @param {SubstitutionAction} action + */ + function SubstitutionAction(action) { + this.id = action.id; + this.tag = action.tag; + this.substitution = action.substitution; + } + + /** + * Lookup a coverage table + * @param {number} glyphIndex glyph index + * @param {CoverageTable} coverage coverage table + */ + function lookupCoverage(glyphIndex, coverage) { + if (!glyphIndex) { return -1; } + switch (coverage.format) { + case 1: + return coverage.glyphs.indexOf(glyphIndex); + + case 2: + var ranges = coverage.ranges; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (glyphIndex >= range.start && glyphIndex <= range.end) { + var offset = glyphIndex - range.start; + return range.index + offset; + } + } + break; + default: + return -1; // not found + } + return -1; + } + + /** + * Handle a single substitution - format 1 + * @param {ContextParams} contextParams context params to lookup + */ + function singleSubstitutionFormat1(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { return null; } + return glyphIndex + subtable.deltaGlyphId; + } + + /** + * Handle a single substitution - format 2 + * @param {ContextParams} contextParams context params to lookup + */ + function singleSubstitutionFormat2(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { return null; } + return subtable.substitute[substituteIndex]; + } + + /** + * Lookup a list of coverage tables + * @param {any} coverageList a list of coverage tables + * @param {ContextParams} contextParams context params to lookup + */ + function lookupCoverageList(coverageList, contextParams) { + var lookupList = []; + for (var i = 0; i < coverageList.length; i++) { + var coverage = coverageList[i]; + var glyphIndex = contextParams.current; + glyphIndex = Array.isArray(glyphIndex) ? glyphIndex[0] : glyphIndex; + var lookupIndex = lookupCoverage(glyphIndex, coverage); + if (lookupIndex !== -1) { + lookupList.push(lookupIndex); + } + } + if (lookupList.length !== coverageList.length) { return -1; } + return lookupList; + } + + /** + * Handle chaining context substitution - format 3 + * @param {ContextParams} contextParams context params to lookup + */ + function chainingSubstitutionFormat3(contextParams, subtable) { + var lookupsCount = ( + subtable.inputCoverage.length + + subtable.lookaheadCoverage.length + + subtable.backtrackCoverage.length + ); + if (contextParams.context.length < lookupsCount) { return []; } + // INPUT LOOKUP // + var inputLookups = lookupCoverageList( + subtable.inputCoverage, contextParams + ); + if (inputLookups === -1) { return []; } + // LOOKAHEAD LOOKUP // + var lookaheadOffset = subtable.inputCoverage.length - 1; + if (contextParams.lookahead.length < subtable.lookaheadCoverage.length) { return []; } + var lookaheadContext = contextParams.lookahead.slice(lookaheadOffset); + while (lookaheadContext.length && isTashkeelArabicChar(lookaheadContext[0].char)) { + lookaheadContext.shift(); + } + var lookaheadParams = new ContextParams(lookaheadContext, 0); + var lookaheadLookups = lookupCoverageList( + subtable.lookaheadCoverage, lookaheadParams + ); + // BACKTRACK LOOKUP // + var backtrackContext = [].concat(contextParams.backtrack); + backtrackContext.reverse(); + while (backtrackContext.length && isTashkeelArabicChar(backtrackContext[0].char)) { + backtrackContext.shift(); + } + if (backtrackContext.length < subtable.backtrackCoverage.length) { return []; } + var backtrackParams = new ContextParams(backtrackContext, 0); + var backtrackLookups = lookupCoverageList( + subtable.backtrackCoverage, backtrackParams + ); + var contextRulesMatch = ( + inputLookups.length === subtable.inputCoverage.length && + lookaheadLookups.length === subtable.lookaheadCoverage.length && + backtrackLookups.length === subtable.backtrackCoverage.length + ); + var substitutions = []; + if (contextRulesMatch) { + for (var i = 0; i < subtable.lookupRecords.length; i++) { + var lookupRecord = subtable.lookupRecords[i]; + var lookupListIndex = lookupRecord.lookupListIndex; + var lookupTable = this.getLookupByIndex(lookupListIndex); + for (var s = 0; s < lookupTable.subtables.length; s++) { + var subtable$1 = lookupTable.subtables[s]; + var lookup = this.getLookupMethod(lookupTable, subtable$1); + var substitutionType = this.getSubstitutionType(lookupTable, subtable$1); + if (substitutionType === '12') { + for (var n = 0; n < inputLookups.length; n++) { + var glyphIndex = contextParams.get(n); + var substitution = lookup(glyphIndex); + if (substitution) { substitutions.push(substitution); } + } + } + } + } + } + return substitutions; + } + + /** + * Handle ligature substitution - format 1 + * @param {ContextParams} contextParams context params to lookup + */ + function ligatureSubstitutionFormat1(contextParams, subtable) { + // COVERAGE LOOKUP // + var glyphIndex = contextParams.current; + var ligSetIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (ligSetIndex === -1) { return null; } + // COMPONENTS LOOKUP + // (!) note, components are ordered in the written direction. + var ligature; + var ligatureSet = subtable.ligatureSets[ligSetIndex]; + for (var s = 0; s < ligatureSet.length; s++) { + ligature = ligatureSet[s]; + for (var l = 0; l < ligature.components.length; l++) { + var lookaheadItem = contextParams.lookahead[l]; + var component = ligature.components[l]; + if (lookaheadItem !== component) { break; } + if (l === ligature.components.length - 1) { return ligature; } + } + } + return null; + } + + /** + * Handle decomposition substitution - format 1 + * @param {number} glyphIndex glyph index + * @param {any} subtable subtable + */ + function decompositionSubstitutionFormat1(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { return null; } + return subtable.sequences[substituteIndex]; + } + + /** + * Get default script features indexes + */ + FeatureQuery.prototype.getDefaultScriptFeaturesIndexes = function () { + var scripts = this.font.tables.gsub.scripts; + for (var s = 0; s < scripts.length; s++) { + var script = scripts[s]; + if (script.tag === 'DFLT') { return ( + script.script.defaultLangSys.featureIndexes + ); } + } + return []; + }; + + /** + * Get feature indexes of a specific script + * @param {string} scriptTag script tag + */ + FeatureQuery.prototype.getScriptFeaturesIndexes = function(scriptTag) { + var tables = this.font.tables; + if (!tables.gsub) { return []; } + if (!scriptTag) { return this.getDefaultScriptFeaturesIndexes(); } + var scripts = this.font.tables.gsub.scripts; + for (var i = 0; i < scripts.length; i++) { + var script = scripts[i]; + if (script.tag === scriptTag && script.script.defaultLangSys) { + return script.script.defaultLangSys.featureIndexes; + } else { + var langSysRecords = script.langSysRecords; + if (!!langSysRecords) { + for (var j = 0; j < langSysRecords.length; j++) { + var langSysRecord = langSysRecords[j]; + if (langSysRecord.tag === scriptTag) { + var langSys = langSysRecord.langSys; + return langSys.featureIndexes; + } + } + } + } + } + return this.getDefaultScriptFeaturesIndexes(); + }; + + /** + * Map a feature tag to a gsub feature + * @param {any} features gsub features + * @param {string} scriptTag script tag + */ + FeatureQuery.prototype.mapTagsToFeatures = function (features, scriptTag) { + var tags = {}; + for (var i = 0; i < features.length; i++) { + var tag = features[i].tag; + var feature = features[i].feature; + tags[tag] = feature; + } + this.features[scriptTag].tags = tags; + }; + + /** + * Get features of a specific script + * @param {string} scriptTag script tag + */ + FeatureQuery.prototype.getScriptFeatures = function (scriptTag) { + var features = this.features[scriptTag]; + if (this.features.hasOwnProperty(scriptTag)) { return features; } + var featuresIndexes = this.getScriptFeaturesIndexes(scriptTag); + if (!featuresIndexes) { return null; } + var gsub = this.font.tables.gsub; + features = featuresIndexes.map(function (index) { return gsub.features[index]; }); + this.features[scriptTag] = features; + this.mapTagsToFeatures(features, scriptTag); + return features; + }; + + /** + * Get substitution type + * @param {any} lookupTable lookup table + * @param {any} subtable subtable + */ + FeatureQuery.prototype.getSubstitutionType = function(lookupTable, subtable) { + var lookupType = lookupTable.lookupType.toString(); + var substFormat = subtable.substFormat.toString(); + return lookupType + substFormat; + }; + + /** + * Get lookup method + * @param {any} lookupTable lookup table + * @param {any} subtable subtable + */ + FeatureQuery.prototype.getLookupMethod = function(lookupTable, subtable) { + var this$1 = this; + + var substitutionType = this.getSubstitutionType(lookupTable, subtable); + switch (substitutionType) { + case '11': + return function (glyphIndex) { return singleSubstitutionFormat1.apply( + this$1, [glyphIndex, subtable] + ); }; + case '12': + return function (glyphIndex) { return singleSubstitutionFormat2.apply( + this$1, [glyphIndex, subtable] + ); }; + case '63': + return function (contextParams) { return chainingSubstitutionFormat3.apply( + this$1, [contextParams, subtable] + ); }; + case '41': + return function (contextParams) { return ligatureSubstitutionFormat1.apply( + this$1, [contextParams, subtable] + ); }; + case '21': + return function (glyphIndex) { return decompositionSubstitutionFormat1.apply( + this$1, [glyphIndex, subtable] + ); }; + default: + throw new Error( + "lookupType: " + (lookupTable.lookupType) + " - " + + "substFormat: " + (subtable.substFormat) + " " + + "is not yet supported" + ); + } + }; + + /** + * [ LOOKUP TYPES ] + * ------------------------------- + * Single 1; + * Multiple 2; + * Alternate 3; + * Ligature 4; + * Context 5; + * ChainingContext 6; + * ExtensionSubstitution 7; + * ReverseChainingContext 8; + * ------------------------------- + * + */ + + /** + * @typedef FQuery + * @type Object + * @param {string} tag feature tag + * @param {string} script feature script + * @param {ContextParams} contextParams context params + */ + + /** + * Lookup a feature using a query parameters + * @param {FQuery} query feature query + */ + FeatureQuery.prototype.lookupFeature = function (query) { + var contextParams = query.contextParams; + var currentIndex = contextParams.index; + var feature = this.getFeature({ + tag: query.tag, script: query.script + }); + if (!feature) { return new Error( + "font '" + (this.font.names.fullName.en) + "' " + + "doesn't support feature '" + (query.tag) + "' " + + "for script '" + (query.script) + "'." + ); } + var lookups = this.getFeatureLookups(feature); + var substitutions = [].concat(contextParams.context); + for (var l = 0; l < lookups.length; l++) { + var lookupTable = lookups[l]; + var subtables = this.getLookupSubtables(lookupTable); + for (var s = 0; s < subtables.length; s++) { + var subtable = subtables[s]; + var substType = this.getSubstitutionType(lookupTable, subtable); + var lookup = this.getLookupMethod(lookupTable, subtable); + var substitution = (void 0); + switch (substType) { + case '11': + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 11, tag: query.tag, substitution: substitution + })); + } + break; + case '12': + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 12, tag: query.tag, substitution: substitution + })); + } + break; + case '63': + substitution = lookup(contextParams); + if (Array.isArray(substitution) && substitution.length) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 63, tag: query.tag, substitution: substitution + })); + } + break; + case '41': + substitution = lookup(contextParams); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 41, tag: query.tag, substitution: substitution + })); + } + break; + case '21': + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 21, tag: query.tag, substitution: substitution + })); + } + break; + } + contextParams = new ContextParams(substitutions, currentIndex); + if (Array.isArray(substitution) && !substitution.length) { continue; } + substitution = null; + } + } + return substitutions.length ? substitutions : null; + }; + + /** + * Checks if a font supports a specific features + * @param {FQuery} query feature query object + */ + FeatureQuery.prototype.supports = function (query) { + if (!query.script) { return false; } + this.getScriptFeatures(query.script); + var supportedScript = this.features.hasOwnProperty(query.script); + if (!query.tag) { return supportedScript; } + var supportedFeature = ( + this.features[query.script].some(function (feature) { return feature.tag === query.tag; }) + ); + return supportedScript && supportedFeature; + }; + + /** + * Get lookup table subtables + * @param {any} lookupTable lookup table + */ + FeatureQuery.prototype.getLookupSubtables = function (lookupTable) { + return lookupTable.subtables || null; + }; + + /** + * Get lookup table by index + * @param {number} index lookup table index + */ + FeatureQuery.prototype.getLookupByIndex = function (index) { + var lookups = this.font.tables.gsub.lookups; + return lookups[index] || null; + }; + + /** + * Get lookup tables for a feature + * @param {string} feature + */ + FeatureQuery.prototype.getFeatureLookups = function (feature) { + // TODO: memoize + return feature.lookupListIndexes.map(this.getLookupByIndex.bind(this)); + }; + + /** + * Query a feature by it's properties + * @param {any} query an object that describes the properties of a query + */ + FeatureQuery.prototype.getFeature = function getFeature(query) { + if (!this.font) { return { FAIL: "No font was found"}; } + if (!this.features.hasOwnProperty(query.script)) { + this.getScriptFeatures(query.script); + } + var scriptFeatures = this.features[query.script]; + if (!scriptFeatures) { return ( + { FAIL: ("No feature for script " + (query.script))} + ); } + if (!scriptFeatures.tags[query.tag]) { return null; } + return this.features[query.script].tags[query.tag]; + }; + + /** + * Arabic word context checkers + */ + + function arabicWordStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? arabic first char + (prevChar === null && isArabicChar(char)) || + // ? arabic char preceded with a non arabic char + (!isArabicChar(prevChar) && isArabicChar(char)) + ); + } + + function arabicWordEndCheck(contextParams) { + var nextChar = contextParams.get(1); + return ( + // ? last arabic char + (nextChar === null) || + // ? next char is not arabic + (!isArabicChar(nextChar)) + ); + } + + var arabicWordCheck = { + startCheck: arabicWordStartCheck, + endCheck: arabicWordEndCheck + }; + + /** + * Arabic sentence context checkers + */ + + function arabicSentenceStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? an arabic char preceded with a non arabic char + (isArabicChar(char) || isTashkeelArabicChar(char)) && + !isArabicChar(prevChar) + ); + } + + function arabicSentenceEndCheck(contextParams) { + var nextChar = contextParams.get(1); + switch (true) { + case nextChar === null: + return true; + case (!isArabicChar(nextChar) && !isTashkeelArabicChar(nextChar)): + var nextIsWhitespace = isWhiteSpace(nextChar); + if (!nextIsWhitespace) { return true; } + if (nextIsWhitespace) { + var arabicCharAhead = false; + arabicCharAhead = ( + contextParams.lookahead.some( + function (c) { return isArabicChar(c) || isTashkeelArabicChar(c); } + ) + ); + if (!arabicCharAhead) { return true; } + } + break; + default: + return false; + } + } + + var arabicSentenceCheck = { + startCheck: arabicSentenceStartCheck, + endCheck: arabicSentenceEndCheck + }; + + /** + * Apply single substitution format 1 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ + function singleSubstitutionFormat1$1(action, tokens, index) { + tokens[index].setState(action.tag, action.substitution); + } + + /** + * Apply single substitution format 2 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ + function singleSubstitutionFormat2$1(action, tokens, index) { + tokens[index].setState(action.tag, action.substitution); + } + + /** + * Apply chaining context substitution format 3 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ + function chainingSubstitutionFormat3$1(action, tokens, index) { + action.substitution.forEach(function (subst, offset) { + var token = tokens[index + offset]; + token.setState(action.tag, subst); + }); + } + + /** + * Apply ligature substitution format 1 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ + function ligatureSubstitutionFormat1$1(action, tokens, index) { + var token = tokens[index]; + token.setState(action.tag, action.substitution.ligGlyph); + var compsCount = action.substitution.components.length; + for (var i = 0; i < compsCount; i++) { + token = tokens[index + i + 1]; + token.setState('deleted', true); + } + } + + /** + * Supported substitutions + */ + var SUBSTITUTIONS = { + 11: singleSubstitutionFormat1$1, + 12: singleSubstitutionFormat2$1, + 63: chainingSubstitutionFormat3$1, + 41: ligatureSubstitutionFormat1$1 + }; + + /** + * Apply substitutions to a list of tokens + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ + function applySubstitution(action, tokens, index) { + if (action instanceof SubstitutionAction && SUBSTITUTIONS[action.id]) { + SUBSTITUTIONS[action.id](action, tokens, index); + } + } + + /** + * Apply Arabic presentation forms to a range of tokens + */ + + /** + * Check if a char can be connected to it's preceding char + * @param {ContextParams} charContextParams context params of a char + */ + function willConnectPrev(charContextParams) { + var backtrack = [].concat(charContextParams.backtrack); + for (var i = backtrack.length - 1; i >= 0; i--) { + var prevChar = backtrack[i]; + var isolated = isIsolatedArabicChar(prevChar); + var tashkeel = isTashkeelArabicChar(prevChar); + if (!isolated && !tashkeel) { return true; } + if (isolated) { return false; } + } + return false; + } + + /** + * Check if a char can be connected to it's proceeding char + * @param {ContextParams} charContextParams context params of a char + */ + function willConnectNext(charContextParams) { + if (isIsolatedArabicChar(charContextParams.current)) { return false; } + for (var i = 0; i < charContextParams.lookahead.length; i++) { + var nextChar = charContextParams.lookahead[i]; + var tashkeel = isTashkeelArabicChar(nextChar); + if (!tashkeel) { return true; } + } + return false; + } + + /** + * Apply arabic presentation forms to a list of tokens + * @param {ContextRange} range a range of tokens + */ + function arabicPresentationForms(range) { + var this$1 = this; + + var script = 'arab'; + var tags = this.featuresTags[script]; + var tokens = this.tokenizer.getRangeTokens(range); + if (tokens.length === 1) { return; } + var contextParams = new ContextParams( + tokens.map(function (token) { return token.getState('glyphIndex'); } + ), 0); + var charContextParams = new ContextParams( + tokens.map(function (token) { return token.char; } + ), 0); + tokens.forEach(function (token, index) { + if (isTashkeelArabicChar(token.char)) { return; } + contextParams.setCurrentIndex(index); + charContextParams.setCurrentIndex(index); + var CONNECT = 0; // 2 bits 00 (10: can connect next) (01: can connect prev) + if (willConnectPrev(charContextParams)) { CONNECT |= 1; } + if (willConnectNext(charContextParams)) { CONNECT |= 2; } + var tag; + switch (CONNECT) { + case 1: (tag = 'fina'); break; + case 2: (tag = 'init'); break; + case 3: (tag = 'medi'); break; + } + if (tags.indexOf(tag) === -1) { return; } + var substitutions = this$1.query.lookupFeature({ + tag: tag, script: script, contextParams: contextParams + }); + if (substitutions instanceof Error) { return console.info(substitutions.message); } + substitutions.forEach(function (action, index) { + if (action instanceof SubstitutionAction) { + applySubstitution(action, tokens, index); + contextParams.context[index] = action.substitution; + } + }); + }); + } + + /** + * Apply Arabic required ligatures feature to a range of tokens + */ + + /** + * Update context params + * @param {any} tokens a list of tokens + * @param {number} index current item index + */ + function getContextParams(tokens, index) { + var context = tokens.map(function (token) { return token.activeState.value; }); + return new ContextParams(context, index || 0); + } + + /** + * Apply Arabic required ligatures to a context range + * @param {ContextRange} range a range of tokens + */ + function arabicRequiredLigatures(range) { + var this$1 = this; + + var script = 'arab'; + var tokens = this.tokenizer.getRangeTokens(range); + var contextParams = getContextParams(tokens); + contextParams.context.forEach(function (glyphIndex, index) { + contextParams.setCurrentIndex(index); + var substitutions = this$1.query.lookupFeature({ + tag: 'rlig', script: script, contextParams: contextParams + }); + if (substitutions.length) { + substitutions.forEach( + function (action) { return applySubstitution(action, tokens, index); } + ); + contextParams = getContextParams(tokens); + } + }); + } + + /** + * Latin word context checkers + */ + + function latinWordStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? latin first char + (prevChar === null && isLatinChar(char)) || + // ? latin char preceded with a non latin char + (!isLatinChar(prevChar) && isLatinChar(char)) + ); + } + + function latinWordEndCheck(contextParams) { + var nextChar = contextParams.get(1); + return ( + // ? last latin char + (nextChar === null) || + // ? next char is not latin + (!isLatinChar(nextChar)) + ); + } + + var latinWordCheck = { + startCheck: latinWordStartCheck, + endCheck: latinWordEndCheck + }; + + /** + * Apply Latin ligature feature to a range of tokens + */ + + /** + * Update context params + * @param {any} tokens a list of tokens + * @param {number} index current item index + */ + function getContextParams$1(tokens, index) { + var context = tokens.map(function (token) { return token.activeState.value; }); + return new ContextParams(context, index || 0); + } + + /** + * Apply Arabic required ligatures to a context range + * @param {ContextRange} range a range of tokens + */ + function latinLigature(range) { + var this$1 = this; + + var script = 'latn'; + var tokens = this.tokenizer.getRangeTokens(range); + var contextParams = getContextParams$1(tokens); + contextParams.context.forEach(function (glyphIndex, index) { + contextParams.setCurrentIndex(index); + var substitutions = this$1.query.lookupFeature({ + tag: 'liga', script: script, contextParams: contextParams + }); + if (substitutions.length) { + substitutions.forEach( + function (action) { return applySubstitution(action, tokens, index); } + ); + contextParams = getContextParams$1(tokens); + } + }); + } + + /** + * Infer bidirectional properties for a given text and apply + * the corresponding layout rules. + */ + + /** + * Create Bidi. features + * @param {string} baseDir text base direction. value either 'ltr' or 'rtl' + */ + function Bidi(baseDir) { + this.baseDir = baseDir || 'ltr'; + this.tokenizer = new Tokenizer(); + this.featuresTags = {}; + } + + /** + * Sets Bidi text + * @param {string} text a text input + */ + Bidi.prototype.setText = function (text) { + this.text = text; + }; + + /** + * Store essential context checks: + * arabic word check for applying gsub features + * arabic sentence check for adjusting arabic layout + */ + Bidi.prototype.contextChecks = ({ + latinWordCheck: latinWordCheck, + arabicWordCheck: arabicWordCheck, + arabicSentenceCheck: arabicSentenceCheck + }); + + /** + * Register arabic word check + */ + function registerContextChecker(checkId) { + var check = this.contextChecks[(checkId + "Check")]; + return this.tokenizer.registerContextChecker( + checkId, check.startCheck, check.endCheck + ); + } + + /** + * Perform pre tokenization procedure then + * tokenize text input + */ + function tokenizeText() { + registerContextChecker.call(this, 'latinWord'); + registerContextChecker.call(this, 'arabicWord'); + registerContextChecker.call(this, 'arabicSentence'); + return this.tokenizer.tokenize(this.text); + } + + /** + * Reverse arabic sentence layout + * TODO: check base dir before applying adjustments - priority low + */ + function reverseArabicSentences() { + var this$1 = this; + + var ranges = this.tokenizer.getContextRanges('arabicSentence'); + ranges.forEach(function (range) { + var rangeTokens = this$1.tokenizer.getRangeTokens(range); + this$1.tokenizer.replaceRange( + range.startIndex, + range.endOffset, + rangeTokens.reverse() + ); + }); + } + + /** + * Register supported features tags + * @param {script} script script tag + * @param {Array} tags features tags list + */ + Bidi.prototype.registerFeatures = function (script, tags) { + var this$1 = this; + + var supportedTags = tags.filter( + function (tag) { return this$1.query.supports({script: script, tag: tag}); } + ); + if (!this.featuresTags.hasOwnProperty(script)) { + this.featuresTags[script] = supportedTags; + } else { + this.featuresTags[script] = + this.featuresTags[script].concat(supportedTags); + } + }; + + /** + * Apply GSUB features + * @param {Array} tagsList a list of features tags + * @param {string} script a script tag + * @param {Font} font opentype font instance + */ + Bidi.prototype.applyFeatures = function (font, features) { + if (!font) { throw new Error( + 'No valid font was provided to apply features' + ); } + if (!this.query) { this.query = new FeatureQuery(font); } + for (var f = 0; f < features.length; f++) { + var feature = features[f]; + if (!this.query.supports({script: feature.script})) { continue; } + this.registerFeatures(feature.script, feature.tags); + } + }; + + /** + * Register a state modifier + * @param {string} modifierId state modifier id + * @param {function} condition a predicate function that returns true or false + * @param {function} modifier a modifier function to set token state + */ + Bidi.prototype.registerModifier = function (modifierId, condition, modifier) { + this.tokenizer.registerModifier(modifierId, condition, modifier); + }; + + /** + * Check if 'glyphIndex' is registered + */ + function checkGlyphIndexStatus() { + if (this.tokenizer.registeredModifiers.indexOf('glyphIndex') === -1) { + throw new Error( + 'glyphIndex modifier is required to apply ' + + 'arabic presentation features.' + ); + } + } + + /** + * Apply arabic presentation forms features + */ + function applyArabicPresentationForms() { + var this$1 = this; + + var script = 'arab'; + if (!this.featuresTags.hasOwnProperty(script)) { return; } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges('arabicWord'); + ranges.forEach(function (range) { + arabicPresentationForms.call(this$1, range); + }); + } + + /** + * Apply required arabic ligatures + */ + function applyArabicRequireLigatures() { + var this$1 = this; + + var script = 'arab'; + if (!this.featuresTags.hasOwnProperty(script)) { return; } + var tags = this.featuresTags[script]; + if (tags.indexOf('rlig') === -1) { return; } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges('arabicWord'); + ranges.forEach(function (range) { + arabicRequiredLigatures.call(this$1, range); + }); + } + + /** + * Apply required arabic ligatures + */ + function applyLatinLigatures() { + var this$1 = this; + + var script = 'latn'; + if (!this.featuresTags.hasOwnProperty(script)) { return; } + var tags = this.featuresTags[script]; + if (tags.indexOf('liga') === -1) { return; } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges('latinWord'); + ranges.forEach(function (range) { + latinLigature.call(this$1, range); + }); + } + + /** + * Check if a context is registered + * @param {string} contextId context id + */ + Bidi.prototype.checkContextReady = function (contextId) { + return !!this.tokenizer.getContext(contextId); + }; + + /** + * Apply features to registered contexts + */ + Bidi.prototype.applyFeaturesToContexts = function () { + if (this.checkContextReady('arabicWord')) { + applyArabicPresentationForms.call(this); + applyArabicRequireLigatures.call(this); + } + if (this.checkContextReady('latinWord')) { + applyLatinLigatures.call(this); + } + if (this.checkContextReady('arabicSentence')) { + reverseArabicSentences.call(this); + } + }; + + /** + * process text input + * @param {string} text an input text + */ + Bidi.prototype.processText = function(text) { + if (!this.text || this.text !== text) { + this.setText(text); + tokenizeText.call(this); + this.applyFeaturesToContexts(); + } + }; + + /** + * Process a string of text to identify and adjust + * bidirectional text entities. + * @param {string} text input text + */ + Bidi.prototype.getBidiText = function (text) { + this.processText(text); + return this.tokenizer.getText(); + }; + + /** + * Get the current state index of each token + * @param {text} text an input text + */ + Bidi.prototype.getTextGlyphs = function (text) { + this.processText(text); + var indexes = []; + for (var i = 0; i < this.tokenizer.tokens.length; i++) { + var token = this.tokenizer.tokens[i]; + if (token.state.deleted) { continue; } + var index = token.activeState.value; + indexes.push(Array.isArray(index) ? index[0] : index); + } + return indexes; + }; + + // The Font object + + /** + * @typedef FontOptions + * @type Object + * @property {Boolean} empty - whether to create a new empty font + * @property {string} familyName + * @property {string} styleName + * @property {string=} fullName + * @property {string=} postScriptName + * @property {string=} designer + * @property {string=} designerURL + * @property {string=} manufacturer + * @property {string=} manufacturerURL + * @property {string=} license + * @property {string=} licenseURL + * @property {string=} version + * @property {string=} description + * @property {string=} copyright + * @property {string=} trademark + * @property {Number} unitsPerEm + * @property {Number} ascender + * @property {Number} descender + * @property {Number} createdTimestamp + * @property {string=} weightClass + * @property {string=} widthClass + * @property {string=} fsSelection + */ + + /** + * A Font represents a loaded OpenType font file. + * It contains a set of glyphs and methods to draw text on a drawing context, + * or to get a path representing the text. + * @exports opentype.Font + * @class + * @param {FontOptions} + * @constructor + */ + function Font(options) { + options = options || {}; + options.tables = options.tables || {}; + + if (!options.empty) { + // Check that we've provided the minimum set of names. + checkArgument(options.familyName, 'When creating a new Font object, familyName is required.'); + checkArgument(options.styleName, 'When creating a new Font object, styleName is required.'); + checkArgument(options.unitsPerEm, 'When creating a new Font object, unitsPerEm is required.'); + checkArgument(options.ascender, 'When creating a new Font object, ascender is required.'); + checkArgument(options.descender <= 0, 'When creating a new Font object, negative descender value is required.'); + + // OS X will complain if the names are empty, so we put a single space everywhere by default. + this.names = { + fontFamily: {en: options.familyName || ' '}, + fontSubfamily: {en: options.styleName || ' '}, + fullName: {en: options.fullName || options.familyName + ' ' + options.styleName}, + // postScriptName may not contain any whitespace + postScriptName: {en: options.postScriptName || (options.familyName + options.styleName).replace(/\s/g, '')}, + designer: {en: options.designer || ' '}, + designerURL: {en: options.designerURL || ' '}, + manufacturer: {en: options.manufacturer || ' '}, + manufacturerURL: {en: options.manufacturerURL || ' '}, + license: {en: options.license || ' '}, + licenseURL: {en: options.licenseURL || ' '}, + version: {en: options.version || 'Version 0.1'}, + description: {en: options.description || ' '}, + copyright: {en: options.copyright || ' '}, + trademark: {en: options.trademark || ' '} + }; + this.unitsPerEm = options.unitsPerEm || 1000; + this.ascender = options.ascender; + this.descender = options.descender; + this.createdTimestamp = options.createdTimestamp; + this.tables = Object.assign(options.tables, { + os2: Object.assign({ + usWeightClass: options.weightClass || this.usWeightClasses.MEDIUM, + usWidthClass: options.widthClass || this.usWidthClasses.MEDIUM, + fsSelection: options.fsSelection || this.fsSelectionValues.REGULAR, + }, options.tables.os2) + }); + } + + this.supported = true; // Deprecated: parseBuffer will throw an error if font is not supported. + this.glyphs = new glyphset.GlyphSet(this, options.glyphs || []); + this.encoding = new DefaultEncoding(this); + this.position = new Position(this); + this.substitution = new Substitution(this); + this.tables = this.tables || {}; + + // needed for low memory mode only. + this._push = null; + this._hmtxTableData = {}; + + Object.defineProperty(this, 'hinting', { + get: function() { + if (this._hinting) { return this._hinting; } + if (this.outlinesFormat === 'truetype') { + return (this._hinting = new Hinting(this)); + } + } + }); + } + + /** + * Check if the font has a glyph for the given character. + * @param {string} + * @return {Boolean} + */ + Font.prototype.hasChar = function(c) { + return this.encoding.charToGlyphIndex(c) !== null; + }; + + /** + * Convert the given character to a single glyph index. + * Note that this function assumes that there is a one-to-one mapping between + * the given character and a glyph; for complex scripts this might not be the case. + * @param {string} + * @return {Number} + */ + Font.prototype.charToGlyphIndex = function(s) { + return this.encoding.charToGlyphIndex(s); + }; + + /** + * Convert the given character to a single Glyph object. + * Note that this function assumes that there is a one-to-one mapping between + * the given character and a glyph; for complex scripts this might not be the case. + * @param {string} + * @return {opentype.Glyph} + */ + Font.prototype.charToGlyph = function(c) { + var glyphIndex = this.charToGlyphIndex(c); + var glyph = this.glyphs.get(glyphIndex); + if (!glyph) { + // .notdef + glyph = this.glyphs.get(0); + } + + return glyph; + }; + + /** + * Update features + * @param {any} options features options + */ + Font.prototype.updateFeatures = function (options) { + // TODO: update all features options not only 'latn'. + return this.defaultRenderOptions.features.map(function (feature) { + if (feature.script === 'latn') { + return { + script: 'latn', + tags: feature.tags.filter(function (tag) { return options[tag]; }) + }; + } else { + return feature; + } + }); + }; + + /** + * Convert the given text to a list of Glyph objects. + * Note that there is no strict one-to-one mapping between characters and + * glyphs, so the list of returned glyphs can be larger or smaller than the + * length of the given string. + * @param {string} + * @param {GlyphRenderOptions} [options] + * @return {opentype.Glyph[]} + */ + Font.prototype.stringToGlyphs = function(s, options) { + var this$1 = this; + + + var bidi = new Bidi(); + + // Create and register 'glyphIndex' state modifier + var charToGlyphIndexMod = function (token) { return this$1.charToGlyphIndex(token.char); }; + bidi.registerModifier('glyphIndex', null, charToGlyphIndexMod); + + // roll-back to default features + var features = options ? + this.updateFeatures(options.features) : + this.defaultRenderOptions.features; + + bidi.applyFeatures(this, features); + + var indexes = bidi.getTextGlyphs(s); + + var length = indexes.length; + + // convert glyph indexes to glyph objects + var glyphs = new Array(length); + var notdef = this.glyphs.get(0); + for (var i = 0; i < length; i += 1) { + glyphs[i] = this.glyphs.get(indexes[i]) || notdef; + } + return glyphs; + }; + + /** + * @param {string} + * @return {Number} + */ + Font.prototype.nameToGlyphIndex = function(name) { + return this.glyphNames.nameToGlyphIndex(name); + }; + + /** + * @param {string} + * @return {opentype.Glyph} + */ + Font.prototype.nameToGlyph = function(name) { + var glyphIndex = this.nameToGlyphIndex(name); + var glyph = this.glyphs.get(glyphIndex); + if (!glyph) { + // .notdef + glyph = this.glyphs.get(0); + } + + return glyph; + }; + + /** + * @param {Number} + * @return {String} + */ + Font.prototype.glyphIndexToName = function(gid) { + if (!this.glyphNames.glyphIndexToName) { + return ''; + } + + return this.glyphNames.glyphIndexToName(gid); + }; + + /** + * Retrieve the value of the kerning pair between the left glyph (or its index) + * and the right glyph (or its index). If no kerning pair is found, return 0. + * The kerning value gets added to the advance width when calculating the spacing + * between glyphs. + * For GPOS kerning, this method uses the default script and language, which covers + * most use cases. To have greater control, use font.position.getKerningValue . + * @param {opentype.Glyph} leftGlyph + * @param {opentype.Glyph} rightGlyph + * @return {Number} + */ + Font.prototype.getKerningValue = function(leftGlyph, rightGlyph) { + leftGlyph = leftGlyph.index || leftGlyph; + rightGlyph = rightGlyph.index || rightGlyph; + var gposKerning = this.position.defaultKerningTables; + if (gposKerning) { + return this.position.getKerningValue(gposKerning, leftGlyph, rightGlyph); + } + // "kern" table + return this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0; + }; + + /** + * @typedef GlyphRenderOptions + * @type Object + * @property {string} [script] - script used to determine which features to apply. By default, 'DFLT' or 'latn' is used. + * See https://www.microsoft.com/typography/otspec/scripttags.htm + * @property {string} [language='dflt'] - language system used to determine which features to apply. + * See https://www.microsoft.com/typography/developers/opentype/languagetags.aspx + * @property {boolean} [kerning=true] - whether to include kerning values + * @property {object} [features] - OpenType Layout feature tags. Used to enable or disable the features of the given script/language system. + * See https://www.microsoft.com/typography/otspec/featuretags.htm + */ + Font.prototype.defaultRenderOptions = { + kerning: true, + features: [ + /** + * these 4 features are required to render Arabic text properly + * and shouldn't be turned off when rendering arabic text. + */ + { script: 'arab', tags: ['init', 'medi', 'fina', 'rlig'] }, + { script: 'latn', tags: ['liga', 'rlig'] } + ] + }; + + /** + * Helper function that invokes the given callback for each glyph in the given text. + * The callback gets `(glyph, x, y, fontSize, options)`.* @param {string} text + * @param {string} text - The text to apply. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @param {Function} callback + */ + Font.prototype.forEachGlyph = function(text, x, y, fontSize, options, callback) { + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 72; + options = Object.assign({}, this.defaultRenderOptions, options); + var fontScale = 1 / this.unitsPerEm * fontSize; + var glyphs = this.stringToGlyphs(text, options); + var kerningLookups; + if (options.kerning) { + var script = options.script || this.position.getDefaultScriptName(); + kerningLookups = this.position.getKerningTables(script, options.language); + } + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs[i]; + callback.call(this, glyph, x, y, fontSize, options); + if (glyph.advanceWidth) { + x += glyph.advanceWidth * fontScale; + } + + if (options.kerning && i < glyphs.length - 1) { + // We should apply position adjustment lookups in a more generic way. + // Here we only use the xAdvance value. + var kerningValue = kerningLookups ? + this.position.getKerningValue(kerningLookups, glyph.index, glyphs[i + 1].index) : + this.getKerningValue(glyph, glyphs[i + 1]); + x += kerningValue * fontScale; + } + + if (options.letterSpacing) { + x += options.letterSpacing * fontSize; + } else if (options.tracking) { + x += (options.tracking / 1000) * fontSize; + } + } + return x; + }; + + /** + * Create a Path object that represents the given text. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @return {opentype.Path} + */ + Font.prototype.getPath = function(text, x, y, fontSize, options) { + var fullPath = new Path(); + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); + fullPath.extend(glyphPath); + }); + return fullPath; + }; + + /** + * Create an array of Path objects that represent the glyphs of a given text. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @return {opentype.Path[]} + */ + Font.prototype.getPaths = function(text, x, y, fontSize, options) { + var glyphPaths = []; + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); + glyphPaths.push(glyphPath); + }); + + return glyphPaths; + }; + + /** + * Returns the advance width of a text. + * + * This is something different than Path.getBoundingBox() as for example a + * suffixed whitespace increases the advanceWidth but not the bounding box + * or an overhanging letter like a calligraphic 'f' might have a quite larger + * bounding box than its advance width. + * + * This corresponds to canvas2dContext.measureText(text).width + * + * @param {string} text - The text to create. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @return advance width + */ + Font.prototype.getAdvanceWidth = function(text, fontSize, options) { + return this.forEachGlyph(text, 0, 0, fontSize, options, function() {}); + }; + + /** + * Draw the text on the given drawing context. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + */ + Font.prototype.draw = function(ctx, text, x, y, fontSize, options) { + this.getPath(text, x, y, fontSize, options).draw(ctx); + }; + + /** + * Draw the points of all glyphs in the text. + * On-curve points will be drawn in blue, off-curve points will be drawn in red. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + */ + Font.prototype.drawPoints = function(ctx, text, x, y, fontSize, options) { + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + glyph.drawPoints(ctx, gX, gY, gFontSize); + }); + }; + + /** + * Draw lines indicating important font measurements for all glyphs in the text. + * Black lines indicate the origin of the coordinate system (point 0,0). + * Blue lines indicate the glyph bounding box. + * Green line indicates the advance width of the glyph. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + */ + Font.prototype.drawMetrics = function(ctx, text, x, y, fontSize, options) { + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + glyph.drawMetrics(ctx, gX, gY, gFontSize); + }); + }; + + /** + * @param {string} + * @return {string} + */ + Font.prototype.getEnglishName = function(name) { + var translations = this.names[name]; + if (translations) { + return translations.en; + } + }; + + /** + * Validate + */ + Font.prototype.validate = function() { + var _this = this; + + function assert(predicate, message) { + } + + function assertNamePresent(name) { + var englishName = _this.getEnglishName(name); + assert(englishName && englishName.trim().length > 0); + } + + // Identification information + assertNamePresent('fontFamily'); + assertNamePresent('weightName'); + assertNamePresent('manufacturer'); + assertNamePresent('copyright'); + assertNamePresent('version'); + + // Dimension information + assert(this.unitsPerEm > 0); + }; + + /** + * Convert the font object to a SFNT data structure. + * This structure contains all the necessary tables and metadata to create a binary OTF file. + * @return {opentype.Table} + */ + Font.prototype.toTables = function() { + return sfnt.fontToTable(this); + }; + /** + * @deprecated Font.toBuffer is deprecated. Use Font.toArrayBuffer instead. + */ + Font.prototype.toBuffer = function() { + console.warn('Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.'); + return this.toArrayBuffer(); + }; + /** + * Converts a `opentype.Font` into an `ArrayBuffer` + * @return {ArrayBuffer} + */ + Font.prototype.toArrayBuffer = function() { + var sfntTable = this.toTables(); + var bytes = sfntTable.encode(); + var buffer = new ArrayBuffer(bytes.length); + var intArray = new Uint8Array(buffer); + for (var i = 0; i < bytes.length; i++) { + intArray[i] = bytes[i]; + } + + return buffer; + }; + + /** + * Initiate a download of the OpenType font. + */ + Font.prototype.download = function(fileName) { + var familyName = this.getEnglishName('fontFamily'); + var styleName = this.getEnglishName('fontSubfamily'); + fileName = fileName || familyName.replace(/\s/g, '') + '-' + styleName + '.otf'; + var arrayBuffer = this.toArrayBuffer(); + + if (isBrowser()) { + window.URL = window.URL || window.webkitURL; + + if (window.URL) { + var dataView = new DataView(arrayBuffer); + var blob = new Blob([dataView], {type: 'font/opentype'}); + + var link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = fileName; + + var event = document.createEvent('MouseEvents'); + event.initEvent('click', true, false); + link.dispatchEvent(event); + } else { + console.warn('Font file could not be downloaded. Try using a different browser.'); + } + } else { + var fs = require('fs'); + var buffer = arrayBufferToNodeBuffer(arrayBuffer); + fs.writeFileSync(fileName, buffer); + } + }; + /** + * @private + */ + Font.prototype.fsSelectionValues = { + ITALIC: 0x001, //1 + UNDERSCORE: 0x002, //2 + NEGATIVE: 0x004, //4 + OUTLINED: 0x008, //8 + STRIKEOUT: 0x010, //16 + BOLD: 0x020, //32 + REGULAR: 0x040, //64 + USER_TYPO_METRICS: 0x080, //128 + WWS: 0x100, //256 + OBLIQUE: 0x200 //512 + }; + + /** + * @private + */ + Font.prototype.usWidthClasses = { + ULTRA_CONDENSED: 1, + EXTRA_CONDENSED: 2, + CONDENSED: 3, + SEMI_CONDENSED: 4, + MEDIUM: 5, + SEMI_EXPANDED: 6, + EXPANDED: 7, + EXTRA_EXPANDED: 8, + ULTRA_EXPANDED: 9 + }; + + /** + * @private + */ + Font.prototype.usWeightClasses = { + THIN: 100, + EXTRA_LIGHT: 200, + LIGHT: 300, + NORMAL: 400, + MEDIUM: 500, + SEMI_BOLD: 600, + BOLD: 700, + EXTRA_BOLD: 800, + BLACK: 900 + }; + + // The `fvar` table stores font variation axes and instances. + + function addName(name, names) { + var nameString = JSON.stringify(name); + var nameID = 256; + for (var nameKey in names) { + var n = parseInt(nameKey); + if (!n || n < 256) { + continue; + } + + if (JSON.stringify(names[nameKey]) === nameString) { + return n; + } + + if (nameID <= n) { + nameID = n + 1; + } + } + + names[nameID] = name; + return nameID; + } + + function makeFvarAxis(n, axis, names) { + var nameID = addName(axis.name, names); + return [ + {name: 'tag_' + n, type: 'TAG', value: axis.tag}, + {name: 'minValue_' + n, type: 'FIXED', value: axis.minValue << 16}, + {name: 'defaultValue_' + n, type: 'FIXED', value: axis.defaultValue << 16}, + {name: 'maxValue_' + n, type: 'FIXED', value: axis.maxValue << 16}, + {name: 'flags_' + n, type: 'USHORT', value: 0}, + {name: 'nameID_' + n, type: 'USHORT', value: nameID} + ]; + } + + function parseFvarAxis(data, start, names) { + var axis = {}; + var p = new parse.Parser(data, start); + axis.tag = p.parseTag(); + axis.minValue = p.parseFixed(); + axis.defaultValue = p.parseFixed(); + axis.maxValue = p.parseFixed(); + p.skip('uShort', 1); // reserved for flags; no values defined + axis.name = names[p.parseUShort()] || {}; + return axis; + } + + function makeFvarInstance(n, inst, axes, names) { + var nameID = addName(inst.name, names); + var fields = [ + {name: 'nameID_' + n, type: 'USHORT', value: nameID}, + {name: 'flags_' + n, type: 'USHORT', value: 0} + ]; + + for (var i = 0; i < axes.length; ++i) { + var axisTag = axes[i].tag; + fields.push({ + name: 'axis_' + n + ' ' + axisTag, + type: 'FIXED', + value: inst.coordinates[axisTag] << 16 + }); + } + + return fields; + } + + function parseFvarInstance(data, start, axes, names) { + var inst = {}; + var p = new parse.Parser(data, start); + inst.name = names[p.parseUShort()] || {}; + p.skip('uShort', 1); // reserved for flags; no values defined + + inst.coordinates = {}; + for (var i = 0; i < axes.length; ++i) { + inst.coordinates[axes[i].tag] = p.parseFixed(); + } + + return inst; + } + + function makeFvarTable(fvar, names) { + var result = new table.Table('fvar', [ + {name: 'version', type: 'ULONG', value: 0x10000}, + {name: 'offsetToData', type: 'USHORT', value: 0}, + {name: 'countSizePairs', type: 'USHORT', value: 2}, + {name: 'axisCount', type: 'USHORT', value: fvar.axes.length}, + {name: 'axisSize', type: 'USHORT', value: 20}, + {name: 'instanceCount', type: 'USHORT', value: fvar.instances.length}, + {name: 'instanceSize', type: 'USHORT', value: 4 + fvar.axes.length * 4} + ]); + result.offsetToData = result.sizeOf(); + + for (var i = 0; i < fvar.axes.length; i++) { + result.fields = result.fields.concat(makeFvarAxis(i, fvar.axes[i], names)); + } + + for (var j = 0; j < fvar.instances.length; j++) { + result.fields = result.fields.concat(makeFvarInstance(j, fvar.instances[j], fvar.axes, names)); + } + + return result; + } + + function parseFvarTable(data, start, names) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 0x00010000, 'Unsupported fvar table version.'); + var offsetToData = p.parseOffset16(); + // Skip countSizePairs. + p.skip('uShort', 1); + var axisCount = p.parseUShort(); + var axisSize = p.parseUShort(); + var instanceCount = p.parseUShort(); + var instanceSize = p.parseUShort(); + + var axes = []; + for (var i = 0; i < axisCount; i++) { + axes.push(parseFvarAxis(data, start + offsetToData + i * axisSize, names)); + } + + var instances = []; + var instanceStart = start + offsetToData + axisCount * axisSize; + for (var j = 0; j < instanceCount; j++) { + instances.push(parseFvarInstance(data, instanceStart + j * instanceSize, axes, names)); + } + + return {axes: axes, instances: instances}; + } + + var fvar = { make: makeFvarTable, parse: parseFvarTable }; + + // The `GPOS` table contains kerning pairs, among other things. + + var subtableParsers$1 = new Array(10); // subtableParsers[0] is unused + + // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-1-single-adjustment-positioning-subtable + // this = Parser instance + subtableParsers$1[1] = function parseLookup1() { + var start = this.offset + this.relativeOffset; + var posformat = this.parseUShort(); + if (posformat === 1) { + return { + posFormat: 1, + coverage: this.parsePointer(Parser.coverage), + value: this.parseValueRecord() + }; + } else if (posformat === 2) { + return { + posFormat: 2, + coverage: this.parsePointer(Parser.coverage), + values: this.parseValueRecordList() + }; + } + check.assert(false, '0x' + start.toString(16) + ': GPOS lookup type 1 format must be 1 or 2.'); + }; + + // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-2-pair-adjustment-positioning-subtable + subtableParsers$1[2] = function parseLookup2() { + var start = this.offset + this.relativeOffset; + var posFormat = this.parseUShort(); + check.assert(posFormat === 1 || posFormat === 2, '0x' + start.toString(16) + ': GPOS lookup type 2 format must be 1 or 2.'); + var coverage = this.parsePointer(Parser.coverage); + var valueFormat1 = this.parseUShort(); + var valueFormat2 = this.parseUShort(); + if (posFormat === 1) { + // Adjustments for Glyph Pairs + return { + posFormat: posFormat, + coverage: coverage, + valueFormat1: valueFormat1, + valueFormat2: valueFormat2, + pairSets: this.parseList(Parser.pointer(Parser.list(function() { + return { // pairValueRecord + secondGlyph: this.parseUShort(), + value1: this.parseValueRecord(valueFormat1), + value2: this.parseValueRecord(valueFormat2) + }; + }))) + }; + } else if (posFormat === 2) { + var classDef1 = this.parsePointer(Parser.classDef); + var classDef2 = this.parsePointer(Parser.classDef); + var class1Count = this.parseUShort(); + var class2Count = this.parseUShort(); + return { + // Class Pair Adjustment + posFormat: posFormat, + coverage: coverage, + valueFormat1: valueFormat1, + valueFormat2: valueFormat2, + classDef1: classDef1, + classDef2: classDef2, + class1Count: class1Count, + class2Count: class2Count, + classRecords: this.parseList(class1Count, Parser.list(class2Count, function() { + return { + value1: this.parseValueRecord(valueFormat1), + value2: this.parseValueRecord(valueFormat2) + }; + })) + }; + } + }; + + subtableParsers$1[3] = function parseLookup3() { return { error: 'GPOS Lookup 3 not supported' }; }; + subtableParsers$1[4] = function parseLookup4() { return { error: 'GPOS Lookup 4 not supported' }; }; + subtableParsers$1[5] = function parseLookup5() { return { error: 'GPOS Lookup 5 not supported' }; }; + subtableParsers$1[6] = function parseLookup6() { return { error: 'GPOS Lookup 6 not supported' }; }; + subtableParsers$1[7] = function parseLookup7() { return { error: 'GPOS Lookup 7 not supported' }; }; + subtableParsers$1[8] = function parseLookup8() { return { error: 'GPOS Lookup 8 not supported' }; }; + subtableParsers$1[9] = function parseLookup9() { return { error: 'GPOS Lookup 9 not supported' }; }; + + // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos + function parseGposTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GPOS table version ' + tableVersion); + + if (tableVersion === 1) { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers$1) + }; + } else { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers$1), + variations: p.parseFeatureVariationsList() + }; + } + + } + + // GPOS Writing ////////////////////////////////////////////// + // NOT SUPPORTED + var subtableMakers$1 = new Array(10); + + function makeGposTable(gpos) { + return new table.Table('GPOS', [ + {name: 'version', type: 'ULONG', value: 0x10000}, + {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gpos.scripts)}, + {name: 'features', type: 'TABLE', value: new table.FeatureList(gpos.features)}, + {name: 'lookups', type: 'TABLE', value: new table.LookupList(gpos.lookups, subtableMakers$1)} + ]); + } + + var gpos = { parse: parseGposTable, make: makeGposTable }; + + // The `kern` table contains kerning pairs. + + function parseWindowsKernTable(p) { + var pairs = {}; + // Skip nTables. + p.skip('uShort'); + var subtableVersion = p.parseUShort(); + check.argument(subtableVersion === 0, 'Unsupported kern sub-table version.'); + // Skip subtableLength, subtableCoverage + p.skip('uShort', 2); + var nPairs = p.parseUShort(); + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + for (var i = 0; i < nPairs; i += 1) { + var leftIndex = p.parseUShort(); + var rightIndex = p.parseUShort(); + var value = p.parseShort(); + pairs[leftIndex + ',' + rightIndex] = value; + } + return pairs; + } + + function parseMacKernTable(p) { + var pairs = {}; + // The Mac kern table stores the version as a fixed (32 bits) but we only loaded the first 16 bits. + // Skip the rest. + p.skip('uShort'); + var nTables = p.parseULong(); + //check.argument(nTables === 1, 'Only 1 subtable is supported (got ' + nTables + ').'); + if (nTables > 1) { + console.warn('Only the first kern subtable is supported.'); + } + p.skip('uLong'); + var coverage = p.parseUShort(); + var subtableVersion = coverage & 0xFF; + p.skip('uShort'); + if (subtableVersion === 0) { + var nPairs = p.parseUShort(); + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + for (var i = 0; i < nPairs; i += 1) { + var leftIndex = p.parseUShort(); + var rightIndex = p.parseUShort(); + var value = p.parseShort(); + pairs[leftIndex + ',' + rightIndex] = value; + } + } + return pairs; + } + + // Parse the `kern` table which contains kerning pairs. + function parseKernTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseUShort(); + if (tableVersion === 0) { + return parseWindowsKernTable(p); + } else if (tableVersion === 1) { + return parseMacKernTable(p); + } else { + throw new Error('Unsupported kern table version (' + tableVersion + ').'); + } + } + + var kern = { parse: parseKernTable }; + + // The `loca` table stores the offsets to the locations of the glyphs in the font. + + // Parse the `loca` table. This table stores the offsets to the locations of the glyphs in the font, + // relative to the beginning of the glyphData table. + // The number of glyphs stored in the `loca` table is specified in the `maxp` table (under numGlyphs) + // The loca table has two versions: a short version where offsets are stored as uShorts, and a long + // version where offsets are stored as uLongs. The `head` table specifies which version to use + // (under indexToLocFormat). + function parseLocaTable(data, start, numGlyphs, shortVersion) { + var p = new parse.Parser(data, start); + var parseFn = shortVersion ? p.parseUShort : p.parseULong; + // There is an extra entry after the last index element to compute the length of the last glyph. + // That's why we use numGlyphs + 1. + var glyphOffsets = []; + for (var i = 0; i < numGlyphs + 1; i += 1) { + var glyphOffset = parseFn.call(p); + if (shortVersion) { + // The short table version stores the actual offset divided by 2. + glyphOffset *= 2; + } + + glyphOffsets.push(glyphOffset); + } + + return glyphOffsets; + } + + var loca = { parse: parseLocaTable }; + + // opentype.js + + /** + * The opentype library. + * @namespace opentype + */ + + // File loaders ///////////////////////////////////////////////////////// + /** + * Loads a font from a file. The callback throws an error message as the first parameter if it fails + * and the font as an ArrayBuffer in the second parameter if it succeeds. + * @param {string} path - The path of the file + * @param {Function} callback - The function to call when the font load completes + */ + function loadFromFile(path, callback) { + var fs = require('fs'); + fs.readFile(path, function(err, buffer) { + if (err) { + return callback(err.message); + } + + callback(null, nodeBufferToArrayBuffer(buffer)); + }); + } + /** + * Loads a font from a URL. The callback throws an error message as the first parameter if it fails + * and the font as an ArrayBuffer in the second parameter if it succeeds. + * @param {string} url - The URL of the font file. + * @param {Function} callback - The function to call when the font load completes + */ + function loadFromUrl(url, callback) { + var request = new XMLHttpRequest(); + request.open('get', url, true); + request.responseType = 'arraybuffer'; + request.onload = function() { + if (request.response) { + return callback(null, request.response); + } else { + return callback('Font could not be loaded: ' + request.statusText); + } + }; + + request.onerror = function () { + callback('Font could not be loaded'); + }; + + request.send(); + } + + // Table Directory Entries ////////////////////////////////////////////// + /** + * Parses OpenType table entries. + * @param {DataView} + * @param {Number} + * @return {Object[]} + */ + function parseOpenTypeTableEntries(data, numTables) { + var tableEntries = []; + var p = 12; + for (var i = 0; i < numTables; i += 1) { + var tag = parse.getTag(data, p); + var checksum = parse.getULong(data, p + 4); + var offset = parse.getULong(data, p + 8); + var length = parse.getULong(data, p + 12); + tableEntries.push({tag: tag, checksum: checksum, offset: offset, length: length, compression: false}); + p += 16; + } + + return tableEntries; + } + + /** + * Parses WOFF table entries. + * @param {DataView} + * @param {Number} + * @return {Object[]} + */ + function parseWOFFTableEntries(data, numTables) { + var tableEntries = []; + var p = 44; // offset to the first table directory entry. + for (var i = 0; i < numTables; i += 1) { + var tag = parse.getTag(data, p); + var offset = parse.getULong(data, p + 4); + var compLength = parse.getULong(data, p + 8); + var origLength = parse.getULong(data, p + 12); + var compression = (void 0); + if (compLength < origLength) { + compression = 'WOFF'; + } else { + compression = false; + } + + tableEntries.push({tag: tag, offset: offset, compression: compression, + compressedLength: compLength, length: origLength}); + p += 20; + } + + return tableEntries; + } + + /** + * @typedef TableData + * @type Object + * @property {DataView} data - The DataView + * @property {number} offset - The data offset. + */ + + /** + * @param {DataView} + * @param {Object} + * @return {TableData} + */ + function uncompressTable(data, tableEntry) { + if (tableEntry.compression === 'WOFF') { + var inBuffer = new Uint8Array(data.buffer, tableEntry.offset + 2, tableEntry.compressedLength - 2); + var outBuffer = new Uint8Array(tableEntry.length); + tinyInflate(inBuffer, outBuffer); + if (outBuffer.byteLength !== tableEntry.length) { + throw new Error('Decompression error: ' + tableEntry.tag + ' decompressed length doesn\'t match recorded length'); + } + + var view = new DataView(outBuffer.buffer, 0); + return {data: view, offset: 0}; + } else { + return {data: data, offset: tableEntry.offset}; + } + } + + // Public API /////////////////////////////////////////////////////////// + + /** + * Parse the OpenType file data (as an ArrayBuffer) and return a Font object. + * Throws an error if the font could not be parsed. + * @param {ArrayBuffer} + * @param {Object} opt - options for parsing + * @return {opentype.Font} + */ + function parseBuffer(buffer, opt) { + opt = (opt === undefined || opt === null) ? {} : opt; + + var indexToLocFormat; + var ltagTable; + + // Since the constructor can also be called to create new fonts from scratch, we indicate this + // should be an empty font that we'll fill with our own data. + var font = new Font({empty: true}); + + // OpenType fonts use big endian byte ordering. + // We can't rely on typed array view types, because they operate with the endianness of the host computer. + // Instead we use DataViews where we can specify endianness. + var data = new DataView(buffer, 0); + var numTables; + var tableEntries = []; + var signature = parse.getTag(data, 0); + if (signature === String.fromCharCode(0, 1, 0, 0) || signature === 'true' || signature === 'typ1') { + font.outlinesFormat = 'truetype'; + numTables = parse.getUShort(data, 4); + tableEntries = parseOpenTypeTableEntries(data, numTables); + } else if (signature === 'OTTO') { + font.outlinesFormat = 'cff'; + numTables = parse.getUShort(data, 4); + tableEntries = parseOpenTypeTableEntries(data, numTables); + } else if (signature === 'wOFF') { + var flavor = parse.getTag(data, 4); + if (flavor === String.fromCharCode(0, 1, 0, 0)) { + font.outlinesFormat = 'truetype'; + } else if (flavor === 'OTTO') { + font.outlinesFormat = 'cff'; + } else { + throw new Error('Unsupported OpenType flavor ' + signature); + } + + numTables = parse.getUShort(data, 12); + tableEntries = parseWOFFTableEntries(data, numTables); + } else { + throw new Error('Unsupported OpenType signature ' + signature); + } + + var cffTableEntry; + var fvarTableEntry; + var glyfTableEntry; + var gposTableEntry; + var gsubTableEntry; + var hmtxTableEntry; + var kernTableEntry; + var locaTableEntry; + var nameTableEntry; + var metaTableEntry; + var p; + + for (var i = 0; i < numTables; i += 1) { + var tableEntry = tableEntries[i]; + var table = (void 0); + switch (tableEntry.tag) { + case 'cmap': + table = uncompressTable(data, tableEntry); + font.tables.cmap = cmap.parse(table.data, table.offset); + font.encoding = new CmapEncoding(font.tables.cmap); + break; + case 'cvt ' : + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.cvt = p.parseShortList(tableEntry.length / 2); + break; + case 'fvar': + fvarTableEntry = tableEntry; + break; + case 'fpgm' : + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.fpgm = p.parseByteList(tableEntry.length); + break; + case 'head': + table = uncompressTable(data, tableEntry); + font.tables.head = head.parse(table.data, table.offset); + font.unitsPerEm = font.tables.head.unitsPerEm; + indexToLocFormat = font.tables.head.indexToLocFormat; + break; + case 'hhea': + table = uncompressTable(data, tableEntry); + font.tables.hhea = hhea.parse(table.data, table.offset); + font.ascender = font.tables.hhea.ascender; + font.descender = font.tables.hhea.descender; + font.numberOfHMetrics = font.tables.hhea.numberOfHMetrics; + break; + case 'hmtx': + hmtxTableEntry = tableEntry; + break; + case 'ltag': + table = uncompressTable(data, tableEntry); + ltagTable = ltag.parse(table.data, table.offset); + break; + case 'maxp': + table = uncompressTable(data, tableEntry); + font.tables.maxp = maxp.parse(table.data, table.offset); + font.numGlyphs = font.tables.maxp.numGlyphs; + break; + case 'name': + nameTableEntry = tableEntry; + break; + case 'OS/2': + table = uncompressTable(data, tableEntry); + font.tables.os2 = os2.parse(table.data, table.offset); + break; + case 'post': + table = uncompressTable(data, tableEntry); + font.tables.post = post.parse(table.data, table.offset); + font.glyphNames = new GlyphNames(font.tables.post); + break; + case 'prep' : + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.prep = p.parseByteList(tableEntry.length); + break; + case 'glyf': + glyfTableEntry = tableEntry; + break; + case 'loca': + locaTableEntry = tableEntry; + break; + case 'CFF ': + cffTableEntry = tableEntry; + break; + case 'kern': + kernTableEntry = tableEntry; + break; + case 'GPOS': + gposTableEntry = tableEntry; + break; + case 'GSUB': + gsubTableEntry = tableEntry; + break; + case 'meta': + metaTableEntry = tableEntry; + break; + } + } + + var nameTable = uncompressTable(data, nameTableEntry); + font.tables.name = _name.parse(nameTable.data, nameTable.offset, ltagTable); + font.names = font.tables.name; + + if (glyfTableEntry && locaTableEntry) { + var shortVersion = indexToLocFormat === 0; + var locaTable = uncompressTable(data, locaTableEntry); + var locaOffsets = loca.parse(locaTable.data, locaTable.offset, font.numGlyphs, shortVersion); + var glyfTable = uncompressTable(data, glyfTableEntry); + font.glyphs = glyf.parse(glyfTable.data, glyfTable.offset, locaOffsets, font, opt); + } else if (cffTableEntry) { + var cffTable = uncompressTable(data, cffTableEntry); + cff.parse(cffTable.data, cffTable.offset, font, opt); + } else { + throw new Error('Font doesn\'t contain TrueType or CFF outlines.'); + } + + var hmtxTable = uncompressTable(data, hmtxTableEntry); + hmtx.parse(font, hmtxTable.data, hmtxTable.offset, font.numberOfHMetrics, font.numGlyphs, font.glyphs, opt); + addGlyphNames(font, opt); + + if (kernTableEntry) { + var kernTable = uncompressTable(data, kernTableEntry); + font.kerningPairs = kern.parse(kernTable.data, kernTable.offset); + } else { + font.kerningPairs = {}; + } + + if (gposTableEntry) { + var gposTable = uncompressTable(data, gposTableEntry); + font.tables.gpos = gpos.parse(gposTable.data, gposTable.offset); + font.position.init(); + } + + if (gsubTableEntry) { + var gsubTable = uncompressTable(data, gsubTableEntry); + font.tables.gsub = gsub.parse(gsubTable.data, gsubTable.offset); + } + + if (fvarTableEntry) { + var fvarTable = uncompressTable(data, fvarTableEntry); + font.tables.fvar = fvar.parse(fvarTable.data, fvarTable.offset, font.names); + } + + if (metaTableEntry) { + var metaTable = uncompressTable(data, metaTableEntry); + font.tables.meta = meta.parse(metaTable.data, metaTable.offset); + font.metas = font.tables.meta; + } + + return font; + } + + /** + * Asynchronously load the font from a URL or a filesystem. When done, call the callback + * with two arguments `(err, font)`. The `err` will be null on success, + * the `font` is a Font object. + * We use the node.js callback convention so that + * opentype.js can integrate with frameworks like async.js. + * @alias opentype.load + * @param {string} url - The URL of the font to load. + * @param {Function} callback - The callback. + */ + function load(url, callback, opt) { + var isNode = typeof window === 'undefined'; + var loadFn = isNode ? loadFromFile : loadFromUrl; + + return new Promise(function (resolve, reject) { + loadFn(url, function(err, arrayBuffer) { + if (err) { + if (callback) { + return callback(err); + } else { + reject(err); + } + } + var font; + try { + font = parseBuffer(arrayBuffer, opt); + } catch (e) { + if (callback) { + return callback(e, null); + } else { + reject(e); + } + } + if (callback) { + return callback(null, font); + } else { + resolve(font); + } + }); + }); + } + + /** + * Synchronously load the font from a URL or file. + * When done, returns the font object or throws an error. + * @alias opentype.loadSync + * @param {string} url - The URL of the font to load. + * @param {Object} opt - opt.lowMemory + * @return {opentype.Font} + */ + function loadSync(url, opt) { + var fs = require('fs'); + var buffer = fs.readFileSync(url); + return parseBuffer(nodeBufferToArrayBuffer(buffer), opt); + } + + var opentype = /*#__PURE__*/Object.freeze({ + __proto__: null, + Font: Font, + Glyph: Glyph, + Path: Path, + BoundingBox: BoundingBox, + _parse: parse, + parse: parseBuffer, + load: load, + loadSync: loadSync + }); + + exports.BoundingBox = BoundingBox; + exports.Font = Font; + exports.Glyph = Glyph; + exports.Path = Path; + exports._parse = parse; + exports.default = opentype; + exports.load = load; + exports.loadSync = loadSync; + exports.parse = parseBuffer; + + Object.defineProperty(exports, '__esModule', { value: true }); + +}))); +//# sourceMappingURL=opentype.js.map diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/package.json b/node_modules/lv_font_conv/node_modules/opentype.js/package.json new file mode 100644 index 00000000..8c75ccad --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/opentype.js/package.json @@ -0,0 +1,102 @@ +{ + "_args": [ + [ + "opentype.js@1.3.3", + "/home/vitaly/Dropbox/Coding/lv_font_conv" + ] + ], + "_from": "opentype.js@1.3.3", + "_id": "opentype.js@1.3.3", + "_inBundle": false, + "_integrity": "sha512-/qIY/+WnKGlPIIPhbeNjynfD2PO15G9lA/xqlX2bDH+4lc3Xz5GCQ68mqxj3DdUv6AJqCeaPvuAoH8mVL0zcuA==", + "_location": "/opentype.js", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "opentype.js@1.3.3", + "name": "opentype.js", + "escapedName": "opentype.js", + "rawSpec": "1.3.3", + "saveSpec": null, + "fetchSpec": "1.3.3" + }, + "_requiredBy": [ + "/" + ], + "_resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.3.tgz", + "_spec": "1.3.3", + "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", + "author": { + "name": "Frederik De Bleser", + "email": "frederik@debleser.be" + }, + "bin": { + "ot": "bin/ot" + }, + "browser": { + "fs": false + }, + "bugs": { + "url": "https://github.com/opentypejs/opentype.js/issues" + }, + "dependencies": { + "string.prototype.codepointat": "^0.2.1", + "tiny-inflate": "^1.0.3" + }, + "description": "OpenType font parser", + "devDependencies": { + "@babel/preset-env": "^7.9.5", + "buble": "^0.20.0", + "cross-env": "^7.0.2", + "jscs": "^3.0.7", + "jshint": "^2.11.0", + "mocha": "^7.1.1", + "reify": "^0.20.12", + "rollup": "^1.32.1", + "rollup-plugin-buble": "^0.19.8", + "rollup-plugin-commonjs": "^10.1.0", + "rollup-plugin-license": "^0.9.0", + "rollup-plugin-node-resolve": "^5.2.0", + "uglify-js": "^3.8.1" + }, + "engines": { + "node": ">= 8.0.0" + }, + "files": [ + "LICENSE", + "RELEASES.md", + "README.md", + "bin", + "dist", + "src" + ], + "homepage": "https://github.com/opentypejs/opentype.js#readme", + "keywords": [ + "graphics", + "fonts", + "font", + "opentype", + "otf", + "ttf", + "woff", + "type" + ], + "license": "MIT", + "main": "dist/opentype.js", + "module": "dist/opentype.module.js", + "name": "opentype.js", + "repository": { + "type": "git", + "url": "git://github.com/opentypejs/opentype.js.git" + }, + "scripts": { + "build": "rollup -c", + "dist": "npm run test && npm run build && npm run minify", + "minify": "uglifyjs --source-map \"url='opentype.min.js.map'\" --compress --mangle --output ./dist/opentype.min.js -- ./dist/opentype.js", + "start": "node ./bin/server.js", + "test": "mocha --require reify --recursive && jshint . && jscs .", + "watch": "rollup -c -w" + }, + "version": "1.3.3" +} diff --git a/node_modules/lv_font_conv/node_modules/pngjs/LICENSE b/node_modules/lv_font_conv/node_modules/pngjs/LICENSE new file mode 100644 index 00000000..6942e254 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/LICENSE @@ -0,0 +1,20 @@ +pngjs2 original work Copyright (c) 2015 Luke Page & Original Contributors +pngjs derived work Copyright (c) 2012 Kuba Niegowski + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/pngjs/README.md b/node_modules/lv_font_conv/node_modules/pngjs/README.md new file mode 100644 index 00000000..2aef03d9 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/README.md @@ -0,0 +1,433 @@ +[![Build Status](https://travis-ci.com/lukeapage/pngjs.svg?branch=master)](https://travis-ci.com/lukeapage/pngjs) [![Build status](https://ci.appveyor.com/api/projects/status/qo5x8ayutr028108/branch/master?svg=true)](https://ci.appveyor.com/project/lukeapage/pngjs/branch/master) [![codecov](https://codecov.io/gh/lukeapage/pngjs/branch/master/graph/badge.svg)](https://codecov.io/gh/lukeapage/pngjs) [![npm version](https://badge.fury.io/js/pngjs.svg)](http://badge.fury.io/js/pngjs) + +# pngjs + +Simple PNG encoder/decoder for Node.js with no dependencies. + +Based on the original [pngjs](https://github.com/niegowski/node-pngjs) with the follow enhancements. + +- Support for reading 1,2,4 & 16 bit files +- Support for reading interlace files +- Support for reading `tTRNS` transparent colours +- Support for writing colortype 0 (grayscale), colortype 2 (RGB), colortype 4 (grayscale alpha) and colortype 6 (RGBA) +- Sync interface as well as async +- API compatible with pngjs and node-pngjs + +Known lack of support for: + +- Extended PNG e.g. Animation +- Writing in colortype 3 (indexed color) + +# Table of Contents + +- [Requirements](#requirements) +- [Comparison Table](#comparison-table) +- [Tests](#tests) +- [Installation](#installation) +- [Browser](#browser) +- [Example](#example) +- [Async API](#async-api) +- [Sync API](#sync-api) +- [Changelog](#changelog) + +# Comparison Table + +| Name | Forked From | Sync | Async | 16 Bit | 1/2/4 Bit | Interlace | Gamma | Encodes | Tested | +| ------------- | ----------- | ---- | ----- | ------ | --------- | --------- | ------ | ------- | ------ | +| pngjs | | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | +| node-png | pngjs | No | Yes | No | No | No | Hidden | Yes | Manual | +| png-coder | pngjs | No | Yes | Yes | No | No | Hidden | Yes | Manual | +| pngparse | | No | Yes | No | Yes | No | No | No | Yes | +| pngparse-sync | pngparse | Yes | No | No | Yes | No | No | No | Yes | +| png-async | | No | Yes | No | No | No | No | Yes | Yes | +| png-js | | No | Yes | No | No | No | No | No | No | + +Native C++ node decoders: + +- png +- png-sync (sync version of above) +- pixel-png +- png-img + +# Tests + +Tested using [PNG Suite](http://www.schaik.com/pngsuite/). We read every file into pngjs, output it in standard 8bit colour, synchronously and asynchronously, then compare the original with the newly saved images. + +To run the tests, fetch the repo (tests are not distributed via npm) and install with `npm i`, run `npm test`. + +The only thing not converted is gamma correction - this is because multiple vendors will do gamma correction differently, so the tests will have different results on different browsers. + +# Installation + +``` +$ npm install pngjs --save +``` + +# Browser + +The package has been build with a [Browserify](browserify.org) version (`npm run browserify`) and you can use the browser version by including in your code: + +``` +import { PNG } from 'pngjs/browser'; +``` + +# Example + +```js +var fs = require("fs"), + PNG = require("pngjs").PNG; + +fs.createReadStream("in.png") + .pipe( + new PNG({ + filterType: 4, + }) + ) + .on("parsed", function () { + for (var y = 0; y < this.height; y++) { + for (var x = 0; x < this.width; x++) { + var idx = (this.width * y + x) << 2; + + // invert color + this.data[idx] = 255 - this.data[idx]; + this.data[idx + 1] = 255 - this.data[idx + 1]; + this.data[idx + 2] = 255 - this.data[idx + 2]; + + // and reduce opacity + this.data[idx + 3] = this.data[idx + 3] >> 1; + } + } + + this.pack().pipe(fs.createWriteStream("out.png")); + }); +``` + +For more examples see `examples` folder. + +# Async API + +As input any color type is accepted (grayscale, rgb, palette, grayscale with alpha, rgb with alpha) but 8 bit per sample (channel) is the only supported bit depth. Interlaced mode is not supported. + +## Class: PNG + +`PNG` is readable and writable `Stream`. + +### Options + +- `width` - use this with `height` if you want to create png from scratch +- `height` - as above +- `checkCRC` - whether parser should be strict about checksums in source stream (default: `true`) +- `deflateChunkSize` - chunk size used for deflating data chunks, this should be power of 2 and must not be less than 256 and more than 32\*1024 (default: 32 kB) +- `deflateLevel` - compression level for deflate (default: 9) +- `deflateStrategy` - compression strategy for deflate (default: 3) +- `deflateFactory` - deflate stream factory (default: `zlib.createDeflate`) +- `filterType` - png filtering method for scanlines (default: -1 => auto, accepts array of numbers 0-4) +- `colorType` - the output colorType - see constants. 0 = grayscale, no alpha, 2 = color, no alpha, 4 = grayscale & alpha, 6 = color & alpha. Default currently 6, but in the future may calculate best mode. +- `inputColorType` - the input colorType - see constants. Default is 6 (RGBA) +- `bitDepth` - the bitDepth of the output, 8 or 16 bits. Input data is expected to have this bit depth. + 16 bit data is expected in the system endianness (Default: 8) +- `inputHasAlpha` - whether the input bitmap has 4 bytes per pixel (rgb and alpha) or 3 (rgb - no alpha). +- `bgColor` - an object containing red, green, and blue values between 0 and 255 + that is used when packing a PNG if alpha is not to be included (default: 255,255,255) + +### Event "metadata" + +`function(metadata) { }` +Image's header has been parsed, metadata contains this information: + +- `width` image size in pixels +- `height` image size in pixels +- `palette` image is paletted +- `color` image is not grayscale +- `alpha` image contains alpha channel +- `interlace` image is interlaced + +### Event: "parsed" + +`function(data) { }` +Input image has been completely parsed, `data` is complete and ready for modification. + +### Event: "error" + +`function(error) { }` + +### png.parse(data, [callback]) + +Parses PNG file data. Can be `String` or `Buffer`. Alternatively you can stream data to instance of PNG. + +Optional `callback` is once called on `error` or `parsed`. The callback gets +two arguments `(err, data)`. + +Returns `this` for method chaining. + +#### Example + +```js +new PNG({ filterType: 4 }).parse(imageData, function (error, data) { + console.log(error, data); +}); +``` + +### png.pack() + +Starts converting data to PNG file Stream. + +Returns `this` for method chaining. + +### png.bitblt(dst, sx, sy, w, h, dx, dy) + +Helper for image manipulation, copies a rectangle of pixels from current (i.e. the source) image (`sx`, `sy`, `w`, `h`) to `dst` image (at `dx`, `dy`). + +Returns `this` for method chaining. + +For example, the following code copies the top-left 100x50 px of `in.png` into dst and writes it to `out.png`: + +```js +var dst = new PNG({ width: 100, height: 50 }); +fs.createReadStream("in.png") + .pipe(new PNG()) + .on("parsed", function () { + this.bitblt(dst, 0, 0, 100, 50, 0, 0); + dst.pack().pipe(fs.createWriteStream("out.png")); + }); +``` + +### Property: adjustGamma() + +Helper that takes data and adjusts it to be gamma corrected. Note that it is not 100% reliable with transparent colours because that requires knowing the background colour the bitmap is rendered on to. + +In tests against PNG suite it compared 100% with chrome on all 8 bit and below images. On IE there were some differences. + +The following example reads a file, adjusts the gamma (which sets the gamma to 0) and writes it out again, effectively removing any gamma correction from the image. + +```js +fs.createReadStream("in.png") + .pipe(new PNG()) + .on("parsed", function () { + this.adjustGamma(); + this.pack().pipe(fs.createWriteStream("out.png")); + }); +``` + +### Property: width + +Width of image in pixels + +### Property: height + +Height of image in pixels + +### Property: data + +Buffer of image pixel data. Every pixel consists 4 bytes: R, G, B, A (opacity). + +### Property: gamma + +Gamma of image (0 if not specified) + +## Packing a PNG and removing alpha (RGBA to RGB) + +When removing the alpha channel from an image, there needs to be a background color to correctly +convert each pixel's transparency to the appropriate RGB value. By default, pngjs will flatten +the image against a white background. You can override this in the options: + +```js +var fs = require("fs"), + PNG = require("pngjs").PNG; + +fs.createReadStream("in.png") + .pipe( + new PNG({ + colorType: 2, + bgColor: { + red: 0, + green: 255, + blue: 0, + }, + }) + ) + .on("parsed", function () { + this.pack().pipe(fs.createWriteStream("out.png")); + }); +``` + +# Sync API + +## PNG.sync + +### PNG.sync.read(buffer) + +Take a buffer and returns a PNG image. The properties on the image include the meta data and `data` as per the async API above. + +``` +var data = fs.readFileSync('in.png'); +var png = PNG.sync.read(data); +``` + +### PNG.sync.write(png) + +Take a PNG image and returns a buffer. The properties on the image include the meta data and `data` as per the async API above. + +``` +var data = fs.readFileSync('in.png'); +var png = PNG.sync.read(data); +var options = { colorType: 6 }; +var buffer = PNG.sync.write(png, options); +fs.writeFileSync('out.png', buffer); +``` + +### PNG.adjustGamma(src) + +Adjusts the gamma of a sync image. See the async adjustGamma. + +``` +var data = fs.readFileSync('in.png'); +var png = PNG.sync.read(data); +PNG.adjustGamma(png); +``` + +# Changelog + +### 6.0.0 - 24/10/2020 + +- BREAKING - Sync version now throws if there is unexpected content at the end of the stream. +- BREAKING - Drop support for node 10 (Though nothing incompatible in this release yet) +- Reduce the number of files included in the package + +### 5.1.0 - 13/09/2020 + +- Add option to skip rescaling + +### 5.0.0 - 15/04/2020 + +- Drop support for Node 8 +- Browserified bundle may now contain ES20(15-20) code if the supported node version supports it. Please run the browserified version through babel if you need to support older browsers. + +### 4.0.1 - 15/04/2020 + +- Fix to possible null reference in nextTick of async method + +### 4.0.0 - 09/04/2020 + +- Fix issue in newer nodes with using Buffer +- Fix async issue with some png files +- Drop support for Node 4 & 6 + +### 3.4.0 - 09/03/2019 + +- Include whether the png has alpha in the meta data +- emit an error if the image is truncated instead of hanging +- Add a browserified version +- speed up some mapping functions + +### 3.3.3 - 19/04/2018 + +- Real fix for node 9 + +### 3.3.2 - 16/02/2018 + +- Fix for node 9 + +### 3.3.1 - 15/11/2017 + +- Bugfixes and removal of es6 + +### 3.3.0 + +- Add writing 16 bit channels and support for grayscale input + +### 3.2.0 - 30/04/2017 + +- Support for encoding 8-bit grayscale images + +### 3.1.0 - 30/04/2017 + +- Support for pngs with zlib chunks that are malformed after valid data + +### 3.0.1 - 16/02/2017 + +- Fix single pixel pngs + +### 3.0.0 - 03/08/2016 + +- Drop support for node below v4 and iojs. Pin to 2.3.0 to use with old, unsupported or patched node versions. + +### 2.3.0 - 22/04/2016 + +- Support for sync in node 0.10 + +### 2.2.0 - 04/12/2015 + +- Add sync write api +- Fix newfile example +- Correct comparison table + +### 2.1.0 - 28/10/2015 + +- rename package to pngjs +- added 'bgColor' option + +### 2.0.0 - 08/10/2015 + +- fixes to readme +- _breaking change_ - bitblt on the png prototype now doesn't take a unused, unnecessary src first argument + +### 1.2.0 - 13/09/2015 + +- support passing colorType to write PNG's and writing bitmaps without alpha information + +### 1.1.0 - 07/09/2015 + +- support passing a deflate factory for controlled compression + +### 1.0.2 - 22/08/2015 + +- Expose all PNG creation info + +### 1.0.1 - 21/08/2015 + +- Fix non square interlaced files + +### 1.0.0 - 08/08/2015 + +- More tests +- source linted +- maintainability refactorings +- async API - exceptions in reading now emit warnings +- documentation improvement - sync api now documented, adjustGamma documented +- breaking change - gamma chunk is now written. previously a read then write would destroy gamma information, now it is persisted. + +### 0.0.3 - 03/08/2015 + +- Error handling fixes +- ignore files for smaller npm footprint + +### 0.0.2 - 02/08/2015 + +- Bugfixes to interlacing, support for transparent colours + +### 0.0.1 - 02/08/2015 + +- Initial release, see pngjs for older changelog. + +# License + +(The MIT License) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/bitmapper.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/bitmapper.js new file mode 100644 index 00000000..18378a02 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/bitmapper.js @@ -0,0 +1,267 @@ +"use strict"; + +let interlaceUtils = require("./interlace"); + +let pixelBppMapper = [ + // 0 - dummy entry + function () {}, + + // 1 - L + // 0: 0, 1: 0, 2: 0, 3: 0xff + function (pxData, data, pxPos, rawPos) { + if (rawPos === data.length) { + throw new Error("Ran out of data"); + } + + let pixel = data[rawPos]; + pxData[pxPos] = pixel; + pxData[pxPos + 1] = pixel; + pxData[pxPos + 2] = pixel; + pxData[pxPos + 3] = 0xff; + }, + + // 2 - LA + // 0: 0, 1: 0, 2: 0, 3: 1 + function (pxData, data, pxPos, rawPos) { + if (rawPos + 1 >= data.length) { + throw new Error("Ran out of data"); + } + + let pixel = data[rawPos]; + pxData[pxPos] = pixel; + pxData[pxPos + 1] = pixel; + pxData[pxPos + 2] = pixel; + pxData[pxPos + 3] = data[rawPos + 1]; + }, + + // 3 - RGB + // 0: 0, 1: 1, 2: 2, 3: 0xff + function (pxData, data, pxPos, rawPos) { + if (rawPos + 2 >= data.length) { + throw new Error("Ran out of data"); + } + + pxData[pxPos] = data[rawPos]; + pxData[pxPos + 1] = data[rawPos + 1]; + pxData[pxPos + 2] = data[rawPos + 2]; + pxData[pxPos + 3] = 0xff; + }, + + // 4 - RGBA + // 0: 0, 1: 1, 2: 2, 3: 3 + function (pxData, data, pxPos, rawPos) { + if (rawPos + 3 >= data.length) { + throw new Error("Ran out of data"); + } + + pxData[pxPos] = data[rawPos]; + pxData[pxPos + 1] = data[rawPos + 1]; + pxData[pxPos + 2] = data[rawPos + 2]; + pxData[pxPos + 3] = data[rawPos + 3]; + }, +]; + +let pixelBppCustomMapper = [ + // 0 - dummy entry + function () {}, + + // 1 - L + // 0: 0, 1: 0, 2: 0, 3: 0xff + function (pxData, pixelData, pxPos, maxBit) { + let pixel = pixelData[0]; + pxData[pxPos] = pixel; + pxData[pxPos + 1] = pixel; + pxData[pxPos + 2] = pixel; + pxData[pxPos + 3] = maxBit; + }, + + // 2 - LA + // 0: 0, 1: 0, 2: 0, 3: 1 + function (pxData, pixelData, pxPos) { + let pixel = pixelData[0]; + pxData[pxPos] = pixel; + pxData[pxPos + 1] = pixel; + pxData[pxPos + 2] = pixel; + pxData[pxPos + 3] = pixelData[1]; + }, + + // 3 - RGB + // 0: 0, 1: 1, 2: 2, 3: 0xff + function (pxData, pixelData, pxPos, maxBit) { + pxData[pxPos] = pixelData[0]; + pxData[pxPos + 1] = pixelData[1]; + pxData[pxPos + 2] = pixelData[2]; + pxData[pxPos + 3] = maxBit; + }, + + // 4 - RGBA + // 0: 0, 1: 1, 2: 2, 3: 3 + function (pxData, pixelData, pxPos) { + pxData[pxPos] = pixelData[0]; + pxData[pxPos + 1] = pixelData[1]; + pxData[pxPos + 2] = pixelData[2]; + pxData[pxPos + 3] = pixelData[3]; + }, +]; + +function bitRetriever(data, depth) { + let leftOver = []; + let i = 0; + + function split() { + if (i === data.length) { + throw new Error("Ran out of data"); + } + let byte = data[i]; + i++; + let byte8, byte7, byte6, byte5, byte4, byte3, byte2, byte1; + switch (depth) { + default: + throw new Error("unrecognised depth"); + case 16: + byte2 = data[i]; + i++; + leftOver.push((byte << 8) + byte2); + break; + case 4: + byte2 = byte & 0x0f; + byte1 = byte >> 4; + leftOver.push(byte1, byte2); + break; + case 2: + byte4 = byte & 3; + byte3 = (byte >> 2) & 3; + byte2 = (byte >> 4) & 3; + byte1 = (byte >> 6) & 3; + leftOver.push(byte1, byte2, byte3, byte4); + break; + case 1: + byte8 = byte & 1; + byte7 = (byte >> 1) & 1; + byte6 = (byte >> 2) & 1; + byte5 = (byte >> 3) & 1; + byte4 = (byte >> 4) & 1; + byte3 = (byte >> 5) & 1; + byte2 = (byte >> 6) & 1; + byte1 = (byte >> 7) & 1; + leftOver.push(byte1, byte2, byte3, byte4, byte5, byte6, byte7, byte8); + break; + } + } + + return { + get: function (count) { + while (leftOver.length < count) { + split(); + } + let returner = leftOver.slice(0, count); + leftOver = leftOver.slice(count); + return returner; + }, + resetAfterLine: function () { + leftOver.length = 0; + }, + end: function () { + if (i !== data.length) { + throw new Error("extra data found"); + } + }, + }; +} + +function mapImage8Bit(image, pxData, getPxPos, bpp, data, rawPos) { + // eslint-disable-line max-params + let imageWidth = image.width; + let imageHeight = image.height; + let imagePass = image.index; + for (let y = 0; y < imageHeight; y++) { + for (let x = 0; x < imageWidth; x++) { + let pxPos = getPxPos(x, y, imagePass); + pixelBppMapper[bpp](pxData, data, pxPos, rawPos); + rawPos += bpp; //eslint-disable-line no-param-reassign + } + } + return rawPos; +} + +function mapImageCustomBit(image, pxData, getPxPos, bpp, bits, maxBit) { + // eslint-disable-line max-params + let imageWidth = image.width; + let imageHeight = image.height; + let imagePass = image.index; + for (let y = 0; y < imageHeight; y++) { + for (let x = 0; x < imageWidth; x++) { + let pixelData = bits.get(bpp); + let pxPos = getPxPos(x, y, imagePass); + pixelBppCustomMapper[bpp](pxData, pixelData, pxPos, maxBit); + } + bits.resetAfterLine(); + } +} + +exports.dataToBitMap = function (data, bitmapInfo) { + let width = bitmapInfo.width; + let height = bitmapInfo.height; + let depth = bitmapInfo.depth; + let bpp = bitmapInfo.bpp; + let interlace = bitmapInfo.interlace; + let bits; + + if (depth !== 8) { + bits = bitRetriever(data, depth); + } + let pxData; + if (depth <= 8) { + pxData = Buffer.alloc(width * height * 4); + } else { + pxData = new Uint16Array(width * height * 4); + } + let maxBit = Math.pow(2, depth) - 1; + let rawPos = 0; + let images; + let getPxPos; + + if (interlace) { + images = interlaceUtils.getImagePasses(width, height); + getPxPos = interlaceUtils.getInterlaceIterator(width, height); + } else { + let nonInterlacedPxPos = 0; + getPxPos = function () { + let returner = nonInterlacedPxPos; + nonInterlacedPxPos += 4; + return returner; + }; + images = [{ width: width, height: height }]; + } + + for (let imageIndex = 0; imageIndex < images.length; imageIndex++) { + if (depth === 8) { + rawPos = mapImage8Bit( + images[imageIndex], + pxData, + getPxPos, + bpp, + data, + rawPos + ); + } else { + mapImageCustomBit( + images[imageIndex], + pxData, + getPxPos, + bpp, + bits, + maxBit + ); + } + } + if (depth === 8) { + if (rawPos !== data.length) { + throw new Error("extra data found"); + } + } else { + bits.end(); + } + + return pxData; +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/bitpacker.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/bitpacker.js new file mode 100644 index 00000000..d7a4e656 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/bitpacker.js @@ -0,0 +1,158 @@ +"use strict"; + +let constants = require("./constants"); + +module.exports = function (dataIn, width, height, options) { + let outHasAlpha = + [constants.COLORTYPE_COLOR_ALPHA, constants.COLORTYPE_ALPHA].indexOf( + options.colorType + ) !== -1; + if (options.colorType === options.inputColorType) { + let bigEndian = (function () { + let buffer = new ArrayBuffer(2); + new DataView(buffer).setInt16(0, 256, true /* littleEndian */); + // Int16Array uses the platform's endianness. + return new Int16Array(buffer)[0] !== 256; + })(); + // If no need to convert to grayscale and alpha is present/absent in both, take a fast route + if (options.bitDepth === 8 || (options.bitDepth === 16 && bigEndian)) { + return dataIn; + } + } + + // map to a UInt16 array if data is 16bit, fix endianness below + let data = options.bitDepth !== 16 ? dataIn : new Uint16Array(dataIn.buffer); + + let maxValue = 255; + let inBpp = constants.COLORTYPE_TO_BPP_MAP[options.inputColorType]; + if (inBpp === 4 && !options.inputHasAlpha) { + inBpp = 3; + } + let outBpp = constants.COLORTYPE_TO_BPP_MAP[options.colorType]; + if (options.bitDepth === 16) { + maxValue = 65535; + outBpp *= 2; + } + let outData = Buffer.alloc(width * height * outBpp); + + let inIndex = 0; + let outIndex = 0; + + let bgColor = options.bgColor || {}; + if (bgColor.red === undefined) { + bgColor.red = maxValue; + } + if (bgColor.green === undefined) { + bgColor.green = maxValue; + } + if (bgColor.blue === undefined) { + bgColor.blue = maxValue; + } + + function getRGBA() { + let red; + let green; + let blue; + let alpha = maxValue; + switch (options.inputColorType) { + case constants.COLORTYPE_COLOR_ALPHA: + alpha = data[inIndex + 3]; + red = data[inIndex]; + green = data[inIndex + 1]; + blue = data[inIndex + 2]; + break; + case constants.COLORTYPE_COLOR: + red = data[inIndex]; + green = data[inIndex + 1]; + blue = data[inIndex + 2]; + break; + case constants.COLORTYPE_ALPHA: + alpha = data[inIndex + 1]; + red = data[inIndex]; + green = red; + blue = red; + break; + case constants.COLORTYPE_GRAYSCALE: + red = data[inIndex]; + green = red; + blue = red; + break; + default: + throw new Error( + "input color type:" + + options.inputColorType + + " is not supported at present" + ); + } + + if (options.inputHasAlpha) { + if (!outHasAlpha) { + alpha /= maxValue; + red = Math.min( + Math.max(Math.round((1 - alpha) * bgColor.red + alpha * red), 0), + maxValue + ); + green = Math.min( + Math.max(Math.round((1 - alpha) * bgColor.green + alpha * green), 0), + maxValue + ); + blue = Math.min( + Math.max(Math.round((1 - alpha) * bgColor.blue + alpha * blue), 0), + maxValue + ); + } + } + return { red: red, green: green, blue: blue, alpha: alpha }; + } + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + let rgba = getRGBA(data, inIndex); + + switch (options.colorType) { + case constants.COLORTYPE_COLOR_ALPHA: + case constants.COLORTYPE_COLOR: + if (options.bitDepth === 8) { + outData[outIndex] = rgba.red; + outData[outIndex + 1] = rgba.green; + outData[outIndex + 2] = rgba.blue; + if (outHasAlpha) { + outData[outIndex + 3] = rgba.alpha; + } + } else { + outData.writeUInt16BE(rgba.red, outIndex); + outData.writeUInt16BE(rgba.green, outIndex + 2); + outData.writeUInt16BE(rgba.blue, outIndex + 4); + if (outHasAlpha) { + outData.writeUInt16BE(rgba.alpha, outIndex + 6); + } + } + break; + case constants.COLORTYPE_ALPHA: + case constants.COLORTYPE_GRAYSCALE: { + // Convert to grayscale and alpha + let grayscale = (rgba.red + rgba.green + rgba.blue) / 3; + if (options.bitDepth === 8) { + outData[outIndex] = grayscale; + if (outHasAlpha) { + outData[outIndex + 1] = rgba.alpha; + } + } else { + outData.writeUInt16BE(grayscale, outIndex); + if (outHasAlpha) { + outData.writeUInt16BE(rgba.alpha, outIndex + 2); + } + } + break; + } + default: + throw new Error("unrecognised color Type " + options.colorType); + } + + inIndex += inBpp; + outIndex += outBpp; + } + } + + return outData; +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/chunkstream.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/chunkstream.js new file mode 100644 index 00000000..95b46d48 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/chunkstream.js @@ -0,0 +1,189 @@ +"use strict"; + +let util = require("util"); +let Stream = require("stream"); + +let ChunkStream = (module.exports = function () { + Stream.call(this); + + this._buffers = []; + this._buffered = 0; + + this._reads = []; + this._paused = false; + + this._encoding = "utf8"; + this.writable = true; +}); +util.inherits(ChunkStream, Stream); + +ChunkStream.prototype.read = function (length, callback) { + this._reads.push({ + length: Math.abs(length), // if length < 0 then at most this length + allowLess: length < 0, + func: callback, + }); + + process.nextTick( + function () { + this._process(); + + // its paused and there is not enought data then ask for more + if (this._paused && this._reads && this._reads.length > 0) { + this._paused = false; + + this.emit("drain"); + } + }.bind(this) + ); +}; + +ChunkStream.prototype.write = function (data, encoding) { + if (!this.writable) { + this.emit("error", new Error("Stream not writable")); + return false; + } + + let dataBuffer; + if (Buffer.isBuffer(data)) { + dataBuffer = data; + } else { + dataBuffer = Buffer.from(data, encoding || this._encoding); + } + + this._buffers.push(dataBuffer); + this._buffered += dataBuffer.length; + + this._process(); + + // ok if there are no more read requests + if (this._reads && this._reads.length === 0) { + this._paused = true; + } + + return this.writable && !this._paused; +}; + +ChunkStream.prototype.end = function (data, encoding) { + if (data) { + this.write(data, encoding); + } + + this.writable = false; + + // already destroyed + if (!this._buffers) { + return; + } + + // enqueue or handle end + if (this._buffers.length === 0) { + this._end(); + } else { + this._buffers.push(null); + this._process(); + } +}; + +ChunkStream.prototype.destroySoon = ChunkStream.prototype.end; + +ChunkStream.prototype._end = function () { + if (this._reads.length > 0) { + this.emit("error", new Error("Unexpected end of input")); + } + + this.destroy(); +}; + +ChunkStream.prototype.destroy = function () { + if (!this._buffers) { + return; + } + + this.writable = false; + this._reads = null; + this._buffers = null; + + this.emit("close"); +}; + +ChunkStream.prototype._processReadAllowingLess = function (read) { + // ok there is any data so that we can satisfy this request + this._reads.shift(); // == read + + // first we need to peek into first buffer + let smallerBuf = this._buffers[0]; + + // ok there is more data than we need + if (smallerBuf.length > read.length) { + this._buffered -= read.length; + this._buffers[0] = smallerBuf.slice(read.length); + + read.func.call(this, smallerBuf.slice(0, read.length)); + } else { + // ok this is less than maximum length so use it all + this._buffered -= smallerBuf.length; + this._buffers.shift(); // == smallerBuf + + read.func.call(this, smallerBuf); + } +}; + +ChunkStream.prototype._processRead = function (read) { + this._reads.shift(); // == read + + let pos = 0; + let count = 0; + let data = Buffer.alloc(read.length); + + // create buffer for all data + while (pos < read.length) { + let buf = this._buffers[count++]; + let len = Math.min(buf.length, read.length - pos); + + buf.copy(data, pos, 0, len); + pos += len; + + // last buffer wasn't used all so just slice it and leave + if (len !== buf.length) { + this._buffers[--count] = buf.slice(len); + } + } + + // remove all used buffers + if (count > 0) { + this._buffers.splice(0, count); + } + + this._buffered -= read.length; + + read.func.call(this, data); +}; + +ChunkStream.prototype._process = function () { + try { + // as long as there is any data and read requests + while (this._buffered > 0 && this._reads && this._reads.length > 0) { + let read = this._reads[0]; + + // read any data (but no more than length) + if (read.allowLess) { + this._processReadAllowingLess(read); + } else if (this._buffered >= read.length) { + // ok we can meet some expectations + + this._processRead(read); + } else { + // not enought data to satisfy first request in queue + // so we need to wait for more + break; + } + } + + if (this._buffers && !this.writable) { + this._end(); + } + } catch (ex) { + this.emit("error", ex); + } +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/constants.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/constants.js new file mode 100644 index 00000000..21fdad68 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/constants.js @@ -0,0 +1,32 @@ +"use strict"; + +module.exports = { + PNG_SIGNATURE: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], + + TYPE_IHDR: 0x49484452, + TYPE_IEND: 0x49454e44, + TYPE_IDAT: 0x49444154, + TYPE_PLTE: 0x504c5445, + TYPE_tRNS: 0x74524e53, // eslint-disable-line camelcase + TYPE_gAMA: 0x67414d41, // eslint-disable-line camelcase + + // color-type bits + COLORTYPE_GRAYSCALE: 0, + COLORTYPE_PALETTE: 1, + COLORTYPE_COLOR: 2, + COLORTYPE_ALPHA: 4, // e.g. grayscale and alpha + + // color-type combinations + COLORTYPE_PALETTE_COLOR: 3, + COLORTYPE_COLOR_ALPHA: 6, + + COLORTYPE_TO_BPP_MAP: { + 0: 1, + 2: 3, + 3: 1, + 4: 2, + 6: 4, + }, + + GAMMA_DIVISION: 100000, +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/crc.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/crc.js new file mode 100644 index 00000000..950ec8ae --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/crc.js @@ -0,0 +1,40 @@ +"use strict"; + +let crcTable = []; + +(function () { + for (let i = 0; i < 256; i++) { + let currentCrc = i; + for (let j = 0; j < 8; j++) { + if (currentCrc & 1) { + currentCrc = 0xedb88320 ^ (currentCrc >>> 1); + } else { + currentCrc = currentCrc >>> 1; + } + } + crcTable[i] = currentCrc; + } +})(); + +let CrcCalculator = (module.exports = function () { + this._crc = -1; +}); + +CrcCalculator.prototype.write = function (data) { + for (let i = 0; i < data.length; i++) { + this._crc = crcTable[(this._crc ^ data[i]) & 0xff] ^ (this._crc >>> 8); + } + return true; +}; + +CrcCalculator.prototype.crc32 = function () { + return this._crc ^ -1; +}; + +CrcCalculator.crc32 = function (buf) { + let crc = -1; + for (let i = 0; i < buf.length; i++) { + crc = crcTable[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8); + } + return crc ^ -1; +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-pack.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-pack.js new file mode 100644 index 00000000..32c85c40 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-pack.js @@ -0,0 +1,171 @@ +"use strict"; + +let paethPredictor = require("./paeth-predictor"); + +function filterNone(pxData, pxPos, byteWidth, rawData, rawPos) { + for (let x = 0; x < byteWidth; x++) { + rawData[rawPos + x] = pxData[pxPos + x]; + } +} + +function filterSumNone(pxData, pxPos, byteWidth) { + let sum = 0; + let length = pxPos + byteWidth; + + for (let i = pxPos; i < length; i++) { + sum += Math.abs(pxData[i]); + } + return sum; +} + +function filterSub(pxData, pxPos, byteWidth, rawData, rawPos, bpp) { + for (let x = 0; x < byteWidth; x++) { + let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; + let val = pxData[pxPos + x] - left; + + rawData[rawPos + x] = val; + } +} + +function filterSumSub(pxData, pxPos, byteWidth, bpp) { + let sum = 0; + for (let x = 0; x < byteWidth; x++) { + let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; + let val = pxData[pxPos + x] - left; + + sum += Math.abs(val); + } + + return sum; +} + +function filterUp(pxData, pxPos, byteWidth, rawData, rawPos) { + for (let x = 0; x < byteWidth; x++) { + let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; + let val = pxData[pxPos + x] - up; + + rawData[rawPos + x] = val; + } +} + +function filterSumUp(pxData, pxPos, byteWidth) { + let sum = 0; + let length = pxPos + byteWidth; + for (let x = pxPos; x < length; x++) { + let up = pxPos > 0 ? pxData[x - byteWidth] : 0; + let val = pxData[x] - up; + + sum += Math.abs(val); + } + + return sum; +} + +function filterAvg(pxData, pxPos, byteWidth, rawData, rawPos, bpp) { + for (let x = 0; x < byteWidth; x++) { + let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; + let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; + let val = pxData[pxPos + x] - ((left + up) >> 1); + + rawData[rawPos + x] = val; + } +} + +function filterSumAvg(pxData, pxPos, byteWidth, bpp) { + let sum = 0; + for (let x = 0; x < byteWidth; x++) { + let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; + let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; + let val = pxData[pxPos + x] - ((left + up) >> 1); + + sum += Math.abs(val); + } + + return sum; +} + +function filterPaeth(pxData, pxPos, byteWidth, rawData, rawPos, bpp) { + for (let x = 0; x < byteWidth; x++) { + let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; + let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; + let upleft = + pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0; + let val = pxData[pxPos + x] - paethPredictor(left, up, upleft); + + rawData[rawPos + x] = val; + } +} + +function filterSumPaeth(pxData, pxPos, byteWidth, bpp) { + let sum = 0; + for (let x = 0; x < byteWidth; x++) { + let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; + let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; + let upleft = + pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0; + let val = pxData[pxPos + x] - paethPredictor(left, up, upleft); + + sum += Math.abs(val); + } + + return sum; +} + +let filters = { + 0: filterNone, + 1: filterSub, + 2: filterUp, + 3: filterAvg, + 4: filterPaeth, +}; + +let filterSums = { + 0: filterSumNone, + 1: filterSumSub, + 2: filterSumUp, + 3: filterSumAvg, + 4: filterSumPaeth, +}; + +module.exports = function (pxData, width, height, options, bpp) { + let filterTypes; + if (!("filterType" in options) || options.filterType === -1) { + filterTypes = [0, 1, 2, 3, 4]; + } else if (typeof options.filterType === "number") { + filterTypes = [options.filterType]; + } else { + throw new Error("unrecognised filter types"); + } + + if (options.bitDepth === 16) { + bpp *= 2; + } + let byteWidth = width * bpp; + let rawPos = 0; + let pxPos = 0; + let rawData = Buffer.alloc((byteWidth + 1) * height); + + let sel = filterTypes[0]; + + for (let y = 0; y < height; y++) { + if (filterTypes.length > 1) { + // find best filter for this line (with lowest sum of values) + let min = Infinity; + + for (let i = 0; i < filterTypes.length; i++) { + let sum = filterSums[filterTypes[i]](pxData, pxPos, byteWidth, bpp); + if (sum < min) { + sel = filterTypes[i]; + min = sum; + } + } + } + + rawData[rawPos] = sel; + rawPos++; + filters[sel](pxData, pxPos, byteWidth, rawData, rawPos, bpp); + rawPos += byteWidth; + pxPos += byteWidth; + } + return rawData; +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-async.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-async.js new file mode 100644 index 00000000..832b86cd --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-async.js @@ -0,0 +1,24 @@ +"use strict"; + +let util = require("util"); +let ChunkStream = require("./chunkstream"); +let Filter = require("./filter-parse"); + +let FilterAsync = (module.exports = function (bitmapInfo) { + ChunkStream.call(this); + + let buffers = []; + let that = this; + this._filter = new Filter(bitmapInfo, { + read: this.read.bind(this), + write: function (buffer) { + buffers.push(buffer); + }, + complete: function () { + that.emit("complete", Buffer.concat(buffers)); + }, + }); + + this._filter.start(); +}); +util.inherits(FilterAsync, ChunkStream); diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-sync.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-sync.js new file mode 100644 index 00000000..6924d161 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-sync.js @@ -0,0 +1,21 @@ +"use strict"; + +let SyncReader = require("./sync-reader"); +let Filter = require("./filter-parse"); + +exports.process = function (inBuffer, bitmapInfo) { + let outBuffers = []; + let reader = new SyncReader(inBuffer); + let filter = new Filter(bitmapInfo, { + read: reader.read.bind(reader), + write: function (bufferPart) { + outBuffers.push(bufferPart); + }, + complete: function () {}, + }); + + filter.start(); + reader.process(); + + return Buffer.concat(outBuffers); +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse.js new file mode 100644 index 00000000..3a32e5ee --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse.js @@ -0,0 +1,177 @@ +"use strict"; + +let interlaceUtils = require("./interlace"); +let paethPredictor = require("./paeth-predictor"); + +function getByteWidth(width, bpp, depth) { + let byteWidth = width * bpp; + if (depth !== 8) { + byteWidth = Math.ceil(byteWidth / (8 / depth)); + } + return byteWidth; +} + +let Filter = (module.exports = function (bitmapInfo, dependencies) { + let width = bitmapInfo.width; + let height = bitmapInfo.height; + let interlace = bitmapInfo.interlace; + let bpp = bitmapInfo.bpp; + let depth = bitmapInfo.depth; + + this.read = dependencies.read; + this.write = dependencies.write; + this.complete = dependencies.complete; + + this._imageIndex = 0; + this._images = []; + if (interlace) { + let passes = interlaceUtils.getImagePasses(width, height); + for (let i = 0; i < passes.length; i++) { + this._images.push({ + byteWidth: getByteWidth(passes[i].width, bpp, depth), + height: passes[i].height, + lineIndex: 0, + }); + } + } else { + this._images.push({ + byteWidth: getByteWidth(width, bpp, depth), + height: height, + lineIndex: 0, + }); + } + + // when filtering the line we look at the pixel to the left + // the spec also says it is done on a byte level regardless of the number of pixels + // so if the depth is byte compatible (8 or 16) we subtract the bpp in order to compare back + // a pixel rather than just a different byte part. However if we are sub byte, we ignore. + if (depth === 8) { + this._xComparison = bpp; + } else if (depth === 16) { + this._xComparison = bpp * 2; + } else { + this._xComparison = 1; + } +}); + +Filter.prototype.start = function () { + this.read( + this._images[this._imageIndex].byteWidth + 1, + this._reverseFilterLine.bind(this) + ); +}; + +Filter.prototype._unFilterType1 = function ( + rawData, + unfilteredLine, + byteWidth +) { + let xComparison = this._xComparison; + let xBiggerThan = xComparison - 1; + + for (let x = 0; x < byteWidth; x++) { + let rawByte = rawData[1 + x]; + let f1Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0; + unfilteredLine[x] = rawByte + f1Left; + } +}; + +Filter.prototype._unFilterType2 = function ( + rawData, + unfilteredLine, + byteWidth +) { + let lastLine = this._lastLine; + + for (let x = 0; x < byteWidth; x++) { + let rawByte = rawData[1 + x]; + let f2Up = lastLine ? lastLine[x] : 0; + unfilteredLine[x] = rawByte + f2Up; + } +}; + +Filter.prototype._unFilterType3 = function ( + rawData, + unfilteredLine, + byteWidth +) { + let xComparison = this._xComparison; + let xBiggerThan = xComparison - 1; + let lastLine = this._lastLine; + + for (let x = 0; x < byteWidth; x++) { + let rawByte = rawData[1 + x]; + let f3Up = lastLine ? lastLine[x] : 0; + let f3Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0; + let f3Add = Math.floor((f3Left + f3Up) / 2); + unfilteredLine[x] = rawByte + f3Add; + } +}; + +Filter.prototype._unFilterType4 = function ( + rawData, + unfilteredLine, + byteWidth +) { + let xComparison = this._xComparison; + let xBiggerThan = xComparison - 1; + let lastLine = this._lastLine; + + for (let x = 0; x < byteWidth; x++) { + let rawByte = rawData[1 + x]; + let f4Up = lastLine ? lastLine[x] : 0; + let f4Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0; + let f4UpLeft = x > xBiggerThan && lastLine ? lastLine[x - xComparison] : 0; + let f4Add = paethPredictor(f4Left, f4Up, f4UpLeft); + unfilteredLine[x] = rawByte + f4Add; + } +}; + +Filter.prototype._reverseFilterLine = function (rawData) { + let filter = rawData[0]; + let unfilteredLine; + let currentImage = this._images[this._imageIndex]; + let byteWidth = currentImage.byteWidth; + + if (filter === 0) { + unfilteredLine = rawData.slice(1, byteWidth + 1); + } else { + unfilteredLine = Buffer.alloc(byteWidth); + + switch (filter) { + case 1: + this._unFilterType1(rawData, unfilteredLine, byteWidth); + break; + case 2: + this._unFilterType2(rawData, unfilteredLine, byteWidth); + break; + case 3: + this._unFilterType3(rawData, unfilteredLine, byteWidth); + break; + case 4: + this._unFilterType4(rawData, unfilteredLine, byteWidth); + break; + default: + throw new Error("Unrecognised filter type - " + filter); + } + } + + this.write(unfilteredLine); + + currentImage.lineIndex++; + if (currentImage.lineIndex >= currentImage.height) { + this._lastLine = null; + this._imageIndex++; + currentImage = this._images[this._imageIndex]; + } else { + this._lastLine = unfilteredLine; + } + + if (currentImage) { + // read, using the byte width that may be from the new current image + this.read(currentImage.byteWidth + 1, this._reverseFilterLine.bind(this)); + } else { + this._lastLine = null; + this.complete(); + } +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/format-normaliser.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/format-normaliser.js new file mode 100644 index 00000000..209b66bb --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/format-normaliser.js @@ -0,0 +1,93 @@ +"use strict"; + +function dePalette(indata, outdata, width, height, palette) { + let pxPos = 0; + // use values from palette + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + let color = palette[indata[pxPos]]; + + if (!color) { + throw new Error("index " + indata[pxPos] + " not in palette"); + } + + for (let i = 0; i < 4; i++) { + outdata[pxPos + i] = color[i]; + } + pxPos += 4; + } + } +} + +function replaceTransparentColor(indata, outdata, width, height, transColor) { + let pxPos = 0; + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + let makeTrans = false; + + if (transColor.length === 1) { + if (transColor[0] === indata[pxPos]) { + makeTrans = true; + } + } else if ( + transColor[0] === indata[pxPos] && + transColor[1] === indata[pxPos + 1] && + transColor[2] === indata[pxPos + 2] + ) { + makeTrans = true; + } + if (makeTrans) { + for (let i = 0; i < 4; i++) { + outdata[pxPos + i] = 0; + } + } + pxPos += 4; + } + } +} + +function scaleDepth(indata, outdata, width, height, depth) { + let maxOutSample = 255; + let maxInSample = Math.pow(2, depth) - 1; + let pxPos = 0; + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + for (let i = 0; i < 4; i++) { + outdata[pxPos + i] = Math.floor( + (indata[pxPos + i] * maxOutSample) / maxInSample + 0.5 + ); + } + pxPos += 4; + } + } +} + +module.exports = function (indata, imageData, skipRescale = false) { + let depth = imageData.depth; + let width = imageData.width; + let height = imageData.height; + let colorType = imageData.colorType; + let transColor = imageData.transColor; + let palette = imageData.palette; + + let outdata = indata; // only different for 16 bits + + if (colorType === 3) { + // paletted + dePalette(indata, outdata, width, height, palette); + } else { + if (transColor) { + replaceTransparentColor(indata, outdata, width, height, transColor); + } + // if it needs scaling + if (depth !== 8 && !skipRescale) { + // if we need to change the buffer size + if (depth === 16) { + outdata = Buffer.alloc(width * height * 4); + } + scaleDepth(indata, outdata, width, height, depth); + } + } + return outdata; +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/interlace.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/interlace.js new file mode 100644 index 00000000..a035cb15 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/interlace.js @@ -0,0 +1,95 @@ +"use strict"; + +// Adam 7 +// 0 1 2 3 4 5 6 7 +// 0 x 6 4 6 x 6 4 6 +// 1 7 7 7 7 7 7 7 7 +// 2 5 6 5 6 5 6 5 6 +// 3 7 7 7 7 7 7 7 7 +// 4 3 6 4 6 3 6 4 6 +// 5 7 7 7 7 7 7 7 7 +// 6 5 6 5 6 5 6 5 6 +// 7 7 7 7 7 7 7 7 7 + +let imagePasses = [ + { + // pass 1 - 1px + x: [0], + y: [0], + }, + { + // pass 2 - 1px + x: [4], + y: [0], + }, + { + // pass 3 - 2px + x: [0, 4], + y: [4], + }, + { + // pass 4 - 4px + x: [2, 6], + y: [0, 4], + }, + { + // pass 5 - 8px + x: [0, 2, 4, 6], + y: [2, 6], + }, + { + // pass 6 - 16px + x: [1, 3, 5, 7], + y: [0, 2, 4, 6], + }, + { + // pass 7 - 32px + x: [0, 1, 2, 3, 4, 5, 6, 7], + y: [1, 3, 5, 7], + }, +]; + +exports.getImagePasses = function (width, height) { + let images = []; + let xLeftOver = width % 8; + let yLeftOver = height % 8; + let xRepeats = (width - xLeftOver) / 8; + let yRepeats = (height - yLeftOver) / 8; + for (let i = 0; i < imagePasses.length; i++) { + let pass = imagePasses[i]; + let passWidth = xRepeats * pass.x.length; + let passHeight = yRepeats * pass.y.length; + for (let j = 0; j < pass.x.length; j++) { + if (pass.x[j] < xLeftOver) { + passWidth++; + } else { + break; + } + } + for (let j = 0; j < pass.y.length; j++) { + if (pass.y[j] < yLeftOver) { + passHeight++; + } else { + break; + } + } + if (passWidth > 0 && passHeight > 0) { + images.push({ width: passWidth, height: passHeight, index: i }); + } + } + return images; +}; + +exports.getInterlaceIterator = function (width) { + return function (x, y, pass) { + let outerXLeftOver = x % imagePasses[pass].x.length; + let outerX = + ((x - outerXLeftOver) / imagePasses[pass].x.length) * 8 + + imagePasses[pass].x[outerXLeftOver]; + let outerYLeftOver = y % imagePasses[pass].y.length; + let outerY = + ((y - outerYLeftOver) / imagePasses[pass].y.length) * 8 + + imagePasses[pass].y[outerYLeftOver]; + return outerX * 4 + outerY * width * 4; + }; +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-async.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-async.js new file mode 100644 index 00000000..f3df73aa --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-async.js @@ -0,0 +1,50 @@ +"use strict"; + +let util = require("util"); +let Stream = require("stream"); +let constants = require("./constants"); +let Packer = require("./packer"); + +let PackerAsync = (module.exports = function (opt) { + Stream.call(this); + + let options = opt || {}; + + this._packer = new Packer(options); + this._deflate = this._packer.createDeflate(); + + this.readable = true; +}); +util.inherits(PackerAsync, Stream); + +PackerAsync.prototype.pack = function (data, width, height, gamma) { + // Signature + this.emit("data", Buffer.from(constants.PNG_SIGNATURE)); + this.emit("data", this._packer.packIHDR(width, height)); + + if (gamma) { + this.emit("data", this._packer.packGAMA(gamma)); + } + + let filteredData = this._packer.filterData(data, width, height); + + // compress it + this._deflate.on("error", this.emit.bind(this, "error")); + + this._deflate.on( + "data", + function (compressedData) { + this.emit("data", this._packer.packIDAT(compressedData)); + }.bind(this) + ); + + this._deflate.on( + "end", + function () { + this.emit("data", this._packer.packIEND()); + this.emit("end"); + }.bind(this) + ); + + this._deflate.end(filteredData); +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-sync.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-sync.js new file mode 100644 index 00000000..f5ab0b3d --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-sync.js @@ -0,0 +1,56 @@ +"use strict"; + +let hasSyncZlib = true; +let zlib = require("zlib"); +if (!zlib.deflateSync) { + hasSyncZlib = false; +} +let constants = require("./constants"); +let Packer = require("./packer"); + +module.exports = function (metaData, opt) { + if (!hasSyncZlib) { + throw new Error( + "To use the sync capability of this library in old node versions, please pin pngjs to v2.3.0" + ); + } + + let options = opt || {}; + + let packer = new Packer(options); + + let chunks = []; + + // Signature + chunks.push(Buffer.from(constants.PNG_SIGNATURE)); + + // Header + chunks.push(packer.packIHDR(metaData.width, metaData.height)); + + if (metaData.gamma) { + chunks.push(packer.packGAMA(metaData.gamma)); + } + + let filteredData = packer.filterData( + metaData.data, + metaData.width, + metaData.height + ); + + // compress it + let compressedData = zlib.deflateSync( + filteredData, + packer.getDeflateOptions() + ); + filteredData = null; + + if (!compressedData || !compressedData.length) { + throw new Error("bad png - invalid compressed data response"); + } + chunks.push(packer.packIDAT(compressedData)); + + // End + chunks.push(packer.packIEND()); + + return Buffer.concat(chunks); +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/packer.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/packer.js new file mode 100644 index 00000000..4aba12c8 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/packer.js @@ -0,0 +1,129 @@ +"use strict"; + +let constants = require("./constants"); +let CrcStream = require("./crc"); +let bitPacker = require("./bitpacker"); +let filter = require("./filter-pack"); +let zlib = require("zlib"); + +let Packer = (module.exports = function (options) { + this._options = options; + + options.deflateChunkSize = options.deflateChunkSize || 32 * 1024; + options.deflateLevel = + options.deflateLevel != null ? options.deflateLevel : 9; + options.deflateStrategy = + options.deflateStrategy != null ? options.deflateStrategy : 3; + options.inputHasAlpha = + options.inputHasAlpha != null ? options.inputHasAlpha : true; + options.deflateFactory = options.deflateFactory || zlib.createDeflate; + options.bitDepth = options.bitDepth || 8; + // This is outputColorType + options.colorType = + typeof options.colorType === "number" + ? options.colorType + : constants.COLORTYPE_COLOR_ALPHA; + options.inputColorType = + typeof options.inputColorType === "number" + ? options.inputColorType + : constants.COLORTYPE_COLOR_ALPHA; + + if ( + [ + constants.COLORTYPE_GRAYSCALE, + constants.COLORTYPE_COLOR, + constants.COLORTYPE_COLOR_ALPHA, + constants.COLORTYPE_ALPHA, + ].indexOf(options.colorType) === -1 + ) { + throw new Error( + "option color type:" + options.colorType + " is not supported at present" + ); + } + if ( + [ + constants.COLORTYPE_GRAYSCALE, + constants.COLORTYPE_COLOR, + constants.COLORTYPE_COLOR_ALPHA, + constants.COLORTYPE_ALPHA, + ].indexOf(options.inputColorType) === -1 + ) { + throw new Error( + "option input color type:" + + options.inputColorType + + " is not supported at present" + ); + } + if (options.bitDepth !== 8 && options.bitDepth !== 16) { + throw new Error( + "option bit depth:" + options.bitDepth + " is not supported at present" + ); + } +}); + +Packer.prototype.getDeflateOptions = function () { + return { + chunkSize: this._options.deflateChunkSize, + level: this._options.deflateLevel, + strategy: this._options.deflateStrategy, + }; +}; + +Packer.prototype.createDeflate = function () { + return this._options.deflateFactory(this.getDeflateOptions()); +}; + +Packer.prototype.filterData = function (data, width, height) { + // convert to correct format for filtering (e.g. right bpp and bit depth) + let packedData = bitPacker(data, width, height, this._options); + + // filter pixel data + let bpp = constants.COLORTYPE_TO_BPP_MAP[this._options.colorType]; + let filteredData = filter(packedData, width, height, this._options, bpp); + return filteredData; +}; + +Packer.prototype._packChunk = function (type, data) { + let len = data ? data.length : 0; + let buf = Buffer.alloc(len + 12); + + buf.writeUInt32BE(len, 0); + buf.writeUInt32BE(type, 4); + + if (data) { + data.copy(buf, 8); + } + + buf.writeInt32BE( + CrcStream.crc32(buf.slice(4, buf.length - 4)), + buf.length - 4 + ); + return buf; +}; + +Packer.prototype.packGAMA = function (gamma) { + let buf = Buffer.alloc(4); + buf.writeUInt32BE(Math.floor(gamma * constants.GAMMA_DIVISION), 0); + return this._packChunk(constants.TYPE_gAMA, buf); +}; + +Packer.prototype.packIHDR = function (width, height) { + let buf = Buffer.alloc(13); + buf.writeUInt32BE(width, 0); + buf.writeUInt32BE(height, 4); + buf[8] = this._options.bitDepth; // Bit depth + buf[9] = this._options.colorType; // colorType + buf[10] = 0; // compression + buf[11] = 0; // filter + buf[12] = 0; // interlace + + return this._packChunk(constants.TYPE_IHDR, buf); +}; + +Packer.prototype.packIDAT = function (data) { + return this._packChunk(constants.TYPE_IDAT, data); +}; + +Packer.prototype.packIEND = function () { + return this._packChunk(constants.TYPE_IEND, null); +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/paeth-predictor.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/paeth-predictor.js new file mode 100644 index 00000000..9634497d --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/paeth-predictor.js @@ -0,0 +1,16 @@ +"use strict"; + +module.exports = function paethPredictor(left, above, upLeft) { + let paeth = left + above - upLeft; + let pLeft = Math.abs(paeth - left); + let pAbove = Math.abs(paeth - above); + let pUpLeft = Math.abs(paeth - upLeft); + + if (pLeft <= pAbove && pLeft <= pUpLeft) { + return left; + } + if (pAbove <= pUpLeft) { + return above; + } + return upLeft; +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-async.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-async.js new file mode 100644 index 00000000..1aacde34 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-async.js @@ -0,0 +1,169 @@ +"use strict"; + +let util = require("util"); +let zlib = require("zlib"); +let ChunkStream = require("./chunkstream"); +let FilterAsync = require("./filter-parse-async"); +let Parser = require("./parser"); +let bitmapper = require("./bitmapper"); +let formatNormaliser = require("./format-normaliser"); + +let ParserAsync = (module.exports = function (options) { + ChunkStream.call(this); + + this._parser = new Parser(options, { + read: this.read.bind(this), + error: this._handleError.bind(this), + metadata: this._handleMetaData.bind(this), + gamma: this.emit.bind(this, "gamma"), + palette: this._handlePalette.bind(this), + transColor: this._handleTransColor.bind(this), + finished: this._finished.bind(this), + inflateData: this._inflateData.bind(this), + simpleTransparency: this._simpleTransparency.bind(this), + headersFinished: this._headersFinished.bind(this), + }); + this._options = options; + this.writable = true; + + this._parser.start(); +}); +util.inherits(ParserAsync, ChunkStream); + +ParserAsync.prototype._handleError = function (err) { + this.emit("error", err); + + this.writable = false; + + this.destroy(); + + if (this._inflate && this._inflate.destroy) { + this._inflate.destroy(); + } + + if (this._filter) { + this._filter.destroy(); + // For backward compatibility with Node 7 and below. + // Suppress errors due to _inflate calling write() even after + // it's destroy()'ed. + this._filter.on("error", function () {}); + } + + this.errord = true; +}; + +ParserAsync.prototype._inflateData = function (data) { + if (!this._inflate) { + if (this._bitmapInfo.interlace) { + this._inflate = zlib.createInflate(); + + this._inflate.on("error", this.emit.bind(this, "error")); + this._filter.on("complete", this._complete.bind(this)); + + this._inflate.pipe(this._filter); + } else { + let rowSize = + ((this._bitmapInfo.width * + this._bitmapInfo.bpp * + this._bitmapInfo.depth + + 7) >> + 3) + + 1; + let imageSize = rowSize * this._bitmapInfo.height; + let chunkSize = Math.max(imageSize, zlib.Z_MIN_CHUNK); + + this._inflate = zlib.createInflate({ chunkSize: chunkSize }); + let leftToInflate = imageSize; + + let emitError = this.emit.bind(this, "error"); + this._inflate.on("error", function (err) { + if (!leftToInflate) { + return; + } + + emitError(err); + }); + this._filter.on("complete", this._complete.bind(this)); + + let filterWrite = this._filter.write.bind(this._filter); + this._inflate.on("data", function (chunk) { + if (!leftToInflate) { + return; + } + + if (chunk.length > leftToInflate) { + chunk = chunk.slice(0, leftToInflate); + } + + leftToInflate -= chunk.length; + + filterWrite(chunk); + }); + + this._inflate.on("end", this._filter.end.bind(this._filter)); + } + } + this._inflate.write(data); +}; + +ParserAsync.prototype._handleMetaData = function (metaData) { + this._metaData = metaData; + this._bitmapInfo = Object.create(metaData); + + this._filter = new FilterAsync(this._bitmapInfo); +}; + +ParserAsync.prototype._handleTransColor = function (transColor) { + this._bitmapInfo.transColor = transColor; +}; + +ParserAsync.prototype._handlePalette = function (palette) { + this._bitmapInfo.palette = palette; +}; + +ParserAsync.prototype._simpleTransparency = function () { + this._metaData.alpha = true; +}; + +ParserAsync.prototype._headersFinished = function () { + // Up until this point, we don't know if we have a tRNS chunk (alpha) + // so we can't emit metadata any earlier + this.emit("metadata", this._metaData); +}; + +ParserAsync.prototype._finished = function () { + if (this.errord) { + return; + } + + if (!this._inflate) { + this.emit("error", "No Inflate block"); + } else { + // no more data to inflate + this._inflate.end(); + } +}; + +ParserAsync.prototype._complete = function (filteredData) { + if (this.errord) { + return; + } + + let normalisedBitmapData; + + try { + let bitmapData = bitmapper.dataToBitMap(filteredData, this._bitmapInfo); + + normalisedBitmapData = formatNormaliser( + bitmapData, + this._bitmapInfo, + this._options.skipRescale + ); + bitmapData = null; + } catch (ex) { + this._handleError(ex); + return; + } + + this.emit("parsed", normalisedBitmapData); +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-sync.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-sync.js new file mode 100644 index 00000000..76cb134b --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-sync.js @@ -0,0 +1,112 @@ +"use strict"; + +let hasSyncZlib = true; +let zlib = require("zlib"); +let inflateSync = require("./sync-inflate"); +if (!zlib.deflateSync) { + hasSyncZlib = false; +} +let SyncReader = require("./sync-reader"); +let FilterSync = require("./filter-parse-sync"); +let Parser = require("./parser"); +let bitmapper = require("./bitmapper"); +let formatNormaliser = require("./format-normaliser"); + +module.exports = function (buffer, options) { + if (!hasSyncZlib) { + throw new Error( + "To use the sync capability of this library in old node versions, please pin pngjs to v2.3.0" + ); + } + + let err; + function handleError(_err_) { + err = _err_; + } + + let metaData; + function handleMetaData(_metaData_) { + metaData = _metaData_; + } + + function handleTransColor(transColor) { + metaData.transColor = transColor; + } + + function handlePalette(palette) { + metaData.palette = palette; + } + + function handleSimpleTransparency() { + metaData.alpha = true; + } + + let gamma; + function handleGamma(_gamma_) { + gamma = _gamma_; + } + + let inflateDataList = []; + function handleInflateData(inflatedData) { + inflateDataList.push(inflatedData); + } + + let reader = new SyncReader(buffer); + + let parser = new Parser(options, { + read: reader.read.bind(reader), + error: handleError, + metadata: handleMetaData, + gamma: handleGamma, + palette: handlePalette, + transColor: handleTransColor, + inflateData: handleInflateData, + simpleTransparency: handleSimpleTransparency, + }); + + parser.start(); + reader.process(); + + if (err) { + throw err; + } + + //join together the inflate datas + let inflateData = Buffer.concat(inflateDataList); + inflateDataList.length = 0; + + let inflatedData; + if (metaData.interlace) { + inflatedData = zlib.inflateSync(inflateData); + } else { + let rowSize = + ((metaData.width * metaData.bpp * metaData.depth + 7) >> 3) + 1; + let imageSize = rowSize * metaData.height; + inflatedData = inflateSync(inflateData, { + chunkSize: imageSize, + maxLength: imageSize, + }); + } + inflateData = null; + + if (!inflatedData || !inflatedData.length) { + throw new Error("bad png - invalid inflate data response"); + } + + let unfilteredData = FilterSync.process(inflatedData, metaData); + inflateData = null; + + let bitmapData = bitmapper.dataToBitMap(unfilteredData, metaData); + unfilteredData = null; + + let normalisedBitmapData = formatNormaliser( + bitmapData, + metaData, + options.skipRescale + ); + + metaData.data = normalisedBitmapData; + metaData.gamma = gamma || 0; + + return metaData; +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/parser.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/parser.js new file mode 100644 index 00000000..51a8f2a5 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/parser.js @@ -0,0 +1,290 @@ +"use strict"; + +let constants = require("./constants"); +let CrcCalculator = require("./crc"); + +let Parser = (module.exports = function (options, dependencies) { + this._options = options; + options.checkCRC = options.checkCRC !== false; + + this._hasIHDR = false; + this._hasIEND = false; + this._emittedHeadersFinished = false; + + // input flags/metadata + this._palette = []; + this._colorType = 0; + + this._chunks = {}; + this._chunks[constants.TYPE_IHDR] = this._handleIHDR.bind(this); + this._chunks[constants.TYPE_IEND] = this._handleIEND.bind(this); + this._chunks[constants.TYPE_IDAT] = this._handleIDAT.bind(this); + this._chunks[constants.TYPE_PLTE] = this._handlePLTE.bind(this); + this._chunks[constants.TYPE_tRNS] = this._handleTRNS.bind(this); + this._chunks[constants.TYPE_gAMA] = this._handleGAMA.bind(this); + + this.read = dependencies.read; + this.error = dependencies.error; + this.metadata = dependencies.metadata; + this.gamma = dependencies.gamma; + this.transColor = dependencies.transColor; + this.palette = dependencies.palette; + this.parsed = dependencies.parsed; + this.inflateData = dependencies.inflateData; + this.finished = dependencies.finished; + this.simpleTransparency = dependencies.simpleTransparency; + this.headersFinished = dependencies.headersFinished || function () {}; +}); + +Parser.prototype.start = function () { + this.read(constants.PNG_SIGNATURE.length, this._parseSignature.bind(this)); +}; + +Parser.prototype._parseSignature = function (data) { + let signature = constants.PNG_SIGNATURE; + + for (let i = 0; i < signature.length; i++) { + if (data[i] !== signature[i]) { + this.error(new Error("Invalid file signature")); + return; + } + } + this.read(8, this._parseChunkBegin.bind(this)); +}; + +Parser.prototype._parseChunkBegin = function (data) { + // chunk content length + let length = data.readUInt32BE(0); + + // chunk type + let type = data.readUInt32BE(4); + let name = ""; + for (let i = 4; i < 8; i++) { + name += String.fromCharCode(data[i]); + } + + //console.log('chunk ', name, length); + + // chunk flags + let ancillary = Boolean(data[4] & 0x20); // or critical + // priv = Boolean(data[5] & 0x20), // or public + // safeToCopy = Boolean(data[7] & 0x20); // or unsafe + + if (!this._hasIHDR && type !== constants.TYPE_IHDR) { + this.error(new Error("Expected IHDR on beggining")); + return; + } + + this._crc = new CrcCalculator(); + this._crc.write(Buffer.from(name)); + + if (this._chunks[type]) { + return this._chunks[type](length); + } + + if (!ancillary) { + this.error(new Error("Unsupported critical chunk type " + name)); + return; + } + + this.read(length + 4, this._skipChunk.bind(this)); +}; + +Parser.prototype._skipChunk = function (/*data*/) { + this.read(8, this._parseChunkBegin.bind(this)); +}; + +Parser.prototype._handleChunkEnd = function () { + this.read(4, this._parseChunkEnd.bind(this)); +}; + +Parser.prototype._parseChunkEnd = function (data) { + let fileCrc = data.readInt32BE(0); + let calcCrc = this._crc.crc32(); + + // check CRC + if (this._options.checkCRC && calcCrc !== fileCrc) { + this.error(new Error("Crc error - " + fileCrc + " - " + calcCrc)); + return; + } + + if (!this._hasIEND) { + this.read(8, this._parseChunkBegin.bind(this)); + } +}; + +Parser.prototype._handleIHDR = function (length) { + this.read(length, this._parseIHDR.bind(this)); +}; +Parser.prototype._parseIHDR = function (data) { + this._crc.write(data); + + let width = data.readUInt32BE(0); + let height = data.readUInt32BE(4); + let depth = data[8]; + let colorType = data[9]; // bits: 1 palette, 2 color, 4 alpha + let compr = data[10]; + let filter = data[11]; + let interlace = data[12]; + + // console.log(' width', width, 'height', height, + // 'depth', depth, 'colorType', colorType, + // 'compr', compr, 'filter', filter, 'interlace', interlace + // ); + + if ( + depth !== 8 && + depth !== 4 && + depth !== 2 && + depth !== 1 && + depth !== 16 + ) { + this.error(new Error("Unsupported bit depth " + depth)); + return; + } + if (!(colorType in constants.COLORTYPE_TO_BPP_MAP)) { + this.error(new Error("Unsupported color type")); + return; + } + if (compr !== 0) { + this.error(new Error("Unsupported compression method")); + return; + } + if (filter !== 0) { + this.error(new Error("Unsupported filter method")); + return; + } + if (interlace !== 0 && interlace !== 1) { + this.error(new Error("Unsupported interlace method")); + return; + } + + this._colorType = colorType; + + let bpp = constants.COLORTYPE_TO_BPP_MAP[this._colorType]; + + this._hasIHDR = true; + + this.metadata({ + width: width, + height: height, + depth: depth, + interlace: Boolean(interlace), + palette: Boolean(colorType & constants.COLORTYPE_PALETTE), + color: Boolean(colorType & constants.COLORTYPE_COLOR), + alpha: Boolean(colorType & constants.COLORTYPE_ALPHA), + bpp: bpp, + colorType: colorType, + }); + + this._handleChunkEnd(); +}; + +Parser.prototype._handlePLTE = function (length) { + this.read(length, this._parsePLTE.bind(this)); +}; +Parser.prototype._parsePLTE = function (data) { + this._crc.write(data); + + let entries = Math.floor(data.length / 3); + // console.log('Palette:', entries); + + for (let i = 0; i < entries; i++) { + this._palette.push([data[i * 3], data[i * 3 + 1], data[i * 3 + 2], 0xff]); + } + + this.palette(this._palette); + + this._handleChunkEnd(); +}; + +Parser.prototype._handleTRNS = function (length) { + this.simpleTransparency(); + this.read(length, this._parseTRNS.bind(this)); +}; +Parser.prototype._parseTRNS = function (data) { + this._crc.write(data); + + // palette + if (this._colorType === constants.COLORTYPE_PALETTE_COLOR) { + if (this._palette.length === 0) { + this.error(new Error("Transparency chunk must be after palette")); + return; + } + if (data.length > this._palette.length) { + this.error(new Error("More transparent colors than palette size")); + return; + } + for (let i = 0; i < data.length; i++) { + this._palette[i][3] = data[i]; + } + this.palette(this._palette); + } + + // for colorType 0 (grayscale) and 2 (rgb) + // there might be one gray/color defined as transparent + if (this._colorType === constants.COLORTYPE_GRAYSCALE) { + // grey, 2 bytes + this.transColor([data.readUInt16BE(0)]); + } + if (this._colorType === constants.COLORTYPE_COLOR) { + this.transColor([ + data.readUInt16BE(0), + data.readUInt16BE(2), + data.readUInt16BE(4), + ]); + } + + this._handleChunkEnd(); +}; + +Parser.prototype._handleGAMA = function (length) { + this.read(length, this._parseGAMA.bind(this)); +}; +Parser.prototype._parseGAMA = function (data) { + this._crc.write(data); + this.gamma(data.readUInt32BE(0) / constants.GAMMA_DIVISION); + + this._handleChunkEnd(); +}; + +Parser.prototype._handleIDAT = function (length) { + if (!this._emittedHeadersFinished) { + this._emittedHeadersFinished = true; + this.headersFinished(); + } + this.read(-length, this._parseIDAT.bind(this, length)); +}; +Parser.prototype._parseIDAT = function (length, data) { + this._crc.write(data); + + if ( + this._colorType === constants.COLORTYPE_PALETTE_COLOR && + this._palette.length === 0 + ) { + throw new Error("Expected palette not found"); + } + + this.inflateData(data); + let leftOverLength = length - data.length; + + if (leftOverLength > 0) { + this._handleIDAT(leftOverLength); + } else { + this._handleChunkEnd(); + } +}; + +Parser.prototype._handleIEND = function (length) { + this.read(length, this._parseIEND.bind(this)); +}; +Parser.prototype._parseIEND = function (data) { + this._crc.write(data); + + this._hasIEND = true; + this._handleChunkEnd(); + + if (this.finished) { + this.finished(); + } +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/png-sync.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/png-sync.js new file mode 100644 index 00000000..68cac9bc --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/png-sync.js @@ -0,0 +1,12 @@ +"use strict"; + +let parse = require("./parser-sync"); +let pack = require("./packer-sync"); + +exports.read = function (buffer, options) { + return parse(buffer, options || {}); +}; + +exports.write = function (png, options) { + return pack(png, options); +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/png.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/png.js new file mode 100644 index 00000000..0b8af3f7 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/png.js @@ -0,0 +1,194 @@ +"use strict"; + +let util = require("util"); +let Stream = require("stream"); +let Parser = require("./parser-async"); +let Packer = require("./packer-async"); +let PNGSync = require("./png-sync"); + +let PNG = (exports.PNG = function (options) { + Stream.call(this); + + options = options || {}; // eslint-disable-line no-param-reassign + + // coerce pixel dimensions to integers (also coerces undefined -> 0): + this.width = options.width | 0; + this.height = options.height | 0; + + this.data = + this.width > 0 && this.height > 0 + ? Buffer.alloc(4 * this.width * this.height) + : null; + + if (options.fill && this.data) { + this.data.fill(0); + } + + this.gamma = 0; + this.readable = this.writable = true; + + this._parser = new Parser(options); + + this._parser.on("error", this.emit.bind(this, "error")); + this._parser.on("close", this._handleClose.bind(this)); + this._parser.on("metadata", this._metadata.bind(this)); + this._parser.on("gamma", this._gamma.bind(this)); + this._parser.on( + "parsed", + function (data) { + this.data = data; + this.emit("parsed", data); + }.bind(this) + ); + + this._packer = new Packer(options); + this._packer.on("data", this.emit.bind(this, "data")); + this._packer.on("end", this.emit.bind(this, "end")); + this._parser.on("close", this._handleClose.bind(this)); + this._packer.on("error", this.emit.bind(this, "error")); +}); +util.inherits(PNG, Stream); + +PNG.sync = PNGSync; + +PNG.prototype.pack = function () { + if (!this.data || !this.data.length) { + this.emit("error", "No data provided"); + return this; + } + + process.nextTick( + function () { + this._packer.pack(this.data, this.width, this.height, this.gamma); + }.bind(this) + ); + + return this; +}; + +PNG.prototype.parse = function (data, callback) { + if (callback) { + let onParsed, onError; + + onParsed = function (parsedData) { + this.removeListener("error", onError); + + this.data = parsedData; + callback(null, this); + }.bind(this); + + onError = function (err) { + this.removeListener("parsed", onParsed); + + callback(err, null); + }.bind(this); + + this.once("parsed", onParsed); + this.once("error", onError); + } + + this.end(data); + return this; +}; + +PNG.prototype.write = function (data) { + this._parser.write(data); + return true; +}; + +PNG.prototype.end = function (data) { + this._parser.end(data); +}; + +PNG.prototype._metadata = function (metadata) { + this.width = metadata.width; + this.height = metadata.height; + + this.emit("metadata", metadata); +}; + +PNG.prototype._gamma = function (gamma) { + this.gamma = gamma; +}; + +PNG.prototype._handleClose = function () { + if (!this._parser.writable && !this._packer.readable) { + this.emit("close"); + } +}; + +PNG.bitblt = function (src, dst, srcX, srcY, width, height, deltaX, deltaY) { + // eslint-disable-line max-params + // coerce pixel dimensions to integers (also coerces undefined -> 0): + /* eslint-disable no-param-reassign */ + srcX |= 0; + srcY |= 0; + width |= 0; + height |= 0; + deltaX |= 0; + deltaY |= 0; + /* eslint-enable no-param-reassign */ + + if ( + srcX > src.width || + srcY > src.height || + srcX + width > src.width || + srcY + height > src.height + ) { + throw new Error("bitblt reading outside image"); + } + + if ( + deltaX > dst.width || + deltaY > dst.height || + deltaX + width > dst.width || + deltaY + height > dst.height + ) { + throw new Error("bitblt writing outside image"); + } + + for (let y = 0; y < height; y++) { + src.data.copy( + dst.data, + ((deltaY + y) * dst.width + deltaX) << 2, + ((srcY + y) * src.width + srcX) << 2, + ((srcY + y) * src.width + srcX + width) << 2 + ); + } +}; + +PNG.prototype.bitblt = function ( + dst, + srcX, + srcY, + width, + height, + deltaX, + deltaY +) { + // eslint-disable-line max-params + + PNG.bitblt(this, dst, srcX, srcY, width, height, deltaX, deltaY); + return this; +}; + +PNG.adjustGamma = function (src) { + if (src.gamma) { + for (let y = 0; y < src.height; y++) { + for (let x = 0; x < src.width; x++) { + let idx = (src.width * y + x) << 2; + + for (let i = 0; i < 3; i++) { + let sample = src.data[idx + i] / 255; + sample = Math.pow(sample, 1 / 2.2 / src.gamma); + src.data[idx + i] = Math.round(sample * 255); + } + } + } + src.gamma = 0; + } +}; + +PNG.prototype.adjustGamma = function () { + PNG.adjustGamma(this); +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-inflate.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-inflate.js new file mode 100644 index 00000000..4da0d5f0 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-inflate.js @@ -0,0 +1,168 @@ +"use strict"; + +let assert = require("assert").ok; +let zlib = require("zlib"); +let util = require("util"); + +let kMaxLength = require("buffer").kMaxLength; + +function Inflate(opts) { + if (!(this instanceof Inflate)) { + return new Inflate(opts); + } + + if (opts && opts.chunkSize < zlib.Z_MIN_CHUNK) { + opts.chunkSize = zlib.Z_MIN_CHUNK; + } + + zlib.Inflate.call(this, opts); + + // Node 8 --> 9 compatibility check + this._offset = this._offset === undefined ? this._outOffset : this._offset; + this._buffer = this._buffer || this._outBuffer; + + if (opts && opts.maxLength != null) { + this._maxLength = opts.maxLength; + } +} + +function createInflate(opts) { + return new Inflate(opts); +} + +function _close(engine, callback) { + if (callback) { + process.nextTick(callback); + } + + // Caller may invoke .close after a zlib error (which will null _handle). + if (!engine._handle) { + return; + } + + engine._handle.close(); + engine._handle = null; +} + +Inflate.prototype._processChunk = function (chunk, flushFlag, asyncCb) { + if (typeof asyncCb === "function") { + return zlib.Inflate._processChunk.call(this, chunk, flushFlag, asyncCb); + } + + let self = this; + + let availInBefore = chunk && chunk.length; + let availOutBefore = this._chunkSize - this._offset; + let leftToInflate = this._maxLength; + let inOff = 0; + + let buffers = []; + let nread = 0; + + let error; + this.on("error", function (err) { + error = err; + }); + + function handleChunk(availInAfter, availOutAfter) { + if (self._hadError) { + return; + } + + let have = availOutBefore - availOutAfter; + assert(have >= 0, "have should not go down"); + + if (have > 0) { + let out = self._buffer.slice(self._offset, self._offset + have); + self._offset += have; + + if (out.length > leftToInflate) { + out = out.slice(0, leftToInflate); + } + + buffers.push(out); + nread += out.length; + leftToInflate -= out.length; + + if (leftToInflate === 0) { + return false; + } + } + + if (availOutAfter === 0 || self._offset >= self._chunkSize) { + availOutBefore = self._chunkSize; + self._offset = 0; + self._buffer = Buffer.allocUnsafe(self._chunkSize); + } + + if (availOutAfter === 0) { + inOff += availInBefore - availInAfter; + availInBefore = availInAfter; + + return true; + } + + return false; + } + + assert(this._handle, "zlib binding closed"); + let res; + do { + res = this._handle.writeSync( + flushFlag, + chunk, // in + inOff, // in_off + availInBefore, // in_len + this._buffer, // out + this._offset, //out_off + availOutBefore + ); // out_len + // Node 8 --> 9 compatibility check + res = res || this._writeState; + } while (!this._hadError && handleChunk(res[0], res[1])); + + if (this._hadError) { + throw error; + } + + if (nread >= kMaxLength) { + _close(this); + throw new RangeError( + "Cannot create final Buffer. It would be larger than 0x" + + kMaxLength.toString(16) + + " bytes" + ); + } + + let buf = Buffer.concat(buffers, nread); + _close(this); + + return buf; +}; + +util.inherits(Inflate, zlib.Inflate); + +function zlibBufferSync(engine, buffer) { + if (typeof buffer === "string") { + buffer = Buffer.from(buffer); + } + if (!(buffer instanceof Buffer)) { + throw new TypeError("Not a string or buffer"); + } + + let flushFlag = engine._finishFlushFlag; + if (flushFlag == null) { + flushFlag = zlib.Z_FINISH; + } + + return engine._processChunk(buffer, flushFlag); +} + +function inflateSync(buffer, opts) { + return zlibBufferSync(new Inflate(opts), buffer); +} + +module.exports = exports = inflateSync; +exports.Inflate = Inflate; +exports.createInflate = createInflate; +exports.inflateSync = inflateSync; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-reader.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-reader.js new file mode 100644 index 00000000..213d1a75 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-reader.js @@ -0,0 +1,45 @@ +"use strict"; + +let SyncReader = (module.exports = function (buffer) { + this._buffer = buffer; + this._reads = []; +}); + +SyncReader.prototype.read = function (length, callback) { + this._reads.push({ + length: Math.abs(length), // if length < 0 then at most this length + allowLess: length < 0, + func: callback, + }); +}; + +SyncReader.prototype.process = function () { + // as long as there is any data and read requests + while (this._reads.length > 0 && this._buffer.length) { + let read = this._reads[0]; + + if ( + this._buffer.length && + (this._buffer.length >= read.length || read.allowLess) + ) { + // ok there is any data so that we can satisfy this request + this._reads.shift(); // == read + + let buf = this._buffer; + + this._buffer = buf.slice(read.length); + + read.func.call(this, buf.slice(0, read.length)); + } else { + break; + } + } + + if (this._reads.length > 0) { + throw new Error("There are some read requests waitng on finished stream"); + } + + if (this._buffer.length > 0) { + throw new Error("unrecognised content at end of stream"); + } +}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/package.json b/node_modules/lv_font_conv/node_modules/pngjs/package.json new file mode 100644 index 00000000..1f696a7e --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/pngjs/package.json @@ -0,0 +1,129 @@ +{ + "_args": [ + [ + "pngjs@6.0.0", + "/home/vitaly/Dropbox/Coding/lv_font_conv" + ] + ], + "_from": "pngjs@6.0.0", + "_id": "pngjs@6.0.0", + "_inBundle": false, + "_integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "_location": "/pngjs", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "pngjs@6.0.0", + "name": "pngjs", + "escapedName": "pngjs", + "rawSpec": "6.0.0", + "saveSpec": null, + "fetchSpec": "6.0.0" + }, + "_requiredBy": [ + "/" + ], + "_resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "_spec": "6.0.0", + "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", + "bugs": { + "url": "https://github.com/lukeapage/pngjs/issues" + }, + "contributors": [ + { + "name": "Alexandre Paré" + }, + { + "name": "Gaurav Mali" + }, + { + "name": "Gusts Kaksis" + }, + { + "name": "Kuba Niegowski" + }, + { + "name": "Luke Page" + }, + { + "name": "Pietajan De Potter" + }, + { + "name": "Steven Sojka" + }, + { + "name": "liangzeng" + }, + { + "name": "Michael Vogt" + }, + { + "name": "Xin-Xin Wang" + }, + { + "name": "toriningen" + }, + { + "name": "Eugene Kulabuhov" + } + ], + "description": "PNG encoder/decoder in pure JS, supporting any bit size & interlace, async & sync with full test suite.", + "devDependencies": { + "browserify": "17.0.0", + "buffer-equal": "1.0.0", + "codecov": "3.7.1", + "connect": "3.7.0", + "eslint": "7.8.1", + "eslint-config-prettier": "6.14.0", + "nyc": "15.1.0", + "prettier": "2.1.1", + "puppeteer": "5.4.0", + "serve-static": "1.14.1", + "tap-dot": "2.0.0", + "tape": "5.0.1" + }, + "directories": { + "lib": "lib", + "example": "examples", + "test": "test" + }, + "engines": { + "node": ">=12.13.0" + }, + "files": [ + "browser.js", + "lib/" + ], + "homepage": "https://github.com/lukeapage/pngjs", + "keywords": [ + "PNG", + "decoder", + "encoder", + "js-png", + "node-png", + "parser", + "png", + "png-js", + "png-parse", + "pngjs" + ], + "license": "MIT", + "main": "./lib/png.js", + "name": "pngjs", + "repository": { + "type": "git", + "url": "git://github.com/lukeapage/pngjs.git" + }, + "scripts": { + "browserify": "browserify lib/png.js --standalone png > browser.js", + "build": "yarn prepublish", + "coverage": "nyc --reporter=lcov --reporter=text-summary tape test/*-spec.js nolarge", + "lint": "eslint .", + "prepublish": "yarn browserify", + "prettier:check": "prettier --check .", + "prettier:write": "prettier --write .", + "test": "yarn lint && yarn prettier:check && tape test/*-spec.js | tap-dot && node test/run-compare" + }, + "version": "6.0.0" +} diff --git a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/LICENSE-MIT.txt b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/LICENSE-MIT.txt new file mode 100644 index 00000000..a41e0a7e --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/LICENSE-MIT.txt @@ -0,0 +1,20 @@ +Copyright Mathias Bynens + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/README.md b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/README.md new file mode 100644 index 00000000..d648b879 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/README.md @@ -0,0 +1,47 @@ +# ES6 `String.prototype.codePointAt` polyfill [![Build status](https://travis-ci.org/mathiasbynens/String.prototype.codePointAt.svg?branch=master)](https://travis-ci.org/mathiasbynens/String.prototype.codePointAt) + +A robust & optimized ES3-compatible polyfill for [the `String.prototype.codePointAt` method in ECMAScript 6](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-string.prototype.codepointat). + +Other polyfills for `String.prototype.codePointAt` are available: + +* by [Norbert Lindenberg](http://norbertlindenberg.com/) (fails some tests) +* by [Steven Levithan](http://stevenlevithan.com/) (fails some tests) +* by [Paul Miller](http://paulmillr.com/) (~~[fails some tests](https://github.com/paulmillr/es6-shim/issues/166)~~ passes all tests) + +## Installation + +In a browser: + +```html + +``` + +Via [npm](http://npmjs.org/): + +```bash +npm install string.prototype.codepointat +``` + +Then, in [Node.js](http://nodejs.org/): + +```js +require('string.prototype.codepointat'); + +// On Windows and on Mac systems with default settings, case doesn’t matter, +// which allows you to do this instead: +require('String.prototype.codePointAt'); +``` + +## Notes + +[A polyfill + test suite for `String.fromCodePoint`](https://mths.be/fromcodepoint) is available, too. + +## Author + +| [![twitter/mathias](https://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") | +|---| +| [Mathias Bynens](https://mathiasbynens.be/) | + +## License + +This polyfill is available under the [MIT](https://mths.be/mit) license. diff --git a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/codepointat.js b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/codepointat.js new file mode 100644 index 00000000..f724c892 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/codepointat.js @@ -0,0 +1,54 @@ +/*! https://mths.be/codepointat v0.2.0 by @mathias */ +if (!String.prototype.codePointAt) { + (function() { + 'use strict'; // needed to support `apply`/`call` with `undefined`/`null` + var defineProperty = (function() { + // IE 8 only supports `Object.defineProperty` on DOM elements + try { + var object = {}; + var $defineProperty = Object.defineProperty; + var result = $defineProperty(object, object, object) && $defineProperty; + } catch(error) {} + return result; + }()); + var codePointAt = function(position) { + if (this == null) { + throw TypeError(); + } + var string = String(this); + var size = string.length; + // `ToInteger` + var index = position ? Number(position) : 0; + if (index != index) { // better `isNaN` + index = 0; + } + // Account for out-of-bounds indices: + if (index < 0 || index >= size) { + return undefined; + } + // Get the first code unit + var first = string.charCodeAt(index); + var second; + if ( // check if it’s the start of a surrogate pair + first >= 0xD800 && first <= 0xDBFF && // high surrogate + size > index + 1 // there is a next code unit + ) { + second = string.charCodeAt(index + 1); + if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate + // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; + } + } + return first; + }; + if (defineProperty) { + defineProperty(String.prototype, 'codePointAt', { + 'value': codePointAt, + 'configurable': true, + 'writable': true + }); + } else { + String.prototype.codePointAt = codePointAt; + } + }()); +} diff --git a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/package.json b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/package.json new file mode 100644 index 00000000..cee4e79d --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/package.json @@ -0,0 +1,62 @@ +{ + "_args": [ + [ + "string.prototype.codepointat@0.2.1", + "/home/vitaly/Dropbox/Coding/lv_font_conv" + ] + ], + "_from": "string.prototype.codepointat@0.2.1", + "_id": "string.prototype.codepointat@0.2.1", + "_inBundle": false, + "_integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==", + "_location": "/string.prototype.codepointat", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "string.prototype.codepointat@0.2.1", + "name": "string.prototype.codepointat", + "escapedName": "string.prototype.codepointat", + "rawSpec": "0.2.1", + "saveSpec": null, + "fetchSpec": "0.2.1" + }, + "_requiredBy": [ + "/opentype.js" + ], + "_resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", + "_spec": "0.2.1", + "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", + "author": { + "name": "Mathias Bynens", + "url": "https://mathiasbynens.be/" + }, + "bugs": { + "url": "https://github.com/mathiasbynens/String.prototype.codePointAt/issues" + }, + "description": "A robust & optimized `String.prototype.codePointAt` polyfill, based on the ECMAScript 6 specification.", + "files": [ + "LICENSE-MIT.txt", + "codepointat.js" + ], + "homepage": "https://mths.be/codepointat", + "keywords": [ + "string", + "unicode", + "es6", + "ecmascript", + "polyfill" + ], + "license": "MIT", + "main": "codepointat.js", + "name": "string.prototype.codepointat", + "repository": { + "type": "git", + "url": "git+https://github.com/mathiasbynens/String.prototype.codePointAt.git" + }, + "scripts": { + "cover": "istanbul cover --report html --verbose --dir coverage tests/tests.js", + "test": "node tests/tests.js" + }, + "version": "0.2.1" +} diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/LICENSE b/node_modules/lv_font_conv/node_modules/tiny-inflate/LICENSE new file mode 100644 index 00000000..62914548 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/tiny-inflate/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2015-present Devon Govett + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/index.js b/node_modules/lv_font_conv/node_modules/tiny-inflate/index.js new file mode 100644 index 00000000..44d1151b --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/tiny-inflate/index.js @@ -0,0 +1,375 @@ +var TINF_OK = 0; +var TINF_DATA_ERROR = -3; + +function Tree() { + this.table = new Uint16Array(16); /* table of code length counts */ + this.trans = new Uint16Array(288); /* code -> symbol translation table */ +} + +function Data(source, dest) { + this.source = source; + this.sourceIndex = 0; + this.tag = 0; + this.bitcount = 0; + + this.dest = dest; + this.destLen = 0; + + this.ltree = new Tree(); /* dynamic length/symbol tree */ + this.dtree = new Tree(); /* dynamic distance tree */ +} + +/* --------------------------------------------------- * + * -- uninitialized global data (static structures) -- * + * --------------------------------------------------- */ + +var sltree = new Tree(); +var sdtree = new Tree(); + +/* extra bits and base tables for length codes */ +var length_bits = new Uint8Array(30); +var length_base = new Uint16Array(30); + +/* extra bits and base tables for distance codes */ +var dist_bits = new Uint8Array(30); +var dist_base = new Uint16Array(30); + +/* special ordering of code length codes */ +var clcidx = new Uint8Array([ + 16, 17, 18, 0, 8, 7, 9, 6, + 10, 5, 11, 4, 12, 3, 13, 2, + 14, 1, 15 +]); + +/* used by tinf_decode_trees, avoids allocations every call */ +var code_tree = new Tree(); +var lengths = new Uint8Array(288 + 32); + +/* ----------------------- * + * -- utility functions -- * + * ----------------------- */ + +/* build extra bits and base tables */ +function tinf_build_bits_base(bits, base, delta, first) { + var i, sum; + + /* build bits table */ + for (i = 0; i < delta; ++i) bits[i] = 0; + for (i = 0; i < 30 - delta; ++i) bits[i + delta] = i / delta | 0; + + /* build base table */ + for (sum = first, i = 0; i < 30; ++i) { + base[i] = sum; + sum += 1 << bits[i]; + } +} + +/* build the fixed huffman trees */ +function tinf_build_fixed_trees(lt, dt) { + var i; + + /* build fixed length tree */ + for (i = 0; i < 7; ++i) lt.table[i] = 0; + + lt.table[7] = 24; + lt.table[8] = 152; + lt.table[9] = 112; + + for (i = 0; i < 24; ++i) lt.trans[i] = 256 + i; + for (i = 0; i < 144; ++i) lt.trans[24 + i] = i; + for (i = 0; i < 8; ++i) lt.trans[24 + 144 + i] = 280 + i; + for (i = 0; i < 112; ++i) lt.trans[24 + 144 + 8 + i] = 144 + i; + + /* build fixed distance tree */ + for (i = 0; i < 5; ++i) dt.table[i] = 0; + + dt.table[5] = 32; + + for (i = 0; i < 32; ++i) dt.trans[i] = i; +} + +/* given an array of code lengths, build a tree */ +var offs = new Uint16Array(16); + +function tinf_build_tree(t, lengths, off, num) { + var i, sum; + + /* clear code length count table */ + for (i = 0; i < 16; ++i) t.table[i] = 0; + + /* scan symbol lengths, and sum code length counts */ + for (i = 0; i < num; ++i) t.table[lengths[off + i]]++; + + t.table[0] = 0; + + /* compute offset table for distribution sort */ + for (sum = 0, i = 0; i < 16; ++i) { + offs[i] = sum; + sum += t.table[i]; + } + + /* create code->symbol translation table (symbols sorted by code) */ + for (i = 0; i < num; ++i) { + if (lengths[off + i]) t.trans[offs[lengths[off + i]]++] = i; + } +} + +/* ---------------------- * + * -- decode functions -- * + * ---------------------- */ + +/* get one bit from source stream */ +function tinf_getbit(d) { + /* check if tag is empty */ + if (!d.bitcount--) { + /* load next tag */ + d.tag = d.source[d.sourceIndex++]; + d.bitcount = 7; + } + + /* shift bit out of tag */ + var bit = d.tag & 1; + d.tag >>>= 1; + + return bit; +} + +/* read a num bit value from a stream and add base */ +function tinf_read_bits(d, num, base) { + if (!num) + return base; + + while (d.bitcount < 24) { + d.tag |= d.source[d.sourceIndex++] << d.bitcount; + d.bitcount += 8; + } + + var val = d.tag & (0xffff >>> (16 - num)); + d.tag >>>= num; + d.bitcount -= num; + return val + base; +} + +/* given a data stream and a tree, decode a symbol */ +function tinf_decode_symbol(d, t) { + while (d.bitcount < 24) { + d.tag |= d.source[d.sourceIndex++] << d.bitcount; + d.bitcount += 8; + } + + var sum = 0, cur = 0, len = 0; + var tag = d.tag; + + /* get more bits while code value is above sum */ + do { + cur = 2 * cur + (tag & 1); + tag >>>= 1; + ++len; + + sum += t.table[len]; + cur -= t.table[len]; + } while (cur >= 0); + + d.tag = tag; + d.bitcount -= len; + + return t.trans[sum + cur]; +} + +/* given a data stream, decode dynamic trees from it */ +function tinf_decode_trees(d, lt, dt) { + var hlit, hdist, hclen; + var i, num, length; + + /* get 5 bits HLIT (257-286) */ + hlit = tinf_read_bits(d, 5, 257); + + /* get 5 bits HDIST (1-32) */ + hdist = tinf_read_bits(d, 5, 1); + + /* get 4 bits HCLEN (4-19) */ + hclen = tinf_read_bits(d, 4, 4); + + for (i = 0; i < 19; ++i) lengths[i] = 0; + + /* read code lengths for code length alphabet */ + for (i = 0; i < hclen; ++i) { + /* get 3 bits code length (0-7) */ + var clen = tinf_read_bits(d, 3, 0); + lengths[clcidx[i]] = clen; + } + + /* build code length tree */ + tinf_build_tree(code_tree, lengths, 0, 19); + + /* decode code lengths for the dynamic trees */ + for (num = 0; num < hlit + hdist;) { + var sym = tinf_decode_symbol(d, code_tree); + + switch (sym) { + case 16: + /* copy previous code length 3-6 times (read 2 bits) */ + var prev = lengths[num - 1]; + for (length = tinf_read_bits(d, 2, 3); length; --length) { + lengths[num++] = prev; + } + break; + case 17: + /* repeat code length 0 for 3-10 times (read 3 bits) */ + for (length = tinf_read_bits(d, 3, 3); length; --length) { + lengths[num++] = 0; + } + break; + case 18: + /* repeat code length 0 for 11-138 times (read 7 bits) */ + for (length = tinf_read_bits(d, 7, 11); length; --length) { + lengths[num++] = 0; + } + break; + default: + /* values 0-15 represent the actual code lengths */ + lengths[num++] = sym; + break; + } + } + + /* build dynamic trees */ + tinf_build_tree(lt, lengths, 0, hlit); + tinf_build_tree(dt, lengths, hlit, hdist); +} + +/* ----------------------------- * + * -- block inflate functions -- * + * ----------------------------- */ + +/* given a stream and two trees, inflate a block of data */ +function tinf_inflate_block_data(d, lt, dt) { + while (1) { + var sym = tinf_decode_symbol(d, lt); + + /* check for end of block */ + if (sym === 256) { + return TINF_OK; + } + + if (sym < 256) { + d.dest[d.destLen++] = sym; + } else { + var length, dist, offs; + var i; + + sym -= 257; + + /* possibly get more bits from length code */ + length = tinf_read_bits(d, length_bits[sym], length_base[sym]); + + dist = tinf_decode_symbol(d, dt); + + /* possibly get more bits from distance code */ + offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]); + + /* copy match */ + for (i = offs; i < offs + length; ++i) { + d.dest[d.destLen++] = d.dest[i]; + } + } + } +} + +/* inflate an uncompressed block of data */ +function tinf_inflate_uncompressed_block(d) { + var length, invlength; + var i; + + /* unread from bitbuffer */ + while (d.bitcount > 8) { + d.sourceIndex--; + d.bitcount -= 8; + } + + /* get length */ + length = d.source[d.sourceIndex + 1]; + length = 256 * length + d.source[d.sourceIndex]; + + /* get one's complement of length */ + invlength = d.source[d.sourceIndex + 3]; + invlength = 256 * invlength + d.source[d.sourceIndex + 2]; + + /* check length */ + if (length !== (~invlength & 0x0000ffff)) + return TINF_DATA_ERROR; + + d.sourceIndex += 4; + + /* copy block */ + for (i = length; i; --i) + d.dest[d.destLen++] = d.source[d.sourceIndex++]; + + /* make sure we start next block on a byte boundary */ + d.bitcount = 0; + + return TINF_OK; +} + +/* inflate stream from source to dest */ +function tinf_uncompress(source, dest) { + var d = new Data(source, dest); + var bfinal, btype, res; + + do { + /* read final block flag */ + bfinal = tinf_getbit(d); + + /* read block type (2 bits) */ + btype = tinf_read_bits(d, 2, 0); + + /* decompress block */ + switch (btype) { + case 0: + /* decompress uncompressed block */ + res = tinf_inflate_uncompressed_block(d); + break; + case 1: + /* decompress block with fixed huffman trees */ + res = tinf_inflate_block_data(d, sltree, sdtree); + break; + case 2: + /* decompress block with dynamic huffman trees */ + tinf_decode_trees(d, d.ltree, d.dtree); + res = tinf_inflate_block_data(d, d.ltree, d.dtree); + break; + default: + res = TINF_DATA_ERROR; + } + + if (res !== TINF_OK) + throw new Error('Data error'); + + } while (!bfinal); + + if (d.destLen < d.dest.length) { + if (typeof d.dest.slice === 'function') + return d.dest.slice(0, d.destLen); + else + return d.dest.subarray(0, d.destLen); + } + + return d.dest; +} + +/* -------------------- * + * -- initialization -- * + * -------------------- */ + +/* build fixed huffman trees */ +tinf_build_fixed_trees(sltree, sdtree); + +/* build extra bits and base tables */ +tinf_build_bits_base(length_bits, length_base, 4, 3); +tinf_build_bits_base(dist_bits, dist_base, 2, 1); + +/* fix a special case */ +length_bits[28] = 0; +length_base[28] = 258; + +module.exports = tinf_uncompress; diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/package.json b/node_modules/lv_font_conv/node_modules/tiny-inflate/package.json new file mode 100644 index 00000000..53399e20 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/tiny-inflate/package.json @@ -0,0 +1,60 @@ +{ + "_args": [ + [ + "tiny-inflate@1.0.3", + "/home/vitaly/Dropbox/Coding/lv_font_conv" + ] + ], + "_from": "tiny-inflate@1.0.3", + "_id": "tiny-inflate@1.0.3", + "_inBundle": false, + "_integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "_location": "/tiny-inflate", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "tiny-inflate@1.0.3", + "name": "tiny-inflate", + "escapedName": "tiny-inflate", + "rawSpec": "1.0.3", + "saveSpec": null, + "fetchSpec": "1.0.3" + }, + "_requiredBy": [ + "/opentype.js", + "/unicode-trie" + ], + "_resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "_spec": "1.0.3", + "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", + "author": { + "name": "Devon Govett", + "email": "devongovett@gmail.com" + }, + "bugs": { + "url": "https://github.com/devongovett/tiny-inflate/issues" + }, + "description": "A tiny inflate implementation", + "devDependencies": { + "mocha": "^2.1.0" + }, + "homepage": "https://github.com/devongovett/tiny-inflate", + "keywords": [ + "inflate", + "zlib", + "gzip", + "zip" + ], + "license": "MIT", + "main": "index.js", + "name": "tiny-inflate", + "repository": { + "type": "git", + "url": "git://github.com/devongovett/tiny-inflate.git" + }, + "scripts": { + "test": "mocha" + }, + "version": "1.0.3" +} diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/readme.md b/node_modules/lv_font_conv/node_modules/tiny-inflate/readme.md new file mode 100644 index 00000000..dd8c408c --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/tiny-inflate/readme.md @@ -0,0 +1,31 @@ +# tiny-inflate + +This is a port of Joergen Ibsen's [tiny inflate](https://bitbucket.org/jibsen/tinf) to JavaScript. +Minified it is about 3KB, or 1.3KB gzipped. While being very small, it is also reasonably fast +(about 30% - 50% slower than [pako](https://github.com/nodeca/pako) on average), and should be +good enough for many applications. If you need the absolute best performance, however, you'll +need to use a larger library such as pako that contains additional optimizations. + +## Installation + + npm install tiny-inflate + +## Example + +To use tiny-inflate, you need two things: a buffer of data compressed with deflate, +and the decompressed size (often stored in a file header) to allocate your output buffer. +Input and output buffers can be either node `Buffer`s, or `Uint8Array`s. + +```javascript +var inflate = require('tiny-inflate'); + +var compressedBuffer = new Bufer([ ... ]); +var decompressedSize = ...; +var outputBuffer = new Buffer(decompressedSize); + +inflate(compressedBuffer, outputBuffer); +``` + +## License + +MIT diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/test/index.js b/node_modules/lv_font_conv/node_modules/tiny-inflate/test/index.js new file mode 100644 index 00000000..f4e8c88f --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/tiny-inflate/test/index.js @@ -0,0 +1,75 @@ +var inflate = require('../'); +var zlib = require('zlib'); +var fs = require('fs'); +var assert = require('assert'); +var uncompressed = fs.readFileSync(__dirname + '/lorem.txt'); + +describe('tiny-inflate', function() { + var compressed, noCompression, fixed; + + function deflate(buf, options, fn) { + var chunks = []; + zlib.createDeflateRaw(options) + .on('data', function(chunk) { + chunks.push(chunk); + }) + .on('error', fn) + .on('end', function() { + fn(null, Buffer.concat(chunks)); + }) + .end(buf); + } + + before(function(done) { + zlib.deflateRaw(uncompressed, function(err, data) { + compressed = data; + done(); + }); + }); + + before(function(done) { + deflate(uncompressed, { level: zlib.Z_NO_COMPRESSION }, function(err, data) { + noCompression = data; + done(); + }); + }); + + before(function(done) { + deflate(uncompressed, { strategy: zlib.Z_FIXED }, function(err, data) { + fixed = data; + done(); + }); + }); + + it('should inflate some data', function() { + var out = Buffer.alloc(uncompressed.length); + inflate(compressed, out); + assert.deepEqual(out, uncompressed); + }); + + it('should slice output buffer', function() { + var out = Buffer.alloc(uncompressed.length + 1024); + var res = inflate(compressed, out); + assert.deepEqual(res, uncompressed); + assert.equal(res.length, uncompressed.length); + }); + + it('should handle uncompressed blocks', function() { + var out = Buffer.alloc(uncompressed.length); + inflate(noCompression, out); + assert.deepEqual(out, uncompressed); + }); + + it('should handle fixed huffman blocks', function() { + var out = Buffer.alloc(uncompressed.length); + inflate(fixed, out); + assert.deepEqual(out, uncompressed); + }); + + it('should handle typed arrays', function() { + var input = new Uint8Array(compressed); + var out = new Uint8Array(uncompressed.length); + inflate(input, out); + assert.deepEqual(out, new Uint8Array(uncompressed)); + }); +}); diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/test/lorem.txt b/node_modules/lv_font_conv/node_modules/tiny-inflate/test/lorem.txt new file mode 100644 index 00000000..c37b0a59 --- /dev/null +++ b/node_modules/lv_font_conv/node_modules/tiny-inflate/test/lorem.txt @@ -0,0 +1,199 @@ +Lorem ipsum dolor sit amet, sea pertinax pertinacia appellantur in, est ad esse assentior mediocritatem, magna populo menandri cum te. Vel augue menandri eu, at integre appareat splendide duo. Est ne tollit ullamcorper, eu pro falli diceret perpetua, sea ferri numquam legendos ut. Diceret suscipiantur at nec, his ei nulla mentitum efficiantur. Errem saepe ei vis. + + Per melius aperiri eu. Et interesset philosophia vim, graece denique intellegam duo at, te vix quot apeirian dignissim. Ei essent percipitur nam, natum possit interpretaris sea ea. Cum assum adipisci cotidieque ut, ut veri tollit duo. Erat idque volutpat mea ut, mel nominati splendide vulputate ea. + + No ferri partem ceteros pro. Everti volumus menandri at pro. Cum illud euripidis cu, mazim deterruisset ei eum. Ex alia dolorem insolens per, malis clita laboramus duo ut, ridens appareat philosophia ea quo. + + Vix elit tantas phaedrum et, ea quo vide facete scriptorem. Ut facer laboramus definitiones has, viris dictas regione at eos. Noluisse constituto vix at, nec malorum rationibus te. Nec numquam definiebas id, vim id liber munere. Simul discere reprimique qui eu. Et aeterno aperiri disputando vix. + + Usu ne denique albucius gloriatur. Pri in novum electram, ei amet electram quo. Vix summo recusabo dissentiunt no. Natum mediocrem maiestatis ut eum, vocibus nominavi has in, affert civibus te nam. Cibo minim ex nec. + + Ipsum tibique deseruisse vel ut. Ad laudem iracundia eam. Eum id legere scripta nominavi, vim melius ceteros et, ea tale enim nec. Aeque facete signiferumque ne est, vis ei sint persequeris. Ei magna veritus nec, enim aliquando ex pro. + + Verear noluisse qui eu, id mutat possit nec. Ad est melius placerat, soluta facilisi et vel. Id his simul consul praesent, no nam nihil perfecto, quo in mucius corrumpit. Assum ceteros cotidieque et nam, illum quaeque cum et. Numquam laoreet his at, in vis suas quando suscipit. + + Ne graece efficiendi interpretaris cum. In vide dictas cotidieque sed. Ea est augue vidisse. Eu epicurei salutandi est, etiam oratio imperdiet ex his. Te pri novum meliore sensibus. An nec eius dicant, iracundia consectetuer per ex. An iuvaret meliore constituto eam, latine detraxit mea eu, mel eu aperiri moderatius. + + Per commodo virtute eu. Eius euripidis nam eu, cum ne praesent vituperata, vix at integre verterem posidonium. Eu adhuc labores eam, temporibus reformidans eos id. Feugiat labores nam ne, eu sed nostro veritus, mea in consul evertitur repudiandae. Cu eos detracto voluptaria consetetur. + + Sit vidit mundi offendit ad. Usu semper vivendo eloquentiam eu. Nonumes deleniti ex vim, fabulas forensibus ad eam. Mazim admodum petentium sed et, has eu eirmod eruditi laoreet. An eam hinc erant. Et semper accumsan similique cum. Sea at inani error. + + Te commodo delicata abhorreant cum, iusto lucilius ut sed, ea his evertitur scripserit. Te eligendi scriptorem sit. Mel quodsi meliore dissentias ei, cum deserunt moderatius ex, error sanctus adversarium eam et. Eam et appareat placerat tincidunt, ius an quidam putant delenit, utinam partem quo ea. Mei legendos constituto scribentur eu, usu ea consul facilisis. Purto veritus lobortis te sea. + + Ne sea ludus solet decore, in cum erat dicta labitur. Doctus maluisset scripserit qui in. Qui at postea audiam, has ut aperiam dissentiet, vix ut harum nemore integre. Sit ad lucilius deseruisse, iuvaret percipit in pro. Sed referrentur voluptatibus ne, his ei nullam omnesque aliquando. + + Ius te purto augue fierent, mea et decore feugiat definiebas. Dolor abhorreant deseruisse at sit, corpora tacimates duo ut. Dicta equidem ne has. At pri elit magna vocibus, ipsum vidisse definiebas id cum. Ex pri laudem assentior. + + Aliquip referrentur id vim, sea labores nominati recteque id. Torquatos adversarium no mel, pri eu impedit fastidii, in usu tollit electram. Ad esse facer nec, mel id laudem adipisci, pri eu vocent efficiantur. Cu est posse possit. Vis definiebas neglegentur ad, pro facer sanctus propriae id. Sit epicurei comprehensam signiferumque ea, ad eam vero admodum scaevola. + + Nec an vivendo ocurreret, qui id nihil fastidii ocurreret. Vis ei mazim zril, dictas aperiri aliquando per ad, sit ne harum postea appellantur. No munere periculis reprimique duo, ut nam unum animal repudiandae. Cibo vocent dissentias te mea. Sea te elit denique volutpat, nec ne epicuri mentitum delicata. + + Ius eros aeterno torquatos ex, et per nisl accusata. Ne nam nonumy discere, in vim nemore commune. Cu pri nibh pertinacia assueverit, duo percipit definitionem ne, delectus voluptaria et vix. Mutat appellantur mea ad, ad pericula suavitate nam. Primis rationibus mei eu. Ad dolorem verterem deserunt eam, in decore probatus consulatu sed. Quo ut ullum epicuri. + + Quod contentiones ea quo. Et nihil conclusionemque mel, offendit perfecto eam eu. Suas case nam et. Ex elit doctus civibus mei, eam facilis scaevola ne. His elit scripta constituto eu, mea tantas petentium ut. + + Eos dicam intellegam id. Cum omnes concludaturque ea. Qui in semper legendos intellegebat. Option mediocrem eam id. Doctus principes deterruisset pro ad, qui ea fugit populo repudiare. + + Sed ne summo percipitur. Eu porro persius vix, an vix esse fastidii delicatissimi, elit rationibus dissentias cu eam. Te dolorem scriptorem mei, no mei aeque tibique. Iisque molestie ei ius. + + Ius eu vide salutatus. Ut modus errem nam, latine vocibus referrentur an has, his laudem docendi at. Mei in enim aeterno, sea id dolores placerat signiferumque. Mea modo semper maluisset at, vel te magna eruditi. Eam in verear tacimates concludaturque. Nam cu porro mediocritatem, platonem splendide in has, dicit dolor populo mei et. Melius saperet tacimates nam an, mentitum fabellas assueverit duo id, sea an nihil tritani inciderint. + + At vim option nominati quaerendum. Dolor vituperata dissentiunt eu nec, an modo appareat suscipiantur sea. In vim suas eligendi sadipscing. Ne nihil molestie ius, sumo aperiri argumentum sit ea, eius principes te sit. Eam no fugit mazim alterum. Sed ignota sententiae in. At pro illud reprimique. + + At nec vivendo luptatum, vel congue oratio cu. Audire sententiae ius an. Antiopam scripserit et duo, illud commune te his. Posse disputationi vix in, quod oporteat id eam. Legimus admodum docendi cum at, cu postea deserunt periculis per. + + Sonet clita ponderum sea ei, est ex semper fabellas. Te vix elitr congue phaedrum, ea eum argumentum eloquentiam. Te elitr suavitate definitionem eum. Est inciderint comprehensam cu, ei ius dolor dignissim assueverit. + + Eos ut dolorum albucius placerat, mel erat mentitum cu. Nec copiosae periculis in, ex vix everti consectetuer. Pro at dicat utroque, iracundia sententiae in usu, sit no cibo natum alienum. Duo audiam placerat ei. Cu mel philosophia disputationi. + + Eam fierent maiestatis instructior no, cu sea iusto iriure voluptatum. Vide numquam vivendo ex est, an vel nulla similique posidonium. No mea tritani molestiae, pro te placerat sensibus. Mel ad posse mucius vocent, te populo timeam democritum mel. + + Pri suscipit luptatum eu, et persius accumsan argumentum sea. In paulo tempor possit sea. Vidisse viderer ut vix, vix ex hinc solet. Altera principes qui in, est utroque scaevola eu. + + Lobortis gubergren mediocritatem ne has, te eum adhuc pericula vulputate, cu antiopam posidonium percipitur mei. Mandamus sapientem et sed. Ea dicta quodsi aperiri sea, ad putent posidonium sea. Duo et molestie erroribus, facete gloriatur eam te, ne eam accusam interesset instructior. Eam posidonium reformidans at. Duo menandri pericula te, qui accumsan facilisi expetendis ut. Et eum dicunt persius pertinacia, vis recteque eloquentiam te. + + Habemus blandit at pri, ad quando consequat per. Mea id adhuc dolores definitionem, in platonem mediocrem abhorreant per, vis cu hinc harum qualisque. Et purto lorem intellegam vel, summo apeirian cum no. At vel nonumy volutpat quaerendum. Ludus fastidii fabellas ut eum, no nec assum homero sanctus. + + His at doming forensibus honestatis, et nostrum praesent eum. At dolores patrioque sententiae eos, quaeque corpora qui no. Nec postea senserit persecuti ei, ad sea invenire voluptatibus, ne veniam lobortis repudiandae mea. Et nam integre democritum. Ex populo menandri qui. Ei duo aeterno accommodare, nibh ponderum iudicabit id vel, cu eos platonem postulant omittantur. + + Mollis molestie nam an, duo an option patrioque posidonium. Putant luptatum aliquando mea ei. Ut quas semper perfecto mei, ei ius justo possit epicurei. Cum odio omnium eu, gubergren definitionem eum ex. Tempor definitionem pri at, ad sit nihil postea moderatius. + + An cum verear scribentur, duo et purto tempor euripidis. Aeterno postulant ut eam, no diam inermis argumentum eos, posse blandit incorrupte ne pri. Est commodo laoreet conclusionemque ei. Graeci tacimates ei eum. Ad nec forensibus voluptaria, mea in alii putent, sed facer ceteros ad. + + Vidisse vivendo placerat duo ex, pri malorum definitionem ex. Decore virtute accumsan cum ad, ex eum postea putant euismod. Mei id alia populo democritum, mei ea adhuc posse. Et primis hendrerit signiferumque nam, ei vis modo lobortis recteque. Saepe persius aliquid et sit, vel everti feugait ne. + + Qui ex laoreet argumentum temporibus, agam tacimates intellegam ad qui. At wisi perfecto has, an sit viris definitionem. No quo sint tincidunt, vim adhuc expetenda ut, nam ad everti expetendis. Adhuc soleat doming nec eu, dicta summo sapientem mei te, nostro mediocrem dignissim ex eos. Id alienum mediocritatem mea, te error iriure placerat pri, feugiat platonem vituperatoribus mea te. + + Quo elit legere consequuntur in, in fabulas ancillae mea, sed scripta mentitum tacimates ei. Mazim phaedrum interpretaris et per, te patrioque assueverit vim. Populo commodo imperdiet vix ut. Mea modo bonorum ut, cu pri enim posse efficiantur. + + Sea veri ancillae adipisci te, quot tota modus ad sea. Suas malorum per ex, mazim rationibus ad vix. Ius ullum deserunt id. Quas corrumpit constituam no mei. Ut quo deleniti atomorum omittantur. Clita feugait docendi ei cum, no mea modo menandri, nam mundi doming an. No altera commodo pri. + + Vel fastidii convenire ex. Vim soleat maluisset te, et expetendis sadipscing liberavisse has. Per sale facilisis accommodare in. Cu option incorrupte nec. + + An eam vocibus intellegat, possit aliquid ex eum, tale mentitum oportere at duo. Ad eam audiam consectetuer, eu meliore verterem mediocritatem has, nam etiam reprimique ut. Nullam adipisci mei in, ad duo quem simul veniam. Vis at tempor sententiae. Te essent iisque aperiam vis. + + Te vix etiam quando ullamcorper, mel ei offendit iudicabit necessitatibus, usu assum facilisi sensibus ex. Alterum adversarium vis ad. Odio fierent deleniti ex cum. Quo an bonorum inciderint, harum simul maiorum in mel, ex legimus alienum corrumpit has. Pri soluta lobortis adipiscing te, eos clita ponderum mandamus at, elit meis assum mea in. Tale intellegebat cu vim, facilis expetenda democritum duo te, cu his causae dissentiet liberavisse. + + Primis latine epicurei no mea. Nam ad quis putant everti, no fugit minimum disputando vix. Laudem neglegentur te qui. Vel splendide efficiendi at, sed liber urbanitas no. Id his atqui inermis scriptorem, vituperata adversarium eos cu, pro in movet accommodare. + + Munere indoctum eu duo. Id eam duis voluptua expetenda, et prodesset inciderint ius. An nibh elitr deseruisse usu. Idque copiosae nam ea, ne tempor omittantur definitionem vis, et cum sumo principes. Quo rebum viderer minimum ex, est at melius blandit. + + Ut adolescens definitiones sed. Est ut erant legendos, in quo facilis salutatus. Agam expetenda salutatus ut sit, quo ex fuisset repudiandae, tale probo aliquip mea id. No vix diceret scaevola. Posidonium conclusionemque ut est. Quo porro menandri assentior ei. + + Vocent neglegentur intellegebat sit id. Et ullum accusam sea, ex nam aeque ubique ocurreret, ea cum quidam euismod. Eam ne zril alienum. Mea id veritus alienum. Mei simul appareat nominati no. Cum option accumsan ea, ne eos legimus dissentiunt. + + Everti prodesset scripserit ea cum, eam nostrud adolescens deterruisset an. Deseruisse definiebas eos ne, mel ex nisl meliore consulatu, per te scripta gubergren. Vix cu novum admodum recusabo, te omnes similique efficiantur nec. Suas novum semper duo ex. Vocibus cotidieque cu qui, at sale malorum intellegam mel. Quo errem accumsan ullamcorper cu, eu quo liber quidam conceptam. + + Scripta habemus quaestio id usu. Id vis utroque forensibus, cu simul fabulas efficiantur vis, ad mel cibo quas feugait. Eros expetendis in cum. No cum aeterno menandri consetetur, quo ex alterum probatus. Eu audire tritani ius, ei labore commune detraxit est. Unum sapientem cotidieque ei vel, ullum prompta per ex. Recteque persequeris quo ei, volutpat quaerendum ex sea. + + Qui phaedrum dissentiunt ne, sea debet fuisset ut. Ridens virtute pro an. Eos at stet modus iisque. Usu ea esse sententiae, deleniti salutandi ne sit, semper graecis sensibus vis ne. Ea corrumpit assueverit mel, in ignota quodsi nominati vix. + + Sea equidem vivendo ut, sed cibo nusquam id, nostro integre te mei. Etiam tractatos et duo, ut ludus dolore pri. Eius hendrerit cu vel, quo ei quodsi causae ullamcorper, per cibo nihil at. Ea voluptatum incorrupte duo, dolore debitis no usu. Ei clita concludaturque cum. Quo quas quando persecuti cu, eruditi scripserit cum eu. + + Ex eleifend philosophia has. Sint repudiandae in sea, et prima latine persecuti eum, pro ea everti expetendis. At his stet facete minimum, sed in delenit maiestatis. Cum ei omittam contentiones, suas sale melius ei cum, mel id vocent propriae necessitatibus. Vix amet nibh ea, autem movet ne vel. + + Accusata percipitur ut vim. Decore tritani scriptorem vis eu, vis volutpat reprehendunt ea. Postea inciderint vix an, no dicunt tamquam mel, usu id illud sensibus expetendis. Ex his aliquip blandit appellantur. + + Officiis evertitur ut mea, cu illud omnesque scripserit eam. Simul vituperatoribus an eum, mel quidam disputando cu, nibh probatus consequat ei mei. Eam populo appareat inimicus ei. Ne officiis definitiones his, vis vero simul similique ei. Iudico oratio elaboraret vim et, id posse nemore eirmod has. + + Pro in veniam consul expetenda, an est movet consequuntur, ex ius admodum recusabo ullamcorper. Mei an utroque ceteros singulis, id eum iudico latine. At est reque lorem intellegat. Quando corpora qui ea. Quo vocent salutatus id. + + Wisi ignota concludaturque est ex. Mea ad inermis vituperatoribus. Dolor persius inimicus ad nec, etiam dignissim qui ex. Ridens quodsi sed ex. Pro in etiam antiopam, eos graece eripuit ad. Affert soluta mei te, pri illud graecis id, vel sint vivendo at. Mea eu veri dicta offendit, cu has sint copiosae. + + Duo libris salutatus ad, porro principes mel ex. Nec iudico consectetuer no. Ut pri causae qualisque democritum. Habeo homero iuvaret at mei. Atqui aliquam eu mea. Gubergren delicatissimi vim ad, amet quodsi efficiendi te ius. Everti latine vulputate pro te, in nam falli definiebas, duo harum graeco nusquam no. + + Per ne aeterno appareat, melius verear tamquam eam id, mei ei invidunt atomorum. Eu doctus viderer eam, sea eu possit dolorem appetere, efficiantur necessitatibus mel cu. Ad est suas officiis, assum erant eum cu, eum erat vitae te. Habemus scaevola no per, nominati adipiscing et eam. + + Ei stet quidam scaevola quo, ius cu noster officiis. Has dolorum vulputate voluptaria ea, ei ius audiam liberavisse. Duo in accumsan constituam, sea esse deseruisse ad. Ne eam eligendi sensibus. Ea rebum porro interesset sed, te alii tritani singulis vis, vel enim liber ne. Movet numquam salutatus eu vim. Nam ad deleniti interpretaris conclusionemque. + + Ea offendit apeirian reprimique ius, accusam incorrupte voluptatibus ei duo. Hinc ponderum detraxit vel te, has no labore regione. Sint impetus duo ex, cu has liber soluta fierent. Vix ut cibo mollis deseruisse. + + Cum id tale disputationi, usu adhuc tritani ea. Id vim volumus quaerendum delicatissimi, no vix dolorum legimus corpora, justo dicit id duo. Ancillae concludaturque at usu. Vim diceret singulis incorrupte eu, oratio nullam quo et. Et sea mediocrem vituperatoribus, fugit tacimates deterruisset cum et. Mel ne sale soleat, vim ad labitur equidem, eos ea justo noluisse. + + An nam consectetuer necessitatibus, eos ei mazim persecuti. Libris explicari dissentiunt te vis, te per veniam sadipscing. Ut diceret euismod vix, duo discere inermis ea. No sit veri sensibus cotidieque, inermis sadipscing reprehendunt qui ad, elitr referrentur repudiandae eu est. Vis quem probo postulant no. Ad viris tollit ullamcorper pri, congue discere ad usu. + + Eam te cetero reprehendunt. His ad ferri feugiat invenire, oratio indoctum id pro. Errem omittam sed et, est no quando omnesque platonem, sed quis philosophia ei. Vix ut porro aeque habemus, eu eam consul nominati omittantur. + + At pri everti indoctum, ullum adipiscing instructior qui ad. Usu ignota omittam ex. At audire vocibus pericula vix, usu ex reque feugait. Ne sea electram salutandi moderatius, te qui verterem scripserit adversarium, an sed tantas lobortis intellegat. + + Ad quas suscipit atomorum duo, quo saepe maiestatis eu. Nostro expetenda ea usu, in atqui doming eam. Vis utroque consulatu ne. Zril noster scripta in eum, vim ad dicam facete legendos, et sit civibus consequat. Eum autem periculis ex. Sed ne nemore eligendi, his legimus verterem ad. + + Est ad amet possit latine. Sed ne legere populo, has pericula scribentur voluptatibus eu. Usu in nonumy vituperata. Aeque oratio gubergren mea ad, quo eu debet dolorum contentiones. Veri expetenda ex mel, eu veniam apeirian vis, aeterno debitis id his. + + Ei ius consul nonumes. Id qui porro periculis, quando dolore iisque qui id. Quo ex simul convenire, vix ei erat petentium, mea cu clita causae. Tale facilisis ex pri, vim ne vide laudem mnesarchum. Duo option blandit ex. Eu est modus vitae, nam in latine maiorum. + + Est an quis quaeque disputando, sit an postulant expetenda, dolor erroribus consequuntur ad vel. Ius phaedrum cotidieque ei, omnis persius copiosae eu vim. Pri facer consequat eu, esse copiosae facilisis mei ne. Ubique convenire no sit. + + Nec regione prompta no. Et quo recteque concludaturque, et ius nostro mollis regione. Ad aliquid lucilius scriptorem eam. Sit at summo eligendi omittantur, ius ea paulo option referrentur. Rationibus inciderint mediocritatem sea id, in dicant assentior sea, quidam copiosae reprehendunt in usu. Cu appetere scripserit vix. Mea mollis audiam aliquam ea, te qui adhuc nonumes deserunt. + + Duo ad facilis consequuntur, vis vide mutat in. At inani ludus eam, an sit quod primis, ea est integre consetetur. Has dolorem salutandi te, tota doctus sit ne, cum ex minimum convenire. Ne legere deterruisset vel, partem phaedrum ne pro, vim modo facete fabellas ex. Cu vix possim eleifend posidonium. Ne est sumo impetus. No quo ubique neglegentur, in usu aliquando scripserit reformidans. + + Vix et numquam expetendis. Ei quas senserit vel, ne has placerat conclusionemque. Noster perpetua euripidis ex sit. Ex mel vidit nonumy vituperata, duo ad nostrum liberavisse. Primis signiferumque duo te, ius te hinc aeque laoreet, ne eius voluptua pri. Dico eros copiosae sed ei, duo cu laudem propriae gubergren. + + Unum erant oratio duo cu, qui no audiam fabulas ornatus. Nihil omnium offendit ad cum, ea ius inermis appetere nominati. Ei nam vero oratio corrumpit. Atqui voluptatibus mei id, at duo assum nostrud aliquando. At nec laudem ridens phaedrum. Dicunt qualisque eum an, ex natum persecuti adipiscing vix, tritani consulatu persecuti nam id. + + In pro mundi percipit, eum tibique eloquentiam in, mea illud ullum altera ex. Veniam epicuri ex mea, quot eruditi definiebas eu duo. Vel augue regione consectetuer ei, appetere moderatius eos in. Laoreet lucilius vim eu. At oratio eirmod qui. + + Altera labitur qui ei, in eam libris primis. Eirmod audiam te vel, eu mei case vide ponderum, an principes persecuti neglegentur mei. Ferri vulputate instructior no vix, in est vidisse detraxit molestiae. Te sit quot choro adipisci, no labore indoctum deterruisset cum. Elit ancillae appetere usu at, mundi dissentias te quo. Mentitum erroribus ad pri. + + Usu ei vero possit appetere. Id erroribus constituam quo. Sit id quidam pertinacia, epicuri delicata eu has, debet melius evertitur ut sed. Id est alienum voluptua. Sit eu dico discere accusata, cu mazim viderer numquam usu, has an solum pertinax. Vel natum summo te, mel integre perfecto consetetur et. + + Postea luptatum menandri cu has, no nam neglegentur necessitatibus. Deseruisse reprehendunt ne mea, lorem tollit nonumes ne vim. Eu pro amet populo, omnesque ponderum sadipscing et ius, ad debet consequat dissentiet vix. Ius nulla aliquip complectitur et, id sed repudiare necessitatibus, et erant legimus invidunt vel. Magna labore democritum vis ei. + + Ne has consulatu reprehendunt, ad nam nulla integre admodum, no has everti impedit perpetua. Tempor antiopam dissentias pro et, ex per legere electram. Ut ius brute omnesque consequat, an his dissentias persequeris. Dicta ludus tritani eam ei. + + Minim rationibus et usu, eam in elit senserit. Stet harum qualisque eu has. Cu decore nostrud sit, mea magna iracundia te, vix tritani convenire imperdiet at. Te pri dictas appetere, brute velit ius ad. Veri dicit legere pri at, sea tation fierent molestie eu. Eum et paulo consul. + + Veri iusto mei id, reque invidunt ne his, te agam dolore electram nam. Est ullum oporteat facilisi eu, dicunt officiis ad eos. Vis ut populo similique. Et minim platonem percipitur usu. Et usu saperet alienum consequuntur, per ad luptatum concludaturque, cibo duis definitionem vix an. + + Ancillae iracundia eu vix, mazim conceptam no qui. Sit nihil epicuri voluptatibus in, an sale debet vis. Porro congue senserit quo an. Facer constituam vel no, facete pertinacia adolescens pro ut, errem nullam menandri ius an. Ex tractatos periculis interpretaris eum, vim ornatus patrioque an. Ius magna iudicabit reprehendunt at, ea viris ornatus sit, eum in vidisse percipitur. + + Vel agam interpretaris ex, zril deserunt electram in duo. Eu eam vero atqui maiorum. Ne pri alia meis decore, vis ea expetenda dissentiet. Cu quis evertitur intellegat cum, ea sed posse expetendis liberavisse, sed ne quis deseruisse. Solum ignota causae ad quo, ferri erant est id, vim ut choro liberavisse. Ne nam causae reformidans, possit percipitur id has. Case tota id has, pri tation ancillae sensibus te. + + Posse aperiam sit id, est in dicam iracundia. Eum mollis dolores te, at vis laoreet habemus fuisset. Vim nibh civibus signiferumque eu. Fierent reformidans an quo. Exerci dissentiunt ex pri, per illum debitis et. + + Vel tota exerci facilis eu. Est primis atomorum id. Hinc insolens dissentiet duo et, iusto inermis salutatus vel ne. Corpora propriae vituperata ex est, sit quod ignota deterruisset no, elitr tempor suscipit ex nam. Eripuit repudiandae his cu, fabulas inermis accumsan ei eum. Nisl molestie ex pri, sea veniam graecis expetenda eu. + + Vel possim moderatius cu, sea no dicit aliquip erroribus, eam te debet partem. Sea no graeco vocent probatus, qui ut fugit delectus definiebas. Id soluta viderer per, tritani disputando necessitatibus ad vix. Eleifend facilisis percipitur at pri. Sit dicat adipisci ex, mundi solet doming nec te, ne maiorum tractatos evertitur sit. At velit propriae eos, mel alii invenire id. + + Ei per minim voluptaria, ea mel scaevola mediocrem euripidis. Et quo wisi sonet eirmod, consul tritani delenit vis id, per id mutat facer. Accusam patrioque eu cum, te cum quot saepe scribentur. Eros summo mnesarchum sed ut, ad possit nostrum comprehensam ius. + + In elitr nullam eam, cu mea mentitum omnesque. Ex has wisi consequat. Sed simul offendit argumentum no, solet corpora lucilius cu vis. Nec ut legimus suscipit, ne usu mucius latine, nusquam mentitum perpetua ad vel. Vidisse feugait facilisis an sea. + + Mel ne oblique menandri, nam graecis antiopam id. Id sanctus referrentur contentiones eam, et eam purto consequat, te vix quod tantas bonorum. Fabellas sapientem consulatu eu per. Iudico possim per ei, eu vide adversarium per. Usu vero essent albucius et, nec veritus ancillae prodesset eu, ipsum probatus cu sea. In quod tantas iudicabit eam, his et odio augue iuvaret. + + Eum an iisque oportere dignissim, est convenire molestiae interesset at, eam id dico audiam quaerendum. Est prompta ocurreret adversarium id, sumo legendos iracundia pri ea, quo ei agam aeque. Ei vix omnes conclusionemque, nec ut novum urbanitas honestatis. Sumo moderatius eos no, ea saperet impedit petentium vel. Vim in harum blandit, posse gubergren deterruisset ex ius. Ei error menandri platonem duo, partiendo qualisque nam te. + + Erat nobis maluisset at sed. Ius no dolor soluta. Has ne sumo brute suavitate, his ne viris aeterno omittantur. Te usu delenit philosophia. Mei wisi libris deseruisse eu. + + Tantas constituam per ei, doctus timeam ea vim. Quo ei putent delicata, eu quo aeterno labores. Qui liber eripuit singulis te, qui diam appareat similique ex, aliquip feugait noluisse cu vis. Dicant recteque definitiones ex mea. Cu exerci dictas eleifend duo, id nonumes denique vix, pri magna facer saperet ei. + + Aeque quodsi partiendo in est, partem malorum intellegebat et per, mea no porro ipsum oratio. Mel sale meliore fuisset ad, per aeque aperiam nominavi ex. Ad tantas meliore placerat mel, mel in prompta torquatos, nulla mollis tamquam pri cu. An quo noster intellegat. Utroque antiopam similique ius eu, at duo ullum apeirian reprimique, vide quaeque assueverit eam ea. + + Mel no quod viris latine, no platonem dissentiunt ius, quis amet gloriatur no eos. Ei mea ancillae probatus. Ad molestiae moderatius vim. Ex soluta meliore molestiae has, ne sea quas natum. Movet verterem vis no, vis amet homero an. Ius dicta tantas id. + + Pri ut modus disputationi, iudico ignota commune eam ut. Vim integre eripuit appareat in, malorum inermis perpetua his cu. Altera alienum mediocrem eu vis. Omnis honestatis repudiandae in qui, his quis duis te, novum rationibus cu est. Eos ut dicant molestie, pri argumentum quaerendum adversarium te. Ea cetero deserunt conceptam pri. + + Augue utroque iudicabit ei vim, ea per dico debet. Cu per regione feugait, sensibus necessitatibus cu cum, at mundi aliquando per. In cum latine evertitur definitionem. Sit movet aperiri liberavisse ad, pro paulo veniam eu. Vel ut cotidieque definitionem, adhuc movet intellegebat quo ne. + + Eum deleniti gubergren an, dolor utamur omittam ea ius. Eam movet possim accommodare et. Ius cu malorum tibique, ludus eligendi id pro. Solum vulputate efficiendi ex quo. + + Regione fierent eu per. No amet iuvaret efficiantur usu. Sit minim sensibus no, his principes gloriatur adversarium no. Ubique definitionem ne vis, vis propriae intellegebat te. Tibique suscipiantur his no. + + Eu quo elit cetero scripserit, maiestatis scripserit pri in, vim cu tibique eligendi. Ne tritani gubergren vituperatoribus quo, melius facilisi ne has. Id pro vivendo fuisset, ex causae utroque deleniti mea. Ad eam percipit perfecto, mutat justo essent at vim. Eros novum duo et, in pri melius blandit. Affert albucius eos ad. Singulis mnesarchum ea mel, eos dictas nominavi reprimique no. + + Ei qui congue voluptatibus. Nam no tritani elaboraret. Conceptam rationibus expetendis duo no. Vivendo reprimique cum te, qui timeam copiosae id, modo delicata ius ad. Nam cu dico aliquip, eu pro legimus officiis delicata, fugit quando ea per. + + In eos equidem accommodare. Electram principes ad usu. Nec adversarium disputationi in, sed feugait lucilius ut. Ludus hendrerit cu ius. + + Cum suscipit gloriatur ea. Id aeterno principes euripidis nam, mea id probo graeco verterem, vulputate ullamcorper definitionem in pri. Mel cu detraxit assueverit. Quo et posse fastidii, ei ponderum delicata sed, has brute forensibus ut. Quo option pericula ea, nam gubergren assueverit te. + + Graece doming intellegebat duo in, ullum clita expetenda nam at. Diam alienum menandri sit id. Unum clita consulatu duo id. Per in mucius legendos scribentur, sea quod phaedrum ut, facete animal dissentias no usu. Quo te dico suavitate. Est te erant congue vivendum, oporteat forensibus in his. + + Pro no eleifend reprehendunt, ut meis consetetur argumentum mei. Id vis harum ornatus cotidieque. Inani libris volumus ea qui. Ius at suas percipit voluptatum, pro solet invidunt honestatis ei, et nam delectus reprimique instructior. Nam id tacimates argumentum dissentiet, mei an sint adipiscing. + + Quando cotidieque sit at, ei sed tantas ancillae verterem, cum nibh omittam ut. Erant laboramus moderatius te eum. Civibus adipiscing sed ne, vix eu erant euripidis. Illud qualisque at nec, id tale sint facete per. Vero autem democritum eam an. + + Mel mazim prodesset ad. An vis alii suas congue, vim veri illum iisque et, in modus perfecto deseruisse vel. Sed summo fuisset fierent an. Vel eu bonorum ornatus, alii decore nec ad. Eu dicta constituto mea. + + Id sint stet graece usu. Cu sit essent reformidans, eos eius ridens et. Usu voluptaria posidonium cu. \ No newline at end of file diff --git a/node_modules/lv_font_conv/package.json b/node_modules/lv_font_conv/package.json new file mode 100644 index 00000000..e0f1464f --- /dev/null +++ b/node_modules/lv_font_conv/package.json @@ -0,0 +1,61 @@ +{ + "name": "lv_font_conv", + "version": "1.5.2", + "description": "Rasterize vector fonts for embedded use. Supports subsettings & merge.", + "keywords": [ + "font", + "convertor", + "embedded" + ], + "repository": "lvgl/lv_font_conv", + "license": "MIT", + "files": [ + "lv_font_conv.js", + "lib/" + ], + "bin": { + "lv_font_conv": "lv_font_conv.js" + }, + "scripts": { + "start": "parcel ./web/index.html --open", + "build": "parcel build ./web/index.html ./web/content.html --public-url ./", + "build:dockerimage": "docker build -t lv_font_conv_freetype ./support", + "build:freetype": "docker run --rm -v $(pwd):/src/lv_font_conv -it lv_font_conv_freetype ./lv_font_conv/support/build.sh", + "lint": "eslint .", + "test": "npm run lint && nyc mocha --recursive", + "coverage": "npm run test && nyc report --reporter html", + "shrink-deps": "shx rm -rf node_modules/opentype.js/src node_modules/opentype.js/dist/opentype.{m,js.m}* node_modules/pngjs/browser.js", + "prepublishOnly": "npm run shrink-deps" + }, + "dependencies": { + "argparse": "^2.0.0", + "bit-buffer": "^0.2.5", + "debug": "^4.1.1", + "make-error": "^1.3.5", + "mkdirp": "^1.0.4", + "opentype.js": "^1.1.0", + "pngjs": "^6.0.0" + }, + "bundledDependencies": [ + "argparse", + "bit-buffer", + "debug", + "make-error", + "mkdirp", + "opentype.js", + "pngjs" + ], + "devDependencies": { + "eslint": "^7.21.0", + "file-saver": "^2.0.2", + "mocha": "^8.3.0", + "nyc": "^15.1.0", + "parcel-bundler": "^1.12.4", + "posthtml-include": "^1.6.2", + "roboto-fontface": "^0.10.0", + "shx": "^0.3.2" + }, + "browserslist": [ + "last 1 Chrome version" + ] +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..b8320c49 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,183 @@ +{ + "name": "InfiniTime", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "dependencies": { + "lv_font_conv": "^1.5.2" + } + }, + "node_modules/lv_font_conv": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/lv_font_conv/-/lv_font_conv-1.5.2.tgz", + "integrity": "sha512-0UapRSTkVP/pnB8Z4r2HDHx5p2dJx/xUG1+14u/WXo59mwuC7BahR+Bnx/66jKoDrG1wFQwn9ZzoyMxRHOD9bg==", + "bundleDependencies": [ + "argparse", + "bit-buffer", + "debug", + "make-error", + "mkdirp", + "opentype.js", + "pngjs" + ], + "dependencies": { + "argparse": "^2.0.0", + "bit-buffer": "^0.2.5", + "debug": "^4.1.1", + "make-error": "^1.3.5", + "mkdirp": "^1.0.4", + "opentype.js": "^1.1.0", + "pngjs": "^6.0.0" + }, + "bin": { + "lv_font_conv": "lv_font_conv.js" + } + }, + "node_modules/lv_font_conv/node_modules/argparse": { + "version": "2.0.1", + "inBundle": true, + "license": "Python-2.0" + }, + "node_modules/lv_font_conv/node_modules/bit-buffer": { + "version": "0.2.5", + "inBundle": true, + "license": "MIT" + }, + "node_modules/lv_font_conv/node_modules/debug": { + "version": "4.3.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/lv_font_conv/node_modules/make-error": { + "version": "1.3.6", + "inBundle": true, + "license": "ISC" + }, + "node_modules/lv_font_conv/node_modules/mkdirp": { + "version": "1.0.4", + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lv_font_conv/node_modules/ms": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/lv_font_conv/node_modules/opentype.js": { + "version": "1.3.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "string.prototype.codepointat": "^0.2.1", + "tiny-inflate": "^1.0.3" + }, + "bin": { + "ot": "bin/ot" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/lv_font_conv/node_modules/pngjs": { + "version": "6.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/lv_font_conv/node_modules/string.prototype.codepointat": { + "version": "0.2.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/lv_font_conv/node_modules/tiny-inflate": { + "version": "1.0.3", + "inBundle": true, + "license": "MIT" + } + }, + "dependencies": { + "lv_font_conv": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/lv_font_conv/-/lv_font_conv-1.5.2.tgz", + "integrity": "sha512-0UapRSTkVP/pnB8Z4r2HDHx5p2dJx/xUG1+14u/WXo59mwuC7BahR+Bnx/66jKoDrG1wFQwn9ZzoyMxRHOD9bg==", + "requires": { + "argparse": "^2.0.0", + "bit-buffer": "^0.2.5", + "debug": "^4.1.1", + "make-error": "^1.3.5", + "mkdirp": "^1.0.4", + "opentype.js": "^1.1.0", + "pngjs": "^6.0.0" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "bundled": true + }, + "bit-buffer": { + "version": "0.2.5", + "bundled": true + }, + "debug": { + "version": "4.3.1", + "bundled": true, + "requires": { + "ms": "2.1.2" + } + }, + "make-error": { + "version": "1.3.6", + "bundled": true + }, + "mkdirp": { + "version": "1.0.4", + "bundled": true + }, + "ms": { + "version": "2.1.2", + "bundled": true + }, + "opentype.js": { + "version": "1.3.3", + "bundled": true, + "requires": { + "string.prototype.codepointat": "^0.2.1", + "tiny-inflate": "^1.0.3" + } + }, + "pngjs": { + "version": "6.0.0", + "bundled": true + }, + "string.prototype.codepointat": { + "version": "0.2.1", + "bundled": true + }, + "tiny-inflate": { + "version": "1.0.3", + "bundled": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..d339766c --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "lv_font_conv": "^1.5.2" + } +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 21413bdb..22957dff 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -371,6 +371,7 @@ list(APPEND SOURCE_FILES displayapp/screens/StopWatch.cpp displayapp/screens/BatteryIcon.cpp displayapp/screens/BleIcon.cpp + displayapp/screens/AlarmIcon.cpp displayapp/screens/NotificationIcon.cpp displayapp/screens/SystemInfo.cpp displayapp/screens/Label.cpp diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json index b88af94f..6eb8b7fa 100644 --- a/src/displayapp/fonts/fonts.json +++ b/src/displayapp/fonts/fonts.json @@ -7,7 +7,7 @@ }, { "file": "FontAwesome5-Solid+Brands+Regular.woff", - "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf073" + "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf0f3, 0xf1f6, 0xf073" } ], "bpp": 1, diff --git a/src/displayapp/screens/AlarmIcon.cpp b/src/displayapp/screens/AlarmIcon.cpp new file mode 100644 index 00000000..fda87130 --- /dev/null +++ b/src/displayapp/screens/AlarmIcon.cpp @@ -0,0 +1,11 @@ +#include "displayapp/screens/AlarmIcon.h" +#include "displayapp/screens/Symbols.h" +using namespace Pinetime::Applications::Screens; + +const char* AlarmIcon::GetIcon(bool isSet) { + if (isSet) { + return Symbols::bell; + } + + return Symbols::notbell; +} diff --git a/src/displayapp/screens/AlarmIcon.h b/src/displayapp/screens/AlarmIcon.h new file mode 100644 index 00000000..678a4cb7 --- /dev/null +++ b/src/displayapp/screens/AlarmIcon.h @@ -0,0 +1,14 @@ +#pragma once + +#include "components/alarm/AlarmController.h" + +namespace Pinetime { + namespace Applications { + namespace Screens { + class AlarmIcon { + public: + static const char* GetIcon(bool isSet); + }; + } + } +} diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index e48b4336..9d9bbea3 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -13,6 +13,7 @@ namespace Pinetime { static constexpr const char* clock = "\xEF\x80\x97"; static constexpr const char* bell = "\xEF\x83\xB3"; static constexpr const char* info = "\xEF\x84\xA9"; + static constexpr const char* notbell = "\xEF\x87\xB6"; static constexpr const char* list = "\xEF\x80\xBA"; static constexpr const char* sun = "\xEF\x86\x85"; static constexpr const char* check = "\xEF\x95\xA0"; diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index 4c6fc196..2d295d0d 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -4,9 +4,11 @@ #include #include "displayapp/screens/Symbols.h" #include "displayapp/screens/BleIcon.h" +#include "displayapp/screens/AlarmIcon.h" #include "components/settings/Settings.h" #include "components/battery/BatteryController.h" #include "components/ble/BleController.h" +#include "components/alarm/AlarmController.h" #include "components/ble/NotificationManager.h" #include "components/motion/MotionController.h" @@ -122,7 +124,8 @@ namespace { WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, - Controllers::NotificationManager& notificationManager, + Controllers::AlarmController& alarmController, + Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::MotionController& motionController, Controllers::FS& filesystem) @@ -130,6 +133,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, dateTimeController {dateTimeController}, batteryController {batteryController}, bleController {bleController}, + alarmController {alarmController}, notificationManager {notificationManager}, settingsController {settingsController}, motionController {motionController} { @@ -228,7 +232,18 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, bleIcon = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); lv_label_set_text_static(bleIcon, Symbols::bluetooth); - lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_TOP_MID, 0, 0); + + alarmIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(alarmIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_label_set_text_static(alarmIcon, Symbols::notbell); + lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + + labelAlarm = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_obj_set_style_local_text_font(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(labelAlarm, alarmIcon, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); + lv_label_set_text_static(labelAlarm, "00:00"); stepValue = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); @@ -449,9 +464,20 @@ void WatchFaceInfineat::Refresh() { bleRadioEnabled = bleController.IsRadioEnabled(); if (bleState.IsUpdated()) { lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); - lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 3); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_TOP_MID, 0, 3); } - + alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; + lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); + lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + if (alarmState) { + uint8_t alarmHours = alarmController.Hours(); + uint8_t alarmMinutes = alarmController.Minutes(); + lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); + } + else { + lv_label_set_text_static(labelAlarm, Symbols::none); + } + stepCount = motionController.NbSteps(); if (stepCount.IsUpdated()) { lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); diff --git a/src/displayapp/screens/WatchFaceInfineat.h b/src/displayapp/screens/WatchFaceInfineat.h index 55c43f98..68821c45 100644 --- a/src/displayapp/screens/WatchFaceInfineat.h +++ b/src/displayapp/screens/WatchFaceInfineat.h @@ -28,6 +28,7 @@ namespace Pinetime { WatchFaceInfineat(Controllers::DateTime& dateTimeController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, + Controllers::AlarmController& alarmController, Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::MotionController& motionController, @@ -52,6 +53,7 @@ namespace Pinetime { Utility::DirtyValue isCharging {}; Utility::DirtyValue bleState {}; Utility::DirtyValue bleRadioEnabled {}; + bool alarmState {}; Utility::DirtyValue> currentDateTime {}; Utility::DirtyValue stepCount {}; Utility::DirtyValue notificationState {}; @@ -71,6 +73,8 @@ namespace Pinetime { lv_obj_t* dateContainer; lv_obj_t* labelDate; lv_obj_t* bleIcon; + lv_obj_t* labelAlarm; + lv_obj_t* alarmIcon; lv_obj_t* stepIcon; lv_obj_t* stepValue; lv_obj_t* notificationIcon; @@ -87,6 +91,7 @@ namespace Pinetime { Controllers::DateTime& dateTimeController; const Controllers::Battery& batteryController; const Controllers::Ble& bleController; + Controllers::AlarmController& alarmController; Controllers::NotificationManager& notificationManager; Controllers::Settings& settingsController; Controllers::MotionController& motionController; @@ -109,6 +114,7 @@ namespace Pinetime { return new Screens::WatchFaceInfineat(controllers.dateTimeController, controllers.batteryController, controllers.bleController, + controllers.alarmController, controllers.notificationManager, controllers.settingsController, controllers.motionController, From feb7631f45e3d9b7ada8f887e44236934d5414c0 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Tue, 28 May 2024 13:40:16 +0200 Subject: [PATCH 056/101] added option to show or hide alarm status --- src/components/settings/Settings.h | 14 +++++- src/displayapp/screens/WatchFaceInfineat.cpp | 51 +++++++++++++++----- src/displayapp/screens/WatchFaceInfineat.h | 2 + 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 06312077..73225ac1 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -47,7 +47,8 @@ namespace Pinetime { struct WatchFaceInfineat { bool showSideCover = true; - int colorIndex = 0; + bool showAlarmStatus = true; + int colorIndex = 0; }; Settings(Pinetime::Controllers::FS& fs); @@ -123,6 +124,17 @@ namespace Pinetime { return settings.watchFaceInfineat.showSideCover; }; + void SetInfineatShowAlarmStatus(bool show) { + if (show != settings.watchFaceInfineat.showAlarmStatus) { + settings.watchFaceInfineat.showAlarmStatus = show; + settingsChanged = true; + } + }; + + bool GetInfineatShowAlarmStatus() const { + return settings.watchFaceInfineat.showAlarmStatus; + }; + void SetInfineatColorIndex(int index) { if (index != settings.watchFaceInfineat.colorIndex) { settings.watchFaceInfineat.colorIndex = index; diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index 2d295d0d..350fa651 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -232,19 +232,20 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, bleIcon = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); lv_label_set_text_static(bleIcon, Symbols::bluetooth); - lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_TOP_MID, 0, 0); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + + labelAlarm = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_obj_set_style_local_text_font(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); + lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); + lv_label_set_text_static(labelAlarm, "00:00"); alarmIcon = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(alarmIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); lv_label_set_text_static(alarmIcon, Symbols::notbell); - lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); - labelAlarm = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); - lv_obj_set_style_local_text_font(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); - lv_obj_align(labelAlarm, alarmIcon, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); - lv_label_set_text_static(labelAlarm, "00:00"); - stepValue = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); @@ -290,7 +291,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, btnToggleCover = lv_btn_create(lv_scr_act(), nullptr); btnToggleCover->user_data = this; lv_obj_set_size(btnToggleCover, 60, 60); - lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_CENTER, 0, 80); + lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 15,-15); lv_obj_set_style_local_bg_opa(btnToggleCover, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); const char* labelToggle = settingsController.GetInfineatShowSideCover() ? "ON" : "OFF"; lblToggle = lv_label_create(btnToggleCover, nullptr); @@ -298,6 +299,17 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, lv_obj_set_event_cb(btnToggleCover, event_handler); lv_obj_set_hidden(btnToggleCover, true); + btnToggleAlarm = lv_btn_create(lv_scr_act(), nullptr); + btnToggleAlarm->user_data = this; + lv_obj_set_size(btnToggleAlarm, 60, 60); + lv_obj_align(btnToggleAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -15, -15); + lv_obj_set_style_local_bg_opa(btnToggleAlarm, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + const char* labelToggleAlarm = settingsController.GetInfineatShowAlarmStatus() ? Symbols::notbell : Symbols::bell; + lblAlarm = lv_label_create(btnToggleAlarm, nullptr); + lv_label_set_text_static(lblAlarm, labelToggleAlarm); + lv_obj_set_event_cb(btnToggleAlarm, event_handler); + lv_obj_set_hidden(btnToggleAlarm, true); + // Button to access the settings btnSettings = lv_btn_create(lv_scr_act(), nullptr); btnSettings->user_data = this; @@ -347,6 +359,7 @@ void WatchFaceInfineat::CloseMenu() { lv_obj_set_hidden(btnNextColor, true); lv_obj_set_hidden(btnPrevColor, true); lv_obj_set_hidden(btnToggleCover, true); + lv_obj_set_hidden(btnToggleAlarm, true); } bool WatchFaceInfineat::OnButtonPushed() { @@ -361,6 +374,7 @@ void WatchFaceInfineat::UpdateSelected(lv_obj_t* object, lv_event_t event) { if (event == LV_EVENT_CLICKED) { bool showSideCover = settingsController.GetInfineatShowSideCover(); int colorIndex = settingsController.GetInfineatColorIndex(); + bool showAlarmStatus = settingsController.GetInfineatShowAlarmStatus(); if (object == btnSettings) { lv_obj_set_hidden(btnSettings, true); @@ -368,6 +382,7 @@ void WatchFaceInfineat::UpdateSelected(lv_obj_t* object, lv_event_t event) { lv_obj_set_hidden(btnNextColor, !showSideCover); lv_obj_set_hidden(btnPrevColor, !showSideCover); lv_obj_set_hidden(btnToggleCover, false); + lv_obj_set_hidden(btnToggleAlarm, false); } if (object == btnClose) { CloseMenu(); @@ -383,6 +398,15 @@ void WatchFaceInfineat::UpdateSelected(lv_obj_t* object, lv_event_t event) { const char* labelToggle = showSideCover ? "OFF" : "ON"; lv_label_set_text_static(lblToggle, labelToggle); } + if (object == btnToggleAlarm) { + settingsController.SetInfineatShowAlarmStatus(!showAlarmStatus); + lv_obj_set_hidden(labelAlarm, showAlarmStatus); + lv_obj_set_hidden(alarmIcon, showAlarmStatus); + const char* labelToggleAlarm = showAlarmStatus ? Symbols::notbell : Symbols::bell; + lv_label_set_text_static(lblAlarm, labelToggleAlarm); + } + + if (object == btnNextColor) { colorIndex = (colorIndex + 1) % nColors; settingsController.SetInfineatColorIndex(colorIndex); @@ -464,18 +488,23 @@ void WatchFaceInfineat::Refresh() { bleRadioEnabled = bleController.IsRadioEnabled(); if (bleState.IsUpdated()) { lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); - lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_TOP_MID, 0, 3); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 3); } alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); - lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); if (alarmState) { uint8_t alarmHours = alarmController.Hours(); uint8_t alarmMinutes = alarmController.Minutes(); lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); + lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); + lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); + lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); + } else { lv_label_set_text_static(labelAlarm, Symbols::none); + lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + lv_obj_align(alarmIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); } stepCount = motionController.NbSteps(); diff --git a/src/displayapp/screens/WatchFaceInfineat.h b/src/displayapp/screens/WatchFaceInfineat.h index 68821c45..70598b26 100644 --- a/src/displayapp/screens/WatchFaceInfineat.h +++ b/src/displayapp/screens/WatchFaceInfineat.h @@ -81,10 +81,12 @@ namespace Pinetime { lv_obj_t* btnClose; lv_obj_t* btnNextColor; lv_obj_t* btnToggleCover; + lv_obj_t* btnToggleAlarm; lv_obj_t* btnPrevColor; lv_obj_t* btnSettings; lv_obj_t* labelBtnSettings; lv_obj_t* lblToggle; + lv_obj_t* lblAlarm; lv_obj_t* lines[nLines]; From f366354e287f4be56be96d0e96d3d67fb1515734 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Tue, 28 May 2024 16:44:33 +0200 Subject: [PATCH 057/101] fixed alarm status reappearing when alarm changed, even if not set to be displayed. TODO : clean comments related to this, and add am/pm format for alarm --- src/displayapp/screens/Symbols.h | 2 +- src/displayapp/screens/WatchFaceInfineat.cpp | 80 +++++++++++++++----- src/displayapp/screens/WatchFaceInfineat.h | 4 +- 3 files changed, 63 insertions(+), 23 deletions(-) diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index 9d9bbea3..4641b8bc 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -12,8 +12,8 @@ namespace Pinetime { static constexpr const char* shoe = "\xEF\x95\x8B"; static constexpr const char* clock = "\xEF\x80\x97"; static constexpr const char* bell = "\xEF\x83\xB3"; - static constexpr const char* info = "\xEF\x84\xA9"; static constexpr const char* notbell = "\xEF\x87\xB6"; + static constexpr const char* info = "\xEF\x84\xA9"; static constexpr const char* list = "\xEF\x80\xBA"; static constexpr const char* sun = "\xEF\x86\x85"; static constexpr const char* check = "\xEF\x95\xA0"; diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index 350fa651..1fe1e0cd 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -125,7 +125,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, Controllers::AlarmController& alarmController, - Controllers::NotificationManager& notificationManager, + Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::MotionController& motionController, Controllers::FS& filesystem) @@ -245,6 +245,13 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, lv_obj_set_style_local_text_color(alarmIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); lv_label_set_text_static(alarmIcon, Symbols::notbell); lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); + + // if we don't show alarm status + if (!settingsController.GetInfineatShowAlarmStatus()) { + //ToggleShowAlarmStatus(false); + lv_obj_set_hidden(labelAlarm, true); + lv_obj_set_hidden(alarmIcon, true); + } stepValue = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); @@ -304,7 +311,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, lv_obj_set_size(btnToggleAlarm, 60, 60); lv_obj_align(btnToggleAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -15, -15); lv_obj_set_style_local_bg_opa(btnToggleAlarm, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); - const char* labelToggleAlarm = settingsController.GetInfineatShowAlarmStatus() ? Symbols::notbell : Symbols::bell; + const char* labelToggleAlarm = settingsController.GetInfineatShowAlarmStatus() ? Symbols::bell : Symbols::notbell; lblAlarm = lv_label_create(btnToggleAlarm, nullptr); lv_label_set_text_static(lblAlarm, labelToggleAlarm); lv_obj_set_event_cb(btnToggleAlarm, event_handler); @@ -398,11 +405,13 @@ void WatchFaceInfineat::UpdateSelected(lv_obj_t* object, lv_event_t event) { const char* labelToggle = showSideCover ? "OFF" : "ON"; lv_label_set_text_static(lblToggle, labelToggle); } + //si je change l'état de l'alarme, ca la re affiche if (object == btnToggleAlarm) { settingsController.SetInfineatShowAlarmStatus(!showAlarmStatus); - lv_obj_set_hidden(labelAlarm, showAlarmStatus); - lv_obj_set_hidden(alarmIcon, showAlarmStatus); - const char* labelToggleAlarm = showAlarmStatus ? Symbols::notbell : Symbols::bell; + bool newShowAlarmStatus = settingsController.GetInfineatShowAlarmStatus(); + lv_obj_set_hidden(labelAlarm, !newShowAlarmStatus); + lv_obj_set_hidden(alarmIcon, !newShowAlarmStatus); + const char* labelToggleAlarm = newShowAlarmStatus ? Symbols::bell : Symbols::notbell; lv_label_set_text_static(lblAlarm, labelToggleAlarm); } @@ -490,23 +499,27 @@ void WatchFaceInfineat::Refresh() { lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 3); } - alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; - lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); - if (alarmState) { - uint8_t alarmHours = alarmController.Hours(); - uint8_t alarmMinutes = alarmController.Minutes(); - lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); - lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); - lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); - lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); - + + if (settingsController.GetInfineatShowAlarmStatus()) { + alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; + // sets the icon as bell or barred bell + lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); + //displays the time of the alarm or nothing + if (alarmState) { + uint8_t alarmHours = alarmController.Hours(); + uint8_t alarmMinutes = alarmController.Minutes(); + lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); + lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); + lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); + lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); + + } + else { + lv_label_set_text_static(labelAlarm, Symbols::none); + lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + lv_obj_align(alarmIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); + } } - else { - lv_label_set_text_static(labelAlarm, Symbols::none); - lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); - lv_obj_align(alarmIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); - } - stepCount = motionController.NbSteps(); if (stepCount.IsUpdated()) { lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); @@ -541,6 +554,31 @@ void WatchFaceInfineat::ToggleBatteryIndicatorColor(bool showSideCover) { lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); } } +/* +void WatchFaceInfineat::ToggleShowAlarmStatus(bool showAlarmStatus) { + // If show alarm option is on, check alarm state to display + if (showAlarmStatus) { + alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; + // sets the icon as bell or barred bell + lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); + //displays the time of the alarm or nothing + if (alarmState) { + uint8_t alarmHours = alarmController.Hours(); + uint8_t alarmMinutes = alarmController.Minutes(); + lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); + lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); + lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); + lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); + + } + else { + lv_label_set_text_static(labelAlarm, Symbols::none); + lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + lv_obj_align(alarmIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); + } + } +} +*/ bool WatchFaceInfineat::IsAvailable(Pinetime::Controllers::FS& filesystem) { lfs_file file = {}; diff --git a/src/displayapp/screens/WatchFaceInfineat.h b/src/displayapp/screens/WatchFaceInfineat.h index 70598b26..5e258dcf 100644 --- a/src/displayapp/screens/WatchFaceInfineat.h +++ b/src/displayapp/screens/WatchFaceInfineat.h @@ -81,7 +81,7 @@ namespace Pinetime { lv_obj_t* btnClose; lv_obj_t* btnNextColor; lv_obj_t* btnToggleCover; - lv_obj_t* btnToggleAlarm; + lv_obj_t* btnToggleAlarm; lv_obj_t* btnPrevColor; lv_obj_t* btnSettings; lv_obj_t* labelBtnSettings; @@ -101,6 +101,8 @@ namespace Pinetime { void SetBatteryLevel(uint8_t batteryPercent); void ToggleBatteryIndicatorColor(bool showSideCover); + void ToggleShowAlarmStatus(bool showAlarmStatus); + lv_task_t* taskRefresh; lv_font_t* font_teko = nullptr; lv_font_t* font_bebas = nullptr; From 8ca38885404231deb5c9f4cafff7627ec7af04bf Mon Sep 17 00:00:00 2001 From: ecarlett Date: Tue, 28 May 2024 17:45:31 +0200 Subject: [PATCH 058/101] AMPM for alarm seems to work - but overloads the screen, of course. check tomorrow all cases --- src/displayapp/screens/WatchFaceInfineat.cpp | 56 ++++++++++---------- src/displayapp/screens/WatchFaceInfineat.h | 1 + 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index 1fe1e0cd..db96c991 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -148,6 +148,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, font_bebas = lv_font_load("F:/fonts/bebas.bin"); } + // Side Cover static constexpr lv_point_t linePoints[nLines][2] = {{{30, 25}, {68, -8}}, {{26, 167}, {43, 216}}, @@ -241,14 +242,19 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); lv_label_set_text_static(labelAlarm, "00:00"); + labelTimeAmPmAlarm = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(labelTimeAmPmAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_label_set_text_static(labelTimeAmPmAlarm, ""); + lv_obj_set_style_local_text_color(labelTimeAmPmAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_obj_align(labelTimeAmPmAlarm, labelAlarm, LV_ALIGN_OUT_TOP_RIGHT, 0, 0); + alarmIcon = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(alarmIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); lv_label_set_text_static(alarmIcon, Symbols::notbell); lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); - // if we don't show alarm status + // don't show the icons jsut set if we don't show alarm status if (!settingsController.GetInfineatShowAlarmStatus()) { - //ToggleShowAlarmStatus(false); lv_obj_set_hidden(labelAlarm, true); lv_obj_set_hidden(alarmIcon, true); } @@ -405,7 +411,7 @@ void WatchFaceInfineat::UpdateSelected(lv_obj_t* object, lv_event_t event) { const char* labelToggle = showSideCover ? "OFF" : "ON"; lv_label_set_text_static(lblToggle, labelToggle); } - //si je change l'état de l'alarme, ca la re affiche + if (object == btnToggleAlarm) { settingsController.SetInfineatShowAlarmStatus(!showAlarmStatus); bool newShowAlarmStatus = settingsController.GetInfineatShowAlarmStatus(); @@ -504,11 +510,28 @@ void WatchFaceInfineat::Refresh() { alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; // sets the icon as bell or barred bell lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); - //displays the time of the alarm or nothing + //displays the time of the alarm or nothing if the alarm is not set if (alarmState) { uint8_t alarmHours = alarmController.Hours(); uint8_t alarmMinutes = alarmController.Minutes(); + //handles the am pm format. + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + char ampmChar[3] = "AM"; + if (alarmHours == 0) { + alarmHours = 12; + } else if (alarmHours == 12) { + ampmChar[0]='P'; + } else if (alarmHours > 12) { + alarmHours = alarmHours - 12; + ampmChar[0]='P'; + } + lv_label_set_text(labelTimeAmPmAlarm, ampmChar); + lv_obj_set_style_local_text_font(labelTimeAmPmAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(labelTimeAmPmAlarm, labelAlarm, LV_ALIGN_OUT_TOP_RIGHT, 0, 0); + } + lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); + lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); @@ -554,31 +577,6 @@ void WatchFaceInfineat::ToggleBatteryIndicatorColor(bool showSideCover) { lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); } } -/* -void WatchFaceInfineat::ToggleShowAlarmStatus(bool showAlarmStatus) { - // If show alarm option is on, check alarm state to display - if (showAlarmStatus) { - alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; - // sets the icon as bell or barred bell - lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); - //displays the time of the alarm or nothing - if (alarmState) { - uint8_t alarmHours = alarmController.Hours(); - uint8_t alarmMinutes = alarmController.Minutes(); - lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); - lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); - lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); - lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); - - } - else { - lv_label_set_text_static(labelAlarm, Symbols::none); - lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); - lv_obj_align(alarmIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); - } - } -} -*/ bool WatchFaceInfineat::IsAvailable(Pinetime::Controllers::FS& filesystem) { lfs_file file = {}; diff --git a/src/displayapp/screens/WatchFaceInfineat.h b/src/displayapp/screens/WatchFaceInfineat.h index 5e258dcf..7ea134f2 100644 --- a/src/displayapp/screens/WatchFaceInfineat.h +++ b/src/displayapp/screens/WatchFaceInfineat.h @@ -74,6 +74,7 @@ namespace Pinetime { lv_obj_t* labelDate; lv_obj_t* bleIcon; lv_obj_t* labelAlarm; + lv_obj_t* labelTimeAmPmAlarm; lv_obj_t* alarmIcon; lv_obj_t* stepIcon; lv_obj_t* stepValue; From 194b061e195962b7b8e4aa195164836bd3d2102f Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 11:18:10 +0200 Subject: [PATCH 059/101] update settings of watchfaceInifineat with alarm settings so it looks better --- src/displayapp/screens/WatchFaceInfineat.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index db96c991..1849e0fb 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -304,7 +304,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, btnToggleCover = lv_btn_create(lv_scr_act(), nullptr); btnToggleCover->user_data = this; lv_obj_set_size(btnToggleCover, 60, 60); - lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 15,-15); + lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_CENTER, 0,0); lv_obj_set_style_local_bg_opa(btnToggleCover, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); const char* labelToggle = settingsController.GetInfineatShowSideCover() ? "ON" : "OFF"; lblToggle = lv_label_create(btnToggleCover, nullptr); @@ -315,7 +315,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, btnToggleAlarm = lv_btn_create(lv_scr_act(), nullptr); btnToggleAlarm->user_data = this; lv_obj_set_size(btnToggleAlarm, 60, 60); - lv_obj_align(btnToggleAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -15, -15); + lv_obj_align(btnToggleAlarm, lv_scr_act(), LV_ALIGN_CENTER, 0, 80); lv_obj_set_style_local_bg_opa(btnToggleAlarm, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); const char* labelToggleAlarm = settingsController.GetInfineatShowAlarmStatus() ? Symbols::bell : Symbols::notbell; lblAlarm = lv_label_create(btnToggleAlarm, nullptr); From e8a1b12e17170380cea47cd6b67848b71bb963ff Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 12:08:25 +0200 Subject: [PATCH 060/101] fixed that label ampm stays shown when alarm status is hidden --- .../alarmStatusOnInfineat.md | 22 ++++++++++++++++++ .../infineat_alarm_notset.png | Bin 0 -> 6312 bytes .../infineat_alarm_set_12hrs.png | Bin 0 -> 6649 bytes .../infineat_alarm_set_24hrs.png | Bin 0 -> 6286 bytes .../infineat_settings.png | Bin 0 -> 7646 bytes src/displayapp/screens/WatchFaceInfineat.cpp | 2 ++ 6 files changed, 24 insertions(+) create mode 100644 doc/alarmStatusOnInfineat/alarmStatusOnInfineat.md create mode 100644 doc/alarmStatusOnInfineat/infineat_alarm_notset.png create mode 100644 doc/alarmStatusOnInfineat/infineat_alarm_set_12hrs.png create mode 100644 doc/alarmStatusOnInfineat/infineat_alarm_set_24hrs.png create mode 100644 doc/alarmStatusOnInfineat/infineat_settings.png diff --git a/doc/alarmStatusOnInfineat/alarmStatusOnInfineat.md b/doc/alarmStatusOnInfineat/alarmStatusOnInfineat.md new file mode 100644 index 00000000..22885a84 --- /dev/null +++ b/doc/alarmStatusOnInfineat/alarmStatusOnInfineat.md @@ -0,0 +1,22 @@ +# [InfiniTime : show alarm status on infineat watchface](https://github.com/Eve1374/InfiniTime/tree/alarm-status-on-infineat) +- I forked from [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime) and added a branch alarm-status-on-infineat +- I modified the watchface settings to have the possibility to show alarm status on watchface + +Here are pictures with alarm set in 12 and 24hrs format : + +![alarm set and shown, 12hrs format](infineat_alarm_set_12hrs.png "alarm set and shown, 12hrs format") +![alarm set and shown, 24hrs format](infineat_alarm_set_24hrs.png "alarm set and shown, 24hrs format") + +Alarm not set : + +![alarm shown and not set](infineat_alarm_notset.png "alarm shown and not set") + +Settings view : + +![settings](infineat_settings.png "settings modified with a button to turn on or off alarm display") + + +## Possible further development : +- Move this setting to the Alarm app and include alarm display in all watchfaces ? + + diff --git a/doc/alarmStatusOnInfineat/infineat_alarm_notset.png b/doc/alarmStatusOnInfineat/infineat_alarm_notset.png new file mode 100644 index 0000000000000000000000000000000000000000..a8883db00ecbc263f434564fa9b143e69fd31ae3 GIT binary patch literal 6312 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 azudktU3C@JdxQ7~0?=01S1VJ6zWqPFIzyQN literal 0 HcmV?d00001 diff --git a/doc/alarmStatusOnInfineat/infineat_alarm_set_12hrs.png b/doc/alarmStatusOnInfineat/infineat_alarm_set_12hrs.png new file mode 100644 index 0000000000000000000000000000000000000000..259c09833f9e8cdfee2144c64412497e07b1f15c GIT binary patch literal 6649 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 GgZ>8t;MQ3H literal 0 HcmV?d00001 diff --git a/doc/alarmStatusOnInfineat/infineat_alarm_set_24hrs.png b/doc/alarmStatusOnInfineat/infineat_alarm_set_24hrs.png new file mode 100644 index 0000000000000000000000000000000000000000..d31de7980157ae4cbe52482f1f9393cca795abde GIT binary patch literal 6286 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 ICFuMA0c@dJw*UYD literal 0 HcmV?d00001 diff --git a/doc/alarmStatusOnInfineat/infineat_settings.png b/doc/alarmStatusOnInfineat/infineat_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..0dffd5e781ae4b945d25c629d4f88d9c7435a770 GIT binary patch literal 7646 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 literal 0 HcmV?d00001 diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index 1849e0fb..20d0beb3 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -257,6 +257,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, if (!settingsController.GetInfineatShowAlarmStatus()) { lv_obj_set_hidden(labelAlarm, true); lv_obj_set_hidden(alarmIcon, true); + lv_obj_set_hidden(labelTimeAmPmAlarm, true); } stepValue = lv_label_create(lv_scr_act(), nullptr); @@ -417,6 +418,7 @@ void WatchFaceInfineat::UpdateSelected(lv_obj_t* object, lv_event_t event) { bool newShowAlarmStatus = settingsController.GetInfineatShowAlarmStatus(); lv_obj_set_hidden(labelAlarm, !newShowAlarmStatus); lv_obj_set_hidden(alarmIcon, !newShowAlarmStatus); + lv_obj_set_hidden(labelTimeAmPmAlarm, !newShowAlarmStatus); const char* labelToggleAlarm = newShowAlarmStatus ? Symbols::bell : Symbols::notbell; lv_label_set_text_static(lblAlarm, labelToggleAlarm); } From b588241207219b510621d961a168c9c134d3ae56 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Tue, 7 May 2024 15:12:52 +0200 Subject: [PATCH 061/101] =?UTF-8?q?compile=20sans=20avoir=20modifi=C3=A9?= =?UTF-8?q?=20de=20code=20ok?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- compile.sh | 7 +++++++ make_pine.sh | 6 ++++++ make_pine_mcu.sh | 6 ++++++ 3 files changed, 19 insertions(+) create mode 100755 compile.sh create mode 100755 make_pine.sh create mode 100755 make_pine_mcu.sh diff --git a/compile.sh b/compile.sh new file mode 100755 index 00000000..6ee6c930 --- /dev/null +++ b/compile.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +rm -r build +cp make_pine.sh build/ + +cmake -DARM_NONE_EABI_TOOLCHAIN_PATH=/home/eve/Work/gcc-arm-none-eabi-10.3-2021.10 -DNRF5_SDK_PATH=/home/eve/Work/nRF5_SDK_17.1.0_ddde560 -DTARGET_DEVICE=PINETIME -DBUILD_DFU=1 -DBUILD_RESOURCES=1 -B build -DCMAKE_BUILD_TYPE=Release + diff --git a/make_pine.sh b/make_pine.sh new file mode 100755 index 00000000..006c2421 --- /dev/null +++ b/make_pine.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +#cp ./displayapp/apps/Apps.h ../src/displayapp/apps/Apps.h + +make -j4 pinetime-app + diff --git a/make_pine_mcu.sh b/make_pine_mcu.sh new file mode 100755 index 00000000..d7af1cb8 --- /dev/null +++ b/make_pine_mcu.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +#cp ./displayapp/apps/Apps.h ../src/displayapp/apps/Apps.h + +make -j4 pinetime-mcuboot-app + From 5cd1bcf460637368d2c13e7c1e186eb2fc5818c7 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Thu, 23 May 2024 11:07:12 +0200 Subject: [PATCH 062/101] add WatchFaceInfineatColors that has different color sets than infineat --- doc/palettes.xcf | Bin 0 -> 151003 bytes src/CMakeLists.txt | 3 +- src/components/settings/Settings.h | 7 + src/displayapp/UserApps.h | 1 + src/displayapp/apps/Apps.h.in | 1 + src/displayapp/apps/CMakeLists.txt | 3 +- .../screens/WatchFaceInfineatColors.cpp | 510 ++++++++++++++++++ .../screens/WatchFaceInfineatColors.h | 123 +++++ .../screens/settings/SettingWatchFace.h | 1 + 9 files changed, 647 insertions(+), 2 deletions(-) create mode 100644 doc/palettes.xcf create mode 100644 src/displayapp/screens/WatchFaceInfineatColors.cpp create mode 100644 src/displayapp/screens/WatchFaceInfineatColors.h diff --git a/doc/palettes.xcf b/doc/palettes.xcf new file mode 100644 index 0000000000000000000000000000000000000000..bfad74e04e73ac4ee19c70cfbfa222a5e9d36d36 GIT binary patch literal 151003 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 z wakeUpMode {0}; uint16_t shakeWakeThreshold = 150; diff --git a/src/displayapp/UserApps.h b/src/displayapp/UserApps.h index 67bbfa7d..76ffa684 100644 --- a/src/displayapp/UserApps.h +++ b/src/displayapp/UserApps.h @@ -12,6 +12,7 @@ #include "displayapp/screens/WatchFaceAnalog.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" #include "displayapp/screens/WatchFaceInfineat.h" +#include "displayapp/screens/WatchFaceInfineatColors.h" #include "displayapp/screens/WatchFacePineTimeStyle.h" #include "displayapp/screens/WatchFaceTerminal.h" diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index 9fa4843a..cd452c71 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -52,6 +52,7 @@ namespace Pinetime { PineTimeStyle, Terminal, Infineat, + InfineatColors, CasioStyleG7710, }; diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index 99b0a936..22ff91ee 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -27,7 +27,8 @@ else() set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::PineTimeStyle") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::InfineatColors") + #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware") endif() diff --git a/src/displayapp/screens/WatchFaceInfineatColors.cpp b/src/displayapp/screens/WatchFaceInfineatColors.cpp new file mode 100644 index 00000000..88241942 --- /dev/null +++ b/src/displayapp/screens/WatchFaceInfineatColors.cpp @@ -0,0 +1,510 @@ +#include "displayapp/screens/WatchFaceInfineatColors.h" + +#include +#include +#include "displayapp/screens/Symbols.h" +#include "displayapp/screens/BleIcon.h" +#include "components/settings/Settings.h" +#include "components/battery/BatteryController.h" +#include "components/ble/BleController.h" +#include "components/ble/NotificationManager.h" +#include "components/motion/MotionController.h" + +using namespace Pinetime::Applications::Screens; + +namespace { + void event_handler(lv_obj_t* obj, lv_event_t event) { + auto* screen = static_cast(obj->user_data); + screen->UpdateSelected(obj, event); + } + + enum class colors { + orange, + blue, + green, + rainbow, + vivid, + pink, + nordGreen, + }; + + constexpr int nColors = 7; // must match number of colors in InfineatColorsColors + + constexpr int nLines = WatchFaceInfineatColors::nLines; + + constexpr std::array orangeColors = {LV_COLOR_MAKE(0xfd, 0x87, 0x2b), + LV_COLOR_MAKE(0xdb, 0x33, 0x16), + LV_COLOR_MAKE(0x6f, 0x10, 0x00), + LV_COLOR_MAKE(0xfd, 0x7a, 0x0a), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xe8, 0x51, 0x02), + LV_COLOR_MAKE(0xea, 0x1c, 0x00)}; + constexpr std::array blueColors = {LV_COLOR_MAKE(0xe7, 0xf8, 0xff), + LV_COLOR_MAKE(0x22, 0x32, 0xd0), + LV_COLOR_MAKE(0x18, 0x2a, 0x8b), + LV_COLOR_MAKE(0xe7, 0xf8, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0x59, 0x91, 0xff), + LV_COLOR_MAKE(0x16, 0x36, 0xff)}; + constexpr std::array greenColors = {LV_COLOR_MAKE(0xb8, 0xff, 0x9b), + LV_COLOR_MAKE(0x08, 0x86, 0x08), + LV_COLOR_MAKE(0x00, 0x4a, 0x00), + LV_COLOR_MAKE(0xb8, 0xff, 0x9b), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0x62, 0xd5, 0x15), + LV_COLOR_MAKE(0x00, 0x74, 0x00)}; + constexpr std::array rainbowColors = {LV_COLOR_MAKE(0x2d, 0xa4, 0x00), //vert petit triangle le plus haut + LV_COLOR_MAKE(0xac, 0x09, 0xc4), //purple petit triangle bas côté horloge + LV_COLOR_MAKE(0xfe, 0x03, 0x03), //rouge 2 petits triangles à côté de batterie + LV_COLOR_MAKE(0x0d, 0x57, 0xff), //bleu petit triangle le plus bas + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xe0, 0xb9, 0x00), //jaune gd triangle haut + LV_COLOR_MAKE(0xe8, 0x51, 0x02)}; //orange gd triangle bas + constexpr std::array rainbowVividColors ={LV_COLOR_MAKE(0xa5, 0xeb, 0x64), + LV_COLOR_MAKE(0xfc, 0x42, 0xb5), + LV_COLOR_MAKE(0xe7, 0xc1, 0xff), + LV_COLOR_MAKE(0x11, 0xdf, 0xfa), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xec, 0x5d), + LV_COLOR_MAKE(0xff, 0x93, 0xaf)}; + + constexpr std::array pinkColors = {LV_COLOR_MAKE(0xff, 0xe5, 0xec), + LV_COLOR_MAKE(0xff, 0xb3, 0xc6), + LV_COLOR_MAKE(0xfb, 0x6f, 0x92), + LV_COLOR_MAKE(0xff, 0xe5, 0xec), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xc2, 0xd1), + LV_COLOR_MAKE(0xff, 0x8f, 0xab)}; + constexpr std::array nordGreenColors = {LV_COLOR_MAKE(0xd5, 0xf0, 0xe9), + LV_COLOR_MAKE(0x23, 0x83, 0x73), + LV_COLOR_MAKE(0x1d, 0x41, 0x3f), + LV_COLOR_MAKE(0xd5, 0xf0, 0xe9), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0x2f, 0xb8, 0xa2), + LV_COLOR_MAKE(0x11, 0x70, 0x5a)}; + + constexpr const std::array* returnColor(colors color) { + if (color == colors::orange) { + return &orangeColors; + } + if (color == colors::blue) { + return &blueColors; + } + if (color == colors::green) { + return &greenColors; + } + if (color == colors::rainbow) { + return &rainbowColors; + } + if (color == colors::vivid) { + return &rainbowVividColors; + } + if (color == colors::pink) { + return &pinkColors; + } + return &nordGreenColors; + } +} + +WatchFaceInfineatColors::WatchFaceInfineatColors(Controllers::DateTime& dateTimeController, + const Controllers::Battery& batteryController, + const Controllers::Ble& bleController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::MotionController& motionController, + Controllers::FS& filesystem) + : currentDateTime {{}}, + dateTimeController {dateTimeController}, + batteryController {batteryController}, + bleController {bleController}, + notificationManager {notificationManager}, + settingsController {settingsController}, + motionController {motionController} { + lfs_file f = {}; + if (filesystem.FileOpen(&f, "/fonts/teko.bin", LFS_O_RDONLY) >= 0) { + filesystem.FileClose(&f); + font_teko = lv_font_load("F:/fonts/teko.bin"); + } + + if (filesystem.FileOpen(&f, "/fonts/bebas.bin", LFS_O_RDONLY) >= 0) { + filesystem.FileClose(&f); + font_bebas = lv_font_load("F:/fonts/bebas.bin"); + } + + // Side Cover + static constexpr lv_point_t linePoints[nLines][2] = {{{30, 25}, {68, -8}}, + {{26, 167}, {43, 216}}, + {{27, 40}, {27, 196}}, + {{12, 182}, {65, 249}}, + {{17, 99}, {17, 144}}, + {{14, 81}, {40, 127}}, + {{14, 163}, {40, 118}}, + {{-20, 124}, {25, -11}}, + {{-29, 89}, {27, 254}}}; + + static constexpr lv_style_int_t lineWidths[nLines] = {18, 15, 14, 22, 20, 18, 18, 52, 48}; + + const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); + for (int i = 0; i < nLines; i++) { + lines[i] = lv_line_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_line_width(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, lineWidths[i]); + lv_color_t color = (*colors)[i]; + lv_obj_set_style_local_line_color(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, color); + lv_line_set_points(lines[i], linePoints[i], 2); + } + + logoPine = lv_img_create(lv_scr_act(), nullptr); + lv_img_set_src(logoPine, "F:/images/pine_small.bin"); + lv_obj_set_pos(logoPine, 15, 106); + + lineBattery = lv_line_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_line_width(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 24); + lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); + lv_obj_set_style_local_line_opa(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 190); + lineBatteryPoints[0] = {27, 105}; + lineBatteryPoints[1] = {27, 106}; + lv_line_set_points(lineBattery, lineBatteryPoints, 2); + lv_obj_move_foreground(lineBattery); + + notificationIcon = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); + lv_obj_set_style_local_radius(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE); + lv_obj_set_size(notificationIcon, 13, 13); + lv_obj_set_hidden(notificationIcon, true); + + if (!settingsController.GetInfineatShowSideCover()) { + ToggleBatteryIndicatorColor(false); + for (auto& line : lines) { + lv_obj_set_hidden(line, true); + } + } + + timeContainer = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_opa(timeContainer, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); + lv_obj_set_size(timeContainer, 185, 185); + lv_obj_align(timeContainer, lv_scr_act(), LV_ALIGN_CENTER, 0, -10); + + labelHour = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_static(labelHour, "01"); + lv_obj_set_style_local_text_font(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); + lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 0); + + labelMinutes = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); + lv_label_set_text_static(labelMinutes, "00"); + lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + + labelTimeAmPm = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + + lv_label_set_text_static(labelTimeAmPm, ""); + lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 15); + + dateContainer = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_opa(dateContainer, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); + lv_obj_set_size(dateContainer, 60, 30); + lv_obj_align(dateContainer, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 5); + + static constexpr lv_color_t grayColor = LV_COLOR_MAKE(0x99, 0x99, 0x99); + labelDate = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_obj_set_style_local_text_font(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(labelDate, dateContainer, LV_ALIGN_IN_TOP_MID, 0, 0); + lv_label_set_text_static(labelDate, "Mon 01"); + + bleIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_label_set_text_static(bleIcon, Symbols::bluetooth); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + + stepValue = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 10, 0); + lv_label_set_text_static(stepValue, "0"); + + stepIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_label_set_text_static(stepIcon, Symbols::shoe); + lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); + + // Setting buttons + btnClose = lv_btn_create(lv_scr_act(), nullptr); + btnClose->user_data = this; + lv_obj_set_size(btnClose, 60, 60); + lv_obj_align(btnClose, lv_scr_act(), LV_ALIGN_CENTER, 0, -80); + lv_obj_set_style_local_bg_opa(btnClose, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + lv_obj_t* lblClose = lv_label_create(btnClose, nullptr); + lv_label_set_text_static(lblClose, "X"); + lv_obj_set_event_cb(btnClose, event_handler); + lv_obj_set_hidden(btnClose, true); + + btnNextColor = lv_btn_create(lv_scr_act(), nullptr); + btnNextColor->user_data = this; + lv_obj_set_size(btnNextColor, 60, 60); + lv_obj_align(btnNextColor, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -15, 0); + lv_obj_set_style_local_bg_opa(btnNextColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + lv_obj_t* lblNextColor = lv_label_create(btnNextColor, nullptr); + lv_label_set_text_static(lblNextColor, ">"); + lv_obj_set_event_cb(btnNextColor, event_handler); + lv_obj_set_hidden(btnNextColor, true); + + btnPrevColor = lv_btn_create(lv_scr_act(), nullptr); + btnPrevColor->user_data = this; + lv_obj_set_size(btnPrevColor, 60, 60); + lv_obj_align(btnPrevColor, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 15, 0); + lv_obj_set_style_local_bg_opa(btnPrevColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + lv_obj_t* lblPrevColor = lv_label_create(btnPrevColor, nullptr); + lv_label_set_text_static(lblPrevColor, "<"); + lv_obj_set_event_cb(btnPrevColor, event_handler); + lv_obj_set_hidden(btnPrevColor, true); + + btnToggleCover = lv_btn_create(lv_scr_act(), nullptr); + btnToggleCover->user_data = this; + lv_obj_set_size(btnToggleCover, 60, 60); + lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_CENTER, 0, 80); + lv_obj_set_style_local_bg_opa(btnToggleCover, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + const char* labelToggle = settingsController.GetInfineatShowSideCover() ? "ON" : "OFF"; + lblToggle = lv_label_create(btnToggleCover, nullptr); + lv_label_set_text_static(lblToggle, labelToggle); + lv_obj_set_event_cb(btnToggleCover, event_handler); + lv_obj_set_hidden(btnToggleCover, true); + + // Button to access the settings + btnSettings = lv_btn_create(lv_scr_act(), nullptr); + btnSettings->user_data = this; + lv_obj_set_size(btnSettings, 150, 150); + lv_obj_align(btnSettings, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); + lv_obj_set_style_local_radius(btnSettings, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 30); + lv_obj_set_style_local_bg_opa(btnSettings, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + lv_obj_set_event_cb(btnSettings, event_handler); + labelBtnSettings = lv_label_create(btnSettings, nullptr); + lv_obj_set_style_local_text_font(labelBtnSettings, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48); + lv_label_set_text_static(labelBtnSettings, Symbols::settings); + lv_obj_set_hidden(btnSettings, true); + + taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); + Refresh(); +} + +WatchFaceInfineatColors::~WatchFaceInfineatColors() { + lv_task_del(taskRefresh); + + if (font_bebas != nullptr) { + lv_font_free(font_bebas); + } + if (font_teko != nullptr) { + lv_font_free(font_teko); + } + + lv_obj_clean(lv_scr_act()); +} + +bool WatchFaceInfineatColors::OnTouchEvent(Pinetime::Applications::TouchEvents event) { + if ((event == Pinetime::Applications::TouchEvents::LongTap) && lv_obj_get_hidden(btnSettings)) { + lv_obj_set_hidden(btnSettings, false); + savedTick = lv_tick_get(); + return true; + } + // Prevent screen from sleeping when double tapping with settings on + if ((event == Pinetime::Applications::TouchEvents::DoubleTap) && !lv_obj_get_hidden(btnClose)) { + return true; + } + return false; +} + +void WatchFaceInfineatColors::CloseMenu() { + settingsController.SaveSettings(); + lv_obj_set_hidden(btnClose, true); + lv_obj_set_hidden(btnNextColor, true); + lv_obj_set_hidden(btnPrevColor, true); + lv_obj_set_hidden(btnToggleCover, true); +} + +bool WatchFaceInfineatColors::OnButtonPushed() { + if (!lv_obj_get_hidden(btnClose)) { + CloseMenu(); + return true; + } + return false; +} + +void WatchFaceInfineatColors::UpdateSelected(lv_obj_t* object, lv_event_t event) { + if (event == LV_EVENT_CLICKED) { + bool showSideCover = settingsController.GetInfineatShowSideCover(); + int colorIndex = settingsController.GetInfineatColorIndex(); + + if (object == btnSettings) { + lv_obj_set_hidden(btnSettings, true); + lv_obj_set_hidden(btnClose, false); + lv_obj_set_hidden(btnNextColor, !showSideCover); + lv_obj_set_hidden(btnPrevColor, !showSideCover); + lv_obj_set_hidden(btnToggleCover, false); + } + if (object == btnClose) { + CloseMenu(); + } + if (object == btnToggleCover) { + settingsController.SetInfineatShowSideCover(!showSideCover); + ToggleBatteryIndicatorColor(!showSideCover); + for (auto& line : lines) { + lv_obj_set_hidden(line, showSideCover); + } + lv_obj_set_hidden(btnNextColor, showSideCover); + lv_obj_set_hidden(btnPrevColor, showSideCover); + const char* labelToggle = showSideCover ? "OFF" : "ON"; + lv_label_set_text_static(lblToggle, labelToggle); + } + if (object == btnNextColor) { + colorIndex = (colorIndex + 1) % nColors; + settingsController.SetInfineatColorIndex(colorIndex); + } + if (object == btnPrevColor) { + colorIndex -= 1; + if (colorIndex < 0) + colorIndex = nColors - 1; + settingsController.SetInfineatColorIndex(colorIndex); + } + if (object == btnNextColor || object == btnPrevColor) { + const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); + for (int i = 0; i < nLines; i++) { + lv_color_t color = (*colors)[i]; + lv_obj_set_style_local_line_color(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, color); + } + lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); + lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); + } + } +} + +void WatchFaceInfineatColors::Refresh() { + notificationState = notificationManager.AreNewNotificationsAvailable(); + if (notificationState.IsUpdated()) { + lv_obj_set_hidden(notificationIcon, !notificationState.Get()); + lv_obj_align(notificationIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0); + } + + currentDateTime = std::chrono::time_point_cast(dateTimeController.CurrentDateTime()); + if (currentDateTime.IsUpdated()) { + uint8_t hour = dateTimeController.Hours(); + uint8_t minute = dateTimeController.Minutes(); + + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + char ampmChar[3] = "AM"; + if (hour == 0) { + hour = 12; + } else if (hour == 12) { + ampmChar[0] = 'P'; + } else if (hour > 12) { + hour = hour - 12; + ampmChar[0] = 'P'; + } + lv_label_set_text(labelTimeAmPm, ampmChar); + } + lv_label_set_text_fmt(labelHour, "%02d", hour); + lv_label_set_text_fmt(labelMinutes, "%02d", minute); + + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 10); + lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 5); + lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + } + + currentDate = std::chrono::time_point_cast(currentDateTime.Get()); + if (currentDate.IsUpdated()) { + uint8_t day = dateTimeController.Day(); + Controllers::DateTime::Days dayOfWeek = dateTimeController.DayOfWeek(); + lv_label_set_text_fmt(labelDate, "%s %02d", dateTimeController.DayOfWeekShortToStringLow(dayOfWeek), day); + lv_obj_realign(labelDate); + } + } + + batteryPercentRemaining = batteryController.PercentRemaining(); + isCharging = batteryController.IsCharging(); + if (batteryController.IsCharging()) { // Charging battery animation + chargingBatteryPercent += 1; + if (chargingBatteryPercent > 100) { + chargingBatteryPercent = batteryPercentRemaining.Get(); + } + SetBatteryLevel(chargingBatteryPercent); + } else if (isCharging.IsUpdated() || batteryPercentRemaining.IsUpdated()) { + chargingBatteryPercent = batteryPercentRemaining.Get(); + SetBatteryLevel(chargingBatteryPercent); + } + + bleState = bleController.IsConnected(); + bleRadioEnabled = bleController.IsRadioEnabled(); + if (bleState.IsUpdated()) { + lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 3); + } + + stepCount = motionController.NbSteps(); + if (stepCount.IsUpdated()) { + lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); + lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 10, 0); + lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); + } + + if (!lv_obj_get_hidden(btnSettings)) { + if ((savedTick > 0) && (lv_tick_get() - savedTick > 3000)) { + lv_obj_set_hidden(btnSettings, true); + savedTick = 0; + } + } +} + +void WatchFaceInfineatColors::SetBatteryLevel(uint8_t batteryPercent) { + // starting point (y) + Pine64 logo height * (100 - batteryPercent) / 100 + lineBatteryPoints[1] = {27, static_cast(105 + 32 * (100 - batteryPercent) / 100)}; + lv_line_set_points(lineBattery, lineBatteryPoints, 2); +} + +void WatchFaceInfineatColors::ToggleBatteryIndicatorColor(bool showSideCover) { + if (!showSideCover) { // make indicator and notification icon color white + lv_obj_set_style_local_image_recolor_opa(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_100); + lv_obj_set_style_local_image_recolor(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + } else { + lv_obj_set_style_local_image_recolor_opa(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_0); + const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); + lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); + lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); + } +} + +bool WatchFaceInfineatColors::IsAvailable(Pinetime::Controllers::FS& filesystem) { + lfs_file file = {}; + + if (filesystem.FileOpen(&file, "/fonts/teko.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + if (filesystem.FileOpen(&file, "/fonts/bebas.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + if (filesystem.FileOpen(&file, "/images/pine_small.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + return true; +} diff --git a/src/displayapp/screens/WatchFaceInfineatColors.h b/src/displayapp/screens/WatchFaceInfineatColors.h new file mode 100644 index 00000000..3a164278 --- /dev/null +++ b/src/displayapp/screens/WatchFaceInfineatColors.h @@ -0,0 +1,123 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "displayapp/screens/Screen.h" +#include "components/datetime/DateTimeController.h" +#include "utility/DirtyValue.h" +#include "displayapp/apps/Apps.h" + +namespace Pinetime { + namespace Controllers { + class Settings; + class Battery; + class Ble; + class NotificationManager; + class MotionController; + } + + namespace Applications { + namespace Screens { + + class WatchFaceInfineatColors : public Screen { + public: + static constexpr int nLines = 9; + WatchFaceInfineatColors(Controllers::DateTime& dateTimeController, + const Controllers::Battery& batteryController, + const Controllers::Ble& bleController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::MotionController& motionController, + Controllers::FS& fs); + + ~WatchFaceInfineatColors() override; + + bool OnTouchEvent(TouchEvents event) override; + bool OnButtonPushed() override; + void UpdateSelected(lv_obj_t* object, lv_event_t event); + void CloseMenu(); + + void Refresh() override; + + static bool IsAvailable(Pinetime::Controllers::FS& filesystem); + + private: + uint32_t savedTick = 0; + uint8_t chargingBatteryPercent = 101; // not a mistake ;) + + Utility::DirtyValue batteryPercentRemaining {}; + Utility::DirtyValue isCharging {}; + Utility::DirtyValue bleState {}; + Utility::DirtyValue bleRadioEnabled {}; + Utility::DirtyValue> currentDateTime {}; + Utility::DirtyValue stepCount {}; + Utility::DirtyValue notificationState {}; + Utility::DirtyValue> currentDate; + + // Lines making up the side cover + lv_obj_t* lineBattery; + + lv_point_t lineBatteryPoints[2]; + + lv_obj_t* logoPine; + + lv_obj_t* timeContainer; + lv_obj_t* labelHour; + lv_obj_t* labelMinutes; + lv_obj_t* labelTimeAmPm; + lv_obj_t* dateContainer; + lv_obj_t* labelDate; + lv_obj_t* bleIcon; + lv_obj_t* stepIcon; + lv_obj_t* stepValue; + lv_obj_t* notificationIcon; + lv_obj_t* btnClose; + lv_obj_t* btnNextColor; + lv_obj_t* btnToggleCover; + lv_obj_t* btnPrevColor; + lv_obj_t* btnSettings; + lv_obj_t* labelBtnSettings; + lv_obj_t* lblToggle; + + lv_obj_t* lines[nLines]; + + Controllers::DateTime& dateTimeController; + const Controllers::Battery& batteryController; + const Controllers::Ble& bleController; + Controllers::NotificationManager& notificationManager; + Controllers::Settings& settingsController; + Controllers::MotionController& motionController; + + void SetBatteryLevel(uint8_t batteryPercent); + void ToggleBatteryIndicatorColor(bool showSideCover); + + lv_task_t* taskRefresh; + lv_font_t* font_teko = nullptr; + lv_font_t* font_bebas = nullptr; + }; + } + + template <> + struct WatchFaceTraits { + static constexpr WatchFace watchFace = WatchFace::InfineatColors; + static constexpr const char* name = "InfineatColors face"; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::WatchFaceInfineatColors(controllers.dateTimeController, + controllers.batteryController, + controllers.bleController, + controllers.notificationManager, + controllers.settingsController, + controllers.motionController, + controllers.filesystem); + }; + + static bool IsAvailable(Pinetime::Controllers::FS& filesystem) { + return Screens::WatchFaceInfineatColors::IsAvailable(filesystem); + } + }; + } +} diff --git a/src/displayapp/screens/settings/SettingWatchFace.h b/src/displayapp/screens/settings/SettingWatchFace.h index 4c75b0ab..1ff03e22 100644 --- a/src/displayapp/screens/settings/SettingWatchFace.h +++ b/src/displayapp/screens/settings/SettingWatchFace.h @@ -10,6 +10,7 @@ #include "displayapp/screens/Symbols.h" #include "displayapp/screens/CheckboxList.h" #include "displayapp/screens/WatchFaceInfineat.h" +#include "displayapp/screens/WatchFaceInfineatColors.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" namespace Pinetime { From 6494faa249fe00cc4dc3bc76d0ff6c1ee252a6f4 Mon Sep 17 00:00:00 2001 From: Eve C Date: Thu, 30 May 2024 15:50:41 +0200 Subject: [PATCH 063/101] try to rebase scottcalendar into this testbranch --- make_pine_mcu.sh | 2 +- src/CMakeLists.txt | 6 + src/components/settings/Settings.h | 7 +- src/displayapp/UserApps.h | 1 + src/displayapp/apps/Apps.h.in | 1 + src/displayapp/apps/CMakeLists.txt | 9 +- src/displayapp/fonts/fonts.json | 8 + src/displayapp/screens/AlarmIcon.cpp | 7 + src/displayapp/screens/README.md | 8 + src/displayapp/screens/Symbols.h | 8 +- .../screens/WatchFaceInfineatColors.cpp | 20 +- .../screens/WatchFaceInfineatColors.h | 1 + src/displayapp/screens/WatchFaceMeow.cpp | 561 ++++++++++++++++++ src/displayapp/screens/WatchFaceMeow.h | 130 ++++ .../screens/settings/SettingWatchFace.h | 3 + 15 files changed, 758 insertions(+), 14 deletions(-) create mode 100644 src/displayapp/screens/README.md create mode 100644 src/displayapp/screens/WatchFaceMeow.cpp create mode 100644 src/displayapp/screens/WatchFaceMeow.h diff --git a/make_pine_mcu.sh b/make_pine_mcu.sh index d7af1cb8..87c00ff2 100755 --- a/make_pine_mcu.sh +++ b/make_pine_mcu.sh @@ -2,5 +2,5 @@ #cp ./displayapp/apps/Apps.h ../src/displayapp/apps/Apps.h -make -j4 pinetime-mcuboot-app +make clean -j4 pinetime-mcuboot-app diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1f05ea5b..daf3f8ed 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -371,7 +371,11 @@ list(APPEND SOURCE_FILES displayapp/screens/StopWatch.cpp displayapp/screens/BatteryIcon.cpp displayapp/screens/BleIcon.cpp +<<<<<<< HEAD displayapp/screens/AlarmIcon.cpp +======= + displayapp/screens/AlarmIcon.cpp +>>>>>>> f5dfbc44 (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) displayapp/screens/NotificationIcon.cpp displayapp/screens/SystemInfo.cpp displayapp/screens/Label.cpp @@ -428,6 +432,7 @@ list(APPEND SOURCE_FILES displayapp/screens/WatchFaceTerminal.cpp displayapp/screens/WatchFacePineTimeStyle.cpp displayapp/screens/WatchFaceInfineatColors.cpp + displayapp/screens/WatchFaceMeow.cpp #displayapp/screens/WatchFaceCasioStyleG7710.cpp ## @@ -598,6 +603,7 @@ set(INCLUDE_FILES displayapp/screens/Paddle.h displayapp/screens/BatteryIcon.h displayapp/screens/BleIcon.h + displayapp/screens/AlarmIcon.h displayapp/screens/NotificationIcon.h displayapp/screens/SystemInfo.h displayapp/screens/ScreenList.h diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 099f0dcf..16dcdfd2 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -56,7 +56,11 @@ namespace Pinetime { int colorIndex = 0; }; - + struct WatchFaceMeow { + bool showSideCover = true; + int colorIndex = 0; + }; + Settings(Pinetime::Controllers::FS& fs); Settings(const Settings&) = delete; @@ -322,6 +326,7 @@ namespace Pinetime { WatchFaceInfineat watchFaceInfineat; WatchFaceInfineatColors watchFaceInfineatColors; + WatchFaceMeow watchFaceMeow; std::bitset<5> wakeUpMode {0}; uint16_t shakeWakeThreshold = 150; diff --git a/src/displayapp/UserApps.h b/src/displayapp/UserApps.h index 76ffa684..f76af024 100644 --- a/src/displayapp/UserApps.h +++ b/src/displayapp/UserApps.h @@ -13,6 +13,7 @@ #include "displayapp/screens/WatchFaceCasioStyleG7710.h" #include "displayapp/screens/WatchFaceInfineat.h" #include "displayapp/screens/WatchFaceInfineatColors.h" +#include "displayapp/screens/WatchFaceMeow.h" #include "displayapp/screens/WatchFacePineTimeStyle.h" #include "displayapp/screens/WatchFaceTerminal.h" diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index cd452c71..bf7a803c 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -53,6 +53,7 @@ namespace Pinetime { Terminal, Infineat, InfineatColors, + Meow, CasioStyleG7710, }; diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index 22ff91ee..b462c09e 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -15,7 +15,7 @@ else () set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Navigation") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Weather") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Calendar") - #set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Motion") + set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Motion") set(USERAPP_TYPES "${DEFAULT_USER_APP_TYPES}" CACHE STRING "List of user apps to build into the firmware") endif () @@ -23,11 +23,12 @@ if(DEFINED ENABLE_WATCHFACES) set(WATCHFACE_TYPES ${ENABLE_WATCHFACES} CACHE STRING "List of watch faces to build into the firmware") else() set(DEFAULT_WATCHFACE_TYPES "WatchFace::Digital") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Analog") + #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Analog") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::PineTimeStyle") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") + #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") + #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::InfineatColors") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Meow") #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware") endif() diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json index 6eb8b7fa..7a3da446 100644 --- a/src/displayapp/fonts/fonts.json +++ b/src/displayapp/fonts/fonts.json @@ -7,7 +7,15 @@ }, { "file": "FontAwesome5-Solid+Brands+Regular.woff", +<<<<<<< HEAD "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf0f3, 0xf1f6, 0xf073" +======= +<<<<<<< HEAD + "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf0f3, 0xf1f6" +======= + "range": "0xf294, 0xf242, 0xf54b, 0xf1b0, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf4ba, 0xf236" +>>>>>>> f5dfbc44 (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) +>>>>>>> d7de641b (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) } ], "bpp": 1, diff --git a/src/displayapp/screens/AlarmIcon.cpp b/src/displayapp/screens/AlarmIcon.cpp index fda87130..0ea1a8cb 100644 --- a/src/displayapp/screens/AlarmIcon.cpp +++ b/src/displayapp/screens/AlarmIcon.cpp @@ -4,8 +4,15 @@ using namespace Pinetime::Applications::Screens; const char* AlarmIcon::GetIcon(bool isSet) { if (isSet) { +<<<<<<< HEAD return Symbols::bell; } return Symbols::notbell; +======= + return Symbols::bird; + } + + return Symbols::zzz; +>>>>>>> f5dfbc44 (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) } diff --git a/src/displayapp/screens/README.md b/src/displayapp/screens/README.md new file mode 100644 index 00000000..4f785766 --- /dev/null +++ b/src/displayapp/screens/README.md @@ -0,0 +1,8 @@ + to edit with new watch face : + +/src/displayapp/apps/Apps.h.in +/src/components/settings/Settings.h +/src/displayapp/UserApps.h +/src/displayapp/apps/CMakeLists.txt +CMakelists.txt + diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index 4641b8bc..321adf29 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -1,5 +1,5 @@ #pragma once - +//check https://github.com/InfiniTimeOrg/InfiniTime/blob/main/src/displayapp/fonts/README.md to add new symbols namespace Pinetime { namespace Applications { namespace Screens { @@ -10,6 +10,7 @@ namespace Pinetime { static constexpr const char* bluetooth = "\xEF\x8A\x94"; static constexpr const char* plug = "\xEF\x87\xA6"; static constexpr const char* shoe = "\xEF\x95\x8B"; + static constexpr const char* paw = "\xEF\x86\xB0"; static constexpr const char* clock = "\xEF\x80\x97"; static constexpr const char* bell = "\xEF\x83\xB3"; static constexpr const char* notbell = "\xEF\x87\xB6"; @@ -40,7 +41,12 @@ namespace Pinetime { static constexpr const char* eye = "\xEF\x81\xAE"; static constexpr const char* home = "\xEF\x80\x95"; static constexpr const char* sleep = "\xEE\xBD\x84"; +<<<<<<< HEAD static constexpr const char* calendar = "\xEF\x81\xB3"; +======= + static constexpr const char* bird = "\xEF\x92\xBA"; + static constexpr const char* zzz = "\xEF\x88\xB6"; +>>>>>>> d7de641b (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) // fontawesome_weathericons.c // static constexpr const char* sun = "\xEF\x86\x85"; diff --git a/src/displayapp/screens/WatchFaceInfineatColors.cpp b/src/displayapp/screens/WatchFaceInfineatColors.cpp index 88241942..ab963493 100644 --- a/src/displayapp/screens/WatchFaceInfineatColors.cpp +++ b/src/displayapp/screens/WatchFaceInfineatColors.cpp @@ -96,7 +96,10 @@ namespace { LV_COLOR_MAKE(0xff, 0xff, 0xff), LV_COLOR_MAKE(0x2f, 0xb8, 0xa2), LV_COLOR_MAKE(0x11, 0x70, 0x5a)}; - + //define colors for texts and symbols + //static constexpr lv_color_t grayColor = LV_COLOR_MAKE(0x99, 0x99, 0x99); + static constexpr lv_color_t pinkColor = LV_COLOR_MAKE(0xff, 0x93, 0xaf); + constexpr const std::array* returnColor(colors color) { if (color == colors::orange) { return &orangeColors; @@ -201,15 +204,18 @@ WatchFaceInfineatColors::WatchFaceInfineatColors(Controllers::DateTime& dateTime labelHour = lv_label_create(lv_scr_act(), nullptr); lv_label_set_text_static(labelHour, "01"); lv_obj_set_style_local_text_font(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); + lv_obj_set_style_local_text_color(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 0); labelMinutes = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_font(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); + lv_obj_set_style_local_text_color(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_label_set_text_static(labelMinutes, "00"); lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); labelTimeAmPm = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_font(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_set_style_local_text_color(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_label_set_text_static(labelTimeAmPm, ""); lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 15); @@ -219,29 +225,29 @@ WatchFaceInfineatColors::WatchFaceInfineatColors(Controllers::DateTime& dateTime lv_obj_set_size(dateContainer, 60, 30); lv_obj_align(dateContainer, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 5); - static constexpr lv_color_t grayColor = LV_COLOR_MAKE(0x99, 0x99, 0x99); labelDate = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_obj_set_style_local_text_color(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_obj_set_style_local_text_font(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); lv_obj_align(labelDate, dateContainer, LV_ALIGN_IN_TOP_MID, 0, 0); lv_label_set_text_static(labelDate, "Mon 01"); bleIcon = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_label_set_text_static(bleIcon, Symbols::bluetooth); lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); stepValue = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); + lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 10, 0); lv_label_set_text_static(stepValue, "0"); stepIcon = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); - lv_label_set_text_static(stepIcon, Symbols::shoe); + lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_label_set_text_static(stepIcon, Symbols::paw); lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); + // Setting buttons btnClose = lv_btn_create(lv_scr_act(), nullptr); btnClose->user_data = this; diff --git a/src/displayapp/screens/WatchFaceInfineatColors.h b/src/displayapp/screens/WatchFaceInfineatColors.h index 3a164278..27557c2d 100644 --- a/src/displayapp/screens/WatchFaceInfineatColors.h +++ b/src/displayapp/screens/WatchFaceInfineatColors.h @@ -72,6 +72,7 @@ namespace Pinetime { lv_obj_t* labelDate; lv_obj_t* bleIcon; lv_obj_t* stepIcon; + lv_obj_t* pawIcon; lv_obj_t* stepValue; lv_obj_t* notificationIcon; lv_obj_t* btnClose; diff --git a/src/displayapp/screens/WatchFaceMeow.cpp b/src/displayapp/screens/WatchFaceMeow.cpp new file mode 100644 index 00000000..e8324534 --- /dev/null +++ b/src/displayapp/screens/WatchFaceMeow.cpp @@ -0,0 +1,561 @@ +#include "displayapp/screens/WatchFaceMeow.h" + +#include +#include +#include "displayapp/screens/Symbols.h" +#include "displayapp/screens/BleIcon.h" +#include "displayapp/screens/AlarmIcon.h" +#include "components/settings/Settings.h" +#include "components/battery/BatteryController.h" +#include "components/ble/BleController.h" +#include "components/alarm/AlarmController.h" +#include "components/ble/NotificationManager.h" +#include "components/motion/MotionController.h" + +using namespace Pinetime::Applications::Screens; + +namespace { + void event_handler(lv_obj_t* obj, lv_event_t event) { + auto* screen = static_cast(obj->user_data); + screen->UpdateSelected(obj, event); + } + + enum class colors { + orange, + blue, + green, + rainbow, + vivid, + pink, + nordGreen, + }; + + constexpr int nColors = 7; // must match number of colors in InfineatColorsColors + + constexpr int nLines = WatchFaceMeow::nLines; + + constexpr std::array orangeColors = {LV_COLOR_MAKE(0xfd, 0x87, 0x2b), + LV_COLOR_MAKE(0xdb, 0x33, 0x16), + LV_COLOR_MAKE(0x6f, 0x10, 0x00), + LV_COLOR_MAKE(0xfd, 0x7a, 0x0a), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xe8, 0x51, 0x02), + LV_COLOR_MAKE(0xea, 0x1c, 0x00)}; + constexpr std::array blueColors = {LV_COLOR_MAKE(0xe7, 0xf8, 0xff), + LV_COLOR_MAKE(0x22, 0x32, 0xd0), + LV_COLOR_MAKE(0x18, 0x2a, 0x8b), + LV_COLOR_MAKE(0xe7, 0xf8, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0x59, 0x91, 0xff), + LV_COLOR_MAKE(0x16, 0x36, 0xff)}; + constexpr std::array greenColors = {LV_COLOR_MAKE(0xb8, 0xff, 0x9b), + LV_COLOR_MAKE(0x08, 0x86, 0x08), + LV_COLOR_MAKE(0x00, 0x4a, 0x00), + LV_COLOR_MAKE(0xb8, 0xff, 0x9b), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0x62, 0xd5, 0x15), + LV_COLOR_MAKE(0x00, 0x74, 0x00)}; + constexpr std::array rainbowColors = {LV_COLOR_MAKE(0x2d, 0xa4, 0x00), //vert petit triangle le plus haut + LV_COLOR_MAKE(0xac, 0x09, 0xc4), //purple petit triangle bas côté horloge + LV_COLOR_MAKE(0xfe, 0x03, 0x03), //rouge 2 petits triangles à côté de batterie + LV_COLOR_MAKE(0x0d, 0x57, 0xff), //bleu petit triangle le plus bas + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xe0, 0xb9, 0x00), //jaune gd triangle haut + LV_COLOR_MAKE(0xe8, 0x51, 0x02)}; //orange gd triangle bas + constexpr std::array rainbowVividColors ={LV_COLOR_MAKE(0xa5, 0xeb, 0x64), + LV_COLOR_MAKE(0xfc, 0x42, 0xb5), + LV_COLOR_MAKE(0xe7, 0xc1, 0xff), + LV_COLOR_MAKE(0x11, 0xdf, 0xfa), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xec, 0x5d), + LV_COLOR_MAKE(0xff, 0x93, 0xaf)}; + + constexpr std::array pinkColors = {LV_COLOR_MAKE(0xff, 0xe5, 0xec), + LV_COLOR_MAKE(0xff, 0xb3, 0xc6), + LV_COLOR_MAKE(0xfb, 0x6f, 0x92), + LV_COLOR_MAKE(0xff, 0xe5, 0xec), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xc2, 0xd1), + LV_COLOR_MAKE(0xff, 0x8f, 0xab)}; + constexpr std::array nordGreenColors = {LV_COLOR_MAKE(0xd5, 0xf0, 0xe9), + LV_COLOR_MAKE(0x23, 0x83, 0x73), + LV_COLOR_MAKE(0x1d, 0x41, 0x3f), + LV_COLOR_MAKE(0xd5, 0xf0, 0xe9), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0xff, 0xff, 0xff), + LV_COLOR_MAKE(0x2f, 0xb8, 0xa2), + LV_COLOR_MAKE(0x11, 0x70, 0x5a)}; + + //define colors for texts and symbols + //static constexpr lv_color_t grayColor = LV_COLOR_MAKE(0x99, 0x99, 0x99); + static constexpr lv_color_t grayColor = LV_COLOR_MAKE(0x99, 0x99, 0x99); + static constexpr lv_color_t pinkColor = LV_COLOR_MAKE(0xfc, 0x42, 0xb5); + + constexpr const std::array* returnColor(colors color) { + if (color == colors::orange) { + return &orangeColors; + } + if (color == colors::blue) { + return &blueColors; + } + if (color == colors::green) { + return &greenColors; + } + if (color == colors::rainbow) { + return &rainbowColors; + } + if (color == colors::vivid) { + return &rainbowVividColors; + } + if (color == colors::pink) { + return &pinkColors; + } + return &nordGreenColors; + } +} + +WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, + const Controllers::Battery& batteryController, + const Controllers::Ble& bleController, + Controllers::AlarmController& alarmController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::MotionController& motionController, + Controllers::FS& filesystem) + : currentDateTime {{}}, + dateTimeController {dateTimeController}, + batteryController {batteryController}, + bleController {bleController}, + alarmController {alarmController}, + notificationManager {notificationManager}, + settingsController {settingsController}, + motionController {motionController} { + lfs_file f = {}; + if (filesystem.FileOpen(&f, "/fonts/teko.bin", LFS_O_RDONLY) >= 0) { + filesystem.FileClose(&f); + font_teko = lv_font_load("F:/fonts/teko.bin"); + } + + if (filesystem.FileOpen(&f, "/fonts/bebas.bin", LFS_O_RDONLY) >= 0) { + filesystem.FileClose(&f); + font_bebas = lv_font_load("F:/fonts/bebas.bin"); + } + + // Side Cover + static constexpr lv_point_t linePoints[nLines][2] = {{{30, 25}, {68, -8}}, + {{26, 167}, {43, 216}}, + {{27, 40}, {27, 196}}, + {{12, 182}, {65, 249}}, + {{17, 99}, {17, 144}}, + {{14, 81}, {40, 127}}, + {{14, 163}, {40, 118}}, + {{-20, 124}, {25, -11}}, + {{-29, 89}, {27, 254}}}; + + static constexpr lv_style_int_t lineWidths[nLines] = {18, 15, 14, 22, 20, 18, 18, 52, 48}; + + const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); + for (int i = 0; i < nLines; i++) { + lines[i] = lv_line_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_line_width(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, lineWidths[i]); + lv_color_t color = (*colors)[i]; + lv_obj_set_style_local_line_color(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, color); + lv_line_set_points(lines[i], linePoints[i], 2); + } + + logoPine = lv_img_create(lv_scr_act(), nullptr); + lv_img_set_src(logoPine, "F:/images/pine_small.bin"); + lv_obj_set_pos(logoPine, 15, 106); + + lineBattery = lv_line_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_line_width(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 24); + lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); + lv_obj_set_style_local_line_opa(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 190); + lineBatteryPoints[0] = {27, 105}; + lineBatteryPoints[1] = {27, 106}; + lv_line_set_points(lineBattery, lineBatteryPoints, 2); + lv_obj_move_foreground(lineBattery); + + notificationIcon = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); + lv_obj_set_style_local_radius(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE); + lv_obj_set_size(notificationIcon, 13, 13); + lv_obj_set_hidden(notificationIcon, true); + + if (!settingsController.GetInfineatShowSideCover()) { + ToggleBatteryIndicatorColor(false); + for (auto& line : lines) { + lv_obj_set_hidden(line, true); + } + } + + timeContainer = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_opa(timeContainer, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); + lv_obj_set_size(timeContainer, 185, 185); + lv_obj_align(timeContainer, lv_scr_act(), LV_ALIGN_CENTER, 0, -10); + + labelHour = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_static(labelHour, "01"); + lv_obj_set_style_local_text_font(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); + lv_obj_set_style_local_text_color(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 0); + + labelMinutes = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); + lv_obj_set_style_local_text_color(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_label_set_text_static(labelMinutes, "00"); + lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + + labelTimeAmPm = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_set_style_local_text_color(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + + lv_label_set_text_static(labelTimeAmPm, ""); + lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 15); + + dateContainer = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_opa(dateContainer, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); + lv_obj_set_size(dateContainer, 60, 30); + lv_obj_align(dateContainer, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 5); + + labelDate = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_obj_set_style_local_text_font(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(labelDate, dateContainer, LV_ALIGN_IN_TOP_MID, 0, 0); + lv_label_set_text_static(labelDate, "Mon 01"); + + bleIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_label_set_text_static(bleIcon, Symbols::bluetooth); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + + + //version par defaut de l'icône avant qu'il aie regardé le statut de l'alarme ? + alarmIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(alarmIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_label_set_text_static(alarmIcon, Symbols::paw); + lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, -10); + //version par defaut de l'icône avant qu'il aie regardé le statut de l'alarme ? + labelAlarm = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_obj_set_style_local_text_font(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(labelAlarm, alarmIcon, LV_ALIGN_OUT_BOTTOM_MID, -15, 10); + lv_label_set_text_static(labelAlarm, "00:00"); + + + stepValue = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 10, 0); + lv_label_set_text_static(stepValue, "0"); + + stepIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_label_set_text_static(stepIcon, Symbols::paw); + lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); + + + // Setting buttons + btnClose = lv_btn_create(lv_scr_act(), nullptr); + btnClose->user_data = this; + lv_obj_set_size(btnClose, 60, 60); + lv_obj_align(btnClose, lv_scr_act(), LV_ALIGN_CENTER, 0, -80); + lv_obj_set_style_local_bg_opa(btnClose, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + lv_obj_t* lblClose = lv_label_create(btnClose, nullptr); + lv_label_set_text_static(lblClose, "X"); + lv_obj_set_event_cb(btnClose, event_handler); + lv_obj_set_hidden(btnClose, true); + + btnNextColor = lv_btn_create(lv_scr_act(), nullptr); + btnNextColor->user_data = this; + lv_obj_set_size(btnNextColor, 60, 60); + lv_obj_align(btnNextColor, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -15, 0); + lv_obj_set_style_local_bg_opa(btnNextColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + lv_obj_t* lblNextColor = lv_label_create(btnNextColor, nullptr); + lv_label_set_text_static(lblNextColor, ">"); + lv_obj_set_event_cb(btnNextColor, event_handler); + lv_obj_set_hidden(btnNextColor, true); + + btnPrevColor = lv_btn_create(lv_scr_act(), nullptr); + btnPrevColor->user_data = this; + lv_obj_set_size(btnPrevColor, 60, 60); + lv_obj_align(btnPrevColor, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 15, 0); + lv_obj_set_style_local_bg_opa(btnPrevColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + lv_obj_t* lblPrevColor = lv_label_create(btnPrevColor, nullptr); + lv_label_set_text_static(lblPrevColor, "<"); + lv_obj_set_event_cb(btnPrevColor, event_handler); + lv_obj_set_hidden(btnPrevColor, true); + + btnToggleCover = lv_btn_create(lv_scr_act(), nullptr); + btnToggleCover->user_data = this; + lv_obj_set_size(btnToggleCover, 60, 60); + lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_CENTER, 0, 80); + lv_obj_set_style_local_bg_opa(btnToggleCover, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + const char* labelToggle = settingsController.GetInfineatShowSideCover() ? "ON" : "OFF"; + lblToggle = lv_label_create(btnToggleCover, nullptr); + lv_label_set_text_static(lblToggle, labelToggle); + lv_obj_set_event_cb(btnToggleCover, event_handler); + lv_obj_set_hidden(btnToggleCover, true); + + // Button to access the settings + btnSettings = lv_btn_create(lv_scr_act(), nullptr); + btnSettings->user_data = this; + lv_obj_set_size(btnSettings, 150, 150); + lv_obj_align(btnSettings, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); + lv_obj_set_style_local_radius(btnSettings, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 30); + lv_obj_set_style_local_bg_opa(btnSettings, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + lv_obj_set_event_cb(btnSettings, event_handler); + labelBtnSettings = lv_label_create(btnSettings, nullptr); + lv_obj_set_style_local_text_font(labelBtnSettings, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48); + lv_label_set_text_static(labelBtnSettings, Symbols::settings); + lv_obj_set_hidden(btnSettings, true); + + taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); + Refresh(); +} + +WatchFaceMeow::~WatchFaceMeow() { + lv_task_del(taskRefresh); + + if (font_bebas != nullptr) { + lv_font_free(font_bebas); + } + if (font_teko != nullptr) { + lv_font_free(font_teko); + } + + lv_obj_clean(lv_scr_act()); +} + +bool WatchFaceMeow::OnTouchEvent(Pinetime::Applications::TouchEvents event) { + if ((event == Pinetime::Applications::TouchEvents::LongTap) && lv_obj_get_hidden(btnSettings)) { + lv_obj_set_hidden(btnSettings, false); + savedTick = lv_tick_get(); + return true; + } + // Prevent screen from sleeping when double tapping with settings on + if ((event == Pinetime::Applications::TouchEvents::DoubleTap) && !lv_obj_get_hidden(btnClose)) { + return true; + } + return false; +} + +void WatchFaceMeow::CloseMenu() { + settingsController.SaveSettings(); + lv_obj_set_hidden(btnClose, true); + lv_obj_set_hidden(btnNextColor, true); + lv_obj_set_hidden(btnPrevColor, true); + lv_obj_set_hidden(btnToggleCover, true); +} + +bool WatchFaceMeow::OnButtonPushed() { + if (!lv_obj_get_hidden(btnClose)) { + CloseMenu(); + return true; + } + return false; +} + +void WatchFaceMeow::UpdateSelected(lv_obj_t* object, lv_event_t event) { + if (event == LV_EVENT_CLICKED) { + bool showSideCover = settingsController.GetInfineatShowSideCover(); + int colorIndex = settingsController.GetInfineatColorIndex(); + + if (object == btnSettings) { + lv_obj_set_hidden(btnSettings, true); + lv_obj_set_hidden(btnClose, false); + lv_obj_set_hidden(btnNextColor, !showSideCover); + lv_obj_set_hidden(btnPrevColor, !showSideCover); + lv_obj_set_hidden(btnToggleCover, false); + } + if (object == btnClose) { + CloseMenu(); + } + if (object == btnToggleCover) { + settingsController.SetInfineatShowSideCover(!showSideCover); + ToggleBatteryIndicatorColor(!showSideCover); + for (auto& line : lines) { + lv_obj_set_hidden(line, showSideCover); + } + lv_obj_set_hidden(btnNextColor, showSideCover); + lv_obj_set_hidden(btnPrevColor, showSideCover); + const char* labelToggle = showSideCover ? "OFF" : "ON"; + lv_label_set_text_static(lblToggle, labelToggle); + } + if (object == btnNextColor) { + colorIndex = (colorIndex + 1) % nColors; + settingsController.SetInfineatColorIndex(colorIndex); + } + if (object == btnPrevColor) { + colorIndex -= 1; + if (colorIndex < 0) + colorIndex = nColors - 1; + settingsController.SetInfineatColorIndex(colorIndex); + } + if (object == btnNextColor || object == btnPrevColor) { + const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); + for (int i = 0; i < nLines; i++) { + lv_color_t color = (*colors)[i]; + lv_obj_set_style_local_line_color(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, color); + } + lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); + lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); + } + } +} + +void WatchFaceMeow::Refresh() { + notificationState = notificationManager.AreNewNotificationsAvailable(); + if (notificationState.IsUpdated()) { + lv_obj_set_hidden(notificationIcon, !notificationState.Get()); + lv_obj_align(notificationIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0); + } + + currentDateTime = std::chrono::time_point_cast(dateTimeController.CurrentDateTime()); + if (currentDateTime.IsUpdated()) { + uint8_t hour = dateTimeController.Hours(); + uint8_t minute = dateTimeController.Minutes(); + + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + char ampmChar[3] = "AM"; + if (hour == 0) { + hour = 12; + } else if (hour == 12) { + ampmChar[0] = 'P'; + } else if (hour > 12) { + hour = hour - 12; + ampmChar[0] = 'P'; + } + lv_label_set_text(labelTimeAmPm, ampmChar); + } + lv_label_set_text_fmt(labelHour, "%02d", hour); + lv_label_set_text_fmt(labelMinutes, "%02d", minute); + + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 10); + lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 5); + lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + } + + currentDate = std::chrono::time_point_cast(currentDateTime.Get()); + if (currentDate.IsUpdated()) { + uint8_t day = dateTimeController.Day(); + Controllers::DateTime::Days dayOfWeek = dateTimeController.DayOfWeek(); + lv_label_set_text_fmt(labelDate, "%s %02d", dateTimeController.DayOfWeekShortToStringLow(dayOfWeek), day); + lv_obj_realign(labelDate); + } + } + + batteryPercentRemaining = batteryController.PercentRemaining(); + isCharging = batteryController.IsCharging(); + if (batteryController.IsCharging()) { // Charging battery animation + chargingBatteryPercent += 1; + if (chargingBatteryPercent > 100) { + chargingBatteryPercent = batteryPercentRemaining.Get(); + } + SetBatteryLevel(chargingBatteryPercent); + } else if (isCharging.IsUpdated() || batteryPercentRemaining.IsUpdated()) { + chargingBatteryPercent = batteryPercentRemaining.Get(); + SetBatteryLevel(chargingBatteryPercent); + } + + bleState = bleController.IsConnected(); + bleRadioEnabled = bleController.IsRadioEnabled(); + if (bleState.IsUpdated()) { + //bleState.Get : in displayapp/widgets/StatusIcons.cpp: bleState = bleController.IsConnected(); + //dynamic icons have their definitions in displayApp/screens/BleIcon.h / cpp + lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 3); + } + + //Add alarm state and time + // AlarmState is an enum type in class AlarmController that is in namespace controllers + alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; + lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); + lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, -3); + if (alarmState) { + uint8_t alarmHours = alarmController.Hours(); + uint8_t alarmMinutes = alarmController.Minutes(); + lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); + } + else { + lv_label_set_text_static(labelAlarm, Symbols::none); + } + + //lv_label_set_text_fmt(labelMinutes, "%02d", minute); +/* + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 5); + lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + } + */ + + + stepCount = motionController.NbSteps(); + if (stepCount.IsUpdated()) { + lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); + lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 10, 0); + lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); + } + + if (!lv_obj_get_hidden(btnSettings)) { + if ((savedTick > 0) && (lv_tick_get() - savedTick > 3000)) { + lv_obj_set_hidden(btnSettings, true); + savedTick = 0; + } + } +} + +void WatchFaceMeow::SetBatteryLevel(uint8_t batteryPercent) { + // starting point (y) + Pine64 logo height * (100 - batteryPercent) / 100 + lineBatteryPoints[1] = {27, static_cast(105 + 32 * (100 - batteryPercent) / 100)}; + lv_line_set_points(lineBattery, lineBatteryPoints, 2); +} + +void WatchFaceMeow::ToggleBatteryIndicatorColor(bool showSideCover) { + if (!showSideCover) { // make indicator and notification icon color white + lv_obj_set_style_local_image_recolor_opa(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_100); + lv_obj_set_style_local_image_recolor(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + } else { + lv_obj_set_style_local_image_recolor_opa(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_0); + const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); + lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); + lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); + } +} + +bool WatchFaceMeow::IsAvailable(Pinetime::Controllers::FS& filesystem) { + lfs_file file = {}; + + if (filesystem.FileOpen(&file, "/fonts/teko.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + if (filesystem.FileOpen(&file, "/fonts/bebas.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + if (filesystem.FileOpen(&file, "/images/pine_small.bin", LFS_O_RDONLY) < 0) { + return false; + } + + filesystem.FileClose(&file); + return true; +} diff --git a/src/displayapp/screens/WatchFaceMeow.h b/src/displayapp/screens/WatchFaceMeow.h new file mode 100644 index 00000000..8978f5bf --- /dev/null +++ b/src/displayapp/screens/WatchFaceMeow.h @@ -0,0 +1,130 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "displayapp/screens/Screen.h" +#include "components/datetime/DateTimeController.h" +#include "utility/DirtyValue.h" +#include "displayapp/apps/Apps.h" + +namespace Pinetime { + namespace Controllers { + class Settings; + class Battery; + class Ble; + class NotificationManager; + class MotionController; + } + + namespace Applications { + namespace Screens { + + class WatchFaceMeow : public Screen { + public: + static constexpr int nLines = 9; + WatchFaceMeow(Controllers::DateTime& dateTimeController, + const Controllers::Battery& batteryController, + const Controllers::Ble& bleController, + Controllers::AlarmController& alarmController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::MotionController& motionController, + Controllers::FS& fs); + + ~WatchFaceMeow() override; + + bool OnTouchEvent(TouchEvents event) override; + bool OnButtonPushed() override; + void UpdateSelected(lv_obj_t* object, lv_event_t event); + void CloseMenu(); + + void Refresh() override; + + static bool IsAvailable(Pinetime::Controllers::FS& filesystem); + + private: + uint32_t savedTick = 0; + uint8_t chargingBatteryPercent = 101; // not a mistake ;) + + Utility::DirtyValue batteryPercentRemaining {}; + Utility::DirtyValue isCharging {}; + Utility::DirtyValue bleState {}; + Utility::DirtyValue bleRadioEnabled {}; + bool alarmState {}; + Utility::DirtyValue> currentDateTime {}; + Utility::DirtyValue stepCount {}; + Utility::DirtyValue notificationState {}; + Utility::DirtyValue> currentDate; + + // Lines making up the side cover + lv_obj_t* lineBattery; + + lv_point_t lineBatteryPoints[2]; + + lv_obj_t* logoPine; + + lv_obj_t* timeContainer; + lv_obj_t* labelHour; + lv_obj_t* labelMinutes; + lv_obj_t* labelTimeAmPm; + lv_obj_t* dateContainer; + lv_obj_t* labelDate; + lv_obj_t* bleIcon; + lv_obj_t* labelAlarm; + lv_obj_t* alarmIcon; + lv_obj_t* stepIcon; + lv_obj_t* pawIcon; + lv_obj_t* stepValue; + lv_obj_t* notificationIcon; + lv_obj_t* btnClose; + lv_obj_t* btnNextColor; + lv_obj_t* btnToggleCover; + lv_obj_t* btnPrevColor; + lv_obj_t* btnSettings; + lv_obj_t* labelBtnSettings; + lv_obj_t* lblToggle; + + lv_obj_t* lines[nLines]; + + Controllers::DateTime& dateTimeController; + const Controllers::Battery& batteryController; + const Controllers::Ble& bleController; + Controllers::AlarmController& alarmController; + Controllers::NotificationManager& notificationManager; + Controllers::Settings& settingsController; + Controllers::MotionController& motionController; + + void SetBatteryLevel(uint8_t batteryPercent); + void ToggleBatteryIndicatorColor(bool showSideCover); + + lv_task_t* taskRefresh; + lv_font_t* font_teko = nullptr; + lv_font_t* font_bebas = nullptr; + }; + } + + template <> + struct WatchFaceTraits { + static constexpr WatchFace watchFace = WatchFace::Meow; + static constexpr const char* name = "Meow face"; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::WatchFaceMeow(controllers.dateTimeController, + controllers.batteryController, + controllers.bleController, + controllers.alarmController, + controllers.notificationManager, + controllers.settingsController, + controllers.motionController, + controllers.filesystem); + }; + + static bool IsAvailable(Pinetime::Controllers::FS& filesystem) { + return Screens::WatchFaceMeow::IsAvailable(filesystem); + } + }; + } +} diff --git a/src/displayapp/screens/settings/SettingWatchFace.h b/src/displayapp/screens/settings/SettingWatchFace.h index 1ff03e22..4425bdcd 100644 --- a/src/displayapp/screens/settings/SettingWatchFace.h +++ b/src/displayapp/screens/settings/SettingWatchFace.h @@ -11,8 +11,11 @@ #include "displayapp/screens/CheckboxList.h" #include "displayapp/screens/WatchFaceInfineat.h" #include "displayapp/screens/WatchFaceInfineatColors.h" +#include "displayapp/screens/WatchFaceMeow.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" + + namespace Pinetime { namespace Applications { From 91fb716ea399328200ddd00825a49b989245e2e1 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Thu, 23 May 2024 16:36:07 +0200 Subject: [PATCH 064/101] =?UTF-8?q?c'est=20mieux=20align=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- compile.sh | 3 ++- src/displayapp/screens/WatchFaceMeow.cpp | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/compile.sh b/compile.sh index 6ee6c930..fc3a6836 100755 --- a/compile.sh +++ b/compile.sh @@ -1,7 +1,8 @@ #!/bin/bash rm -r build -cp make_pine.sh build/ cmake -DARM_NONE_EABI_TOOLCHAIN_PATH=/home/eve/Work/gcc-arm-none-eabi-10.3-2021.10 -DNRF5_SDK_PATH=/home/eve/Work/nRF5_SDK_17.1.0_ddde560 -DTARGET_DEVICE=PINETIME -DBUILD_DFU=1 -DBUILD_RESOURCES=1 -B build -DCMAKE_BUILD_TYPE=Release +cp make_pine_mcu.sh build/ + diff --git a/src/displayapp/screens/WatchFaceMeow.cpp b/src/displayapp/screens/WatchFaceMeow.cpp index e8324534..5b1c94c3 100644 --- a/src/displayapp/screens/WatchFaceMeow.cpp +++ b/src/displayapp/screens/WatchFaceMeow.cpp @@ -240,19 +240,19 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, bleIcon = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_label_set_text_static(bleIcon, Symbols::bluetooth); - lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_TOP_MID, 0, -10); //version par defaut de l'icône avant qu'il aie regardé le statut de l'alarme ? alarmIcon = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(alarmIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_label_set_text_static(alarmIcon, Symbols::paw); - lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, -10); + lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); //version par defaut de l'icône avant qu'il aie regardé le statut de l'alarme ? labelAlarm = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_obj_set_style_local_text_font(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); - lv_obj_align(labelAlarm, alarmIcon, LV_ALIGN_OUT_BOTTOM_MID, -15, 10); + lv_obj_align(labelAlarm, alarmIcon, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); lv_label_set_text_static(labelAlarm, "00:00"); @@ -478,14 +478,14 @@ void WatchFaceMeow::Refresh() { //bleState.Get : in displayapp/widgets/StatusIcons.cpp: bleState = bleController.IsConnected(); //dynamic icons have their definitions in displayApp/screens/BleIcon.h / cpp lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); - lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 3); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_TOP_MID, 0, -10); } //Add alarm state and time // AlarmState is an enum type in class AlarmController that is in namespace controllers alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); - lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, -3); + lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); if (alarmState) { uint8_t alarmHours = alarmController.Hours(); uint8_t alarmMinutes = alarmController.Minutes(); From 55866d4470f345302a1e7a531b52cf39f2074aef Mon Sep 17 00:00:00 2001 From: ecarlett Date: Mon, 27 May 2024 14:13:30 +0200 Subject: [PATCH 065/101] InfiniTime firmware for pinetime with a new watchface. Note that some other watchfaces are disabled from compilation to save memory space, in case. The WatchFaceMeow, named Meow in the interface, displays the status of the alarm on the screen --- README.md | 9 +++- build/src/pinetime-mcuboot-app-dfu-1.14.0.zip | Bin 0 -> 378895 bytes src/Version.h.in | 2 +- src/displayapp/screens/README.md | 14 ++--- src/displayapp/screens/WatchFaceMeow.cpp | 48 +++++++++++------- 5 files changed, 47 insertions(+), 26 deletions(-) create mode 100644 build/src/pinetime-mcuboot-app-dfu-1.14.0.zip diff --git a/README.md b/README.md index e4f6707f..b58821ef 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,16 @@ -# [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime) +# Infinitime with a new watchface ![InfiniTime logo](doc/logo/infinitime-logo-small.jpg "InfiniTime Logo") Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devices/pinetime/) with many features, written in modern C++. +## Quick notes on this InfiniTime version + +- I copied the source code from this git repo : [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime) +- I added a watch face "WatchFaceMeow" whose main features are to be pink and have info about the alarm status +- I stored the compile commands in scripts compile.sh to run from InfiniTime/ folder, and make_pine_mcu.sh to build the image must be run from InfiniTime/build/ (compile.sh copies make_pine_mcu.sh to build/ +- The file to flash to the pinetime is InfiniTime/build/pinetime-mcuboot-app-dfu-1.14.0.zip : I didn't change the version compared to the one I downloaded from [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime) so make sure not to keep keep a copy of it + ## New to InfiniTime? - [Getting started with InfiniTime](doc/gettingStarted/gettingStarted-1.0.md) diff --git a/build/src/pinetime-mcuboot-app-dfu-1.14.0.zip b/build/src/pinetime-mcuboot-app-dfu-1.14.0.zip new file mode 100644 index 0000000000000000000000000000000000000000..4904d149c01b8f34763e30cbadf8c98a7b703961 GIT binary patch literal 378895 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

g07$tydb60cWII2A`KR>!Qc4##SC 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 literal 0 HcmV?d00001 diff --git a/src/Version.h.in b/src/Version.h.in index 0d6219c0..dfeccdbc 100644 --- a/src/Version.h.in +++ b/src/Version.h.in @@ -19,4 +19,4 @@ namespace Pinetime { static constexpr const char* commitHash = "@PROJECT_GIT_COMMIT_HASH@"; static constexpr const char* versionString = "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@"; }; -} \ No newline at end of file +} diff --git a/src/displayapp/screens/README.md b/src/displayapp/screens/README.md index 4f785766..f84a5fc6 100644 --- a/src/displayapp/screens/README.md +++ b/src/displayapp/screens/README.md @@ -1,8 +1,10 @@ - to edit with new watch face : +# Add a new watchface : +## Modify the following files with the names of your source files : + +- /src/displayapp/apps/Apps.h.in +- /src/components/settings/Settings.h +- /src/displayapp/UserApps.h +- /src/displayapp/apps/CMakeLists.txt +- CMakelists.txt -/src/displayapp/apps/Apps.h.in -/src/components/settings/Settings.h -/src/displayapp/UserApps.h -/src/displayapp/apps/CMakeLists.txt -CMakelists.txt diff --git a/src/displayapp/screens/WatchFaceMeow.cpp b/src/displayapp/screens/WatchFaceMeow.cpp index 5b1c94c3..0adff72c 100644 --- a/src/displayapp/screens/WatchFaceMeow.cpp +++ b/src/displayapp/screens/WatchFaceMeow.cpp @@ -1,3 +1,15 @@ +/*********************************************************/ +/* + * I modified the watchface Infineat : + * - added alarm info on the screen + * - modified the colors + * - modified step count icon + * Except colors, modifications are at line 254 and 500 + */ +/*********************************************************/ + + + #include "displayapp/screens/WatchFaceMeow.h" #include @@ -61,15 +73,18 @@ namespace { LV_COLOR_MAKE(0xff, 0xff, 0xff), LV_COLOR_MAKE(0x62, 0xd5, 0x15), LV_COLOR_MAKE(0x00, 0x74, 0x00)}; - constexpr std::array rainbowColors = {LV_COLOR_MAKE(0x2d, 0xa4, 0x00), //vert petit triangle le plus haut - LV_COLOR_MAKE(0xac, 0x09, 0xc4), //purple petit triangle bas côté horloge - LV_COLOR_MAKE(0xfe, 0x03, 0x03), //rouge 2 petits triangles à côté de batterie - LV_COLOR_MAKE(0x0d, 0x57, 0xff), //bleu petit triangle le plus bas + //added comments to say which color is which triangle + constexpr std::array rainbowColors = {LV_COLOR_MAKE(0x2d, 0xa4, 0x00), //green, small triangle on top + LV_COLOR_MAKE(0xac, 0x09, 0xc4), //purple, smalltriangle in the bottom half part on the clock display side + LV_COLOR_MAKE(0xfe, 0x03, 0x03), //red, the two small triangles above and below the battery + LV_COLOR_MAKE(0x0d, 0x57, 0xff), //blue, the small triangle at the bottom LV_COLOR_MAKE(0xff, 0xff, 0xff), LV_COLOR_MAKE(0xff, 0xff, 0xff), LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xe0, 0xb9, 0x00), //jaune gd triangle haut - LV_COLOR_MAKE(0xe8, 0x51, 0x02)}; //orange gd triangle bas + LV_COLOR_MAKE(0xe0, 0xb9, 0x00), //Yellow, large triangle on top left + LV_COLOR_MAKE(0xe8, 0x51, 0x02)}; //orange, large triangle on bottom left + +// Add new colors, still rainbow but more pastel like constexpr std::array rainbowVividColors ={LV_COLOR_MAKE(0xa5, 0xeb, 0x64), LV_COLOR_MAKE(0xfc, 0x42, 0xb5), LV_COLOR_MAKE(0xe7, 0xc1, 0xff), @@ -100,8 +115,8 @@ namespace { LV_COLOR_MAKE(0x11, 0x70, 0x5a)}; //define colors for texts and symbols + // gray is used for text symbols and time. I changed it to pink, because I can. //static constexpr lv_color_t grayColor = LV_COLOR_MAKE(0x99, 0x99, 0x99); - static constexpr lv_color_t grayColor = LV_COLOR_MAKE(0x99, 0x99, 0x99); static constexpr lv_color_t pinkColor = LV_COLOR_MAKE(0xfc, 0x42, 0xb5); constexpr const std::array* returnColor(colors color) { @@ -243,12 +258,16 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_TOP_MID, 0, -10); - //version par defaut de l'icône avant qu'il aie regardé le statut de l'alarme ? + // Based on existing code, I understand that items on the screen (date, bluteooth status..) + // are declared here with default states, and later below the state (date, ...) is assigned + // So I do the same to add the alarm status : I put a symbol that has a default value + // and a text that has a default value + // symbol alarmIcon = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(alarmIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_label_set_text_static(alarmIcon, Symbols::paw); lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); - //version par defaut de l'icône avant qu'il aie regardé le statut de l'alarme ? + // text labelAlarm = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_obj_set_style_local_text_font(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); @@ -481,8 +500,9 @@ void WatchFaceMeow::Refresh() { lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_TOP_MID, 0, -10); } - //Add alarm state and time + // Add alarm state and time // AlarmState is an enum type in class AlarmController that is in namespace controllers + // Not sure if it can handle automatically am / pm format (TODO) alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); @@ -495,14 +515,6 @@ void WatchFaceMeow::Refresh() { lv_label_set_text_static(labelAlarm, Symbols::none); } - //lv_label_set_text_fmt(labelMinutes, "%02d", minute); -/* - if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { - lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 5); - lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); - } - */ - stepCount = motionController.NbSteps(); if (stepCount.IsUpdated()) { From ecea1955330d39a8d2cdec33bb1871ac5625dc5a Mon Sep 17 00:00:00 2001 From: ecarlett Date: Mon, 27 May 2024 14:21:05 +0200 Subject: [PATCH 066/101] Add pictures in readme --- README.md | 4 ++++ doc/ui/meow_alarmnotset.png | Bin 0 -> 5721 bytes doc/ui/meow_alarmset.png | Bin 0 -> 6100 bytes 3 files changed, 4 insertions(+) create mode 100644 doc/ui/meow_alarmnotset.png create mode 100644 doc/ui/meow_alarmset.png diff --git a/README.md b/README.md index b58821ef..287c12c8 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,10 @@ Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devic - I stored the compile commands in scripts compile.sh to run from InfiniTime/ folder, and make_pine_mcu.sh to build the image must be run from InfiniTime/build/ (compile.sh copies make_pine_mcu.sh to build/ - The file to flash to the pinetime is InfiniTime/build/pinetime-mcuboot-app-dfu-1.14.0.zip : I didn't change the version compared to the one I downloaded from [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime) so make sure not to keep keep a copy of it +Here are pictures with and without alarm set : + +![Meow alarm set](doc/ui/meow_alarmset.png "Meow WatchFace, alarm set") ![Meow alarm not set](doc/ui/meow_alarmnotset.png "Meow WatchFace, alarm not set") + ## New to InfiniTime? - [Getting started with InfiniTime](doc/gettingStarted/gettingStarted-1.0.md) diff --git a/doc/ui/meow_alarmnotset.png b/doc/ui/meow_alarmnotset.png new file mode 100644 index 0000000000000000000000000000000000000000..ac11e47e6c88f1571c9a0648c67a6528fc01eb11 GIT binary patch literal 5721 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 O444?2BfsmrKKwtJMjBuM literal 0 HcmV?d00001 diff --git a/doc/ui/meow_alarmset.png b/doc/ui/meow_alarmset.png new file mode 100644 index 0000000000000000000000000000000000000000..e0b4b3f787e9c0f363ef0747f25c7ea53c079eff GIT binary patch literal 6100 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 literal 0 HcmV?d00001 From d6a6bda6ba29a32630483090351043ea5357a643 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Mon, 27 May 2024 17:15:02 +0200 Subject: [PATCH 067/101] start cat icon --- draft_pictures/cat.png | Bin 0 -> 10018 bytes draft_pictures/cat.xcf | Bin 0 -> 102679 bytes src/displayapp/screens/WatchFaceMeow.cpp | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 draft_pictures/cat.png create mode 100644 draft_pictures/cat.xcf diff --git a/draft_pictures/cat.png b/draft_pictures/cat.png new file mode 100644 index 0000000000000000000000000000000000000000..da3e6f725f415b34f1118bce1ca79c88ab6e762d GIT binary patch literal 10018 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 M4CC(}%pMp22X3;B`v3p{ literal 0 HcmV?d00001 diff --git a/draft_pictures/cat.xcf b/draft_pictures/cat.xcf new file mode 100644 index 0000000000000000000000000000000000000000..27de1c78bbd4412c0d542b41092f9b4a6e8823f6 GIT binary patch literal 102679 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 Date: Wed, 29 May 2024 12:56:22 +0200 Subject: [PATCH 068/101] test --- draft_pictures/cat.xcf | Bin 102679 -> 97057 bytes src/displayapp/screens/WatchFaceMeow.cpp | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/draft_pictures/cat.xcf b/draft_pictures/cat.xcf index 27de1c78bbd4412c0d542b41092f9b4a6e8823f6..f1df51f8ca87820d122e937e168457535113c542 100644 GIT binary patch delta 954 zcmXxiO-K}B7zgn8aZw5p(#?;hAXgy|6$ov*cnEftD7x7}S2xNa$ilFNaCQX8#X}&Q zm9M&`#`-~qnpjQIVTlf&Jb1_m5(L4V3fCtgt`TrZ3`OW`%=VjjanMq9KjBjVZ ztP)R$at>V$c@<+(k2ksPYf>cnmrx8{p|{dQMLL<)8tOwOFhO6#;8}R_DQpP9#tj&9 zQ8z88r)jO-;;J|!1$Ny3-7AsIQgnCI0nZf1HRtJL%qP-M6kA*tWf1SFgl$^w=1dl}dZgUs8 z^B^ruZ@thGQzM%RIzp3ll0K)ibe`h7i208B|NA~LFVH3Wk$%otr^H-g0bLUl%@^}G y>woDQU8h@gC(&ohQC%L+Ka#7ImHu&^GjC4q?O9p%NV(b@c57GYaN^%Xsn`eMLzuDv delta 6473 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 + 32 * (100 - batteryPercent) / 100)}; lv_line_set_points(lineBattery, lineBatteryPoints, 2); } From ad5a472b4b404214b7a4ee4ef2f95c47fe59970b Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 12:58:38 +0200 Subject: [PATCH 069/101] modified: .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 81e49ae0..13a8b796 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,9 @@ Makefile build tools +# My stuff +#draft_pictures + # Resulting binary files *.a *.so From ee99e554f0bf54d76190b7b06727baa8a3f1aaaf Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 13:15:28 +0200 Subject: [PATCH 070/101] copy :( files from gitlab repo interactive rebase in progress; onto b7a353cf Last command done (1 command done): pick 504f5774 copy :( files from gitlab repo Next commands to do (2 remaining commands): pick 9d04c32f recovering manually changes to original Infineat pick 70f9025f now I have the Meow Watchface AND the Infineat Watchface with alarm display You are currently rebasing branch 'my-custom-infinitime' on 'b7a353cf'. Changes to be committed: modified: src/CMakeLists.txt modified: src/displayapp/apps/Apps.h.in modified: src/displayapp/apps/CMakeLists.txt modified: src/displayapp/screens/Symbols.h --- src/CMakeLists.txt | 1 + src/displayapp/apps/Apps.h.in | 3 +++ src/displayapp/apps/CMakeLists.txt | 7 +++++++ src/displayapp/screens/Symbols.h | 9 +++------ 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index daf3f8ed..692ec23b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -429,6 +429,7 @@ list(APPEND SOURCE_FILES displayapp/screens/WatchFaceAnalog.cpp displayapp/screens/WatchFaceDigital.cpp displayapp/screens/WatchFaceInfineat.cpp + displayapp/screens/WatchFaceMeow.cpp displayapp/screens/WatchFaceTerminal.cpp displayapp/screens/WatchFacePineTimeStyle.cpp displayapp/screens/WatchFaceInfineatColors.cpp diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index bf7a803c..e0a80200 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -52,7 +52,10 @@ namespace Pinetime { PineTimeStyle, Terminal, Infineat, +<<<<<<< HEAD InfineatColors, +======= +>>>>>>> 504f5774 (copy :( files from gitlab repo) Meow, CasioStyleG7710, }; diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index b462c09e..be3e468b 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -25,11 +25,18 @@ else() set(DEFAULT_WATCHFACE_TYPES "WatchFace::Digital") #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Analog") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::PineTimeStyle") +<<<<<<< HEAD #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::InfineatColors") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Meow") #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") +======= + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Meow") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") +>>>>>>> 504f5774 (copy :( files from gitlab repo) set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware") endif() diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index 321adf29..016c91bd 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -14,7 +14,7 @@ namespace Pinetime { static constexpr const char* clock = "\xEF\x80\x97"; static constexpr const char* bell = "\xEF\x83\xB3"; static constexpr const char* notbell = "\xEF\x87\xB6"; - static constexpr const char* info = "\xEF\x84\xA9"; + static constexpr const char* info = "\xEF\x84\xA9"; static constexpr const char* list = "\xEF\x80\xBA"; static constexpr const char* sun = "\xEF\x86\x85"; static constexpr const char* check = "\xEF\x95\xA0"; @@ -41,14 +41,11 @@ namespace Pinetime { static constexpr const char* eye = "\xEF\x81\xAE"; static constexpr const char* home = "\xEF\x80\x95"; static constexpr const char* sleep = "\xEE\xBD\x84"; -<<<<<<< HEAD static constexpr const char* calendar = "\xEF\x81\xB3"; -======= static constexpr const char* bird = "\xEF\x92\xBA"; static constexpr const char* zzz = "\xEF\x88\xB6"; ->>>>>>> d7de641b (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) - - // fontawesome_weathericons.c + + // fontawesome_weathericons.c // static constexpr const char* sun = "\xEF\x86\x85"; static constexpr const char* cloudSun = "\xEF\x9B\x84"; static constexpr const char* cloudSunRain = "\xEF\x9D\x83"; From 6cb3b6060ec1ad0bec5e20dd64e79c4e12275072 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 15:22:16 +0200 Subject: [PATCH 071/101] test combiner tout --- src/CMakeLists.txt | 4 +++ src/components/settings/Settings.h | 10 ++++++ src/displayapp/UserApps.h | 3 ++ src/displayapp/fonts/fonts.json | 9 +++++ src/displayapp/screens/AlarmIcon.cpp | 6 ++++ src/displayapp/screens/Symbols.h | 33 +++++++++++++++++++ src/displayapp/screens/WatchFaceInfineat.cpp | 4 +++ src/displayapp/screens/WatchFaceMeow.cpp | 2 +- .../screens/settings/SettingWatchFace.h | 3 ++ 9 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 692ec23b..b94cd582 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -371,11 +371,15 @@ list(APPEND SOURCE_FILES displayapp/screens/StopWatch.cpp displayapp/screens/BatteryIcon.cpp displayapp/screens/BleIcon.cpp +<<<<<<< HEAD <<<<<<< HEAD displayapp/screens/AlarmIcon.cpp ======= displayapp/screens/AlarmIcon.cpp >>>>>>> f5dfbc44 (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) +======= + displayapp/screens/AlarmIcon.cpp +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) displayapp/screens/NotificationIcon.cpp displayapp/screens/SystemInfo.cpp displayapp/screens/Label.cpp diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 16dcdfd2..57fedbcf 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -55,12 +55,20 @@ namespace Pinetime { bool showSideCover = true; int colorIndex = 0; }; +<<<<<<< HEAD +======= + +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) struct WatchFaceMeow { bool showSideCover = true; int colorIndex = 0; }; +<<<<<<< HEAD +======= + +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) Settings(Pinetime::Controllers::FS& fs); Settings(const Settings&) = delete; @@ -328,6 +336,8 @@ namespace Pinetime { WatchFaceInfineatColors watchFaceInfineatColors; WatchFaceMeow watchFaceMeow; + WatchFaceMeow watchFaceMeow; + std::bitset<5> wakeUpMode {0}; uint16_t shakeWakeThreshold = 150; diff --git a/src/displayapp/UserApps.h b/src/displayapp/UserApps.h index f76af024..f4fb97ba 100644 --- a/src/displayapp/UserApps.h +++ b/src/displayapp/UserApps.h @@ -12,7 +12,10 @@ #include "displayapp/screens/WatchFaceAnalog.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" #include "displayapp/screens/WatchFaceInfineat.h" +<<<<<<< HEAD #include "displayapp/screens/WatchFaceInfineatColors.h" +======= +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) #include "displayapp/screens/WatchFaceMeow.h" #include "displayapp/screens/WatchFacePineTimeStyle.h" #include "displayapp/screens/WatchFaceTerminal.h" diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json index 7a3da446..30738176 100644 --- a/src/displayapp/fonts/fonts.json +++ b/src/displayapp/fonts/fonts.json @@ -7,15 +7,24 @@ }, { "file": "FontAwesome5-Solid+Brands+Regular.woff", +<<<<<<< HEAD <<<<<<< HEAD "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf0f3, 0xf1f6, 0xf073" ======= +======= +>>>>>>> 055b75d5 (test combiner tout) <<<<<<< HEAD "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf0f3, 0xf1f6" ======= "range": "0xf294, 0xf242, 0xf54b, 0xf1b0, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf4ba, 0xf236" >>>>>>> f5dfbc44 (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) +<<<<<<< HEAD >>>>>>> d7de641b (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) +======= +======= + "range": "0xf294, 0xf242, 0xf54b, 0xf1b0, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf4ba, 0xf236" +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) +>>>>>>> 055b75d5 (test combiner tout) } ], "bpp": 1, diff --git a/src/displayapp/screens/AlarmIcon.cpp b/src/displayapp/screens/AlarmIcon.cpp index 0ea1a8cb..4877adb8 100644 --- a/src/displayapp/screens/AlarmIcon.cpp +++ b/src/displayapp/screens/AlarmIcon.cpp @@ -4,15 +4,21 @@ using namespace Pinetime::Applications::Screens; const char* AlarmIcon::GetIcon(bool isSet) { if (isSet) { +<<<<<<< HEAD <<<<<<< HEAD return Symbols::bell; } return Symbols::notbell; ======= +======= +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) return Symbols::bird; } return Symbols::zzz; +<<<<<<< HEAD >>>>>>> f5dfbc44 (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) +======= +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) } diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index 016c91bd..6ddf87a8 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -10,11 +10,27 @@ namespace Pinetime { static constexpr const char* bluetooth = "\xEF\x8A\x94"; static constexpr const char* plug = "\xEF\x87\xA6"; static constexpr const char* shoe = "\xEF\x95\x8B"; +<<<<<<< HEAD +======= +<<<<<<< HEAD +<<<<<<< HEAD +>>>>>>> 055b75d5 (test combiner tout) static constexpr const char* paw = "\xEF\x86\xB0"; static constexpr const char* clock = "\xEF\x80\x97"; static constexpr const char* bell = "\xEF\x83\xB3"; static constexpr const char* notbell = "\xEF\x87\xB6"; static constexpr const char* info = "\xEF\x84\xA9"; +<<<<<<< HEAD +======= +>>>>>>> 504f5774 (copy :( files from gitlab repo) +======= + static constexpr const char* paw = "\xEF\x86\xB0"; + static constexpr const char* clock = "\xEF\x80\x97"; + static constexpr const char* bell = "\xEF\x83\xB3"; + static constexpr const char* notbell = "\xEF\x87\xB6"; + static constexpr const char* info = "\xEF\x84\xA9"; +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) +>>>>>>> 055b75d5 (test combiner tout) static constexpr const char* list = "\xEF\x80\xBA"; static constexpr const char* sun = "\xEF\x86\x85"; static constexpr const char* check = "\xEF\x95\xA0"; @@ -41,11 +57,28 @@ namespace Pinetime { static constexpr const char* eye = "\xEF\x81\xAE"; static constexpr const char* home = "\xEF\x80\x95"; static constexpr const char* sleep = "\xEE\xBD\x84"; +<<<<<<< HEAD static constexpr const char* calendar = "\xEF\x81\xB3"; static constexpr const char* bird = "\xEF\x92\xBA"; static constexpr const char* zzz = "\xEF\x88\xB6"; // fontawesome_weathericons.c +======= +<<<<<<< HEAD +<<<<<<< HEAD + static constexpr const char* bird = "\xEF\x92\xBA"; + static constexpr const char* zzz = "\xEF\x88\xB6"; +======= + static constexpr const char* bird = "\xEF\x92\xBA"; + static constexpr const char* zzz = "\xEF\x88\xB6"; +>>>>>>> 504f5774 (copy :( files from gitlab repo) +======= + static constexpr const char* bird = "\xEF\x92\xBA"; + static constexpr const char* zzz = "\xEF\x88\xB6"; +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) + + // fontawesome_weathericons.c +>>>>>>> 055b75d5 (test combiner tout) // static constexpr const char* sun = "\xEF\x86\x85"; static constexpr const char* cloudSun = "\xEF\x9B\x84"; static constexpr const char* cloudSunRain = "\xEF\x9D\x83"; diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index 20d0beb3..f0ed2b7b 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -259,7 +259,11 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, lv_obj_set_hidden(alarmIcon, true); lv_obj_set_hidden(labelTimeAmPmAlarm, true); } +<<<<<<< HEAD +======= + +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) stepValue = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); diff --git a/src/displayapp/screens/WatchFaceMeow.cpp b/src/displayapp/screens/WatchFaceMeow.cpp index c7328b4d..a4abadaa 100644 --- a/src/displayapp/screens/WatchFaceMeow.cpp +++ b/src/displayapp/screens/WatchFaceMeow.cpp @@ -199,7 +199,6 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, lineBattery = lv_line_create(lv_scr_act(), nullptr); lv_obj_set_style_local_line_width(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 24); lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); - lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); lv_obj_set_style_local_line_opa(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 190); lineBatteryPoints[0] = {27, 105}; lineBatteryPoints[1] = {27, 106}; @@ -534,6 +533,7 @@ void WatchFaceMeow::Refresh() { } void WatchFaceMeow::SetBatteryLevel(uint8_t batteryPercent) { + // starting point (y) + Pine64 logo height * (100 - batteryPercent) / 100 lineBatteryPoints[1] = {27, static_cast(105 + 32 * (100 - batteryPercent) / 100)}; lv_line_set_points(lineBattery, lineBatteryPoints, 2); } diff --git a/src/displayapp/screens/settings/SettingWatchFace.h b/src/displayapp/screens/settings/SettingWatchFace.h index 4425bdcd..ededf93f 100644 --- a/src/displayapp/screens/settings/SettingWatchFace.h +++ b/src/displayapp/screens/settings/SettingWatchFace.h @@ -10,7 +10,10 @@ #include "displayapp/screens/Symbols.h" #include "displayapp/screens/CheckboxList.h" #include "displayapp/screens/WatchFaceInfineat.h" +<<<<<<< HEAD #include "displayapp/screens/WatchFaceInfineatColors.h" +======= +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) #include "displayapp/screens/WatchFaceMeow.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" From bbbc9d9910aa170aaab11757e23233ba77f921df Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 15:17:36 +0200 Subject: [PATCH 072/101] now I have the Meow Watchface AND the Infineat Watchface with alarm display interactive rebase in progress; onto b7a353cf Last commands done (3 commands done): pick 9d04c32f recovering manually changes to original Infineat pick 70f9025f now I have the Meow Watchface AND the Infineat Watchface with alarm display No commands remaining. You are currently rebasing branch 'my-custom-infinitime' on 'b7a353cf'. Changes to be committed: modified: src/components/settings/Settings.h --- src/components/settings/Settings.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 57fedbcf..6d3100bd 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -48,11 +48,14 @@ namespace Pinetime { struct WatchFaceInfineat { bool showSideCover = true; bool showAlarmStatus = true; +<<<<<<< HEAD int colorIndex = 0; }; struct WatchFaceInfineatColors { bool showSideCover = true; +======= +>>>>>>> 70f9025f (now I have the Meow Watchface AND the Infineat Watchface with alarm display) int colorIndex = 0; }; <<<<<<< HEAD From ad19b9e798c0e31b8ecb4f615a9b34fcba9b8ee9 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 15:25:05 +0200 Subject: [PATCH 073/101] fix src/displayapp/apps/CMakeLists.txt after merge mess --- src/displayapp/apps/CMakeLists.txt | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index be3e468b..ccfd7347 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -23,20 +23,16 @@ if(DEFINED ENABLE_WATCHFACES) set(WATCHFACE_TYPES ${ENABLE_WATCHFACES} CACHE STRING "List of watch faces to build into the firmware") else() set(DEFAULT_WATCHFACE_TYPES "WatchFace::Digital") - #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Analog") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Analog") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::PineTimeStyle") -<<<<<<< HEAD - #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") - #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::InfineatColors") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Meow") - #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") -======= set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Meow") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") ->>>>>>> 504f5774 (copy :( files from gitlab repo) + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Meow") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware") endif() From 99d223b4d170351e6a734c61c593ffbfcc84fdf5 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 15:26:59 +0200 Subject: [PATCH 074/101] fix src/CMakeLists.txt after merge mess --- src/CMakeLists.txt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b94cd582..12e2e487 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -371,15 +371,7 @@ list(APPEND SOURCE_FILES displayapp/screens/StopWatch.cpp displayapp/screens/BatteryIcon.cpp displayapp/screens/BleIcon.cpp -<<<<<<< HEAD -<<<<<<< HEAD - displayapp/screens/AlarmIcon.cpp -======= displayapp/screens/AlarmIcon.cpp ->>>>>>> f5dfbc44 (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) -======= - displayapp/screens/AlarmIcon.cpp ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) displayapp/screens/NotificationIcon.cpp displayapp/screens/SystemInfo.cpp displayapp/screens/Label.cpp From d1c00370d1e2f2536e097392a2e71ea3201c7d51 Mon Sep 17 00:00:00 2001 From: Eve C Date: Thu, 30 May 2024 15:54:21 +0200 Subject: [PATCH 075/101] combiner calendar et meow --- src/displayapp/fonts/fonts.json | 4 ++++ src/displayapp/screens/Symbols.h | 18 ++++++++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json index 30738176..9b6c416e 100644 --- a/src/displayapp/fonts/fonts.json +++ b/src/displayapp/fonts/fonts.json @@ -8,6 +8,7 @@ { "file": "FontAwesome5-Solid+Brands+Regular.woff", <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf0f3, 0xf1f6, 0xf073" ======= @@ -25,6 +26,9 @@ "range": "0xf294, 0xf242, 0xf54b, 0xf1b0, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf4ba, 0xf236" >>>>>>> 9d04c32f (recovering manually changes to original Infineat) >>>>>>> 055b75d5 (test combiner tout) +======= + "range": "0xf294, 0xf242, 0xf54b, 0xf1b0, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf4ba, 0xf236, 0xf0f3, 0xf1f6" +>>>>>>> ce13c721 (fix fonts after merge mess) } ], "bpp": 1, diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index 6ddf87a8..14197a11 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -11,14 +11,18 @@ namespace Pinetime { static constexpr const char* plug = "\xEF\x87\xA6"; static constexpr const char* shoe = "\xEF\x95\x8B"; <<<<<<< HEAD +<<<<<<< HEAD ======= <<<<<<< HEAD <<<<<<< HEAD >>>>>>> 055b75d5 (test combiner tout) +======= +>>>>>>> ce13c721 (fix fonts after merge mess) static constexpr const char* paw = "\xEF\x86\xB0"; static constexpr const char* clock = "\xEF\x80\x97"; static constexpr const char* bell = "\xEF\x83\xB3"; static constexpr const char* notbell = "\xEF\x87\xB6"; +<<<<<<< HEAD static constexpr const char* info = "\xEF\x84\xA9"; <<<<<<< HEAD ======= @@ -31,6 +35,9 @@ namespace Pinetime { static constexpr const char* info = "\xEF\x84\xA9"; >>>>>>> 9d04c32f (recovering manually changes to original Infineat) >>>>>>> 055b75d5 (test combiner tout) +======= + static constexpr const char* info = "\xEF\x84\xA9"; +>>>>>>> ce13c721 (fix fonts after merge mess) static constexpr const char* list = "\xEF\x80\xBA"; static constexpr const char* sun = "\xEF\x86\x85"; static constexpr const char* check = "\xEF\x95\xA0"; @@ -57,6 +64,7 @@ namespace Pinetime { static constexpr const char* eye = "\xEF\x81\xAE"; static constexpr const char* home = "\xEF\x80\x95"; static constexpr const char* sleep = "\xEE\xBD\x84"; +<<<<<<< HEAD <<<<<<< HEAD static constexpr const char* calendar = "\xEF\x81\xB3"; static constexpr const char* bird = "\xEF\x92\xBA"; @@ -66,16 +74,10 @@ namespace Pinetime { ======= <<<<<<< HEAD <<<<<<< HEAD +======= +>>>>>>> ce13c721 (fix fonts after merge mess) static constexpr const char* bird = "\xEF\x92\xBA"; static constexpr const char* zzz = "\xEF\x88\xB6"; -======= - static constexpr const char* bird = "\xEF\x92\xBA"; - static constexpr const char* zzz = "\xEF\x88\xB6"; ->>>>>>> 504f5774 (copy :( files from gitlab repo) -======= - static constexpr const char* bird = "\xEF\x92\xBA"; - static constexpr const char* zzz = "\xEF\x88\xB6"; ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) // fontawesome_weathericons.c >>>>>>> 055b75d5 (test combiner tout) From dbc1dcfbdfe97f3a9ae69b50e80b471d97b48bdf Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 15:35:21 +0200 Subject: [PATCH 076/101] fix src/displayapp/UserApps.h after merge mess --- src/displayapp/UserApps.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/displayapp/UserApps.h b/src/displayapp/UserApps.h index f4fb97ba..a0e41f72 100644 --- a/src/displayapp/UserApps.h +++ b/src/displayapp/UserApps.h @@ -12,10 +12,6 @@ #include "displayapp/screens/WatchFaceAnalog.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" #include "displayapp/screens/WatchFaceInfineat.h" -<<<<<<< HEAD -#include "displayapp/screens/WatchFaceInfineatColors.h" -======= ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) #include "displayapp/screens/WatchFaceMeow.h" #include "displayapp/screens/WatchFacePineTimeStyle.h" #include "displayapp/screens/WatchFaceTerminal.h" From e8b1b94507fafabb0ae350ce37e11d47d2d73fbb Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 15:37:19 +0200 Subject: [PATCH 077/101] fix src/displayapp/screens/settings/SettingWatchFace.h after merge mess --- src/displayapp/screens/settings/SettingWatchFace.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/displayapp/screens/settings/SettingWatchFace.h b/src/displayapp/screens/settings/SettingWatchFace.h index ededf93f..359f12a6 100644 --- a/src/displayapp/screens/settings/SettingWatchFace.h +++ b/src/displayapp/screens/settings/SettingWatchFace.h @@ -10,11 +10,7 @@ #include "displayapp/screens/Symbols.h" #include "displayapp/screens/CheckboxList.h" #include "displayapp/screens/WatchFaceInfineat.h" -<<<<<<< HEAD -#include "displayapp/screens/WatchFaceInfineatColors.h" -======= ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) -#include "displayapp/screens/WatchFaceMeow.h" +include "displayapp/screens/WatchFaceMeow.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" From 055b9a93c4730d130fca7359ea07d4fa3bef4a49 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 15:40:21 +0200 Subject: [PATCH 078/101] fix AlarmIcon.cpp and WatchFaceInfineat.cpp after merge mess --- src/displayapp/screens/AlarmIcon.cpp | 15 +-------------- src/displayapp/screens/WatchFaceInfineat.cpp | 5 ----- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/displayapp/screens/AlarmIcon.cpp b/src/displayapp/screens/AlarmIcon.cpp index 4877adb8..77018dea 100644 --- a/src/displayapp/screens/AlarmIcon.cpp +++ b/src/displayapp/screens/AlarmIcon.cpp @@ -4,21 +4,8 @@ using namespace Pinetime::Applications::Screens; const char* AlarmIcon::GetIcon(bool isSet) { if (isSet) { -<<<<<<< HEAD -<<<<<<< HEAD - return Symbols::bell; - } - - return Symbols::notbell; -======= -======= ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) - return Symbols::bird; + return Symbols::bird; } return Symbols::zzz; -<<<<<<< HEAD ->>>>>>> f5dfbc44 (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) -======= ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) } diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index f0ed2b7b..0ebb5bff 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -259,11 +259,6 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, lv_obj_set_hidden(alarmIcon, true); lv_obj_set_hidden(labelTimeAmPmAlarm, true); } -<<<<<<< HEAD - -======= - ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) stepValue = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); From 7bab4a66aadcb106f507c24aa2cf4ebe1fd14174 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 15:43:30 +0200 Subject: [PATCH 079/101] fix Apps.h.in and src/components/settings/Settings.h after merge mess --- src/components/settings/Settings.h | 18 +----------------- src/displayapp/apps/Apps.h.in | 4 ---- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 6d3100bd..9237ea7f 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -48,30 +48,14 @@ namespace Pinetime { struct WatchFaceInfineat { bool showSideCover = true; bool showAlarmStatus = true; -<<<<<<< HEAD int colorIndex = 0; }; - struct WatchFaceInfineatColors { - bool showSideCover = true; -======= ->>>>>>> 70f9025f (now I have the Meow Watchface AND the Infineat Watchface with alarm display) - int colorIndex = 0; - }; -<<<<<<< HEAD - -======= - ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) struct WatchFaceMeow { bool showSideCover = true; int colorIndex = 0; }; -<<<<<<< HEAD - -======= - ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) + Settings(Pinetime::Controllers::FS& fs); Settings(const Settings&) = delete; diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index e0a80200..7f4d03fc 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -52,10 +52,6 @@ namespace Pinetime { PineTimeStyle, Terminal, Infineat, -<<<<<<< HEAD - InfineatColors, -======= ->>>>>>> 504f5774 (copy :( files from gitlab repo) Meow, CasioStyleG7710, }; From 5dfdec4ee243d44008813666fcda6b80fec2075a Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 15:48:35 +0200 Subject: [PATCH 080/101] fixing mess again after merge mess --- build/src/pinetime-mcuboot-app-dfu-1.14.0.zip | Bin 378895 -> 0 bytes src/CMakeLists.txt | 3 +- src/components/settings/Settings.h | 4 +- .../screens/WatchFaceInfineatColors.cpp | 516 ------------------ .../screens/WatchFaceInfineatColors.h | 124 ----- 5 files changed, 2 insertions(+), 645 deletions(-) delete mode 100644 build/src/pinetime-mcuboot-app-dfu-1.14.0.zip delete mode 100644 src/displayapp/screens/WatchFaceInfineatColors.cpp delete mode 100644 src/displayapp/screens/WatchFaceInfineatColors.h diff --git a/build/src/pinetime-mcuboot-app-dfu-1.14.0.zip b/build/src/pinetime-mcuboot-app-dfu-1.14.0.zip deleted file mode 100644 index 4904d149c01b8f34763e30cbadf8c98a7b703961..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 378895 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

g07$tydb60cWII2A`KR>!Qc4##SC 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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 12e2e487..a1d8bbd6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -428,9 +428,8 @@ list(APPEND SOURCE_FILES displayapp/screens/WatchFaceMeow.cpp displayapp/screens/WatchFaceTerminal.cpp displayapp/screens/WatchFacePineTimeStyle.cpp - displayapp/screens/WatchFaceInfineatColors.cpp displayapp/screens/WatchFaceMeow.cpp - #displayapp/screens/WatchFaceCasioStyleG7710.cpp + displayapp/screens/WatchFaceCasioStyleG7710.cpp ## diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 9237ea7f..1a9abcf4 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -320,9 +320,7 @@ namespace Pinetime { PineTimeStyle PTS; WatchFaceInfineat watchFaceInfineat; - WatchFaceInfineatColors watchFaceInfineatColors; - WatchFaceMeow watchFaceMeow; - + WatchFaceMeow watchFaceMeow; std::bitset<5> wakeUpMode {0}; diff --git a/src/displayapp/screens/WatchFaceInfineatColors.cpp b/src/displayapp/screens/WatchFaceInfineatColors.cpp deleted file mode 100644 index ab963493..00000000 --- a/src/displayapp/screens/WatchFaceInfineatColors.cpp +++ /dev/null @@ -1,516 +0,0 @@ -#include "displayapp/screens/WatchFaceInfineatColors.h" - -#include -#include -#include "displayapp/screens/Symbols.h" -#include "displayapp/screens/BleIcon.h" -#include "components/settings/Settings.h" -#include "components/battery/BatteryController.h" -#include "components/ble/BleController.h" -#include "components/ble/NotificationManager.h" -#include "components/motion/MotionController.h" - -using namespace Pinetime::Applications::Screens; - -namespace { - void event_handler(lv_obj_t* obj, lv_event_t event) { - auto* screen = static_cast(obj->user_data); - screen->UpdateSelected(obj, event); - } - - enum class colors { - orange, - blue, - green, - rainbow, - vivid, - pink, - nordGreen, - }; - - constexpr int nColors = 7; // must match number of colors in InfineatColorsColors - - constexpr int nLines = WatchFaceInfineatColors::nLines; - - constexpr std::array orangeColors = {LV_COLOR_MAKE(0xfd, 0x87, 0x2b), - LV_COLOR_MAKE(0xdb, 0x33, 0x16), - LV_COLOR_MAKE(0x6f, 0x10, 0x00), - LV_COLOR_MAKE(0xfd, 0x7a, 0x0a), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xe8, 0x51, 0x02), - LV_COLOR_MAKE(0xea, 0x1c, 0x00)}; - constexpr std::array blueColors = {LV_COLOR_MAKE(0xe7, 0xf8, 0xff), - LV_COLOR_MAKE(0x22, 0x32, 0xd0), - LV_COLOR_MAKE(0x18, 0x2a, 0x8b), - LV_COLOR_MAKE(0xe7, 0xf8, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0x59, 0x91, 0xff), - LV_COLOR_MAKE(0x16, 0x36, 0xff)}; - constexpr std::array greenColors = {LV_COLOR_MAKE(0xb8, 0xff, 0x9b), - LV_COLOR_MAKE(0x08, 0x86, 0x08), - LV_COLOR_MAKE(0x00, 0x4a, 0x00), - LV_COLOR_MAKE(0xb8, 0xff, 0x9b), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0x62, 0xd5, 0x15), - LV_COLOR_MAKE(0x00, 0x74, 0x00)}; - constexpr std::array rainbowColors = {LV_COLOR_MAKE(0x2d, 0xa4, 0x00), //vert petit triangle le plus haut - LV_COLOR_MAKE(0xac, 0x09, 0xc4), //purple petit triangle bas côté horloge - LV_COLOR_MAKE(0xfe, 0x03, 0x03), //rouge 2 petits triangles à côté de batterie - LV_COLOR_MAKE(0x0d, 0x57, 0xff), //bleu petit triangle le plus bas - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xe0, 0xb9, 0x00), //jaune gd triangle haut - LV_COLOR_MAKE(0xe8, 0x51, 0x02)}; //orange gd triangle bas - constexpr std::array rainbowVividColors ={LV_COLOR_MAKE(0xa5, 0xeb, 0x64), - LV_COLOR_MAKE(0xfc, 0x42, 0xb5), - LV_COLOR_MAKE(0xe7, 0xc1, 0xff), - LV_COLOR_MAKE(0x11, 0xdf, 0xfa), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xec, 0x5d), - LV_COLOR_MAKE(0xff, 0x93, 0xaf)}; - - constexpr std::array pinkColors = {LV_COLOR_MAKE(0xff, 0xe5, 0xec), - LV_COLOR_MAKE(0xff, 0xb3, 0xc6), - LV_COLOR_MAKE(0xfb, 0x6f, 0x92), - LV_COLOR_MAKE(0xff, 0xe5, 0xec), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xc2, 0xd1), - LV_COLOR_MAKE(0xff, 0x8f, 0xab)}; - constexpr std::array nordGreenColors = {LV_COLOR_MAKE(0xd5, 0xf0, 0xe9), - LV_COLOR_MAKE(0x23, 0x83, 0x73), - LV_COLOR_MAKE(0x1d, 0x41, 0x3f), - LV_COLOR_MAKE(0xd5, 0xf0, 0xe9), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0x2f, 0xb8, 0xa2), - LV_COLOR_MAKE(0x11, 0x70, 0x5a)}; - //define colors for texts and symbols - //static constexpr lv_color_t grayColor = LV_COLOR_MAKE(0x99, 0x99, 0x99); - static constexpr lv_color_t pinkColor = LV_COLOR_MAKE(0xff, 0x93, 0xaf); - - constexpr const std::array* returnColor(colors color) { - if (color == colors::orange) { - return &orangeColors; - } - if (color == colors::blue) { - return &blueColors; - } - if (color == colors::green) { - return &greenColors; - } - if (color == colors::rainbow) { - return &rainbowColors; - } - if (color == colors::vivid) { - return &rainbowVividColors; - } - if (color == colors::pink) { - return &pinkColors; - } - return &nordGreenColors; - } -} - -WatchFaceInfineatColors::WatchFaceInfineatColors(Controllers::DateTime& dateTimeController, - const Controllers::Battery& batteryController, - const Controllers::Ble& bleController, - Controllers::NotificationManager& notificationManager, - Controllers::Settings& settingsController, - Controllers::MotionController& motionController, - Controllers::FS& filesystem) - : currentDateTime {{}}, - dateTimeController {dateTimeController}, - batteryController {batteryController}, - bleController {bleController}, - notificationManager {notificationManager}, - settingsController {settingsController}, - motionController {motionController} { - lfs_file f = {}; - if (filesystem.FileOpen(&f, "/fonts/teko.bin", LFS_O_RDONLY) >= 0) { - filesystem.FileClose(&f); - font_teko = lv_font_load("F:/fonts/teko.bin"); - } - - if (filesystem.FileOpen(&f, "/fonts/bebas.bin", LFS_O_RDONLY) >= 0) { - filesystem.FileClose(&f); - font_bebas = lv_font_load("F:/fonts/bebas.bin"); - } - - // Side Cover - static constexpr lv_point_t linePoints[nLines][2] = {{{30, 25}, {68, -8}}, - {{26, 167}, {43, 216}}, - {{27, 40}, {27, 196}}, - {{12, 182}, {65, 249}}, - {{17, 99}, {17, 144}}, - {{14, 81}, {40, 127}}, - {{14, 163}, {40, 118}}, - {{-20, 124}, {25, -11}}, - {{-29, 89}, {27, 254}}}; - - static constexpr lv_style_int_t lineWidths[nLines] = {18, 15, 14, 22, 20, 18, 18, 52, 48}; - - const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); - for (int i = 0; i < nLines; i++) { - lines[i] = lv_line_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_line_width(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, lineWidths[i]); - lv_color_t color = (*colors)[i]; - lv_obj_set_style_local_line_color(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, color); - lv_line_set_points(lines[i], linePoints[i], 2); - } - - logoPine = lv_img_create(lv_scr_act(), nullptr); - lv_img_set_src(logoPine, "F:/images/pine_small.bin"); - lv_obj_set_pos(logoPine, 15, 106); - - lineBattery = lv_line_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_line_width(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 24); - lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); - lv_obj_set_style_local_line_opa(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 190); - lineBatteryPoints[0] = {27, 105}; - lineBatteryPoints[1] = {27, 106}; - lv_line_set_points(lineBattery, lineBatteryPoints, 2); - lv_obj_move_foreground(lineBattery); - - notificationIcon = lv_obj_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); - lv_obj_set_style_local_radius(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE); - lv_obj_set_size(notificationIcon, 13, 13); - lv_obj_set_hidden(notificationIcon, true); - - if (!settingsController.GetInfineatShowSideCover()) { - ToggleBatteryIndicatorColor(false); - for (auto& line : lines) { - lv_obj_set_hidden(line, true); - } - } - - timeContainer = lv_obj_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_bg_opa(timeContainer, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); - lv_obj_set_size(timeContainer, 185, 185); - lv_obj_align(timeContainer, lv_scr_act(), LV_ALIGN_CENTER, 0, -10); - - labelHour = lv_label_create(lv_scr_act(), nullptr); - lv_label_set_text_static(labelHour, "01"); - lv_obj_set_style_local_text_font(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); - lv_obj_set_style_local_text_color(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 0); - - labelMinutes = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_font(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); - lv_obj_set_style_local_text_color(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_label_set_text_static(labelMinutes, "00"); - lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); - - labelTimeAmPm = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_font(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); - lv_obj_set_style_local_text_color(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - - lv_label_set_text_static(labelTimeAmPm, ""); - lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 15); - - dateContainer = lv_obj_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_bg_opa(dateContainer, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); - lv_obj_set_size(dateContainer, 60, 30); - lv_obj_align(dateContainer, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 5); - - labelDate = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_obj_set_style_local_text_font(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); - lv_obj_align(labelDate, dateContainer, LV_ALIGN_IN_TOP_MID, 0, 0); - lv_label_set_text_static(labelDate, "Mon 01"); - - bleIcon = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_label_set_text_static(bleIcon, Symbols::bluetooth); - lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); - - stepValue = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); - lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 10, 0); - lv_label_set_text_static(stepValue, "0"); - - stepIcon = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_label_set_text_static(stepIcon, Symbols::paw); - lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); - - - // Setting buttons - btnClose = lv_btn_create(lv_scr_act(), nullptr); - btnClose->user_data = this; - lv_obj_set_size(btnClose, 60, 60); - lv_obj_align(btnClose, lv_scr_act(), LV_ALIGN_CENTER, 0, -80); - lv_obj_set_style_local_bg_opa(btnClose, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); - lv_obj_t* lblClose = lv_label_create(btnClose, nullptr); - lv_label_set_text_static(lblClose, "X"); - lv_obj_set_event_cb(btnClose, event_handler); - lv_obj_set_hidden(btnClose, true); - - btnNextColor = lv_btn_create(lv_scr_act(), nullptr); - btnNextColor->user_data = this; - lv_obj_set_size(btnNextColor, 60, 60); - lv_obj_align(btnNextColor, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -15, 0); - lv_obj_set_style_local_bg_opa(btnNextColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); - lv_obj_t* lblNextColor = lv_label_create(btnNextColor, nullptr); - lv_label_set_text_static(lblNextColor, ">"); - lv_obj_set_event_cb(btnNextColor, event_handler); - lv_obj_set_hidden(btnNextColor, true); - - btnPrevColor = lv_btn_create(lv_scr_act(), nullptr); - btnPrevColor->user_data = this; - lv_obj_set_size(btnPrevColor, 60, 60); - lv_obj_align(btnPrevColor, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 15, 0); - lv_obj_set_style_local_bg_opa(btnPrevColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); - lv_obj_t* lblPrevColor = lv_label_create(btnPrevColor, nullptr); - lv_label_set_text_static(lblPrevColor, "<"); - lv_obj_set_event_cb(btnPrevColor, event_handler); - lv_obj_set_hidden(btnPrevColor, true); - - btnToggleCover = lv_btn_create(lv_scr_act(), nullptr); - btnToggleCover->user_data = this; - lv_obj_set_size(btnToggleCover, 60, 60); - lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_CENTER, 0, 80); - lv_obj_set_style_local_bg_opa(btnToggleCover, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); - const char* labelToggle = settingsController.GetInfineatShowSideCover() ? "ON" : "OFF"; - lblToggle = lv_label_create(btnToggleCover, nullptr); - lv_label_set_text_static(lblToggle, labelToggle); - lv_obj_set_event_cb(btnToggleCover, event_handler); - lv_obj_set_hidden(btnToggleCover, true); - - // Button to access the settings - btnSettings = lv_btn_create(lv_scr_act(), nullptr); - btnSettings->user_data = this; - lv_obj_set_size(btnSettings, 150, 150); - lv_obj_align(btnSettings, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); - lv_obj_set_style_local_radius(btnSettings, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 30); - lv_obj_set_style_local_bg_opa(btnSettings, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); - lv_obj_set_event_cb(btnSettings, event_handler); - labelBtnSettings = lv_label_create(btnSettings, nullptr); - lv_obj_set_style_local_text_font(labelBtnSettings, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48); - lv_label_set_text_static(labelBtnSettings, Symbols::settings); - lv_obj_set_hidden(btnSettings, true); - - taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); - Refresh(); -} - -WatchFaceInfineatColors::~WatchFaceInfineatColors() { - lv_task_del(taskRefresh); - - if (font_bebas != nullptr) { - lv_font_free(font_bebas); - } - if (font_teko != nullptr) { - lv_font_free(font_teko); - } - - lv_obj_clean(lv_scr_act()); -} - -bool WatchFaceInfineatColors::OnTouchEvent(Pinetime::Applications::TouchEvents event) { - if ((event == Pinetime::Applications::TouchEvents::LongTap) && lv_obj_get_hidden(btnSettings)) { - lv_obj_set_hidden(btnSettings, false); - savedTick = lv_tick_get(); - return true; - } - // Prevent screen from sleeping when double tapping with settings on - if ((event == Pinetime::Applications::TouchEvents::DoubleTap) && !lv_obj_get_hidden(btnClose)) { - return true; - } - return false; -} - -void WatchFaceInfineatColors::CloseMenu() { - settingsController.SaveSettings(); - lv_obj_set_hidden(btnClose, true); - lv_obj_set_hidden(btnNextColor, true); - lv_obj_set_hidden(btnPrevColor, true); - lv_obj_set_hidden(btnToggleCover, true); -} - -bool WatchFaceInfineatColors::OnButtonPushed() { - if (!lv_obj_get_hidden(btnClose)) { - CloseMenu(); - return true; - } - return false; -} - -void WatchFaceInfineatColors::UpdateSelected(lv_obj_t* object, lv_event_t event) { - if (event == LV_EVENT_CLICKED) { - bool showSideCover = settingsController.GetInfineatShowSideCover(); - int colorIndex = settingsController.GetInfineatColorIndex(); - - if (object == btnSettings) { - lv_obj_set_hidden(btnSettings, true); - lv_obj_set_hidden(btnClose, false); - lv_obj_set_hidden(btnNextColor, !showSideCover); - lv_obj_set_hidden(btnPrevColor, !showSideCover); - lv_obj_set_hidden(btnToggleCover, false); - } - if (object == btnClose) { - CloseMenu(); - } - if (object == btnToggleCover) { - settingsController.SetInfineatShowSideCover(!showSideCover); - ToggleBatteryIndicatorColor(!showSideCover); - for (auto& line : lines) { - lv_obj_set_hidden(line, showSideCover); - } - lv_obj_set_hidden(btnNextColor, showSideCover); - lv_obj_set_hidden(btnPrevColor, showSideCover); - const char* labelToggle = showSideCover ? "OFF" : "ON"; - lv_label_set_text_static(lblToggle, labelToggle); - } - if (object == btnNextColor) { - colorIndex = (colorIndex + 1) % nColors; - settingsController.SetInfineatColorIndex(colorIndex); - } - if (object == btnPrevColor) { - colorIndex -= 1; - if (colorIndex < 0) - colorIndex = nColors - 1; - settingsController.SetInfineatColorIndex(colorIndex); - } - if (object == btnNextColor || object == btnPrevColor) { - const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); - for (int i = 0; i < nLines; i++) { - lv_color_t color = (*colors)[i]; - lv_obj_set_style_local_line_color(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, color); - } - lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); - lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); - } - } -} - -void WatchFaceInfineatColors::Refresh() { - notificationState = notificationManager.AreNewNotificationsAvailable(); - if (notificationState.IsUpdated()) { - lv_obj_set_hidden(notificationIcon, !notificationState.Get()); - lv_obj_align(notificationIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0); - } - - currentDateTime = std::chrono::time_point_cast(dateTimeController.CurrentDateTime()); - if (currentDateTime.IsUpdated()) { - uint8_t hour = dateTimeController.Hours(); - uint8_t minute = dateTimeController.Minutes(); - - if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { - char ampmChar[3] = "AM"; - if (hour == 0) { - hour = 12; - } else if (hour == 12) { - ampmChar[0] = 'P'; - } else if (hour > 12) { - hour = hour - 12; - ampmChar[0] = 'P'; - } - lv_label_set_text(labelTimeAmPm, ampmChar); - } - lv_label_set_text_fmt(labelHour, "%02d", hour); - lv_label_set_text_fmt(labelMinutes, "%02d", minute); - - if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { - lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 10); - lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 5); - lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); - } - - currentDate = std::chrono::time_point_cast(currentDateTime.Get()); - if (currentDate.IsUpdated()) { - uint8_t day = dateTimeController.Day(); - Controllers::DateTime::Days dayOfWeek = dateTimeController.DayOfWeek(); - lv_label_set_text_fmt(labelDate, "%s %02d", dateTimeController.DayOfWeekShortToStringLow(dayOfWeek), day); - lv_obj_realign(labelDate); - } - } - - batteryPercentRemaining = batteryController.PercentRemaining(); - isCharging = batteryController.IsCharging(); - if (batteryController.IsCharging()) { // Charging battery animation - chargingBatteryPercent += 1; - if (chargingBatteryPercent > 100) { - chargingBatteryPercent = batteryPercentRemaining.Get(); - } - SetBatteryLevel(chargingBatteryPercent); - } else if (isCharging.IsUpdated() || batteryPercentRemaining.IsUpdated()) { - chargingBatteryPercent = batteryPercentRemaining.Get(); - SetBatteryLevel(chargingBatteryPercent); - } - - bleState = bleController.IsConnected(); - bleRadioEnabled = bleController.IsRadioEnabled(); - if (bleState.IsUpdated()) { - lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); - lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 3); - } - - stepCount = motionController.NbSteps(); - if (stepCount.IsUpdated()) { - lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); - lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 10, 0); - lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); - } - - if (!lv_obj_get_hidden(btnSettings)) { - if ((savedTick > 0) && (lv_tick_get() - savedTick > 3000)) { - lv_obj_set_hidden(btnSettings, true); - savedTick = 0; - } - } -} - -void WatchFaceInfineatColors::SetBatteryLevel(uint8_t batteryPercent) { - // starting point (y) + Pine64 logo height * (100 - batteryPercent) / 100 - lineBatteryPoints[1] = {27, static_cast(105 + 32 * (100 - batteryPercent) / 100)}; - lv_line_set_points(lineBattery, lineBatteryPoints, 2); -} - -void WatchFaceInfineatColors::ToggleBatteryIndicatorColor(bool showSideCover) { - if (!showSideCover) { // make indicator and notification icon color white - lv_obj_set_style_local_image_recolor_opa(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_100); - lv_obj_set_style_local_image_recolor(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); - lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); - lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); - } else { - lv_obj_set_style_local_image_recolor_opa(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_0); - const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); - lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); - lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); - } -} - -bool WatchFaceInfineatColors::IsAvailable(Pinetime::Controllers::FS& filesystem) { - lfs_file file = {}; - - if (filesystem.FileOpen(&file, "/fonts/teko.bin", LFS_O_RDONLY) < 0) { - return false; - } - - filesystem.FileClose(&file); - if (filesystem.FileOpen(&file, "/fonts/bebas.bin", LFS_O_RDONLY) < 0) { - return false; - } - - filesystem.FileClose(&file); - if (filesystem.FileOpen(&file, "/images/pine_small.bin", LFS_O_RDONLY) < 0) { - return false; - } - - filesystem.FileClose(&file); - return true; -} diff --git a/src/displayapp/screens/WatchFaceInfineatColors.h b/src/displayapp/screens/WatchFaceInfineatColors.h deleted file mode 100644 index 27557c2d..00000000 --- a/src/displayapp/screens/WatchFaceInfineatColors.h +++ /dev/null @@ -1,124 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include "displayapp/screens/Screen.h" -#include "components/datetime/DateTimeController.h" -#include "utility/DirtyValue.h" -#include "displayapp/apps/Apps.h" - -namespace Pinetime { - namespace Controllers { - class Settings; - class Battery; - class Ble; - class NotificationManager; - class MotionController; - } - - namespace Applications { - namespace Screens { - - class WatchFaceInfineatColors : public Screen { - public: - static constexpr int nLines = 9; - WatchFaceInfineatColors(Controllers::DateTime& dateTimeController, - const Controllers::Battery& batteryController, - const Controllers::Ble& bleController, - Controllers::NotificationManager& notificationManager, - Controllers::Settings& settingsController, - Controllers::MotionController& motionController, - Controllers::FS& fs); - - ~WatchFaceInfineatColors() override; - - bool OnTouchEvent(TouchEvents event) override; - bool OnButtonPushed() override; - void UpdateSelected(lv_obj_t* object, lv_event_t event); - void CloseMenu(); - - void Refresh() override; - - static bool IsAvailable(Pinetime::Controllers::FS& filesystem); - - private: - uint32_t savedTick = 0; - uint8_t chargingBatteryPercent = 101; // not a mistake ;) - - Utility::DirtyValue batteryPercentRemaining {}; - Utility::DirtyValue isCharging {}; - Utility::DirtyValue bleState {}; - Utility::DirtyValue bleRadioEnabled {}; - Utility::DirtyValue> currentDateTime {}; - Utility::DirtyValue stepCount {}; - Utility::DirtyValue notificationState {}; - Utility::DirtyValue> currentDate; - - // Lines making up the side cover - lv_obj_t* lineBattery; - - lv_point_t lineBatteryPoints[2]; - - lv_obj_t* logoPine; - - lv_obj_t* timeContainer; - lv_obj_t* labelHour; - lv_obj_t* labelMinutes; - lv_obj_t* labelTimeAmPm; - lv_obj_t* dateContainer; - lv_obj_t* labelDate; - lv_obj_t* bleIcon; - lv_obj_t* stepIcon; - lv_obj_t* pawIcon; - lv_obj_t* stepValue; - lv_obj_t* notificationIcon; - lv_obj_t* btnClose; - lv_obj_t* btnNextColor; - lv_obj_t* btnToggleCover; - lv_obj_t* btnPrevColor; - lv_obj_t* btnSettings; - lv_obj_t* labelBtnSettings; - lv_obj_t* lblToggle; - - lv_obj_t* lines[nLines]; - - Controllers::DateTime& dateTimeController; - const Controllers::Battery& batteryController; - const Controllers::Ble& bleController; - Controllers::NotificationManager& notificationManager; - Controllers::Settings& settingsController; - Controllers::MotionController& motionController; - - void SetBatteryLevel(uint8_t batteryPercent); - void ToggleBatteryIndicatorColor(bool showSideCover); - - lv_task_t* taskRefresh; - lv_font_t* font_teko = nullptr; - lv_font_t* font_bebas = nullptr; - }; - } - - template <> - struct WatchFaceTraits { - static constexpr WatchFace watchFace = WatchFace::InfineatColors; - static constexpr const char* name = "InfineatColors face"; - - static Screens::Screen* Create(AppControllers& controllers) { - return new Screens::WatchFaceInfineatColors(controllers.dateTimeController, - controllers.batteryController, - controllers.bleController, - controllers.notificationManager, - controllers.settingsController, - controllers.motionController, - controllers.filesystem); - }; - - static bool IsAvailable(Pinetime::Controllers::FS& filesystem) { - return Screens::WatchFaceInfineatColors::IsAvailable(filesystem); - } - }; - } -} From 9437a8d12557554d6c01b5b211c7e81b0450cd0d Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 15:54:56 +0200 Subject: [PATCH 081/101] now there is my watchface in pink and stuff adn the Infineat watchface with alarm status but the icons are not the same as on branch alarm-on-infineat --- src/displayapp/screens/settings/SettingWatchFace.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/displayapp/screens/settings/SettingWatchFace.h b/src/displayapp/screens/settings/SettingWatchFace.h index 359f12a6..0e74e2f9 100644 --- a/src/displayapp/screens/settings/SettingWatchFace.h +++ b/src/displayapp/screens/settings/SettingWatchFace.h @@ -10,7 +10,7 @@ #include "displayapp/screens/Symbols.h" #include "displayapp/screens/CheckboxList.h" #include "displayapp/screens/WatchFaceInfineat.h" -include "displayapp/screens/WatchFaceMeow.h" +#include "displayapp/screens/WatchFaceMeow.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" From f7333c66bd1325e987e928d10b651e13b6d142ae Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 16:27:58 +0200 Subject: [PATCH 082/101] copied a memo I made with files to modify when ADDING a watchface. may be incomplete.. --- src/displayapp/screens/addWatchface.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/displayapp/screens/addWatchface.md diff --git a/src/displayapp/screens/addWatchface.md b/src/displayapp/screens/addWatchface.md new file mode 100644 index 00000000..aa8459e8 --- /dev/null +++ b/src/displayapp/screens/addWatchface.md @@ -0,0 +1,11 @@ +# Add a new watchface : +## Modify the following files with the names of your source files : + +- /src/displayapp/apps/Apps.h.in +- /src/components/settings/Settings.h +- /src/displayapp/screens/settings/SettingWatchFace.h +- /src/displayapp/UserApps.h +- /src/displayapp/apps/CMakeLists.txt +- CMakelists.txt + + From 5d261f579d7fc3e31f5d59b4401a2bf2fde8f517 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 17:38:21 +0200 Subject: [PATCH 083/101] modified the battery icon in Meow to be a cat face. add pictures accordingly. doesnt work on pt --- draft_pictures/cat.xcf | Bin 97057 -> 105537 bytes draft_pictures/cat_clean.xcf | Bin 0 -> 18361 bytes draft_pictures/cat_small.xcf | Bin 0 -> 1846 bytes src/displayapp/screens/WatchFaceMeow.cpp | 4 ++-- src/resources/images.json | 9 ++++++++- src/resources/images/cat_clean.png | Bin 0 -> 1344 bytes 6 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 draft_pictures/cat_clean.xcf create mode 100644 draft_pictures/cat_small.xcf create mode 100644 src/resources/images/cat_clean.png diff --git a/draft_pictures/cat.xcf b/draft_pictures/cat.xcf index f1df51f8ca87820d122e937e168457535113c542..6bd6671c8e4ca121d0f3e2d4adc94ca226181841 100644 GIT binary patch delta 28754 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 diff --git a/draft_pictures/cat_clean.xcf b/draft_pictures/cat_clean.xcf new file mode 100644 index 0000000000000000000000000000000000000000..b554ef179d5e16775450e00bcd95508ac8345bda GIT binary patch literal 18361 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 JLH$g<{tu&V1{VMT literal 0 HcmV?d00001 diff --git a/draft_pictures/cat_small.xcf b/draft_pictures/cat_small.xcf new file mode 100644 index 0000000000000000000000000000000000000000..76c7751d249a931636095194a823061a79946aed GIT binary patch literal 1846 zcmeHIT})eb6hFN!EiEl~6A&YF-fcQTgQ0uCJ#B{J$0E~ZAGv1n!BX0K18cEm1hSA3 zr^aP5CVuRtQVL~TMoacVMobj9=@M~66gHEYIG4<%QCTeuD^ON?Jg4-UyJSB2;G5^> zch2ve^FOEmdzzkll)bG|htn;!*Vyeq@*ts{3CR({N`g*Ds)SIu<&&SQ7{p5n27-ma zMa%_48DWgD!b7dAmAwv+tJ>pNnkWR^Csr9~>uhmJ0bh&KRAs4jdz`)&pI@rXnQFhM z(IM639(xs~8}F00OSy;B9f4MdQ}KEvdkuM=_jq}ydY@NmlN7J>4Of6Gd~SEZ)h6Z6 zYxpv~_b}gCBf1wf4pHN?8ebh8zhhCI!`bZd`}TQrugzm&F%o!8NLa&sb2n)kD}m!& zYR+>quvWuYX*iFZ&$CDbl^1EaRl{u>E)wU@Q^iDt>$(5qc}@l%V~!+b*J-%=wX5$$ zDHdVJvdw9A6#07z-uvXZ=3O-|h$0BGUbKQiHi#BLy+JMz3&ALxMJ%?kF4O4J9RDX> zVxb-gdJ#+YGC~1$LJ-IkWF2|biz*Qc$(kubw#+Q+!6;&hNj3r+3&aB10tV3_7Xj+1 zay|7iTe-&*f{cYmV*lUwznt&VQynzn^tx6z)qXrahc_!sw(>`wuC-Z}3?55ybo%sk?=QD;>E(|Foc-)drXTT{ z=fmMWrfrC1C2?gIANuO||i~|FVF0GvVkT5sGGa^$dRSz@dJO^a$7uQzP+c z@?-+>%i~wErztW%b#(GOTo}M;a+}Q}3O@-T@3@{5%ntS8$tjG# zmilHQa-lzXaUc=t#rekH@bd79;Kr_xcKnHVJ4X@Ub}C!zKOH{1bN1#)JqTxzI^Zn* z?p%EfiOpM2g|hK-D2;P;Kfp`#GqJO^meE6qG5eO?nP~FI`anYxvHgSq&wW3JiJ{?x zgV98DI|dQII`=4m-W|KX&>xyO(tYgxFRle~VzABtGpk?=lJByEM b#F!;V-BtOV>0!+QHAf-S1~q;_w}^iM?F8FJ literal 0 HcmV?d00001 diff --git a/src/displayapp/screens/WatchFaceMeow.cpp b/src/displayapp/screens/WatchFaceMeow.cpp index a4abadaa..b6fb030b 100644 --- a/src/displayapp/screens/WatchFaceMeow.cpp +++ b/src/displayapp/screens/WatchFaceMeow.cpp @@ -193,7 +193,7 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, //Battery indicator logoPine = lv_img_create(lv_scr_act(), nullptr); - lv_img_set_src(logoPine, "F:/images/pine_small.bin"); + lv_img_set_src(logoPine, "F:/images/cat_small.bin"); lv_obj_set_pos(logoPine, 15, 106); lineBattery = lv_line_create(lv_scr_act(), nullptr); @@ -565,7 +565,7 @@ bool WatchFaceMeow::IsAvailable(Pinetime::Controllers::FS& filesystem) { } filesystem.FileClose(&file); - if (filesystem.FileOpen(&file, "/images/pine_small.bin", LFS_O_RDONLY) < 0) { + if (filesystem.FileOpen(&file, "/images/cat_small.bin", LFS_O_RDONLY) < 0) { return false; } diff --git a/src/resources/images.json b/src/resources/images.json index e4247188..2591bcbb 100644 --- a/src/resources/images.json +++ b/src/resources/images.json @@ -1,11 +1,18 @@ { - "pine_small" : { + "pine_small" : { "sources": "images/pine_logo.png", "color_format": "CF_TRUE_COLOR_ALPHA", "output_format": "bin", "binary_format": "ARGB8565_RBSWAP", "target_path": "/images/" }, + "cat_small" : { + "sources": "images/cat_clean.png", + "color_format": "CF_TRUE_COLOR_ALPHA", + "output_format": "bin", + "binary_format": "ARGB8565_RBSWAP", + "target_path": "/images/" + }, "navigation0" : { "sources": "images/navigation0.png", "color_format": "CF_INDEXED_1_BIT", diff --git a/src/resources/images/cat_clean.png b/src/resources/images/cat_clean.png new file mode 100644 index 0000000000000000000000000000000000000000..6f8da93a9c060c3405fff35442a8c96114417fdb GIT binary patch literal 1344 zcmV-G1;6@EX>4Tx04R}tkv&MmKpe$iTZ>XE9qb^Y5TQC*5EXIMDionYs1;guFuC*#nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|?BJy6A|?JWDYS_3;J6>}?mh0_0scmXsb<$WplX(p zP9}tGZdK@hMG!-XU>G5Znfjb4rrU7TlmpZjz4DtVIuK7n|a>4rtTK|H-_ z>74h8qpTz;#OK6g23?T&k?XR{Z=8z``*~*6$fW0qqr^h7gXIopB|{~iB91AlM*04% z%L?Z$&T6H`TKD8H4Cl3#Wv<2)@|%#|Y593pDGt{e5iP%@ZK-3|wh#f3*S3e3D*o zYq29BvJG5Zw>5bWxZDBypLEHP94SE4Unl_YXY@@upzjt4t+~Cm_Hp_EWT>mu4RCM> zj20<--Q(RooxS~grq$mMT6=PxXbY~@-zvhh?@bWyut84^Sxb|E8%u)?Gw2#PL`(OV=HJ#@hyf((h+_|S`*d94i7 z(j>DZMxln6I(E!$scqDzmt_nG70M5Wy=Q&j|G(C^F8`gSl&-{Gm{XQz=@eGG-5wWQ zZ`E)yF2TZ-(#TUFy#XKK9-LDP`#3c5c}i*Y{~)~+OVE!g7*@ljn2axQ4OaXY@^-uZ zDCQ%>h(v6QksO{!alsy=?t4Xb?PYO*o+Py!kdBrDAE9GUCwXA(jZ%o zRT#j2?CR<18IQ-YHf(ei)X-^Wh;*KMBWYVi0GC{SJJEmvJxl zg}tW{e{PS%8*#x9(xVvYbUMu_%*S}JYyWz@8n_YIiA^=!hh>q=@j>3bmV{$CEu!Ar zwf|{w+=%-z64Ptp=?Z)okzR*y0#pS_vx{gN7Q}l8+XDX=zK$F8)sQd|>*L_n_%?v^ zqf5G12R}rSSK?N*ussTS6)vk`Y*!%`;~*}_hOi&Pyr{+Z;q2Ks`*YwMSQs2IrqShT zKYqk)oEOo&i%~ez>2zL<$Yun1JGMqwY{TJ5Q7>u@%#Gjg7>(Dq81p>RG7;OnBQ2A$G|RFLtyb$G{ti`J9M{jH zlU^%|;$LTt?uwLUxdU@kN*iNz7ll?V8XO#46C`(sk{!k#yigRy)}cCGKc0A3Rkb_M z^9-}1N2la@zPqol?}5;RGeU44DT-p?cpUW;eHp5%+L!0~x@h>ly}iAYn$2d4NO$3> zq9}Hr2wywdXQwR76?g`_vMiezoiZOYilXR08TKD9tNxU0K26vF0000Q literal 0 HcmV?d00001 From e2056f642062cd97d8fb7a1c3a8eb7dae10e5f50 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 17:42:59 +0200 Subject: [PATCH 084/101] revert meow to pine icon, rework on cat later --- src/displayapp/screens/WatchFaceMeow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/displayapp/screens/WatchFaceMeow.cpp b/src/displayapp/screens/WatchFaceMeow.cpp index b6fb030b..a4abadaa 100644 --- a/src/displayapp/screens/WatchFaceMeow.cpp +++ b/src/displayapp/screens/WatchFaceMeow.cpp @@ -193,7 +193,7 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, //Battery indicator logoPine = lv_img_create(lv_scr_act(), nullptr); - lv_img_set_src(logoPine, "F:/images/cat_small.bin"); + lv_img_set_src(logoPine, "F:/images/pine_small.bin"); lv_obj_set_pos(logoPine, 15, 106); lineBattery = lv_line_create(lv_scr_act(), nullptr); @@ -565,7 +565,7 @@ bool WatchFaceMeow::IsAvailable(Pinetime::Controllers::FS& filesystem) { } filesystem.FileClose(&file); - if (filesystem.FileOpen(&file, "/images/cat_small.bin", LFS_O_RDONLY) < 0) { + if (filesystem.FileOpen(&file, "/images/pine_small.bin", LFS_O_RDONLY) < 0) { return false; } From acbcd531b5fab19871063bc01cb5e780f2d58eb4 Mon Sep 17 00:00:00 2001 From: Eve C Date: Thu, 30 May 2024 13:23:25 +0200 Subject: [PATCH 085/101] cat as battery icon : modified the lines of infineat and position of picture --- .gitignore | 1 + draft_pictures/cat_small.xcf | Bin 1846 -> 2316 bytes draft_pictures/coordinates.xcf | Bin 0 -> 1366918 bytes node_modules/.package-lock.json | 113 - node_modules/lv_font_conv/CHANGELOG.md | 164 - node_modules/lv_font_conv/LICENSE | 22 - node_modules/lv_font_conv/README.md | 150 - node_modules/lv_font_conv/lib/app_error.js | 9 - node_modules/lv_font_conv/lib/cli.js | 318 - .../lv_font_conv/lib/collect_font_data.js | 173 - node_modules/lv_font_conv/lib/convert.js | 30 - .../lib/font/cmap_build_subtables.js | 105 - .../lv_font_conv/lib/font/compress.js | 107 - node_modules/lv_font_conv/lib/font/font.js | 131 - .../lv_font_conv/lib/font/table_cmap.js | 201 - .../lv_font_conv/lib/font/table_glyf.js | 147 - .../lv_font_conv/lib/font/table_head.js | 99 - .../lv_font_conv/lib/font/table_kern.js | 256 - .../lv_font_conv/lib/font/table_loca.js | 42 - .../lv_font_conv/lib/freetype/index.js | 317 - .../lv_font_conv/lib/freetype/render.c | 83 - node_modules/lv_font_conv/lib/ranger.js | 51 - node_modules/lv_font_conv/lib/utils.js | 131 - node_modules/lv_font_conv/lib/writers/bin.js | 17 - node_modules/lv_font_conv/lib/writers/dump.js | 68 - .../lv_font_conv/lib/writers/lvgl/index.js | 17 - .../lv_font_conv/lib/writers/lvgl/lv_font.js | 98 - .../lib/writers/lvgl/lv_table_cmap.js | 125 - .../lib/writers/lvgl/lv_table_glyf.js | 121 - .../lib/writers/lvgl/lv_table_head.js | 99 - .../lib/writers/lvgl/lv_table_kern.js | 121 - node_modules/lv_font_conv/lv_font_conv.js | 17 - .../node_modules/argparse/CHANGELOG.md | 216 - .../node_modules/argparse/LICENSE | 254 - .../node_modules/argparse/README.md | 84 - .../node_modules/argparse/argparse.js | 3707 ---- .../node_modules/argparse/lib/sub.js | 67 - .../node_modules/argparse/lib/textwrap.js | 440 - .../node_modules/argparse/package.json | 67 - .../bit-buffer/.github/workflows/ci.yml | 36 - .../node_modules/bit-buffer/LICENSE | 21 - .../node_modules/bit-buffer/README.md | 148 - .../node_modules/bit-buffer/bit-buffer.d.ts | 115 - .../node_modules/bit-buffer/bit-buffer.js | 502 - .../node_modules/bit-buffer/package.json | 71 - .../node_modules/bit-buffer/test.js | 628 - .../lv_font_conv/node_modules/debug/LICENSE | 19 - .../lv_font_conv/node_modules/debug/README.md | 455 - .../node_modules/debug/package.json | 111 - .../node_modules/debug/src/browser.js | 269 - .../node_modules/debug/src/common.js | 261 - .../node_modules/debug/src/index.js | 10 - .../node_modules/debug/src/node.js | 263 - .../node_modules/make-error/LICENSE | 5 - .../node_modules/make-error/README.md | 112 - .../make-error/dist/make-error.js | 1 - .../node_modules/make-error/index.d.ts | 47 - .../node_modules/make-error/index.js | 151 - .../node_modules/make-error/package.json | 95 - .../node_modules/mkdirp/CHANGELOG.md | 15 - .../lv_font_conv/node_modules/mkdirp/LICENSE | 21 - .../node_modules/mkdirp/bin/cmd.js | 68 - .../lv_font_conv/node_modules/mkdirp/index.js | 31 - .../node_modules/mkdirp/lib/find-made.js | 29 - .../node_modules/mkdirp/lib/mkdirp-manual.js | 64 - .../node_modules/mkdirp/lib/mkdirp-native.js | 39 - .../node_modules/mkdirp/lib/opts-arg.js | 23 - .../node_modules/mkdirp/lib/path-arg.js | 29 - .../node_modules/mkdirp/lib/use-native.js | 10 - .../node_modules/mkdirp/package.json | 78 - .../node_modules/mkdirp/readme.markdown | 266 - .../lv_font_conv/node_modules/ms/index.js | 162 - .../lv_font_conv/node_modules/ms/license.md | 21 - .../lv_font_conv/node_modules/ms/package.json | 72 - .../lv_font_conv/node_modules/ms/readme.md | 60 - .../node_modules/opentype.js/LICENSE | 20 - .../node_modules/opentype.js/README.md | 313 - .../node_modules/opentype.js/RELEASES.md | 267 - .../node_modules/opentype.js/bin/ot | 84 - .../node_modules/opentype.js/bin/server.js | 64 - .../node_modules/opentype.js/bin/test-render | 96 - .../node_modules/opentype.js/dist/opentype.js | 14254 ---------------- .../node_modules/opentype.js/package.json | 102 - .../lv_font_conv/node_modules/pngjs/LICENSE | 20 - .../lv_font_conv/node_modules/pngjs/README.md | 433 - .../node_modules/pngjs/lib/bitmapper.js | 267 - .../node_modules/pngjs/lib/bitpacker.js | 158 - .../node_modules/pngjs/lib/chunkstream.js | 189 - .../node_modules/pngjs/lib/constants.js | 32 - .../node_modules/pngjs/lib/crc.js | 40 - .../node_modules/pngjs/lib/filter-pack.js | 171 - .../pngjs/lib/filter-parse-async.js | 24 - .../pngjs/lib/filter-parse-sync.js | 21 - .../node_modules/pngjs/lib/filter-parse.js | 177 - .../pngjs/lib/format-normaliser.js | 93 - .../node_modules/pngjs/lib/interlace.js | 95 - .../node_modules/pngjs/lib/packer-async.js | 50 - .../node_modules/pngjs/lib/packer-sync.js | 56 - .../node_modules/pngjs/lib/packer.js | 129 - .../node_modules/pngjs/lib/paeth-predictor.js | 16 - .../node_modules/pngjs/lib/parser-async.js | 169 - .../node_modules/pngjs/lib/parser-sync.js | 112 - .../node_modules/pngjs/lib/parser.js | 290 - .../node_modules/pngjs/lib/png-sync.js | 12 - .../node_modules/pngjs/lib/png.js | 194 - .../node_modules/pngjs/lib/sync-inflate.js | 168 - .../node_modules/pngjs/lib/sync-reader.js | 45 - .../node_modules/pngjs/package.json | 129 - .../LICENSE-MIT.txt | 20 - .../string.prototype.codepointat/README.md | 47 - .../codepointat.js | 54 - .../string.prototype.codepointat/package.json | 62 - .../node_modules/tiny-inflate/LICENSE | 21 - .../node_modules/tiny-inflate/index.js | 375 - .../node_modules/tiny-inflate/package.json | 60 - .../node_modules/tiny-inflate/readme.md | 31 - .../node_modules/tiny-inflate/test/index.js | 75 - .../node_modules/tiny-inflate/test/lorem.txt | 199 - node_modules/lv_font_conv/package.json | 61 - src/displayapp/screens/WatchFaceMeow.cpp | 39 +- src/resources/images/cat_clean.png | Bin 1344 -> 1778 bytes 121 files changed, 23 insertions(+), 31787 deletions(-) create mode 100644 draft_pictures/coordinates.xcf delete mode 100644 node_modules/.package-lock.json delete mode 100644 node_modules/lv_font_conv/CHANGELOG.md delete mode 100644 node_modules/lv_font_conv/LICENSE delete mode 100644 node_modules/lv_font_conv/README.md delete mode 100644 node_modules/lv_font_conv/lib/app_error.js delete mode 100644 node_modules/lv_font_conv/lib/cli.js delete mode 100644 node_modules/lv_font_conv/lib/collect_font_data.js delete mode 100644 node_modules/lv_font_conv/lib/convert.js delete mode 100644 node_modules/lv_font_conv/lib/font/cmap_build_subtables.js delete mode 100644 node_modules/lv_font_conv/lib/font/compress.js delete mode 100644 node_modules/lv_font_conv/lib/font/font.js delete mode 100644 node_modules/lv_font_conv/lib/font/table_cmap.js delete mode 100644 node_modules/lv_font_conv/lib/font/table_glyf.js delete mode 100644 node_modules/lv_font_conv/lib/font/table_head.js delete mode 100644 node_modules/lv_font_conv/lib/font/table_kern.js delete mode 100644 node_modules/lv_font_conv/lib/font/table_loca.js delete mode 100644 node_modules/lv_font_conv/lib/freetype/index.js delete mode 100644 node_modules/lv_font_conv/lib/freetype/render.c delete mode 100644 node_modules/lv_font_conv/lib/ranger.js delete mode 100644 node_modules/lv_font_conv/lib/utils.js delete mode 100644 node_modules/lv_font_conv/lib/writers/bin.js delete mode 100644 node_modules/lv_font_conv/lib/writers/dump.js delete mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/index.js delete mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/lv_font.js delete mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/lv_table_cmap.js delete mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/lv_table_glyf.js delete mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/lv_table_head.js delete mode 100644 node_modules/lv_font_conv/lib/writers/lvgl/lv_table_kern.js delete mode 100755 node_modules/lv_font_conv/lv_font_conv.js delete mode 100644 node_modules/lv_font_conv/node_modules/argparse/CHANGELOG.md delete mode 100644 node_modules/lv_font_conv/node_modules/argparse/LICENSE delete mode 100644 node_modules/lv_font_conv/node_modules/argparse/README.md delete mode 100644 node_modules/lv_font_conv/node_modules/argparse/argparse.js delete mode 100644 node_modules/lv_font_conv/node_modules/argparse/lib/sub.js delete mode 100644 node_modules/lv_font_conv/node_modules/argparse/lib/textwrap.js delete mode 100644 node_modules/lv_font_conv/node_modules/argparse/package.json delete mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/.github/workflows/ci.yml delete mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/LICENSE delete mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/README.md delete mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.d.ts delete mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.js delete mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/package.json delete mode 100644 node_modules/lv_font_conv/node_modules/bit-buffer/test.js delete mode 100644 node_modules/lv_font_conv/node_modules/debug/LICENSE delete mode 100644 node_modules/lv_font_conv/node_modules/debug/README.md delete mode 100644 node_modules/lv_font_conv/node_modules/debug/package.json delete mode 100644 node_modules/lv_font_conv/node_modules/debug/src/browser.js delete mode 100644 node_modules/lv_font_conv/node_modules/debug/src/common.js delete mode 100644 node_modules/lv_font_conv/node_modules/debug/src/index.js delete mode 100644 node_modules/lv_font_conv/node_modules/debug/src/node.js delete mode 100644 node_modules/lv_font_conv/node_modules/make-error/LICENSE delete mode 100644 node_modules/lv_font_conv/node_modules/make-error/README.md delete mode 100644 node_modules/lv_font_conv/node_modules/make-error/dist/make-error.js delete mode 100644 node_modules/lv_font_conv/node_modules/make-error/index.d.ts delete mode 100644 node_modules/lv_font_conv/node_modules/make-error/index.js delete mode 100644 node_modules/lv_font_conv/node_modules/make-error/package.json delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/CHANGELOG.md delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/LICENSE delete mode 100755 node_modules/lv_font_conv/node_modules/mkdirp/bin/cmd.js delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/index.js delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/find-made.js delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-manual.js delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-native.js delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/opts-arg.js delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/path-arg.js delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/lib/use-native.js delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/package.json delete mode 100644 node_modules/lv_font_conv/node_modules/mkdirp/readme.markdown delete mode 100644 node_modules/lv_font_conv/node_modules/ms/index.js delete mode 100644 node_modules/lv_font_conv/node_modules/ms/license.md delete mode 100644 node_modules/lv_font_conv/node_modules/ms/package.json delete mode 100644 node_modules/lv_font_conv/node_modules/ms/readme.md delete mode 100644 node_modules/lv_font_conv/node_modules/opentype.js/LICENSE delete mode 100644 node_modules/lv_font_conv/node_modules/opentype.js/README.md delete mode 100644 node_modules/lv_font_conv/node_modules/opentype.js/RELEASES.md delete mode 100755 node_modules/lv_font_conv/node_modules/opentype.js/bin/ot delete mode 100755 node_modules/lv_font_conv/node_modules/opentype.js/bin/server.js delete mode 100755 node_modules/lv_font_conv/node_modules/opentype.js/bin/test-render delete mode 100644 node_modules/lv_font_conv/node_modules/opentype.js/dist/opentype.js delete mode 100644 node_modules/lv_font_conv/node_modules/opentype.js/package.json delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/LICENSE delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/README.md delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/bitmapper.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/bitpacker.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/chunkstream.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/constants.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/crc.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/filter-pack.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-async.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-sync.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/format-normaliser.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/interlace.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/packer-async.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/packer-sync.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/packer.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/paeth-predictor.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/parser-async.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/parser-sync.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/parser.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/png-sync.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/png.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/sync-inflate.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/lib/sync-reader.js delete mode 100644 node_modules/lv_font_conv/node_modules/pngjs/package.json delete mode 100644 node_modules/lv_font_conv/node_modules/string.prototype.codepointat/LICENSE-MIT.txt delete mode 100644 node_modules/lv_font_conv/node_modules/string.prototype.codepointat/README.md delete mode 100644 node_modules/lv_font_conv/node_modules/string.prototype.codepointat/codepointat.js delete mode 100644 node_modules/lv_font_conv/node_modules/string.prototype.codepointat/package.json delete mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/LICENSE delete mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/index.js delete mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/package.json delete mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/readme.md delete mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/test/index.js delete mode 100644 node_modules/lv_font_conv/node_modules/tiny-inflate/test/lorem.txt delete mode 100644 node_modules/lv_font_conv/package.json diff --git a/.gitignore b/.gitignore index 13a8b796..73a489dd 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ tools # My stuff #draft_pictures +node_modules # Resulting binary files *.a diff --git a/draft_pictures/cat_small.xcf b/draft_pictures/cat_small.xcf index 76c7751d249a931636095194a823061a79946aed..254bdd310bf51b7d13e52cc142ba3bcccb7dac04 100644 GIT binary patch literal 2316 zcmeHJeQZ-z6hD2h-(B~{7!Vckfru=y$kbpo8bA;g!34>Ch(su(tIbA7*1^`TUB^c| z$2t+11XLtAzC>aS2`q!*0B1sEQ-b3|EwuLZ+pinqg;%Njm!AKY$ z;~e>6gj0kR8>P;$FSppN8MXrZGMWOG=fDJKp)1cSI`i}F%hHV#a%|c8dHDt6gs96X zur0NSlcQH=I>}2PB4>)xi^;{#6_#wf!zN}|&6GHB@Gv{DL7JYFNE0%e?}mePO|a3U`mDgM92|NdK)^|@~i zhdyr3JTPs)urLjJO}jmNz;q`NTBI5R-RAm>JDb6z`0qu0DgnAiwPIzk8D7RS`%$Un z%+NXI5(fR)3{z1lkx|)VFu|D@?;>uy9-YL4rx1HBPfyFr@{C!s!JU;g{ddH$7YEcH zAGTK9;xY2`^Eony9pTNt;Ng>q9l9wOE?!)*es6@wu;QuM)u={!Z+!`eeLV)ga>tJM z{2C0k90>eTCaY1|_7YcmKaYRYhgg#vd*vezy zkKbaTU3M!RmczcLLq&)U$0oYu)sf~pUpJy$rjU-ujv^|hauJ5S%A6`ZySKUS*PV!U z-y>5MPXrr};#y_<6rx`3MvgV-E?v%XSE#J`NqPQZalr%SNYYVSt@S0 zn@1(k&LQsRq4U+!{T1H1<1XV*``hT#q;KXhM0ds8k8sd7yMJvjULBpj>1J67Ptccf zJk`WhI|n^4+{NFv->iO2b;$P_hrx9K{E}$3CzH+QG1oY3EBR<6!2ETU_Yu2n$G6Si zMSJ_6+Z^feb9k-0q$U+W=MM&C43}V8(}GKZ_D?Ovk?S>xeO0|PcxW@OIky#+qPx}i z%KYn2Zz*p4u4NyFYfjHoY2fz6%?EH*?K#DD1I9*j2T_t4V`?&77@Q>Wjv*HZu4hAk4SsS+fi6`3`Ze#hJ^b*k^sP zoq1_)pq(-GpSLrS_QZ20$2e<+DFmKCHlDybTB5}}5nGyI`c?|!9Vn*hg9!`b8ci7n KJE52V68{EcShnc^ literal 1846 zcmeHIT})eb6hFN!EiEl~6A&YF-fcQTgQ0uCJ#B{J$0E~ZAGv1n!BX0K18cEm1hSA3 zr^aP5CVuRtQVL~TMoacVMobj9=@M~66gHEYIG4<%QCTeuD^ON?Jg4-UyJSB2;G5^> zch2ve^FOEmdzzkll)bG|htn;!*Vyeq@*ts{3CR({N`g*Ds)SIu<&&SQ7{p5n27-ma zMa%_48DWgD!b7dAmAwv+tJ>pNnkWR^Csr9~>uhmJ0bh&KRAs4jdz`)&pI@rXnQFhM z(IM639(xs~8}F00OSy;B9f4MdQ}KEvdkuM=_jq}ydY@NmlN7J>4Of6Gd~SEZ)h6Z6 zYxpv~_b}gCBf1wf4pHN?8ebh8zhhCI!`bZd`}TQrugzm&F%o!8NLa&sb2n)kD}m!& zYR+>quvWuYX*iFZ&$CDbl^1EaRl{u>E)wU@Q^iDt>$(5qc}@l%V~!+b*J-%=wX5$$ zDHdVJvdw9A6#07z-uvXZ=3O-|h$0BGUbKQiHi#BLy+JMz3&ALxMJ%?kF4O4J9RDX> zVxb-gdJ#+YGC~1$LJ-IkWF2|biz*Qc$(kubw#+Q+!6;&hNj3r+3&aB10tV3_7Xj+1 zay|7iTe-&*f{cYmV*lUwznt&VQynzn^tx6z)qXrahc_!sw(>`wuC-Z}3?55ybo%sk?=QD;>E(|Foc-)drXTT{ z=fmMWrfrC1C2?gIANuO||i~|FVF0GvVkT5sGGa^$dRSz@dJO^a$7uQzP+c z@?-+>%i~wErztW%b#(GOTo}M;a+}Q}3O@-T@3@{5%ntS8$tjG# zmilHQa-lzXaUc=t#rekH@bd79;Kr_xcKnHVJ4X@Ub}C!zKOH{1bN1#)JqTxzI^Zn* z?p%EfiOpM2g|hK-D2;P;Kfp`#GqJO^meE6qG5eO?nP~FI`anYxvHgSq&wW3JiJ{?x zgV98DI|dQII`=4m-W|KX&>xyO(tYgxFRle~VzABtGpk?=lJByEM b#F!;V-BtOV>0!+QHAf-S1~q;_w}^iM?F8FJ diff --git a/draft_pictures/coordinates.xcf b/draft_pictures/coordinates.xcf new file mode 100644 index 0000000000000000000000000000000000000000..e9e6d040cd1c7b33629e6522e3f3d1155dac751c GIT binary patch literal 1366918 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ⅈkIp#eUsr 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 J(d|xhpsyVbyHzc&r3y~Lsuqj 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�uH=bBs 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

Y2Q4 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[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 literal 0 HcmV?d00001 diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json deleted file mode 100644 index a519c455..00000000 --- a/node_modules/.package-lock.json +++ /dev/null @@ -1,113 +0,0 @@ -{ - "name": "InfiniTime", - "lockfileVersion": 2, - "requires": true, - "packages": { - "node_modules/lv_font_conv": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/lv_font_conv/-/lv_font_conv-1.5.2.tgz", - "integrity": "sha512-0UapRSTkVP/pnB8Z4r2HDHx5p2dJx/xUG1+14u/WXo59mwuC7BahR+Bnx/66jKoDrG1wFQwn9ZzoyMxRHOD9bg==", - "bundleDependencies": [ - "argparse", - "bit-buffer", - "debug", - "make-error", - "mkdirp", - "opentype.js", - "pngjs" - ], - "dependencies": { - "argparse": "^2.0.0", - "bit-buffer": "^0.2.5", - "debug": "^4.1.1", - "make-error": "^1.3.5", - "mkdirp": "^1.0.4", - "opentype.js": "^1.1.0", - "pngjs": "^6.0.0" - }, - "bin": { - "lv_font_conv": "lv_font_conv.js" - } - }, - "node_modules/lv_font_conv/node_modules/argparse": { - "version": "2.0.1", - "inBundle": true, - "license": "Python-2.0" - }, - "node_modules/lv_font_conv/node_modules/bit-buffer": { - "version": "0.2.5", - "inBundle": true, - "license": "MIT" - }, - "node_modules/lv_font_conv/node_modules/debug": { - "version": "4.3.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/lv_font_conv/node_modules/make-error": { - "version": "1.3.6", - "inBundle": true, - "license": "ISC" - }, - "node_modules/lv_font_conv/node_modules/mkdirp": { - "version": "1.0.4", - "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lv_font_conv/node_modules/ms": { - "version": "2.1.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/lv_font_conv/node_modules/opentype.js": { - "version": "1.3.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "string.prototype.codepointat": "^0.2.1", - "tiny-inflate": "^1.0.3" - }, - "bin": { - "ot": "bin/ot" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/lv_font_conv/node_modules/pngjs": { - "version": "6.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/lv_font_conv/node_modules/string.prototype.codepointat": { - "version": "0.2.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/lv_font_conv/node_modules/tiny-inflate": { - "version": "1.0.3", - "inBundle": true, - "license": "MIT" - } - } -} diff --git a/node_modules/lv_font_conv/CHANGELOG.md b/node_modules/lv_font_conv/CHANGELOG.md deleted file mode 100644 index 94512228..00000000 --- a/node_modules/lv_font_conv/CHANGELOG.md +++ /dev/null @@ -1,164 +0,0 @@ -1.5.2 / 2021-07-18 ------------------- - -- Fixed lvgl version check for v8+, #64. - - -1.5.1 / 2021-04-06 ------------------- - -- Fixed fail of CMAP generation for edge cases, #62. -- Dev deps bump. - - -1.5.0 / 2021-03-08 ------------------- - -- More `const` in generated font (for v8+), #59. - - -1.4.1 / 2021-01-26 ------------------- - -- Fix charcodes padding in comments, #54. - - -1.4.0 / 2021-01-03 ------------------- - -- Added OTF fonts support. -- Added `--use-color-info` for limited multi-tone glyphs support. - - -1.3.1 / 2020-12-28 ------------------- - -- Unify `lvgl.h` include. -- Updated repo refs (littlevgl => lvgl). -- Deps bump. -- Moved CI to github actions. - - -1.3.0 / 2020-10-25 ------------------- - -- Drop `lodash` use. -- Deps bump. - - -1.2.1 / 2020-10-24 ------------------- - -- Reduced npm package size (drop unneeded files before publish). - - -1.2.0 / 2020-10-24 ------------------- - -- Bump FreeType to 2.10.4. -- Bundle dependencies to npm package. - - -1.1.3 / 2020-09-22 ------------------- - -- lvgl: added `LV_FONT_FMT_TXT_LARGE` check or very large fonts. - - -1.1.2 / 2020-08-23 ------------------- - -- Fix: skip `glyph.advanceWidth` for monospace fonts, #43. -- Spec fix: version size should be 4 bytes, #44. -- Spec fix: bbox x/y bits => unsigned, #45. -- Bump argparse. -- Cleanup help formatter. - - -1.1.1 / 2020-08-01 ------------------- - -- `--version` should show number from `package.json`. - - -1.1.0 / 2020-07-27 ------------------- - -- Added `post.underlinePosition` & `post.underlineThickness` info to font header. - - -1.0.0 / 2020-06-26 ------------------- - -- Maintenance release. -- Set package version 1.x, to label package as stable. -- Deps bump. - - -0.4.3 / 2020-03-05 ------------------- - -- Enabled `--bpp 8` mode. - - -0.4.2 / 2020-01-05 ------------------- - -- Added `--lv_include` option to set alternate `lvgl.h` path. -- Added guards to hide `.subpx` property for lvgl 6.0 (supported from 6.1 only), #32. -- Dev deps bump - - -0.4.1 / 2019-12-09 ------------------- - -- Allow memory growth for FreeType build, #29. -- Dev deps bump. -- Web build update. - - -0.4.0 / 2019-11-29 ------------------- - -- Note, this release is for lvgl 6.1 and has potentially breaking changes - (see below). If you have compatibility issues with lvgl 6.0 - use previous - versions or update your code. -- Spec change: added subpixels info field to font header (header size increased). -- Updated `bin` & `lvgl` writers to match new spec. -- lvgl: fixed data type for kerning values (needs appropriate update - in LittlevGL 6.1+). -- Fix errors display (disable emscripten error catcher). - - -0.3.1 / 2019-10-24 ------------------- - -- Fixed "out of range" error for big `--size`. - - -0.3.0 / 2019-10-12 ------------------- - -- Added beta options `--lcd` & `--lcd-v` for subpixel rendering (still need - header info update). -- Added FreeType data properties to dump info. -- Fixed glyph width (missed fractional part after switch to FreeType). -- Fixed missed sigh for negative X/Y bitmap offsets. -- Deps bump. - - -0.2.0 / 2019-09-26 ------------------- - -- Use FreeType renderer. Should solve all regressions, reported in 0.1.0. -- Enforced light autohinting (horizontal lines only). -- Use special hinter for monochrome output (improve quality). -- API changed to async. -- Fix: added missed `.bitmap_format` field to lvgl writer. -- Fix: changed struct fields init order to match declaration, #25. - - -0.1.0 / 2019-09-03 ------------------- - -- First release. diff --git a/node_modules/lv_font_conv/LICENSE b/node_modules/lv_font_conv/LICENSE deleted file mode 100644 index 4dc31bfa..00000000 --- a/node_modules/lv_font_conv/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) 2018 authors - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/lv_font_conv/README.md b/node_modules/lv_font_conv/README.md deleted file mode 100644 index 6716ebd9..00000000 --- a/node_modules/lv_font_conv/README.md +++ /dev/null @@ -1,150 +0,0 @@ -lv_font_conv - font convertor to compact bitmap format -====================================================== - -[![CI](https://github.com/lvgl/lv_font_conv/workflows/CI/badge.svg?branch=master)](https://github.com/lvgl/lv_font_conv/actions) -[![NPM version](https://img.shields.io/npm/v/lv_font_conv.svg?style=flat)](https://www.npmjs.org/package/lv_font_conv) - -Converts TTF/WOFF/OTF fonts to __[compact format](https://github.com/lvgl/lv_font_conv/blob/master/doc/font_spec.md)__, suitable for small embedded systems. Main features are: - -- Allows bitonal and anti-aliased glyphs (1-4 bits per pixel). -- Preserves kerning info. -- Compression. -- Users can select required glyphs only (subsetting). -- Multiple font sources can be merged. -- Simple CLI interface, easy to integrate into external build systems. - - -## Install the script - -[node.js](https://nodejs.org/en/download/) v10+ required. - -Global install of the last version, execute as "lv_font_conv" - -```sh -# install release from npm registry -npm i lv_font_conv -g -# install from github's repo, master branch -npm i lvgl/lv_font_conv -g -``` - -**run via [npx](https://www.npmjs.com/package/npx) without install** - -```sh -# run from npm registry -npx lv_font_conv -h -# run from github master -npx github:lvgl/lv_font_conv -h -``` - -Note, runing via `npx` may take some time until modules installed, be patient. - - -## CLI params - -Common: - -- `--bpp` - bits per pixel (antialiasing). -- `--size` - output font size (pixels). -- `-o`, `--output` - output path (file or directory, depends on format). -- `--format` - output format. - - `--format dump` - dump glyph images and font info, useful for debug. - - `--format bin` - dump font in binary form (as described in [spec](https://github.com/lvgl/lv_font_conv/blob/master/doc/font_spec.md)). - - `--format lvgl` - dump font in [LittlevGL](https://github.com/lvgl/lvgl) format. -- `--force-fast-kern-format` - always use more fast kering storage format, - at cost of some size. If size difference appears, it will be displayed. -- `--lcd` - generate bitmaps with 3x horizontal resolution, for subpixel - smoothing. -- `--lcd-v` - generate bitmaps with 3x vertical resolution, for subpixel - smoothing. -- `--use-color-info` - try to use glyph color info from font to create - grayscale icons. Since gray tones are emulated via transparency, result - will be good on contrast background only. -- `--lv-include` - only with `--format lvgl`, set alternate path for `lvgl.h`. - -Per font: - -- `--font` - path to font file (ttf/woff/woff2/otf). May be used multiple time for - merge. -- `-r`, `--range` - single glyph or range + optional mapping, belongs to - previously declared `--font`. Can be used multiple times. Examples: - - `-r 0x1F450` - single value, dec or hex format. - - `-r 0x1F450-0x1F470` - range. - - `-r '0x1F450=>0xF005'` - single glyph with mapping. - - `-r '0x1F450-0x1F470=>0xF005'` - range with mapping. - - `-r 0x1F450 -r 0x1F451-0x1F470` - 2 ranges. - - `-r 0x1F450,0x1F451-0x1F470` - the same as above, but defined with single `-r`. -- `--symbols` - list of characters to copy (instead of numeric format in `-r`). - - `--symbols 0123456789.,` - extract chars to display numbers. -- `--autohint-off` - do not force autohinting ("light" is on by default). -- `--autohint-strong` - use more strong autohinting (will break kerning). - -Additional debug options: - -- `--no-compress` - disable built-in RLE compression. -- `--no-prefilter` - disable bitmap lines filter (XOR), used to improve - compression ratio. -- `--no-kerning` - drop kerning info to reduce size (not recommended). -- `--full-info` - don't shorten 'font_info.json' (include pixels data). - - -## Examples - -Merge english from Roboto Regular and icons from Font Awesome, and show debug -info: - -`env DEBUG=* lv_font_conv --font Roboto-Regular.ttf -r 0x20-0x7F --font FontAwesome.ttf -r 0xFE00=>0x81 --size 16 --format bin --bpp 3 --no-compress -o output.font` - -Merge english & russian from Roboto Regular, and show debug info: - -`env DEBUG=* lv_font_conv --font Roboto-Regular.ttf -r 0x20-0x7F -r 0x401,0x410-0x44F,0x451 --size 16 --format bin --bpp 3 --no-compress -o output.font` - -Dump all Roboto glyphs to inspect icons and font details: - -`lv_font_conv --font Roboto-Regular.ttf -r 0x20-0x7F --size 16 --format dump --bpp 3 -o ./dump` - -**Note**. Option `--no-compress` exists temporary, to avoid confusion until LVGL -adds compression support. - - -## Technical notes - -### Supported output formats - -1. **bin** - universal binary format, as described in https://github.com/lvgl/lv_font_conv/tree/master/doc. -2. **lvgl** - format for LittlevGL, C file. Has minor limitations and a bit - bigger size, because C does not allow to effectively define relative offsets - in data blocks. -3. **dump** - create folder with each glyph in separate image, and other font - data as `json`. Useful for debug. - -### Merged font metrics - -When multiple fonts merged into one, sources can have different metrics. Result -will follow principles below: - -1. No scaling. Glyphs will have exactly the same size, as intended by font authors. -2. The same baseline. -3. `OS/2` metrics (`sTypoAscender`, `sTypoDescender`, `sTypoLineGap`) will be - used from the first font in list. -4. `hhea` metrics (`ascender`, `descender`), defined as max/min point of all - font glyphs, are recalculated, according to new glyphs set. - - -## Development - -Current package includes WebAssembly build of FreeType with some helper -functions. Everything is wrapped into Docker and requires zero knowledge about -additional tools install. See `package.json` for additional commands. You may -need those if decide to upgrade FreeType or update helpers. - -This builds image with emscripten & freetype, usually should be done only once: - -``` -npm run build:dockerimage -``` - -This compiles helpers and creates WebAssembly files: - -``` -npm run build:freetype -``` diff --git a/node_modules/lv_font_conv/lib/app_error.js b/node_modules/lv_font_conv/lib/app_error.js deleted file mode 100644 index 98e9052e..00000000 --- a/node_modules/lv_font_conv/lib/app_error.js +++ /dev/null @@ -1,9 +0,0 @@ -// Custom Error type to simplify error messaging -// -'use strict'; - - -//const ExtendableError = require('es6-error'); -//module.exports = class AppError extends ExtendableError {}; - -module.exports = require('make-error')('AppError'); diff --git a/node_modules/lv_font_conv/lib/cli.js b/node_modules/lv_font_conv/lib/cli.js deleted file mode 100644 index a73a9b22..00000000 --- a/node_modules/lv_font_conv/lib/cli.js +++ /dev/null @@ -1,318 +0,0 @@ -// Parse input arguments and execute convertor - -'use strict'; - - -const argparse = require('argparse'); -const fs = require('fs'); -const mkdirp = require('mkdirp'); -const path = require('path'); -const convert = require('./convert'); - - -class ActionFontAdd extends argparse.Action { - call(parser, namespace, value/*, option_string*/) { - let items = (namespace[this.dest] || []).slice(); - items.push({ source_path: value, ranges: [] }); - namespace[this.dest] = items; - } -} - - -// add range or symbols to font; -// need to merge them into one array here so overrides work correctly -class ActionFontRangeAdd extends argparse.Action { - call(parser, namespace, value, option_string) { - let fonts = namespace.font || []; - - if (fonts.length === 0) { - parser.error(`argument ${option_string}: Only allowed after --font`); - } - - let lastFont = fonts[fonts.length - 1]; - - // { symbols: 'ABC' }, or { range: [ 65, 67, 65 ] } - lastFont.ranges.push({ [this.dest]: value }); - } -} - - -// add hinting option to font; -class ActionFontStoreTrue extends argparse.Action { - constructor(options) { - options = options || {}; - options.const = true; - options.default = options.default !== null ? options.default : false; - options.nargs = 0; - super(options); - } - - call(parser, namespace, value, option_string) { - let fonts = namespace.font || []; - - if (fonts.length === 0) { - parser.error(`argument ${option_string}: Only allowed after --font`); - } - - let lastFont = fonts[fonts.length - 1]; - - lastFont[this.dest] = this.const; - } -} - - -// Formatter with support of `\n` in Help texts. -class RawTextHelpFormatter2 extends argparse.RawDescriptionHelpFormatter { - // executes parent _split_lines for each line of the help, then flattens the result - _split_lines(text, width) { - return [].concat(...text.split('\n').map(line => super._split_lines(line, width))); - } -} - - -// parse decimal or hex code in unicode range -function unicode_point(str) { - let m = /^(?:(?:0x([0-9a-f]+))|([0-9]+))$/i.exec(str.trim()); - - if (!m) throw new TypeError(`${str} is not a number`); - - let [ , hex, dec ] = m; - - let value = hex ? parseInt(hex, 16) : parseInt(dec, 10); - - if (value > 0x10FFFF) throw new TypeError(`${str} is out of unicode range`); - - return value; -} - - -// parse range -function range(str) { - let result = []; - - for (let s of str.split(',')) { - let m = /^(.+?)(?:-(.+?))?(?:=>(.+?))?$/i.exec(s); - - let [ , start, end, mapped_start ] = m; - - if (!end) end = start; - if (!mapped_start) mapped_start = start; - - start = unicode_point(start); - end = unicode_point(end); - - if (start > end) throw new TypeError(`Invalid range: ${s}`); - - mapped_start = unicode_point(mapped_start); - - result.push(start, end, mapped_start); - } - - return result; -} - - -// exclude negative numbers and non-numbers -function positive_int(str) { - if (!/^\d+$/.test(str)) throw new TypeError(`${str} is not a valid number`); - - let n = parseInt(str, 10); - - if (n <= 0) throw new TypeError(`${str} is not a valid number`); - - return n; -} - - -module.exports.run = async function (argv, debug = false) { - - // - // Configure CLI - // - - let parser = new argparse.ArgumentParser({ - add_help: true, - formatter_class: RawTextHelpFormatter2 - }); - - if (debug) { - parser.exit = function (status, message) { - throw new Error(message); - }; - } - - parser.add_argument('-v', '--version', { - action: 'version', - version: require('../package.json').version - }); - - parser.add_argument('--size', { - metavar: 'PIXELS', - type: positive_int, - required: true, - help: 'Output font size, pixels.' - }); - - parser.add_argument('-o', '--output', { - metavar: '', - help: 'Output path.' - }); - - parser.add_argument('--bpp', { - choices: [ 1, 2, 3, 4, 8 ], - type: positive_int, - required: true, - help: 'Bits per pixel, for antialiasing.' - }); - - let lcd_group = parser.add_mutually_exclusive_group(); - - lcd_group.add_argument('--lcd', { - action: 'store_true', - default: false, - help: 'Enable subpixel rendering (horizontal pixel layout).' - }); - - lcd_group.add_argument('--lcd-v', { - action: 'store_true', - default: false, - help: 'Enable subpixel rendering (vertical pixel layout).' - }); - - parser.add_argument('--use-color-info', { - dest: 'use_color_info', - action: 'store_true', - default: false, - help: 'Try to use glyph color info from font to create grayscale icons. ' + - 'Since gray tones are emulated via transparency, result will be good on contrast background only.' - }); - - parser.add_argument('--format', { - choices: convert.formats, - required: true, - help: 'Output format.' - }); - - parser.add_argument('--font', { - metavar: '', - action: ActionFontAdd, - required: true, - help: 'Source font path. Can be used multiple times to merge glyphs from different fonts.' - }); - - parser.add_argument('-r', '--range', { - type: range, - action: ActionFontRangeAdd, - help: ` -Range of glyphs to copy. Can be used multiple times, belongs to previously declared "--font". Examples: - -r 0x1F450 - -r 0x20-0x7F - -r 32-127 - -r 32-127,0x1F450 - -r '0x1F450=>0xF005' - -r '0x1F450-0x1F470=>0xF005' -` - }); - - parser.add_argument('--symbols', { - action: ActionFontRangeAdd, - help: ` -List of characters to copy, belongs to previously declared "--font". Examples: - --symbols ,.0123456789 - --symbols abcdefghigklmnopqrstuvwxyz -` - }); - - parser.add_argument('--autohint-off', { - type: range, - action: ActionFontStoreTrue, - help: 'Disable autohinting for previously declared "--font"' - }); - - parser.add_argument('--autohint-strong', { - type: range, - action: ActionFontStoreTrue, - help: 'Use more strong autohinting for previously declared "--font" (will break kerning)' - }); - - parser.add_argument('--force-fast-kern-format', { - dest: 'fast_kerning', - action: 'store_true', - default: false, - help: 'Always use kern classes instead of pairs (might be larger but faster).' - }); - - parser.add_argument('--no-compress', { - dest: 'no_compress', - action: 'store_true', - default: false, - help: 'Disable built-in RLE compression.' - }); - - parser.add_argument('--no-prefilter', { - dest: 'no_prefilter', - action: 'store_true', - default: false, - help: 'Disable bitmap lines filter (XOR), used to improve compression ratio.' - }); - - parser.add_argument('--no-kerning', { - dest: 'no_kerning', - action: 'store_true', - default: false, - help: 'Drop kerning info to reduce size (not recommended).' - }); - - parser.add_argument('--lv-include', { - metavar: '', - help: 'Set alternate "lvgl.h" path (for --format lvgl).' - }); - - parser.add_argument('--full-info', { - dest: 'full_info', - action: 'store_true', - default: false, - help: 'Don\'t shorten "font_info.json" (include pixels data).' - }); - - // - // Process CLI options - // - - let args = parser.parse_args(argv.length ? argv : [ '-h' ]); - - for (let font of args.font) { - if (font.ranges.length === 0) { - parser.error(`You need to specify either "--range" or "--symbols" for font "${font.source_path}"`); - } - - try { - font.source_bin = fs.readFileSync(font.source_path); - } catch (err) { - parser.error(`Cannot read file "${font.source_path}": ${err.message}`); - } - } - - // - // Convert - // - - let files = await convert(args); - - // - // Store files - // - - for (let [ filename, data ] of Object.entries(files)) { - let dir = path.dirname(filename); - - mkdirp.sync(dir); - - fs.writeFileSync(filename, data); - } - -}; - - -// export for tests -module.exports._range = range; diff --git a/node_modules/lv_font_conv/lib/collect_font_data.js b/node_modules/lv_font_conv/lib/collect_font_data.js deleted file mode 100644 index 227c5835..00000000 --- a/node_modules/lv_font_conv/lib/collect_font_data.js +++ /dev/null @@ -1,173 +0,0 @@ -// Read fonts - -'use strict'; - - -const opentype = require('opentype.js'); -const ft_render = require('./freetype'); -const AppError = require('./app_error'); -const Ranger = require('./ranger'); - - -module.exports = async function collect_font_data(args) { - await ft_render.init(); - - // Duplicate font options as k/v for quick access - let fonts_options = {}; - args.font.forEach(f => { fonts_options[f.source_path] = f; }); - - // read fonts - let fonts_opentype = {}; - let fonts_freetype = {}; - - for (let { source_path, source_bin } of args.font) { - // don't load font again if it's specified multiple times in args - if (fonts_opentype[source_path]) continue; - - try { - let b = source_bin; - - if (Buffer.isBuffer(b)) { - // node.js Buffer -> ArrayBuffer - b = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength); - } - - fonts_opentype[source_path] = opentype.parse(b); - } catch (err) { - throw new AppError(`Cannot load font "${source_path}": ${err.message}`); - } - - fonts_freetype[source_path] = ft_render.fontface_create(source_bin, args.size); - } - - // merge all ranges - let ranger = new Ranger(); - - for (let { source_path, ranges } of args.font) { - let font = fonts_freetype[source_path]; - - for (let item of ranges) { - /* eslint-disable max-depth */ - if (item.range) { - for (let i = 0; i < item.range.length; i += 3) { - let range = item.range.slice(i, i + 3); - let chars = ranger.add_range(source_path, ...range); - let is_empty = true; - - for (let code of chars) { - if (ft_render.glyph_exists(font, code)) { - is_empty = false; - break; - } - } - - if (is_empty) { - let a = '0x' + range[0].toString(16); - let b = '0x' + range[1].toString(16); - throw new AppError(`Font "${source_path}" doesn't have any characters included in range ${a}-${b}`); - } - } - } - - if (item.symbols) { - let chars = ranger.add_symbols(source_path, item.symbols); - let is_empty = true; - - for (let code of chars) { - if (ft_render.glyph_exists(font, code)) { - is_empty = false; - break; - } - } - - if (is_empty) { - throw new AppError(`Font "${source_path}" doesn't have any characters included in "${item.symbols}"`); - } - } - } - } - - let mapping = ranger.get(); - let glyphs = []; - let all_dst_charcodes = Object.keys(mapping).sort((a, b) => a - b).map(Number); - - for (let dst_code of all_dst_charcodes) { - let src_code = mapping[dst_code].code; - let src_font = mapping[dst_code].font; - - if (!ft_render.glyph_exists(fonts_freetype[src_font], src_code)) continue; - - let ft_result = ft_render.glyph_render( - fonts_freetype[src_font], - src_code, - { - autohint_off: fonts_options[src_font].autohint_off, - autohint_strong: fonts_options[src_font].autohint_strong, - lcd: args.lcd, - lcd_v: args.lcd_v, - mono: !args.lcd && !args.lcd_v && args.bpp === 1, - use_color_info: args.use_color_info - } - ); - - glyphs.push({ - code: dst_code, - advanceWidth: ft_result.advance_x, - bbox: { - x: ft_result.x, - y: ft_result.y - ft_result.height, - width: ft_result.width, - height: ft_result.height - }, - kerning: {}, - freetype: ft_result.freetype, - pixels: ft_result.pixels - }); - } - - if (!args.no_kerning) { - let existing_dst_charcodes = glyphs.map(g => g.code); - - for (let { code, kerning } of glyphs) { - let src_code = mapping[code].code; - let src_font = mapping[code].font; - let font = fonts_opentype[src_font]; - let glyph = font.charToGlyph(String.fromCodePoint(src_code)); - - for (let dst_code2 of existing_dst_charcodes) { - // can't merge kerning values from 2 different fonts - if (mapping[dst_code2].font !== src_font) continue; - - let src_code2 = mapping[dst_code2].code; - let glyph2 = font.charToGlyph(String.fromCodePoint(src_code2)); - let krn_value = font.getKerningValue(glyph, glyph2); - - if (krn_value) kerning[dst_code2] = krn_value * args.size / font.unitsPerEm; - - //let krn_value = ft_render.get_kerning(font, src_code, src_code2).x; - //if (krn_value) kerning[dst_code2] = krn_value; - } - } - } - - let first_font = fonts_freetype[args.font[0].source_path]; - let first_font_scale = args.size / first_font.units_per_em; - let os2_metrics = ft_render.fontface_os2_table(first_font); - let post_table = fonts_opentype[args.font[0].source_path].tables.post; - - for (let font of Object.values(fonts_freetype)) ft_render.fontface_destroy(font); - - ft_render.destroy(); - - return { - ascent: Math.max(...glyphs.map(g => g.bbox.y + g.bbox.height)), - descent: Math.min(...glyphs.map(g => g.bbox.y)), - typoAscent: Math.round(os2_metrics.typoAscent * first_font_scale), - typoDescent: Math.round(os2_metrics.typoDescent * first_font_scale), - typoLineGap: Math.round(os2_metrics.typoLineGap * first_font_scale), - size: args.size, - glyphs, - underlinePosition: Math.round(post_table.underlinePosition * first_font_scale), - underlineThickness: Math.round(post_table.underlineThickness * first_font_scale) - }; -}; diff --git a/node_modules/lv_font_conv/lib/convert.js b/node_modules/lv_font_conv/lib/convert.js deleted file mode 100644 index 0cf088a0..00000000 --- a/node_modules/lv_font_conv/lib/convert.js +++ /dev/null @@ -1,30 +0,0 @@ -// Internal API to convert input data into output font data -// Used by both CLI and Web wrappers. -'use strict'; - -const collect_font_data = require('./collect_font_data'); - -let writers = { - dump: require('./writers/dump'), - bin: require('./writers/bin'), - lvgl: require('./writers/lvgl') -}; - - -// -// Input: -// - args like from CLI (optionally extended with binary content of files) -// -// Output: -// - { name1: bin_data1, name2: bin_data2, ... } -// -// returns hash with files to write -// -module.exports = async function convert(args) { - let font_data = await collect_font_data(args); - let files = writers[args.format](args, font_data); - - return files; -}; - -module.exports.formats = Object.keys(writers); diff --git a/node_modules/lv_font_conv/lib/font/cmap_build_subtables.js b/node_modules/lv_font_conv/lib/font/cmap_build_subtables.js deleted file mode 100644 index c80a219e..00000000 --- a/node_modules/lv_font_conv/lib/font/cmap_build_subtables.js +++ /dev/null @@ -1,105 +0,0 @@ -// Find an optimal configuration of cmap tables representing set of codepoints, -// using simple breadth-first algorithm -// -// Assume that: -// - codepoints have one-to-one correspondence to glyph ids -// - glyph ids are always bigger for bigger codepoints -// - glyph ids are always consecutive (1..N without gaps) -// -// This way we can omit glyph ids from all calculations entirely: if codepoints -// fit in format0, then glyph ids also will. -// -// format6 is not considered, because if glyph ids can be delta-coded, -// multiple format0 tables are guaranteed to be smaller than a single format6. -// -// sparse format is not used because as long as glyph ids are consecutive, -// sparse_tiny will always be preferred. -// - -'use strict'; - - -function estimate_format0_tiny_size(/*start_code, end_code*/) { - return 16; -} - -function estimate_format0_size(start_code, end_code) { - return 16 + (end_code - start_code + 1); -} - -//function estimate_sparse_size(count) { -// return 16 + count * 4; -//} - -function estimate_sparse_tiny_size(count) { - return 16 + count * 2; -} - -module.exports = function cmap_split(all_codepoints) { - all_codepoints = all_codepoints.sort((a, b) => a - b); - - let min_paths = []; - - for (let i = 0; i < all_codepoints.length; i++) { - let min = { dist: Infinity }; - - for (let j = 0; j <= i; j++) { - let prev_dist = (j - 1 >= 0) ? min_paths[j - 1].dist : 0; - let s; - - if (all_codepoints[i] - all_codepoints[j] < 256) { - s = estimate_format0_size(all_codepoints[j], all_codepoints[i]); - - /* eslint-disable max-depth */ - if (prev_dist + s < min.dist) { - min = { - dist: prev_dist + s, - start: j, - end: i, - format: 'format0' - }; - } - } - - if (all_codepoints[i] - all_codepoints[j] < 256 && all_codepoints[i] - i === all_codepoints[j] - j) { - s = estimate_format0_tiny_size(all_codepoints[j], all_codepoints[i]); - - /* eslint-disable max-depth */ - if (prev_dist + s < min.dist) { - min = { - dist: prev_dist + s, - start: j, - end: i, - format: 'format0_tiny' - }; - } - } - - // tiny sparse will always be preferred over full sparse because glyph ids are consecutive - if (all_codepoints[i] - all_codepoints[j] < 65536) { - s = estimate_sparse_tiny_size(i - j + 1); - - if (prev_dist + s < min.dist) { - min = { - dist: prev_dist + s, - start: j, - end: i, - format: 'sparse_tiny' - }; - } - } - } - - min_paths[i] = min; - } - - let result = []; - - for (let i = all_codepoints.length - 1; i >= 0;) { - let path = min_paths[i]; - result.unshift([ path.format, all_codepoints.slice(path.start, path.end + 1) ]); - i = path.start - 1; - } - - return result; -}; diff --git a/node_modules/lv_font_conv/lib/font/compress.js b/node_modules/lv_font_conv/lib/font/compress.js deleted file mode 100644 index aa30a3a1..00000000 --- a/node_modules/lv_font_conv/lib/font/compress.js +++ /dev/null @@ -1,107 +0,0 @@ -'use strict'; - -//const debug = require('debug')('compress'); - -function count_same(arr, offset) { - let same = 1; - let val = arr[offset]; - - for (let i = offset + 1; i < arr.length; i++) { - if (arr[i] !== val) break; - same++; - } - - return same; -} - -// -// Compress pixels with RLE-like algorythm (modified I3BN) -// -// 1. Require minimal repeat count (1) to enter I3BN mode -// 2. Increased 1-bit-replaced repeat limit (2 => 10) -// 3. Length of direct repetition counter reduced (8 => 6 bits). -// -// pixels - flat array of pixels (one per entry) -// options.bpp - bits per pixels -// -module.exports = function compress(bitStream, pixels, options) { - const opts = Object.assign({}, { repeat: 1 }, options); - - // Minimal repetitions count to enable RLE mode. - const RLE_SKIP_COUNT = 1; - // Number of repeats, when `1` used to replace data - // If more - write as number - const RLE_BIT_COLLAPSED_COUNT = 10; - - const RLE_COUNTER_BITS = 6; // (2^bits - 1) - max value - const RLE_COUNTER_MAX = (1 << RLE_COUNTER_BITS) - 1; - // Force flush if counter dencity exceeded. - const RLE_MAX_REPEATS = RLE_COUNTER_MAX + RLE_BIT_COLLAPSED_COUNT + 1; - - //let bits_start_offset = bitStream.index; - - let offset = 0; - - while (offset < pixels.length) { - const p = pixels[offset]; - - let same = count_same(pixels, offset); - - // Clamp value because RLE counter density is limited - if (same > RLE_MAX_REPEATS + RLE_SKIP_COUNT) { - same = RLE_MAX_REPEATS + RLE_SKIP_COUNT; - } - - //debug(`offset: ${offset}, count: ${same}, pixel: ${p}`); - - offset += same; - - // If not enough for RLE - write as is. - if (same <= RLE_SKIP_COUNT) { - for (let i = 0; i < same; i++) { - bitStream.writeBits(p, opts.bpp); - //debug(`==> ${opts.bpp} bits`); - } - continue; - } - - // First, write "skipped" head as is. - for (let i = 0; i < RLE_SKIP_COUNT; i++) { - bitStream.writeBits(p, opts.bpp); - //debug(`==> ${opts.bpp} bits`); - } - - same -= RLE_SKIP_COUNT; - - // Not reached state to use counter => dump bit-extended - if (same <= RLE_BIT_COLLAPSED_COUNT) { - bitStream.writeBits(p, opts.bpp); - //debug(`==> ${opts.bpp} bits (val)`); - for (let i = 0; i < same; i++) { - /*eslint-disable max-depth*/ - if (i < same - 1) { - bitStream.writeBits(1, 1); - //debug('==> 1 bit (rle repeat)'); - } else { - bitStream.writeBits(0, 1); - //debug('==> 1 bit (rle repeat last)'); - } - } - continue; - } - - same -= RLE_BIT_COLLAPSED_COUNT + 1; - - bitStream.writeBits(p, opts.bpp); - //debug(`==> ${opts.bpp} bits (val)`); - - for (let i = 0; i < RLE_BIT_COLLAPSED_COUNT + 1; i++) { - bitStream.writeBits(1, 1); - //debug('==> 1 bit (rle repeat)'); - } - bitStream.writeBits(same, RLE_COUNTER_BITS); - //debug(`==> 4 bits (rle repeat count ${same})`); - } - - //debug(`output bits: ${bitStream.index - bits_start_offset}`); -}; diff --git a/node_modules/lv_font_conv/lib/font/font.js b/node_modules/lv_font_conv/lib/font/font.js deleted file mode 100644 index e5743629..00000000 --- a/node_modules/lv_font_conv/lib/font/font.js +++ /dev/null @@ -1,131 +0,0 @@ -// Font class to generate tables -'use strict'; - -const u = require('../utils'); -const debug = require('debug')('font'); -const Head = require('./table_head'); -const Cmap = require('./table_cmap'); -const Glyf = require('./table_glyf'); -const Loca = require('./table_loca'); -const Kern = require('./table_kern'); - -class Font { - constructor(fontData, options) { - this.src = fontData; - - this.opts = options; - - // Map chars to IDs (zero is reserved) - this.glyph_id = { 0: 0 }; - - this.last_id = 1; - this.createIDs(); - debug(`last_id: ${this.last_id}`); - - this.init_tables(); - - this.minY = Math.min(...this.src.glyphs.map(g => g.bbox.y)); - debug(`minY: ${this.minY}`); - this.maxY = Math.max(...this.src.glyphs.map(g => g.bbox.y + g.bbox.height)); - debug(`maxY: ${this.maxY}`); - - // 0 => 1 byte, 1 => 2 bytes - this.glyphIdFormat = Math.max(...Object.values(this.glyph_id)) > 255 ? 1 : 0; - debug(`glyphIdFormat: ${this.glyphIdFormat}`); - - // 1.0 by default, will be stored in font as FP12.4 - this.kerningScale = 1.0; - let kerningMax = Math.max(...this.src.glyphs.map(g => Object.values(g.kerning).map(Math.abs)).flat()); - if (kerningMax >= 7.5) this.kerningScale = Math.ceil(kerningMax / 7.5 * 16) / 16; - debug(`kerningScale: ${this.kerningScale}`); - - // 0 => int, 1 => FP4 - this.advanceWidthFormat = this.hasKerning() ? 1 : 0; - debug(`advanceWidthFormat: ${this.advanceWidthFormat}`); - - this.xy_bits = Math.max(...this.src.glyphs.map(g => Math.max( - u.signed_bits(g.bbox.x), u.signed_bits(g.bbox.y) - ))); - debug(`xy_bits: ${this.xy_bits}`); - - this.wh_bits = Math.max(...this.src.glyphs.map(g => Math.max( - u.unsigned_bits(g.bbox.width), u.unsigned_bits(g.bbox.height) - ))); - debug(`wh_bits: ${this.wh_bits}`); - - this.advanceWidthBits = Math.max(...this.src.glyphs.map( - g => u.signed_bits(this.widthToInt(g.advanceWidth)) - )); - debug(`advanceWidthBits: ${this.advanceWidthBits}`); - - let glyphs = this.src.glyphs; - - this.monospaced = glyphs.every((v, i, arr) => v.advanceWidth === arr[0].advanceWidth); - debug(`monospaced: ${this.monospaced}`); - - // This should stay in the end, because depends on previous variables - // 0 => 2 bytes, 1 => 4 bytes - this.indexToLocFormat = this.glyf.getSize() > 65535 ? 1 : 0; - debug(`indexToLocFormat: ${this.indexToLocFormat}`); - - this.subpixels_mode = options.lcd ? 1 : (options.lcd_v ? 2 : 0); - debug(`subpixels_mode: ${this.subpixels_mode}`); - } - - init_tables() { - this.head = new Head(this); - this.glyf = new Glyf(this); - this.cmap = new Cmap(this); - this.loca = new Loca(this); - this.kern = new Kern(this); - } - - createIDs() { - // Simplified, don't check dupes - this.last_id = 1; - - for (let i = 0; i < this.src.glyphs.length; i++) { - // reserve zero for special cases - this.glyph_id[this.src.glyphs[i].code] = this.last_id; - this.last_id++; - } - } - - hasKerning() { - if (this.opts.no_kerning) return false; - - for (let glyph of this.src.glyphs) { - if (glyph.kerning && Object.keys(glyph.kerning).length) return true; - } - return false; - } - - // Returns integer width, depending on format - widthToInt(val) { - if (this.advanceWidthFormat === 0) return Math.round(val); - - return Math.round(val * 16); - } - - // Convert kerning to FP4.4, useable for writer. Apply `kerningScale`. - kernToFP(val) { - return Math.round(val / this.kerningScale * 16); - } - - toBin() { - const result = Buffer.concat([ - this.head.toBin(), - this.cmap.toBin(), - this.loca.toBin(), - this.glyf.toBin(), - this.kern.toBin() - ]); - - debug(`font size: ${result.length}`); - - return result; - } -} - - -module.exports = Font; diff --git a/node_modules/lv_font_conv/lib/font/table_cmap.js b/node_modules/lv_font_conv/lib/font/table_cmap.js deleted file mode 100644 index 8e94bde1..00000000 --- a/node_modules/lv_font_conv/lib/font/table_cmap.js +++ /dev/null @@ -1,201 +0,0 @@ -'use strict'; - - -const build_subtables = require('./cmap_build_subtables'); -const u = require('../utils'); -const debug = require('debug')('font.table.cmap'); - - -const O_SIZE = 0; -const O_LABEL = O_SIZE + 4; -const O_COUNT = O_LABEL + 4; - -const HEAD_LENGTH = O_COUNT + 4; - -const SUB_FORMAT_0 = 0; -const SUB_FORMAT_0_TINY = 2; -const SUB_FORMAT_SPARSE = 1; -const SUB_FORMAT_SPARSE_TINY = 3; - - -class Cmap { - constructor(font) { - this.font = font; - this.label = 'cmap'; - - this.sub_heads = []; - this.sub_data = []; - - this.compiled = false; - } - - compile() { - if (this.compiled) return; - this.compiled = true; - - const f = this.font; - - let subtables_plan = build_subtables(f.src.glyphs.map(g => g.code)); - - const count_format0 = subtables_plan.filter(s => s[0] === 'format0').length; - const count_sparse = subtables_plan.length - count_format0; - debug(`${subtables_plan.length} subtable(s): ${count_format0} "format 0", ${count_sparse} "sparse"`); - - for (let [ format, codepoints ] of subtables_plan) { - let g = this.glyphByCode(codepoints[0]); - let start_glyph_id = f.glyph_id[g.code]; - let min_code = codepoints[0]; - let max_code = codepoints[codepoints.length - 1]; - let entries_count = max_code - min_code + 1; - let format_code = 0; - - if (format === 'format0_tiny') { - format_code = SUB_FORMAT_0_TINY; - this.sub_data.push(Buffer.alloc(0)); - } else if (format === 'format0') { - format_code = SUB_FORMAT_0; - this.sub_data.push(this.create_format0_data(min_code, max_code, start_glyph_id)); - } else if (format === 'sparse_tiny') { - entries_count = codepoints.length; - format_code = SUB_FORMAT_SPARSE_TINY; - this.sub_data.push(this.create_sparse_tiny_data(codepoints, start_glyph_id)); - } else { // assume format === 'sparse' - entries_count = codepoints.length; - format_code = SUB_FORMAT_SPARSE; - this.sub_data.push(this.create_sparse_data(codepoints, start_glyph_id)); - } - - this.sub_heads.push(this.createSubHeader( - min_code, - max_code - min_code + 1, - start_glyph_id, - entries_count, - format_code - )); - } - - this.subHeaderUpdateAllOffsets(); - } - - createSubHeader(rangeStart, rangeLen, glyphIdOffset, total, type) { - const buf = Buffer.alloc(16); - - // buf.writeUInt32LE(offset, 0); offset unknown at this moment - buf.writeUInt32LE(rangeStart, 4); - buf.writeUInt16LE(rangeLen, 8); - buf.writeUInt16LE(glyphIdOffset, 10); - buf.writeUInt16LE(total, 12); - buf.writeUInt8(type, 14); - - return buf; - } - - subHeaderUpdateOffset(header, val) { - header.writeUInt32LE(val, 0); - } - - subHeaderUpdateAllOffsets() { - for (let i = 0; i < this.sub_heads.length; i++) { - const offset = HEAD_LENGTH + - u.sum(this.sub_heads.map(h => h.length)) + - u.sum(this.sub_data.slice(0, i).map(d => d.length)); - - this.subHeaderUpdateOffset(this.sub_heads[i], offset); - } - } - - glyphByCode(code) { - for (let g of this.font.src.glyphs) { - if (g.code === code) return g; - } - - return null; - } - - - collect_format0_data(min_code, max_code, start_glyph_id) { - let data = []; - - for (let i = min_code; i <= max_code; i++) { - const g = this.glyphByCode(i); - - if (!g) { - data.push(0); - continue; - } - - const id_delta = this.font.glyph_id[g.code] - start_glyph_id; - - if (id_delta < 0 || id_delta > 255) throw new Error('Glyph ID delta out of Format 0 range'); - - data.push(id_delta); - } - - return data; - } - - create_format0_data(min_code, max_code, start_glyph_id) { - const data = this.collect_format0_data(min_code, max_code, start_glyph_id); - - return u.balign4(Buffer.from(data)); - } - - collect_sparse_data(codepoints, start_glyph_id) { - let codepoints_list = []; - let ids_list = []; - - for (let code of codepoints) { - let g = this.glyphByCode(code); - let id = this.font.glyph_id[g.code]; - - let code_delta = code - codepoints[0]; - let id_delta = id - start_glyph_id; - - if (code_delta < 0 || code_delta > 65535) throw new Error('Codepoint delta out of range'); - if (id_delta < 0 || id_delta > 65535) throw new Error('Glyph ID delta out of range'); - - codepoints_list.push(code_delta); - ids_list.push(id_delta); - } - - return { - codes: codepoints_list, - ids: ids_list - }; - } - - create_sparse_data(codepoints, start_glyph_id) { - const data = this.collect_sparse_data(codepoints, start_glyph_id); - - return u.balign4(Buffer.concat([ - u.bFromA16(data.codes), - u.bFromA16(data.ids) - ])); - } - - create_sparse_tiny_data(codepoints, start_glyph_id) { - const data = this.collect_sparse_data(codepoints, start_glyph_id); - - return u.balign4(u.bFromA16(data.codes)); - } - - toBin() { - if (!this.compiled) this.compile(); - - const buf = Buffer.concat([ - Buffer.alloc(HEAD_LENGTH), - Buffer.concat(this.sub_heads), - Buffer.concat(this.sub_data) - ]); - debug(`table size = ${buf.length}`); - - buf.writeUInt32LE(buf.length, O_SIZE); - buf.write(this.label, O_LABEL); - buf.writeUInt32LE(this.sub_heads.length, O_COUNT); - - return buf; - } -} - - -module.exports = Cmap; diff --git a/node_modules/lv_font_conv/lib/font/table_glyf.js b/node_modules/lv_font_conv/lib/font/table_glyf.js deleted file mode 100644 index e7a62999..00000000 --- a/node_modules/lv_font_conv/lib/font/table_glyf.js +++ /dev/null @@ -1,147 +0,0 @@ -'use strict'; - -const u = require('../utils'); -const { BitStream } = require('bit-buffer'); -const debug = require('debug')('font.table.glyf'); -const compress = require('./compress'); - - -const O_SIZE = 0; -const O_LABEL = O_SIZE + 4; - -const HEAD_LENGTH = O_LABEL + 4; - - -class Glyf { - constructor(font) { - this.font = font; - this.label = 'glyf'; - - this.compiled = false; - - this.binData = []; - } - - // convert 8-bit opacity to bpp-bit - pixelsToBpp(pixels) { - const bpp = this.font.opts.bpp; - return pixels.map(line => line.map(p => (p >>> (8 - bpp)))); - } - - // Returns "binary stream" (Buffer) of compiled glyph data - compileGlyph(glyph) { - // Allocate memory, enough for eny storage formats - const buf = Buffer.alloc(100 + glyph.bbox.width * glyph.bbox.height * 4); - const bs = new BitStream(buf); - bs.bigEndian = true; - const f = this.font; - - // Store Width - if (!f.monospaced) { - let w = f.widthToInt(glyph.advanceWidth); - bs.writeBits(w, f.advanceWidthBits); - } - - // Store X, Y - bs.writeBits(glyph.bbox.x, f.xy_bits); - bs.writeBits(glyph.bbox.y, f.xy_bits); - bs.writeBits(glyph.bbox.width, f.wh_bits); - bs.writeBits(glyph.bbox.height, f.wh_bits); - - const pixels = this.pixelsToBpp(glyph.pixels); - - this.storePixels(bs, pixels); - - // Shrink size - const result = Buffer.alloc(bs.byteIndex); - buf.copy(result, 0, 0, bs.byteIndex); - - return result; - } - - storePixels(bitStream, pixels) { - if (this.getCompressionCode() === 0) this.storePixelsRaw(bitStream, pixels); - else this.storePixelsCompressed(bitStream, pixels); - } - - storePixelsRaw(bitStream, pixels) { - const bpp = this.font.opts.bpp; - - for (let y = 0; y < pixels.length; y++) { - const line = pixels[y]; - for (let x = 0; x < line.length; x++) { - bitStream.writeBits(line[x], bpp); - } - } - } - - storePixelsCompressed(bitStream, pixels) { - let p; - - if (this.font.opts.no_prefilter) p = pixels.flat(); - else p = u.prefilter(pixels).flat(); - - compress(bitStream, p, this.font.opts); - } - - // Create internal struct with binary data for each glyph - // Needed to calculate offsets & build final result - compile() { - this.compiled = true; - - this.binData = [ - Buffer.alloc(0) // Reserve id 0 - ]; - - const f = this.font; - - f.src.glyphs.forEach(g => { - const id = f.glyph_id[g.code]; - - this.binData[id] = this.compileGlyph(g); - }); - } - - toBin() { - if (!this.compiled) this.compile(); - - const buf = u.balign4(Buffer.concat([ - Buffer.alloc(HEAD_LENGTH), - Buffer.concat(this.binData) - ])); - - buf.writeUInt32LE(buf.length, O_SIZE); - buf.write(this.label, O_LABEL); - - debug(`table size = ${buf.length}`); - - return buf; - } - - getSize() { - if (!this.compiled) this.compile(); - - return u.align4(HEAD_LENGTH + u.sum(this.binData.map(b => b.length))); - } - - getOffset(id) { - if (!this.compiled) this.compile(); - - let offset = HEAD_LENGTH; - - for (let i = 0; i < id; i++) offset += this.binData[i].length; - - return offset; - } - - getCompressionCode() { - if (this.font.opts.no_compress) return 0; - if (this.font.opts.bpp === 1) return 0; - - if (this.font.opts.no_prefilter) return 2; - return 1; - } -} - - -module.exports = Glyf; diff --git a/node_modules/lv_font_conv/lib/font/table_head.js b/node_modules/lv_font_conv/lib/font/table_head.js deleted file mode 100644 index 4cb9f676..00000000 --- a/node_modules/lv_font_conv/lib/font/table_head.js +++ /dev/null @@ -1,99 +0,0 @@ -'use strict'; - - -const u = require('../utils'); -const debug = require('debug')('font.table.head'); - -const O_SIZE = 0; -const O_LABEL = O_SIZE + 4; -const O_VERSION = O_LABEL + 4; -const O_TABLES = O_VERSION + 4; -const O_FONT_SIZE = O_TABLES + 2; -const O_ASCENT = O_FONT_SIZE + 2; -const O_DESCENT = O_ASCENT + 2; -const O_TYPO_ASCENT = O_DESCENT + 2; -const O_TYPO_DESCENT = O_TYPO_ASCENT + 2; -const O_TYPO_LINE_GAP = O_TYPO_DESCENT + 2; -const O_MIN_Y = O_TYPO_LINE_GAP + 2; -const O_MAX_Y = O_MIN_Y + 2; -const O_DEF_ADVANCE_WIDTH = O_MAX_Y + 2; -const O_KERNING_SCALE = O_DEF_ADVANCE_WIDTH + 2; -const O_INDEX_TO_LOC_FORMAT = O_KERNING_SCALE + 2; -const O_GLYPH_ID_FORMAT = O_INDEX_TO_LOC_FORMAT + 1; -const O_ADVANCE_WIDTH_FORMAT = O_GLYPH_ID_FORMAT + 1; -const O_BITS_PER_PIXEL = O_ADVANCE_WIDTH_FORMAT + 1; -const O_XY_BITS = O_BITS_PER_PIXEL + 1; -const O_WH_BITS = O_XY_BITS + 1; -const O_ADVANCE_WIDTH_BITS = O_WH_BITS + 1; -const O_COMPRESSION_ID = O_ADVANCE_WIDTH_BITS + 1; -const O_SUBPIXELS_MODE = O_COMPRESSION_ID + 1; -const O_TMP_RESERVED1 = O_SUBPIXELS_MODE + 1; -const O_UNDERLINE_POSITION = O_TMP_RESERVED1 + 1; -const O_UNDERLINE_THICKNESS = O_UNDERLINE_POSITION + 2; -const HEAD_LENGTH = u.align4(O_UNDERLINE_THICKNESS + 2); - - -class Head { - constructor(font) { - this.font = font; - this.label = 'head'; - this.version = 1; - } - - toBin() { - const buf = Buffer.alloc(HEAD_LENGTH); - debug(`table size = ${buf.length}`); - - buf.writeUInt32LE(HEAD_LENGTH, O_SIZE); - buf.write(this.label, O_LABEL); - buf.writeUInt32LE(this.version, O_VERSION); - - const f = this.font; - - const tables_count = f.hasKerning() ? 4 : 3; - - buf.writeUInt16LE(tables_count, O_TABLES); - - buf.writeUInt16LE(f.src.size, O_FONT_SIZE); - buf.writeUInt16LE(f.src.ascent, O_ASCENT); - buf.writeInt16LE(f.src.descent, O_DESCENT); - - buf.writeUInt16LE(f.src.typoAscent, O_TYPO_ASCENT); - buf.writeInt16LE(f.src.typoDescent, O_TYPO_DESCENT); - buf.writeUInt16LE(f.src.typoLineGap, O_TYPO_LINE_GAP); - - buf.writeInt16LE(f.minY, O_MIN_Y); - buf.writeInt16LE(f.maxY, O_MAX_Y); - - if (f.monospaced) { - buf.writeUInt16LE(f.widthToInt(f.src.glyphs[0].advanceWidth), O_DEF_ADVANCE_WIDTH); - } else { - buf.writeUInt16LE(0, O_DEF_ADVANCE_WIDTH); - } - - buf.writeUInt16LE(Math.round(f.kerningScale * 16), O_KERNING_SCALE); // FP12.4 - - buf.writeUInt8(f.indexToLocFormat, O_INDEX_TO_LOC_FORMAT); - buf.writeUInt8(f.glyphIdFormat, O_GLYPH_ID_FORMAT); - buf.writeUInt8(f.advanceWidthFormat, O_ADVANCE_WIDTH_FORMAT); - - buf.writeUInt8(f.opts.bpp, O_BITS_PER_PIXEL); - buf.writeUInt8(f.xy_bits, O_XY_BITS); - buf.writeUInt8(f.wh_bits, O_WH_BITS); - - if (f.monospaced) buf.writeUInt8(0, O_ADVANCE_WIDTH_BITS); - else buf.writeUInt8(f.advanceWidthBits, O_ADVANCE_WIDTH_BITS); - - buf.writeUInt8(f.glyf.getCompressionCode(), O_COMPRESSION_ID); - - buf.writeUInt8(f.subpixels_mode, O_SUBPIXELS_MODE); - - buf.writeInt16LE(f.src.underlinePosition, O_UNDERLINE_POSITION); - buf.writeUInt16LE(f.src.underlineThickness, O_UNDERLINE_POSITION); - - return buf; - } -} - - -module.exports = Head; diff --git a/node_modules/lv_font_conv/lib/font/table_kern.js b/node_modules/lv_font_conv/lib/font/table_kern.js deleted file mode 100644 index e879b3c7..00000000 --- a/node_modules/lv_font_conv/lib/font/table_kern.js +++ /dev/null @@ -1,256 +0,0 @@ -'use strict'; - -const u = require('../utils'); -const debug = require('debug')('font.table.kern'); - - -const O_SIZE = 0; -const O_LABEL = O_SIZE + 4; -const O_FORMAT = O_LABEL + 4; - -const HEAD_LENGTH = u.align4(O_FORMAT + 1); - - -class Kern { - constructor(font) { - this.font = font; - this.label = 'kern'; - this.format3_forced = false; - } - - collect_format0_data() { - const f = this.font; - const glyphs = u.sort_by(this.font.src.glyphs, g => f.glyph_id[g.code]); - const kernSorted = []; - - for (let g of glyphs) { - if (!g.kerning || !Object.keys(g.kerning).length) continue; - - const glyph_id = f.glyph_id[g.code]; - const paired = u.sort_by(Object.keys(g.kerning), code => f.glyph_id[code]); - - for (let code of paired) { - const glyph_id2 = f.glyph_id[code]; - kernSorted.push([ glyph_id, glyph_id2, g.kerning[code] ]); - } - } - - return kernSorted; - } - - create_format0_data() { - const f = this.font; - const glyphs = this.font.src.glyphs; - const kernSorted = this.collect_format0_data(); - - const count = kernSorted.length; - - const kerned_glyphs = glyphs.filter(g => Object.keys(g.kerning).length).length; - const kerning_list_max = Math.max(...glyphs.map(g => Object.keys(g.kerning).length)); - debug(`${kerned_glyphs} kerned glyphs of ${glyphs.length}, ${kerning_list_max} max list, ${count} total pairs`); - - const subheader = Buffer.alloc(4); - - subheader.writeUInt32LE(count, 0); - - const pairs_buf = Buffer.alloc((f.glyphIdFormat ? 4 : 2) * count); - - // Write kerning pairs - for (let i = 0; i < count; i++) { - if (f.glyphIdFormat === 0) { - pairs_buf.writeUInt8(kernSorted[i][0], 2 * i); - pairs_buf.writeUInt8(kernSorted[i][1], 2 * i + 1); - } else { - pairs_buf.writeUInt16LE(kernSorted[i][0], 4 * i); - pairs_buf.writeUInt16LE(kernSorted[i][1], 4 * i + 2); - } - } - - const values_buf = Buffer.alloc(count); - - // Write kerning values - for (let i = 0; i < count; i++) { - values_buf.writeInt8(f.kernToFP(kernSorted[i][2]), i); // FP4.4 - } - - let buf = Buffer.concat([ - subheader, - pairs_buf, - values_buf - ]); - - let buf_aligned = u.balign4(buf); - - debug(`table format0 size = ${buf_aligned.length}`); - return buf_aligned; - } - - collect_format3_data() { - const f = this.font; - const glyphs = u.sort_by(this.font.src.glyphs, g => f.glyph_id[g.code]); - - // extract kerning pairs for each character; - // left kernings are kerning values based on left char (already there), - // right kernings are kerning values based on right char (extracted from left) - const left_kernings = {}; - const right_kernings = {}; - - for (let g of glyphs) { - if (!g.kerning || !Object.keys(g.kerning).length) continue; - - const paired = Object.keys(g.kerning); - - left_kernings[g.code] = g.kerning; - - for (let code of paired) { - right_kernings[code] = right_kernings[code] || {}; - right_kernings[code][g.code] = g.kerning[code]; - } - } - - // input: - // - kernings, char => { hash: String, [char1]: Number, [char2]: Number, ... } - // - // returns: - // - array of [ char1, char2, ... ] - // - function build_classes(kernings) { - const classes = []; - - for (let code of Object.keys(kernings)) { - // for each kerning table calculate unique value representing it; - // keys needs to be sorted for this (but we're using numeric keys, so - // sorting happens automatically and can't be changed) - const hash = JSON.stringify(kernings[code]); - - classes[hash] = classes[hash] || []; - classes[hash].push(Number(code)); - } - - return Object.values(classes); - } - - const left_classes = build_classes(left_kernings); - debug(`unique left classes: ${left_classes.length}`); - - const right_classes = build_classes(right_kernings); - debug(`unique right classes: ${right_classes.length}`); - - if (left_classes.length >= 255 || right_classes.length >= 255) { - debug('too many classes for format3 subtable'); - return null; - } - - function kern_class_mapping(classes) { - const arr = Array(f.last_id).fill(0); - - classes.forEach((members, idx) => { - for (let code of members) { - arr[f.glyph_id[code]] = idx + 1; - } - }); - - return arr; - } - - function kern_class_values() { - const arr = []; - - for (let left_class of left_classes) { - for (let right_class of right_classes) { - let code1 = left_class[0]; - let code2 = right_class[0]; - arr.push(left_kernings[code1][code2] || 0); - } - } - - return arr; - } - - return { - left_classes: left_classes.length, - right_classes: right_classes.length, - left_mapping: kern_class_mapping(left_classes), - right_mapping: kern_class_mapping(right_classes), - values: kern_class_values() - }; - } - - create_format3_data() { - const f = this.font; - const { - left_classes, - right_classes, - left_mapping, - right_mapping, - values - } = this.collect_format3_data(); - - const subheader = Buffer.alloc(4); - subheader.writeUInt16LE(f.last_id); - subheader.writeUInt8(left_classes, 2); - subheader.writeUInt8(right_classes, 3); - - let buf = Buffer.concat([ - subheader, - Buffer.from(left_mapping), - Buffer.from(right_mapping), - Buffer.from(values.map(v => f.kernToFP(v))) - ]); - - let buf_aligned = u.balign4(buf); - - debug(`table format3 size = ${buf_aligned.length}`); - return buf_aligned; - } - - should_use_format3() { - if (!this.font.hasKerning()) return false; - - const format0_data = this.create_format0_data(); - const format3_data = this.create_format3_data(); - - if (format3_data && format3_data.length <= format0_data.length) return true; - - if (this.font.opts.fast_kerning && format3_data) { - this.format3_forced = true; - return true; - } - - return false; - } - - toBin() { - if (!this.font.hasKerning()) return Buffer.alloc(0); - - const format0_data = this.create_format0_data(); - const format3_data = this.create_format3_data(); - - let header = Buffer.alloc(HEAD_LENGTH); - - let data = format0_data; - header.writeUInt8(0, O_FORMAT); - - /* eslint-disable no-console */ - - if (this.should_use_format3()) { - data = format3_data; - header.writeUInt8(3, O_FORMAT); - - if (this.format3_forced) { - let diff = format3_data.length - format0_data.length; - console.log(`Forced faster kerning format (via classes). Size increase is ${diff} bytes.`); - } - } else if (this.font.opts.fast_kerning) { - console.log('Forced faster kerning format (via classes), but data exceeds it\'s limits. Continue use pairs.'); - } - - header.writeUInt32LE(header.length + data.length, O_SIZE); - header.write(this.label, O_LABEL); - - return Buffer.concat([ header, data ]); - } -} - - -module.exports = Kern; diff --git a/node_modules/lv_font_conv/lib/font/table_loca.js b/node_modules/lv_font_conv/lib/font/table_loca.js deleted file mode 100644 index 4aa50d22..00000000 --- a/node_modules/lv_font_conv/lib/font/table_loca.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - - -const u = require('../utils'); -const debug = require('debug')('font.table.loca'); - - -const O_SIZE = 0; -const O_LABEL = O_SIZE + 4; -const O_COUNT = O_LABEL + 4; - -const HEAD_LENGTH = O_COUNT + 4; - - -class Loca { - constructor(font) { - this.font = font; - this.label = 'loca'; - } - - toBin() { - const f = this.font; - - const offsets = [ ...Array(f.last_id).keys() ].map(i => f.glyf.getOffset(i)); - - const buf = u.balign4(Buffer.concat([ - Buffer.alloc(HEAD_LENGTH), - f.indexToLocFormat ? u.bFromA32(offsets) : u.bFromA16(offsets) - ])); - - buf.writeUInt32LE(buf.length, O_SIZE); - buf.write(this.label, O_LABEL); - buf.writeUInt32LE(f.last_id, O_COUNT); - - debug(`table size = ${buf.length}`); - - return buf; - } -} - - -module.exports = Loca; diff --git a/node_modules/lv_font_conv/lib/freetype/index.js b/node_modules/lv_font_conv/lib/freetype/index.js deleted file mode 100644 index d05f68c3..00000000 --- a/node_modules/lv_font_conv/lib/freetype/index.js +++ /dev/null @@ -1,317 +0,0 @@ -'use strict'; - - -const ft_render_fabric = require('./build/ft_render'); - -let m = null; // compiled module instance -let library = 0; // pointer to library struct in wasm memory - - -// workaround because of bug in emscripten: -// https://github.com/emscripten-core/emscripten/issues/5820 -const runtime_initialized = new Promise(resolve => { - ft_render_fabric().then(module_instance => { - m = module_instance; - resolve(); - }); -}); - -function from_16_16(fixed_point) { - return fixed_point / (1 << 16); -} - -function from_26_6(fixed_point) { - return fixed_point / (1 << 6); -} - -function int8_to_uint8(value) { - return value >= 0 ? value : value + 0x100; -} - -let FT_New_Memory_Face, - FT_Set_Char_Size, - FT_Set_Pixel_Sizes, - FT_Get_Char_Index, - FT_Load_Glyph, - FT_Get_Sfnt_Table, - FT_Get_Kerning, - FT_Done_Face; - -module.exports.init = async function () { - await runtime_initialized; - m._init_constants(); - - FT_New_Memory_Face = module.exports.FT_New_Memory_Face = - m.cwrap('FT_New_Memory_Face', 'number', [ 'number', 'number', 'number', 'number', 'number' ]); - - FT_Set_Char_Size = module.exports.FT_Set_Char_Size = - m.cwrap('FT_Set_Char_Size', 'number', [ 'number', 'number', 'number', 'number', 'number' ]); - - FT_Set_Pixel_Sizes = module.exports.FT_Set_Pixel_Sizes = - m.cwrap('FT_Set_Pixel_Sizes', 'number', [ 'number', 'number', 'number' ]); - - FT_Get_Char_Index = module.exports.FT_Get_Char_Index = - m.cwrap('FT_Get_Char_Index', 'number', [ 'number', 'number' ]); - - FT_Load_Glyph = module.exports.FT_Load_Glyph = - m.cwrap('FT_Load_Glyph', 'number', [ 'number', 'number', 'number' ]); - - FT_Get_Sfnt_Table = module.exports.FT_Get_Sfnt_Table = - m.cwrap('FT_Get_Sfnt_Table', 'number', [ 'number', 'number' ]); - - FT_Get_Kerning = module.exports.FT_Get_Kerning = - m.cwrap('FT_Get_Kerning', 'number', [ 'number', 'number', 'number', 'number', 'number' ]); - - FT_Done_Face = module.exports.FT_Done_Face = - m.cwrap('FT_Done_Face', 'number', [ 'number' ]); - - if (!library) { - let ptr = m._malloc(4); - - try { - let error = m.ccall('FT_Init_FreeType', 'number', [ 'number' ], [ ptr ]); - - if (error) throw new Error(`error in FT_Init_FreeType: ${error}`); - - library = m.getValue(ptr, 'i32'); - } finally { - m._free(ptr); - } - } -}; - - -module.exports.fontface_create = function (source, size) { - let error; - let face = { - ptr: 0, - font: m._malloc(source.length) - }; - - m.writeArrayToMemory(source, face.font); - - let ptr = m._malloc(4); - - try { - error = FT_New_Memory_Face(library, face.font, source.length, 0, ptr); - - if (error) throw new Error(`error in FT_New_Memory_Face: ${error}`); - - face.ptr = m.getValue(ptr, 'i32'); - } finally { - m._free(ptr); - } - - error = FT_Set_Char_Size(face.ptr, 0, size * 64, 300, 300); - - if (error) throw new Error(`error in FT_Set_Char_Size: ${error}`); - - error = FT_Set_Pixel_Sizes(face.ptr, 0, size); - - if (error) throw new Error(`error in FT_Set_Pixel_Sizes: ${error}`); - - let units_per_em = m.getValue(face.ptr + m.OFFSET_FACE_UNITS_PER_EM, 'i16'); - let ascender = m.getValue(face.ptr + m.OFFSET_FACE_ASCENDER, 'i16'); - let descender = m.getValue(face.ptr + m.OFFSET_FACE_DESCENDER, 'i16'); - let height = m.getValue(face.ptr + m.OFFSET_FACE_HEIGHT, 'i16'); - - return Object.assign(face, { - units_per_em, - ascender, - descender, - height - }); -}; - - -module.exports.fontface_os2_table = function (face) { - let sfnt_ptr = FT_Get_Sfnt_Table(face.ptr, m.FT_SFNT_OS2); - - if (!sfnt_ptr) throw new Error('os/2 table not found for this font'); - - let typoAscent = m.getValue(sfnt_ptr + m.OFFSET_TT_OS2_ASCENDER, 'i16'); - let typoDescent = m.getValue(sfnt_ptr + m.OFFSET_TT_OS2_DESCENDER, 'i16'); - let typoLineGap = m.getValue(sfnt_ptr + m.OFFSET_TT_OS2_LINEGAP, 'i16'); - - return { - typoAscent, - typoDescent, - typoLineGap - }; -}; - - -module.exports.get_kerning = function (face, code1, code2) { - let glyph1 = FT_Get_Char_Index(face.ptr, code1); - let glyph2 = FT_Get_Char_Index(face.ptr, code2); - let ptr = m._malloc(4 * 2); - - try { - let error = FT_Get_Kerning(face.ptr, glyph1, glyph2, m.FT_KERNING_DEFAULT, ptr); - - if (error) throw new Error(`error in FT_Get_Kerning: ${error}`); - } finally { - m._free(ptr); - } - - return { - x: from_26_6(m.getValue(ptr, 'i32')), - y: from_26_6(m.getValue(ptr + 4, 'i32')) - }; -}; - - -module.exports.glyph_exists = function (face, code) { - let glyph_index = FT_Get_Char_Index(face.ptr, code); - - return glyph_index !== 0; -}; - - -module.exports.glyph_render = function (face, code, opts = {}) { - let glyph_index = FT_Get_Char_Index(face.ptr, code); - - if (glyph_index === 0) throw new Error(`glyph does not exist for codepoint ${code}`); - - let load_flags = m.FT_LOAD_RENDER; - - if (opts.mono) { - load_flags |= m.FT_LOAD_TARGET_MONO; - - } else if (opts.lcd) { - load_flags |= m.FT_LOAD_TARGET_LCD; - - } else if (opts.lcd_v) { - load_flags |= m.FT_LOAD_TARGET_LCD_V; - - } else { - /* eslint-disable no-lonely-if */ - - // Use "light" by default, it changes horizontal lines only. - // "normal" is more strong (with vertical lines), but will break kerning, if - // no additional care taken. More advanced rendering requires upper level - // layout support (via Harfbuzz, for example). - if (!opts.autohint_strong) load_flags |= m.FT_LOAD_TARGET_LIGHT; - else load_flags |= m.FT_LOAD_TARGET_NORMAL; - } - - if (opts.autohint_off) load_flags |= m.FT_LOAD_NO_AUTOHINT; - else load_flags |= m.FT_LOAD_FORCE_AUTOHINT; - - if (opts.use_color_info) load_flags |= m.FT_LOAD_COLOR; - - let error = FT_Load_Glyph(face.ptr, glyph_index, load_flags); - - if (error) throw new Error(`error in FT_Load_Glyph: ${error}`); - - let glyph = m.getValue(face.ptr + m.OFFSET_FACE_GLYPH, 'i32'); - - let glyph_data = { - glyph_index: m.getValue(glyph + m.OFFSET_GLYPH_INDEX, 'i32'), - metrics: { - width: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_WIDTH, 'i32')), - height: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_HEIGHT, 'i32')), - horiBearingX: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_HORI_BEARING_X, 'i32')), - horiBearingY: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_HORI_BEARING_Y, 'i32')), - horiAdvance: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_HORI_ADVANCE, 'i32')), - vertBearingX: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_VERT_BEARING_X, 'i32')), - vertBearingY: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_VERT_BEARING_Y, 'i32')), - vertAdvance: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_METRICS_VERT_ADVANCE, 'i32')) - }, - linearHoriAdvance: from_16_16(m.getValue(glyph + m.OFFSET_GLYPH_LINEAR_HORI_ADVANCE, 'i32')), - linearVertAdvance: from_16_16(m.getValue(glyph + m.OFFSET_GLYPH_LINEAR_VERT_ADVANCE, 'i32')), - advance: { - x: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_ADVANCE_X, 'i32')), - y: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_ADVANCE_Y, 'i32')) - }, - bitmap: { - width: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_WIDTH, 'i32'), - rows: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_ROWS, 'i32'), - pitch: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_PITCH, 'i32'), - num_grays: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_NUM_GRAYS, 'i16'), - pixel_mode: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_PIXEL_MODE, 'i8'), - palette_mode: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_PALETTE_MODE, 'i8') - }, - bitmap_left: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_LEFT, 'i32'), - bitmap_top: m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_TOP, 'i32'), - lsb_delta: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_LSB_DELTA, 'i32')), - rsb_delta: from_26_6(m.getValue(glyph + m.OFFSET_GLYPH_RSB_DELTA, 'i32')) - }; - - let g_w = glyph_data.bitmap.width; - let g_h = glyph_data.bitmap.rows; - let g_x = glyph_data.bitmap_left; - let g_y = glyph_data.bitmap_top; - - let buffer = m.getValue(glyph + m.OFFSET_GLYPH_BITMAP_BUFFER, 'i32'); - let pitch = Math.abs(glyph_data.bitmap.pitch); - - let advance_x = glyph_data.linearHoriAdvance; - let advance_y = glyph_data.linearVertAdvance; - - let pixel_mode = glyph_data.bitmap.pixel_mode; - - let output = []; - - for (let y = 0; y < g_h; y++) { - let row_start = buffer + y * pitch; - let line = []; - - for (let x = 0; x < g_w; x++) { - if (pixel_mode === m.FT_PIXEL_MODE_MONO) { - let value = m.getValue(row_start + ~~(x / 8), 'i8'); - line.push(value & (1 << (7 - (x % 8))) ? 255 : 0); - } else if (pixel_mode === m.FT_PIXEL_MODE_BGRA) { - let blue = int8_to_uint8(m.getValue(row_start + (x * 4) + 0, 'i8')); - let green = int8_to_uint8(m.getValue(row_start + (x * 4) + 1, 'i8')); - let red = int8_to_uint8(m.getValue(row_start + (x * 4) + 2, 'i8')); - let alpha = int8_to_uint8(m.getValue(row_start + (x * 4) + 3, 'i8')); - // convert RGBA to grayscale - let grayscale = Math.round(0.299 * red + 0.587 * green + 0.114 * blue); - if (grayscale > 255) grayscale = 255; - // meld grayscale into alpha channel - alpha = ((255 - grayscale) * alpha) / 255; - line.push(alpha); - } else { - let value = m.getValue(row_start + x, 'i8'); - line.push(int8_to_uint8(value)); - } - } - - output.push(line); - } - - return { - x: g_x, - y: g_y, - width: g_w, - height: g_h, - advance_x, - advance_y, - pixels: output, - freetype: glyph_data - }; -}; - - -module.exports.fontface_destroy = function (face) { - let error = FT_Done_Face(face.ptr); - - if (error) throw new Error(`error in FT_Done_Face: ${error}`); - - m._free(face.font); - face.ptr = 0; - face.font = 0; -}; - - -module.exports.destroy = function () { - let error = m.ccall('FT_Done_FreeType', 'number', [ 'number' ], [ library ]); - - if (error) throw new Error(`error in FT_Done_FreeType: ${error}`); - - library = 0; - - // don't unload wasm - slows down tests too much - //m = null; -}; diff --git a/node_modules/lv_font_conv/lib/freetype/render.c b/node_modules/lv_font_conv/lib/freetype/render.c deleted file mode 100644 index 901d4974..00000000 --- a/node_modules/lv_font_conv/lib/freetype/render.c +++ /dev/null @@ -1,83 +0,0 @@ -#include -#include -#include FT_FREETYPE_H -#include FT_TRUETYPE_TABLES_H - -static void set_js_variable(char* name, int value) { - char buffer[strlen(name) + 32]; - sprintf(buffer, "Module.%s = %d;", name, value); - emscripten_run_script(buffer); -} - -// Expose constants, used in calls from js -void init_constants() -{ - set_js_variable("FT_LOAD_DEFAULT", FT_LOAD_DEFAULT); - set_js_variable("FT_LOAD_NO_HINTING", FT_LOAD_NO_HINTING); - set_js_variable("FT_LOAD_RENDER", FT_LOAD_RENDER); - set_js_variable("FT_LOAD_FORCE_AUTOHINT", FT_LOAD_FORCE_AUTOHINT); - set_js_variable("FT_LOAD_PEDANTIC", FT_LOAD_PEDANTIC); - set_js_variable("FT_LOAD_MONOCHROME", FT_LOAD_MONOCHROME); - set_js_variable("FT_LOAD_NO_AUTOHINT", FT_LOAD_NO_AUTOHINT); - set_js_variable("FT_LOAD_COLOR", FT_LOAD_COLOR); - - set_js_variable("FT_LOAD_TARGET_NORMAL", FT_LOAD_TARGET_NORMAL); - set_js_variable("FT_LOAD_TARGET_LIGHT", FT_LOAD_TARGET_LIGHT); - set_js_variable("FT_LOAD_TARGET_MONO", FT_LOAD_TARGET_MONO); - set_js_variable("FT_LOAD_TARGET_LCD", FT_LOAD_TARGET_LCD); - set_js_variable("FT_LOAD_TARGET_LCD_V", FT_LOAD_TARGET_LCD_V); - - set_js_variable("FT_RENDER_MODE_NORMAL", FT_RENDER_MODE_NORMAL); - set_js_variable("FT_RENDER_MODE_MONO", FT_RENDER_MODE_MONO); - set_js_variable("FT_RENDER_MODE_LCD", FT_RENDER_MODE_LCD); - set_js_variable("FT_RENDER_MODE_LCD_V", FT_RENDER_MODE_LCD_V); - - set_js_variable("FT_KERNING_DEFAULT", FT_KERNING_DEFAULT); - set_js_variable("FT_KERNING_UNFITTED", FT_KERNING_UNFITTED); - set_js_variable("FT_KERNING_UNSCALED", FT_KERNING_UNSCALED); - - set_js_variable("FT_SFNT_OS2", FT_SFNT_OS2); - - set_js_variable("FT_FACE_FLAG_COLOR", FT_FACE_FLAG_COLOR); - - set_js_variable("FT_PIXEL_MODE_MONO", FT_PIXEL_MODE_MONO); - set_js_variable("FT_PIXEL_MODE_BGRA", FT_PIXEL_MODE_BGRA); - - set_js_variable("OFFSET_FACE_GLYPH", offsetof(FT_FaceRec, glyph)); - set_js_variable("OFFSET_FACE_UNITS_PER_EM", offsetof(FT_FaceRec, units_per_EM)); - set_js_variable("OFFSET_FACE_ASCENDER", offsetof(FT_FaceRec, ascender)); - set_js_variable("OFFSET_FACE_DESCENDER", offsetof(FT_FaceRec, descender)); - set_js_variable("OFFSET_FACE_HEIGHT", offsetof(FT_FaceRec, height)); - set_js_variable("OFFSET_FACE_FACE_FLAGS", offsetof(FT_FaceRec, face_flags)); - - set_js_variable("OFFSET_GLYPH_BITMAP_WIDTH", offsetof(FT_GlyphSlotRec, bitmap.width)); - set_js_variable("OFFSET_GLYPH_BITMAP_ROWS", offsetof(FT_GlyphSlotRec, bitmap.rows)); - set_js_variable("OFFSET_GLYPH_BITMAP_PITCH", offsetof(FT_GlyphSlotRec, bitmap.pitch)); - set_js_variable("OFFSET_GLYPH_BITMAP_BUFFER", offsetof(FT_GlyphSlotRec, bitmap.buffer)); - set_js_variable("OFFSET_GLYPH_BITMAP_NUM_GRAYS", offsetof(FT_GlyphSlotRec, bitmap.num_grays)); - set_js_variable("OFFSET_GLYPH_BITMAP_PIXEL_MODE", offsetof(FT_GlyphSlotRec, bitmap.pixel_mode)); - set_js_variable("OFFSET_GLYPH_BITMAP_PALETTE_MODE", offsetof(FT_GlyphSlotRec, bitmap.palette_mode)); - - set_js_variable("OFFSET_GLYPH_METRICS_WIDTH", offsetof(FT_GlyphSlotRec, metrics.width)); - set_js_variable("OFFSET_GLYPH_METRICS_HEIGHT", offsetof(FT_GlyphSlotRec, metrics.height)); - set_js_variable("OFFSET_GLYPH_METRICS_HORI_BEARING_X", offsetof(FT_GlyphSlotRec, metrics.horiBearingX)); - set_js_variable("OFFSET_GLYPH_METRICS_HORI_BEARING_Y", offsetof(FT_GlyphSlotRec, metrics.horiBearingY)); - set_js_variable("OFFSET_GLYPH_METRICS_HORI_ADVANCE", offsetof(FT_GlyphSlotRec, metrics.horiAdvance)); - set_js_variable("OFFSET_GLYPH_METRICS_VERT_BEARING_X", offsetof(FT_GlyphSlotRec, metrics.vertBearingX)); - set_js_variable("OFFSET_GLYPH_METRICS_VERT_BEARING_Y", offsetof(FT_GlyphSlotRec, metrics.vertBearingY)); - set_js_variable("OFFSET_GLYPH_METRICS_VERT_ADVANCE", offsetof(FT_GlyphSlotRec, metrics.vertAdvance)); - - set_js_variable("OFFSET_GLYPH_BITMAP_LEFT", offsetof(FT_GlyphSlotRec, bitmap_left)); - set_js_variable("OFFSET_GLYPH_BITMAP_TOP", offsetof(FT_GlyphSlotRec, bitmap_top)); - set_js_variable("OFFSET_GLYPH_INDEX", offsetof(FT_GlyphSlotRec, glyph_index)); - set_js_variable("OFFSET_GLYPH_LINEAR_HORI_ADVANCE", offsetof(FT_GlyphSlotRec, linearHoriAdvance)); - set_js_variable("OFFSET_GLYPH_LINEAR_VERT_ADVANCE", offsetof(FT_GlyphSlotRec, linearVertAdvance)); - set_js_variable("OFFSET_GLYPH_ADVANCE_X", offsetof(FT_GlyphSlotRec, advance.x)); - set_js_variable("OFFSET_GLYPH_ADVANCE_Y", offsetof(FT_GlyphSlotRec, advance.y)); - set_js_variable("OFFSET_GLYPH_LSB_DELTA", offsetof(FT_GlyphSlotRec, lsb_delta)); - set_js_variable("OFFSET_GLYPH_RSB_DELTA", offsetof(FT_GlyphSlotRec, rsb_delta)); - - set_js_variable("OFFSET_TT_OS2_ASCENDER", offsetof(TT_OS2, sTypoAscender)); - set_js_variable("OFFSET_TT_OS2_DESCENDER", offsetof(TT_OS2, sTypoDescender)); - set_js_variable("OFFSET_TT_OS2_LINEGAP", offsetof(TT_OS2, sTypoLineGap)); -} diff --git a/node_modules/lv_font_conv/lib/ranger.js b/node_modules/lv_font_conv/lib/ranger.js deleted file mode 100644 index 34372d75..00000000 --- a/node_modules/lv_font_conv/lib/ranger.js +++ /dev/null @@ -1,51 +0,0 @@ -// Merge ranges into single object - -'use strict'; - - -class Ranger { - constructor() { - this.data = {}; - } - - // input: - // -r 0x1F450 - single value, dec or hex format - // -r 0x1F450-0x1F470 - range - // -r 0x1F450=>0xF005 - single glyph with mapping - // -r 0x1F450-0x1F470=>0xF005 - range with mapping - add_range(font, start, end, mapped_start) { - let offset = mapped_start - start; - let output = []; - - for (let i = start; i <= end; i++) { - this._set_char(font, i, i + offset); - output.push(i); - } - - return output; - } - - // input: characters to copy, e.g. '1234567890abcdef' - add_symbols(font, str) { - let output = []; - - for (let chr of str) { - let code = chr.codePointAt(0); - this._set_char(font, code, code); - output.push(code); - } - - return output; - } - - _set_char(font, code, mapped_to) { - this.data[mapped_to] = { font, code }; - } - - get() { - return this.data; - } -} - - -module.exports = Ranger; diff --git a/node_modules/lv_font_conv/lib/utils.js b/node_modules/lv_font_conv/lib/utils.js deleted file mode 100644 index 0ca79322..00000000 --- a/node_modules/lv_font_conv/lib/utils.js +++ /dev/null @@ -1,131 +0,0 @@ -'use strict'; - - -function set_byte_depth(depth) { - return function (byte) { - // calculate significant bits, e.g. for depth=2 it's 0, 1, 2 or 3 - let value = ~~(byte / (256 >> depth)); - - // spread those bits around 0..255 range, e.g. for depth=2 it's 0, 85, 170 or 255 - let scale = (2 << (depth - 1)) - 1; - - return (value * 0xFFFF / scale) >> 8; - }; -} - - -module.exports.set_depth = function set_depth(glyph, depth) { - let pixels = []; - let fn = set_byte_depth(depth); - - for (let y = 0; y < glyph.bbox.height; y++) { - pixels.push(glyph.pixels[y].map(fn)); - } - - return Object.assign({}, glyph, { pixels }); -}; - - -function count_bits(val) { - let count = 0; - val = ~~val; - - while (val) { - count++; - val >>= 1; - } - - return count; -} - -// Minimal number of bits to store unsigned value -module.exports.unsigned_bits = count_bits; - -// Minimal number of bits to store signed value -module.exports.signed_bits = function signed_bits(val) { - if (val >= 0) return count_bits(val) + 1; - - return count_bits(Math.abs(val) - 1) + 1; -}; - -// Align value to 4x - useful to create word-aligned arrays -function align4(size) { - if (size % 4 === 0) return size; - return size + 4 - (size % 4); -} -module.exports.align4 = align4; - -// Align buffer length to 4x (returns copy with zero-filled tail) -module.exports.balign4 = function balign4(buf) { - let buf_aligned = Buffer.alloc(align4(buf.length)); - buf.copy(buf_aligned); - return buf_aligned; -}; - -// Pre-filter image to improve compression ratio -// In this case - XOR lines, because it's very effective -// in decompressor and does not depend on bpp. -module.exports.prefilter = function prefilter(pixels) { - return pixels.map((line, l_idx, arr) => { - if (l_idx === 0) return line.slice(); - - return line.map((p, idx) => p ^ arr[l_idx - 1][idx]); - }); -}; - - -// Convert array with uint16 data to buffer -module.exports.bFromA16 = function bFromA16(arr) { - const buf = Buffer.alloc(arr.length * 2); - - for (let i = 0; i < arr.length; i++) buf.writeUInt16LE(arr[i], i * 2); - - return buf; -}; - -// Convert array with uint32 data to buffer -module.exports.bFromA32 = function bFromA32(arr) { - const buf = Buffer.alloc(arr.length * 4); - - for (let i = 0; i < arr.length; i++) buf.writeUInt32LE(arr[i], i * 4); - - return buf; -}; - - -function chunk(arr, size) { - const result = []; - for (let i = 0; i < arr.length; i += size) { - result.push(arr.slice(i, i + size)); - } - return result; -} - -// Dump long array to multiline format with X columns and Y indent -module.exports.long_dump = function long_dump(arr, options = {}) { - const defaults = { - col: 8, - indent: 4, - hex: false - }; - - let opts = Object.assign({}, defaults, options); - let indent = ' '.repeat(opts.indent); - - return chunk(Array.from(arr), opts.col) - .map(l => l.map(v => (opts.hex ? `0x${v.toString(16)}` : v.toString()))) - .map(l => `${indent}${l.join(', ')}`) - .join(',\n'); -}; - -// stable sort by pick() result -module.exports.sort_by = function sort_by(arr, pick) { - return arr - .map((el, idx) => ({ el, idx })) - .sort((a, b) => (pick(a.el) - pick(b.el)) || (a.idx - b.idx)) - .map(({ el }) => el); -}; - -module.exports.sum = function sum(arr) { - return arr.reduce((a, v) => a + v, 0); -}; diff --git a/node_modules/lv_font_conv/lib/writers/bin.js b/node_modules/lv_font_conv/lib/writers/bin.js deleted file mode 100644 index bb482080..00000000 --- a/node_modules/lv_font_conv/lib/writers/bin.js +++ /dev/null @@ -1,17 +0,0 @@ -// Write font in binary format -'use strict'; - - -const AppError = require('../app_error'); -const Font = require('../font/font'); - - -module.exports = function write_images(args, fontData) { - if (!args.output) throw new AppError('Output is required for "bin" writer'); - - const font = new Font(fontData, args); - - return { - [args.output]: font.toBin() - }; -}; diff --git a/node_modules/lv_font_conv/lib/writers/dump.js b/node_modules/lv_font_conv/lib/writers/dump.js deleted file mode 100644 index 150d3b99..00000000 --- a/node_modules/lv_font_conv/lib/writers/dump.js +++ /dev/null @@ -1,68 +0,0 @@ -// Write font data into png images - -'use strict'; - - -const path = require('path'); -const { PNG } = require('pngjs'); -const AppError = require('../app_error'); -const utils = require('../utils'); - -const normal_color = [ 255, 255, 255 ]; -const outside_color = [ 255, 127, 184 ]; - - -module.exports = function write_images(args, font) { - if (!args.output) throw new AppError('Output is required for "dump" writer'); - - let files = {}; - - let glyphs = font.glyphs.map(glyph => utils.set_depth(glyph, args.bpp)); - - for (let glyph of glyphs) { - let { code, advanceWidth, bbox, pixels } = glyph; - - advanceWidth = Math.round(advanceWidth); - - let minX = bbox.x; - let maxX = Math.max(bbox.x + bbox.width - 1, bbox.x); - let minY = Math.min(bbox.y, font.typoDescent); - let maxY = Math.max(bbox.y + bbox.height - 1, font.typoAscent); - - let png = new PNG({ width: maxX - minX + 1, height: maxY - minY + 1 }); - - /* eslint-disable max-depth */ - for (let pos = 0, y = maxY; y >= minY; y--) { - for (let x = minX; x <= maxX; x++) { - let value = 0; - - if (x >= bbox.x && x < bbox.x + bbox.width && y >= bbox.y && y < bbox.y + bbox.height) { - value = pixels[bbox.height - (y - bbox.y) - 1][x - bbox.x]; - } - - let r, g, b; - - if (x < 0 || x >= advanceWidth || y < font.typoDescent || y > font.typoAscent) { - [ r, g, b ] = outside_color; - } else { - [ r, g, b ] = normal_color; - } - - png.data[pos++] = (255 - value) * r / 255; - png.data[pos++] = (255 - value) * g / 255; - png.data[pos++] = (255 - value) * b / 255; - png.data[pos++] = 255; - } - } - - - files[path.join(args.output, `${code.toString(16)}.png`)] = PNG.sync.write(png); - } - - files[path.join(args.output, 'font_info.json')] = JSON.stringify( - font, - (k, v) => (k === 'pixels' && !args.full_info ? undefined : v), - 2); - - return files; -}; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/index.js b/node_modules/lv_font_conv/lib/writers/lvgl/index.js deleted file mode 100644 index b592104f..00000000 --- a/node_modules/lv_font_conv/lib/writers/lvgl/index.js +++ /dev/null @@ -1,17 +0,0 @@ -// Write font in lvgl format -'use strict'; - - -const AppError = require('../../app_error'); -const Font = require('./lv_font'); - - -module.exports = function write_images(args, fontData) { - if (!args.output) throw new AppError('Output is required for "lvgl" writer'); - - const font = new Font(fontData, args); - - return { - [args.output]: font.toLVGL() - }; -}; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/lv_font.js b/node_modules/lv_font_conv/lib/writers/lvgl/lv_font.js deleted file mode 100644 index e3ad9c72..00000000 --- a/node_modules/lv_font_conv/lib/writers/lvgl/lv_font.js +++ /dev/null @@ -1,98 +0,0 @@ -'use strict'; - - -const path = require('path'); - -const Font = require('../../font/font'); -const Head = require('./lv_table_head'); -const Cmap = require('./lv_table_cmap'); -const Glyf = require('./lv_table_glyf'); -const Kern = require('./lv_table_kern'); -const AppError = require('../../app_error'); - - -class LvFont extends Font { - constructor(fontData, options) { - super(fontData, options); - - const ext = path.extname(options.output); - this.font_name = path.basename(options.output, ext); - - if (options.bpp === 3 & options.no_compress) { - throw new AppError('LittlevGL supports "--bpp 3" with compression only'); - } - } - - init_tables() { - this.head = new Head(this); - this.glyf = new Glyf(this); - this.cmap = new Cmap(this); - this.kern = new Kern(this); - } - - large_format_guard() { - let guard_required = false; - let glyphs_bin_size = 0; - - this.glyf.lv_data.forEach(d => { - glyphs_bin_size += d.bin.length; - - if (d.glyph.bbox.width > 255 || - d.glyph.bbox.height > 255 || - Math.abs(d.glyph.bbox.x) > 127 || - Math.abs(d.glyph.bbox.y) > 127 || - Math.round(d.glyph.advanceWidth * 16) > 4096) { - guard_required = true; - } - }); - - if (glyphs_bin_size > 1024 * 1024) guard_required = true; - - if (!guard_required) return ''; - - return ` -#if (LV_FONT_FMT_TXT_LARGE == 0) -# error "Too large font or glyphs in ${this.font_name.toUpperCase()}. Enable LV_FONT_FMT_TXT_LARGE in lv_conf.h") -#endif -`.trimLeft(); - } - - toLVGL() { - let guard_name = this.font_name.toUpperCase(); - - return `/******************************************************************************* - * Size: ${this.src.size} px - * Bpp: ${this.opts.bpp} - * Opts: ${process.argv.slice(2).join(' ')} - ******************************************************************************/ - -#ifdef LV_LVGL_H_INCLUDE_SIMPLE -#include "lvgl.h" -#else -#include "${this.opts.lv_include || 'lvgl/lvgl.h'}" -#endif - -#ifndef ${guard_name} -#define ${guard_name} 1 -#endif - -#if ${guard_name} - -${this.glyf.toLVGL()} - -${this.cmap.toLVGL()} - -${this.kern.toLVGL()} - -${this.head.toLVGL()} - -${this.large_format_guard()} - -#endif /*#if ${guard_name}*/ - -`; - } -} - - -module.exports = LvFont; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_cmap.js b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_cmap.js deleted file mode 100644 index 56c1e6aa..00000000 --- a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_cmap.js +++ /dev/null @@ -1,125 +0,0 @@ -'use strict'; - - -const u = require('../../utils'); -const build_subtables = require('../../font/cmap_build_subtables'); -const Cmap = require('../../font/table_cmap'); - - -class LvCmap extends Cmap { - constructor(font) { - super(font); - - this.lv_compiled = false; - this.lv_subtables = []; - } - - lv_format2enum(name) { - switch (name) { - case 'format0_tiny': return 'LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY'; - case 'format0': return 'LV_FONT_FMT_TXT_CMAP_FORMAT0_FULL'; - case 'sparse_tiny': return 'LV_FONT_FMT_TXT_CMAP_SPARSE_TINY'; - case 'sparse': return 'LV_FONT_FMT_TXT_CMAP_SPARSE_FULL'; - default: throw new Error('Unknown subtable format'); - } - } - - lv_compile() { - if (this.lv_compiled) return; - this.lv_compiled = true; - - const f = this.font; - - let subtables_plan = build_subtables(f.src.glyphs.map(g => g.code)); - let idx = 0; - - for (let [ format, codepoints ] of subtables_plan) { - let g = this.glyphByCode(codepoints[0]); - let start_glyph_id = f.glyph_id[g.code]; - let min_code = codepoints[0]; - let max_code = codepoints[codepoints.length - 1]; - - let has_charcodes = false; - let has_ids = false; - let defs = ''; - let entries_count = 0; - - if (format === 'format0_tiny') { - // use default empty values - } else if (format === 'format0') { - has_ids = true; - let d = this.collect_format0_data(min_code, max_code, start_glyph_id); - entries_count = d.length; - - defs = ` -static const uint8_t glyph_id_ofs_list_${idx}[] = { -${u.long_dump(d)} -}; -`.trim(); - - } else if (format === 'sparse_tiny') { - has_charcodes = true; - let d = this.collect_sparse_data(codepoints, start_glyph_id); - entries_count = d.codes.length; - - defs = ` -static const uint16_t unicode_list_${idx}[] = { -${u.long_dump(d.codes, { hex: true })} -}; -`.trim(); - - } else { // assume format === 'sparse' - has_charcodes = true; - has_ids = true; - let d = this.collect_sparse_data(codepoints, start_glyph_id); - entries_count = d.codes.length; - - defs = ` -static const uint16_t unicode_list_${idx}[] = { -${u.long_dump(d.codes, { hex: true })} -}; -static const uint16_t glyph_id_ofs_list_${idx}[] = { -${u.long_dump(d.ids)} -}; -`.trim(); - } - - const u_list = has_charcodes ? `unicode_list_${idx}` : 'NULL'; - const id_list = has_ids ? `glyph_id_ofs_list_${idx}` : 'NULL'; - - /* eslint-disable max-len */ - const head = ` { - .range_start = ${min_code}, .range_length = ${max_code - min_code + 1}, .glyph_id_start = ${start_glyph_id}, - .unicode_list = ${u_list}, .glyph_id_ofs_list = ${id_list}, .list_length = ${entries_count}, .type = ${this.lv_format2enum(format)} - }`; - - this.lv_subtables.push({ - defs, - head - }); - - idx++; - } - } - - toLVGL() { - this.lv_compile(); - - return ` -/*--------------------- - * CHARACTER MAPPING - *--------------------*/ - -${this.lv_subtables.map(d => d.defs).filter(Boolean).join('\n\n')} - -/*Collect the unicode lists and glyph_id offsets*/ -static const lv_font_fmt_txt_cmap_t cmaps[] = -{ -${this.lv_subtables.map(d => d.head).join(',\n')} -}; - `.trim(); - } -} - - -module.exports = LvCmap; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_glyf.js b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_glyf.js deleted file mode 100644 index 3f14851f..00000000 --- a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_glyf.js +++ /dev/null @@ -1,121 +0,0 @@ -'use strict'; - - -const { BitStream } = require('bit-buffer'); -const u = require('../../utils'); -const Glyf = require('../../font/table_glyf'); - - -class LvGlyf extends Glyf { - constructor(font) { - super(font); - - this.lv_data = []; - this.lv_compiled = false; - } - - lv_bitmap(glyph) { - const buf = Buffer.alloc(100 + glyph.bbox.width * glyph.bbox.height * 4); - const bs = new BitStream(buf); - bs.bigEndian = true; - - const pixels = this.font.glyf.pixelsToBpp(glyph.pixels); - - this.font.glyf.storePixels(bs, pixels); - - const glyph_bitmap = Buffer.alloc(bs.byteIndex); - buf.copy(glyph_bitmap, 0, 0, bs.byteIndex); - - return glyph_bitmap; - } - - lv_compile() { - if (this.lv_compiled) return; - - this.lv_compiled = true; - - const f = this.font; - this.lv_data = []; - let offset = 0; - - f.src.glyphs.forEach(g => { - const id = f.glyph_id[g.code]; - const bin = this.lv_bitmap(g); - this.lv_data[id] = { - bin, - offset, - glyph: g - }; - offset += bin.length; - }); - } - - to_lv_bitmaps() { - this.lv_compile(); - - let result = []; - this.lv_data.forEach((d, idx) => { - if (idx === 0) return; - const code_hex = d.glyph.code.toString(16).toUpperCase(); - const code_str = JSON.stringify(String.fromCodePoint(d.glyph.code)); - - let txt = ` /* U+${code_hex.padStart(4, '0')} ${code_str} */ -${u.long_dump(d.bin, { hex: true })}`; - - if (idx < this.lv_data.length - 1) { - // skip comma for zero data - txt += d.bin.length ? ',\n\n' : '\n'; - } - - result.push(txt); - }); - - return result.join(''); - } - - to_lv_glyph_dsc() { - this.lv_compile(); - - /* eslint-disable max-len */ - - let result = [ ' {.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */' ]; - - this.lv_data.forEach(d => { - const idx = d.offset, - adv_w = Math.round(d.glyph.advanceWidth * 16), - h = d.glyph.bbox.height, - w = d.glyph.bbox.width, - x = d.glyph.bbox.x, - y = d.glyph.bbox.y; - result.push(` {.bitmap_index = ${idx}, .adv_w = ${adv_w}, .box_w = ${w}, .box_h = ${h}, .ofs_x = ${x}, .ofs_y = ${y}}`); - }); - - return result.join(',\n'); - } - - - toLVGL() { - return ` -/*----------------- - * BITMAPS - *----------------*/ - -/*Store the image of the glyphs*/ -static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = { -${this.to_lv_bitmaps()} -}; - - -/*--------------------- - * GLYPH DESCRIPTION - *--------------------*/ - -static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = { -${this.to_lv_glyph_dsc()} -}; -`.trim(); - } -} - - -module.exports = LvGlyf; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_head.js b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_head.js deleted file mode 100644 index f5c0173e..00000000 --- a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_head.js +++ /dev/null @@ -1,99 +0,0 @@ -'use strict'; - - -const Head = require('../../font/table_head'); - - -class LvHead extends Head { - constructor(font) { - super(font); - } - - kern_ref() { - const f = this.font; - - if (!f.hasKerning()) { - return { - scale: '0', - dsc: 'NULL', - classes: '0' - }; - } - - if (!f.kern.should_use_format3()) { - return { - scale: `${Math.round(f.kerningScale * 16)}`, - dsc: '&kern_pairs', - classes: '0' - }; - } - - return { - scale: `${Math.round(f.kerningScale * 16)}`, - dsc: '&kern_classes', - classes: '1' - }; - } - - toLVGL() { - const f = this.font; - const kern = this.kern_ref(); - const subpixels = (f.subpixels_mode === 0) ? 'LV_FONT_SUBPX_NONE' : - (f.subpixels_mode === 1) ? 'LV_FONT_SUBPX_HOR' : 'LV_FONT_SUBPX_VER'; - - return ` -/*-------------------- - * ALL CUSTOM DATA - *--------------------*/ - -#if LV_VERSION_CHECK(8, 0, 0) -/*Store all the custom data of the font*/ -static lv_font_fmt_txt_glyph_cache_t cache; -static const lv_font_fmt_txt_dsc_t font_dsc = { -#else -static lv_font_fmt_txt_dsc_t font_dsc = { -#endif - .glyph_bitmap = glyph_bitmap, - .glyph_dsc = glyph_dsc, - .cmaps = cmaps, - .kern_dsc = ${kern.dsc}, - .kern_scale = ${kern.scale}, - .cmap_num = ${f.cmap.toBin().readUInt32LE(8)}, - .bpp = ${f.opts.bpp}, - .kern_classes = ${kern.classes}, - .bitmap_format = ${f.glyf.getCompressionCode()}, -#if LV_VERSION_CHECK(8, 0, 0) - .cache = &cache -#endif -}; - - -/*----------------- - * PUBLIC FONT - *----------------*/ - -/*Initialize a public general font descriptor*/ -#if LV_VERSION_CHECK(8, 0, 0) -const lv_font_t ${f.font_name} = { -#else -lv_font_t ${f.font_name} = { -#endif - .get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/ - .get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/ - .line_height = ${f.src.ascent - f.src.descent}, /*The maximum line height required by the font*/ - .base_line = ${-f.src.descent}, /*Baseline measured from the bottom of the line*/ -#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0) - .subpx = ${subpixels}, -#endif -#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8 - .underline_position = ${f.src.underlinePosition}, - .underline_thickness = ${f.src.underlineThickness}, -#endif - .dsc = &font_dsc /*The custom font data. Will be accessed by \`get_glyph_bitmap/dsc\` */ -}; -`.trim(); - } -} - - -module.exports = LvHead; diff --git a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_kern.js b/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_kern.js deleted file mode 100644 index e50ba427..00000000 --- a/node_modules/lv_font_conv/lib/writers/lvgl/lv_table_kern.js +++ /dev/null @@ -1,121 +0,0 @@ -'use strict'; - - -const u = require('../../utils'); -const Kern = require('../../font/table_kern'); - - -class LvKern extends Kern { - constructor(font) { - super(font); - } - - to_lv_format0() { - const f = this.font; - let kern_pairs = this.collect_format0_data(); - - return ` -/*----------------- - * KERNING - *----------------*/ - - -/*Pair left and right glyphs for kerning*/ -static const ${f.glyphIdFormat ? 'uint16_t' : 'uint8_t'} kern_pair_glyph_ids[] = -{ -${kern_pairs.map(pair => ` ${pair[0]}, ${pair[1]}`).join(',\n')} -}; - -/* Kerning between the respective left and right glyphs - * 4.4 format which needs to scaled with \`kern_scale\`*/ -static const int8_t kern_pair_values[] = -{ -${u.long_dump(kern_pairs.map(pair => f.kernToFP(pair[2])))} -}; - -/*Collect the kern pair's data in one place*/ -static const lv_font_fmt_txt_kern_pair_t kern_pairs = -{ - .glyph_ids = kern_pair_glyph_ids, - .values = kern_pair_values, - .pair_cnt = ${kern_pairs.length}, - .glyph_ids_size = ${f.glyphIdFormat} -}; - - -`.trim(); - } - - to_lv_format3() { - const f = this.font; - const { - left_classes, - right_classes, - left_mapping, - right_mapping, - values - } = this.collect_format3_data(); - - return ` -/*----------------- - * KERNING - *----------------*/ - - -/*Map glyph_ids to kern left classes*/ -static const uint8_t kern_left_class_mapping[] = -{ -${u.long_dump(left_mapping)} -}; - -/*Map glyph_ids to kern right classes*/ -static const uint8_t kern_right_class_mapping[] = -{ -${u.long_dump(right_mapping)} -}; - -/*Kern values between classes*/ -static const int8_t kern_class_values[] = -{ -${u.long_dump(values.map(v => f.kernToFP(v)))} -}; - - -/*Collect the kern class' data in one place*/ -static const lv_font_fmt_txt_kern_classes_t kern_classes = -{ - .class_pair_values = kern_class_values, - .left_class_mapping = kern_left_class_mapping, - .right_class_mapping = kern_right_class_mapping, - .left_class_cnt = ${left_classes}, - .right_class_cnt = ${right_classes}, -}; - - -`.trim(); - } - - toLVGL() { - const f = this.font; - - if (!f.hasKerning()) return ''; - - /* eslint-disable no-console */ - - if (f.kern.should_use_format3()) { - if (f.kern.format3_forced) { - let diff = this.create_format3_data().length - this.create_format0_data().length; - console.log(`Forced faster kerning format (via classes). Size increase is ${diff} bytes.`); - } - return this.to_lv_format3(); - } - - if (this.font.opts.fast_kerning) { - console.log('Forced faster kerning format (via classes), but data exceeds it\'s limits. Continue use pairs.'); - } - return this.to_lv_format0(); - } -} - - -module.exports = LvKern; diff --git a/node_modules/lv_font_conv/lv_font_conv.js b/node_modules/lv_font_conv/lv_font_conv.js deleted file mode 100755 index f76cbde4..00000000 --- a/node_modules/lv_font_conv/lv_font_conv.js +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const AppError = require('./lib/app_error'); - -require('./lib/cli').run(process.argv.slice(2)).catch(err => { - /*eslint-disable no-console*/ - if (err instanceof AppError) { - // Try to beautify normal errors - console.error(err.message.trim()); - } else { - // Print crashes - console.error(err.stack); - } - process.exit(1); -}); diff --git a/node_modules/lv_font_conv/node_modules/argparse/CHANGELOG.md b/node_modules/lv_font_conv/node_modules/argparse/CHANGELOG.md deleted file mode 100644 index dc39ed69..00000000 --- a/node_modules/lv_font_conv/node_modules/argparse/CHANGELOG.md +++ /dev/null @@ -1,216 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - - -## [2.0.1] - 2020-08-29 -### Fixed -- Fix issue with `process.argv` when used with interpreters (`coffee`, `ts-node`, etc.), #150. - - -## [2.0.0] - 2020-08-14 -### Changed -- Full rewrite. Now port from python 3.9.0 & more precise following. - See [doc](./doc) for difference and migration info. -- node.js 10+ required -- Removed most of local docs in favour of original ones. - - -## [1.0.10] - 2018-02-15 -### Fixed -- Use .concat instead of + for arrays, #122. - - -## [1.0.9] - 2016-09-29 -### Changed -- Rerelease after 1.0.8 - deps cleanup. - - -## [1.0.8] - 2016-09-29 -### Changed -- Maintenance (deps bump, fix node 6.5+ tests, coverage report). - - -## [1.0.7] - 2016-03-17 -### Changed -- Teach `addArgument` to accept string arg names. #97, @tomxtobin. - - -## [1.0.6] - 2016-02-06 -### Changed -- Maintenance: moved to eslint & updated CS. - - -## [1.0.5] - 2016-02-05 -### Changed -- Removed lodash dependency to significantly reduce install size. - Thanks to @mourner. - - -## [1.0.4] - 2016-01-17 -### Changed -- Maintenance: lodash update to 4.0.0. - - -## [1.0.3] - 2015-10-27 -### Fixed -- Fix parse `=` in args: `--examplepath="C:\myfolder\env=x64"`. #84, @CatWithApple. - - -## [1.0.2] - 2015-03-22 -### Changed -- Relaxed lodash version dependency. - - -## [1.0.1] - 2015-02-20 -### Changed -- Changed dependencies to be compatible with ancient nodejs. - - -## [1.0.0] - 2015-02-19 -### Changed -- Maintenance release. -- Replaced `underscore` with `lodash`. -- Bumped version to 1.0.0 to better reflect semver meaning. -- HISTORY.md -> CHANGELOG.md - - -## [0.1.16] - 2013-12-01 -### Changed -- Maintenance release. Updated dependencies and docs. - - -## [0.1.15] - 2013-05-13 -### Fixed -- Fixed #55, @trebor89 - - -## [0.1.14] - 2013-05-12 -### Fixed -- Fixed #62, @maxtaco - - -## [0.1.13] - 2013-04-08 -### Changed -- Added `.npmignore` to reduce package size - - -## [0.1.12] - 2013-02-10 -### Fixed -- Fixed conflictHandler (#46), @hpaulj - - -## [0.1.11] - 2013-02-07 -### Added -- Added 70+ tests (ported from python), @hpaulj -- Added conflictHandler, @applepicke -- Added fromfilePrefixChar, @hpaulj - -### Fixed -- Multiple bugfixes, @hpaulj - - -## [0.1.10] - 2012-12-30 -### Added -- Added [mutual exclusion](http://docs.python.org/dev/library/argparse.html#mutual-exclusion) - support, thanks to @hpaulj - -### Fixed -- Fixed options check for `storeConst` & `appendConst` actions, thanks to @hpaulj - - -## [0.1.9] - 2012-12-27 -### Fixed -- Fixed option dest interferens with other options (issue #23), thanks to @hpaulj -- Fixed default value behavior with `*` positionals, thanks to @hpaulj -- Improve `getDefault()` behavior, thanks to @hpaulj -- Improve negative argument parsing, thanks to @hpaulj - - -## [0.1.8] - 2012-12-01 -### Fixed -- Fixed parser parents (issue #19), thanks to @hpaulj -- Fixed negative argument parse (issue #20), thanks to @hpaulj - - -## [0.1.7] - 2012-10-14 -### Fixed -- Fixed 'choices' argument parse (issue #16) -- Fixed stderr output (issue #15) - - -## [0.1.6] - 2012-09-09 -### Fixed -- Fixed check for conflict of options (thanks to @tomxtobin) - - -## [0.1.5] - 2012-09-03 -### Fixed -- Fix parser #setDefaults method (thanks to @tomxtobin) - - -## [0.1.4] - 2012-07-30 -### Fixed -- Fixed pseudo-argument support (thanks to @CGamesPlay) -- Fixed addHelp default (should be true), if not set (thanks to @benblank) - - -## [0.1.3] - 2012-06-27 -### Fixed -- Fixed formatter api name: Formatter -> HelpFormatter - - -## [0.1.2] - 2012-05-29 -### Fixed -- Removed excess whitespace in help -- Fixed error reporting, when parcer with subcommands - called with empty arguments - -### Added -- Added basic tests - - -## [0.1.1] - 2012-05-23 -### Fixed -- Fixed line wrapping in help formatter -- Added better error reporting on invalid arguments - - -## [0.1.0] - 2012-05-16 -### Added -- First release. - - -[2.0.1]: https://github.com/nodeca/argparse/compare/2.0.0...2.0.1 -[2.0.0]: https://github.com/nodeca/argparse/compare/1.0.10...2.0.0 -[1.0.10]: https://github.com/nodeca/argparse/compare/1.0.9...1.0.10 -[1.0.9]: https://github.com/nodeca/argparse/compare/1.0.8...1.0.9 -[1.0.8]: https://github.com/nodeca/argparse/compare/1.0.7...1.0.8 -[1.0.7]: https://github.com/nodeca/argparse/compare/1.0.6...1.0.7 -[1.0.6]: https://github.com/nodeca/argparse/compare/1.0.5...1.0.6 -[1.0.5]: https://github.com/nodeca/argparse/compare/1.0.4...1.0.5 -[1.0.4]: https://github.com/nodeca/argparse/compare/1.0.3...1.0.4 -[1.0.3]: https://github.com/nodeca/argparse/compare/1.0.2...1.0.3 -[1.0.2]: https://github.com/nodeca/argparse/compare/1.0.1...1.0.2 -[1.0.1]: https://github.com/nodeca/argparse/compare/1.0.0...1.0.1 -[1.0.0]: https://github.com/nodeca/argparse/compare/0.1.16...1.0.0 -[0.1.16]: https://github.com/nodeca/argparse/compare/0.1.15...0.1.16 -[0.1.15]: https://github.com/nodeca/argparse/compare/0.1.14...0.1.15 -[0.1.14]: https://github.com/nodeca/argparse/compare/0.1.13...0.1.14 -[0.1.13]: https://github.com/nodeca/argparse/compare/0.1.12...0.1.13 -[0.1.12]: https://github.com/nodeca/argparse/compare/0.1.11...0.1.12 -[0.1.11]: https://github.com/nodeca/argparse/compare/0.1.10...0.1.11 -[0.1.10]: https://github.com/nodeca/argparse/compare/0.1.9...0.1.10 -[0.1.9]: https://github.com/nodeca/argparse/compare/0.1.8...0.1.9 -[0.1.8]: https://github.com/nodeca/argparse/compare/0.1.7...0.1.8 -[0.1.7]: https://github.com/nodeca/argparse/compare/0.1.6...0.1.7 -[0.1.6]: https://github.com/nodeca/argparse/compare/0.1.5...0.1.6 -[0.1.5]: https://github.com/nodeca/argparse/compare/0.1.4...0.1.5 -[0.1.4]: https://github.com/nodeca/argparse/compare/0.1.3...0.1.4 -[0.1.3]: https://github.com/nodeca/argparse/compare/0.1.2...0.1.3 -[0.1.2]: https://github.com/nodeca/argparse/compare/0.1.1...0.1.2 -[0.1.1]: https://github.com/nodeca/argparse/compare/0.1.0...0.1.1 -[0.1.0]: https://github.com/nodeca/argparse/releases/tag/0.1.0 diff --git a/node_modules/lv_font_conv/node_modules/argparse/LICENSE b/node_modules/lv_font_conv/node_modules/argparse/LICENSE deleted file mode 100644 index 66a3ac80..00000000 --- a/node_modules/lv_font_conv/node_modules/argparse/LICENSE +++ /dev/null @@ -1,254 +0,0 @@ -A. HISTORY OF THE SOFTWARE -========================== - -Python was created in the early 1990s by Guido van Rossum at Stichting -Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands -as a successor of a language called ABC. Guido remains Python's -principal author, although it includes many contributions from others. - -In 1995, Guido continued his work on Python at the Corporation for -National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) -in Reston, Virginia where he released several versions of the -software. - -In May 2000, Guido and the Python core development team moved to -BeOpen.com to form the BeOpen PythonLabs team. In October of the same -year, the PythonLabs team moved to Digital Creations, which became -Zope Corporation. In 2001, the Python Software Foundation (PSF, see -https://www.python.org/psf/) was formed, a non-profit organization -created specifically to own Python-related Intellectual Property. -Zope Corporation was a sponsoring member of the PSF. - -All Python releases are Open Source (see http://www.opensource.org for -the Open Source Definition). Historically, most, but not all, Python -releases have also been GPL-compatible; the table below summarizes -the various releases. - - Release Derived Year Owner GPL- - from compatible? (1) - - 0.9.0 thru 1.2 1991-1995 CWI yes - 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes - 1.6 1.5.2 2000 CNRI no - 2.0 1.6 2000 BeOpen.com no - 1.6.1 1.6 2001 CNRI yes (2) - 2.1 2.0+1.6.1 2001 PSF no - 2.0.1 2.0+1.6.1 2001 PSF yes - 2.1.1 2.1+2.0.1 2001 PSF yes - 2.1.2 2.1.1 2002 PSF yes - 2.1.3 2.1.2 2002 PSF yes - 2.2 and above 2.1.1 2001-now PSF yes - -Footnotes: - -(1) GPL-compatible doesn't mean that we're distributing Python under - the GPL. All Python licenses, unlike the GPL, let you distribute - a modified version without making your changes open source. The - GPL-compatible licenses make it possible to combine Python with - other software that is released under the GPL; the others don't. - -(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, - because its license has a choice of law clause. According to - CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 - is "not incompatible" with the GPL. - -Thanks to the many outside volunteers who have worked under Guido's -direction to make these releases possible. - - -B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON -=============================================================== - -PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 --------------------------------------------- - -1. This LICENSE AGREEMENT is between the Python Software Foundation -("PSF"), and the Individual or Organization ("Licensee") accessing and -otherwise using this software ("Python") in source or binary form and -its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, PSF hereby -grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, -analyze, test, perform and/or display publicly, prepare derivative works, -distribute, and otherwise use Python alone or in any derivative version, -provided, however, that PSF's License Agreement and PSF's notice of copyright, -i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation; -All Rights Reserved" are retained in Python alone or in any derivative version -prepared by Licensee. - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python. - -4. PSF is making Python available to Licensee on an "AS IS" -basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any -relationship of agency, partnership, or joint venture between PSF and -Licensee. This License Agreement does not grant permission to use PSF -trademarks or trade name in a trademark sense to endorse or promote -products or services of Licensee, or any third party. - -8. By copying, installing or otherwise using Python, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - - -BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 -------------------------------------------- - -BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 - -1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an -office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the -Individual or Organization ("Licensee") accessing and otherwise using -this software in source or binary form and its associated -documentation ("the Software"). - -2. Subject to the terms and conditions of this BeOpen Python License -Agreement, BeOpen hereby grants Licensee a non-exclusive, -royalty-free, world-wide license to reproduce, analyze, test, perform -and/or display publicly, prepare derivative works, distribute, and -otherwise use the Software alone or in any derivative version, -provided, however, that the BeOpen Python License is retained in the -Software, alone or in any derivative version prepared by Licensee. - -3. BeOpen is making the Software available to Licensee on an "AS IS" -basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE -SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS -AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY -DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -5. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -6. This License Agreement shall be governed by and interpreted in all -respects by the law of the State of California, excluding conflict of -law provisions. Nothing in this License Agreement shall be deemed to -create any relationship of agency, partnership, or joint venture -between BeOpen and Licensee. This License Agreement does not grant -permission to use BeOpen trademarks or trade names in a trademark -sense to endorse or promote products or services of Licensee, or any -third party. As an exception, the "BeOpen Python" logos available at -http://www.pythonlabs.com/logos.html may be used according to the -permissions granted on that web page. - -7. By copying, installing or otherwise using the software, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - - -CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 ---------------------------------------- - -1. This LICENSE AGREEMENT is between the Corporation for National -Research Initiatives, having an office at 1895 Preston White Drive, -Reston, VA 20191 ("CNRI"), and the Individual or Organization -("Licensee") accessing and otherwise using Python 1.6.1 software in -source or binary form and its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, CNRI -hereby grants Licensee a nonexclusive, royalty-free, world-wide -license to reproduce, analyze, test, perform and/or display publicly, -prepare derivative works, distribute, and otherwise use Python 1.6.1 -alone or in any derivative version, provided, however, that CNRI's -License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) -1995-2001 Corporation for National Research Initiatives; All Rights -Reserved" are retained in Python 1.6.1 alone or in any derivative -version prepared by Licensee. Alternately, in lieu of CNRI's License -Agreement, Licensee may substitute the following text (omitting the -quotes): "Python 1.6.1 is made available subject to the terms and -conditions in CNRI's License Agreement. This Agreement together with -Python 1.6.1 may be located on the Internet using the following -unique, persistent identifier (known as a handle): 1895.22/1013. This -Agreement may also be obtained from a proxy server on the Internet -using the following URL: http://hdl.handle.net/1895.22/1013". - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python 1.6.1 or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python 1.6.1. - -4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" -basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. This License Agreement shall be governed by the federal -intellectual property law of the United States, including without -limitation the federal copyright law, and, to the extent such -U.S. federal law does not apply, by the law of the Commonwealth of -Virginia, excluding Virginia's conflict of law provisions. -Notwithstanding the foregoing, with regard to derivative works based -on Python 1.6.1 that incorporate non-separable material that was -previously distributed under the GNU General Public License (GPL), the -law of the Commonwealth of Virginia shall govern this License -Agreement only as to issues arising under or with respect to -Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this -License Agreement shall be deemed to create any relationship of -agency, partnership, or joint venture between CNRI and Licensee. This -License Agreement does not grant permission to use CNRI trademarks or -trade name in a trademark sense to endorse or promote products or -services of Licensee, or any third party. - -8. By clicking on the "ACCEPT" button where indicated, or by copying, -installing or otherwise using Python 1.6.1, Licensee agrees to be -bound by the terms and conditions of this License Agreement. - - ACCEPT - - -CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 --------------------------------------------------- - -Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, -The Netherlands. All rights reserved. - -Permission to use, copy, modify, and distribute this software and its -documentation for any purpose and without fee is hereby granted, -provided that the above copyright notice appear in all copies and that -both that copyright notice and this permission notice appear in -supporting documentation, and that the name of Stichting Mathematisch -Centrum or CWI not be used in advertising or publicity pertaining to -distribution of the software without specific, written prior -permission. - -STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO -THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE -FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT -OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/argparse/README.md b/node_modules/lv_font_conv/node_modules/argparse/README.md deleted file mode 100644 index 550b5c9b..00000000 --- a/node_modules/lv_font_conv/node_modules/argparse/README.md +++ /dev/null @@ -1,84 +0,0 @@ -argparse -======== - -[![Build Status](https://secure.travis-ci.org/nodeca/argparse.svg?branch=master)](http://travis-ci.org/nodeca/argparse) -[![NPM version](https://img.shields.io/npm/v/argparse.svg)](https://www.npmjs.org/package/argparse) - -CLI arguments parser for node.js, with [sub-commands](https://docs.python.org/3.9/library/argparse.html#sub-commands) support. Port of python's [argparse](http://docs.python.org/dev/library/argparse.html) (version [3.9.0](https://github.com/python/cpython/blob/v3.9.0rc1/Lib/argparse.py)). - -**Difference with original.** - -- JS has no keyword arguments support. - - Pass options instead: `new ArgumentParser({ description: 'example', add_help: true })`. -- JS has no python's types `int`, `float`, ... - - Use string-typed names: `.add_argument('-b', { type: 'int', help: 'help' })`. -- `%r` format specifier uses `require('util').inspect()`. - -More details in [doc](./doc). - - -Example -------- - -`test.js` file: - -```javascript -#!/usr/bin/env node -'use strict'; - -const { ArgumentParser } = require('argparse'); -const { version } = require('./package.json'); - -const parser = new ArgumentParser({ - description: 'Argparse example' -}); - -parser.add_argument('-v', '--version', { action: 'version', version }); -parser.add_argument('-f', '--foo', { help: 'foo bar' }); -parser.add_argument('-b', '--bar', { help: 'bar foo' }); -parser.add_argument('--baz', { help: 'baz bar' }); - -console.dir(parser.parse_args()); -``` - -Display help: - -``` -$ ./test.js -h -usage: test.js [-h] [-v] [-f FOO] [-b BAR] [--baz BAZ] - -Argparse example - -optional arguments: - -h, --help show this help message and exit - -v, --version show program's version number and exit - -f FOO, --foo FOO foo bar - -b BAR, --bar BAR bar foo - --baz BAZ baz bar -``` - -Parse arguments: - -``` -$ ./test.js -f=3 --bar=4 --baz 5 -{ foo: '3', bar: '4', baz: '5' } -``` - - -API docs --------- - -Since this is a port with minimal divergence, there's no separate documentation. -Use original one instead, with notes about difference. - -1. [Original doc](https://docs.python.org/3.9/library/argparse.html). -2. [Original tutorial](https://docs.python.org/3.9/howto/argparse.html). -3. [Difference with python](./doc). - - -argparse for enterprise ------------------------ - -Available as part of the Tidelift Subscription - -The maintainers of argparse and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-argparse?utm_source=npm-argparse&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/node_modules/lv_font_conv/node_modules/argparse/argparse.js b/node_modules/lv_font_conv/node_modules/argparse/argparse.js deleted file mode 100644 index 2b8c8c63..00000000 --- a/node_modules/lv_font_conv/node_modules/argparse/argparse.js +++ /dev/null @@ -1,3707 +0,0 @@ -// Port of python's argparse module, version 3.9.0: -// https://github.com/python/cpython/blob/v3.9.0rc1/Lib/argparse.py - -'use strict' - -// Copyright (C) 2010-2020 Python Software Foundation. -// Copyright (C) 2020 argparse.js authors - -/* - * Command-line parsing library - * - * This module is an optparse-inspired command-line parsing library that: - * - * - handles both optional and positional arguments - * - produces highly informative usage messages - * - supports parsers that dispatch to sub-parsers - * - * The following is a simple usage example that sums integers from the - * command-line and writes the result to a file:: - * - * parser = argparse.ArgumentParser( - * description='sum the integers at the command line') - * parser.add_argument( - * 'integers', metavar='int', nargs='+', type=int, - * help='an integer to be summed') - * parser.add_argument( - * '--log', default=sys.stdout, type=argparse.FileType('w'), - * help='the file where the sum should be written') - * args = parser.parse_args() - * args.log.write('%s' % sum(args.integers)) - * args.log.close() - * - * The module contains the following public classes: - * - * - ArgumentParser -- The main entry point for command-line parsing. As the - * example above shows, the add_argument() method is used to populate - * the parser with actions for optional and positional arguments. Then - * the parse_args() method is invoked to convert the args at the - * command-line into an object with attributes. - * - * - ArgumentError -- The exception raised by ArgumentParser objects when - * there are errors with the parser's actions. Errors raised while - * parsing the command-line are caught by ArgumentParser and emitted - * as command-line messages. - * - * - FileType -- A factory for defining types of files to be created. As the - * example above shows, instances of FileType are typically passed as - * the type= argument of add_argument() calls. - * - * - Action -- The base class for parser actions. Typically actions are - * selected by passing strings like 'store_true' or 'append_const' to - * the action= argument of add_argument(). However, for greater - * customization of ArgumentParser actions, subclasses of Action may - * be defined and passed as the action= argument. - * - * - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter, - * ArgumentDefaultsHelpFormatter -- Formatter classes which - * may be passed as the formatter_class= argument to the - * ArgumentParser constructor. HelpFormatter is the default, - * RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser - * not to change the formatting for help text, and - * ArgumentDefaultsHelpFormatter adds information about argument defaults - * to the help. - * - * All other classes in this module are considered implementation details. - * (Also note that HelpFormatter and RawDescriptionHelpFormatter are only - * considered public as object names -- the API of the formatter objects is - * still considered an implementation detail.) - */ - -const SUPPRESS = '==SUPPRESS==' - -const OPTIONAL = '?' -const ZERO_OR_MORE = '*' -const ONE_OR_MORE = '+' -const PARSER = 'A...' -const REMAINDER = '...' -const _UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args' - - -// ================================== -// Utility functions used for porting -// ================================== -const assert = require('assert') -const util = require('util') -const fs = require('fs') -const sub = require('./lib/sub') -const path = require('path') -const repr = util.inspect - -function get_argv() { - // omit first argument (which is assumed to be interpreter - `node`, `coffee`, `ts-node`, etc.) - return process.argv.slice(1) -} - -function get_terminal_size() { - return { - columns: +process.env.COLUMNS || process.stdout.columns || 80 - } -} - -function hasattr(object, name) { - return Object.prototype.hasOwnProperty.call(object, name) -} - -function getattr(object, name, value) { - return hasattr(object, name) ? object[name] : value -} - -function setattr(object, name, value) { - object[name] = value -} - -function setdefault(object, name, value) { - if (!hasattr(object, name)) object[name] = value - return object[name] -} - -function delattr(object, name) { - delete object[name] -} - -function range(from, to, step=1) { - // range(10) is equivalent to range(0, 10) - if (arguments.length === 1) [ to, from ] = [ from, 0 ] - if (typeof from !== 'number' || typeof to !== 'number' || typeof step !== 'number') { - throw new TypeError('argument cannot be interpreted as an integer') - } - if (step === 0) throw new TypeError('range() arg 3 must not be zero') - - let result = [] - if (step > 0) { - for (let i = from; i < to; i += step) result.push(i) - } else { - for (let i = from; i > to; i += step) result.push(i) - } - return result -} - -function splitlines(str, keepends = false) { - let result - if (!keepends) { - result = str.split(/\r\n|[\n\r\v\f\x1c\x1d\x1e\x85\u2028\u2029]/) - } else { - result = [] - let parts = str.split(/(\r\n|[\n\r\v\f\x1c\x1d\x1e\x85\u2028\u2029])/) - for (let i = 0; i < parts.length; i += 2) { - result.push(parts[i] + (i + 1 < parts.length ? parts[i + 1] : '')) - } - } - if (!result[result.length - 1]) result.pop() - return result -} - -function _string_lstrip(string, prefix_chars) { - let idx = 0 - while (idx < string.length && prefix_chars.includes(string[idx])) idx++ - return idx ? string.slice(idx) : string -} - -function _string_split(string, sep, maxsplit) { - let result = string.split(sep) - if (result.length > maxsplit) { - result = result.slice(0, maxsplit).concat([ result.slice(maxsplit).join(sep) ]) - } - return result -} - -function _array_equal(array1, array2) { - if (array1.length !== array2.length) return false - for (let i = 0; i < array1.length; i++) { - if (array1[i] !== array2[i]) return false - } - return true -} - -function _array_remove(array, item) { - let idx = array.indexOf(item) - if (idx === -1) throw new TypeError(sub('%r not in list', item)) - array.splice(idx, 1) -} - -// normalize choices to array; -// this isn't required in python because `in` and `map` operators work with anything, -// but in js dealing with multiple types here is too clunky -function _choices_to_array(choices) { - if (choices === undefined) { - return [] - } else if (Array.isArray(choices)) { - return choices - } else if (choices !== null && typeof choices[Symbol.iterator] === 'function') { - return Array.from(choices) - } else if (typeof choices === 'object' && choices !== null) { - return Object.keys(choices) - } else { - throw new Error(sub('invalid choices value: %r', choices)) - } -} - -// decorator that allows a class to be called without new -function _callable(cls) { - let result = { // object is needed for inferred class name - [cls.name]: function (...args) { - let this_class = new.target === result || !new.target - return Reflect.construct(cls, args, this_class ? cls : new.target) - } - } - result[cls.name].prototype = cls.prototype - // fix default tag for toString, e.g. [object Action] instead of [object Object] - cls.prototype[Symbol.toStringTag] = cls.name - return result[cls.name] -} - -function _alias(object, from, to) { - try { - let name = object.constructor.name - Object.defineProperty(object, from, { - value: util.deprecate(object[to], sub('%s.%s() is renamed to %s.%s()', - name, from, name, to)), - enumerable: false - }) - } catch {} -} - -// decorator that allows snake_case class methods to be called with camelCase and vice versa -function _camelcase_alias(_class) { - for (let name of Object.getOwnPropertyNames(_class.prototype)) { - let camelcase = name.replace(/\w_[a-z]/g, s => s[0] + s[2].toUpperCase()) - if (camelcase !== name) _alias(_class.prototype, camelcase, name) - } - return _class -} - -function _to_legacy_name(key) { - key = key.replace(/\w_[a-z]/g, s => s[0] + s[2].toUpperCase()) - if (key === 'default') key = 'defaultValue' - if (key === 'const') key = 'constant' - return key -} - -function _to_new_name(key) { - if (key === 'defaultValue') key = 'default' - if (key === 'constant') key = 'const' - key = key.replace(/[A-Z]/g, c => '_' + c.toLowerCase()) - return key -} - -// parse options -let no_default = Symbol('no_default_value') -function _parse_opts(args, descriptor) { - function get_name() { - let stack = new Error().stack.split('\n') - .map(x => x.match(/^ at (.*) \(.*\)$/)) - .filter(Boolean) - .map(m => m[1]) - .map(fn => fn.match(/[^ .]*$/)[0]) - - if (stack.length && stack[0] === get_name.name) stack.shift() - if (stack.length && stack[0] === _parse_opts.name) stack.shift() - return stack.length ? stack[0] : '' - } - - args = Array.from(args) - let kwargs = {} - let result = [] - let last_opt = args.length && args[args.length - 1] - - if (typeof last_opt === 'object' && last_opt !== null && !Array.isArray(last_opt) && - (!last_opt.constructor || last_opt.constructor.name === 'Object')) { - kwargs = Object.assign({}, args.pop()) - } - - // LEGACY (v1 compatibility): camelcase - let renames = [] - for (let key of Object.keys(descriptor)) { - let old_name = _to_legacy_name(key) - if (old_name !== key && (old_name in kwargs)) { - if (key in kwargs) { - // default and defaultValue specified at the same time, happens often in old tests - //throw new TypeError(sub('%s() got multiple values for argument %r', get_name(), key)) - } else { - kwargs[key] = kwargs[old_name] - } - renames.push([ old_name, key ]) - delete kwargs[old_name] - } - } - if (renames.length) { - let name = get_name() - deprecate('camelcase_' + name, sub('%s(): following options are renamed: %s', - name, renames.map(([ a, b ]) => sub('%r -> %r', a, b)))) - } - // end - - let missing_positionals = [] - let positional_count = args.length - - for (let [ key, def ] of Object.entries(descriptor)) { - if (key[0] === '*') { - if (key.length > 0 && key[1] === '*') { - // LEGACY (v1 compatibility): camelcase - let renames = [] - for (let key of Object.keys(kwargs)) { - let new_name = _to_new_name(key) - if (new_name !== key && (key in kwargs)) { - if (new_name in kwargs) { - // default and defaultValue specified at the same time, happens often in old tests - //throw new TypeError(sub('%s() got multiple values for argument %r', get_name(), new_name)) - } else { - kwargs[new_name] = kwargs[key] - } - renames.push([ key, new_name ]) - delete kwargs[key] - } - } - if (renames.length) { - let name = get_name() - deprecate('camelcase_' + name, sub('%s(): following options are renamed: %s', - name, renames.map(([ a, b ]) => sub('%r -> %r', a, b)))) - } - // end - result.push(kwargs) - kwargs = {} - } else { - result.push(args) - args = [] - } - } else if (key in kwargs && args.length > 0) { - throw new TypeError(sub('%s() got multiple values for argument %r', get_name(), key)) - } else if (key in kwargs) { - result.push(kwargs[key]) - delete kwargs[key] - } else if (args.length > 0) { - result.push(args.shift()) - } else if (def !== no_default) { - result.push(def) - } else { - missing_positionals.push(key) - } - } - - if (Object.keys(kwargs).length) { - throw new TypeError(sub('%s() got an unexpected keyword argument %r', - get_name(), Object.keys(kwargs)[0])) - } - - if (args.length) { - let from = Object.entries(descriptor).filter(([ k, v ]) => k[0] !== '*' && v !== no_default).length - let to = Object.entries(descriptor).filter(([ k ]) => k[0] !== '*').length - throw new TypeError(sub('%s() takes %s positional argument%s but %s %s given', - get_name(), - from === to ? sub('from %s to %s', from, to) : to, - from === to && to === 1 ? '' : 's', - positional_count, - positional_count === 1 ? 'was' : 'were')) - } - - if (missing_positionals.length) { - let strs = missing_positionals.map(repr) - if (strs.length > 1) strs[strs.length - 1] = 'and ' + strs[strs.length - 1] - let str_joined = strs.join(strs.length === 2 ? '' : ', ') - throw new TypeError(sub('%s() missing %i required positional argument%s: %s', - get_name(), strs.length, strs.length === 1 ? '' : 's', str_joined)) - } - - return result -} - -let _deprecations = {} -function deprecate(id, string) { - _deprecations[id] = _deprecations[id] || util.deprecate(() => {}, string) - _deprecations[id]() -} - - -// ============================= -// Utility functions and classes -// ============================= -function _AttributeHolder(cls = Object) { - /* - * Abstract base class that provides __repr__. - * - * The __repr__ method returns a string in the format:: - * ClassName(attr=name, attr=name, ...) - * The attributes are determined either by a class-level attribute, - * '_kwarg_names', or by inspecting the instance __dict__. - */ - - return class _AttributeHolder extends cls { - [util.inspect.custom]() { - let type_name = this.constructor.name - let arg_strings = [] - let star_args = {} - for (let arg of this._get_args()) { - arg_strings.push(repr(arg)) - } - for (let [ name, value ] of this._get_kwargs()) { - if (/^[a-z_][a-z0-9_$]*$/i.test(name)) { - arg_strings.push(sub('%s=%r', name, value)) - } else { - star_args[name] = value - } - } - if (Object.keys(star_args).length) { - arg_strings.push(sub('**%s', repr(star_args))) - } - return sub('%s(%s)', type_name, arg_strings.join(', ')) - } - - toString() { - return this[util.inspect.custom]() - } - - _get_kwargs() { - return Object.entries(this) - } - - _get_args() { - return [] - } - } -} - - -function _copy_items(items) { - if (items === undefined) { - return [] - } - return items.slice(0) -} - - -// =============== -// Formatting Help -// =============== -const HelpFormatter = _camelcase_alias(_callable(class HelpFormatter { - /* - * Formatter for generating usage messages and argument help strings. - * - * Only the name of this class is considered a public API. All the methods - * provided by the class are considered an implementation detail. - */ - - constructor() { - let [ - prog, - indent_increment, - max_help_position, - width - ] = _parse_opts(arguments, { - prog: no_default, - indent_increment: 2, - max_help_position: 24, - width: undefined - }) - - // default setting for width - if (width === undefined) { - width = get_terminal_size().columns - width -= 2 - } - - this._prog = prog - this._indent_increment = indent_increment - this._max_help_position = Math.min(max_help_position, - Math.max(width - 20, indent_increment * 2)) - this._width = width - - this._current_indent = 0 - this._level = 0 - this._action_max_length = 0 - - this._root_section = this._Section(this, undefined) - this._current_section = this._root_section - - this._whitespace_matcher = /[ \t\n\r\f\v]+/g // equivalent to python /\s+/ with ASCII flag - this._long_break_matcher = /\n\n\n+/g - } - - // =============================== - // Section and indentation methods - // =============================== - _indent() { - this._current_indent += this._indent_increment - this._level += 1 - } - - _dedent() { - this._current_indent -= this._indent_increment - assert(this._current_indent >= 0, 'Indent decreased below 0.') - this._level -= 1 - } - - _add_item(func, args) { - this._current_section.items.push([ func, args ]) - } - - // ======================== - // Message building methods - // ======================== - start_section(heading) { - this._indent() - let section = this._Section(this, this._current_section, heading) - this._add_item(section.format_help.bind(section), []) - this._current_section = section - } - - end_section() { - this._current_section = this._current_section.parent - this._dedent() - } - - add_text(text) { - if (text !== SUPPRESS && text !== undefined) { - this._add_item(this._format_text.bind(this), [text]) - } - } - - add_usage(usage, actions, groups, prefix = undefined) { - if (usage !== SUPPRESS) { - let args = [ usage, actions, groups, prefix ] - this._add_item(this._format_usage.bind(this), args) - } - } - - add_argument(action) { - if (action.help !== SUPPRESS) { - - // find all invocations - let invocations = [this._format_action_invocation(action)] - for (let subaction of this._iter_indented_subactions(action)) { - invocations.push(this._format_action_invocation(subaction)) - } - - // update the maximum item length - let invocation_length = Math.max(...invocations.map(invocation => invocation.length)) - let action_length = invocation_length + this._current_indent - this._action_max_length = Math.max(this._action_max_length, - action_length) - - // add the item to the list - this._add_item(this._format_action.bind(this), [action]) - } - } - - add_arguments(actions) { - for (let action of actions) { - this.add_argument(action) - } - } - - // ======================= - // Help-formatting methods - // ======================= - format_help() { - let help = this._root_section.format_help() - if (help) { - help = help.replace(this._long_break_matcher, '\n\n') - help = help.replace(/^\n+|\n+$/g, '') + '\n' - } - return help - } - - _join_parts(part_strings) { - return part_strings.filter(part => part && part !== SUPPRESS).join('') - } - - _format_usage(usage, actions, groups, prefix) { - if (prefix === undefined) { - prefix = 'usage: ' - } - - // if usage is specified, use that - if (usage !== undefined) { - usage = sub(usage, { prog: this._prog }) - - // if no optionals or positionals are available, usage is just prog - } else if (usage === undefined && !actions.length) { - usage = sub('%(prog)s', { prog: this._prog }) - - // if optionals and positionals are available, calculate usage - } else if (usage === undefined) { - let prog = sub('%(prog)s', { prog: this._prog }) - - // split optionals from positionals - let optionals = [] - let positionals = [] - for (let action of actions) { - if (action.option_strings.length) { - optionals.push(action) - } else { - positionals.push(action) - } - } - - // build full usage string - let action_usage = this._format_actions_usage([].concat(optionals).concat(positionals), groups) - usage = [ prog, action_usage ].map(String).join(' ') - - // wrap the usage parts if it's too long - let text_width = this._width - this._current_indent - if (prefix.length + usage.length > text_width) { - - // break usage into wrappable parts - let part_regexp = /\(.*?\)+(?=\s|$)|\[.*?\]+(?=\s|$)|\S+/g - let opt_usage = this._format_actions_usage(optionals, groups) - let pos_usage = this._format_actions_usage(positionals, groups) - let opt_parts = opt_usage.match(part_regexp) || [] - let pos_parts = pos_usage.match(part_regexp) || [] - assert(opt_parts.join(' ') === opt_usage) - assert(pos_parts.join(' ') === pos_usage) - - // helper for wrapping lines - let get_lines = (parts, indent, prefix = undefined) => { - let lines = [] - let line = [] - let line_len - if (prefix !== undefined) { - line_len = prefix.length - 1 - } else { - line_len = indent.length - 1 - } - for (let part of parts) { - if (line_len + 1 + part.length > text_width && line) { - lines.push(indent + line.join(' ')) - line = [] - line_len = indent.length - 1 - } - line.push(part) - line_len += part.length + 1 - } - if (line.length) { - lines.push(indent + line.join(' ')) - } - if (prefix !== undefined) { - lines[0] = lines[0].slice(indent.length) - } - return lines - } - - let lines - - // if prog is short, follow it with optionals or positionals - if (prefix.length + prog.length <= 0.75 * text_width) { - let indent = ' '.repeat(prefix.length + prog.length + 1) - if (opt_parts.length) { - lines = get_lines([prog].concat(opt_parts), indent, prefix) - lines = lines.concat(get_lines(pos_parts, indent)) - } else if (pos_parts.length) { - lines = get_lines([prog].concat(pos_parts), indent, prefix) - } else { - lines = [prog] - } - - // if prog is long, put it on its own line - } else { - let indent = ' '.repeat(prefix.length) - let parts = [].concat(opt_parts).concat(pos_parts) - lines = get_lines(parts, indent) - if (lines.length > 1) { - lines = [] - lines = lines.concat(get_lines(opt_parts, indent)) - lines = lines.concat(get_lines(pos_parts, indent)) - } - lines = [prog].concat(lines) - } - - // join lines into usage - usage = lines.join('\n') - } - } - - // prefix with 'usage:' - return sub('%s%s\n\n', prefix, usage) - } - - _format_actions_usage(actions, groups) { - // find group indices and identify actions in groups - let group_actions = new Set() - let inserts = {} - for (let group of groups) { - let start = actions.indexOf(group._group_actions[0]) - if (start === -1) { - continue - } else { - let end = start + group._group_actions.length - if (_array_equal(actions.slice(start, end), group._group_actions)) { - for (let action of group._group_actions) { - group_actions.add(action) - } - if (!group.required) { - if (start in inserts) { - inserts[start] += ' [' - } else { - inserts[start] = '[' - } - if (end in inserts) { - inserts[end] += ']' - } else { - inserts[end] = ']' - } - } else { - if (start in inserts) { - inserts[start] += ' (' - } else { - inserts[start] = '(' - } - if (end in inserts) { - inserts[end] += ')' - } else { - inserts[end] = ')' - } - } - for (let i of range(start + 1, end)) { - inserts[i] = '|' - } - } - } - } - - // collect all actions format strings - let parts = [] - for (let [ i, action ] of Object.entries(actions)) { - - // suppressed arguments are marked with None - // remove | separators for suppressed arguments - if (action.help === SUPPRESS) { - parts.push(undefined) - if (inserts[+i] === '|') { - delete inserts[+i] - } else if (inserts[+i + 1] === '|') { - delete inserts[+i + 1] - } - - // produce all arg strings - } else if (!action.option_strings.length) { - let default_value = this._get_default_metavar_for_positional(action) - let part = this._format_args(action, default_value) - - // if it's in a group, strip the outer [] - if (group_actions.has(action)) { - if (part[0] === '[' && part[part.length - 1] === ']') { - part = part.slice(1, -1) - } - } - - // add the action string to the list - parts.push(part) - - // produce the first way to invoke the option in brackets - } else { - let option_string = action.option_strings[0] - let part - - // if the Optional doesn't take a value, format is: - // -s or --long - if (action.nargs === 0) { - part = action.format_usage() - - // if the Optional takes a value, format is: - // -s ARGS or --long ARGS - } else { - let default_value = this._get_default_metavar_for_optional(action) - let args_string = this._format_args(action, default_value) - part = sub('%s %s', option_string, args_string) - } - - // make it look optional if it's not required or in a group - if (!action.required && !group_actions.has(action)) { - part = sub('[%s]', part) - } - - // add the action string to the list - parts.push(part) - } - } - - // insert things at the necessary indices - for (let i of Object.keys(inserts).map(Number).sort((a, b) => b - a)) { - parts.splice(+i, 0, inserts[+i]) - } - - // join all the action items with spaces - let text = parts.filter(Boolean).join(' ') - - // clean up separators for mutually exclusive groups - text = text.replace(/([\[(]) /g, '$1') - text = text.replace(/ ([\])])/g, '$1') - text = text.replace(/[\[(] *[\])]/g, '') - text = text.replace(/\(([^|]*)\)/g, '$1', text) - text = text.trim() - - // return the text - return text - } - - _format_text(text) { - if (text.includes('%(prog)')) { - text = sub(text, { prog: this._prog }) - } - let text_width = Math.max(this._width - this._current_indent, 11) - let indent = ' '.repeat(this._current_indent) - return this._fill_text(text, text_width, indent) + '\n\n' - } - - _format_action(action) { - // determine the required width and the entry label - let help_position = Math.min(this._action_max_length + 2, - this._max_help_position) - let help_width = Math.max(this._width - help_position, 11) - let action_width = help_position - this._current_indent - 2 - let action_header = this._format_action_invocation(action) - let indent_first - - // no help; start on same line and add a final newline - if (!action.help) { - let tup = [ this._current_indent, '', action_header ] - action_header = sub('%*s%s\n', ...tup) - - // short action name; start on the same line and pad two spaces - } else if (action_header.length <= action_width) { - let tup = [ this._current_indent, '', action_width, action_header ] - action_header = sub('%*s%-*s ', ...tup) - indent_first = 0 - - // long action name; start on the next line - } else { - let tup = [ this._current_indent, '', action_header ] - action_header = sub('%*s%s\n', ...tup) - indent_first = help_position - } - - // collect the pieces of the action help - let parts = [action_header] - - // if there was help for the action, add lines of help text - if (action.help) { - let help_text = this._expand_help(action) - let help_lines = this._split_lines(help_text, help_width) - parts.push(sub('%*s%s\n', indent_first, '', help_lines[0])) - for (let line of help_lines.slice(1)) { - parts.push(sub('%*s%s\n', help_position, '', line)) - } - - // or add a newline if the description doesn't end with one - } else if (!action_header.endsWith('\n')) { - parts.push('\n') - } - - // if there are any sub-actions, add their help as well - for (let subaction of this._iter_indented_subactions(action)) { - parts.push(this._format_action(subaction)) - } - - // return a single string - return this._join_parts(parts) - } - - _format_action_invocation(action) { - if (!action.option_strings.length) { - let default_value = this._get_default_metavar_for_positional(action) - let metavar = this._metavar_formatter(action, default_value)(1)[0] - return metavar - - } else { - let parts = [] - - // if the Optional doesn't take a value, format is: - // -s, --long - if (action.nargs === 0) { - parts = parts.concat(action.option_strings) - - // if the Optional takes a value, format is: - // -s ARGS, --long ARGS - } else { - let default_value = this._get_default_metavar_for_optional(action) - let args_string = this._format_args(action, default_value) - for (let option_string of action.option_strings) { - parts.push(sub('%s %s', option_string, args_string)) - } - } - - return parts.join(', ') - } - } - - _metavar_formatter(action, default_metavar) { - let result - if (action.metavar !== undefined) { - result = action.metavar - } else if (action.choices !== undefined) { - let choice_strs = _choices_to_array(action.choices).map(String) - result = sub('{%s}', choice_strs.join(',')) - } else { - result = default_metavar - } - - function format(tuple_size) { - if (Array.isArray(result)) { - return result - } else { - return Array(tuple_size).fill(result) - } - } - return format - } - - _format_args(action, default_metavar) { - let get_metavar = this._metavar_formatter(action, default_metavar) - let result - if (action.nargs === undefined) { - result = sub('%s', ...get_metavar(1)) - } else if (action.nargs === OPTIONAL) { - result = sub('[%s]', ...get_metavar(1)) - } else if (action.nargs === ZERO_OR_MORE) { - let metavar = get_metavar(1) - if (metavar.length === 2) { - result = sub('[%s [%s ...]]', ...metavar) - } else { - result = sub('[%s ...]', ...metavar) - } - } else if (action.nargs === ONE_OR_MORE) { - result = sub('%s [%s ...]', ...get_metavar(2)) - } else if (action.nargs === REMAINDER) { - result = '...' - } else if (action.nargs === PARSER) { - result = sub('%s ...', ...get_metavar(1)) - } else if (action.nargs === SUPPRESS) { - result = '' - } else { - let formats - try { - formats = range(action.nargs).map(() => '%s') - } catch (err) { - throw new TypeError('invalid nargs value') - } - result = sub(formats.join(' '), ...get_metavar(action.nargs)) - } - return result - } - - _expand_help(action) { - let params = Object.assign({ prog: this._prog }, action) - for (let name of Object.keys(params)) { - if (params[name] === SUPPRESS) { - delete params[name] - } - } - for (let name of Object.keys(params)) { - if (params[name] && params[name].name) { - params[name] = params[name].name - } - } - if (params.choices !== undefined) { - let choices_str = _choices_to_array(params.choices).map(String).join(', ') - params.choices = choices_str - } - // LEGACY (v1 compatibility): camelcase - for (let key of Object.keys(params)) { - let old_name = _to_legacy_name(key) - if (old_name !== key) { - params[old_name] = params[key] - } - } - // end - return sub(this._get_help_string(action), params) - } - - * _iter_indented_subactions(action) { - if (typeof action._get_subactions === 'function') { - this._indent() - yield* action._get_subactions() - this._dedent() - } - } - - _split_lines(text, width) { - text = text.replace(this._whitespace_matcher, ' ').trim() - // The textwrap module is used only for formatting help. - // Delay its import for speeding up the common usage of argparse. - let textwrap = require('./lib/textwrap') - return textwrap.wrap(text, { width }) - } - - _fill_text(text, width, indent) { - text = text.replace(this._whitespace_matcher, ' ').trim() - let textwrap = require('./lib/textwrap') - return textwrap.fill(text, { width, - initial_indent: indent, - subsequent_indent: indent }) - } - - _get_help_string(action) { - return action.help - } - - _get_default_metavar_for_optional(action) { - return action.dest.toUpperCase() - } - - _get_default_metavar_for_positional(action) { - return action.dest - } -})) - -HelpFormatter.prototype._Section = _callable(class _Section { - - constructor(formatter, parent, heading = undefined) { - this.formatter = formatter - this.parent = parent - this.heading = heading - this.items = [] - } - - format_help() { - // format the indented section - if (this.parent !== undefined) { - this.formatter._indent() - } - let item_help = this.formatter._join_parts(this.items.map(([ func, args ]) => func.apply(null, args))) - if (this.parent !== undefined) { - this.formatter._dedent() - } - - // return nothing if the section was empty - if (!item_help) { - return '' - } - - // add the heading if the section was non-empty - let heading - if (this.heading !== SUPPRESS && this.heading !== undefined) { - let current_indent = this.formatter._current_indent - heading = sub('%*s%s:\n', current_indent, '', this.heading) - } else { - heading = '' - } - - // join the section-initial newline, the heading and the help - return this.formatter._join_parts(['\n', heading, item_help, '\n']) - } -}) - - -const RawDescriptionHelpFormatter = _camelcase_alias(_callable(class RawDescriptionHelpFormatter extends HelpFormatter { - /* - * Help message formatter which retains any formatting in descriptions. - * - * Only the name of this class is considered a public API. All the methods - * provided by the class are considered an implementation detail. - */ - - _fill_text(text, width, indent) { - return splitlines(text, true).map(line => indent + line).join('') - } -})) - - -const RawTextHelpFormatter = _camelcase_alias(_callable(class RawTextHelpFormatter extends RawDescriptionHelpFormatter { - /* - * Help message formatter which retains formatting of all help text. - * - * Only the name of this class is considered a public API. All the methods - * provided by the class are considered an implementation detail. - */ - - _split_lines(text/*, width*/) { - return splitlines(text) - } -})) - - -const ArgumentDefaultsHelpFormatter = _camelcase_alias(_callable(class ArgumentDefaultsHelpFormatter extends HelpFormatter { - /* - * Help message formatter which adds default values to argument help. - * - * Only the name of this class is considered a public API. All the methods - * provided by the class are considered an implementation detail. - */ - - _get_help_string(action) { - let help = action.help - // LEGACY (v1 compatibility): additional check for defaultValue needed - if (!action.help.includes('%(default)') && !action.help.includes('%(defaultValue)')) { - if (action.default !== SUPPRESS) { - let defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] - if (action.option_strings.length || defaulting_nargs.includes(action.nargs)) { - help += ' (default: %(default)s)' - } - } - } - return help - } -})) - - -const MetavarTypeHelpFormatter = _camelcase_alias(_callable(class MetavarTypeHelpFormatter extends HelpFormatter { - /* - * Help message formatter which uses the argument 'type' as the default - * metavar value (instead of the argument 'dest') - * - * Only the name of this class is considered a public API. All the methods - * provided by the class are considered an implementation detail. - */ - - _get_default_metavar_for_optional(action) { - return typeof action.type === 'function' ? action.type.name : action.type - } - - _get_default_metavar_for_positional(action) { - return typeof action.type === 'function' ? action.type.name : action.type - } -})) - - -// ===================== -// Options and Arguments -// ===================== -function _get_action_name(argument) { - if (argument === undefined) { - return undefined - } else if (argument.option_strings.length) { - return argument.option_strings.join('/') - } else if (![ undefined, SUPPRESS ].includes(argument.metavar)) { - return argument.metavar - } else if (![ undefined, SUPPRESS ].includes(argument.dest)) { - return argument.dest - } else { - return undefined - } -} - - -const ArgumentError = _callable(class ArgumentError extends Error { - /* - * An error from creating or using an argument (optional or positional). - * - * The string value of this exception is the message, augmented with - * information about the argument that caused it. - */ - - constructor(argument, message) { - super() - this.name = 'ArgumentError' - this._argument_name = _get_action_name(argument) - this._message = message - this.message = this.str() - } - - str() { - let format - if (this._argument_name === undefined) { - format = '%(message)s' - } else { - format = 'argument %(argument_name)s: %(message)s' - } - return sub(format, { message: this._message, - argument_name: this._argument_name }) - } -}) - - -const ArgumentTypeError = _callable(class ArgumentTypeError extends Error { - /* - * An error from trying to convert a command line string to a type. - */ - - constructor(message) { - super(message) - this.name = 'ArgumentTypeError' - } -}) - - -// ============== -// Action classes -// ============== -const Action = _camelcase_alias(_callable(class Action extends _AttributeHolder(Function) { - /* - * Information about how to convert command line strings to Python objects. - * - * Action objects are used by an ArgumentParser to represent the information - * needed to parse a single argument from one or more strings from the - * command line. The keyword arguments to the Action constructor are also - * all attributes of Action instances. - * - * Keyword Arguments: - * - * - option_strings -- A list of command-line option strings which - * should be associated with this action. - * - * - dest -- The name of the attribute to hold the created object(s) - * - * - nargs -- The number of command-line arguments that should be - * consumed. By default, one argument will be consumed and a single - * value will be produced. Other values include: - * - N (an integer) consumes N arguments (and produces a list) - * - '?' consumes zero or one arguments - * - '*' consumes zero or more arguments (and produces a list) - * - '+' consumes one or more arguments (and produces a list) - * Note that the difference between the default and nargs=1 is that - * with the default, a single value will be produced, while with - * nargs=1, a list containing a single value will be produced. - * - * - const -- The value to be produced if the option is specified and the - * option uses an action that takes no values. - * - * - default -- The value to be produced if the option is not specified. - * - * - type -- A callable that accepts a single string argument, and - * returns the converted value. The standard Python types str, int, - * float, and complex are useful examples of such callables. If None, - * str is used. - * - * - choices -- A container of values that should be allowed. If not None, - * after a command-line argument has been converted to the appropriate - * type, an exception will be raised if it is not a member of this - * collection. - * - * - required -- True if the action must always be specified at the - * command line. This is only meaningful for optional command-line - * arguments. - * - * - help -- The help string describing the argument. - * - * - metavar -- The name to be used for the option's argument with the - * help string. If None, the 'dest' value will be used as the name. - */ - - constructor() { - let [ - option_strings, - dest, - nargs, - const_value, - default_value, - type, - choices, - required, - help, - metavar - ] = _parse_opts(arguments, { - option_strings: no_default, - dest: no_default, - nargs: undefined, - const: undefined, - default: undefined, - type: undefined, - choices: undefined, - required: false, - help: undefined, - metavar: undefined - }) - - // when this class is called as a function, redirect it to .call() method of itself - super('return arguments.callee.call.apply(arguments.callee, arguments)') - - this.option_strings = option_strings - this.dest = dest - this.nargs = nargs - this.const = const_value - this.default = default_value - this.type = type - this.choices = choices - this.required = required - this.help = help - this.metavar = metavar - } - - _get_kwargs() { - let names = [ - 'option_strings', - 'dest', - 'nargs', - 'const', - 'default', - 'type', - 'choices', - 'help', - 'metavar' - ] - return names.map(name => [ name, getattr(this, name) ]) - } - - format_usage() { - return this.option_strings[0] - } - - call(/*parser, namespace, values, option_string = undefined*/) { - throw new Error('.call() not defined') - } -})) - - -const BooleanOptionalAction = _camelcase_alias(_callable(class BooleanOptionalAction extends Action { - - constructor() { - let [ - option_strings, - dest, - default_value, - type, - choices, - required, - help, - metavar - ] = _parse_opts(arguments, { - option_strings: no_default, - dest: no_default, - default: undefined, - type: undefined, - choices: undefined, - required: false, - help: undefined, - metavar: undefined - }) - - let _option_strings = [] - for (let option_string of option_strings) { - _option_strings.push(option_string) - - if (option_string.startsWith('--')) { - option_string = '--no-' + option_string.slice(2) - _option_strings.push(option_string) - } - } - - if (help !== undefined && default_value !== undefined) { - help += ` (default: ${default_value})` - } - - super({ - option_strings: _option_strings, - dest, - nargs: 0, - default: default_value, - type, - choices, - required, - help, - metavar - }) - } - - call(parser, namespace, values, option_string = undefined) { - if (this.option_strings.includes(option_string)) { - setattr(namespace, this.dest, !option_string.startsWith('--no-')) - } - } - - format_usage() { - return this.option_strings.join(' | ') - } -})) - - -const _StoreAction = _callable(class _StoreAction extends Action { - - constructor() { - let [ - option_strings, - dest, - nargs, - const_value, - default_value, - type, - choices, - required, - help, - metavar - ] = _parse_opts(arguments, { - option_strings: no_default, - dest: no_default, - nargs: undefined, - const: undefined, - default: undefined, - type: undefined, - choices: undefined, - required: false, - help: undefined, - metavar: undefined - }) - - if (nargs === 0) { - throw new TypeError('nargs for store actions must be != 0; if you ' + - 'have nothing to store, actions such as store ' + - 'true or store const may be more appropriate') - } - if (const_value !== undefined && nargs !== OPTIONAL) { - throw new TypeError(sub('nargs must be %r to supply const', OPTIONAL)) - } - super({ - option_strings, - dest, - nargs, - const: const_value, - default: default_value, - type, - choices, - required, - help, - metavar - }) - } - - call(parser, namespace, values/*, option_string = undefined*/) { - setattr(namespace, this.dest, values) - } -}) - - -const _StoreConstAction = _callable(class _StoreConstAction extends Action { - - constructor() { - let [ - option_strings, - dest, - const_value, - default_value, - required, - help - //, metavar - ] = _parse_opts(arguments, { - option_strings: no_default, - dest: no_default, - const: no_default, - default: undefined, - required: false, - help: undefined, - metavar: undefined - }) - - super({ - option_strings, - dest, - nargs: 0, - const: const_value, - default: default_value, - required, - help - }) - } - - call(parser, namespace/*, values, option_string = undefined*/) { - setattr(namespace, this.dest, this.const) - } -}) - - -const _StoreTrueAction = _callable(class _StoreTrueAction extends _StoreConstAction { - - constructor() { - let [ - option_strings, - dest, - default_value, - required, - help - ] = _parse_opts(arguments, { - option_strings: no_default, - dest: no_default, - default: false, - required: false, - help: undefined - }) - - super({ - option_strings, - dest, - const: true, - default: default_value, - required, - help - }) - } -}) - - -const _StoreFalseAction = _callable(class _StoreFalseAction extends _StoreConstAction { - - constructor() { - let [ - option_strings, - dest, - default_value, - required, - help - ] = _parse_opts(arguments, { - option_strings: no_default, - dest: no_default, - default: true, - required: false, - help: undefined - }) - - super({ - option_strings, - dest, - const: false, - default: default_value, - required, - help - }) - } -}) - - -const _AppendAction = _callable(class _AppendAction extends Action { - - constructor() { - let [ - option_strings, - dest, - nargs, - const_value, - default_value, - type, - choices, - required, - help, - metavar - ] = _parse_opts(arguments, { - option_strings: no_default, - dest: no_default, - nargs: undefined, - const: undefined, - default: undefined, - type: undefined, - choices: undefined, - required: false, - help: undefined, - metavar: undefined - }) - - if (nargs === 0) { - throw new TypeError('nargs for append actions must be != 0; if arg ' + - 'strings are not supplying the value to append, ' + - 'the append const action may be more appropriate') - } - if (const_value !== undefined && nargs !== OPTIONAL) { - throw new TypeError(sub('nargs must be %r to supply const', OPTIONAL)) - } - super({ - option_strings, - dest, - nargs, - const: const_value, - default: default_value, - type, - choices, - required, - help, - metavar - }) - } - - call(parser, namespace, values/*, option_string = undefined*/) { - let items = getattr(namespace, this.dest, undefined) - items = _copy_items(items) - items.push(values) - setattr(namespace, this.dest, items) - } -}) - - -const _AppendConstAction = _callable(class _AppendConstAction extends Action { - - constructor() { - let [ - option_strings, - dest, - const_value, - default_value, - required, - help, - metavar - ] = _parse_opts(arguments, { - option_strings: no_default, - dest: no_default, - const: no_default, - default: undefined, - required: false, - help: undefined, - metavar: undefined - }) - - super({ - option_strings, - dest, - nargs: 0, - const: const_value, - default: default_value, - required, - help, - metavar - }) - } - - call(parser, namespace/*, values, option_string = undefined*/) { - let items = getattr(namespace, this.dest, undefined) - items = _copy_items(items) - items.push(this.const) - setattr(namespace, this.dest, items) - } -}) - - -const _CountAction = _callable(class _CountAction extends Action { - - constructor() { - let [ - option_strings, - dest, - default_value, - required, - help - ] = _parse_opts(arguments, { - option_strings: no_default, - dest: no_default, - default: undefined, - required: false, - help: undefined - }) - - super({ - option_strings, - dest, - nargs: 0, - default: default_value, - required, - help - }) - } - - call(parser, namespace/*, values, option_string = undefined*/) { - let count = getattr(namespace, this.dest, undefined) - if (count === undefined) { - count = 0 - } - setattr(namespace, this.dest, count + 1) - } -}) - - -const _HelpAction = _callable(class _HelpAction extends Action { - - constructor() { - let [ - option_strings, - dest, - default_value, - help - ] = _parse_opts(arguments, { - option_strings: no_default, - dest: SUPPRESS, - default: SUPPRESS, - help: undefined - }) - - super({ - option_strings, - dest, - default: default_value, - nargs: 0, - help - }) - } - - call(parser/*, namespace, values, option_string = undefined*/) { - parser.print_help() - parser.exit() - } -}) - - -const _VersionAction = _callable(class _VersionAction extends Action { - - constructor() { - let [ - option_strings, - version, - dest, - default_value, - help - ] = _parse_opts(arguments, { - option_strings: no_default, - version: undefined, - dest: SUPPRESS, - default: SUPPRESS, - help: "show program's version number and exit" - }) - - super({ - option_strings, - dest, - default: default_value, - nargs: 0, - help - }) - this.version = version - } - - call(parser/*, namespace, values, option_string = undefined*/) { - let version = this.version - if (version === undefined) { - version = parser.version - } - let formatter = parser._get_formatter() - formatter.add_text(version) - parser._print_message(formatter.format_help(), process.stdout) - parser.exit() - } -}) - - -const _SubParsersAction = _camelcase_alias(_callable(class _SubParsersAction extends Action { - - constructor() { - let [ - option_strings, - prog, - parser_class, - dest, - required, - help, - metavar - ] = _parse_opts(arguments, { - option_strings: no_default, - prog: no_default, - parser_class: no_default, - dest: SUPPRESS, - required: false, - help: undefined, - metavar: undefined - }) - - let name_parser_map = {} - - super({ - option_strings, - dest, - nargs: PARSER, - choices: name_parser_map, - required, - help, - metavar - }) - - this._prog_prefix = prog - this._parser_class = parser_class - this._name_parser_map = name_parser_map - this._choices_actions = [] - } - - add_parser() { - let [ - name, - kwargs - ] = _parse_opts(arguments, { - name: no_default, - '**kwargs': no_default - }) - - // set prog from the existing prefix - if (kwargs.prog === undefined) { - kwargs.prog = sub('%s %s', this._prog_prefix, name) - } - - let aliases = getattr(kwargs, 'aliases', []) - delete kwargs.aliases - - // create a pseudo-action to hold the choice help - if ('help' in kwargs) { - let help = kwargs.help - delete kwargs.help - let choice_action = this._ChoicesPseudoAction(name, aliases, help) - this._choices_actions.push(choice_action) - } - - // create the parser and add it to the map - let parser = new this._parser_class(kwargs) - this._name_parser_map[name] = parser - - // make parser available under aliases also - for (let alias of aliases) { - this._name_parser_map[alias] = parser - } - - return parser - } - - _get_subactions() { - return this._choices_actions - } - - call(parser, namespace, values/*, option_string = undefined*/) { - let parser_name = values[0] - let arg_strings = values.slice(1) - - // set the parser name if requested - if (this.dest !== SUPPRESS) { - setattr(namespace, this.dest, parser_name) - } - - // select the parser - if (hasattr(this._name_parser_map, parser_name)) { - parser = this._name_parser_map[parser_name] - } else { - let args = {parser_name, - choices: this._name_parser_map.join(', ')} - let msg = sub('unknown parser %(parser_name)r (choices: %(choices)s)', args) - throw new ArgumentError(this, msg) - } - - // parse all the remaining options into the namespace - // store any unrecognized options on the object, so that the top - // level parser can decide what to do with them - - // In case this subparser defines new defaults, we parse them - // in a new namespace object and then update the original - // namespace for the relevant parts. - let subnamespace - [ subnamespace, arg_strings ] = parser.parse_known_args(arg_strings, undefined) - for (let [ key, value ] of Object.entries(subnamespace)) { - setattr(namespace, key, value) - } - - if (arg_strings.length) { - setdefault(namespace, _UNRECOGNIZED_ARGS_ATTR, []) - getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).push(...arg_strings) - } - } -})) - - -_SubParsersAction.prototype._ChoicesPseudoAction = _callable(class _ChoicesPseudoAction extends Action { - constructor(name, aliases, help) { - let metavar = name, dest = name - if (aliases.length) { - metavar += sub(' (%s)', aliases.join(', ')) - } - super({ option_strings: [], dest, help, metavar }) - } -}) - - -const _ExtendAction = _callable(class _ExtendAction extends _AppendAction { - call(parser, namespace, values/*, option_string = undefined*/) { - let items = getattr(namespace, this.dest, undefined) - items = _copy_items(items) - items = items.concat(values) - setattr(namespace, this.dest, items) - } -}) - - -// ============== -// Type classes -// ============== -const FileType = _callable(class FileType extends Function { - /* - * Factory for creating file object types - * - * Instances of FileType are typically passed as type= arguments to the - * ArgumentParser add_argument() method. - * - * Keyword Arguments: - * - mode -- A string indicating how the file is to be opened. Accepts the - * same values as the builtin open() function. - * - bufsize -- The file's desired buffer size. Accepts the same values as - * the builtin open() function. - * - encoding -- The file's encoding. Accepts the same values as the - * builtin open() function. - * - errors -- A string indicating how encoding and decoding errors are to - * be handled. Accepts the same value as the builtin open() function. - */ - - constructor() { - let [ - flags, - encoding, - mode, - autoClose, - emitClose, - start, - end, - highWaterMark, - fs - ] = _parse_opts(arguments, { - flags: 'r', - encoding: undefined, - mode: undefined, // 0o666 - autoClose: undefined, // true - emitClose: undefined, // false - start: undefined, // 0 - end: undefined, // Infinity - highWaterMark: undefined, // 64 * 1024 - fs: undefined - }) - - // when this class is called as a function, redirect it to .call() method of itself - super('return arguments.callee.call.apply(arguments.callee, arguments)') - - Object.defineProperty(this, 'name', { - get() { - return sub('FileType(%r)', flags) - } - }) - this._flags = flags - this._options = {} - if (encoding !== undefined) this._options.encoding = encoding - if (mode !== undefined) this._options.mode = mode - if (autoClose !== undefined) this._options.autoClose = autoClose - if (emitClose !== undefined) this._options.emitClose = emitClose - if (start !== undefined) this._options.start = start - if (end !== undefined) this._options.end = end - if (highWaterMark !== undefined) this._options.highWaterMark = highWaterMark - if (fs !== undefined) this._options.fs = fs - } - - call(string) { - // the special argument "-" means sys.std{in,out} - if (string === '-') { - if (this._flags.includes('r')) { - return process.stdin - } else if (this._flags.includes('w')) { - return process.stdout - } else { - let msg = sub('argument "-" with mode %r', this._flags) - throw new TypeError(msg) - } - } - - // all other arguments are used as file names - let fd - try { - fd = fs.openSync(string, this._flags, this._options.mode) - } catch (e) { - let args = { filename: string, error: e.message } - let message = "can't open '%(filename)s': %(error)s" - throw new ArgumentTypeError(sub(message, args)) - } - - let options = Object.assign({ fd, flags: this._flags }, this._options) - if (this._flags.includes('r')) { - return fs.createReadStream(undefined, options) - } else if (this._flags.includes('w')) { - return fs.createWriteStream(undefined, options) - } else { - let msg = sub('argument "%s" with mode %r', string, this._flags) - throw new TypeError(msg) - } - } - - [util.inspect.custom]() { - let args = [ this._flags ] - let kwargs = Object.entries(this._options).map(([ k, v ]) => { - if (k === 'mode') v = { value: v, [util.inspect.custom]() { return '0o' + this.value.toString(8) } } - return [ k, v ] - }) - let args_str = [] - .concat(args.filter(arg => arg !== -1).map(repr)) - .concat(kwargs.filter(([/*kw*/, arg]) => arg !== undefined) - .map(([kw, arg]) => sub('%s=%r', kw, arg))) - .join(', ') - return sub('%s(%s)', this.constructor.name, args_str) - } - - toString() { - return this[util.inspect.custom]() - } -}) - -// =========================== -// Optional and Positional Parsing -// =========================== -const Namespace = _callable(class Namespace extends _AttributeHolder() { - /* - * Simple object for storing attributes. - * - * Implements equality by attribute names and values, and provides a simple - * string representation. - */ - - constructor(options = {}) { - super() - Object.assign(this, options) - } -}) - -// unset string tag to mimic plain object -Namespace.prototype[Symbol.toStringTag] = undefined - - -const _ActionsContainer = _camelcase_alias(_callable(class _ActionsContainer { - - constructor() { - let [ - description, - prefix_chars, - argument_default, - conflict_handler - ] = _parse_opts(arguments, { - description: no_default, - prefix_chars: no_default, - argument_default: no_default, - conflict_handler: no_default - }) - - this.description = description - this.argument_default = argument_default - this.prefix_chars = prefix_chars - this.conflict_handler = conflict_handler - - // set up registries - this._registries = {} - - // register actions - this.register('action', undefined, _StoreAction) - this.register('action', 'store', _StoreAction) - this.register('action', 'store_const', _StoreConstAction) - this.register('action', 'store_true', _StoreTrueAction) - this.register('action', 'store_false', _StoreFalseAction) - this.register('action', 'append', _AppendAction) - this.register('action', 'append_const', _AppendConstAction) - this.register('action', 'count', _CountAction) - this.register('action', 'help', _HelpAction) - this.register('action', 'version', _VersionAction) - this.register('action', 'parsers', _SubParsersAction) - this.register('action', 'extend', _ExtendAction) - // LEGACY (v1 compatibility): camelcase variants - ;[ 'storeConst', 'storeTrue', 'storeFalse', 'appendConst' ].forEach(old_name => { - let new_name = _to_new_name(old_name) - this.register('action', old_name, util.deprecate(this._registry_get('action', new_name), - sub('{action: "%s"} is renamed to {action: "%s"}', old_name, new_name))) - }) - // end - - // raise an exception if the conflict handler is invalid - this._get_handler() - - // action storage - this._actions = [] - this._option_string_actions = {} - - // groups - this._action_groups = [] - this._mutually_exclusive_groups = [] - - // defaults storage - this._defaults = {} - - // determines whether an "option" looks like a negative number - this._negative_number_matcher = /^-\d+$|^-\d*\.\d+$/ - - // whether or not there are any optionals that look like negative - // numbers -- uses a list so it can be shared and edited - this._has_negative_number_optionals = [] - } - - // ==================== - // Registration methods - // ==================== - register(registry_name, value, object) { - let registry = setdefault(this._registries, registry_name, {}) - registry[value] = object - } - - _registry_get(registry_name, value, default_value = undefined) { - return getattr(this._registries[registry_name], value, default_value) - } - - // ================================== - // Namespace default accessor methods - // ================================== - set_defaults(kwargs) { - Object.assign(this._defaults, kwargs) - - // if these defaults match any existing arguments, replace - // the previous default on the object with the new one - for (let action of this._actions) { - if (action.dest in kwargs) { - action.default = kwargs[action.dest] - } - } - } - - get_default(dest) { - for (let action of this._actions) { - if (action.dest === dest && action.default !== undefined) { - return action.default - } - } - return this._defaults[dest] - } - - - // ======================= - // Adding argument actions - // ======================= - add_argument() { - /* - * add_argument(dest, ..., name=value, ...) - * add_argument(option_string, option_string, ..., name=value, ...) - */ - let [ - args, - kwargs - ] = _parse_opts(arguments, { - '*args': no_default, - '**kwargs': no_default - }) - // LEGACY (v1 compatibility), old-style add_argument([ args ], { options }) - if (args.length === 1 && Array.isArray(args[0])) { - args = args[0] - deprecate('argument-array', - sub('use add_argument(%(args)s, {...}) instead of add_argument([ %(args)s ], { ... })', { - args: args.map(repr).join(', ') - })) - } - // end - - // if no positional args are supplied or only one is supplied and - // it doesn't look like an option string, parse a positional - // argument - let chars = this.prefix_chars - if (!args.length || args.length === 1 && !chars.includes(args[0][0])) { - if (args.length && 'dest' in kwargs) { - throw new TypeError('dest supplied twice for positional argument') - } - kwargs = this._get_positional_kwargs(...args, kwargs) - - // otherwise, we're adding an optional argument - } else { - kwargs = this._get_optional_kwargs(...args, kwargs) - } - - // if no default was supplied, use the parser-level default - if (!('default' in kwargs)) { - let dest = kwargs.dest - if (dest in this._defaults) { - kwargs.default = this._defaults[dest] - } else if (this.argument_default !== undefined) { - kwargs.default = this.argument_default - } - } - - // create the action object, and add it to the parser - let action_class = this._pop_action_class(kwargs) - if (typeof action_class !== 'function') { - throw new TypeError(sub('unknown action "%s"', action_class)) - } - // eslint-disable-next-line new-cap - let action = new action_class(kwargs) - - // raise an error if the action type is not callable - let type_func = this._registry_get('type', action.type, action.type) - if (typeof type_func !== 'function') { - throw new TypeError(sub('%r is not callable', type_func)) - } - - if (type_func === FileType) { - throw new TypeError(sub('%r is a FileType class object, instance of it' + - ' must be passed', type_func)) - } - - // raise an error if the metavar does not match the type - if ('_get_formatter' in this) { - try { - this._get_formatter()._format_args(action, undefined) - } catch (err) { - // check for 'invalid nargs value' is an artifact of TypeError and ValueError in js being the same - if (err instanceof TypeError && err.message !== 'invalid nargs value') { - throw new TypeError('length of metavar tuple does not match nargs') - } else { - throw err - } - } - } - - return this._add_action(action) - } - - add_argument_group() { - let group = _ArgumentGroup(this, ...arguments) - this._action_groups.push(group) - return group - } - - add_mutually_exclusive_group() { - // eslint-disable-next-line no-use-before-define - let group = _MutuallyExclusiveGroup(this, ...arguments) - this._mutually_exclusive_groups.push(group) - return group - } - - _add_action(action) { - // resolve any conflicts - this._check_conflict(action) - - // add to actions list - this._actions.push(action) - action.container = this - - // index the action by any option strings it has - for (let option_string of action.option_strings) { - this._option_string_actions[option_string] = action - } - - // set the flag if any option strings look like negative numbers - for (let option_string of action.option_strings) { - if (this._negative_number_matcher.test(option_string)) { - if (!this._has_negative_number_optionals.length) { - this._has_negative_number_optionals.push(true) - } - } - } - - // return the created action - return action - } - - _remove_action(action) { - _array_remove(this._actions, action) - } - - _add_container_actions(container) { - // collect groups by titles - let title_group_map = {} - for (let group of this._action_groups) { - if (group.title in title_group_map) { - let msg = 'cannot merge actions - two groups are named %r' - throw new TypeError(sub(msg, group.title)) - } - title_group_map[group.title] = group - } - - // map each action to its group - let group_map = new Map() - for (let group of container._action_groups) { - - // if a group with the title exists, use that, otherwise - // create a new group matching the container's group - if (!(group.title in title_group_map)) { - title_group_map[group.title] = this.add_argument_group({ - title: group.title, - description: group.description, - conflict_handler: group.conflict_handler - }) - } - - // map the actions to their new group - for (let action of group._group_actions) { - group_map.set(action, title_group_map[group.title]) - } - } - - // add container's mutually exclusive groups - // NOTE: if add_mutually_exclusive_group ever gains title= and - // description= then this code will need to be expanded as above - for (let group of container._mutually_exclusive_groups) { - let mutex_group = this.add_mutually_exclusive_group({ - required: group.required - }) - - // map the actions to their new mutex group - for (let action of group._group_actions) { - group_map.set(action, mutex_group) - } - } - - // add all actions to this container or their group - for (let action of container._actions) { - group_map.get(action)._add_action(action) - } - } - - _get_positional_kwargs() { - let [ - dest, - kwargs - ] = _parse_opts(arguments, { - dest: no_default, - '**kwargs': no_default - }) - - // make sure required is not specified - if ('required' in kwargs) { - let msg = "'required' is an invalid argument for positionals" - throw new TypeError(msg) - } - - // mark positional arguments as required if at least one is - // always required - if (![OPTIONAL, ZERO_OR_MORE].includes(kwargs.nargs)) { - kwargs.required = true - } - if (kwargs.nargs === ZERO_OR_MORE && !('default' in kwargs)) { - kwargs.required = true - } - - // return the keyword arguments with no option strings - return Object.assign(kwargs, { dest, option_strings: [] }) - } - - _get_optional_kwargs() { - let [ - args, - kwargs - ] = _parse_opts(arguments, { - '*args': no_default, - '**kwargs': no_default - }) - - // determine short and long option strings - let option_strings = [] - let long_option_strings = [] - let option_string - for (option_string of args) { - // error on strings that don't start with an appropriate prefix - if (!this.prefix_chars.includes(option_string[0])) { - let args = {option: option_string, - prefix_chars: this.prefix_chars} - let msg = 'invalid option string %(option)r: ' + - 'must start with a character %(prefix_chars)r' - throw new TypeError(sub(msg, args)) - } - - // strings starting with two prefix characters are long options - option_strings.push(option_string) - if (option_string.length > 1 && this.prefix_chars.includes(option_string[1])) { - long_option_strings.push(option_string) - } - } - - // infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x' - let dest = kwargs.dest - delete kwargs.dest - if (dest === undefined) { - let dest_option_string - if (long_option_strings.length) { - dest_option_string = long_option_strings[0] - } else { - dest_option_string = option_strings[0] - } - dest = _string_lstrip(dest_option_string, this.prefix_chars) - if (!dest) { - let msg = 'dest= is required for options like %r' - throw new TypeError(sub(msg, option_string)) - } - dest = dest.replace(/-/g, '_') - } - - // return the updated keyword arguments - return Object.assign(kwargs, { dest, option_strings }) - } - - _pop_action_class(kwargs, default_value = undefined) { - let action = getattr(kwargs, 'action', default_value) - delete kwargs.action - return this._registry_get('action', action, action) - } - - _get_handler() { - // determine function from conflict handler string - let handler_func_name = sub('_handle_conflict_%s', this.conflict_handler) - if (typeof this[handler_func_name] === 'function') { - return this[handler_func_name] - } else { - let msg = 'invalid conflict_resolution value: %r' - throw new TypeError(sub(msg, this.conflict_handler)) - } - } - - _check_conflict(action) { - - // find all options that conflict with this option - let confl_optionals = [] - for (let option_string of action.option_strings) { - if (hasattr(this._option_string_actions, option_string)) { - let confl_optional = this._option_string_actions[option_string] - confl_optionals.push([ option_string, confl_optional ]) - } - } - - // resolve any conflicts - if (confl_optionals.length) { - let conflict_handler = this._get_handler() - conflict_handler.call(this, action, confl_optionals) - } - } - - _handle_conflict_error(action, conflicting_actions) { - let message = conflicting_actions.length === 1 ? - 'conflicting option string: %s' : - 'conflicting option strings: %s' - let conflict_string = conflicting_actions.map(([ option_string/*, action*/ ]) => option_string).join(', ') - throw new ArgumentError(action, sub(message, conflict_string)) - } - - _handle_conflict_resolve(action, conflicting_actions) { - - // remove all conflicting options - for (let [ option_string, action ] of conflicting_actions) { - - // remove the conflicting option - _array_remove(action.option_strings, option_string) - delete this._option_string_actions[option_string] - - // if the option now has no option string, remove it from the - // container holding it - if (!action.option_strings.length) { - action.container._remove_action(action) - } - } - } -})) - - -const _ArgumentGroup = _callable(class _ArgumentGroup extends _ActionsContainer { - - constructor() { - let [ - container, - title, - description, - kwargs - ] = _parse_opts(arguments, { - container: no_default, - title: undefined, - description: undefined, - '**kwargs': no_default - }) - - // add any missing keyword arguments by checking the container - setdefault(kwargs, 'conflict_handler', container.conflict_handler) - setdefault(kwargs, 'prefix_chars', container.prefix_chars) - setdefault(kwargs, 'argument_default', container.argument_default) - super(Object.assign({ description }, kwargs)) - - // group attributes - this.title = title - this._group_actions = [] - - // share most attributes with the container - this._registries = container._registries - this._actions = container._actions - this._option_string_actions = container._option_string_actions - this._defaults = container._defaults - this._has_negative_number_optionals = - container._has_negative_number_optionals - this._mutually_exclusive_groups = container._mutually_exclusive_groups - } - - _add_action(action) { - action = super._add_action(action) - this._group_actions.push(action) - return action - } - - _remove_action(action) { - super._remove_action(action) - _array_remove(this._group_actions, action) - } -}) - - -const _MutuallyExclusiveGroup = _callable(class _MutuallyExclusiveGroup extends _ArgumentGroup { - - constructor() { - let [ - container, - required - ] = _parse_opts(arguments, { - container: no_default, - required: false - }) - - super(container) - this.required = required - this._container = container - } - - _add_action(action) { - if (action.required) { - let msg = 'mutually exclusive arguments must be optional' - throw new TypeError(msg) - } - action = this._container._add_action(action) - this._group_actions.push(action) - return action - } - - _remove_action(action) { - this._container._remove_action(action) - _array_remove(this._group_actions, action) - } -}) - - -const ArgumentParser = _camelcase_alias(_callable(class ArgumentParser extends _AttributeHolder(_ActionsContainer) { - /* - * Object for parsing command line strings into Python objects. - * - * Keyword Arguments: - * - prog -- The name of the program (default: sys.argv[0]) - * - usage -- A usage message (default: auto-generated from arguments) - * - description -- A description of what the program does - * - epilog -- Text following the argument descriptions - * - parents -- Parsers whose arguments should be copied into this one - * - formatter_class -- HelpFormatter class for printing help messages - * - prefix_chars -- Characters that prefix optional arguments - * - fromfile_prefix_chars -- Characters that prefix files containing - * additional arguments - * - argument_default -- The default value for all arguments - * - conflict_handler -- String indicating how to handle conflicts - * - add_help -- Add a -h/-help option - * - allow_abbrev -- Allow long options to be abbreviated unambiguously - * - exit_on_error -- Determines whether or not ArgumentParser exits with - * error info when an error occurs - */ - - constructor() { - let [ - prog, - usage, - description, - epilog, - parents, - formatter_class, - prefix_chars, - fromfile_prefix_chars, - argument_default, - conflict_handler, - add_help, - allow_abbrev, - exit_on_error, - debug, // LEGACY (v1 compatibility), debug mode - version // LEGACY (v1 compatibility), version - ] = _parse_opts(arguments, { - prog: undefined, - usage: undefined, - description: undefined, - epilog: undefined, - parents: [], - formatter_class: HelpFormatter, - prefix_chars: '-', - fromfile_prefix_chars: undefined, - argument_default: undefined, - conflict_handler: 'error', - add_help: true, - allow_abbrev: true, - exit_on_error: true, - debug: undefined, // LEGACY (v1 compatibility), debug mode - version: undefined // LEGACY (v1 compatibility), version - }) - - // LEGACY (v1 compatibility) - if (debug !== undefined) { - deprecate('debug', - 'The "debug" argument to ArgumentParser is deprecated. Please ' + - 'override ArgumentParser.exit function instead.' - ) - } - - if (version !== undefined) { - deprecate('version', - 'The "version" argument to ArgumentParser is deprecated. Please use ' + - "add_argument(..., { action: 'version', version: 'N', ... }) instead." - ) - } - // end - - super({ - description, - prefix_chars, - argument_default, - conflict_handler - }) - - // default setting for prog - if (prog === undefined) { - prog = path.basename(get_argv()[0] || '') - } - - this.prog = prog - this.usage = usage - this.epilog = epilog - this.formatter_class = formatter_class - this.fromfile_prefix_chars = fromfile_prefix_chars - this.add_help = add_help - this.allow_abbrev = allow_abbrev - this.exit_on_error = exit_on_error - // LEGACY (v1 compatibility), debug mode - this.debug = debug - // end - - this._positionals = this.add_argument_group('positional arguments') - this._optionals = this.add_argument_group('optional arguments') - this._subparsers = undefined - - // register types - function identity(string) { - return string - } - this.register('type', undefined, identity) - this.register('type', null, identity) - this.register('type', 'auto', identity) - this.register('type', 'int', function (x) { - let result = Number(x) - if (!Number.isInteger(result)) { - throw new TypeError(sub('could not convert string to int: %r', x)) - } - return result - }) - this.register('type', 'float', function (x) { - let result = Number(x) - if (isNaN(result)) { - throw new TypeError(sub('could not convert string to float: %r', x)) - } - return result - }) - this.register('type', 'str', String) - // LEGACY (v1 compatibility): custom types - this.register('type', 'string', - util.deprecate(String, 'use {type:"str"} or {type:String} instead of {type:"string"}')) - // end - - // add help argument if necessary - // (using explicit default to override global argument_default) - let default_prefix = prefix_chars.includes('-') ? '-' : prefix_chars[0] - if (this.add_help) { - this.add_argument( - default_prefix + 'h', - default_prefix.repeat(2) + 'help', - { - action: 'help', - default: SUPPRESS, - help: 'show this help message and exit' - } - ) - } - // LEGACY (v1 compatibility), version - if (version) { - this.add_argument( - default_prefix + 'v', - default_prefix.repeat(2) + 'version', - { - action: 'version', - default: SUPPRESS, - version: this.version, - help: "show program's version number and exit" - } - ) - } - // end - - // add parent arguments and defaults - for (let parent of parents) { - this._add_container_actions(parent) - Object.assign(this._defaults, parent._defaults) - } - } - - // ======================= - // Pretty __repr__ methods - // ======================= - _get_kwargs() { - let names = [ - 'prog', - 'usage', - 'description', - 'formatter_class', - 'conflict_handler', - 'add_help' - ] - return names.map(name => [ name, getattr(this, name) ]) - } - - // ================================== - // Optional/Positional adding methods - // ================================== - add_subparsers() { - let [ - kwargs - ] = _parse_opts(arguments, { - '**kwargs': no_default - }) - - if (this._subparsers !== undefined) { - this.error('cannot have multiple subparser arguments') - } - - // add the parser class to the arguments if it's not present - setdefault(kwargs, 'parser_class', this.constructor) - - if ('title' in kwargs || 'description' in kwargs) { - let title = getattr(kwargs, 'title', 'subcommands') - let description = getattr(kwargs, 'description', undefined) - delete kwargs.title - delete kwargs.description - this._subparsers = this.add_argument_group(title, description) - } else { - this._subparsers = this._positionals - } - - // prog defaults to the usage message of this parser, skipping - // optional arguments and with no "usage:" prefix - if (kwargs.prog === undefined) { - let formatter = this._get_formatter() - let positionals = this._get_positional_actions() - let groups = this._mutually_exclusive_groups - formatter.add_usage(this.usage, positionals, groups, '') - kwargs.prog = formatter.format_help().trim() - } - - // create the parsers action and add it to the positionals list - let parsers_class = this._pop_action_class(kwargs, 'parsers') - // eslint-disable-next-line new-cap - let action = new parsers_class(Object.assign({ option_strings: [] }, kwargs)) - this._subparsers._add_action(action) - - // return the created parsers action - return action - } - - _add_action(action) { - if (action.option_strings.length) { - this._optionals._add_action(action) - } else { - this._positionals._add_action(action) - } - return action - } - - _get_optional_actions() { - return this._actions.filter(action => action.option_strings.length) - } - - _get_positional_actions() { - return this._actions.filter(action => !action.option_strings.length) - } - - // ===================================== - // Command line argument parsing methods - // ===================================== - parse_args(args = undefined, namespace = undefined) { - let argv - [ args, argv ] = this.parse_known_args(args, namespace) - if (argv && argv.length > 0) { - let msg = 'unrecognized arguments: %s' - this.error(sub(msg, argv.join(' '))) - } - return args - } - - parse_known_args(args = undefined, namespace = undefined) { - if (args === undefined) { - args = get_argv().slice(1) - } - - // default Namespace built from parser defaults - if (namespace === undefined) { - namespace = new Namespace() - } - - // add any action defaults that aren't present - for (let action of this._actions) { - if (action.dest !== SUPPRESS) { - if (!hasattr(namespace, action.dest)) { - if (action.default !== SUPPRESS) { - setattr(namespace, action.dest, action.default) - } - } - } - } - - // add any parser defaults that aren't present - for (let dest of Object.keys(this._defaults)) { - if (!hasattr(namespace, dest)) { - setattr(namespace, dest, this._defaults[dest]) - } - } - - // parse the arguments and exit if there are any errors - if (this.exit_on_error) { - try { - [ namespace, args ] = this._parse_known_args(args, namespace) - } catch (err) { - if (err instanceof ArgumentError) { - this.error(err.message) - } else { - throw err - } - } - } else { - [ namespace, args ] = this._parse_known_args(args, namespace) - } - - if (hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) { - args = args.concat(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) - delattr(namespace, _UNRECOGNIZED_ARGS_ATTR) - } - - return [ namespace, args ] - } - - _parse_known_args(arg_strings, namespace) { - // replace arg strings that are file references - if (this.fromfile_prefix_chars !== undefined) { - arg_strings = this._read_args_from_files(arg_strings) - } - - // map all mutually exclusive arguments to the other arguments - // they can't occur with - let action_conflicts = new Map() - for (let mutex_group of this._mutually_exclusive_groups) { - let group_actions = mutex_group._group_actions - for (let [ i, mutex_action ] of Object.entries(mutex_group._group_actions)) { - let conflicts = action_conflicts.get(mutex_action) || [] - conflicts = conflicts.concat(group_actions.slice(0, +i)) - conflicts = conflicts.concat(group_actions.slice(+i + 1)) - action_conflicts.set(mutex_action, conflicts) - } - } - - // find all option indices, and determine the arg_string_pattern - // which has an 'O' if there is an option at an index, - // an 'A' if there is an argument, or a '-' if there is a '--' - let option_string_indices = {} - let arg_string_pattern_parts = [] - let arg_strings_iter = Object.entries(arg_strings)[Symbol.iterator]() - for (let [ i, arg_string ] of arg_strings_iter) { - - // all args after -- are non-options - if (arg_string === '--') { - arg_string_pattern_parts.push('-') - for ([ i, arg_string ] of arg_strings_iter) { - arg_string_pattern_parts.push('A') - } - - // otherwise, add the arg to the arg strings - // and note the index if it was an option - } else { - let option_tuple = this._parse_optional(arg_string) - let pattern - if (option_tuple === undefined) { - pattern = 'A' - } else { - option_string_indices[i] = option_tuple - pattern = 'O' - } - arg_string_pattern_parts.push(pattern) - } - } - - // join the pieces together to form the pattern - let arg_strings_pattern = arg_string_pattern_parts.join('') - - // converts arg strings to the appropriate and then takes the action - let seen_actions = new Set() - let seen_non_default_actions = new Set() - let extras - - let take_action = (action, argument_strings, option_string = undefined) => { - seen_actions.add(action) - let argument_values = this._get_values(action, argument_strings) - - // error if this argument is not allowed with other previously - // seen arguments, assuming that actions that use the default - // value don't really count as "present" - if (argument_values !== action.default) { - seen_non_default_actions.add(action) - for (let conflict_action of action_conflicts.get(action) || []) { - if (seen_non_default_actions.has(conflict_action)) { - let msg = 'not allowed with argument %s' - let action_name = _get_action_name(conflict_action) - throw new ArgumentError(action, sub(msg, action_name)) - } - } - } - - // take the action if we didn't receive a SUPPRESS value - // (e.g. from a default) - if (argument_values !== SUPPRESS) { - action(this, namespace, argument_values, option_string) - } - } - - // function to convert arg_strings into an optional action - let consume_optional = start_index => { - - // get the optional identified at this index - let option_tuple = option_string_indices[start_index] - let [ action, option_string, explicit_arg ] = option_tuple - - // identify additional optionals in the same arg string - // (e.g. -xyz is the same as -x -y -z if no args are required) - let action_tuples = [] - let stop - for (;;) { - - // if we found no optional action, skip it - if (action === undefined) { - extras.push(arg_strings[start_index]) - return start_index + 1 - } - - // if there is an explicit argument, try to match the - // optional's string arguments to only this - if (explicit_arg !== undefined) { - let arg_count = this._match_argument(action, 'A') - - // if the action is a single-dash option and takes no - // arguments, try to parse more single-dash options out - // of the tail of the option string - let chars = this.prefix_chars - if (arg_count === 0 && !chars.includes(option_string[1])) { - action_tuples.push([ action, [], option_string ]) - let char = option_string[0] - option_string = char + explicit_arg[0] - let new_explicit_arg = explicit_arg.slice(1) || undefined - let optionals_map = this._option_string_actions - if (hasattr(optionals_map, option_string)) { - action = optionals_map[option_string] - explicit_arg = new_explicit_arg - } else { - let msg = 'ignored explicit argument %r' - throw new ArgumentError(action, sub(msg, explicit_arg)) - } - - // if the action expect exactly one argument, we've - // successfully matched the option; exit the loop - } else if (arg_count === 1) { - stop = start_index + 1 - let args = [ explicit_arg ] - action_tuples.push([ action, args, option_string ]) - break - - // error if a double-dash option did not use the - // explicit argument - } else { - let msg = 'ignored explicit argument %r' - throw new ArgumentError(action, sub(msg, explicit_arg)) - } - - // if there is no explicit argument, try to match the - // optional's string arguments with the following strings - // if successful, exit the loop - } else { - let start = start_index + 1 - let selected_patterns = arg_strings_pattern.slice(start) - let arg_count = this._match_argument(action, selected_patterns) - stop = start + arg_count - let args = arg_strings.slice(start, stop) - action_tuples.push([ action, args, option_string ]) - break - } - } - - // add the Optional to the list and return the index at which - // the Optional's string args stopped - assert(action_tuples.length) - for (let [ action, args, option_string ] of action_tuples) { - take_action(action, args, option_string) - } - return stop - } - - // the list of Positionals left to be parsed; this is modified - // by consume_positionals() - let positionals = this._get_positional_actions() - - // function to convert arg_strings into positional actions - let consume_positionals = start_index => { - // match as many Positionals as possible - let selected_pattern = arg_strings_pattern.slice(start_index) - let arg_counts = this._match_arguments_partial(positionals, selected_pattern) - - // slice off the appropriate arg strings for each Positional - // and add the Positional and its args to the list - for (let i = 0; i < positionals.length && i < arg_counts.length; i++) { - let action = positionals[i] - let arg_count = arg_counts[i] - let args = arg_strings.slice(start_index, start_index + arg_count) - start_index += arg_count - take_action(action, args) - } - - // slice off the Positionals that we just parsed and return the - // index at which the Positionals' string args stopped - positionals = positionals.slice(arg_counts.length) - return start_index - } - - // consume Positionals and Optionals alternately, until we have - // passed the last option string - extras = [] - let start_index = 0 - let max_option_string_index = Math.max(-1, ...Object.keys(option_string_indices).map(Number)) - while (start_index <= max_option_string_index) { - - // consume any Positionals preceding the next option - let next_option_string_index = Math.min( - // eslint-disable-next-line no-loop-func - ...Object.keys(option_string_indices).map(Number).filter(index => index >= start_index) - ) - if (start_index !== next_option_string_index) { - let positionals_end_index = consume_positionals(start_index) - - // only try to parse the next optional if we didn't consume - // the option string during the positionals parsing - if (positionals_end_index > start_index) { - start_index = positionals_end_index - continue - } else { - start_index = positionals_end_index - } - } - - // if we consumed all the positionals we could and we're not - // at the index of an option string, there were extra arguments - if (!(start_index in option_string_indices)) { - let strings = arg_strings.slice(start_index, next_option_string_index) - extras = extras.concat(strings) - start_index = next_option_string_index - } - - // consume the next optional and any arguments for it - start_index = consume_optional(start_index) - } - - // consume any positionals following the last Optional - let stop_index = consume_positionals(start_index) - - // if we didn't consume all the argument strings, there were extras - extras = extras.concat(arg_strings.slice(stop_index)) - - // make sure all required actions were present and also convert - // action defaults which were not given as arguments - let required_actions = [] - for (let action of this._actions) { - if (!seen_actions.has(action)) { - if (action.required) { - required_actions.push(_get_action_name(action)) - } else { - // Convert action default now instead of doing it before - // parsing arguments to avoid calling convert functions - // twice (which may fail) if the argument was given, but - // only if it was defined already in the namespace - if (action.default !== undefined && - typeof action.default === 'string' && - hasattr(namespace, action.dest) && - action.default === getattr(namespace, action.dest)) { - setattr(namespace, action.dest, - this._get_value(action, action.default)) - } - } - } - } - - if (required_actions.length) { - this.error(sub('the following arguments are required: %s', - required_actions.join(', '))) - } - - // make sure all required groups had one option present - for (let group of this._mutually_exclusive_groups) { - if (group.required) { - let no_actions_used = true - for (let action of group._group_actions) { - if (seen_non_default_actions.has(action)) { - no_actions_used = false - break - } - } - - // if no actions were used, report the error - if (no_actions_used) { - let names = group._group_actions - .filter(action => action.help !== SUPPRESS) - .map(action => _get_action_name(action)) - let msg = 'one of the arguments %s is required' - this.error(sub(msg, names.join(' '))) - } - } - } - - // return the updated namespace and the extra arguments - return [ namespace, extras ] - } - - _read_args_from_files(arg_strings) { - // expand arguments referencing files - let new_arg_strings = [] - for (let arg_string of arg_strings) { - - // for regular arguments, just add them back into the list - if (!arg_string || !this.fromfile_prefix_chars.includes(arg_string[0])) { - new_arg_strings.push(arg_string) - - // replace arguments referencing files with the file content - } else { - try { - let args_file = fs.readFileSync(arg_string.slice(1), 'utf8') - let arg_strings = [] - for (let arg_line of splitlines(args_file)) { - for (let arg of this.convert_arg_line_to_args(arg_line)) { - arg_strings.push(arg) - } - } - arg_strings = this._read_args_from_files(arg_strings) - new_arg_strings = new_arg_strings.concat(arg_strings) - } catch (err) { - this.error(err.message) - } - } - } - - // return the modified argument list - return new_arg_strings - } - - convert_arg_line_to_args(arg_line) { - return [arg_line] - } - - _match_argument(action, arg_strings_pattern) { - // match the pattern for this action to the arg strings - let nargs_pattern = this._get_nargs_pattern(action) - let match = arg_strings_pattern.match(new RegExp('^' + nargs_pattern)) - - // raise an exception if we weren't able to find a match - if (match === null) { - let nargs_errors = { - undefined: 'expected one argument', - [OPTIONAL]: 'expected at most one argument', - [ONE_OR_MORE]: 'expected at least one argument' - } - let msg = nargs_errors[action.nargs] - if (msg === undefined) { - msg = sub(action.nargs === 1 ? 'expected %s argument' : 'expected %s arguments', action.nargs) - } - throw new ArgumentError(action, msg) - } - - // return the number of arguments matched - return match[1].length - } - - _match_arguments_partial(actions, arg_strings_pattern) { - // progressively shorten the actions list by slicing off the - // final actions until we find a match - let result = [] - for (let i of range(actions.length, 0, -1)) { - let actions_slice = actions.slice(0, i) - let pattern = actions_slice.map(action => this._get_nargs_pattern(action)).join('') - let match = arg_strings_pattern.match(new RegExp('^' + pattern)) - if (match !== null) { - result = result.concat(match.slice(1).map(string => string.length)) - break - } - } - - // return the list of arg string counts - return result - } - - _parse_optional(arg_string) { - // if it's an empty string, it was meant to be a positional - if (!arg_string) { - return undefined - } - - // if it doesn't start with a prefix, it was meant to be positional - if (!this.prefix_chars.includes(arg_string[0])) { - return undefined - } - - // if the option string is present in the parser, return the action - if (arg_string in this._option_string_actions) { - let action = this._option_string_actions[arg_string] - return [ action, arg_string, undefined ] - } - - // if it's just a single character, it was meant to be positional - if (arg_string.length === 1) { - return undefined - } - - // if the option string before the "=" is present, return the action - if (arg_string.includes('=')) { - let [ option_string, explicit_arg ] = _string_split(arg_string, '=', 1) - if (option_string in this._option_string_actions) { - let action = this._option_string_actions[option_string] - return [ action, option_string, explicit_arg ] - } - } - - // search through all possible prefixes of the option string - // and all actions in the parser for possible interpretations - let option_tuples = this._get_option_tuples(arg_string) - - // if multiple actions match, the option string was ambiguous - if (option_tuples.length > 1) { - let options = option_tuples.map(([ /*action*/, option_string/*, explicit_arg*/ ]) => option_string).join(', ') - let args = {option: arg_string, matches: options} - let msg = 'ambiguous option: %(option)s could match %(matches)s' - this.error(sub(msg, args)) - - // if exactly one action matched, this segmentation is good, - // so return the parsed action - } else if (option_tuples.length === 1) { - let [ option_tuple ] = option_tuples - return option_tuple - } - - // if it was not found as an option, but it looks like a negative - // number, it was meant to be positional - // unless there are negative-number-like options - if (this._negative_number_matcher.test(arg_string)) { - if (!this._has_negative_number_optionals.length) { - return undefined - } - } - - // if it contains a space, it was meant to be a positional - if (arg_string.includes(' ')) { - return undefined - } - - // it was meant to be an optional but there is no such option - // in this parser (though it might be a valid option in a subparser) - return [ undefined, arg_string, undefined ] - } - - _get_option_tuples(option_string) { - let result = [] - - // option strings starting with two prefix characters are only - // split at the '=' - let chars = this.prefix_chars - if (chars.includes(option_string[0]) && chars.includes(option_string[1])) { - if (this.allow_abbrev) { - let option_prefix, explicit_arg - if (option_string.includes('=')) { - [ option_prefix, explicit_arg ] = _string_split(option_string, '=', 1) - } else { - option_prefix = option_string - explicit_arg = undefined - } - for (let option_string of Object.keys(this._option_string_actions)) { - if (option_string.startsWith(option_prefix)) { - let action = this._option_string_actions[option_string] - let tup = [ action, option_string, explicit_arg ] - result.push(tup) - } - } - } - - // single character options can be concatenated with their arguments - // but multiple character options always have to have their argument - // separate - } else if (chars.includes(option_string[0]) && !chars.includes(option_string[1])) { - let option_prefix = option_string - let explicit_arg = undefined - let short_option_prefix = option_string.slice(0, 2) - let short_explicit_arg = option_string.slice(2) - - for (let option_string of Object.keys(this._option_string_actions)) { - if (option_string === short_option_prefix) { - let action = this._option_string_actions[option_string] - let tup = [ action, option_string, short_explicit_arg ] - result.push(tup) - } else if (option_string.startsWith(option_prefix)) { - let action = this._option_string_actions[option_string] - let tup = [ action, option_string, explicit_arg ] - result.push(tup) - } - } - - // shouldn't ever get here - } else { - this.error(sub('unexpected option string: %s', option_string)) - } - - // return the collected option tuples - return result - } - - _get_nargs_pattern(action) { - // in all examples below, we have to allow for '--' args - // which are represented as '-' in the pattern - let nargs = action.nargs - let nargs_pattern - - // the default (None) is assumed to be a single argument - if (nargs === undefined) { - nargs_pattern = '(-*A-*)' - - // allow zero or one arguments - } else if (nargs === OPTIONAL) { - nargs_pattern = '(-*A?-*)' - - // allow zero or more arguments - } else if (nargs === ZERO_OR_MORE) { - nargs_pattern = '(-*[A-]*)' - - // allow one or more arguments - } else if (nargs === ONE_OR_MORE) { - nargs_pattern = '(-*A[A-]*)' - - // allow any number of options or arguments - } else if (nargs === REMAINDER) { - nargs_pattern = '([-AO]*)' - - // allow one argument followed by any number of options or arguments - } else if (nargs === PARSER) { - nargs_pattern = '(-*A[-AO]*)' - - // suppress action, like nargs=0 - } else if (nargs === SUPPRESS) { - nargs_pattern = '(-*-*)' - - // all others should be integers - } else { - nargs_pattern = sub('(-*%s-*)', 'A'.repeat(nargs).split('').join('-*')) - } - - // if this is an optional action, -- is not allowed - if (action.option_strings.length) { - nargs_pattern = nargs_pattern.replace(/-\*/g, '') - nargs_pattern = nargs_pattern.replace(/-/g, '') - } - - // return the pattern - return nargs_pattern - } - - // ======================== - // Alt command line argument parsing, allowing free intermix - // ======================== - - parse_intermixed_args(args = undefined, namespace = undefined) { - let argv - [ args, argv ] = this.parse_known_intermixed_args(args, namespace) - if (argv.length) { - let msg = 'unrecognized arguments: %s' - this.error(sub(msg, argv.join(' '))) - } - return args - } - - parse_known_intermixed_args(args = undefined, namespace = undefined) { - // returns a namespace and list of extras - // - // positional can be freely intermixed with optionals. optionals are - // first parsed with all positional arguments deactivated. The 'extras' - // are then parsed. If the parser definition is incompatible with the - // intermixed assumptions (e.g. use of REMAINDER, subparsers) a - // TypeError is raised. - // - // positionals are 'deactivated' by setting nargs and default to - // SUPPRESS. This blocks the addition of that positional to the - // namespace - - let extras - let positionals = this._get_positional_actions() - let a = positionals.filter(action => [ PARSER, REMAINDER ].includes(action.nargs)) - if (a.length) { - throw new TypeError(sub('parse_intermixed_args: positional arg' + - ' with nargs=%s', a[0].nargs)) - } - - for (let group of this._mutually_exclusive_groups) { - for (let action of group._group_actions) { - if (positionals.includes(action)) { - throw new TypeError('parse_intermixed_args: positional in' + - ' mutuallyExclusiveGroup') - } - } - } - - let save_usage - try { - save_usage = this.usage - let remaining_args - try { - if (this.usage === undefined) { - // capture the full usage for use in error messages - this.usage = this.format_usage().slice(7) - } - for (let action of positionals) { - // deactivate positionals - action.save_nargs = action.nargs - // action.nargs = 0 - action.nargs = SUPPRESS - action.save_default = action.default - action.default = SUPPRESS - } - [ namespace, remaining_args ] = this.parse_known_args(args, - namespace) - for (let action of positionals) { - // remove the empty positional values from namespace - let attr = getattr(namespace, action.dest) - if (Array.isArray(attr) && attr.length === 0) { - // eslint-disable-next-line no-console - console.warn(sub('Do not expect %s in %s', action.dest, namespace)) - delattr(namespace, action.dest) - } - } - } finally { - // restore nargs and usage before exiting - for (let action of positionals) { - action.nargs = action.save_nargs - action.default = action.save_default - } - } - let optionals = this._get_optional_actions() - try { - // parse positionals. optionals aren't normally required, but - // they could be, so make sure they aren't. - for (let action of optionals) { - action.save_required = action.required - action.required = false - } - for (let group of this._mutually_exclusive_groups) { - group.save_required = group.required - group.required = false - } - [ namespace, extras ] = this.parse_known_args(remaining_args, - namespace) - } finally { - // restore parser values before exiting - for (let action of optionals) { - action.required = action.save_required - } - for (let group of this._mutually_exclusive_groups) { - group.required = group.save_required - } - } - } finally { - this.usage = save_usage - } - return [ namespace, extras ] - } - - // ======================== - // Value conversion methods - // ======================== - _get_values(action, arg_strings) { - // for everything but PARSER, REMAINDER args, strip out first '--' - if (![PARSER, REMAINDER].includes(action.nargs)) { - try { - _array_remove(arg_strings, '--') - } catch (err) {} - } - - let value - // optional argument produces a default when not present - if (!arg_strings.length && action.nargs === OPTIONAL) { - if (action.option_strings.length) { - value = action.const - } else { - value = action.default - } - if (typeof value === 'string') { - value = this._get_value(action, value) - this._check_value(action, value) - } - - // when nargs='*' on a positional, if there were no command-line - // args, use the default if it is anything other than None - } else if (!arg_strings.length && action.nargs === ZERO_OR_MORE && - !action.option_strings.length) { - if (action.default !== undefined) { - value = action.default - } else { - value = arg_strings - } - this._check_value(action, value) - - // single argument or optional argument produces a single value - } else if (arg_strings.length === 1 && [undefined, OPTIONAL].includes(action.nargs)) { - let arg_string = arg_strings[0] - value = this._get_value(action, arg_string) - this._check_value(action, value) - - // REMAINDER arguments convert all values, checking none - } else if (action.nargs === REMAINDER) { - value = arg_strings.map(v => this._get_value(action, v)) - - // PARSER arguments convert all values, but check only the first - } else if (action.nargs === PARSER) { - value = arg_strings.map(v => this._get_value(action, v)) - this._check_value(action, value[0]) - - // SUPPRESS argument does not put anything in the namespace - } else if (action.nargs === SUPPRESS) { - value = SUPPRESS - - // all other types of nargs produce a list - } else { - value = arg_strings.map(v => this._get_value(action, v)) - for (let v of value) { - this._check_value(action, v) - } - } - - // return the converted value - return value - } - - _get_value(action, arg_string) { - let type_func = this._registry_get('type', action.type, action.type) - if (typeof type_func !== 'function') { - let msg = '%r is not callable' - throw new ArgumentError(action, sub(msg, type_func)) - } - - // convert the value to the appropriate type - let result - try { - try { - result = type_func(arg_string) - } catch (err) { - // Dear TC39, why would you ever consider making es6 classes not callable? - // We had one universal interface, [[Call]], which worked for anything - // (with familiar this-instanceof guard for classes). Now we have two. - if (err instanceof TypeError && - /Class constructor .* cannot be invoked without 'new'/.test(err.message)) { - // eslint-disable-next-line new-cap - result = new type_func(arg_string) - } else { - throw err - } - } - - } catch (err) { - // ArgumentTypeErrors indicate errors - if (err instanceof ArgumentTypeError) { - //let name = getattr(action.type, 'name', repr(action.type)) - let msg = err.message - throw new ArgumentError(action, msg) - - // TypeErrors or ValueErrors also indicate errors - } else if (err instanceof TypeError) { - let name = getattr(action.type, 'name', repr(action.type)) - let args = {type: name, value: arg_string} - let msg = 'invalid %(type)s value: %(value)r' - throw new ArgumentError(action, sub(msg, args)) - } else { - throw err - } - } - - // return the converted value - return result - } - - _check_value(action, value) { - // converted value must be one of the choices (if specified) - if (action.choices !== undefined && !_choices_to_array(action.choices).includes(value)) { - let args = {value, - choices: _choices_to_array(action.choices).map(repr).join(', ')} - let msg = 'invalid choice: %(value)r (choose from %(choices)s)' - throw new ArgumentError(action, sub(msg, args)) - } - } - - // ======================= - // Help-formatting methods - // ======================= - format_usage() { - let formatter = this._get_formatter() - formatter.add_usage(this.usage, this._actions, - this._mutually_exclusive_groups) - return formatter.format_help() - } - - format_help() { - let formatter = this._get_formatter() - - // usage - formatter.add_usage(this.usage, this._actions, - this._mutually_exclusive_groups) - - // description - formatter.add_text(this.description) - - // positionals, optionals and user-defined groups - for (let action_group of this._action_groups) { - formatter.start_section(action_group.title) - formatter.add_text(action_group.description) - formatter.add_arguments(action_group._group_actions) - formatter.end_section() - } - - // epilog - formatter.add_text(this.epilog) - - // determine help from format above - return formatter.format_help() - } - - _get_formatter() { - // eslint-disable-next-line new-cap - return new this.formatter_class({ prog: this.prog }) - } - - // ===================== - // Help-printing methods - // ===================== - print_usage(file = undefined) { - if (file === undefined) file = process.stdout - this._print_message(this.format_usage(), file) - } - - print_help(file = undefined) { - if (file === undefined) file = process.stdout - this._print_message(this.format_help(), file) - } - - _print_message(message, file = undefined) { - if (message) { - if (file === undefined) file = process.stderr - file.write(message) - } - } - - // =============== - // Exiting methods - // =============== - exit(status = 0, message = undefined) { - if (message) { - this._print_message(message, process.stderr) - } - process.exit(status) - } - - error(message) { - /* - * error(message: string) - * - * Prints a usage message incorporating the message to stderr and - * exits. - * - * If you override this in a subclass, it should not return -- it - * should either exit or raise an exception. - */ - - // LEGACY (v1 compatibility), debug mode - if (this.debug === true) throw new Error(message) - // end - this.print_usage(process.stderr) - let args = {prog: this.prog, message: message} - this.exit(2, sub('%(prog)s: error: %(message)s\n', args)) - } -})) - - -module.exports = { - ArgumentParser, - ArgumentError, - ArgumentTypeError, - BooleanOptionalAction, - FileType, - HelpFormatter, - ArgumentDefaultsHelpFormatter, - RawDescriptionHelpFormatter, - RawTextHelpFormatter, - MetavarTypeHelpFormatter, - Namespace, - Action, - ONE_OR_MORE, - OPTIONAL, - PARSER, - REMAINDER, - SUPPRESS, - ZERO_OR_MORE -} - -// LEGACY (v1 compatibility), Const alias -Object.defineProperty(module.exports, 'Const', { - get() { - let result = {} - Object.entries({ ONE_OR_MORE, OPTIONAL, PARSER, REMAINDER, SUPPRESS, ZERO_OR_MORE }).forEach(([ n, v ]) => { - Object.defineProperty(result, n, { - get() { - deprecate(n, sub('use argparse.%s instead of argparse.Const.%s', n, n)) - return v - } - }) - }) - Object.entries({ _UNRECOGNIZED_ARGS_ATTR }).forEach(([ n, v ]) => { - Object.defineProperty(result, n, { - get() { - deprecate(n, sub('argparse.Const.%s is an internal symbol and will no longer be available', n)) - return v - } - }) - }) - return result - }, - enumerable: false -}) -// end diff --git a/node_modules/lv_font_conv/node_modules/argparse/lib/sub.js b/node_modules/lv_font_conv/node_modules/argparse/lib/sub.js deleted file mode 100644 index e3eb3215..00000000 --- a/node_modules/lv_font_conv/node_modules/argparse/lib/sub.js +++ /dev/null @@ -1,67 +0,0 @@ -// Limited implementation of python % string operator, supports only %s and %r for now -// (other formats are not used here, but may appear in custom templates) - -'use strict' - -const { inspect } = require('util') - - -module.exports = function sub(pattern, ...values) { - let regex = /%(?:(%)|(-)?(\*)?(?:\((\w+)\))?([A-Za-z]))/g - - let result = pattern.replace(regex, function (_, is_literal, is_left_align, is_padded, name, format) { - if (is_literal) return '%' - - let padded_count = 0 - if (is_padded) { - if (values.length === 0) throw new TypeError('not enough arguments for format string') - padded_count = values.shift() - if (!Number.isInteger(padded_count)) throw new TypeError('* wants int') - } - - let str - if (name !== undefined) { - let dict = values[0] - if (typeof dict !== 'object' || dict === null) throw new TypeError('format requires a mapping') - if (!(name in dict)) throw new TypeError(`no such key: '${name}'`) - str = dict[name] - } else { - if (values.length === 0) throw new TypeError('not enough arguments for format string') - str = values.shift() - } - - switch (format) { - case 's': - str = String(str) - break - case 'r': - str = inspect(str) - break - case 'd': - case 'i': - if (typeof str !== 'number') { - throw new TypeError(`%${format} format: a number is required, not ${typeof str}`) - } - str = String(str.toFixed(0)) - break - default: - throw new TypeError(`unsupported format character '${format}'`) - } - - if (padded_count > 0) { - return is_left_align ? str.padEnd(padded_count) : str.padStart(padded_count) - } else { - return str - } - }) - - if (values.length) { - if (values.length === 1 && typeof values[0] === 'object' && values[0] !== null) { - // mapping - } else { - throw new TypeError('not all arguments converted during string formatting') - } - } - - return result -} diff --git a/node_modules/lv_font_conv/node_modules/argparse/lib/textwrap.js b/node_modules/lv_font_conv/node_modules/argparse/lib/textwrap.js deleted file mode 100644 index 23d51cdb..00000000 --- a/node_modules/lv_font_conv/node_modules/argparse/lib/textwrap.js +++ /dev/null @@ -1,440 +0,0 @@ -// Partial port of python's argparse module, version 3.9.0 (only wrap and fill functions): -// https://github.com/python/cpython/blob/v3.9.0b4/Lib/textwrap.py - -'use strict' - -/* - * Text wrapping and filling. - */ - -// Copyright (C) 1999-2001 Gregory P. Ward. -// Copyright (C) 2002, 2003 Python Software Foundation. -// Copyright (C) 2020 argparse.js authors -// Originally written by Greg Ward - -// Hardcode the recognized whitespace characters to the US-ASCII -// whitespace characters. The main reason for doing this is that -// some Unicode spaces (like \u00a0) are non-breaking whitespaces. -// -// This less funky little regex just split on recognized spaces. E.g. -// "Hello there -- you goof-ball, use the -b option!" -// splits into -// Hello/ /there/ /--/ /you/ /goof-ball,/ /use/ /the/ /-b/ /option!/ -const wordsep_simple_re = /([\t\n\x0b\x0c\r ]+)/ - -class TextWrapper { - /* - * Object for wrapping/filling text. The public interface consists of - * the wrap() and fill() methods; the other methods are just there for - * subclasses to override in order to tweak the default behaviour. - * If you want to completely replace the main wrapping algorithm, - * you'll probably have to override _wrap_chunks(). - * - * Several instance attributes control various aspects of wrapping: - * width (default: 70) - * the maximum width of wrapped lines (unless break_long_words - * is false) - * initial_indent (default: "") - * string that will be prepended to the first line of wrapped - * output. Counts towards the line's width. - * subsequent_indent (default: "") - * string that will be prepended to all lines save the first - * of wrapped output; also counts towards each line's width. - * expand_tabs (default: true) - * Expand tabs in input text to spaces before further processing. - * Each tab will become 0 .. 'tabsize' spaces, depending on its position - * in its line. If false, each tab is treated as a single character. - * tabsize (default: 8) - * Expand tabs in input text to 0 .. 'tabsize' spaces, unless - * 'expand_tabs' is false. - * replace_whitespace (default: true) - * Replace all whitespace characters in the input text by spaces - * after tab expansion. Note that if expand_tabs is false and - * replace_whitespace is true, every tab will be converted to a - * single space! - * fix_sentence_endings (default: false) - * Ensure that sentence-ending punctuation is always followed - * by two spaces. Off by default because the algorithm is - * (unavoidably) imperfect. - * break_long_words (default: true) - * Break words longer than 'width'. If false, those words will not - * be broken, and some lines might be longer than 'width'. - * break_on_hyphens (default: true) - * Allow breaking hyphenated words. If true, wrapping will occur - * preferably on whitespaces and right after hyphens part of - * compound words. - * drop_whitespace (default: true) - * Drop leading and trailing whitespace from lines. - * max_lines (default: None) - * Truncate wrapped lines. - * placeholder (default: ' [...]') - * Append to the last line of truncated text. - */ - - constructor(options = {}) { - let { - width = 70, - initial_indent = '', - subsequent_indent = '', - expand_tabs = true, - replace_whitespace = true, - fix_sentence_endings = false, - break_long_words = true, - drop_whitespace = true, - break_on_hyphens = true, - tabsize = 8, - max_lines = undefined, - placeholder=' [...]' - } = options - - this.width = width - this.initial_indent = initial_indent - this.subsequent_indent = subsequent_indent - this.expand_tabs = expand_tabs - this.replace_whitespace = replace_whitespace - this.fix_sentence_endings = fix_sentence_endings - this.break_long_words = break_long_words - this.drop_whitespace = drop_whitespace - this.break_on_hyphens = break_on_hyphens - this.tabsize = tabsize - this.max_lines = max_lines - this.placeholder = placeholder - } - - - // -- Private methods ----------------------------------------------- - // (possibly useful for subclasses to override) - - _munge_whitespace(text) { - /* - * _munge_whitespace(text : string) -> string - * - * Munge whitespace in text: expand tabs and convert all other - * whitespace characters to spaces. Eg. " foo\\tbar\\n\\nbaz" - * becomes " foo bar baz". - */ - if (this.expand_tabs) { - text = text.replace(/\t/g, ' '.repeat(this.tabsize)) // not strictly correct in js - } - if (this.replace_whitespace) { - text = text.replace(/[\t\n\x0b\x0c\r]/g, ' ') - } - return text - } - - _split(text) { - /* - * _split(text : string) -> [string] - * - * Split the text to wrap into indivisible chunks. Chunks are - * not quite the same as words; see _wrap_chunks() for full - * details. As an example, the text - * Look, goof-ball -- use the -b option! - * breaks into the following chunks: - * 'Look,', ' ', 'goof-', 'ball', ' ', '--', ' ', - * 'use', ' ', 'the', ' ', '-b', ' ', 'option!' - * if break_on_hyphens is True, or in: - * 'Look,', ' ', 'goof-ball', ' ', '--', ' ', - * 'use', ' ', 'the', ' ', '-b', ' ', option!' - * otherwise. - */ - let chunks = text.split(wordsep_simple_re) - chunks = chunks.filter(Boolean) - return chunks - } - - _handle_long_word(reversed_chunks, cur_line, cur_len, width) { - /* - * _handle_long_word(chunks : [string], - * cur_line : [string], - * cur_len : int, width : int) - * - * Handle a chunk of text (most likely a word, not whitespace) that - * is too long to fit in any line. - */ - // Figure out when indent is larger than the specified width, and make - // sure at least one character is stripped off on every pass - let space_left - if (width < 1) { - space_left = 1 - } else { - space_left = width - cur_len - } - - // If we're allowed to break long words, then do so: put as much - // of the next chunk onto the current line as will fit. - if (this.break_long_words) { - cur_line.push(reversed_chunks[reversed_chunks.length - 1].slice(0, space_left)) - reversed_chunks[reversed_chunks.length - 1] = reversed_chunks[reversed_chunks.length - 1].slice(space_left) - - // Otherwise, we have to preserve the long word intact. Only add - // it to the current line if there's nothing already there -- - // that minimizes how much we violate the width constraint. - } else if (!cur_line) { - cur_line.push(...reversed_chunks.pop()) - } - - // If we're not allowed to break long words, and there's already - // text on the current line, do nothing. Next time through the - // main loop of _wrap_chunks(), we'll wind up here again, but - // cur_len will be zero, so the next line will be entirely - // devoted to the long word that we can't handle right now. - } - - _wrap_chunks(chunks) { - /* - * _wrap_chunks(chunks : [string]) -> [string] - * - * Wrap a sequence of text chunks and return a list of lines of - * length 'self.width' or less. (If 'break_long_words' is false, - * some lines may be longer than this.) Chunks correspond roughly - * to words and the whitespace between them: each chunk is - * indivisible (modulo 'break_long_words'), but a line break can - * come between any two chunks. Chunks should not have internal - * whitespace; ie. a chunk is either all whitespace or a "word". - * Whitespace chunks will be removed from the beginning and end of - * lines, but apart from that whitespace is preserved. - */ - let lines = [] - let indent - if (this.width <= 0) { - throw Error(`invalid width ${this.width} (must be > 0)`) - } - if (this.max_lines !== undefined) { - if (this.max_lines > 1) { - indent = this.subsequent_indent - } else { - indent = this.initial_indent - } - if (indent.length + this.placeholder.trimStart().length > this.width) { - throw Error('placeholder too large for max width') - } - } - - // Arrange in reverse order so items can be efficiently popped - // from a stack of chucks. - chunks = chunks.reverse() - - while (chunks.length > 0) { - - // Start the list of chunks that will make up the current line. - // cur_len is just the length of all the chunks in cur_line. - let cur_line = [] - let cur_len = 0 - - // Figure out which static string will prefix this line. - let indent - if (lines) { - indent = this.subsequent_indent - } else { - indent = this.initial_indent - } - - // Maximum width for this line. - let width = this.width - indent.length - - // First chunk on line is whitespace -- drop it, unless this - // is the very beginning of the text (ie. no lines started yet). - if (this.drop_whitespace && chunks[chunks.length - 1].trim() === '' && lines.length > 0) { - chunks.pop() - } - - while (chunks.length > 0) { - let l = chunks[chunks.length - 1].length - - // Can at least squeeze this chunk onto the current line. - if (cur_len + l <= width) { - cur_line.push(chunks.pop()) - cur_len += l - - // Nope, this line is full. - } else { - break - } - } - - // The current line is full, and the next chunk is too big to - // fit on *any* line (not just this one). - if (chunks.length && chunks[chunks.length - 1].length > width) { - this._handle_long_word(chunks, cur_line, cur_len, width) - cur_len = cur_line.map(l => l.length).reduce((a, b) => a + b, 0) - } - - // If the last chunk on this line is all whitespace, drop it. - if (this.drop_whitespace && cur_line.length > 0 && cur_line[cur_line.length - 1].trim() === '') { - cur_len -= cur_line[cur_line.length - 1].length - cur_line.pop() - } - - if (cur_line) { - if (this.max_lines === undefined || - lines.length + 1 < this.max_lines || - (chunks.length === 0 || - this.drop_whitespace && - chunks.length === 1 && - !chunks[0].trim()) && cur_len <= width) { - // Convert current line back to a string and store it in - // list of all lines (return value). - lines.push(indent + cur_line.join('')) - } else { - let had_break = false - while (cur_line) { - if (cur_line[cur_line.length - 1].trim() && - cur_len + this.placeholder.length <= width) { - cur_line.push(this.placeholder) - lines.push(indent + cur_line.join('')) - had_break = true - break - } - cur_len -= cur_line[-1].length - cur_line.pop() - } - if (!had_break) { - if (lines) { - let prev_line = lines[lines.length - 1].trimEnd() - if (prev_line.length + this.placeholder.length <= - this.width) { - lines[lines.length - 1] = prev_line + this.placeholder - break - } - } - lines.push(indent + this.placeholder.lstrip()) - } - break - } - } - } - - return lines - } - - _split_chunks(text) { - text = this._munge_whitespace(text) - return this._split(text) - } - - // -- Public interface ---------------------------------------------- - - wrap(text) { - /* - * wrap(text : string) -> [string] - * - * Reformat the single paragraph in 'text' so it fits in lines of - * no more than 'self.width' columns, and return a list of wrapped - * lines. Tabs in 'text' are expanded with string.expandtabs(), - * and all other whitespace characters (including newline) are - * converted to space. - */ - let chunks = this._split_chunks(text) - // not implemented in js - //if (this.fix_sentence_endings) { - // this._fix_sentence_endings(chunks) - //} - return this._wrap_chunks(chunks) - } - - fill(text) { - /* - * fill(text : string) -> string - * - * Reformat the single paragraph in 'text' to fit in lines of no - * more than 'self.width' columns, and return a new string - * containing the entire wrapped paragraph. - */ - return this.wrap(text).join('\n') - } -} - - -// -- Convenience interface --------------------------------------------- - -function wrap(text, options = {}) { - /* - * Wrap a single paragraph of text, returning a list of wrapped lines. - * - * Reformat the single paragraph in 'text' so it fits in lines of no - * more than 'width' columns, and return a list of wrapped lines. By - * default, tabs in 'text' are expanded with string.expandtabs(), and - * all other whitespace characters (including newline) are converted to - * space. See TextWrapper class for available keyword args to customize - * wrapping behaviour. - */ - let { width = 70, ...kwargs } = options - let w = new TextWrapper(Object.assign({ width }, kwargs)) - return w.wrap(text) -} - -function fill(text, options = {}) { - /* - * Fill a single paragraph of text, returning a new string. - * - * Reformat the single paragraph in 'text' to fit in lines of no more - * than 'width' columns, and return a new string containing the entire - * wrapped paragraph. As with wrap(), tabs are expanded and other - * whitespace characters converted to space. See TextWrapper class for - * available keyword args to customize wrapping behaviour. - */ - let { width = 70, ...kwargs } = options - let w = new TextWrapper(Object.assign({ width }, kwargs)) - return w.fill(text) -} - -// -- Loosely related functionality ------------------------------------- - -let _whitespace_only_re = /^[ \t]+$/mg -let _leading_whitespace_re = /(^[ \t]*)(?:[^ \t\n])/mg - -function dedent(text) { - /* - * Remove any common leading whitespace from every line in `text`. - * - * This can be used to make triple-quoted strings line up with the left - * edge of the display, while still presenting them in the source code - * in indented form. - * - * Note that tabs and spaces are both treated as whitespace, but they - * are not equal: the lines " hello" and "\\thello" are - * considered to have no common leading whitespace. - * - * Entirely blank lines are normalized to a newline character. - */ - // Look for the longest leading string of spaces and tabs common to - // all lines. - let margin = undefined - text = text.replace(_whitespace_only_re, '') - let indents = text.match(_leading_whitespace_re) || [] - for (let indent of indents) { - indent = indent.slice(0, -1) - - if (margin === undefined) { - margin = indent - - // Current line more deeply indented than previous winner: - // no change (previous winner is still on top). - } else if (indent.startsWith(margin)) { - // pass - - // Current line consistent with and no deeper than previous winner: - // it's the new winner. - } else if (margin.startsWith(indent)) { - margin = indent - - // Find the largest common whitespace between current line and previous - // winner. - } else { - for (let i = 0; i < margin.length && i < indent.length; i++) { - if (margin[i] !== indent[i]) { - margin = margin.slice(0, i) - break - } - } - } - } - - if (margin) { - text = text.replace(new RegExp('^' + margin, 'mg'), '') - } - return text -} - -module.exports = { wrap, fill, dedent } diff --git a/node_modules/lv_font_conv/node_modules/argparse/package.json b/node_modules/lv_font_conv/node_modules/argparse/package.json deleted file mode 100644 index 22fb0a3e..00000000 --- a/node_modules/lv_font_conv/node_modules/argparse/package.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "_args": [ - [ - "argparse@2.0.1", - "/home/vitaly/Dropbox/Coding/lv_font_conv" - ] - ], - "_from": "argparse@2.0.1", - "_id": "argparse@2.0.1", - "_inBundle": false, - "_integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "_location": "/argparse", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "argparse@2.0.1", - "name": "argparse", - "escapedName": "argparse", - "rawSpec": "2.0.1", - "saveSpec": null, - "fetchSpec": "2.0.1" - }, - "_requiredBy": [ - "/", - "/mocha/js-yaml" - ], - "_resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "_spec": "2.0.1", - "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", - "bugs": { - "url": "https://github.com/nodeca/argparse/issues" - }, - "description": "CLI arguments parser. Native port of python's argparse.", - "devDependencies": { - "@babel/eslint-parser": "^7.11.0", - "@babel/plugin-syntax-class-properties": "^7.10.4", - "eslint": "^7.5.0", - "mocha": "^8.0.1", - "nyc": "^15.1.0" - }, - "files": [ - "argparse.js", - "lib/" - ], - "homepage": "https://github.com/nodeca/argparse#readme", - "keywords": [ - "cli", - "parser", - "argparse", - "option", - "args" - ], - "license": "Python-2.0", - "main": "argparse.js", - "name": "argparse", - "repository": { - "type": "git", - "url": "git+https://github.com/nodeca/argparse.git" - }, - "scripts": { - "coverage": "npm run test && nyc report --reporter html", - "lint": "eslint .", - "test": "npm run lint && nyc mocha" - }, - "version": "2.0.1" -} diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/.github/workflows/ci.yml b/node_modules/lv_font_conv/node_modules/bit-buffer/.github/workflows/ci.yml deleted file mode 100644 index 8f0f168b..00000000 --- a/node_modules/lv_font_conv/node_modules/bit-buffer/.github/workflows/ci.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Node.js CI - -on: [push, pull_request] - -jobs: - test: - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [10.x, 12.x, 14.x, 15.x] - - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: npm ci - - run: npm test - - lint: - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [10.x, 12.x, 14.x, 15.x] - - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: npm ci - - run: npm run lint diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/LICENSE b/node_modules/lv_font_conv/node_modules/bit-buffer/LICENSE deleted file mode 100644 index 9194ecce..00000000 --- a/node_modules/lv_font_conv/node_modules/bit-buffer/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 bit-buffer developers - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/README.md b/node_modules/lv_font_conv/node_modules/bit-buffer/README.md deleted file mode 100644 index 3ecf4ab7..00000000 --- a/node_modules/lv_font_conv/node_modules/bit-buffer/README.md +++ /dev/null @@ -1,148 +0,0 @@ -# BitBuffer - -![Node.js CI](https://github.com/inolen/bit-buffer/workflows/Node.js%20CI/badge.svg) - -BitBuffer provides two objects, `BitView` and `BitStream`. `BitView` is a wrapper for ArrayBuffers, similar to JavaScript's [DataView](https://developer.mozilla.org/en-US/docs/JavaScript/Typed_arrays/DataView), but with support for bit-level reads and writes. `BitStream` is a wrapper for a `BitView` used to help maintain your current buffer position, as well as to provide higher-level read / write operations such as for ASCII strings. - -## BitView - -### Attributes - -```javascript -bb.buffer // Underlying ArrayBuffer. -``` - -```javascript -bb.bigEndian = true; // Switch to big endian (default is little) -``` - -### Methods - -#### BitView(buffer, optional byteOffset, optional byteLength) - -Default constructor, takes in a single argument of an ArrayBuffer. Optional are the `byteOffset` and `byteLength` arguments to offset and truncate the view's representation of the buffer. - -### getBits(offset, bits, signed) - -Reads `bits` number of bits starting at `offset`, twiddling the bits appropriately to return a proper 32-bit signed or unsigned value. NOTE: While JavaScript numbers are 64-bit floating-point values, we don't bother with anything other than the first 32 bits. - -### getInt8, getUint8, getInt16, getUint16, getInt32, getUint32(offset) - -Shortcuts for getBits, setting the correct `bits` / `signed` values. - -### getFloat32(offset) - -Gets 32 bits from `offset`, and coerces and returns as a proper float32 value. - -### getFloat64(offset) - -Gets 64 bits from `offset`, and coerces and returns as a proper float64 value. - -### setBits(offset, value, bits) - -Sets `bits` number of bits at `offset`. - -### setInt8, setUint8, setInt16, setUint16, setInt32, setUint32(offset) - -Shortcuts for setBits, setting the correct `bits` count. - -### setFloat32(offset) - -Coerces a float32 to uint32 and sets at `offset`. - -### setFloat64(offset) - -Coerces a float64 to two uint32s and sets at `offset`. - - -## BitStream - -### Attributes - -```javascript -bb.byteIndex; // Get current index in bytes. -bb.byteIndex = 0; // Set current index in bytes. -``` - -```javascript -bb.view; // Underlying BitView -``` - -```javascript -bb.length; // Get the length of the stream in bits -``` - -```javascript -bb.bitsLeft; // The number of bits left in the stream -``` - -```javascript -bb.index; // Get the current index in bits -bb.index = 0// Set the current index in bits -``` - -```javascript -bb.bigEndian = true; // Switch to big endian (default is little) -``` - -### Methods - -#### BitStream(view) - -Default constructor, takes in a single argument of a `BitView`, `ArrayBuffer` or node `Buffer`. - -#### BitSteam(buffer, optional byteOffset, optional byteLength) - -Shortcut constructor that initializes a new `BitView(buffer, byteOffset, byteLength)` for the stream to use. - -#### readBits(bits, signed) - -Returns `bits` numbers of bits from the view at the current index, updating the index. - -#### writeBits(value, bits) - -Sets `bits` numbers of bits from `value` in the view at the current index, updating the index. - -#### readUint8(), readUint16(), readUint32(), readInt8(), readInt16(), readInt32() - -Read a 8, 16 or 32 bits (unsigned) integer at the current index, updating the index. - -#### writeUint8(value), writeUint16(value), writeUint32(value), writeInt8(value), writeInt16(value), writeInt32(value) - -Write 8, 16 or 32 bits from `value` as (unsigned) integer at the current index, updating the index. - -#### readFloat32(), readFloat64() - -Read a 32 or 64 bit floating point number at the current index, updating the index. - -#### writeFloat32(value), writeFloat64() - -Set 32 or 64 bits from `value` as floating point value at the current index, updating the index. - -#### readBoolean() - -Read a single bit from the view at the current index, updating the index. - -#### writeBoolean(value) - -Write a single bit to the view at the current index, updating the index. - -#### readASCIIString(optional bytes), readUTF8String(optional bytes) - -Reads bytes from the underlying view at the current index until either `bytes` count is reached or a 0x00 terminator is reached. - -#### writeASCIIString(string, optional bytes), writeUTF8String(string, optional bytes) - -Writes a string followed by a NULL character to the underlying view starting at the current index. If the string is longer than `bytes` it will be truncated, and if it is shorter 0x00 will be written in its place. - -#### readBitStream(length) - -Create a new `BitStream` from the underlying view starting the the current index and a length of `length` bits. Updating the index of the existing `BitStream` - -#### readArrayBuffer(byteLength) - -Read `byteLength` bytes of data from the underlying view as `ArrayBuffer`, updating the index. - -## license - -MIT diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.d.ts b/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.d.ts deleted file mode 100644 index 72c4b13e..00000000 --- a/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.d.ts +++ /dev/null @@ -1,115 +0,0 @@ -declare module 'bit-buffer' { - import {Buffer} from 'buffer'; - - export class BitView { - constructor(buffer: ArrayBuffer | Buffer, byteLength?: number); - - readonly buffer: Buffer; - readonly byteLength: number; - bigEndian: boolean; - - getBits(offset: number, bits: number, signed?: boolean): number; - - getInt8(offset: number): number; - - getInt16(offset: number): number; - - getInt32(offset: number): number; - - getUint8(offset: number): number; - - getUint16(offset: number): number; - - getUint32(offset: number): number; - - getFloat32(offset: number): number; - - getFloat64(offset: number): number; - - setBits(offset: number, value: number, bits: number); - - setInt8(offset: number); - - setInt16(offset: number); - - setInt32(offset: number); - - setUint8(offset: number); - - setUint16(offset: number); - - setUint32(offset: number); - - setFloat32(offset: number, value: number); - - setFloat64(offset: number, value: number); - } - - export class BitStream { - constructor(source: ArrayBuffer | Buffer | BitView, byteOffset?: number, byteLength?: number) - - readonly length: number; - readonly bitsLeft: number; - readonly buffer: Buffer; - readonly view: BitView; - byteIndex: number; - index: number; - bigEndian: boolean; - - readBits(bits: number, signed?: boolean): number; - - writeBits(value: number, bits: number); - - readBoolean(): boolean; - - readInt8(): number; - - readUint8(): number; - - readInt16(): number; - - readUint16(): number; - - readInt32(): number; - - readUint32(): number; - - readFloat32(): number; - - readFloat64(): number; - - writeBoolean(value: boolean); - - writeInt8(value: number); - - writeUint8(value: number); - - writeInt16(value: number); - - writeUint16(value: number); - - writeInt32(value: number); - - writeUint32(value: number); - - writeFloat32(value: number); - - writeFloat64(value: number); - - readASCIIString(length?: number): string; - - readUTF8String(length?: number): string; - - writeASCIIString(data: string, length?: number); - - writeUTF8String(data: string, length?: number); - - readBitStream(length: number): BitStream; - - readArrayBuffer(byteLength: number): Uint8Array; - - writeBitStream(stream: BitStream, length?: number); - - writeArrayBuffer(buffer: BitStream, length?: number); - } -} diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.js b/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.js deleted file mode 100644 index 60c7f795..00000000 --- a/node_modules/lv_font_conv/node_modules/bit-buffer/bit-buffer.js +++ /dev/null @@ -1,502 +0,0 @@ -(function (root) { - -/********************************************************** - * - * BitView - * - * BitView provides a similar interface to the standard - * DataView, but with support for bit-level reads / writes. - * - **********************************************************/ -var BitView = function (source, byteOffset, byteLength) { - var isBuffer = source instanceof ArrayBuffer || - (typeof Buffer !== 'undefined' && source instanceof Buffer); - - if (!isBuffer) { - throw new Error('Must specify a valid ArrayBuffer or Buffer.'); - } - - byteOffset = byteOffset || 0; - byteLength = byteLength || source.byteLength /* ArrayBuffer */ || source.length /* Buffer */; - - this._view = new Uint8Array(source.buffer || source, byteOffset, byteLength); - - this.bigEndian = false; -}; - -// Used to massage fp values so we can operate on them -// at the bit level. -BitView._scratch = new DataView(new ArrayBuffer(8)); - -Object.defineProperty(BitView.prototype, 'buffer', { - get: function () { return typeof Buffer !== 'undefined' ? Buffer.from(this._view.buffer) : this._view.buffer; }, - enumerable: true, - configurable: false -}); - -Object.defineProperty(BitView.prototype, 'byteLength', { - get: function () { return this._view.length; }, - enumerable: true, - configurable: false -}); - -BitView.prototype._setBit = function (offset, on) { - if (on) { - this._view[offset >> 3] |= 1 << (offset & 7); - } else { - this._view[offset >> 3] &= ~(1 << (offset & 7)); - } -}; - -BitView.prototype.getBits = function (offset, bits, signed) { - var available = (this._view.length * 8 - offset); - - if (bits > available) { - throw new Error('Cannot get ' + bits + ' bit(s) from offset ' + offset + ', ' + available + ' available'); - } - - var value = 0; - for (var i = 0; i < bits;) { - var remaining = bits - i; - var bitOffset = offset & 7; - var currentByte = this._view[offset >> 3]; - - // the max number of bits we can read from the current byte - var read = Math.min(remaining, 8 - bitOffset); - - var mask, readBits; - if (this.bigEndian) { - // create a mask with the correct bit width - mask = ~(0xFF << read); - // shift the bits we want to the start of the byte and mask of the rest - readBits = (currentByte >> (8 - read - bitOffset)) & mask; - - value <<= read; - value |= readBits; - } else { - // create a mask with the correct bit width - mask = ~(0xFF << read); - // shift the bits we want to the start of the byte and mask off the rest - readBits = (currentByte >> bitOffset) & mask; - - value |= readBits << i; - } - - offset += read; - i += read; - } - - if (signed) { - // If we're not working with a full 32 bits, check the - // imaginary MSB for this bit count and convert to a - // valid 32-bit signed value if set. - if (bits !== 32 && value & (1 << (bits - 1))) { - value |= -1 ^ ((1 << bits) - 1); - } - - return value; - } - - return value >>> 0; -}; - -BitView.prototype.setBits = function (offset, value, bits) { - var available = (this._view.length * 8 - offset); - - if (bits > available) { - throw new Error('Cannot set ' + bits + ' bit(s) from offset ' + offset + ', ' + available + ' available'); - } - - for (var i = 0; i < bits;) { - var remaining = bits - i; - var bitOffset = offset & 7; - var byteOffset = offset >> 3; - var wrote = Math.min(remaining, 8 - bitOffset); - - var mask, writeBits, destMask; - if (this.bigEndian) { - // create a mask with the correct bit width - mask = ~(~0 << wrote); - // shift the bits we want to the start of the byte and mask of the rest - writeBits = (value >> (bits - i - wrote)) & mask; - - var destShift = 8 - bitOffset - wrote; - // destination mask to zero all the bits we're changing first - destMask = ~(mask << destShift); - - this._view[byteOffset] = - (this._view[byteOffset] & destMask) - | (writeBits << destShift); - - } else { - // create a mask with the correct bit width - mask = ~(0xFF << wrote); - // shift the bits we want to the start of the byte and mask of the rest - writeBits = value & mask; - value >>= wrote; - - // destination mask to zero all the bits we're changing first - destMask = ~(mask << bitOffset); - - this._view[byteOffset] = - (this._view[byteOffset] & destMask) - | (writeBits << bitOffset); - } - - offset += wrote; - i += wrote; - } -}; - -BitView.prototype.getBoolean = function (offset) { - return this.getBits(offset, 1, false) !== 0; -}; -BitView.prototype.getInt8 = function (offset) { - return this.getBits(offset, 8, true); -}; -BitView.prototype.getUint8 = function (offset) { - return this.getBits(offset, 8, false); -}; -BitView.prototype.getInt16 = function (offset) { - return this.getBits(offset, 16, true); -}; -BitView.prototype.getUint16 = function (offset) { - return this.getBits(offset, 16, false); -}; -BitView.prototype.getInt32 = function (offset) { - return this.getBits(offset, 32, true); -}; -BitView.prototype.getUint32 = function (offset) { - return this.getBits(offset, 32, false); -}; -BitView.prototype.getFloat32 = function (offset) { - BitView._scratch.setUint32(0, this.getUint32(offset)); - return BitView._scratch.getFloat32(0); -}; -BitView.prototype.getFloat64 = function (offset) { - BitView._scratch.setUint32(0, this.getUint32(offset)); - // DataView offset is in bytes. - BitView._scratch.setUint32(4, this.getUint32(offset+32)); - return BitView._scratch.getFloat64(0); -}; - -BitView.prototype.setBoolean = function (offset, value) { - this.setBits(offset, value ? 1 : 0, 1); -}; -BitView.prototype.setInt8 = -BitView.prototype.setUint8 = function (offset, value) { - this.setBits(offset, value, 8); -}; -BitView.prototype.setInt16 = -BitView.prototype.setUint16 = function (offset, value) { - this.setBits(offset, value, 16); -}; -BitView.prototype.setInt32 = -BitView.prototype.setUint32 = function (offset, value) { - this.setBits(offset, value, 32); -}; -BitView.prototype.setFloat32 = function (offset, value) { - BitView._scratch.setFloat32(0, value); - this.setBits(offset, BitView._scratch.getUint32(0), 32); -}; -BitView.prototype.setFloat64 = function (offset, value) { - BitView._scratch.setFloat64(0, value); - this.setBits(offset, BitView._scratch.getUint32(0), 32); - this.setBits(offset+32, BitView._scratch.getUint32(4), 32); -}; -BitView.prototype.getArrayBuffer = function (offset, byteLength) { - var buffer = new Uint8Array(byteLength); - for (var i = 0; i < byteLength; i++) { - buffer[i] = this.getUint8(offset + (i * 8)); - } - return buffer; -}; - -/********************************************************** - * - * BitStream - * - * Small wrapper for a BitView to maintain your position, - * as well as to handle reading / writing of string data - * to the underlying buffer. - * - **********************************************************/ -var reader = function (name, size) { - return function () { - if (this._index + size > this._length) { - throw new Error('Trying to read past the end of the stream'); - } - var val = this._view[name](this._index); - this._index += size; - return val; - }; -}; - -var writer = function (name, size) { - return function (value) { - this._view[name](this._index, value); - this._index += size; - }; -}; - -function readASCIIString(stream, bytes) { - return readString(stream, bytes, false); -} - -function readUTF8String(stream, bytes) { - return readString(stream, bytes, true); -} - -function readString(stream, bytes, utf8) { - if (bytes === 0) { - return ''; - } - var i = 0; - var chars = []; - var append = true; - var fixedLength = !!bytes; - if (!bytes) { - bytes = Math.floor((stream._length - stream._index) / 8); - } - - // Read while we still have space available, or until we've - // hit the fixed byte length passed in. - while (i < bytes) { - var c = stream.readUint8(); - - // Stop appending chars once we hit 0x00 - if (c === 0x00) { - append = false; - - // If we don't have a fixed length to read, break out now. - if (!fixedLength) { - break; - } - } - if (append) { - chars.push(c); - } - - i++; - } - - var string = String.fromCharCode.apply(null, chars); - if (utf8) { - try { - return decodeURIComponent(escape(string)); // https://stackoverflow.com/a/17192845 - } catch (e) { - return string; - } - } else { - return string; - } -} - -function writeASCIIString(stream, string, bytes) { - var length = bytes || string.length + 1; // + 1 for NULL - - for (var i = 0; i < length; i++) { - stream.writeUint8(i < string.length ? string.charCodeAt(i) : 0x00); - } -} - -function writeUTF8String(stream, string, bytes) { - var byteArray = stringToByteArray(string); - - var length = bytes || byteArray.length + 1; // + 1 for NULL - for (var i = 0; i < length; i++) { - stream.writeUint8(i < byteArray.length ? byteArray[i] : 0x00); - } -} - -function stringToByteArray(str) { // https://gist.github.com/volodymyr-mykhailyk/2923227 - var b = [], i, unicode; - for (i = 0; i < str.length; i++) { - unicode = str.charCodeAt(i); - // 0x00000000 - 0x0000007f -> 0xxxxxxx - if (unicode <= 0x7f) { - b.push(unicode); - // 0x00000080 - 0x000007ff -> 110xxxxx 10xxxxxx - } else if (unicode <= 0x7ff) { - b.push((unicode >> 6) | 0xc0); - b.push((unicode & 0x3F) | 0x80); - // 0x00000800 - 0x0000ffff -> 1110xxxx 10xxxxxx 10xxxxxx - } else if (unicode <= 0xffff) { - b.push((unicode >> 12) | 0xe0); - b.push(((unicode >> 6) & 0x3f) | 0x80); - b.push((unicode & 0x3f) | 0x80); - // 0x00010000 - 0x001fffff -> 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - } else { - b.push((unicode >> 18) | 0xf0); - b.push(((unicode >> 12) & 0x3f) | 0x80); - b.push(((unicode >> 6) & 0x3f) | 0x80); - b.push((unicode & 0x3f) | 0x80); - } - } - - return b; -} - -var BitStream = function (source, byteOffset, byteLength) { - var isBuffer = source instanceof ArrayBuffer || - (typeof Buffer !== 'undefined' && source instanceof Buffer); - - if (!(source instanceof BitView) && !isBuffer) { - throw new Error('Must specify a valid BitView, ArrayBuffer or Buffer'); - } - - if (isBuffer) { - this._view = new BitView(source, byteOffset, byteLength); - } else { - this._view = source; - } - - this._index = 0; - this._startIndex = 0; - this._length = this._view.byteLength * 8; -}; - -Object.defineProperty(BitStream.prototype, 'index', { - get: function () { return this._index - this._startIndex; }, - set: function (val) { this._index = val + this._startIndex; }, - enumerable: true, - configurable: true -}); - -Object.defineProperty(BitStream.prototype, 'length', { - get: function () { return this._length - this._startIndex; }, - set: function (val) { this._length = val + this._startIndex; }, - enumerable : true, - configurable: true -}); - -Object.defineProperty(BitStream.prototype, 'bitsLeft', { - get: function () { return this._length - this._index; }, - enumerable : true, - configurable: true -}); - -Object.defineProperty(BitStream.prototype, 'byteIndex', { - // Ceil the returned value, over compensating for the amount of - // bits written to the stream. - get: function () { return Math.ceil(this._index / 8); }, - set: function (val) { this._index = val * 8; }, - enumerable: true, - configurable: true -}); - -Object.defineProperty(BitStream.prototype, 'buffer', { - get: function () { return this._view.buffer; }, - enumerable: true, - configurable: false -}); - -Object.defineProperty(BitStream.prototype, 'view', { - get: function () { return this._view; }, - enumerable: true, - configurable: false -}); - -Object.defineProperty(BitStream.prototype, 'bigEndian', { - get: function () { return this._view.bigEndian; }, - set: function (val) { this._view.bigEndian = val; }, - enumerable: true, - configurable: false -}); - -BitStream.prototype.readBits = function (bits, signed) { - var val = this._view.getBits(this._index, bits, signed); - this._index += bits; - return val; -}; - -BitStream.prototype.writeBits = function (value, bits) { - this._view.setBits(this._index, value, bits); - this._index += bits; -}; - -BitStream.prototype.readBoolean = reader('getBoolean', 1); -BitStream.prototype.readInt8 = reader('getInt8', 8); -BitStream.prototype.readUint8 = reader('getUint8', 8); -BitStream.prototype.readInt16 = reader('getInt16', 16); -BitStream.prototype.readUint16 = reader('getUint16', 16); -BitStream.prototype.readInt32 = reader('getInt32', 32); -BitStream.prototype.readUint32 = reader('getUint32', 32); -BitStream.prototype.readFloat32 = reader('getFloat32', 32); -BitStream.prototype.readFloat64 = reader('getFloat64', 64); - -BitStream.prototype.writeBoolean = writer('setBoolean', 1); -BitStream.prototype.writeInt8 = writer('setInt8', 8); -BitStream.prototype.writeUint8 = writer('setUint8', 8); -BitStream.prototype.writeInt16 = writer('setInt16', 16); -BitStream.prototype.writeUint16 = writer('setUint16', 16); -BitStream.prototype.writeInt32 = writer('setInt32', 32); -BitStream.prototype.writeUint32 = writer('setUint32', 32); -BitStream.prototype.writeFloat32 = writer('setFloat32', 32); -BitStream.prototype.writeFloat64 = writer('setFloat64', 64); - -BitStream.prototype.readASCIIString = function (bytes) { - return readASCIIString(this, bytes); -}; - -BitStream.prototype.readUTF8String = function (bytes) { - return readUTF8String(this, bytes); -}; - -BitStream.prototype.writeASCIIString = function (string, bytes) { - writeASCIIString(this, string, bytes); -}; - -BitStream.prototype.writeUTF8String = function (string, bytes) { - writeUTF8String(this, string, bytes); -}; -BitStream.prototype.readBitStream = function(bitLength) { - var slice = new BitStream(this._view); - slice._startIndex = this._index; - slice._index = this._index; - slice.length = bitLength; - this._index += bitLength; - return slice; -}; - -BitStream.prototype.writeBitStream = function(stream, length) { - if (!length) { - length = stream.bitsLeft; - } - - var bitsToWrite; - while (length > 0) { - bitsToWrite = Math.min(length, 32); - this.writeBits(stream.readBits(bitsToWrite), bitsToWrite); - length -= bitsToWrite; - } -}; - -BitStream.prototype.readArrayBuffer = function(byteLength) { - var buffer = this._view.getArrayBuffer(this._index, byteLength); - this._index += (byteLength * 8); - return buffer; -}; - -BitStream.prototype.writeArrayBuffer = function(buffer, byteLength) { - this.writeBitStream(new BitStream(buffer), byteLength * 8); -}; - -// AMD / RequireJS -if (typeof define !== 'undefined' && define.amd) { - define(function () { - return { - BitView: BitView, - BitStream: BitStream - }; - }); -} -// Node.js -else if (typeof module !== 'undefined' && module.exports) { - module.exports = { - BitView: BitView, - BitStream: BitStream - }; -} - -}(this)); diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/package.json b/node_modules/lv_font_conv/node_modules/bit-buffer/package.json deleted file mode 100644 index f2e2d2ce..00000000 --- a/node_modules/lv_font_conv/node_modules/bit-buffer/package.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "_args": [ - [ - "bit-buffer@0.2.5", - "/home/vitaly/Dropbox/Coding/lv_font_conv" - ] - ], - "_from": "bit-buffer@0.2.5", - "_id": "bit-buffer@0.2.5", - "_inBundle": false, - "_integrity": "sha512-x1yGnmXvFg6e3DiyRztElbcn1bsCTFSoM/ncAzY62uE0JdTl5xlKJd0ooqLYoPbhdsnpehSIQrdIvclcZJYwiA==", - "_location": "/bit-buffer", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "bit-buffer@0.2.5", - "name": "bit-buffer", - "escapedName": "bit-buffer", - "rawSpec": "0.2.5", - "saveSpec": null, - "fetchSpec": "0.2.5" - }, - "_requiredBy": [ - "/" - ], - "_resolved": "https://registry.npmjs.org/bit-buffer/-/bit-buffer-0.2.5.tgz", - "_spec": "0.2.5", - "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", - "author": { - "name": "Anthony Pesch" - }, - "bugs": { - "url": "https://github.com/inolen/bit-buffer/issues" - }, - "contributors": [ - { - "name": "Robin Appelman" - } - ], - "description": "Bit-level reads and writes for ArrayBuffers", - "devDependencies": { - "@types/node": "^14.14.22", - "jshint": "^2.12.0", - "mocha": "^8.2.1" - }, - "directories": { - "test": "test" - }, - "gitHead": "cd4417237bed1f22dd5adfd8a6b961ea7234d9c9", - "homepage": "https://github.com/inolen/bit-buffer#readme", - "keywords": [ - "dataview", - "arraybuffer", - "bit", - "bits" - ], - "license": "MIT", - "main": "bit-buffer.js", - "name": "bit-buffer", - "repository": { - "type": "git", - "url": "git://github.com/inolen/bit-buffer.git" - }, - "scripts": { - "lint": "jshint bit-buffer.js", - "test": "mocha --ui tdd" - }, - "types": "./bit-buffer.d.ts", - "version": "0.2.5" -} diff --git a/node_modules/lv_font_conv/node_modules/bit-buffer/test.js b/node_modules/lv_font_conv/node_modules/bit-buffer/test.js deleted file mode 100644 index a53fc87b..00000000 --- a/node_modules/lv_font_conv/node_modules/bit-buffer/test.js +++ /dev/null @@ -1,628 +0,0 @@ -var assert = require('assert'), - BitView = require('./bit-buffer').BitView, - BitStream = require('./bit-buffer').BitStream; - -suite('BitBuffer', function () { - var array, bv, bsw, bsr; - - setup(function () { - array = new ArrayBuffer(64); - bv = new BitView(array); - bsw = new BitStream(bv); - // Test initializing straight from the array. - bsr = new BitStream(array); - }); - - test('Min / max signed 5 bits', function () { - var signed_max = (1 << 4) - 1; - - bsw.writeBits(signed_max, 5); - bsw.writeBits(-signed_max - 1, 5); - assert(bsr.readBits(5, true) === signed_max); - assert(bsr.readBits(5, true) === -signed_max - 1); - }); - - test('Min / max unsigned 5 bits', function () { - var unsigned_max = (1 << 5) - 1; - - bsw.writeBits(unsigned_max, 5); - bsw.writeBits(-unsigned_max, 5); - assert.equal(bsr.readBits(5), unsigned_max); - assert.equal(bsr.readBits(5), 1); - }); - - test('Min / max int8', function () { - var signed_max = 0x7F; - - bsw.writeInt8(signed_max); - bsw.writeInt8(-signed_max - 1); - assert.equal(bsr.readInt8(), signed_max); - assert.equal(bsr.readInt8(), -signed_max - 1); - }); - - test('Min / max uint8', function () { - var unsigned_max = 0xFF; - - bsw.writeUint8(unsigned_max); - bsw.writeUint8(-unsigned_max); - assert.equal(bsr.readUint8(), unsigned_max); - assert.equal(bsr.readUint8(), 1); - }); - - test('Min / max int16', function () { - var signed_max = 0x7FFF; - - bsw.writeInt16(signed_max); - bsw.writeInt16(-signed_max - 1); - assert.equal(bsr.readInt16(), signed_max); - assert.equal(bsr.readInt16(), -signed_max - 1); - }); - - test('Min / max uint16', function () { - var unsigned_max = 0xFFFF; - - bsw.writeUint16(unsigned_max); - bsw.writeUint16(-unsigned_max); - assert.equal(bsr.readUint16(), unsigned_max); - assert.equal(bsr.readUint16(), 1); - }); - - test('Min / max int32', function () { - var signed_max = 0x7FFFFFFF; - - bsw.writeInt32(signed_max); - bsw.writeInt32(-signed_max - 1); - assert.equal(bsr.readInt32(), signed_max); - assert.equal(bsr.readInt32(), -signed_max - 1); - }); - - test('Min / max uint32', function () { - var unsigned_max = 0xFFFFFFFF; - - bsw.writeUint32(unsigned_max); - bsw.writeUint32(-unsigned_max); - assert.equal(bsr.readUint32(), unsigned_max); - assert.equal(bsr.readUint32(), 1); - }); - - test('Unaligned reads', function () { - bsw.writeBits(13, 5); - bsw.writeUint8(0xFF); - bsw.writeBits(14, 5); - - assert.equal(bsr.readBits(5), 13); - assert.equal(bsr.readUint8(), 0xFF); - assert.equal(bsr.readBits(5), 14); - }); - - test('Min / max float32 (normal values)', function () { - var scratch = new DataView(new ArrayBuffer(8)); - - scratch.setUint32(0, 0x00800000); - scratch.setUint32(4, 0x7f7fffff); - - var min = scratch.getFloat32(0); - var max = scratch.getFloat32(4); - - bsw.writeFloat32(min); - bsw.writeFloat32(max); - - assert.equal(bsr.readFloat32(), min); - assert.equal(bsr.readFloat32(), max); - }); - - test('Min / max float64 (normal values)', function () { - var scratch = new DataView(new ArrayBuffer(16)); - - scratch.setUint32(0, 0x00100000); - scratch.setUint32(4, 0x00000000); - scratch.setUint32(8, 0x7fefffff); - scratch.setUint32(12, 0xffffffff); - - var min = scratch.getFloat64(0); - var max = scratch.getFloat64(8); - - bsw.writeFloat64(min); - bsw.writeFloat64(max); - - assert.equal(bsr.readFloat64(), min); - assert.equal(bsr.readFloat64(), max); - }); - - test('Overwrite previous value with 0', function () { - bv.setUint8(0, 13); - bv.setUint8(0, 0); - - assert.equal(bv.getUint8(0), 0); - }); - - test('Read / write ASCII string, fixed length', function () { - var str = 'foobar'; - var len = 16; - - bsw.writeASCIIString(str, len); - assert.equal(bsw.byteIndex, len); - - assert.equal(bsr.readASCIIString(len), str); - assert.equal(bsr.byteIndex, len); - }); - - test('Read / write ASCII string, unknown length', function () { - var str = 'foobar'; - - bsw.writeASCIIString(str); - assert.equal(bsw.byteIndex, str.length + 1); // +1 for 0x00 - - assert.equal(bsr.readASCIIString(), str); - assert.equal(bsr.byteIndex, str.length + 1); - }); - - test('Read ASCII string, 0 length', function () { - var str = 'foobar'; - - bsw.writeASCIIString(str); - assert.equal(bsw.byteIndex, str.length + 1); // +1 for 0x00 - - assert.equal(bsr.readASCIIString(0), ''); - assert.equal(bsr.byteIndex, 0); - }); - - test('Read overflow', function () { - var exception = false; - - try { - bsr.readASCIIString(128); - } catch (e) { - exception = true; - } - - assert(exception); - }); - - test('Write overflow', function () { - var exception = false; - - try { - bsw.writeASCIIString('foobar', 128); - } catch (e) { - exception = true; - } - - assert(exception); - }); - - test('Get boolean', function () { - bv.setUint8(0, 1); - - assert(bv.getBoolean(0)); - - bv.setUint8(0, 0); - assert(!bv.getBoolean(0)); - }); - - test('Set boolean', function () { - bv.setBoolean(0, true); - - assert(bv.getBoolean(0)); - - bv.setBoolean(0, false); - - assert(!bv.getBoolean(0)); - }); - - test('Read boolean', function () { - bv.setBits(0, 1, 1); - bv.setBits(1, 0, 1); - - assert(bsr.readBoolean()); - assert(!bsr.readBoolean()); - }); - - test('Write boolean', function () { - bsr.writeBoolean(true); - assert.equal(bv.getBits(0, 1, false), 1); - bsr.writeBoolean(false); - assert.equal(bv.getBits(1, 1, false), 0); - }); - - test('Read / write UTF8 string, only ASCII characters', function () { - var str = 'foobar'; - - bsw.writeUTF8String(str); - assert(bsw.byteIndex === str.length + 1); // +1 for 0x00 - - assert.equal(bsr.readUTF8String(), str); - assert.equal(bsr.byteIndex, str.length + 1); - }); - - test('Read / write UTF8 string, non ASCII characters', function () { - var str = '日本語'; - - var bytes = [ - 0xE6, - 0x97, - 0xA5, - 0xE6, - 0x9C, - 0xAC, - 0xE8, - 0xAA, - 0x9E - ]; - - bsw.writeUTF8String(str); - - for (var i = 0; i < bytes.length; i++) { - assert.equal(bytes[i], bv.getBits(i * 8, 8)); - } - - assert.equal(bsw.byteIndex, bytes.length + 1); // +1 for 0x00 - - assert.equal(str, bsr.readUTF8String()); - assert.equal(bsr.byteIndex, bytes.length + 1); - }); - - test('readBitStream', function () { - bsw.writeBits(0xF0, 8); //0b11110000 - bsw.writeBits(0xF1, 8); //0b11110001 - bsr.readBits(3); //offset - var slice = bsr.readBitStream(8); - assert.equal(slice.readBits(6), 0x3E); //0b111110 - assert.equal(9, slice._index); - assert.equal(6, slice.index); - assert.equal(8, slice.length); - assert.equal(2, slice.bitsLeft); - - assert.equal(bsr._index, 11); - assert.equal((64 * 8) - 11, bsr.bitsLeft); - }); - - test('readBitStream overflow', function () { - bsw.writeBits(0xF0, 8); //0b11110000 - bsw.writeBits(0xF1, 8); //0b11110001 - bsr.readBits(3); //offset - var slice = bsr.readBitStream(4); - - var exception = false; - - try { - slice.readUint8(); - } catch (e) { - exception = true; - } - - assert(exception); - }); - - test('writeBitStream', function () { - var buf = new ArrayBuffer(64); - var sourceStream = new BitStream(buf); - - sourceStream.writeBits(0xF0, 8); //0b11110000 - sourceStream.writeBits(0xF1, 8); //0b11110001 - sourceStream.index = 0; - sourceStream.readBits(3); //offset - bsr.writeBitStream(sourceStream, 8); - assert.equal(8, bsr.index); - bsr.index = 0; - assert.equal(bsr.readBits(6), 0x3E); //0b00111110 - assert.equal(11, sourceStream.index); - - var bin = new Uint8Array(buf); - assert.equal(bin[0], 0xF0); - assert.equal(bin[1], 0xF1); - }); - - test('writeBitStream Buffer', function () { - var buf = Buffer.alloc(64); - var sourceStream = new BitStream(buf); - - sourceStream.writeBits(0xF0, 8); //0b11110000 - sourceStream.writeBits(0xF1, 8); //0b11110001 - sourceStream.index = 0; - sourceStream.readBits(3); //offset - bsr.writeBitStream(sourceStream, 8); - assert.equal(8, bsr.index); - bsr.index = 0; - assert.equal(bsr.readBits(6), 0x3E); //0b00111110 - assert.equal(11, sourceStream.index); - - var bin = new Uint8Array(buf.buffer); - assert.equal(bin[0], 0xF0); - assert.equal(bin[1], 0xF1); - }); - - test('writeBitStream long', function () { - var sourceStream = new BitStream(new ArrayBuffer(64)); - - sourceStream.writeBits(0xF0, 8); - sourceStream.writeBits(0xF1, 8); - sourceStream.writeBits(0xF1, 8); - sourceStream.writeBits(0xF1, 8); - sourceStream.writeBits(0xF1, 8); - sourceStream.index = 0; - sourceStream.readBits(3); //offset - bsr.index = 3; - bsr.writeBitStream(sourceStream, 35); - assert.equal(38, bsr.index); - bsr.index = 3; - assert.equal(bsr.readBits(35), 1044266558); - assert.equal(38, sourceStream.index); - }); - - test('readArrayBuffer', function () { - bsw.writeBits(0xF0, 8); //0b11110000 - bsw.writeBits(0xF1, 8); //0b11110001 - bsw.writeBits(0xF0, 8); //0b11110000 - bsr.readBits(3); //offset - - var buffer = bsr.readArrayBuffer(2); - - assert.equal(0x3E, buffer[0]); //0b00111110 - assert.equal(0x1E, buffer[1]); //0b00011110 - - assert.equal(3 + (2 * 8), bsr._index); - }); - - test('writeArrayBuffer', function () { - var source = new Uint8Array(4); - source[0] = 0xF0; - source[1] = 0xF1; - source[2] = 0xF1; - bsr.readBits(3); //offset - - bsr.writeArrayBuffer(source.buffer, 2); - assert.equal(19, bsr.index); - - bsr.index = 0; - - assert.equal(bsr.readBits(8), 128); - }); - - test('Get buffer from view', function () { - bv.setBits(0, 0xFFFFFFFF, 32); - var buffer = bv.buffer; - - assert.equal(64, buffer.length); - assert.equal(0xFFFF, buffer.readUInt16LE(0)); - }); - - test('Get buffer from stream', function () { - bsw.writeBits(0xFFFFFFFF, 32); - var buffer = bsr.buffer; - - assert.equal(64, buffer.length); - assert.equal(0xFFFF, buffer.readUInt16LE(0)); - }); -}); - -suite('Reading big/little endian', function () { - var array, u8, bv, bsw, bsr; - - setup(function () { - array = new ArrayBuffer(64); - u8 = new Uint8Array(array); - u8[0] = 0x01; - u8[1] = 0x02; - // Test initializing straight from the array. - bsr = new BitStream(array); - }); - - test('4b, little-endian', function () { - assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); - - var result = []; - result.push(bsr.readBits(4)); - result.push(bsr.readBits(4)); - result.push(bsr.readBits(4)); - result.push(bsr.readBits(4)); - - // 0000 0001 0000 0010 [01 02] - // [#2] [#1] [#4] [#3] - assert.deepEqual(result, [1, 0, 2, 0]); - }); - - test('8b, little-endian', function () { - assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); - - var result = []; - result.push(bsr.readBits(8)); - result.push(bsr.readBits(8)); - - // 0000 0001 0000 0010 [01 02] - // [ #1] [ #2] - assert.deepEqual(result, [1, 2]); - }); - - test('10b, little-endian', function () { - assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); - - var result = []; - result.push(bsr.readBits(10)); - - // 0000 0001 0000 0010 [01 02] - // ... #1] [ #2][#1... - assert.deepEqual(result, [513]); - }); - - test('16b, little-endian', function () { - assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); - - var result = []; - result.push(bsr.readBits(16)); - - // 0000 0001 0000 0010 [01 02] - // [ #1] - assert.deepEqual(result, [0x201]); - }); - - test('24b, little-endian', function () { - u8[2] = 0x03; - assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); - - var result = []; - result.push(bsr.readBits(24)); - - // 0000 0001 0000 0010 0000 0011 [01 02 03] - // [ #1] - assert.deepEqual(result, [0x30201]); - }); - - test('4b, big-endian', function () { - bsr.bigEndian = true; - assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); - - var result = []; - result.push(bsr.readBits(4)); - result.push(bsr.readBits(4)); - result.push(bsr.readBits(4)); - result.push(bsr.readBits(4)); - - // 0000 0001 0000 0010 [01 02] - // [#1] [#2] [#3] [#4] - assert.deepEqual(result, [0, 1, 0, 2]); - }); - - test('8b, big-endian', function () { - bsr.bigEndian = true; - assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); - - var result = []; - result.push(bsr.readBits(8)); - result.push(bsr.readBits(8)); - - // 0000 0001 0000 0010 [01 02] - // [ #1] [ #2] - assert.deepEqual(result, [1, 2]); - }); - - test('10b, big-endian', function () { - bsr.bigEndian = true; - assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); - - var result = []; - result.push(bsr.readBits(10)); - result.push(bsr.readBits(6)); - - // 0000 0001 0000 0010 [01 02] - // [ #1][ #2] - assert.deepEqual(result, [4, 2]); - }); - - test('16b, big-endian', function () { - bsr.bigEndian = true; - assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); - - var result = []; - result.push(bsr.readBits(16)); - - // 0000 0001 0000 0010 [01 02] - // [ #1] - assert.deepEqual(result, [0x102]); - }); - - test('24b, big-endian', function () { - u8[2] = 0x03; - bsr.bigEndian = true; - assert.equal(bsr.index, 0, 'BitStream didn\'t init at offset 0'); - - var result = []; - result.push(bsr.readBits(24)); - - // 0000 0001 0000 0010 0000 0011 [01 02 03] - // [ #1] - assert.deepEqual(result, [0x10203]); - }); -}); - -suite('Writing big/little endian', function () { - var array, u8, bv, bsw, bsr; - - setup(function () { - array = new ArrayBuffer(2); - u8 = new Uint8Array(array); - bv = new BitView(array); - bsw = new BitStream(bv); - }); - - test('4b, little-endian', function () { - // 0000 0001 0000 0010 [01 02] - // [#2] [#1] [#4] [#3] - bsw.writeBits(1, 4); - bsw.writeBits(0, 4); - bsw.writeBits(2, 4); - bsw.writeBits(0, 4); - - assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); - }); - - test('8b, little-endian', function () { - // 0000 0001 0000 0010 [01 02] - // [ #1] [ #2] - bsw.writeBits(1, 8); - bsw.writeBits(2, 8); - - assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); - }); - - test('10b, little-endian', function () { - // 0000 0001 0000 0010 [01 02] - // ... #1] [ #2][#1... - bsw.writeBits(513, 10); - - assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); - }); - - test('16b, little-endian', function () { - // 0000 0001 0000 0010 [01 02] - // [ #1] - bsw.writeBits(0x201, 16); - - assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); - }); - - test('4b, big-endian', function () { - bsw.bigEndian = true; - - // 0000 0001 0000 0010 [01 02] - // [#1] [#2] [#3] [#4] - bsw.writeBits(0, 4); - bsw.writeBits(1, 4); - bsw.writeBits(0, 4); - bsw.writeBits(2, 4); - - assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); - }); - - test('8b, big-endian', function () { - bsw.bigEndian = true; - - // 0000 0001 0000 0010 [01 02] - // [ #1] [ #2] - bsw.writeBits(1, 8); - bsw.writeBits(2, 8); - - assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); - }); - - test('10b, big-endian', function () { - bsw.bigEndian = true; - - // 0000 0001 0000 0010 [01 02] - // [ #1][ #2] - bsw.writeBits(4, 10); - bsw.writeBits(2, 6); - - assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); - }); - - test('16b, big-endian', function () { - bsw.bigEndian = true; - - // 0000 0001 0000 0010 [01 02] - // [ #1] - bsw.writeBits(0x102, 16); - - assert.deepEqual(u8, new Uint8Array([0x01, 0x02])); - }); -}); diff --git a/node_modules/lv_font_conv/node_modules/debug/LICENSE b/node_modules/lv_font_conv/node_modules/debug/LICENSE deleted file mode 100644 index 658c933d..00000000 --- a/node_modules/lv_font_conv/node_modules/debug/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -(The MIT License) - -Copyright (c) 2014 TJ Holowaychuk - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software -and associated documentation files (the 'Software'), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT -LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/node_modules/lv_font_conv/node_modules/debug/README.md b/node_modules/lv_font_conv/node_modules/debug/README.md deleted file mode 100644 index 88dae35d..00000000 --- a/node_modules/lv_font_conv/node_modules/debug/README.md +++ /dev/null @@ -1,455 +0,0 @@ -# debug -[![Build Status](https://travis-ci.org/visionmedia/debug.svg?branch=master)](https://travis-ci.org/visionmedia/debug) [![Coverage Status](https://coveralls.io/repos/github/visionmedia/debug/badge.svg?branch=master)](https://coveralls.io/github/visionmedia/debug?branch=master) [![Slack](https://visionmedia-community-slackin.now.sh/badge.svg)](https://visionmedia-community-slackin.now.sh/) [![OpenCollective](https://opencollective.com/debug/backers/badge.svg)](#backers) -[![OpenCollective](https://opencollective.com/debug/sponsors/badge.svg)](#sponsors) - - - -A tiny JavaScript debugging utility modelled after Node.js core's debugging -technique. Works in Node.js and web browsers. - -## Installation - -```bash -$ npm install debug -``` - -## Usage - -`debug` exposes a function; simply pass this function the name of your module, and it will return a decorated version of `console.error` for you to pass debug statements to. This will allow you to toggle the debug output for different parts of your module as well as the module as a whole. - -Example [_app.js_](./examples/node/app.js): - -```js -var debug = require('debug')('http') - , http = require('http') - , name = 'My App'; - -// fake app - -debug('booting %o', name); - -http.createServer(function(req, res){ - debug(req.method + ' ' + req.url); - res.end('hello\n'); -}).listen(3000, function(){ - debug('listening'); -}); - -// fake worker of some kind - -require('./worker'); -``` - -Example [_worker.js_](./examples/node/worker.js): - -```js -var a = require('debug')('worker:a') - , b = require('debug')('worker:b'); - -function work() { - a('doing lots of uninteresting work'); - setTimeout(work, Math.random() * 1000); -} - -work(); - -function workb() { - b('doing some work'); - setTimeout(workb, Math.random() * 2000); -} - -workb(); -``` - -The `DEBUG` environment variable is then used to enable these based on space or -comma-delimited names. - -Here are some examples: - -screen shot 2017-08-08 at 12 53 04 pm -screen shot 2017-08-08 at 12 53 38 pm -screen shot 2017-08-08 at 12 53 25 pm - -#### Windows command prompt notes - -##### CMD - -On Windows the environment variable is set using the `set` command. - -```cmd -set DEBUG=*,-not_this -``` - -Example: - -```cmd -set DEBUG=* & node app.js -``` - -##### PowerShell (VS Code default) - -PowerShell uses different syntax to set environment variables. - -```cmd -$env:DEBUG = "*,-not_this" -``` - -Example: - -```cmd -$env:DEBUG='app';node app.js -``` - -Then, run the program to be debugged as usual. - -npm script example: -```js - "windowsDebug": "@powershell -Command $env:DEBUG='*';node app.js", -``` - -## Namespace Colors - -Every debug instance has a color generated for it based on its namespace name. -This helps when visually parsing the debug output to identify which debug instance -a debug line belongs to. - -#### Node.js - -In Node.js, colors are enabled when stderr is a TTY. You also _should_ install -the [`supports-color`](https://npmjs.org/supports-color) module alongside debug, -otherwise debug will only use a small handful of basic colors. - - - -#### Web Browser - -Colors are also enabled on "Web Inspectors" that understand the `%c` formatting -option. These are WebKit web inspectors, Firefox ([since version -31](https://hacks.mozilla.org/2014/05/editable-box-model-multiple-selection-sublime-text-keys-much-more-firefox-developer-tools-episode-31/)) -and the Firebug plugin for Firefox (any version). - - - - -## Millisecond diff - -When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the "+NNNms" will show you how much time was spent between calls. - - - -When stdout is not a TTY, `Date#toISOString()` is used, making it more useful for logging the debug information as shown below: - - - - -## Conventions - -If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use ":" to separate features. For example "bodyParser" from Connect would then be "connect:bodyParser". If you append a "*" to the end of your name, it will always be enabled regardless of the setting of the DEBUG environment variable. You can then use it for normal output as well as debug output. - -## Wildcards - -The `*` character may be used as a wildcard. Suppose for example your library has -debuggers named "connect:bodyParser", "connect:compress", "connect:session", -instead of listing all three with -`DEBUG=connect:bodyParser,connect:compress,connect:session`, you may simply do -`DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`. - -You can also exclude specific debuggers by prefixing them with a "-" character. -For example, `DEBUG=*,-connect:*` would include all debuggers except those -starting with "connect:". - -## Environment Variables - -When running through Node.js, you can set a few environment variables that will -change the behavior of the debug logging: - -| Name | Purpose | -|-----------|-------------------------------------------------| -| `DEBUG` | Enables/disables specific debugging namespaces. | -| `DEBUG_HIDE_DATE` | Hide date from debug output (non-TTY). | -| `DEBUG_COLORS`| Whether or not to use colors in the debug output. | -| `DEBUG_DEPTH` | Object inspection depth. | -| `DEBUG_SHOW_HIDDEN` | Shows hidden properties on inspected objects. | - - -__Note:__ The environment variables beginning with `DEBUG_` end up being -converted into an Options object that gets used with `%o`/`%O` formatters. -See the Node.js documentation for -[`util.inspect()`](https://nodejs.org/api/util.html#util_util_inspect_object_options) -for the complete list. - -## Formatters - -Debug uses [printf-style](https://wikipedia.org/wiki/Printf_format_string) formatting. -Below are the officially supported formatters: - -| Formatter | Representation | -|-----------|----------------| -| `%O` | Pretty-print an Object on multiple lines. | -| `%o` | Pretty-print an Object all on a single line. | -| `%s` | String. | -| `%d` | Number (both integer and float). | -| `%j` | JSON. Replaced with the string '[Circular]' if the argument contains circular references. | -| `%%` | Single percent sign ('%'). This does not consume an argument. | - - -### Custom formatters - -You can add custom formatters by extending the `debug.formatters` object. -For example, if you wanted to add support for rendering a Buffer as hex with -`%h`, you could do something like: - -```js -const createDebug = require('debug') -createDebug.formatters.h = (v) => { - return v.toString('hex') -} - -// …elsewhere -const debug = createDebug('foo') -debug('this is hex: %h', new Buffer('hello world')) -// foo this is hex: 68656c6c6f20776f726c6421 +0ms -``` - - -## Browser Support - -You can build a browser-ready script using [browserify](https://github.com/substack/node-browserify), -or just use the [browserify-as-a-service](https://wzrd.in/) [build](https://wzrd.in/standalone/debug@latest), -if you don't want to build it yourself. - -Debug's enable state is currently persisted by `localStorage`. -Consider the situation shown below where you have `worker:a` and `worker:b`, -and wish to debug both. You can enable this using `localStorage.debug`: - -```js -localStorage.debug = 'worker:*' -``` - -And then refresh the page. - -```js -a = debug('worker:a'); -b = debug('worker:b'); - -setInterval(function(){ - a('doing some work'); -}, 1000); - -setInterval(function(){ - b('doing some work'); -}, 1200); -``` - - -## Output streams - - By default `debug` will log to stderr, however this can be configured per-namespace by overriding the `log` method: - -Example [_stdout.js_](./examples/node/stdout.js): - -```js -var debug = require('debug'); -var error = debug('app:error'); - -// by default stderr is used -error('goes to stderr!'); - -var log = debug('app:log'); -// set this namespace to log via console.log -log.log = console.log.bind(console); // don't forget to bind to console! -log('goes to stdout'); -error('still goes to stderr!'); - -// set all output to go via console.info -// overrides all per-namespace log settings -debug.log = console.info.bind(console); -error('now goes to stdout via console.info'); -log('still goes to stdout, but via console.info now'); -``` - -## Extend -You can simply extend debugger -```js -const log = require('debug')('auth'); - -//creates new debug instance with extended namespace -const logSign = log.extend('sign'); -const logLogin = log.extend('login'); - -log('hello'); // auth hello -logSign('hello'); //auth:sign hello -logLogin('hello'); //auth:login hello -``` - -## Set dynamically - -You can also enable debug dynamically by calling the `enable()` method : - -```js -let debug = require('debug'); - -console.log(1, debug.enabled('test')); - -debug.enable('test'); -console.log(2, debug.enabled('test')); - -debug.disable(); -console.log(3, debug.enabled('test')); - -``` - -print : -``` -1 false -2 true -3 false -``` - -Usage : -`enable(namespaces)` -`namespaces` can include modes separated by a colon and wildcards. - -Note that calling `enable()` completely overrides previously set DEBUG variable : - -``` -$ DEBUG=foo node -e 'var dbg = require("debug"); dbg.enable("bar"); console.log(dbg.enabled("foo"))' -=> false -``` - -`disable()` - -Will disable all namespaces. The functions returns the namespaces currently -enabled (and skipped). This can be useful if you want to disable debugging -temporarily without knowing what was enabled to begin with. - -For example: - -```js -let debug = require('debug'); -debug.enable('foo:*,-foo:bar'); -let namespaces = debug.disable(); -debug.enable(namespaces); -``` - -Note: There is no guarantee that the string will be identical to the initial -enable string, but semantically they will be identical. - -## Checking whether a debug target is enabled - -After you've created a debug instance, you can determine whether or not it is -enabled by checking the `enabled` property: - -```javascript -const debug = require('debug')('http'); - -if (debug.enabled) { - // do stuff... -} -``` - -You can also manually toggle this property to force the debug instance to be -enabled or disabled. - - -## Authors - - - TJ Holowaychuk - - Nathan Rajlich - - Andrew Rhyne - -## Backers - -Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/debug#backer)] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -## Sponsors - -Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/debug#sponsor)] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -## License - -(The MIT License) - -Copyright (c) 2014-2017 TJ Holowaychuk <tj@vision-media.ca> - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/debug/package.json b/node_modules/lv_font_conv/node_modules/debug/package.json deleted file mode 100644 index c9f15f8c..00000000 --- a/node_modules/lv_font_conv/node_modules/debug/package.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "_args": [ - [ - "debug@4.3.1", - "/home/vitaly/Dropbox/Coding/lv_font_conv" - ] - ], - "_from": "debug@4.3.1", - "_id": "debug@4.3.1", - "_inBundle": false, - "_integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "_location": "/debug", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "debug@4.3.1", - "name": "debug", - "escapedName": "debug", - "rawSpec": "4.3.1", - "saveSpec": null, - "fetchSpec": "4.3.1" - }, - "_requiredBy": [ - "/", - "/@babel/core", - "/@babel/helper-define-polyfill-provider", - "/@babel/traverse", - "/@eslint/eslintrc", - "/eslint", - "/istanbul-lib-source-maps", - "/mocha" - ], - "_resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "_spec": "4.3.1", - "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", - "author": { - "name": "TJ Holowaychuk", - "email": "tj@vision-media.ca" - }, - "browser": "./src/browser.js", - "bugs": { - "url": "https://github.com/visionmedia/debug/issues" - }, - "contributors": [ - { - "name": "Nathan Rajlich", - "email": "nathan@tootallnate.net", - "url": "http://n8.io" - }, - { - "name": "Andrew Rhyne", - "email": "rhyneandrew@gmail.com" - }, - { - "name": "Josh Junon", - "email": "josh@junon.me" - } - ], - "dependencies": { - "ms": "2.1.2" - }, - "description": "small debugging utility", - "devDependencies": { - "brfs": "^2.0.1", - "browserify": "^16.2.3", - "coveralls": "^3.0.2", - "istanbul": "^0.4.5", - "karma": "^3.1.4", - "karma-browserify": "^6.0.0", - "karma-chrome-launcher": "^2.2.0", - "karma-mocha": "^1.3.0", - "mocha": "^5.2.0", - "mocha-lcov-reporter": "^1.2.0", - "xo": "^0.23.0" - }, - "engines": { - "node": ">=6.0" - }, - "files": [ - "src", - "LICENSE", - "README.md" - ], - "homepage": "https://github.com/visionmedia/debug#readme", - "keywords": [ - "debug", - "log", - "debugger" - ], - "license": "MIT", - "main": "./src/index.js", - "name": "debug", - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - }, - "repository": { - "type": "git", - "url": "git://github.com/visionmedia/debug.git" - }, - "scripts": { - "lint": "xo", - "test": "npm run test:node && npm run test:browser && npm run lint", - "test:browser": "karma start --single-run", - "test:coverage": "cat ./coverage/lcov.info | coveralls", - "test:node": "istanbul cover _mocha -- test.js" - }, - "version": "4.3.1" -} diff --git a/node_modules/lv_font_conv/node_modules/debug/src/browser.js b/node_modules/lv_font_conv/node_modules/debug/src/browser.js deleted file mode 100644 index cd0fc35d..00000000 --- a/node_modules/lv_font_conv/node_modules/debug/src/browser.js +++ /dev/null @@ -1,269 +0,0 @@ -/* eslint-env browser */ - -/** - * This is the web browser implementation of `debug()`. - */ - -exports.formatArgs = formatArgs; -exports.save = save; -exports.load = load; -exports.useColors = useColors; -exports.storage = localstorage(); -exports.destroy = (() => { - let warned = false; - - return () => { - if (!warned) { - warned = true; - console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); - } - }; -})(); - -/** - * Colors. - */ - -exports.colors = [ - '#0000CC', - '#0000FF', - '#0033CC', - '#0033FF', - '#0066CC', - '#0066FF', - '#0099CC', - '#0099FF', - '#00CC00', - '#00CC33', - '#00CC66', - '#00CC99', - '#00CCCC', - '#00CCFF', - '#3300CC', - '#3300FF', - '#3333CC', - '#3333FF', - '#3366CC', - '#3366FF', - '#3399CC', - '#3399FF', - '#33CC00', - '#33CC33', - '#33CC66', - '#33CC99', - '#33CCCC', - '#33CCFF', - '#6600CC', - '#6600FF', - '#6633CC', - '#6633FF', - '#66CC00', - '#66CC33', - '#9900CC', - '#9900FF', - '#9933CC', - '#9933FF', - '#99CC00', - '#99CC33', - '#CC0000', - '#CC0033', - '#CC0066', - '#CC0099', - '#CC00CC', - '#CC00FF', - '#CC3300', - '#CC3333', - '#CC3366', - '#CC3399', - '#CC33CC', - '#CC33FF', - '#CC6600', - '#CC6633', - '#CC9900', - '#CC9933', - '#CCCC00', - '#CCCC33', - '#FF0000', - '#FF0033', - '#FF0066', - '#FF0099', - '#FF00CC', - '#FF00FF', - '#FF3300', - '#FF3333', - '#FF3366', - '#FF3399', - '#FF33CC', - '#FF33FF', - '#FF6600', - '#FF6633', - '#FF9900', - '#FF9933', - '#FFCC00', - '#FFCC33' -]; - -/** - * Currently only WebKit-based Web Inspectors, Firefox >= v31, - * and the Firebug extension (any Firefox version) are known - * to support "%c" CSS customizations. - * - * TODO: add a `localStorage` variable to explicitly enable/disable colors - */ - -// eslint-disable-next-line complexity -function useColors() { - // NB: In an Electron preload script, document will be defined but not fully - // initialized. Since we know we're in Chrome, we'll just detect this case - // explicitly - if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) { - return true; - } - - // Internet Explorer and Edge do not support colors. - if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) { - return false; - } - - // Is webkit? http://stackoverflow.com/a/16459606/376773 - // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 - return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || - // Is firebug? http://stackoverflow.com/a/398120/376773 - (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || - // Is firefox >= v31? - // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || - // Double check webkit in userAgent just in case we are in a worker - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); -} - -/** - * Colorize log arguments if enabled. - * - * @api public - */ - -function formatArgs(args) { - args[0] = (this.useColors ? '%c' : '') + - this.namespace + - (this.useColors ? ' %c' : ' ') + - args[0] + - (this.useColors ? '%c ' : ' ') + - '+' + module.exports.humanize(this.diff); - - if (!this.useColors) { - return; - } - - const c = 'color: ' + this.color; - args.splice(1, 0, c, 'color: inherit'); - - // The final "%c" is somewhat tricky, because there could be other - // arguments passed either before or after the %c, so we need to - // figure out the correct index to insert the CSS into - let index = 0; - let lastC = 0; - args[0].replace(/%[a-zA-Z%]/g, match => { - if (match === '%%') { - return; - } - index++; - if (match === '%c') { - // We only are interested in the *last* %c - // (the user may have provided their own) - lastC = index; - } - }); - - args.splice(lastC, 0, c); -} - -/** - * Invokes `console.debug()` when available. - * No-op when `console.debug` is not a "function". - * If `console.debug` is not available, falls back - * to `console.log`. - * - * @api public - */ -exports.log = console.debug || console.log || (() => {}); - -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ -function save(namespaces) { - try { - if (namespaces) { - exports.storage.setItem('debug', namespaces); - } else { - exports.storage.removeItem('debug'); - } - } catch (error) { - // Swallow - // XXX (@Qix-) should we be logging these? - } -} - -/** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ -function load() { - let r; - try { - r = exports.storage.getItem('debug'); - } catch (error) { - // Swallow - // XXX (@Qix-) should we be logging these? - } - - // If debug isn't set in LS, and we're in Electron, try to load $DEBUG - if (!r && typeof process !== 'undefined' && 'env' in process) { - r = process.env.DEBUG; - } - - return r; -} - -/** - * Localstorage attempts to return the localstorage. - * - * This is necessary because safari throws - * when a user disables cookies/localstorage - * and you attempt to access it. - * - * @return {LocalStorage} - * @api private - */ - -function localstorage() { - try { - // TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context - // The Browser also has localStorage in the global context. - return localStorage; - } catch (error) { - // Swallow - // XXX (@Qix-) should we be logging these? - } -} - -module.exports = require('./common')(exports); - -const {formatters} = module.exports; - -/** - * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. - */ - -formatters.j = function (v) { - try { - return JSON.stringify(v); - } catch (error) { - return '[UnexpectedJSONParseError]: ' + error.message; - } -}; diff --git a/node_modules/lv_font_conv/node_modules/debug/src/common.js b/node_modules/lv_font_conv/node_modules/debug/src/common.js deleted file mode 100644 index 392a8e00..00000000 --- a/node_modules/lv_font_conv/node_modules/debug/src/common.js +++ /dev/null @@ -1,261 +0,0 @@ - -/** - * This is the common logic for both the Node.js and web browser - * implementations of `debug()`. - */ - -function setup(env) { - createDebug.debug = createDebug; - createDebug.default = createDebug; - createDebug.coerce = coerce; - createDebug.disable = disable; - createDebug.enable = enable; - createDebug.enabled = enabled; - createDebug.humanize = require('ms'); - createDebug.destroy = destroy; - - Object.keys(env).forEach(key => { - createDebug[key] = env[key]; - }); - - /** - * The currently active debug mode names, and names to skip. - */ - - createDebug.names = []; - createDebug.skips = []; - - /** - * Map of special "%n" handling functions, for the debug "format" argument. - * - * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". - */ - createDebug.formatters = {}; - - /** - * Selects a color for a debug namespace - * @param {String} namespace The namespace string for the for the debug instance to be colored - * @return {Number|String} An ANSI color code for the given namespace - * @api private - */ - function selectColor(namespace) { - let hash = 0; - - for (let i = 0; i < namespace.length; i++) { - hash = ((hash << 5) - hash) + namespace.charCodeAt(i); - hash |= 0; // Convert to 32bit integer - } - - return createDebug.colors[Math.abs(hash) % createDebug.colors.length]; - } - createDebug.selectColor = selectColor; - - /** - * Create a debugger with the given `namespace`. - * - * @param {String} namespace - * @return {Function} - * @api public - */ - function createDebug(namespace) { - let prevTime; - let enableOverride = null; - - function debug(...args) { - // Disabled? - if (!debug.enabled) { - return; - } - - const self = debug; - - // Set `diff` timestamp - const curr = Number(new Date()); - const ms = curr - (prevTime || curr); - self.diff = ms; - self.prev = prevTime; - self.curr = curr; - prevTime = curr; - - args[0] = createDebug.coerce(args[0]); - - if (typeof args[0] !== 'string') { - // Anything else let's inspect with %O - args.unshift('%O'); - } - - // Apply any `formatters` transformations - let index = 0; - args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => { - // If we encounter an escaped % then don't increase the array index - if (match === '%%') { - return '%'; - } - index++; - const formatter = createDebug.formatters[format]; - if (typeof formatter === 'function') { - const val = args[index]; - match = formatter.call(self, val); - - // Now we need to remove `args[index]` since it's inlined in the `format` - args.splice(index, 1); - index--; - } - return match; - }); - - // Apply env-specific formatting (colors, etc.) - createDebug.formatArgs.call(self, args); - - const logFn = self.log || createDebug.log; - logFn.apply(self, args); - } - - debug.namespace = namespace; - debug.useColors = createDebug.useColors(); - debug.color = createDebug.selectColor(namespace); - debug.extend = extend; - debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release. - - Object.defineProperty(debug, 'enabled', { - enumerable: true, - configurable: false, - get: () => enableOverride === null ? createDebug.enabled(namespace) : enableOverride, - set: v => { - enableOverride = v; - } - }); - - // Env-specific initialization logic for debug instances - if (typeof createDebug.init === 'function') { - createDebug.init(debug); - } - - return debug; - } - - function extend(namespace, delimiter) { - const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace); - newDebug.log = this.log; - return newDebug; - } - - /** - * Enables a debug mode by namespaces. This can include modes - * separated by a colon and wildcards. - * - * @param {String} namespaces - * @api public - */ - function enable(namespaces) { - createDebug.save(namespaces); - - createDebug.names = []; - createDebug.skips = []; - - let i; - const split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); - const len = split.length; - - for (i = 0; i < len; i++) { - if (!split[i]) { - // ignore empty strings - continue; - } - - namespaces = split[i].replace(/\*/g, '.*?'); - - if (namespaces[0] === '-') { - createDebug.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); - } else { - createDebug.names.push(new RegExp('^' + namespaces + '$')); - } - } - } - - /** - * Disable debug output. - * - * @return {String} namespaces - * @api public - */ - function disable() { - const namespaces = [ - ...createDebug.names.map(toNamespace), - ...createDebug.skips.map(toNamespace).map(namespace => '-' + namespace) - ].join(','); - createDebug.enable(''); - return namespaces; - } - - /** - * Returns true if the given mode name is enabled, false otherwise. - * - * @param {String} name - * @return {Boolean} - * @api public - */ - function enabled(name) { - if (name[name.length - 1] === '*') { - return true; - } - - let i; - let len; - - for (i = 0, len = createDebug.skips.length; i < len; i++) { - if (createDebug.skips[i].test(name)) { - return false; - } - } - - for (i = 0, len = createDebug.names.length; i < len; i++) { - if (createDebug.names[i].test(name)) { - return true; - } - } - - return false; - } - - /** - * Convert regexp to namespace - * - * @param {RegExp} regxep - * @return {String} namespace - * @api private - */ - function toNamespace(regexp) { - return regexp.toString() - .substring(2, regexp.toString().length - 2) - .replace(/\.\*\?$/, '*'); - } - - /** - * Coerce `val`. - * - * @param {Mixed} val - * @return {Mixed} - * @api private - */ - function coerce(val) { - if (val instanceof Error) { - return val.stack || val.message; - } - return val; - } - - /** - * XXX DO NOT USE. This is a temporary stub function. - * XXX It WILL be removed in the next major release. - */ - function destroy() { - console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); - } - - createDebug.enable(createDebug.load()); - - return createDebug; -} - -module.exports = setup; diff --git a/node_modules/lv_font_conv/node_modules/debug/src/index.js b/node_modules/lv_font_conv/node_modules/debug/src/index.js deleted file mode 100644 index bf4c57f2..00000000 --- a/node_modules/lv_font_conv/node_modules/debug/src/index.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Detect Electron renderer / nwjs process, which is node, but we should - * treat as a browser. - */ - -if (typeof process === 'undefined' || process.type === 'renderer' || process.browser === true || process.__nwjs) { - module.exports = require('./browser.js'); -} else { - module.exports = require('./node.js'); -} diff --git a/node_modules/lv_font_conv/node_modules/debug/src/node.js b/node_modules/lv_font_conv/node_modules/debug/src/node.js deleted file mode 100644 index 79bc085c..00000000 --- a/node_modules/lv_font_conv/node_modules/debug/src/node.js +++ /dev/null @@ -1,263 +0,0 @@ -/** - * Module dependencies. - */ - -const tty = require('tty'); -const util = require('util'); - -/** - * This is the Node.js implementation of `debug()`. - */ - -exports.init = init; -exports.log = log; -exports.formatArgs = formatArgs; -exports.save = save; -exports.load = load; -exports.useColors = useColors; -exports.destroy = util.deprecate( - () => {}, - 'Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.' -); - -/** - * Colors. - */ - -exports.colors = [6, 2, 3, 4, 5, 1]; - -try { - // Optional dependency (as in, doesn't need to be installed, NOT like optionalDependencies in package.json) - // eslint-disable-next-line import/no-extraneous-dependencies - const supportsColor = require('supports-color'); - - if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) { - exports.colors = [ - 20, - 21, - 26, - 27, - 32, - 33, - 38, - 39, - 40, - 41, - 42, - 43, - 44, - 45, - 56, - 57, - 62, - 63, - 68, - 69, - 74, - 75, - 76, - 77, - 78, - 79, - 80, - 81, - 92, - 93, - 98, - 99, - 112, - 113, - 128, - 129, - 134, - 135, - 148, - 149, - 160, - 161, - 162, - 163, - 164, - 165, - 166, - 167, - 168, - 169, - 170, - 171, - 172, - 173, - 178, - 179, - 184, - 185, - 196, - 197, - 198, - 199, - 200, - 201, - 202, - 203, - 204, - 205, - 206, - 207, - 208, - 209, - 214, - 215, - 220, - 221 - ]; - } -} catch (error) { - // Swallow - we only care if `supports-color` is available; it doesn't have to be. -} - -/** - * Build up the default `inspectOpts` object from the environment variables. - * - * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js - */ - -exports.inspectOpts = Object.keys(process.env).filter(key => { - return /^debug_/i.test(key); -}).reduce((obj, key) => { - // Camel-case - const prop = key - .substring(6) - .toLowerCase() - .replace(/_([a-z])/g, (_, k) => { - return k.toUpperCase(); - }); - - // Coerce string value into JS value - let val = process.env[key]; - if (/^(yes|on|true|enabled)$/i.test(val)) { - val = true; - } else if (/^(no|off|false|disabled)$/i.test(val)) { - val = false; - } else if (val === 'null') { - val = null; - } else { - val = Number(val); - } - - obj[prop] = val; - return obj; -}, {}); - -/** - * Is stdout a TTY? Colored output is enabled when `true`. - */ - -function useColors() { - return 'colors' in exports.inspectOpts ? - Boolean(exports.inspectOpts.colors) : - tty.isatty(process.stderr.fd); -} - -/** - * Adds ANSI color escape codes if enabled. - * - * @api public - */ - -function formatArgs(args) { - const {namespace: name, useColors} = this; - - if (useColors) { - const c = this.color; - const colorCode = '\u001B[3' + (c < 8 ? c : '8;5;' + c); - const prefix = ` ${colorCode};1m${name} \u001B[0m`; - - args[0] = prefix + args[0].split('\n').join('\n' + prefix); - args.push(colorCode + 'm+' + module.exports.humanize(this.diff) + '\u001B[0m'); - } else { - args[0] = getDate() + name + ' ' + args[0]; - } -} - -function getDate() { - if (exports.inspectOpts.hideDate) { - return ''; - } - return new Date().toISOString() + ' '; -} - -/** - * Invokes `util.format()` with the specified arguments and writes to stderr. - */ - -function log(...args) { - return process.stderr.write(util.format(...args) + '\n'); -} - -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ -function save(namespaces) { - if (namespaces) { - process.env.DEBUG = namespaces; - } else { - // If you set a process.env field to null or undefined, it gets cast to the - // string 'null' or 'undefined'. Just delete instead. - delete process.env.DEBUG; - } -} - -/** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ - -function load() { - return process.env.DEBUG; -} - -/** - * Init logic for `debug` instances. - * - * Create a new `inspectOpts` object in case `useColors` is set - * differently for a particular `debug` instance. - */ - -function init(debug) { - debug.inspectOpts = {}; - - const keys = Object.keys(exports.inspectOpts); - for (let i = 0; i < keys.length; i++) { - debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]]; - } -} - -module.exports = require('./common')(exports); - -const {formatters} = module.exports; - -/** - * Map %o to `util.inspect()`, all on a single line. - */ - -formatters.o = function (v) { - this.inspectOpts.colors = this.useColors; - return util.inspect(v, this.inspectOpts) - .split('\n') - .map(str => str.trim()) - .join(' '); -}; - -/** - * Map %O to `util.inspect()`, allowing multiple lines if needed. - */ - -formatters.O = function (v) { - this.inspectOpts.colors = this.useColors; - return util.inspect(v, this.inspectOpts); -}; diff --git a/node_modules/lv_font_conv/node_modules/make-error/LICENSE b/node_modules/lv_font_conv/node_modules/make-error/LICENSE deleted file mode 100644 index 9dcf797e..00000000 --- a/node_modules/lv_font_conv/node_modules/make-error/LICENSE +++ /dev/null @@ -1,5 +0,0 @@ -Copyright 2014 Julien Fontanet - -Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/make-error/README.md b/node_modules/lv_font_conv/node_modules/make-error/README.md deleted file mode 100644 index 5c089a26..00000000 --- a/node_modules/lv_font_conv/node_modules/make-error/README.md +++ /dev/null @@ -1,112 +0,0 @@ -# make-error - -[![Package Version](https://badgen.net/npm/v/make-error)](https://npmjs.org/package/make-error) [![Build Status](https://travis-ci.org/JsCommunity/make-error.png?branch=master)](https://travis-ci.org/JsCommunity/make-error) [![PackagePhobia](https://badgen.net/packagephobia/install/make-error)](https://packagephobia.now.sh/result?p=make-error) [![Latest Commit](https://badgen.net/github/last-commit/JsCommunity/make-error)](https://github.com/JsCommunity/make-error/commits/master) - -> Make your own error types! - -## Features - -- Compatible Node & browsers -- `instanceof` support -- `error.name` & `error.stack` support -- compatible with [CSP](https://en.wikipedia.org/wiki/Content_Security_Policy) (i.e. no `eval()`) - -## Installation - -### Node & [Browserify](http://browserify.org/)/[Webpack](https://webpack.js.org/) - -Installation of the [npm package](https://npmjs.org/package/make-error): - -``` -> npm install --save make-error -``` - -Then require the package: - -```javascript -var makeError = require("make-error"); -``` - -### Browser - -You can directly use the build provided at [unpkg.com](https://unpkg.com): - -```html - -``` - -## Usage - -### Basic named error - -```javascript -var CustomError = makeError("CustomError"); - -// Parameters are forwarded to the super class (here Error). -throw new CustomError("a message"); -``` - -### Advanced error class - -```javascript -function CustomError(customValue) { - CustomError.super.call(this, "custom error message"); - - this.customValue = customValue; -} -makeError(CustomError); - -// Feel free to extend the prototype. -CustomError.prototype.myMethod = function CustomError$myMethod() { - console.log("CustomError.myMethod (%s, %s)", this.code, this.message); -}; - -//----- - -try { - throw new CustomError(42); -} catch (error) { - error.myMethod(); -} -``` - -### Specialized error - -```javascript -var SpecializedError = makeError("SpecializedError", CustomError); - -throw new SpecializedError(42); -``` - -### Inheritance - -> Best for ES2015+. - -```javascript -import { BaseError } from "make-error"; - -class CustomError extends BaseError { - constructor() { - super("custom error message"); - } -} -``` - -## Related - -- [make-error-cause](https://www.npmjs.com/package/make-error-cause): Make your own error types, with a cause! - -## Contributions - -Contributions are _very_ welcomed, either on the documentation or on -the code. - -You may: - -- report any [issue](https://github.com/JsCommunity/make-error/issues) - you've encountered; -- fork and create a pull request. - -## License - -ISC © [Julien Fontanet](http://julien.isonoe.net) diff --git a/node_modules/lv_font_conv/node_modules/make-error/dist/make-error.js b/node_modules/lv_font_conv/node_modules/make-error/dist/make-error.js deleted file mode 100644 index 32444c69..00000000 --- a/node_modules/lv_font_conv/node_modules/make-error/dist/make-error.js +++ /dev/null @@ -1 +0,0 @@ -!function(f){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=f();else if("function"==typeof define&&define.amd)define([],f);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).makeError=f()}}(function(){return function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){return o(e[i][1][r]||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i; - -/** - * Set the constructor prototype to `BaseError`. - */ -declare function makeError(super_: { - new (...args: any[]): T; -}): makeError.Constructor; - -/** - * Create a specialized error instance. - */ -declare function makeError( - name: string | Function, - super_: K -): K & makeError.SpecializedConstructor; - -declare namespace makeError { - /** - * Use with ES2015+ inheritance. - */ - export class BaseError extends Error { - message: string; - name: string; - stack: string; - - constructor(message?: string); - } - - export interface Constructor { - new (message?: string): T; - super_: any; - prototype: T; - } - - export interface SpecializedConstructor { - super_: any; - prototype: T; - } -} - -export = makeError; diff --git a/node_modules/lv_font_conv/node_modules/make-error/index.js b/node_modules/lv_font_conv/node_modules/make-error/index.js deleted file mode 100644 index fab60407..00000000 --- a/node_modules/lv_font_conv/node_modules/make-error/index.js +++ /dev/null @@ -1,151 +0,0 @@ -// ISC @ Julien Fontanet - -"use strict"; - -// =================================================================== - -var construct = typeof Reflect !== "undefined" ? Reflect.construct : undefined; -var defineProperty = Object.defineProperty; - -// ------------------------------------------------------------------- - -var captureStackTrace = Error.captureStackTrace; -if (captureStackTrace === undefined) { - captureStackTrace = function captureStackTrace(error) { - var container = new Error(); - - defineProperty(error, "stack", { - configurable: true, - get: function getStack() { - var stack = container.stack; - - // Replace property with value for faster future accesses. - defineProperty(this, "stack", { - configurable: true, - value: stack, - writable: true, - }); - - return stack; - }, - set: function setStack(stack) { - defineProperty(error, "stack", { - configurable: true, - value: stack, - writable: true, - }); - }, - }); - }; -} - -// ------------------------------------------------------------------- - -function BaseError(message) { - if (message !== undefined) { - defineProperty(this, "message", { - configurable: true, - value: message, - writable: true, - }); - } - - var cname = this.constructor.name; - if (cname !== undefined && cname !== this.name) { - defineProperty(this, "name", { - configurable: true, - value: cname, - writable: true, - }); - } - - captureStackTrace(this, this.constructor); -} - -BaseError.prototype = Object.create(Error.prototype, { - // See: https://github.com/JsCommunity/make-error/issues/4 - constructor: { - configurable: true, - value: BaseError, - writable: true, - }, -}); - -// ------------------------------------------------------------------- - -// Sets the name of a function if possible (depends of the JS engine). -var setFunctionName = (function() { - function setFunctionName(fn, name) { - return defineProperty(fn, "name", { - configurable: true, - value: name, - }); - } - try { - var f = function() {}; - setFunctionName(f, "foo"); - if (f.name === "foo") { - return setFunctionName; - } - } catch (_) {} -})(); - -// ------------------------------------------------------------------- - -function makeError(constructor, super_) { - if (super_ == null || super_ === Error) { - super_ = BaseError; - } else if (typeof super_ !== "function") { - throw new TypeError("super_ should be a function"); - } - - var name; - if (typeof constructor === "string") { - name = constructor; - constructor = - construct !== undefined - ? function() { - return construct(super_, arguments, this.constructor); - } - : function() { - super_.apply(this, arguments); - }; - - // If the name can be set, do it once and for all. - if (setFunctionName !== undefined) { - setFunctionName(constructor, name); - name = undefined; - } - } else if (typeof constructor !== "function") { - throw new TypeError("constructor should be either a string or a function"); - } - - // Also register the super constructor also as `constructor.super_` just - // like Node's `util.inherits()`. - // - // eslint-disable-next-line dot-notation - constructor.super_ = constructor["super"] = super_; - - var properties = { - constructor: { - configurable: true, - value: constructor, - writable: true, - }, - }; - - // If the name could not be set on the constructor, set it on the - // prototype. - if (name !== undefined) { - properties.name = { - configurable: true, - value: name, - writable: true, - }; - } - constructor.prototype = Object.create(super_.prototype, properties); - - return constructor; -} -exports = module.exports = makeError; -exports.BaseError = BaseError; diff --git a/node_modules/lv_font_conv/node_modules/make-error/package.json b/node_modules/lv_font_conv/node_modules/make-error/package.json deleted file mode 100644 index e5f69904..00000000 --- a/node_modules/lv_font_conv/node_modules/make-error/package.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "_args": [ - [ - "make-error@1.3.6", - "/home/vitaly/Dropbox/Coding/lv_font_conv" - ] - ], - "_from": "make-error@1.3.6", - "_id": "make-error@1.3.6", - "_inBundle": false, - "_integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "_location": "/make-error", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "make-error@1.3.6", - "name": "make-error", - "escapedName": "make-error", - "rawSpec": "1.3.6", - "saveSpec": null, - "fetchSpec": "1.3.6" - }, - "_requiredBy": [ - "/" - ], - "_resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "_spec": "1.3.6", - "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", - "author": { - "name": "Julien Fontanet", - "email": "julien.fontanet@isonoe.net" - }, - "bugs": { - "url": "https://github.com/JsCommunity/make-error/issues" - }, - "description": "Make your own error types!", - "devDependencies": { - "browserify": "^16.2.3", - "eslint": "^6.5.1", - "eslint-config-prettier": "^6.4.0", - "eslint-config-standard": "^14.1.0", - "eslint-plugin-import": "^2.14.0", - "eslint-plugin-node": "^10.0.0", - "eslint-plugin-promise": "^4.0.1", - "eslint-plugin-standard": "^4.0.0", - "husky": "^3.0.9", - "jest": "^24", - "prettier": "^1.14.3", - "uglify-js": "^3.3.2" - }, - "files": [ - "dist/", - "index.js", - "index.d.ts" - ], - "homepage": "https://github.com/JsCommunity/make-error", - "husky": { - "hooks": { - "commit-msg": "npm run test" - } - }, - "jest": { - "testEnvironment": "node" - }, - "keywords": [ - "create", - "custom", - "derive", - "error", - "errors", - "extend", - "extending", - "extension", - "factory", - "inherit", - "make", - "subclass" - ], - "license": "ISC", - "main": "index.js", - "name": "make-error", - "repository": { - "type": "git", - "url": "git://github.com/JsCommunity/make-error.git" - }, - "scripts": { - "dev-test": "jest --watch", - "format": "prettier --write '**'", - "prepublishOnly": "mkdir -p dist && browserify -s makeError index.js | uglifyjs -c > dist/make-error.js", - "pretest": "eslint --ignore-path .gitignore .", - "test": "jest" - }, - "version": "1.3.6" -} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/CHANGELOG.md b/node_modules/lv_font_conv/node_modules/mkdirp/CHANGELOG.md deleted file mode 100644 index 81458380..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/CHANGELOG.md +++ /dev/null @@ -1,15 +0,0 @@ -# Changers Lorgs! - -## 1.0 - -Full rewrite. Essentially a brand new module. - -- Return a promise instead of taking a callback. -- Use native `fs.mkdir(path, { recursive: true })` when available. -- Drop support for outdated Node.js versions. (Technically still works on - Node.js v8, but only 10 and above are officially supported.) - -## 0.x - -Original and most widely used recursive directory creation implementation -in JavaScript, dating back to 2010. diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/LICENSE b/node_modules/lv_font_conv/node_modules/mkdirp/LICENSE deleted file mode 100644 index 13fcd15f..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -Copyright James Halliday (mail@substack.net) and Isaac Z. Schlueter (i@izs.me) - -This project is free software released under the MIT license: - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/bin/cmd.js b/node_modules/lv_font_conv/node_modules/mkdirp/bin/cmd.js deleted file mode 100755 index 6e0aa8dc..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/bin/cmd.js +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env node - -const usage = () => ` -usage: mkdirp [DIR1,DIR2..] {OPTIONS} - - Create each supplied directory including any necessary parent directories - that don't yet exist. - - If the directory already exists, do nothing. - -OPTIONS are: - - -m If a directory needs to be created, set the mode as an octal - --mode= permission string. - - -v --version Print the mkdirp version number - - -h --help Print this helpful banner - - -p --print Print the first directories created for each path provided - - --manual Use manual implementation, even if native is available -` - -const dirs = [] -const opts = {} -let print = false -let dashdash = false -let manual = false -for (const arg of process.argv.slice(2)) { - if (dashdash) - dirs.push(arg) - else if (arg === '--') - dashdash = true - else if (arg === '--manual') - manual = true - else if (/^-h/.test(arg) || /^--help/.test(arg)) { - console.log(usage()) - process.exit(0) - } else if (arg === '-v' || arg === '--version') { - console.log(require('../package.json').version) - process.exit(0) - } else if (arg === '-p' || arg === '--print') { - print = true - } else if (/^-m/.test(arg) || /^--mode=/.test(arg)) { - const mode = parseInt(arg.replace(/^(-m|--mode=)/, ''), 8) - if (isNaN(mode)) { - console.error(`invalid mode argument: ${arg}\nMust be an octal number.`) - process.exit(1) - } - opts.mode = mode - } else - dirs.push(arg) -} - -const mkdirp = require('../') -const impl = manual ? mkdirp.manual : mkdirp -if (dirs.length === 0) - console.error(usage()) - -Promise.all(dirs.map(dir => impl(dir, opts))) - .then(made => print ? made.forEach(m => m && console.log(m)) : null) - .catch(er => { - console.error(er.message) - if (er.code) - console.error(' code: ' + er.code) - process.exit(1) - }) diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/index.js b/node_modules/lv_font_conv/node_modules/mkdirp/index.js deleted file mode 100644 index ad7a16c9..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/index.js +++ /dev/null @@ -1,31 +0,0 @@ -const optsArg = require('./lib/opts-arg.js') -const pathArg = require('./lib/path-arg.js') - -const {mkdirpNative, mkdirpNativeSync} = require('./lib/mkdirp-native.js') -const {mkdirpManual, mkdirpManualSync} = require('./lib/mkdirp-manual.js') -const {useNative, useNativeSync} = require('./lib/use-native.js') - - -const mkdirp = (path, opts) => { - path = pathArg(path) - opts = optsArg(opts) - return useNative(opts) - ? mkdirpNative(path, opts) - : mkdirpManual(path, opts) -} - -const mkdirpSync = (path, opts) => { - path = pathArg(path) - opts = optsArg(opts) - return useNativeSync(opts) - ? mkdirpNativeSync(path, opts) - : mkdirpManualSync(path, opts) -} - -mkdirp.sync = mkdirpSync -mkdirp.native = (path, opts) => mkdirpNative(pathArg(path), optsArg(opts)) -mkdirp.manual = (path, opts) => mkdirpManual(pathArg(path), optsArg(opts)) -mkdirp.nativeSync = (path, opts) => mkdirpNativeSync(pathArg(path), optsArg(opts)) -mkdirp.manualSync = (path, opts) => mkdirpManualSync(pathArg(path), optsArg(opts)) - -module.exports = mkdirp diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/find-made.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/find-made.js deleted file mode 100644 index 022e492c..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/lib/find-made.js +++ /dev/null @@ -1,29 +0,0 @@ -const {dirname} = require('path') - -const findMade = (opts, parent, path = undefined) => { - // we never want the 'made' return value to be a root directory - if (path === parent) - return Promise.resolve() - - return opts.statAsync(parent).then( - st => st.isDirectory() ? path : undefined, // will fail later - er => er.code === 'ENOENT' - ? findMade(opts, dirname(parent), parent) - : undefined - ) -} - -const findMadeSync = (opts, parent, path = undefined) => { - if (path === parent) - return undefined - - try { - return opts.statSync(parent).isDirectory() ? path : undefined - } catch (er) { - return er.code === 'ENOENT' - ? findMadeSync(opts, dirname(parent), parent) - : undefined - } -} - -module.exports = {findMade, findMadeSync} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-manual.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-manual.js deleted file mode 100644 index 2eb18cd6..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-manual.js +++ /dev/null @@ -1,64 +0,0 @@ -const {dirname} = require('path') - -const mkdirpManual = (path, opts, made) => { - opts.recursive = false - const parent = dirname(path) - if (parent === path) { - return opts.mkdirAsync(path, opts).catch(er => { - // swallowed by recursive implementation on posix systems - // any other error is a failure - if (er.code !== 'EISDIR') - throw er - }) - } - - return opts.mkdirAsync(path, opts).then(() => made || path, er => { - if (er.code === 'ENOENT') - return mkdirpManual(parent, opts) - .then(made => mkdirpManual(path, opts, made)) - if (er.code !== 'EEXIST' && er.code !== 'EROFS') - throw er - return opts.statAsync(path).then(st => { - if (st.isDirectory()) - return made - else - throw er - }, () => { throw er }) - }) -} - -const mkdirpManualSync = (path, opts, made) => { - const parent = dirname(path) - opts.recursive = false - - if (parent === path) { - try { - return opts.mkdirSync(path, opts) - } catch (er) { - // swallowed by recursive implementation on posix systems - // any other error is a failure - if (er.code !== 'EISDIR') - throw er - else - return - } - } - - try { - opts.mkdirSync(path, opts) - return made || path - } catch (er) { - if (er.code === 'ENOENT') - return mkdirpManualSync(path, opts, mkdirpManualSync(parent, opts, made)) - if (er.code !== 'EEXIST' && er.code !== 'EROFS') - throw er - try { - if (!opts.statSync(path).isDirectory()) - throw er - } catch (_) { - throw er - } - } -} - -module.exports = {mkdirpManual, mkdirpManualSync} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-native.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-native.js deleted file mode 100644 index c7a6b698..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/lib/mkdirp-native.js +++ /dev/null @@ -1,39 +0,0 @@ -const {dirname} = require('path') -const {findMade, findMadeSync} = require('./find-made.js') -const {mkdirpManual, mkdirpManualSync} = require('./mkdirp-manual.js') - -const mkdirpNative = (path, opts) => { - opts.recursive = true - const parent = dirname(path) - if (parent === path) - return opts.mkdirAsync(path, opts) - - return findMade(opts, path).then(made => - opts.mkdirAsync(path, opts).then(() => made) - .catch(er => { - if (er.code === 'ENOENT') - return mkdirpManual(path, opts) - else - throw er - })) -} - -const mkdirpNativeSync = (path, opts) => { - opts.recursive = true - const parent = dirname(path) - if (parent === path) - return opts.mkdirSync(path, opts) - - const made = findMadeSync(opts, path) - try { - opts.mkdirSync(path, opts) - return made - } catch (er) { - if (er.code === 'ENOENT') - return mkdirpManualSync(path, opts) - else - throw er - } -} - -module.exports = {mkdirpNative, mkdirpNativeSync} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/opts-arg.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/opts-arg.js deleted file mode 100644 index 2fa4833f..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/lib/opts-arg.js +++ /dev/null @@ -1,23 +0,0 @@ -const { promisify } = require('util') -const fs = require('fs') -const optsArg = opts => { - if (!opts) - opts = { mode: 0o777, fs } - else if (typeof opts === 'object') - opts = { mode: 0o777, fs, ...opts } - else if (typeof opts === 'number') - opts = { mode: opts, fs } - else if (typeof opts === 'string') - opts = { mode: parseInt(opts, 8), fs } - else - throw new TypeError('invalid options argument') - - opts.mkdir = opts.mkdir || opts.fs.mkdir || fs.mkdir - opts.mkdirAsync = promisify(opts.mkdir) - opts.stat = opts.stat || opts.fs.stat || fs.stat - opts.statAsync = promisify(opts.stat) - opts.statSync = opts.statSync || opts.fs.statSync || fs.statSync - opts.mkdirSync = opts.mkdirSync || opts.fs.mkdirSync || fs.mkdirSync - return opts -} -module.exports = optsArg diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/path-arg.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/path-arg.js deleted file mode 100644 index cc07de5a..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/lib/path-arg.js +++ /dev/null @@ -1,29 +0,0 @@ -const platform = process.env.__TESTING_MKDIRP_PLATFORM__ || process.platform -const { resolve, parse } = require('path') -const pathArg = path => { - if (/\0/.test(path)) { - // simulate same failure that node raises - throw Object.assign( - new TypeError('path must be a string without null bytes'), - { - path, - code: 'ERR_INVALID_ARG_VALUE', - } - ) - } - - path = resolve(path) - if (platform === 'win32') { - const badWinChars = /[*|"<>?:]/ - const {root} = parse(path) - if (badWinChars.test(path.substr(root.length))) { - throw Object.assign(new Error('Illegal characters in path.'), { - path, - code: 'EINVAL', - }) - } - } - - return path -} -module.exports = pathArg diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/lib/use-native.js b/node_modules/lv_font_conv/node_modules/mkdirp/lib/use-native.js deleted file mode 100644 index 079361de..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/lib/use-native.js +++ /dev/null @@ -1,10 +0,0 @@ -const fs = require('fs') - -const version = process.env.__TESTING_MKDIRP_NODE_VERSION__ || process.version -const versArr = version.replace(/^v/, '').split('.') -const hasNative = +versArr[0] > 10 || +versArr[0] === 10 && +versArr[1] >= 12 - -const useNative = !hasNative ? () => false : opts => opts.mkdir === fs.mkdir -const useNativeSync = !hasNative ? () => false : opts => opts.mkdirSync === fs.mkdirSync - -module.exports = {useNative, useNativeSync} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/package.json b/node_modules/lv_font_conv/node_modules/mkdirp/package.json deleted file mode 100644 index 1fb2e3d9..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/package.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "_args": [ - [ - "mkdirp@1.0.4", - "/home/vitaly/Dropbox/Coding/lv_font_conv" - ] - ], - "_from": "mkdirp@1.0.4", - "_id": "mkdirp@1.0.4", - "_inBundle": false, - "_integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "_location": "/mkdirp", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "mkdirp@1.0.4", - "name": "mkdirp", - "escapedName": "mkdirp", - "rawSpec": "1.0.4", - "saveSpec": null, - "fetchSpec": "1.0.4" - }, - "_requiredBy": [ - "/" - ], - "_resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "_spec": "1.0.4", - "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "bugs": { - "url": "https://github.com/isaacs/node-mkdirp/issues" - }, - "description": "Recursively mkdir, like `mkdir -p`", - "devDependencies": { - "require-inject": "^1.4.4", - "tap": "^14.10.7" - }, - "engines": { - "node": ">=10" - }, - "files": [ - "bin", - "lib", - "index.js" - ], - "homepage": "https://github.com/isaacs/node-mkdirp#readme", - "keywords": [ - "mkdir", - "directory", - "make dir", - "make", - "dir", - "recursive", - "native" - ], - "license": "MIT", - "main": "index.js", - "name": "mkdirp", - "repository": { - "type": "git", - "url": "git+https://github.com/isaacs/node-mkdirp.git" - }, - "scripts": { - "postpublish": "git push origin --follow-tags", - "postversion": "npm publish", - "preversion": "npm test", - "snap": "tap", - "test": "tap" - }, - "tap": { - "check-coverage": true, - "coverage-map": "map.js" - }, - "version": "1.0.4" -} diff --git a/node_modules/lv_font_conv/node_modules/mkdirp/readme.markdown b/node_modules/lv_font_conv/node_modules/mkdirp/readme.markdown deleted file mode 100644 index 827de590..00000000 --- a/node_modules/lv_font_conv/node_modules/mkdirp/readme.markdown +++ /dev/null @@ -1,266 +0,0 @@ -# mkdirp - -Like `mkdir -p`, but in Node.js! - -Now with a modern API and no\* bugs! - -\* may contain some bugs - -# example - -## pow.js - -```js -const mkdirp = require('mkdirp') - -// return value is a Promise resolving to the first directory created -mkdirp('/tmp/foo/bar/baz').then(made => - console.log(`made directories, starting with ${made}`)) -``` - -Output (where `/tmp/foo` already exists) - -``` -made directories, starting with /tmp/foo/bar -``` - -Or, if you don't have time to wait around for promises: - -```js -const mkdirp = require('mkdirp') - -// return value is the first directory created -const made = mkdirp.sync('/tmp/foo/bar/baz') -console.log(`made directories, starting with ${made}`) -``` - -And now /tmp/foo/bar/baz exists, huzzah! - -# methods - -```js -const mkdirp = require('mkdirp') -``` - -## mkdirp(dir, [opts]) -> Promise - -Create a new directory and any necessary subdirectories at `dir` with octal -permission string `opts.mode`. If `opts` is a string or number, it will be -treated as the `opts.mode`. - -If `opts.mode` isn't specified, it defaults to `0o777 & -(~process.umask())`. - -Promise resolves to first directory `made` that had to be created, or -`undefined` if everything already exists. Promise rejects if any errors -are encountered. Note that, in the case of promise rejection, some -directories _may_ have been created, as recursive directory creation is not -an atomic operation. - -You can optionally pass in an alternate `fs` implementation by passing in -`opts.fs`. Your implementation should have `opts.fs.mkdir(path, opts, cb)` -and `opts.fs.stat(path, cb)`. - -You can also override just one or the other of `mkdir` and `stat` by -passing in `opts.stat` or `opts.mkdir`, or providing an `fs` option that -only overrides one of these. - -## mkdirp.sync(dir, opts) -> String|null - -Synchronously create a new directory and any necessary subdirectories at -`dir` with octal permission string `opts.mode`. If `opts` is a string or -number, it will be treated as the `opts.mode`. - -If `opts.mode` isn't specified, it defaults to `0o777 & -(~process.umask())`. - -Returns the first directory that had to be created, or undefined if -everything already exists. - -You can optionally pass in an alternate `fs` implementation by passing in -`opts.fs`. Your implementation should have `opts.fs.mkdirSync(path, mode)` -and `opts.fs.statSync(path)`. - -You can also override just one or the other of `mkdirSync` and `statSync` -by passing in `opts.statSync` or `opts.mkdirSync`, or providing an `fs` -option that only overrides one of these. - -## mkdirp.manual, mkdirp.manualSync - -Use the manual implementation (not the native one). This is the default -when the native implementation is not available or the stat/mkdir -implementation is overridden. - -## mkdirp.native, mkdirp.nativeSync - -Use the native implementation (not the manual one). This is the default -when the native implementation is available and stat/mkdir are not -overridden. - -# implementation - -On Node.js v10.12.0 and above, use the native `fs.mkdir(p, -{recursive:true})` option, unless `fs.mkdir`/`fs.mkdirSync` has been -overridden by an option. - -## native implementation - -- If the path is a root directory, then pass it to the underlying - implementation and return the result/error. (In this case, it'll either - succeed or fail, but we aren't actually creating any dirs.) -- Walk up the path statting each directory, to find the first path that - will be created, `made`. -- Call `fs.mkdir(path, { recursive: true })` (or `fs.mkdirSync`) -- If error, raise it to the caller. -- Return `made`. - -## manual implementation - -- Call underlying `fs.mkdir` implementation, with `recursive: false` -- If error: - - If path is a root directory, raise to the caller and do not handle it - - If ENOENT, mkdirp parent dir, store result as `made` - - stat(path) - - If error, raise original `mkdir` error - - If directory, return `made` - - Else, raise original `mkdir` error -- else - - return `undefined` if a root dir, or `made` if set, or `path` - -## windows vs unix caveat - -On Windows file systems, attempts to create a root directory (ie, a drive -letter or root UNC path) will fail. If the root directory exists, then it -will fail with `EPERM`. If the root directory does not exist, then it will -fail with `ENOENT`. - -On posix file systems, attempts to create a root directory (in recursive -mode) will succeed silently, as it is treated like just another directory -that already exists. (In non-recursive mode, of course, it fails with -`EEXIST`.) - -In order to preserve this system-specific behavior (and because it's not as -if we can create the parent of a root directory anyway), attempts to create -a root directory are passed directly to the `fs` implementation, and any -errors encountered are not handled. - -## native error caveat - -The native implementation (as of at least Node.js v13.4.0) does not provide -appropriate errors in some cases (see -[nodejs/node#31481](https://github.com/nodejs/node/issues/31481) and -[nodejs/node#28015](https://github.com/nodejs/node/issues/28015)). - -In order to work around this issue, the native implementation will fall -back to the manual implementation if an `ENOENT` error is encountered. - -# choosing a recursive mkdir implementation - -There are a few to choose from! Use the one that suits your needs best :D - -## use `fs.mkdir(path, {recursive: true}, cb)` if: - -- You wish to optimize performance even at the expense of other factors. -- You don't need to know the first dir created. -- You are ok with getting `ENOENT` as the error when some other problem is - the actual cause. -- You can limit your platforms to Node.js v10.12 and above. -- You're ok with using callbacks instead of promises. -- You don't need/want a CLI. -- You don't need to override the `fs` methods in use. - -## use this module (mkdirp 1.x) if: - -- You need to know the first directory that was created. -- You wish to use the native implementation if available, but fall back - when it's not. -- You prefer promise-returning APIs to callback-taking APIs. -- You want more useful error messages than the native recursive mkdir - provides (at least as of Node.js v13.4), and are ok with re-trying on - `ENOENT` to achieve this. -- You need (or at least, are ok with) a CLI. -- You need to override the `fs` methods in use. - -## use [`make-dir`](http://npm.im/make-dir) if: - -- You do not need to know the first dir created (and wish to save a few - `stat` calls when using the native implementation for this reason). -- You wish to use the native implementation if available, but fall back - when it's not. -- You prefer promise-returning APIs to callback-taking APIs. -- You are ok with occasionally getting `ENOENT` errors for failures that - are actually related to something other than a missing file system entry. -- You don't need/want a CLI. -- You need to override the `fs` methods in use. - -## use mkdirp 0.x if: - -- You need to know the first directory that was created. -- You need (or at least, are ok with) a CLI. -- You need to override the `fs` methods in use. -- You're ok with using callbacks instead of promises. -- You are not running on Windows, where the root-level ENOENT errors can - lead to infinite regress. -- You think vinyl just sounds warmer and richer for some weird reason. -- You are supporting truly ancient Node.js versions, before even the advent - of a `Promise` language primitive. (Please don't. You deserve better.) - -# cli - -This package also ships with a `mkdirp` command. - -``` -$ mkdirp -h - -usage: mkdirp [DIR1,DIR2..] {OPTIONS} - - Create each supplied directory including any necessary parent directories - that don't yet exist. - - If the directory already exists, do nothing. - -OPTIONS are: - - -m If a directory needs to be created, set the mode as an octal - --mode= permission string. - - -v --version Print the mkdirp version number - - -h --help Print this helpful banner - - -p --print Print the first directories created for each path provided - - --manual Use manual implementation, even if native is available -``` - -# install - -With [npm](http://npmjs.org) do: - -``` -npm install mkdirp -``` - -to get the library locally, or - -``` -npm install -g mkdirp -``` - -to get the command everywhere, or - -``` -npx mkdirp ... -``` - -to run the command without installing it globally. - -# platform support - -This module works on node v8, but only v10 and above are officially -supported, as Node v8 reached its LTS end of life 2020-01-01, which is in -the past, as of this writing. - -# license - -MIT diff --git a/node_modules/lv_font_conv/node_modules/ms/index.js b/node_modules/lv_font_conv/node_modules/ms/index.js deleted file mode 100644 index c4498bcc..00000000 --- a/node_modules/lv_font_conv/node_modules/ms/index.js +++ /dev/null @@ -1,162 +0,0 @@ -/** - * Helpers. - */ - -var s = 1000; -var m = s * 60; -var h = m * 60; -var d = h * 24; -var w = d * 7; -var y = d * 365.25; - -/** - * Parse or format the given `val`. - * - * Options: - * - * - `long` verbose formatting [false] - * - * @param {String|Number} val - * @param {Object} [options] - * @throws {Error} throw an error if val is not a non-empty string or a number - * @return {String|Number} - * @api public - */ - -module.exports = function(val, options) { - options = options || {}; - var type = typeof val; - if (type === 'string' && val.length > 0) { - return parse(val); - } else if (type === 'number' && isFinite(val)) { - return options.long ? fmtLong(val) : fmtShort(val); - } - throw new Error( - 'val is not a non-empty string or a valid number. val=' + - JSON.stringify(val) - ); -}; - -/** - * Parse the given `str` and return milliseconds. - * - * @param {String} str - * @return {Number} - * @api private - */ - -function parse(str) { - str = String(str); - if (str.length > 100) { - return; - } - var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec( - str - ); - if (!match) { - return; - } - var n = parseFloat(match[1]); - var type = (match[2] || 'ms').toLowerCase(); - switch (type) { - case 'years': - case 'year': - case 'yrs': - case 'yr': - case 'y': - return n * y; - case 'weeks': - case 'week': - case 'w': - return n * w; - case 'days': - case 'day': - case 'd': - return n * d; - case 'hours': - case 'hour': - case 'hrs': - case 'hr': - case 'h': - return n * h; - case 'minutes': - case 'minute': - case 'mins': - case 'min': - case 'm': - return n * m; - case 'seconds': - case 'second': - case 'secs': - case 'sec': - case 's': - return n * s; - case 'milliseconds': - case 'millisecond': - case 'msecs': - case 'msec': - case 'ms': - return n; - default: - return undefined; - } -} - -/** - * Short format for `ms`. - * - * @param {Number} ms - * @return {String} - * @api private - */ - -function fmtShort(ms) { - var msAbs = Math.abs(ms); - if (msAbs >= d) { - return Math.round(ms / d) + 'd'; - } - if (msAbs >= h) { - return Math.round(ms / h) + 'h'; - } - if (msAbs >= m) { - return Math.round(ms / m) + 'm'; - } - if (msAbs >= s) { - return Math.round(ms / s) + 's'; - } - return ms + 'ms'; -} - -/** - * Long format for `ms`. - * - * @param {Number} ms - * @return {String} - * @api private - */ - -function fmtLong(ms) { - var msAbs = Math.abs(ms); - if (msAbs >= d) { - return plural(ms, msAbs, d, 'day'); - } - if (msAbs >= h) { - return plural(ms, msAbs, h, 'hour'); - } - if (msAbs >= m) { - return plural(ms, msAbs, m, 'minute'); - } - if (msAbs >= s) { - return plural(ms, msAbs, s, 'second'); - } - return ms + ' ms'; -} - -/** - * Pluralization helper. - */ - -function plural(ms, msAbs, n, name) { - var isPlural = msAbs >= n * 1.5; - return Math.round(ms / n) + ' ' + name + (isPlural ? 's' : ''); -} diff --git a/node_modules/lv_font_conv/node_modules/ms/license.md b/node_modules/lv_font_conv/node_modules/ms/license.md deleted file mode 100644 index 69b61253..00000000 --- a/node_modules/lv_font_conv/node_modules/ms/license.md +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Zeit, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/ms/package.json b/node_modules/lv_font_conv/node_modules/ms/package.json deleted file mode 100644 index 6d514e71..00000000 --- a/node_modules/lv_font_conv/node_modules/ms/package.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "_args": [ - [ - "ms@2.1.2", - "/home/vitaly/Dropbox/Coding/lv_font_conv" - ] - ], - "_from": "ms@2.1.2", - "_id": "ms@2.1.2", - "_inBundle": false, - "_integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "_location": "/ms", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "ms@2.1.2", - "name": "ms", - "escapedName": "ms", - "rawSpec": "2.1.2", - "saveSpec": null, - "fetchSpec": "2.1.2" - }, - "_requiredBy": [ - "/debug" - ], - "_resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "_spec": "2.1.2", - "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", - "bugs": { - "url": "https://github.com/zeit/ms/issues" - }, - "description": "Tiny millisecond conversion utility", - "devDependencies": { - "eslint": "4.12.1", - "expect.js": "0.3.1", - "husky": "0.14.3", - "lint-staged": "5.0.0", - "mocha": "4.0.1" - }, - "eslintConfig": { - "extends": "eslint:recommended", - "env": { - "node": true, - "es6": true - } - }, - "files": [ - "index.js" - ], - "homepage": "https://github.com/zeit/ms#readme", - "license": "MIT", - "lint-staged": { - "*.js": [ - "npm run lint", - "prettier --single-quote --write", - "git add" - ] - }, - "main": "./index", - "name": "ms", - "repository": { - "type": "git", - "url": "git+https://github.com/zeit/ms.git" - }, - "scripts": { - "lint": "eslint lib/* bin/*", - "precommit": "lint-staged", - "test": "mocha tests.js" - }, - "version": "2.1.2" -} diff --git a/node_modules/lv_font_conv/node_modules/ms/readme.md b/node_modules/lv_font_conv/node_modules/ms/readme.md deleted file mode 100644 index 9a1996b1..00000000 --- a/node_modules/lv_font_conv/node_modules/ms/readme.md +++ /dev/null @@ -1,60 +0,0 @@ -# ms - -[![Build Status](https://travis-ci.org/zeit/ms.svg?branch=master)](https://travis-ci.org/zeit/ms) -[![Join the community on Spectrum](https://withspectrum.github.io/badge/badge.svg)](https://spectrum.chat/zeit) - -Use this package to easily convert various time formats to milliseconds. - -## Examples - -```js -ms('2 days') // 172800000 -ms('1d') // 86400000 -ms('10h') // 36000000 -ms('2.5 hrs') // 9000000 -ms('2h') // 7200000 -ms('1m') // 60000 -ms('5s') // 5000 -ms('1y') // 31557600000 -ms('100') // 100 -ms('-3 days') // -259200000 -ms('-1h') // -3600000 -ms('-200') // -200 -``` - -### Convert from Milliseconds - -```js -ms(60000) // "1m" -ms(2 * 60000) // "2m" -ms(-3 * 60000) // "-3m" -ms(ms('10 hours')) // "10h" -``` - -### Time Format Written-Out - -```js -ms(60000, { long: true }) // "1 minute" -ms(2 * 60000, { long: true }) // "2 minutes" -ms(-3 * 60000, { long: true }) // "-3 minutes" -ms(ms('10 hours'), { long: true }) // "10 hours" -``` - -## Features - -- Works both in [Node.js](https://nodejs.org) and in the browser -- If a number is supplied to `ms`, a string with a unit is returned -- If a string that contains the number is supplied, it returns it as a number (e.g.: it returns `100` for `'100'`) -- If you pass a string with a number and a valid unit, the number of equivalent milliseconds is returned - -## Related Packages - -- [ms.macro](https://github.com/knpwrs/ms.macro) - Run `ms` as a macro at build-time. - -## Caught a Bug? - -1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device -2. Link the package to the global module directory: `npm link` -3. Within the module you want to test your local development instance of ms, just link it to the dependencies: `npm link ms`. Instead of the default one from npm, Node.js will now use your clone of ms! - -As always, you can run the tests using: `npm test` diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/LICENSE b/node_modules/lv_font_conv/node_modules/opentype.js/LICENSE deleted file mode 100644 index c9b39953..00000000 --- a/node_modules/lv_font_conv/node_modules/opentype.js/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2017 Frederik De Bleser - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/README.md b/node_modules/lv_font_conv/node_modules/opentype.js/README.md deleted file mode 100644 index bcb557e8..00000000 --- a/node_modules/lv_font_conv/node_modules/opentype.js/README.md +++ /dev/null @@ -1,313 +0,0 @@ - -# opentype.js · [![Build Status](https://img.shields.io/travis/opentypejs/opentype.js.svg?style=flat-square)](https://travis-ci.org/opentypejs/opentype.js) [![npm](https://img.shields.io/npm/v/opentype.js.svg?style=flat-square)](https://www.npmjs.com/package/opentype.js) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://github.com/opentypejs/opentype.js/blob/master/LICENSE) [![david-dm](https://david-dm.org/opentypejs/opentype.js.svg)](https://david-dm.org/opentypejs/opentype.js) [![Gitter](https://badges.gitter.im/opentypejs/opentype.js.svg)](https://gitter.im/opentypejs/opentype.js) - -opentype.js is a JavaScript parser and writer for TrueType and OpenType fonts. - -It gives you access to the letterforms of text from the browser or Node.js. -See [https://opentype.js.org/](https://opentype.js.org/) for a live demo. - -Features -======== - -* Create a bézier path out of a piece of text. -* Support for composite glyphs (accented letters). -* Support for WOFF, OTF, TTF (both with TrueType `glyf` and PostScript `cff` outlines) -* Support for kerning (Using GPOS or the kern table). -* Support for ligatures. -* Support for TrueType font hinting. -* Support arabic text rendering (See issue #364 & PR #359 #361) -* A low memory mode is available as an option (see #329) -* Runs in the browser and Node.js. - -Installation -============ - -### Using [npm](http://npmjs.org/) package manager - - npm install opentype.js - - const opentype = require('opentype.js'); - - import opentype from 'opentype.js' - - import { load } from 'opentype.js' - -Using TypeScript? [See this example](examples/typescript) - -Note: OpenType.js uses ES6-style imports, so if you want to edit it and debug it in Node.js run `npm run build` first and use `npm run watch` to automatically rebuild when files change. - -### Directly - -[Download the latest ZIP](https://github.com/opentypejs/opentype.js/archive/master.zip) and grab the files in the `dist` -folder. These are compiled. - -### Using via a CDN - -To use via a CDN, include the following code in your html: - - - -### Using Bower (Deprecated [see official post](https://bower.io/blog/2017/how-to-migrate-away-from-bower/)) - -To install using [Bower](https://bower.io/), enter the following command in your project directory: - - bower install opentype.js - -You can then include them in your scripts using: - - - - -API -=== -### Loading a font -![OpenType.js example Hello World](https://raw.github.com/opentypejs/opentype.js/master/g/hello-world.png) - -Use `opentype.load(url, callback)` to load a font from a URL. Since this method goes out the network, it is asynchronous. -The callback gets `(err, font)` where `font` is a `Font` object. Check if the `err` is null before using the font. -```javascript -opentype.load('fonts/Roboto-Black.ttf', function(err, font) { - if (err) { - alert('Font could not be loaded: ' + err); - } else { - // Now let's display it on a canvas with id "canvas" - const ctx = document.getElementById('canvas').getContext('2d'); - - // Construct a Path object containing the letter shapes of the given text. - // The other parameters are x, y and fontSize. - // Note that y is the position of the baseline. - const path = font.getPath('Hello, World!', 0, 150, 72); - - // If you just want to draw the text you can also use font.draw(ctx, text, x, y, fontSize). - path.draw(ctx); - } -}); -``` - -You can also use `es6 async/await` syntax to load your fonts - -```javascript -async function make(){ - const font = await opentype.load('fonts/Roboto-Black.ttf'); - const path = font.getPath('Hello, World!', 0, 150, 72); - console.log(path); -} -``` - -If you already have an `ArrayBuffer`, you can use `opentype.parse(buffer)` to parse the buffer. This method always -returns a Font, but check `font.supported` to see if the font is in a supported format. (Fonts can be marked unsupported -if they have encoding tables we can't read). - - const font = opentype.parse(myBuffer); - -### Loading a font synchronously (Node.js) -Use `opentype.loadSync(url)` to load a font from a file and return a `Font` object. -Throws an error if the font could not be parsed. This only works in Node.js. - - const font = opentype.loadSync('fonts/Roboto-Black.ttf'); - -### Writing a font -Once you have a `Font` object (either by using `opentype.load` or by creating a new one from scratch) you can write it -back out as a binary file. - -In the browser, you can use `Font.download()` to instruct the browser to download a binary .OTF file. The name is based -on the font name. -```javascript -// Create the bézier paths for each of the glyphs. -// Note that the .notdef glyph is required. -const notdefGlyph = new opentype.Glyph({ - name: '.notdef', - unicode: 0, - advanceWidth: 650, - path: new opentype.Path() -}); - -const aPath = new opentype.Path(); -aPath.moveTo(100, 0); -aPath.lineTo(100, 700); -// more drawing instructions... -const aGlyph = new opentype.Glyph({ - name: 'A', - unicode: 65, - advanceWidth: 650, - path: aPath -}); - -const glyphs = [notdefGlyph, aGlyph]; -const font = new opentype.Font({ - familyName: 'OpenTypeSans', - styleName: 'Medium', - unitsPerEm: 1000, - ascender: 800, - descender: -200, - glyphs: glyphs}); -font.download(); -``` - -If you want to inspect the font, use `font.toTables()` to generate an object showing the data structures that map -directly to binary values. If you want to get an `ArrayBuffer`, use `font.toArrayBuffer()`. - - -### The Font object -A Font represents a loaded OpenType font file. It contains a set of glyphs and methods to draw text on a drawing context, or to get a path representing the text. - -* `glyphs`: an indexed list of Glyph objects. -* `unitsPerEm`: X/Y coordinates in fonts are stored as integers. This value determines the size of the grid. Common values are 2048 and 4096. -* `ascender`: Distance from baseline of highest ascender. In font units, not pixels. -* `descender`: Distance from baseline of lowest descender. In font units, not pixels. - -#### `Font.getPath(text, x, y, fontSize, options)` -Create a Path that represents the given text. -* `x`: Horizontal position of the beginning of the text. (default: 0) -* `y`: Vertical position of the *baseline* of the text. (default: 0) -* `fontSize`: Size of the text in pixels (default: 72). - -Options is an optional object containing: -* `kerning`: if true takes kerning information into account (default: true) -* `features`: an object with [OpenType feature tags](https://docs.microsoft.com/en-us/typography/opentype/spec/featuretags) as keys, and a boolean value to enable each feature. -Currently only ligature features "liga" and "rlig" are supported (default: true). -* `hinting`: if true uses TrueType font hinting if available (default: false). - -_Note: there is also `Font.getPaths` with the same arguments which returns a list of Paths._ - -#### `Font.draw(ctx, text, x, y, fontSize, options)` -Create a Path that represents the given text. -* `ctx`: A 2D drawing context, like Canvas. -* `x`: Horizontal position of the beginning of the text. (default: 0) -* `y`: Vertical position of the *baseline* of the text. (default: 0) -* `fontSize`: Size of the text in pixels (default: 72). - -Options is an optional object containing: -* `kerning`: if true takes kerning information into account (default: true) -* `features`: an object with [OpenType feature tags](https://docs.microsoft.com/en-us/typography/opentype/spec/featuretags) as keys, and a boolean value to enable each feature. -Currently only ligature features "liga" and "rlig" are supported (default: true). -* `hinting`: if true uses TrueType font hinting if available (default: false). - -#### `Font.drawPoints(ctx, text, x, y, fontSize, options)` -Draw the points of all glyphs in the text. On-curve points will be drawn in blue, off-curve points will be drawn in red. The arguments are the same as `Font.draw`. - -#### `Font.drawMetrics(ctx, text, x, y, fontSize, options)` -Draw lines indicating important font measurements for all glyphs in the text. -Black lines indicate the origin of the coordinate system (point 0,0). -Blue lines indicate the glyph bounding box. -Green line indicates the advance width of the glyph. - -#### `Font.stringToGlyphs(string)` -Convert the string to a list of glyph objects. -Note that there is no strict 1-to-1 correspondence between the string and glyph list due to -possible substitutions such as ligatures. The list of returned glyphs can be larger or smaller than the length of the given string. - -#### `Font.charToGlyph(char)` -Convert the character to a `Glyph` object. Returns null if the glyph could not be found. Note that this function assumes that there is a one-to-one mapping between the given character and a glyph; for complex scripts this might not be the case. - -#### `Font.getKerningValue(leftGlyph, rightGlyph)` -Retrieve the value of the [kerning pair](https://en.wikipedia.org/wiki/Kerning) between the left glyph (or its index) and the right glyph (or its index). If no kerning pair is found, return 0. The kerning value gets added to the advance width when calculating the spacing between glyphs. - -#### `Font.getAdvanceWidth(text, fontSize, options)` -Returns the advance width of a text. - -This is something different than Path.getBoundingBox() as for example a -suffixed whitespace increases the advancewidth but not the bounding box -or an overhanging letter like a calligraphic 'f' might have a quite larger -bounding box than its advance width. - -This corresponds to canvas2dContext.measureText(text).width -* `fontSize`: Size of the text in pixels (default: 72). -* `options`: See Font.getPath - -#### The Glyph object -A Glyph is an individual mark that often corresponds to a character. Some glyphs, such as ligatures, are a combination of many characters. Glyphs are the basic building blocks of a font. - -* `font`: A reference to the `Font` object. -* `name`: The glyph name (e.g. "Aring", "five") -* `unicode`: The primary unicode value of this glyph (can be `undefined`). -* `unicodes`: The list of unicode values for this glyph (most of the time this will be 1, can also be empty). -* `index`: The index number of the glyph. -* `advanceWidth`: The width to advance the pen when drawing this glyph. -* `xMin`, `yMin`, `xMax`, `yMax`: The bounding box of the glyph. -* `path`: The raw, unscaled path of the glyph. - -##### `Glyph.getPath(x, y, fontSize)` -Get a scaled glyph Path object we can draw on a drawing context. -* `x`: Horizontal position of the glyph. (default: 0) -* `y`: Vertical position of the *baseline* of the glyph. (default: 0) -* `fontSize`: Font size in pixels (default: 72). - -##### `Glyph.getBoundingBox()` -Calculate the minimum bounding box for the unscaled path of the given glyph. Returns an `opentype.BoundingBox` object that contains x1/y1/x2/y2. -If the glyph has no points (e.g. a space character), all coordinates will be zero. - -##### `Glyph.draw(ctx, x, y, fontSize)` -Draw the glyph on the given context. -* `ctx`: The drawing context. -* `x`: Horizontal position of the glyph. (default: 0) -* `y`: Vertical position of the *baseline* of the glyph. (default: 0) -* `fontSize`: Font size, in pixels (default: 72). - -##### `Glyph.drawPoints(ctx, x, y, fontSize)` -Draw the points of the glyph on the given context. -On-curve points will be drawn in blue, off-curve points will be drawn in red. -The arguments are the same as `Glyph.draw`. - -##### `Glyph.drawMetrics(ctx, x, y, fontSize)` -Draw lines indicating important font measurements for all glyphs in the text. -Black lines indicate the origin of the coordinate system (point 0,0). -Blue lines indicate the glyph bounding box. -Green line indicates the advance width of the glyph. -The arguments are the same as `Glyph.draw`. - -### The Path object -Once you have a path through `Font.getPath` or `Glyph.getPath`, you can use it. - -* `commands`: The path commands. Each command is a dictionary containing a type and coordinates. See below for examples. -* `fill`: The fill color of the `Path`. Color is a string representing a [CSS color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value). (default: 'black') -* `stroke`: The stroke color of the `Path`. Color is a string representing a [CSS color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value). (default: `null`: the path will not be stroked) -* `strokeWidth`: The line thickness of the `Path`. (default: 1, but since the `stroke` is null no stroke will be drawn) - -##### `Path.draw(ctx)` -Draw the path on the given 2D context. This uses the `fill`, `stroke` and `strokeWidth` properties of the `Path` object. -* `ctx`: The drawing context. - -##### `Path.getBoundingBox()` -Calculate the minimum bounding box for the given path. Returns an `opentype.BoundingBox` object that contains x1/y1/x2/y2. -If the path is empty (e.g. a space character), all coordinates will be zero. - -##### `Path.toPathData(decimalPlaces)` -Convert the Path to a string of path data instructions. -See https://www.w3.org/TR/SVG/paths.html#PathData -* `decimalPlaces`: The amount of decimal places for floating-point values. (default: 2) - -##### `Path.toSVG(decimalPlaces)` -Convert the path to a SVG <path> element, as a string. -* `decimalPlaces`: The amount of decimal places for floating-point values. (default: 2) - -#### Path commands -* **Move To**: Move to a new position. This creates a new contour. Example: `{type: 'M', x: 100, y: 200}` -* **Line To**: Draw a line from the previous position to the given coordinate. Example: `{type: 'L', x: 100, y: 200}` -* **Curve To**: Draw a bézier curve from the current position to the given coordinate. Example: `{type: 'C', x1: 0, y1: 50, x2: 100, y2: 200, x: 100, y: 200}` -* **Quad To**: Draw a quadratic bézier curve from the current position to the given coordinate. Example: `{type: 'Q', x1: 0, y1: 50, x: 100, y: 200}` -* **Close**: Close the path. If stroked, this will draw a line from the first to the last point of the contour. Example: `{type: 'Z'}` - - -## Versioning - -We use [SemVer](https://semver.org/) for versioning. - - -## License - -MIT - - -Thanks -====== -I would like to acknowledge the work of others without which opentype.js wouldn't be possible: - -* [pdf.js](https://mozilla.github.io/pdf.js/): for an awesome implementation of font parsing in the browser. -* [FreeType](https://www.freetype.org/): for the nitty-gritty details and filling in the gaps when the spec was incomplete. -* [ttf.js](https://ynakajima.github.io/ttf.js/demo/glyflist/): for hints about the TrueType parsing code. -* [CFF-glyphlet-fonts](https://pomax.github.io/CFF-glyphlet-fonts/): for a great explanation/implementation of CFF font writing. -* [tiny-inflate](https://github.com/foliojs/tiny-inflate): for WOFF decompression. -* [Microsoft Typography](https://docs.microsoft.com/en-us/typography/opentype/spec/otff): the go-to reference for all things OpenType. -* [Adobe Compact Font Format spec](http://download.microsoft.com/download/8/0/1/801a191c-029d-4af3-9642-555f6fe514ee/cff.pdf) and the [Adobe Type 2 Charstring spec](http://download.microsoft.com/download/8/0/1/801a191c-029d-4af3-9642-555f6fe514ee/type2.pdf): explains the data structures and commands for the CFF glyph format. -* All contributing authors mentioned in the [AUTHORS](https://github.com/opentypejs/opentype.js/blob/master/AUTHORS.md) file. diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/RELEASES.md b/node_modules/lv_font_conv/node_modules/opentype.js/RELEASES.md deleted file mode 100644 index e1c698b1..00000000 --- a/node_modules/lv_font_conv/node_modules/opentype.js/RELEASES.md +++ /dev/null @@ -1,267 +0,0 @@ -1.3.3 (April 20, 2020) -===================== -* fix GlyphOptions with falsy values (#430) - -1.3.2 (April 20, 2020) -===================== -* Re-export named exports with a default export and add a TypeScript import example - -* 1.3.1 (April 13, 2020) -===================== -* Revert Fix Path.toPathData and Path.toSVG - X Axis is flipped (#369) - -1.3.0 (April 13, 2020) -===================== -* Forward os2 Table attributs during font construction (#422) -* Add default export - -1.2.1 (April 13, 2020) -===================== -* Fix Path.toPathData and Path.toSVG - X Axis is flipped (#369) -* Fix use of Promise / async/await in the load function (#427) -* Fix a bug for unsupported SUBSTITUTIONS #403 - -1.2.0 (April 13, 2020) -===================== -* Fix issue #385, merge default options with user options (#386) -* Adds support for browser Async/Await for .load() (#389) -* Introduce ES6 module build (#391) -* Fix test in featureQuery -* Remove Node 4 from Travis (#392) -* Update dependencies & build dist files - -1.1.0 (May 1, 2019) -===================== -* Support reading GSUB Single substitution format 1 (PR #382) (thanks @solomancode!) - -1.0.1 (April 19, 2019) -===================== -* Fix error if defaultLangSys is undefined (Issue #378) - -1.0.0 (April 17, 2019) -===================== -* Render arabic rtl text properly (PR #361, partial fix of #364) (thanks @solomancode!) -* #361 introduced a breaking change to `Font.prototype.defaultRenderOptions` -Before -```js -Font.prototype.defaultRenderOptions = { - kerning: true, - features: { - liga: true, - rlig: true - } -}; -``` - -Now -```js -Font.prototype.defaultRenderOptions = { - kerning: true, - features: [ - /** - * these 4 features are required to render Arabic text properly - * and shouldn't be turned off when rendering arabic text. - */ - { script: 'arab', tags: ['init', 'medi', 'fina', 'rlig'] }, - { script: 'latn', tags: ['liga', 'rlig'] } - ] -}; -``` - -Also as this project is now using SemVer, the breaking change required a new major version, 1.0.0! - -0.12.0 (April 17, 2019) -===================== -* Fix Glyph.getPath() issue (PR #362, fixes #363) (thanks @solomancode!) -* Add lowMemory mode (PR #329) (thanks @debussy2k!) -* Update README (PR #377) (thanks @jolg42!) - -0.11.0 (October 22, 2018) -===================== -* Support Arabic text rendering (PR #359, fixes #46) (thanks @solomancode!) - -0.10.0 (August 14, 2018) -===================== -* font.download(): use window.URL instead of window.requestFileSystem, which works on a larger set of browsers : Chrome (32+), Opera (19+), Firefox (26+), Safari (7.1+), and all of Edge. - -0.9.0 (June 21, 2018) -===================== -* Update/Migrate rollup, update all dependencies, add package-lock.json and fix circular dependency (thanks @jolg42!) -* Parse cmap table with platform id 0 as well (PR #350, fixes #348) (thanks @moyogo!) -* Prevent auto-generated postScriptName from containing whitespace (#339) (thanks @mqudsi!) -* Support non-Basic-Multilingual-Plane (BMP) characters (#338) (thanks @antonytse!) -* GPOS: display correct error message in some cases of malformed data (#336) (thanks @fpirsch!) -* Restore simple GPOS kerning in font.getKerningValue (#335) (thanks @fpirsch!) -* Fix duplicated lineTo when using `getPath` (#328) (thanks @jolg42!) -* Change example generate-font-node.js to be compatible with any Node.js version (thanks @jolg42!) - -0.8.0 (March 6, 2018) -===================== -* Fix loading font file on Android devices (thanks @maoamid!). -* Fix loading fonts from a local source (file://data/... for Android for example (thanks @IntuilabGit!). -* Fixing 2 issues when hinting "mutlu.ttf" (thanks @axkibe!). -* Add some support for OpenType font variations (thanks @taylorb-monotype!). -* Make cmap table format 12 if needed (thanks @Jolg42!). -* Enable uglify's mangle and compress optimizations for a ~30% smaller minified file. (thanks @lojjic & @Jolg42!). -* Better parsing of NULL pointers (thanks @fpirsch!). -* Fix bad path init (empty glyphs) (thanks @fpirsch!). -* Rewrite GPOS parsing (thanks @fpirsch!). -* Roboto-Black.ttf updated (thanks @Jolg42!). - -0.7.3 (July 18, 2017) -===================== -* Fix "Object x already has key" error in Safari (thanks @neiltron!). -* Fixed a bug where Font.getPaths() didn't pass options (thanks @keeslinp!). - -0.7.2 (June 7, 2017) -==================== -* WOFF fonts with cvt tables now parse correctly. -* Migrated to ES6 modules and let/const. -* Use Rollup to bundle the JavaScript. - -0.7.1 (Apr 25, 2017) -==================== -* Auto-generated glyph IDs (CID-keyed fonts) are now prefixed with "gid", e.g. "gid42". -* Fix ligature substitution for fonts with coverage table format 2. -* Better error messages when no valid cmap is found. - -0.7.0 (Apr 25, 2017) -==================== -* Add font hinting (thanks @axkibe!) -* Add support for CID-keyed fonts, thanks to @tshinnic. -* TrueType fonts with signature 'true' or 'typ1' are also supported. -* Fixing rounding issues. -* Add GSUB and kern output in font-inspector. -* Add font loading error callback. -* Dev server turns browser caching off. -* Add encoding support for variation adjustment deltas (thanks @brawer!). - -0.6.9 (Jan 17, 2017) -==================== -* Add ligature rendering (thanks @fpirsch!) - -0.6.8 (Jan 9, 2017) -========================= -* Add a `getBoundingBox` method to the `Path` and `Glyph` objects. - -0.6.7 (Jan 5, 2017) -========================= -* Add basic support for Mac OS X format kern tables. - -0.6.6 (October 25, 2016) -========================= -* Add support for letter-spacing and tracking (thanks @lachmanski!). -* Fixed a bug in the nameToGlyph function. - -0.6.5 (September 9, 2016) -========================= -* GSUB reading and writing by @fpirsch. This is still missing a user-friendly API. -* Add support for cmap table format 12, which enables support for Unicode characters outside of the 0x0 - 0xFFFF range. -* Better API documentation using [JSDoc](http://usejsdoc.org/). -* Accessing xMin/... metrics works before path load.
 - -0.6.4 (June 30, 2016) -========================= -* Add X/Y scale options to compute a streched path of a glyph. -* Correct reading/writing of font timestamps. -* examples/generate-font-node.js now generates "full" Latin font. -* Add OS/2 value options for weight, width and fsSelection. - -0.6.3 (May 10, 2016) -========================= -* Wrapped parseBuffer in a try/catch so it doesn't throw exceptions. Thanks @rBurgett! -* Fix a leaking global variable. Thanks @cuixiping! - -0.6.2 (March 11, 2016) -========================= -* Improve table writing to support nested subtables. Thanks @fpirsch! - -0.6.1 (February 20, 2016) -========================= -* Left side bearing is now correctly reported. -* Simplified code for including ascender / descender values. - -0.6.0 (December 1, 2015) -======================== -* Improvements to font writing: generated fonts now work properly on OS X. -* When creating a new font, ascender and descender are now required. - -0.5.1 (October 26, 2015) -======================== -* Add `Font.getPaths()` which returns a list of paths. - -0.5.0 (October 6, 2015) -======================= -* Read support for WOFF. - -0.4.11 (September 27, 2015) -=========================== -* Fix issue with loading of TrueType composite glyphs. -* Fix issue with missing hmtx values. -* Sensible getMetrics() values for empty glyphs (e.g. space). - -0.4.10 (July 30, 2015) -====================== -* Add loadSync method for Node.js. -* Unit tests for basic types and tables. -* Implement MACSTRING codec. -* Support multilingual names. -* Handle names of font variation axes and instances. - -0.4.9 (June 23, 2015) -===================== -* Improve memory usage by deferring glyph / path loading. Thanks @Pomax! -* Put examples in the "examples" directory. Use the local web server to see them. - -0.4.8 (June 3, 2015) -==================== -* Fix an issue with writing out fonts that have an UPM != 1000. - -0.4.6 (March 26, 2015) -====================== -* Fix issues with exporting/subsetting TrueType fonts. -* Improve validness of exported fonts. -* Empty paths (think: space) no longer contain a single closePath command. -* Fix issues with exporting fonts with TrueType half-point values. -* Expose the internal byte parsing algorithms as opentype._parse. - -0.4.5 (March 10, 2015) -====================== -* Add support for writing quad curves. -* Add support for CFF flex operators. -* Close CFF subpaths. - -0.4.4 (Dec 8, 2014) -=================== -* Solve issues with Browserify. - -0.4.3 (Nov 26, 2014) -==================== -* Un-break node.js support. - -0.4.2 (Nov 24, 2014) -==================== -* 2x speedup when writing fonts, thanks @louisremi! - -0.4.1 (Nov 10, 2014) -==================== -* Fix bug that prevented `npm install`. - -0.4.0 (Nov 10, 2014) -==================== -* Add support for font writing. - -0.3.0 (Jun 10, 2014) -==================== -* Support for GPOS kerning, which works in both PostScript and OpenType. -* Big performance improvements. -* The font and glyph inspector can visually debug a font. - -0.2.0 (Feb 7, 2014) -=================== -* Support for reading PostScript fonts. - -0.1.0 (Sep 27, 2013) -==================== -* Initial release. -* Supports reading TrueType CFF fonts. diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/bin/ot b/node_modules/lv_font_conv/node_modules/opentype.js/bin/ot deleted file mode 100755 index af990cd2..00000000 --- a/node_modules/lv_font_conv/node_modules/opentype.js/bin/ot +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env node -/* eslint no-console: off */ - -import fs from 'fs'; -import path from 'path'; -import { load } from '../src/opentype'; - -// Print out information about the font on the console. -function printFontInfo(font) { - console.log(' glyphs:', font.glyphs.length); - console.log(' kerning pairs (kern table):', Object.keys(font.kerningPairs).length); - console.log(' kerning pairs (GPOS table):', (font.getGposKerningValue) ? 'yes' : 'no'); -} - -// Recursively walk a directory and execute the function for every file. -function walk(dir, fn) { - var files, i, file; - files = fs.readdirSync(dir); - for (i = 0; i < files.length; i += 1) { - file = files[i]; - var fullName = path.join(dir, file); - var stat = fs.statSync(fullName); - if (stat.isFile()) { - fn(fullName); - } else if (stat.isDirectory()) { - walk(fullName, fn); - } - } -} - -// Print out usage information. -function printUsage() { - console.log('Usage: ot command [dir|file]'); - console.log(); - console.log('Commands:'); - console.log(); - console.log(' info Get information of specified font or fonts in the specified directory.'); - console.log(); -} - -function fileInfo(file) { - load(file, function(err, font) { - console.log(path.basename(file)); - if (err) { - console.log(' (Error: ' + err + ')'); - } else if (!font.supported) { - console.log(' (Unsupported)'); - } else { - printFontInfo(font); - } - }); -} - -function recursiveInfo(fontDirectory) { - walk(fontDirectory, function(file) { - var ext = path.extname(file).toLowerCase(); - if (ext === '.ttf' || ext === '.otf') { - fileInfo(file); - } - }); -} - -if (process.argv.length < 3) { - printUsage(); -} else { - var command = process.argv[2]; - if (command === 'info') { - var fontpath = process.argv.length === 3 ? '.' : process.argv[3]; - if (fs.existsSync(fontpath)) { - var ext = path.extname(fontpath).toLowerCase(); - if (fs.statSync(fontpath).isDirectory()) { - recursiveInfo(fontpath); - } else if (ext === '.ttf' || ext === '.otf') { - fileInfo(fontpath); - } else { - printUsage(); - } - } else { - console.log('Path not found'); - } - } else { - printUsage(); - } -} diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/bin/server.js b/node_modules/lv_font_conv/node_modules/opentype.js/bin/server.js deleted file mode 100755 index f6f450a4..00000000 --- a/node_modules/lv_font_conv/node_modules/opentype.js/bin/server.js +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env node - -var fs = require('fs'); -var http = require('http'); -var path = require('path'); -var rollup = require('rollup'); -var rollupConfig = require('../rollup.config'); - -var CONTENT_TYPES = { - '.html': 'text/html', - '.css': 'text/css', - '.png': 'image/png', - '.js': 'text/javascript', - '.ttf': 'font/otf', - '.otf': 'font/otf', - '.woff': 'font/woff', - '.woff2': 'font/woff2', -}; - -http.createServer(function(req, res) { - var rewrite = ''; - var url = req.url.substring(1); - if (url.length === 0) { - url = 'index.html'; - rewrite = ' -> ' + url; - } - - console.log('HTTP', req.url, rewrite); - var filePath = './' + url; - fs.readFile(filePath, function(err, data) { - if (err) { - res.writeHead(404, {'Content-Type': 'text/plain'}); - res.end('Error: ' + err); - } else { - var contentType = CONTENT_TYPES[path.extname(filePath)] || 'text/plain'; - res.writeHead(200, { - 'Content-Type': contentType, - 'Cache-Control': 'max-age=0' - }); - res.end(data); - } - }); -}).listen(8080); -console.log('Server running at http://localhost:8080/'); - -// Watch changes and rebundle -var watcher = rollup.watch(rollupConfig); -watcher.on('event', e => { - // event.code can be one of: - // START — the watcher is (re)starting - // BUNDLE_START — building an individual bundle - // BUNDLE_END — finished building a bundle - // END — finished building all bundles - // ERROR — encountered an error while bundling - // FATAL — encountered an unrecoverable error - - if (e.code === 'BUNDLE_START') { - console.log('Bundling...'); - } else if (e.code === 'BUNDLE_END') { - console.log('Bundled in ' + e.duration + 'ms.'); - } else if (e.code === 'ERROR' || e.code === 'FATAL') { - console.error(e.error); - } -}); diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/bin/test-render b/node_modules/lv_font_conv/node_modules/opentype.js/bin/test-render deleted file mode 100755 index 4bfc31e6..00000000 --- a/node_modules/lv_font_conv/node_modules/opentype.js/bin/test-render +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env node -// This is a command to test the text rendering compliance of OpenType.js. -// It is designed to operate with https://github.com/unicode-org/text-rendering-tests. -// -// Call it like this: -// -// ./bin/test-render --font=fonts/FiraSansOT-Medium.otf --testcase=TEST-1 --render=BALL -// -// The output will look like this: -// -// -// -// -// -// -// -// -// -// -// -// -// When viewing the SVG, it will be upside-down (since glyphs are designed Y-up). - -var opentype = require('../dist/opentype.js'); - -const SVG_FOOTER = ``; - -function printUsage() { - console.log('Usage: test-render --font=filename.otf --testcase=TEST_NAME --render=TEXT_TO_RENDER'); - console.log('This commands output the text to render as an SVG file.'); - console.log(); -} - -let filename; -let testcase; -let textToRender; -for (let i = 0; i < process.argv.length; i++) { - const arg = process.argv[i]; - if (arg.startsWith('--font=')) { - filename = arg.substring('--font='.length); - } else if (arg.startsWith('--testcase=')) { - testcase = arg.substring('--testcase='.length); - } else if (arg.startsWith('--render=')) { - textToRender = arg.substring('--render='.length); - } -} - -if (filename === undefined || testcase === undefined || textToRender === undefined) { - printUsage(); - process.exit(1); -} - -function renderSVG() { - var font = opentype.loadSync(filename); - - let svgSymbols = []; - let svgBody = []; - - var glyphSet = new Set(); - let x = 0; - const glyphs = font.stringToGlyphs(textToRender); - for (let i = 0; i < glyphs.length; i++) { - const glyph = glyphs[i]; - const symbolId = testcase + '.' + glyph.name; - if (!glyphSet.has(glyph)) { - glyphSet.add(glyph); - const svgPath = glyph.path.toSVG(); - svgSymbols.push(` ${svgPath}`); - } - svgBody.push(` `); - x += glyph.advanceWidth; - } - - let minX = 0; - let minY = Math.round(font.descender); - let width = Math.round(x); - let height = Math.round(font.ascender - font.descender); - let svgHeader = ` -`; - - return svgHeader + svgSymbols.join('\n') + svgBody.join('\n') + SVG_FOOTER; -} - -try { - var svg = renderSVG(); - console.log(svg); -} catch(e) { - console.error(e.stack); - process.exit(1); -} diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/dist/opentype.js b/node_modules/lv_font_conv/node_modules/opentype.js/dist/opentype.js deleted file mode 100644 index 11b0a548..00000000 --- a/node_modules/lv_font_conv/node_modules/opentype.js/dist/opentype.js +++ /dev/null @@ -1,14254 +0,0 @@ -/** - * https://opentype.js.org v1.3.3 | (c) Frederik De Bleser and other contributors | MIT License | Uses tiny-inflate by Devon Govett and string.prototype.codepointat polyfill by Mathias Bynens - */ - -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : - typeof define === 'function' && define.amd ? define(['exports'], factory) : - (global = global || self, factory(global.opentype = {})); -}(this, (function (exports) { 'use strict'; - - /*! https://mths.be/codepointat v0.2.0 by @mathias */ - if (!String.prototype.codePointAt) { - (function() { - var defineProperty = (function() { - // IE 8 only supports `Object.defineProperty` on DOM elements - try { - var object = {}; - var $defineProperty = Object.defineProperty; - var result = $defineProperty(object, object, object) && $defineProperty; - } catch(error) {} - return result; - }()); - var codePointAt = function(position) { - if (this == null) { - throw TypeError(); - } - var string = String(this); - var size = string.length; - // `ToInteger` - var index = position ? Number(position) : 0; - if (index != index) { // better `isNaN` - index = 0; - } - // Account for out-of-bounds indices: - if (index < 0 || index >= size) { - return undefined; - } - // Get the first code unit - var first = string.charCodeAt(index); - var second; - if ( // check if it’s the start of a surrogate pair - first >= 0xD800 && first <= 0xDBFF && // high surrogate - size > index + 1 // there is a next code unit - ) { - second = string.charCodeAt(index + 1); - if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate - // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae - return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; - } - } - return first; - }; - if (defineProperty) { - defineProperty(String.prototype, 'codePointAt', { - 'value': codePointAt, - 'configurable': true, - 'writable': true - }); - } else { - String.prototype.codePointAt = codePointAt; - } - }()); - } - - var TINF_OK = 0; - var TINF_DATA_ERROR = -3; - - function Tree() { - this.table = new Uint16Array(16); /* table of code length counts */ - this.trans = new Uint16Array(288); /* code -> symbol translation table */ - } - - function Data(source, dest) { - this.source = source; - this.sourceIndex = 0; - this.tag = 0; - this.bitcount = 0; - - this.dest = dest; - this.destLen = 0; - - this.ltree = new Tree(); /* dynamic length/symbol tree */ - this.dtree = new Tree(); /* dynamic distance tree */ - } - - /* --------------------------------------------------- * - * -- uninitialized global data (static structures) -- * - * --------------------------------------------------- */ - - var sltree = new Tree(); - var sdtree = new Tree(); - - /* extra bits and base tables for length codes */ - var length_bits = new Uint8Array(30); - var length_base = new Uint16Array(30); - - /* extra bits and base tables for distance codes */ - var dist_bits = new Uint8Array(30); - var dist_base = new Uint16Array(30); - - /* special ordering of code length codes */ - var clcidx = new Uint8Array([ - 16, 17, 18, 0, 8, 7, 9, 6, - 10, 5, 11, 4, 12, 3, 13, 2, - 14, 1, 15 - ]); - - /* used by tinf_decode_trees, avoids allocations every call */ - var code_tree = new Tree(); - var lengths = new Uint8Array(288 + 32); - - /* ----------------------- * - * -- utility functions -- * - * ----------------------- */ - - /* build extra bits and base tables */ - function tinf_build_bits_base(bits, base, delta, first) { - var i, sum; - - /* build bits table */ - for (i = 0; i < delta; ++i) { bits[i] = 0; } - for (i = 0; i < 30 - delta; ++i) { bits[i + delta] = i / delta | 0; } - - /* build base table */ - for (sum = first, i = 0; i < 30; ++i) { - base[i] = sum; - sum += 1 << bits[i]; - } - } - - /* build the fixed huffman trees */ - function tinf_build_fixed_trees(lt, dt) { - var i; - - /* build fixed length tree */ - for (i = 0; i < 7; ++i) { lt.table[i] = 0; } - - lt.table[7] = 24; - lt.table[8] = 152; - lt.table[9] = 112; - - for (i = 0; i < 24; ++i) { lt.trans[i] = 256 + i; } - for (i = 0; i < 144; ++i) { lt.trans[24 + i] = i; } - for (i = 0; i < 8; ++i) { lt.trans[24 + 144 + i] = 280 + i; } - for (i = 0; i < 112; ++i) { lt.trans[24 + 144 + 8 + i] = 144 + i; } - - /* build fixed distance tree */ - for (i = 0; i < 5; ++i) { dt.table[i] = 0; } - - dt.table[5] = 32; - - for (i = 0; i < 32; ++i) { dt.trans[i] = i; } - } - - /* given an array of code lengths, build a tree */ - var offs = new Uint16Array(16); - - function tinf_build_tree(t, lengths, off, num) { - var i, sum; - - /* clear code length count table */ - for (i = 0; i < 16; ++i) { t.table[i] = 0; } - - /* scan symbol lengths, and sum code length counts */ - for (i = 0; i < num; ++i) { t.table[lengths[off + i]]++; } - - t.table[0] = 0; - - /* compute offset table for distribution sort */ - for (sum = 0, i = 0; i < 16; ++i) { - offs[i] = sum; - sum += t.table[i]; - } - - /* create code->symbol translation table (symbols sorted by code) */ - for (i = 0; i < num; ++i) { - if (lengths[off + i]) { t.trans[offs[lengths[off + i]]++] = i; } - } - } - - /* ---------------------- * - * -- decode functions -- * - * ---------------------- */ - - /* get one bit from source stream */ - function tinf_getbit(d) { - /* check if tag is empty */ - if (!d.bitcount--) { - /* load next tag */ - d.tag = d.source[d.sourceIndex++]; - d.bitcount = 7; - } - - /* shift bit out of tag */ - var bit = d.tag & 1; - d.tag >>>= 1; - - return bit; - } - - /* read a num bit value from a stream and add base */ - function tinf_read_bits(d, num, base) { - if (!num) - { return base; } - - while (d.bitcount < 24) { - d.tag |= d.source[d.sourceIndex++] << d.bitcount; - d.bitcount += 8; - } - - var val = d.tag & (0xffff >>> (16 - num)); - d.tag >>>= num; - d.bitcount -= num; - return val + base; - } - - /* given a data stream and a tree, decode a symbol */ - function tinf_decode_symbol(d, t) { - while (d.bitcount < 24) { - d.tag |= d.source[d.sourceIndex++] << d.bitcount; - d.bitcount += 8; - } - - var sum = 0, cur = 0, len = 0; - var tag = d.tag; - - /* get more bits while code value is above sum */ - do { - cur = 2 * cur + (tag & 1); - tag >>>= 1; - ++len; - - sum += t.table[len]; - cur -= t.table[len]; - } while (cur >= 0); - - d.tag = tag; - d.bitcount -= len; - - return t.trans[sum + cur]; - } - - /* given a data stream, decode dynamic trees from it */ - function tinf_decode_trees(d, lt, dt) { - var hlit, hdist, hclen; - var i, num, length; - - /* get 5 bits HLIT (257-286) */ - hlit = tinf_read_bits(d, 5, 257); - - /* get 5 bits HDIST (1-32) */ - hdist = tinf_read_bits(d, 5, 1); - - /* get 4 bits HCLEN (4-19) */ - hclen = tinf_read_bits(d, 4, 4); - - for (i = 0; i < 19; ++i) { lengths[i] = 0; } - - /* read code lengths for code length alphabet */ - for (i = 0; i < hclen; ++i) { - /* get 3 bits code length (0-7) */ - var clen = tinf_read_bits(d, 3, 0); - lengths[clcidx[i]] = clen; - } - - /* build code length tree */ - tinf_build_tree(code_tree, lengths, 0, 19); - - /* decode code lengths for the dynamic trees */ - for (num = 0; num < hlit + hdist;) { - var sym = tinf_decode_symbol(d, code_tree); - - switch (sym) { - case 16: - /* copy previous code length 3-6 times (read 2 bits) */ - var prev = lengths[num - 1]; - for (length = tinf_read_bits(d, 2, 3); length; --length) { - lengths[num++] = prev; - } - break; - case 17: - /* repeat code length 0 for 3-10 times (read 3 bits) */ - for (length = tinf_read_bits(d, 3, 3); length; --length) { - lengths[num++] = 0; - } - break; - case 18: - /* repeat code length 0 for 11-138 times (read 7 bits) */ - for (length = tinf_read_bits(d, 7, 11); length; --length) { - lengths[num++] = 0; - } - break; - default: - /* values 0-15 represent the actual code lengths */ - lengths[num++] = sym; - break; - } - } - - /* build dynamic trees */ - tinf_build_tree(lt, lengths, 0, hlit); - tinf_build_tree(dt, lengths, hlit, hdist); - } - - /* ----------------------------- * - * -- block inflate functions -- * - * ----------------------------- */ - - /* given a stream and two trees, inflate a block of data */ - function tinf_inflate_block_data(d, lt, dt) { - while (1) { - var sym = tinf_decode_symbol(d, lt); - - /* check for end of block */ - if (sym === 256) { - return TINF_OK; - } - - if (sym < 256) { - d.dest[d.destLen++] = sym; - } else { - var length, dist, offs; - var i; - - sym -= 257; - - /* possibly get more bits from length code */ - length = tinf_read_bits(d, length_bits[sym], length_base[sym]); - - dist = tinf_decode_symbol(d, dt); - - /* possibly get more bits from distance code */ - offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]); - - /* copy match */ - for (i = offs; i < offs + length; ++i) { - d.dest[d.destLen++] = d.dest[i]; - } - } - } - } - - /* inflate an uncompressed block of data */ - function tinf_inflate_uncompressed_block(d) { - var length, invlength; - var i; - - /* unread from bitbuffer */ - while (d.bitcount > 8) { - d.sourceIndex--; - d.bitcount -= 8; - } - - /* get length */ - length = d.source[d.sourceIndex + 1]; - length = 256 * length + d.source[d.sourceIndex]; - - /* get one's complement of length */ - invlength = d.source[d.sourceIndex + 3]; - invlength = 256 * invlength + d.source[d.sourceIndex + 2]; - - /* check length */ - if (length !== (~invlength & 0x0000ffff)) - { return TINF_DATA_ERROR; } - - d.sourceIndex += 4; - - /* copy block */ - for (i = length; i; --i) - { d.dest[d.destLen++] = d.source[d.sourceIndex++]; } - - /* make sure we start next block on a byte boundary */ - d.bitcount = 0; - - return TINF_OK; - } - - /* inflate stream from source to dest */ - function tinf_uncompress(source, dest) { - var d = new Data(source, dest); - var bfinal, btype, res; - - do { - /* read final block flag */ - bfinal = tinf_getbit(d); - - /* read block type (2 bits) */ - btype = tinf_read_bits(d, 2, 0); - - /* decompress block */ - switch (btype) { - case 0: - /* decompress uncompressed block */ - res = tinf_inflate_uncompressed_block(d); - break; - case 1: - /* decompress block with fixed huffman trees */ - res = tinf_inflate_block_data(d, sltree, sdtree); - break; - case 2: - /* decompress block with dynamic huffman trees */ - tinf_decode_trees(d, d.ltree, d.dtree); - res = tinf_inflate_block_data(d, d.ltree, d.dtree); - break; - default: - res = TINF_DATA_ERROR; - } - - if (res !== TINF_OK) - { throw new Error('Data error'); } - - } while (!bfinal); - - if (d.destLen < d.dest.length) { - if (typeof d.dest.slice === 'function') - { return d.dest.slice(0, d.destLen); } - else - { return d.dest.subarray(0, d.destLen); } - } - - return d.dest; - } - - /* -------------------- * - * -- initialization -- * - * -------------------- */ - - /* build fixed huffman trees */ - tinf_build_fixed_trees(sltree, sdtree); - - /* build extra bits and base tables */ - tinf_build_bits_base(length_bits, length_base, 4, 3); - tinf_build_bits_base(dist_bits, dist_base, 2, 1); - - /* fix a special case */ - length_bits[28] = 0; - length_base[28] = 258; - - var tinyInflate = tinf_uncompress; - - // The Bounding Box object - - function derive(v0, v1, v2, v3, t) { - return Math.pow(1 - t, 3) * v0 + - 3 * Math.pow(1 - t, 2) * t * v1 + - 3 * (1 - t) * Math.pow(t, 2) * v2 + - Math.pow(t, 3) * v3; - } - /** - * A bounding box is an enclosing box that describes the smallest measure within which all the points lie. - * It is used to calculate the bounding box of a glyph or text path. - * - * On initialization, x1/y1/x2/y2 will be NaN. Check if the bounding box is empty using `isEmpty()`. - * - * @exports opentype.BoundingBox - * @class - * @constructor - */ - function BoundingBox() { - this.x1 = Number.NaN; - this.y1 = Number.NaN; - this.x2 = Number.NaN; - this.y2 = Number.NaN; - } - - /** - * Returns true if the bounding box is empty, that is, no points have been added to the box yet. - */ - BoundingBox.prototype.isEmpty = function() { - return isNaN(this.x1) || isNaN(this.y1) || isNaN(this.x2) || isNaN(this.y2); - }; - - /** - * Add the point to the bounding box. - * The x1/y1/x2/y2 coordinates of the bounding box will now encompass the given point. - * @param {number} x - The X coordinate of the point. - * @param {number} y - The Y coordinate of the point. - */ - BoundingBox.prototype.addPoint = function(x, y) { - if (typeof x === 'number') { - if (isNaN(this.x1) || isNaN(this.x2)) { - this.x1 = x; - this.x2 = x; - } - if (x < this.x1) { - this.x1 = x; - } - if (x > this.x2) { - this.x2 = x; - } - } - if (typeof y === 'number') { - if (isNaN(this.y1) || isNaN(this.y2)) { - this.y1 = y; - this.y2 = y; - } - if (y < this.y1) { - this.y1 = y; - } - if (y > this.y2) { - this.y2 = y; - } - } - }; - - /** - * Add a X coordinate to the bounding box. - * This extends the bounding box to include the X coordinate. - * This function is used internally inside of addBezier. - * @param {number} x - The X coordinate of the point. - */ - BoundingBox.prototype.addX = function(x) { - this.addPoint(x, null); - }; - - /** - * Add a Y coordinate to the bounding box. - * This extends the bounding box to include the Y coordinate. - * This function is used internally inside of addBezier. - * @param {number} y - The Y coordinate of the point. - */ - BoundingBox.prototype.addY = function(y) { - this.addPoint(null, y); - }; - - /** - * Add a Bézier curve to the bounding box. - * This extends the bounding box to include the entire Bézier. - * @param {number} x0 - The starting X coordinate. - * @param {number} y0 - The starting Y coordinate. - * @param {number} x1 - The X coordinate of the first control point. - * @param {number} y1 - The Y coordinate of the first control point. - * @param {number} x2 - The X coordinate of the second control point. - * @param {number} y2 - The Y coordinate of the second control point. - * @param {number} x - The ending X coordinate. - * @param {number} y - The ending Y coordinate. - */ - BoundingBox.prototype.addBezier = function(x0, y0, x1, y1, x2, y2, x, y) { - // This code is based on http://nishiohirokazu.blogspot.com/2009/06/how-to-calculate-bezier-curves-bounding.html - // and https://github.com/icons8/svg-path-bounding-box - - var p0 = [x0, y0]; - var p1 = [x1, y1]; - var p2 = [x2, y2]; - var p3 = [x, y]; - - this.addPoint(x0, y0); - this.addPoint(x, y); - - for (var i = 0; i <= 1; i++) { - var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i]; - var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i]; - var c = 3 * p1[i] - 3 * p0[i]; - - if (a === 0) { - if (b === 0) { continue; } - var t = -c / b; - if (0 < t && t < 1) { - if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t)); } - if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t)); } - } - continue; - } - - var b2ac = Math.pow(b, 2) - 4 * c * a; - if (b2ac < 0) { continue; } - var t1 = (-b + Math.sqrt(b2ac)) / (2 * a); - if (0 < t1 && t1 < 1) { - if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t1)); } - if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t1)); } - } - var t2 = (-b - Math.sqrt(b2ac)) / (2 * a); - if (0 < t2 && t2 < 1) { - if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t2)); } - if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t2)); } - } - } - }; - - /** - * Add a quadratic curve to the bounding box. - * This extends the bounding box to include the entire quadratic curve. - * @param {number} x0 - The starting X coordinate. - * @param {number} y0 - The starting Y coordinate. - * @param {number} x1 - The X coordinate of the control point. - * @param {number} y1 - The Y coordinate of the control point. - * @param {number} x - The ending X coordinate. - * @param {number} y - The ending Y coordinate. - */ - BoundingBox.prototype.addQuad = function(x0, y0, x1, y1, x, y) { - var cp1x = x0 + 2 / 3 * (x1 - x0); - var cp1y = y0 + 2 / 3 * (y1 - y0); - var cp2x = cp1x + 1 / 3 * (x - x0); - var cp2y = cp1y + 1 / 3 * (y - y0); - this.addBezier(x0, y0, cp1x, cp1y, cp2x, cp2y, x, y); - }; - - // Geometric objects - - /** - * A bézier path containing a set of path commands similar to a SVG path. - * Paths can be drawn on a context using `draw`. - * @exports opentype.Path - * @class - * @constructor - */ - function Path() { - this.commands = []; - this.fill = 'black'; - this.stroke = null; - this.strokeWidth = 1; - } - - /** - * @param {number} x - * @param {number} y - */ - Path.prototype.moveTo = function(x, y) { - this.commands.push({ - type: 'M', - x: x, - y: y - }); - }; - - /** - * @param {number} x - * @param {number} y - */ - Path.prototype.lineTo = function(x, y) { - this.commands.push({ - type: 'L', - x: x, - y: y - }); - }; - - /** - * Draws cubic curve - * @function - * curveTo - * @memberof opentype.Path.prototype - * @param {number} x1 - x of control 1 - * @param {number} y1 - y of control 1 - * @param {number} x2 - x of control 2 - * @param {number} y2 - y of control 2 - * @param {number} x - x of path point - * @param {number} y - y of path point - */ - - /** - * Draws cubic curve - * @function - * bezierCurveTo - * @memberof opentype.Path.prototype - * @param {number} x1 - x of control 1 - * @param {number} y1 - y of control 1 - * @param {number} x2 - x of control 2 - * @param {number} y2 - y of control 2 - * @param {number} x - x of path point - * @param {number} y - y of path point - * @see curveTo - */ - Path.prototype.curveTo = Path.prototype.bezierCurveTo = function(x1, y1, x2, y2, x, y) { - this.commands.push({ - type: 'C', - x1: x1, - y1: y1, - x2: x2, - y2: y2, - x: x, - y: y - }); - }; - - /** - * Draws quadratic curve - * @function - * quadraticCurveTo - * @memberof opentype.Path.prototype - * @param {number} x1 - x of control - * @param {number} y1 - y of control - * @param {number} x - x of path point - * @param {number} y - y of path point - */ - - /** - * Draws quadratic curve - * @function - * quadTo - * @memberof opentype.Path.prototype - * @param {number} x1 - x of control - * @param {number} y1 - y of control - * @param {number} x - x of path point - * @param {number} y - y of path point - */ - Path.prototype.quadTo = Path.prototype.quadraticCurveTo = function(x1, y1, x, y) { - this.commands.push({ - type: 'Q', - x1: x1, - y1: y1, - x: x, - y: y - }); - }; - - /** - * Closes the path - * @function closePath - * @memberof opentype.Path.prototype - */ - - /** - * Close the path - * @function close - * @memberof opentype.Path.prototype - */ - Path.prototype.close = Path.prototype.closePath = function() { - this.commands.push({ - type: 'Z' - }); - }; - - /** - * Add the given path or list of commands to the commands of this path. - * @param {Array} pathOrCommands - another opentype.Path, an opentype.BoundingBox, or an array of commands. - */ - Path.prototype.extend = function(pathOrCommands) { - if (pathOrCommands.commands) { - pathOrCommands = pathOrCommands.commands; - } else if (pathOrCommands instanceof BoundingBox) { - var box = pathOrCommands; - this.moveTo(box.x1, box.y1); - this.lineTo(box.x2, box.y1); - this.lineTo(box.x2, box.y2); - this.lineTo(box.x1, box.y2); - this.close(); - return; - } - - Array.prototype.push.apply(this.commands, pathOrCommands); - }; - - /** - * Calculate the bounding box of the path. - * @returns {opentype.BoundingBox} - */ - Path.prototype.getBoundingBox = function() { - var box = new BoundingBox(); - - var startX = 0; - var startY = 0; - var prevX = 0; - var prevY = 0; - for (var i = 0; i < this.commands.length; i++) { - var cmd = this.commands[i]; - switch (cmd.type) { - case 'M': - box.addPoint(cmd.x, cmd.y); - startX = prevX = cmd.x; - startY = prevY = cmd.y; - break; - case 'L': - box.addPoint(cmd.x, cmd.y); - prevX = cmd.x; - prevY = cmd.y; - break; - case 'Q': - box.addQuad(prevX, prevY, cmd.x1, cmd.y1, cmd.x, cmd.y); - prevX = cmd.x; - prevY = cmd.y; - break; - case 'C': - box.addBezier(prevX, prevY, cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); - prevX = cmd.x; - prevY = cmd.y; - break; - case 'Z': - prevX = startX; - prevY = startY; - break; - default: - throw new Error('Unexpected path command ' + cmd.type); - } - } - if (box.isEmpty()) { - box.addPoint(0, 0); - } - return box; - }; - - /** - * Draw the path to a 2D context. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context. - */ - Path.prototype.draw = function(ctx) { - ctx.beginPath(); - for (var i = 0; i < this.commands.length; i += 1) { - var cmd = this.commands[i]; - if (cmd.type === 'M') { - ctx.moveTo(cmd.x, cmd.y); - } else if (cmd.type === 'L') { - ctx.lineTo(cmd.x, cmd.y); - } else if (cmd.type === 'C') { - ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); - } else if (cmd.type === 'Q') { - ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y); - } else if (cmd.type === 'Z') { - ctx.closePath(); - } - } - - if (this.fill) { - ctx.fillStyle = this.fill; - ctx.fill(); - } - - if (this.stroke) { - ctx.strokeStyle = this.stroke; - ctx.lineWidth = this.strokeWidth; - ctx.stroke(); - } - }; - - /** - * Convert the Path to a string of path data instructions - * See http://www.w3.org/TR/SVG/paths.html#PathData - * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values - * @return {string} - */ - Path.prototype.toPathData = function(decimalPlaces) { - decimalPlaces = decimalPlaces !== undefined ? decimalPlaces : 2; - - function floatToString(v) { - if (Math.round(v) === v) { - return '' + Math.round(v); - } else { - return v.toFixed(decimalPlaces); - } - } - - function packValues() { - var arguments$1 = arguments; - - var s = ''; - for (var i = 0; i < arguments.length; i += 1) { - var v = arguments$1[i]; - if (v >= 0 && i > 0) { - s += ' '; - } - - s += floatToString(v); - } - - return s; - } - - var d = ''; - for (var i = 0; i < this.commands.length; i += 1) { - var cmd = this.commands[i]; - if (cmd.type === 'M') { - d += 'M' + packValues(cmd.x, cmd.y); - } else if (cmd.type === 'L') { - d += 'L' + packValues(cmd.x, cmd.y); - } else if (cmd.type === 'C') { - d += 'C' + packValues(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); - } else if (cmd.type === 'Q') { - d += 'Q' + packValues(cmd.x1, cmd.y1, cmd.x, cmd.y); - } else if (cmd.type === 'Z') { - d += 'Z'; - } - } - - return d; - }; - - /** - * Convert the path to an SVG element, as a string. - * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values - * @return {string} - */ - Path.prototype.toSVG = function(decimalPlaces) { - var svg = '= 0 && v <= 255, 'Byte value should be between 0 and 255.'); - return [v]; - }; - /** - * @constant - * @type {number} - */ - sizeOf.BYTE = constant(1); - - /** - * Convert a 8-bit signed integer to a list of 1 byte. - * @param {string} - * @returns {Array} - */ - encode.CHAR = function(v) { - return [v.charCodeAt(0)]; - }; - - /** - * @constant - * @type {number} - */ - sizeOf.CHAR = constant(1); - - /** - * Convert an ASCII string to a list of bytes. - * @param {string} - * @returns {Array} - */ - encode.CHARARRAY = function(v) { - var b = []; - for (var i = 0; i < v.length; i += 1) { - b[i] = v.charCodeAt(i); - } - - return b; - }; - - /** - * @param {Array} - * @returns {number} - */ - sizeOf.CHARARRAY = function(v) { - return v.length; - }; - - /** - * Convert a 16-bit unsigned integer to a list of 2 bytes. - * @param {number} - * @returns {Array} - */ - encode.USHORT = function(v) { - return [(v >> 8) & 0xFF, v & 0xFF]; - }; - - /** - * @constant - * @type {number} - */ - sizeOf.USHORT = constant(2); - - /** - * Convert a 16-bit signed integer to a list of 2 bytes. - * @param {number} - * @returns {Array} - */ - encode.SHORT = function(v) { - // Two's complement - if (v >= LIMIT16) { - v = -(2 * LIMIT16 - v); - } - - return [(v >> 8) & 0xFF, v & 0xFF]; - }; - - /** - * @constant - * @type {number} - */ - sizeOf.SHORT = constant(2); - - /** - * Convert a 24-bit unsigned integer to a list of 3 bytes. - * @param {number} - * @returns {Array} - */ - encode.UINT24 = function(v) { - return [(v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; - }; - - /** - * @constant - * @type {number} - */ - sizeOf.UINT24 = constant(3); - - /** - * Convert a 32-bit unsigned integer to a list of 4 bytes. - * @param {number} - * @returns {Array} - */ - encode.ULONG = function(v) { - return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; - }; - - /** - * @constant - * @type {number} - */ - sizeOf.ULONG = constant(4); - - /** - * Convert a 32-bit unsigned integer to a list of 4 bytes. - * @param {number} - * @returns {Array} - */ - encode.LONG = function(v) { - // Two's complement - if (v >= LIMIT32) { - v = -(2 * LIMIT32 - v); - } - - return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; - }; - - /** - * @constant - * @type {number} - */ - sizeOf.LONG = constant(4); - - encode.FIXED = encode.ULONG; - sizeOf.FIXED = sizeOf.ULONG; - - encode.FWORD = encode.SHORT; - sizeOf.FWORD = sizeOf.SHORT; - - encode.UFWORD = encode.USHORT; - sizeOf.UFWORD = sizeOf.USHORT; - - /** - * Convert a 32-bit Apple Mac timestamp integer to a list of 8 bytes, 64-bit timestamp. - * @param {number} - * @returns {Array} - */ - encode.LONGDATETIME = function(v) { - return [0, 0, 0, 0, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; - }; - - /** - * @constant - * @type {number} - */ - sizeOf.LONGDATETIME = constant(8); - - /** - * Convert a 4-char tag to a list of 4 bytes. - * @param {string} - * @returns {Array} - */ - encode.TAG = function(v) { - check.argument(v.length === 4, 'Tag should be exactly 4 ASCII characters.'); - return [v.charCodeAt(0), - v.charCodeAt(1), - v.charCodeAt(2), - v.charCodeAt(3)]; - }; - - /** - * @constant - * @type {number} - */ - sizeOf.TAG = constant(4); - - // CFF data types /////////////////////////////////////////////////////////// - - encode.Card8 = encode.BYTE; - sizeOf.Card8 = sizeOf.BYTE; - - encode.Card16 = encode.USHORT; - sizeOf.Card16 = sizeOf.USHORT; - - encode.OffSize = encode.BYTE; - sizeOf.OffSize = sizeOf.BYTE; - - encode.SID = encode.USHORT; - sizeOf.SID = sizeOf.USHORT; - - // Convert a numeric operand or charstring number to a variable-size list of bytes. - /** - * Convert a numeric operand or charstring number to a variable-size list of bytes. - * @param {number} - * @returns {Array} - */ - encode.NUMBER = function(v) { - if (v >= -107 && v <= 107) { - return [v + 139]; - } else if (v >= 108 && v <= 1131) { - v = v - 108; - return [(v >> 8) + 247, v & 0xFF]; - } else if (v >= -1131 && v <= -108) { - v = -v - 108; - return [(v >> 8) + 251, v & 0xFF]; - } else if (v >= -32768 && v <= 32767) { - return encode.NUMBER16(v); - } else { - return encode.NUMBER32(v); - } - }; - - /** - * @param {number} - * @returns {number} - */ - sizeOf.NUMBER = function(v) { - return encode.NUMBER(v).length; - }; - - /** - * Convert a signed number between -32768 and +32767 to a three-byte value. - * This ensures we always use three bytes, but is not the most compact format. - * @param {number} - * @returns {Array} - */ - encode.NUMBER16 = function(v) { - return [28, (v >> 8) & 0xFF, v & 0xFF]; - }; - - /** - * @constant - * @type {number} - */ - sizeOf.NUMBER16 = constant(3); - - /** - * Convert a signed number between -(2^31) and +(2^31-1) to a five-byte value. - * This is useful if you want to be sure you always use four bytes, - * at the expense of wasting a few bytes for smaller numbers. - * @param {number} - * @returns {Array} - */ - encode.NUMBER32 = function(v) { - return [29, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; - }; - - /** - * @constant - * @type {number} - */ - sizeOf.NUMBER32 = constant(5); - - /** - * @param {number} - * @returns {Array} - */ - encode.REAL = function(v) { - var value = v.toString(); - - // Some numbers use an epsilon to encode the value. (e.g. JavaScript will store 0.0000001 as 1e-7) - // This code converts it back to a number without the epsilon. - var m = /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value); - if (m) { - var epsilon = parseFloat('1e' + ((m[2] ? +m[2] : 0) + m[1].length)); - value = (Math.round(v * epsilon) / epsilon).toString(); - } - - var nibbles = ''; - for (var i = 0, ii = value.length; i < ii; i += 1) { - var c = value[i]; - if (c === 'e') { - nibbles += value[++i] === '-' ? 'c' : 'b'; - } else if (c === '.') { - nibbles += 'a'; - } else if (c === '-') { - nibbles += 'e'; - } else { - nibbles += c; - } - } - - nibbles += (nibbles.length & 1) ? 'f' : 'ff'; - var out = [30]; - for (var i$1 = 0, ii$1 = nibbles.length; i$1 < ii$1; i$1 += 2) { - out.push(parseInt(nibbles.substr(i$1, 2), 16)); - } - - return out; - }; - - /** - * @param {number} - * @returns {number} - */ - sizeOf.REAL = function(v) { - return encode.REAL(v).length; - }; - - encode.NAME = encode.CHARARRAY; - sizeOf.NAME = sizeOf.CHARARRAY; - - encode.STRING = encode.CHARARRAY; - sizeOf.STRING = sizeOf.CHARARRAY; - - /** - * @param {DataView} data - * @param {number} offset - * @param {number} numBytes - * @returns {string} - */ - decode.UTF8 = function(data, offset, numBytes) { - var codePoints = []; - var numChars = numBytes; - for (var j = 0; j < numChars; j++, offset += 1) { - codePoints[j] = data.getUint8(offset); - } - - return String.fromCharCode.apply(null, codePoints); - }; - - /** - * @param {DataView} data - * @param {number} offset - * @param {number} numBytes - * @returns {string} - */ - decode.UTF16 = function(data, offset, numBytes) { - var codePoints = []; - var numChars = numBytes / 2; - for (var j = 0; j < numChars; j++, offset += 2) { - codePoints[j] = data.getUint16(offset); - } - - return String.fromCharCode.apply(null, codePoints); - }; - - /** - * Convert a JavaScript string to UTF16-BE. - * @param {string} - * @returns {Array} - */ - encode.UTF16 = function(v) { - var b = []; - for (var i = 0; i < v.length; i += 1) { - var codepoint = v.charCodeAt(i); - b[b.length] = (codepoint >> 8) & 0xFF; - b[b.length] = codepoint & 0xFF; - } - - return b; - }; - - /** - * @param {string} - * @returns {number} - */ - sizeOf.UTF16 = function(v) { - return v.length * 2; - }; - - // Data for converting old eight-bit Macintosh encodings to Unicode. - // This representation is optimized for decoding; encoding is slower - // and needs more memory. The assumption is that all opentype.js users - // want to open fonts, but saving a font will be comparatively rare - // so it can be more expensive. Keyed by IANA character set name. - // - // Python script for generating these strings: - // - // s = u''.join([chr(c).decode('mac_greek') for c in range(128, 256)]) - // print(s.encode('utf-8')) - /** - * @private - */ - var eightBitMacEncodings = { - 'x-mac-croatian': // Python: 'mac_croatian' - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø' + - '¿¡¬√ƒ≈Ć«Č… ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ', - 'x-mac-cyrillic': // Python: 'mac_cyrillic' - 'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ†°Ґ£§•¶І®©™Ђђ≠Ѓѓ∞±≤≥іµґЈЄєЇїЉљЊњ' + - 'јЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю', - 'x-mac-gaelic': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/GAELIC.TXT - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØḂ±≤≥ḃĊċḊḋḞḟĠġṀæø' + - 'ṁṖṗɼƒſṠ«»… ÀÃÕŒœ–—“”‘’ṡẛÿŸṪ€‹›Ŷŷṫ·Ỳỳ⁊ÂÊÁËÈÍÎÏÌÓÔ♣ÒÚÛÙıÝýŴŵẄẅẀẁẂẃ', - 'x-mac-greek': // Python: 'mac_greek' - 'Ĺ²É³ÖÜ΅àâä΄¨çéèê룙î‰ôö¦€ùûü†ΓΔΘΛΞΠß®©ΣΪ§≠°·Α±≤≥¥ΒΕΖΗΙΚΜΦΫΨΩ' + - 'άΝ¬ΟΡ≈Τ«»… ΥΧΆΈœ–―“”‘’÷ΉΊΌΎέήίόΏύαβψδεφγηιξκλμνοπώρστθωςχυζϊϋΐΰ\u00AD', - 'x-mac-icelandic': // Python: 'mac_iceland' - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûüÝ°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + - '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€ÐðÞþý·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', - 'x-mac-inuit': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/INUIT.TXT - 'ᐃᐄᐅᐆᐊᐋᐱᐲᐳᐴᐸᐹᑉᑎᑏᑐᑑᑕᑖᑦᑭᑮᑯᑰᑲᑳᒃᒋᒌᒍᒎᒐᒑ°ᒡᒥᒦ•¶ᒧ®©™ᒨᒪᒫᒻᓂᓃᓄᓅᓇᓈᓐᓯᓰᓱᓲᓴᓵᔅᓕᓖᓗ' + - 'ᓘᓚᓛᓪᔨᔩᔪᔫᔭ… ᔮᔾᕕᕖᕗ–—“”‘’ᕘᕙᕚᕝᕆᕇᕈᕉᕋᕌᕐᕿᖀᖁᖂᖃᖄᖅᖏᖐᖑᖒᖓᖔᖕᙱᙲᙳᙴᙵᙶᖖᖠᖡᖢᖣᖤᖥᖦᕼŁł', - 'x-mac-ce': // Python: 'mac_latin2' - 'ÄĀāÉĄÖÜáąČäčĆć鏟ĎíďĒēĖóėôöõúĚěü†°Ę£§•¶ß®©™ę¨≠ģĮįĪ≤≥īĶ∂∑łĻļĽľĹĺŅ' + - 'ņѬ√ńŇ∆«»… ňŐÕőŌ–—“”‘’÷◊ōŔŕŘ‹›řŖŗŠ‚„šŚśÁŤťÍŽžŪÓÔūŮÚůŰűŲųÝýķŻŁżĢˇ', - macintosh: // Python: 'mac_roman' - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + - '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', - 'x-mac-romanian': // Python: 'mac_romanian' - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ĂȘ∞±≤≥¥µ∂∑∏π∫ªºΩăș' + - '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›Țț‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', - 'x-mac-turkish': // Python: 'mac_turkish' - 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + - '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸĞğİıŞş‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙˆ˜¯˘˙˚¸˝˛ˇ' - }; - - /** - * Decodes an old-style Macintosh string. Returns either a Unicode JavaScript - * string, or 'undefined' if the encoding is unsupported. For example, we do - * not support Chinese, Japanese or Korean because these would need large - * mapping tables. - * @param {DataView} dataView - * @param {number} offset - * @param {number} dataLength - * @param {string} encoding - * @returns {string} - */ - decode.MACSTRING = function(dataView, offset, dataLength, encoding) { - var table = eightBitMacEncodings[encoding]; - if (table === undefined) { - return undefined; - } - - var result = ''; - for (var i = 0; i < dataLength; i++) { - var c = dataView.getUint8(offset + i); - // In all eight-bit Mac encodings, the characters 0x00..0x7F are - // mapped to U+0000..U+007F; we only need to look up the others. - if (c <= 0x7F) { - result += String.fromCharCode(c); - } else { - result += table[c & 0x7F]; - } - } - - return result; - }; - - // Helper function for encode.MACSTRING. Returns a dictionary for mapping - // Unicode character codes to their 8-bit MacOS equivalent. This table - // is not exactly a super cheap data structure, but we do not care because - // encoding Macintosh strings is only rarely needed in typical applications. - var macEncodingTableCache = typeof WeakMap === 'function' && new WeakMap(); - var macEncodingCacheKeys; - var getMacEncodingTable = function (encoding) { - // Since we use encoding as a cache key for WeakMap, it has to be - // a String object and not a literal. And at least on NodeJS 2.10.1, - // WeakMap requires that the same String instance is passed for cache hits. - if (!macEncodingCacheKeys) { - macEncodingCacheKeys = {}; - for (var e in eightBitMacEncodings) { - /*jshint -W053 */ // Suppress "Do not use String as a constructor." - macEncodingCacheKeys[e] = new String(e); - } - } - - var cacheKey = macEncodingCacheKeys[encoding]; - if (cacheKey === undefined) { - return undefined; - } - - // We can't do "if (cache.has(key)) {return cache.get(key)}" here: - // since garbage collection may run at any time, it could also kick in - // between the calls to cache.has() and cache.get(). In that case, - // we would return 'undefined' even though we do support the encoding. - if (macEncodingTableCache) { - var cachedTable = macEncodingTableCache.get(cacheKey); - if (cachedTable !== undefined) { - return cachedTable; - } - } - - var decodingTable = eightBitMacEncodings[encoding]; - if (decodingTable === undefined) { - return undefined; - } - - var encodingTable = {}; - for (var i = 0; i < decodingTable.length; i++) { - encodingTable[decodingTable.charCodeAt(i)] = i + 0x80; - } - - if (macEncodingTableCache) { - macEncodingTableCache.set(cacheKey, encodingTable); - } - - return encodingTable; - }; - - /** - * Encodes an old-style Macintosh string. Returns a byte array upon success. - * If the requested encoding is unsupported, or if the input string contains - * a character that cannot be expressed in the encoding, the function returns - * 'undefined'. - * @param {string} str - * @param {string} encoding - * @returns {Array} - */ - encode.MACSTRING = function(str, encoding) { - var table = getMacEncodingTable(encoding); - if (table === undefined) { - return undefined; - } - - var result = []; - for (var i = 0; i < str.length; i++) { - var c = str.charCodeAt(i); - - // In all eight-bit Mac encodings, the characters 0x00..0x7F are - // mapped to U+0000..U+007F; we only need to look up the others. - if (c >= 0x80) { - c = table[c]; - if (c === undefined) { - // str contains a Unicode character that cannot be encoded - // in the requested encoding. - return undefined; - } - } - result[i] = c; - // result.push(c); - } - - return result; - }; - - /** - * @param {string} str - * @param {string} encoding - * @returns {number} - */ - sizeOf.MACSTRING = function(str, encoding) { - var b = encode.MACSTRING(str, encoding); - if (b !== undefined) { - return b.length; - } else { - return 0; - } - }; - - // Helper for encode.VARDELTAS - function isByteEncodable(value) { - return value >= -128 && value <= 127; - } - - // Helper for encode.VARDELTAS - function encodeVarDeltaRunAsZeroes(deltas, pos, result) { - var runLength = 0; - var numDeltas = deltas.length; - while (pos < numDeltas && runLength < 64 && deltas[pos] === 0) { - ++pos; - ++runLength; - } - result.push(0x80 | (runLength - 1)); - return pos; - } - - // Helper for encode.VARDELTAS - function encodeVarDeltaRunAsBytes(deltas, offset, result) { - var runLength = 0; - var numDeltas = deltas.length; - var pos = offset; - while (pos < numDeltas && runLength < 64) { - var value = deltas[pos]; - if (!isByteEncodable(value)) { - break; - } - - // Within a byte-encoded run of deltas, a single zero is best - // stored literally as 0x00 value. However, if we have two or - // more zeroes in a sequence, it is better to start a new run. - // Fore example, the sequence of deltas [15, 15, 0, 15, 15] - // becomes 6 bytes (04 0F 0F 00 0F 0F) when storing the zero - // within the current run, but 7 bytes (01 0F 0F 80 01 0F 0F) - // when starting a new run. - if (value === 0 && pos + 1 < numDeltas && deltas[pos + 1] === 0) { - break; - } - - ++pos; - ++runLength; - } - result.push(runLength - 1); - for (var i = offset; i < pos; ++i) { - result.push((deltas[i] + 256) & 0xff); - } - return pos; - } - - // Helper for encode.VARDELTAS - function encodeVarDeltaRunAsWords(deltas, offset, result) { - var runLength = 0; - var numDeltas = deltas.length; - var pos = offset; - while (pos < numDeltas && runLength < 64) { - var value = deltas[pos]; - - // Within a word-encoded run of deltas, it is easiest to start - // a new run (with a different encoding) whenever we encounter - // a zero value. For example, the sequence [0x6666, 0, 0x7777] - // needs 7 bytes when storing the zero inside the current run - // (42 66 66 00 00 77 77), and equally 7 bytes when starting a - // new run (40 66 66 80 40 77 77). - if (value === 0) { - break; - } - - // Within a word-encoded run of deltas, a single value in the - // range (-128..127) should be encoded within the current run - // because it is more compact. For example, the sequence - // [0x6666, 2, 0x7777] becomes 7 bytes when storing the value - // literally (42 66 66 00 02 77 77), but 8 bytes when starting - // a new run (40 66 66 00 02 40 77 77). - if (isByteEncodable(value) && pos + 1 < numDeltas && isByteEncodable(deltas[pos + 1])) { - break; - } - - ++pos; - ++runLength; - } - result.push(0x40 | (runLength - 1)); - for (var i = offset; i < pos; ++i) { - var val = deltas[i]; - result.push(((val + 0x10000) >> 8) & 0xff, (val + 0x100) & 0xff); - } - return pos; - } - - /** - * Encode a list of variation adjustment deltas. - * - * Variation adjustment deltas are used in ‘gvar’ and ‘cvar’ tables. - * They indicate how points (in ‘gvar’) or values (in ‘cvar’) get adjusted - * when generating instances of variation fonts. - * - * @see https://www.microsoft.com/typography/otspec/gvar.htm - * @see https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html - * @param {Array} - * @return {Array} - */ - encode.VARDELTAS = function(deltas) { - var pos = 0; - var result = []; - while (pos < deltas.length) { - var value = deltas[pos]; - if (value === 0) { - pos = encodeVarDeltaRunAsZeroes(deltas, pos, result); - } else if (value >= -128 && value <= 127) { - pos = encodeVarDeltaRunAsBytes(deltas, pos, result); - } else { - pos = encodeVarDeltaRunAsWords(deltas, pos, result); - } - } - return result; - }; - - // Convert a list of values to a CFF INDEX structure. - // The values should be objects containing name / type / value. - /** - * @param {Array} l - * @returns {Array} - */ - encode.INDEX = function(l) { - //var offset, offsets, offsetEncoder, encodedOffsets, encodedOffset, data, - // i, v; - // Because we have to know which data type to use to encode the offsets, - // we have to go through the values twice: once to encode the data and - // calculate the offsets, then again to encode the offsets using the fitting data type. - var offset = 1; // First offset is always 1. - var offsets = [offset]; - var data = []; - for (var i = 0; i < l.length; i += 1) { - var v = encode.OBJECT(l[i]); - Array.prototype.push.apply(data, v); - offset += v.length; - offsets.push(offset); - } - - if (data.length === 0) { - return [0, 0]; - } - - var encodedOffsets = []; - var offSize = (1 + Math.floor(Math.log(offset) / Math.log(2)) / 8) | 0; - var offsetEncoder = [undefined, encode.BYTE, encode.USHORT, encode.UINT24, encode.ULONG][offSize]; - for (var i$1 = 0; i$1 < offsets.length; i$1 += 1) { - var encodedOffset = offsetEncoder(offsets[i$1]); - Array.prototype.push.apply(encodedOffsets, encodedOffset); - } - - return Array.prototype.concat(encode.Card16(l.length), - encode.OffSize(offSize), - encodedOffsets, - data); - }; - - /** - * @param {Array} - * @returns {number} - */ - sizeOf.INDEX = function(v) { - return encode.INDEX(v).length; - }; - - /** - * Convert an object to a CFF DICT structure. - * The keys should be numeric. - * The values should be objects containing name / type / value. - * @param {Object} m - * @returns {Array} - */ - encode.DICT = function(m) { - var d = []; - var keys = Object.keys(m); - var length = keys.length; - - for (var i = 0; i < length; i += 1) { - // Object.keys() return string keys, but our keys are always numeric. - var k = parseInt(keys[i], 0); - var v = m[k]; - // Value comes before the key. - d = d.concat(encode.OPERAND(v.value, v.type)); - d = d.concat(encode.OPERATOR(k)); - } - - return d; - }; - - /** - * @param {Object} - * @returns {number} - */ - sizeOf.DICT = function(m) { - return encode.DICT(m).length; - }; - - /** - * @param {number} - * @returns {Array} - */ - encode.OPERATOR = function(v) { - if (v < 1200) { - return [v]; - } else { - return [12, v - 1200]; - } - }; - - /** - * @param {Array} v - * @param {string} - * @returns {Array} - */ - encode.OPERAND = function(v, type) { - var d = []; - if (Array.isArray(type)) { - for (var i = 0; i < type.length; i += 1) { - check.argument(v.length === type.length, 'Not enough arguments given for type' + type); - d = d.concat(encode.OPERAND(v[i], type[i])); - } - } else { - if (type === 'SID') { - d = d.concat(encode.NUMBER(v)); - } else if (type === 'offset') { - // We make it easy for ourselves and always encode offsets as - // 4 bytes. This makes offset calculation for the top dict easier. - d = d.concat(encode.NUMBER32(v)); - } else if (type === 'number') { - d = d.concat(encode.NUMBER(v)); - } else if (type === 'real') { - d = d.concat(encode.REAL(v)); - } else { - throw new Error('Unknown operand type ' + type); - // FIXME Add support for booleans - } - } - - return d; - }; - - encode.OP = encode.BYTE; - sizeOf.OP = sizeOf.BYTE; - - // memoize charstring encoding using WeakMap if available - var wmm = typeof WeakMap === 'function' && new WeakMap(); - - /** - * Convert a list of CharString operations to bytes. - * @param {Array} - * @returns {Array} - */ - encode.CHARSTRING = function(ops) { - // See encode.MACSTRING for why we don't do "if (wmm && wmm.has(ops))". - if (wmm) { - var cachedValue = wmm.get(ops); - if (cachedValue !== undefined) { - return cachedValue; - } - } - - var d = []; - var length = ops.length; - - for (var i = 0; i < length; i += 1) { - var op = ops[i]; - d = d.concat(encode[op.type](op.value)); - } - - if (wmm) { - wmm.set(ops, d); - } - - return d; - }; - - /** - * @param {Array} - * @returns {number} - */ - sizeOf.CHARSTRING = function(ops) { - return encode.CHARSTRING(ops).length; - }; - - // Utility functions //////////////////////////////////////////////////////// - - /** - * Convert an object containing name / type / value to bytes. - * @param {Object} - * @returns {Array} - */ - encode.OBJECT = function(v) { - var encodingFunction = encode[v.type]; - check.argument(encodingFunction !== undefined, 'No encoding function for type ' + v.type); - return encodingFunction(v.value); - }; - - /** - * @param {Object} - * @returns {number} - */ - sizeOf.OBJECT = function(v) { - var sizeOfFunction = sizeOf[v.type]; - check.argument(sizeOfFunction !== undefined, 'No sizeOf function for type ' + v.type); - return sizeOfFunction(v.value); - }; - - /** - * Convert a table object to bytes. - * A table contains a list of fields containing the metadata (name, type and default value). - * The table itself has the field values set as attributes. - * @param {opentype.Table} - * @returns {Array} - */ - encode.TABLE = function(table) { - var d = []; - var length = table.fields.length; - var subtables = []; - var subtableOffsets = []; - - for (var i = 0; i < length; i += 1) { - var field = table.fields[i]; - var encodingFunction = encode[field.type]; - check.argument(encodingFunction !== undefined, 'No encoding function for field type ' + field.type + ' (' + field.name + ')'); - var value = table[field.name]; - if (value === undefined) { - value = field.value; - } - - var bytes = encodingFunction(value); - - if (field.type === 'TABLE') { - subtableOffsets.push(d.length); - d = d.concat([0, 0]); - subtables.push(bytes); - } else { - d = d.concat(bytes); - } - } - - for (var i$1 = 0; i$1 < subtables.length; i$1 += 1) { - var o = subtableOffsets[i$1]; - var offset = d.length; - check.argument(offset < 65536, 'Table ' + table.tableName + ' too big.'); - d[o] = offset >> 8; - d[o + 1] = offset & 0xff; - d = d.concat(subtables[i$1]); - } - - return d; - }; - - /** - * @param {opentype.Table} - * @returns {number} - */ - sizeOf.TABLE = function(table) { - var numBytes = 0; - var length = table.fields.length; - - for (var i = 0; i < length; i += 1) { - var field = table.fields[i]; - var sizeOfFunction = sizeOf[field.type]; - check.argument(sizeOfFunction !== undefined, 'No sizeOf function for field type ' + field.type + ' (' + field.name + ')'); - var value = table[field.name]; - if (value === undefined) { - value = field.value; - } - - numBytes += sizeOfFunction(value); - - // Subtables take 2 more bytes for offsets. - if (field.type === 'TABLE') { - numBytes += 2; - } - } - - return numBytes; - }; - - encode.RECORD = encode.TABLE; - sizeOf.RECORD = sizeOf.TABLE; - - // Merge in a list of bytes. - encode.LITERAL = function(v) { - return v; - }; - - sizeOf.LITERAL = function(v) { - return v.length; - }; - - // Table metadata - - /** - * @exports opentype.Table - * @class - * @param {string} tableName - * @param {Array} fields - * @param {Object} options - * @constructor - */ - function Table(tableName, fields, options) { - for (var i = 0; i < fields.length; i += 1) { - var field = fields[i]; - this[field.name] = field.value; - } - - this.tableName = tableName; - this.fields = fields; - if (options) { - var optionKeys = Object.keys(options); - for (var i$1 = 0; i$1 < optionKeys.length; i$1 += 1) { - var k = optionKeys[i$1]; - var v = options[k]; - if (this[k] !== undefined) { - this[k] = v; - } - } - } - } - - /** - * Encodes the table and returns an array of bytes - * @return {Array} - */ - Table.prototype.encode = function() { - return encode.TABLE(this); - }; - - /** - * Get the size of the table. - * @return {number} - */ - Table.prototype.sizeOf = function() { - return sizeOf.TABLE(this); - }; - - /** - * @private - */ - function ushortList(itemName, list, count) { - if (count === undefined) { - count = list.length; - } - var fields = new Array(list.length + 1); - fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; - for (var i = 0; i < list.length; i++) { - fields[i + 1] = {name: itemName + i, type: 'USHORT', value: list[i]}; - } - return fields; - } - - /** - * @private - */ - function tableList(itemName, records, itemCallback) { - var count = records.length; - var fields = new Array(count + 1); - fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; - for (var i = 0; i < count; i++) { - fields[i + 1] = {name: itemName + i, type: 'TABLE', value: itemCallback(records[i], i)}; - } - return fields; - } - - /** - * @private - */ - function recordList(itemName, records, itemCallback) { - var count = records.length; - var fields = []; - fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; - for (var i = 0; i < count; i++) { - fields = fields.concat(itemCallback(records[i], i)); - } - return fields; - } - - // Common Layout Tables - - /** - * @exports opentype.Coverage - * @class - * @param {opentype.Table} - * @constructor - * @extends opentype.Table - */ - function Coverage(coverageTable) { - if (coverageTable.format === 1) { - Table.call(this, 'coverageTable', - [{name: 'coverageFormat', type: 'USHORT', value: 1}] - .concat(ushortList('glyph', coverageTable.glyphs)) - ); - } else { - check.assert(false, 'Can\'t create coverage table format 2 yet.'); - } - } - Coverage.prototype = Object.create(Table.prototype); - Coverage.prototype.constructor = Coverage; - - function ScriptList(scriptListTable) { - Table.call(this, 'scriptListTable', - recordList('scriptRecord', scriptListTable, function(scriptRecord, i) { - var script = scriptRecord.script; - var defaultLangSys = script.defaultLangSys; - check.assert(!!defaultLangSys, 'Unable to write GSUB: script ' + scriptRecord.tag + ' has no default language system.'); - return [ - {name: 'scriptTag' + i, type: 'TAG', value: scriptRecord.tag}, - {name: 'script' + i, type: 'TABLE', value: new Table('scriptTable', [ - {name: 'defaultLangSys', type: 'TABLE', value: new Table('defaultLangSys', [ - {name: 'lookupOrder', type: 'USHORT', value: 0}, - {name: 'reqFeatureIndex', type: 'USHORT', value: defaultLangSys.reqFeatureIndex}] - .concat(ushortList('featureIndex', defaultLangSys.featureIndexes)))} - ].concat(recordList('langSys', script.langSysRecords, function(langSysRecord, i) { - var langSys = langSysRecord.langSys; - return [ - {name: 'langSysTag' + i, type: 'TAG', value: langSysRecord.tag}, - {name: 'langSys' + i, type: 'TABLE', value: new Table('langSys', [ - {name: 'lookupOrder', type: 'USHORT', value: 0}, - {name: 'reqFeatureIndex', type: 'USHORT', value: langSys.reqFeatureIndex} - ].concat(ushortList('featureIndex', langSys.featureIndexes)))} - ]; - })))} - ]; - }) - ); - } - ScriptList.prototype = Object.create(Table.prototype); - ScriptList.prototype.constructor = ScriptList; - - /** - * @exports opentype.FeatureList - * @class - * @param {opentype.Table} - * @constructor - * @extends opentype.Table - */ - function FeatureList(featureListTable) { - Table.call(this, 'featureListTable', - recordList('featureRecord', featureListTable, function(featureRecord, i) { - var feature = featureRecord.feature; - return [ - {name: 'featureTag' + i, type: 'TAG', value: featureRecord.tag}, - {name: 'feature' + i, type: 'TABLE', value: new Table('featureTable', [ - {name: 'featureParams', type: 'USHORT', value: feature.featureParams} ].concat(ushortList('lookupListIndex', feature.lookupListIndexes)))} - ]; - }) - ); - } - FeatureList.prototype = Object.create(Table.prototype); - FeatureList.prototype.constructor = FeatureList; - - /** - * @exports opentype.LookupList - * @class - * @param {opentype.Table} - * @param {Object} - * @constructor - * @extends opentype.Table - */ - function LookupList(lookupListTable, subtableMakers) { - Table.call(this, 'lookupListTable', tableList('lookup', lookupListTable, function(lookupTable) { - var subtableCallback = subtableMakers[lookupTable.lookupType]; - check.assert(!!subtableCallback, 'Unable to write GSUB lookup type ' + lookupTable.lookupType + ' tables.'); - return new Table('lookupTable', [ - {name: 'lookupType', type: 'USHORT', value: lookupTable.lookupType}, - {name: 'lookupFlag', type: 'USHORT', value: lookupTable.lookupFlag} - ].concat(tableList('subtable', lookupTable.subtables, subtableCallback))); - })); - } - LookupList.prototype = Object.create(Table.prototype); - LookupList.prototype.constructor = LookupList; - - // Record = same as Table, but inlined (a Table has an offset and its data is further in the stream) - // Don't use offsets inside Records (probable bug), only in Tables. - var table = { - Table: Table, - Record: Table, - Coverage: Coverage, - ScriptList: ScriptList, - FeatureList: FeatureList, - LookupList: LookupList, - ushortList: ushortList, - tableList: tableList, - recordList: recordList, - }; - - // Parsing utility functions - - // Retrieve an unsigned byte from the DataView. - function getByte(dataView, offset) { - return dataView.getUint8(offset); - } - - // Retrieve an unsigned 16-bit short from the DataView. - // The value is stored in big endian. - function getUShort(dataView, offset) { - return dataView.getUint16(offset, false); - } - - // Retrieve a signed 16-bit short from the DataView. - // The value is stored in big endian. - function getShort(dataView, offset) { - return dataView.getInt16(offset, false); - } - - // Retrieve an unsigned 32-bit long from the DataView. - // The value is stored in big endian. - function getULong(dataView, offset) { - return dataView.getUint32(offset, false); - } - - // Retrieve a 32-bit signed fixed-point number (16.16) from the DataView. - // The value is stored in big endian. - function getFixed(dataView, offset) { - var decimal = dataView.getInt16(offset, false); - var fraction = dataView.getUint16(offset + 2, false); - return decimal + fraction / 65535; - } - - // Retrieve a 4-character tag from the DataView. - // Tags are used to identify tables. - function getTag(dataView, offset) { - var tag = ''; - for (var i = offset; i < offset + 4; i += 1) { - tag += String.fromCharCode(dataView.getInt8(i)); - } - - return tag; - } - - // Retrieve an offset from the DataView. - // Offsets are 1 to 4 bytes in length, depending on the offSize argument. - function getOffset(dataView, offset, offSize) { - var v = 0; - for (var i = 0; i < offSize; i += 1) { - v <<= 8; - v += dataView.getUint8(offset + i); - } - - return v; - } - - // Retrieve a number of bytes from start offset to the end offset from the DataView. - function getBytes(dataView, startOffset, endOffset) { - var bytes = []; - for (var i = startOffset; i < endOffset; i += 1) { - bytes.push(dataView.getUint8(i)); - } - - return bytes; - } - - // Convert the list of bytes to a string. - function bytesToString(bytes) { - var s = ''; - for (var i = 0; i < bytes.length; i += 1) { - s += String.fromCharCode(bytes[i]); - } - - return s; - } - - var typeOffsets = { - byte: 1, - uShort: 2, - short: 2, - uLong: 4, - fixed: 4, - longDateTime: 8, - tag: 4 - }; - - // A stateful parser that changes the offset whenever a value is retrieved. - // The data is a DataView. - function Parser(data, offset) { - this.data = data; - this.offset = offset; - this.relativeOffset = 0; - } - - Parser.prototype.parseByte = function() { - var v = this.data.getUint8(this.offset + this.relativeOffset); - this.relativeOffset += 1; - return v; - }; - - Parser.prototype.parseChar = function() { - var v = this.data.getInt8(this.offset + this.relativeOffset); - this.relativeOffset += 1; - return v; - }; - - Parser.prototype.parseCard8 = Parser.prototype.parseByte; - - Parser.prototype.parseUShort = function() { - var v = this.data.getUint16(this.offset + this.relativeOffset); - this.relativeOffset += 2; - return v; - }; - - Parser.prototype.parseCard16 = Parser.prototype.parseUShort; - Parser.prototype.parseSID = Parser.prototype.parseUShort; - Parser.prototype.parseOffset16 = Parser.prototype.parseUShort; - - Parser.prototype.parseShort = function() { - var v = this.data.getInt16(this.offset + this.relativeOffset); - this.relativeOffset += 2; - return v; - }; - - Parser.prototype.parseF2Dot14 = function() { - var v = this.data.getInt16(this.offset + this.relativeOffset) / 16384; - this.relativeOffset += 2; - return v; - }; - - Parser.prototype.parseULong = function() { - var v = getULong(this.data, this.offset + this.relativeOffset); - this.relativeOffset += 4; - return v; - }; - - Parser.prototype.parseOffset32 = Parser.prototype.parseULong; - - Parser.prototype.parseFixed = function() { - var v = getFixed(this.data, this.offset + this.relativeOffset); - this.relativeOffset += 4; - return v; - }; - - Parser.prototype.parseString = function(length) { - var dataView = this.data; - var offset = this.offset + this.relativeOffset; - var string = ''; - this.relativeOffset += length; - for (var i = 0; i < length; i++) { - string += String.fromCharCode(dataView.getUint8(offset + i)); - } - - return string; - }; - - Parser.prototype.parseTag = function() { - return this.parseString(4); - }; - - // LONGDATETIME is a 64-bit integer. - // JavaScript and unix timestamps traditionally use 32 bits, so we - // only take the last 32 bits. - // + Since until 2038 those bits will be filled by zeros we can ignore them. - Parser.prototype.parseLongDateTime = function() { - var v = getULong(this.data, this.offset + this.relativeOffset + 4); - // Subtract seconds between 01/01/1904 and 01/01/1970 - // to convert Apple Mac timestamp to Standard Unix timestamp - v -= 2082844800; - this.relativeOffset += 8; - return v; - }; - - Parser.prototype.parseVersion = function(minorBase) { - var major = getUShort(this.data, this.offset + this.relativeOffset); - - // How to interpret the minor version is very vague in the spec. 0x5000 is 5, 0x1000 is 1 - // Default returns the correct number if minor = 0xN000 where N is 0-9 - // Set minorBase to 1 for tables that use minor = N where N is 0-9 - var minor = getUShort(this.data, this.offset + this.relativeOffset + 2); - this.relativeOffset += 4; - if (minorBase === undefined) { minorBase = 0x1000; } - return major + minor / minorBase / 10; - }; - - Parser.prototype.skip = function(type, amount) { - if (amount === undefined) { - amount = 1; - } - - this.relativeOffset += typeOffsets[type] * amount; - }; - - ///// Parsing lists and records /////////////////////////////// - - // Parse a list of 32 bit unsigned integers. - Parser.prototype.parseULongList = function(count) { - if (count === undefined) { count = this.parseULong(); } - var offsets = new Array(count); - var dataView = this.data; - var offset = this.offset + this.relativeOffset; - for (var i = 0; i < count; i++) { - offsets[i] = dataView.getUint32(offset); - offset += 4; - } - - this.relativeOffset += count * 4; - return offsets; - }; - - // Parse a list of 16 bit unsigned integers. The length of the list can be read on the stream - // or provided as an argument. - Parser.prototype.parseOffset16List = - Parser.prototype.parseUShortList = function(count) { - if (count === undefined) { count = this.parseUShort(); } - var offsets = new Array(count); - var dataView = this.data; - var offset = this.offset + this.relativeOffset; - for (var i = 0; i < count; i++) { - offsets[i] = dataView.getUint16(offset); - offset += 2; - } - - this.relativeOffset += count * 2; - return offsets; - }; - - // Parses a list of 16 bit signed integers. - Parser.prototype.parseShortList = function(count) { - var list = new Array(count); - var dataView = this.data; - var offset = this.offset + this.relativeOffset; - for (var i = 0; i < count; i++) { - list[i] = dataView.getInt16(offset); - offset += 2; - } - - this.relativeOffset += count * 2; - return list; - }; - - // Parses a list of bytes. - Parser.prototype.parseByteList = function(count) { - var list = new Array(count); - var dataView = this.data; - var offset = this.offset + this.relativeOffset; - for (var i = 0; i < count; i++) { - list[i] = dataView.getUint8(offset++); - } - - this.relativeOffset += count; - return list; - }; - - /** - * Parse a list of items. - * Record count is optional, if omitted it is read from the stream. - * itemCallback is one of the Parser methods. - */ - Parser.prototype.parseList = function(count, itemCallback) { - if (!itemCallback) { - itemCallback = count; - count = this.parseUShort(); - } - var list = new Array(count); - for (var i = 0; i < count; i++) { - list[i] = itemCallback.call(this); - } - return list; - }; - - Parser.prototype.parseList32 = function(count, itemCallback) { - if (!itemCallback) { - itemCallback = count; - count = this.parseULong(); - } - var list = new Array(count); - for (var i = 0; i < count; i++) { - list[i] = itemCallback.call(this); - } - return list; - }; - - /** - * Parse a list of records. - * Record count is optional, if omitted it is read from the stream. - * Example of recordDescription: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } - */ - Parser.prototype.parseRecordList = function(count, recordDescription) { - // If the count argument is absent, read it in the stream. - if (!recordDescription) { - recordDescription = count; - count = this.parseUShort(); - } - var records = new Array(count); - var fields = Object.keys(recordDescription); - for (var i = 0; i < count; i++) { - var rec = {}; - for (var j = 0; j < fields.length; j++) { - var fieldName = fields[j]; - var fieldType = recordDescription[fieldName]; - rec[fieldName] = fieldType.call(this); - } - records[i] = rec; - } - return records; - }; - - Parser.prototype.parseRecordList32 = function(count, recordDescription) { - // If the count argument is absent, read it in the stream. - if (!recordDescription) { - recordDescription = count; - count = this.parseULong(); - } - var records = new Array(count); - var fields = Object.keys(recordDescription); - for (var i = 0; i < count; i++) { - var rec = {}; - for (var j = 0; j < fields.length; j++) { - var fieldName = fields[j]; - var fieldType = recordDescription[fieldName]; - rec[fieldName] = fieldType.call(this); - } - records[i] = rec; - } - return records; - }; - - // Parse a data structure into an object - // Example of description: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } - Parser.prototype.parseStruct = function(description) { - if (typeof description === 'function') { - return description.call(this); - } else { - var fields = Object.keys(description); - var struct = {}; - for (var j = 0; j < fields.length; j++) { - var fieldName = fields[j]; - var fieldType = description[fieldName]; - struct[fieldName] = fieldType.call(this); - } - return struct; - } - }; - - /** - * Parse a GPOS valueRecord - * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record - * valueFormat is optional, if omitted it is read from the stream. - */ - Parser.prototype.parseValueRecord = function(valueFormat) { - if (valueFormat === undefined) { - valueFormat = this.parseUShort(); - } - if (valueFormat === 0) { - // valueFormat2 in kerning pairs is most often 0 - // in this case return undefined instead of an empty object, to save space - return; - } - var valueRecord = {}; - - if (valueFormat & 0x0001) { valueRecord.xPlacement = this.parseShort(); } - if (valueFormat & 0x0002) { valueRecord.yPlacement = this.parseShort(); } - if (valueFormat & 0x0004) { valueRecord.xAdvance = this.parseShort(); } - if (valueFormat & 0x0008) { valueRecord.yAdvance = this.parseShort(); } - - // Device table (non-variable font) / VariationIndex table (variable font) not supported - // https://docs.microsoft.com/fr-fr/typography/opentype/spec/chapter2#devVarIdxTbls - if (valueFormat & 0x0010) { valueRecord.xPlaDevice = undefined; this.parseShort(); } - if (valueFormat & 0x0020) { valueRecord.yPlaDevice = undefined; this.parseShort(); } - if (valueFormat & 0x0040) { valueRecord.xAdvDevice = undefined; this.parseShort(); } - if (valueFormat & 0x0080) { valueRecord.yAdvDevice = undefined; this.parseShort(); } - - return valueRecord; - }; - - /** - * Parse a list of GPOS valueRecords - * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record - * valueFormat and valueCount are read from the stream. - */ - Parser.prototype.parseValueRecordList = function() { - var valueFormat = this.parseUShort(); - var valueCount = this.parseUShort(); - var values = new Array(valueCount); - for (var i = 0; i < valueCount; i++) { - values[i] = this.parseValueRecord(valueFormat); - } - return values; - }; - - Parser.prototype.parsePointer = function(description) { - var structOffset = this.parseOffset16(); - if (structOffset > 0) { - // NULL offset => return undefined - return new Parser(this.data, this.offset + structOffset).parseStruct(description); - } - return undefined; - }; - - Parser.prototype.parsePointer32 = function(description) { - var structOffset = this.parseOffset32(); - if (structOffset > 0) { - // NULL offset => return undefined - return new Parser(this.data, this.offset + structOffset).parseStruct(description); - } - return undefined; - }; - - /** - * Parse a list of offsets to lists of 16-bit integers, - * or a list of offsets to lists of offsets to any kind of items. - * If itemCallback is not provided, a list of list of UShort is assumed. - * If provided, itemCallback is called on each item and must parse the item. - * See examples in tables/gsub.js - */ - Parser.prototype.parseListOfLists = function(itemCallback) { - var offsets = this.parseOffset16List(); - var count = offsets.length; - var relativeOffset = this.relativeOffset; - var list = new Array(count); - for (var i = 0; i < count; i++) { - var start = offsets[i]; - if (start === 0) { - // NULL offset - // Add i as owned property to list. Convenient with assert. - list[i] = undefined; - continue; - } - this.relativeOffset = start; - if (itemCallback) { - var subOffsets = this.parseOffset16List(); - var subList = new Array(subOffsets.length); - for (var j = 0; j < subOffsets.length; j++) { - this.relativeOffset = start + subOffsets[j]; - subList[j] = itemCallback.call(this); - } - list[i] = subList; - } else { - list[i] = this.parseUShortList(); - } - } - this.relativeOffset = relativeOffset; - return list; - }; - - ///// Complex tables parsing ////////////////////////////////// - - // Parse a coverage table in a GSUB, GPOS or GDEF table. - // https://www.microsoft.com/typography/OTSPEC/chapter2.htm - // parser.offset must point to the start of the table containing the coverage. - Parser.prototype.parseCoverage = function() { - var startOffset = this.offset + this.relativeOffset; - var format = this.parseUShort(); - var count = this.parseUShort(); - if (format === 1) { - return { - format: 1, - glyphs: this.parseUShortList(count) - }; - } else if (format === 2) { - var ranges = new Array(count); - for (var i = 0; i < count; i++) { - ranges[i] = { - start: this.parseUShort(), - end: this.parseUShort(), - index: this.parseUShort() - }; - } - return { - format: 2, - ranges: ranges - }; - } - throw new Error('0x' + startOffset.toString(16) + ': Coverage format must be 1 or 2.'); - }; - - // Parse a Class Definition Table in a GSUB, GPOS or GDEF table. - // https://www.microsoft.com/typography/OTSPEC/chapter2.htm - Parser.prototype.parseClassDef = function() { - var startOffset = this.offset + this.relativeOffset; - var format = this.parseUShort(); - if (format === 1) { - return { - format: 1, - startGlyph: this.parseUShort(), - classes: this.parseUShortList() - }; - } else if (format === 2) { - return { - format: 2, - ranges: this.parseRecordList({ - start: Parser.uShort, - end: Parser.uShort, - classId: Parser.uShort - }) - }; - } - throw new Error('0x' + startOffset.toString(16) + ': ClassDef format must be 1 or 2.'); - }; - - ///// Static methods /////////////////////////////////// - // These convenience methods can be used as callbacks and should be called with "this" context set to a Parser instance. - - Parser.list = function(count, itemCallback) { - return function() { - return this.parseList(count, itemCallback); - }; - }; - - Parser.list32 = function(count, itemCallback) { - return function() { - return this.parseList32(count, itemCallback); - }; - }; - - Parser.recordList = function(count, recordDescription) { - return function() { - return this.parseRecordList(count, recordDescription); - }; - }; - - Parser.recordList32 = function(count, recordDescription) { - return function() { - return this.parseRecordList32(count, recordDescription); - }; - }; - - Parser.pointer = function(description) { - return function() { - return this.parsePointer(description); - }; - }; - - Parser.pointer32 = function(description) { - return function() { - return this.parsePointer32(description); - }; - }; - - Parser.tag = Parser.prototype.parseTag; - Parser.byte = Parser.prototype.parseByte; - Parser.uShort = Parser.offset16 = Parser.prototype.parseUShort; - Parser.uShortList = Parser.prototype.parseUShortList; - Parser.uLong = Parser.offset32 = Parser.prototype.parseULong; - Parser.uLongList = Parser.prototype.parseULongList; - Parser.struct = Parser.prototype.parseStruct; - Parser.coverage = Parser.prototype.parseCoverage; - Parser.classDef = Parser.prototype.parseClassDef; - - ///// Script, Feature, Lookup lists /////////////////////////////////////////////// - // https://www.microsoft.com/typography/OTSPEC/chapter2.htm - - var langSysTable = { - reserved: Parser.uShort, - reqFeatureIndex: Parser.uShort, - featureIndexes: Parser.uShortList - }; - - Parser.prototype.parseScriptList = function() { - return this.parsePointer(Parser.recordList({ - tag: Parser.tag, - script: Parser.pointer({ - defaultLangSys: Parser.pointer(langSysTable), - langSysRecords: Parser.recordList({ - tag: Parser.tag, - langSys: Parser.pointer(langSysTable) - }) - }) - })) || []; - }; - - Parser.prototype.parseFeatureList = function() { - return this.parsePointer(Parser.recordList({ - tag: Parser.tag, - feature: Parser.pointer({ - featureParams: Parser.offset16, - lookupListIndexes: Parser.uShortList - }) - })) || []; - }; - - Parser.prototype.parseLookupList = function(lookupTableParsers) { - return this.parsePointer(Parser.list(Parser.pointer(function() { - var lookupType = this.parseUShort(); - check.argument(1 <= lookupType && lookupType <= 9, 'GPOS/GSUB lookup type ' + lookupType + ' unknown.'); - var lookupFlag = this.parseUShort(); - var useMarkFilteringSet = lookupFlag & 0x10; - return { - lookupType: lookupType, - lookupFlag: lookupFlag, - subtables: this.parseList(Parser.pointer(lookupTableParsers[lookupType])), - markFilteringSet: useMarkFilteringSet ? this.parseUShort() : undefined - }; - }))) || []; - }; - - Parser.prototype.parseFeatureVariationsList = function() { - return this.parsePointer32(function() { - var majorVersion = this.parseUShort(); - var minorVersion = this.parseUShort(); - check.argument(majorVersion === 1 && minorVersion < 1, 'GPOS/GSUB feature variations table unknown.'); - var featureVariations = this.parseRecordList32({ - conditionSetOffset: Parser.offset32, - featureTableSubstitutionOffset: Parser.offset32 - }); - return featureVariations; - }) || []; - }; - - var parse = { - getByte: getByte, - getCard8: getByte, - getUShort: getUShort, - getCard16: getUShort, - getShort: getShort, - getULong: getULong, - getFixed: getFixed, - getTag: getTag, - getOffset: getOffset, - getBytes: getBytes, - bytesToString: bytesToString, - Parser: Parser, - }; - - // The `cmap` table stores the mappings from characters to glyphs. - - function parseCmapTableFormat12(cmap, p) { - //Skip reserved. - p.parseUShort(); - - // Length in bytes of the sub-tables. - cmap.length = p.parseULong(); - cmap.language = p.parseULong(); - - var groupCount; - cmap.groupCount = groupCount = p.parseULong(); - cmap.glyphIndexMap = {}; - - for (var i = 0; i < groupCount; i += 1) { - var startCharCode = p.parseULong(); - var endCharCode = p.parseULong(); - var startGlyphId = p.parseULong(); - - for (var c = startCharCode; c <= endCharCode; c += 1) { - cmap.glyphIndexMap[c] = startGlyphId; - startGlyphId++; - } - } - } - - function parseCmapTableFormat4(cmap, p, data, start, offset) { - // Length in bytes of the sub-tables. - cmap.length = p.parseUShort(); - cmap.language = p.parseUShort(); - - // segCount is stored x 2. - var segCount; - cmap.segCount = segCount = p.parseUShort() >> 1; - - // Skip searchRange, entrySelector, rangeShift. - p.skip('uShort', 3); - - // The "unrolled" mapping from character codes to glyph indices. - cmap.glyphIndexMap = {}; - var endCountParser = new parse.Parser(data, start + offset + 14); - var startCountParser = new parse.Parser(data, start + offset + 16 + segCount * 2); - var idDeltaParser = new parse.Parser(data, start + offset + 16 + segCount * 4); - var idRangeOffsetParser = new parse.Parser(data, start + offset + 16 + segCount * 6); - var glyphIndexOffset = start + offset + 16 + segCount * 8; - for (var i = 0; i < segCount - 1; i += 1) { - var glyphIndex = (void 0); - var endCount = endCountParser.parseUShort(); - var startCount = startCountParser.parseUShort(); - var idDelta = idDeltaParser.parseShort(); - var idRangeOffset = idRangeOffsetParser.parseUShort(); - for (var c = startCount; c <= endCount; c += 1) { - if (idRangeOffset !== 0) { - // The idRangeOffset is relative to the current position in the idRangeOffset array. - // Take the current offset in the idRangeOffset array. - glyphIndexOffset = (idRangeOffsetParser.offset + idRangeOffsetParser.relativeOffset - 2); - - // Add the value of the idRangeOffset, which will move us into the glyphIndex array. - glyphIndexOffset += idRangeOffset; - - // Then add the character index of the current segment, multiplied by 2 for USHORTs. - glyphIndexOffset += (c - startCount) * 2; - glyphIndex = parse.getUShort(data, glyphIndexOffset); - if (glyphIndex !== 0) { - glyphIndex = (glyphIndex + idDelta) & 0xFFFF; - } - } else { - glyphIndex = (c + idDelta) & 0xFFFF; - } - - cmap.glyphIndexMap[c] = glyphIndex; - } - } - } - - // Parse the `cmap` table. This table stores the mappings from characters to glyphs. - // There are many available formats, but we only support the Windows format 4 and 12. - // This function returns a `CmapEncoding` object or null if no supported format could be found. - function parseCmapTable(data, start) { - var cmap = {}; - cmap.version = parse.getUShort(data, start); - check.argument(cmap.version === 0, 'cmap table version should be 0.'); - - // The cmap table can contain many sub-tables, each with their own format. - // We're only interested in a "platform 0" (Unicode format) and "platform 3" (Windows format) table. - cmap.numTables = parse.getUShort(data, start + 2); - var offset = -1; - for (var i = cmap.numTables - 1; i >= 0; i -= 1) { - var platformId = parse.getUShort(data, start + 4 + (i * 8)); - var encodingId = parse.getUShort(data, start + 4 + (i * 8) + 2); - if ((platformId === 3 && (encodingId === 0 || encodingId === 1 || encodingId === 10)) || - (platformId === 0 && (encodingId === 0 || encodingId === 1 || encodingId === 2 || encodingId === 3 || encodingId === 4))) { - offset = parse.getULong(data, start + 4 + (i * 8) + 4); - break; - } - } - - if (offset === -1) { - // There is no cmap table in the font that we support. - throw new Error('No valid cmap sub-tables found.'); - } - - var p = new parse.Parser(data, start + offset); - cmap.format = p.parseUShort(); - - if (cmap.format === 12) { - parseCmapTableFormat12(cmap, p); - } else if (cmap.format === 4) { - parseCmapTableFormat4(cmap, p, data, start, offset); - } else { - throw new Error('Only format 4 and 12 cmap tables are supported (found format ' + cmap.format + ').'); - } - - return cmap; - } - - function addSegment(t, code, glyphIndex) { - t.segments.push({ - end: code, - start: code, - delta: -(code - glyphIndex), - offset: 0, - glyphIndex: glyphIndex - }); - } - - function addTerminatorSegment(t) { - t.segments.push({ - end: 0xFFFF, - start: 0xFFFF, - delta: 1, - offset: 0 - }); - } - - // Make cmap table, format 4 by default, 12 if needed only - function makeCmapTable(glyphs) { - // Plan 0 is the base Unicode Plan but emojis, for example are on another plan, and needs cmap 12 format (with 32bit) - var isPlan0Only = true; - var i; - - // Check if we need to add cmap format 12 or if format 4 only is fine - for (i = glyphs.length - 1; i > 0; i -= 1) { - var g = glyphs.get(i); - if (g.unicode > 65535) { - console.log('Adding CMAP format 12 (needed!)'); - isPlan0Only = false; - break; - } - } - - var cmapTable = [ - {name: 'version', type: 'USHORT', value: 0}, - {name: 'numTables', type: 'USHORT', value: isPlan0Only ? 1 : 2}, - - // CMAP 4 header - {name: 'platformID', type: 'USHORT', value: 3}, - {name: 'encodingID', type: 'USHORT', value: 1}, - {name: 'offset', type: 'ULONG', value: isPlan0Only ? 12 : (12 + 8)} - ]; - - if (!isPlan0Only) - { cmapTable = cmapTable.concat([ - // CMAP 12 header - {name: 'cmap12PlatformID', type: 'USHORT', value: 3}, // We encode only for PlatformID = 3 (Windows) because it is supported everywhere - {name: 'cmap12EncodingID', type: 'USHORT', value: 10}, - {name: 'cmap12Offset', type: 'ULONG', value: 0} - ]); } - - cmapTable = cmapTable.concat([ - // CMAP 4 Subtable - {name: 'format', type: 'USHORT', value: 4}, - {name: 'cmap4Length', type: 'USHORT', value: 0}, - {name: 'language', type: 'USHORT', value: 0}, - {name: 'segCountX2', type: 'USHORT', value: 0}, - {name: 'searchRange', type: 'USHORT', value: 0}, - {name: 'entrySelector', type: 'USHORT', value: 0}, - {name: 'rangeShift', type: 'USHORT', value: 0} - ]); - - var t = new table.Table('cmap', cmapTable); - - t.segments = []; - for (i = 0; i < glyphs.length; i += 1) { - var glyph = glyphs.get(i); - for (var j = 0; j < glyph.unicodes.length; j += 1) { - addSegment(t, glyph.unicodes[j], i); - } - - t.segments = t.segments.sort(function (a, b) { - return a.start - b.start; - }); - } - - addTerminatorSegment(t); - - var segCount = t.segments.length; - var segCountToRemove = 0; - - // CMAP 4 - // Set up parallel segment arrays. - var endCounts = []; - var startCounts = []; - var idDeltas = []; - var idRangeOffsets = []; - var glyphIds = []; - - // CMAP 12 - var cmap12Groups = []; - - // Reminder this loop is not following the specification at 100% - // The specification -> find suites of characters and make a group - // Here we're doing one group for each letter - // Doing as the spec can save 8 times (or more) space - for (i = 0; i < segCount; i += 1) { - var segment = t.segments[i]; - - // CMAP 4 - if (segment.end <= 65535 && segment.start <= 65535) { - endCounts = endCounts.concat({name: 'end_' + i, type: 'USHORT', value: segment.end}); - startCounts = startCounts.concat({name: 'start_' + i, type: 'USHORT', value: segment.start}); - idDeltas = idDeltas.concat({name: 'idDelta_' + i, type: 'SHORT', value: segment.delta}); - idRangeOffsets = idRangeOffsets.concat({name: 'idRangeOffset_' + i, type: 'USHORT', value: segment.offset}); - if (segment.glyphId !== undefined) { - glyphIds = glyphIds.concat({name: 'glyph_' + i, type: 'USHORT', value: segment.glyphId}); - } - } else { - // Skip Unicode > 65535 (16bit unsigned max) for CMAP 4, will be added in CMAP 12 - segCountToRemove += 1; - } - - // CMAP 12 - // Skip Terminator Segment - if (!isPlan0Only && segment.glyphIndex !== undefined) { - cmap12Groups = cmap12Groups.concat({name: 'cmap12Start_' + i, type: 'ULONG', value: segment.start}); - cmap12Groups = cmap12Groups.concat({name: 'cmap12End_' + i, type: 'ULONG', value: segment.end}); - cmap12Groups = cmap12Groups.concat({name: 'cmap12Glyph_' + i, type: 'ULONG', value: segment.glyphIndex}); - } - } - - // CMAP 4 Subtable - t.segCountX2 = (segCount - segCountToRemove) * 2; - t.searchRange = Math.pow(2, Math.floor(Math.log((segCount - segCountToRemove)) / Math.log(2))) * 2; - t.entrySelector = Math.log(t.searchRange / 2) / Math.log(2); - t.rangeShift = t.segCountX2 - t.searchRange; - - t.fields = t.fields.concat(endCounts); - t.fields.push({name: 'reservedPad', type: 'USHORT', value: 0}); - t.fields = t.fields.concat(startCounts); - t.fields = t.fields.concat(idDeltas); - t.fields = t.fields.concat(idRangeOffsets); - t.fields = t.fields.concat(glyphIds); - - t.cmap4Length = 14 + // Subtable header - endCounts.length * 2 + - 2 + // reservedPad - startCounts.length * 2 + - idDeltas.length * 2 + - idRangeOffsets.length * 2 + - glyphIds.length * 2; - - if (!isPlan0Only) { - // CMAP 12 Subtable - var cmap12Length = 16 + // Subtable header - cmap12Groups.length * 4; - - t.cmap12Offset = 12 + (2 * 2) + 4 + t.cmap4Length; - t.fields = t.fields.concat([ - {name: 'cmap12Format', type: 'USHORT', value: 12}, - {name: 'cmap12Reserved', type: 'USHORT', value: 0}, - {name: 'cmap12Length', type: 'ULONG', value: cmap12Length}, - {name: 'cmap12Language', type: 'ULONG', value: 0}, - {name: 'cmap12nGroups', type: 'ULONG', value: cmap12Groups.length / 3} - ]); - - t.fields = t.fields.concat(cmap12Groups); - } - - return t; - } - - var cmap = { parse: parseCmapTable, make: makeCmapTable }; - - // Glyph encoding - - var cffStandardStrings = [ - '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', - 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', - 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', - 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', - 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', - 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling', - 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', - 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph', - 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', - 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', - 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', - 'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu', - 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', - 'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright', - 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', - 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex', - 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', - 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute', - 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', - 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', - 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', - 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff', 'onedotenleader', - 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', - 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', - 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', - 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', - 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', - 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', - 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', - 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', - 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', - 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', - 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', - 'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', - 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', - 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', - 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', - 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', - 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', - 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', - 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', - '001.001', '001.002', '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold']; - - var cffStandardEncoding = [ - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', - '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', - 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', - 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', - 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', - 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', - 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', - 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', - 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', - 'daggerdbl', 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', - 'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', 'tilde', - 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', - 'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', 'ordfeminine', '', '', '', - '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', - 'lslash', 'oslash', 'oe', 'germandbls']; - - var cffExpertEncoding = [ - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', - '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle', 'dollarsuperior', - 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', - 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', - 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', - 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', '', 'asuperior', - 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', '', '', 'isuperior', '', '', 'lsuperior', 'msuperior', - 'nsuperior', 'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior', '', 'ff', 'fi', 'fl', 'ffi', 'ffl', - 'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', - 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', - 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', - 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', - 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', '', '', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', - 'Brevesmall', 'Caronsmall', '', 'Dotaccentsmall', '', '', 'Macronsmall', '', '', 'figuredash', 'hypheninferior', - '', '', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters', - 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', '', - '', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', - 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', - 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', - 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', - 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', - 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', - 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', - 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', - 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall']; - - var standardNames = [ - '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', - 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', - 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', - 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', - 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', - 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', - 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', 'Odieresis', 'Udieresis', 'aacute', 'agrave', - 'acircumflex', 'adieresis', 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis', - 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex', 'odieresis', - 'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', - 'bullet', 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute', 'dieresis', 'notequal', - 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal', 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', - 'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', 'questiondown', - 'exclamdown', 'logicalnot', 'radical', 'florin', 'approxequal', 'Delta', 'guillemotleft', 'guillemotright', - 'ellipsis', 'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft', - 'quotedblright', 'quoteleft', 'quoteright', 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', - 'currency', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered', 'quotesinglbase', - 'quotedblbase', 'perthousand', 'Acircumflex', 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', - 'Icircumflex', 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute', 'Ucircumflex', - 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', - 'ogonek', 'caron', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar', 'Eth', 'eth', - 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply', 'onesuperior', 'twosuperior', 'threesuperior', - 'onehalf', 'onequarter', 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', 'scedilla', - 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat']; - - /** - * This is the encoding used for fonts created from scratch. - * It loops through all glyphs and finds the appropriate unicode value. - * Since it's linear time, other encodings will be faster. - * @exports opentype.DefaultEncoding - * @class - * @constructor - * @param {opentype.Font} - */ - function DefaultEncoding(font) { - this.font = font; - } - - DefaultEncoding.prototype.charToGlyphIndex = function(c) { - var code = c.codePointAt(0); - var glyphs = this.font.glyphs; - if (glyphs) { - for (var i = 0; i < glyphs.length; i += 1) { - var glyph = glyphs.get(i); - for (var j = 0; j < glyph.unicodes.length; j += 1) { - if (glyph.unicodes[j] === code) { - return i; - } - } - } - } - return null; - }; - - /** - * @exports opentype.CmapEncoding - * @class - * @constructor - * @param {Object} cmap - a object with the cmap encoded data - */ - function CmapEncoding(cmap) { - this.cmap = cmap; - } - - /** - * @param {string} c - the character - * @return {number} The glyph index. - */ - CmapEncoding.prototype.charToGlyphIndex = function(c) { - return this.cmap.glyphIndexMap[c.codePointAt(0)] || 0; - }; - - /** - * @exports opentype.CffEncoding - * @class - * @constructor - * @param {string} encoding - The encoding - * @param {Array} charset - The character set. - */ - function CffEncoding(encoding, charset) { - this.encoding = encoding; - this.charset = charset; - } - - /** - * @param {string} s - The character - * @return {number} The index. - */ - CffEncoding.prototype.charToGlyphIndex = function(s) { - var code = s.codePointAt(0); - var charName = this.encoding[code]; - return this.charset.indexOf(charName); - }; - - /** - * @exports opentype.GlyphNames - * @class - * @constructor - * @param {Object} post - */ - function GlyphNames(post) { - switch (post.version) { - case 1: - this.names = standardNames.slice(); - break; - case 2: - this.names = new Array(post.numberOfGlyphs); - for (var i = 0; i < post.numberOfGlyphs; i++) { - if (post.glyphNameIndex[i] < standardNames.length) { - this.names[i] = standardNames[post.glyphNameIndex[i]]; - } else { - this.names[i] = post.names[post.glyphNameIndex[i] - standardNames.length]; - } - } - - break; - case 2.5: - this.names = new Array(post.numberOfGlyphs); - for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { - this.names[i$1] = standardNames[i$1 + post.glyphNameIndex[i$1]]; - } - - break; - case 3: - this.names = []; - break; - default: - this.names = []; - break; - } - } - - /** - * Gets the index of a glyph by name. - * @param {string} name - The glyph name - * @return {number} The index - */ - GlyphNames.prototype.nameToGlyphIndex = function(name) { - return this.names.indexOf(name); - }; - - /** - * @param {number} gid - * @return {string} - */ - GlyphNames.prototype.glyphIndexToName = function(gid) { - return this.names[gid]; - }; - - function addGlyphNamesAll(font) { - var glyph; - var glyphIndexMap = font.tables.cmap.glyphIndexMap; - var charCodes = Object.keys(glyphIndexMap); - - for (var i = 0; i < charCodes.length; i += 1) { - var c = charCodes[i]; - var glyphIndex = glyphIndexMap[c]; - glyph = font.glyphs.get(glyphIndex); - glyph.addUnicode(parseInt(c)); - } - - for (var i$1 = 0; i$1 < font.glyphs.length; i$1 += 1) { - glyph = font.glyphs.get(i$1); - if (font.cffEncoding) { - if (font.isCIDFont) { - glyph.name = 'gid' + i$1; - } else { - glyph.name = font.cffEncoding.charset[i$1]; - } - } else if (font.glyphNames.names) { - glyph.name = font.glyphNames.glyphIndexToName(i$1); - } - } - } - - function addGlyphNamesToUnicodeMap(font) { - font._IndexToUnicodeMap = {}; - - var glyphIndexMap = font.tables.cmap.glyphIndexMap; - var charCodes = Object.keys(glyphIndexMap); - - for (var i = 0; i < charCodes.length; i += 1) { - var c = charCodes[i]; - var glyphIndex = glyphIndexMap[c]; - if (font._IndexToUnicodeMap[glyphIndex] === undefined) { - font._IndexToUnicodeMap[glyphIndex] = { - unicodes: [parseInt(c)] - }; - } else { - font._IndexToUnicodeMap[glyphIndex].unicodes.push(parseInt(c)); - } - } - } - - /** - * @alias opentype.addGlyphNames - * @param {opentype.Font} - * @param {Object} - */ - function addGlyphNames(font, opt) { - if (opt.lowMemory) { - addGlyphNamesToUnicodeMap(font); - } else { - addGlyphNamesAll(font); - } - } - - // Drawing utility functions. - - // Draw a line on the given context from point `x1,y1` to point `x2,y2`. - function line(ctx, x1, y1, x2, y2) { - ctx.beginPath(); - ctx.moveTo(x1, y1); - ctx.lineTo(x2, y2); - ctx.stroke(); - } - - var draw = { line: line }; - - // The Glyph object - // import glyf from './tables/glyf' Can't be imported here, because it's a circular dependency - - function getPathDefinition(glyph, path) { - var _path = path || new Path(); - return { - configurable: true, - - get: function() { - if (typeof _path === 'function') { - _path = _path(); - } - - return _path; - }, - - set: function(p) { - _path = p; - } - }; - } - /** - * @typedef GlyphOptions - * @type Object - * @property {string} [name] - The glyph name - * @property {number} [unicode] - * @property {Array} [unicodes] - * @property {number} [xMin] - * @property {number} [yMin] - * @property {number} [xMax] - * @property {number} [yMax] - * @property {number} [advanceWidth] - */ - - // A Glyph is an individual mark that often corresponds to a character. - // Some glyphs, such as ligatures, are a combination of many characters. - // Glyphs are the basic building blocks of a font. - // - // The `Glyph` class contains utility methods for drawing the path and its points. - /** - * @exports opentype.Glyph - * @class - * @param {GlyphOptions} - * @constructor - */ - function Glyph(options) { - // By putting all the code on a prototype function (which is only declared once) - // we reduce the memory requirements for larger fonts by some 2% - this.bindConstructorValues(options); - } - - /** - * @param {GlyphOptions} - */ - Glyph.prototype.bindConstructorValues = function(options) { - this.index = options.index || 0; - - // These three values cannot be deferred for memory optimization: - this.name = options.name || null; - this.unicode = options.unicode || undefined; - this.unicodes = options.unicodes || options.unicode !== undefined ? [options.unicode] : []; - - // But by binding these values only when necessary, we reduce can - // the memory requirements by almost 3% for larger fonts. - if ('xMin' in options) { - this.xMin = options.xMin; - } - - if ('yMin' in options) { - this.yMin = options.yMin; - } - - if ('xMax' in options) { - this.xMax = options.xMax; - } - - if ('yMax' in options) { - this.yMax = options.yMax; - } - - if ('advanceWidth' in options) { - this.advanceWidth = options.advanceWidth; - } - - // The path for a glyph is the most memory intensive, and is bound as a value - // with a getter/setter to ensure we actually do path parsing only once the - // path is actually needed by anything. - Object.defineProperty(this, 'path', getPathDefinition(this, options.path)); - }; - - /** - * @param {number} - */ - Glyph.prototype.addUnicode = function(unicode) { - if (this.unicodes.length === 0) { - this.unicode = unicode; - } - - this.unicodes.push(unicode); - }; - - /** - * Calculate the minimum bounding box for this glyph. - * @return {opentype.BoundingBox} - */ - Glyph.prototype.getBoundingBox = function() { - return this.path.getBoundingBox(); - }; - - /** - * Convert the glyph to a Path we can draw on a drawing context. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {Object=} options - xScale, yScale to stretch the glyph. - * @param {opentype.Font} if hinting is to be used, the font - * @return {opentype.Path} - */ - Glyph.prototype.getPath = function(x, y, fontSize, options, font) { - x = x !== undefined ? x : 0; - y = y !== undefined ? y : 0; - fontSize = fontSize !== undefined ? fontSize : 72; - var commands; - var hPoints; - if (!options) { options = { }; } - var xScale = options.xScale; - var yScale = options.yScale; - - if (options.hinting && font && font.hinting) { - // in case of hinting, the hinting engine takes care - // of scaling the points (not the path) before hinting. - hPoints = this.path && font.hinting.exec(this, fontSize); - // in case the hinting engine failed hPoints is undefined - // and thus reverts to plain rending - } - - if (hPoints) { - // Call font.hinting.getCommands instead of `glyf.getPath(hPoints).commands` to avoid a circular dependency - commands = font.hinting.getCommands(hPoints); - x = Math.round(x); - y = Math.round(y); - // TODO in case of hinting xyScaling is not yet supported - xScale = yScale = 1; - } else { - commands = this.path.commands; - var scale = 1 / (this.path.unitsPerEm || 1000) * fontSize; - if (xScale === undefined) { xScale = scale; } - if (yScale === undefined) { yScale = scale; } - } - - var p = new Path(); - for (var i = 0; i < commands.length; i += 1) { - var cmd = commands[i]; - if (cmd.type === 'M') { - p.moveTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); - } else if (cmd.type === 'L') { - p.lineTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); - } else if (cmd.type === 'Q') { - p.quadraticCurveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), - x + (cmd.x * xScale), y + (-cmd.y * yScale)); - } else if (cmd.type === 'C') { - p.curveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), - x + (cmd.x2 * xScale), y + (-cmd.y2 * yScale), - x + (cmd.x * xScale), y + (-cmd.y * yScale)); - } else if (cmd.type === 'Z') { - p.closePath(); - } - } - - return p; - }; - - /** - * Split the glyph into contours. - * This function is here for backwards compatibility, and to - * provide raw access to the TrueType glyph outlines. - * @return {Array} - */ - Glyph.prototype.getContours = function() { - if (this.points === undefined) { - return []; - } - - var contours = []; - var currentContour = []; - for (var i = 0; i < this.points.length; i += 1) { - var pt = this.points[i]; - currentContour.push(pt); - if (pt.lastPointOfContour) { - contours.push(currentContour); - currentContour = []; - } - } - - check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); - return contours; - }; - - /** - * Calculate the xMin/yMin/xMax/yMax/lsb/rsb for a Glyph. - * @return {Object} - */ - Glyph.prototype.getMetrics = function() { - var commands = this.path.commands; - var xCoords = []; - var yCoords = []; - for (var i = 0; i < commands.length; i += 1) { - var cmd = commands[i]; - if (cmd.type !== 'Z') { - xCoords.push(cmd.x); - yCoords.push(cmd.y); - } - - if (cmd.type === 'Q' || cmd.type === 'C') { - xCoords.push(cmd.x1); - yCoords.push(cmd.y1); - } - - if (cmd.type === 'C') { - xCoords.push(cmd.x2); - yCoords.push(cmd.y2); - } - } - - var metrics = { - xMin: Math.min.apply(null, xCoords), - yMin: Math.min.apply(null, yCoords), - xMax: Math.max.apply(null, xCoords), - yMax: Math.max.apply(null, yCoords), - leftSideBearing: this.leftSideBearing - }; - - if (!isFinite(metrics.xMin)) { - metrics.xMin = 0; - } - - if (!isFinite(metrics.xMax)) { - metrics.xMax = this.advanceWidth; - } - - if (!isFinite(metrics.yMin)) { - metrics.yMin = 0; - } - - if (!isFinite(metrics.yMax)) { - metrics.yMax = 0; - } - - metrics.rightSideBearing = this.advanceWidth - metrics.leftSideBearing - (metrics.xMax - metrics.xMin); - return metrics; - }; - - /** - * Draw the glyph on the given context. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {Object=} options - xScale, yScale to stretch the glyph. - */ - Glyph.prototype.draw = function(ctx, x, y, fontSize, options) { - this.getPath(x, y, fontSize, options).draw(ctx); - }; - - /** - * Draw the points of the glyph. - * On-curve points will be drawn in blue, off-curve points will be drawn in red. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - */ - Glyph.prototype.drawPoints = function(ctx, x, y, fontSize) { - function drawCircles(l, x, y, scale) { - ctx.beginPath(); - for (var j = 0; j < l.length; j += 1) { - ctx.moveTo(x + (l[j].x * scale), y + (l[j].y * scale)); - ctx.arc(x + (l[j].x * scale), y + (l[j].y * scale), 2, 0, Math.PI * 2, false); - } - - ctx.closePath(); - ctx.fill(); - } - - x = x !== undefined ? x : 0; - y = y !== undefined ? y : 0; - fontSize = fontSize !== undefined ? fontSize : 24; - var scale = 1 / this.path.unitsPerEm * fontSize; - - var blueCircles = []; - var redCircles = []; - var path = this.path; - for (var i = 0; i < path.commands.length; i += 1) { - var cmd = path.commands[i]; - if (cmd.x !== undefined) { - blueCircles.push({x: cmd.x, y: -cmd.y}); - } - - if (cmd.x1 !== undefined) { - redCircles.push({x: cmd.x1, y: -cmd.y1}); - } - - if (cmd.x2 !== undefined) { - redCircles.push({x: cmd.x2, y: -cmd.y2}); - } - } - - ctx.fillStyle = 'blue'; - drawCircles(blueCircles, x, y, scale); - ctx.fillStyle = 'red'; - drawCircles(redCircles, x, y, scale); - }; - - /** - * Draw lines indicating important font measurements. - * Black lines indicate the origin of the coordinate system (point 0,0). - * Blue lines indicate the glyph bounding box. - * Green line indicates the advance width of the glyph. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - */ - Glyph.prototype.drawMetrics = function(ctx, x, y, fontSize) { - var scale; - x = x !== undefined ? x : 0; - y = y !== undefined ? y : 0; - fontSize = fontSize !== undefined ? fontSize : 24; - scale = 1 / this.path.unitsPerEm * fontSize; - ctx.lineWidth = 1; - - // Draw the origin - ctx.strokeStyle = 'black'; - draw.line(ctx, x, -10000, x, 10000); - draw.line(ctx, -10000, y, 10000, y); - - // This code is here due to memory optimization: by not using - // defaults in the constructor, we save a notable amount of memory. - var xMin = this.xMin || 0; - var yMin = this.yMin || 0; - var xMax = this.xMax || 0; - var yMax = this.yMax || 0; - var advanceWidth = this.advanceWidth || 0; - - // Draw the glyph box - ctx.strokeStyle = 'blue'; - draw.line(ctx, x + (xMin * scale), -10000, x + (xMin * scale), 10000); - draw.line(ctx, x + (xMax * scale), -10000, x + (xMax * scale), 10000); - draw.line(ctx, -10000, y + (-yMin * scale), 10000, y + (-yMin * scale)); - draw.line(ctx, -10000, y + (-yMax * scale), 10000, y + (-yMax * scale)); - - // Draw the advance width - ctx.strokeStyle = 'green'; - draw.line(ctx, x + (advanceWidth * scale), -10000, x + (advanceWidth * scale), 10000); - }; - - // The GlyphSet object - - // Define a property on the glyph that depends on the path being loaded. - function defineDependentProperty(glyph, externalName, internalName) { - Object.defineProperty(glyph, externalName, { - get: function() { - // Request the path property to make sure the path is loaded. - glyph.path; // jshint ignore:line - return glyph[internalName]; - }, - set: function(newValue) { - glyph[internalName] = newValue; - }, - enumerable: true, - configurable: true - }); - } - - /** - * A GlyphSet represents all glyphs available in the font, but modelled using - * a deferred glyph loader, for retrieving glyphs only once they are absolutely - * necessary, to keep the memory footprint down. - * @exports opentype.GlyphSet - * @class - * @param {opentype.Font} - * @param {Array} - */ - function GlyphSet(font, glyphs) { - this.font = font; - this.glyphs = {}; - if (Array.isArray(glyphs)) { - for (var i = 0; i < glyphs.length; i++) { - var glyph = glyphs[i]; - glyph.path.unitsPerEm = font.unitsPerEm; - this.glyphs[i] = glyph; - } - } - - this.length = (glyphs && glyphs.length) || 0; - } - - /** - * @param {number} index - * @return {opentype.Glyph} - */ - GlyphSet.prototype.get = function(index) { - // this.glyphs[index] is 'undefined' when low memory mode is on. glyph is pushed on request only. - if (this.glyphs[index] === undefined) { - this.font._push(index); - if (typeof this.glyphs[index] === 'function') { - this.glyphs[index] = this.glyphs[index](); - } - - var glyph = this.glyphs[index]; - var unicodeObj = this.font._IndexToUnicodeMap[index]; - - if (unicodeObj) { - for (var j = 0; j < unicodeObj.unicodes.length; j++) - { glyph.addUnicode(unicodeObj.unicodes[j]); } - } - - if (this.font.cffEncoding) { - if (this.font.isCIDFont) { - glyph.name = 'gid' + index; - } else { - glyph.name = this.font.cffEncoding.charset[index]; - } - } else if (this.font.glyphNames.names) { - glyph.name = this.font.glyphNames.glyphIndexToName(index); - } - - this.glyphs[index].advanceWidth = this.font._hmtxTableData[index].advanceWidth; - this.glyphs[index].leftSideBearing = this.font._hmtxTableData[index].leftSideBearing; - } else { - if (typeof this.glyphs[index] === 'function') { - this.glyphs[index] = this.glyphs[index](); - } - } - - return this.glyphs[index]; - }; - - /** - * @param {number} index - * @param {Object} - */ - GlyphSet.prototype.push = function(index, loader) { - this.glyphs[index] = loader; - this.length++; - }; - - /** - * @alias opentype.glyphLoader - * @param {opentype.Font} font - * @param {number} index - * @return {opentype.Glyph} - */ - function glyphLoader(font, index) { - return new Glyph({index: index, font: font}); - } - - /** - * Generate a stub glyph that can be filled with all metadata *except* - * the "points" and "path" properties, which must be loaded only once - * the glyph's path is actually requested for text shaping. - * @alias opentype.ttfGlyphLoader - * @param {opentype.Font} font - * @param {number} index - * @param {Function} parseGlyph - * @param {Object} data - * @param {number} position - * @param {Function} buildPath - * @return {opentype.Glyph} - */ - function ttfGlyphLoader(font, index, parseGlyph, data, position, buildPath) { - return function() { - var glyph = new Glyph({index: index, font: font}); - - glyph.path = function() { - parseGlyph(glyph, data, position); - var path = buildPath(font.glyphs, glyph); - path.unitsPerEm = font.unitsPerEm; - return path; - }; - - defineDependentProperty(glyph, 'xMin', '_xMin'); - defineDependentProperty(glyph, 'xMax', '_xMax'); - defineDependentProperty(glyph, 'yMin', '_yMin'); - defineDependentProperty(glyph, 'yMax', '_yMax'); - - return glyph; - }; - } - /** - * @alias opentype.cffGlyphLoader - * @param {opentype.Font} font - * @param {number} index - * @param {Function} parseCFFCharstring - * @param {string} charstring - * @return {opentype.Glyph} - */ - function cffGlyphLoader(font, index, parseCFFCharstring, charstring) { - return function() { - var glyph = new Glyph({index: index, font: font}); - - glyph.path = function() { - var path = parseCFFCharstring(font, glyph, charstring); - path.unitsPerEm = font.unitsPerEm; - return path; - }; - - return glyph; - }; - } - - var glyphset = { GlyphSet: GlyphSet, glyphLoader: glyphLoader, ttfGlyphLoader: ttfGlyphLoader, cffGlyphLoader: cffGlyphLoader }; - - // The `CFF` table contains the glyph outlines in PostScript format. - - // Custom equals function that can also check lists. - function equals(a, b) { - if (a === b) { - return true; - } else if (Array.isArray(a) && Array.isArray(b)) { - if (a.length !== b.length) { - return false; - } - - for (var i = 0; i < a.length; i += 1) { - if (!equals(a[i], b[i])) { - return false; - } - } - - return true; - } else { - return false; - } - } - - // Subroutines are encoded using the negative half of the number space. - // See type 2 chapter 4.7 "Subroutine operators". - function calcCFFSubroutineBias(subrs) { - var bias; - if (subrs.length < 1240) { - bias = 107; - } else if (subrs.length < 33900) { - bias = 1131; - } else { - bias = 32768; - } - - return bias; - } - - // Parse a `CFF` INDEX array. - // An index array consists of a list of offsets, then a list of objects at those offsets. - function parseCFFIndex(data, start, conversionFn) { - var offsets = []; - var objects = []; - var count = parse.getCard16(data, start); - var objectOffset; - var endOffset; - if (count !== 0) { - var offsetSize = parse.getByte(data, start + 2); - objectOffset = start + ((count + 1) * offsetSize) + 2; - var pos = start + 3; - for (var i = 0; i < count + 1; i += 1) { - offsets.push(parse.getOffset(data, pos, offsetSize)); - pos += offsetSize; - } - - // The total size of the index array is 4 header bytes + the value of the last offset. - endOffset = objectOffset + offsets[count]; - } else { - endOffset = start + 2; - } - - for (var i$1 = 0; i$1 < offsets.length - 1; i$1 += 1) { - var value = parse.getBytes(data, objectOffset + offsets[i$1], objectOffset + offsets[i$1 + 1]); - if (conversionFn) { - value = conversionFn(value); - } - - objects.push(value); - } - - return {objects: objects, startOffset: start, endOffset: endOffset}; - } - - function parseCFFIndexLowMemory(data, start) { - var offsets = []; - var count = parse.getCard16(data, start); - var objectOffset; - var endOffset; - if (count !== 0) { - var offsetSize = parse.getByte(data, start + 2); - objectOffset = start + ((count + 1) * offsetSize) + 2; - var pos = start + 3; - for (var i = 0; i < count + 1; i += 1) { - offsets.push(parse.getOffset(data, pos, offsetSize)); - pos += offsetSize; - } - - // The total size of the index array is 4 header bytes + the value of the last offset. - endOffset = objectOffset + offsets[count]; - } else { - endOffset = start + 2; - } - - return {offsets: offsets, startOffset: start, endOffset: endOffset}; - } - function getCffIndexObject(i, offsets, data, start, conversionFn) { - var count = parse.getCard16(data, start); - var objectOffset = 0; - if (count !== 0) { - var offsetSize = parse.getByte(data, start + 2); - objectOffset = start + ((count + 1) * offsetSize) + 2; - } - - var value = parse.getBytes(data, objectOffset + offsets[i], objectOffset + offsets[i + 1]); - if (conversionFn) { - value = conversionFn(value); - } - return value; - } - - // Parse a `CFF` DICT real value. - function parseFloatOperand(parser) { - var s = ''; - var eof = 15; - var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-']; - while (true) { - var b = parser.parseByte(); - var n1 = b >> 4; - var n2 = b & 15; - - if (n1 === eof) { - break; - } - - s += lookup[n1]; - - if (n2 === eof) { - break; - } - - s += lookup[n2]; - } - - return parseFloat(s); - } - - // Parse a `CFF` DICT operand. - function parseOperand(parser, b0) { - var b1; - var b2; - var b3; - var b4; - if (b0 === 28) { - b1 = parser.parseByte(); - b2 = parser.parseByte(); - return b1 << 8 | b2; - } - - if (b0 === 29) { - b1 = parser.parseByte(); - b2 = parser.parseByte(); - b3 = parser.parseByte(); - b4 = parser.parseByte(); - return b1 << 24 | b2 << 16 | b3 << 8 | b4; - } - - if (b0 === 30) { - return parseFloatOperand(parser); - } - - if (b0 >= 32 && b0 <= 246) { - return b0 - 139; - } - - if (b0 >= 247 && b0 <= 250) { - b1 = parser.parseByte(); - return (b0 - 247) * 256 + b1 + 108; - } - - if (b0 >= 251 && b0 <= 254) { - b1 = parser.parseByte(); - return -(b0 - 251) * 256 - b1 - 108; - } - - throw new Error('Invalid b0 ' + b0); - } - - // Convert the entries returned by `parseDict` to a proper dictionary. - // If a value is a list of one, it is unpacked. - function entriesToObject(entries) { - var o = {}; - for (var i = 0; i < entries.length; i += 1) { - var key = entries[i][0]; - var values = entries[i][1]; - var value = (void 0); - if (values.length === 1) { - value = values[0]; - } else { - value = values; - } - - if (o.hasOwnProperty(key) && !isNaN(o[key])) { - throw new Error('Object ' + o + ' already has key ' + key); - } - - o[key] = value; - } - - return o; - } - - // Parse a `CFF` DICT object. - // A dictionary contains key-value pairs in a compact tokenized format. - function parseCFFDict(data, start, size) { - start = start !== undefined ? start : 0; - var parser = new parse.Parser(data, start); - var entries = []; - var operands = []; - size = size !== undefined ? size : data.length; - - while (parser.relativeOffset < size) { - var op = parser.parseByte(); - - // The first byte for each dict item distinguishes between operator (key) and operand (value). - // Values <= 21 are operators. - if (op <= 21) { - // Two-byte operators have an initial escape byte of 12. - if (op === 12) { - op = 1200 + parser.parseByte(); - } - - entries.push([op, operands]); - operands = []; - } else { - // Since the operands (values) come before the operators (keys), we store all operands in a list - // until we encounter an operator. - operands.push(parseOperand(parser, op)); - } - } - - return entriesToObject(entries); - } - - // Given a String Index (SID), return the value of the string. - // Strings below index 392 are standard CFF strings and are not encoded in the font. - function getCFFString(strings, index) { - if (index <= 390) { - index = cffStandardStrings[index]; - } else { - index = strings[index - 391]; - } - - return index; - } - - // Interpret a dictionary and return a new dictionary with readable keys and values for missing entries. - // This function takes `meta` which is a list of objects containing `operand`, `name` and `default`. - function interpretDict(dict, meta, strings) { - var newDict = {}; - var value; - - // Because we also want to include missing values, we start out from the meta list - // and lookup values in the dict. - for (var i = 0; i < meta.length; i += 1) { - var m = meta[i]; - - if (Array.isArray(m.type)) { - var values = []; - values.length = m.type.length; - for (var j = 0; j < m.type.length; j++) { - value = dict[m.op] !== undefined ? dict[m.op][j] : undefined; - if (value === undefined) { - value = m.value !== undefined && m.value[j] !== undefined ? m.value[j] : null; - } - if (m.type[j] === 'SID') { - value = getCFFString(strings, value); - } - values[j] = value; - } - newDict[m.name] = values; - } else { - value = dict[m.op]; - if (value === undefined) { - value = m.value !== undefined ? m.value : null; - } - - if (m.type === 'SID') { - value = getCFFString(strings, value); - } - newDict[m.name] = value; - } - } - - return newDict; - } - - // Parse the CFF header. - function parseCFFHeader(data, start) { - var header = {}; - header.formatMajor = parse.getCard8(data, start); - header.formatMinor = parse.getCard8(data, start + 1); - header.size = parse.getCard8(data, start + 2); - header.offsetSize = parse.getCard8(data, start + 3); - header.startOffset = start; - header.endOffset = start + 4; - return header; - } - - var TOP_DICT_META = [ - {name: 'version', op: 0, type: 'SID'}, - {name: 'notice', op: 1, type: 'SID'}, - {name: 'copyright', op: 1200, type: 'SID'}, - {name: 'fullName', op: 2, type: 'SID'}, - {name: 'familyName', op: 3, type: 'SID'}, - {name: 'weight', op: 4, type: 'SID'}, - {name: 'isFixedPitch', op: 1201, type: 'number', value: 0}, - {name: 'italicAngle', op: 1202, type: 'number', value: 0}, - {name: 'underlinePosition', op: 1203, type: 'number', value: -100}, - {name: 'underlineThickness', op: 1204, type: 'number', value: 50}, - {name: 'paintType', op: 1205, type: 'number', value: 0}, - {name: 'charstringType', op: 1206, type: 'number', value: 2}, - { - name: 'fontMatrix', - op: 1207, - type: ['real', 'real', 'real', 'real', 'real', 'real'], - value: [0.001, 0, 0, 0.001, 0, 0] - }, - {name: 'uniqueId', op: 13, type: 'number'}, - {name: 'fontBBox', op: 5, type: ['number', 'number', 'number', 'number'], value: [0, 0, 0, 0]}, - {name: 'strokeWidth', op: 1208, type: 'number', value: 0}, - {name: 'xuid', op: 14, type: [], value: null}, - {name: 'charset', op: 15, type: 'offset', value: 0}, - {name: 'encoding', op: 16, type: 'offset', value: 0}, - {name: 'charStrings', op: 17, type: 'offset', value: 0}, - {name: 'private', op: 18, type: ['number', 'offset'], value: [0, 0]}, - {name: 'ros', op: 1230, type: ['SID', 'SID', 'number']}, - {name: 'cidFontVersion', op: 1231, type: 'number', value: 0}, - {name: 'cidFontRevision', op: 1232, type: 'number', value: 0}, - {name: 'cidFontType', op: 1233, type: 'number', value: 0}, - {name: 'cidCount', op: 1234, type: 'number', value: 8720}, - {name: 'uidBase', op: 1235, type: 'number'}, - {name: 'fdArray', op: 1236, type: 'offset'}, - {name: 'fdSelect', op: 1237, type: 'offset'}, - {name: 'fontName', op: 1238, type: 'SID'} - ]; - - var PRIVATE_DICT_META = [ - {name: 'subrs', op: 19, type: 'offset', value: 0}, - {name: 'defaultWidthX', op: 20, type: 'number', value: 0}, - {name: 'nominalWidthX', op: 21, type: 'number', value: 0} - ]; - - // Parse the CFF top dictionary. A CFF table can contain multiple fonts, each with their own top dictionary. - // The top dictionary contains the essential metadata for the font, together with the private dictionary. - function parseCFFTopDict(data, strings) { - var dict = parseCFFDict(data, 0, data.byteLength); - return interpretDict(dict, TOP_DICT_META, strings); - } - - // Parse the CFF private dictionary. We don't fully parse out all the values, only the ones we need. - function parseCFFPrivateDict(data, start, size, strings) { - var dict = parseCFFDict(data, start, size); - return interpretDict(dict, PRIVATE_DICT_META, strings); - } - - // Returns a list of "Top DICT"s found using an INDEX list. - // Used to read both the usual high-level Top DICTs and also the FDArray - // discovered inside CID-keyed fonts. When a Top DICT has a reference to - // a Private DICT that is read and saved into the Top DICT. - // - // In addition to the expected/optional values as outlined in TOP_DICT_META - // the following values might be saved into the Top DICT. - // - // _subrs [] array of local CFF subroutines from Private DICT - // _subrsBias bias value computed from number of subroutines - // (see calcCFFSubroutineBias() and parseCFFCharstring()) - // _defaultWidthX default widths for CFF characters - // _nominalWidthX bias added to width embedded within glyph description - // - // _privateDict saved copy of parsed Private DICT from Top DICT - function gatherCFFTopDicts(data, start, cffIndex, strings) { - var topDictArray = []; - for (var iTopDict = 0; iTopDict < cffIndex.length; iTopDict += 1) { - var topDictData = new DataView(new Uint8Array(cffIndex[iTopDict]).buffer); - var topDict = parseCFFTopDict(topDictData, strings); - topDict._subrs = []; - topDict._subrsBias = 0; - var privateSize = topDict.private[0]; - var privateOffset = topDict.private[1]; - if (privateSize !== 0 && privateOffset !== 0) { - var privateDict = parseCFFPrivateDict(data, privateOffset + start, privateSize, strings); - topDict._defaultWidthX = privateDict.defaultWidthX; - topDict._nominalWidthX = privateDict.nominalWidthX; - if (privateDict.subrs !== 0) { - var subrOffset = privateOffset + privateDict.subrs; - var subrIndex = parseCFFIndex(data, subrOffset + start); - topDict._subrs = subrIndex.objects; - topDict._subrsBias = calcCFFSubroutineBias(topDict._subrs); - } - topDict._privateDict = privateDict; - } - topDictArray.push(topDict); - } - return topDictArray; - } - - // Parse the CFF charset table, which contains internal names for all the glyphs. - // This function will return a list of glyph names. - // See Adobe TN #5176 chapter 13, "Charsets". - function parseCFFCharset(data, start, nGlyphs, strings) { - var sid; - var count; - var parser = new parse.Parser(data, start); - - // The .notdef glyph is not included, so subtract 1. - nGlyphs -= 1; - var charset = ['.notdef']; - - var format = parser.parseCard8(); - if (format === 0) { - for (var i = 0; i < nGlyphs; i += 1) { - sid = parser.parseSID(); - charset.push(getCFFString(strings, sid)); - } - } else if (format === 1) { - while (charset.length <= nGlyphs) { - sid = parser.parseSID(); - count = parser.parseCard8(); - for (var i$1 = 0; i$1 <= count; i$1 += 1) { - charset.push(getCFFString(strings, sid)); - sid += 1; - } - } - } else if (format === 2) { - while (charset.length <= nGlyphs) { - sid = parser.parseSID(); - count = parser.parseCard16(); - for (var i$2 = 0; i$2 <= count; i$2 += 1) { - charset.push(getCFFString(strings, sid)); - sid += 1; - } - } - } else { - throw new Error('Unknown charset format ' + format); - } - - return charset; - } - - // Parse the CFF encoding data. Only one encoding can be specified per font. - // See Adobe TN #5176 chapter 12, "Encodings". - function parseCFFEncoding(data, start, charset) { - var code; - var enc = {}; - var parser = new parse.Parser(data, start); - var format = parser.parseCard8(); - if (format === 0) { - var nCodes = parser.parseCard8(); - for (var i = 0; i < nCodes; i += 1) { - code = parser.parseCard8(); - enc[code] = i; - } - } else if (format === 1) { - var nRanges = parser.parseCard8(); - code = 1; - for (var i$1 = 0; i$1 < nRanges; i$1 += 1) { - var first = parser.parseCard8(); - var nLeft = parser.parseCard8(); - for (var j = first; j <= first + nLeft; j += 1) { - enc[j] = code; - code += 1; - } - } - } else { - throw new Error('Unknown encoding format ' + format); - } - - return new CffEncoding(enc, charset); - } - - // Take in charstring code and return a Glyph object. - // The encoding is described in the Type 2 Charstring Format - // https://www.microsoft.com/typography/OTSPEC/charstr2.htm - function parseCFFCharstring(font, glyph, code) { - var c1x; - var c1y; - var c2x; - var c2y; - var p = new Path(); - var stack = []; - var nStems = 0; - var haveWidth = false; - var open = false; - var x = 0; - var y = 0; - var subrs; - var subrsBias; - var defaultWidthX; - var nominalWidthX; - if (font.isCIDFont) { - var fdIndex = font.tables.cff.topDict._fdSelect[glyph.index]; - var fdDict = font.tables.cff.topDict._fdArray[fdIndex]; - subrs = fdDict._subrs; - subrsBias = fdDict._subrsBias; - defaultWidthX = fdDict._defaultWidthX; - nominalWidthX = fdDict._nominalWidthX; - } else { - subrs = font.tables.cff.topDict._subrs; - subrsBias = font.tables.cff.topDict._subrsBias; - defaultWidthX = font.tables.cff.topDict._defaultWidthX; - nominalWidthX = font.tables.cff.topDict._nominalWidthX; - } - var width = defaultWidthX; - - function newContour(x, y) { - if (open) { - p.closePath(); - } - - p.moveTo(x, y); - open = true; - } - - function parseStems() { - var hasWidthArg; - - // The number of stem operators on the stack is always even. - // If the value is uneven, that means a width is specified. - hasWidthArg = stack.length % 2 !== 0; - if (hasWidthArg && !haveWidth) { - width = stack.shift() + nominalWidthX; - } - - nStems += stack.length >> 1; - stack.length = 0; - haveWidth = true; - } - - function parse(code) { - var b1; - var b2; - var b3; - var b4; - var codeIndex; - var subrCode; - var jpx; - var jpy; - var c3x; - var c3y; - var c4x; - var c4y; - - var i = 0; - while (i < code.length) { - var v = code[i]; - i += 1; - switch (v) { - case 1: // hstem - parseStems(); - break; - case 3: // vstem - parseStems(); - break; - case 4: // vmoveto - if (stack.length > 1 && !haveWidth) { - width = stack.shift() + nominalWidthX; - haveWidth = true; - } - - y += stack.pop(); - newContour(x, y); - break; - case 5: // rlineto - while (stack.length > 0) { - x += stack.shift(); - y += stack.shift(); - p.lineTo(x, y); - } - - break; - case 6: // hlineto - while (stack.length > 0) { - x += stack.shift(); - p.lineTo(x, y); - if (stack.length === 0) { - break; - } - - y += stack.shift(); - p.lineTo(x, y); - } - - break; - case 7: // vlineto - while (stack.length > 0) { - y += stack.shift(); - p.lineTo(x, y); - if (stack.length === 0) { - break; - } - - x += stack.shift(); - p.lineTo(x, y); - } - - break; - case 8: // rrcurveto - while (stack.length > 0) { - c1x = x + stack.shift(); - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y + stack.shift(); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } - - break; - case 10: // callsubr - codeIndex = stack.pop() + subrsBias; - subrCode = subrs[codeIndex]; - if (subrCode) { - parse(subrCode); - } - - break; - case 11: // return - return; - case 12: // flex operators - v = code[i]; - i += 1; - switch (v) { - case 35: // flex - // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |- - c1x = x + stack.shift(); // dx1 - c1y = y + stack.shift(); // dy1 - c2x = c1x + stack.shift(); // dx2 - c2y = c1y + stack.shift(); // dy2 - jpx = c2x + stack.shift(); // dx3 - jpy = c2y + stack.shift(); // dy3 - c3x = jpx + stack.shift(); // dx4 - c3y = jpy + stack.shift(); // dy4 - c4x = c3x + stack.shift(); // dx5 - c4y = c3y + stack.shift(); // dy5 - x = c4x + stack.shift(); // dx6 - y = c4y + stack.shift(); // dy6 - stack.shift(); // flex depth - p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); - p.curveTo(c3x, c3y, c4x, c4y, x, y); - break; - case 34: // hflex - // |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |- - c1x = x + stack.shift(); // dx1 - c1y = y; // dy1 - c2x = c1x + stack.shift(); // dx2 - c2y = c1y + stack.shift(); // dy2 - jpx = c2x + stack.shift(); // dx3 - jpy = c2y; // dy3 - c3x = jpx + stack.shift(); // dx4 - c3y = c2y; // dy4 - c4x = c3x + stack.shift(); // dx5 - c4y = y; // dy5 - x = c4x + stack.shift(); // dx6 - p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); - p.curveTo(c3x, c3y, c4x, c4y, x, y); - break; - case 36: // hflex1 - // |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |- - c1x = x + stack.shift(); // dx1 - c1y = y + stack.shift(); // dy1 - c2x = c1x + stack.shift(); // dx2 - c2y = c1y + stack.shift(); // dy2 - jpx = c2x + stack.shift(); // dx3 - jpy = c2y; // dy3 - c3x = jpx + stack.shift(); // dx4 - c3y = c2y; // dy4 - c4x = c3x + stack.shift(); // dx5 - c4y = c3y + stack.shift(); // dy5 - x = c4x + stack.shift(); // dx6 - p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); - p.curveTo(c3x, c3y, c4x, c4y, x, y); - break; - case 37: // flex1 - // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |- - c1x = x + stack.shift(); // dx1 - c1y = y + stack.shift(); // dy1 - c2x = c1x + stack.shift(); // dx2 - c2y = c1y + stack.shift(); // dy2 - jpx = c2x + stack.shift(); // dx3 - jpy = c2y + stack.shift(); // dy3 - c3x = jpx + stack.shift(); // dx4 - c3y = jpy + stack.shift(); // dy4 - c4x = c3x + stack.shift(); // dx5 - c4y = c3y + stack.shift(); // dy5 - if (Math.abs(c4x - x) > Math.abs(c4y - y)) { - x = c4x + stack.shift(); - } else { - y = c4y + stack.shift(); - } - - p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); - p.curveTo(c3x, c3y, c4x, c4y, x, y); - break; - default: - console.log('Glyph ' + glyph.index + ': unknown operator ' + 1200 + v); - stack.length = 0; - } - break; - case 14: // endchar - if (stack.length > 0 && !haveWidth) { - width = stack.shift() + nominalWidthX; - haveWidth = true; - } - - if (open) { - p.closePath(); - open = false; - } - - break; - case 18: // hstemhm - parseStems(); - break; - case 19: // hintmask - case 20: // cntrmask - parseStems(); - i += (nStems + 7) >> 3; - break; - case 21: // rmoveto - if (stack.length > 2 && !haveWidth) { - width = stack.shift() + nominalWidthX; - haveWidth = true; - } - - y += stack.pop(); - x += stack.pop(); - newContour(x, y); - break; - case 22: // hmoveto - if (stack.length > 1 && !haveWidth) { - width = stack.shift() + nominalWidthX; - haveWidth = true; - } - - x += stack.pop(); - newContour(x, y); - break; - case 23: // vstemhm - parseStems(); - break; - case 24: // rcurveline - while (stack.length > 2) { - c1x = x + stack.shift(); - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y + stack.shift(); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } - - x += stack.shift(); - y += stack.shift(); - p.lineTo(x, y); - break; - case 25: // rlinecurve - while (stack.length > 6) { - x += stack.shift(); - y += stack.shift(); - p.lineTo(x, y); - } - - c1x = x + stack.shift(); - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y + stack.shift(); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - break; - case 26: // vvcurveto - if (stack.length % 2) { - x += stack.shift(); - } - - while (stack.length > 0) { - c1x = x; - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x; - y = c2y + stack.shift(); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } - - break; - case 27: // hhcurveto - if (stack.length % 2) { - y += stack.shift(); - } - - while (stack.length > 0) { - c1x = x + stack.shift(); - c1y = y; - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y; - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } - - break; - case 28: // shortint - b1 = code[i]; - b2 = code[i + 1]; - stack.push(((b1 << 24) | (b2 << 16)) >> 16); - i += 2; - break; - case 29: // callgsubr - codeIndex = stack.pop() + font.gsubrsBias; - subrCode = font.gsubrs[codeIndex]; - if (subrCode) { - parse(subrCode); - } - - break; - case 30: // vhcurveto - while (stack.length > 0) { - c1x = x; - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y + (stack.length === 1 ? stack.shift() : 0); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - if (stack.length === 0) { - break; - } - - c1x = x + stack.shift(); - c1y = y; - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - y = c2y + stack.shift(); - x = c2x + (stack.length === 1 ? stack.shift() : 0); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } - - break; - case 31: // hvcurveto - while (stack.length > 0) { - c1x = x + stack.shift(); - c1y = y; - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - y = c2y + stack.shift(); - x = c2x + (stack.length === 1 ? stack.shift() : 0); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - if (stack.length === 0) { - break; - } - - c1x = x; - c1y = y + stack.shift(); - c2x = c1x + stack.shift(); - c2y = c1y + stack.shift(); - x = c2x + stack.shift(); - y = c2y + (stack.length === 1 ? stack.shift() : 0); - p.curveTo(c1x, c1y, c2x, c2y, x, y); - } - - break; - default: - if (v < 32) { - console.log('Glyph ' + glyph.index + ': unknown operator ' + v); - } else if (v < 247) { - stack.push(v - 139); - } else if (v < 251) { - b1 = code[i]; - i += 1; - stack.push((v - 247) * 256 + b1 + 108); - } else if (v < 255) { - b1 = code[i]; - i += 1; - stack.push(-(v - 251) * 256 - b1 - 108); - } else { - b1 = code[i]; - b2 = code[i + 1]; - b3 = code[i + 2]; - b4 = code[i + 3]; - i += 4; - stack.push(((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) / 65536); - } - } - } - } - - parse(code); - - glyph.advanceWidth = width; - return p; - } - - function parseCFFFDSelect(data, start, nGlyphs, fdArrayCount) { - var fdSelect = []; - var fdIndex; - var parser = new parse.Parser(data, start); - var format = parser.parseCard8(); - if (format === 0) { - // Simple list of nGlyphs elements - for (var iGid = 0; iGid < nGlyphs; iGid++) { - fdIndex = parser.parseCard8(); - if (fdIndex >= fdArrayCount) { - throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); - } - fdSelect.push(fdIndex); - } - } else if (format === 3) { - // Ranges - var nRanges = parser.parseCard16(); - var first = parser.parseCard16(); - if (first !== 0) { - throw new Error('CFF Table CID Font FDSelect format 3 range has bad initial GID ' + first); - } - var next; - for (var iRange = 0; iRange < nRanges; iRange++) { - fdIndex = parser.parseCard8(); - next = parser.parseCard16(); - if (fdIndex >= fdArrayCount) { - throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); - } - if (next > nGlyphs) { - throw new Error('CFF Table CID Font FDSelect format 3 range has bad GID ' + next); - } - for (; first < next; first++) { - fdSelect.push(fdIndex); - } - first = next; - } - if (next !== nGlyphs) { - throw new Error('CFF Table CID Font FDSelect format 3 range has bad final GID ' + next); - } - } else { - throw new Error('CFF Table CID Font FDSelect table has unsupported format ' + format); - } - return fdSelect; - } - - // Parse the `CFF` table, which contains the glyph outlines in PostScript format. - function parseCFFTable(data, start, font, opt) { - font.tables.cff = {}; - var header = parseCFFHeader(data, start); - var nameIndex = parseCFFIndex(data, header.endOffset, parse.bytesToString); - var topDictIndex = parseCFFIndex(data, nameIndex.endOffset); - var stringIndex = parseCFFIndex(data, topDictIndex.endOffset, parse.bytesToString); - var globalSubrIndex = parseCFFIndex(data, stringIndex.endOffset); - font.gsubrs = globalSubrIndex.objects; - font.gsubrsBias = calcCFFSubroutineBias(font.gsubrs); - - var topDictArray = gatherCFFTopDicts(data, start, topDictIndex.objects, stringIndex.objects); - if (topDictArray.length !== 1) { - throw new Error('CFF table has too many fonts in \'FontSet\' - count of fonts NameIndex.length = ' + topDictArray.length); - } - - var topDict = topDictArray[0]; - font.tables.cff.topDict = topDict; - - if (topDict._privateDict) { - font.defaultWidthX = topDict._privateDict.defaultWidthX; - font.nominalWidthX = topDict._privateDict.nominalWidthX; - } - - if (topDict.ros[0] !== undefined && topDict.ros[1] !== undefined) { - font.isCIDFont = true; - } - - if (font.isCIDFont) { - var fdArrayOffset = topDict.fdArray; - var fdSelectOffset = topDict.fdSelect; - if (fdArrayOffset === 0 || fdSelectOffset === 0) { - throw new Error('Font is marked as a CID font, but FDArray and/or FDSelect information is missing'); - } - fdArrayOffset += start; - var fdArrayIndex = parseCFFIndex(data, fdArrayOffset); - var fdArray = gatherCFFTopDicts(data, start, fdArrayIndex.objects, stringIndex.objects); - topDict._fdArray = fdArray; - fdSelectOffset += start; - topDict._fdSelect = parseCFFFDSelect(data, fdSelectOffset, font.numGlyphs, fdArray.length); - } - - var privateDictOffset = start + topDict.private[1]; - var privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict.private[0], stringIndex.objects); - font.defaultWidthX = privateDict.defaultWidthX; - font.nominalWidthX = privateDict.nominalWidthX; - - if (privateDict.subrs !== 0) { - var subrOffset = privateDictOffset + privateDict.subrs; - var subrIndex = parseCFFIndex(data, subrOffset); - font.subrs = subrIndex.objects; - font.subrsBias = calcCFFSubroutineBias(font.subrs); - } else { - font.subrs = []; - font.subrsBias = 0; - } - - // Offsets in the top dict are relative to the beginning of the CFF data, so add the CFF start offset. - var charStringsIndex; - if (opt.lowMemory) { - charStringsIndex = parseCFFIndexLowMemory(data, start + topDict.charStrings); - font.nGlyphs = charStringsIndex.offsets.length; - } else { - charStringsIndex = parseCFFIndex(data, start + topDict.charStrings); - font.nGlyphs = charStringsIndex.objects.length; - } - - var charset = parseCFFCharset(data, start + topDict.charset, font.nGlyphs, stringIndex.objects); - if (topDict.encoding === 0) { - // Standard encoding - font.cffEncoding = new CffEncoding(cffStandardEncoding, charset); - } else if (topDict.encoding === 1) { - // Expert encoding - font.cffEncoding = new CffEncoding(cffExpertEncoding, charset); - } else { - font.cffEncoding = parseCFFEncoding(data, start + topDict.encoding, charset); - } - - // Prefer the CMAP encoding to the CFF encoding. - font.encoding = font.encoding || font.cffEncoding; - - font.glyphs = new glyphset.GlyphSet(font); - if (opt.lowMemory) { - font._push = function(i) { - var charString = getCffIndexObject(i, charStringsIndex.offsets, data, start + topDict.charStrings); - font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); - }; - } else { - for (var i = 0; i < font.nGlyphs; i += 1) { - var charString = charStringsIndex.objects[i]; - font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); - } - } - } - - // Convert a string to a String ID (SID). - // The list of strings is modified in place. - function encodeString(s, strings) { - var sid; - - // Is the string in the CFF standard strings? - var i = cffStandardStrings.indexOf(s); - if (i >= 0) { - sid = i; - } - - // Is the string already in the string index? - i = strings.indexOf(s); - if (i >= 0) { - sid = i + cffStandardStrings.length; - } else { - sid = cffStandardStrings.length + strings.length; - strings.push(s); - } - - return sid; - } - - function makeHeader() { - return new table.Record('Header', [ - {name: 'major', type: 'Card8', value: 1}, - {name: 'minor', type: 'Card8', value: 0}, - {name: 'hdrSize', type: 'Card8', value: 4}, - {name: 'major', type: 'Card8', value: 1} - ]); - } - - function makeNameIndex(fontNames) { - var t = new table.Record('Name INDEX', [ - {name: 'names', type: 'INDEX', value: []} - ]); - t.names = []; - for (var i = 0; i < fontNames.length; i += 1) { - t.names.push({name: 'name_' + i, type: 'NAME', value: fontNames[i]}); - } - - return t; - } - - // Given a dictionary's metadata, create a DICT structure. - function makeDict(meta, attrs, strings) { - var m = {}; - for (var i = 0; i < meta.length; i += 1) { - var entry = meta[i]; - var value = attrs[entry.name]; - if (value !== undefined && !equals(value, entry.value)) { - if (entry.type === 'SID') { - value = encodeString(value, strings); - } - - m[entry.op] = {name: entry.name, type: entry.type, value: value}; - } - } - - return m; - } - - // The Top DICT houses the global font attributes. - function makeTopDict(attrs, strings) { - var t = new table.Record('Top DICT', [ - {name: 'dict', type: 'DICT', value: {}} - ]); - t.dict = makeDict(TOP_DICT_META, attrs, strings); - return t; - } - - function makeTopDictIndex(topDict) { - var t = new table.Record('Top DICT INDEX', [ - {name: 'topDicts', type: 'INDEX', value: []} - ]); - t.topDicts = [{name: 'topDict_0', type: 'TABLE', value: topDict}]; - return t; - } - - function makeStringIndex(strings) { - var t = new table.Record('String INDEX', [ - {name: 'strings', type: 'INDEX', value: []} - ]); - t.strings = []; - for (var i = 0; i < strings.length; i += 1) { - t.strings.push({name: 'string_' + i, type: 'STRING', value: strings[i]}); - } - - return t; - } - - function makeGlobalSubrIndex() { - // Currently we don't use subroutines. - return new table.Record('Global Subr INDEX', [ - {name: 'subrs', type: 'INDEX', value: []} - ]); - } - - function makeCharsets(glyphNames, strings) { - var t = new table.Record('Charsets', [ - {name: 'format', type: 'Card8', value: 0} - ]); - for (var i = 0; i < glyphNames.length; i += 1) { - var glyphName = glyphNames[i]; - var glyphSID = encodeString(glyphName, strings); - t.fields.push({name: 'glyph_' + i, type: 'SID', value: glyphSID}); - } - - return t; - } - - function glyphToOps(glyph) { - var ops = []; - var path = glyph.path; - ops.push({name: 'width', type: 'NUMBER', value: glyph.advanceWidth}); - var x = 0; - var y = 0; - for (var i = 0; i < path.commands.length; i += 1) { - var dx = (void 0); - var dy = (void 0); - var cmd = path.commands[i]; - if (cmd.type === 'Q') { - // CFF only supports bézier curves, so convert the quad to a bézier. - var _13 = 1 / 3; - var _23 = 2 / 3; - - // We're going to create a new command so we don't change the original path. - cmd = { - type: 'C', - x: cmd.x, - y: cmd.y, - x1: _13 * x + _23 * cmd.x1, - y1: _13 * y + _23 * cmd.y1, - x2: _13 * cmd.x + _23 * cmd.x1, - y2: _13 * cmd.y + _23 * cmd.y1 - }; - } - - if (cmd.type === 'M') { - dx = Math.round(cmd.x - x); - dy = Math.round(cmd.y - y); - ops.push({name: 'dx', type: 'NUMBER', value: dx}); - ops.push({name: 'dy', type: 'NUMBER', value: dy}); - ops.push({name: 'rmoveto', type: 'OP', value: 21}); - x = Math.round(cmd.x); - y = Math.round(cmd.y); - } else if (cmd.type === 'L') { - dx = Math.round(cmd.x - x); - dy = Math.round(cmd.y - y); - ops.push({name: 'dx', type: 'NUMBER', value: dx}); - ops.push({name: 'dy', type: 'NUMBER', value: dy}); - ops.push({name: 'rlineto', type: 'OP', value: 5}); - x = Math.round(cmd.x); - y = Math.round(cmd.y); - } else if (cmd.type === 'C') { - var dx1 = Math.round(cmd.x1 - x); - var dy1 = Math.round(cmd.y1 - y); - var dx2 = Math.round(cmd.x2 - cmd.x1); - var dy2 = Math.round(cmd.y2 - cmd.y1); - dx = Math.round(cmd.x - cmd.x2); - dy = Math.round(cmd.y - cmd.y2); - ops.push({name: 'dx1', type: 'NUMBER', value: dx1}); - ops.push({name: 'dy1', type: 'NUMBER', value: dy1}); - ops.push({name: 'dx2', type: 'NUMBER', value: dx2}); - ops.push({name: 'dy2', type: 'NUMBER', value: dy2}); - ops.push({name: 'dx', type: 'NUMBER', value: dx}); - ops.push({name: 'dy', type: 'NUMBER', value: dy}); - ops.push({name: 'rrcurveto', type: 'OP', value: 8}); - x = Math.round(cmd.x); - y = Math.round(cmd.y); - } - - // Contours are closed automatically. - } - - ops.push({name: 'endchar', type: 'OP', value: 14}); - return ops; - } - - function makeCharStringsIndex(glyphs) { - var t = new table.Record('CharStrings INDEX', [ - {name: 'charStrings', type: 'INDEX', value: []} - ]); - - for (var i = 0; i < glyphs.length; i += 1) { - var glyph = glyphs.get(i); - var ops = glyphToOps(glyph); - t.charStrings.push({name: glyph.name, type: 'CHARSTRING', value: ops}); - } - - return t; - } - - function makePrivateDict(attrs, strings) { - var t = new table.Record('Private DICT', [ - {name: 'dict', type: 'DICT', value: {}} - ]); - t.dict = makeDict(PRIVATE_DICT_META, attrs, strings); - return t; - } - - function makeCFFTable(glyphs, options) { - var t = new table.Table('CFF ', [ - {name: 'header', type: 'RECORD'}, - {name: 'nameIndex', type: 'RECORD'}, - {name: 'topDictIndex', type: 'RECORD'}, - {name: 'stringIndex', type: 'RECORD'}, - {name: 'globalSubrIndex', type: 'RECORD'}, - {name: 'charsets', type: 'RECORD'}, - {name: 'charStringsIndex', type: 'RECORD'}, - {name: 'privateDict', type: 'RECORD'} - ]); - - var fontScale = 1 / options.unitsPerEm; - // We use non-zero values for the offsets so that the DICT encodes them. - // This is important because the size of the Top DICT plays a role in offset calculation, - // and the size shouldn't change after we've written correct offsets. - var attrs = { - version: options.version, - fullName: options.fullName, - familyName: options.familyName, - weight: options.weightName, - fontBBox: options.fontBBox || [0, 0, 0, 0], - fontMatrix: [fontScale, 0, 0, fontScale, 0, 0], - charset: 999, - encoding: 0, - charStrings: 999, - private: [0, 999] - }; - - var privateAttrs = {}; - - var glyphNames = []; - var glyph; - - // Skip first glyph (.notdef) - for (var i = 1; i < glyphs.length; i += 1) { - glyph = glyphs.get(i); - glyphNames.push(glyph.name); - } - - var strings = []; - - t.header = makeHeader(); - t.nameIndex = makeNameIndex([options.postScriptName]); - var topDict = makeTopDict(attrs, strings); - t.topDictIndex = makeTopDictIndex(topDict); - t.globalSubrIndex = makeGlobalSubrIndex(); - t.charsets = makeCharsets(glyphNames, strings); - t.charStringsIndex = makeCharStringsIndex(glyphs); - t.privateDict = makePrivateDict(privateAttrs, strings); - - // Needs to come at the end, to encode all custom strings used in the font. - t.stringIndex = makeStringIndex(strings); - - var startOffset = t.header.sizeOf() + - t.nameIndex.sizeOf() + - t.topDictIndex.sizeOf() + - t.stringIndex.sizeOf() + - t.globalSubrIndex.sizeOf(); - attrs.charset = startOffset; - - // We use the CFF standard encoding; proper encoding will be handled in cmap. - attrs.encoding = 0; - attrs.charStrings = attrs.charset + t.charsets.sizeOf(); - attrs.private[1] = attrs.charStrings + t.charStringsIndex.sizeOf(); - - // Recreate the Top DICT INDEX with the correct offsets. - topDict = makeTopDict(attrs, strings); - t.topDictIndex = makeTopDictIndex(topDict); - - return t; - } - - var cff = { parse: parseCFFTable, make: makeCFFTable }; - - // The `head` table contains global information about the font. - - // Parse the header `head` table - function parseHeadTable(data, start) { - var head = {}; - var p = new parse.Parser(data, start); - head.version = p.parseVersion(); - head.fontRevision = Math.round(p.parseFixed() * 1000) / 1000; - head.checkSumAdjustment = p.parseULong(); - head.magicNumber = p.parseULong(); - check.argument(head.magicNumber === 0x5F0F3CF5, 'Font header has wrong magic number.'); - head.flags = p.parseUShort(); - head.unitsPerEm = p.parseUShort(); - head.created = p.parseLongDateTime(); - head.modified = p.parseLongDateTime(); - head.xMin = p.parseShort(); - head.yMin = p.parseShort(); - head.xMax = p.parseShort(); - head.yMax = p.parseShort(); - head.macStyle = p.parseUShort(); - head.lowestRecPPEM = p.parseUShort(); - head.fontDirectionHint = p.parseShort(); - head.indexToLocFormat = p.parseShort(); - head.glyphDataFormat = p.parseShort(); - return head; - } - - function makeHeadTable(options) { - // Apple Mac timestamp epoch is 01/01/1904 not 01/01/1970 - var timestamp = Math.round(new Date().getTime() / 1000) + 2082844800; - var createdTimestamp = timestamp; - - if (options.createdTimestamp) { - createdTimestamp = options.createdTimestamp + 2082844800; - } - - return new table.Table('head', [ - {name: 'version', type: 'FIXED', value: 0x00010000}, - {name: 'fontRevision', type: 'FIXED', value: 0x00010000}, - {name: 'checkSumAdjustment', type: 'ULONG', value: 0}, - {name: 'magicNumber', type: 'ULONG', value: 0x5F0F3CF5}, - {name: 'flags', type: 'USHORT', value: 0}, - {name: 'unitsPerEm', type: 'USHORT', value: 1000}, - {name: 'created', type: 'LONGDATETIME', value: createdTimestamp}, - {name: 'modified', type: 'LONGDATETIME', value: timestamp}, - {name: 'xMin', type: 'SHORT', value: 0}, - {name: 'yMin', type: 'SHORT', value: 0}, - {name: 'xMax', type: 'SHORT', value: 0}, - {name: 'yMax', type: 'SHORT', value: 0}, - {name: 'macStyle', type: 'USHORT', value: 0}, - {name: 'lowestRecPPEM', type: 'USHORT', value: 0}, - {name: 'fontDirectionHint', type: 'SHORT', value: 2}, - {name: 'indexToLocFormat', type: 'SHORT', value: 0}, - {name: 'glyphDataFormat', type: 'SHORT', value: 0} - ], options); - } - - var head = { parse: parseHeadTable, make: makeHeadTable }; - - // The `hhea` table contains information for horizontal layout. - - // Parse the horizontal header `hhea` table - function parseHheaTable(data, start) { - var hhea = {}; - var p = new parse.Parser(data, start); - hhea.version = p.parseVersion(); - hhea.ascender = p.parseShort(); - hhea.descender = p.parseShort(); - hhea.lineGap = p.parseShort(); - hhea.advanceWidthMax = p.parseUShort(); - hhea.minLeftSideBearing = p.parseShort(); - hhea.minRightSideBearing = p.parseShort(); - hhea.xMaxExtent = p.parseShort(); - hhea.caretSlopeRise = p.parseShort(); - hhea.caretSlopeRun = p.parseShort(); - hhea.caretOffset = p.parseShort(); - p.relativeOffset += 8; - hhea.metricDataFormat = p.parseShort(); - hhea.numberOfHMetrics = p.parseUShort(); - return hhea; - } - - function makeHheaTable(options) { - return new table.Table('hhea', [ - {name: 'version', type: 'FIXED', value: 0x00010000}, - {name: 'ascender', type: 'FWORD', value: 0}, - {name: 'descender', type: 'FWORD', value: 0}, - {name: 'lineGap', type: 'FWORD', value: 0}, - {name: 'advanceWidthMax', type: 'UFWORD', value: 0}, - {name: 'minLeftSideBearing', type: 'FWORD', value: 0}, - {name: 'minRightSideBearing', type: 'FWORD', value: 0}, - {name: 'xMaxExtent', type: 'FWORD', value: 0}, - {name: 'caretSlopeRise', type: 'SHORT', value: 1}, - {name: 'caretSlopeRun', type: 'SHORT', value: 0}, - {name: 'caretOffset', type: 'SHORT', value: 0}, - {name: 'reserved1', type: 'SHORT', value: 0}, - {name: 'reserved2', type: 'SHORT', value: 0}, - {name: 'reserved3', type: 'SHORT', value: 0}, - {name: 'reserved4', type: 'SHORT', value: 0}, - {name: 'metricDataFormat', type: 'SHORT', value: 0}, - {name: 'numberOfHMetrics', type: 'USHORT', value: 0} - ], options); - } - - var hhea = { parse: parseHheaTable, make: makeHheaTable }; - - // The `hmtx` table contains the horizontal metrics for all glyphs. - - function parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs) { - var advanceWidth; - var leftSideBearing; - var p = new parse.Parser(data, start); - for (var i = 0; i < numGlyphs; i += 1) { - // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. - if (i < numMetrics) { - advanceWidth = p.parseUShort(); - leftSideBearing = p.parseShort(); - } - - var glyph = glyphs.get(i); - glyph.advanceWidth = advanceWidth; - glyph.leftSideBearing = leftSideBearing; - } - } - - function parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs) { - font._hmtxTableData = {}; - - var advanceWidth; - var leftSideBearing; - var p = new parse.Parser(data, start); - for (var i = 0; i < numGlyphs; i += 1) { - // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. - if (i < numMetrics) { - advanceWidth = p.parseUShort(); - leftSideBearing = p.parseShort(); - } - - font._hmtxTableData[i] = { - advanceWidth: advanceWidth, - leftSideBearing: leftSideBearing - }; - } - } - - // Parse the `hmtx` table, which contains the horizontal metrics for all glyphs. - // This function augments the glyph array, adding the advanceWidth and leftSideBearing to each glyph. - function parseHmtxTable(font, data, start, numMetrics, numGlyphs, glyphs, opt) { - if (opt.lowMemory) - { parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs); } - else - { parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs); } - } - - function makeHmtxTable(glyphs) { - var t = new table.Table('hmtx', []); - for (var i = 0; i < glyphs.length; i += 1) { - var glyph = glyphs.get(i); - var advanceWidth = glyph.advanceWidth || 0; - var leftSideBearing = glyph.leftSideBearing || 0; - t.fields.push({name: 'advanceWidth_' + i, type: 'USHORT', value: advanceWidth}); - t.fields.push({name: 'leftSideBearing_' + i, type: 'SHORT', value: leftSideBearing}); - } - - return t; - } - - var hmtx = { parse: parseHmtxTable, make: makeHmtxTable }; - - // The `ltag` table stores IETF BCP-47 language tags. It allows supporting - - function makeLtagTable(tags) { - var result = new table.Table('ltag', [ - {name: 'version', type: 'ULONG', value: 1}, - {name: 'flags', type: 'ULONG', value: 0}, - {name: 'numTags', type: 'ULONG', value: tags.length} - ]); - - var stringPool = ''; - var stringPoolOffset = 12 + tags.length * 4; - for (var i = 0; i < tags.length; ++i) { - var pos = stringPool.indexOf(tags[i]); - if (pos < 0) { - pos = stringPool.length; - stringPool += tags[i]; - } - - result.fields.push({name: 'offset ' + i, type: 'USHORT', value: stringPoolOffset + pos}); - result.fields.push({name: 'length ' + i, type: 'USHORT', value: tags[i].length}); - } - - result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); - return result; - } - - function parseLtagTable(data, start) { - var p = new parse.Parser(data, start); - var tableVersion = p.parseULong(); - check.argument(tableVersion === 1, 'Unsupported ltag table version.'); - // The 'ltag' specification does not define any flags; skip the field. - p.skip('uLong', 1); - var numTags = p.parseULong(); - - var tags = []; - for (var i = 0; i < numTags; i++) { - var tag = ''; - var offset = start + p.parseUShort(); - var length = p.parseUShort(); - for (var j = offset; j < offset + length; ++j) { - tag += String.fromCharCode(data.getInt8(j)); - } - - tags.push(tag); - } - - return tags; - } - - var ltag = { make: makeLtagTable, parse: parseLtagTable }; - - // The `maxp` table establishes the memory requirements for the font. - - // Parse the maximum profile `maxp` table. - function parseMaxpTable(data, start) { - var maxp = {}; - var p = new parse.Parser(data, start); - maxp.version = p.parseVersion(); - maxp.numGlyphs = p.parseUShort(); - if (maxp.version === 1.0) { - maxp.maxPoints = p.parseUShort(); - maxp.maxContours = p.parseUShort(); - maxp.maxCompositePoints = p.parseUShort(); - maxp.maxCompositeContours = p.parseUShort(); - maxp.maxZones = p.parseUShort(); - maxp.maxTwilightPoints = p.parseUShort(); - maxp.maxStorage = p.parseUShort(); - maxp.maxFunctionDefs = p.parseUShort(); - maxp.maxInstructionDefs = p.parseUShort(); - maxp.maxStackElements = p.parseUShort(); - maxp.maxSizeOfInstructions = p.parseUShort(); - maxp.maxComponentElements = p.parseUShort(); - maxp.maxComponentDepth = p.parseUShort(); - } - - return maxp; - } - - function makeMaxpTable(numGlyphs) { - return new table.Table('maxp', [ - {name: 'version', type: 'FIXED', value: 0x00005000}, - {name: 'numGlyphs', type: 'USHORT', value: numGlyphs} - ]); - } - - var maxp = { parse: parseMaxpTable, make: makeMaxpTable }; - - // The `name` naming table. - - // NameIDs for the name table. - var nameTableNames = [ - 'copyright', // 0 - 'fontFamily', // 1 - 'fontSubfamily', // 2 - 'uniqueID', // 3 - 'fullName', // 4 - 'version', // 5 - 'postScriptName', // 6 - 'trademark', // 7 - 'manufacturer', // 8 - 'designer', // 9 - 'description', // 10 - 'manufacturerURL', // 11 - 'designerURL', // 12 - 'license', // 13 - 'licenseURL', // 14 - 'reserved', // 15 - 'preferredFamily', // 16 - 'preferredSubfamily', // 17 - 'compatibleFullName', // 18 - 'sampleText', // 19 - 'postScriptFindFontName', // 20 - 'wwsFamily', // 21 - 'wwsSubfamily' // 22 - ]; - - var macLanguages = { - 0: 'en', - 1: 'fr', - 2: 'de', - 3: 'it', - 4: 'nl', - 5: 'sv', - 6: 'es', - 7: 'da', - 8: 'pt', - 9: 'no', - 10: 'he', - 11: 'ja', - 12: 'ar', - 13: 'fi', - 14: 'el', - 15: 'is', - 16: 'mt', - 17: 'tr', - 18: 'hr', - 19: 'zh-Hant', - 20: 'ur', - 21: 'hi', - 22: 'th', - 23: 'ko', - 24: 'lt', - 25: 'pl', - 26: 'hu', - 27: 'es', - 28: 'lv', - 29: 'se', - 30: 'fo', - 31: 'fa', - 32: 'ru', - 33: 'zh', - 34: 'nl-BE', - 35: 'ga', - 36: 'sq', - 37: 'ro', - 38: 'cz', - 39: 'sk', - 40: 'si', - 41: 'yi', - 42: 'sr', - 43: 'mk', - 44: 'bg', - 45: 'uk', - 46: 'be', - 47: 'uz', - 48: 'kk', - 49: 'az-Cyrl', - 50: 'az-Arab', - 51: 'hy', - 52: 'ka', - 53: 'mo', - 54: 'ky', - 55: 'tg', - 56: 'tk', - 57: 'mn-CN', - 58: 'mn', - 59: 'ps', - 60: 'ks', - 61: 'ku', - 62: 'sd', - 63: 'bo', - 64: 'ne', - 65: 'sa', - 66: 'mr', - 67: 'bn', - 68: 'as', - 69: 'gu', - 70: 'pa', - 71: 'or', - 72: 'ml', - 73: 'kn', - 74: 'ta', - 75: 'te', - 76: 'si', - 77: 'my', - 78: 'km', - 79: 'lo', - 80: 'vi', - 81: 'id', - 82: 'tl', - 83: 'ms', - 84: 'ms-Arab', - 85: 'am', - 86: 'ti', - 87: 'om', - 88: 'so', - 89: 'sw', - 90: 'rw', - 91: 'rn', - 92: 'ny', - 93: 'mg', - 94: 'eo', - 128: 'cy', - 129: 'eu', - 130: 'ca', - 131: 'la', - 132: 'qu', - 133: 'gn', - 134: 'ay', - 135: 'tt', - 136: 'ug', - 137: 'dz', - 138: 'jv', - 139: 'su', - 140: 'gl', - 141: 'af', - 142: 'br', - 143: 'iu', - 144: 'gd', - 145: 'gv', - 146: 'ga', - 147: 'to', - 148: 'el-polyton', - 149: 'kl', - 150: 'az', - 151: 'nn' - }; - - // MacOS language ID → MacOS script ID - // - // Note that the script ID is not sufficient to determine what encoding - // to use in TrueType files. For some languages, MacOS used a modification - // of a mainstream script. For example, an Icelandic name would be stored - // with smRoman in the TrueType naming table, but the actual encoding - // is a special Icelandic version of the normal Macintosh Roman encoding. - // As another example, Inuktitut uses an 8-bit encoding for Canadian Aboriginal - // Syllables but MacOS had run out of available script codes, so this was - // done as a (pretty radical) "modification" of Ethiopic. - // - // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt - var macLanguageToScript = { - 0: 0, // langEnglish → smRoman - 1: 0, // langFrench → smRoman - 2: 0, // langGerman → smRoman - 3: 0, // langItalian → smRoman - 4: 0, // langDutch → smRoman - 5: 0, // langSwedish → smRoman - 6: 0, // langSpanish → smRoman - 7: 0, // langDanish → smRoman - 8: 0, // langPortuguese → smRoman - 9: 0, // langNorwegian → smRoman - 10: 5, // langHebrew → smHebrew - 11: 1, // langJapanese → smJapanese - 12: 4, // langArabic → smArabic - 13: 0, // langFinnish → smRoman - 14: 6, // langGreek → smGreek - 15: 0, // langIcelandic → smRoman (modified) - 16: 0, // langMaltese → smRoman - 17: 0, // langTurkish → smRoman (modified) - 18: 0, // langCroatian → smRoman (modified) - 19: 2, // langTradChinese → smTradChinese - 20: 4, // langUrdu → smArabic - 21: 9, // langHindi → smDevanagari - 22: 21, // langThai → smThai - 23: 3, // langKorean → smKorean - 24: 29, // langLithuanian → smCentralEuroRoman - 25: 29, // langPolish → smCentralEuroRoman - 26: 29, // langHungarian → smCentralEuroRoman - 27: 29, // langEstonian → smCentralEuroRoman - 28: 29, // langLatvian → smCentralEuroRoman - 29: 0, // langSami → smRoman - 30: 0, // langFaroese → smRoman (modified) - 31: 4, // langFarsi → smArabic (modified) - 32: 7, // langRussian → smCyrillic - 33: 25, // langSimpChinese → smSimpChinese - 34: 0, // langFlemish → smRoman - 35: 0, // langIrishGaelic → smRoman (modified) - 36: 0, // langAlbanian → smRoman - 37: 0, // langRomanian → smRoman (modified) - 38: 29, // langCzech → smCentralEuroRoman - 39: 29, // langSlovak → smCentralEuroRoman - 40: 0, // langSlovenian → smRoman (modified) - 41: 5, // langYiddish → smHebrew - 42: 7, // langSerbian → smCyrillic - 43: 7, // langMacedonian → smCyrillic - 44: 7, // langBulgarian → smCyrillic - 45: 7, // langUkrainian → smCyrillic (modified) - 46: 7, // langByelorussian → smCyrillic - 47: 7, // langUzbek → smCyrillic - 48: 7, // langKazakh → smCyrillic - 49: 7, // langAzerbaijani → smCyrillic - 50: 4, // langAzerbaijanAr → smArabic - 51: 24, // langArmenian → smArmenian - 52: 23, // langGeorgian → smGeorgian - 53: 7, // langMoldavian → smCyrillic - 54: 7, // langKirghiz → smCyrillic - 55: 7, // langTajiki → smCyrillic - 56: 7, // langTurkmen → smCyrillic - 57: 27, // langMongolian → smMongolian - 58: 7, // langMongolianCyr → smCyrillic - 59: 4, // langPashto → smArabic - 60: 4, // langKurdish → smArabic - 61: 4, // langKashmiri → smArabic - 62: 4, // langSindhi → smArabic - 63: 26, // langTibetan → smTibetan - 64: 9, // langNepali → smDevanagari - 65: 9, // langSanskrit → smDevanagari - 66: 9, // langMarathi → smDevanagari - 67: 13, // langBengali → smBengali - 68: 13, // langAssamese → smBengali - 69: 11, // langGujarati → smGujarati - 70: 10, // langPunjabi → smGurmukhi - 71: 12, // langOriya → smOriya - 72: 17, // langMalayalam → smMalayalam - 73: 16, // langKannada → smKannada - 74: 14, // langTamil → smTamil - 75: 15, // langTelugu → smTelugu - 76: 18, // langSinhalese → smSinhalese - 77: 19, // langBurmese → smBurmese - 78: 20, // langKhmer → smKhmer - 79: 22, // langLao → smLao - 80: 30, // langVietnamese → smVietnamese - 81: 0, // langIndonesian → smRoman - 82: 0, // langTagalog → smRoman - 83: 0, // langMalayRoman → smRoman - 84: 4, // langMalayArabic → smArabic - 85: 28, // langAmharic → smEthiopic - 86: 28, // langTigrinya → smEthiopic - 87: 28, // langOromo → smEthiopic - 88: 0, // langSomali → smRoman - 89: 0, // langSwahili → smRoman - 90: 0, // langKinyarwanda → smRoman - 91: 0, // langRundi → smRoman - 92: 0, // langNyanja → smRoman - 93: 0, // langMalagasy → smRoman - 94: 0, // langEsperanto → smRoman - 128: 0, // langWelsh → smRoman (modified) - 129: 0, // langBasque → smRoman - 130: 0, // langCatalan → smRoman - 131: 0, // langLatin → smRoman - 132: 0, // langQuechua → smRoman - 133: 0, // langGuarani → smRoman - 134: 0, // langAymara → smRoman - 135: 7, // langTatar → smCyrillic - 136: 4, // langUighur → smArabic - 137: 26, // langDzongkha → smTibetan - 138: 0, // langJavaneseRom → smRoman - 139: 0, // langSundaneseRom → smRoman - 140: 0, // langGalician → smRoman - 141: 0, // langAfrikaans → smRoman - 142: 0, // langBreton → smRoman (modified) - 143: 28, // langInuktitut → smEthiopic (modified) - 144: 0, // langScottishGaelic → smRoman (modified) - 145: 0, // langManxGaelic → smRoman (modified) - 146: 0, // langIrishGaelicScript → smRoman (modified) - 147: 0, // langTongan → smRoman - 148: 6, // langGreekAncient → smRoman - 149: 0, // langGreenlandic → smRoman - 150: 0, // langAzerbaijanRoman → smRoman - 151: 0 // langNynorsk → smRoman - }; - - // While Microsoft indicates a region/country for all its language - // IDs, we omit the region code if it's equal to the "most likely - // region subtag" according to Unicode CLDR. For scripts, we omit - // the subtag if it is equal to the Suppress-Script entry in the - // IANA language subtag registry for IETF BCP 47. - // - // For example, Microsoft states that its language code 0x041A is - // Croatian in Croatia. We transform this to the BCP 47 language code 'hr' - // and not 'hr-HR' because Croatia is the default country for Croatian, - // according to Unicode CLDR. As another example, Microsoft states - // that 0x101A is Croatian (Latin) in Bosnia-Herzegovina. We transform - // this to 'hr-BA' and not 'hr-Latn-BA' because Latin is the default script - // for the Croatian language, according to IANA. - // - // http://www.unicode.org/cldr/charts/latest/supplemental/likely_subtags.html - // http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry - var windowsLanguages = { - 0x0436: 'af', - 0x041C: 'sq', - 0x0484: 'gsw', - 0x045E: 'am', - 0x1401: 'ar-DZ', - 0x3C01: 'ar-BH', - 0x0C01: 'ar', - 0x0801: 'ar-IQ', - 0x2C01: 'ar-JO', - 0x3401: 'ar-KW', - 0x3001: 'ar-LB', - 0x1001: 'ar-LY', - 0x1801: 'ary', - 0x2001: 'ar-OM', - 0x4001: 'ar-QA', - 0x0401: 'ar-SA', - 0x2801: 'ar-SY', - 0x1C01: 'aeb', - 0x3801: 'ar-AE', - 0x2401: 'ar-YE', - 0x042B: 'hy', - 0x044D: 'as', - 0x082C: 'az-Cyrl', - 0x042C: 'az', - 0x046D: 'ba', - 0x042D: 'eu', - 0x0423: 'be', - 0x0845: 'bn', - 0x0445: 'bn-IN', - 0x201A: 'bs-Cyrl', - 0x141A: 'bs', - 0x047E: 'br', - 0x0402: 'bg', - 0x0403: 'ca', - 0x0C04: 'zh-HK', - 0x1404: 'zh-MO', - 0x0804: 'zh', - 0x1004: 'zh-SG', - 0x0404: 'zh-TW', - 0x0483: 'co', - 0x041A: 'hr', - 0x101A: 'hr-BA', - 0x0405: 'cs', - 0x0406: 'da', - 0x048C: 'prs', - 0x0465: 'dv', - 0x0813: 'nl-BE', - 0x0413: 'nl', - 0x0C09: 'en-AU', - 0x2809: 'en-BZ', - 0x1009: 'en-CA', - 0x2409: 'en-029', - 0x4009: 'en-IN', - 0x1809: 'en-IE', - 0x2009: 'en-JM', - 0x4409: 'en-MY', - 0x1409: 'en-NZ', - 0x3409: 'en-PH', - 0x4809: 'en-SG', - 0x1C09: 'en-ZA', - 0x2C09: 'en-TT', - 0x0809: 'en-GB', - 0x0409: 'en', - 0x3009: 'en-ZW', - 0x0425: 'et', - 0x0438: 'fo', - 0x0464: 'fil', - 0x040B: 'fi', - 0x080C: 'fr-BE', - 0x0C0C: 'fr-CA', - 0x040C: 'fr', - 0x140C: 'fr-LU', - 0x180C: 'fr-MC', - 0x100C: 'fr-CH', - 0x0462: 'fy', - 0x0456: 'gl', - 0x0437: 'ka', - 0x0C07: 'de-AT', - 0x0407: 'de', - 0x1407: 'de-LI', - 0x1007: 'de-LU', - 0x0807: 'de-CH', - 0x0408: 'el', - 0x046F: 'kl', - 0x0447: 'gu', - 0x0468: 'ha', - 0x040D: 'he', - 0x0439: 'hi', - 0x040E: 'hu', - 0x040F: 'is', - 0x0470: 'ig', - 0x0421: 'id', - 0x045D: 'iu', - 0x085D: 'iu-Latn', - 0x083C: 'ga', - 0x0434: 'xh', - 0x0435: 'zu', - 0x0410: 'it', - 0x0810: 'it-CH', - 0x0411: 'ja', - 0x044B: 'kn', - 0x043F: 'kk', - 0x0453: 'km', - 0x0486: 'quc', - 0x0487: 'rw', - 0x0441: 'sw', - 0x0457: 'kok', - 0x0412: 'ko', - 0x0440: 'ky', - 0x0454: 'lo', - 0x0426: 'lv', - 0x0427: 'lt', - 0x082E: 'dsb', - 0x046E: 'lb', - 0x042F: 'mk', - 0x083E: 'ms-BN', - 0x043E: 'ms', - 0x044C: 'ml', - 0x043A: 'mt', - 0x0481: 'mi', - 0x047A: 'arn', - 0x044E: 'mr', - 0x047C: 'moh', - 0x0450: 'mn', - 0x0850: 'mn-CN', - 0x0461: 'ne', - 0x0414: 'nb', - 0x0814: 'nn', - 0x0482: 'oc', - 0x0448: 'or', - 0x0463: 'ps', - 0x0415: 'pl', - 0x0416: 'pt', - 0x0816: 'pt-PT', - 0x0446: 'pa', - 0x046B: 'qu-BO', - 0x086B: 'qu-EC', - 0x0C6B: 'qu', - 0x0418: 'ro', - 0x0417: 'rm', - 0x0419: 'ru', - 0x243B: 'smn', - 0x103B: 'smj-NO', - 0x143B: 'smj', - 0x0C3B: 'se-FI', - 0x043B: 'se', - 0x083B: 'se-SE', - 0x203B: 'sms', - 0x183B: 'sma-NO', - 0x1C3B: 'sms', - 0x044F: 'sa', - 0x1C1A: 'sr-Cyrl-BA', - 0x0C1A: 'sr', - 0x181A: 'sr-Latn-BA', - 0x081A: 'sr-Latn', - 0x046C: 'nso', - 0x0432: 'tn', - 0x045B: 'si', - 0x041B: 'sk', - 0x0424: 'sl', - 0x2C0A: 'es-AR', - 0x400A: 'es-BO', - 0x340A: 'es-CL', - 0x240A: 'es-CO', - 0x140A: 'es-CR', - 0x1C0A: 'es-DO', - 0x300A: 'es-EC', - 0x440A: 'es-SV', - 0x100A: 'es-GT', - 0x480A: 'es-HN', - 0x080A: 'es-MX', - 0x4C0A: 'es-NI', - 0x180A: 'es-PA', - 0x3C0A: 'es-PY', - 0x280A: 'es-PE', - 0x500A: 'es-PR', - - // Microsoft has defined two different language codes for - // “Spanish with modern sorting” and “Spanish with traditional - // sorting”. This makes sense for collation APIs, and it would be - // possible to express this in BCP 47 language tags via Unicode - // extensions (eg., es-u-co-trad is Spanish with traditional - // sorting). However, for storing names in fonts, the distinction - // does not make sense, so we give “es” in both cases. - 0x0C0A: 'es', - 0x040A: 'es', - - 0x540A: 'es-US', - 0x380A: 'es-UY', - 0x200A: 'es-VE', - 0x081D: 'sv-FI', - 0x041D: 'sv', - 0x045A: 'syr', - 0x0428: 'tg', - 0x085F: 'tzm', - 0x0449: 'ta', - 0x0444: 'tt', - 0x044A: 'te', - 0x041E: 'th', - 0x0451: 'bo', - 0x041F: 'tr', - 0x0442: 'tk', - 0x0480: 'ug', - 0x0422: 'uk', - 0x042E: 'hsb', - 0x0420: 'ur', - 0x0843: 'uz-Cyrl', - 0x0443: 'uz', - 0x042A: 'vi', - 0x0452: 'cy', - 0x0488: 'wo', - 0x0485: 'sah', - 0x0478: 'ii', - 0x046A: 'yo' - }; - - // Returns a IETF BCP 47 language code, for example 'zh-Hant' - // for 'Chinese in the traditional script'. - function getLanguageCode(platformID, languageID, ltag) { - switch (platformID) { - case 0: // Unicode - if (languageID === 0xFFFF) { - return 'und'; - } else if (ltag) { - return ltag[languageID]; - } - - break; - - case 1: // Macintosh - return macLanguages[languageID]; - - case 3: // Windows - return windowsLanguages[languageID]; - } - - return undefined; - } - - var utf16 = 'utf-16'; - - // MacOS script ID → encoding. This table stores the default case, - // which can be overridden by macLanguageEncodings. - var macScriptEncodings = { - 0: 'macintosh', // smRoman - 1: 'x-mac-japanese', // smJapanese - 2: 'x-mac-chinesetrad', // smTradChinese - 3: 'x-mac-korean', // smKorean - 6: 'x-mac-greek', // smGreek - 7: 'x-mac-cyrillic', // smCyrillic - 9: 'x-mac-devanagai', // smDevanagari - 10: 'x-mac-gurmukhi', // smGurmukhi - 11: 'x-mac-gujarati', // smGujarati - 12: 'x-mac-oriya', // smOriya - 13: 'x-mac-bengali', // smBengali - 14: 'x-mac-tamil', // smTamil - 15: 'x-mac-telugu', // smTelugu - 16: 'x-mac-kannada', // smKannada - 17: 'x-mac-malayalam', // smMalayalam - 18: 'x-mac-sinhalese', // smSinhalese - 19: 'x-mac-burmese', // smBurmese - 20: 'x-mac-khmer', // smKhmer - 21: 'x-mac-thai', // smThai - 22: 'x-mac-lao', // smLao - 23: 'x-mac-georgian', // smGeorgian - 24: 'x-mac-armenian', // smArmenian - 25: 'x-mac-chinesesimp', // smSimpChinese - 26: 'x-mac-tibetan', // smTibetan - 27: 'x-mac-mongolian', // smMongolian - 28: 'x-mac-ethiopic', // smEthiopic - 29: 'x-mac-ce', // smCentralEuroRoman - 30: 'x-mac-vietnamese', // smVietnamese - 31: 'x-mac-extarabic' // smExtArabic - }; - - // MacOS language ID → encoding. This table stores the exceptional - // cases, which override macScriptEncodings. For writing MacOS naming - // tables, we need to emit a MacOS script ID. Therefore, we cannot - // merge macScriptEncodings into macLanguageEncodings. - // - // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt - var macLanguageEncodings = { - 15: 'x-mac-icelandic', // langIcelandic - 17: 'x-mac-turkish', // langTurkish - 18: 'x-mac-croatian', // langCroatian - 24: 'x-mac-ce', // langLithuanian - 25: 'x-mac-ce', // langPolish - 26: 'x-mac-ce', // langHungarian - 27: 'x-mac-ce', // langEstonian - 28: 'x-mac-ce', // langLatvian - 30: 'x-mac-icelandic', // langFaroese - 37: 'x-mac-romanian', // langRomanian - 38: 'x-mac-ce', // langCzech - 39: 'x-mac-ce', // langSlovak - 40: 'x-mac-ce', // langSlovenian - 143: 'x-mac-inuit', // langInuktitut - 146: 'x-mac-gaelic' // langIrishGaelicScript - }; - - function getEncoding(platformID, encodingID, languageID) { - switch (platformID) { - case 0: // Unicode - return utf16; - - case 1: // Apple Macintosh - return macLanguageEncodings[languageID] || macScriptEncodings[encodingID]; - - case 3: // Microsoft Windows - if (encodingID === 1 || encodingID === 10) { - return utf16; - } - - break; - } - - return undefined; - } - - // Parse the naming `name` table. - // FIXME: Format 1 additional fields are not supported yet. - // ltag is the content of the `ltag' table, such as ['en', 'zh-Hans', 'de-CH-1904']. - function parseNameTable(data, start, ltag) { - var name = {}; - var p = new parse.Parser(data, start); - var format = p.parseUShort(); - var count = p.parseUShort(); - var stringOffset = p.offset + p.parseUShort(); - for (var i = 0; i < count; i++) { - var platformID = p.parseUShort(); - var encodingID = p.parseUShort(); - var languageID = p.parseUShort(); - var nameID = p.parseUShort(); - var property = nameTableNames[nameID] || nameID; - var byteLength = p.parseUShort(); - var offset = p.parseUShort(); - var language = getLanguageCode(platformID, languageID, ltag); - var encoding = getEncoding(platformID, encodingID, languageID); - if (encoding !== undefined && language !== undefined) { - var text = (void 0); - if (encoding === utf16) { - text = decode.UTF16(data, stringOffset + offset, byteLength); - } else { - text = decode.MACSTRING(data, stringOffset + offset, byteLength, encoding); - } - - if (text) { - var translations = name[property]; - if (translations === undefined) { - translations = name[property] = {}; - } - - translations[language] = text; - } - } - } - - var langTagCount = 0; - if (format === 1) { - // FIXME: Also handle Microsoft's 'name' table 1. - langTagCount = p.parseUShort(); - } - - return name; - } - - // {23: 'foo'} → {'foo': 23} - // ['bar', 'baz'] → {'bar': 0, 'baz': 1} - function reverseDict(dict) { - var result = {}; - for (var key in dict) { - result[dict[key]] = parseInt(key); - } - - return result; - } - - function makeNameRecord(platformID, encodingID, languageID, nameID, length, offset) { - return new table.Record('NameRecord', [ - {name: 'platformID', type: 'USHORT', value: platformID}, - {name: 'encodingID', type: 'USHORT', value: encodingID}, - {name: 'languageID', type: 'USHORT', value: languageID}, - {name: 'nameID', type: 'USHORT', value: nameID}, - {name: 'length', type: 'USHORT', value: length}, - {name: 'offset', type: 'USHORT', value: offset} - ]); - } - - // Finds the position of needle in haystack, or -1 if not there. - // Like String.indexOf(), but for arrays. - function findSubArray(needle, haystack) { - var needleLength = needle.length; - var limit = haystack.length - needleLength + 1; - - loop: - for (var pos = 0; pos < limit; pos++) { - for (; pos < limit; pos++) { - for (var k = 0; k < needleLength; k++) { - if (haystack[pos + k] !== needle[k]) { - continue loop; - } - } - - return pos; - } - } - - return -1; - } - - function addStringToPool(s, pool) { - var offset = findSubArray(s, pool); - if (offset < 0) { - offset = pool.length; - var i = 0; - var len = s.length; - for (; i < len; ++i) { - pool.push(s[i]); - } - - } - - return offset; - } - - function makeNameTable(names, ltag) { - var nameID; - var nameIDs = []; - - var namesWithNumericKeys = {}; - var nameTableIds = reverseDict(nameTableNames); - for (var key in names) { - var id = nameTableIds[key]; - if (id === undefined) { - id = key; - } - - nameID = parseInt(id); - - if (isNaN(nameID)) { - throw new Error('Name table entry "' + key + '" does not exist, see nameTableNames for complete list.'); - } - - namesWithNumericKeys[nameID] = names[key]; - nameIDs.push(nameID); - } - - var macLanguageIds = reverseDict(macLanguages); - var windowsLanguageIds = reverseDict(windowsLanguages); - - var nameRecords = []; - var stringPool = []; - - for (var i = 0; i < nameIDs.length; i++) { - nameID = nameIDs[i]; - var translations = namesWithNumericKeys[nameID]; - for (var lang in translations) { - var text = translations[lang]; - - // For MacOS, we try to emit the name in the form that was introduced - // in the initial version of the TrueType spec (in the late 1980s). - // However, this can fail for various reasons: the requested BCP 47 - // language code might not have an old-style Mac equivalent; - // we might not have a codec for the needed character encoding; - // or the name might contain characters that cannot be expressed - // in the old-style Macintosh encoding. In case of failure, we emit - // the name in a more modern fashion (Unicode encoding with BCP 47 - // language tags) that is recognized by MacOS 10.5, released in 2009. - // If fonts were only read by operating systems, we could simply - // emit all names in the modern form; this would be much easier. - // However, there are many applications and libraries that read - // 'name' tables directly, and these will usually only recognize - // the ancient form (silently skipping the unrecognized names). - var macPlatform = 1; // Macintosh - var macLanguage = macLanguageIds[lang]; - var macScript = macLanguageToScript[macLanguage]; - var macEncoding = getEncoding(macPlatform, macScript, macLanguage); - var macName = encode.MACSTRING(text, macEncoding); - if (macName === undefined) { - macPlatform = 0; // Unicode - macLanguage = ltag.indexOf(lang); - if (macLanguage < 0) { - macLanguage = ltag.length; - ltag.push(lang); - } - - macScript = 4; // Unicode 2.0 and later - macName = encode.UTF16(text); - } - - var macNameOffset = addStringToPool(macName, stringPool); - nameRecords.push(makeNameRecord(macPlatform, macScript, macLanguage, - nameID, macName.length, macNameOffset)); - - var winLanguage = windowsLanguageIds[lang]; - if (winLanguage !== undefined) { - var winName = encode.UTF16(text); - var winNameOffset = addStringToPool(winName, stringPool); - nameRecords.push(makeNameRecord(3, 1, winLanguage, - nameID, winName.length, winNameOffset)); - } - } - } - - nameRecords.sort(function(a, b) { - return ((a.platformID - b.platformID) || - (a.encodingID - b.encodingID) || - (a.languageID - b.languageID) || - (a.nameID - b.nameID)); - }); - - var t = new table.Table('name', [ - {name: 'format', type: 'USHORT', value: 0}, - {name: 'count', type: 'USHORT', value: nameRecords.length}, - {name: 'stringOffset', type: 'USHORT', value: 6 + nameRecords.length * 12} - ]); - - for (var r = 0; r < nameRecords.length; r++) { - t.fields.push({name: 'record_' + r, type: 'RECORD', value: nameRecords[r]}); - } - - t.fields.push({name: 'strings', type: 'LITERAL', value: stringPool}); - return t; - } - - var _name = { parse: parseNameTable, make: makeNameTable }; - - // The `OS/2` table contains metrics required in OpenType fonts. - - var unicodeRanges = [ - {begin: 0x0000, end: 0x007F}, // Basic Latin - {begin: 0x0080, end: 0x00FF}, // Latin-1 Supplement - {begin: 0x0100, end: 0x017F}, // Latin Extended-A - {begin: 0x0180, end: 0x024F}, // Latin Extended-B - {begin: 0x0250, end: 0x02AF}, // IPA Extensions - {begin: 0x02B0, end: 0x02FF}, // Spacing Modifier Letters - {begin: 0x0300, end: 0x036F}, // Combining Diacritical Marks - {begin: 0x0370, end: 0x03FF}, // Greek and Coptic - {begin: 0x2C80, end: 0x2CFF}, // Coptic - {begin: 0x0400, end: 0x04FF}, // Cyrillic - {begin: 0x0530, end: 0x058F}, // Armenian - {begin: 0x0590, end: 0x05FF}, // Hebrew - {begin: 0xA500, end: 0xA63F}, // Vai - {begin: 0x0600, end: 0x06FF}, // Arabic - {begin: 0x07C0, end: 0x07FF}, // NKo - {begin: 0x0900, end: 0x097F}, // Devanagari - {begin: 0x0980, end: 0x09FF}, // Bengali - {begin: 0x0A00, end: 0x0A7F}, // Gurmukhi - {begin: 0x0A80, end: 0x0AFF}, // Gujarati - {begin: 0x0B00, end: 0x0B7F}, // Oriya - {begin: 0x0B80, end: 0x0BFF}, // Tamil - {begin: 0x0C00, end: 0x0C7F}, // Telugu - {begin: 0x0C80, end: 0x0CFF}, // Kannada - {begin: 0x0D00, end: 0x0D7F}, // Malayalam - {begin: 0x0E00, end: 0x0E7F}, // Thai - {begin: 0x0E80, end: 0x0EFF}, // Lao - {begin: 0x10A0, end: 0x10FF}, // Georgian - {begin: 0x1B00, end: 0x1B7F}, // Balinese - {begin: 0x1100, end: 0x11FF}, // Hangul Jamo - {begin: 0x1E00, end: 0x1EFF}, // Latin Extended Additional - {begin: 0x1F00, end: 0x1FFF}, // Greek Extended - {begin: 0x2000, end: 0x206F}, // General Punctuation - {begin: 0x2070, end: 0x209F}, // Superscripts And Subscripts - {begin: 0x20A0, end: 0x20CF}, // Currency Symbol - {begin: 0x20D0, end: 0x20FF}, // Combining Diacritical Marks For Symbols - {begin: 0x2100, end: 0x214F}, // Letterlike Symbols - {begin: 0x2150, end: 0x218F}, // Number Forms - {begin: 0x2190, end: 0x21FF}, // Arrows - {begin: 0x2200, end: 0x22FF}, // Mathematical Operators - {begin: 0x2300, end: 0x23FF}, // Miscellaneous Technical - {begin: 0x2400, end: 0x243F}, // Control Pictures - {begin: 0x2440, end: 0x245F}, // Optical Character Recognition - {begin: 0x2460, end: 0x24FF}, // Enclosed Alphanumerics - {begin: 0x2500, end: 0x257F}, // Box Drawing - {begin: 0x2580, end: 0x259F}, // Block Elements - {begin: 0x25A0, end: 0x25FF}, // Geometric Shapes - {begin: 0x2600, end: 0x26FF}, // Miscellaneous Symbols - {begin: 0x2700, end: 0x27BF}, // Dingbats - {begin: 0x3000, end: 0x303F}, // CJK Symbols And Punctuation - {begin: 0x3040, end: 0x309F}, // Hiragana - {begin: 0x30A0, end: 0x30FF}, // Katakana - {begin: 0x3100, end: 0x312F}, // Bopomofo - {begin: 0x3130, end: 0x318F}, // Hangul Compatibility Jamo - {begin: 0xA840, end: 0xA87F}, // Phags-pa - {begin: 0x3200, end: 0x32FF}, // Enclosed CJK Letters And Months - {begin: 0x3300, end: 0x33FF}, // CJK Compatibility - {begin: 0xAC00, end: 0xD7AF}, // Hangul Syllables - {begin: 0xD800, end: 0xDFFF}, // Non-Plane 0 * - {begin: 0x10900, end: 0x1091F}, // Phoenicia - {begin: 0x4E00, end: 0x9FFF}, // CJK Unified Ideographs - {begin: 0xE000, end: 0xF8FF}, // Private Use Area (plane 0) - {begin: 0x31C0, end: 0x31EF}, // CJK Strokes - {begin: 0xFB00, end: 0xFB4F}, // Alphabetic Presentation Forms - {begin: 0xFB50, end: 0xFDFF}, // Arabic Presentation Forms-A - {begin: 0xFE20, end: 0xFE2F}, // Combining Half Marks - {begin: 0xFE10, end: 0xFE1F}, // Vertical Forms - {begin: 0xFE50, end: 0xFE6F}, // Small Form Variants - {begin: 0xFE70, end: 0xFEFF}, // Arabic Presentation Forms-B - {begin: 0xFF00, end: 0xFFEF}, // Halfwidth And Fullwidth Forms - {begin: 0xFFF0, end: 0xFFFF}, // Specials - {begin: 0x0F00, end: 0x0FFF}, // Tibetan - {begin: 0x0700, end: 0x074F}, // Syriac - {begin: 0x0780, end: 0x07BF}, // Thaana - {begin: 0x0D80, end: 0x0DFF}, // Sinhala - {begin: 0x1000, end: 0x109F}, // Myanmar - {begin: 0x1200, end: 0x137F}, // Ethiopic - {begin: 0x13A0, end: 0x13FF}, // Cherokee - {begin: 0x1400, end: 0x167F}, // Unified Canadian Aboriginal Syllabics - {begin: 0x1680, end: 0x169F}, // Ogham - {begin: 0x16A0, end: 0x16FF}, // Runic - {begin: 0x1780, end: 0x17FF}, // Khmer - {begin: 0x1800, end: 0x18AF}, // Mongolian - {begin: 0x2800, end: 0x28FF}, // Braille Patterns - {begin: 0xA000, end: 0xA48F}, // Yi Syllables - {begin: 0x1700, end: 0x171F}, // Tagalog - {begin: 0x10300, end: 0x1032F}, // Old Italic - {begin: 0x10330, end: 0x1034F}, // Gothic - {begin: 0x10400, end: 0x1044F}, // Deseret - {begin: 0x1D000, end: 0x1D0FF}, // Byzantine Musical Symbols - {begin: 0x1D400, end: 0x1D7FF}, // Mathematical Alphanumeric Symbols - {begin: 0xFF000, end: 0xFFFFD}, // Private Use (plane 15) - {begin: 0xFE00, end: 0xFE0F}, // Variation Selectors - {begin: 0xE0000, end: 0xE007F}, // Tags - {begin: 0x1900, end: 0x194F}, // Limbu - {begin: 0x1950, end: 0x197F}, // Tai Le - {begin: 0x1980, end: 0x19DF}, // New Tai Lue - {begin: 0x1A00, end: 0x1A1F}, // Buginese - {begin: 0x2C00, end: 0x2C5F}, // Glagolitic - {begin: 0x2D30, end: 0x2D7F}, // Tifinagh - {begin: 0x4DC0, end: 0x4DFF}, // Yijing Hexagram Symbols - {begin: 0xA800, end: 0xA82F}, // Syloti Nagri - {begin: 0x10000, end: 0x1007F}, // Linear B Syllabary - {begin: 0x10140, end: 0x1018F}, // Ancient Greek Numbers - {begin: 0x10380, end: 0x1039F}, // Ugaritic - {begin: 0x103A0, end: 0x103DF}, // Old Persian - {begin: 0x10450, end: 0x1047F}, // Shavian - {begin: 0x10480, end: 0x104AF}, // Osmanya - {begin: 0x10800, end: 0x1083F}, // Cypriot Syllabary - {begin: 0x10A00, end: 0x10A5F}, // Kharoshthi - {begin: 0x1D300, end: 0x1D35F}, // Tai Xuan Jing Symbols - {begin: 0x12000, end: 0x123FF}, // Cuneiform - {begin: 0x1D360, end: 0x1D37F}, // Counting Rod Numerals - {begin: 0x1B80, end: 0x1BBF}, // Sundanese - {begin: 0x1C00, end: 0x1C4F}, // Lepcha - {begin: 0x1C50, end: 0x1C7F}, // Ol Chiki - {begin: 0xA880, end: 0xA8DF}, // Saurashtra - {begin: 0xA900, end: 0xA92F}, // Kayah Li - {begin: 0xA930, end: 0xA95F}, // Rejang - {begin: 0xAA00, end: 0xAA5F}, // Cham - {begin: 0x10190, end: 0x101CF}, // Ancient Symbols - {begin: 0x101D0, end: 0x101FF}, // Phaistos Disc - {begin: 0x102A0, end: 0x102DF}, // Carian - {begin: 0x1F030, end: 0x1F09F} // Domino Tiles - ]; - - function getUnicodeRange(unicode) { - for (var i = 0; i < unicodeRanges.length; i += 1) { - var range = unicodeRanges[i]; - if (unicode >= range.begin && unicode < range.end) { - return i; - } - } - - return -1; - } - - // Parse the OS/2 and Windows metrics `OS/2` table - function parseOS2Table(data, start) { - var os2 = {}; - var p = new parse.Parser(data, start); - os2.version = p.parseUShort(); - os2.xAvgCharWidth = p.parseShort(); - os2.usWeightClass = p.parseUShort(); - os2.usWidthClass = p.parseUShort(); - os2.fsType = p.parseUShort(); - os2.ySubscriptXSize = p.parseShort(); - os2.ySubscriptYSize = p.parseShort(); - os2.ySubscriptXOffset = p.parseShort(); - os2.ySubscriptYOffset = p.parseShort(); - os2.ySuperscriptXSize = p.parseShort(); - os2.ySuperscriptYSize = p.parseShort(); - os2.ySuperscriptXOffset = p.parseShort(); - os2.ySuperscriptYOffset = p.parseShort(); - os2.yStrikeoutSize = p.parseShort(); - os2.yStrikeoutPosition = p.parseShort(); - os2.sFamilyClass = p.parseShort(); - os2.panose = []; - for (var i = 0; i < 10; i++) { - os2.panose[i] = p.parseByte(); - } - - os2.ulUnicodeRange1 = p.parseULong(); - os2.ulUnicodeRange2 = p.parseULong(); - os2.ulUnicodeRange3 = p.parseULong(); - os2.ulUnicodeRange4 = p.parseULong(); - os2.achVendID = String.fromCharCode(p.parseByte(), p.parseByte(), p.parseByte(), p.parseByte()); - os2.fsSelection = p.parseUShort(); - os2.usFirstCharIndex = p.parseUShort(); - os2.usLastCharIndex = p.parseUShort(); - os2.sTypoAscender = p.parseShort(); - os2.sTypoDescender = p.parseShort(); - os2.sTypoLineGap = p.parseShort(); - os2.usWinAscent = p.parseUShort(); - os2.usWinDescent = p.parseUShort(); - if (os2.version >= 1) { - os2.ulCodePageRange1 = p.parseULong(); - os2.ulCodePageRange2 = p.parseULong(); - } - - if (os2.version >= 2) { - os2.sxHeight = p.parseShort(); - os2.sCapHeight = p.parseShort(); - os2.usDefaultChar = p.parseUShort(); - os2.usBreakChar = p.parseUShort(); - os2.usMaxContent = p.parseUShort(); - } - - return os2; - } - - function makeOS2Table(options) { - return new table.Table('OS/2', [ - {name: 'version', type: 'USHORT', value: 0x0003}, - {name: 'xAvgCharWidth', type: 'SHORT', value: 0}, - {name: 'usWeightClass', type: 'USHORT', value: 0}, - {name: 'usWidthClass', type: 'USHORT', value: 0}, - {name: 'fsType', type: 'USHORT', value: 0}, - {name: 'ySubscriptXSize', type: 'SHORT', value: 650}, - {name: 'ySubscriptYSize', type: 'SHORT', value: 699}, - {name: 'ySubscriptXOffset', type: 'SHORT', value: 0}, - {name: 'ySubscriptYOffset', type: 'SHORT', value: 140}, - {name: 'ySuperscriptXSize', type: 'SHORT', value: 650}, - {name: 'ySuperscriptYSize', type: 'SHORT', value: 699}, - {name: 'ySuperscriptXOffset', type: 'SHORT', value: 0}, - {name: 'ySuperscriptYOffset', type: 'SHORT', value: 479}, - {name: 'yStrikeoutSize', type: 'SHORT', value: 49}, - {name: 'yStrikeoutPosition', type: 'SHORT', value: 258}, - {name: 'sFamilyClass', type: 'SHORT', value: 0}, - {name: 'bFamilyType', type: 'BYTE', value: 0}, - {name: 'bSerifStyle', type: 'BYTE', value: 0}, - {name: 'bWeight', type: 'BYTE', value: 0}, - {name: 'bProportion', type: 'BYTE', value: 0}, - {name: 'bContrast', type: 'BYTE', value: 0}, - {name: 'bStrokeVariation', type: 'BYTE', value: 0}, - {name: 'bArmStyle', type: 'BYTE', value: 0}, - {name: 'bLetterform', type: 'BYTE', value: 0}, - {name: 'bMidline', type: 'BYTE', value: 0}, - {name: 'bXHeight', type: 'BYTE', value: 0}, - {name: 'ulUnicodeRange1', type: 'ULONG', value: 0}, - {name: 'ulUnicodeRange2', type: 'ULONG', value: 0}, - {name: 'ulUnicodeRange3', type: 'ULONG', value: 0}, - {name: 'ulUnicodeRange4', type: 'ULONG', value: 0}, - {name: 'achVendID', type: 'CHARARRAY', value: 'XXXX'}, - {name: 'fsSelection', type: 'USHORT', value: 0}, - {name: 'usFirstCharIndex', type: 'USHORT', value: 0}, - {name: 'usLastCharIndex', type: 'USHORT', value: 0}, - {name: 'sTypoAscender', type: 'SHORT', value: 0}, - {name: 'sTypoDescender', type: 'SHORT', value: 0}, - {name: 'sTypoLineGap', type: 'SHORT', value: 0}, - {name: 'usWinAscent', type: 'USHORT', value: 0}, - {name: 'usWinDescent', type: 'USHORT', value: 0}, - {name: 'ulCodePageRange1', type: 'ULONG', value: 0}, - {name: 'ulCodePageRange2', type: 'ULONG', value: 0}, - {name: 'sxHeight', type: 'SHORT', value: 0}, - {name: 'sCapHeight', type: 'SHORT', value: 0}, - {name: 'usDefaultChar', type: 'USHORT', value: 0}, - {name: 'usBreakChar', type: 'USHORT', value: 0}, - {name: 'usMaxContext', type: 'USHORT', value: 0} - ], options); - } - - var os2 = { parse: parseOS2Table, make: makeOS2Table, unicodeRanges: unicodeRanges, getUnicodeRange: getUnicodeRange }; - - // The `post` table stores additional PostScript information, such as glyph names. - - // Parse the PostScript `post` table - function parsePostTable(data, start) { - var post = {}; - var p = new parse.Parser(data, start); - post.version = p.parseVersion(); - post.italicAngle = p.parseFixed(); - post.underlinePosition = p.parseShort(); - post.underlineThickness = p.parseShort(); - post.isFixedPitch = p.parseULong(); - post.minMemType42 = p.parseULong(); - post.maxMemType42 = p.parseULong(); - post.minMemType1 = p.parseULong(); - post.maxMemType1 = p.parseULong(); - switch (post.version) { - case 1: - post.names = standardNames.slice(); - break; - case 2: - post.numberOfGlyphs = p.parseUShort(); - post.glyphNameIndex = new Array(post.numberOfGlyphs); - for (var i = 0; i < post.numberOfGlyphs; i++) { - post.glyphNameIndex[i] = p.parseUShort(); - } - - post.names = []; - for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { - if (post.glyphNameIndex[i$1] >= standardNames.length) { - var nameLength = p.parseChar(); - post.names.push(p.parseString(nameLength)); - } - } - - break; - case 2.5: - post.numberOfGlyphs = p.parseUShort(); - post.offset = new Array(post.numberOfGlyphs); - for (var i$2 = 0; i$2 < post.numberOfGlyphs; i$2++) { - post.offset[i$2] = p.parseChar(); - } - - break; - } - return post; - } - - function makePostTable() { - return new table.Table('post', [ - {name: 'version', type: 'FIXED', value: 0x00030000}, - {name: 'italicAngle', type: 'FIXED', value: 0}, - {name: 'underlinePosition', type: 'FWORD', value: 0}, - {name: 'underlineThickness', type: 'FWORD', value: 0}, - {name: 'isFixedPitch', type: 'ULONG', value: 0}, - {name: 'minMemType42', type: 'ULONG', value: 0}, - {name: 'maxMemType42', type: 'ULONG', value: 0}, - {name: 'minMemType1', type: 'ULONG', value: 0}, - {name: 'maxMemType1', type: 'ULONG', value: 0} - ]); - } - - var post = { parse: parsePostTable, make: makePostTable }; - - // The `GSUB` table contains ligatures, among other things. - - var subtableParsers = new Array(9); // subtableParsers[0] is unused - - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#SS - subtableParsers[1] = function parseLookup1() { - var start = this.offset + this.relativeOffset; - var substFormat = this.parseUShort(); - if (substFormat === 1) { - return { - substFormat: 1, - coverage: this.parsePointer(Parser.coverage), - deltaGlyphId: this.parseUShort() - }; - } else if (substFormat === 2) { - return { - substFormat: 2, - coverage: this.parsePointer(Parser.coverage), - substitute: this.parseOffset16List() - }; - } - check.assert(false, '0x' + start.toString(16) + ': lookup type 1 format must be 1 or 2.'); - }; - - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#MS - subtableParsers[2] = function parseLookup2() { - var substFormat = this.parseUShort(); - check.argument(substFormat === 1, 'GSUB Multiple Substitution Subtable identifier-format must be 1'); - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - sequences: this.parseListOfLists() - }; - }; - - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#AS - subtableParsers[3] = function parseLookup3() { - var substFormat = this.parseUShort(); - check.argument(substFormat === 1, 'GSUB Alternate Substitution Subtable identifier-format must be 1'); - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - alternateSets: this.parseListOfLists() - }; - }; - - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#LS - subtableParsers[4] = function parseLookup4() { - var substFormat = this.parseUShort(); - check.argument(substFormat === 1, 'GSUB ligature table identifier-format must be 1'); - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - ligatureSets: this.parseListOfLists(function() { - return { - ligGlyph: this.parseUShort(), - components: this.parseUShortList(this.parseUShort() - 1) - }; - }) - }; - }; - - var lookupRecordDesc = { - sequenceIndex: Parser.uShort, - lookupListIndex: Parser.uShort - }; - - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CSF - subtableParsers[5] = function parseLookup5() { - var start = this.offset + this.relativeOffset; - var substFormat = this.parseUShort(); - - if (substFormat === 1) { - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - ruleSets: this.parseListOfLists(function() { - var glyphCount = this.parseUShort(); - var substCount = this.parseUShort(); - return { - input: this.parseUShortList(glyphCount - 1), - lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) - }; - }) - }; - } else if (substFormat === 2) { - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - classDef: this.parsePointer(Parser.classDef), - classSets: this.parseListOfLists(function() { - var glyphCount = this.parseUShort(); - var substCount = this.parseUShort(); - return { - classes: this.parseUShortList(glyphCount - 1), - lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) - }; - }) - }; - } else if (substFormat === 3) { - var glyphCount = this.parseUShort(); - var substCount = this.parseUShort(); - return { - substFormat: substFormat, - coverages: this.parseList(glyphCount, Parser.pointer(Parser.coverage)), - lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) - }; - } - check.assert(false, '0x' + start.toString(16) + ': lookup type 5 format must be 1, 2 or 3.'); - }; - - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CC - subtableParsers[6] = function parseLookup6() { - var start = this.offset + this.relativeOffset; - var substFormat = this.parseUShort(); - if (substFormat === 1) { - return { - substFormat: 1, - coverage: this.parsePointer(Parser.coverage), - chainRuleSets: this.parseListOfLists(function() { - return { - backtrack: this.parseUShortList(), - input: this.parseUShortList(this.parseShort() - 1), - lookahead: this.parseUShortList(), - lookupRecords: this.parseRecordList(lookupRecordDesc) - }; - }) - }; - } else if (substFormat === 2) { - return { - substFormat: 2, - coverage: this.parsePointer(Parser.coverage), - backtrackClassDef: this.parsePointer(Parser.classDef), - inputClassDef: this.parsePointer(Parser.classDef), - lookaheadClassDef: this.parsePointer(Parser.classDef), - chainClassSet: this.parseListOfLists(function() { - return { - backtrack: this.parseUShortList(), - input: this.parseUShortList(this.parseShort() - 1), - lookahead: this.parseUShortList(), - lookupRecords: this.parseRecordList(lookupRecordDesc) - }; - }) - }; - } else if (substFormat === 3) { - return { - substFormat: 3, - backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), - inputCoverage: this.parseList(Parser.pointer(Parser.coverage)), - lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), - lookupRecords: this.parseRecordList(lookupRecordDesc) - }; - } - check.assert(false, '0x' + start.toString(16) + ': lookup type 6 format must be 1, 2 or 3.'); - }; - - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#ES - subtableParsers[7] = function parseLookup7() { - // Extension Substitution subtable - var substFormat = this.parseUShort(); - check.argument(substFormat === 1, 'GSUB Extension Substitution subtable identifier-format must be 1'); - var extensionLookupType = this.parseUShort(); - var extensionParser = new Parser(this.data, this.offset + this.parseULong()); - return { - substFormat: 1, - lookupType: extensionLookupType, - extension: subtableParsers[extensionLookupType].call(extensionParser) - }; - }; - - // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#RCCS - subtableParsers[8] = function parseLookup8() { - var substFormat = this.parseUShort(); - check.argument(substFormat === 1, 'GSUB Reverse Chaining Contextual Single Substitution Subtable identifier-format must be 1'); - return { - substFormat: substFormat, - coverage: this.parsePointer(Parser.coverage), - backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), - lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), - substitutes: this.parseUShortList() - }; - }; - - // https://www.microsoft.com/typography/OTSPEC/gsub.htm - function parseGsubTable(data, start) { - start = start || 0; - var p = new Parser(data, start); - var tableVersion = p.parseVersion(1); - check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GSUB table version.'); - if (tableVersion === 1) { - return { - version: tableVersion, - scripts: p.parseScriptList(), - features: p.parseFeatureList(), - lookups: p.parseLookupList(subtableParsers) - }; - } else { - return { - version: tableVersion, - scripts: p.parseScriptList(), - features: p.parseFeatureList(), - lookups: p.parseLookupList(subtableParsers), - variations: p.parseFeatureVariationsList() - }; - } - - } - - // GSUB Writing ////////////////////////////////////////////// - var subtableMakers = new Array(9); - - subtableMakers[1] = function makeLookup1(subtable) { - if (subtable.substFormat === 1) { - return new table.Table('substitutionTable', [ - {name: 'substFormat', type: 'USHORT', value: 1}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}, - {name: 'deltaGlyphID', type: 'USHORT', value: subtable.deltaGlyphId} - ]); - } else { - return new table.Table('substitutionTable', [ - {name: 'substFormat', type: 'USHORT', value: 2}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.ushortList('substitute', subtable.substitute))); - } - }; - - subtableMakers[3] = function makeLookup3(subtable) { - check.assert(subtable.substFormat === 1, 'Lookup type 3 substFormat must be 1.'); - return new table.Table('substitutionTable', [ - {name: 'substFormat', type: 'USHORT', value: 1}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.tableList('altSet', subtable.alternateSets, function(alternateSet) { - return new table.Table('alternateSetTable', table.ushortList('alternate', alternateSet)); - }))); - }; - - subtableMakers[4] = function makeLookup4(subtable) { - check.assert(subtable.substFormat === 1, 'Lookup type 4 substFormat must be 1.'); - return new table.Table('substitutionTable', [ - {name: 'substFormat', type: 'USHORT', value: 1}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.tableList('ligSet', subtable.ligatureSets, function(ligatureSet) { - return new table.Table('ligatureSetTable', table.tableList('ligature', ligatureSet, function(ligature) { - return new table.Table('ligatureTable', - [{name: 'ligGlyph', type: 'USHORT', value: ligature.ligGlyph}] - .concat(table.ushortList('component', ligature.components, ligature.components.length + 1)) - ); - })); - }))); - }; - - function makeGsubTable(gsub) { - return new table.Table('GSUB', [ - {name: 'version', type: 'ULONG', value: 0x10000}, - {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gsub.scripts)}, - {name: 'features', type: 'TABLE', value: new table.FeatureList(gsub.features)}, - {name: 'lookups', type: 'TABLE', value: new table.LookupList(gsub.lookups, subtableMakers)} - ]); - } - - var gsub = { parse: parseGsubTable, make: makeGsubTable }; - - // The `GPOS` table contains kerning pairs, among other things. - - // Parse the metadata `meta` table. - // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6meta.html - function parseMetaTable(data, start) { - var p = new parse.Parser(data, start); - var tableVersion = p.parseULong(); - check.argument(tableVersion === 1, 'Unsupported META table version.'); - p.parseULong(); // flags - currently unused and set to 0 - p.parseULong(); // tableOffset - var numDataMaps = p.parseULong(); - - var tags = {}; - for (var i = 0; i < numDataMaps; i++) { - var tag = p.parseTag(); - var dataOffset = p.parseULong(); - var dataLength = p.parseULong(); - var text = decode.UTF8(data, start + dataOffset, dataLength); - - tags[tag] = text; - } - return tags; - } - - function makeMetaTable(tags) { - var numTags = Object.keys(tags).length; - var stringPool = ''; - var stringPoolOffset = 16 + numTags * 12; - - var result = new table.Table('meta', [ - {name: 'version', type: 'ULONG', value: 1}, - {name: 'flags', type: 'ULONG', value: 0}, - {name: 'offset', type: 'ULONG', value: stringPoolOffset}, - {name: 'numTags', type: 'ULONG', value: numTags} - ]); - - for (var tag in tags) { - var pos = stringPool.length; - stringPool += tags[tag]; - - result.fields.push({name: 'tag ' + tag, type: 'TAG', value: tag}); - result.fields.push({name: 'offset ' + tag, type: 'ULONG', value: stringPoolOffset + pos}); - result.fields.push({name: 'length ' + tag, type: 'ULONG', value: tags[tag].length}); - } - - result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); - - return result; - } - - var meta = { parse: parseMetaTable, make: makeMetaTable }; - - // The `sfnt` wrapper provides organization for the tables in the font. - - function log2(v) { - return Math.log(v) / Math.log(2) | 0; - } - - function computeCheckSum(bytes) { - while (bytes.length % 4 !== 0) { - bytes.push(0); - } - - var sum = 0; - for (var i = 0; i < bytes.length; i += 4) { - sum += (bytes[i] << 24) + - (bytes[i + 1] << 16) + - (bytes[i + 2] << 8) + - (bytes[i + 3]); - } - - sum %= Math.pow(2, 32); - return sum; - } - - function makeTableRecord(tag, checkSum, offset, length) { - return new table.Record('Table Record', [ - {name: 'tag', type: 'TAG', value: tag !== undefined ? tag : ''}, - {name: 'checkSum', type: 'ULONG', value: checkSum !== undefined ? checkSum : 0}, - {name: 'offset', type: 'ULONG', value: offset !== undefined ? offset : 0}, - {name: 'length', type: 'ULONG', value: length !== undefined ? length : 0} - ]); - } - - function makeSfntTable(tables) { - var sfnt = new table.Table('sfnt', [ - {name: 'version', type: 'TAG', value: 'OTTO'}, - {name: 'numTables', type: 'USHORT', value: 0}, - {name: 'searchRange', type: 'USHORT', value: 0}, - {name: 'entrySelector', type: 'USHORT', value: 0}, - {name: 'rangeShift', type: 'USHORT', value: 0} - ]); - sfnt.tables = tables; - sfnt.numTables = tables.length; - var highestPowerOf2 = Math.pow(2, log2(sfnt.numTables)); - sfnt.searchRange = 16 * highestPowerOf2; - sfnt.entrySelector = log2(highestPowerOf2); - sfnt.rangeShift = sfnt.numTables * 16 - sfnt.searchRange; - - var recordFields = []; - var tableFields = []; - - var offset = sfnt.sizeOf() + (makeTableRecord().sizeOf() * sfnt.numTables); - while (offset % 4 !== 0) { - offset += 1; - tableFields.push({name: 'padding', type: 'BYTE', value: 0}); - } - - for (var i = 0; i < tables.length; i += 1) { - var t = tables[i]; - check.argument(t.tableName.length === 4, 'Table name' + t.tableName + ' is invalid.'); - var tableLength = t.sizeOf(); - var tableRecord = makeTableRecord(t.tableName, computeCheckSum(t.encode()), offset, tableLength); - recordFields.push({name: tableRecord.tag + ' Table Record', type: 'RECORD', value: tableRecord}); - tableFields.push({name: t.tableName + ' table', type: 'RECORD', value: t}); - offset += tableLength; - check.argument(!isNaN(offset), 'Something went wrong calculating the offset.'); - while (offset % 4 !== 0) { - offset += 1; - tableFields.push({name: 'padding', type: 'BYTE', value: 0}); - } - } - - // Table records need to be sorted alphabetically. - recordFields.sort(function(r1, r2) { - if (r1.value.tag > r2.value.tag) { - return 1; - } else { - return -1; - } - }); - - sfnt.fields = sfnt.fields.concat(recordFields); - sfnt.fields = sfnt.fields.concat(tableFields); - return sfnt; - } - - // Get the metrics for a character. If the string has more than one character - // this function returns metrics for the first available character. - // You can provide optional fallback metrics if no characters are available. - function metricsForChar(font, chars, notFoundMetrics) { - for (var i = 0; i < chars.length; i += 1) { - var glyphIndex = font.charToGlyphIndex(chars[i]); - if (glyphIndex > 0) { - var glyph = font.glyphs.get(glyphIndex); - return glyph.getMetrics(); - } - } - - return notFoundMetrics; - } - - function average(vs) { - var sum = 0; - for (var i = 0; i < vs.length; i += 1) { - sum += vs[i]; - } - - return sum / vs.length; - } - - // Convert the font object to a SFNT data structure. - // This structure contains all the necessary tables and metadata to create a binary OTF file. - function fontToSfntTable(font) { - var xMins = []; - var yMins = []; - var xMaxs = []; - var yMaxs = []; - var advanceWidths = []; - var leftSideBearings = []; - var rightSideBearings = []; - var firstCharIndex; - var lastCharIndex = 0; - var ulUnicodeRange1 = 0; - var ulUnicodeRange2 = 0; - var ulUnicodeRange3 = 0; - var ulUnicodeRange4 = 0; - - for (var i = 0; i < font.glyphs.length; i += 1) { - var glyph = font.glyphs.get(i); - var unicode = glyph.unicode | 0; - - if (isNaN(glyph.advanceWidth)) { - throw new Error('Glyph ' + glyph.name + ' (' + i + '): advanceWidth is not a number.'); - } - - if (firstCharIndex > unicode || firstCharIndex === undefined) { - // ignore .notdef char - if (unicode > 0) { - firstCharIndex = unicode; - } - } - - if (lastCharIndex < unicode) { - lastCharIndex = unicode; - } - - var position = os2.getUnicodeRange(unicode); - if (position < 32) { - ulUnicodeRange1 |= 1 << position; - } else if (position < 64) { - ulUnicodeRange2 |= 1 << position - 32; - } else if (position < 96) { - ulUnicodeRange3 |= 1 << position - 64; - } else if (position < 123) { - ulUnicodeRange4 |= 1 << position - 96; - } else { - throw new Error('Unicode ranges bits > 123 are reserved for internal usage'); - } - // Skip non-important characters. - if (glyph.name === '.notdef') { continue; } - var metrics = glyph.getMetrics(); - xMins.push(metrics.xMin); - yMins.push(metrics.yMin); - xMaxs.push(metrics.xMax); - yMaxs.push(metrics.yMax); - leftSideBearings.push(metrics.leftSideBearing); - rightSideBearings.push(metrics.rightSideBearing); - advanceWidths.push(glyph.advanceWidth); - } - - var globals = { - xMin: Math.min.apply(null, xMins), - yMin: Math.min.apply(null, yMins), - xMax: Math.max.apply(null, xMaxs), - yMax: Math.max.apply(null, yMaxs), - advanceWidthMax: Math.max.apply(null, advanceWidths), - advanceWidthAvg: average(advanceWidths), - minLeftSideBearing: Math.min.apply(null, leftSideBearings), - maxLeftSideBearing: Math.max.apply(null, leftSideBearings), - minRightSideBearing: Math.min.apply(null, rightSideBearings) - }; - globals.ascender = font.ascender; - globals.descender = font.descender; - - var headTable = head.make({ - flags: 3, // 00000011 (baseline for font at y=0; left sidebearing point at x=0) - unitsPerEm: font.unitsPerEm, - xMin: globals.xMin, - yMin: globals.yMin, - xMax: globals.xMax, - yMax: globals.yMax, - lowestRecPPEM: 3, - createdTimestamp: font.createdTimestamp - }); - - var hheaTable = hhea.make({ - ascender: globals.ascender, - descender: globals.descender, - advanceWidthMax: globals.advanceWidthMax, - minLeftSideBearing: globals.minLeftSideBearing, - minRightSideBearing: globals.minRightSideBearing, - xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin), - numberOfHMetrics: font.glyphs.length - }); - - var maxpTable = maxp.make(font.glyphs.length); - - var os2Table = os2.make(Object.assign({ - xAvgCharWidth: Math.round(globals.advanceWidthAvg), - usFirstCharIndex: firstCharIndex, - usLastCharIndex: lastCharIndex, - ulUnicodeRange1: ulUnicodeRange1, - ulUnicodeRange2: ulUnicodeRange2, - ulUnicodeRange3: ulUnicodeRange3, - ulUnicodeRange4: ulUnicodeRange4, - // See http://typophile.com/node/13081 for more info on vertical metrics. - // We get metrics for typical characters (such as "x" for xHeight). - // We provide some fallback characters if characters are unavailable: their - // ordering was chosen experimentally. - sTypoAscender: globals.ascender, - sTypoDescender: globals.descender, - sTypoLineGap: 0, - usWinAscent: globals.yMax, - usWinDescent: Math.abs(globals.yMin), - ulCodePageRange1: 1, // FIXME: hard-code Latin 1 support for now - sxHeight: metricsForChar(font, 'xyvw', {yMax: Math.round(globals.ascender / 2)}).yMax, - sCapHeight: metricsForChar(font, 'HIKLEFJMNTZBDPRAGOQSUVWXY', globals).yMax, - usDefaultChar: font.hasChar(' ') ? 32 : 0, // Use space as the default character, if available. - usBreakChar: font.hasChar(' ') ? 32 : 0, // Use space as the break character, if available. - }, font.tables.os2)); - - var hmtxTable = hmtx.make(font.glyphs); - var cmapTable = cmap.make(font.glyphs); - - var englishFamilyName = font.getEnglishName('fontFamily'); - var englishStyleName = font.getEnglishName('fontSubfamily'); - var englishFullName = englishFamilyName + ' ' + englishStyleName; - var postScriptName = font.getEnglishName('postScriptName'); - if (!postScriptName) { - postScriptName = englishFamilyName.replace(/\s/g, '') + '-' + englishStyleName; - } - - var names = {}; - for (var n in font.names) { - names[n] = font.names[n]; - } - - if (!names.uniqueID) { - names.uniqueID = {en: font.getEnglishName('manufacturer') + ':' + englishFullName}; - } - - if (!names.postScriptName) { - names.postScriptName = {en: postScriptName}; - } - - if (!names.preferredFamily) { - names.preferredFamily = font.names.fontFamily; - } - - if (!names.preferredSubfamily) { - names.preferredSubfamily = font.names.fontSubfamily; - } - - var languageTags = []; - var nameTable = _name.make(names, languageTags); - var ltagTable = (languageTags.length > 0 ? ltag.make(languageTags) : undefined); - - var postTable = post.make(); - var cffTable = cff.make(font.glyphs, { - version: font.getEnglishName('version'), - fullName: englishFullName, - familyName: englishFamilyName, - weightName: englishStyleName, - postScriptName: postScriptName, - unitsPerEm: font.unitsPerEm, - fontBBox: [0, globals.yMin, globals.ascender, globals.advanceWidthMax] - }); - - var metaTable = (font.metas && Object.keys(font.metas).length > 0) ? meta.make(font.metas) : undefined; - - // The order does not matter because makeSfntTable() will sort them. - var tables = [headTable, hheaTable, maxpTable, os2Table, nameTable, cmapTable, postTable, cffTable, hmtxTable]; - if (ltagTable) { - tables.push(ltagTable); - } - // Optional tables - if (font.tables.gsub) { - tables.push(gsub.make(font.tables.gsub)); - } - if (metaTable) { - tables.push(metaTable); - } - - var sfntTable = makeSfntTable(tables); - - // Compute the font's checkSum and store it in head.checkSumAdjustment. - var bytes = sfntTable.encode(); - var checkSum = computeCheckSum(bytes); - var tableFields = sfntTable.fields; - var checkSumAdjusted = false; - for (var i$1 = 0; i$1 < tableFields.length; i$1 += 1) { - if (tableFields[i$1].name === 'head table') { - tableFields[i$1].value.checkSumAdjustment = 0xB1B0AFBA - checkSum; - checkSumAdjusted = true; - break; - } - } - - if (!checkSumAdjusted) { - throw new Error('Could not find head table with checkSum to adjust.'); - } - - return sfntTable; - } - - var sfnt = { make: makeSfntTable, fontToTable: fontToSfntTable, computeCheckSum: computeCheckSum }; - - // The Layout object is the prototype of Substitution objects, and provides - - function searchTag(arr, tag) { - /* jshint bitwise: false */ - var imin = 0; - var imax = arr.length - 1; - while (imin <= imax) { - var imid = (imin + imax) >>> 1; - var val = arr[imid].tag; - if (val === tag) { - return imid; - } else if (val < tag) { - imin = imid + 1; - } else { imax = imid - 1; } - } - // Not found: return -1-insertion point - return -imin - 1; - } - - function binSearch(arr, value) { - /* jshint bitwise: false */ - var imin = 0; - var imax = arr.length - 1; - while (imin <= imax) { - var imid = (imin + imax) >>> 1; - var val = arr[imid]; - if (val === value) { - return imid; - } else if (val < value) { - imin = imid + 1; - } else { imax = imid - 1; } - } - // Not found: return -1-insertion point - return -imin - 1; - } - - // binary search in a list of ranges (coverage, class definition) - function searchRange(ranges, value) { - // jshint bitwise: false - var range; - var imin = 0; - var imax = ranges.length - 1; - while (imin <= imax) { - var imid = (imin + imax) >>> 1; - range = ranges[imid]; - var start = range.start; - if (start === value) { - return range; - } else if (start < value) { - imin = imid + 1; - } else { imax = imid - 1; } - } - if (imin > 0) { - range = ranges[imin - 1]; - if (value > range.end) { return 0; } - return range; - } - } - - /** - * @exports opentype.Layout - * @class - */ - function Layout(font, tableName) { - this.font = font; - this.tableName = tableName; - } - - Layout.prototype = { - - /** - * Binary search an object by "tag" property - * @instance - * @function searchTag - * @memberof opentype.Layout - * @param {Array} arr - * @param {string} tag - * @return {number} - */ - searchTag: searchTag, - - /** - * Binary search in a list of numbers - * @instance - * @function binSearch - * @memberof opentype.Layout - * @param {Array} arr - * @param {number} value - * @return {number} - */ - binSearch: binSearch, - - /** - * Get or create the Layout table (GSUB, GPOS etc). - * @param {boolean} create - Whether to create a new one. - * @return {Object} The GSUB or GPOS table. - */ - getTable: function(create) { - var layout = this.font.tables[this.tableName]; - if (!layout && create) { - layout = this.font.tables[this.tableName] = this.createDefaultTable(); - } - return layout; - }, - - /** - * Returns all scripts in the substitution table. - * @instance - * @return {Array} - */ - getScriptNames: function() { - var layout = this.getTable(); - if (!layout) { return []; } - return layout.scripts.map(function(script) { - return script.tag; - }); - }, - - /** - * Returns the best bet for a script name. - * Returns 'DFLT' if it exists. - * If not, returns 'latn' if it exists. - * If neither exist, returns undefined. - */ - getDefaultScriptName: function() { - var layout = this.getTable(); - if (!layout) { return; } - var hasLatn = false; - for (var i = 0; i < layout.scripts.length; i++) { - var name = layout.scripts[i].tag; - if (name === 'DFLT') { return name; } - if (name === 'latn') { hasLatn = true; } - } - if (hasLatn) { return 'latn'; } - }, - - /** - * Returns all LangSysRecords in the given script. - * @instance - * @param {string} [script='DFLT'] - * @param {boolean} create - forces the creation of this script table if it doesn't exist. - * @return {Object} An object with tag and script properties. - */ - getScriptTable: function(script, create) { - var layout = this.getTable(create); - if (layout) { - script = script || 'DFLT'; - var scripts = layout.scripts; - var pos = searchTag(layout.scripts, script); - if (pos >= 0) { - return scripts[pos].script; - } else if (create) { - var scr = { - tag: script, - script: { - defaultLangSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []}, - langSysRecords: [] - } - }; - scripts.splice(-1 - pos, 0, scr); - return scr.script; - } - } - }, - - /** - * Returns a language system table - * @instance - * @param {string} [script='DFLT'] - * @param {string} [language='dlft'] - * @param {boolean} create - forces the creation of this langSysTable if it doesn't exist. - * @return {Object} - */ - getLangSysTable: function(script, language, create) { - var scriptTable = this.getScriptTable(script, create); - if (scriptTable) { - if (!language || language === 'dflt' || language === 'DFLT') { - return scriptTable.defaultLangSys; - } - var pos = searchTag(scriptTable.langSysRecords, language); - if (pos >= 0) { - return scriptTable.langSysRecords[pos].langSys; - } else if (create) { - var langSysRecord = { - tag: language, - langSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []} - }; - scriptTable.langSysRecords.splice(-1 - pos, 0, langSysRecord); - return langSysRecord.langSys; - } - } - }, - - /** - * Get a specific feature table. - * @instance - * @param {string} [script='DFLT'] - * @param {string} [language='dlft'] - * @param {string} feature - One of the codes listed at https://www.microsoft.com/typography/OTSPEC/featurelist.htm - * @param {boolean} create - forces the creation of the feature table if it doesn't exist. - * @return {Object} - */ - getFeatureTable: function(script, language, feature, create) { - var langSysTable = this.getLangSysTable(script, language, create); - if (langSysTable) { - var featureRecord; - var featIndexes = langSysTable.featureIndexes; - var allFeatures = this.font.tables[this.tableName].features; - // The FeatureIndex array of indices is in arbitrary order, - // even if allFeatures is sorted alphabetically by feature tag. - for (var i = 0; i < featIndexes.length; i++) { - featureRecord = allFeatures[featIndexes[i]]; - if (featureRecord.tag === feature) { - return featureRecord.feature; - } - } - if (create) { - var index = allFeatures.length; - // Automatic ordering of features would require to shift feature indexes in the script list. - check.assert(index === 0 || feature >= allFeatures[index - 1].tag, 'Features must be added in alphabetical order.'); - featureRecord = { - tag: feature, - feature: { params: 0, lookupListIndexes: [] } - }; - allFeatures.push(featureRecord); - featIndexes.push(index); - return featureRecord.feature; - } - } - }, - - /** - * Get the lookup tables of a given type for a script/language/feature. - * @instance - * @param {string} [script='DFLT'] - * @param {string} [language='dlft'] - * @param {string} feature - 4-letter feature code - * @param {number} lookupType - 1 to 9 - * @param {boolean} create - forces the creation of the lookup table if it doesn't exist, with no subtables. - * @return {Object[]} - */ - getLookupTables: function(script, language, feature, lookupType, create) { - var featureTable = this.getFeatureTable(script, language, feature, create); - var tables = []; - if (featureTable) { - var lookupTable; - var lookupListIndexes = featureTable.lookupListIndexes; - var allLookups = this.font.tables[this.tableName].lookups; - // lookupListIndexes are in no particular order, so use naive search. - for (var i = 0; i < lookupListIndexes.length; i++) { - lookupTable = allLookups[lookupListIndexes[i]]; - if (lookupTable.lookupType === lookupType) { - tables.push(lookupTable); - } - } - if (tables.length === 0 && create) { - lookupTable = { - lookupType: lookupType, - lookupFlag: 0, - subtables: [], - markFilteringSet: undefined - }; - var index = allLookups.length; - allLookups.push(lookupTable); - lookupListIndexes.push(index); - return [lookupTable]; - } - } - return tables; - }, - - /** - * Find a glyph in a class definition table - * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table - * @param {object} classDefTable - an OpenType Layout class definition table - * @param {number} glyphIndex - the index of the glyph to find - * @returns {number} -1 if not found - */ - getGlyphClass: function(classDefTable, glyphIndex) { - switch (classDefTable.format) { - case 1: - if (classDefTable.startGlyph <= glyphIndex && glyphIndex < classDefTable.startGlyph + classDefTable.classes.length) { - return classDefTable.classes[glyphIndex - classDefTable.startGlyph]; - } - return 0; - case 2: - var range = searchRange(classDefTable.ranges, glyphIndex); - return range ? range.classId : 0; - } - }, - - /** - * Find a glyph in a coverage table - * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-table - * @param {object} coverageTable - an OpenType Layout coverage table - * @param {number} glyphIndex - the index of the glyph to find - * @returns {number} -1 if not found - */ - getCoverageIndex: function(coverageTable, glyphIndex) { - switch (coverageTable.format) { - case 1: - var index = binSearch(coverageTable.glyphs, glyphIndex); - return index >= 0 ? index : -1; - case 2: - var range = searchRange(coverageTable.ranges, glyphIndex); - return range ? range.index + glyphIndex - range.start : -1; - } - }, - - /** - * Returns the list of glyph indexes of a coverage table. - * Format 1: the list is stored raw - * Format 2: compact list as range records. - * @instance - * @param {Object} coverageTable - * @return {Array} - */ - expandCoverage: function(coverageTable) { - if (coverageTable.format === 1) { - return coverageTable.glyphs; - } else { - var glyphs = []; - var ranges = coverageTable.ranges; - for (var i = 0; i < ranges.length; i++) { - var range = ranges[i]; - var start = range.start; - var end = range.end; - for (var j = start; j <= end; j++) { - glyphs.push(j); - } - } - return glyphs; - } - } - - }; - - // The Position object provides utility methods to manipulate - - /** - * @exports opentype.Position - * @class - * @extends opentype.Layout - * @param {opentype.Font} - * @constructor - */ - function Position(font) { - Layout.call(this, font, 'gpos'); - } - - Position.prototype = Layout.prototype; - - /** - * Init some data for faster and easier access later. - */ - Position.prototype.init = function() { - var script = this.getDefaultScriptName(); - this.defaultKerningTables = this.getKerningTables(script); - }; - - /** - * Find a glyph pair in a list of lookup tables of type 2 and retrieve the xAdvance kerning value. - * - * @param {integer} leftIndex - left glyph index - * @param {integer} rightIndex - right glyph index - * @returns {integer} - */ - Position.prototype.getKerningValue = function(kerningLookups, leftIndex, rightIndex) { - for (var i = 0; i < kerningLookups.length; i++) { - var subtables = kerningLookups[i].subtables; - for (var j = 0; j < subtables.length; j++) { - var subtable = subtables[j]; - var covIndex = this.getCoverageIndex(subtable.coverage, leftIndex); - if (covIndex < 0) { continue; } - switch (subtable.posFormat) { - case 1: - // Search Pair Adjustment Positioning Format 1 - var pairSet = subtable.pairSets[covIndex]; - for (var k = 0; k < pairSet.length; k++) { - var pair = pairSet[k]; - if (pair.secondGlyph === rightIndex) { - return pair.value1 && pair.value1.xAdvance || 0; - } - } - break; // left glyph found, not right glyph - try next subtable - case 2: - // Search Pair Adjustment Positioning Format 2 - var class1 = this.getGlyphClass(subtable.classDef1, leftIndex); - var class2 = this.getGlyphClass(subtable.classDef2, rightIndex); - var pair$1 = subtable.classRecords[class1][class2]; - return pair$1.value1 && pair$1.value1.xAdvance || 0; - } - } - } - return 0; - }; - - /** - * List all kerning lookup tables. - * - * @param {string} [script='DFLT'] - use font.position.getDefaultScriptName() for a better default value - * @param {string} [language='dflt'] - * @return {object[]} The list of kerning lookup tables (may be empty), or undefined if there is no GPOS table (and we should use the kern table) - */ - Position.prototype.getKerningTables = function(script, language) { - if (this.font.tables.gpos) { - return this.getLookupTables(script, language, 'kern', 2); - } - }; - - // The Substitution object provides utility methods to manipulate - - /** - * @exports opentype.Substitution - * @class - * @extends opentype.Layout - * @param {opentype.Font} - * @constructor - */ - function Substitution(font) { - Layout.call(this, font, 'gsub'); - } - - // Check if 2 arrays of primitives are equal. - function arraysEqual(ar1, ar2) { - var n = ar1.length; - if (n !== ar2.length) { return false; } - for (var i = 0; i < n; i++) { - if (ar1[i] !== ar2[i]) { return false; } - } - return true; - } - - // Find the first subtable of a lookup table in a particular format. - function getSubstFormat(lookupTable, format, defaultSubtable) { - var subtables = lookupTable.subtables; - for (var i = 0; i < subtables.length; i++) { - var subtable = subtables[i]; - if (subtable.substFormat === format) { - return subtable; - } - } - if (defaultSubtable) { - subtables.push(defaultSubtable); - return defaultSubtable; - } - return undefined; - } - - Substitution.prototype = Layout.prototype; - - /** - * Create a default GSUB table. - * @return {Object} gsub - The GSUB table. - */ - Substitution.prototype.createDefaultTable = function() { - // Generate a default empty GSUB table with just a DFLT script and dflt lang sys. - return { - version: 1, - scripts: [{ - tag: 'DFLT', - script: { - defaultLangSys: { reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: [] }, - langSysRecords: [] - } - }], - features: [], - lookups: [] - }; - }; - - /** - * List all single substitutions (lookup type 1) for a given script, language, and feature. - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - * @param {string} feature - 4-character feature name ('aalt', 'salt', 'ss01'...) - * @return {Array} substitutions - The list of substitutions. - */ - Substitution.prototype.getSingle = function(feature, script, language) { - var substitutions = []; - var lookupTables = this.getLookupTables(script, language, feature, 1); - for (var idx = 0; idx < lookupTables.length; idx++) { - var subtables = lookupTables[idx].subtables; - for (var i = 0; i < subtables.length; i++) { - var subtable = subtables[i]; - var glyphs = this.expandCoverage(subtable.coverage); - var j = (void 0); - if (subtable.substFormat === 1) { - var delta = subtable.deltaGlyphId; - for (j = 0; j < glyphs.length; j++) { - var glyph = glyphs[j]; - substitutions.push({ sub: glyph, by: glyph + delta }); - } - } else { - var substitute = subtable.substitute; - for (j = 0; j < glyphs.length; j++) { - substitutions.push({ sub: glyphs[j], by: substitute[j] }); - } - } - } - } - return substitutions; - }; - - /** - * List all alternates (lookup type 3) for a given script, language, and feature. - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - * @param {string} feature - 4-character feature name ('aalt', 'salt'...) - * @return {Array} alternates - The list of alternates - */ - Substitution.prototype.getAlternates = function(feature, script, language) { - var alternates = []; - var lookupTables = this.getLookupTables(script, language, feature, 3); - for (var idx = 0; idx < lookupTables.length; idx++) { - var subtables = lookupTables[idx].subtables; - for (var i = 0; i < subtables.length; i++) { - var subtable = subtables[i]; - var glyphs = this.expandCoverage(subtable.coverage); - var alternateSets = subtable.alternateSets; - for (var j = 0; j < glyphs.length; j++) { - alternates.push({ sub: glyphs[j], by: alternateSets[j] }); - } - } - } - return alternates; - }; - - /** - * List all ligatures (lookup type 4) for a given script, language, and feature. - * The result is an array of ligature objects like { sub: [ids], by: id } - * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - * @return {Array} ligatures - The list of ligatures. - */ - Substitution.prototype.getLigatures = function(feature, script, language) { - var ligatures = []; - var lookupTables = this.getLookupTables(script, language, feature, 4); - for (var idx = 0; idx < lookupTables.length; idx++) { - var subtables = lookupTables[idx].subtables; - for (var i = 0; i < subtables.length; i++) { - var subtable = subtables[i]; - var glyphs = this.expandCoverage(subtable.coverage); - var ligatureSets = subtable.ligatureSets; - for (var j = 0; j < glyphs.length; j++) { - var startGlyph = glyphs[j]; - var ligSet = ligatureSets[j]; - for (var k = 0; k < ligSet.length; k++) { - var lig = ligSet[k]; - ligatures.push({ - sub: [startGlyph].concat(lig.components), - by: lig.ligGlyph - }); - } - } - } - } - return ligatures; - }; - - /** - * Add or modify a single substitution (lookup type 1) - * Format 2, more flexible, is always used. - * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) - * @param {Object} substitution - { sub: id, delta: number } for format 1 or { sub: id, by: id } for format 2. - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - */ - Substitution.prototype.addSingle = function(feature, substitution, script, language) { - var lookupTable = this.getLookupTables(script, language, feature, 1, true)[0]; - var subtable = getSubstFormat(lookupTable, 2, { // lookup type 1 subtable, format 2, coverage format 1 - substFormat: 2, - coverage: {format: 1, glyphs: []}, - substitute: [] - }); - check.assert(subtable.coverage.format === 1, 'Ligature: unable to modify coverage table format ' + subtable.coverage.format); - var coverageGlyph = substitution.sub; - var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); - if (pos < 0) { - pos = -1 - pos; - subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); - subtable.substitute.splice(pos, 0, 0); - } - subtable.substitute[pos] = substitution.by; - }; - - /** - * Add or modify an alternate substitution (lookup type 1) - * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) - * @param {Object} substitution - { sub: id, by: [ids] } - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - */ - Substitution.prototype.addAlternate = function(feature, substitution, script, language) { - var lookupTable = this.getLookupTables(script, language, feature, 3, true)[0]; - var subtable = getSubstFormat(lookupTable, 1, { // lookup type 3 subtable, format 1, coverage format 1 - substFormat: 1, - coverage: {format: 1, glyphs: []}, - alternateSets: [] - }); - check.assert(subtable.coverage.format === 1, 'Ligature: unable to modify coverage table format ' + subtable.coverage.format); - var coverageGlyph = substitution.sub; - var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); - if (pos < 0) { - pos = -1 - pos; - subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); - subtable.alternateSets.splice(pos, 0, 0); - } - subtable.alternateSets[pos] = substitution.by; - }; - - /** - * Add a ligature (lookup type 4) - * Ligatures with more components must be stored ahead of those with fewer components in order to be found - * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) - * @param {Object} ligature - { sub: [ids], by: id } - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - */ - Substitution.prototype.addLigature = function(feature, ligature, script, language) { - var lookupTable = this.getLookupTables(script, language, feature, 4, true)[0]; - var subtable = lookupTable.subtables[0]; - if (!subtable) { - subtable = { // lookup type 4 subtable, format 1, coverage format 1 - substFormat: 1, - coverage: { format: 1, glyphs: [] }, - ligatureSets: [] - }; - lookupTable.subtables[0] = subtable; - } - check.assert(subtable.coverage.format === 1, 'Ligature: unable to modify coverage table format ' + subtable.coverage.format); - var coverageGlyph = ligature.sub[0]; - var ligComponents = ligature.sub.slice(1); - var ligatureTable = { - ligGlyph: ligature.by, - components: ligComponents - }; - var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); - if (pos >= 0) { - // ligatureSet already exists - var ligatureSet = subtable.ligatureSets[pos]; - for (var i = 0; i < ligatureSet.length; i++) { - // If ligature already exists, return. - if (arraysEqual(ligatureSet[i].components, ligComponents)) { - return; - } - } - // ligature does not exist: add it. - ligatureSet.push(ligatureTable); - } else { - // Create a new ligatureSet and add coverage for the first glyph. - pos = -1 - pos; - subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); - subtable.ligatureSets.splice(pos, 0, [ligatureTable]); - } - }; - - /** - * List all feature data for a given script and language. - * @param {string} feature - 4-letter feature name - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - * @return {Array} substitutions - The list of substitutions. - */ - Substitution.prototype.getFeature = function(feature, script, language) { - if (/ss\d\d/.test(feature)) { - // ss01 - ss20 - return this.getSingle(feature, script, language); - } - switch (feature) { - case 'aalt': - case 'salt': - return this.getSingle(feature, script, language) - .concat(this.getAlternates(feature, script, language)); - case 'dlig': - case 'liga': - case 'rlig': return this.getLigatures(feature, script, language); - } - return undefined; - }; - - /** - * Add a substitution to a feature for a given script and language. - * @param {string} feature - 4-letter feature name - * @param {Object} sub - the substitution to add (an object like { sub: id or [ids], by: id or [ids] }) - * @param {string} [script='DFLT'] - * @param {string} [language='dflt'] - */ - Substitution.prototype.add = function(feature, sub, script, language) { - if (/ss\d\d/.test(feature)) { - // ss01 - ss20 - return this.addSingle(feature, sub, script, language); - } - switch (feature) { - case 'aalt': - case 'salt': - if (typeof sub.by === 'number') { - return this.addSingle(feature, sub, script, language); - } - return this.addAlternate(feature, sub, script, language); - case 'dlig': - case 'liga': - case 'rlig': - return this.addLigature(feature, sub, script, language); - } - return undefined; - }; - - function isBrowser() { - return typeof window !== 'undefined'; - } - - function nodeBufferToArrayBuffer(buffer) { - var ab = new ArrayBuffer(buffer.length); - var view = new Uint8Array(ab); - for (var i = 0; i < buffer.length; ++i) { - view[i] = buffer[i]; - } - - return ab; - } - - function arrayBufferToNodeBuffer(ab) { - var buffer = new Buffer(ab.byteLength); - var view = new Uint8Array(ab); - for (var i = 0; i < buffer.length; ++i) { - buffer[i] = view[i]; - } - - return buffer; - } - - function checkArgument(expression, message) { - if (!expression) { - throw message; - } - } - - // The `glyf` table describes the glyphs in TrueType outline format. - - // Parse the coordinate data for a glyph. - function parseGlyphCoordinate(p, flag, previousValue, shortVectorBitMask, sameBitMask) { - var v; - if ((flag & shortVectorBitMask) > 0) { - // The coordinate is 1 byte long. - v = p.parseByte(); - // The `same` bit is re-used for short values to signify the sign of the value. - if ((flag & sameBitMask) === 0) { - v = -v; - } - - v = previousValue + v; - } else { - // The coordinate is 2 bytes long. - // If the `same` bit is set, the coordinate is the same as the previous coordinate. - if ((flag & sameBitMask) > 0) { - v = previousValue; - } else { - // Parse the coordinate as a signed 16-bit delta value. - v = previousValue + p.parseShort(); - } - } - - return v; - } - - // Parse a TrueType glyph. - function parseGlyph(glyph, data, start) { - var p = new parse.Parser(data, start); - glyph.numberOfContours = p.parseShort(); - glyph._xMin = p.parseShort(); - glyph._yMin = p.parseShort(); - glyph._xMax = p.parseShort(); - glyph._yMax = p.parseShort(); - var flags; - var flag; - - if (glyph.numberOfContours > 0) { - // This glyph is not a composite. - var endPointIndices = glyph.endPointIndices = []; - for (var i = 0; i < glyph.numberOfContours; i += 1) { - endPointIndices.push(p.parseUShort()); - } - - glyph.instructionLength = p.parseUShort(); - glyph.instructions = []; - for (var i$1 = 0; i$1 < glyph.instructionLength; i$1 += 1) { - glyph.instructions.push(p.parseByte()); - } - - var numberOfCoordinates = endPointIndices[endPointIndices.length - 1] + 1; - flags = []; - for (var i$2 = 0; i$2 < numberOfCoordinates; i$2 += 1) { - flag = p.parseByte(); - flags.push(flag); - // If bit 3 is set, we repeat this flag n times, where n is the next byte. - if ((flag & 8) > 0) { - var repeatCount = p.parseByte(); - for (var j = 0; j < repeatCount; j += 1) { - flags.push(flag); - i$2 += 1; - } - } - } - - check.argument(flags.length === numberOfCoordinates, 'Bad flags.'); - - if (endPointIndices.length > 0) { - var points = []; - var point; - // X/Y coordinates are relative to the previous point, except for the first point which is relative to 0,0. - if (numberOfCoordinates > 0) { - for (var i$3 = 0; i$3 < numberOfCoordinates; i$3 += 1) { - flag = flags[i$3]; - point = {}; - point.onCurve = !!(flag & 1); - point.lastPointOfContour = endPointIndices.indexOf(i$3) >= 0; - points.push(point); - } - - var px = 0; - for (var i$4 = 0; i$4 < numberOfCoordinates; i$4 += 1) { - flag = flags[i$4]; - point = points[i$4]; - point.x = parseGlyphCoordinate(p, flag, px, 2, 16); - px = point.x; - } - - var py = 0; - for (var i$5 = 0; i$5 < numberOfCoordinates; i$5 += 1) { - flag = flags[i$5]; - point = points[i$5]; - point.y = parseGlyphCoordinate(p, flag, py, 4, 32); - py = point.y; - } - } - - glyph.points = points; - } else { - glyph.points = []; - } - } else if (glyph.numberOfContours === 0) { - glyph.points = []; - } else { - glyph.isComposite = true; - glyph.points = []; - glyph.components = []; - var moreComponents = true; - while (moreComponents) { - flags = p.parseUShort(); - var component = { - glyphIndex: p.parseUShort(), - xScale: 1, - scale01: 0, - scale10: 0, - yScale: 1, - dx: 0, - dy: 0 - }; - if ((flags & 1) > 0) { - // The arguments are words - if ((flags & 2) > 0) { - // values are offset - component.dx = p.parseShort(); - component.dy = p.parseShort(); - } else { - // values are matched points - component.matchedPoints = [p.parseUShort(), p.parseUShort()]; - } - - } else { - // The arguments are bytes - if ((flags & 2) > 0) { - // values are offset - component.dx = p.parseChar(); - component.dy = p.parseChar(); - } else { - // values are matched points - component.matchedPoints = [p.parseByte(), p.parseByte()]; - } - } - - if ((flags & 8) > 0) { - // We have a scale - component.xScale = component.yScale = p.parseF2Dot14(); - } else if ((flags & 64) > 0) { - // We have an X / Y scale - component.xScale = p.parseF2Dot14(); - component.yScale = p.parseF2Dot14(); - } else if ((flags & 128) > 0) { - // We have a 2x2 transformation - component.xScale = p.parseF2Dot14(); - component.scale01 = p.parseF2Dot14(); - component.scale10 = p.parseF2Dot14(); - component.yScale = p.parseF2Dot14(); - } - - glyph.components.push(component); - moreComponents = !!(flags & 32); - } - if (flags & 0x100) { - // We have instructions - glyph.instructionLength = p.parseUShort(); - glyph.instructions = []; - for (var i$6 = 0; i$6 < glyph.instructionLength; i$6 += 1) { - glyph.instructions.push(p.parseByte()); - } - } - } - } - - // Transform an array of points and return a new array. - function transformPoints(points, transform) { - var newPoints = []; - for (var i = 0; i < points.length; i += 1) { - var pt = points[i]; - var newPt = { - x: transform.xScale * pt.x + transform.scale01 * pt.y + transform.dx, - y: transform.scale10 * pt.x + transform.yScale * pt.y + transform.dy, - onCurve: pt.onCurve, - lastPointOfContour: pt.lastPointOfContour - }; - newPoints.push(newPt); - } - - return newPoints; - } - - function getContours(points) { - var contours = []; - var currentContour = []; - for (var i = 0; i < points.length; i += 1) { - var pt = points[i]; - currentContour.push(pt); - if (pt.lastPointOfContour) { - contours.push(currentContour); - currentContour = []; - } - } - - check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); - return contours; - } - - // Convert the TrueType glyph outline to a Path. - function getPath(points) { - var p = new Path(); - if (!points) { - return p; - } - - var contours = getContours(points); - - for (var contourIndex = 0; contourIndex < contours.length; ++contourIndex) { - var contour = contours[contourIndex]; - - var prev = null; - var curr = contour[contour.length - 1]; - var next = contour[0]; - - if (curr.onCurve) { - p.moveTo(curr.x, curr.y); - } else { - if (next.onCurve) { - p.moveTo(next.x, next.y); - } else { - // If both first and last points are off-curve, start at their middle. - var start = {x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5}; - p.moveTo(start.x, start.y); - } - } - - for (var i = 0; i < contour.length; ++i) { - prev = curr; - curr = next; - next = contour[(i + 1) % contour.length]; - - if (curr.onCurve) { - // This is a straight line. - p.lineTo(curr.x, curr.y); - } else { - var prev2 = prev; - var next2 = next; - - if (!prev.onCurve) { - prev2 = { x: (curr.x + prev.x) * 0.5, y: (curr.y + prev.y) * 0.5 }; - } - - if (!next.onCurve) { - next2 = { x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5 }; - } - - p.quadraticCurveTo(curr.x, curr.y, next2.x, next2.y); - } - } - - p.closePath(); - } - return p; - } - - function buildPath(glyphs, glyph) { - if (glyph.isComposite) { - for (var j = 0; j < glyph.components.length; j += 1) { - var component = glyph.components[j]; - var componentGlyph = glyphs.get(component.glyphIndex); - // Force the ttfGlyphLoader to parse the glyph. - componentGlyph.getPath(); - if (componentGlyph.points) { - var transformedPoints = (void 0); - if (component.matchedPoints === undefined) { - // component positioned by offset - transformedPoints = transformPoints(componentGlyph.points, component); - } else { - // component positioned by matched points - if ((component.matchedPoints[0] > glyph.points.length - 1) || - (component.matchedPoints[1] > componentGlyph.points.length - 1)) { - throw Error('Matched points out of range in ' + glyph.name); - } - var firstPt = glyph.points[component.matchedPoints[0]]; - var secondPt = componentGlyph.points[component.matchedPoints[1]]; - var transform = { - xScale: component.xScale, scale01: component.scale01, - scale10: component.scale10, yScale: component.yScale, - dx: 0, dy: 0 - }; - secondPt = transformPoints([secondPt], transform)[0]; - transform.dx = firstPt.x - secondPt.x; - transform.dy = firstPt.y - secondPt.y; - transformedPoints = transformPoints(componentGlyph.points, transform); - } - glyph.points = glyph.points.concat(transformedPoints); - } - } - } - - return getPath(glyph.points); - } - - function parseGlyfTableAll(data, start, loca, font) { - var glyphs = new glyphset.GlyphSet(font); - - // The last element of the loca table is invalid. - for (var i = 0; i < loca.length - 1; i += 1) { - var offset = loca[i]; - var nextOffset = loca[i + 1]; - if (offset !== nextOffset) { - glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); - } else { - glyphs.push(i, glyphset.glyphLoader(font, i)); - } - } - - return glyphs; - } - - function parseGlyfTableOnLowMemory(data, start, loca, font) { - var glyphs = new glyphset.GlyphSet(font); - - font._push = function(i) { - var offset = loca[i]; - var nextOffset = loca[i + 1]; - if (offset !== nextOffset) { - glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); - } else { - glyphs.push(i, glyphset.glyphLoader(font, i)); - } - }; - - return glyphs; - } - - // Parse all the glyphs according to the offsets from the `loca` table. - function parseGlyfTable(data, start, loca, font, opt) { - if (opt.lowMemory) - { return parseGlyfTableOnLowMemory(data, start, loca, font); } - else - { return parseGlyfTableAll(data, start, loca, font); } - } - - var glyf = { getPath: getPath, parse: parseGlyfTable}; - - /* A TrueType font hinting interpreter. - * - * (c) 2017 Axel Kittenberger - * - * This interpreter has been implemented according to this documentation: - * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM05/Chap5.html - * - * According to the documentation F24DOT6 values are used for pixels. - * That means calculation is 1/64 pixel accurate and uses integer operations. - * However, Javascript has floating point operations by default and only - * those are available. One could make a case to simulate the 1/64 accuracy - * exactly by truncating after every division operation - * (for example with << 0) to get pixel exactly results as other TrueType - * implementations. It may make sense since some fonts are pixel optimized - * by hand using DELTAP instructions. The current implementation doesn't - * and rather uses full floating point precision. - * - * xScale, yScale and rotation is currently ignored. - * - * A few non-trivial instructions are missing as I didn't encounter yet - * a font that used them to test a possible implementation. - * - * Some fonts seem to use undocumented features regarding the twilight zone. - * Only some of them are implemented as they were encountered. - * - * The exports.DEBUG statements are removed on the minified distribution file. - */ - - var instructionTable; - var exec; - var execGlyph; - var execComponent; - - /* - * Creates a hinting object. - * - * There ought to be exactly one - * for each truetype font that is used for hinting. - */ - function Hinting(font) { - // the font this hinting object is for - this.font = font; - - this.getCommands = function (hPoints) { - return glyf.getPath(hPoints).commands; - }; - - // cached states - this._fpgmState = - this._prepState = - undefined; - - // errorState - // 0 ... all okay - // 1 ... had an error in a glyf, - // continue working but stop spamming - // the console - // 2 ... error at prep, stop hinting at this ppem - // 3 ... error at fpeg, stop hinting for this font at all - this._errorState = 0; - } - - /* - * Not rounding. - */ - function roundOff(v) { - return v; - } - - /* - * Rounding to grid. - */ - function roundToGrid(v) { - //Rounding in TT is supposed to "symmetrical around zero" - return Math.sign(v) * Math.round(Math.abs(v)); - } - - /* - * Rounding to double grid. - */ - function roundToDoubleGrid(v) { - return Math.sign(v) * Math.round(Math.abs(v * 2)) / 2; - } - - /* - * Rounding to half grid. - */ - function roundToHalfGrid(v) { - return Math.sign(v) * (Math.round(Math.abs(v) + 0.5) - 0.5); - } - - /* - * Rounding to up to grid. - */ - function roundUpToGrid(v) { - return Math.sign(v) * Math.ceil(Math.abs(v)); - } - - /* - * Rounding to down to grid. - */ - function roundDownToGrid(v) { - return Math.sign(v) * Math.floor(Math.abs(v)); - } - - /* - * Super rounding. - */ - var roundSuper = function (v) { - var period = this.srPeriod; - var phase = this.srPhase; - var threshold = this.srThreshold; - var sign = 1; - - if (v < 0) { - v = -v; - sign = -1; - } - - v += threshold - phase; - - v = Math.trunc(v / period) * period; - - v += phase; - - // according to http://xgridfit.sourceforge.net/round.html - if (v < 0) { return phase * sign; } - - return v * sign; - }; - - /* - * Unit vector of x-axis. - */ - var xUnitVector = { - x: 1, - - y: 0, - - axis: 'x', - - // Gets the projected distance between two points. - // o1/o2 ... if true, respective original position is used. - distance: function (p1, p2, o1, o2) { - return (o1 ? p1.xo : p1.x) - (o2 ? p2.xo : p2.x); - }, - - // Moves point p so the moved position has the same relative - // position to the moved positions of rp1 and rp2 than the - // original positions had. - // - // See APPENDIX on INTERPOLATE at the bottom of this file. - interpolate: function (p, rp1, rp2, pv) { - var do1; - var do2; - var doa1; - var doa2; - var dm1; - var dm2; - var dt; - - if (!pv || pv === this) { - do1 = p.xo - rp1.xo; - do2 = p.xo - rp2.xo; - dm1 = rp1.x - rp1.xo; - dm2 = rp2.x - rp2.xo; - doa1 = Math.abs(do1); - doa2 = Math.abs(do2); - dt = doa1 + doa2; - - if (dt === 0) { - p.x = p.xo + (dm1 + dm2) / 2; - return; - } - - p.x = p.xo + (dm1 * doa2 + dm2 * doa1) / dt; - return; - } - - do1 = pv.distance(p, rp1, true, true); - do2 = pv.distance(p, rp2, true, true); - dm1 = pv.distance(rp1, rp1, false, true); - dm2 = pv.distance(rp2, rp2, false, true); - doa1 = Math.abs(do1); - doa2 = Math.abs(do2); - dt = doa1 + doa2; - - if (dt === 0) { - xUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); - return; - } - - xUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); - }, - - // Slope of line normal to this - normalSlope: Number.NEGATIVE_INFINITY, - - // Sets the point 'p' relative to point 'rp' - // by the distance 'd'. - // - // See APPENDIX on SETRELATIVE at the bottom of this file. - // - // p ... point to set - // rp ... reference point - // d ... distance on projection vector - // pv ... projection vector (undefined = this) - // org ... if true, uses the original position of rp as reference. - setRelative: function (p, rp, d, pv, org) { - if (!pv || pv === this) { - p.x = (org ? rp.xo : rp.x) + d; - return; - } - - var rpx = org ? rp.xo : rp.x; - var rpy = org ? rp.yo : rp.y; - var rpdx = rpx + d * pv.x; - var rpdy = rpy + d * pv.y; - - p.x = rpdx + (p.y - rpdy) / pv.normalSlope; - }, - - // Slope of vector line. - slope: 0, - - // Touches the point p. - touch: function (p) { - p.xTouched = true; - }, - - // Tests if a point p is touched. - touched: function (p) { - return p.xTouched; - }, - - // Untouches the point p. - untouch: function (p) { - p.xTouched = false; - } - }; - - /* - * Unit vector of y-axis. - */ - var yUnitVector = { - x: 0, - - y: 1, - - axis: 'y', - - // Gets the projected distance between two points. - // o1/o2 ... if true, respective original position is used. - distance: function (p1, p2, o1, o2) { - return (o1 ? p1.yo : p1.y) - (o2 ? p2.yo : p2.y); - }, - - // Moves point p so the moved position has the same relative - // position to the moved positions of rp1 and rp2 than the - // original positions had. - // - // See APPENDIX on INTERPOLATE at the bottom of this file. - interpolate: function (p, rp1, rp2, pv) { - var do1; - var do2; - var doa1; - var doa2; - var dm1; - var dm2; - var dt; - - if (!pv || pv === this) { - do1 = p.yo - rp1.yo; - do2 = p.yo - rp2.yo; - dm1 = rp1.y - rp1.yo; - dm2 = rp2.y - rp2.yo; - doa1 = Math.abs(do1); - doa2 = Math.abs(do2); - dt = doa1 + doa2; - - if (dt === 0) { - p.y = p.yo + (dm1 + dm2) / 2; - return; - } - - p.y = p.yo + (dm1 * doa2 + dm2 * doa1) / dt; - return; - } - - do1 = pv.distance(p, rp1, true, true); - do2 = pv.distance(p, rp2, true, true); - dm1 = pv.distance(rp1, rp1, false, true); - dm2 = pv.distance(rp2, rp2, false, true); - doa1 = Math.abs(do1); - doa2 = Math.abs(do2); - dt = doa1 + doa2; - - if (dt === 0) { - yUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); - return; - } - - yUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); - }, - - // Slope of line normal to this. - normalSlope: 0, - - // Sets the point 'p' relative to point 'rp' - // by the distance 'd' - // - // See APPENDIX on SETRELATIVE at the bottom of this file. - // - // p ... point to set - // rp ... reference point - // d ... distance on projection vector - // pv ... projection vector (undefined = this) - // org ... if true, uses the original position of rp as reference. - setRelative: function (p, rp, d, pv, org) { - if (!pv || pv === this) { - p.y = (org ? rp.yo : rp.y) + d; - return; - } - - var rpx = org ? rp.xo : rp.x; - var rpy = org ? rp.yo : rp.y; - var rpdx = rpx + d * pv.x; - var rpdy = rpy + d * pv.y; - - p.y = rpdy + pv.normalSlope * (p.x - rpdx); - }, - - // Slope of vector line. - slope: Number.POSITIVE_INFINITY, - - // Touches the point p. - touch: function (p) { - p.yTouched = true; - }, - - // Tests if a point p is touched. - touched: function (p) { - return p.yTouched; - }, - - // Untouches the point p. - untouch: function (p) { - p.yTouched = false; - } - }; - - Object.freeze(xUnitVector); - Object.freeze(yUnitVector); - - /* - * Creates a unit vector that is not x- or y-axis. - */ - function UnitVector(x, y) { - this.x = x; - this.y = y; - this.axis = undefined; - this.slope = y / x; - this.normalSlope = -x / y; - Object.freeze(this); - } - - /* - * Gets the projected distance between two points. - * o1/o2 ... if true, respective original position is used. - */ - UnitVector.prototype.distance = function(p1, p2, o1, o2) { - return ( - this.x * xUnitVector.distance(p1, p2, o1, o2) + - this.y * yUnitVector.distance(p1, p2, o1, o2) - ); - }; - - /* - * Moves point p so the moved position has the same relative - * position to the moved positions of rp1 and rp2 than the - * original positions had. - * - * See APPENDIX on INTERPOLATE at the bottom of this file. - */ - UnitVector.prototype.interpolate = function(p, rp1, rp2, pv) { - var dm1; - var dm2; - var do1; - var do2; - var doa1; - var doa2; - var dt; - - do1 = pv.distance(p, rp1, true, true); - do2 = pv.distance(p, rp2, true, true); - dm1 = pv.distance(rp1, rp1, false, true); - dm2 = pv.distance(rp2, rp2, false, true); - doa1 = Math.abs(do1); - doa2 = Math.abs(do2); - dt = doa1 + doa2; - - if (dt === 0) { - this.setRelative(p, p, (dm1 + dm2) / 2, pv, true); - return; - } - - this.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); - }; - - /* - * Sets the point 'p' relative to point 'rp' - * by the distance 'd' - * - * See APPENDIX on SETRELATIVE at the bottom of this file. - * - * p ... point to set - * rp ... reference point - * d ... distance on projection vector - * pv ... projection vector (undefined = this) - * org ... if true, uses the original position of rp as reference. - */ - UnitVector.prototype.setRelative = function(p, rp, d, pv, org) { - pv = pv || this; - - var rpx = org ? rp.xo : rp.x; - var rpy = org ? rp.yo : rp.y; - var rpdx = rpx + d * pv.x; - var rpdy = rpy + d * pv.y; - - var pvns = pv.normalSlope; - var fvs = this.slope; - - var px = p.x; - var py = p.y; - - p.x = (fvs * px - pvns * rpdx + rpdy - py) / (fvs - pvns); - p.y = fvs * (p.x - px) + py; - }; - - /* - * Touches the point p. - */ - UnitVector.prototype.touch = function(p) { - p.xTouched = true; - p.yTouched = true; - }; - - /* - * Returns a unit vector with x/y coordinates. - */ - function getUnitVector(x, y) { - var d = Math.sqrt(x * x + y * y); - - x /= d; - y /= d; - - if (x === 1 && y === 0) { return xUnitVector; } - else if (x === 0 && y === 1) { return yUnitVector; } - else { return new UnitVector(x, y); } - } - - /* - * Creates a point in the hinting engine. - */ - function HPoint( - x, - y, - lastPointOfContour, - onCurve - ) { - this.x = this.xo = Math.round(x * 64) / 64; // hinted x value and original x-value - this.y = this.yo = Math.round(y * 64) / 64; // hinted y value and original y-value - - this.lastPointOfContour = lastPointOfContour; - this.onCurve = onCurve; - this.prevPointOnContour = undefined; - this.nextPointOnContour = undefined; - this.xTouched = false; - this.yTouched = false; - - Object.preventExtensions(this); - } - - /* - * Returns the next touched point on the contour. - * - * v ... unit vector to test touch axis. - */ - HPoint.prototype.nextTouched = function(v) { - var p = this.nextPointOnContour; - - while (!v.touched(p) && p !== this) { p = p.nextPointOnContour; } - - return p; - }; - - /* - * Returns the previous touched point on the contour - * - * v ... unit vector to test touch axis. - */ - HPoint.prototype.prevTouched = function(v) { - var p = this.prevPointOnContour; - - while (!v.touched(p) && p !== this) { p = p.prevPointOnContour; } - - return p; - }; - - /* - * The zero point. - */ - var HPZero = Object.freeze(new HPoint(0, 0)); - - /* - * The default state of the interpreter. - * - * Note: Freezing the defaultState and then deriving from it - * makes the V8 Javascript engine going awkward, - * so this is avoided, albeit the defaultState shouldn't - * ever change. - */ - var defaultState = { - cvCutIn: 17 / 16, // control value cut in - deltaBase: 9, - deltaShift: 0.125, - loop: 1, // loops some instructions - minDis: 1, // minimum distance - autoFlip: true - }; - - /* - * The current state of the interpreter. - * - * env ... 'fpgm' or 'prep' or 'glyf' - * prog ... the program - */ - function State(env, prog) { - this.env = env; - this.stack = []; - this.prog = prog; - - switch (env) { - case 'glyf' : - this.zp0 = this.zp1 = this.zp2 = 1; - this.rp0 = this.rp1 = this.rp2 = 0; - /* fall through */ - case 'prep' : - this.fv = this.pv = this.dpv = xUnitVector; - this.round = roundToGrid; - } - } - - /* - * Executes a glyph program. - * - * This does the hinting for each glyph. - * - * Returns an array of moved points. - * - * glyph: the glyph to hint - * ppem: the size the glyph is rendered for - */ - Hinting.prototype.exec = function(glyph, ppem) { - if (typeof ppem !== 'number') { - throw new Error('Point size is not a number!'); - } - - // Received a fatal error, don't do any hinting anymore. - if (this._errorState > 2) { return; } - - var font = this.font; - var prepState = this._prepState; - - if (!prepState || prepState.ppem !== ppem) { - var fpgmState = this._fpgmState; - - if (!fpgmState) { - // Executes the fpgm state. - // This is used by fonts to define functions. - State.prototype = defaultState; - - fpgmState = - this._fpgmState = - new State('fpgm', font.tables.fpgm); - - fpgmState.funcs = [ ]; - fpgmState.font = font; - - if (exports.DEBUG) { - console.log('---EXEC FPGM---'); - fpgmState.step = -1; - } - - try { - exec(fpgmState); - } catch (e) { - console.log('Hinting error in FPGM:' + e); - this._errorState = 3; - return; - } - } - - // Executes the prep program for this ppem setting. - // This is used by fonts to set cvt values - // depending on to be rendered font size. - - State.prototype = fpgmState; - prepState = - this._prepState = - new State('prep', font.tables.prep); - - prepState.ppem = ppem; - - // Creates a copy of the cvt table - // and scales it to the current ppem setting. - var oCvt = font.tables.cvt; - if (oCvt) { - var cvt = prepState.cvt = new Array(oCvt.length); - var scale = ppem / font.unitsPerEm; - for (var c = 0; c < oCvt.length; c++) { - cvt[c] = oCvt[c] * scale; - } - } else { - prepState.cvt = []; - } - - if (exports.DEBUG) { - console.log('---EXEC PREP---'); - prepState.step = -1; - } - - try { - exec(prepState); - } catch (e) { - if (this._errorState < 2) { - console.log('Hinting error in PREP:' + e); - } - this._errorState = 2; - } - } - - if (this._errorState > 1) { return; } - - try { - return execGlyph(glyph, prepState); - } catch (e) { - if (this._errorState < 1) { - console.log('Hinting error:' + e); - console.log('Note: further hinting errors are silenced'); - } - this._errorState = 1; - return undefined; - } - }; - - /* - * Executes the hinting program for a glyph. - */ - execGlyph = function(glyph, prepState) { - // original point positions - var xScale = prepState.ppem / prepState.font.unitsPerEm; - var yScale = xScale; - var components = glyph.components; - var contours; - var gZone; - var state; - - State.prototype = prepState; - if (!components) { - state = new State('glyf', glyph.instructions); - if (exports.DEBUG) { - console.log('---EXEC GLYPH---'); - state.step = -1; - } - execComponent(glyph, state, xScale, yScale); - gZone = state.gZone; - } else { - var font = prepState.font; - gZone = []; - contours = []; - for (var i = 0; i < components.length; i++) { - var c = components[i]; - var cg = font.glyphs.get(c.glyphIndex); - - state = new State('glyf', cg.instructions); - - if (exports.DEBUG) { - console.log('---EXEC COMP ' + i + '---'); - state.step = -1; - } - - execComponent(cg, state, xScale, yScale); - // appends the computed points to the result array - // post processes the component points - var dx = Math.round(c.dx * xScale); - var dy = Math.round(c.dy * yScale); - var gz = state.gZone; - var cc = state.contours; - for (var pi = 0; pi < gz.length; pi++) { - var p = gz[pi]; - p.xTouched = p.yTouched = false; - p.xo = p.x = p.x + dx; - p.yo = p.y = p.y + dy; - } - - var gLen = gZone.length; - gZone.push.apply(gZone, gz); - for (var j = 0; j < cc.length; j++) { - contours.push(cc[j] + gLen); - } - } - - if (glyph.instructions && !state.inhibitGridFit) { - // the composite has instructions on its own - state = new State('glyf', glyph.instructions); - - state.gZone = state.z0 = state.z1 = state.z2 = gZone; - - state.contours = contours; - - // note: HPZero cannot be used here, since - // the point might be modified - gZone.push( - new HPoint(0, 0), - new HPoint(Math.round(glyph.advanceWidth * xScale), 0) - ); - - if (exports.DEBUG) { - console.log('---EXEC COMPOSITE---'); - state.step = -1; - } - - exec(state); - - gZone.length -= 2; - } - } - - return gZone; - }; - - /* - * Executes the hinting program for a component of a multi-component glyph - * or of the glyph itself for a non-component glyph. - */ - execComponent = function(glyph, state, xScale, yScale) - { - var points = glyph.points || []; - var pLen = points.length; - var gZone = state.gZone = state.z0 = state.z1 = state.z2 = []; - var contours = state.contours = []; - - // Scales the original points and - // makes copies for the hinted points. - var cp; // current point - for (var i = 0; i < pLen; i++) { - cp = points[i]; - - gZone[i] = new HPoint( - cp.x * xScale, - cp.y * yScale, - cp.lastPointOfContour, - cp.onCurve - ); - } - - // Chain links the contours. - var sp; // start point - var np; // next point - - for (var i$1 = 0; i$1 < pLen; i$1++) { - cp = gZone[i$1]; - - if (!sp) { - sp = cp; - contours.push(i$1); - } - - if (cp.lastPointOfContour) { - cp.nextPointOnContour = sp; - sp.prevPointOnContour = cp; - sp = undefined; - } else { - np = gZone[i$1 + 1]; - cp.nextPointOnContour = np; - np.prevPointOnContour = cp; - } - } - - if (state.inhibitGridFit) { return; } - - if (exports.DEBUG) { - console.log('PROCESSING GLYPH', state.stack); - for (var i$2 = 0; i$2 < pLen; i$2++) { - console.log(i$2, gZone[i$2].x, gZone[i$2].y); - } - } - - gZone.push( - new HPoint(0, 0), - new HPoint(Math.round(glyph.advanceWidth * xScale), 0) - ); - - exec(state); - - // Removes the extra points. - gZone.length -= 2; - - if (exports.DEBUG) { - console.log('FINISHED GLYPH', state.stack); - for (var i$3 = 0; i$3 < pLen; i$3++) { - console.log(i$3, gZone[i$3].x, gZone[i$3].y); - } - } - }; - - /* - * Executes the program loaded in state. - */ - exec = function(state) { - var prog = state.prog; - - if (!prog) { return; } - - var pLen = prog.length; - var ins; - - for (state.ip = 0; state.ip < pLen; state.ip++) { - if (exports.DEBUG) { state.step++; } - ins = instructionTable[prog[state.ip]]; - - if (!ins) { - throw new Error( - 'unknown instruction: 0x' + - Number(prog[state.ip]).toString(16) - ); - } - - ins(state); - - // very extensive debugging for each step - /* - if (exports.DEBUG) { - var da; - if (state.gZone) { - da = []; - for (let i = 0; i < state.gZone.length; i++) - { - da.push(i + ' ' + - state.gZone[i].x * 64 + ' ' + - state.gZone[i].y * 64 + ' ' + - (state.gZone[i].xTouched ? 'x' : '') + - (state.gZone[i].yTouched ? 'y' : '') - ); - } - console.log('GZ', da); - } - - if (state.tZone) { - da = []; - for (let i = 0; i < state.tZone.length; i++) { - da.push(i + ' ' + - state.tZone[i].x * 64 + ' ' + - state.tZone[i].y * 64 + ' ' + - (state.tZone[i].xTouched ? 'x' : '') + - (state.tZone[i].yTouched ? 'y' : '') - ); - } - console.log('TZ', da); - } - - if (state.stack.length > 10) { - console.log( - state.stack.length, - '...', state.stack.slice(state.stack.length - 10) - ); - } else { - console.log(state.stack.length, state.stack); - } - } - */ - } - }; - - /* - * Initializes the twilight zone. - * - * This is only done if a SZPx instruction - * refers to the twilight zone. - */ - function initTZone(state) - { - var tZone = state.tZone = new Array(state.gZone.length); - - // no idea if this is actually correct... - for (var i = 0; i < tZone.length; i++) - { - tZone[i] = new HPoint(0, 0); - } - } - - /* - * Skips the instruction pointer ahead over an IF/ELSE block. - * handleElse .. if true breaks on matching ELSE - */ - function skip(state, handleElse) - { - var prog = state.prog; - var ip = state.ip; - var nesting = 1; - var ins; - - do { - ins = prog[++ip]; - if (ins === 0x58) // IF - { nesting++; } - else if (ins === 0x59) // EIF - { nesting--; } - else if (ins === 0x40) // NPUSHB - { ip += prog[ip + 1] + 1; } - else if (ins === 0x41) // NPUSHW - { ip += 2 * prog[ip + 1] + 1; } - else if (ins >= 0xB0 && ins <= 0xB7) // PUSHB - { ip += ins - 0xB0 + 1; } - else if (ins >= 0xB8 && ins <= 0xBF) // PUSHW - { ip += (ins - 0xB8 + 1) * 2; } - else if (handleElse && nesting === 1 && ins === 0x1B) // ELSE - { break; } - } while (nesting > 0); - - state.ip = ip; - } - - /*----------------------------------------------------------* - * And then a lot of instructions... * - *----------------------------------------------------------*/ - - // SVTCA[a] Set freedom and projection Vectors To Coordinate Axis - // 0x00-0x01 - function SVTCA(v, state) { - if (exports.DEBUG) { console.log(state.step, 'SVTCA[' + v.axis + ']'); } - - state.fv = state.pv = state.dpv = v; - } - - // SPVTCA[a] Set Projection Vector to Coordinate Axis - // 0x02-0x03 - function SPVTCA(v, state) { - if (exports.DEBUG) { console.log(state.step, 'SPVTCA[' + v.axis + ']'); } - - state.pv = state.dpv = v; - } - - // SFVTCA[a] Set Freedom Vector to Coordinate Axis - // 0x04-0x05 - function SFVTCA(v, state) { - if (exports.DEBUG) { console.log(state.step, 'SFVTCA[' + v.axis + ']'); } - - state.fv = v; - } - - // SPVTL[a] Set Projection Vector To Line - // 0x06-0x07 - function SPVTL(a, state) { - var stack = state.stack; - var p2i = stack.pop(); - var p1i = stack.pop(); - var p2 = state.z2[p2i]; - var p1 = state.z1[p1i]; - - if (exports.DEBUG) { console.log('SPVTL[' + a + ']', p2i, p1i); } - - var dx; - var dy; - - if (!a) { - dx = p1.x - p2.x; - dy = p1.y - p2.y; - } else { - dx = p2.y - p1.y; - dy = p1.x - p2.x; - } - - state.pv = state.dpv = getUnitVector(dx, dy); - } - - // SFVTL[a] Set Freedom Vector To Line - // 0x08-0x09 - function SFVTL(a, state) { - var stack = state.stack; - var p2i = stack.pop(); - var p1i = stack.pop(); - var p2 = state.z2[p2i]; - var p1 = state.z1[p1i]; - - if (exports.DEBUG) { console.log('SFVTL[' + a + ']', p2i, p1i); } - - var dx; - var dy; - - if (!a) { - dx = p1.x - p2.x; - dy = p1.y - p2.y; - } else { - dx = p2.y - p1.y; - dy = p1.x - p2.x; - } - - state.fv = getUnitVector(dx, dy); - } - - // SPVFS[] Set Projection Vector From Stack - // 0x0A - function SPVFS(state) { - var stack = state.stack; - var y = stack.pop(); - var x = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } - - state.pv = state.dpv = getUnitVector(x, y); - } - - // SFVFS[] Set Freedom Vector From Stack - // 0x0B - function SFVFS(state) { - var stack = state.stack; - var y = stack.pop(); - var x = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } - - state.fv = getUnitVector(x, y); - } - - // GPV[] Get Projection Vector - // 0x0C - function GPV(state) { - var stack = state.stack; - var pv = state.pv; - - if (exports.DEBUG) { console.log(state.step, 'GPV[]'); } - - stack.push(pv.x * 0x4000); - stack.push(pv.y * 0x4000); - } - - // GFV[] Get Freedom Vector - // 0x0C - function GFV(state) { - var stack = state.stack; - var fv = state.fv; - - if (exports.DEBUG) { console.log(state.step, 'GFV[]'); } - - stack.push(fv.x * 0x4000); - stack.push(fv.y * 0x4000); - } - - // SFVTPV[] Set Freedom Vector To Projection Vector - // 0x0E - function SFVTPV(state) { - state.fv = state.pv; - - if (exports.DEBUG) { console.log(state.step, 'SFVTPV[]'); } - } - - // ISECT[] moves point p to the InterSECTion of two lines - // 0x0F - function ISECT(state) - { - var stack = state.stack; - var pa0i = stack.pop(); - var pa1i = stack.pop(); - var pb0i = stack.pop(); - var pb1i = stack.pop(); - var pi = stack.pop(); - var z0 = state.z0; - var z1 = state.z1; - var pa0 = z0[pa0i]; - var pa1 = z0[pa1i]; - var pb0 = z1[pb0i]; - var pb1 = z1[pb1i]; - var p = state.z2[pi]; - - if (exports.DEBUG) { console.log('ISECT[], ', pa0i, pa1i, pb0i, pb1i, pi); } - - // math from - // en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line - - var x1 = pa0.x; - var y1 = pa0.y; - var x2 = pa1.x; - var y2 = pa1.y; - var x3 = pb0.x; - var y3 = pb0.y; - var x4 = pb1.x; - var y4 = pb1.y; - - var div = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); - var f1 = x1 * y2 - y1 * x2; - var f2 = x3 * y4 - y3 * x4; - - p.x = (f1 * (x3 - x4) - f2 * (x1 - x2)) / div; - p.y = (f1 * (y3 - y4) - f2 * (y1 - y2)) / div; - } - - // SRP0[] Set Reference Point 0 - // 0x10 - function SRP0(state) { - state.rp0 = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SRP0[]', state.rp0); } - } - - // SRP1[] Set Reference Point 1 - // 0x11 - function SRP1(state) { - state.rp1 = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SRP1[]', state.rp1); } - } - - // SRP1[] Set Reference Point 2 - // 0x12 - function SRP2(state) { - state.rp2 = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SRP2[]', state.rp2); } - } - - // SZP0[] Set Zone Pointer 0 - // 0x13 - function SZP0(state) { - var n = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SZP0[]', n); } - - state.zp0 = n; - - switch (n) { - case 0: - if (!state.tZone) { initTZone(state); } - state.z0 = state.tZone; - break; - case 1 : - state.z0 = state.gZone; - break; - default : - throw new Error('Invalid zone pointer'); - } - } - - // SZP1[] Set Zone Pointer 1 - // 0x14 - function SZP1(state) { - var n = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SZP1[]', n); } - - state.zp1 = n; - - switch (n) { - case 0: - if (!state.tZone) { initTZone(state); } - state.z1 = state.tZone; - break; - case 1 : - state.z1 = state.gZone; - break; - default : - throw new Error('Invalid zone pointer'); - } - } - - // SZP2[] Set Zone Pointer 2 - // 0x15 - function SZP2(state) { - var n = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SZP2[]', n); } - - state.zp2 = n; - - switch (n) { - case 0: - if (!state.tZone) { initTZone(state); } - state.z2 = state.tZone; - break; - case 1 : - state.z2 = state.gZone; - break; - default : - throw new Error('Invalid zone pointer'); - } - } - - // SZPS[] Set Zone PointerS - // 0x16 - function SZPS(state) { - var n = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SZPS[]', n); } - - state.zp0 = state.zp1 = state.zp2 = n; - - switch (n) { - case 0: - if (!state.tZone) { initTZone(state); } - state.z0 = state.z1 = state.z2 = state.tZone; - break; - case 1 : - state.z0 = state.z1 = state.z2 = state.gZone; - break; - default : - throw new Error('Invalid zone pointer'); - } - } - - // SLOOP[] Set LOOP variable - // 0x17 - function SLOOP(state) { - state.loop = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SLOOP[]', state.loop); } - } - - // RTG[] Round To Grid - // 0x18 - function RTG(state) { - if (exports.DEBUG) { console.log(state.step, 'RTG[]'); } - - state.round = roundToGrid; - } - - // RTHG[] Round To Half Grid - // 0x19 - function RTHG(state) { - if (exports.DEBUG) { console.log(state.step, 'RTHG[]'); } - - state.round = roundToHalfGrid; - } - - // SMD[] Set Minimum Distance - // 0x1A - function SMD(state) { - var d = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SMD[]', d); } - - state.minDis = d / 0x40; - } - - // ELSE[] ELSE clause - // 0x1B - function ELSE(state) { - // This instruction has been reached by executing a then branch - // so it just skips ahead until matching EIF. - // - // In case the IF was negative the IF[] instruction already - // skipped forward over the ELSE[] - - if (exports.DEBUG) { console.log(state.step, 'ELSE[]'); } - - skip(state, false); - } - - // JMPR[] JuMP Relative - // 0x1C - function JMPR(state) { - var o = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'JMPR[]', o); } - - // A jump by 1 would do nothing. - state.ip += o - 1; - } - - // SCVTCI[] Set Control Value Table Cut-In - // 0x1D - function SCVTCI(state) { - var n = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SCVTCI[]', n); } - - state.cvCutIn = n / 0x40; - } - - // DUP[] DUPlicate top stack element - // 0x20 - function DUP(state) { - var stack = state.stack; - - if (exports.DEBUG) { console.log(state.step, 'DUP[]'); } - - stack.push(stack[stack.length - 1]); - } - - // POP[] POP top stack element - // 0x21 - function POP(state) { - if (exports.DEBUG) { console.log(state.step, 'POP[]'); } - - state.stack.pop(); - } - - // CLEAR[] CLEAR the stack - // 0x22 - function CLEAR(state) { - if (exports.DEBUG) { console.log(state.step, 'CLEAR[]'); } - - state.stack.length = 0; - } - - // SWAP[] SWAP the top two elements on the stack - // 0x23 - function SWAP(state) { - var stack = state.stack; - - var a = stack.pop(); - var b = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SWAP[]'); } - - stack.push(a); - stack.push(b); - } - - // DEPTH[] DEPTH of the stack - // 0x24 - function DEPTH(state) { - var stack = state.stack; - - if (exports.DEBUG) { console.log(state.step, 'DEPTH[]'); } - - stack.push(stack.length); - } - - // LOOPCALL[] LOOPCALL function - // 0x2A - function LOOPCALL(state) { - var stack = state.stack; - var fn = stack.pop(); - var c = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'LOOPCALL[]', fn, c); } - - // saves callers program - var cip = state.ip; - var cprog = state.prog; - - state.prog = state.funcs[fn]; - - // executes the function - for (var i = 0; i < c; i++) { - exec(state); - - if (exports.DEBUG) { console.log( - ++state.step, - i + 1 < c ? 'next loopcall' : 'done loopcall', - i - ); } - } - - // restores the callers program - state.ip = cip; - state.prog = cprog; - } - - // CALL[] CALL function - // 0x2B - function CALL(state) { - var fn = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'CALL[]', fn); } - - // saves callers program - var cip = state.ip; - var cprog = state.prog; - - state.prog = state.funcs[fn]; - - // executes the function - exec(state); - - // restores the callers program - state.ip = cip; - state.prog = cprog; - - if (exports.DEBUG) { console.log(++state.step, 'returning from', fn); } - } - - // CINDEX[] Copy the INDEXed element to the top of the stack - // 0x25 - function CINDEX(state) { - var stack = state.stack; - var k = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'CINDEX[]', k); } - - // In case of k == 1, it copies the last element after popping - // thus stack.length - k. - stack.push(stack[stack.length - k]); - } - - // MINDEX[] Move the INDEXed element to the top of the stack - // 0x26 - function MINDEX(state) { - var stack = state.stack; - var k = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'MINDEX[]', k); } - - stack.push(stack.splice(stack.length - k, 1)[0]); - } - - // FDEF[] Function DEFinition - // 0x2C - function FDEF(state) { - if (state.env !== 'fpgm') { throw new Error('FDEF not allowed here'); } - var stack = state.stack; - var prog = state.prog; - var ip = state.ip; - - var fn = stack.pop(); - var ipBegin = ip; - - if (exports.DEBUG) { console.log(state.step, 'FDEF[]', fn); } - - while (prog[++ip] !== 0x2D){ } - - state.ip = ip; - state.funcs[fn] = prog.slice(ipBegin + 1, ip); - } - - // MDAP[a] Move Direct Absolute Point - // 0x2E-0x2F - function MDAP(round, state) { - var pi = state.stack.pop(); - var p = state.z0[pi]; - var fv = state.fv; - var pv = state.pv; - - if (exports.DEBUG) { console.log(state.step, 'MDAP[' + round + ']', pi); } - - var d = pv.distance(p, HPZero); - - if (round) { d = state.round(d); } - - fv.setRelative(p, HPZero, d, pv); - fv.touch(p); - - state.rp0 = state.rp1 = pi; - } - - // IUP[a] Interpolate Untouched Points through the outline - // 0x30 - function IUP(v, state) { - var z2 = state.z2; - var pLen = z2.length - 2; - var cp; - var pp; - var np; - - if (exports.DEBUG) { console.log(state.step, 'IUP[' + v.axis + ']'); } - - for (var i = 0; i < pLen; i++) { - cp = z2[i]; // current point - - // if this point has been touched go on - if (v.touched(cp)) { continue; } - - pp = cp.prevTouched(v); - - // no point on the contour has been touched? - if (pp === cp) { continue; } - - np = cp.nextTouched(v); - - if (pp === np) { - // only one point on the contour has been touched - // so simply moves the point like that - - v.setRelative(cp, cp, v.distance(pp, pp, false, true), v, true); - } - - v.interpolate(cp, pp, np, v); - } - } - - // SHP[] SHift Point using reference point - // 0x32-0x33 - function SHP(a, state) { - var stack = state.stack; - var rpi = a ? state.rp1 : state.rp2; - var rp = (a ? state.z0 : state.z1)[rpi]; - var fv = state.fv; - var pv = state.pv; - var loop = state.loop; - var z2 = state.z2; - - while (loop--) - { - var pi = stack.pop(); - var p = z2[pi]; - - var d = pv.distance(rp, rp, false, true); - fv.setRelative(p, p, d, pv); - fv.touch(p); - - if (exports.DEBUG) { - console.log( - state.step, - (state.loop > 1 ? - 'loop ' + (state.loop - loop) + ': ' : - '' - ) + - 'SHP[' + (a ? 'rp1' : 'rp2') + ']', pi - ); - } - } - - state.loop = 1; - } - - // SHC[] SHift Contour using reference point - // 0x36-0x37 - function SHC(a, state) { - var stack = state.stack; - var rpi = a ? state.rp1 : state.rp2; - var rp = (a ? state.z0 : state.z1)[rpi]; - var fv = state.fv; - var pv = state.pv; - var ci = stack.pop(); - var sp = state.z2[state.contours[ci]]; - var p = sp; - - if (exports.DEBUG) { console.log(state.step, 'SHC[' + a + ']', ci); } - - var d = pv.distance(rp, rp, false, true); - - do { - if (p !== rp) { fv.setRelative(p, p, d, pv); } - p = p.nextPointOnContour; - } while (p !== sp); - } - - // SHZ[] SHift Zone using reference point - // 0x36-0x37 - function SHZ(a, state) { - var stack = state.stack; - var rpi = a ? state.rp1 : state.rp2; - var rp = (a ? state.z0 : state.z1)[rpi]; - var fv = state.fv; - var pv = state.pv; - - var e = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SHZ[' + a + ']', e); } - - var z; - switch (e) { - case 0 : z = state.tZone; break; - case 1 : z = state.gZone; break; - default : throw new Error('Invalid zone'); - } - - var p; - var d = pv.distance(rp, rp, false, true); - var pLen = z.length - 2; - for (var i = 0; i < pLen; i++) - { - p = z[i]; - fv.setRelative(p, p, d, pv); - //if (p !== rp) fv.setRelative(p, p, d, pv); - } - } - - // SHPIX[] SHift point by a PIXel amount - // 0x38 - function SHPIX(state) { - var stack = state.stack; - var loop = state.loop; - var fv = state.fv; - var d = stack.pop() / 0x40; - var z2 = state.z2; - - while (loop--) { - var pi = stack.pop(); - var p = z2[pi]; - - if (exports.DEBUG) { - console.log( - state.step, - (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + - 'SHPIX[]', pi, d - ); - } - - fv.setRelative(p, p, d); - fv.touch(p); - } - - state.loop = 1; - } - - // IP[] Interpolate Point - // 0x39 - function IP(state) { - var stack = state.stack; - var rp1i = state.rp1; - var rp2i = state.rp2; - var loop = state.loop; - var rp1 = state.z0[rp1i]; - var rp2 = state.z1[rp2i]; - var fv = state.fv; - var pv = state.dpv; - var z2 = state.z2; - - while (loop--) { - var pi = stack.pop(); - var p = z2[pi]; - - if (exports.DEBUG) { - console.log( - state.step, - (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + - 'IP[]', pi, rp1i, '<->', rp2i - ); - } - - fv.interpolate(p, rp1, rp2, pv); - - fv.touch(p); - } - - state.loop = 1; - } - - // MSIRP[a] Move Stack Indirect Relative Point - // 0x3A-0x3B - function MSIRP(a, state) { - var stack = state.stack; - var d = stack.pop() / 64; - var pi = stack.pop(); - var p = state.z1[pi]; - var rp0 = state.z0[state.rp0]; - var fv = state.fv; - var pv = state.pv; - - fv.setRelative(p, rp0, d, pv); - fv.touch(p); - - if (exports.DEBUG) { console.log(state.step, 'MSIRP[' + a + ']', d, pi); } - - state.rp1 = state.rp0; - state.rp2 = pi; - if (a) { state.rp0 = pi; } - } - - // ALIGNRP[] Align to reference point. - // 0x3C - function ALIGNRP(state) { - var stack = state.stack; - var rp0i = state.rp0; - var rp0 = state.z0[rp0i]; - var loop = state.loop; - var fv = state.fv; - var pv = state.pv; - var z1 = state.z1; - - while (loop--) { - var pi = stack.pop(); - var p = z1[pi]; - - if (exports.DEBUG) { - console.log( - state.step, - (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + - 'ALIGNRP[]', pi - ); - } - - fv.setRelative(p, rp0, 0, pv); - fv.touch(p); - } - - state.loop = 1; - } - - // RTG[] Round To Double Grid - // 0x3D - function RTDG(state) { - if (exports.DEBUG) { console.log(state.step, 'RTDG[]'); } - - state.round = roundToDoubleGrid; - } - - // MIAP[a] Move Indirect Absolute Point - // 0x3E-0x3F - function MIAP(round, state) { - var stack = state.stack; - var n = stack.pop(); - var pi = stack.pop(); - var p = state.z0[pi]; - var fv = state.fv; - var pv = state.pv; - var cv = state.cvt[n]; - - if (exports.DEBUG) { - console.log( - state.step, - 'MIAP[' + round + ']', - n, '(', cv, ')', pi - ); - } - - var d = pv.distance(p, HPZero); - - if (round) { - if (Math.abs(d - cv) < state.cvCutIn) { d = cv; } - - d = state.round(d); - } - - fv.setRelative(p, HPZero, d, pv); - - if (state.zp0 === 0) { - p.xo = p.x; - p.yo = p.y; - } - - fv.touch(p); - - state.rp0 = state.rp1 = pi; - } - - // NPUSB[] PUSH N Bytes - // 0x40 - function NPUSHB(state) { - var prog = state.prog; - var ip = state.ip; - var stack = state.stack; - - var n = prog[++ip]; - - if (exports.DEBUG) { console.log(state.step, 'NPUSHB[]', n); } - - for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } - - state.ip = ip; - } - - // NPUSHW[] PUSH N Words - // 0x41 - function NPUSHW(state) { - var ip = state.ip; - var prog = state.prog; - var stack = state.stack; - var n = prog[++ip]; - - if (exports.DEBUG) { console.log(state.step, 'NPUSHW[]', n); } - - for (var i = 0; i < n; i++) { - var w = (prog[++ip] << 8) | prog[++ip]; - if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } - stack.push(w); - } - - state.ip = ip; - } - - // WS[] Write Store - // 0x42 - function WS(state) { - var stack = state.stack; - var store = state.store; - - if (!store) { store = state.store = []; } - - var v = stack.pop(); - var l = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'WS', v, l); } - - store[l] = v; - } - - // RS[] Read Store - // 0x43 - function RS(state) { - var stack = state.stack; - var store = state.store; - - var l = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'RS', l); } - - var v = (store && store[l]) || 0; - - stack.push(v); - } - - // WCVTP[] Write Control Value Table in Pixel units - // 0x44 - function WCVTP(state) { - var stack = state.stack; - - var v = stack.pop(); - var l = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'WCVTP', v, l); } - - state.cvt[l] = v / 0x40; - } - - // RCVT[] Read Control Value Table entry - // 0x45 - function RCVT(state) { - var stack = state.stack; - var cvte = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'RCVT', cvte); } - - stack.push(state.cvt[cvte] * 0x40); - } - - // GC[] Get Coordinate projected onto the projection vector - // 0x46-0x47 - function GC(a, state) { - var stack = state.stack; - var pi = stack.pop(); - var p = state.z2[pi]; - - if (exports.DEBUG) { console.log(state.step, 'GC[' + a + ']', pi); } - - stack.push(state.dpv.distance(p, HPZero, a, false) * 0x40); - } - - // MD[a] Measure Distance - // 0x49-0x4A - function MD(a, state) { - var stack = state.stack; - var pi2 = stack.pop(); - var pi1 = stack.pop(); - var p2 = state.z1[pi2]; - var p1 = state.z0[pi1]; - var d = state.dpv.distance(p1, p2, a, a); - - if (exports.DEBUG) { console.log(state.step, 'MD[' + a + ']', pi2, pi1, '->', d); } - - state.stack.push(Math.round(d * 64)); - } - - // MPPEM[] Measure Pixels Per EM - // 0x4B - function MPPEM(state) { - if (exports.DEBUG) { console.log(state.step, 'MPPEM[]'); } - state.stack.push(state.ppem); - } - - // FLIPON[] set the auto FLIP Boolean to ON - // 0x4D - function FLIPON(state) { - if (exports.DEBUG) { console.log(state.step, 'FLIPON[]'); } - state.autoFlip = true; - } - - // LT[] Less Than - // 0x50 - function LT(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'LT[]', e2, e1); } - - stack.push(e1 < e2 ? 1 : 0); - } - - // LTEQ[] Less Than or EQual - // 0x53 - function LTEQ(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'LTEQ[]', e2, e1); } - - stack.push(e1 <= e2 ? 1 : 0); - } - - // GTEQ[] Greater Than - // 0x52 - function GT(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'GT[]', e2, e1); } - - stack.push(e1 > e2 ? 1 : 0); - } - - // GTEQ[] Greater Than or EQual - // 0x53 - function GTEQ(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'GTEQ[]', e2, e1); } - - stack.push(e1 >= e2 ? 1 : 0); - } - - // EQ[] EQual - // 0x54 - function EQ(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'EQ[]', e2, e1); } - - stack.push(e2 === e1 ? 1 : 0); - } - - // NEQ[] Not EQual - // 0x55 - function NEQ(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'NEQ[]', e2, e1); } - - stack.push(e2 !== e1 ? 1 : 0); - } - - // ODD[] ODD - // 0x56 - function ODD(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'ODD[]', n); } - - stack.push(Math.trunc(n) % 2 ? 1 : 0); - } - - // EVEN[] EVEN - // 0x57 - function EVEN(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'EVEN[]', n); } - - stack.push(Math.trunc(n) % 2 ? 0 : 1); - } - - // IF[] IF test - // 0x58 - function IF(state) { - var test = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'IF[]', test); } - - // if test is true it just continues - // if not the ip is skipped until matching ELSE or EIF - if (!test) { - skip(state, true); - - if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } - } - } - - // EIF[] End IF - // 0x59 - function EIF(state) { - // this can be reached normally when - // executing an else branch. - // -> just ignore it - - if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } - } - - // AND[] logical AND - // 0x5A - function AND(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'AND[]', e2, e1); } - - stack.push(e2 && e1 ? 1 : 0); - } - - // OR[] logical OR - // 0x5B - function OR(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'OR[]', e2, e1); } - - stack.push(e2 || e1 ? 1 : 0); - } - - // NOT[] logical NOT - // 0x5C - function NOT(state) { - var stack = state.stack; - var e = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'NOT[]', e); } - - stack.push(e ? 0 : 1); - } - - // DELTAP1[] DELTA exception P1 - // DELTAP2[] DELTA exception P2 - // DELTAP3[] DELTA exception P3 - // 0x5D, 0x71, 0x72 - function DELTAP123(b, state) { - var stack = state.stack; - var n = stack.pop(); - var fv = state.fv; - var pv = state.pv; - var ppem = state.ppem; - var base = state.deltaBase + (b - 1) * 16; - var ds = state.deltaShift; - var z0 = state.z0; - - if (exports.DEBUG) { console.log(state.step, 'DELTAP[' + b + ']', n, stack); } - - for (var i = 0; i < n; i++) { - var pi = stack.pop(); - var arg = stack.pop(); - var appem = base + ((arg & 0xF0) >> 4); - if (appem !== ppem) { continue; } - - var mag = (arg & 0x0F) - 8; - if (mag >= 0) { mag++; } - if (exports.DEBUG) { console.log(state.step, 'DELTAPFIX', pi, 'by', mag * ds); } - - var p = z0[pi]; - fv.setRelative(p, p, mag * ds, pv); - } - } - - // SDB[] Set Delta Base in the graphics state - // 0x5E - function SDB(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SDB[]', n); } - - state.deltaBase = n; - } - - // SDS[] Set Delta Shift in the graphics state - // 0x5F - function SDS(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SDS[]', n); } - - state.deltaShift = Math.pow(0.5, n); - } - - // ADD[] ADD - // 0x60 - function ADD(state) { - var stack = state.stack; - var n2 = stack.pop(); - var n1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'ADD[]', n2, n1); } - - stack.push(n1 + n2); - } - - // SUB[] SUB - // 0x61 - function SUB(state) { - var stack = state.stack; - var n2 = stack.pop(); - var n1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SUB[]', n2, n1); } - - stack.push(n1 - n2); - } - - // DIV[] DIV - // 0x62 - function DIV(state) { - var stack = state.stack; - var n2 = stack.pop(); - var n1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'DIV[]', n2, n1); } - - stack.push(n1 * 64 / n2); - } - - // MUL[] MUL - // 0x63 - function MUL(state) { - var stack = state.stack; - var n2 = stack.pop(); - var n1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'MUL[]', n2, n1); } - - stack.push(n1 * n2 / 64); - } - - // ABS[] ABSolute value - // 0x64 - function ABS(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'ABS[]', n); } - - stack.push(Math.abs(n)); - } - - // NEG[] NEGate - // 0x65 - function NEG(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'NEG[]', n); } - - stack.push(-n); - } - - // FLOOR[] FLOOR - // 0x66 - function FLOOR(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'FLOOR[]', n); } - - stack.push(Math.floor(n / 0x40) * 0x40); - } - - // CEILING[] CEILING - // 0x67 - function CEILING(state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'CEILING[]', n); } - - stack.push(Math.ceil(n / 0x40) * 0x40); - } - - // ROUND[ab] ROUND value - // 0x68-0x6B - function ROUND(dt, state) { - var stack = state.stack; - var n = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'ROUND[]'); } - - stack.push(state.round(n / 0x40) * 0x40); - } - - // WCVTF[] Write Control Value Table in Funits - // 0x70 - function WCVTF(state) { - var stack = state.stack; - var v = stack.pop(); - var l = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'WCVTF[]', v, l); } - - state.cvt[l] = v * state.ppem / state.font.unitsPerEm; - } - - // DELTAC1[] DELTA exception C1 - // DELTAC2[] DELTA exception C2 - // DELTAC3[] DELTA exception C3 - // 0x73, 0x74, 0x75 - function DELTAC123(b, state) { - var stack = state.stack; - var n = stack.pop(); - var ppem = state.ppem; - var base = state.deltaBase + (b - 1) * 16; - var ds = state.deltaShift; - - if (exports.DEBUG) { console.log(state.step, 'DELTAC[' + b + ']', n, stack); } - - for (var i = 0; i < n; i++) { - var c = stack.pop(); - var arg = stack.pop(); - var appem = base + ((arg & 0xF0) >> 4); - if (appem !== ppem) { continue; } - - var mag = (arg & 0x0F) - 8; - if (mag >= 0) { mag++; } - - var delta = mag * ds; - - if (exports.DEBUG) { console.log(state.step, 'DELTACFIX', c, 'by', delta); } - - state.cvt[c] += delta; - } - } - - // SROUND[] Super ROUND - // 0x76 - function SROUND(state) { - var n = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'SROUND[]', n); } - - state.round = roundSuper; - - var period; - - switch (n & 0xC0) { - case 0x00: - period = 0.5; - break; - case 0x40: - period = 1; - break; - case 0x80: - period = 2; - break; - default: - throw new Error('invalid SROUND value'); - } - - state.srPeriod = period; - - switch (n & 0x30) { - case 0x00: - state.srPhase = 0; - break; - case 0x10: - state.srPhase = 0.25 * period; - break; - case 0x20: - state.srPhase = 0.5 * period; - break; - case 0x30: - state.srPhase = 0.75 * period; - break; - default: throw new Error('invalid SROUND value'); - } - - n &= 0x0F; - - if (n === 0) { state.srThreshold = 0; } - else { state.srThreshold = (n / 8 - 0.5) * period; } - } - - // S45ROUND[] Super ROUND 45 degrees - // 0x77 - function S45ROUND(state) { - var n = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'S45ROUND[]', n); } - - state.round = roundSuper; - - var period; - - switch (n & 0xC0) { - case 0x00: - period = Math.sqrt(2) / 2; - break; - case 0x40: - period = Math.sqrt(2); - break; - case 0x80: - period = 2 * Math.sqrt(2); - break; - default: - throw new Error('invalid S45ROUND value'); - } - - state.srPeriod = period; - - switch (n & 0x30) { - case 0x00: - state.srPhase = 0; - break; - case 0x10: - state.srPhase = 0.25 * period; - break; - case 0x20: - state.srPhase = 0.5 * period; - break; - case 0x30: - state.srPhase = 0.75 * period; - break; - default: - throw new Error('invalid S45ROUND value'); - } - - n &= 0x0F; - - if (n === 0) { state.srThreshold = 0; } - else { state.srThreshold = (n / 8 - 0.5) * period; } - } - - // ROFF[] Round Off - // 0x7A - function ROFF(state) { - if (exports.DEBUG) { console.log(state.step, 'ROFF[]'); } - - state.round = roundOff; - } - - // RUTG[] Round Up To Grid - // 0x7C - function RUTG(state) { - if (exports.DEBUG) { console.log(state.step, 'RUTG[]'); } - - state.round = roundUpToGrid; - } - - // RDTG[] Round Down To Grid - // 0x7D - function RDTG(state) { - if (exports.DEBUG) { console.log(state.step, 'RDTG[]'); } - - state.round = roundDownToGrid; - } - - // SCANCTRL[] SCAN conversion ConTRoL - // 0x85 - function SCANCTRL(state) { - var n = state.stack.pop(); - - // ignored by opentype.js - - if (exports.DEBUG) { console.log(state.step, 'SCANCTRL[]', n); } - } - - // SDPVTL[a] Set Dual Projection Vector To Line - // 0x86-0x87 - function SDPVTL(a, state) { - var stack = state.stack; - var p2i = stack.pop(); - var p1i = stack.pop(); - var p2 = state.z2[p2i]; - var p1 = state.z1[p1i]; - - if (exports.DEBUG) { console.log(state.step, 'SDPVTL[' + a + ']', p2i, p1i); } - - var dx; - var dy; - - if (!a) { - dx = p1.x - p2.x; - dy = p1.y - p2.y; - } else { - dx = p2.y - p1.y; - dy = p1.x - p2.x; - } - - state.dpv = getUnitVector(dx, dy); - } - - // GETINFO[] GET INFOrmation - // 0x88 - function GETINFO(state) { - var stack = state.stack; - var sel = stack.pop(); - var r = 0; - - if (exports.DEBUG) { console.log(state.step, 'GETINFO[]', sel); } - - // v35 as in no subpixel hinting - if (sel & 0x01) { r = 35; } - - // TODO rotation and stretch currently not supported - // and thus those GETINFO are always 0. - - // opentype.js is always gray scaling - if (sel & 0x20) { r |= 0x1000; } - - stack.push(r); - } - - // ROLL[] ROLL the top three stack elements - // 0x8A - function ROLL(state) { - var stack = state.stack; - var a = stack.pop(); - var b = stack.pop(); - var c = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'ROLL[]'); } - - stack.push(b); - stack.push(a); - stack.push(c); - } - - // MAX[] MAXimum of top two stack elements - // 0x8B - function MAX(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'MAX[]', e2, e1); } - - stack.push(Math.max(e1, e2)); - } - - // MIN[] MINimum of top two stack elements - // 0x8C - function MIN(state) { - var stack = state.stack; - var e2 = stack.pop(); - var e1 = stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'MIN[]', e2, e1); } - - stack.push(Math.min(e1, e2)); - } - - // SCANTYPE[] SCANTYPE - // 0x8D - function SCANTYPE(state) { - var n = state.stack.pop(); - // ignored by opentype.js - if (exports.DEBUG) { console.log(state.step, 'SCANTYPE[]', n); } - } - - // INSTCTRL[] INSTCTRL - // 0x8D - function INSTCTRL(state) { - var s = state.stack.pop(); - var v = state.stack.pop(); - - if (exports.DEBUG) { console.log(state.step, 'INSTCTRL[]', s, v); } - - switch (s) { - case 1 : state.inhibitGridFit = !!v; return; - case 2 : state.ignoreCvt = !!v; return; - default: throw new Error('invalid INSTCTRL[] selector'); - } - } - - // PUSHB[abc] PUSH Bytes - // 0xB0-0xB7 - function PUSHB(n, state) { - var stack = state.stack; - var prog = state.prog; - var ip = state.ip; - - if (exports.DEBUG) { console.log(state.step, 'PUSHB[' + n + ']'); } - - for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } - - state.ip = ip; - } - - // PUSHW[abc] PUSH Words - // 0xB8-0xBF - function PUSHW(n, state) { - var ip = state.ip; - var prog = state.prog; - var stack = state.stack; - - if (exports.DEBUG) { console.log(state.ip, 'PUSHW[' + n + ']'); } - - for (var i = 0; i < n; i++) { - var w = (prog[++ip] << 8) | prog[++ip]; - if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } - stack.push(w); - } - - state.ip = ip; - } - - // MDRP[abcde] Move Direct Relative Point - // 0xD0-0xEF - // (if indirect is 0) - // - // and - // - // MIRP[abcde] Move Indirect Relative Point - // 0xE0-0xFF - // (if indirect is 1) - - function MDRP_MIRP(indirect, setRp0, keepD, ro, dt, state) { - var stack = state.stack; - var cvte = indirect && stack.pop(); - var pi = stack.pop(); - var rp0i = state.rp0; - var rp = state.z0[rp0i]; - var p = state.z1[pi]; - - var md = state.minDis; - var fv = state.fv; - var pv = state.dpv; - var od; // original distance - var d; // moving distance - var sign; // sign of distance - var cv; - - d = od = pv.distance(p, rp, true, true); - sign = d >= 0 ? 1 : -1; // Math.sign would be 0 in case of 0 - - // TODO consider autoFlip - d = Math.abs(d); - - if (indirect) { - cv = state.cvt[cvte]; - - if (ro && Math.abs(d - cv) < state.cvCutIn) { d = cv; } - } - - if (keepD && d < md) { d = md; } - - if (ro) { d = state.round(d); } - - fv.setRelative(p, rp, sign * d, pv); - fv.touch(p); - - if (exports.DEBUG) { - console.log( - state.step, - (indirect ? 'MIRP[' : 'MDRP[') + - (setRp0 ? 'M' : 'm') + - (keepD ? '>' : '_') + - (ro ? 'R' : '_') + - (dt === 0 ? 'Gr' : (dt === 1 ? 'Bl' : (dt === 2 ? 'Wh' : ''))) + - ']', - indirect ? - cvte + '(' + state.cvt[cvte] + ',' + cv + ')' : - '', - pi, - '(d =', od, '->', sign * d, ')' - ); - } - - state.rp1 = state.rp0; - state.rp2 = pi; - if (setRp0) { state.rp0 = pi; } - } - - /* - * The instruction table. - */ - instructionTable = [ - /* 0x00 */ SVTCA.bind(undefined, yUnitVector), - /* 0x01 */ SVTCA.bind(undefined, xUnitVector), - /* 0x02 */ SPVTCA.bind(undefined, yUnitVector), - /* 0x03 */ SPVTCA.bind(undefined, xUnitVector), - /* 0x04 */ SFVTCA.bind(undefined, yUnitVector), - /* 0x05 */ SFVTCA.bind(undefined, xUnitVector), - /* 0x06 */ SPVTL.bind(undefined, 0), - /* 0x07 */ SPVTL.bind(undefined, 1), - /* 0x08 */ SFVTL.bind(undefined, 0), - /* 0x09 */ SFVTL.bind(undefined, 1), - /* 0x0A */ SPVFS, - /* 0x0B */ SFVFS, - /* 0x0C */ GPV, - /* 0x0D */ GFV, - /* 0x0E */ SFVTPV, - /* 0x0F */ ISECT, - /* 0x10 */ SRP0, - /* 0x11 */ SRP1, - /* 0x12 */ SRP2, - /* 0x13 */ SZP0, - /* 0x14 */ SZP1, - /* 0x15 */ SZP2, - /* 0x16 */ SZPS, - /* 0x17 */ SLOOP, - /* 0x18 */ RTG, - /* 0x19 */ RTHG, - /* 0x1A */ SMD, - /* 0x1B */ ELSE, - /* 0x1C */ JMPR, - /* 0x1D */ SCVTCI, - /* 0x1E */ undefined, // TODO SSWCI - /* 0x1F */ undefined, // TODO SSW - /* 0x20 */ DUP, - /* 0x21 */ POP, - /* 0x22 */ CLEAR, - /* 0x23 */ SWAP, - /* 0x24 */ DEPTH, - /* 0x25 */ CINDEX, - /* 0x26 */ MINDEX, - /* 0x27 */ undefined, // TODO ALIGNPTS - /* 0x28 */ undefined, - /* 0x29 */ undefined, // TODO UTP - /* 0x2A */ LOOPCALL, - /* 0x2B */ CALL, - /* 0x2C */ FDEF, - /* 0x2D */ undefined, // ENDF (eaten by FDEF) - /* 0x2E */ MDAP.bind(undefined, 0), - /* 0x2F */ MDAP.bind(undefined, 1), - /* 0x30 */ IUP.bind(undefined, yUnitVector), - /* 0x31 */ IUP.bind(undefined, xUnitVector), - /* 0x32 */ SHP.bind(undefined, 0), - /* 0x33 */ SHP.bind(undefined, 1), - /* 0x34 */ SHC.bind(undefined, 0), - /* 0x35 */ SHC.bind(undefined, 1), - /* 0x36 */ SHZ.bind(undefined, 0), - /* 0x37 */ SHZ.bind(undefined, 1), - /* 0x38 */ SHPIX, - /* 0x39 */ IP, - /* 0x3A */ MSIRP.bind(undefined, 0), - /* 0x3B */ MSIRP.bind(undefined, 1), - /* 0x3C */ ALIGNRP, - /* 0x3D */ RTDG, - /* 0x3E */ MIAP.bind(undefined, 0), - /* 0x3F */ MIAP.bind(undefined, 1), - /* 0x40 */ NPUSHB, - /* 0x41 */ NPUSHW, - /* 0x42 */ WS, - /* 0x43 */ RS, - /* 0x44 */ WCVTP, - /* 0x45 */ RCVT, - /* 0x46 */ GC.bind(undefined, 0), - /* 0x47 */ GC.bind(undefined, 1), - /* 0x48 */ undefined, // TODO SCFS - /* 0x49 */ MD.bind(undefined, 0), - /* 0x4A */ MD.bind(undefined, 1), - /* 0x4B */ MPPEM, - /* 0x4C */ undefined, // TODO MPS - /* 0x4D */ FLIPON, - /* 0x4E */ undefined, // TODO FLIPOFF - /* 0x4F */ undefined, // TODO DEBUG - /* 0x50 */ LT, - /* 0x51 */ LTEQ, - /* 0x52 */ GT, - /* 0x53 */ GTEQ, - /* 0x54 */ EQ, - /* 0x55 */ NEQ, - /* 0x56 */ ODD, - /* 0x57 */ EVEN, - /* 0x58 */ IF, - /* 0x59 */ EIF, - /* 0x5A */ AND, - /* 0x5B */ OR, - /* 0x5C */ NOT, - /* 0x5D */ DELTAP123.bind(undefined, 1), - /* 0x5E */ SDB, - /* 0x5F */ SDS, - /* 0x60 */ ADD, - /* 0x61 */ SUB, - /* 0x62 */ DIV, - /* 0x63 */ MUL, - /* 0x64 */ ABS, - /* 0x65 */ NEG, - /* 0x66 */ FLOOR, - /* 0x67 */ CEILING, - /* 0x68 */ ROUND.bind(undefined, 0), - /* 0x69 */ ROUND.bind(undefined, 1), - /* 0x6A */ ROUND.bind(undefined, 2), - /* 0x6B */ ROUND.bind(undefined, 3), - /* 0x6C */ undefined, // TODO NROUND[ab] - /* 0x6D */ undefined, // TODO NROUND[ab] - /* 0x6E */ undefined, // TODO NROUND[ab] - /* 0x6F */ undefined, // TODO NROUND[ab] - /* 0x70 */ WCVTF, - /* 0x71 */ DELTAP123.bind(undefined, 2), - /* 0x72 */ DELTAP123.bind(undefined, 3), - /* 0x73 */ DELTAC123.bind(undefined, 1), - /* 0x74 */ DELTAC123.bind(undefined, 2), - /* 0x75 */ DELTAC123.bind(undefined, 3), - /* 0x76 */ SROUND, - /* 0x77 */ S45ROUND, - /* 0x78 */ undefined, // TODO JROT[] - /* 0x79 */ undefined, // TODO JROF[] - /* 0x7A */ ROFF, - /* 0x7B */ undefined, - /* 0x7C */ RUTG, - /* 0x7D */ RDTG, - /* 0x7E */ POP, // actually SANGW, supposed to do only a pop though - /* 0x7F */ POP, // actually AA, supposed to do only a pop though - /* 0x80 */ undefined, // TODO FLIPPT - /* 0x81 */ undefined, // TODO FLIPRGON - /* 0x82 */ undefined, // TODO FLIPRGOFF - /* 0x83 */ undefined, - /* 0x84 */ undefined, - /* 0x85 */ SCANCTRL, - /* 0x86 */ SDPVTL.bind(undefined, 0), - /* 0x87 */ SDPVTL.bind(undefined, 1), - /* 0x88 */ GETINFO, - /* 0x89 */ undefined, // TODO IDEF - /* 0x8A */ ROLL, - /* 0x8B */ MAX, - /* 0x8C */ MIN, - /* 0x8D */ SCANTYPE, - /* 0x8E */ INSTCTRL, - /* 0x8F */ undefined, - /* 0x90 */ undefined, - /* 0x91 */ undefined, - /* 0x92 */ undefined, - /* 0x93 */ undefined, - /* 0x94 */ undefined, - /* 0x95 */ undefined, - /* 0x96 */ undefined, - /* 0x97 */ undefined, - /* 0x98 */ undefined, - /* 0x99 */ undefined, - /* 0x9A */ undefined, - /* 0x9B */ undefined, - /* 0x9C */ undefined, - /* 0x9D */ undefined, - /* 0x9E */ undefined, - /* 0x9F */ undefined, - /* 0xA0 */ undefined, - /* 0xA1 */ undefined, - /* 0xA2 */ undefined, - /* 0xA3 */ undefined, - /* 0xA4 */ undefined, - /* 0xA5 */ undefined, - /* 0xA6 */ undefined, - /* 0xA7 */ undefined, - /* 0xA8 */ undefined, - /* 0xA9 */ undefined, - /* 0xAA */ undefined, - /* 0xAB */ undefined, - /* 0xAC */ undefined, - /* 0xAD */ undefined, - /* 0xAE */ undefined, - /* 0xAF */ undefined, - /* 0xB0 */ PUSHB.bind(undefined, 1), - /* 0xB1 */ PUSHB.bind(undefined, 2), - /* 0xB2 */ PUSHB.bind(undefined, 3), - /* 0xB3 */ PUSHB.bind(undefined, 4), - /* 0xB4 */ PUSHB.bind(undefined, 5), - /* 0xB5 */ PUSHB.bind(undefined, 6), - /* 0xB6 */ PUSHB.bind(undefined, 7), - /* 0xB7 */ PUSHB.bind(undefined, 8), - /* 0xB8 */ PUSHW.bind(undefined, 1), - /* 0xB9 */ PUSHW.bind(undefined, 2), - /* 0xBA */ PUSHW.bind(undefined, 3), - /* 0xBB */ PUSHW.bind(undefined, 4), - /* 0xBC */ PUSHW.bind(undefined, 5), - /* 0xBD */ PUSHW.bind(undefined, 6), - /* 0xBE */ PUSHW.bind(undefined, 7), - /* 0xBF */ PUSHW.bind(undefined, 8), - /* 0xC0 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 0), - /* 0xC1 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 1), - /* 0xC2 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 2), - /* 0xC3 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 3), - /* 0xC4 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 0), - /* 0xC5 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 1), - /* 0xC6 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 2), - /* 0xC7 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 3), - /* 0xC8 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 0), - /* 0xC9 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 1), - /* 0xCA */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 2), - /* 0xCB */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 3), - /* 0xCC */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 0), - /* 0xCD */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 1), - /* 0xCE */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 2), - /* 0xCF */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 3), - /* 0xD0 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 0), - /* 0xD1 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 1), - /* 0xD2 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 2), - /* 0xD3 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 3), - /* 0xD4 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 0), - /* 0xD5 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 1), - /* 0xD6 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 2), - /* 0xD7 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 3), - /* 0xD8 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 0), - /* 0xD9 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 1), - /* 0xDA */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 2), - /* 0xDB */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 3), - /* 0xDC */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 0), - /* 0xDD */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 1), - /* 0xDE */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 2), - /* 0xDF */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 3), - /* 0xE0 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 0), - /* 0xE1 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 1), - /* 0xE2 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 2), - /* 0xE3 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 3), - /* 0xE4 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 0), - /* 0xE5 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 1), - /* 0xE6 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 2), - /* 0xE7 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 3), - /* 0xE8 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 0), - /* 0xE9 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 1), - /* 0xEA */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 2), - /* 0xEB */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 3), - /* 0xEC */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 0), - /* 0xED */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 1), - /* 0xEE */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 2), - /* 0xEF */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 3), - /* 0xF0 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 0), - /* 0xF1 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 1), - /* 0xF2 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 2), - /* 0xF3 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 3), - /* 0xF4 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 0), - /* 0xF5 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 1), - /* 0xF6 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 2), - /* 0xF7 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 3), - /* 0xF8 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 0), - /* 0xF9 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 1), - /* 0xFA */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 2), - /* 0xFB */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 3), - /* 0xFC */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 0), - /* 0xFD */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 1), - /* 0xFE */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 2), - /* 0xFF */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 3) - ]; - - /***************************** - Mathematical Considerations - ****************************** - - fv ... refers to freedom vector - pv ... refers to projection vector - rp ... refers to reference point - p ... refers to to point being operated on - d ... refers to distance - - SETRELATIVE: - ============ - - case freedom vector == x-axis: - ------------------------------ - - (pv) - .-' - rpd .-' - .-* - d .-'90°' - .-' ' - .-' ' - *-' ' b - rp ' - ' - ' - p *----------*-------------- (fv) - pm - - rpdx = rpx + d * pv.x - rpdy = rpy + d * pv.y - - equation of line b - - y - rpdy = pvns * (x- rpdx) - - y = p.y - - x = rpdx + ( p.y - rpdy ) / pvns - - - case freedom vector == y-axis: - ------------------------------ - - * pm - |\ - | \ - | \ - | \ - | \ - | \ - | \ - | \ - | \ - | \ b - | \ - | \ - | \ .-' (pv) - | 90° \.-' - | .-'* rpd - | .-' - * *-' d - p rp - - rpdx = rpx + d * pv.x - rpdy = rpy + d * pv.y - - equation of line b: - pvns ... normal slope to pv - - y - rpdy = pvns * (x - rpdx) - - x = p.x - - y = rpdy + pvns * (p.x - rpdx) - - - - generic case: - ------------- - - - .'(fv) - .' - .* pm - .' ! - .' . - .' ! - .' . b - .' ! - * . - p ! - 90° . ... (pv) - ...-*-''' - ...---''' rpd - ...---''' d - *--''' - rp - - rpdx = rpx + d * pv.x - rpdy = rpy + d * pv.y - - equation of line b: - pvns... normal slope to pv - - y - rpdy = pvns * (x - rpdx) - - equation of freedom vector line: - fvs ... slope of freedom vector (=fy/fx) - - y - py = fvs * (x - px) - - - on pm both equations are true for same x/y - - y - rpdy = pvns * (x - rpdx) - - y - py = fvs * (x - px) - - form to y and set equal: - - pvns * (x - rpdx) + rpdy = fvs * (x - px) + py - - expand: - - pvns * x - pvns * rpdx + rpdy = fvs * x - fvs * px + py - - switch: - - fvs * x - fvs * px + py = pvns * x - pvns * rpdx + rpdy - - solve for x: - - fvs * x - pvns * x = fvs * px - pvns * rpdx - py + rpdy - - - - fvs * px - pvns * rpdx + rpdy - py - x = ----------------------------------- - fvs - pvns - - and: - - y = fvs * (x - px) + py - - - - INTERPOLATE: - ============ - - Examples of point interpolation. - - The weight of the movement of the reference point gets bigger - the further the other reference point is away, thus the safest - option (that is avoiding 0/0 divisions) is to weight the - original distance of the other point by the sum of both distances. - - If the sum of both distances is 0, then move the point by the - arithmetic average of the movement of both reference points. - - - - - (+6) - rp1o *---->*rp1 - . . (+12) - . . rp2o *---------->* rp2 - . . . . - . . . . - . 10 20 . . - |.........|...................| . - . . . - . . (+8) . - po *------>*p . - . . . - . 12 . 24 . - |...........|.......................| - 36 - - - ------- - - - - (+10) - rp1o *-------->*rp1 - . . (-10) - . . rp2 *<---------* rpo2 - . . . . - . . . . - . 10 . 30 . . - |.........|.............................| - . . - . (+5) . - po *--->* p . - . . . - . . 20 . - |....|..............| - 5 15 - - - ------- - - - (+10) - rp1o *-------->*rp1 - . . - . . - rp2o *-------->*rp2 - - - (+10) - po *-------->* p - - ------- - - - (+10) - rp1o *-------->*rp1 - . . - . .(+30) - rp2o *---------------------------->*rp2 - - - (+25) - po *----------------------->* p - - - - vim: set ts=4 sw=4 expandtab: - *****/ - - /** - * Converts a string into a list of tokens. - */ - - /** - * Create a new token - * @param {string} char a single char - */ - function Token(char) { - this.char = char; - this.state = {}; - this.activeState = null; - } - - /** - * Create a new context range - * @param {number} startIndex range start index - * @param {number} endOffset range end index offset - * @param {string} contextName owner context name - */ - function ContextRange(startIndex, endOffset, contextName) { - this.contextName = contextName; - this.startIndex = startIndex; - this.endOffset = endOffset; - } - - /** - * Check context start and end - * @param {string} contextName a unique context name - * @param {function} checkStart a predicate function the indicates a context's start - * @param {function} checkEnd a predicate function the indicates a context's end - */ - function ContextChecker(contextName, checkStart, checkEnd) { - this.contextName = contextName; - this.openRange = null; - this.ranges = []; - this.checkStart = checkStart; - this.checkEnd = checkEnd; - } - - /** - * @typedef ContextParams - * @type Object - * @property {array} context context items - * @property {number} currentIndex current item index - */ - - /** - * Create a context params - * @param {array} context a list of items - * @param {number} currentIndex current item index - */ - function ContextParams(context, currentIndex) { - this.context = context; - this.index = currentIndex; - this.length = context.length; - this.current = context[currentIndex]; - this.backtrack = context.slice(0, currentIndex); - this.lookahead = context.slice(currentIndex + 1); - } - - /** - * Create an event instance - * @param {string} eventId event unique id - */ - function Event(eventId) { - this.eventId = eventId; - this.subscribers = []; - } - - /** - * Initialize a core events and auto subscribe required event handlers - * @param {any} events an object that enlists core events handlers - */ - function initializeCoreEvents(events) { - var this$1 = this; - - var coreEvents = [ - 'start', 'end', 'next', 'newToken', 'contextStart', - 'contextEnd', 'insertToken', 'removeToken', 'removeRange', - 'replaceToken', 'replaceRange', 'composeRUD', 'updateContextsRanges' - ]; - - coreEvents.forEach(function (eventId) { - Object.defineProperty(this$1.events, eventId, { - value: new Event(eventId) - }); - }); - - if (!!events) { - coreEvents.forEach(function (eventId) { - var event = events[eventId]; - if (typeof event === 'function') { - this$1.events[eventId].subscribe(event); - } - }); - } - var requiresContextUpdate = [ - 'insertToken', 'removeToken', 'removeRange', - 'replaceToken', 'replaceRange', 'composeRUD' - ]; - requiresContextUpdate.forEach(function (eventId) { - this$1.events[eventId].subscribe( - this$1.updateContextsRanges - ); - }); - } - - /** - * Converts a string into a list of tokens - * @param {any} events tokenizer core events - */ - function Tokenizer(events) { - this.tokens = []; - this.registeredContexts = {}; - this.contextCheckers = []; - this.events = {}; - this.registeredModifiers = []; - - initializeCoreEvents.call(this, events); - } - - /** - * Sets the state of a token, usually called by a state modifier. - * @param {string} key state item key - * @param {any} value state item value - */ - Token.prototype.setState = function(key, value) { - this.state[key] = value; - this.activeState = { key: key, value: this.state[key] }; - return this.activeState; - }; - - Token.prototype.getState = function (stateId) { - return this.state[stateId] || null; - }; - - /** - * Checks if an index exists in the tokens list. - * @param {number} index token index - */ - Tokenizer.prototype.inboundIndex = function(index) { - return index >= 0 && index < this.tokens.length; - }; - - /** - * Compose and apply a list of operations (replace, update, delete) - * @param {array} RUDs replace, update and delete operations - * TODO: Perf. Optimization (lengthBefore === lengthAfter ? dispatch once) - */ - Tokenizer.prototype.composeRUD = function (RUDs) { - var this$1 = this; - - var silent = true; - var state = RUDs.map(function (RUD) { return ( - this$1[RUD[0]].apply(this$1, RUD.slice(1).concat(silent)) - ); }); - var hasFAILObject = function (obj) { return ( - typeof obj === 'object' && - obj.hasOwnProperty('FAIL') - ); }; - if (state.every(hasFAILObject)) { - return { - FAIL: "composeRUD: one or more operations hasn't completed successfully", - report: state.filter(hasFAILObject) - }; - } - this.dispatch('composeRUD', [state.filter(function (op) { return !hasFAILObject(op); })]); - }; - - /** - * Replace a range of tokens with a list of tokens - * @param {number} startIndex range start index - * @param {number} offset range offset - * @param {token} tokens a list of tokens to replace - * @param {boolean} silent dispatch events and update context ranges - */ - Tokenizer.prototype.replaceRange = function (startIndex, offset, tokens, silent) { - offset = offset !== null ? offset : this.tokens.length; - var isTokenType = tokens.every(function (token) { return token instanceof Token; }); - if (!isNaN(startIndex) && this.inboundIndex(startIndex) && isTokenType) { - var replaced = this.tokens.splice.apply( - this.tokens, [startIndex, offset].concat(tokens) - ); - if (!silent) { this.dispatch('replaceToken', [startIndex, offset, tokens]); } - return [replaced, tokens]; - } else { - return { FAIL: 'replaceRange: invalid tokens or startIndex.' }; - } - }; - - /** - * Replace a token with another token - * @param {number} index token index - * @param {token} token a token to replace - * @param {boolean} silent dispatch events and update context ranges - */ - Tokenizer.prototype.replaceToken = function (index, token, silent) { - if (!isNaN(index) && this.inboundIndex(index) && token instanceof Token) { - var replaced = this.tokens.splice(index, 1, token); - if (!silent) { this.dispatch('replaceToken', [index, token]); } - return [replaced[0], token]; - } else { - return { FAIL: 'replaceToken: invalid token or index.' }; - } - }; - - /** - * Removes a range of tokens - * @param {number} startIndex range start index - * @param {number} offset range offset - * @param {boolean} silent dispatch events and update context ranges - */ - Tokenizer.prototype.removeRange = function(startIndex, offset, silent) { - offset = !isNaN(offset) ? offset : this.tokens.length; - var tokens = this.tokens.splice(startIndex, offset); - if (!silent) { this.dispatch('removeRange', [tokens, startIndex, offset]); } - return tokens; - }; - - /** - * Remove a token at a certain index - * @param {number} index token index - * @param {boolean} silent dispatch events and update context ranges - */ - Tokenizer.prototype.removeToken = function(index, silent) { - if (!isNaN(index) && this.inboundIndex(index)) { - var token = this.tokens.splice(index, 1); - if (!silent) { this.dispatch('removeToken', [token, index]); } - return token; - } else { - return { FAIL: 'removeToken: invalid token index.' }; - } - }; - - /** - * Insert a list of tokens at a certain index - * @param {array} tokens a list of tokens to insert - * @param {number} index insert the list of tokens at index - * @param {boolean} silent dispatch events and update context ranges - */ - Tokenizer.prototype.insertToken = function (tokens, index, silent) { - var tokenType = tokens.every( - function (token) { return token instanceof Token; } - ); - if (tokenType) { - this.tokens.splice.apply( - this.tokens, [index, 0].concat(tokens) - ); - if (!silent) { this.dispatch('insertToken', [tokens, index]); } - return tokens; - } else { - return { FAIL: 'insertToken: invalid token(s).' }; - } - }; - - /** - * A state modifier that is called on 'newToken' event - * @param {string} modifierId state modifier id - * @param {function} condition a predicate function that returns true or false - * @param {function} modifier a function to update token state - */ - Tokenizer.prototype.registerModifier = function(modifierId, condition, modifier) { - this.events.newToken.subscribe(function(token, contextParams) { - var conditionParams = [token, contextParams]; - var canApplyModifier = ( - condition === null || - condition.apply(this, conditionParams) === true - ); - var modifierParams = [token, contextParams]; - if (canApplyModifier) { - var newStateValue = modifier.apply(this, modifierParams); - token.setState(modifierId, newStateValue); - } - }); - this.registeredModifiers.push(modifierId); - }; - - /** - * Subscribe a handler to an event - * @param {function} eventHandler an event handler function - */ - Event.prototype.subscribe = function (eventHandler) { - if (typeof eventHandler === 'function') { - return ((this.subscribers.push(eventHandler)) - 1); - } else { - return { FAIL: ("invalid '" + (this.eventId) + "' event handler")}; - } - }; - - /** - * Unsubscribe an event handler - * @param {string} subsId subscription id - */ - Event.prototype.unsubscribe = function (subsId) { - this.subscribers.splice(subsId, 1); - }; - - /** - * Sets context params current value index - * @param {number} index context params current value index - */ - ContextParams.prototype.setCurrentIndex = function(index) { - this.index = index; - this.current = this.context[index]; - this.backtrack = this.context.slice(0, index); - this.lookahead = this.context.slice(index + 1); - }; - - /** - * Get an item at an offset from the current value - * example (current value is 3): - * 1 2 [3] 4 5 | items values - * -2 -1 0 1 2 | offset values - * @param {number} offset an offset from current value index - */ - ContextParams.prototype.get = function (offset) { - switch (true) { - case (offset === 0): - return this.current; - case (offset < 0 && Math.abs(offset) <= this.backtrack.length): - return this.backtrack.slice(offset)[0]; - case (offset > 0 && offset <= this.lookahead.length): - return this.lookahead[offset - 1]; - default: - return null; - } - }; - - /** - * Converts a context range into a string value - * @param {contextRange} range a context range - */ - Tokenizer.prototype.rangeToText = function (range) { - if (range instanceof ContextRange) { - return ( - this.getRangeTokens(range) - .map(function (token) { return token.char; }).join('') - ); - } - }; - - /** - * Converts all tokens into a string - */ - Tokenizer.prototype.getText = function () { - return this.tokens.map(function (token) { return token.char; }).join(''); - }; - - /** - * Get a context by name - * @param {string} contextName context name to get - */ - Tokenizer.prototype.getContext = function (contextName) { - var context = this.registeredContexts[contextName]; - return !!context ? context : null; - }; - - /** - * Subscribes a new event handler to an event - * @param {string} eventName event name to subscribe to - * @param {function} eventHandler a function to be invoked on event - */ - Tokenizer.prototype.on = function(eventName, eventHandler) { - var event = this.events[eventName]; - if (!!event) { - return event.subscribe(eventHandler); - } else { - return null; - } - }; - - /** - * Dispatches an event - * @param {string} eventName event name - * @param {any} args event handler arguments - */ - Tokenizer.prototype.dispatch = function(eventName, args) { - var this$1 = this; - - var event = this.events[eventName]; - if (event instanceof Event) { - event.subscribers.forEach(function (subscriber) { - subscriber.apply(this$1, args || []); - }); - } - }; - - /** - * Register a new context checker - * @param {string} contextName a unique context name - * @param {function} contextStartCheck a predicate function that returns true on context start - * @param {function} contextEndCheck a predicate function that returns true on context end - * TODO: call tokenize on registration to update context ranges with the new context. - */ - Tokenizer.prototype.registerContextChecker = function(contextName, contextStartCheck, contextEndCheck) { - if (!!this.getContext(contextName)) { return { - FAIL: - ("context name '" + contextName + "' is already registered.") - }; } - if (typeof contextStartCheck !== 'function') { return { - FAIL: - "missing context start check." - }; } - if (typeof contextEndCheck !== 'function') { return { - FAIL: - "missing context end check." - }; } - var contextCheckers = new ContextChecker( - contextName, contextStartCheck, contextEndCheck - ); - this.registeredContexts[contextName] = contextCheckers; - this.contextCheckers.push(contextCheckers); - return contextCheckers; - }; - - /** - * Gets a context range tokens - * @param {contextRange} range a context range - */ - Tokenizer.prototype.getRangeTokens = function(range) { - var endIndex = range.startIndex + range.endOffset; - return [].concat( - this.tokens - .slice(range.startIndex, endIndex) - ); - }; - - /** - * Gets the ranges of a context - * @param {string} contextName context name - */ - Tokenizer.prototype.getContextRanges = function(contextName) { - var context = this.getContext(contextName); - if (!!context) { - return context.ranges; - } else { - return { FAIL: ("context checker '" + contextName + "' is not registered.") }; - } - }; - - /** - * Resets context ranges to run context update - */ - Tokenizer.prototype.resetContextsRanges = function () { - var registeredContexts = this.registeredContexts; - for (var contextName in registeredContexts) { - if (registeredContexts.hasOwnProperty(contextName)) { - var context = registeredContexts[contextName]; - context.ranges = []; - } - } - }; - - /** - * Updates context ranges - */ - Tokenizer.prototype.updateContextsRanges = function () { - this.resetContextsRanges(); - var chars = this.tokens.map(function (token) { return token.char; }); - for (var i = 0; i < chars.length; i++) { - var contextParams = new ContextParams(chars, i); - this.runContextCheck(contextParams); - } - this.dispatch('updateContextsRanges', [this.registeredContexts]); - }; - - /** - * Sets the end offset of an open range - * @param {number} offset range end offset - * @param {string} contextName context name - */ - Tokenizer.prototype.setEndOffset = function (offset, contextName) { - var startIndex = this.getContext(contextName).openRange.startIndex; - var range = new ContextRange(startIndex, offset, contextName); - var ranges = this.getContext(contextName).ranges; - range.rangeId = contextName + "." + (ranges.length); - ranges.push(range); - this.getContext(contextName).openRange = null; - return range; - }; - - /** - * Runs a context check on the current context - * @param {contextParams} contextParams current context params - */ - Tokenizer.prototype.runContextCheck = function(contextParams) { - var this$1 = this; - - var index = contextParams.index; - this.contextCheckers.forEach(function (contextChecker) { - var contextName = contextChecker.contextName; - var openRange = this$1.getContext(contextName).openRange; - if (!openRange && contextChecker.checkStart(contextParams)) { - openRange = new ContextRange(index, null, contextName); - this$1.getContext(contextName).openRange = openRange; - this$1.dispatch('contextStart', [contextName, index]); - } - if (!!openRange && contextChecker.checkEnd(contextParams)) { - var offset = (index - openRange.startIndex) + 1; - var range = this$1.setEndOffset(offset, contextName); - this$1.dispatch('contextEnd', [contextName, range]); - } - }); - }; - - /** - * Converts a text into a list of tokens - * @param {string} text a text to tokenize - */ - Tokenizer.prototype.tokenize = function (text) { - this.tokens = []; - this.resetContextsRanges(); - var chars = Array.from(text); - this.dispatch('start'); - for (var i = 0; i < chars.length; i++) { - var char = chars[i]; - var contextParams = new ContextParams(chars, i); - this.dispatch('next', [contextParams]); - this.runContextCheck(contextParams); - var token = new Token(char); - this.tokens.push(token); - this.dispatch('newToken', [token, contextParams]); - } - this.dispatch('end', [this.tokens]); - return this.tokens; - }; - - // ╭─┄┄┄────────────────────────┄─────────────────────────────────────────────╮ - // ┊ Character Class Assertions ┊ Checks if a char belongs to a certain class ┊ - // ╰─╾──────────────────────────┄─────────────────────────────────────────────╯ - // jscs:disable maximumLineLength - /** - * Check if a char is Arabic - * @param {string} c a single char - */ - function isArabicChar(c) { - return /[\u0600-\u065F\u066A-\u06D2\u06FA-\u06FF]/.test(c); - } - - /** - * Check if a char is an isolated arabic char - * @param {string} c a single char - */ - function isIsolatedArabicChar(char) { - return /[\u0630\u0690\u0621\u0631\u0661\u0671\u0622\u0632\u0672\u0692\u06C2\u0623\u0673\u0693\u06C3\u0624\u0694\u06C4\u0625\u0675\u0695\u06C5\u06E5\u0676\u0696\u06C6\u0627\u0677\u0697\u06C7\u0648\u0688\u0698\u06C8\u0689\u0699\u06C9\u068A\u06CA\u066B\u068B\u06CB\u068C\u068D\u06CD\u06FD\u068E\u06EE\u06FE\u062F\u068F\u06CF\u06EF]/.test(char); - } - - /** - * Check if a char is an Arabic Tashkeel char - * @param {string} c a single char - */ - function isTashkeelArabicChar(char) { - return /[\u0600-\u0605\u060C-\u060E\u0610-\u061B\u061E\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED]/.test(char); - } - - /** - * Check if a char is Latin - * @param {string} c a single char - */ - function isLatinChar(c) { - return /[A-z]/.test(c); - } - - /** - * Check if a char is whitespace char - * @param {string} c a single char - */ - function isWhiteSpace(c) { - return /\s/.test(c); - } - - /** - * Query a feature by some of it's properties to lookup a glyph substitution. - */ - - /** - * Create feature query instance - * @param {Font} font opentype font instance - */ - function FeatureQuery(font) { - this.font = font; - this.features = {}; - } - - /** - * @typedef SubstitutionAction - * @type Object - * @property {number} id substitution type - * @property {string} tag feature tag - * @property {any} substitution substitution value(s) - */ - - /** - * Create a substitution action instance - * @param {SubstitutionAction} action - */ - function SubstitutionAction(action) { - this.id = action.id; - this.tag = action.tag; - this.substitution = action.substitution; - } - - /** - * Lookup a coverage table - * @param {number} glyphIndex glyph index - * @param {CoverageTable} coverage coverage table - */ - function lookupCoverage(glyphIndex, coverage) { - if (!glyphIndex) { return -1; } - switch (coverage.format) { - case 1: - return coverage.glyphs.indexOf(glyphIndex); - - case 2: - var ranges = coverage.ranges; - for (var i = 0; i < ranges.length; i++) { - var range = ranges[i]; - if (glyphIndex >= range.start && glyphIndex <= range.end) { - var offset = glyphIndex - range.start; - return range.index + offset; - } - } - break; - default: - return -1; // not found - } - return -1; - } - - /** - * Handle a single substitution - format 1 - * @param {ContextParams} contextParams context params to lookup - */ - function singleSubstitutionFormat1(glyphIndex, subtable) { - var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); - if (substituteIndex === -1) { return null; } - return glyphIndex + subtable.deltaGlyphId; - } - - /** - * Handle a single substitution - format 2 - * @param {ContextParams} contextParams context params to lookup - */ - function singleSubstitutionFormat2(glyphIndex, subtable) { - var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); - if (substituteIndex === -1) { return null; } - return subtable.substitute[substituteIndex]; - } - - /** - * Lookup a list of coverage tables - * @param {any} coverageList a list of coverage tables - * @param {ContextParams} contextParams context params to lookup - */ - function lookupCoverageList(coverageList, contextParams) { - var lookupList = []; - for (var i = 0; i < coverageList.length; i++) { - var coverage = coverageList[i]; - var glyphIndex = contextParams.current; - glyphIndex = Array.isArray(glyphIndex) ? glyphIndex[0] : glyphIndex; - var lookupIndex = lookupCoverage(glyphIndex, coverage); - if (lookupIndex !== -1) { - lookupList.push(lookupIndex); - } - } - if (lookupList.length !== coverageList.length) { return -1; } - return lookupList; - } - - /** - * Handle chaining context substitution - format 3 - * @param {ContextParams} contextParams context params to lookup - */ - function chainingSubstitutionFormat3(contextParams, subtable) { - var lookupsCount = ( - subtable.inputCoverage.length + - subtable.lookaheadCoverage.length + - subtable.backtrackCoverage.length - ); - if (contextParams.context.length < lookupsCount) { return []; } - // INPUT LOOKUP // - var inputLookups = lookupCoverageList( - subtable.inputCoverage, contextParams - ); - if (inputLookups === -1) { return []; } - // LOOKAHEAD LOOKUP // - var lookaheadOffset = subtable.inputCoverage.length - 1; - if (contextParams.lookahead.length < subtable.lookaheadCoverage.length) { return []; } - var lookaheadContext = contextParams.lookahead.slice(lookaheadOffset); - while (lookaheadContext.length && isTashkeelArabicChar(lookaheadContext[0].char)) { - lookaheadContext.shift(); - } - var lookaheadParams = new ContextParams(lookaheadContext, 0); - var lookaheadLookups = lookupCoverageList( - subtable.lookaheadCoverage, lookaheadParams - ); - // BACKTRACK LOOKUP // - var backtrackContext = [].concat(contextParams.backtrack); - backtrackContext.reverse(); - while (backtrackContext.length && isTashkeelArabicChar(backtrackContext[0].char)) { - backtrackContext.shift(); - } - if (backtrackContext.length < subtable.backtrackCoverage.length) { return []; } - var backtrackParams = new ContextParams(backtrackContext, 0); - var backtrackLookups = lookupCoverageList( - subtable.backtrackCoverage, backtrackParams - ); - var contextRulesMatch = ( - inputLookups.length === subtable.inputCoverage.length && - lookaheadLookups.length === subtable.lookaheadCoverage.length && - backtrackLookups.length === subtable.backtrackCoverage.length - ); - var substitutions = []; - if (contextRulesMatch) { - for (var i = 0; i < subtable.lookupRecords.length; i++) { - var lookupRecord = subtable.lookupRecords[i]; - var lookupListIndex = lookupRecord.lookupListIndex; - var lookupTable = this.getLookupByIndex(lookupListIndex); - for (var s = 0; s < lookupTable.subtables.length; s++) { - var subtable$1 = lookupTable.subtables[s]; - var lookup = this.getLookupMethod(lookupTable, subtable$1); - var substitutionType = this.getSubstitutionType(lookupTable, subtable$1); - if (substitutionType === '12') { - for (var n = 0; n < inputLookups.length; n++) { - var glyphIndex = contextParams.get(n); - var substitution = lookup(glyphIndex); - if (substitution) { substitutions.push(substitution); } - } - } - } - } - } - return substitutions; - } - - /** - * Handle ligature substitution - format 1 - * @param {ContextParams} contextParams context params to lookup - */ - function ligatureSubstitutionFormat1(contextParams, subtable) { - // COVERAGE LOOKUP // - var glyphIndex = contextParams.current; - var ligSetIndex = lookupCoverage(glyphIndex, subtable.coverage); - if (ligSetIndex === -1) { return null; } - // COMPONENTS LOOKUP - // (!) note, components are ordered in the written direction. - var ligature; - var ligatureSet = subtable.ligatureSets[ligSetIndex]; - for (var s = 0; s < ligatureSet.length; s++) { - ligature = ligatureSet[s]; - for (var l = 0; l < ligature.components.length; l++) { - var lookaheadItem = contextParams.lookahead[l]; - var component = ligature.components[l]; - if (lookaheadItem !== component) { break; } - if (l === ligature.components.length - 1) { return ligature; } - } - } - return null; - } - - /** - * Handle decomposition substitution - format 1 - * @param {number} glyphIndex glyph index - * @param {any} subtable subtable - */ - function decompositionSubstitutionFormat1(glyphIndex, subtable) { - var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); - if (substituteIndex === -1) { return null; } - return subtable.sequences[substituteIndex]; - } - - /** - * Get default script features indexes - */ - FeatureQuery.prototype.getDefaultScriptFeaturesIndexes = function () { - var scripts = this.font.tables.gsub.scripts; - for (var s = 0; s < scripts.length; s++) { - var script = scripts[s]; - if (script.tag === 'DFLT') { return ( - script.script.defaultLangSys.featureIndexes - ); } - } - return []; - }; - - /** - * Get feature indexes of a specific script - * @param {string} scriptTag script tag - */ - FeatureQuery.prototype.getScriptFeaturesIndexes = function(scriptTag) { - var tables = this.font.tables; - if (!tables.gsub) { return []; } - if (!scriptTag) { return this.getDefaultScriptFeaturesIndexes(); } - var scripts = this.font.tables.gsub.scripts; - for (var i = 0; i < scripts.length; i++) { - var script = scripts[i]; - if (script.tag === scriptTag && script.script.defaultLangSys) { - return script.script.defaultLangSys.featureIndexes; - } else { - var langSysRecords = script.langSysRecords; - if (!!langSysRecords) { - for (var j = 0; j < langSysRecords.length; j++) { - var langSysRecord = langSysRecords[j]; - if (langSysRecord.tag === scriptTag) { - var langSys = langSysRecord.langSys; - return langSys.featureIndexes; - } - } - } - } - } - return this.getDefaultScriptFeaturesIndexes(); - }; - - /** - * Map a feature tag to a gsub feature - * @param {any} features gsub features - * @param {string} scriptTag script tag - */ - FeatureQuery.prototype.mapTagsToFeatures = function (features, scriptTag) { - var tags = {}; - for (var i = 0; i < features.length; i++) { - var tag = features[i].tag; - var feature = features[i].feature; - tags[tag] = feature; - } - this.features[scriptTag].tags = tags; - }; - - /** - * Get features of a specific script - * @param {string} scriptTag script tag - */ - FeatureQuery.prototype.getScriptFeatures = function (scriptTag) { - var features = this.features[scriptTag]; - if (this.features.hasOwnProperty(scriptTag)) { return features; } - var featuresIndexes = this.getScriptFeaturesIndexes(scriptTag); - if (!featuresIndexes) { return null; } - var gsub = this.font.tables.gsub; - features = featuresIndexes.map(function (index) { return gsub.features[index]; }); - this.features[scriptTag] = features; - this.mapTagsToFeatures(features, scriptTag); - return features; - }; - - /** - * Get substitution type - * @param {any} lookupTable lookup table - * @param {any} subtable subtable - */ - FeatureQuery.prototype.getSubstitutionType = function(lookupTable, subtable) { - var lookupType = lookupTable.lookupType.toString(); - var substFormat = subtable.substFormat.toString(); - return lookupType + substFormat; - }; - - /** - * Get lookup method - * @param {any} lookupTable lookup table - * @param {any} subtable subtable - */ - FeatureQuery.prototype.getLookupMethod = function(lookupTable, subtable) { - var this$1 = this; - - var substitutionType = this.getSubstitutionType(lookupTable, subtable); - switch (substitutionType) { - case '11': - return function (glyphIndex) { return singleSubstitutionFormat1.apply( - this$1, [glyphIndex, subtable] - ); }; - case '12': - return function (glyphIndex) { return singleSubstitutionFormat2.apply( - this$1, [glyphIndex, subtable] - ); }; - case '63': - return function (contextParams) { return chainingSubstitutionFormat3.apply( - this$1, [contextParams, subtable] - ); }; - case '41': - return function (contextParams) { return ligatureSubstitutionFormat1.apply( - this$1, [contextParams, subtable] - ); }; - case '21': - return function (glyphIndex) { return decompositionSubstitutionFormat1.apply( - this$1, [glyphIndex, subtable] - ); }; - default: - throw new Error( - "lookupType: " + (lookupTable.lookupType) + " - " + - "substFormat: " + (subtable.substFormat) + " " + - "is not yet supported" - ); - } - }; - - /** - * [ LOOKUP TYPES ] - * ------------------------------- - * Single 1; - * Multiple 2; - * Alternate 3; - * Ligature 4; - * Context 5; - * ChainingContext 6; - * ExtensionSubstitution 7; - * ReverseChainingContext 8; - * ------------------------------- - * - */ - - /** - * @typedef FQuery - * @type Object - * @param {string} tag feature tag - * @param {string} script feature script - * @param {ContextParams} contextParams context params - */ - - /** - * Lookup a feature using a query parameters - * @param {FQuery} query feature query - */ - FeatureQuery.prototype.lookupFeature = function (query) { - var contextParams = query.contextParams; - var currentIndex = contextParams.index; - var feature = this.getFeature({ - tag: query.tag, script: query.script - }); - if (!feature) { return new Error( - "font '" + (this.font.names.fullName.en) + "' " + - "doesn't support feature '" + (query.tag) + "' " + - "for script '" + (query.script) + "'." - ); } - var lookups = this.getFeatureLookups(feature); - var substitutions = [].concat(contextParams.context); - for (var l = 0; l < lookups.length; l++) { - var lookupTable = lookups[l]; - var subtables = this.getLookupSubtables(lookupTable); - for (var s = 0; s < subtables.length; s++) { - var subtable = subtables[s]; - var substType = this.getSubstitutionType(lookupTable, subtable); - var lookup = this.getLookupMethod(lookupTable, subtable); - var substitution = (void 0); - switch (substType) { - case '11': - substitution = lookup(contextParams.current); - if (substitution) { - substitutions.splice(currentIndex, 1, new SubstitutionAction({ - id: 11, tag: query.tag, substitution: substitution - })); - } - break; - case '12': - substitution = lookup(contextParams.current); - if (substitution) { - substitutions.splice(currentIndex, 1, new SubstitutionAction({ - id: 12, tag: query.tag, substitution: substitution - })); - } - break; - case '63': - substitution = lookup(contextParams); - if (Array.isArray(substitution) && substitution.length) { - substitutions.splice(currentIndex, 1, new SubstitutionAction({ - id: 63, tag: query.tag, substitution: substitution - })); - } - break; - case '41': - substitution = lookup(contextParams); - if (substitution) { - substitutions.splice(currentIndex, 1, new SubstitutionAction({ - id: 41, tag: query.tag, substitution: substitution - })); - } - break; - case '21': - substitution = lookup(contextParams.current); - if (substitution) { - substitutions.splice(currentIndex, 1, new SubstitutionAction({ - id: 21, tag: query.tag, substitution: substitution - })); - } - break; - } - contextParams = new ContextParams(substitutions, currentIndex); - if (Array.isArray(substitution) && !substitution.length) { continue; } - substitution = null; - } - } - return substitutions.length ? substitutions : null; - }; - - /** - * Checks if a font supports a specific features - * @param {FQuery} query feature query object - */ - FeatureQuery.prototype.supports = function (query) { - if (!query.script) { return false; } - this.getScriptFeatures(query.script); - var supportedScript = this.features.hasOwnProperty(query.script); - if (!query.tag) { return supportedScript; } - var supportedFeature = ( - this.features[query.script].some(function (feature) { return feature.tag === query.tag; }) - ); - return supportedScript && supportedFeature; - }; - - /** - * Get lookup table subtables - * @param {any} lookupTable lookup table - */ - FeatureQuery.prototype.getLookupSubtables = function (lookupTable) { - return lookupTable.subtables || null; - }; - - /** - * Get lookup table by index - * @param {number} index lookup table index - */ - FeatureQuery.prototype.getLookupByIndex = function (index) { - var lookups = this.font.tables.gsub.lookups; - return lookups[index] || null; - }; - - /** - * Get lookup tables for a feature - * @param {string} feature - */ - FeatureQuery.prototype.getFeatureLookups = function (feature) { - // TODO: memoize - return feature.lookupListIndexes.map(this.getLookupByIndex.bind(this)); - }; - - /** - * Query a feature by it's properties - * @param {any} query an object that describes the properties of a query - */ - FeatureQuery.prototype.getFeature = function getFeature(query) { - if (!this.font) { return { FAIL: "No font was found"}; } - if (!this.features.hasOwnProperty(query.script)) { - this.getScriptFeatures(query.script); - } - var scriptFeatures = this.features[query.script]; - if (!scriptFeatures) { return ( - { FAIL: ("No feature for script " + (query.script))} - ); } - if (!scriptFeatures.tags[query.tag]) { return null; } - return this.features[query.script].tags[query.tag]; - }; - - /** - * Arabic word context checkers - */ - - function arabicWordStartCheck(contextParams) { - var char = contextParams.current; - var prevChar = contextParams.get(-1); - return ( - // ? arabic first char - (prevChar === null && isArabicChar(char)) || - // ? arabic char preceded with a non arabic char - (!isArabicChar(prevChar) && isArabicChar(char)) - ); - } - - function arabicWordEndCheck(contextParams) { - var nextChar = contextParams.get(1); - return ( - // ? last arabic char - (nextChar === null) || - // ? next char is not arabic - (!isArabicChar(nextChar)) - ); - } - - var arabicWordCheck = { - startCheck: arabicWordStartCheck, - endCheck: arabicWordEndCheck - }; - - /** - * Arabic sentence context checkers - */ - - function arabicSentenceStartCheck(contextParams) { - var char = contextParams.current; - var prevChar = contextParams.get(-1); - return ( - // ? an arabic char preceded with a non arabic char - (isArabicChar(char) || isTashkeelArabicChar(char)) && - !isArabicChar(prevChar) - ); - } - - function arabicSentenceEndCheck(contextParams) { - var nextChar = contextParams.get(1); - switch (true) { - case nextChar === null: - return true; - case (!isArabicChar(nextChar) && !isTashkeelArabicChar(nextChar)): - var nextIsWhitespace = isWhiteSpace(nextChar); - if (!nextIsWhitespace) { return true; } - if (nextIsWhitespace) { - var arabicCharAhead = false; - arabicCharAhead = ( - contextParams.lookahead.some( - function (c) { return isArabicChar(c) || isTashkeelArabicChar(c); } - ) - ); - if (!arabicCharAhead) { return true; } - } - break; - default: - return false; - } - } - - var arabicSentenceCheck = { - startCheck: arabicSentenceStartCheck, - endCheck: arabicSentenceEndCheck - }; - - /** - * Apply single substitution format 1 - * @param {Array} substitutions substitutions - * @param {any} tokens a list of tokens - * @param {number} index token index - */ - function singleSubstitutionFormat1$1(action, tokens, index) { - tokens[index].setState(action.tag, action.substitution); - } - - /** - * Apply single substitution format 2 - * @param {Array} substitutions substitutions - * @param {any} tokens a list of tokens - * @param {number} index token index - */ - function singleSubstitutionFormat2$1(action, tokens, index) { - tokens[index].setState(action.tag, action.substitution); - } - - /** - * Apply chaining context substitution format 3 - * @param {Array} substitutions substitutions - * @param {any} tokens a list of tokens - * @param {number} index token index - */ - function chainingSubstitutionFormat3$1(action, tokens, index) { - action.substitution.forEach(function (subst, offset) { - var token = tokens[index + offset]; - token.setState(action.tag, subst); - }); - } - - /** - * Apply ligature substitution format 1 - * @param {Array} substitutions substitutions - * @param {any} tokens a list of tokens - * @param {number} index token index - */ - function ligatureSubstitutionFormat1$1(action, tokens, index) { - var token = tokens[index]; - token.setState(action.tag, action.substitution.ligGlyph); - var compsCount = action.substitution.components.length; - for (var i = 0; i < compsCount; i++) { - token = tokens[index + i + 1]; - token.setState('deleted', true); - } - } - - /** - * Supported substitutions - */ - var SUBSTITUTIONS = { - 11: singleSubstitutionFormat1$1, - 12: singleSubstitutionFormat2$1, - 63: chainingSubstitutionFormat3$1, - 41: ligatureSubstitutionFormat1$1 - }; - - /** - * Apply substitutions to a list of tokens - * @param {Array} substitutions substitutions - * @param {any} tokens a list of tokens - * @param {number} index token index - */ - function applySubstitution(action, tokens, index) { - if (action instanceof SubstitutionAction && SUBSTITUTIONS[action.id]) { - SUBSTITUTIONS[action.id](action, tokens, index); - } - } - - /** - * Apply Arabic presentation forms to a range of tokens - */ - - /** - * Check if a char can be connected to it's preceding char - * @param {ContextParams} charContextParams context params of a char - */ - function willConnectPrev(charContextParams) { - var backtrack = [].concat(charContextParams.backtrack); - for (var i = backtrack.length - 1; i >= 0; i--) { - var prevChar = backtrack[i]; - var isolated = isIsolatedArabicChar(prevChar); - var tashkeel = isTashkeelArabicChar(prevChar); - if (!isolated && !tashkeel) { return true; } - if (isolated) { return false; } - } - return false; - } - - /** - * Check if a char can be connected to it's proceeding char - * @param {ContextParams} charContextParams context params of a char - */ - function willConnectNext(charContextParams) { - if (isIsolatedArabicChar(charContextParams.current)) { return false; } - for (var i = 0; i < charContextParams.lookahead.length; i++) { - var nextChar = charContextParams.lookahead[i]; - var tashkeel = isTashkeelArabicChar(nextChar); - if (!tashkeel) { return true; } - } - return false; - } - - /** - * Apply arabic presentation forms to a list of tokens - * @param {ContextRange} range a range of tokens - */ - function arabicPresentationForms(range) { - var this$1 = this; - - var script = 'arab'; - var tags = this.featuresTags[script]; - var tokens = this.tokenizer.getRangeTokens(range); - if (tokens.length === 1) { return; } - var contextParams = new ContextParams( - tokens.map(function (token) { return token.getState('glyphIndex'); } - ), 0); - var charContextParams = new ContextParams( - tokens.map(function (token) { return token.char; } - ), 0); - tokens.forEach(function (token, index) { - if (isTashkeelArabicChar(token.char)) { return; } - contextParams.setCurrentIndex(index); - charContextParams.setCurrentIndex(index); - var CONNECT = 0; // 2 bits 00 (10: can connect next) (01: can connect prev) - if (willConnectPrev(charContextParams)) { CONNECT |= 1; } - if (willConnectNext(charContextParams)) { CONNECT |= 2; } - var tag; - switch (CONNECT) { - case 1: (tag = 'fina'); break; - case 2: (tag = 'init'); break; - case 3: (tag = 'medi'); break; - } - if (tags.indexOf(tag) === -1) { return; } - var substitutions = this$1.query.lookupFeature({ - tag: tag, script: script, contextParams: contextParams - }); - if (substitutions instanceof Error) { return console.info(substitutions.message); } - substitutions.forEach(function (action, index) { - if (action instanceof SubstitutionAction) { - applySubstitution(action, tokens, index); - contextParams.context[index] = action.substitution; - } - }); - }); - } - - /** - * Apply Arabic required ligatures feature to a range of tokens - */ - - /** - * Update context params - * @param {any} tokens a list of tokens - * @param {number} index current item index - */ - function getContextParams(tokens, index) { - var context = tokens.map(function (token) { return token.activeState.value; }); - return new ContextParams(context, index || 0); - } - - /** - * Apply Arabic required ligatures to a context range - * @param {ContextRange} range a range of tokens - */ - function arabicRequiredLigatures(range) { - var this$1 = this; - - var script = 'arab'; - var tokens = this.tokenizer.getRangeTokens(range); - var contextParams = getContextParams(tokens); - contextParams.context.forEach(function (glyphIndex, index) { - contextParams.setCurrentIndex(index); - var substitutions = this$1.query.lookupFeature({ - tag: 'rlig', script: script, contextParams: contextParams - }); - if (substitutions.length) { - substitutions.forEach( - function (action) { return applySubstitution(action, tokens, index); } - ); - contextParams = getContextParams(tokens); - } - }); - } - - /** - * Latin word context checkers - */ - - function latinWordStartCheck(contextParams) { - var char = contextParams.current; - var prevChar = contextParams.get(-1); - return ( - // ? latin first char - (prevChar === null && isLatinChar(char)) || - // ? latin char preceded with a non latin char - (!isLatinChar(prevChar) && isLatinChar(char)) - ); - } - - function latinWordEndCheck(contextParams) { - var nextChar = contextParams.get(1); - return ( - // ? last latin char - (nextChar === null) || - // ? next char is not latin - (!isLatinChar(nextChar)) - ); - } - - var latinWordCheck = { - startCheck: latinWordStartCheck, - endCheck: latinWordEndCheck - }; - - /** - * Apply Latin ligature feature to a range of tokens - */ - - /** - * Update context params - * @param {any} tokens a list of tokens - * @param {number} index current item index - */ - function getContextParams$1(tokens, index) { - var context = tokens.map(function (token) { return token.activeState.value; }); - return new ContextParams(context, index || 0); - } - - /** - * Apply Arabic required ligatures to a context range - * @param {ContextRange} range a range of tokens - */ - function latinLigature(range) { - var this$1 = this; - - var script = 'latn'; - var tokens = this.tokenizer.getRangeTokens(range); - var contextParams = getContextParams$1(tokens); - contextParams.context.forEach(function (glyphIndex, index) { - contextParams.setCurrentIndex(index); - var substitutions = this$1.query.lookupFeature({ - tag: 'liga', script: script, contextParams: contextParams - }); - if (substitutions.length) { - substitutions.forEach( - function (action) { return applySubstitution(action, tokens, index); } - ); - contextParams = getContextParams$1(tokens); - } - }); - } - - /** - * Infer bidirectional properties for a given text and apply - * the corresponding layout rules. - */ - - /** - * Create Bidi. features - * @param {string} baseDir text base direction. value either 'ltr' or 'rtl' - */ - function Bidi(baseDir) { - this.baseDir = baseDir || 'ltr'; - this.tokenizer = new Tokenizer(); - this.featuresTags = {}; - } - - /** - * Sets Bidi text - * @param {string} text a text input - */ - Bidi.prototype.setText = function (text) { - this.text = text; - }; - - /** - * Store essential context checks: - * arabic word check for applying gsub features - * arabic sentence check for adjusting arabic layout - */ - Bidi.prototype.contextChecks = ({ - latinWordCheck: latinWordCheck, - arabicWordCheck: arabicWordCheck, - arabicSentenceCheck: arabicSentenceCheck - }); - - /** - * Register arabic word check - */ - function registerContextChecker(checkId) { - var check = this.contextChecks[(checkId + "Check")]; - return this.tokenizer.registerContextChecker( - checkId, check.startCheck, check.endCheck - ); - } - - /** - * Perform pre tokenization procedure then - * tokenize text input - */ - function tokenizeText() { - registerContextChecker.call(this, 'latinWord'); - registerContextChecker.call(this, 'arabicWord'); - registerContextChecker.call(this, 'arabicSentence'); - return this.tokenizer.tokenize(this.text); - } - - /** - * Reverse arabic sentence layout - * TODO: check base dir before applying adjustments - priority low - */ - function reverseArabicSentences() { - var this$1 = this; - - var ranges = this.tokenizer.getContextRanges('arabicSentence'); - ranges.forEach(function (range) { - var rangeTokens = this$1.tokenizer.getRangeTokens(range); - this$1.tokenizer.replaceRange( - range.startIndex, - range.endOffset, - rangeTokens.reverse() - ); - }); - } - - /** - * Register supported features tags - * @param {script} script script tag - * @param {Array} tags features tags list - */ - Bidi.prototype.registerFeatures = function (script, tags) { - var this$1 = this; - - var supportedTags = tags.filter( - function (tag) { return this$1.query.supports({script: script, tag: tag}); } - ); - if (!this.featuresTags.hasOwnProperty(script)) { - this.featuresTags[script] = supportedTags; - } else { - this.featuresTags[script] = - this.featuresTags[script].concat(supportedTags); - } - }; - - /** - * Apply GSUB features - * @param {Array} tagsList a list of features tags - * @param {string} script a script tag - * @param {Font} font opentype font instance - */ - Bidi.prototype.applyFeatures = function (font, features) { - if (!font) { throw new Error( - 'No valid font was provided to apply features' - ); } - if (!this.query) { this.query = new FeatureQuery(font); } - for (var f = 0; f < features.length; f++) { - var feature = features[f]; - if (!this.query.supports({script: feature.script})) { continue; } - this.registerFeatures(feature.script, feature.tags); - } - }; - - /** - * Register a state modifier - * @param {string} modifierId state modifier id - * @param {function} condition a predicate function that returns true or false - * @param {function} modifier a modifier function to set token state - */ - Bidi.prototype.registerModifier = function (modifierId, condition, modifier) { - this.tokenizer.registerModifier(modifierId, condition, modifier); - }; - - /** - * Check if 'glyphIndex' is registered - */ - function checkGlyphIndexStatus() { - if (this.tokenizer.registeredModifiers.indexOf('glyphIndex') === -1) { - throw new Error( - 'glyphIndex modifier is required to apply ' + - 'arabic presentation features.' - ); - } - } - - /** - * Apply arabic presentation forms features - */ - function applyArabicPresentationForms() { - var this$1 = this; - - var script = 'arab'; - if (!this.featuresTags.hasOwnProperty(script)) { return; } - checkGlyphIndexStatus.call(this); - var ranges = this.tokenizer.getContextRanges('arabicWord'); - ranges.forEach(function (range) { - arabicPresentationForms.call(this$1, range); - }); - } - - /** - * Apply required arabic ligatures - */ - function applyArabicRequireLigatures() { - var this$1 = this; - - var script = 'arab'; - if (!this.featuresTags.hasOwnProperty(script)) { return; } - var tags = this.featuresTags[script]; - if (tags.indexOf('rlig') === -1) { return; } - checkGlyphIndexStatus.call(this); - var ranges = this.tokenizer.getContextRanges('arabicWord'); - ranges.forEach(function (range) { - arabicRequiredLigatures.call(this$1, range); - }); - } - - /** - * Apply required arabic ligatures - */ - function applyLatinLigatures() { - var this$1 = this; - - var script = 'latn'; - if (!this.featuresTags.hasOwnProperty(script)) { return; } - var tags = this.featuresTags[script]; - if (tags.indexOf('liga') === -1) { return; } - checkGlyphIndexStatus.call(this); - var ranges = this.tokenizer.getContextRanges('latinWord'); - ranges.forEach(function (range) { - latinLigature.call(this$1, range); - }); - } - - /** - * Check if a context is registered - * @param {string} contextId context id - */ - Bidi.prototype.checkContextReady = function (contextId) { - return !!this.tokenizer.getContext(contextId); - }; - - /** - * Apply features to registered contexts - */ - Bidi.prototype.applyFeaturesToContexts = function () { - if (this.checkContextReady('arabicWord')) { - applyArabicPresentationForms.call(this); - applyArabicRequireLigatures.call(this); - } - if (this.checkContextReady('latinWord')) { - applyLatinLigatures.call(this); - } - if (this.checkContextReady('arabicSentence')) { - reverseArabicSentences.call(this); - } - }; - - /** - * process text input - * @param {string} text an input text - */ - Bidi.prototype.processText = function(text) { - if (!this.text || this.text !== text) { - this.setText(text); - tokenizeText.call(this); - this.applyFeaturesToContexts(); - } - }; - - /** - * Process a string of text to identify and adjust - * bidirectional text entities. - * @param {string} text input text - */ - Bidi.prototype.getBidiText = function (text) { - this.processText(text); - return this.tokenizer.getText(); - }; - - /** - * Get the current state index of each token - * @param {text} text an input text - */ - Bidi.prototype.getTextGlyphs = function (text) { - this.processText(text); - var indexes = []; - for (var i = 0; i < this.tokenizer.tokens.length; i++) { - var token = this.tokenizer.tokens[i]; - if (token.state.deleted) { continue; } - var index = token.activeState.value; - indexes.push(Array.isArray(index) ? index[0] : index); - } - return indexes; - }; - - // The Font object - - /** - * @typedef FontOptions - * @type Object - * @property {Boolean} empty - whether to create a new empty font - * @property {string} familyName - * @property {string} styleName - * @property {string=} fullName - * @property {string=} postScriptName - * @property {string=} designer - * @property {string=} designerURL - * @property {string=} manufacturer - * @property {string=} manufacturerURL - * @property {string=} license - * @property {string=} licenseURL - * @property {string=} version - * @property {string=} description - * @property {string=} copyright - * @property {string=} trademark - * @property {Number} unitsPerEm - * @property {Number} ascender - * @property {Number} descender - * @property {Number} createdTimestamp - * @property {string=} weightClass - * @property {string=} widthClass - * @property {string=} fsSelection - */ - - /** - * A Font represents a loaded OpenType font file. - * It contains a set of glyphs and methods to draw text on a drawing context, - * or to get a path representing the text. - * @exports opentype.Font - * @class - * @param {FontOptions} - * @constructor - */ - function Font(options) { - options = options || {}; - options.tables = options.tables || {}; - - if (!options.empty) { - // Check that we've provided the minimum set of names. - checkArgument(options.familyName, 'When creating a new Font object, familyName is required.'); - checkArgument(options.styleName, 'When creating a new Font object, styleName is required.'); - checkArgument(options.unitsPerEm, 'When creating a new Font object, unitsPerEm is required.'); - checkArgument(options.ascender, 'When creating a new Font object, ascender is required.'); - checkArgument(options.descender <= 0, 'When creating a new Font object, negative descender value is required.'); - - // OS X will complain if the names are empty, so we put a single space everywhere by default. - this.names = { - fontFamily: {en: options.familyName || ' '}, - fontSubfamily: {en: options.styleName || ' '}, - fullName: {en: options.fullName || options.familyName + ' ' + options.styleName}, - // postScriptName may not contain any whitespace - postScriptName: {en: options.postScriptName || (options.familyName + options.styleName).replace(/\s/g, '')}, - designer: {en: options.designer || ' '}, - designerURL: {en: options.designerURL || ' '}, - manufacturer: {en: options.manufacturer || ' '}, - manufacturerURL: {en: options.manufacturerURL || ' '}, - license: {en: options.license || ' '}, - licenseURL: {en: options.licenseURL || ' '}, - version: {en: options.version || 'Version 0.1'}, - description: {en: options.description || ' '}, - copyright: {en: options.copyright || ' '}, - trademark: {en: options.trademark || ' '} - }; - this.unitsPerEm = options.unitsPerEm || 1000; - this.ascender = options.ascender; - this.descender = options.descender; - this.createdTimestamp = options.createdTimestamp; - this.tables = Object.assign(options.tables, { - os2: Object.assign({ - usWeightClass: options.weightClass || this.usWeightClasses.MEDIUM, - usWidthClass: options.widthClass || this.usWidthClasses.MEDIUM, - fsSelection: options.fsSelection || this.fsSelectionValues.REGULAR, - }, options.tables.os2) - }); - } - - this.supported = true; // Deprecated: parseBuffer will throw an error if font is not supported. - this.glyphs = new glyphset.GlyphSet(this, options.glyphs || []); - this.encoding = new DefaultEncoding(this); - this.position = new Position(this); - this.substitution = new Substitution(this); - this.tables = this.tables || {}; - - // needed for low memory mode only. - this._push = null; - this._hmtxTableData = {}; - - Object.defineProperty(this, 'hinting', { - get: function() { - if (this._hinting) { return this._hinting; } - if (this.outlinesFormat === 'truetype') { - return (this._hinting = new Hinting(this)); - } - } - }); - } - - /** - * Check if the font has a glyph for the given character. - * @param {string} - * @return {Boolean} - */ - Font.prototype.hasChar = function(c) { - return this.encoding.charToGlyphIndex(c) !== null; - }; - - /** - * Convert the given character to a single glyph index. - * Note that this function assumes that there is a one-to-one mapping between - * the given character and a glyph; for complex scripts this might not be the case. - * @param {string} - * @return {Number} - */ - Font.prototype.charToGlyphIndex = function(s) { - return this.encoding.charToGlyphIndex(s); - }; - - /** - * Convert the given character to a single Glyph object. - * Note that this function assumes that there is a one-to-one mapping between - * the given character and a glyph; for complex scripts this might not be the case. - * @param {string} - * @return {opentype.Glyph} - */ - Font.prototype.charToGlyph = function(c) { - var glyphIndex = this.charToGlyphIndex(c); - var glyph = this.glyphs.get(glyphIndex); - if (!glyph) { - // .notdef - glyph = this.glyphs.get(0); - } - - return glyph; - }; - - /** - * Update features - * @param {any} options features options - */ - Font.prototype.updateFeatures = function (options) { - // TODO: update all features options not only 'latn'. - return this.defaultRenderOptions.features.map(function (feature) { - if (feature.script === 'latn') { - return { - script: 'latn', - tags: feature.tags.filter(function (tag) { return options[tag]; }) - }; - } else { - return feature; - } - }); - }; - - /** - * Convert the given text to a list of Glyph objects. - * Note that there is no strict one-to-one mapping between characters and - * glyphs, so the list of returned glyphs can be larger or smaller than the - * length of the given string. - * @param {string} - * @param {GlyphRenderOptions} [options] - * @return {opentype.Glyph[]} - */ - Font.prototype.stringToGlyphs = function(s, options) { - var this$1 = this; - - - var bidi = new Bidi(); - - // Create and register 'glyphIndex' state modifier - var charToGlyphIndexMod = function (token) { return this$1.charToGlyphIndex(token.char); }; - bidi.registerModifier('glyphIndex', null, charToGlyphIndexMod); - - // roll-back to default features - var features = options ? - this.updateFeatures(options.features) : - this.defaultRenderOptions.features; - - bidi.applyFeatures(this, features); - - var indexes = bidi.getTextGlyphs(s); - - var length = indexes.length; - - // convert glyph indexes to glyph objects - var glyphs = new Array(length); - var notdef = this.glyphs.get(0); - for (var i = 0; i < length; i += 1) { - glyphs[i] = this.glyphs.get(indexes[i]) || notdef; - } - return glyphs; - }; - - /** - * @param {string} - * @return {Number} - */ - Font.prototype.nameToGlyphIndex = function(name) { - return this.glyphNames.nameToGlyphIndex(name); - }; - - /** - * @param {string} - * @return {opentype.Glyph} - */ - Font.prototype.nameToGlyph = function(name) { - var glyphIndex = this.nameToGlyphIndex(name); - var glyph = this.glyphs.get(glyphIndex); - if (!glyph) { - // .notdef - glyph = this.glyphs.get(0); - } - - return glyph; - }; - - /** - * @param {Number} - * @return {String} - */ - Font.prototype.glyphIndexToName = function(gid) { - if (!this.glyphNames.glyphIndexToName) { - return ''; - } - - return this.glyphNames.glyphIndexToName(gid); - }; - - /** - * Retrieve the value of the kerning pair between the left glyph (or its index) - * and the right glyph (or its index). If no kerning pair is found, return 0. - * The kerning value gets added to the advance width when calculating the spacing - * between glyphs. - * For GPOS kerning, this method uses the default script and language, which covers - * most use cases. To have greater control, use font.position.getKerningValue . - * @param {opentype.Glyph} leftGlyph - * @param {opentype.Glyph} rightGlyph - * @return {Number} - */ - Font.prototype.getKerningValue = function(leftGlyph, rightGlyph) { - leftGlyph = leftGlyph.index || leftGlyph; - rightGlyph = rightGlyph.index || rightGlyph; - var gposKerning = this.position.defaultKerningTables; - if (gposKerning) { - return this.position.getKerningValue(gposKerning, leftGlyph, rightGlyph); - } - // "kern" table - return this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0; - }; - - /** - * @typedef GlyphRenderOptions - * @type Object - * @property {string} [script] - script used to determine which features to apply. By default, 'DFLT' or 'latn' is used. - * See https://www.microsoft.com/typography/otspec/scripttags.htm - * @property {string} [language='dflt'] - language system used to determine which features to apply. - * See https://www.microsoft.com/typography/developers/opentype/languagetags.aspx - * @property {boolean} [kerning=true] - whether to include kerning values - * @property {object} [features] - OpenType Layout feature tags. Used to enable or disable the features of the given script/language system. - * See https://www.microsoft.com/typography/otspec/featuretags.htm - */ - Font.prototype.defaultRenderOptions = { - kerning: true, - features: [ - /** - * these 4 features are required to render Arabic text properly - * and shouldn't be turned off when rendering arabic text. - */ - { script: 'arab', tags: ['init', 'medi', 'fina', 'rlig'] }, - { script: 'latn', tags: ['liga', 'rlig'] } - ] - }; - - /** - * Helper function that invokes the given callback for each glyph in the given text. - * The callback gets `(glyph, x, y, fontSize, options)`.* @param {string} text - * @param {string} text - The text to apply. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - * @param {Function} callback - */ - Font.prototype.forEachGlyph = function(text, x, y, fontSize, options, callback) { - x = x !== undefined ? x : 0; - y = y !== undefined ? y : 0; - fontSize = fontSize !== undefined ? fontSize : 72; - options = Object.assign({}, this.defaultRenderOptions, options); - var fontScale = 1 / this.unitsPerEm * fontSize; - var glyphs = this.stringToGlyphs(text, options); - var kerningLookups; - if (options.kerning) { - var script = options.script || this.position.getDefaultScriptName(); - kerningLookups = this.position.getKerningTables(script, options.language); - } - for (var i = 0; i < glyphs.length; i += 1) { - var glyph = glyphs[i]; - callback.call(this, glyph, x, y, fontSize, options); - if (glyph.advanceWidth) { - x += glyph.advanceWidth * fontScale; - } - - if (options.kerning && i < glyphs.length - 1) { - // We should apply position adjustment lookups in a more generic way. - // Here we only use the xAdvance value. - var kerningValue = kerningLookups ? - this.position.getKerningValue(kerningLookups, glyph.index, glyphs[i + 1].index) : - this.getKerningValue(glyph, glyphs[i + 1]); - x += kerningValue * fontScale; - } - - if (options.letterSpacing) { - x += options.letterSpacing * fontSize; - } else if (options.tracking) { - x += (options.tracking / 1000) * fontSize; - } - } - return x; - }; - - /** - * Create a Path object that represents the given text. - * @param {string} text - The text to create. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - * @return {opentype.Path} - */ - Font.prototype.getPath = function(text, x, y, fontSize, options) { - var fullPath = new Path(); - this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { - var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); - fullPath.extend(glyphPath); - }); - return fullPath; - }; - - /** - * Create an array of Path objects that represent the glyphs of a given text. - * @param {string} text - The text to create. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - * @return {opentype.Path[]} - */ - Font.prototype.getPaths = function(text, x, y, fontSize, options) { - var glyphPaths = []; - this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { - var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); - glyphPaths.push(glyphPath); - }); - - return glyphPaths; - }; - - /** - * Returns the advance width of a text. - * - * This is something different than Path.getBoundingBox() as for example a - * suffixed whitespace increases the advanceWidth but not the bounding box - * or an overhanging letter like a calligraphic 'f' might have a quite larger - * bounding box than its advance width. - * - * This corresponds to canvas2dContext.measureText(text).width - * - * @param {string} text - The text to create. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - * @return advance width - */ - Font.prototype.getAdvanceWidth = function(text, fontSize, options) { - return this.forEachGlyph(text, 0, 0, fontSize, options, function() {}); - }; - - /** - * Draw the text on the given drawing context. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {string} text - The text to create. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - */ - Font.prototype.draw = function(ctx, text, x, y, fontSize, options) { - this.getPath(text, x, y, fontSize, options).draw(ctx); - }; - - /** - * Draw the points of all glyphs in the text. - * On-curve points will be drawn in blue, off-curve points will be drawn in red. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {string} text - The text to create. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - */ - Font.prototype.drawPoints = function(ctx, text, x, y, fontSize, options) { - this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { - glyph.drawPoints(ctx, gX, gY, gFontSize); - }); - }; - - /** - * Draw lines indicating important font measurements for all glyphs in the text. - * Black lines indicate the origin of the coordinate system (point 0,0). - * Blue lines indicate the glyph bounding box. - * Green line indicates the advance width of the glyph. - * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. - * @param {string} text - The text to create. - * @param {number} [x=0] - Horizontal position of the beginning of the text. - * @param {number} [y=0] - Vertical position of the *baseline* of the text. - * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. - * @param {GlyphRenderOptions=} options - */ - Font.prototype.drawMetrics = function(ctx, text, x, y, fontSize, options) { - this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { - glyph.drawMetrics(ctx, gX, gY, gFontSize); - }); - }; - - /** - * @param {string} - * @return {string} - */ - Font.prototype.getEnglishName = function(name) { - var translations = this.names[name]; - if (translations) { - return translations.en; - } - }; - - /** - * Validate - */ - Font.prototype.validate = function() { - var _this = this; - - function assert(predicate, message) { - } - - function assertNamePresent(name) { - var englishName = _this.getEnglishName(name); - assert(englishName && englishName.trim().length > 0); - } - - // Identification information - assertNamePresent('fontFamily'); - assertNamePresent('weightName'); - assertNamePresent('manufacturer'); - assertNamePresent('copyright'); - assertNamePresent('version'); - - // Dimension information - assert(this.unitsPerEm > 0); - }; - - /** - * Convert the font object to a SFNT data structure. - * This structure contains all the necessary tables and metadata to create a binary OTF file. - * @return {opentype.Table} - */ - Font.prototype.toTables = function() { - return sfnt.fontToTable(this); - }; - /** - * @deprecated Font.toBuffer is deprecated. Use Font.toArrayBuffer instead. - */ - Font.prototype.toBuffer = function() { - console.warn('Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.'); - return this.toArrayBuffer(); - }; - /** - * Converts a `opentype.Font` into an `ArrayBuffer` - * @return {ArrayBuffer} - */ - Font.prototype.toArrayBuffer = function() { - var sfntTable = this.toTables(); - var bytes = sfntTable.encode(); - var buffer = new ArrayBuffer(bytes.length); - var intArray = new Uint8Array(buffer); - for (var i = 0; i < bytes.length; i++) { - intArray[i] = bytes[i]; - } - - return buffer; - }; - - /** - * Initiate a download of the OpenType font. - */ - Font.prototype.download = function(fileName) { - var familyName = this.getEnglishName('fontFamily'); - var styleName = this.getEnglishName('fontSubfamily'); - fileName = fileName || familyName.replace(/\s/g, '') + '-' + styleName + '.otf'; - var arrayBuffer = this.toArrayBuffer(); - - if (isBrowser()) { - window.URL = window.URL || window.webkitURL; - - if (window.URL) { - var dataView = new DataView(arrayBuffer); - var blob = new Blob([dataView], {type: 'font/opentype'}); - - var link = document.createElement('a'); - link.href = window.URL.createObjectURL(blob); - link.download = fileName; - - var event = document.createEvent('MouseEvents'); - event.initEvent('click', true, false); - link.dispatchEvent(event); - } else { - console.warn('Font file could not be downloaded. Try using a different browser.'); - } - } else { - var fs = require('fs'); - var buffer = arrayBufferToNodeBuffer(arrayBuffer); - fs.writeFileSync(fileName, buffer); - } - }; - /** - * @private - */ - Font.prototype.fsSelectionValues = { - ITALIC: 0x001, //1 - UNDERSCORE: 0x002, //2 - NEGATIVE: 0x004, //4 - OUTLINED: 0x008, //8 - STRIKEOUT: 0x010, //16 - BOLD: 0x020, //32 - REGULAR: 0x040, //64 - USER_TYPO_METRICS: 0x080, //128 - WWS: 0x100, //256 - OBLIQUE: 0x200 //512 - }; - - /** - * @private - */ - Font.prototype.usWidthClasses = { - ULTRA_CONDENSED: 1, - EXTRA_CONDENSED: 2, - CONDENSED: 3, - SEMI_CONDENSED: 4, - MEDIUM: 5, - SEMI_EXPANDED: 6, - EXPANDED: 7, - EXTRA_EXPANDED: 8, - ULTRA_EXPANDED: 9 - }; - - /** - * @private - */ - Font.prototype.usWeightClasses = { - THIN: 100, - EXTRA_LIGHT: 200, - LIGHT: 300, - NORMAL: 400, - MEDIUM: 500, - SEMI_BOLD: 600, - BOLD: 700, - EXTRA_BOLD: 800, - BLACK: 900 - }; - - // The `fvar` table stores font variation axes and instances. - - function addName(name, names) { - var nameString = JSON.stringify(name); - var nameID = 256; - for (var nameKey in names) { - var n = parseInt(nameKey); - if (!n || n < 256) { - continue; - } - - if (JSON.stringify(names[nameKey]) === nameString) { - return n; - } - - if (nameID <= n) { - nameID = n + 1; - } - } - - names[nameID] = name; - return nameID; - } - - function makeFvarAxis(n, axis, names) { - var nameID = addName(axis.name, names); - return [ - {name: 'tag_' + n, type: 'TAG', value: axis.tag}, - {name: 'minValue_' + n, type: 'FIXED', value: axis.minValue << 16}, - {name: 'defaultValue_' + n, type: 'FIXED', value: axis.defaultValue << 16}, - {name: 'maxValue_' + n, type: 'FIXED', value: axis.maxValue << 16}, - {name: 'flags_' + n, type: 'USHORT', value: 0}, - {name: 'nameID_' + n, type: 'USHORT', value: nameID} - ]; - } - - function parseFvarAxis(data, start, names) { - var axis = {}; - var p = new parse.Parser(data, start); - axis.tag = p.parseTag(); - axis.minValue = p.parseFixed(); - axis.defaultValue = p.parseFixed(); - axis.maxValue = p.parseFixed(); - p.skip('uShort', 1); // reserved for flags; no values defined - axis.name = names[p.parseUShort()] || {}; - return axis; - } - - function makeFvarInstance(n, inst, axes, names) { - var nameID = addName(inst.name, names); - var fields = [ - {name: 'nameID_' + n, type: 'USHORT', value: nameID}, - {name: 'flags_' + n, type: 'USHORT', value: 0} - ]; - - for (var i = 0; i < axes.length; ++i) { - var axisTag = axes[i].tag; - fields.push({ - name: 'axis_' + n + ' ' + axisTag, - type: 'FIXED', - value: inst.coordinates[axisTag] << 16 - }); - } - - return fields; - } - - function parseFvarInstance(data, start, axes, names) { - var inst = {}; - var p = new parse.Parser(data, start); - inst.name = names[p.parseUShort()] || {}; - p.skip('uShort', 1); // reserved for flags; no values defined - - inst.coordinates = {}; - for (var i = 0; i < axes.length; ++i) { - inst.coordinates[axes[i].tag] = p.parseFixed(); - } - - return inst; - } - - function makeFvarTable(fvar, names) { - var result = new table.Table('fvar', [ - {name: 'version', type: 'ULONG', value: 0x10000}, - {name: 'offsetToData', type: 'USHORT', value: 0}, - {name: 'countSizePairs', type: 'USHORT', value: 2}, - {name: 'axisCount', type: 'USHORT', value: fvar.axes.length}, - {name: 'axisSize', type: 'USHORT', value: 20}, - {name: 'instanceCount', type: 'USHORT', value: fvar.instances.length}, - {name: 'instanceSize', type: 'USHORT', value: 4 + fvar.axes.length * 4} - ]); - result.offsetToData = result.sizeOf(); - - for (var i = 0; i < fvar.axes.length; i++) { - result.fields = result.fields.concat(makeFvarAxis(i, fvar.axes[i], names)); - } - - for (var j = 0; j < fvar.instances.length; j++) { - result.fields = result.fields.concat(makeFvarInstance(j, fvar.instances[j], fvar.axes, names)); - } - - return result; - } - - function parseFvarTable(data, start, names) { - var p = new parse.Parser(data, start); - var tableVersion = p.parseULong(); - check.argument(tableVersion === 0x00010000, 'Unsupported fvar table version.'); - var offsetToData = p.parseOffset16(); - // Skip countSizePairs. - p.skip('uShort', 1); - var axisCount = p.parseUShort(); - var axisSize = p.parseUShort(); - var instanceCount = p.parseUShort(); - var instanceSize = p.parseUShort(); - - var axes = []; - for (var i = 0; i < axisCount; i++) { - axes.push(parseFvarAxis(data, start + offsetToData + i * axisSize, names)); - } - - var instances = []; - var instanceStart = start + offsetToData + axisCount * axisSize; - for (var j = 0; j < instanceCount; j++) { - instances.push(parseFvarInstance(data, instanceStart + j * instanceSize, axes, names)); - } - - return {axes: axes, instances: instances}; - } - - var fvar = { make: makeFvarTable, parse: parseFvarTable }; - - // The `GPOS` table contains kerning pairs, among other things. - - var subtableParsers$1 = new Array(10); // subtableParsers[0] is unused - - // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-1-single-adjustment-positioning-subtable - // this = Parser instance - subtableParsers$1[1] = function parseLookup1() { - var start = this.offset + this.relativeOffset; - var posformat = this.parseUShort(); - if (posformat === 1) { - return { - posFormat: 1, - coverage: this.parsePointer(Parser.coverage), - value: this.parseValueRecord() - }; - } else if (posformat === 2) { - return { - posFormat: 2, - coverage: this.parsePointer(Parser.coverage), - values: this.parseValueRecordList() - }; - } - check.assert(false, '0x' + start.toString(16) + ': GPOS lookup type 1 format must be 1 or 2.'); - }; - - // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-2-pair-adjustment-positioning-subtable - subtableParsers$1[2] = function parseLookup2() { - var start = this.offset + this.relativeOffset; - var posFormat = this.parseUShort(); - check.assert(posFormat === 1 || posFormat === 2, '0x' + start.toString(16) + ': GPOS lookup type 2 format must be 1 or 2.'); - var coverage = this.parsePointer(Parser.coverage); - var valueFormat1 = this.parseUShort(); - var valueFormat2 = this.parseUShort(); - if (posFormat === 1) { - // Adjustments for Glyph Pairs - return { - posFormat: posFormat, - coverage: coverage, - valueFormat1: valueFormat1, - valueFormat2: valueFormat2, - pairSets: this.parseList(Parser.pointer(Parser.list(function() { - return { // pairValueRecord - secondGlyph: this.parseUShort(), - value1: this.parseValueRecord(valueFormat1), - value2: this.parseValueRecord(valueFormat2) - }; - }))) - }; - } else if (posFormat === 2) { - var classDef1 = this.parsePointer(Parser.classDef); - var classDef2 = this.parsePointer(Parser.classDef); - var class1Count = this.parseUShort(); - var class2Count = this.parseUShort(); - return { - // Class Pair Adjustment - posFormat: posFormat, - coverage: coverage, - valueFormat1: valueFormat1, - valueFormat2: valueFormat2, - classDef1: classDef1, - classDef2: classDef2, - class1Count: class1Count, - class2Count: class2Count, - classRecords: this.parseList(class1Count, Parser.list(class2Count, function() { - return { - value1: this.parseValueRecord(valueFormat1), - value2: this.parseValueRecord(valueFormat2) - }; - })) - }; - } - }; - - subtableParsers$1[3] = function parseLookup3() { return { error: 'GPOS Lookup 3 not supported' }; }; - subtableParsers$1[4] = function parseLookup4() { return { error: 'GPOS Lookup 4 not supported' }; }; - subtableParsers$1[5] = function parseLookup5() { return { error: 'GPOS Lookup 5 not supported' }; }; - subtableParsers$1[6] = function parseLookup6() { return { error: 'GPOS Lookup 6 not supported' }; }; - subtableParsers$1[7] = function parseLookup7() { return { error: 'GPOS Lookup 7 not supported' }; }; - subtableParsers$1[8] = function parseLookup8() { return { error: 'GPOS Lookup 8 not supported' }; }; - subtableParsers$1[9] = function parseLookup9() { return { error: 'GPOS Lookup 9 not supported' }; }; - - // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos - function parseGposTable(data, start) { - start = start || 0; - var p = new Parser(data, start); - var tableVersion = p.parseVersion(1); - check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GPOS table version ' + tableVersion); - - if (tableVersion === 1) { - return { - version: tableVersion, - scripts: p.parseScriptList(), - features: p.parseFeatureList(), - lookups: p.parseLookupList(subtableParsers$1) - }; - } else { - return { - version: tableVersion, - scripts: p.parseScriptList(), - features: p.parseFeatureList(), - lookups: p.parseLookupList(subtableParsers$1), - variations: p.parseFeatureVariationsList() - }; - } - - } - - // GPOS Writing ////////////////////////////////////////////// - // NOT SUPPORTED - var subtableMakers$1 = new Array(10); - - function makeGposTable(gpos) { - return new table.Table('GPOS', [ - {name: 'version', type: 'ULONG', value: 0x10000}, - {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gpos.scripts)}, - {name: 'features', type: 'TABLE', value: new table.FeatureList(gpos.features)}, - {name: 'lookups', type: 'TABLE', value: new table.LookupList(gpos.lookups, subtableMakers$1)} - ]); - } - - var gpos = { parse: parseGposTable, make: makeGposTable }; - - // The `kern` table contains kerning pairs. - - function parseWindowsKernTable(p) { - var pairs = {}; - // Skip nTables. - p.skip('uShort'); - var subtableVersion = p.parseUShort(); - check.argument(subtableVersion === 0, 'Unsupported kern sub-table version.'); - // Skip subtableLength, subtableCoverage - p.skip('uShort', 2); - var nPairs = p.parseUShort(); - // Skip searchRange, entrySelector, rangeShift. - p.skip('uShort', 3); - for (var i = 0; i < nPairs; i += 1) { - var leftIndex = p.parseUShort(); - var rightIndex = p.parseUShort(); - var value = p.parseShort(); - pairs[leftIndex + ',' + rightIndex] = value; - } - return pairs; - } - - function parseMacKernTable(p) { - var pairs = {}; - // The Mac kern table stores the version as a fixed (32 bits) but we only loaded the first 16 bits. - // Skip the rest. - p.skip('uShort'); - var nTables = p.parseULong(); - //check.argument(nTables === 1, 'Only 1 subtable is supported (got ' + nTables + ').'); - if (nTables > 1) { - console.warn('Only the first kern subtable is supported.'); - } - p.skip('uLong'); - var coverage = p.parseUShort(); - var subtableVersion = coverage & 0xFF; - p.skip('uShort'); - if (subtableVersion === 0) { - var nPairs = p.parseUShort(); - // Skip searchRange, entrySelector, rangeShift. - p.skip('uShort', 3); - for (var i = 0; i < nPairs; i += 1) { - var leftIndex = p.parseUShort(); - var rightIndex = p.parseUShort(); - var value = p.parseShort(); - pairs[leftIndex + ',' + rightIndex] = value; - } - } - return pairs; - } - - // Parse the `kern` table which contains kerning pairs. - function parseKernTable(data, start) { - var p = new parse.Parser(data, start); - var tableVersion = p.parseUShort(); - if (tableVersion === 0) { - return parseWindowsKernTable(p); - } else if (tableVersion === 1) { - return parseMacKernTable(p); - } else { - throw new Error('Unsupported kern table version (' + tableVersion + ').'); - } - } - - var kern = { parse: parseKernTable }; - - // The `loca` table stores the offsets to the locations of the glyphs in the font. - - // Parse the `loca` table. This table stores the offsets to the locations of the glyphs in the font, - // relative to the beginning of the glyphData table. - // The number of glyphs stored in the `loca` table is specified in the `maxp` table (under numGlyphs) - // The loca table has two versions: a short version where offsets are stored as uShorts, and a long - // version where offsets are stored as uLongs. The `head` table specifies which version to use - // (under indexToLocFormat). - function parseLocaTable(data, start, numGlyphs, shortVersion) { - var p = new parse.Parser(data, start); - var parseFn = shortVersion ? p.parseUShort : p.parseULong; - // There is an extra entry after the last index element to compute the length of the last glyph. - // That's why we use numGlyphs + 1. - var glyphOffsets = []; - for (var i = 0; i < numGlyphs + 1; i += 1) { - var glyphOffset = parseFn.call(p); - if (shortVersion) { - // The short table version stores the actual offset divided by 2. - glyphOffset *= 2; - } - - glyphOffsets.push(glyphOffset); - } - - return glyphOffsets; - } - - var loca = { parse: parseLocaTable }; - - // opentype.js - - /** - * The opentype library. - * @namespace opentype - */ - - // File loaders ///////////////////////////////////////////////////////// - /** - * Loads a font from a file. The callback throws an error message as the first parameter if it fails - * and the font as an ArrayBuffer in the second parameter if it succeeds. - * @param {string} path - The path of the file - * @param {Function} callback - The function to call when the font load completes - */ - function loadFromFile(path, callback) { - var fs = require('fs'); - fs.readFile(path, function(err, buffer) { - if (err) { - return callback(err.message); - } - - callback(null, nodeBufferToArrayBuffer(buffer)); - }); - } - /** - * Loads a font from a URL. The callback throws an error message as the first parameter if it fails - * and the font as an ArrayBuffer in the second parameter if it succeeds. - * @param {string} url - The URL of the font file. - * @param {Function} callback - The function to call when the font load completes - */ - function loadFromUrl(url, callback) { - var request = new XMLHttpRequest(); - request.open('get', url, true); - request.responseType = 'arraybuffer'; - request.onload = function() { - if (request.response) { - return callback(null, request.response); - } else { - return callback('Font could not be loaded: ' + request.statusText); - } - }; - - request.onerror = function () { - callback('Font could not be loaded'); - }; - - request.send(); - } - - // Table Directory Entries ////////////////////////////////////////////// - /** - * Parses OpenType table entries. - * @param {DataView} - * @param {Number} - * @return {Object[]} - */ - function parseOpenTypeTableEntries(data, numTables) { - var tableEntries = []; - var p = 12; - for (var i = 0; i < numTables; i += 1) { - var tag = parse.getTag(data, p); - var checksum = parse.getULong(data, p + 4); - var offset = parse.getULong(data, p + 8); - var length = parse.getULong(data, p + 12); - tableEntries.push({tag: tag, checksum: checksum, offset: offset, length: length, compression: false}); - p += 16; - } - - return tableEntries; - } - - /** - * Parses WOFF table entries. - * @param {DataView} - * @param {Number} - * @return {Object[]} - */ - function parseWOFFTableEntries(data, numTables) { - var tableEntries = []; - var p = 44; // offset to the first table directory entry. - for (var i = 0; i < numTables; i += 1) { - var tag = parse.getTag(data, p); - var offset = parse.getULong(data, p + 4); - var compLength = parse.getULong(data, p + 8); - var origLength = parse.getULong(data, p + 12); - var compression = (void 0); - if (compLength < origLength) { - compression = 'WOFF'; - } else { - compression = false; - } - - tableEntries.push({tag: tag, offset: offset, compression: compression, - compressedLength: compLength, length: origLength}); - p += 20; - } - - return tableEntries; - } - - /** - * @typedef TableData - * @type Object - * @property {DataView} data - The DataView - * @property {number} offset - The data offset. - */ - - /** - * @param {DataView} - * @param {Object} - * @return {TableData} - */ - function uncompressTable(data, tableEntry) { - if (tableEntry.compression === 'WOFF') { - var inBuffer = new Uint8Array(data.buffer, tableEntry.offset + 2, tableEntry.compressedLength - 2); - var outBuffer = new Uint8Array(tableEntry.length); - tinyInflate(inBuffer, outBuffer); - if (outBuffer.byteLength !== tableEntry.length) { - throw new Error('Decompression error: ' + tableEntry.tag + ' decompressed length doesn\'t match recorded length'); - } - - var view = new DataView(outBuffer.buffer, 0); - return {data: view, offset: 0}; - } else { - return {data: data, offset: tableEntry.offset}; - } - } - - // Public API /////////////////////////////////////////////////////////// - - /** - * Parse the OpenType file data (as an ArrayBuffer) and return a Font object. - * Throws an error if the font could not be parsed. - * @param {ArrayBuffer} - * @param {Object} opt - options for parsing - * @return {opentype.Font} - */ - function parseBuffer(buffer, opt) { - opt = (opt === undefined || opt === null) ? {} : opt; - - var indexToLocFormat; - var ltagTable; - - // Since the constructor can also be called to create new fonts from scratch, we indicate this - // should be an empty font that we'll fill with our own data. - var font = new Font({empty: true}); - - // OpenType fonts use big endian byte ordering. - // We can't rely on typed array view types, because they operate with the endianness of the host computer. - // Instead we use DataViews where we can specify endianness. - var data = new DataView(buffer, 0); - var numTables; - var tableEntries = []; - var signature = parse.getTag(data, 0); - if (signature === String.fromCharCode(0, 1, 0, 0) || signature === 'true' || signature === 'typ1') { - font.outlinesFormat = 'truetype'; - numTables = parse.getUShort(data, 4); - tableEntries = parseOpenTypeTableEntries(data, numTables); - } else if (signature === 'OTTO') { - font.outlinesFormat = 'cff'; - numTables = parse.getUShort(data, 4); - tableEntries = parseOpenTypeTableEntries(data, numTables); - } else if (signature === 'wOFF') { - var flavor = parse.getTag(data, 4); - if (flavor === String.fromCharCode(0, 1, 0, 0)) { - font.outlinesFormat = 'truetype'; - } else if (flavor === 'OTTO') { - font.outlinesFormat = 'cff'; - } else { - throw new Error('Unsupported OpenType flavor ' + signature); - } - - numTables = parse.getUShort(data, 12); - tableEntries = parseWOFFTableEntries(data, numTables); - } else { - throw new Error('Unsupported OpenType signature ' + signature); - } - - var cffTableEntry; - var fvarTableEntry; - var glyfTableEntry; - var gposTableEntry; - var gsubTableEntry; - var hmtxTableEntry; - var kernTableEntry; - var locaTableEntry; - var nameTableEntry; - var metaTableEntry; - var p; - - for (var i = 0; i < numTables; i += 1) { - var tableEntry = tableEntries[i]; - var table = (void 0); - switch (tableEntry.tag) { - case 'cmap': - table = uncompressTable(data, tableEntry); - font.tables.cmap = cmap.parse(table.data, table.offset); - font.encoding = new CmapEncoding(font.tables.cmap); - break; - case 'cvt ' : - table = uncompressTable(data, tableEntry); - p = new parse.Parser(table.data, table.offset); - font.tables.cvt = p.parseShortList(tableEntry.length / 2); - break; - case 'fvar': - fvarTableEntry = tableEntry; - break; - case 'fpgm' : - table = uncompressTable(data, tableEntry); - p = new parse.Parser(table.data, table.offset); - font.tables.fpgm = p.parseByteList(tableEntry.length); - break; - case 'head': - table = uncompressTable(data, tableEntry); - font.tables.head = head.parse(table.data, table.offset); - font.unitsPerEm = font.tables.head.unitsPerEm; - indexToLocFormat = font.tables.head.indexToLocFormat; - break; - case 'hhea': - table = uncompressTable(data, tableEntry); - font.tables.hhea = hhea.parse(table.data, table.offset); - font.ascender = font.tables.hhea.ascender; - font.descender = font.tables.hhea.descender; - font.numberOfHMetrics = font.tables.hhea.numberOfHMetrics; - break; - case 'hmtx': - hmtxTableEntry = tableEntry; - break; - case 'ltag': - table = uncompressTable(data, tableEntry); - ltagTable = ltag.parse(table.data, table.offset); - break; - case 'maxp': - table = uncompressTable(data, tableEntry); - font.tables.maxp = maxp.parse(table.data, table.offset); - font.numGlyphs = font.tables.maxp.numGlyphs; - break; - case 'name': - nameTableEntry = tableEntry; - break; - case 'OS/2': - table = uncompressTable(data, tableEntry); - font.tables.os2 = os2.parse(table.data, table.offset); - break; - case 'post': - table = uncompressTable(data, tableEntry); - font.tables.post = post.parse(table.data, table.offset); - font.glyphNames = new GlyphNames(font.tables.post); - break; - case 'prep' : - table = uncompressTable(data, tableEntry); - p = new parse.Parser(table.data, table.offset); - font.tables.prep = p.parseByteList(tableEntry.length); - break; - case 'glyf': - glyfTableEntry = tableEntry; - break; - case 'loca': - locaTableEntry = tableEntry; - break; - case 'CFF ': - cffTableEntry = tableEntry; - break; - case 'kern': - kernTableEntry = tableEntry; - break; - case 'GPOS': - gposTableEntry = tableEntry; - break; - case 'GSUB': - gsubTableEntry = tableEntry; - break; - case 'meta': - metaTableEntry = tableEntry; - break; - } - } - - var nameTable = uncompressTable(data, nameTableEntry); - font.tables.name = _name.parse(nameTable.data, nameTable.offset, ltagTable); - font.names = font.tables.name; - - if (glyfTableEntry && locaTableEntry) { - var shortVersion = indexToLocFormat === 0; - var locaTable = uncompressTable(data, locaTableEntry); - var locaOffsets = loca.parse(locaTable.data, locaTable.offset, font.numGlyphs, shortVersion); - var glyfTable = uncompressTable(data, glyfTableEntry); - font.glyphs = glyf.parse(glyfTable.data, glyfTable.offset, locaOffsets, font, opt); - } else if (cffTableEntry) { - var cffTable = uncompressTable(data, cffTableEntry); - cff.parse(cffTable.data, cffTable.offset, font, opt); - } else { - throw new Error('Font doesn\'t contain TrueType or CFF outlines.'); - } - - var hmtxTable = uncompressTable(data, hmtxTableEntry); - hmtx.parse(font, hmtxTable.data, hmtxTable.offset, font.numberOfHMetrics, font.numGlyphs, font.glyphs, opt); - addGlyphNames(font, opt); - - if (kernTableEntry) { - var kernTable = uncompressTable(data, kernTableEntry); - font.kerningPairs = kern.parse(kernTable.data, kernTable.offset); - } else { - font.kerningPairs = {}; - } - - if (gposTableEntry) { - var gposTable = uncompressTable(data, gposTableEntry); - font.tables.gpos = gpos.parse(gposTable.data, gposTable.offset); - font.position.init(); - } - - if (gsubTableEntry) { - var gsubTable = uncompressTable(data, gsubTableEntry); - font.tables.gsub = gsub.parse(gsubTable.data, gsubTable.offset); - } - - if (fvarTableEntry) { - var fvarTable = uncompressTable(data, fvarTableEntry); - font.tables.fvar = fvar.parse(fvarTable.data, fvarTable.offset, font.names); - } - - if (metaTableEntry) { - var metaTable = uncompressTable(data, metaTableEntry); - font.tables.meta = meta.parse(metaTable.data, metaTable.offset); - font.metas = font.tables.meta; - } - - return font; - } - - /** - * Asynchronously load the font from a URL or a filesystem. When done, call the callback - * with two arguments `(err, font)`. The `err` will be null on success, - * the `font` is a Font object. - * We use the node.js callback convention so that - * opentype.js can integrate with frameworks like async.js. - * @alias opentype.load - * @param {string} url - The URL of the font to load. - * @param {Function} callback - The callback. - */ - function load(url, callback, opt) { - var isNode = typeof window === 'undefined'; - var loadFn = isNode ? loadFromFile : loadFromUrl; - - return new Promise(function (resolve, reject) { - loadFn(url, function(err, arrayBuffer) { - if (err) { - if (callback) { - return callback(err); - } else { - reject(err); - } - } - var font; - try { - font = parseBuffer(arrayBuffer, opt); - } catch (e) { - if (callback) { - return callback(e, null); - } else { - reject(e); - } - } - if (callback) { - return callback(null, font); - } else { - resolve(font); - } - }); - }); - } - - /** - * Synchronously load the font from a URL or file. - * When done, returns the font object or throws an error. - * @alias opentype.loadSync - * @param {string} url - The URL of the font to load. - * @param {Object} opt - opt.lowMemory - * @return {opentype.Font} - */ - function loadSync(url, opt) { - var fs = require('fs'); - var buffer = fs.readFileSync(url); - return parseBuffer(nodeBufferToArrayBuffer(buffer), opt); - } - - var opentype = /*#__PURE__*/Object.freeze({ - __proto__: null, - Font: Font, - Glyph: Glyph, - Path: Path, - BoundingBox: BoundingBox, - _parse: parse, - parse: parseBuffer, - load: load, - loadSync: loadSync - }); - - exports.BoundingBox = BoundingBox; - exports.Font = Font; - exports.Glyph = Glyph; - exports.Path = Path; - exports._parse = parse; - exports.default = opentype; - exports.load = load; - exports.loadSync = loadSync; - exports.parse = parseBuffer; - - Object.defineProperty(exports, '__esModule', { value: true }); - -}))); -//# sourceMappingURL=opentype.js.map diff --git a/node_modules/lv_font_conv/node_modules/opentype.js/package.json b/node_modules/lv_font_conv/node_modules/opentype.js/package.json deleted file mode 100644 index 8c75ccad..00000000 --- a/node_modules/lv_font_conv/node_modules/opentype.js/package.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "_args": [ - [ - "opentype.js@1.3.3", - "/home/vitaly/Dropbox/Coding/lv_font_conv" - ] - ], - "_from": "opentype.js@1.3.3", - "_id": "opentype.js@1.3.3", - "_inBundle": false, - "_integrity": "sha512-/qIY/+WnKGlPIIPhbeNjynfD2PO15G9lA/xqlX2bDH+4lc3Xz5GCQ68mqxj3DdUv6AJqCeaPvuAoH8mVL0zcuA==", - "_location": "/opentype.js", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "opentype.js@1.3.3", - "name": "opentype.js", - "escapedName": "opentype.js", - "rawSpec": "1.3.3", - "saveSpec": null, - "fetchSpec": "1.3.3" - }, - "_requiredBy": [ - "/" - ], - "_resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.3.tgz", - "_spec": "1.3.3", - "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", - "author": { - "name": "Frederik De Bleser", - "email": "frederik@debleser.be" - }, - "bin": { - "ot": "bin/ot" - }, - "browser": { - "fs": false - }, - "bugs": { - "url": "https://github.com/opentypejs/opentype.js/issues" - }, - "dependencies": { - "string.prototype.codepointat": "^0.2.1", - "tiny-inflate": "^1.0.3" - }, - "description": "OpenType font parser", - "devDependencies": { - "@babel/preset-env": "^7.9.5", - "buble": "^0.20.0", - "cross-env": "^7.0.2", - "jscs": "^3.0.7", - "jshint": "^2.11.0", - "mocha": "^7.1.1", - "reify": "^0.20.12", - "rollup": "^1.32.1", - "rollup-plugin-buble": "^0.19.8", - "rollup-plugin-commonjs": "^10.1.0", - "rollup-plugin-license": "^0.9.0", - "rollup-plugin-node-resolve": "^5.2.0", - "uglify-js": "^3.8.1" - }, - "engines": { - "node": ">= 8.0.0" - }, - "files": [ - "LICENSE", - "RELEASES.md", - "README.md", - "bin", - "dist", - "src" - ], - "homepage": "https://github.com/opentypejs/opentype.js#readme", - "keywords": [ - "graphics", - "fonts", - "font", - "opentype", - "otf", - "ttf", - "woff", - "type" - ], - "license": "MIT", - "main": "dist/opentype.js", - "module": "dist/opentype.module.js", - "name": "opentype.js", - "repository": { - "type": "git", - "url": "git://github.com/opentypejs/opentype.js.git" - }, - "scripts": { - "build": "rollup -c", - "dist": "npm run test && npm run build && npm run minify", - "minify": "uglifyjs --source-map \"url='opentype.min.js.map'\" --compress --mangle --output ./dist/opentype.min.js -- ./dist/opentype.js", - "start": "node ./bin/server.js", - "test": "mocha --require reify --recursive && jshint . && jscs .", - "watch": "rollup -c -w" - }, - "version": "1.3.3" -} diff --git a/node_modules/lv_font_conv/node_modules/pngjs/LICENSE b/node_modules/lv_font_conv/node_modules/pngjs/LICENSE deleted file mode 100644 index 6942e254..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -pngjs2 original work Copyright (c) 2015 Luke Page & Original Contributors -pngjs derived work Copyright (c) 2012 Kuba Niegowski - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/pngjs/README.md b/node_modules/lv_font_conv/node_modules/pngjs/README.md deleted file mode 100644 index 2aef03d9..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/README.md +++ /dev/null @@ -1,433 +0,0 @@ -[![Build Status](https://travis-ci.com/lukeapage/pngjs.svg?branch=master)](https://travis-ci.com/lukeapage/pngjs) [![Build status](https://ci.appveyor.com/api/projects/status/qo5x8ayutr028108/branch/master?svg=true)](https://ci.appveyor.com/project/lukeapage/pngjs/branch/master) [![codecov](https://codecov.io/gh/lukeapage/pngjs/branch/master/graph/badge.svg)](https://codecov.io/gh/lukeapage/pngjs) [![npm version](https://badge.fury.io/js/pngjs.svg)](http://badge.fury.io/js/pngjs) - -# pngjs - -Simple PNG encoder/decoder for Node.js with no dependencies. - -Based on the original [pngjs](https://github.com/niegowski/node-pngjs) with the follow enhancements. - -- Support for reading 1,2,4 & 16 bit files -- Support for reading interlace files -- Support for reading `tTRNS` transparent colours -- Support for writing colortype 0 (grayscale), colortype 2 (RGB), colortype 4 (grayscale alpha) and colortype 6 (RGBA) -- Sync interface as well as async -- API compatible with pngjs and node-pngjs - -Known lack of support for: - -- Extended PNG e.g. Animation -- Writing in colortype 3 (indexed color) - -# Table of Contents - -- [Requirements](#requirements) -- [Comparison Table](#comparison-table) -- [Tests](#tests) -- [Installation](#installation) -- [Browser](#browser) -- [Example](#example) -- [Async API](#async-api) -- [Sync API](#sync-api) -- [Changelog](#changelog) - -# Comparison Table - -| Name | Forked From | Sync | Async | 16 Bit | 1/2/4 Bit | Interlace | Gamma | Encodes | Tested | -| ------------- | ----------- | ---- | ----- | ------ | --------- | --------- | ------ | ------- | ------ | -| pngjs | | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | -| node-png | pngjs | No | Yes | No | No | No | Hidden | Yes | Manual | -| png-coder | pngjs | No | Yes | Yes | No | No | Hidden | Yes | Manual | -| pngparse | | No | Yes | No | Yes | No | No | No | Yes | -| pngparse-sync | pngparse | Yes | No | No | Yes | No | No | No | Yes | -| png-async | | No | Yes | No | No | No | No | Yes | Yes | -| png-js | | No | Yes | No | No | No | No | No | No | - -Native C++ node decoders: - -- png -- png-sync (sync version of above) -- pixel-png -- png-img - -# Tests - -Tested using [PNG Suite](http://www.schaik.com/pngsuite/). We read every file into pngjs, output it in standard 8bit colour, synchronously and asynchronously, then compare the original with the newly saved images. - -To run the tests, fetch the repo (tests are not distributed via npm) and install with `npm i`, run `npm test`. - -The only thing not converted is gamma correction - this is because multiple vendors will do gamma correction differently, so the tests will have different results on different browsers. - -# Installation - -``` -$ npm install pngjs --save -``` - -# Browser - -The package has been build with a [Browserify](browserify.org) version (`npm run browserify`) and you can use the browser version by including in your code: - -``` -import { PNG } from 'pngjs/browser'; -``` - -# Example - -```js -var fs = require("fs"), - PNG = require("pngjs").PNG; - -fs.createReadStream("in.png") - .pipe( - new PNG({ - filterType: 4, - }) - ) - .on("parsed", function () { - for (var y = 0; y < this.height; y++) { - for (var x = 0; x < this.width; x++) { - var idx = (this.width * y + x) << 2; - - // invert color - this.data[idx] = 255 - this.data[idx]; - this.data[idx + 1] = 255 - this.data[idx + 1]; - this.data[idx + 2] = 255 - this.data[idx + 2]; - - // and reduce opacity - this.data[idx + 3] = this.data[idx + 3] >> 1; - } - } - - this.pack().pipe(fs.createWriteStream("out.png")); - }); -``` - -For more examples see `examples` folder. - -# Async API - -As input any color type is accepted (grayscale, rgb, palette, grayscale with alpha, rgb with alpha) but 8 bit per sample (channel) is the only supported bit depth. Interlaced mode is not supported. - -## Class: PNG - -`PNG` is readable and writable `Stream`. - -### Options - -- `width` - use this with `height` if you want to create png from scratch -- `height` - as above -- `checkCRC` - whether parser should be strict about checksums in source stream (default: `true`) -- `deflateChunkSize` - chunk size used for deflating data chunks, this should be power of 2 and must not be less than 256 and more than 32\*1024 (default: 32 kB) -- `deflateLevel` - compression level for deflate (default: 9) -- `deflateStrategy` - compression strategy for deflate (default: 3) -- `deflateFactory` - deflate stream factory (default: `zlib.createDeflate`) -- `filterType` - png filtering method for scanlines (default: -1 => auto, accepts array of numbers 0-4) -- `colorType` - the output colorType - see constants. 0 = grayscale, no alpha, 2 = color, no alpha, 4 = grayscale & alpha, 6 = color & alpha. Default currently 6, but in the future may calculate best mode. -- `inputColorType` - the input colorType - see constants. Default is 6 (RGBA) -- `bitDepth` - the bitDepth of the output, 8 or 16 bits. Input data is expected to have this bit depth. - 16 bit data is expected in the system endianness (Default: 8) -- `inputHasAlpha` - whether the input bitmap has 4 bytes per pixel (rgb and alpha) or 3 (rgb - no alpha). -- `bgColor` - an object containing red, green, and blue values between 0 and 255 - that is used when packing a PNG if alpha is not to be included (default: 255,255,255) - -### Event "metadata" - -`function(metadata) { }` -Image's header has been parsed, metadata contains this information: - -- `width` image size in pixels -- `height` image size in pixels -- `palette` image is paletted -- `color` image is not grayscale -- `alpha` image contains alpha channel -- `interlace` image is interlaced - -### Event: "parsed" - -`function(data) { }` -Input image has been completely parsed, `data` is complete and ready for modification. - -### Event: "error" - -`function(error) { }` - -### png.parse(data, [callback]) - -Parses PNG file data. Can be `String` or `Buffer`. Alternatively you can stream data to instance of PNG. - -Optional `callback` is once called on `error` or `parsed`. The callback gets -two arguments `(err, data)`. - -Returns `this` for method chaining. - -#### Example - -```js -new PNG({ filterType: 4 }).parse(imageData, function (error, data) { - console.log(error, data); -}); -``` - -### png.pack() - -Starts converting data to PNG file Stream. - -Returns `this` for method chaining. - -### png.bitblt(dst, sx, sy, w, h, dx, dy) - -Helper for image manipulation, copies a rectangle of pixels from current (i.e. the source) image (`sx`, `sy`, `w`, `h`) to `dst` image (at `dx`, `dy`). - -Returns `this` for method chaining. - -For example, the following code copies the top-left 100x50 px of `in.png` into dst and writes it to `out.png`: - -```js -var dst = new PNG({ width: 100, height: 50 }); -fs.createReadStream("in.png") - .pipe(new PNG()) - .on("parsed", function () { - this.bitblt(dst, 0, 0, 100, 50, 0, 0); - dst.pack().pipe(fs.createWriteStream("out.png")); - }); -``` - -### Property: adjustGamma() - -Helper that takes data and adjusts it to be gamma corrected. Note that it is not 100% reliable with transparent colours because that requires knowing the background colour the bitmap is rendered on to. - -In tests against PNG suite it compared 100% with chrome on all 8 bit and below images. On IE there were some differences. - -The following example reads a file, adjusts the gamma (which sets the gamma to 0) and writes it out again, effectively removing any gamma correction from the image. - -```js -fs.createReadStream("in.png") - .pipe(new PNG()) - .on("parsed", function () { - this.adjustGamma(); - this.pack().pipe(fs.createWriteStream("out.png")); - }); -``` - -### Property: width - -Width of image in pixels - -### Property: height - -Height of image in pixels - -### Property: data - -Buffer of image pixel data. Every pixel consists 4 bytes: R, G, B, A (opacity). - -### Property: gamma - -Gamma of image (0 if not specified) - -## Packing a PNG and removing alpha (RGBA to RGB) - -When removing the alpha channel from an image, there needs to be a background color to correctly -convert each pixel's transparency to the appropriate RGB value. By default, pngjs will flatten -the image against a white background. You can override this in the options: - -```js -var fs = require("fs"), - PNG = require("pngjs").PNG; - -fs.createReadStream("in.png") - .pipe( - new PNG({ - colorType: 2, - bgColor: { - red: 0, - green: 255, - blue: 0, - }, - }) - ) - .on("parsed", function () { - this.pack().pipe(fs.createWriteStream("out.png")); - }); -``` - -# Sync API - -## PNG.sync - -### PNG.sync.read(buffer) - -Take a buffer and returns a PNG image. The properties on the image include the meta data and `data` as per the async API above. - -``` -var data = fs.readFileSync('in.png'); -var png = PNG.sync.read(data); -``` - -### PNG.sync.write(png) - -Take a PNG image and returns a buffer. The properties on the image include the meta data and `data` as per the async API above. - -``` -var data = fs.readFileSync('in.png'); -var png = PNG.sync.read(data); -var options = { colorType: 6 }; -var buffer = PNG.sync.write(png, options); -fs.writeFileSync('out.png', buffer); -``` - -### PNG.adjustGamma(src) - -Adjusts the gamma of a sync image. See the async adjustGamma. - -``` -var data = fs.readFileSync('in.png'); -var png = PNG.sync.read(data); -PNG.adjustGamma(png); -``` - -# Changelog - -### 6.0.0 - 24/10/2020 - -- BREAKING - Sync version now throws if there is unexpected content at the end of the stream. -- BREAKING - Drop support for node 10 (Though nothing incompatible in this release yet) -- Reduce the number of files included in the package - -### 5.1.0 - 13/09/2020 - -- Add option to skip rescaling - -### 5.0.0 - 15/04/2020 - -- Drop support for Node 8 -- Browserified bundle may now contain ES20(15-20) code if the supported node version supports it. Please run the browserified version through babel if you need to support older browsers. - -### 4.0.1 - 15/04/2020 - -- Fix to possible null reference in nextTick of async method - -### 4.0.0 - 09/04/2020 - -- Fix issue in newer nodes with using Buffer -- Fix async issue with some png files -- Drop support for Node 4 & 6 - -### 3.4.0 - 09/03/2019 - -- Include whether the png has alpha in the meta data -- emit an error if the image is truncated instead of hanging -- Add a browserified version -- speed up some mapping functions - -### 3.3.3 - 19/04/2018 - -- Real fix for node 9 - -### 3.3.2 - 16/02/2018 - -- Fix for node 9 - -### 3.3.1 - 15/11/2017 - -- Bugfixes and removal of es6 - -### 3.3.0 - -- Add writing 16 bit channels and support for grayscale input - -### 3.2.0 - 30/04/2017 - -- Support for encoding 8-bit grayscale images - -### 3.1.0 - 30/04/2017 - -- Support for pngs with zlib chunks that are malformed after valid data - -### 3.0.1 - 16/02/2017 - -- Fix single pixel pngs - -### 3.0.0 - 03/08/2016 - -- Drop support for node below v4 and iojs. Pin to 2.3.0 to use with old, unsupported or patched node versions. - -### 2.3.0 - 22/04/2016 - -- Support for sync in node 0.10 - -### 2.2.0 - 04/12/2015 - -- Add sync write api -- Fix newfile example -- Correct comparison table - -### 2.1.0 - 28/10/2015 - -- rename package to pngjs -- added 'bgColor' option - -### 2.0.0 - 08/10/2015 - -- fixes to readme -- _breaking change_ - bitblt on the png prototype now doesn't take a unused, unnecessary src first argument - -### 1.2.0 - 13/09/2015 - -- support passing colorType to write PNG's and writing bitmaps without alpha information - -### 1.1.0 - 07/09/2015 - -- support passing a deflate factory for controlled compression - -### 1.0.2 - 22/08/2015 - -- Expose all PNG creation info - -### 1.0.1 - 21/08/2015 - -- Fix non square interlaced files - -### 1.0.0 - 08/08/2015 - -- More tests -- source linted -- maintainability refactorings -- async API - exceptions in reading now emit warnings -- documentation improvement - sync api now documented, adjustGamma documented -- breaking change - gamma chunk is now written. previously a read then write would destroy gamma information, now it is persisted. - -### 0.0.3 - 03/08/2015 - -- Error handling fixes -- ignore files for smaller npm footprint - -### 0.0.2 - 02/08/2015 - -- Bugfixes to interlacing, support for transparent colours - -### 0.0.1 - 02/08/2015 - -- Initial release, see pngjs for older changelog. - -# License - -(The MIT License) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/bitmapper.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/bitmapper.js deleted file mode 100644 index 18378a02..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/bitmapper.js +++ /dev/null @@ -1,267 +0,0 @@ -"use strict"; - -let interlaceUtils = require("./interlace"); - -let pixelBppMapper = [ - // 0 - dummy entry - function () {}, - - // 1 - L - // 0: 0, 1: 0, 2: 0, 3: 0xff - function (pxData, data, pxPos, rawPos) { - if (rawPos === data.length) { - throw new Error("Ran out of data"); - } - - let pixel = data[rawPos]; - pxData[pxPos] = pixel; - pxData[pxPos + 1] = pixel; - pxData[pxPos + 2] = pixel; - pxData[pxPos + 3] = 0xff; - }, - - // 2 - LA - // 0: 0, 1: 0, 2: 0, 3: 1 - function (pxData, data, pxPos, rawPos) { - if (rawPos + 1 >= data.length) { - throw new Error("Ran out of data"); - } - - let pixel = data[rawPos]; - pxData[pxPos] = pixel; - pxData[pxPos + 1] = pixel; - pxData[pxPos + 2] = pixel; - pxData[pxPos + 3] = data[rawPos + 1]; - }, - - // 3 - RGB - // 0: 0, 1: 1, 2: 2, 3: 0xff - function (pxData, data, pxPos, rawPos) { - if (rawPos + 2 >= data.length) { - throw new Error("Ran out of data"); - } - - pxData[pxPos] = data[rawPos]; - pxData[pxPos + 1] = data[rawPos + 1]; - pxData[pxPos + 2] = data[rawPos + 2]; - pxData[pxPos + 3] = 0xff; - }, - - // 4 - RGBA - // 0: 0, 1: 1, 2: 2, 3: 3 - function (pxData, data, pxPos, rawPos) { - if (rawPos + 3 >= data.length) { - throw new Error("Ran out of data"); - } - - pxData[pxPos] = data[rawPos]; - pxData[pxPos + 1] = data[rawPos + 1]; - pxData[pxPos + 2] = data[rawPos + 2]; - pxData[pxPos + 3] = data[rawPos + 3]; - }, -]; - -let pixelBppCustomMapper = [ - // 0 - dummy entry - function () {}, - - // 1 - L - // 0: 0, 1: 0, 2: 0, 3: 0xff - function (pxData, pixelData, pxPos, maxBit) { - let pixel = pixelData[0]; - pxData[pxPos] = pixel; - pxData[pxPos + 1] = pixel; - pxData[pxPos + 2] = pixel; - pxData[pxPos + 3] = maxBit; - }, - - // 2 - LA - // 0: 0, 1: 0, 2: 0, 3: 1 - function (pxData, pixelData, pxPos) { - let pixel = pixelData[0]; - pxData[pxPos] = pixel; - pxData[pxPos + 1] = pixel; - pxData[pxPos + 2] = pixel; - pxData[pxPos + 3] = pixelData[1]; - }, - - // 3 - RGB - // 0: 0, 1: 1, 2: 2, 3: 0xff - function (pxData, pixelData, pxPos, maxBit) { - pxData[pxPos] = pixelData[0]; - pxData[pxPos + 1] = pixelData[1]; - pxData[pxPos + 2] = pixelData[2]; - pxData[pxPos + 3] = maxBit; - }, - - // 4 - RGBA - // 0: 0, 1: 1, 2: 2, 3: 3 - function (pxData, pixelData, pxPos) { - pxData[pxPos] = pixelData[0]; - pxData[pxPos + 1] = pixelData[1]; - pxData[pxPos + 2] = pixelData[2]; - pxData[pxPos + 3] = pixelData[3]; - }, -]; - -function bitRetriever(data, depth) { - let leftOver = []; - let i = 0; - - function split() { - if (i === data.length) { - throw new Error("Ran out of data"); - } - let byte = data[i]; - i++; - let byte8, byte7, byte6, byte5, byte4, byte3, byte2, byte1; - switch (depth) { - default: - throw new Error("unrecognised depth"); - case 16: - byte2 = data[i]; - i++; - leftOver.push((byte << 8) + byte2); - break; - case 4: - byte2 = byte & 0x0f; - byte1 = byte >> 4; - leftOver.push(byte1, byte2); - break; - case 2: - byte4 = byte & 3; - byte3 = (byte >> 2) & 3; - byte2 = (byte >> 4) & 3; - byte1 = (byte >> 6) & 3; - leftOver.push(byte1, byte2, byte3, byte4); - break; - case 1: - byte8 = byte & 1; - byte7 = (byte >> 1) & 1; - byte6 = (byte >> 2) & 1; - byte5 = (byte >> 3) & 1; - byte4 = (byte >> 4) & 1; - byte3 = (byte >> 5) & 1; - byte2 = (byte >> 6) & 1; - byte1 = (byte >> 7) & 1; - leftOver.push(byte1, byte2, byte3, byte4, byte5, byte6, byte7, byte8); - break; - } - } - - return { - get: function (count) { - while (leftOver.length < count) { - split(); - } - let returner = leftOver.slice(0, count); - leftOver = leftOver.slice(count); - return returner; - }, - resetAfterLine: function () { - leftOver.length = 0; - }, - end: function () { - if (i !== data.length) { - throw new Error("extra data found"); - } - }, - }; -} - -function mapImage8Bit(image, pxData, getPxPos, bpp, data, rawPos) { - // eslint-disable-line max-params - let imageWidth = image.width; - let imageHeight = image.height; - let imagePass = image.index; - for (let y = 0; y < imageHeight; y++) { - for (let x = 0; x < imageWidth; x++) { - let pxPos = getPxPos(x, y, imagePass); - pixelBppMapper[bpp](pxData, data, pxPos, rawPos); - rawPos += bpp; //eslint-disable-line no-param-reassign - } - } - return rawPos; -} - -function mapImageCustomBit(image, pxData, getPxPos, bpp, bits, maxBit) { - // eslint-disable-line max-params - let imageWidth = image.width; - let imageHeight = image.height; - let imagePass = image.index; - for (let y = 0; y < imageHeight; y++) { - for (let x = 0; x < imageWidth; x++) { - let pixelData = bits.get(bpp); - let pxPos = getPxPos(x, y, imagePass); - pixelBppCustomMapper[bpp](pxData, pixelData, pxPos, maxBit); - } - bits.resetAfterLine(); - } -} - -exports.dataToBitMap = function (data, bitmapInfo) { - let width = bitmapInfo.width; - let height = bitmapInfo.height; - let depth = bitmapInfo.depth; - let bpp = bitmapInfo.bpp; - let interlace = bitmapInfo.interlace; - let bits; - - if (depth !== 8) { - bits = bitRetriever(data, depth); - } - let pxData; - if (depth <= 8) { - pxData = Buffer.alloc(width * height * 4); - } else { - pxData = new Uint16Array(width * height * 4); - } - let maxBit = Math.pow(2, depth) - 1; - let rawPos = 0; - let images; - let getPxPos; - - if (interlace) { - images = interlaceUtils.getImagePasses(width, height); - getPxPos = interlaceUtils.getInterlaceIterator(width, height); - } else { - let nonInterlacedPxPos = 0; - getPxPos = function () { - let returner = nonInterlacedPxPos; - nonInterlacedPxPos += 4; - return returner; - }; - images = [{ width: width, height: height }]; - } - - for (let imageIndex = 0; imageIndex < images.length; imageIndex++) { - if (depth === 8) { - rawPos = mapImage8Bit( - images[imageIndex], - pxData, - getPxPos, - bpp, - data, - rawPos - ); - } else { - mapImageCustomBit( - images[imageIndex], - pxData, - getPxPos, - bpp, - bits, - maxBit - ); - } - } - if (depth === 8) { - if (rawPos !== data.length) { - throw new Error("extra data found"); - } - } else { - bits.end(); - } - - return pxData; -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/bitpacker.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/bitpacker.js deleted file mode 100644 index d7a4e656..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/bitpacker.js +++ /dev/null @@ -1,158 +0,0 @@ -"use strict"; - -let constants = require("./constants"); - -module.exports = function (dataIn, width, height, options) { - let outHasAlpha = - [constants.COLORTYPE_COLOR_ALPHA, constants.COLORTYPE_ALPHA].indexOf( - options.colorType - ) !== -1; - if (options.colorType === options.inputColorType) { - let bigEndian = (function () { - let buffer = new ArrayBuffer(2); - new DataView(buffer).setInt16(0, 256, true /* littleEndian */); - // Int16Array uses the platform's endianness. - return new Int16Array(buffer)[0] !== 256; - })(); - // If no need to convert to grayscale and alpha is present/absent in both, take a fast route - if (options.bitDepth === 8 || (options.bitDepth === 16 && bigEndian)) { - return dataIn; - } - } - - // map to a UInt16 array if data is 16bit, fix endianness below - let data = options.bitDepth !== 16 ? dataIn : new Uint16Array(dataIn.buffer); - - let maxValue = 255; - let inBpp = constants.COLORTYPE_TO_BPP_MAP[options.inputColorType]; - if (inBpp === 4 && !options.inputHasAlpha) { - inBpp = 3; - } - let outBpp = constants.COLORTYPE_TO_BPP_MAP[options.colorType]; - if (options.bitDepth === 16) { - maxValue = 65535; - outBpp *= 2; - } - let outData = Buffer.alloc(width * height * outBpp); - - let inIndex = 0; - let outIndex = 0; - - let bgColor = options.bgColor || {}; - if (bgColor.red === undefined) { - bgColor.red = maxValue; - } - if (bgColor.green === undefined) { - bgColor.green = maxValue; - } - if (bgColor.blue === undefined) { - bgColor.blue = maxValue; - } - - function getRGBA() { - let red; - let green; - let blue; - let alpha = maxValue; - switch (options.inputColorType) { - case constants.COLORTYPE_COLOR_ALPHA: - alpha = data[inIndex + 3]; - red = data[inIndex]; - green = data[inIndex + 1]; - blue = data[inIndex + 2]; - break; - case constants.COLORTYPE_COLOR: - red = data[inIndex]; - green = data[inIndex + 1]; - blue = data[inIndex + 2]; - break; - case constants.COLORTYPE_ALPHA: - alpha = data[inIndex + 1]; - red = data[inIndex]; - green = red; - blue = red; - break; - case constants.COLORTYPE_GRAYSCALE: - red = data[inIndex]; - green = red; - blue = red; - break; - default: - throw new Error( - "input color type:" + - options.inputColorType + - " is not supported at present" - ); - } - - if (options.inputHasAlpha) { - if (!outHasAlpha) { - alpha /= maxValue; - red = Math.min( - Math.max(Math.round((1 - alpha) * bgColor.red + alpha * red), 0), - maxValue - ); - green = Math.min( - Math.max(Math.round((1 - alpha) * bgColor.green + alpha * green), 0), - maxValue - ); - blue = Math.min( - Math.max(Math.round((1 - alpha) * bgColor.blue + alpha * blue), 0), - maxValue - ); - } - } - return { red: red, green: green, blue: blue, alpha: alpha }; - } - - for (let y = 0; y < height; y++) { - for (let x = 0; x < width; x++) { - let rgba = getRGBA(data, inIndex); - - switch (options.colorType) { - case constants.COLORTYPE_COLOR_ALPHA: - case constants.COLORTYPE_COLOR: - if (options.bitDepth === 8) { - outData[outIndex] = rgba.red; - outData[outIndex + 1] = rgba.green; - outData[outIndex + 2] = rgba.blue; - if (outHasAlpha) { - outData[outIndex + 3] = rgba.alpha; - } - } else { - outData.writeUInt16BE(rgba.red, outIndex); - outData.writeUInt16BE(rgba.green, outIndex + 2); - outData.writeUInt16BE(rgba.blue, outIndex + 4); - if (outHasAlpha) { - outData.writeUInt16BE(rgba.alpha, outIndex + 6); - } - } - break; - case constants.COLORTYPE_ALPHA: - case constants.COLORTYPE_GRAYSCALE: { - // Convert to grayscale and alpha - let grayscale = (rgba.red + rgba.green + rgba.blue) / 3; - if (options.bitDepth === 8) { - outData[outIndex] = grayscale; - if (outHasAlpha) { - outData[outIndex + 1] = rgba.alpha; - } - } else { - outData.writeUInt16BE(grayscale, outIndex); - if (outHasAlpha) { - outData.writeUInt16BE(rgba.alpha, outIndex + 2); - } - } - break; - } - default: - throw new Error("unrecognised color Type " + options.colorType); - } - - inIndex += inBpp; - outIndex += outBpp; - } - } - - return outData; -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/chunkstream.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/chunkstream.js deleted file mode 100644 index 95b46d48..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/chunkstream.js +++ /dev/null @@ -1,189 +0,0 @@ -"use strict"; - -let util = require("util"); -let Stream = require("stream"); - -let ChunkStream = (module.exports = function () { - Stream.call(this); - - this._buffers = []; - this._buffered = 0; - - this._reads = []; - this._paused = false; - - this._encoding = "utf8"; - this.writable = true; -}); -util.inherits(ChunkStream, Stream); - -ChunkStream.prototype.read = function (length, callback) { - this._reads.push({ - length: Math.abs(length), // if length < 0 then at most this length - allowLess: length < 0, - func: callback, - }); - - process.nextTick( - function () { - this._process(); - - // its paused and there is not enought data then ask for more - if (this._paused && this._reads && this._reads.length > 0) { - this._paused = false; - - this.emit("drain"); - } - }.bind(this) - ); -}; - -ChunkStream.prototype.write = function (data, encoding) { - if (!this.writable) { - this.emit("error", new Error("Stream not writable")); - return false; - } - - let dataBuffer; - if (Buffer.isBuffer(data)) { - dataBuffer = data; - } else { - dataBuffer = Buffer.from(data, encoding || this._encoding); - } - - this._buffers.push(dataBuffer); - this._buffered += dataBuffer.length; - - this._process(); - - // ok if there are no more read requests - if (this._reads && this._reads.length === 0) { - this._paused = true; - } - - return this.writable && !this._paused; -}; - -ChunkStream.prototype.end = function (data, encoding) { - if (data) { - this.write(data, encoding); - } - - this.writable = false; - - // already destroyed - if (!this._buffers) { - return; - } - - // enqueue or handle end - if (this._buffers.length === 0) { - this._end(); - } else { - this._buffers.push(null); - this._process(); - } -}; - -ChunkStream.prototype.destroySoon = ChunkStream.prototype.end; - -ChunkStream.prototype._end = function () { - if (this._reads.length > 0) { - this.emit("error", new Error("Unexpected end of input")); - } - - this.destroy(); -}; - -ChunkStream.prototype.destroy = function () { - if (!this._buffers) { - return; - } - - this.writable = false; - this._reads = null; - this._buffers = null; - - this.emit("close"); -}; - -ChunkStream.prototype._processReadAllowingLess = function (read) { - // ok there is any data so that we can satisfy this request - this._reads.shift(); // == read - - // first we need to peek into first buffer - let smallerBuf = this._buffers[0]; - - // ok there is more data than we need - if (smallerBuf.length > read.length) { - this._buffered -= read.length; - this._buffers[0] = smallerBuf.slice(read.length); - - read.func.call(this, smallerBuf.slice(0, read.length)); - } else { - // ok this is less than maximum length so use it all - this._buffered -= smallerBuf.length; - this._buffers.shift(); // == smallerBuf - - read.func.call(this, smallerBuf); - } -}; - -ChunkStream.prototype._processRead = function (read) { - this._reads.shift(); // == read - - let pos = 0; - let count = 0; - let data = Buffer.alloc(read.length); - - // create buffer for all data - while (pos < read.length) { - let buf = this._buffers[count++]; - let len = Math.min(buf.length, read.length - pos); - - buf.copy(data, pos, 0, len); - pos += len; - - // last buffer wasn't used all so just slice it and leave - if (len !== buf.length) { - this._buffers[--count] = buf.slice(len); - } - } - - // remove all used buffers - if (count > 0) { - this._buffers.splice(0, count); - } - - this._buffered -= read.length; - - read.func.call(this, data); -}; - -ChunkStream.prototype._process = function () { - try { - // as long as there is any data and read requests - while (this._buffered > 0 && this._reads && this._reads.length > 0) { - let read = this._reads[0]; - - // read any data (but no more than length) - if (read.allowLess) { - this._processReadAllowingLess(read); - } else if (this._buffered >= read.length) { - // ok we can meet some expectations - - this._processRead(read); - } else { - // not enought data to satisfy first request in queue - // so we need to wait for more - break; - } - } - - if (this._buffers && !this.writable) { - this._end(); - } - } catch (ex) { - this.emit("error", ex); - } -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/constants.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/constants.js deleted file mode 100644 index 21fdad68..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/constants.js +++ /dev/null @@ -1,32 +0,0 @@ -"use strict"; - -module.exports = { - PNG_SIGNATURE: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], - - TYPE_IHDR: 0x49484452, - TYPE_IEND: 0x49454e44, - TYPE_IDAT: 0x49444154, - TYPE_PLTE: 0x504c5445, - TYPE_tRNS: 0x74524e53, // eslint-disable-line camelcase - TYPE_gAMA: 0x67414d41, // eslint-disable-line camelcase - - // color-type bits - COLORTYPE_GRAYSCALE: 0, - COLORTYPE_PALETTE: 1, - COLORTYPE_COLOR: 2, - COLORTYPE_ALPHA: 4, // e.g. grayscale and alpha - - // color-type combinations - COLORTYPE_PALETTE_COLOR: 3, - COLORTYPE_COLOR_ALPHA: 6, - - COLORTYPE_TO_BPP_MAP: { - 0: 1, - 2: 3, - 3: 1, - 4: 2, - 6: 4, - }, - - GAMMA_DIVISION: 100000, -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/crc.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/crc.js deleted file mode 100644 index 950ec8ae..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/crc.js +++ /dev/null @@ -1,40 +0,0 @@ -"use strict"; - -let crcTable = []; - -(function () { - for (let i = 0; i < 256; i++) { - let currentCrc = i; - for (let j = 0; j < 8; j++) { - if (currentCrc & 1) { - currentCrc = 0xedb88320 ^ (currentCrc >>> 1); - } else { - currentCrc = currentCrc >>> 1; - } - } - crcTable[i] = currentCrc; - } -})(); - -let CrcCalculator = (module.exports = function () { - this._crc = -1; -}); - -CrcCalculator.prototype.write = function (data) { - for (let i = 0; i < data.length; i++) { - this._crc = crcTable[(this._crc ^ data[i]) & 0xff] ^ (this._crc >>> 8); - } - return true; -}; - -CrcCalculator.prototype.crc32 = function () { - return this._crc ^ -1; -}; - -CrcCalculator.crc32 = function (buf) { - let crc = -1; - for (let i = 0; i < buf.length; i++) { - crc = crcTable[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8); - } - return crc ^ -1; -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-pack.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-pack.js deleted file mode 100644 index 32c85c40..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-pack.js +++ /dev/null @@ -1,171 +0,0 @@ -"use strict"; - -let paethPredictor = require("./paeth-predictor"); - -function filterNone(pxData, pxPos, byteWidth, rawData, rawPos) { - for (let x = 0; x < byteWidth; x++) { - rawData[rawPos + x] = pxData[pxPos + x]; - } -} - -function filterSumNone(pxData, pxPos, byteWidth) { - let sum = 0; - let length = pxPos + byteWidth; - - for (let i = pxPos; i < length; i++) { - sum += Math.abs(pxData[i]); - } - return sum; -} - -function filterSub(pxData, pxPos, byteWidth, rawData, rawPos, bpp) { - for (let x = 0; x < byteWidth; x++) { - let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; - let val = pxData[pxPos + x] - left; - - rawData[rawPos + x] = val; - } -} - -function filterSumSub(pxData, pxPos, byteWidth, bpp) { - let sum = 0; - for (let x = 0; x < byteWidth; x++) { - let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; - let val = pxData[pxPos + x] - left; - - sum += Math.abs(val); - } - - return sum; -} - -function filterUp(pxData, pxPos, byteWidth, rawData, rawPos) { - for (let x = 0; x < byteWidth; x++) { - let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; - let val = pxData[pxPos + x] - up; - - rawData[rawPos + x] = val; - } -} - -function filterSumUp(pxData, pxPos, byteWidth) { - let sum = 0; - let length = pxPos + byteWidth; - for (let x = pxPos; x < length; x++) { - let up = pxPos > 0 ? pxData[x - byteWidth] : 0; - let val = pxData[x] - up; - - sum += Math.abs(val); - } - - return sum; -} - -function filterAvg(pxData, pxPos, byteWidth, rawData, rawPos, bpp) { - for (let x = 0; x < byteWidth; x++) { - let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; - let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; - let val = pxData[pxPos + x] - ((left + up) >> 1); - - rawData[rawPos + x] = val; - } -} - -function filterSumAvg(pxData, pxPos, byteWidth, bpp) { - let sum = 0; - for (let x = 0; x < byteWidth; x++) { - let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; - let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; - let val = pxData[pxPos + x] - ((left + up) >> 1); - - sum += Math.abs(val); - } - - return sum; -} - -function filterPaeth(pxData, pxPos, byteWidth, rawData, rawPos, bpp) { - for (let x = 0; x < byteWidth; x++) { - let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; - let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; - let upleft = - pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0; - let val = pxData[pxPos + x] - paethPredictor(left, up, upleft); - - rawData[rawPos + x] = val; - } -} - -function filterSumPaeth(pxData, pxPos, byteWidth, bpp) { - let sum = 0; - for (let x = 0; x < byteWidth; x++) { - let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; - let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; - let upleft = - pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0; - let val = pxData[pxPos + x] - paethPredictor(left, up, upleft); - - sum += Math.abs(val); - } - - return sum; -} - -let filters = { - 0: filterNone, - 1: filterSub, - 2: filterUp, - 3: filterAvg, - 4: filterPaeth, -}; - -let filterSums = { - 0: filterSumNone, - 1: filterSumSub, - 2: filterSumUp, - 3: filterSumAvg, - 4: filterSumPaeth, -}; - -module.exports = function (pxData, width, height, options, bpp) { - let filterTypes; - if (!("filterType" in options) || options.filterType === -1) { - filterTypes = [0, 1, 2, 3, 4]; - } else if (typeof options.filterType === "number") { - filterTypes = [options.filterType]; - } else { - throw new Error("unrecognised filter types"); - } - - if (options.bitDepth === 16) { - bpp *= 2; - } - let byteWidth = width * bpp; - let rawPos = 0; - let pxPos = 0; - let rawData = Buffer.alloc((byteWidth + 1) * height); - - let sel = filterTypes[0]; - - for (let y = 0; y < height; y++) { - if (filterTypes.length > 1) { - // find best filter for this line (with lowest sum of values) - let min = Infinity; - - for (let i = 0; i < filterTypes.length; i++) { - let sum = filterSums[filterTypes[i]](pxData, pxPos, byteWidth, bpp); - if (sum < min) { - sel = filterTypes[i]; - min = sum; - } - } - } - - rawData[rawPos] = sel; - rawPos++; - filters[sel](pxData, pxPos, byteWidth, rawData, rawPos, bpp); - rawPos += byteWidth; - pxPos += byteWidth; - } - return rawData; -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-async.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-async.js deleted file mode 100644 index 832b86cd..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-async.js +++ /dev/null @@ -1,24 +0,0 @@ -"use strict"; - -let util = require("util"); -let ChunkStream = require("./chunkstream"); -let Filter = require("./filter-parse"); - -let FilterAsync = (module.exports = function (bitmapInfo) { - ChunkStream.call(this); - - let buffers = []; - let that = this; - this._filter = new Filter(bitmapInfo, { - read: this.read.bind(this), - write: function (buffer) { - buffers.push(buffer); - }, - complete: function () { - that.emit("complete", Buffer.concat(buffers)); - }, - }); - - this._filter.start(); -}); -util.inherits(FilterAsync, ChunkStream); diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-sync.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-sync.js deleted file mode 100644 index 6924d161..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse-sync.js +++ /dev/null @@ -1,21 +0,0 @@ -"use strict"; - -let SyncReader = require("./sync-reader"); -let Filter = require("./filter-parse"); - -exports.process = function (inBuffer, bitmapInfo) { - let outBuffers = []; - let reader = new SyncReader(inBuffer); - let filter = new Filter(bitmapInfo, { - read: reader.read.bind(reader), - write: function (bufferPart) { - outBuffers.push(bufferPart); - }, - complete: function () {}, - }); - - filter.start(); - reader.process(); - - return Buffer.concat(outBuffers); -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse.js deleted file mode 100644 index 3a32e5ee..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/filter-parse.js +++ /dev/null @@ -1,177 +0,0 @@ -"use strict"; - -let interlaceUtils = require("./interlace"); -let paethPredictor = require("./paeth-predictor"); - -function getByteWidth(width, bpp, depth) { - let byteWidth = width * bpp; - if (depth !== 8) { - byteWidth = Math.ceil(byteWidth / (8 / depth)); - } - return byteWidth; -} - -let Filter = (module.exports = function (bitmapInfo, dependencies) { - let width = bitmapInfo.width; - let height = bitmapInfo.height; - let interlace = bitmapInfo.interlace; - let bpp = bitmapInfo.bpp; - let depth = bitmapInfo.depth; - - this.read = dependencies.read; - this.write = dependencies.write; - this.complete = dependencies.complete; - - this._imageIndex = 0; - this._images = []; - if (interlace) { - let passes = interlaceUtils.getImagePasses(width, height); - for (let i = 0; i < passes.length; i++) { - this._images.push({ - byteWidth: getByteWidth(passes[i].width, bpp, depth), - height: passes[i].height, - lineIndex: 0, - }); - } - } else { - this._images.push({ - byteWidth: getByteWidth(width, bpp, depth), - height: height, - lineIndex: 0, - }); - } - - // when filtering the line we look at the pixel to the left - // the spec also says it is done on a byte level regardless of the number of pixels - // so if the depth is byte compatible (8 or 16) we subtract the bpp in order to compare back - // a pixel rather than just a different byte part. However if we are sub byte, we ignore. - if (depth === 8) { - this._xComparison = bpp; - } else if (depth === 16) { - this._xComparison = bpp * 2; - } else { - this._xComparison = 1; - } -}); - -Filter.prototype.start = function () { - this.read( - this._images[this._imageIndex].byteWidth + 1, - this._reverseFilterLine.bind(this) - ); -}; - -Filter.prototype._unFilterType1 = function ( - rawData, - unfilteredLine, - byteWidth -) { - let xComparison = this._xComparison; - let xBiggerThan = xComparison - 1; - - for (let x = 0; x < byteWidth; x++) { - let rawByte = rawData[1 + x]; - let f1Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0; - unfilteredLine[x] = rawByte + f1Left; - } -}; - -Filter.prototype._unFilterType2 = function ( - rawData, - unfilteredLine, - byteWidth -) { - let lastLine = this._lastLine; - - for (let x = 0; x < byteWidth; x++) { - let rawByte = rawData[1 + x]; - let f2Up = lastLine ? lastLine[x] : 0; - unfilteredLine[x] = rawByte + f2Up; - } -}; - -Filter.prototype._unFilterType3 = function ( - rawData, - unfilteredLine, - byteWidth -) { - let xComparison = this._xComparison; - let xBiggerThan = xComparison - 1; - let lastLine = this._lastLine; - - for (let x = 0; x < byteWidth; x++) { - let rawByte = rawData[1 + x]; - let f3Up = lastLine ? lastLine[x] : 0; - let f3Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0; - let f3Add = Math.floor((f3Left + f3Up) / 2); - unfilteredLine[x] = rawByte + f3Add; - } -}; - -Filter.prototype._unFilterType4 = function ( - rawData, - unfilteredLine, - byteWidth -) { - let xComparison = this._xComparison; - let xBiggerThan = xComparison - 1; - let lastLine = this._lastLine; - - for (let x = 0; x < byteWidth; x++) { - let rawByte = rawData[1 + x]; - let f4Up = lastLine ? lastLine[x] : 0; - let f4Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0; - let f4UpLeft = x > xBiggerThan && lastLine ? lastLine[x - xComparison] : 0; - let f4Add = paethPredictor(f4Left, f4Up, f4UpLeft); - unfilteredLine[x] = rawByte + f4Add; - } -}; - -Filter.prototype._reverseFilterLine = function (rawData) { - let filter = rawData[0]; - let unfilteredLine; - let currentImage = this._images[this._imageIndex]; - let byteWidth = currentImage.byteWidth; - - if (filter === 0) { - unfilteredLine = rawData.slice(1, byteWidth + 1); - } else { - unfilteredLine = Buffer.alloc(byteWidth); - - switch (filter) { - case 1: - this._unFilterType1(rawData, unfilteredLine, byteWidth); - break; - case 2: - this._unFilterType2(rawData, unfilteredLine, byteWidth); - break; - case 3: - this._unFilterType3(rawData, unfilteredLine, byteWidth); - break; - case 4: - this._unFilterType4(rawData, unfilteredLine, byteWidth); - break; - default: - throw new Error("Unrecognised filter type - " + filter); - } - } - - this.write(unfilteredLine); - - currentImage.lineIndex++; - if (currentImage.lineIndex >= currentImage.height) { - this._lastLine = null; - this._imageIndex++; - currentImage = this._images[this._imageIndex]; - } else { - this._lastLine = unfilteredLine; - } - - if (currentImage) { - // read, using the byte width that may be from the new current image - this.read(currentImage.byteWidth + 1, this._reverseFilterLine.bind(this)); - } else { - this._lastLine = null; - this.complete(); - } -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/format-normaliser.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/format-normaliser.js deleted file mode 100644 index 209b66bb..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/format-normaliser.js +++ /dev/null @@ -1,93 +0,0 @@ -"use strict"; - -function dePalette(indata, outdata, width, height, palette) { - let pxPos = 0; - // use values from palette - for (let y = 0; y < height; y++) { - for (let x = 0; x < width; x++) { - let color = palette[indata[pxPos]]; - - if (!color) { - throw new Error("index " + indata[pxPos] + " not in palette"); - } - - for (let i = 0; i < 4; i++) { - outdata[pxPos + i] = color[i]; - } - pxPos += 4; - } - } -} - -function replaceTransparentColor(indata, outdata, width, height, transColor) { - let pxPos = 0; - for (let y = 0; y < height; y++) { - for (let x = 0; x < width; x++) { - let makeTrans = false; - - if (transColor.length === 1) { - if (transColor[0] === indata[pxPos]) { - makeTrans = true; - } - } else if ( - transColor[0] === indata[pxPos] && - transColor[1] === indata[pxPos + 1] && - transColor[2] === indata[pxPos + 2] - ) { - makeTrans = true; - } - if (makeTrans) { - for (let i = 0; i < 4; i++) { - outdata[pxPos + i] = 0; - } - } - pxPos += 4; - } - } -} - -function scaleDepth(indata, outdata, width, height, depth) { - let maxOutSample = 255; - let maxInSample = Math.pow(2, depth) - 1; - let pxPos = 0; - - for (let y = 0; y < height; y++) { - for (let x = 0; x < width; x++) { - for (let i = 0; i < 4; i++) { - outdata[pxPos + i] = Math.floor( - (indata[pxPos + i] * maxOutSample) / maxInSample + 0.5 - ); - } - pxPos += 4; - } - } -} - -module.exports = function (indata, imageData, skipRescale = false) { - let depth = imageData.depth; - let width = imageData.width; - let height = imageData.height; - let colorType = imageData.colorType; - let transColor = imageData.transColor; - let palette = imageData.palette; - - let outdata = indata; // only different for 16 bits - - if (colorType === 3) { - // paletted - dePalette(indata, outdata, width, height, palette); - } else { - if (transColor) { - replaceTransparentColor(indata, outdata, width, height, transColor); - } - // if it needs scaling - if (depth !== 8 && !skipRescale) { - // if we need to change the buffer size - if (depth === 16) { - outdata = Buffer.alloc(width * height * 4); - } - scaleDepth(indata, outdata, width, height, depth); - } - } - return outdata; -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/interlace.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/interlace.js deleted file mode 100644 index a035cb15..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/interlace.js +++ /dev/null @@ -1,95 +0,0 @@ -"use strict"; - -// Adam 7 -// 0 1 2 3 4 5 6 7 -// 0 x 6 4 6 x 6 4 6 -// 1 7 7 7 7 7 7 7 7 -// 2 5 6 5 6 5 6 5 6 -// 3 7 7 7 7 7 7 7 7 -// 4 3 6 4 6 3 6 4 6 -// 5 7 7 7 7 7 7 7 7 -// 6 5 6 5 6 5 6 5 6 -// 7 7 7 7 7 7 7 7 7 - -let imagePasses = [ - { - // pass 1 - 1px - x: [0], - y: [0], - }, - { - // pass 2 - 1px - x: [4], - y: [0], - }, - { - // pass 3 - 2px - x: [0, 4], - y: [4], - }, - { - // pass 4 - 4px - x: [2, 6], - y: [0, 4], - }, - { - // pass 5 - 8px - x: [0, 2, 4, 6], - y: [2, 6], - }, - { - // pass 6 - 16px - x: [1, 3, 5, 7], - y: [0, 2, 4, 6], - }, - { - // pass 7 - 32px - x: [0, 1, 2, 3, 4, 5, 6, 7], - y: [1, 3, 5, 7], - }, -]; - -exports.getImagePasses = function (width, height) { - let images = []; - let xLeftOver = width % 8; - let yLeftOver = height % 8; - let xRepeats = (width - xLeftOver) / 8; - let yRepeats = (height - yLeftOver) / 8; - for (let i = 0; i < imagePasses.length; i++) { - let pass = imagePasses[i]; - let passWidth = xRepeats * pass.x.length; - let passHeight = yRepeats * pass.y.length; - for (let j = 0; j < pass.x.length; j++) { - if (pass.x[j] < xLeftOver) { - passWidth++; - } else { - break; - } - } - for (let j = 0; j < pass.y.length; j++) { - if (pass.y[j] < yLeftOver) { - passHeight++; - } else { - break; - } - } - if (passWidth > 0 && passHeight > 0) { - images.push({ width: passWidth, height: passHeight, index: i }); - } - } - return images; -}; - -exports.getInterlaceIterator = function (width) { - return function (x, y, pass) { - let outerXLeftOver = x % imagePasses[pass].x.length; - let outerX = - ((x - outerXLeftOver) / imagePasses[pass].x.length) * 8 + - imagePasses[pass].x[outerXLeftOver]; - let outerYLeftOver = y % imagePasses[pass].y.length; - let outerY = - ((y - outerYLeftOver) / imagePasses[pass].y.length) * 8 + - imagePasses[pass].y[outerYLeftOver]; - return outerX * 4 + outerY * width * 4; - }; -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-async.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-async.js deleted file mode 100644 index f3df73aa..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-async.js +++ /dev/null @@ -1,50 +0,0 @@ -"use strict"; - -let util = require("util"); -let Stream = require("stream"); -let constants = require("./constants"); -let Packer = require("./packer"); - -let PackerAsync = (module.exports = function (opt) { - Stream.call(this); - - let options = opt || {}; - - this._packer = new Packer(options); - this._deflate = this._packer.createDeflate(); - - this.readable = true; -}); -util.inherits(PackerAsync, Stream); - -PackerAsync.prototype.pack = function (data, width, height, gamma) { - // Signature - this.emit("data", Buffer.from(constants.PNG_SIGNATURE)); - this.emit("data", this._packer.packIHDR(width, height)); - - if (gamma) { - this.emit("data", this._packer.packGAMA(gamma)); - } - - let filteredData = this._packer.filterData(data, width, height); - - // compress it - this._deflate.on("error", this.emit.bind(this, "error")); - - this._deflate.on( - "data", - function (compressedData) { - this.emit("data", this._packer.packIDAT(compressedData)); - }.bind(this) - ); - - this._deflate.on( - "end", - function () { - this.emit("data", this._packer.packIEND()); - this.emit("end"); - }.bind(this) - ); - - this._deflate.end(filteredData); -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-sync.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-sync.js deleted file mode 100644 index f5ab0b3d..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/packer-sync.js +++ /dev/null @@ -1,56 +0,0 @@ -"use strict"; - -let hasSyncZlib = true; -let zlib = require("zlib"); -if (!zlib.deflateSync) { - hasSyncZlib = false; -} -let constants = require("./constants"); -let Packer = require("./packer"); - -module.exports = function (metaData, opt) { - if (!hasSyncZlib) { - throw new Error( - "To use the sync capability of this library in old node versions, please pin pngjs to v2.3.0" - ); - } - - let options = opt || {}; - - let packer = new Packer(options); - - let chunks = []; - - // Signature - chunks.push(Buffer.from(constants.PNG_SIGNATURE)); - - // Header - chunks.push(packer.packIHDR(metaData.width, metaData.height)); - - if (metaData.gamma) { - chunks.push(packer.packGAMA(metaData.gamma)); - } - - let filteredData = packer.filterData( - metaData.data, - metaData.width, - metaData.height - ); - - // compress it - let compressedData = zlib.deflateSync( - filteredData, - packer.getDeflateOptions() - ); - filteredData = null; - - if (!compressedData || !compressedData.length) { - throw new Error("bad png - invalid compressed data response"); - } - chunks.push(packer.packIDAT(compressedData)); - - // End - chunks.push(packer.packIEND()); - - return Buffer.concat(chunks); -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/packer.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/packer.js deleted file mode 100644 index 4aba12c8..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/packer.js +++ /dev/null @@ -1,129 +0,0 @@ -"use strict"; - -let constants = require("./constants"); -let CrcStream = require("./crc"); -let bitPacker = require("./bitpacker"); -let filter = require("./filter-pack"); -let zlib = require("zlib"); - -let Packer = (module.exports = function (options) { - this._options = options; - - options.deflateChunkSize = options.deflateChunkSize || 32 * 1024; - options.deflateLevel = - options.deflateLevel != null ? options.deflateLevel : 9; - options.deflateStrategy = - options.deflateStrategy != null ? options.deflateStrategy : 3; - options.inputHasAlpha = - options.inputHasAlpha != null ? options.inputHasAlpha : true; - options.deflateFactory = options.deflateFactory || zlib.createDeflate; - options.bitDepth = options.bitDepth || 8; - // This is outputColorType - options.colorType = - typeof options.colorType === "number" - ? options.colorType - : constants.COLORTYPE_COLOR_ALPHA; - options.inputColorType = - typeof options.inputColorType === "number" - ? options.inputColorType - : constants.COLORTYPE_COLOR_ALPHA; - - if ( - [ - constants.COLORTYPE_GRAYSCALE, - constants.COLORTYPE_COLOR, - constants.COLORTYPE_COLOR_ALPHA, - constants.COLORTYPE_ALPHA, - ].indexOf(options.colorType) === -1 - ) { - throw new Error( - "option color type:" + options.colorType + " is not supported at present" - ); - } - if ( - [ - constants.COLORTYPE_GRAYSCALE, - constants.COLORTYPE_COLOR, - constants.COLORTYPE_COLOR_ALPHA, - constants.COLORTYPE_ALPHA, - ].indexOf(options.inputColorType) === -1 - ) { - throw new Error( - "option input color type:" + - options.inputColorType + - " is not supported at present" - ); - } - if (options.bitDepth !== 8 && options.bitDepth !== 16) { - throw new Error( - "option bit depth:" + options.bitDepth + " is not supported at present" - ); - } -}); - -Packer.prototype.getDeflateOptions = function () { - return { - chunkSize: this._options.deflateChunkSize, - level: this._options.deflateLevel, - strategy: this._options.deflateStrategy, - }; -}; - -Packer.prototype.createDeflate = function () { - return this._options.deflateFactory(this.getDeflateOptions()); -}; - -Packer.prototype.filterData = function (data, width, height) { - // convert to correct format for filtering (e.g. right bpp and bit depth) - let packedData = bitPacker(data, width, height, this._options); - - // filter pixel data - let bpp = constants.COLORTYPE_TO_BPP_MAP[this._options.colorType]; - let filteredData = filter(packedData, width, height, this._options, bpp); - return filteredData; -}; - -Packer.prototype._packChunk = function (type, data) { - let len = data ? data.length : 0; - let buf = Buffer.alloc(len + 12); - - buf.writeUInt32BE(len, 0); - buf.writeUInt32BE(type, 4); - - if (data) { - data.copy(buf, 8); - } - - buf.writeInt32BE( - CrcStream.crc32(buf.slice(4, buf.length - 4)), - buf.length - 4 - ); - return buf; -}; - -Packer.prototype.packGAMA = function (gamma) { - let buf = Buffer.alloc(4); - buf.writeUInt32BE(Math.floor(gamma * constants.GAMMA_DIVISION), 0); - return this._packChunk(constants.TYPE_gAMA, buf); -}; - -Packer.prototype.packIHDR = function (width, height) { - let buf = Buffer.alloc(13); - buf.writeUInt32BE(width, 0); - buf.writeUInt32BE(height, 4); - buf[8] = this._options.bitDepth; // Bit depth - buf[9] = this._options.colorType; // colorType - buf[10] = 0; // compression - buf[11] = 0; // filter - buf[12] = 0; // interlace - - return this._packChunk(constants.TYPE_IHDR, buf); -}; - -Packer.prototype.packIDAT = function (data) { - return this._packChunk(constants.TYPE_IDAT, data); -}; - -Packer.prototype.packIEND = function () { - return this._packChunk(constants.TYPE_IEND, null); -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/paeth-predictor.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/paeth-predictor.js deleted file mode 100644 index 9634497d..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/paeth-predictor.js +++ /dev/null @@ -1,16 +0,0 @@ -"use strict"; - -module.exports = function paethPredictor(left, above, upLeft) { - let paeth = left + above - upLeft; - let pLeft = Math.abs(paeth - left); - let pAbove = Math.abs(paeth - above); - let pUpLeft = Math.abs(paeth - upLeft); - - if (pLeft <= pAbove && pLeft <= pUpLeft) { - return left; - } - if (pAbove <= pUpLeft) { - return above; - } - return upLeft; -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-async.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-async.js deleted file mode 100644 index 1aacde34..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-async.js +++ /dev/null @@ -1,169 +0,0 @@ -"use strict"; - -let util = require("util"); -let zlib = require("zlib"); -let ChunkStream = require("./chunkstream"); -let FilterAsync = require("./filter-parse-async"); -let Parser = require("./parser"); -let bitmapper = require("./bitmapper"); -let formatNormaliser = require("./format-normaliser"); - -let ParserAsync = (module.exports = function (options) { - ChunkStream.call(this); - - this._parser = new Parser(options, { - read: this.read.bind(this), - error: this._handleError.bind(this), - metadata: this._handleMetaData.bind(this), - gamma: this.emit.bind(this, "gamma"), - palette: this._handlePalette.bind(this), - transColor: this._handleTransColor.bind(this), - finished: this._finished.bind(this), - inflateData: this._inflateData.bind(this), - simpleTransparency: this._simpleTransparency.bind(this), - headersFinished: this._headersFinished.bind(this), - }); - this._options = options; - this.writable = true; - - this._parser.start(); -}); -util.inherits(ParserAsync, ChunkStream); - -ParserAsync.prototype._handleError = function (err) { - this.emit("error", err); - - this.writable = false; - - this.destroy(); - - if (this._inflate && this._inflate.destroy) { - this._inflate.destroy(); - } - - if (this._filter) { - this._filter.destroy(); - // For backward compatibility with Node 7 and below. - // Suppress errors due to _inflate calling write() even after - // it's destroy()'ed. - this._filter.on("error", function () {}); - } - - this.errord = true; -}; - -ParserAsync.prototype._inflateData = function (data) { - if (!this._inflate) { - if (this._bitmapInfo.interlace) { - this._inflate = zlib.createInflate(); - - this._inflate.on("error", this.emit.bind(this, "error")); - this._filter.on("complete", this._complete.bind(this)); - - this._inflate.pipe(this._filter); - } else { - let rowSize = - ((this._bitmapInfo.width * - this._bitmapInfo.bpp * - this._bitmapInfo.depth + - 7) >> - 3) + - 1; - let imageSize = rowSize * this._bitmapInfo.height; - let chunkSize = Math.max(imageSize, zlib.Z_MIN_CHUNK); - - this._inflate = zlib.createInflate({ chunkSize: chunkSize }); - let leftToInflate = imageSize; - - let emitError = this.emit.bind(this, "error"); - this._inflate.on("error", function (err) { - if (!leftToInflate) { - return; - } - - emitError(err); - }); - this._filter.on("complete", this._complete.bind(this)); - - let filterWrite = this._filter.write.bind(this._filter); - this._inflate.on("data", function (chunk) { - if (!leftToInflate) { - return; - } - - if (chunk.length > leftToInflate) { - chunk = chunk.slice(0, leftToInflate); - } - - leftToInflate -= chunk.length; - - filterWrite(chunk); - }); - - this._inflate.on("end", this._filter.end.bind(this._filter)); - } - } - this._inflate.write(data); -}; - -ParserAsync.prototype._handleMetaData = function (metaData) { - this._metaData = metaData; - this._bitmapInfo = Object.create(metaData); - - this._filter = new FilterAsync(this._bitmapInfo); -}; - -ParserAsync.prototype._handleTransColor = function (transColor) { - this._bitmapInfo.transColor = transColor; -}; - -ParserAsync.prototype._handlePalette = function (palette) { - this._bitmapInfo.palette = palette; -}; - -ParserAsync.prototype._simpleTransparency = function () { - this._metaData.alpha = true; -}; - -ParserAsync.prototype._headersFinished = function () { - // Up until this point, we don't know if we have a tRNS chunk (alpha) - // so we can't emit metadata any earlier - this.emit("metadata", this._metaData); -}; - -ParserAsync.prototype._finished = function () { - if (this.errord) { - return; - } - - if (!this._inflate) { - this.emit("error", "No Inflate block"); - } else { - // no more data to inflate - this._inflate.end(); - } -}; - -ParserAsync.prototype._complete = function (filteredData) { - if (this.errord) { - return; - } - - let normalisedBitmapData; - - try { - let bitmapData = bitmapper.dataToBitMap(filteredData, this._bitmapInfo); - - normalisedBitmapData = formatNormaliser( - bitmapData, - this._bitmapInfo, - this._options.skipRescale - ); - bitmapData = null; - } catch (ex) { - this._handleError(ex); - return; - } - - this.emit("parsed", normalisedBitmapData); -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-sync.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-sync.js deleted file mode 100644 index 76cb134b..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/parser-sync.js +++ /dev/null @@ -1,112 +0,0 @@ -"use strict"; - -let hasSyncZlib = true; -let zlib = require("zlib"); -let inflateSync = require("./sync-inflate"); -if (!zlib.deflateSync) { - hasSyncZlib = false; -} -let SyncReader = require("./sync-reader"); -let FilterSync = require("./filter-parse-sync"); -let Parser = require("./parser"); -let bitmapper = require("./bitmapper"); -let formatNormaliser = require("./format-normaliser"); - -module.exports = function (buffer, options) { - if (!hasSyncZlib) { - throw new Error( - "To use the sync capability of this library in old node versions, please pin pngjs to v2.3.0" - ); - } - - let err; - function handleError(_err_) { - err = _err_; - } - - let metaData; - function handleMetaData(_metaData_) { - metaData = _metaData_; - } - - function handleTransColor(transColor) { - metaData.transColor = transColor; - } - - function handlePalette(palette) { - metaData.palette = palette; - } - - function handleSimpleTransparency() { - metaData.alpha = true; - } - - let gamma; - function handleGamma(_gamma_) { - gamma = _gamma_; - } - - let inflateDataList = []; - function handleInflateData(inflatedData) { - inflateDataList.push(inflatedData); - } - - let reader = new SyncReader(buffer); - - let parser = new Parser(options, { - read: reader.read.bind(reader), - error: handleError, - metadata: handleMetaData, - gamma: handleGamma, - palette: handlePalette, - transColor: handleTransColor, - inflateData: handleInflateData, - simpleTransparency: handleSimpleTransparency, - }); - - parser.start(); - reader.process(); - - if (err) { - throw err; - } - - //join together the inflate datas - let inflateData = Buffer.concat(inflateDataList); - inflateDataList.length = 0; - - let inflatedData; - if (metaData.interlace) { - inflatedData = zlib.inflateSync(inflateData); - } else { - let rowSize = - ((metaData.width * metaData.bpp * metaData.depth + 7) >> 3) + 1; - let imageSize = rowSize * metaData.height; - inflatedData = inflateSync(inflateData, { - chunkSize: imageSize, - maxLength: imageSize, - }); - } - inflateData = null; - - if (!inflatedData || !inflatedData.length) { - throw new Error("bad png - invalid inflate data response"); - } - - let unfilteredData = FilterSync.process(inflatedData, metaData); - inflateData = null; - - let bitmapData = bitmapper.dataToBitMap(unfilteredData, metaData); - unfilteredData = null; - - let normalisedBitmapData = formatNormaliser( - bitmapData, - metaData, - options.skipRescale - ); - - metaData.data = normalisedBitmapData; - metaData.gamma = gamma || 0; - - return metaData; -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/parser.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/parser.js deleted file mode 100644 index 51a8f2a5..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/parser.js +++ /dev/null @@ -1,290 +0,0 @@ -"use strict"; - -let constants = require("./constants"); -let CrcCalculator = require("./crc"); - -let Parser = (module.exports = function (options, dependencies) { - this._options = options; - options.checkCRC = options.checkCRC !== false; - - this._hasIHDR = false; - this._hasIEND = false; - this._emittedHeadersFinished = false; - - // input flags/metadata - this._palette = []; - this._colorType = 0; - - this._chunks = {}; - this._chunks[constants.TYPE_IHDR] = this._handleIHDR.bind(this); - this._chunks[constants.TYPE_IEND] = this._handleIEND.bind(this); - this._chunks[constants.TYPE_IDAT] = this._handleIDAT.bind(this); - this._chunks[constants.TYPE_PLTE] = this._handlePLTE.bind(this); - this._chunks[constants.TYPE_tRNS] = this._handleTRNS.bind(this); - this._chunks[constants.TYPE_gAMA] = this._handleGAMA.bind(this); - - this.read = dependencies.read; - this.error = dependencies.error; - this.metadata = dependencies.metadata; - this.gamma = dependencies.gamma; - this.transColor = dependencies.transColor; - this.palette = dependencies.palette; - this.parsed = dependencies.parsed; - this.inflateData = dependencies.inflateData; - this.finished = dependencies.finished; - this.simpleTransparency = dependencies.simpleTransparency; - this.headersFinished = dependencies.headersFinished || function () {}; -}); - -Parser.prototype.start = function () { - this.read(constants.PNG_SIGNATURE.length, this._parseSignature.bind(this)); -}; - -Parser.prototype._parseSignature = function (data) { - let signature = constants.PNG_SIGNATURE; - - for (let i = 0; i < signature.length; i++) { - if (data[i] !== signature[i]) { - this.error(new Error("Invalid file signature")); - return; - } - } - this.read(8, this._parseChunkBegin.bind(this)); -}; - -Parser.prototype._parseChunkBegin = function (data) { - // chunk content length - let length = data.readUInt32BE(0); - - // chunk type - let type = data.readUInt32BE(4); - let name = ""; - for (let i = 4; i < 8; i++) { - name += String.fromCharCode(data[i]); - } - - //console.log('chunk ', name, length); - - // chunk flags - let ancillary = Boolean(data[4] & 0x20); // or critical - // priv = Boolean(data[5] & 0x20), // or public - // safeToCopy = Boolean(data[7] & 0x20); // or unsafe - - if (!this._hasIHDR && type !== constants.TYPE_IHDR) { - this.error(new Error("Expected IHDR on beggining")); - return; - } - - this._crc = new CrcCalculator(); - this._crc.write(Buffer.from(name)); - - if (this._chunks[type]) { - return this._chunks[type](length); - } - - if (!ancillary) { - this.error(new Error("Unsupported critical chunk type " + name)); - return; - } - - this.read(length + 4, this._skipChunk.bind(this)); -}; - -Parser.prototype._skipChunk = function (/*data*/) { - this.read(8, this._parseChunkBegin.bind(this)); -}; - -Parser.prototype._handleChunkEnd = function () { - this.read(4, this._parseChunkEnd.bind(this)); -}; - -Parser.prototype._parseChunkEnd = function (data) { - let fileCrc = data.readInt32BE(0); - let calcCrc = this._crc.crc32(); - - // check CRC - if (this._options.checkCRC && calcCrc !== fileCrc) { - this.error(new Error("Crc error - " + fileCrc + " - " + calcCrc)); - return; - } - - if (!this._hasIEND) { - this.read(8, this._parseChunkBegin.bind(this)); - } -}; - -Parser.prototype._handleIHDR = function (length) { - this.read(length, this._parseIHDR.bind(this)); -}; -Parser.prototype._parseIHDR = function (data) { - this._crc.write(data); - - let width = data.readUInt32BE(0); - let height = data.readUInt32BE(4); - let depth = data[8]; - let colorType = data[9]; // bits: 1 palette, 2 color, 4 alpha - let compr = data[10]; - let filter = data[11]; - let interlace = data[12]; - - // console.log(' width', width, 'height', height, - // 'depth', depth, 'colorType', colorType, - // 'compr', compr, 'filter', filter, 'interlace', interlace - // ); - - if ( - depth !== 8 && - depth !== 4 && - depth !== 2 && - depth !== 1 && - depth !== 16 - ) { - this.error(new Error("Unsupported bit depth " + depth)); - return; - } - if (!(colorType in constants.COLORTYPE_TO_BPP_MAP)) { - this.error(new Error("Unsupported color type")); - return; - } - if (compr !== 0) { - this.error(new Error("Unsupported compression method")); - return; - } - if (filter !== 0) { - this.error(new Error("Unsupported filter method")); - return; - } - if (interlace !== 0 && interlace !== 1) { - this.error(new Error("Unsupported interlace method")); - return; - } - - this._colorType = colorType; - - let bpp = constants.COLORTYPE_TO_BPP_MAP[this._colorType]; - - this._hasIHDR = true; - - this.metadata({ - width: width, - height: height, - depth: depth, - interlace: Boolean(interlace), - palette: Boolean(colorType & constants.COLORTYPE_PALETTE), - color: Boolean(colorType & constants.COLORTYPE_COLOR), - alpha: Boolean(colorType & constants.COLORTYPE_ALPHA), - bpp: bpp, - colorType: colorType, - }); - - this._handleChunkEnd(); -}; - -Parser.prototype._handlePLTE = function (length) { - this.read(length, this._parsePLTE.bind(this)); -}; -Parser.prototype._parsePLTE = function (data) { - this._crc.write(data); - - let entries = Math.floor(data.length / 3); - // console.log('Palette:', entries); - - for (let i = 0; i < entries; i++) { - this._palette.push([data[i * 3], data[i * 3 + 1], data[i * 3 + 2], 0xff]); - } - - this.palette(this._palette); - - this._handleChunkEnd(); -}; - -Parser.prototype._handleTRNS = function (length) { - this.simpleTransparency(); - this.read(length, this._parseTRNS.bind(this)); -}; -Parser.prototype._parseTRNS = function (data) { - this._crc.write(data); - - // palette - if (this._colorType === constants.COLORTYPE_PALETTE_COLOR) { - if (this._palette.length === 0) { - this.error(new Error("Transparency chunk must be after palette")); - return; - } - if (data.length > this._palette.length) { - this.error(new Error("More transparent colors than palette size")); - return; - } - for (let i = 0; i < data.length; i++) { - this._palette[i][3] = data[i]; - } - this.palette(this._palette); - } - - // for colorType 0 (grayscale) and 2 (rgb) - // there might be one gray/color defined as transparent - if (this._colorType === constants.COLORTYPE_GRAYSCALE) { - // grey, 2 bytes - this.transColor([data.readUInt16BE(0)]); - } - if (this._colorType === constants.COLORTYPE_COLOR) { - this.transColor([ - data.readUInt16BE(0), - data.readUInt16BE(2), - data.readUInt16BE(4), - ]); - } - - this._handleChunkEnd(); -}; - -Parser.prototype._handleGAMA = function (length) { - this.read(length, this._parseGAMA.bind(this)); -}; -Parser.prototype._parseGAMA = function (data) { - this._crc.write(data); - this.gamma(data.readUInt32BE(0) / constants.GAMMA_DIVISION); - - this._handleChunkEnd(); -}; - -Parser.prototype._handleIDAT = function (length) { - if (!this._emittedHeadersFinished) { - this._emittedHeadersFinished = true; - this.headersFinished(); - } - this.read(-length, this._parseIDAT.bind(this, length)); -}; -Parser.prototype._parseIDAT = function (length, data) { - this._crc.write(data); - - if ( - this._colorType === constants.COLORTYPE_PALETTE_COLOR && - this._palette.length === 0 - ) { - throw new Error("Expected palette not found"); - } - - this.inflateData(data); - let leftOverLength = length - data.length; - - if (leftOverLength > 0) { - this._handleIDAT(leftOverLength); - } else { - this._handleChunkEnd(); - } -}; - -Parser.prototype._handleIEND = function (length) { - this.read(length, this._parseIEND.bind(this)); -}; -Parser.prototype._parseIEND = function (data) { - this._crc.write(data); - - this._hasIEND = true; - this._handleChunkEnd(); - - if (this.finished) { - this.finished(); - } -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/png-sync.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/png-sync.js deleted file mode 100644 index 68cac9bc..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/png-sync.js +++ /dev/null @@ -1,12 +0,0 @@ -"use strict"; - -let parse = require("./parser-sync"); -let pack = require("./packer-sync"); - -exports.read = function (buffer, options) { - return parse(buffer, options || {}); -}; - -exports.write = function (png, options) { - return pack(png, options); -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/png.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/png.js deleted file mode 100644 index 0b8af3f7..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/png.js +++ /dev/null @@ -1,194 +0,0 @@ -"use strict"; - -let util = require("util"); -let Stream = require("stream"); -let Parser = require("./parser-async"); -let Packer = require("./packer-async"); -let PNGSync = require("./png-sync"); - -let PNG = (exports.PNG = function (options) { - Stream.call(this); - - options = options || {}; // eslint-disable-line no-param-reassign - - // coerce pixel dimensions to integers (also coerces undefined -> 0): - this.width = options.width | 0; - this.height = options.height | 0; - - this.data = - this.width > 0 && this.height > 0 - ? Buffer.alloc(4 * this.width * this.height) - : null; - - if (options.fill && this.data) { - this.data.fill(0); - } - - this.gamma = 0; - this.readable = this.writable = true; - - this._parser = new Parser(options); - - this._parser.on("error", this.emit.bind(this, "error")); - this._parser.on("close", this._handleClose.bind(this)); - this._parser.on("metadata", this._metadata.bind(this)); - this._parser.on("gamma", this._gamma.bind(this)); - this._parser.on( - "parsed", - function (data) { - this.data = data; - this.emit("parsed", data); - }.bind(this) - ); - - this._packer = new Packer(options); - this._packer.on("data", this.emit.bind(this, "data")); - this._packer.on("end", this.emit.bind(this, "end")); - this._parser.on("close", this._handleClose.bind(this)); - this._packer.on("error", this.emit.bind(this, "error")); -}); -util.inherits(PNG, Stream); - -PNG.sync = PNGSync; - -PNG.prototype.pack = function () { - if (!this.data || !this.data.length) { - this.emit("error", "No data provided"); - return this; - } - - process.nextTick( - function () { - this._packer.pack(this.data, this.width, this.height, this.gamma); - }.bind(this) - ); - - return this; -}; - -PNG.prototype.parse = function (data, callback) { - if (callback) { - let onParsed, onError; - - onParsed = function (parsedData) { - this.removeListener("error", onError); - - this.data = parsedData; - callback(null, this); - }.bind(this); - - onError = function (err) { - this.removeListener("parsed", onParsed); - - callback(err, null); - }.bind(this); - - this.once("parsed", onParsed); - this.once("error", onError); - } - - this.end(data); - return this; -}; - -PNG.prototype.write = function (data) { - this._parser.write(data); - return true; -}; - -PNG.prototype.end = function (data) { - this._parser.end(data); -}; - -PNG.prototype._metadata = function (metadata) { - this.width = metadata.width; - this.height = metadata.height; - - this.emit("metadata", metadata); -}; - -PNG.prototype._gamma = function (gamma) { - this.gamma = gamma; -}; - -PNG.prototype._handleClose = function () { - if (!this._parser.writable && !this._packer.readable) { - this.emit("close"); - } -}; - -PNG.bitblt = function (src, dst, srcX, srcY, width, height, deltaX, deltaY) { - // eslint-disable-line max-params - // coerce pixel dimensions to integers (also coerces undefined -> 0): - /* eslint-disable no-param-reassign */ - srcX |= 0; - srcY |= 0; - width |= 0; - height |= 0; - deltaX |= 0; - deltaY |= 0; - /* eslint-enable no-param-reassign */ - - if ( - srcX > src.width || - srcY > src.height || - srcX + width > src.width || - srcY + height > src.height - ) { - throw new Error("bitblt reading outside image"); - } - - if ( - deltaX > dst.width || - deltaY > dst.height || - deltaX + width > dst.width || - deltaY + height > dst.height - ) { - throw new Error("bitblt writing outside image"); - } - - for (let y = 0; y < height; y++) { - src.data.copy( - dst.data, - ((deltaY + y) * dst.width + deltaX) << 2, - ((srcY + y) * src.width + srcX) << 2, - ((srcY + y) * src.width + srcX + width) << 2 - ); - } -}; - -PNG.prototype.bitblt = function ( - dst, - srcX, - srcY, - width, - height, - deltaX, - deltaY -) { - // eslint-disable-line max-params - - PNG.bitblt(this, dst, srcX, srcY, width, height, deltaX, deltaY); - return this; -}; - -PNG.adjustGamma = function (src) { - if (src.gamma) { - for (let y = 0; y < src.height; y++) { - for (let x = 0; x < src.width; x++) { - let idx = (src.width * y + x) << 2; - - for (let i = 0; i < 3; i++) { - let sample = src.data[idx + i] / 255; - sample = Math.pow(sample, 1 / 2.2 / src.gamma); - src.data[idx + i] = Math.round(sample * 255); - } - } - } - src.gamma = 0; - } -}; - -PNG.prototype.adjustGamma = function () { - PNG.adjustGamma(this); -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-inflate.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-inflate.js deleted file mode 100644 index 4da0d5f0..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-inflate.js +++ /dev/null @@ -1,168 +0,0 @@ -"use strict"; - -let assert = require("assert").ok; -let zlib = require("zlib"); -let util = require("util"); - -let kMaxLength = require("buffer").kMaxLength; - -function Inflate(opts) { - if (!(this instanceof Inflate)) { - return new Inflate(opts); - } - - if (opts && opts.chunkSize < zlib.Z_MIN_CHUNK) { - opts.chunkSize = zlib.Z_MIN_CHUNK; - } - - zlib.Inflate.call(this, opts); - - // Node 8 --> 9 compatibility check - this._offset = this._offset === undefined ? this._outOffset : this._offset; - this._buffer = this._buffer || this._outBuffer; - - if (opts && opts.maxLength != null) { - this._maxLength = opts.maxLength; - } -} - -function createInflate(opts) { - return new Inflate(opts); -} - -function _close(engine, callback) { - if (callback) { - process.nextTick(callback); - } - - // Caller may invoke .close after a zlib error (which will null _handle). - if (!engine._handle) { - return; - } - - engine._handle.close(); - engine._handle = null; -} - -Inflate.prototype._processChunk = function (chunk, flushFlag, asyncCb) { - if (typeof asyncCb === "function") { - return zlib.Inflate._processChunk.call(this, chunk, flushFlag, asyncCb); - } - - let self = this; - - let availInBefore = chunk && chunk.length; - let availOutBefore = this._chunkSize - this._offset; - let leftToInflate = this._maxLength; - let inOff = 0; - - let buffers = []; - let nread = 0; - - let error; - this.on("error", function (err) { - error = err; - }); - - function handleChunk(availInAfter, availOutAfter) { - if (self._hadError) { - return; - } - - let have = availOutBefore - availOutAfter; - assert(have >= 0, "have should not go down"); - - if (have > 0) { - let out = self._buffer.slice(self._offset, self._offset + have); - self._offset += have; - - if (out.length > leftToInflate) { - out = out.slice(0, leftToInflate); - } - - buffers.push(out); - nread += out.length; - leftToInflate -= out.length; - - if (leftToInflate === 0) { - return false; - } - } - - if (availOutAfter === 0 || self._offset >= self._chunkSize) { - availOutBefore = self._chunkSize; - self._offset = 0; - self._buffer = Buffer.allocUnsafe(self._chunkSize); - } - - if (availOutAfter === 0) { - inOff += availInBefore - availInAfter; - availInBefore = availInAfter; - - return true; - } - - return false; - } - - assert(this._handle, "zlib binding closed"); - let res; - do { - res = this._handle.writeSync( - flushFlag, - chunk, // in - inOff, // in_off - availInBefore, // in_len - this._buffer, // out - this._offset, //out_off - availOutBefore - ); // out_len - // Node 8 --> 9 compatibility check - res = res || this._writeState; - } while (!this._hadError && handleChunk(res[0], res[1])); - - if (this._hadError) { - throw error; - } - - if (nread >= kMaxLength) { - _close(this); - throw new RangeError( - "Cannot create final Buffer. It would be larger than 0x" + - kMaxLength.toString(16) + - " bytes" - ); - } - - let buf = Buffer.concat(buffers, nread); - _close(this); - - return buf; -}; - -util.inherits(Inflate, zlib.Inflate); - -function zlibBufferSync(engine, buffer) { - if (typeof buffer === "string") { - buffer = Buffer.from(buffer); - } - if (!(buffer instanceof Buffer)) { - throw new TypeError("Not a string or buffer"); - } - - let flushFlag = engine._finishFlushFlag; - if (flushFlag == null) { - flushFlag = zlib.Z_FINISH; - } - - return engine._processChunk(buffer, flushFlag); -} - -function inflateSync(buffer, opts) { - return zlibBufferSync(new Inflate(opts), buffer); -} - -module.exports = exports = inflateSync; -exports.Inflate = Inflate; -exports.createInflate = createInflate; -exports.inflateSync = inflateSync; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-reader.js b/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-reader.js deleted file mode 100644 index 213d1a75..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/lib/sync-reader.js +++ /dev/null @@ -1,45 +0,0 @@ -"use strict"; - -let SyncReader = (module.exports = function (buffer) { - this._buffer = buffer; - this._reads = []; -}); - -SyncReader.prototype.read = function (length, callback) { - this._reads.push({ - length: Math.abs(length), // if length < 0 then at most this length - allowLess: length < 0, - func: callback, - }); -}; - -SyncReader.prototype.process = function () { - // as long as there is any data and read requests - while (this._reads.length > 0 && this._buffer.length) { - let read = this._reads[0]; - - if ( - this._buffer.length && - (this._buffer.length >= read.length || read.allowLess) - ) { - // ok there is any data so that we can satisfy this request - this._reads.shift(); // == read - - let buf = this._buffer; - - this._buffer = buf.slice(read.length); - - read.func.call(this, buf.slice(0, read.length)); - } else { - break; - } - } - - if (this._reads.length > 0) { - throw new Error("There are some read requests waitng on finished stream"); - } - - if (this._buffer.length > 0) { - throw new Error("unrecognised content at end of stream"); - } -}; diff --git a/node_modules/lv_font_conv/node_modules/pngjs/package.json b/node_modules/lv_font_conv/node_modules/pngjs/package.json deleted file mode 100644 index 1f696a7e..00000000 --- a/node_modules/lv_font_conv/node_modules/pngjs/package.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "_args": [ - [ - "pngjs@6.0.0", - "/home/vitaly/Dropbox/Coding/lv_font_conv" - ] - ], - "_from": "pngjs@6.0.0", - "_id": "pngjs@6.0.0", - "_inBundle": false, - "_integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", - "_location": "/pngjs", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "pngjs@6.0.0", - "name": "pngjs", - "escapedName": "pngjs", - "rawSpec": "6.0.0", - "saveSpec": null, - "fetchSpec": "6.0.0" - }, - "_requiredBy": [ - "/" - ], - "_resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", - "_spec": "6.0.0", - "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", - "bugs": { - "url": "https://github.com/lukeapage/pngjs/issues" - }, - "contributors": [ - { - "name": "Alexandre Paré" - }, - { - "name": "Gaurav Mali" - }, - { - "name": "Gusts Kaksis" - }, - { - "name": "Kuba Niegowski" - }, - { - "name": "Luke Page" - }, - { - "name": "Pietajan De Potter" - }, - { - "name": "Steven Sojka" - }, - { - "name": "liangzeng" - }, - { - "name": "Michael Vogt" - }, - { - "name": "Xin-Xin Wang" - }, - { - "name": "toriningen" - }, - { - "name": "Eugene Kulabuhov" - } - ], - "description": "PNG encoder/decoder in pure JS, supporting any bit size & interlace, async & sync with full test suite.", - "devDependencies": { - "browserify": "17.0.0", - "buffer-equal": "1.0.0", - "codecov": "3.7.1", - "connect": "3.7.0", - "eslint": "7.8.1", - "eslint-config-prettier": "6.14.0", - "nyc": "15.1.0", - "prettier": "2.1.1", - "puppeteer": "5.4.0", - "serve-static": "1.14.1", - "tap-dot": "2.0.0", - "tape": "5.0.1" - }, - "directories": { - "lib": "lib", - "example": "examples", - "test": "test" - }, - "engines": { - "node": ">=12.13.0" - }, - "files": [ - "browser.js", - "lib/" - ], - "homepage": "https://github.com/lukeapage/pngjs", - "keywords": [ - "PNG", - "decoder", - "encoder", - "js-png", - "node-png", - "parser", - "png", - "png-js", - "png-parse", - "pngjs" - ], - "license": "MIT", - "main": "./lib/png.js", - "name": "pngjs", - "repository": { - "type": "git", - "url": "git://github.com/lukeapage/pngjs.git" - }, - "scripts": { - "browserify": "browserify lib/png.js --standalone png > browser.js", - "build": "yarn prepublish", - "coverage": "nyc --reporter=lcov --reporter=text-summary tape test/*-spec.js nolarge", - "lint": "eslint .", - "prepublish": "yarn browserify", - "prettier:check": "prettier --check .", - "prettier:write": "prettier --write .", - "test": "yarn lint && yarn prettier:check && tape test/*-spec.js | tap-dot && node test/run-compare" - }, - "version": "6.0.0" -} diff --git a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/LICENSE-MIT.txt b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/LICENSE-MIT.txt deleted file mode 100644 index a41e0a7e..00000000 --- a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/LICENSE-MIT.txt +++ /dev/null @@ -1,20 +0,0 @@ -Copyright Mathias Bynens - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/README.md b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/README.md deleted file mode 100644 index d648b879..00000000 --- a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# ES6 `String.prototype.codePointAt` polyfill [![Build status](https://travis-ci.org/mathiasbynens/String.prototype.codePointAt.svg?branch=master)](https://travis-ci.org/mathiasbynens/String.prototype.codePointAt) - -A robust & optimized ES3-compatible polyfill for [the `String.prototype.codePointAt` method in ECMAScript 6](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-string.prototype.codepointat). - -Other polyfills for `String.prototype.codePointAt` are available: - -* by [Norbert Lindenberg](http://norbertlindenberg.com/) (fails some tests) -* by [Steven Levithan](http://stevenlevithan.com/) (fails some tests) -* by [Paul Miller](http://paulmillr.com/) (~~[fails some tests](https://github.com/paulmillr/es6-shim/issues/166)~~ passes all tests) - -## Installation - -In a browser: - -```html - -``` - -Via [npm](http://npmjs.org/): - -```bash -npm install string.prototype.codepointat -``` - -Then, in [Node.js](http://nodejs.org/): - -```js -require('string.prototype.codepointat'); - -// On Windows and on Mac systems with default settings, case doesn’t matter, -// which allows you to do this instead: -require('String.prototype.codePointAt'); -``` - -## Notes - -[A polyfill + test suite for `String.fromCodePoint`](https://mths.be/fromcodepoint) is available, too. - -## Author - -| [![twitter/mathias](https://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") | -|---| -| [Mathias Bynens](https://mathiasbynens.be/) | - -## License - -This polyfill is available under the [MIT](https://mths.be/mit) license. diff --git a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/codepointat.js b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/codepointat.js deleted file mode 100644 index f724c892..00000000 --- a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/codepointat.js +++ /dev/null @@ -1,54 +0,0 @@ -/*! https://mths.be/codepointat v0.2.0 by @mathias */ -if (!String.prototype.codePointAt) { - (function() { - 'use strict'; // needed to support `apply`/`call` with `undefined`/`null` - var defineProperty = (function() { - // IE 8 only supports `Object.defineProperty` on DOM elements - try { - var object = {}; - var $defineProperty = Object.defineProperty; - var result = $defineProperty(object, object, object) && $defineProperty; - } catch(error) {} - return result; - }()); - var codePointAt = function(position) { - if (this == null) { - throw TypeError(); - } - var string = String(this); - var size = string.length; - // `ToInteger` - var index = position ? Number(position) : 0; - if (index != index) { // better `isNaN` - index = 0; - } - // Account for out-of-bounds indices: - if (index < 0 || index >= size) { - return undefined; - } - // Get the first code unit - var first = string.charCodeAt(index); - var second; - if ( // check if it’s the start of a surrogate pair - first >= 0xD800 && first <= 0xDBFF && // high surrogate - size > index + 1 // there is a next code unit - ) { - second = string.charCodeAt(index + 1); - if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate - // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae - return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; - } - } - return first; - }; - if (defineProperty) { - defineProperty(String.prototype, 'codePointAt', { - 'value': codePointAt, - 'configurable': true, - 'writable': true - }); - } else { - String.prototype.codePointAt = codePointAt; - } - }()); -} diff --git a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/package.json b/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/package.json deleted file mode 100644 index cee4e79d..00000000 --- a/node_modules/lv_font_conv/node_modules/string.prototype.codepointat/package.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "_args": [ - [ - "string.prototype.codepointat@0.2.1", - "/home/vitaly/Dropbox/Coding/lv_font_conv" - ] - ], - "_from": "string.prototype.codepointat@0.2.1", - "_id": "string.prototype.codepointat@0.2.1", - "_inBundle": false, - "_integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==", - "_location": "/string.prototype.codepointat", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "string.prototype.codepointat@0.2.1", - "name": "string.prototype.codepointat", - "escapedName": "string.prototype.codepointat", - "rawSpec": "0.2.1", - "saveSpec": null, - "fetchSpec": "0.2.1" - }, - "_requiredBy": [ - "/opentype.js" - ], - "_resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", - "_spec": "0.2.1", - "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", - "author": { - "name": "Mathias Bynens", - "url": "https://mathiasbynens.be/" - }, - "bugs": { - "url": "https://github.com/mathiasbynens/String.prototype.codePointAt/issues" - }, - "description": "A robust & optimized `String.prototype.codePointAt` polyfill, based on the ECMAScript 6 specification.", - "files": [ - "LICENSE-MIT.txt", - "codepointat.js" - ], - "homepage": "https://mths.be/codepointat", - "keywords": [ - "string", - "unicode", - "es6", - "ecmascript", - "polyfill" - ], - "license": "MIT", - "main": "codepointat.js", - "name": "string.prototype.codepointat", - "repository": { - "type": "git", - "url": "git+https://github.com/mathiasbynens/String.prototype.codePointAt.git" - }, - "scripts": { - "cover": "istanbul cover --report html --verbose --dir coverage tests/tests.js", - "test": "node tests/tests.js" - }, - "version": "0.2.1" -} diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/LICENSE b/node_modules/lv_font_conv/node_modules/tiny-inflate/LICENSE deleted file mode 100644 index 62914548..00000000 --- a/node_modules/lv_font_conv/node_modules/tiny-inflate/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2015-present Devon Govett - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/index.js b/node_modules/lv_font_conv/node_modules/tiny-inflate/index.js deleted file mode 100644 index 44d1151b..00000000 --- a/node_modules/lv_font_conv/node_modules/tiny-inflate/index.js +++ /dev/null @@ -1,375 +0,0 @@ -var TINF_OK = 0; -var TINF_DATA_ERROR = -3; - -function Tree() { - this.table = new Uint16Array(16); /* table of code length counts */ - this.trans = new Uint16Array(288); /* code -> symbol translation table */ -} - -function Data(source, dest) { - this.source = source; - this.sourceIndex = 0; - this.tag = 0; - this.bitcount = 0; - - this.dest = dest; - this.destLen = 0; - - this.ltree = new Tree(); /* dynamic length/symbol tree */ - this.dtree = new Tree(); /* dynamic distance tree */ -} - -/* --------------------------------------------------- * - * -- uninitialized global data (static structures) -- * - * --------------------------------------------------- */ - -var sltree = new Tree(); -var sdtree = new Tree(); - -/* extra bits and base tables for length codes */ -var length_bits = new Uint8Array(30); -var length_base = new Uint16Array(30); - -/* extra bits and base tables for distance codes */ -var dist_bits = new Uint8Array(30); -var dist_base = new Uint16Array(30); - -/* special ordering of code length codes */ -var clcidx = new Uint8Array([ - 16, 17, 18, 0, 8, 7, 9, 6, - 10, 5, 11, 4, 12, 3, 13, 2, - 14, 1, 15 -]); - -/* used by tinf_decode_trees, avoids allocations every call */ -var code_tree = new Tree(); -var lengths = new Uint8Array(288 + 32); - -/* ----------------------- * - * -- utility functions -- * - * ----------------------- */ - -/* build extra bits and base tables */ -function tinf_build_bits_base(bits, base, delta, first) { - var i, sum; - - /* build bits table */ - for (i = 0; i < delta; ++i) bits[i] = 0; - for (i = 0; i < 30 - delta; ++i) bits[i + delta] = i / delta | 0; - - /* build base table */ - for (sum = first, i = 0; i < 30; ++i) { - base[i] = sum; - sum += 1 << bits[i]; - } -} - -/* build the fixed huffman trees */ -function tinf_build_fixed_trees(lt, dt) { - var i; - - /* build fixed length tree */ - for (i = 0; i < 7; ++i) lt.table[i] = 0; - - lt.table[7] = 24; - lt.table[8] = 152; - lt.table[9] = 112; - - for (i = 0; i < 24; ++i) lt.trans[i] = 256 + i; - for (i = 0; i < 144; ++i) lt.trans[24 + i] = i; - for (i = 0; i < 8; ++i) lt.trans[24 + 144 + i] = 280 + i; - for (i = 0; i < 112; ++i) lt.trans[24 + 144 + 8 + i] = 144 + i; - - /* build fixed distance tree */ - for (i = 0; i < 5; ++i) dt.table[i] = 0; - - dt.table[5] = 32; - - for (i = 0; i < 32; ++i) dt.trans[i] = i; -} - -/* given an array of code lengths, build a tree */ -var offs = new Uint16Array(16); - -function tinf_build_tree(t, lengths, off, num) { - var i, sum; - - /* clear code length count table */ - for (i = 0; i < 16; ++i) t.table[i] = 0; - - /* scan symbol lengths, and sum code length counts */ - for (i = 0; i < num; ++i) t.table[lengths[off + i]]++; - - t.table[0] = 0; - - /* compute offset table for distribution sort */ - for (sum = 0, i = 0; i < 16; ++i) { - offs[i] = sum; - sum += t.table[i]; - } - - /* create code->symbol translation table (symbols sorted by code) */ - for (i = 0; i < num; ++i) { - if (lengths[off + i]) t.trans[offs[lengths[off + i]]++] = i; - } -} - -/* ---------------------- * - * -- decode functions -- * - * ---------------------- */ - -/* get one bit from source stream */ -function tinf_getbit(d) { - /* check if tag is empty */ - if (!d.bitcount--) { - /* load next tag */ - d.tag = d.source[d.sourceIndex++]; - d.bitcount = 7; - } - - /* shift bit out of tag */ - var bit = d.tag & 1; - d.tag >>>= 1; - - return bit; -} - -/* read a num bit value from a stream and add base */ -function tinf_read_bits(d, num, base) { - if (!num) - return base; - - while (d.bitcount < 24) { - d.tag |= d.source[d.sourceIndex++] << d.bitcount; - d.bitcount += 8; - } - - var val = d.tag & (0xffff >>> (16 - num)); - d.tag >>>= num; - d.bitcount -= num; - return val + base; -} - -/* given a data stream and a tree, decode a symbol */ -function tinf_decode_symbol(d, t) { - while (d.bitcount < 24) { - d.tag |= d.source[d.sourceIndex++] << d.bitcount; - d.bitcount += 8; - } - - var sum = 0, cur = 0, len = 0; - var tag = d.tag; - - /* get more bits while code value is above sum */ - do { - cur = 2 * cur + (tag & 1); - tag >>>= 1; - ++len; - - sum += t.table[len]; - cur -= t.table[len]; - } while (cur >= 0); - - d.tag = tag; - d.bitcount -= len; - - return t.trans[sum + cur]; -} - -/* given a data stream, decode dynamic trees from it */ -function tinf_decode_trees(d, lt, dt) { - var hlit, hdist, hclen; - var i, num, length; - - /* get 5 bits HLIT (257-286) */ - hlit = tinf_read_bits(d, 5, 257); - - /* get 5 bits HDIST (1-32) */ - hdist = tinf_read_bits(d, 5, 1); - - /* get 4 bits HCLEN (4-19) */ - hclen = tinf_read_bits(d, 4, 4); - - for (i = 0; i < 19; ++i) lengths[i] = 0; - - /* read code lengths for code length alphabet */ - for (i = 0; i < hclen; ++i) { - /* get 3 bits code length (0-7) */ - var clen = tinf_read_bits(d, 3, 0); - lengths[clcidx[i]] = clen; - } - - /* build code length tree */ - tinf_build_tree(code_tree, lengths, 0, 19); - - /* decode code lengths for the dynamic trees */ - for (num = 0; num < hlit + hdist;) { - var sym = tinf_decode_symbol(d, code_tree); - - switch (sym) { - case 16: - /* copy previous code length 3-6 times (read 2 bits) */ - var prev = lengths[num - 1]; - for (length = tinf_read_bits(d, 2, 3); length; --length) { - lengths[num++] = prev; - } - break; - case 17: - /* repeat code length 0 for 3-10 times (read 3 bits) */ - for (length = tinf_read_bits(d, 3, 3); length; --length) { - lengths[num++] = 0; - } - break; - case 18: - /* repeat code length 0 for 11-138 times (read 7 bits) */ - for (length = tinf_read_bits(d, 7, 11); length; --length) { - lengths[num++] = 0; - } - break; - default: - /* values 0-15 represent the actual code lengths */ - lengths[num++] = sym; - break; - } - } - - /* build dynamic trees */ - tinf_build_tree(lt, lengths, 0, hlit); - tinf_build_tree(dt, lengths, hlit, hdist); -} - -/* ----------------------------- * - * -- block inflate functions -- * - * ----------------------------- */ - -/* given a stream and two trees, inflate a block of data */ -function tinf_inflate_block_data(d, lt, dt) { - while (1) { - var sym = tinf_decode_symbol(d, lt); - - /* check for end of block */ - if (sym === 256) { - return TINF_OK; - } - - if (sym < 256) { - d.dest[d.destLen++] = sym; - } else { - var length, dist, offs; - var i; - - sym -= 257; - - /* possibly get more bits from length code */ - length = tinf_read_bits(d, length_bits[sym], length_base[sym]); - - dist = tinf_decode_symbol(d, dt); - - /* possibly get more bits from distance code */ - offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]); - - /* copy match */ - for (i = offs; i < offs + length; ++i) { - d.dest[d.destLen++] = d.dest[i]; - } - } - } -} - -/* inflate an uncompressed block of data */ -function tinf_inflate_uncompressed_block(d) { - var length, invlength; - var i; - - /* unread from bitbuffer */ - while (d.bitcount > 8) { - d.sourceIndex--; - d.bitcount -= 8; - } - - /* get length */ - length = d.source[d.sourceIndex + 1]; - length = 256 * length + d.source[d.sourceIndex]; - - /* get one's complement of length */ - invlength = d.source[d.sourceIndex + 3]; - invlength = 256 * invlength + d.source[d.sourceIndex + 2]; - - /* check length */ - if (length !== (~invlength & 0x0000ffff)) - return TINF_DATA_ERROR; - - d.sourceIndex += 4; - - /* copy block */ - for (i = length; i; --i) - d.dest[d.destLen++] = d.source[d.sourceIndex++]; - - /* make sure we start next block on a byte boundary */ - d.bitcount = 0; - - return TINF_OK; -} - -/* inflate stream from source to dest */ -function tinf_uncompress(source, dest) { - var d = new Data(source, dest); - var bfinal, btype, res; - - do { - /* read final block flag */ - bfinal = tinf_getbit(d); - - /* read block type (2 bits) */ - btype = tinf_read_bits(d, 2, 0); - - /* decompress block */ - switch (btype) { - case 0: - /* decompress uncompressed block */ - res = tinf_inflate_uncompressed_block(d); - break; - case 1: - /* decompress block with fixed huffman trees */ - res = tinf_inflate_block_data(d, sltree, sdtree); - break; - case 2: - /* decompress block with dynamic huffman trees */ - tinf_decode_trees(d, d.ltree, d.dtree); - res = tinf_inflate_block_data(d, d.ltree, d.dtree); - break; - default: - res = TINF_DATA_ERROR; - } - - if (res !== TINF_OK) - throw new Error('Data error'); - - } while (!bfinal); - - if (d.destLen < d.dest.length) { - if (typeof d.dest.slice === 'function') - return d.dest.slice(0, d.destLen); - else - return d.dest.subarray(0, d.destLen); - } - - return d.dest; -} - -/* -------------------- * - * -- initialization -- * - * -------------------- */ - -/* build fixed huffman trees */ -tinf_build_fixed_trees(sltree, sdtree); - -/* build extra bits and base tables */ -tinf_build_bits_base(length_bits, length_base, 4, 3); -tinf_build_bits_base(dist_bits, dist_base, 2, 1); - -/* fix a special case */ -length_bits[28] = 0; -length_base[28] = 258; - -module.exports = tinf_uncompress; diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/package.json b/node_modules/lv_font_conv/node_modules/tiny-inflate/package.json deleted file mode 100644 index 53399e20..00000000 --- a/node_modules/lv_font_conv/node_modules/tiny-inflate/package.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "_args": [ - [ - "tiny-inflate@1.0.3", - "/home/vitaly/Dropbox/Coding/lv_font_conv" - ] - ], - "_from": "tiny-inflate@1.0.3", - "_id": "tiny-inflate@1.0.3", - "_inBundle": false, - "_integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", - "_location": "/tiny-inflate", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "tiny-inflate@1.0.3", - "name": "tiny-inflate", - "escapedName": "tiny-inflate", - "rawSpec": "1.0.3", - "saveSpec": null, - "fetchSpec": "1.0.3" - }, - "_requiredBy": [ - "/opentype.js", - "/unicode-trie" - ], - "_resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", - "_spec": "1.0.3", - "_where": "/home/vitaly/Dropbox/Coding/lv_font_conv", - "author": { - "name": "Devon Govett", - "email": "devongovett@gmail.com" - }, - "bugs": { - "url": "https://github.com/devongovett/tiny-inflate/issues" - }, - "description": "A tiny inflate implementation", - "devDependencies": { - "mocha": "^2.1.0" - }, - "homepage": "https://github.com/devongovett/tiny-inflate", - "keywords": [ - "inflate", - "zlib", - "gzip", - "zip" - ], - "license": "MIT", - "main": "index.js", - "name": "tiny-inflate", - "repository": { - "type": "git", - "url": "git://github.com/devongovett/tiny-inflate.git" - }, - "scripts": { - "test": "mocha" - }, - "version": "1.0.3" -} diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/readme.md b/node_modules/lv_font_conv/node_modules/tiny-inflate/readme.md deleted file mode 100644 index dd8c408c..00000000 --- a/node_modules/lv_font_conv/node_modules/tiny-inflate/readme.md +++ /dev/null @@ -1,31 +0,0 @@ -# tiny-inflate - -This is a port of Joergen Ibsen's [tiny inflate](https://bitbucket.org/jibsen/tinf) to JavaScript. -Minified it is about 3KB, or 1.3KB gzipped. While being very small, it is also reasonably fast -(about 30% - 50% slower than [pako](https://github.com/nodeca/pako) on average), and should be -good enough for many applications. If you need the absolute best performance, however, you'll -need to use a larger library such as pako that contains additional optimizations. - -## Installation - - npm install tiny-inflate - -## Example - -To use tiny-inflate, you need two things: a buffer of data compressed with deflate, -and the decompressed size (often stored in a file header) to allocate your output buffer. -Input and output buffers can be either node `Buffer`s, or `Uint8Array`s. - -```javascript -var inflate = require('tiny-inflate'); - -var compressedBuffer = new Bufer([ ... ]); -var decompressedSize = ...; -var outputBuffer = new Buffer(decompressedSize); - -inflate(compressedBuffer, outputBuffer); -``` - -## License - -MIT diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/test/index.js b/node_modules/lv_font_conv/node_modules/tiny-inflate/test/index.js deleted file mode 100644 index f4e8c88f..00000000 --- a/node_modules/lv_font_conv/node_modules/tiny-inflate/test/index.js +++ /dev/null @@ -1,75 +0,0 @@ -var inflate = require('../'); -var zlib = require('zlib'); -var fs = require('fs'); -var assert = require('assert'); -var uncompressed = fs.readFileSync(__dirname + '/lorem.txt'); - -describe('tiny-inflate', function() { - var compressed, noCompression, fixed; - - function deflate(buf, options, fn) { - var chunks = []; - zlib.createDeflateRaw(options) - .on('data', function(chunk) { - chunks.push(chunk); - }) - .on('error', fn) - .on('end', function() { - fn(null, Buffer.concat(chunks)); - }) - .end(buf); - } - - before(function(done) { - zlib.deflateRaw(uncompressed, function(err, data) { - compressed = data; - done(); - }); - }); - - before(function(done) { - deflate(uncompressed, { level: zlib.Z_NO_COMPRESSION }, function(err, data) { - noCompression = data; - done(); - }); - }); - - before(function(done) { - deflate(uncompressed, { strategy: zlib.Z_FIXED }, function(err, data) { - fixed = data; - done(); - }); - }); - - it('should inflate some data', function() { - var out = Buffer.alloc(uncompressed.length); - inflate(compressed, out); - assert.deepEqual(out, uncompressed); - }); - - it('should slice output buffer', function() { - var out = Buffer.alloc(uncompressed.length + 1024); - var res = inflate(compressed, out); - assert.deepEqual(res, uncompressed); - assert.equal(res.length, uncompressed.length); - }); - - it('should handle uncompressed blocks', function() { - var out = Buffer.alloc(uncompressed.length); - inflate(noCompression, out); - assert.deepEqual(out, uncompressed); - }); - - it('should handle fixed huffman blocks', function() { - var out = Buffer.alloc(uncompressed.length); - inflate(fixed, out); - assert.deepEqual(out, uncompressed); - }); - - it('should handle typed arrays', function() { - var input = new Uint8Array(compressed); - var out = new Uint8Array(uncompressed.length); - inflate(input, out); - assert.deepEqual(out, new Uint8Array(uncompressed)); - }); -}); diff --git a/node_modules/lv_font_conv/node_modules/tiny-inflate/test/lorem.txt b/node_modules/lv_font_conv/node_modules/tiny-inflate/test/lorem.txt deleted file mode 100644 index c37b0a59..00000000 --- a/node_modules/lv_font_conv/node_modules/tiny-inflate/test/lorem.txt +++ /dev/null @@ -1,199 +0,0 @@ -Lorem ipsum dolor sit amet, sea pertinax pertinacia appellantur in, est ad esse assentior mediocritatem, magna populo menandri cum te. Vel augue menandri eu, at integre appareat splendide duo. Est ne tollit ullamcorper, eu pro falli diceret perpetua, sea ferri numquam legendos ut. Diceret suscipiantur at nec, his ei nulla mentitum efficiantur. Errem saepe ei vis. - - Per melius aperiri eu. Et interesset philosophia vim, graece denique intellegam duo at, te vix quot apeirian dignissim. Ei essent percipitur nam, natum possit interpretaris sea ea. Cum assum adipisci cotidieque ut, ut veri tollit duo. Erat idque volutpat mea ut, mel nominati splendide vulputate ea. - - No ferri partem ceteros pro. Everti volumus menandri at pro. Cum illud euripidis cu, mazim deterruisset ei eum. Ex alia dolorem insolens per, malis clita laboramus duo ut, ridens appareat philosophia ea quo. - - Vix elit tantas phaedrum et, ea quo vide facete scriptorem. Ut facer laboramus definitiones has, viris dictas regione at eos. Noluisse constituto vix at, nec malorum rationibus te. Nec numquam definiebas id, vim id liber munere. Simul discere reprimique qui eu. Et aeterno aperiri disputando vix. - - Usu ne denique albucius gloriatur. Pri in novum electram, ei amet electram quo. Vix summo recusabo dissentiunt no. Natum mediocrem maiestatis ut eum, vocibus nominavi has in, affert civibus te nam. Cibo minim ex nec. - - Ipsum tibique deseruisse vel ut. Ad laudem iracundia eam. Eum id legere scripta nominavi, vim melius ceteros et, ea tale enim nec. Aeque facete signiferumque ne est, vis ei sint persequeris. Ei magna veritus nec, enim aliquando ex pro. - - Verear noluisse qui eu, id mutat possit nec. Ad est melius placerat, soluta facilisi et vel. Id his simul consul praesent, no nam nihil perfecto, quo in mucius corrumpit. Assum ceteros cotidieque et nam, illum quaeque cum et. Numquam laoreet his at, in vis suas quando suscipit. - - Ne graece efficiendi interpretaris cum. In vide dictas cotidieque sed. Ea est augue vidisse. Eu epicurei salutandi est, etiam oratio imperdiet ex his. Te pri novum meliore sensibus. An nec eius dicant, iracundia consectetuer per ex. An iuvaret meliore constituto eam, latine detraxit mea eu, mel eu aperiri moderatius. - - Per commodo virtute eu. Eius euripidis nam eu, cum ne praesent vituperata, vix at integre verterem posidonium. Eu adhuc labores eam, temporibus reformidans eos id. Feugiat labores nam ne, eu sed nostro veritus, mea in consul evertitur repudiandae. Cu eos detracto voluptaria consetetur. - - Sit vidit mundi offendit ad. Usu semper vivendo eloquentiam eu. Nonumes deleniti ex vim, fabulas forensibus ad eam. Mazim admodum petentium sed et, has eu eirmod eruditi laoreet. An eam hinc erant. Et semper accumsan similique cum. Sea at inani error. - - Te commodo delicata abhorreant cum, iusto lucilius ut sed, ea his evertitur scripserit. Te eligendi scriptorem sit. Mel quodsi meliore dissentias ei, cum deserunt moderatius ex, error sanctus adversarium eam et. Eam et appareat placerat tincidunt, ius an quidam putant delenit, utinam partem quo ea. Mei legendos constituto scribentur eu, usu ea consul facilisis. Purto veritus lobortis te sea. - - Ne sea ludus solet decore, in cum erat dicta labitur. Doctus maluisset scripserit qui in. Qui at postea audiam, has ut aperiam dissentiet, vix ut harum nemore integre. Sit ad lucilius deseruisse, iuvaret percipit in pro. Sed referrentur voluptatibus ne, his ei nullam omnesque aliquando. - - Ius te purto augue fierent, mea et decore feugiat definiebas. Dolor abhorreant deseruisse at sit, corpora tacimates duo ut. Dicta equidem ne has. At pri elit magna vocibus, ipsum vidisse definiebas id cum. Ex pri laudem assentior. - - Aliquip referrentur id vim, sea labores nominati recteque id. Torquatos adversarium no mel, pri eu impedit fastidii, in usu tollit electram. Ad esse facer nec, mel id laudem adipisci, pri eu vocent efficiantur. Cu est posse possit. Vis definiebas neglegentur ad, pro facer sanctus propriae id. Sit epicurei comprehensam signiferumque ea, ad eam vero admodum scaevola. - - Nec an vivendo ocurreret, qui id nihil fastidii ocurreret. Vis ei mazim zril, dictas aperiri aliquando per ad, sit ne harum postea appellantur. No munere periculis reprimique duo, ut nam unum animal repudiandae. Cibo vocent dissentias te mea. Sea te elit denique volutpat, nec ne epicuri mentitum delicata. - - Ius eros aeterno torquatos ex, et per nisl accusata. Ne nam nonumy discere, in vim nemore commune. Cu pri nibh pertinacia assueverit, duo percipit definitionem ne, delectus voluptaria et vix. Mutat appellantur mea ad, ad pericula suavitate nam. Primis rationibus mei eu. Ad dolorem verterem deserunt eam, in decore probatus consulatu sed. Quo ut ullum epicuri. - - Quod contentiones ea quo. Et nihil conclusionemque mel, offendit perfecto eam eu. Suas case nam et. Ex elit doctus civibus mei, eam facilis scaevola ne. His elit scripta constituto eu, mea tantas petentium ut. - - Eos dicam intellegam id. Cum omnes concludaturque ea. Qui in semper legendos intellegebat. Option mediocrem eam id. Doctus principes deterruisset pro ad, qui ea fugit populo repudiare. - - Sed ne summo percipitur. Eu porro persius vix, an vix esse fastidii delicatissimi, elit rationibus dissentias cu eam. Te dolorem scriptorem mei, no mei aeque tibique. Iisque molestie ei ius. - - Ius eu vide salutatus. Ut modus errem nam, latine vocibus referrentur an has, his laudem docendi at. Mei in enim aeterno, sea id dolores placerat signiferumque. Mea modo semper maluisset at, vel te magna eruditi. Eam in verear tacimates concludaturque. Nam cu porro mediocritatem, platonem splendide in has, dicit dolor populo mei et. Melius saperet tacimates nam an, mentitum fabellas assueverit duo id, sea an nihil tritani inciderint. - - At vim option nominati quaerendum. Dolor vituperata dissentiunt eu nec, an modo appareat suscipiantur sea. In vim suas eligendi sadipscing. Ne nihil molestie ius, sumo aperiri argumentum sit ea, eius principes te sit. Eam no fugit mazim alterum. Sed ignota sententiae in. At pro illud reprimique. - - At nec vivendo luptatum, vel congue oratio cu. Audire sententiae ius an. Antiopam scripserit et duo, illud commune te his. Posse disputationi vix in, quod oporteat id eam. Legimus admodum docendi cum at, cu postea deserunt periculis per. - - Sonet clita ponderum sea ei, est ex semper fabellas. Te vix elitr congue phaedrum, ea eum argumentum eloquentiam. Te elitr suavitate definitionem eum. Est inciderint comprehensam cu, ei ius dolor dignissim assueverit. - - Eos ut dolorum albucius placerat, mel erat mentitum cu. Nec copiosae periculis in, ex vix everti consectetuer. Pro at dicat utroque, iracundia sententiae in usu, sit no cibo natum alienum. Duo audiam placerat ei. Cu mel philosophia disputationi. - - Eam fierent maiestatis instructior no, cu sea iusto iriure voluptatum. Vide numquam vivendo ex est, an vel nulla similique posidonium. No mea tritani molestiae, pro te placerat sensibus. Mel ad posse mucius vocent, te populo timeam democritum mel. - - Pri suscipit luptatum eu, et persius accumsan argumentum sea. In paulo tempor possit sea. Vidisse viderer ut vix, vix ex hinc solet. Altera principes qui in, est utroque scaevola eu. - - Lobortis gubergren mediocritatem ne has, te eum adhuc pericula vulputate, cu antiopam posidonium percipitur mei. Mandamus sapientem et sed. Ea dicta quodsi aperiri sea, ad putent posidonium sea. Duo et molestie erroribus, facete gloriatur eam te, ne eam accusam interesset instructior. Eam posidonium reformidans at. Duo menandri pericula te, qui accumsan facilisi expetendis ut. Et eum dicunt persius pertinacia, vis recteque eloquentiam te. - - Habemus blandit at pri, ad quando consequat per. Mea id adhuc dolores definitionem, in platonem mediocrem abhorreant per, vis cu hinc harum qualisque. Et purto lorem intellegam vel, summo apeirian cum no. At vel nonumy volutpat quaerendum. Ludus fastidii fabellas ut eum, no nec assum homero sanctus. - - His at doming forensibus honestatis, et nostrum praesent eum. At dolores patrioque sententiae eos, quaeque corpora qui no. Nec postea senserit persecuti ei, ad sea invenire voluptatibus, ne veniam lobortis repudiandae mea. Et nam integre democritum. Ex populo menandri qui. Ei duo aeterno accommodare, nibh ponderum iudicabit id vel, cu eos platonem postulant omittantur. - - Mollis molestie nam an, duo an option patrioque posidonium. Putant luptatum aliquando mea ei. Ut quas semper perfecto mei, ei ius justo possit epicurei. Cum odio omnium eu, gubergren definitionem eum ex. Tempor definitionem pri at, ad sit nihil postea moderatius. - - An cum verear scribentur, duo et purto tempor euripidis. Aeterno postulant ut eam, no diam inermis argumentum eos, posse blandit incorrupte ne pri. Est commodo laoreet conclusionemque ei. Graeci tacimates ei eum. Ad nec forensibus voluptaria, mea in alii putent, sed facer ceteros ad. - - Vidisse vivendo placerat duo ex, pri malorum definitionem ex. Decore virtute accumsan cum ad, ex eum postea putant euismod. Mei id alia populo democritum, mei ea adhuc posse. Et primis hendrerit signiferumque nam, ei vis modo lobortis recteque. Saepe persius aliquid et sit, vel everti feugait ne. - - Qui ex laoreet argumentum temporibus, agam tacimates intellegam ad qui. At wisi perfecto has, an sit viris definitionem. No quo sint tincidunt, vim adhuc expetenda ut, nam ad everti expetendis. Adhuc soleat doming nec eu, dicta summo sapientem mei te, nostro mediocrem dignissim ex eos. Id alienum mediocritatem mea, te error iriure placerat pri, feugiat platonem vituperatoribus mea te. - - Quo elit legere consequuntur in, in fabulas ancillae mea, sed scripta mentitum tacimates ei. Mazim phaedrum interpretaris et per, te patrioque assueverit vim. Populo commodo imperdiet vix ut. Mea modo bonorum ut, cu pri enim posse efficiantur. - - Sea veri ancillae adipisci te, quot tota modus ad sea. Suas malorum per ex, mazim rationibus ad vix. Ius ullum deserunt id. Quas corrumpit constituam no mei. Ut quo deleniti atomorum omittantur. Clita feugait docendi ei cum, no mea modo menandri, nam mundi doming an. No altera commodo pri. - - Vel fastidii convenire ex. Vim soleat maluisset te, et expetendis sadipscing liberavisse has. Per sale facilisis accommodare in. Cu option incorrupte nec. - - An eam vocibus intellegat, possit aliquid ex eum, tale mentitum oportere at duo. Ad eam audiam consectetuer, eu meliore verterem mediocritatem has, nam etiam reprimique ut. Nullam adipisci mei in, ad duo quem simul veniam. Vis at tempor sententiae. Te essent iisque aperiam vis. - - Te vix etiam quando ullamcorper, mel ei offendit iudicabit necessitatibus, usu assum facilisi sensibus ex. Alterum adversarium vis ad. Odio fierent deleniti ex cum. Quo an bonorum inciderint, harum simul maiorum in mel, ex legimus alienum corrumpit has. Pri soluta lobortis adipiscing te, eos clita ponderum mandamus at, elit meis assum mea in. Tale intellegebat cu vim, facilis expetenda democritum duo te, cu his causae dissentiet liberavisse. - - Primis latine epicurei no mea. Nam ad quis putant everti, no fugit minimum disputando vix. Laudem neglegentur te qui. Vel splendide efficiendi at, sed liber urbanitas no. Id his atqui inermis scriptorem, vituperata adversarium eos cu, pro in movet accommodare. - - Munere indoctum eu duo. Id eam duis voluptua expetenda, et prodesset inciderint ius. An nibh elitr deseruisse usu. Idque copiosae nam ea, ne tempor omittantur definitionem vis, et cum sumo principes. Quo rebum viderer minimum ex, est at melius blandit. - - Ut adolescens definitiones sed. Est ut erant legendos, in quo facilis salutatus. Agam expetenda salutatus ut sit, quo ex fuisset repudiandae, tale probo aliquip mea id. No vix diceret scaevola. Posidonium conclusionemque ut est. Quo porro menandri assentior ei. - - Vocent neglegentur intellegebat sit id. Et ullum accusam sea, ex nam aeque ubique ocurreret, ea cum quidam euismod. Eam ne zril alienum. Mea id veritus alienum. Mei simul appareat nominati no. Cum option accumsan ea, ne eos legimus dissentiunt. - - Everti prodesset scripserit ea cum, eam nostrud adolescens deterruisset an. Deseruisse definiebas eos ne, mel ex nisl meliore consulatu, per te scripta gubergren. Vix cu novum admodum recusabo, te omnes similique efficiantur nec. Suas novum semper duo ex. Vocibus cotidieque cu qui, at sale malorum intellegam mel. Quo errem accumsan ullamcorper cu, eu quo liber quidam conceptam. - - Scripta habemus quaestio id usu. Id vis utroque forensibus, cu simul fabulas efficiantur vis, ad mel cibo quas feugait. Eros expetendis in cum. No cum aeterno menandri consetetur, quo ex alterum probatus. Eu audire tritani ius, ei labore commune detraxit est. Unum sapientem cotidieque ei vel, ullum prompta per ex. Recteque persequeris quo ei, volutpat quaerendum ex sea. - - Qui phaedrum dissentiunt ne, sea debet fuisset ut. Ridens virtute pro an. Eos at stet modus iisque. Usu ea esse sententiae, deleniti salutandi ne sit, semper graecis sensibus vis ne. Ea corrumpit assueverit mel, in ignota quodsi nominati vix. - - Sea equidem vivendo ut, sed cibo nusquam id, nostro integre te mei. Etiam tractatos et duo, ut ludus dolore pri. Eius hendrerit cu vel, quo ei quodsi causae ullamcorper, per cibo nihil at. Ea voluptatum incorrupte duo, dolore debitis no usu. Ei clita concludaturque cum. Quo quas quando persecuti cu, eruditi scripserit cum eu. - - Ex eleifend philosophia has. Sint repudiandae in sea, et prima latine persecuti eum, pro ea everti expetendis. At his stet facete minimum, sed in delenit maiestatis. Cum ei omittam contentiones, suas sale melius ei cum, mel id vocent propriae necessitatibus. Vix amet nibh ea, autem movet ne vel. - - Accusata percipitur ut vim. Decore tritani scriptorem vis eu, vis volutpat reprehendunt ea. Postea inciderint vix an, no dicunt tamquam mel, usu id illud sensibus expetendis. Ex his aliquip blandit appellantur. - - Officiis evertitur ut mea, cu illud omnesque scripserit eam. Simul vituperatoribus an eum, mel quidam disputando cu, nibh probatus consequat ei mei. Eam populo appareat inimicus ei. Ne officiis definitiones his, vis vero simul similique ei. Iudico oratio elaboraret vim et, id posse nemore eirmod has. - - Pro in veniam consul expetenda, an est movet consequuntur, ex ius admodum recusabo ullamcorper. Mei an utroque ceteros singulis, id eum iudico latine. At est reque lorem intellegat. Quando corpora qui ea. Quo vocent salutatus id. - - Wisi ignota concludaturque est ex. Mea ad inermis vituperatoribus. Dolor persius inimicus ad nec, etiam dignissim qui ex. Ridens quodsi sed ex. Pro in etiam antiopam, eos graece eripuit ad. Affert soluta mei te, pri illud graecis id, vel sint vivendo at. Mea eu veri dicta offendit, cu has sint copiosae. - - Duo libris salutatus ad, porro principes mel ex. Nec iudico consectetuer no. Ut pri causae qualisque democritum. Habeo homero iuvaret at mei. Atqui aliquam eu mea. Gubergren delicatissimi vim ad, amet quodsi efficiendi te ius. Everti latine vulputate pro te, in nam falli definiebas, duo harum graeco nusquam no. - - Per ne aeterno appareat, melius verear tamquam eam id, mei ei invidunt atomorum. Eu doctus viderer eam, sea eu possit dolorem appetere, efficiantur necessitatibus mel cu. Ad est suas officiis, assum erant eum cu, eum erat vitae te. Habemus scaevola no per, nominati adipiscing et eam. - - Ei stet quidam scaevola quo, ius cu noster officiis. Has dolorum vulputate voluptaria ea, ei ius audiam liberavisse. Duo in accumsan constituam, sea esse deseruisse ad. Ne eam eligendi sensibus. Ea rebum porro interesset sed, te alii tritani singulis vis, vel enim liber ne. Movet numquam salutatus eu vim. Nam ad deleniti interpretaris conclusionemque. - - Ea offendit apeirian reprimique ius, accusam incorrupte voluptatibus ei duo. Hinc ponderum detraxit vel te, has no labore regione. Sint impetus duo ex, cu has liber soluta fierent. Vix ut cibo mollis deseruisse. - - Cum id tale disputationi, usu adhuc tritani ea. Id vim volumus quaerendum delicatissimi, no vix dolorum legimus corpora, justo dicit id duo. Ancillae concludaturque at usu. Vim diceret singulis incorrupte eu, oratio nullam quo et. Et sea mediocrem vituperatoribus, fugit tacimates deterruisset cum et. Mel ne sale soleat, vim ad labitur equidem, eos ea justo noluisse. - - An nam consectetuer necessitatibus, eos ei mazim persecuti. Libris explicari dissentiunt te vis, te per veniam sadipscing. Ut diceret euismod vix, duo discere inermis ea. No sit veri sensibus cotidieque, inermis sadipscing reprehendunt qui ad, elitr referrentur repudiandae eu est. Vis quem probo postulant no. Ad viris tollit ullamcorper pri, congue discere ad usu. - - Eam te cetero reprehendunt. His ad ferri feugiat invenire, oratio indoctum id pro. Errem omittam sed et, est no quando omnesque platonem, sed quis philosophia ei. Vix ut porro aeque habemus, eu eam consul nominati omittantur. - - At pri everti indoctum, ullum adipiscing instructior qui ad. Usu ignota omittam ex. At audire vocibus pericula vix, usu ex reque feugait. Ne sea electram salutandi moderatius, te qui verterem scripserit adversarium, an sed tantas lobortis intellegat. - - Ad quas suscipit atomorum duo, quo saepe maiestatis eu. Nostro expetenda ea usu, in atqui doming eam. Vis utroque consulatu ne. Zril noster scripta in eum, vim ad dicam facete legendos, et sit civibus consequat. Eum autem periculis ex. Sed ne nemore eligendi, his legimus verterem ad. - - Est ad amet possit latine. Sed ne legere populo, has pericula scribentur voluptatibus eu. Usu in nonumy vituperata. Aeque oratio gubergren mea ad, quo eu debet dolorum contentiones. Veri expetenda ex mel, eu veniam apeirian vis, aeterno debitis id his. - - Ei ius consul nonumes. Id qui porro periculis, quando dolore iisque qui id. Quo ex simul convenire, vix ei erat petentium, mea cu clita causae. Tale facilisis ex pri, vim ne vide laudem mnesarchum. Duo option blandit ex. Eu est modus vitae, nam in latine maiorum. - - Est an quis quaeque disputando, sit an postulant expetenda, dolor erroribus consequuntur ad vel. Ius phaedrum cotidieque ei, omnis persius copiosae eu vim. Pri facer consequat eu, esse copiosae facilisis mei ne. Ubique convenire no sit. - - Nec regione prompta no. Et quo recteque concludaturque, et ius nostro mollis regione. Ad aliquid lucilius scriptorem eam. Sit at summo eligendi omittantur, ius ea paulo option referrentur. Rationibus inciderint mediocritatem sea id, in dicant assentior sea, quidam copiosae reprehendunt in usu. Cu appetere scripserit vix. Mea mollis audiam aliquam ea, te qui adhuc nonumes deserunt. - - Duo ad facilis consequuntur, vis vide mutat in. At inani ludus eam, an sit quod primis, ea est integre consetetur. Has dolorem salutandi te, tota doctus sit ne, cum ex minimum convenire. Ne legere deterruisset vel, partem phaedrum ne pro, vim modo facete fabellas ex. Cu vix possim eleifend posidonium. Ne est sumo impetus. No quo ubique neglegentur, in usu aliquando scripserit reformidans. - - Vix et numquam expetendis. Ei quas senserit vel, ne has placerat conclusionemque. Noster perpetua euripidis ex sit. Ex mel vidit nonumy vituperata, duo ad nostrum liberavisse. Primis signiferumque duo te, ius te hinc aeque laoreet, ne eius voluptua pri. Dico eros copiosae sed ei, duo cu laudem propriae gubergren. - - Unum erant oratio duo cu, qui no audiam fabulas ornatus. Nihil omnium offendit ad cum, ea ius inermis appetere nominati. Ei nam vero oratio corrumpit. Atqui voluptatibus mei id, at duo assum nostrud aliquando. At nec laudem ridens phaedrum. Dicunt qualisque eum an, ex natum persecuti adipiscing vix, tritani consulatu persecuti nam id. - - In pro mundi percipit, eum tibique eloquentiam in, mea illud ullum altera ex. Veniam epicuri ex mea, quot eruditi definiebas eu duo. Vel augue regione consectetuer ei, appetere moderatius eos in. Laoreet lucilius vim eu. At oratio eirmod qui. - - Altera labitur qui ei, in eam libris primis. Eirmod audiam te vel, eu mei case vide ponderum, an principes persecuti neglegentur mei. Ferri vulputate instructior no vix, in est vidisse detraxit molestiae. Te sit quot choro adipisci, no labore indoctum deterruisset cum. Elit ancillae appetere usu at, mundi dissentias te quo. Mentitum erroribus ad pri. - - Usu ei vero possit appetere. Id erroribus constituam quo. Sit id quidam pertinacia, epicuri delicata eu has, debet melius evertitur ut sed. Id est alienum voluptua. Sit eu dico discere accusata, cu mazim viderer numquam usu, has an solum pertinax. Vel natum summo te, mel integre perfecto consetetur et. - - Postea luptatum menandri cu has, no nam neglegentur necessitatibus. Deseruisse reprehendunt ne mea, lorem tollit nonumes ne vim. Eu pro amet populo, omnesque ponderum sadipscing et ius, ad debet consequat dissentiet vix. Ius nulla aliquip complectitur et, id sed repudiare necessitatibus, et erant legimus invidunt vel. Magna labore democritum vis ei. - - Ne has consulatu reprehendunt, ad nam nulla integre admodum, no has everti impedit perpetua. Tempor antiopam dissentias pro et, ex per legere electram. Ut ius brute omnesque consequat, an his dissentias persequeris. Dicta ludus tritani eam ei. - - Minim rationibus et usu, eam in elit senserit. Stet harum qualisque eu has. Cu decore nostrud sit, mea magna iracundia te, vix tritani convenire imperdiet at. Te pri dictas appetere, brute velit ius ad. Veri dicit legere pri at, sea tation fierent molestie eu. Eum et paulo consul. - - Veri iusto mei id, reque invidunt ne his, te agam dolore electram nam. Est ullum oporteat facilisi eu, dicunt officiis ad eos. Vis ut populo similique. Et minim platonem percipitur usu. Et usu saperet alienum consequuntur, per ad luptatum concludaturque, cibo duis definitionem vix an. - - Ancillae iracundia eu vix, mazim conceptam no qui. Sit nihil epicuri voluptatibus in, an sale debet vis. Porro congue senserit quo an. Facer constituam vel no, facete pertinacia adolescens pro ut, errem nullam menandri ius an. Ex tractatos periculis interpretaris eum, vim ornatus patrioque an. Ius magna iudicabit reprehendunt at, ea viris ornatus sit, eum in vidisse percipitur. - - Vel agam interpretaris ex, zril deserunt electram in duo. Eu eam vero atqui maiorum. Ne pri alia meis decore, vis ea expetenda dissentiet. Cu quis evertitur intellegat cum, ea sed posse expetendis liberavisse, sed ne quis deseruisse. Solum ignota causae ad quo, ferri erant est id, vim ut choro liberavisse. Ne nam causae reformidans, possit percipitur id has. Case tota id has, pri tation ancillae sensibus te. - - Posse aperiam sit id, est in dicam iracundia. Eum mollis dolores te, at vis laoreet habemus fuisset. Vim nibh civibus signiferumque eu. Fierent reformidans an quo. Exerci dissentiunt ex pri, per illum debitis et. - - Vel tota exerci facilis eu. Est primis atomorum id. Hinc insolens dissentiet duo et, iusto inermis salutatus vel ne. Corpora propriae vituperata ex est, sit quod ignota deterruisset no, elitr tempor suscipit ex nam. Eripuit repudiandae his cu, fabulas inermis accumsan ei eum. Nisl molestie ex pri, sea veniam graecis expetenda eu. - - Vel possim moderatius cu, sea no dicit aliquip erroribus, eam te debet partem. Sea no graeco vocent probatus, qui ut fugit delectus definiebas. Id soluta viderer per, tritani disputando necessitatibus ad vix. Eleifend facilisis percipitur at pri. Sit dicat adipisci ex, mundi solet doming nec te, ne maiorum tractatos evertitur sit. At velit propriae eos, mel alii invenire id. - - Ei per minim voluptaria, ea mel scaevola mediocrem euripidis. Et quo wisi sonet eirmod, consul tritani delenit vis id, per id mutat facer. Accusam patrioque eu cum, te cum quot saepe scribentur. Eros summo mnesarchum sed ut, ad possit nostrum comprehensam ius. - - In elitr nullam eam, cu mea mentitum omnesque. Ex has wisi consequat. Sed simul offendit argumentum no, solet corpora lucilius cu vis. Nec ut legimus suscipit, ne usu mucius latine, nusquam mentitum perpetua ad vel. Vidisse feugait facilisis an sea. - - Mel ne oblique menandri, nam graecis antiopam id. Id sanctus referrentur contentiones eam, et eam purto consequat, te vix quod tantas bonorum. Fabellas sapientem consulatu eu per. Iudico possim per ei, eu vide adversarium per. Usu vero essent albucius et, nec veritus ancillae prodesset eu, ipsum probatus cu sea. In quod tantas iudicabit eam, his et odio augue iuvaret. - - Eum an iisque oportere dignissim, est convenire molestiae interesset at, eam id dico audiam quaerendum. Est prompta ocurreret adversarium id, sumo legendos iracundia pri ea, quo ei agam aeque. Ei vix omnes conclusionemque, nec ut novum urbanitas honestatis. Sumo moderatius eos no, ea saperet impedit petentium vel. Vim in harum blandit, posse gubergren deterruisset ex ius. Ei error menandri platonem duo, partiendo qualisque nam te. - - Erat nobis maluisset at sed. Ius no dolor soluta. Has ne sumo brute suavitate, his ne viris aeterno omittantur. Te usu delenit philosophia. Mei wisi libris deseruisse eu. - - Tantas constituam per ei, doctus timeam ea vim. Quo ei putent delicata, eu quo aeterno labores. Qui liber eripuit singulis te, qui diam appareat similique ex, aliquip feugait noluisse cu vis. Dicant recteque definitiones ex mea. Cu exerci dictas eleifend duo, id nonumes denique vix, pri magna facer saperet ei. - - Aeque quodsi partiendo in est, partem malorum intellegebat et per, mea no porro ipsum oratio. Mel sale meliore fuisset ad, per aeque aperiam nominavi ex. Ad tantas meliore placerat mel, mel in prompta torquatos, nulla mollis tamquam pri cu. An quo noster intellegat. Utroque antiopam similique ius eu, at duo ullum apeirian reprimique, vide quaeque assueverit eam ea. - - Mel no quod viris latine, no platonem dissentiunt ius, quis amet gloriatur no eos. Ei mea ancillae probatus. Ad molestiae moderatius vim. Ex soluta meliore molestiae has, ne sea quas natum. Movet verterem vis no, vis amet homero an. Ius dicta tantas id. - - Pri ut modus disputationi, iudico ignota commune eam ut. Vim integre eripuit appareat in, malorum inermis perpetua his cu. Altera alienum mediocrem eu vis. Omnis honestatis repudiandae in qui, his quis duis te, novum rationibus cu est. Eos ut dicant molestie, pri argumentum quaerendum adversarium te. Ea cetero deserunt conceptam pri. - - Augue utroque iudicabit ei vim, ea per dico debet. Cu per regione feugait, sensibus necessitatibus cu cum, at mundi aliquando per. In cum latine evertitur definitionem. Sit movet aperiri liberavisse ad, pro paulo veniam eu. Vel ut cotidieque definitionem, adhuc movet intellegebat quo ne. - - Eum deleniti gubergren an, dolor utamur omittam ea ius. Eam movet possim accommodare et. Ius cu malorum tibique, ludus eligendi id pro. Solum vulputate efficiendi ex quo. - - Regione fierent eu per. No amet iuvaret efficiantur usu. Sit minim sensibus no, his principes gloriatur adversarium no. Ubique definitionem ne vis, vis propriae intellegebat te. Tibique suscipiantur his no. - - Eu quo elit cetero scripserit, maiestatis scripserit pri in, vim cu tibique eligendi. Ne tritani gubergren vituperatoribus quo, melius facilisi ne has. Id pro vivendo fuisset, ex causae utroque deleniti mea. Ad eam percipit perfecto, mutat justo essent at vim. Eros novum duo et, in pri melius blandit. Affert albucius eos ad. Singulis mnesarchum ea mel, eos dictas nominavi reprimique no. - - Ei qui congue voluptatibus. Nam no tritani elaboraret. Conceptam rationibus expetendis duo no. Vivendo reprimique cum te, qui timeam copiosae id, modo delicata ius ad. Nam cu dico aliquip, eu pro legimus officiis delicata, fugit quando ea per. - - In eos equidem accommodare. Electram principes ad usu. Nec adversarium disputationi in, sed feugait lucilius ut. Ludus hendrerit cu ius. - - Cum suscipit gloriatur ea. Id aeterno principes euripidis nam, mea id probo graeco verterem, vulputate ullamcorper definitionem in pri. Mel cu detraxit assueverit. Quo et posse fastidii, ei ponderum delicata sed, has brute forensibus ut. Quo option pericula ea, nam gubergren assueverit te. - - Graece doming intellegebat duo in, ullum clita expetenda nam at. Diam alienum menandri sit id. Unum clita consulatu duo id. Per in mucius legendos scribentur, sea quod phaedrum ut, facete animal dissentias no usu. Quo te dico suavitate. Est te erant congue vivendum, oporteat forensibus in his. - - Pro no eleifend reprehendunt, ut meis consetetur argumentum mei. Id vis harum ornatus cotidieque. Inani libris volumus ea qui. Ius at suas percipit voluptatum, pro solet invidunt honestatis ei, et nam delectus reprimique instructior. Nam id tacimates argumentum dissentiet, mei an sint adipiscing. - - Quando cotidieque sit at, ei sed tantas ancillae verterem, cum nibh omittam ut. Erant laboramus moderatius te eum. Civibus adipiscing sed ne, vix eu erant euripidis. Illud qualisque at nec, id tale sint facete per. Vero autem democritum eam an. - - Mel mazim prodesset ad. An vis alii suas congue, vim veri illum iisque et, in modus perfecto deseruisse vel. Sed summo fuisset fierent an. Vel eu bonorum ornatus, alii decore nec ad. Eu dicta constituto mea. - - Id sint stet graece usu. Cu sit essent reformidans, eos eius ridens et. Usu voluptaria posidonium cu. \ No newline at end of file diff --git a/node_modules/lv_font_conv/package.json b/node_modules/lv_font_conv/package.json deleted file mode 100644 index e0f1464f..00000000 --- a/node_modules/lv_font_conv/package.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "name": "lv_font_conv", - "version": "1.5.2", - "description": "Rasterize vector fonts for embedded use. Supports subsettings & merge.", - "keywords": [ - "font", - "convertor", - "embedded" - ], - "repository": "lvgl/lv_font_conv", - "license": "MIT", - "files": [ - "lv_font_conv.js", - "lib/" - ], - "bin": { - "lv_font_conv": "lv_font_conv.js" - }, - "scripts": { - "start": "parcel ./web/index.html --open", - "build": "parcel build ./web/index.html ./web/content.html --public-url ./", - "build:dockerimage": "docker build -t lv_font_conv_freetype ./support", - "build:freetype": "docker run --rm -v $(pwd):/src/lv_font_conv -it lv_font_conv_freetype ./lv_font_conv/support/build.sh", - "lint": "eslint .", - "test": "npm run lint && nyc mocha --recursive", - "coverage": "npm run test && nyc report --reporter html", - "shrink-deps": "shx rm -rf node_modules/opentype.js/src node_modules/opentype.js/dist/opentype.{m,js.m}* node_modules/pngjs/browser.js", - "prepublishOnly": "npm run shrink-deps" - }, - "dependencies": { - "argparse": "^2.0.0", - "bit-buffer": "^0.2.5", - "debug": "^4.1.1", - "make-error": "^1.3.5", - "mkdirp": "^1.0.4", - "opentype.js": "^1.1.0", - "pngjs": "^6.0.0" - }, - "bundledDependencies": [ - "argparse", - "bit-buffer", - "debug", - "make-error", - "mkdirp", - "opentype.js", - "pngjs" - ], - "devDependencies": { - "eslint": "^7.21.0", - "file-saver": "^2.0.2", - "mocha": "^8.3.0", - "nyc": "^15.1.0", - "parcel-bundler": "^1.12.4", - "posthtml-include": "^1.6.2", - "roboto-fontface": "^0.10.0", - "shx": "^0.3.2" - }, - "browserslist": [ - "last 1 Chrome version" - ] -} diff --git a/src/displayapp/screens/WatchFaceMeow.cpp b/src/displayapp/screens/WatchFaceMeow.cpp index a4abadaa..656cebf2 100644 --- a/src/displayapp/screens/WatchFaceMeow.cpp +++ b/src/displayapp/screens/WatchFaceMeow.cpp @@ -170,16 +170,19 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, } // Side Cover - static constexpr lv_point_t linePoints[nLines][2] = {{{30, 25}, {68, -8}}, - {{26, 167}, {43, 216}}, - {{27, 40}, {27, 196}}, - {{12, 182}, {65, 249}}, - {{17, 99}, {17, 144}}, - {{14, 81}, {40, 127}}, - {{14, 163}, {40, 118}}, - {{-20, 124}, {25, -11}}, - {{-29, 89}, {27, 254}}}; + //paires de points pour chaque ligne + // les lignes sont pas les contours des triangles, elles sont des grosses bandes superposées + static constexpr lv_point_t linePoints[nLines][2] = {{{30, 25}, {68, -8}}, //1 small triangle on top + {{26, 167}, {43, 216}}, //2 purple, smalltriangle in the bottom half part on the clock display side + {{27, 40}, {27, 196}},// 3 small triangles up above and below battery + {{12, 182}, {65, 249}}, //4 most bottom right triangle + {{17, 97}, {17, 147}}, // 5 left part of battery zone, overlapped after by the large triangles + {{16, 81}, {42, 127}}, //6 upper part of battery zone + {{16, 163}, {42, 118}}, //7 lower part of battery zone + {{-20, 124}, {25, -11}}, //8 large upper triangle + {{-29, 89}, {27, 254}}}; //9 large lower triangle + //largeur des bandes static constexpr lv_style_int_t lineWidths[nLines] = {18, 15, 14, 22, 20, 18, 18, 52, 48}; const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); @@ -193,15 +196,16 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, //Battery indicator logoPine = lv_img_create(lv_scr_act(), nullptr); - lv_img_set_src(logoPine, "F:/images/pine_small.bin"); - lv_obj_set_pos(logoPine, 15, 106); + lv_img_set_src(logoPine, "F:/images/cat_small.bin"); + + lv_obj_set_pos(logoPine, 12, 108); lineBattery = lv_line_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_line_width(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 24); + lv_obj_set_style_local_line_width(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 30); lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); lv_obj_set_style_local_line_opa(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 190); - lineBatteryPoints[0] = {27, 105}; - lineBatteryPoints[1] = {27, 106}; + lineBatteryPoints[0] = {27, 107};//27 = image x offset + image width / 2 + lineBatteryPoints[1] = {27, 108};// the line covering the image is initialized as 1 px high lv_line_set_points(lineBattery, lineBatteryPoints, 2); lv_obj_move_foreground(lineBattery); @@ -533,8 +537,9 @@ void WatchFaceMeow::Refresh() { } void WatchFaceMeow::SetBatteryLevel(uint8_t batteryPercent) { - // starting point (y) + Pine64 logo height * (100 - batteryPercent) / 100 - lineBatteryPoints[1] = {27, static_cast(105 + 32 * (100 - batteryPercent) / 100)}; + // starting point (y) + Pine64 logo height * (100 - batteryPercent) / 100^ + //the ligne grows, it covers the icon starting from the top + lineBatteryPoints[1] = {27, static_cast(107 + 31 * (100 - batteryPercent) / 100)}; lv_line_set_points(lineBattery, lineBatteryPoints, 2); } @@ -565,7 +570,7 @@ bool WatchFaceMeow::IsAvailable(Pinetime::Controllers::FS& filesystem) { } filesystem.FileClose(&file); - if (filesystem.FileOpen(&file, "/images/pine_small.bin", LFS_O_RDONLY) < 0) { + if (filesystem.FileOpen(&file, "/images/cat_small.bin", LFS_O_RDONLY) < 0) { return false; } diff --git a/src/resources/images/cat_clean.png b/src/resources/images/cat_clean.png index 6f8da93a9c060c3405fff35442a8c96114417fdb..e4d98e961fc817f7a566f6a14c3f172962351cbd 100644 GIT binary patch delta 1735 zcmV;&1~~b^3i1shiBL{Q4GJ0x0000DNk~Le0000U0000V2nGNE0Pt*m2azEle^X0E zDh_rK70FPYEQpFYY88r5A=C=3I+$GgAv9@7Qd}Gb*Mfr|i&X~~XI&j!1wrrw#M!|~ z(M3x9Us7lhNc_lk#p5^5 zMVAGh88tKMIpQd>SZrgZjakXmh$o3-s-{!Eko8#Qyv127S6TC({DqOczP!YBnuADU z0gI3zLO~T9D8ojaR-F_JY1&Wt`1@VIL@tF~8(`#^M+F*W*AM;&zq_>xe-q3c&jreNztTy#=~fz22JpIDG&z)K%&RI5-4Gij=+P^X|^}-u^w)?C%FY_HvR1 zq;zKh000JJOGiWi4*(AUlfD5Ze;x-M7{0Mj9RL6X?@2^KR7l6Q)_aUyRTT#C-`u$! zuOf47E7M}Awup@{su=Ns22em-Boz#L6-6*aNmNvP#Rq~!d=NEJ!YfJusUj&`dICV_;Wu~#euy{s_gbNODE$94)PEXZN%W5w^cA=T zFG9g?R88Q%^F>&PJvqoHe{gA4RZo2!phw|mJTI?myt8T3Zu}m?t8fQinddWbM^#l5 zn2Cd8`}|(7_uVB+mK=kZ;1T>Kdk-gHX61Po9>BAw1BTb*n*qKXKf;$WXTWaV=;-Jw zjNn5J*7`tK;#j;Gt8rXo|Ilu?`;<2RjXx(s*n+QOUg#bk0PW$%f2padlVh_sAkAPt zh!u(SSNLLMe*~>Ievg$HYXFbo?bBT0ouPMqBDn%b4@52Be=7$&ukpTMXB&${&zafy z7ycB3YA?~Q#OLshfE|rBxu!we4au9&H{M-HeG!XUL92|a`glpmHImj}+JKrDK!oe{<9REm^Q_<87m`t?S zC82ih1^D*l#Om0t$zl48Y>wYU^KRa7r_=dJ1RP!%fNgkfe+1*_IlyuG@Csf(PT^-cWd9XgWWRewJ^}ZK%}o zxG18t8k=G_Gkz9^`h(i|dS0|KKOpM^I+WylU*m3v68$=y-T;OB>$+Z*eCX@;30M%| zRq22eE&Li?e|$3ZEKO^iS4!DaN_hxhi_q+AZNbL}n#GB!gbv<>PeuR#(83J-BozMu zCnwrv8H@<>#yFQMbu-HuK_w;yE=B8qfgL+9O%^mdDe>qiEon2Mc8eEQt;;^|U@zFGb zNAY*OH~G?E+Zzqy!sh66Q+}U~%aY~rw2Vc^U?V;g+rKuPUKb76nCE|Dbl_lD;w4>dflDcqDT7O}1mDg_!^!fkX&8%A0XKx14H1+%cw^M-;ZWW0%~wUm-jy=cj94k< zu~N$Ue|TGz>-)LOUXpKdYH$jtkBp3rM?B7m&1=#u?#lrskjJpNuIur-u6xr>AWA8B zS58?chrCi&lCg8Nd0pEr7RaLzuPR|VO zz0kM0lyYBP*Sg*A;f+RcYSe2XzEIcoj_GP|f1l%PDP?;pWg-dIZ*8-&VsvzLcDLI- zHd96?z=uSo&e|8~v=Q%R^~+;U{%@H(ckU^f9D8XJyHlsP)^)w}e>oi9AJoao$*E4K z^W)gxg+;Sw&HC`z*w_)NfIIO=e5|hPsr~V}zY!~?JXlrL#VMP6dcEEP+>`S8ZC%$Z d8UCN4|33nqieR2(vn2ok002ovPDHLkV1lCfRb~JH delta 1297 zcmV+s1@8Lt4ZsQ^iBL{Q4GJ0x0000DNk~Le0000M0000N2nGNE01xQDo{=FSe_M-E zDjn<~q7b1vSr8R*)G8FALZ}s5buhW~3z{?}DK3tJYr(;f#j1mgv#t)Vf*|+-;_Tq0 z=prTlFDbN$@!+^0@9sVB-U0qbg{fxOIG}2lkxnLrY;INPenk*Nh+r5YiJAJGD5l^! zzV6}U>s_2@d7t}p^eTCi0X~6vf0pTnMZ7^gy=m#3_lcvdBq_w_#A60skob}7vdeFr ziw^sFX4J@}=ZT}lLa~G84rV1oC7vRVDXK>K{;bOi=Pk}^rN&zKh(v6QksO{!alsy=?t4Xb?PYO*o+Py!kdBrDAE9GUCwXA(jZ%o zRT#j2?CR<18IQ-YHf(GD9p!ruxtN%yc)O>*ojRw+=pe6%ke?py_SSyI4z>y z+qM5`aNLOdF%r{j;pqx|7m;3vZvs>WNwbS+8WzNR2ipSw7ru@gfArOmFcItH;MMpx zfb*kEx>pB3M3GnGRshPS8jDwOQ4GaF+!PL1g-SkI6ve({eOnBQ2A$G|RFLtyb$G{ti`J z9M{jHlU^%|;$LTt?uwLUxdU@kN*iNz7ll?V8XO#46C`(sk{!k#yigRy)}cCGKc0A3 zRkb_M^9-}1V@IdtdA_@^ukV4-gEK;K9w~}q;CLMM6MY$~s@j+5`MPNMy}iA?lbX$D zib!|isiG)$o(Nw%*=MIL%N2MAyRs~s7@aa7Gm4_ Date: Thu, 30 May 2024 16:08:27 +0200 Subject: [PATCH 086/101] update fonts with calendar symbols, rebase failed --- src/displayapp/fonts/fonts.json | 23 +-------------------- src/displayapp/screens/Symbols.h | 34 -------------------------------- 2 files changed, 1 insertion(+), 56 deletions(-) diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json index 9b6c416e..adb20407 100644 --- a/src/displayapp/fonts/fonts.json +++ b/src/displayapp/fonts/fonts.json @@ -7,28 +7,7 @@ }, { "file": "FontAwesome5-Solid+Brands+Regular.woff", -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf0f3, 0xf1f6, 0xf073" -======= -======= ->>>>>>> 055b75d5 (test combiner tout) -<<<<<<< HEAD - "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf0f3, 0xf1f6" -======= - "range": "0xf294, 0xf242, 0xf54b, 0xf1b0, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf4ba, 0xf236" ->>>>>>> f5dfbc44 (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) -<<<<<<< HEAD ->>>>>>> d7de641b (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) -======= -======= - "range": "0xf294, 0xf242, 0xf54b, 0xf1b0, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf4ba, 0xf236" ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) ->>>>>>> 055b75d5 (test combiner tout) -======= - "range": "0xf294, 0xf242, 0xf54b, 0xf1b0, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf4ba, 0xf236, 0xf0f3, 0xf1f6" ->>>>>>> ce13c721 (fix fonts after merge mess) + "range": "0xf294, 0xf242, 0xf54b, 0xf1b0, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf4ba, 0xf236, 0xf0f3, 0xf1f6, 0xf073" } ], "bpp": 1, diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index 14197a11..6da066ef 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -10,34 +10,11 @@ namespace Pinetime { static constexpr const char* bluetooth = "\xEF\x8A\x94"; static constexpr const char* plug = "\xEF\x87\xA6"; static constexpr const char* shoe = "\xEF\x95\x8B"; -<<<<<<< HEAD -<<<<<<< HEAD -======= -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> 055b75d5 (test combiner tout) -======= ->>>>>>> ce13c721 (fix fonts after merge mess) static constexpr const char* paw = "\xEF\x86\xB0"; static constexpr const char* clock = "\xEF\x80\x97"; static constexpr const char* bell = "\xEF\x83\xB3"; static constexpr const char* notbell = "\xEF\x87\xB6"; -<<<<<<< HEAD static constexpr const char* info = "\xEF\x84\xA9"; -<<<<<<< HEAD -======= ->>>>>>> 504f5774 (copy :( files from gitlab repo) -======= - static constexpr const char* paw = "\xEF\x86\xB0"; - static constexpr const char* clock = "\xEF\x80\x97"; - static constexpr const char* bell = "\xEF\x83\xB3"; - static constexpr const char* notbell = "\xEF\x87\xB6"; - static constexpr const char* info = "\xEF\x84\xA9"; ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) ->>>>>>> 055b75d5 (test combiner tout) -======= - static constexpr const char* info = "\xEF\x84\xA9"; ->>>>>>> ce13c721 (fix fonts after merge mess) static constexpr const char* list = "\xEF\x80\xBA"; static constexpr const char* sun = "\xEF\x86\x85"; static constexpr const char* check = "\xEF\x95\xA0"; @@ -64,23 +41,12 @@ namespace Pinetime { static constexpr const char* eye = "\xEF\x81\xAE"; static constexpr const char* home = "\xEF\x80\x95"; static constexpr const char* sleep = "\xEE\xBD\x84"; -<<<<<<< HEAD -<<<<<<< HEAD static constexpr const char* calendar = "\xEF\x81\xB3"; static constexpr const char* bird = "\xEF\x92\xBA"; static constexpr const char* zzz = "\xEF\x88\xB6"; // fontawesome_weathericons.c -======= -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> ce13c721 (fix fonts after merge mess) - static constexpr const char* bird = "\xEF\x92\xBA"; - static constexpr const char* zzz = "\xEF\x88\xB6"; - // fontawesome_weathericons.c ->>>>>>> 055b75d5 (test combiner tout) // static constexpr const char* sun = "\xEF\x86\x85"; static constexpr const char* cloudSun = "\xEF\x9B\x84"; static constexpr const char* cloudSunRain = "\xEF\x9D\x83"; From 3f214a2f84ec9ec9c890d83fdbc29d38a401a8e6 Mon Sep 17 00:00:00 2001 From: Eve C Date: Thu, 30 May 2024 16:22:49 +0200 Subject: [PATCH 087/101] cleaned CMAKELISTS --- src/displayapp/apps/CMakeLists.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index 7ae83634..78dc54d3 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -28,10 +28,6 @@ else() set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Meow") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Meow") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware") endif() From 3133adaba9cad7f5aa4aa5356cdf6836ff75f151 Mon Sep 17 00:00:00 2001 From: Eve C Date: Thu, 30 May 2024 17:12:03 +0200 Subject: [PATCH 088/101] remove apps and wf from cmake --- src/displayapp/apps/CMakeLists.txt | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index ccfd7347..1e567700 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -8,31 +8,27 @@ else () set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::HeartRate") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Music") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Paint") - set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Paddle") + #set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Paddle") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Twos") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Dice") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Metronome") - set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Navigation") - set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Weather") + #set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Navigation") + #set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Weather") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Calendar") - set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Motion") + #set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Motion") set(USERAPP_TYPES "${DEFAULT_USER_APP_TYPES}" CACHE STRING "List of user apps to build into the firmware") endif () if(DEFINED ENABLE_WATCHFACES) set(WATCHFACE_TYPES ${ENABLE_WATCHFACES} CACHE STRING "List of watch faces to build into the firmware") else() - set(DEFAULT_WATCHFACE_TYPES "WatchFace::Digital") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Analog") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::PineTimeStyle") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") + set(DEFAULT_WATCHFACE_TYPES "WatchFace::Meow") + #set(DEFAULT_WATCHFACE_TYPES "WatchFace::Digital") + #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Analog") + #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::PineTimeStyle") + #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Meow") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Meow") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") + #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware") endif() From 164a21218f496295347e49c2a0958c8d2692a859 Mon Sep 17 00:00:00 2001 From: Eve C Date: Tue, 9 Jul 2024 12:05:06 +0200 Subject: [PATCH 089/101] faire en sorte que watchfacemeow et watchface infineat soient compatibles, en cours --- src/displayapp/screens/WatchFaceInfineat.cpp | 2 + src/displayapp/screens/WatchFaceMeow.cpp | 113 ++++++++++++++----- src/displayapp/screens/todo.txt | 4 + 3 files changed, 91 insertions(+), 28 deletions(-) create mode 100644 src/displayapp/screens/todo.txt diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index 0ebb5bff..a411a477 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -544,6 +544,8 @@ void WatchFaceInfineat::Refresh() { lv_obj_align(alarmIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); } } + + stepCount = motionController.NbSteps(); if (stepCount.IsUpdated()) { lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); diff --git a/src/displayapp/screens/WatchFaceMeow.cpp b/src/displayapp/screens/WatchFaceMeow.cpp index 656cebf2..ca2de2c6 100644 --- a/src/displayapp/screens/WatchFaceMeow.cpp +++ b/src/displayapp/screens/WatchFaceMeow.cpp @@ -195,11 +195,10 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, } //Battery indicator - logoPine = lv_img_create(lv_scr_act(), nullptr); - lv_img_set_src(logoPine, "F:/images/cat_small.bin"); - - lv_obj_set_pos(logoPine, 12, 108); - + logoCat = lv_img_create(lv_scr_act(), nullptr); + lv_img_set_src(logoCat, "F:/images/cat_small.bin"); + lv_obj_set_pos(logoCat, 12, 108); + //adjust position for cat lineBattery = lv_line_create(lv_scr_act(), nullptr); lv_obj_set_style_local_line_width(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 30); lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); @@ -260,26 +259,33 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, bleIcon = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_label_set_text_static(bleIcon, Symbols::bluetooth); - lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_TOP_MID, 0, -10); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); // Based on existing code, I understand that items on the screen (date, bluteooth status..) // are declared here with default states, and later below the state (date, ...) is assigned // So I do the same to add the alarm status : I put a symbol that has a default value // and a text that has a default value - // symbol - alarmIcon = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(alarmIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_label_set_text_static(alarmIcon, Symbols::paw); - lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + // text labelAlarm = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_obj_set_style_local_text_font(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); - lv_obj_align(labelAlarm, alarmIcon, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); + //lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); + lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); lv_label_set_text_static(labelAlarm, "00:00"); + labelTimeAmPmAlarm = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(labelTimeAmPmAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_label_set_text_static(labelTimeAmPmAlarm, ""); + lv_obj_set_style_local_text_color(labelTimeAmPmAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_obj_align(labelTimeAmPmAlarm, labelAlarm, LV_ALIGN_OUT_TOP_RIGHT, 0, 0); + // symbol + alarmIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(alarmIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_label_set_text_static(alarmIcon, Symbols::paw); + lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); stepValue = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); @@ -326,7 +332,7 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, btnToggleCover = lv_btn_create(lv_scr_act(), nullptr); btnToggleCover->user_data = this; lv_obj_set_size(btnToggleCover, 60, 60); - lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_CENTER, 0, 80); + lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); lv_obj_set_style_local_bg_opa(btnToggleCover, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); const char* labelToggle = settingsController.GetInfineatShowSideCover() ? "ON" : "OFF"; lblToggle = lv_label_create(btnToggleCover, nullptr); @@ -334,6 +340,17 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, lv_obj_set_event_cb(btnToggleCover, event_handler); lv_obj_set_hidden(btnToggleCover, true); + btnToggleAlarm = lv_btn_create(lv_scr_act(), nullptr); + btnToggleAlarm->user_data = this; + lv_obj_set_size(btnToggleAlarm, 60, 60); + lv_obj_align(btnToggleAlarm, lv_scr_act(), LV_ALIGN_CENTER, 0, 80); + lv_obj_set_style_local_bg_opa(btnToggleAlarm, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); + const char* labelToggleAlarm = settingsController.GetInfineatShowAlarmStatus() ? Symbols::bell : Symbols::notbell; + lblAlarm = lv_label_create(btnToggleAlarm, nullptr); + lv_label_set_text_static(lblAlarm, labelToggleAlarm); + lv_obj_set_event_cb(btnToggleAlarm, event_handler); + lv_obj_set_hidden(btnToggleAlarm, true); + // Button to access the settings btnSettings = lv_btn_create(lv_scr_act(), nullptr); btnSettings->user_data = this; @@ -383,6 +400,7 @@ void WatchFaceMeow::CloseMenu() { lv_obj_set_hidden(btnNextColor, true); lv_obj_set_hidden(btnPrevColor, true); lv_obj_set_hidden(btnToggleCover, true); + lv_obj_set_hidden(btnToggleAlarm, true); } bool WatchFaceMeow::OnButtonPushed() { @@ -397,6 +415,7 @@ void WatchFaceMeow::UpdateSelected(lv_obj_t* object, lv_event_t event) { if (event == LV_EVENT_CLICKED) { bool showSideCover = settingsController.GetInfineatShowSideCover(); int colorIndex = settingsController.GetInfineatColorIndex(); + bool showAlarmStatus = settingsController.GetInfineatShowAlarmStatus(); if (object == btnSettings) { lv_obj_set_hidden(btnSettings, true); @@ -404,6 +423,7 @@ void WatchFaceMeow::UpdateSelected(lv_obj_t* object, lv_event_t event) { lv_obj_set_hidden(btnNextColor, !showSideCover); lv_obj_set_hidden(btnPrevColor, !showSideCover); lv_obj_set_hidden(btnToggleCover, false); + lv_obj_set_hidden(btnToggleAlarm, false); } if (object == btnClose) { CloseMenu(); @@ -419,6 +439,18 @@ void WatchFaceMeow::UpdateSelected(lv_obj_t* object, lv_event_t event) { const char* labelToggle = showSideCover ? "OFF" : "ON"; lv_label_set_text_static(lblToggle, labelToggle); } + + if (object == btnToggleAlarm) { + settingsController.SetInfineatShowAlarmStatus(!showAlarmStatus); + bool newShowAlarmStatus = settingsController.GetInfineatShowAlarmStatus(); + lv_obj_set_hidden(labelAlarm, !newShowAlarmStatus); + lv_obj_set_hidden(alarmIcon, !newShowAlarmStatus); + lv_obj_set_hidden(labelTimeAmPmAlarm, !newShowAlarmStatus); + const char* labelToggleAlarm = newShowAlarmStatus ? Symbols::bell : Symbols::notbell; + lv_label_set_text_static(lblAlarm, labelToggleAlarm); + } + + if (object == btnNextColor) { colorIndex = (colorIndex + 1) % nColors; settingsController.SetInfineatColorIndex(colorIndex); @@ -502,22 +534,47 @@ void WatchFaceMeow::Refresh() { //bleState.Get : in displayapp/widgets/StatusIcons.cpp: bleState = bleController.IsConnected(); //dynamic icons have their definitions in displayApp/screens/BleIcon.h / cpp lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); - lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_TOP_MID, 0, -10); + lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 3); } // Add alarm state and time // AlarmState is an enum type in class AlarmController that is in namespace controllers - // TODO Eve : Not sure if it can handle automatically am / pm format - alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; - lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); - lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); - if (alarmState) { - uint8_t alarmHours = alarmController.Hours(); - uint8_t alarmMinutes = alarmController.Minutes(); - lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); - } - else { - lv_label_set_text_static(labelAlarm, Symbols::none); + if (settingsController.GetInfineatShowAlarmStatus()) { + alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; + // sets the icon as bird or bed + lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); + //displays the time of the alarm or nothing if the alarm is not set + if (alarmState) { + uint8_t alarmHours = alarmController.Hours(); + uint8_t alarmMinutes = alarmController.Minutes(); + //handles the am pm format. + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + char ampmChar[3] = "AM"; + if (alarmHours == 0) { + alarmHours = 12; + } else if (alarmHours == 12) { + ampmChar[0]='P'; + } else if (alarmHours > 12) { + alarmHours = alarmHours - 12; + ampmChar[0]='P'; + } + lv_label_set_text(labelTimeAmPmAlarm, ampmChar); + lv_obj_set_style_local_text_font(labelTimeAmPmAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(labelTimeAmPmAlarm, labelAlarm, LV_ALIGN_OUT_TOP_RIGHT, 0, 0); + } + + lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); + + lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); + lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); + lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); + + } + else { + lv_label_set_text_static(labelAlarm, Symbols::none); + lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + lv_obj_align(alarmIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); + } } @@ -545,12 +602,12 @@ void WatchFaceMeow::SetBatteryLevel(uint8_t batteryPercent) { void WatchFaceMeow::ToggleBatteryIndicatorColor(bool showSideCover) { if (!showSideCover) { // make indicator and notification icon color white - lv_obj_set_style_local_image_recolor_opa(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_100); - lv_obj_set_style_local_image_recolor(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_obj_set_style_local_image_recolor_opa(logoCat, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_100); + lv_obj_set_style_local_image_recolor(logoCat, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); } else { - lv_obj_set_style_local_image_recolor_opa(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_0); + lv_obj_set_style_local_image_recolor_opa(logoCat, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_0); const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); diff --git a/src/displayapp/screens/todo.txt b/src/displayapp/screens/todo.txt new file mode 100644 index 00000000..4696108b --- /dev/null +++ b/src/displayapp/screens/todo.txt @@ -0,0 +1,4 @@ +compare infineat and meow .h files to make declaration correct +faire en sorte to keep bell and not bell for inifneat wf +check the orig files and the files in lib if they need to be in git + From e4d6014d88462b35d702a50e45c7b1edfa9cbd04 Mon Sep 17 00:00:00 2001 From: Eve C Date: Wed, 10 Jul 2024 10:34:37 +0200 Subject: [PATCH 090/101] updated WatchFaceMeow to match alignment, settings, AMPM compatibility of Infineat. also files that seemed to have been modified by submodules update. --- src/displayapp/screens/WatchFaceMeow.cpp | 10 +++++----- src/displayapp/screens/WatchFaceMeow.h | 8 ++++++-- src/libs/QCBOR | 2 +- src/libs/arduinoFFT | 2 +- src/libs/littlefs | 2 +- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/displayapp/screens/WatchFaceMeow.cpp b/src/displayapp/screens/WatchFaceMeow.cpp index ca2de2c6..25891c22 100644 --- a/src/displayapp/screens/WatchFaceMeow.cpp +++ b/src/displayapp/screens/WatchFaceMeow.cpp @@ -292,10 +292,10 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 10, 0); lv_label_set_text_static(stepValue, "0"); - stepIcon = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_label_set_text_static(stepIcon, Symbols::paw); - lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); + pawIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(pawIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_label_set_text_static(pawIcon, Symbols::paw); + lv_obj_align(pawIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); // Setting buttons @@ -582,7 +582,7 @@ void WatchFaceMeow::Refresh() { if (stepCount.IsUpdated()) { lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 10, 0); - lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); + lv_obj_align(pawIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); } if (!lv_obj_get_hidden(btnSettings)) { diff --git a/src/displayapp/screens/WatchFaceMeow.h b/src/displayapp/screens/WatchFaceMeow.h index 8978f5bf..1fdacec3 100644 --- a/src/displayapp/screens/WatchFaceMeow.h +++ b/src/displayapp/screens/WatchFaceMeow.h @@ -64,7 +64,7 @@ namespace Pinetime { lv_point_t lineBatteryPoints[2]; - lv_obj_t* logoPine; + lv_obj_t* logoCat; lv_obj_t* timeContainer; lv_obj_t* labelHour; @@ -74,18 +74,20 @@ namespace Pinetime { lv_obj_t* labelDate; lv_obj_t* bleIcon; lv_obj_t* labelAlarm; + lv_obj_t* labelTimeAmPmAlarm; lv_obj_t* alarmIcon; - lv_obj_t* stepIcon; lv_obj_t* pawIcon; lv_obj_t* stepValue; lv_obj_t* notificationIcon; lv_obj_t* btnClose; lv_obj_t* btnNextColor; lv_obj_t* btnToggleCover; + lv_obj_t* btnToggleAlarm; lv_obj_t* btnPrevColor; lv_obj_t* btnSettings; lv_obj_t* labelBtnSettings; lv_obj_t* lblToggle; + lv_obj_t* lblAlarm; lv_obj_t* lines[nLines]; @@ -100,6 +102,8 @@ namespace Pinetime { void SetBatteryLevel(uint8_t batteryPercent); void ToggleBatteryIndicatorColor(bool showSideCover); + void ToggleShowAlarmStatus(bool showAlarmStatus); + lv_task_t* taskRefresh; lv_font_t* font_teko = nullptr; lv_font_t* font_bebas = nullptr; diff --git a/src/libs/QCBOR b/src/libs/QCBOR index 56b17bf9..5d83b9b4 160000 --- a/src/libs/QCBOR +++ b/src/libs/QCBOR @@ -1 +1 @@ -Subproject commit 56b17bf9f74096774944bcac0829adcd887d391e +Subproject commit 5d83b9b47547b322bb50174fcc6afb74d8b4ae09 diff --git a/src/libs/arduinoFFT b/src/libs/arduinoFFT index 419d7b04..0da88512 160000 --- a/src/libs/arduinoFFT +++ b/src/libs/arduinoFFT @@ -1 +1 @@ -Subproject commit 419d7b044e56b87de8efbcf76f09c04759628fb4 +Subproject commit 0da88512f90b0c8c6c5d50127045ee4f745e343b diff --git a/src/libs/littlefs b/src/libs/littlefs index ead50807..d01280e6 160000 --- a/src/libs/littlefs +++ b/src/libs/littlefs @@ -1 +1 @@ -Subproject commit ead50807f1ca3fdf2da00b77a0ce02651ded2d13 +Subproject commit d01280e64934a09ba16cac60cf9d3a37e228bb66 From 2a762f62ad4f8e37baca906c9d5b6660d3549457 Mon Sep 17 00:00:00 2001 From: Eve C Date: Wed, 10 Jul 2024 11:48:09 +0200 Subject: [PATCH 091/101] On branch my-custom-infinitime modified: package-lock.json : ??? modified: package.json : ??? modified: src/displayapp/screens/AlarmIcon.cpp : put bell / not bell in this file to keep infineat as in the PR I submit modified: src/displayapp/screens/WatchFaceMeow.cpp : don't refer to alarmIcon.cpp to change the icon, instead do it in this file directly to have different icons modified: src/libs/QCBOR : reverted to match original repo's version (git submodule status : compare commit id in this repo and a freshly cloned repo) modified: src/libs/arduinoFFT : reverted modified: src/libs/littlefs : reverted --- package-lock.json | 30 ++++++++++++------------ package.json | 2 +- src/displayapp/screens/AlarmIcon.cpp | 4 ++-- src/displayapp/screens/WatchFaceMeow.cpp | 8 +++++-- src/libs/QCBOR | 2 +- src/libs/arduinoFFT | 2 +- src/libs/littlefs | 2 +- 7 files changed, 27 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index b8320c49..74bb2b90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,13 +5,13 @@ "packages": { "": { "dependencies": { - "lv_font_conv": "^1.5.2" + "lv_font_conv": "^1.5.3" } }, "node_modules/lv_font_conv": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/lv_font_conv/-/lv_font_conv-1.5.2.tgz", - "integrity": "sha512-0UapRSTkVP/pnB8Z4r2HDHx5p2dJx/xUG1+14u/WXo59mwuC7BahR+Bnx/66jKoDrG1wFQwn9ZzoyMxRHOD9bg==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/lv_font_conv/-/lv_font_conv-1.5.3.tgz", + "integrity": "sha512-0xJQThBOw2iptFccSXrKDIUTQAwr/2zhKjCI1lATIRgZo8uvYRTmenKafW9yTw6G0y5AyW00tqGpUtYuTuBIbQ==", "bundleDependencies": [ "argparse", "bit-buffer", @@ -24,10 +24,10 @@ "dependencies": { "argparse": "^2.0.0", "bit-buffer": "^0.2.5", - "debug": "^4.1.1", + "debug": "^4.3.3", "make-error": "^1.3.5", "mkdirp": "^1.0.4", - "opentype.js": "^1.1.0", + "opentype.js": "^1.3.4", "pngjs": "^6.0.0" }, "bin": { @@ -45,7 +45,7 @@ "license": "MIT" }, "node_modules/lv_font_conv/node_modules/debug": { - "version": "4.3.1", + "version": "4.3.4", "inBundle": true, "license": "MIT", "dependencies": { @@ -82,7 +82,7 @@ "license": "MIT" }, "node_modules/lv_font_conv/node_modules/opentype.js": { - "version": "1.3.3", + "version": "1.3.4", "inBundle": true, "license": "MIT", "dependencies": { @@ -117,16 +117,16 @@ }, "dependencies": { "lv_font_conv": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/lv_font_conv/-/lv_font_conv-1.5.2.tgz", - "integrity": "sha512-0UapRSTkVP/pnB8Z4r2HDHx5p2dJx/xUG1+14u/WXo59mwuC7BahR+Bnx/66jKoDrG1wFQwn9ZzoyMxRHOD9bg==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/lv_font_conv/-/lv_font_conv-1.5.3.tgz", + "integrity": "sha512-0xJQThBOw2iptFccSXrKDIUTQAwr/2zhKjCI1lATIRgZo8uvYRTmenKafW9yTw6G0y5AyW00tqGpUtYuTuBIbQ==", "requires": { "argparse": "^2.0.0", "bit-buffer": "^0.2.5", - "debug": "^4.1.1", + "debug": "^4.3.3", "make-error": "^1.3.5", "mkdirp": "^1.0.4", - "opentype.js": "^1.1.0", + "opentype.js": "^1.3.4", "pngjs": "^6.0.0" }, "dependencies": { @@ -139,7 +139,7 @@ "bundled": true }, "debug": { - "version": "4.3.1", + "version": "4.3.4", "bundled": true, "requires": { "ms": "2.1.2" @@ -158,7 +158,7 @@ "bundled": true }, "opentype.js": { - "version": "1.3.3", + "version": "1.3.4", "bundled": true, "requires": { "string.prototype.codepointat": "^0.2.1", diff --git a/package.json b/package.json index d339766c..6577fac5 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { "dependencies": { - "lv_font_conv": "^1.5.2" + "lv_font_conv": "^1.5.3" } } diff --git a/src/displayapp/screens/AlarmIcon.cpp b/src/displayapp/screens/AlarmIcon.cpp index 77018dea..b41c4967 100644 --- a/src/displayapp/screens/AlarmIcon.cpp +++ b/src/displayapp/screens/AlarmIcon.cpp @@ -4,8 +4,8 @@ using namespace Pinetime::Applications::Screens; const char* AlarmIcon::GetIcon(bool isSet) { if (isSet) { - return Symbols::bird; + return Symbols::bell; } - return Symbols::zzz; + return Symbols::notbell; } diff --git a/src/displayapp/screens/WatchFaceMeow.cpp b/src/displayapp/screens/WatchFaceMeow.cpp index 25891c22..5097988b 100644 --- a/src/displayapp/screens/WatchFaceMeow.cpp +++ b/src/displayapp/screens/WatchFaceMeow.cpp @@ -16,7 +16,7 @@ #include #include "displayapp/screens/Symbols.h" #include "displayapp/screens/BleIcon.h" -#include "displayapp/screens/AlarmIcon.h" +//#include "displayapp/screens/AlarmIcon.h" #include "components/settings/Settings.h" #include "components/battery/BatteryController.h" #include "components/ble/BleController.h" @@ -542,7 +542,11 @@ void WatchFaceMeow::Refresh() { if (settingsController.GetInfineatShowAlarmStatus()) { alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; // sets the icon as bird or bed - lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); + const char* alarmSymbol = Symbols::zzz; + if(alarmState) { + alarmSymbol = Symbols::bird; + } + lv_label_set_text_static(alarmIcon, alarmSymbol); //displays the time of the alarm or nothing if the alarm is not set if (alarmState) { uint8_t alarmHours = alarmController.Hours(); diff --git a/src/libs/QCBOR b/src/libs/QCBOR index 5d83b9b4..56b17bf9 160000 --- a/src/libs/QCBOR +++ b/src/libs/QCBOR @@ -1 +1 @@ -Subproject commit 5d83b9b47547b322bb50174fcc6afb74d8b4ae09 +Subproject commit 56b17bf9f74096774944bcac0829adcd887d391e diff --git a/src/libs/arduinoFFT b/src/libs/arduinoFFT index 0da88512..419d7b04 160000 --- a/src/libs/arduinoFFT +++ b/src/libs/arduinoFFT @@ -1 +1 @@ -Subproject commit 0da88512f90b0c8c6c5d50127045ee4f745e343b +Subproject commit 419d7b044e56b87de8efbcf76f09c04759628fb4 diff --git a/src/libs/littlefs b/src/libs/littlefs index d01280e6..ead50807 160000 --- a/src/libs/littlefs +++ b/src/libs/littlefs @@ -1 +1 @@ -Subproject commit d01280e64934a09ba16cac60cf9d3a37e228bb66 +Subproject commit ead50807f1ca3fdf2da00b77a0ce02651ded2d13 From 59f9a5eaff3513e0a3b37d74fcb938112c5df40f Mon Sep 17 00:00:00 2001 From: Eve C Date: Wed, 10 Jul 2024 17:20:52 +0200 Subject: [PATCH 092/101] Changes to be committed: modified: src/displayapp/screens/Calendar.cpp modified: src/libs/lv_conf.h week start on monday with LV_CALENDAR_WEEK_STARTS_MONDAY Changes not staged for commit: modified: src/libs/lvgl (modified content) --- src/displayapp/screens/Calendar.cpp | 4 ++-- src/libs/lv_conf.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/displayapp/screens/Calendar.cpp b/src/displayapp/screens/Calendar.cpp index 43643f6c..d51126c7 100644 --- a/src/displayapp/screens/Calendar.cpp +++ b/src/displayapp/screens/Calendar.cpp @@ -35,10 +35,10 @@ Calendar::Calendar(Controllers::DateTime& dateTimeController) : dateTimeControll // Set style of today's date lv_obj_set_style_local_text_color(calendar, LV_CALENDAR_PART_DATE, LV_STATE_FOCUSED, Colors::deepOrange); - + // Set style of inactive month's days lv_obj_set_style_local_text_color(calendar, LV_CALENDAR_PART_DATE, LV_STATE_DISABLED, Colors::gray); - + // Get today's date current.year = static_cast(dateTimeController.Year()); current.month = static_cast(dateTimeController.Month()); diff --git a/src/libs/lv_conf.h b/src/libs/lv_conf.h index c23647f2..c884482b 100644 --- a/src/libs/lv_conf.h +++ b/src/libs/lv_conf.h @@ -589,7 +589,7 @@ typedef void* lv_obj_user_data_t; /*Calendar (dependencies: -)*/ #define LV_USE_CALENDAR 1 #if LV_USE_CALENDAR -#define LV_CALENDAR_WEEK_STARTS_MONDAY 0 +#define LV_CALENDAR_WEEK_STARTS_MONDAY 1 #endif /*Canvas (dependencies: lv_img)*/ From 6ce316dc0641abd60de8417b80bccbd2e5418d5d Mon Sep 17 00:00:00 2001 From: Eve C Date: Mon, 15 Jul 2024 16:39:17 +0200 Subject: [PATCH 093/101] hide alarm depending on option status --- src/displayapp/screens/WatchFaceInfineat.cpp | 1 + src/displayapp/screens/WatchFaceMeow.cpp | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index a411a477..952688f6 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -259,6 +259,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, lv_obj_set_hidden(alarmIcon, true); lv_obj_set_hidden(labelTimeAmPmAlarm, true); } + stepValue = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); diff --git a/src/displayapp/screens/WatchFaceMeow.cpp b/src/displayapp/screens/WatchFaceMeow.cpp index 5097988b..9f4ae2b8 100644 --- a/src/displayapp/screens/WatchFaceMeow.cpp +++ b/src/displayapp/screens/WatchFaceMeow.cpp @@ -284,8 +284,16 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, // symbol alarmIcon = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(alarmIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_label_set_text_static(alarmIcon, Symbols::paw); + lv_label_set_text_static(alarmIcon, Symbols::zzz); lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); + + // don't show the icons jsut set if we don't show alarm status + if (!settingsController.GetInfineatShowAlarmStatus()) { + lv_obj_set_hidden(labelAlarm, true); + lv_obj_set_hidden(alarmIcon, true); + lv_obj_set_hidden(labelTimeAmPmAlarm, true); + } + stepValue = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); From 5e19e042c0b8307dc84719180bc2575ed239de3a Mon Sep 17 00:00:00 2001 From: Eve C Date: Tue, 16 Jul 2024 08:36:01 +0200 Subject: [PATCH 094/101] new file: doc/lvgl_align.png add ref for alignment names in lvgl modified: src/displayapp/apps/CMakeLists.txt uncomment wome watchfaces modified: src/displayapp/screens/WatchFaceCasioStyleG7710.cpp add alarm, overlaps with heartbeat modified: src/displayapp/screens/WatchFaceCasioStyleG7710.h new file: todo.log --- doc/lvgl_align.png | Bin 0 -> 42761 bytes src/displayapp/apps/CMakeLists.txt | 4 +- .../screens/WatchFaceCasioStyleG7710.cpp | 56 ++++++++++++++++++ .../screens/WatchFaceCasioStyleG7710.h | 7 +++ todo.log | 3 + 5 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 doc/lvgl_align.png create mode 100644 todo.log diff --git a/doc/lvgl_align.png b/doc/lvgl_align.png new file mode 100644 index 0000000000000000000000000000000000000000..189c573e2ebff463282f65fdea50170a4de793d0 GIT binary patch literal 42761 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 #include "displayapp/screens/BatteryIcon.h" #include "displayapp/screens/BleIcon.h" +#include "displayapp/screens/AlarmIcon.h" #include "displayapp/screens/NotificationIcon.h" #include "displayapp/screens/Symbols.h" #include "components/battery/BatteryController.h" #include "components/ble/BleController.h" +#include "components/alarm/AlarmController.h" #include "components/ble/NotificationManager.h" #include "components/heartrate/HeartRateController.h" #include "components/motion/MotionController.h" @@ -17,6 +19,7 @@ using namespace Pinetime::Applications::Screens; WatchFaceCasioStyleG7710::WatchFaceCasioStyleG7710(Controllers::DateTime& dateTimeController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, + Controllers::AlarmController& alarmController, Controllers::NotificationManager& notificatioManager, Controllers::Settings& settingsController, Controllers::HeartRateController& heartRateController, @@ -27,6 +30,7 @@ WatchFaceCasioStyleG7710::WatchFaceCasioStyleG7710(Controllers::DateTime& dateTi dateTimeController {dateTimeController}, batteryController {batteryController}, bleController {bleController}, + alarmController {alarmController}, notificatioManager {notificatioManager}, settingsController {settingsController}, heartRateController {heartRateController}, @@ -168,6 +172,23 @@ WatchFaceCasioStyleG7710::WatchFaceCasioStyleG7710(Controllers::DateTime& dateTi lv_label_set_text_static(stepIcon, Symbols::shoe); lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); + alarmIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(alarmIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); + lv_label_set_text_static(alarmIcon, Symbols::notbell); + lv_obj_align(alarmIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 5, -2); + + labelAlarm = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); +// lv_obj_set_style_local_text_font(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(labelAlarm, alarmIcon, LV_ALIGN_OUT_RIGHT_MID, 3, 0); + lv_label_set_text_static(labelAlarm, "00:00"); + + labelTimeAmPmAlarm = lv_label_create(lv_scr_act(), nullptr); +// lv_obj_set_style_local_text_font(labelTimeAmPmAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_label_set_text_static(labelTimeAmPmAlarm, ""); + lv_obj_set_style_local_text_color(labelTimeAmPmAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text); + lv_obj_align(labelTimeAmPmAlarm, labelAlarm, LV_ALIGN_OUT_RIGHT_MID, 3, 0); + taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); Refresh(); } @@ -310,6 +331,41 @@ void WatchFaceCasioStyleG7710::Refresh() { lv_obj_realign(stepValue); lv_obj_realign(stepIcon); } + alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; + // sets the icon as bell or barred bell + lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); + //displays the time of the alarm or nothing if the alarm is not set + if (alarmState) { + uint8_t alarmHours = alarmController.Hours(); + uint8_t alarmMinutes = alarmController.Minutes(); + //handles the am pm format. + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + char ampmChar[3] = "AM"; + if (alarmHours == 0) { + alarmHours = 12; + } else if (alarmHours == 12) { + ampmChar[0]='P'; + } else if (alarmHours > 12) { + alarmHours = alarmHours - 12; + ampmChar[0]='P'; + } + lv_label_set_text(labelTimeAmPmAlarm, ampmChar); +// lv_obj_set_style_local_text_font(labelTimeAmPmAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(labelTimeAmPmAlarm, labelAlarm, LV_ALIGN_OUT_RIGHT_MID, 3, 0); + } + + lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); + + lv_obj_align(alarmIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 5, -2); + lv_obj_align(labelAlarm, alarmIcon, LV_ALIGN_OUT_RIGHT_MID, 3, 0); + + } + else { + lv_label_set_text_static(labelAlarm, Symbols::none); + lv_obj_align(alarmIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 5, -2); + lv_obj_align(labelAlarm, alarmIcon, LV_ALIGN_OUT_RIGHT_MID, 3, 0); + } + } bool WatchFaceCasioStyleG7710::IsAvailable(Pinetime::Controllers::FS& filesystem) { diff --git a/src/displayapp/screens/WatchFaceCasioStyleG7710.h b/src/displayapp/screens/WatchFaceCasioStyleG7710.h index 0f46a692..7d429451 100644 --- a/src/displayapp/screens/WatchFaceCasioStyleG7710.h +++ b/src/displayapp/screens/WatchFaceCasioStyleG7710.h @@ -30,6 +30,7 @@ namespace Pinetime { WatchFaceCasioStyleG7710(Controllers::DateTime& dateTimeController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, + Controllers::AlarmController& alarmController, Controllers::NotificationManager& notificatioManager, Controllers::Settings& settingsController, Controllers::HeartRateController& heartRateController, @@ -46,6 +47,7 @@ namespace Pinetime { Utility::DirtyValue powerPresent {}; Utility::DirtyValue bleState {}; Utility::DirtyValue bleRadioEnabled {}; + bool alarmState {}; Utility::DirtyValue> currentDateTime {}; Utility::DirtyValue stepCount {}; Utility::DirtyValue heartbeat {}; @@ -76,6 +78,9 @@ namespace Pinetime { lv_obj_t* line_day_of_year; lv_obj_t* backgroundLabel; lv_obj_t* bleIcon; + lv_obj_t* labelAlarm; + lv_obj_t* labelTimeAmPmAlarm; + lv_obj_t* alarmIcon; lv_obj_t* batteryPlug; lv_obj_t* label_battery_value; lv_obj_t* heartbeatIcon; @@ -90,6 +95,7 @@ namespace Pinetime { Controllers::DateTime& dateTimeController; const Controllers::Battery& batteryController; const Controllers::Ble& bleController; + Controllers::AlarmController& alarmController; Controllers::NotificationManager& notificatioManager; Controllers::Settings& settingsController; Controllers::HeartRateController& heartRateController; @@ -111,6 +117,7 @@ namespace Pinetime { return new Screens::WatchFaceCasioStyleG7710(controllers.dateTimeController, controllers.batteryController, controllers.bleController, + controllers.alarmController, controllers.notificationManager, controllers.settingsController, controllers.heartRateController, diff --git a/todo.log b/todo.log new file mode 100644 index 00000000..e7d50d9f --- /dev/null +++ b/todo.log @@ -0,0 +1,3 @@ +replace alarm info by timer info when timer is ongoing +add a shortcut for a 1min timer on the right + From b7263ed7951491719b9611eb0f047c7f90e3b8d0 Mon Sep 17 00:00:00 2001 From: Eve C Date: Thu, 25 Jul 2024 17:27:10 +0200 Subject: [PATCH 095/101] start to add timer info on WatchFaceMeow --- src/displayapp/screens/WatchFaceMeow.cpp | 97 ++++++++++++++++-------- src/displayapp/screens/WatchFaceMeow.h | 7 ++ 2 files changed, 73 insertions(+), 31 deletions(-) diff --git a/src/displayapp/screens/WatchFaceMeow.cpp b/src/displayapp/screens/WatchFaceMeow.cpp index 9f4ae2b8..21d079c7 100644 --- a/src/displayapp/screens/WatchFaceMeow.cpp +++ b/src/displayapp/screens/WatchFaceMeow.cpp @@ -146,6 +146,7 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, Controllers::AlarmController& alarmController, + Controllers::Timer& timerController, Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::MotionController& motionController, @@ -155,6 +156,7 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, batteryController {batteryController}, bleController {bleController}, alarmController {alarmController}, + timerController {timerController}, notificationManager {notificationManager}, settingsController {settingsController}, motionController {motionController} { @@ -293,7 +295,27 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, lv_obj_set_hidden(alarmIcon, true); lv_obj_set_hidden(labelTimeAmPmAlarm, true); } + + // text + labelTimer = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(labelTimer, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_obj_set_style_local_text_font(labelTimer, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + //lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); + lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); + lv_label_set_text_static(labelTimer, "00:00"); + // symbol + timerIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(timerIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); + lv_label_set_text_static(timerIcon, Symbols::hourGlass); + lv_obj_align(timerIcon, labelTimer, LV_ALIGN_OUT_LEFT_MID, -3, 0); + + // don't show the icons jsut set if we don't show alarm status + if (!settingsController.GetInfineatShowAlarmStatus()) { + lv_obj_set_hidden(labelTimer, true); + lv_obj_set_hidden(timerIcon, true); + } + stepValue = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); @@ -549,38 +571,51 @@ void WatchFaceMeow::Refresh() { // AlarmState is an enum type in class AlarmController that is in namespace controllers if (settingsController.GetInfineatShowAlarmStatus()) { alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; - // sets the icon as bird or bed - const char* alarmSymbol = Symbols::zzz; - if(alarmState) { - alarmSymbol = Symbols::bird; - } - lv_label_set_text_static(alarmIcon, alarmSymbol); - //displays the time of the alarm or nothing if the alarm is not set - if (alarmState) { - uint8_t alarmHours = alarmController.Hours(); - uint8_t alarmMinutes = alarmController.Minutes(); - //handles the am pm format. - if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { - char ampmChar[3] = "AM"; - if (alarmHours == 0) { - alarmHours = 12; - } else if (alarmHours == 12) { - ampmChar[0]='P'; - } else if (alarmHours > 12) { - alarmHours = alarmHours - 12; - ampmChar[0]='P'; - } - lv_label_set_text(labelTimeAmPmAlarm, ampmChar); - lv_obj_set_style_local_text_font(labelTimeAmPmAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); - lv_obj_align(labelTimeAmPmAlarm, labelAlarm, LV_ALIGN_OUT_TOP_RIGHT, 0, 0); + timerRunning = Pinetime::Controllers::Timer::timerController.IsRunning() + if(!timerRunning) + // sets the icon as bird or bed + const char* alarmSymbol = Symbols::zzz; + if(alarmState) { + alarmSymbol = Symbols::bird; + } + lv_label_set_text_static(alarmIcon, alarmSymbol); + //displays the time of the alarm or nothing if the alarm is not set + if (alarmState) { + uint8_t alarmHours = alarmController.Hours(); + uint8_t alarmMinutes = alarmController.Minutes(); + //handles the am pm format. + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + char ampmChar[3] = "AM"; + if (alarmHours == 0) { + alarmHours = 12; + } else if (alarmHours == 12) { + ampmChar[0]='P'; + } else if (alarmHours > 12) { + alarmHours = alarmHours - 12; + ampmChar[0]='P'; + } + lv_label_set_text(labelTimeAmPmAlarm, ampmChar); + lv_obj_set_style_local_text_font(labelTimeAmPmAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); + lv_obj_align(labelTimeAmPmAlarm, labelAlarm, LV_ALIGN_OUT_TOP_RIGHT, 0, 0); + } + + lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); + + lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); + lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); + lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); + else{ //timer is running, display timer instead of alarm + const char* timerSymbol = Symbols::hourGlass; + lv_label_set_text_static(timerIcon, timerSymbol); + auto secondsRemaining = std::chrono::duration_cast(timer.GetTimeRemaining())/1000; + timerMinutes = secondsRemaining.count() / 60; + timerSeconds = secondsRemaining.count() % 60; + lv_label_set_text_fmt(labelTimer, "%02d:%02d", timerMinutes, timerSeconds); + + lv_obj_align(timerIcon, labelTimer, LV_ALIGN_OUT_LEFT_MID, -3, 0); + lv_obj_align(labelTimer, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); + lv_obj_align(labelTimer, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); } - - lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); - - lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); - lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); - lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); - } else { lv_label_set_text_static(labelAlarm, Symbols::none); diff --git a/src/displayapp/screens/WatchFaceMeow.h b/src/displayapp/screens/WatchFaceMeow.h index 1fdacec3..91d446c8 100644 --- a/src/displayapp/screens/WatchFaceMeow.h +++ b/src/displayapp/screens/WatchFaceMeow.h @@ -7,6 +7,7 @@ #include #include "displayapp/screens/Screen.h" #include "components/datetime/DateTimeController.h" +#include "displayapp/screens/Timer.h" #include "utility/DirtyValue.h" #include "displayapp/apps/Apps.h" @@ -29,6 +30,7 @@ namespace Pinetime { const Controllers::Battery& batteryController, const Controllers::Ble& bleController, Controllers::AlarmController& alarmController, + Controllers::Timer& timerController, Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::MotionController& motionController, @@ -55,6 +57,7 @@ namespace Pinetime { Utility::DirtyValue bleRadioEnabled {}; bool alarmState {}; Utility::DirtyValue> currentDateTime {}; + bool timerRunning {}; Utility::DirtyValue stepCount {}; Utility::DirtyValue notificationState {}; Utility::DirtyValue> currentDate; @@ -76,6 +79,8 @@ namespace Pinetime { lv_obj_t* labelAlarm; lv_obj_t* labelTimeAmPmAlarm; lv_obj_t* alarmIcon; + lv_obj_t* labelTimer; + lv_obj_t* timerIcon; lv_obj_t* pawIcon; lv_obj_t* stepValue; lv_obj_t* notificationIcon; @@ -95,6 +100,7 @@ namespace Pinetime { const Controllers::Battery& batteryController; const Controllers::Ble& bleController; Controllers::AlarmController& alarmController; + Controllers::Timer& timerController; Controllers::NotificationManager& notificationManager; Controllers::Settings& settingsController; Controllers::MotionController& motionController; @@ -120,6 +126,7 @@ namespace Pinetime { controllers.batteryController, controllers.bleController, controllers.alarmController, + controllers.timer, controllers.notificationManager, controllers.settingsController, controllers.motionController, From 9ff835bb8928f163e37bb78a7e5d6d34a696b44a Mon Sep 17 00:00:00 2001 From: Eve C Date: Wed, 2 Oct 2024 15:06:20 +0200 Subject: [PATCH 096/101] corrections --- .gitconfig | 18 + .gitignore | 4 - package.json | 5 - src/displayapp/UserApps.h | 1 - src/displayapp/screens/WatchFaceInfineat.cpp | 2 +- src/displayapp/screens/WatchFaceInfineat.h | 4 +- src/displayapp/screens/WatchFaceMeow.cpp | 683 ------------------ src/displayapp/screens/WatchFaceMeow.h | 141 ---- .../screens/settings/SettingWatchFace.h | 1 - 9 files changed, 20 insertions(+), 839 deletions(-) create mode 100644 .gitconfig delete mode 100644 package.json delete mode 100644 src/displayapp/screens/WatchFaceMeow.cpp delete mode 100644 src/displayapp/screens/WatchFaceMeow.h diff --git a/.gitconfig b/.gitconfig new file mode 100644 index 00000000..7e0aafbb --- /dev/null +++ b/.gitconfig @@ -0,0 +1,18 @@ +[core] + whitespace = blank-at-eol,blank-at-eof,space-before-tab + autocrlf = input +[apply] + whitespace = fix + +[diff] + tool = meld +[difftool] + prompt = false +[difftool "meld"] + cmd = meld "$LOCAL" "$REMOTE" +[merge] + tool = meld +[mergetool "meld"] + # Choose one of these two lines (not both!) explained below. + cmd = meld "$LOCAL" "$MERGED" "$REMOTE" --output "$MERGED" + cmd = meld "$LOCAL" "$BASE" "$REMOTE" --output "$MERGED" diff --git a/.gitignore b/.gitignore index edb1a2cb..4613b1da 100644 --- a/.gitignore +++ b/.gitignore @@ -11,11 +11,7 @@ cmake_install.cmake Makefile build tools -node_modules -# My stuff -#draft_pictures -node_modules # Resulting binary files *.a diff --git a/package.json b/package.json deleted file mode 100644 index 6577fac5..00000000 --- a/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "dependencies": { - "lv_font_conv": "^1.5.3" - } -} diff --git a/src/displayapp/UserApps.h b/src/displayapp/UserApps.h index a0e41f72..67bbfa7d 100644 --- a/src/displayapp/UserApps.h +++ b/src/displayapp/UserApps.h @@ -12,7 +12,6 @@ #include "displayapp/screens/WatchFaceAnalog.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" #include "displayapp/screens/WatchFaceInfineat.h" -#include "displayapp/screens/WatchFaceMeow.h" #include "displayapp/screens/WatchFacePineTimeStyle.h" #include "displayapp/screens/WatchFaceTerminal.h" diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index 20d0beb3..3223d5f1 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -253,7 +253,7 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, lv_label_set_text_static(alarmIcon, Symbols::notbell); lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); - // don't show the icons jsut set if we don't show alarm status + // don't show the icons just set if we don't show alarm status if (!settingsController.GetInfineatShowAlarmStatus()) { lv_obj_set_hidden(labelAlarm, true); lv_obj_set_hidden(alarmIcon, true); diff --git a/src/displayapp/screens/WatchFaceInfineat.h b/src/displayapp/screens/WatchFaceInfineat.h index 7ea134f2..9edb91b0 100644 --- a/src/displayapp/screens/WatchFaceInfineat.h +++ b/src/displayapp/screens/WatchFaceInfineat.h @@ -53,7 +53,7 @@ namespace Pinetime { Utility::DirtyValue isCharging {}; Utility::DirtyValue bleState {}; Utility::DirtyValue bleRadioEnabled {}; - bool alarmState {}; + Utility::DirtyValue isAlarmSet{}; Utility::DirtyValue> currentDateTime {}; Utility::DirtyValue stepCount {}; Utility::DirtyValue notificationState {}; @@ -102,8 +102,6 @@ namespace Pinetime { void SetBatteryLevel(uint8_t batteryPercent); void ToggleBatteryIndicatorColor(bool showSideCover); - void ToggleShowAlarmStatus(bool showAlarmStatus); - lv_task_t* taskRefresh; lv_font_t* font_teko = nullptr; lv_font_t* font_bebas = nullptr; diff --git a/src/displayapp/screens/WatchFaceMeow.cpp b/src/displayapp/screens/WatchFaceMeow.cpp deleted file mode 100644 index 21d079c7..00000000 --- a/src/displayapp/screens/WatchFaceMeow.cpp +++ /dev/null @@ -1,683 +0,0 @@ -/*********************************************************/ -/* - * I modified the watchface Infineat : - * - added alarm info on the screen - * - modified the colors - * - modified step count icon - * Except colors, modifications are at line 254 and 500 - */ -/*********************************************************/ - - - -#include "displayapp/screens/WatchFaceMeow.h" - -#include -#include -#include "displayapp/screens/Symbols.h" -#include "displayapp/screens/BleIcon.h" -//#include "displayapp/screens/AlarmIcon.h" -#include "components/settings/Settings.h" -#include "components/battery/BatteryController.h" -#include "components/ble/BleController.h" -#include "components/alarm/AlarmController.h" -#include "components/ble/NotificationManager.h" -#include "components/motion/MotionController.h" - -using namespace Pinetime::Applications::Screens; - -namespace { - void event_handler(lv_obj_t* obj, lv_event_t event) { - auto* screen = static_cast(obj->user_data); - screen->UpdateSelected(obj, event); - } - - enum class colors { - orange, - blue, - green, - rainbow, - vivid, - pink, - nordGreen, - }; - - constexpr int nColors = 7; // must match number of colors in InfineatColorsColors - - constexpr int nLines = WatchFaceMeow::nLines; - - constexpr std::array orangeColors = {LV_COLOR_MAKE(0xfd, 0x87, 0x2b), - LV_COLOR_MAKE(0xdb, 0x33, 0x16), - LV_COLOR_MAKE(0x6f, 0x10, 0x00), - LV_COLOR_MAKE(0xfd, 0x7a, 0x0a), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xe8, 0x51, 0x02), - LV_COLOR_MAKE(0xea, 0x1c, 0x00)}; - constexpr std::array blueColors = {LV_COLOR_MAKE(0xe7, 0xf8, 0xff), - LV_COLOR_MAKE(0x22, 0x32, 0xd0), - LV_COLOR_MAKE(0x18, 0x2a, 0x8b), - LV_COLOR_MAKE(0xe7, 0xf8, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0x59, 0x91, 0xff), - LV_COLOR_MAKE(0x16, 0x36, 0xff)}; - constexpr std::array greenColors = {LV_COLOR_MAKE(0xb8, 0xff, 0x9b), - LV_COLOR_MAKE(0x08, 0x86, 0x08), - LV_COLOR_MAKE(0x00, 0x4a, 0x00), - LV_COLOR_MAKE(0xb8, 0xff, 0x9b), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0x62, 0xd5, 0x15), - LV_COLOR_MAKE(0x00, 0x74, 0x00)}; - //added comments to say which color is which triangle - constexpr std::array rainbowColors = {LV_COLOR_MAKE(0x2d, 0xa4, 0x00), //green, small triangle on top - LV_COLOR_MAKE(0xac, 0x09, 0xc4), //purple, smalltriangle in the bottom half part on the clock display side - LV_COLOR_MAKE(0xfe, 0x03, 0x03), //red, the two small triangles above and below the battery - LV_COLOR_MAKE(0x0d, 0x57, 0xff), //blue, the small triangle at the bottom - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xe0, 0xb9, 0x00), //Yellow, large triangle on top left - LV_COLOR_MAKE(0xe8, 0x51, 0x02)}; //orange, large triangle on bottom left - -// Add new colors, still rainbow but more pastel like - constexpr std::array rainbowVividColors ={LV_COLOR_MAKE(0xa5, 0xeb, 0x64), - LV_COLOR_MAKE(0xfc, 0x42, 0xb5), - LV_COLOR_MAKE(0xe7, 0xc1, 0xff), - LV_COLOR_MAKE(0x11, 0xdf, 0xfa), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xec, 0x5d), - LV_COLOR_MAKE(0xff, 0x93, 0xaf)}; - - constexpr std::array pinkColors = {LV_COLOR_MAKE(0xff, 0xe5, 0xec), - LV_COLOR_MAKE(0xff, 0xb3, 0xc6), - LV_COLOR_MAKE(0xfb, 0x6f, 0x92), - LV_COLOR_MAKE(0xff, 0xe5, 0xec), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xc2, 0xd1), - LV_COLOR_MAKE(0xff, 0x8f, 0xab)}; - constexpr std::array nordGreenColors = {LV_COLOR_MAKE(0xd5, 0xf0, 0xe9), - LV_COLOR_MAKE(0x23, 0x83, 0x73), - LV_COLOR_MAKE(0x1d, 0x41, 0x3f), - LV_COLOR_MAKE(0xd5, 0xf0, 0xe9), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0x2f, 0xb8, 0xa2), - LV_COLOR_MAKE(0x11, 0x70, 0x5a)}; - - //define colors for texts and symbols - // gray is used for text symbols and time. I changed it to pink, because I can. - //static constexpr lv_color_t grayColor = LV_COLOR_MAKE(0x99, 0x99, 0x99); - static constexpr lv_color_t pinkColor = LV_COLOR_MAKE(0xfc, 0x42, 0xb5); - - constexpr const std::array* returnColor(colors color) { - if (color == colors::orange) { - return &orangeColors; - } - if (color == colors::blue) { - return &blueColors; - } - if (color == colors::green) { - return &greenColors; - } - if (color == colors::rainbow) { - return &rainbowColors; - } - if (color == colors::vivid) { - return &rainbowVividColors; - } - if (color == colors::pink) { - return &pinkColors; - } - return &nordGreenColors; - } -} - -WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, - const Controllers::Battery& batteryController, - const Controllers::Ble& bleController, - Controllers::AlarmController& alarmController, - Controllers::Timer& timerController, - Controllers::NotificationManager& notificationManager, - Controllers::Settings& settingsController, - Controllers::MotionController& motionController, - Controllers::FS& filesystem) - : currentDateTime {{}}, - dateTimeController {dateTimeController}, - batteryController {batteryController}, - bleController {bleController}, - alarmController {alarmController}, - timerController {timerController}, - notificationManager {notificationManager}, - settingsController {settingsController}, - motionController {motionController} { - lfs_file f = {}; - if (filesystem.FileOpen(&f, "/fonts/teko.bin", LFS_O_RDONLY) >= 0) { - filesystem.FileClose(&f); - font_teko = lv_font_load("F:/fonts/teko.bin"); - } - - if (filesystem.FileOpen(&f, "/fonts/bebas.bin", LFS_O_RDONLY) >= 0) { - filesystem.FileClose(&f); - font_bebas = lv_font_load("F:/fonts/bebas.bin"); - } - - // Side Cover - //paires de points pour chaque ligne - // les lignes sont pas les contours des triangles, elles sont des grosses bandes superposées - - static constexpr lv_point_t linePoints[nLines][2] = {{{30, 25}, {68, -8}}, //1 small triangle on top - {{26, 167}, {43, 216}}, //2 purple, smalltriangle in the bottom half part on the clock display side - {{27, 40}, {27, 196}},// 3 small triangles up above and below battery - {{12, 182}, {65, 249}}, //4 most bottom right triangle - {{17, 97}, {17, 147}}, // 5 left part of battery zone, overlapped after by the large triangles - {{16, 81}, {42, 127}}, //6 upper part of battery zone - {{16, 163}, {42, 118}}, //7 lower part of battery zone - {{-20, 124}, {25, -11}}, //8 large upper triangle - {{-29, 89}, {27, 254}}}; //9 large lower triangle - //largeur des bandes - static constexpr lv_style_int_t lineWidths[nLines] = {18, 15, 14, 22, 20, 18, 18, 52, 48}; - - const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); - for (int i = 0; i < nLines; i++) { - lines[i] = lv_line_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_line_width(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, lineWidths[i]); - lv_color_t color = (*colors)[i]; - lv_obj_set_style_local_line_color(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, color); - lv_line_set_points(lines[i], linePoints[i], 2); - } - - //Battery indicator - logoCat = lv_img_create(lv_scr_act(), nullptr); - lv_img_set_src(logoCat, "F:/images/cat_small.bin"); - lv_obj_set_pos(logoCat, 12, 108); - //adjust position for cat - lineBattery = lv_line_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_line_width(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 30); - lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); - lv_obj_set_style_local_line_opa(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 190); - lineBatteryPoints[0] = {27, 107};//27 = image x offset + image width / 2 - lineBatteryPoints[1] = {27, 108};// the line covering the image is initialized as 1 px high - lv_line_set_points(lineBattery, lineBatteryPoints, 2); - lv_obj_move_foreground(lineBattery); - - notificationIcon = lv_obj_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); - lv_obj_set_style_local_radius(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE); - lv_obj_set_size(notificationIcon, 13, 13); - lv_obj_set_hidden(notificationIcon, true); - - if (!settingsController.GetInfineatShowSideCover()) { - ToggleBatteryIndicatorColor(false); - for (auto& line : lines) { - lv_obj_set_hidden(line, true); - } - } - - timeContainer = lv_obj_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_bg_opa(timeContainer, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); - lv_obj_set_size(timeContainer, 185, 185); - lv_obj_align(timeContainer, lv_scr_act(), LV_ALIGN_CENTER, 0, -10); - - labelHour = lv_label_create(lv_scr_act(), nullptr); - lv_label_set_text_static(labelHour, "01"); - lv_obj_set_style_local_text_font(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); - lv_obj_set_style_local_text_color(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 0); - - labelMinutes = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_font(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas); - lv_obj_set_style_local_text_color(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_label_set_text_static(labelMinutes, "00"); - lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); - - labelTimeAmPm = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_font(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); - lv_obj_set_style_local_text_color(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - - lv_label_set_text_static(labelTimeAmPm, ""); - lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 15); - - dateContainer = lv_obj_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_bg_opa(dateContainer, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); - lv_obj_set_size(dateContainer, 60, 30); - lv_obj_align(dateContainer, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 5); - - labelDate = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_obj_set_style_local_text_font(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); - lv_obj_align(labelDate, dateContainer, LV_ALIGN_IN_TOP_MID, 0, 0); - lv_label_set_text_static(labelDate, "Mon 01"); - - bleIcon = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_label_set_text_static(bleIcon, Symbols::bluetooth); - lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); - - - // Based on existing code, I understand that items on the screen (date, bluteooth status..) - // are declared here with default states, and later below the state (date, ...) is assigned - // So I do the same to add the alarm status : I put a symbol that has a default value - // and a text that has a default value - - // text - labelAlarm = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_obj_set_style_local_text_font(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); - //lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); - lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); - lv_label_set_text_static(labelAlarm, "00:00"); - - labelTimeAmPmAlarm = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_font(labelTimeAmPmAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); - lv_label_set_text_static(labelTimeAmPmAlarm, ""); - lv_obj_set_style_local_text_color(labelTimeAmPmAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_obj_align(labelTimeAmPmAlarm, labelAlarm, LV_ALIGN_OUT_TOP_RIGHT, 0, 0); - - // symbol - alarmIcon = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(alarmIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_label_set_text_static(alarmIcon, Symbols::zzz); - lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); - - // don't show the icons jsut set if we don't show alarm status - if (!settingsController.GetInfineatShowAlarmStatus()) { - lv_obj_set_hidden(labelAlarm, true); - lv_obj_set_hidden(alarmIcon, true); - lv_obj_set_hidden(labelTimeAmPmAlarm, true); - } - - // text - labelTimer = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(labelTimer, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_obj_set_style_local_text_font(labelTimer, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); - //lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); - lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); - lv_label_set_text_static(labelTimer, "00:00"); - - // symbol - timerIcon = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(timerIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_label_set_text_static(timerIcon, Symbols::hourGlass); - lv_obj_align(timerIcon, labelTimer, LV_ALIGN_OUT_LEFT_MID, -3, 0); - - // don't show the icons jsut set if we don't show alarm status - if (!settingsController.GetInfineatShowAlarmStatus()) { - lv_obj_set_hidden(labelTimer, true); - lv_obj_set_hidden(timerIcon, true); - } - - stepValue = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); - lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 10, 0); - lv_label_set_text_static(stepValue, "0"); - - pawIcon = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(pawIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); - lv_label_set_text_static(pawIcon, Symbols::paw); - lv_obj_align(pawIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); - - - // Setting buttons - btnClose = lv_btn_create(lv_scr_act(), nullptr); - btnClose->user_data = this; - lv_obj_set_size(btnClose, 60, 60); - lv_obj_align(btnClose, lv_scr_act(), LV_ALIGN_CENTER, 0, -80); - lv_obj_set_style_local_bg_opa(btnClose, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); - lv_obj_t* lblClose = lv_label_create(btnClose, nullptr); - lv_label_set_text_static(lblClose, "X"); - lv_obj_set_event_cb(btnClose, event_handler); - lv_obj_set_hidden(btnClose, true); - - btnNextColor = lv_btn_create(lv_scr_act(), nullptr); - btnNextColor->user_data = this; - lv_obj_set_size(btnNextColor, 60, 60); - lv_obj_align(btnNextColor, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -15, 0); - lv_obj_set_style_local_bg_opa(btnNextColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); - lv_obj_t* lblNextColor = lv_label_create(btnNextColor, nullptr); - lv_label_set_text_static(lblNextColor, ">"); - lv_obj_set_event_cb(btnNextColor, event_handler); - lv_obj_set_hidden(btnNextColor, true); - - btnPrevColor = lv_btn_create(lv_scr_act(), nullptr); - btnPrevColor->user_data = this; - lv_obj_set_size(btnPrevColor, 60, 60); - lv_obj_align(btnPrevColor, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 15, 0); - lv_obj_set_style_local_bg_opa(btnPrevColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); - lv_obj_t* lblPrevColor = lv_label_create(btnPrevColor, nullptr); - lv_label_set_text_static(lblPrevColor, "<"); - lv_obj_set_event_cb(btnPrevColor, event_handler); - lv_obj_set_hidden(btnPrevColor, true); - - btnToggleCover = lv_btn_create(lv_scr_act(), nullptr); - btnToggleCover->user_data = this; - lv_obj_set_size(btnToggleCover, 60, 60); - lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); - lv_obj_set_style_local_bg_opa(btnToggleCover, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); - const char* labelToggle = settingsController.GetInfineatShowSideCover() ? "ON" : "OFF"; - lblToggle = lv_label_create(btnToggleCover, nullptr); - lv_label_set_text_static(lblToggle, labelToggle); - lv_obj_set_event_cb(btnToggleCover, event_handler); - lv_obj_set_hidden(btnToggleCover, true); - - btnToggleAlarm = lv_btn_create(lv_scr_act(), nullptr); - btnToggleAlarm->user_data = this; - lv_obj_set_size(btnToggleAlarm, 60, 60); - lv_obj_align(btnToggleAlarm, lv_scr_act(), LV_ALIGN_CENTER, 0, 80); - lv_obj_set_style_local_bg_opa(btnToggleAlarm, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); - const char* labelToggleAlarm = settingsController.GetInfineatShowAlarmStatus() ? Symbols::bell : Symbols::notbell; - lblAlarm = lv_label_create(btnToggleAlarm, nullptr); - lv_label_set_text_static(lblAlarm, labelToggleAlarm); - lv_obj_set_event_cb(btnToggleAlarm, event_handler); - lv_obj_set_hidden(btnToggleAlarm, true); - - // Button to access the settings - btnSettings = lv_btn_create(lv_scr_act(), nullptr); - btnSettings->user_data = this; - lv_obj_set_size(btnSettings, 150, 150); - lv_obj_align(btnSettings, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); - lv_obj_set_style_local_radius(btnSettings, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 30); - lv_obj_set_style_local_bg_opa(btnSettings, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70); - lv_obj_set_event_cb(btnSettings, event_handler); - labelBtnSettings = lv_label_create(btnSettings, nullptr); - lv_obj_set_style_local_text_font(labelBtnSettings, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48); - lv_label_set_text_static(labelBtnSettings, Symbols::settings); - lv_obj_set_hidden(btnSettings, true); - - taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); - Refresh(); -} - -WatchFaceMeow::~WatchFaceMeow() { - lv_task_del(taskRefresh); - - if (font_bebas != nullptr) { - lv_font_free(font_bebas); - } - if (font_teko != nullptr) { - lv_font_free(font_teko); - } - - lv_obj_clean(lv_scr_act()); -} - -bool WatchFaceMeow::OnTouchEvent(Pinetime::Applications::TouchEvents event) { - if ((event == Pinetime::Applications::TouchEvents::LongTap) && lv_obj_get_hidden(btnSettings)) { - lv_obj_set_hidden(btnSettings, false); - savedTick = lv_tick_get(); - return true; - } - // Prevent screen from sleeping when double tapping with settings on - if ((event == Pinetime::Applications::TouchEvents::DoubleTap) && !lv_obj_get_hidden(btnClose)) { - return true; - } - return false; -} - -void WatchFaceMeow::CloseMenu() { - settingsController.SaveSettings(); - lv_obj_set_hidden(btnClose, true); - lv_obj_set_hidden(btnNextColor, true); - lv_obj_set_hidden(btnPrevColor, true); - lv_obj_set_hidden(btnToggleCover, true); - lv_obj_set_hidden(btnToggleAlarm, true); -} - -bool WatchFaceMeow::OnButtonPushed() { - if (!lv_obj_get_hidden(btnClose)) { - CloseMenu(); - return true; - } - return false; -} - -void WatchFaceMeow::UpdateSelected(lv_obj_t* object, lv_event_t event) { - if (event == LV_EVENT_CLICKED) { - bool showSideCover = settingsController.GetInfineatShowSideCover(); - int colorIndex = settingsController.GetInfineatColorIndex(); - bool showAlarmStatus = settingsController.GetInfineatShowAlarmStatus(); - - if (object == btnSettings) { - lv_obj_set_hidden(btnSettings, true); - lv_obj_set_hidden(btnClose, false); - lv_obj_set_hidden(btnNextColor, !showSideCover); - lv_obj_set_hidden(btnPrevColor, !showSideCover); - lv_obj_set_hidden(btnToggleCover, false); - lv_obj_set_hidden(btnToggleAlarm, false); - } - if (object == btnClose) { - CloseMenu(); - } - if (object == btnToggleCover) { - settingsController.SetInfineatShowSideCover(!showSideCover); - ToggleBatteryIndicatorColor(!showSideCover); - for (auto& line : lines) { - lv_obj_set_hidden(line, showSideCover); - } - lv_obj_set_hidden(btnNextColor, showSideCover); - lv_obj_set_hidden(btnPrevColor, showSideCover); - const char* labelToggle = showSideCover ? "OFF" : "ON"; - lv_label_set_text_static(lblToggle, labelToggle); - } - - if (object == btnToggleAlarm) { - settingsController.SetInfineatShowAlarmStatus(!showAlarmStatus); - bool newShowAlarmStatus = settingsController.GetInfineatShowAlarmStatus(); - lv_obj_set_hidden(labelAlarm, !newShowAlarmStatus); - lv_obj_set_hidden(alarmIcon, !newShowAlarmStatus); - lv_obj_set_hidden(labelTimeAmPmAlarm, !newShowAlarmStatus); - const char* labelToggleAlarm = newShowAlarmStatus ? Symbols::bell : Symbols::notbell; - lv_label_set_text_static(lblAlarm, labelToggleAlarm); - } - - - if (object == btnNextColor) { - colorIndex = (colorIndex + 1) % nColors; - settingsController.SetInfineatColorIndex(colorIndex); - } - if (object == btnPrevColor) { - colorIndex -= 1; - if (colorIndex < 0) - colorIndex = nColors - 1; - settingsController.SetInfineatColorIndex(colorIndex); - } - if (object == btnNextColor || object == btnPrevColor) { - const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); - for (int i = 0; i < nLines; i++) { - lv_color_t color = (*colors)[i]; - lv_obj_set_style_local_line_color(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, color); - } - lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); - lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); - } - } -} - -void WatchFaceMeow::Refresh() { - notificationState = notificationManager.AreNewNotificationsAvailable(); - if (notificationState.IsUpdated()) { - lv_obj_set_hidden(notificationIcon, !notificationState.Get()); - lv_obj_align(notificationIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0); - } - - currentDateTime = std::chrono::time_point_cast(dateTimeController.CurrentDateTime()); - if (currentDateTime.IsUpdated()) { - uint8_t hour = dateTimeController.Hours(); - uint8_t minute = dateTimeController.Minutes(); - - if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { - char ampmChar[3] = "AM"; - if (hour == 0) { - hour = 12; - } else if (hour == 12) { - ampmChar[0] = 'P'; - } else if (hour > 12) { - hour = hour - 12; - ampmChar[0] = 'P'; - } - lv_label_set_text(labelTimeAmPm, ampmChar); - } - lv_label_set_text_fmt(labelHour, "%02d", hour); - lv_label_set_text_fmt(labelMinutes, "%02d", minute); - - if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { - lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 10); - lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 5); - lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); - } - - currentDate = std::chrono::time_point_cast(currentDateTime.Get()); - if (currentDate.IsUpdated()) { - uint8_t day = dateTimeController.Day(); - Controllers::DateTime::Days dayOfWeek = dateTimeController.DayOfWeek(); - lv_label_set_text_fmt(labelDate, "%s %02d", dateTimeController.DayOfWeekShortToStringLow(dayOfWeek), day); - lv_obj_realign(labelDate); - } - } - - batteryPercentRemaining = batteryController.PercentRemaining(); - isCharging = batteryController.IsCharging(); - if (batteryController.IsCharging()) { // Charging battery animation - chargingBatteryPercent += 1; - if (chargingBatteryPercent > 100) { - chargingBatteryPercent = batteryPercentRemaining.Get(); - } - SetBatteryLevel(chargingBatteryPercent); - } else if (isCharging.IsUpdated() || batteryPercentRemaining.IsUpdated()) { - chargingBatteryPercent = batteryPercentRemaining.Get(); - SetBatteryLevel(chargingBatteryPercent); - } - - bleState = bleController.IsConnected(); - bleRadioEnabled = bleController.IsRadioEnabled(); - if (bleState.IsUpdated()) { - //bleState.Get : in displayapp/widgets/StatusIcons.cpp: bleState = bleController.IsConnected(); - //dynamic icons have their definitions in displayApp/screens/BleIcon.h / cpp - lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get())); - lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 3); - } - - // Add alarm state and time - // AlarmState is an enum type in class AlarmController that is in namespace controllers - if (settingsController.GetInfineatShowAlarmStatus()) { - alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; - timerRunning = Pinetime::Controllers::Timer::timerController.IsRunning() - if(!timerRunning) - // sets the icon as bird or bed - const char* alarmSymbol = Symbols::zzz; - if(alarmState) { - alarmSymbol = Symbols::bird; - } - lv_label_set_text_static(alarmIcon, alarmSymbol); - //displays the time of the alarm or nothing if the alarm is not set - if (alarmState) { - uint8_t alarmHours = alarmController.Hours(); - uint8_t alarmMinutes = alarmController.Minutes(); - //handles the am pm format. - if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { - char ampmChar[3] = "AM"; - if (alarmHours == 0) { - alarmHours = 12; - } else if (alarmHours == 12) { - ampmChar[0]='P'; - } else if (alarmHours > 12) { - alarmHours = alarmHours - 12; - ampmChar[0]='P'; - } - lv_label_set_text(labelTimeAmPmAlarm, ampmChar); - lv_obj_set_style_local_text_font(labelTimeAmPmAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); - lv_obj_align(labelTimeAmPmAlarm, labelAlarm, LV_ALIGN_OUT_TOP_RIGHT, 0, 0); - } - - lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); - - lv_obj_align(alarmIcon, labelAlarm, LV_ALIGN_OUT_LEFT_MID, -3, 0); - lv_obj_align(labelAlarm, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); - lv_obj_align(labelAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); - else{ //timer is running, display timer instead of alarm - const char* timerSymbol = Symbols::hourGlass; - lv_label_set_text_static(timerIcon, timerSymbol); - auto secondsRemaining = std::chrono::duration_cast(timer.GetTimeRemaining())/1000; - timerMinutes = secondsRemaining.count() / 60; - timerSeconds = secondsRemaining.count() % 60; - lv_label_set_text_fmt(labelTimer, "%02d:%02d", timerMinutes, timerSeconds); - - lv_obj_align(timerIcon, labelTimer, LV_ALIGN_OUT_LEFT_MID, -3, 0); - lv_obj_align(labelTimer, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, -10, 0); - lv_obj_align(labelTimer, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); - } - } - else { - lv_label_set_text_static(labelAlarm, Symbols::none); - lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); - lv_obj_align(alarmIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -3, 0); - } - } - - - stepCount = motionController.NbSteps(); - if (stepCount.IsUpdated()) { - lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); - lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 10, 0); - lv_obj_align(pawIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); - } - - if (!lv_obj_get_hidden(btnSettings)) { - if ((savedTick > 0) && (lv_tick_get() - savedTick > 3000)) { - lv_obj_set_hidden(btnSettings, true); - savedTick = 0; - } - } -} - -void WatchFaceMeow::SetBatteryLevel(uint8_t batteryPercent) { - // starting point (y) + Pine64 logo height * (100 - batteryPercent) / 100^ - //the ligne grows, it covers the icon starting from the top - lineBatteryPoints[1] = {27, static_cast(107 + 31 * (100 - batteryPercent) / 100)}; - lv_line_set_points(lineBattery, lineBatteryPoints, 2); -} - -void WatchFaceMeow::ToggleBatteryIndicatorColor(bool showSideCover) { - if (!showSideCover) { // make indicator and notification icon color white - lv_obj_set_style_local_image_recolor_opa(logoCat, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_100); - lv_obj_set_style_local_image_recolor(logoCat, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); - lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); - lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); - } else { - lv_obj_set_style_local_image_recolor_opa(logoCat, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_0); - const std::array* colors = returnColor(static_cast(settingsController.GetInfineatColorIndex())); - lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); - lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]); - } -} - -bool WatchFaceMeow::IsAvailable(Pinetime::Controllers::FS& filesystem) { - lfs_file file = {}; - - if (filesystem.FileOpen(&file, "/fonts/teko.bin", LFS_O_RDONLY) < 0) { - return false; - } - - filesystem.FileClose(&file); - if (filesystem.FileOpen(&file, "/fonts/bebas.bin", LFS_O_RDONLY) < 0) { - return false; - } - - filesystem.FileClose(&file); - if (filesystem.FileOpen(&file, "/images/cat_small.bin", LFS_O_RDONLY) < 0) { - return false; - } - - filesystem.FileClose(&file); - return true; -} diff --git a/src/displayapp/screens/WatchFaceMeow.h b/src/displayapp/screens/WatchFaceMeow.h deleted file mode 100644 index 91d446c8..00000000 --- a/src/displayapp/screens/WatchFaceMeow.h +++ /dev/null @@ -1,141 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include "displayapp/screens/Screen.h" -#include "components/datetime/DateTimeController.h" -#include "displayapp/screens/Timer.h" -#include "utility/DirtyValue.h" -#include "displayapp/apps/Apps.h" - -namespace Pinetime { - namespace Controllers { - class Settings; - class Battery; - class Ble; - class NotificationManager; - class MotionController; - } - - namespace Applications { - namespace Screens { - - class WatchFaceMeow : public Screen { - public: - static constexpr int nLines = 9; - WatchFaceMeow(Controllers::DateTime& dateTimeController, - const Controllers::Battery& batteryController, - const Controllers::Ble& bleController, - Controllers::AlarmController& alarmController, - Controllers::Timer& timerController, - Controllers::NotificationManager& notificationManager, - Controllers::Settings& settingsController, - Controllers::MotionController& motionController, - Controllers::FS& fs); - - ~WatchFaceMeow() override; - - bool OnTouchEvent(TouchEvents event) override; - bool OnButtonPushed() override; - void UpdateSelected(lv_obj_t* object, lv_event_t event); - void CloseMenu(); - - void Refresh() override; - - static bool IsAvailable(Pinetime::Controllers::FS& filesystem); - - private: - uint32_t savedTick = 0; - uint8_t chargingBatteryPercent = 101; // not a mistake ;) - - Utility::DirtyValue batteryPercentRemaining {}; - Utility::DirtyValue isCharging {}; - Utility::DirtyValue bleState {}; - Utility::DirtyValue bleRadioEnabled {}; - bool alarmState {}; - Utility::DirtyValue> currentDateTime {}; - bool timerRunning {}; - Utility::DirtyValue stepCount {}; - Utility::DirtyValue notificationState {}; - Utility::DirtyValue> currentDate; - - // Lines making up the side cover - lv_obj_t* lineBattery; - - lv_point_t lineBatteryPoints[2]; - - lv_obj_t* logoCat; - - lv_obj_t* timeContainer; - lv_obj_t* labelHour; - lv_obj_t* labelMinutes; - lv_obj_t* labelTimeAmPm; - lv_obj_t* dateContainer; - lv_obj_t* labelDate; - lv_obj_t* bleIcon; - lv_obj_t* labelAlarm; - lv_obj_t* labelTimeAmPmAlarm; - lv_obj_t* alarmIcon; - lv_obj_t* labelTimer; - lv_obj_t* timerIcon; - lv_obj_t* pawIcon; - lv_obj_t* stepValue; - lv_obj_t* notificationIcon; - lv_obj_t* btnClose; - lv_obj_t* btnNextColor; - lv_obj_t* btnToggleCover; - lv_obj_t* btnToggleAlarm; - lv_obj_t* btnPrevColor; - lv_obj_t* btnSettings; - lv_obj_t* labelBtnSettings; - lv_obj_t* lblToggle; - lv_obj_t* lblAlarm; - - lv_obj_t* lines[nLines]; - - Controllers::DateTime& dateTimeController; - const Controllers::Battery& batteryController; - const Controllers::Ble& bleController; - Controllers::AlarmController& alarmController; - Controllers::Timer& timerController; - Controllers::NotificationManager& notificationManager; - Controllers::Settings& settingsController; - Controllers::MotionController& motionController; - - void SetBatteryLevel(uint8_t batteryPercent); - void ToggleBatteryIndicatorColor(bool showSideCover); - - void ToggleShowAlarmStatus(bool showAlarmStatus); - - lv_task_t* taskRefresh; - lv_font_t* font_teko = nullptr; - lv_font_t* font_bebas = nullptr; - }; - } - - template <> - struct WatchFaceTraits { - static constexpr WatchFace watchFace = WatchFace::Meow; - static constexpr const char* name = "Meow face"; - - static Screens::Screen* Create(AppControllers& controllers) { - return new Screens::WatchFaceMeow(controllers.dateTimeController, - controllers.batteryController, - controllers.bleController, - controllers.alarmController, - controllers.timer, - controllers.notificationManager, - controllers.settingsController, - controllers.motionController, - controllers.filesystem); - }; - - static bool IsAvailable(Pinetime::Controllers::FS& filesystem) { - return Screens::WatchFaceMeow::IsAvailable(filesystem); - } - }; - } -} diff --git a/src/displayapp/screens/settings/SettingWatchFace.h b/src/displayapp/screens/settings/SettingWatchFace.h index 0e74e2f9..2d0554fa 100644 --- a/src/displayapp/screens/settings/SettingWatchFace.h +++ b/src/displayapp/screens/settings/SettingWatchFace.h @@ -10,7 +10,6 @@ #include "displayapp/screens/Symbols.h" #include "displayapp/screens/CheckboxList.h" #include "displayapp/screens/WatchFaceInfineat.h" -#include "displayapp/screens/WatchFaceMeow.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" From a18190e37f7a759f3ec1607e348359c2f4e8cece Mon Sep 17 00:00:00 2001 From: Eve C Date: Wed, 2 Oct 2024 15:09:19 +0200 Subject: [PATCH 097/101] corrections --- src/displayapp/apps/CMakeLists.txt | 16 +++++++--------- src/displayapp/screens/todo.txt | 4 ---- 2 files changed, 7 insertions(+), 13 deletions(-) delete mode 100644 src/displayapp/screens/todo.txt diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index c7a188f2..d7858760 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -8,13 +8,12 @@ else () set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::HeartRate") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Music") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Paint") - #set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Paddle") + set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Paddle") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Twos") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Dice") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Metronome") - #set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Navigation") - #set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Weather") - set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Calendar") + set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Navigation") + set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Weather") #set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Motion") set(USERAPP_TYPES "${DEFAULT_USER_APP_TYPES}" CACHE STRING "List of user apps to build into the firmware") endif () @@ -22,11 +21,10 @@ endif () if(DEFINED ENABLE_WATCHFACES) set(WATCHFACE_TYPES ${ENABLE_WATCHFACES} CACHE STRING "List of watch faces to build into the firmware") else() - set(DEFAULT_WATCHFACE_TYPES "WatchFace::Meow") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Digital") - #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Analog") - #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::PineTimeStyle") - #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") + set(DEFAULT_WATCHFACE_TYPES "WatchFace::Digital") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Analog") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::PineTimeStyle") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware") diff --git a/src/displayapp/screens/todo.txt b/src/displayapp/screens/todo.txt deleted file mode 100644 index 4696108b..00000000 --- a/src/displayapp/screens/todo.txt +++ /dev/null @@ -1,4 +0,0 @@ -compare infineat and meow .h files to make declaration correct -faire en sorte to keep bell and not bell for inifneat wf -check the orig files and the files in lib if they need to be in git - From e2ca5393a693de12afc74c8ddfc96314aa1d00eb Mon Sep 17 00:00:00 2001 From: Eve C Date: Wed, 2 Oct 2024 15:35:10 +0200 Subject: [PATCH 098/101] corrections --- src/displayapp/DisplayApp.cpp | 1 - src/displayapp/screens/Calendar.cpp | 96 -------------------- src/displayapp/screens/Calendar.h | 59 ------------ src/displayapp/screens/WatchFaceInfineat.cpp | 6 +- 4 files changed, 3 insertions(+), 159 deletions(-) delete mode 100644 src/displayapp/screens/Calendar.cpp delete mode 100644 src/displayapp/screens/Calendar.h diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 17f268ff..3fd34b3a 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -19,7 +19,6 @@ #include "displayapp/screens/Metronome.h" #include "displayapp/screens/Music.h" #include "displayapp/screens/Navigation.h" -#include "displayapp/screens/Calendar.h" #include "displayapp/screens/Notifications.h" #include "displayapp/screens/SystemInfo.h" #include "displayapp/screens/Tile.h" diff --git a/src/displayapp/screens/Calendar.cpp b/src/displayapp/screens/Calendar.cpp deleted file mode 100644 index d51126c7..00000000 --- a/src/displayapp/screens/Calendar.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* Copyright (C) 2024 thnikk, Boteium, JustScott - - This file is part of InfiniTime. - - InfiniTime is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - InfiniTime is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#include "displayapp/screens/Calendar.h" -#include "components/datetime/DateTimeController.h" -#include "displayapp/InfiniTimeTheme.h" - -using namespace Pinetime::Applications::Screens; - -Calendar::Calendar(Controllers::DateTime& dateTimeController) : dateTimeController {dateTimeController} { - - // Create calendar object - calendar = lv_calendar_create(lv_scr_act(), NULL); - // Set size - lv_obj_set_size(calendar, LV_HOR_RES, LV_VER_RES); - // Set alignment - lv_obj_align(calendar, NULL, LV_ALIGN_IN_BOTTOM_MID, 0, -5); - // Disable clicks - lv_obj_set_click(calendar, false); - - // Set style of today's date - lv_obj_set_style_local_text_color(calendar, LV_CALENDAR_PART_DATE, LV_STATE_FOCUSED, Colors::deepOrange); - - // Set style of inactive month's days - lv_obj_set_style_local_text_color(calendar, LV_CALENDAR_PART_DATE, LV_STATE_DISABLED, Colors::gray); - - // Get today's date - current.year = static_cast(dateTimeController.Year()); - current.month = static_cast(dateTimeController.Month()); - current.day = static_cast(dateTimeController.Day()); - - // Set today's date - lv_calendar_set_today_date(calendar, ¤t); - lv_calendar_set_showed_date(calendar, ¤t); -} - -bool Calendar::OnTouchEvent(Pinetime::Applications::TouchEvents event) { - switch (event) { - case TouchEvents::SwipeLeft: { - if (current.month == 12) { - current.month = 1; - current.year++; - } else { - current.month++; - } - - lv_calendar_set_showed_date(calendar, ¤t); - return true; - } - case TouchEvents::SwipeRight: { - if (current.month == 1) { - current.month = 12; - current.year--; - } else { - current.month--; - } - - lv_calendar_set_showed_date(calendar, ¤t); - return true; - } - /* - case TouchEvents::SwipeUp: { - current.year++; - lv_calendar_set_showed_date(calendar, ¤t); - return true; - } - case TouchEvents::SwipeDown: { - current.year--; - lv_calendar_set_showed_date(calendar, ¤t); - return true; - } - */ - default: { - return false; - } - } -} - -Calendar::~Calendar() { - lv_obj_clean(lv_scr_act()); -} diff --git a/src/displayapp/screens/Calendar.h b/src/displayapp/screens/Calendar.h deleted file mode 100644 index bbdc8322..00000000 --- a/src/displayapp/screens/Calendar.h +++ /dev/null @@ -1,59 +0,0 @@ -/* Copyright (C) 2024 thnikk, Boteium, JustScott - - This file is part of InfiniTime. - - InfiniTime is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - InfiniTime is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#pragma once - -#include "displayapp/apps/Apps.h" -#include "displayapp/Controllers.h" -#include "displayapp/screens/Screen.h" -#include "components/datetime/DateTimeController.h" -#include - -#include "Symbols.h" - -namespace Pinetime { - namespace Controllers { - class Settings; - } - - namespace Applications { - namespace Screens { - class Calendar : public Screen { - public: - Calendar(Controllers::DateTime& dateTimeController); - ~Calendar() override; - - private: - bool OnTouchEvent(TouchEvents event); - Controllers::DateTime& dateTimeController; - lv_obj_t* calendar; - lv_calendar_date_t current; - }; - } - - template <> - struct AppTraits { - static constexpr Apps app = Apps::Calendar; - static constexpr const char* icon = Screens::Symbols::calendar; - - static Screens::Screen* Create(AppControllers& controllers) { - return new Screens::Calendar(controllers.dateTimeController); - }; - }; - } -} diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index 3223d5f1..19de1fba 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -509,11 +509,11 @@ void WatchFaceInfineat::Refresh() { } if (settingsController.GetInfineatShowAlarmStatus()) { - alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; + isAlarmSet = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; // sets the icon as bell or barred bell - lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); + lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(isAlarmSet.Get())); //displays the time of the alarm or nothing if the alarm is not set - if (alarmState) { + if (isAlarmSet.Get()) { uint8_t alarmHours = alarmController.Hours(); uint8_t alarmMinutes = alarmController.Minutes(); //handles the am pm format. From 39241889be5478eb14f4b25857abaaf0407aa2c1 Mon Sep 17 00:00:00 2001 From: Eve C Date: Wed, 2 Oct 2024 15:39:45 +0200 Subject: [PATCH 099/101] corrections --- src/components/settings/Settings.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 40fd2915..f65f95c1 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -155,6 +155,13 @@ namespace Pinetime { return settings.PTS.weatherEnable; }; + void SetInfineatShowAlarmStatus(bool show) { + if (show != settings.watchFaceInfineat.showAlarmStatus) { + settings.watchFaceInfineat.showAlarmStatus = show; + settingsChanged = true; + } + }; + void SetAppMenu(uint8_t menu) { appMenu = menu; }; From e16c8f8bab066fd3953bab0bc0413cee4de7d8b3 Mon Sep 17 00:00:00 2001 From: Eve C Date: Wed, 16 Oct 2024 17:01:53 +0200 Subject: [PATCH 100/101] correcitons --- src/components/settings/Settings.h | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index f65f95c1..e175ae50 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -124,6 +124,18 @@ namespace Pinetime { return settings.watchFaceInfineat.showSideCover; }; + void SetInfineatShowAlarmStatus(bool show) { + if (show != settings.watchFaceInfineat.showAlarmStatus) { + settings.watchFaceInfineat.showAlarmStatus = show; + settingsChanged = true; + } + }; + + bool GetInfineatShowAlarmStatus() const { + return settings.watchFaceInfineat.showAlarmStatus; + }; + + void SetInfineatColorIndex(int index) { if (index != settings.watchFaceInfineat.colorIndex) { settings.watchFaceInfineat.colorIndex = index; @@ -155,13 +167,6 @@ namespace Pinetime { return settings.PTS.weatherEnable; }; - void SetInfineatShowAlarmStatus(bool show) { - if (show != settings.watchFaceInfineat.showAlarmStatus) { - settings.watchFaceInfineat.showAlarmStatus = show; - settingsChanged = true; - } - }; - void SetAppMenu(uint8_t menu) { appMenu = menu; }; From de119b569e5efac60589b3aa67033a9d039bc77d Mon Sep 17 00:00:00 2001 From: Eve C Date: Wed, 16 Oct 2024 18:08:41 +0200 Subject: [PATCH 101/101] merged from main, fixed WatchFaceCasio after merge mess, fix my Infineat modifications according to new alarmcontroller --- .../screens/WatchFaceCasioStyleG7710.cpp | 37 ------------------- .../screens/WatchFaceCasioStyleG7710.h | 7 ---- src/displayapp/screens/WatchFaceInfineat.cpp | 2 +- 3 files changed, 1 insertion(+), 45 deletions(-) diff --git a/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp b/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp index ea0bc887..9fed69d7 100644 --- a/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp +++ b/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp @@ -331,43 +331,6 @@ void WatchFaceCasioStyleG7710::Refresh() { lv_obj_realign(stepValue); lv_obj_realign(stepIcon); } - alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; - // sets the icon as bell or barred bell - lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); - //displays the time of the alarm or nothing if the alarm is not set - if (alarmState) { - uint8_t alarmHours = alarmController.Hours(); - uint8_t alarmMinutes = alarmController.Minutes(); - //handles the am pm format. - if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { - char ampmChar[3] = "AM"; - if (alarmHours == 0) { - alarmHours = 12; - } else if (alarmHours == 12) { - ampmChar[0]='P'; - } else if (alarmHours > 12) { - alarmHours = alarmHours - 12; - ampmChar[0]='P'; - } - lv_label_set_text(labelTimeAmPmAlarm, ampmChar); -// lv_obj_set_style_local_text_font(labelTimeAmPmAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); - lv_obj_align(labelTimeAmPmAlarm, labelAlarm, LV_ALIGN_OUT_RIGHT_MID, 3, 0); - } - - lv_label_set_text_fmt(labelAlarm, "%02d:%02d", alarmHours, alarmMinutes); - - lv_obj_align(alarmIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 5, -2); - lv_obj_align(labelAlarm, alarmIcon, LV_ALIGN_OUT_RIGHT_MID, 3, 0); - - } - else { - lv_label_set_text_static(labelAlarm, Symbols::none); - lv_obj_align(alarmIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 5, -2); - lv_obj_align(labelAlarm, alarmIcon, LV_ALIGN_OUT_RIGHT_MID, 3, 0); - } - -} - bool WatchFaceCasioStyleG7710::IsAvailable(Pinetime::Controllers::FS& filesystem) { lfs_file file = {}; diff --git a/src/displayapp/screens/WatchFaceCasioStyleG7710.h b/src/displayapp/screens/WatchFaceCasioStyleG7710.h index 7d429451..0f46a692 100644 --- a/src/displayapp/screens/WatchFaceCasioStyleG7710.h +++ b/src/displayapp/screens/WatchFaceCasioStyleG7710.h @@ -30,7 +30,6 @@ namespace Pinetime { WatchFaceCasioStyleG7710(Controllers::DateTime& dateTimeController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, - Controllers::AlarmController& alarmController, Controllers::NotificationManager& notificatioManager, Controllers::Settings& settingsController, Controllers::HeartRateController& heartRateController, @@ -47,7 +46,6 @@ namespace Pinetime { Utility::DirtyValue powerPresent {}; Utility::DirtyValue bleState {}; Utility::DirtyValue bleRadioEnabled {}; - bool alarmState {}; Utility::DirtyValue> currentDateTime {}; Utility::DirtyValue stepCount {}; Utility::DirtyValue heartbeat {}; @@ -78,9 +76,6 @@ namespace Pinetime { lv_obj_t* line_day_of_year; lv_obj_t* backgroundLabel; lv_obj_t* bleIcon; - lv_obj_t* labelAlarm; - lv_obj_t* labelTimeAmPmAlarm; - lv_obj_t* alarmIcon; lv_obj_t* batteryPlug; lv_obj_t* label_battery_value; lv_obj_t* heartbeatIcon; @@ -95,7 +90,6 @@ namespace Pinetime { Controllers::DateTime& dateTimeController; const Controllers::Battery& batteryController; const Controllers::Ble& bleController; - Controllers::AlarmController& alarmController; Controllers::NotificationManager& notificatioManager; Controllers::Settings& settingsController; Controllers::HeartRateController& heartRateController; @@ -117,7 +111,6 @@ namespace Pinetime { return new Screens::WatchFaceCasioStyleG7710(controllers.dateTimeController, controllers.batteryController, controllers.bleController, - controllers.alarmController, controllers.notificationManager, controllers.settingsController, controllers.heartRateController, diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index 19de1fba..561899da 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -509,7 +509,7 @@ void WatchFaceInfineat::Refresh() { } if (settingsController.GetInfineatShowAlarmStatus()) { - isAlarmSet = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; + isAlarmSet = alarmController.IsEnabled()==true; // sets the icon as bell or barred bell lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(isAlarmSet.Get())); //displays the time of the alarm or nothing if the alarm is not set

EMlbg7(g+X>_|OZTePOT*gLOV#UFDL(t7WV;i|urO}qHM1Rd+Ve}DFe@g7qKru) zkwiY-GSvlCfap>UBr50!*Tb8o+NuKmZ_AJzW2)^X)D^i4Z< z-nQkY^vp31K$ip2Iohphlbj>gA?LAOaLqQ3qu#`MVmEBxx~plkoTJWZWSsaVr&Y;G zWH;^F)!wmo<;pD^H(s)3$8DGFymiY;Qgp1maP5L7oT{7>sD4nTn%?@l*Zua}o3?M* z#*tx=-MDenrW-fis8(*@v1{dS&RjDGpiK$A;mRvltZH7-zO|hL!*^_2`R+|SZr-}1 zW5o>oiq)%IuD`VT(p9UPSG8Qf>T(VyTy^=S&8yA1Z96vY=IFMSDImCU*V)QeqZ7DJ$`(9LY={@$j$tIo8R-LFX@-_ zo2%@(aR1Q)?AUdq$n;hYoI`&9iKSoGLiun1ef5T`->%ME)%?ysXkM}A;+Lv)T06y87h~2tHp=mXYn#-Djt&l+7t=rIypD64HgDi?wHwzq$r)<~ zb8(YcVbggxp4TCVviHm4x$`I<^eyEE6^tm2o4gk!!xp(?rx#eiu>3Mf4p_7hYH)DXaYAx=w+&v9tXhTer(G zmZx--MVU1h{S>A9K`!G>?q6lJ?2AT2~6h)X4Rx#_RuHKdHD6MjlX&L zu{U?zeBQD5U3Ad}kKFsg^WU~){h3Gp@Pwi;|eeteZm zZQrnC>*h@zyDoWm$Icyhn2E|cCEPh{n=GaYcg}|P_HA3`aMPVTrbU>@8@BGaUWNu@ z&S_^{klDnF?Hg~qVdu_WE6BNG>voQsU9swtRhQAdkoug9O<9sz+&LW1{5%MddL{vm zr`^SQwi|EWwCj2~>3J59rkE*}xc>G{x60IxgqK~q=CU=Hzw**Gub9r2g6m(D?K^VVCpbLQ-(>o?xIan8ZrKyW^SlYSjo!4CDR4vI=OmOUy_y6SWkJTvo3CWWCa<_y|UjWuK xljKaRu8{mFg_P{s*!aw>Gb>ohI`nwWbKr*RUUOBA=~6QEJfq}aIwZaN{{_LARs;Y5 literal 0 HcmV?d00001 diff --git a/src/Version.h.in b/src/Version.h.in index 0d6219c0..dfeccdbc 100644 --- a/src/Version.h.in +++ b/src/Version.h.in @@ -19,4 +19,4 @@ namespace Pinetime { static constexpr const char* commitHash = "@PROJECT_GIT_COMMIT_HASH@"; static constexpr const char* versionString = "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@"; }; -} \ No newline at end of file +} diff --git a/src/displayapp/screens/README.md b/src/displayapp/screens/README.md index 4f785766..f84a5fc6 100644 --- a/src/displayapp/screens/README.md +++ b/src/displayapp/screens/README.md @@ -1,8 +1,10 @@ - to edit with new watch face : +# Add a new watchface : +## Modify the following files with the names of your source files : + +- /src/displayapp/apps/Apps.h.in +- /src/components/settings/Settings.h +- /src/displayapp/UserApps.h +- /src/displayapp/apps/CMakeLists.txt +- CMakelists.txt -/src/displayapp/apps/Apps.h.in -/src/components/settings/Settings.h -/src/displayapp/UserApps.h -/src/displayapp/apps/CMakeLists.txt -CMakelists.txt diff --git a/src/displayapp/screens/WatchFaceMeow.cpp b/src/displayapp/screens/WatchFaceMeow.cpp index 5b1c94c3..0adff72c 100644 --- a/src/displayapp/screens/WatchFaceMeow.cpp +++ b/src/displayapp/screens/WatchFaceMeow.cpp @@ -1,3 +1,15 @@ +/*********************************************************/ +/* + * I modified the watchface Infineat : + * - added alarm info on the screen + * - modified the colors + * - modified step count icon + * Except colors, modifications are at line 254 and 500 + */ +/*********************************************************/ + + + #include "displayapp/screens/WatchFaceMeow.h" #include @@ -61,15 +73,18 @@ namespace { LV_COLOR_MAKE(0xff, 0xff, 0xff), LV_COLOR_MAKE(0x62, 0xd5, 0x15), LV_COLOR_MAKE(0x00, 0x74, 0x00)}; - constexpr std::array rainbowColors = {LV_COLOR_MAKE(0x2d, 0xa4, 0x00), //vert petit triangle le plus haut - LV_COLOR_MAKE(0xac, 0x09, 0xc4), //purple petit triangle bas côté horloge - LV_COLOR_MAKE(0xfe, 0x03, 0x03), //rouge 2 petits triangles à côté de batterie - LV_COLOR_MAKE(0x0d, 0x57, 0xff), //bleu petit triangle le plus bas + //added comments to say which color is which triangle + constexpr std::array rainbowColors = {LV_COLOR_MAKE(0x2d, 0xa4, 0x00), //green, small triangle on top + LV_COLOR_MAKE(0xac, 0x09, 0xc4), //purple, smalltriangle in the bottom half part on the clock display side + LV_COLOR_MAKE(0xfe, 0x03, 0x03), //red, the two small triangles above and below the battery + LV_COLOR_MAKE(0x0d, 0x57, 0xff), //blue, the small triangle at the bottom LV_COLOR_MAKE(0xff, 0xff, 0xff), LV_COLOR_MAKE(0xff, 0xff, 0xff), LV_COLOR_MAKE(0xff, 0xff, 0xff), - LV_COLOR_MAKE(0xe0, 0xb9, 0x00), //jaune gd triangle haut - LV_COLOR_MAKE(0xe8, 0x51, 0x02)}; //orange gd triangle bas + LV_COLOR_MAKE(0xe0, 0xb9, 0x00), //Yellow, large triangle on top left + LV_COLOR_MAKE(0xe8, 0x51, 0x02)}; //orange, large triangle on bottom left + +// Add new colors, still rainbow but more pastel like constexpr std::array rainbowVividColors ={LV_COLOR_MAKE(0xa5, 0xeb, 0x64), LV_COLOR_MAKE(0xfc, 0x42, 0xb5), LV_COLOR_MAKE(0xe7, 0xc1, 0xff), @@ -100,8 +115,8 @@ namespace { LV_COLOR_MAKE(0x11, 0x70, 0x5a)}; //define colors for texts and symbols + // gray is used for text symbols and time. I changed it to pink, because I can. //static constexpr lv_color_t grayColor = LV_COLOR_MAKE(0x99, 0x99, 0x99); - static constexpr lv_color_t grayColor = LV_COLOR_MAKE(0x99, 0x99, 0x99); static constexpr lv_color_t pinkColor = LV_COLOR_MAKE(0xfc, 0x42, 0xb5); constexpr const std::array* returnColor(colors color) { @@ -243,12 +258,16 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_TOP_MID, 0, -10); - //version par defaut de l'icône avant qu'il aie regardé le statut de l'alarme ? + // Based on existing code, I understand that items on the screen (date, bluteooth status..) + // are declared here with default states, and later below the state (date, ...) is assigned + // So I do the same to add the alarm status : I put a symbol that has a default value + // and a text that has a default value + // symbol alarmIcon = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(alarmIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_label_set_text_static(alarmIcon, Symbols::paw); lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); - //version par defaut de l'icône avant qu'il aie regardé le statut de l'alarme ? + // text labelAlarm = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, pinkColor); lv_obj_set_style_local_text_font(labelAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); @@ -481,8 +500,9 @@ void WatchFaceMeow::Refresh() { lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_TOP_MID, 0, -10); } - //Add alarm state and time + // Add alarm state and time // AlarmState is an enum type in class AlarmController that is in namespace controllers + // Not sure if it can handle automatically am / pm format (TODO) alarmState = alarmController.State()==Pinetime::Controllers::AlarmController::AlarmState::Set; lv_label_set_text_static(alarmIcon, AlarmIcon::GetIcon(alarmState)); lv_obj_align(alarmIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); @@ -495,14 +515,6 @@ void WatchFaceMeow::Refresh() { lv_label_set_text_static(labelAlarm, Symbols::none); } - //lv_label_set_text_fmt(labelMinutes, "%02d", minute); -/* - if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { - lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 5); - lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); - } - */ - stepCount = motionController.NbSteps(); if (stepCount.IsUpdated()) { From d2fab8d99a54cf3f92633cfa8676078077cb590f Mon Sep 17 00:00:00 2001 From: ecarlett Date: Mon, 27 May 2024 14:21:05 +0200 Subject: [PATCH 016/101] Add pictures in readme --- README.md | 4 ++++ doc/ui/meow_alarmnotset.png | Bin 0 -> 5721 bytes doc/ui/meow_alarmset.png | Bin 0 -> 6100 bytes 3 files changed, 4 insertions(+) create mode 100644 doc/ui/meow_alarmnotset.png create mode 100644 doc/ui/meow_alarmset.png diff --git a/README.md b/README.md index b58821ef..287c12c8 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,10 @@ Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devic - I stored the compile commands in scripts compile.sh to run from InfiniTime/ folder, and make_pine_mcu.sh to build the image must be run from InfiniTime/build/ (compile.sh copies make_pine_mcu.sh to build/ - The file to flash to the pinetime is InfiniTime/build/pinetime-mcuboot-app-dfu-1.14.0.zip : I didn't change the version compared to the one I downloaded from [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime) so make sure not to keep keep a copy of it +Here are pictures with and without alarm set : + +![Meow alarm set](doc/ui/meow_alarmset.png "Meow WatchFace, alarm set") ![Meow alarm not set](doc/ui/meow_alarmnotset.png "Meow WatchFace, alarm not set") + ## New to InfiniTime? - [Getting started with InfiniTime](doc/gettingStarted/gettingStarted-1.0.md) diff --git a/doc/ui/meow_alarmnotset.png b/doc/ui/meow_alarmnotset.png new file mode 100644 index 0000000000000000000000000000000000000000..ac11e47e6c88f1571c9a0648c67a6528fc01eb11 GIT binary patch literal 5721 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 O444?2BfsmrKKwtJMjBuM literal 0 HcmV?d00001 diff --git a/doc/ui/meow_alarmset.png b/doc/ui/meow_alarmset.png new file mode 100644 index 0000000000000000000000000000000000000000..e0b4b3f787e9c0f363ef0747f25c7ea53c079eff GIT binary patch literal 6100 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 literal 0 HcmV?d00001 From ef59bed954710b6a50af376788df8de60a45510a Mon Sep 17 00:00:00 2001 From: ecarlett Date: Mon, 27 May 2024 17:15:02 +0200 Subject: [PATCH 017/101] start cat icon --- draft_pictures/cat.png | Bin 0 -> 10018 bytes draft_pictures/cat.xcf | Bin 0 -> 102679 bytes src/displayapp/screens/WatchFaceMeow.cpp | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 draft_pictures/cat.png create mode 100644 draft_pictures/cat.xcf diff --git a/draft_pictures/cat.png b/draft_pictures/cat.png new file mode 100644 index 0000000000000000000000000000000000000000..da3e6f725f415b34f1118bce1ca79c88ab6e762d GIT binary patch literal 10018 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 M4CC(}%pMp22X3;B`v3p{ literal 0 HcmV?d00001 diff --git a/draft_pictures/cat.xcf b/draft_pictures/cat.xcf new file mode 100644 index 0000000000000000000000000000000000000000..27de1c78bbd4412c0d542b41092f9b4a6e8823f6 GIT binary patch literal 102679 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 Date: Wed, 29 May 2024 12:56:22 +0200 Subject: [PATCH 018/101] test --- draft_pictures/cat.xcf | Bin 102679 -> 97057 bytes src/displayapp/screens/WatchFaceMeow.cpp | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/draft_pictures/cat.xcf b/draft_pictures/cat.xcf index 27de1c78bbd4412c0d542b41092f9b4a6e8823f6..f1df51f8ca87820d122e937e168457535113c542 100644 GIT binary patch delta 954 zcmXxiO-K}B7zgn8aZw5p(#?;hAXgy|6$ov*cnEftD7x7}S2xNa$ilFNaCQX8#X}&Q zm9M&`#`-~qnpjQIVTlf&Jb1_m5(L4V3fCtgt`TrZ3`OW`%=VjjanMq9KjBjVZ ztP)R$at>V$c@<+(k2ksPYf>cnmrx8{p|{dQMLL<)8tOwOFhO6#;8}R_DQpP9#tj&9 zQ8z88r)jO-;;J|!1$Ny3-7AsIQgnCI0nZf1HRtJL%qP-M6kA*tWf1SFgl$^w=1dl}dZgUs8 z^B^ruZ@thGQzM%RIzp3ll0K)ibe`h7i208B|NA~LFVH3Wk$%otr^H-g0bLUl%@^}G y>woDQU8h@gC(&ohQC%L+Ka#7ImHu&^GjC4q?O9p%NV(b@c57GYaN^%Xsn`eMLzuDv delta 6473 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 + 32 * (100 - batteryPercent) / 100)}; lv_line_set_points(lineBattery, lineBatteryPoints, 2); } From b7a353cfb62323203f1a524345420f631d8bad40 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 12:58:38 +0200 Subject: [PATCH 019/101] modified: .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 81e49ae0..13a8b796 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,9 @@ Makefile build tools +# My stuff +#draft_pictures + # Resulting binary files *.a *.so From b8f7b65d84764d01e933af659af5f733bde6ddc9 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 13:15:28 +0200 Subject: [PATCH 020/101] copy :( files from gitlab repo interactive rebase in progress; onto b7a353cf Last command done (1 command done): pick 504f5774 copy :( files from gitlab repo Next commands to do (2 remaining commands): pick 9d04c32f recovering manually changes to original Infineat pick 70f9025f now I have the Meow Watchface AND the Infineat Watchface with alarm display You are currently rebasing branch 'my-custom-infinitime' on 'b7a353cf'. Changes to be committed: modified: src/CMakeLists.txt modified: src/displayapp/apps/Apps.h.in modified: src/displayapp/apps/CMakeLists.txt modified: src/displayapp/screens/Symbols.h --- src/CMakeLists.txt | 1 + src/displayapp/apps/Apps.h.in | 3 +++ src/displayapp/apps/CMakeLists.txt | 7 +++++++ src/displayapp/screens/Symbols.h | 13 +++++++++++++ 4 files changed, 24 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9a579eff..3344309e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -428,6 +428,7 @@ list(APPEND SOURCE_FILES displayapp/screens/WatchFaceAnalog.cpp displayapp/screens/WatchFaceDigital.cpp displayapp/screens/WatchFaceInfineat.cpp + displayapp/screens/WatchFaceMeow.cpp displayapp/screens/WatchFaceTerminal.cpp displayapp/screens/WatchFacePineTimeStyle.cpp displayapp/screens/WatchFaceInfineatColors.cpp diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index e12a1ab9..43fc3cbc 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -51,7 +51,10 @@ namespace Pinetime { PineTimeStyle, Terminal, Infineat, +<<<<<<< HEAD InfineatColors, +======= +>>>>>>> 504f5774 (copy :( files from gitlab repo) Meow, CasioStyleG7710, }; diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index ae6b1a46..e02bb554 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -24,11 +24,18 @@ else() set(DEFAULT_WATCHFACE_TYPES "WatchFace::Digital") #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Analog") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::PineTimeStyle") +<<<<<<< HEAD #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::InfineatColors") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Meow") #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") +======= + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Meow") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") +>>>>>>> 504f5774 (copy :( files from gitlab repo) set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware") endif() diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index d392d72a..b95da840 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -10,11 +10,19 @@ namespace Pinetime { static constexpr const char* bluetooth = "\xEF\x8A\x94"; static constexpr const char* plug = "\xEF\x87\xA6"; static constexpr const char* shoe = "\xEF\x95\x8B"; +<<<<<<< HEAD static constexpr const char* paw = "\xEF\x86\xB0"; static constexpr const char* clock = "\xEF\x80\x97"; static constexpr const char* bell = "\xEF\x83\xB3"; static constexpr const char* notbell = "\xEF\x87\xB6"; static constexpr const char* info = "\xEF\x84\xA9"; +======= + static constexpr const char* paw = "\xEF\x86\xB0"; + static constexpr const char* clock = "\xEF\x80\x97"; + static constexpr const char* bell = "\xEF\x83\xB3"; + static constexpr const char* notbell = "\xEF\x87\xB6"; + static constexpr const char* info = "\xEF\x84\xA9"; +>>>>>>> 504f5774 (copy :( files from gitlab repo) static constexpr const char* list = "\xEF\x80\xBA"; static constexpr const char* sun = "\xEF\x86\x85"; static constexpr const char* check = "\xEF\x95\xA0"; @@ -41,8 +49,13 @@ namespace Pinetime { static constexpr const char* eye = "\xEF\x81\xAE"; static constexpr const char* home = "\xEF\x80\x95"; static constexpr const char* sleep = "\xEE\xBD\x84"; +<<<<<<< HEAD static constexpr const char* bird = "\xEF\x92\xBA"; static constexpr const char* zzz = "\xEF\x88\xB6"; +======= + static constexpr const char* bird = "\xEF\x92\xBA"; + static constexpr const char* zzz = "\xEF\x88\xB6"; +>>>>>>> 504f5774 (copy :( files from gitlab repo) // fontawesome_weathericons.c // static constexpr const char* sun = "\xEF\x86\x85"; From 055b75d55daf78271f4ef56b22c9fd627a8c9ab8 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 15:22:16 +0200 Subject: [PATCH 021/101] test combiner tout --- src/CMakeLists.txt | 4 ++++ src/components/settings/Settings.h | 10 ++++++++++ src/displayapp/UserApps.h | 3 +++ src/displayapp/fonts/fonts.json | 4 ++++ src/displayapp/screens/AlarmIcon.cpp | 6 ++++++ src/displayapp/screens/Symbols.h | 13 +++++++++++++ src/displayapp/screens/WatchFaceInfineat.cpp | 4 ++++ src/displayapp/screens/WatchFaceMeow.cpp | 2 +- src/displayapp/screens/settings/SettingWatchFace.h | 3 +++ 9 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3344309e..5fa13e0e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -371,11 +371,15 @@ list(APPEND SOURCE_FILES displayapp/screens/StopWatch.cpp displayapp/screens/BatteryIcon.cpp displayapp/screens/BleIcon.cpp +<<<<<<< HEAD <<<<<<< HEAD displayapp/screens/AlarmIcon.cpp ======= displayapp/screens/AlarmIcon.cpp >>>>>>> f5dfbc44 (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) +======= + displayapp/screens/AlarmIcon.cpp +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) displayapp/screens/NotificationIcon.cpp displayapp/screens/SystemInfo.cpp displayapp/screens/Label.cpp diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 16dcdfd2..57fedbcf 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -55,12 +55,20 @@ namespace Pinetime { bool showSideCover = true; int colorIndex = 0; }; +<<<<<<< HEAD +======= + +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) struct WatchFaceMeow { bool showSideCover = true; int colorIndex = 0; }; +<<<<<<< HEAD +======= + +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) Settings(Pinetime::Controllers::FS& fs); Settings(const Settings&) = delete; @@ -328,6 +336,8 @@ namespace Pinetime { WatchFaceInfineatColors watchFaceInfineatColors; WatchFaceMeow watchFaceMeow; + WatchFaceMeow watchFaceMeow; + std::bitset<5> wakeUpMode {0}; uint16_t shakeWakeThreshold = 150; diff --git a/src/displayapp/UserApps.h b/src/displayapp/UserApps.h index f76af024..f4fb97ba 100644 --- a/src/displayapp/UserApps.h +++ b/src/displayapp/UserApps.h @@ -12,7 +12,10 @@ #include "displayapp/screens/WatchFaceAnalog.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" #include "displayapp/screens/WatchFaceInfineat.h" +<<<<<<< HEAD #include "displayapp/screens/WatchFaceInfineatColors.h" +======= +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) #include "displayapp/screens/WatchFaceMeow.h" #include "displayapp/screens/WatchFacePineTimeStyle.h" #include "displayapp/screens/WatchFaceTerminal.h" diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json index 2a9eea43..11dcd7b3 100644 --- a/src/displayapp/fonts/fonts.json +++ b/src/displayapp/fonts/fonts.json @@ -7,11 +7,15 @@ }, { "file": "FontAwesome5-Solid+Brands+Regular.woff", +<<<<<<< HEAD <<<<<<< HEAD "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf0f3, 0xf1f6" ======= "range": "0xf294, 0xf242, 0xf54b, 0xf1b0, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf4ba, 0xf236" >>>>>>> f5dfbc44 (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) +======= + "range": "0xf294, 0xf242, 0xf54b, 0xf1b0, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf4ba, 0xf236" +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) } ], "bpp": 1, diff --git a/src/displayapp/screens/AlarmIcon.cpp b/src/displayapp/screens/AlarmIcon.cpp index 0ea1a8cb..4877adb8 100644 --- a/src/displayapp/screens/AlarmIcon.cpp +++ b/src/displayapp/screens/AlarmIcon.cpp @@ -4,15 +4,21 @@ using namespace Pinetime::Applications::Screens; const char* AlarmIcon::GetIcon(bool isSet) { if (isSet) { +<<<<<<< HEAD <<<<<<< HEAD return Symbols::bell; } return Symbols::notbell; ======= +======= +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) return Symbols::bird; } return Symbols::zzz; +<<<<<<< HEAD >>>>>>> f5dfbc44 (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) +======= +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) } diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index b95da840..a68d45d0 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -10,6 +10,7 @@ namespace Pinetime { static constexpr const char* bluetooth = "\xEF\x8A\x94"; static constexpr const char* plug = "\xEF\x87\xA6"; static constexpr const char* shoe = "\xEF\x95\x8B"; +<<<<<<< HEAD <<<<<<< HEAD static constexpr const char* paw = "\xEF\x86\xB0"; static constexpr const char* clock = "\xEF\x80\x97"; @@ -23,6 +24,13 @@ namespace Pinetime { static constexpr const char* notbell = "\xEF\x87\xB6"; static constexpr const char* info = "\xEF\x84\xA9"; >>>>>>> 504f5774 (copy :( files from gitlab repo) +======= + static constexpr const char* paw = "\xEF\x86\xB0"; + static constexpr const char* clock = "\xEF\x80\x97"; + static constexpr const char* bell = "\xEF\x83\xB3"; + static constexpr const char* notbell = "\xEF\x87\xB6"; + static constexpr const char* info = "\xEF\x84\xA9"; +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) static constexpr const char* list = "\xEF\x80\xBA"; static constexpr const char* sun = "\xEF\x86\x85"; static constexpr const char* check = "\xEF\x95\xA0"; @@ -49,6 +57,7 @@ namespace Pinetime { static constexpr const char* eye = "\xEF\x81\xAE"; static constexpr const char* home = "\xEF\x80\x95"; static constexpr const char* sleep = "\xEE\xBD\x84"; +<<<<<<< HEAD <<<<<<< HEAD static constexpr const char* bird = "\xEF\x92\xBA"; static constexpr const char* zzz = "\xEF\x88\xB6"; @@ -56,6 +65,10 @@ namespace Pinetime { static constexpr const char* bird = "\xEF\x92\xBA"; static constexpr const char* zzz = "\xEF\x88\xB6"; >>>>>>> 504f5774 (copy :( files from gitlab repo) +======= + static constexpr const char* bird = "\xEF\x92\xBA"; + static constexpr const char* zzz = "\xEF\x88\xB6"; +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) // fontawesome_weathericons.c // static constexpr const char* sun = "\xEF\x86\x85"; diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index 20d0beb3..f0ed2b7b 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -259,7 +259,11 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, lv_obj_set_hidden(alarmIcon, true); lv_obj_set_hidden(labelTimeAmPmAlarm, true); } +<<<<<<< HEAD +======= + +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) stepValue = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); diff --git a/src/displayapp/screens/WatchFaceMeow.cpp b/src/displayapp/screens/WatchFaceMeow.cpp index c7328b4d..a4abadaa 100644 --- a/src/displayapp/screens/WatchFaceMeow.cpp +++ b/src/displayapp/screens/WatchFaceMeow.cpp @@ -199,7 +199,6 @@ WatchFaceMeow::WatchFaceMeow(Controllers::DateTime& dateTimeController, lineBattery = lv_line_create(lv_scr_act(), nullptr); lv_obj_set_style_local_line_width(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 24); lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); - lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]); lv_obj_set_style_local_line_opa(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 190); lineBatteryPoints[0] = {27, 105}; lineBatteryPoints[1] = {27, 106}; @@ -534,6 +533,7 @@ void WatchFaceMeow::Refresh() { } void WatchFaceMeow::SetBatteryLevel(uint8_t batteryPercent) { + // starting point (y) + Pine64 logo height * (100 - batteryPercent) / 100 lineBatteryPoints[1] = {27, static_cast(105 + 32 * (100 - batteryPercent) / 100)}; lv_line_set_points(lineBattery, lineBatteryPoints, 2); } diff --git a/src/displayapp/screens/settings/SettingWatchFace.h b/src/displayapp/screens/settings/SettingWatchFace.h index 4425bdcd..ededf93f 100644 --- a/src/displayapp/screens/settings/SettingWatchFace.h +++ b/src/displayapp/screens/settings/SettingWatchFace.h @@ -10,7 +10,10 @@ #include "displayapp/screens/Symbols.h" #include "displayapp/screens/CheckboxList.h" #include "displayapp/screens/WatchFaceInfineat.h" +<<<<<<< HEAD #include "displayapp/screens/WatchFaceInfineatColors.h" +======= +>>>>>>> 9d04c32f (recovering manually changes to original Infineat) #include "displayapp/screens/WatchFaceMeow.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" From 0ecfbee4b7d2b96baa9b91c5ffa90e948d60f9b1 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 15:17:36 +0200 Subject: [PATCH 022/101] now I have the Meow Watchface AND the Infineat Watchface with alarm display interactive rebase in progress; onto b7a353cf Last commands done (3 commands done): pick 9d04c32f recovering manually changes to original Infineat pick 70f9025f now I have the Meow Watchface AND the Infineat Watchface with alarm display No commands remaining. You are currently rebasing branch 'my-custom-infinitime' on 'b7a353cf'. Changes to be committed: modified: src/components/settings/Settings.h --- src/components/settings/Settings.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 57fedbcf..6d3100bd 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -48,11 +48,14 @@ namespace Pinetime { struct WatchFaceInfineat { bool showSideCover = true; bool showAlarmStatus = true; +<<<<<<< HEAD int colorIndex = 0; }; struct WatchFaceInfineatColors { bool showSideCover = true; +======= +>>>>>>> 70f9025f (now I have the Meow Watchface AND the Infineat Watchface with alarm display) int colorIndex = 0; }; <<<<<<< HEAD From 9a377f6baffa74312214b5af32bd0e054185c5c2 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 15:25:05 +0200 Subject: [PATCH 023/101] fix src/displayapp/apps/CMakeLists.txt after merge mess --- src/displayapp/apps/CMakeLists.txt | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index e02bb554..7ae83634 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -22,20 +22,16 @@ if(DEFINED ENABLE_WATCHFACES) set(WATCHFACE_TYPES ${ENABLE_WATCHFACES} CACHE STRING "List of watch faces to build into the firmware") else() set(DEFAULT_WATCHFACE_TYPES "WatchFace::Digital") - #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Analog") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Analog") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::PineTimeStyle") -<<<<<<< HEAD - #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") - #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::InfineatColors") - set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Meow") - #set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") -======= set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Meow") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") ->>>>>>> 504f5774 (copy :( files from gitlab repo) + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Meow") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware") endif() From 5ac1e90040a8955fb9d6ee30d99f764bb6a1219e Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 15:26:59 +0200 Subject: [PATCH 024/101] fix src/CMakeLists.txt after merge mess --- src/CMakeLists.txt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5fa13e0e..dc0a8494 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -371,15 +371,7 @@ list(APPEND SOURCE_FILES displayapp/screens/StopWatch.cpp displayapp/screens/BatteryIcon.cpp displayapp/screens/BleIcon.cpp -<<<<<<< HEAD -<<<<<<< HEAD - displayapp/screens/AlarmIcon.cpp -======= displayapp/screens/AlarmIcon.cpp ->>>>>>> f5dfbc44 (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) -======= - displayapp/screens/AlarmIcon.cpp ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) displayapp/screens/NotificationIcon.cpp displayapp/screens/SystemInfo.cpp displayapp/screens/Label.cpp From ce13c721be8b4716a38f00148f767f052dd0708d Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 15:33:21 +0200 Subject: [PATCH 025/101] fix fonts after merge mess --- src/displayapp/fonts/fonts.json | 10 +--------- src/displayapp/screens/Symbols.h | 26 -------------------------- 2 files changed, 1 insertion(+), 35 deletions(-) diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json index 11dcd7b3..61c40ce7 100644 --- a/src/displayapp/fonts/fonts.json +++ b/src/displayapp/fonts/fonts.json @@ -7,15 +7,7 @@ }, { "file": "FontAwesome5-Solid+Brands+Regular.woff", -<<<<<<< HEAD -<<<<<<< HEAD - "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf0f3, 0xf1f6" -======= - "range": "0xf294, 0xf242, 0xf54b, 0xf1b0, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf4ba, 0xf236" ->>>>>>> f5dfbc44 (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) -======= - "range": "0xf294, 0xf242, 0xf54b, 0xf1b0, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf4ba, 0xf236" ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) + "range": "0xf294, 0xf242, 0xf54b, 0xf1b0, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf4ba, 0xf236, 0xf0f3, 0xf1f6" } ], "bpp": 1, diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index a68d45d0..d392d72a 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -10,27 +10,11 @@ namespace Pinetime { static constexpr const char* bluetooth = "\xEF\x8A\x94"; static constexpr const char* plug = "\xEF\x87\xA6"; static constexpr const char* shoe = "\xEF\x95\x8B"; -<<<<<<< HEAD -<<<<<<< HEAD static constexpr const char* paw = "\xEF\x86\xB0"; static constexpr const char* clock = "\xEF\x80\x97"; static constexpr const char* bell = "\xEF\x83\xB3"; static constexpr const char* notbell = "\xEF\x87\xB6"; static constexpr const char* info = "\xEF\x84\xA9"; -======= - static constexpr const char* paw = "\xEF\x86\xB0"; - static constexpr const char* clock = "\xEF\x80\x97"; - static constexpr const char* bell = "\xEF\x83\xB3"; - static constexpr const char* notbell = "\xEF\x87\xB6"; - static constexpr const char* info = "\xEF\x84\xA9"; ->>>>>>> 504f5774 (copy :( files from gitlab repo) -======= - static constexpr const char* paw = "\xEF\x86\xB0"; - static constexpr const char* clock = "\xEF\x80\x97"; - static constexpr const char* bell = "\xEF\x83\xB3"; - static constexpr const char* notbell = "\xEF\x87\xB6"; - static constexpr const char* info = "\xEF\x84\xA9"; ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) static constexpr const char* list = "\xEF\x80\xBA"; static constexpr const char* sun = "\xEF\x86\x85"; static constexpr const char* check = "\xEF\x95\xA0"; @@ -57,18 +41,8 @@ namespace Pinetime { static constexpr const char* eye = "\xEF\x81\xAE"; static constexpr const char* home = "\xEF\x80\x95"; static constexpr const char* sleep = "\xEE\xBD\x84"; -<<<<<<< HEAD -<<<<<<< HEAD static constexpr const char* bird = "\xEF\x92\xBA"; static constexpr const char* zzz = "\xEF\x88\xB6"; -======= - static constexpr const char* bird = "\xEF\x92\xBA"; - static constexpr const char* zzz = "\xEF\x88\xB6"; ->>>>>>> 504f5774 (copy :( files from gitlab repo) -======= - static constexpr const char* bird = "\xEF\x92\xBA"; - static constexpr const char* zzz = "\xEF\x88\xB6"; ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) // fontawesome_weathericons.c // static constexpr const char* sun = "\xEF\x86\x85"; From 6475d6e624ad98c293a35a360429d457b23e9348 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 15:35:21 +0200 Subject: [PATCH 026/101] fix src/displayapp/UserApps.h after merge mess --- src/displayapp/UserApps.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/displayapp/UserApps.h b/src/displayapp/UserApps.h index f4fb97ba..a0e41f72 100644 --- a/src/displayapp/UserApps.h +++ b/src/displayapp/UserApps.h @@ -12,10 +12,6 @@ #include "displayapp/screens/WatchFaceAnalog.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" #include "displayapp/screens/WatchFaceInfineat.h" -<<<<<<< HEAD -#include "displayapp/screens/WatchFaceInfineatColors.h" -======= ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) #include "displayapp/screens/WatchFaceMeow.h" #include "displayapp/screens/WatchFacePineTimeStyle.h" #include "displayapp/screens/WatchFaceTerminal.h" From a250fa45ec87c6b8a501225596b2d811c083b43c Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 15:37:19 +0200 Subject: [PATCH 027/101] fix src/displayapp/screens/settings/SettingWatchFace.h after merge mess --- src/displayapp/screens/settings/SettingWatchFace.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/displayapp/screens/settings/SettingWatchFace.h b/src/displayapp/screens/settings/SettingWatchFace.h index ededf93f..359f12a6 100644 --- a/src/displayapp/screens/settings/SettingWatchFace.h +++ b/src/displayapp/screens/settings/SettingWatchFace.h @@ -10,11 +10,7 @@ #include "displayapp/screens/Symbols.h" #include "displayapp/screens/CheckboxList.h" #include "displayapp/screens/WatchFaceInfineat.h" -<<<<<<< HEAD -#include "displayapp/screens/WatchFaceInfineatColors.h" -======= ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) -#include "displayapp/screens/WatchFaceMeow.h" +include "displayapp/screens/WatchFaceMeow.h" #include "displayapp/screens/WatchFaceCasioStyleG7710.h" From a6b932e608e3cd2478d6ce7481c03977ff6c2b67 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 15:40:21 +0200 Subject: [PATCH 028/101] fix AlarmIcon.cpp and WatchFaceInfineat.cpp after merge mess --- src/displayapp/screens/AlarmIcon.cpp | 15 +-------------- src/displayapp/screens/WatchFaceInfineat.cpp | 5 ----- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/displayapp/screens/AlarmIcon.cpp b/src/displayapp/screens/AlarmIcon.cpp index 4877adb8..77018dea 100644 --- a/src/displayapp/screens/AlarmIcon.cpp +++ b/src/displayapp/screens/AlarmIcon.cpp @@ -4,21 +4,8 @@ using namespace Pinetime::Applications::Screens; const char* AlarmIcon::GetIcon(bool isSet) { if (isSet) { -<<<<<<< HEAD -<<<<<<< HEAD - return Symbols::bell; - } - - return Symbols::notbell; -======= -======= ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) - return Symbols::bird; + return Symbols::bird; } return Symbols::zzz; -<<<<<<< HEAD ->>>>>>> f5dfbc44 (added symbols for alarm states, added alarm state and hour to watchface meow, changed steps to paw. Need to align everythign nicely and find where to change release number) -======= ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) } diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index f0ed2b7b..0ebb5bff 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -259,11 +259,6 @@ WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController, lv_obj_set_hidden(alarmIcon, true); lv_obj_set_hidden(labelTimeAmPmAlarm, true); } -<<<<<<< HEAD - -======= - ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) stepValue = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor); lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko); From 3cfe46ec8f7d37d6b472dc0097bf13d16d54ecd8 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 15:43:30 +0200 Subject: [PATCH 029/101] fix Apps.h.in and src/components/settings/Settings.h after merge mess --- src/components/settings/Settings.h | 18 +----------------- src/displayapp/apps/Apps.h.in | 4 ---- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 6d3100bd..9237ea7f 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -48,30 +48,14 @@ namespace Pinetime { struct WatchFaceInfineat { bool showSideCover = true; bool showAlarmStatus = true; -<<<<<<< HEAD int colorIndex = 0; }; - struct WatchFaceInfineatColors { - bool showSideCover = true; -======= ->>>>>>> 70f9025f (now I have the Meow Watchface AND the Infineat Watchface with alarm display) - int colorIndex = 0; - }; -<<<<<<< HEAD - -======= - ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) struct WatchFaceMeow { bool showSideCover = true; int colorIndex = 0; }; -<<<<<<< HEAD - -======= - ->>>>>>> 9d04c32f (recovering manually changes to original Infineat) + Settings(Pinetime::Controllers::FS& fs); Settings(const Settings&) = delete; diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index 43fc3cbc..6a6c99e6 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -51,10 +51,6 @@ namespace Pinetime { PineTimeStyle, Terminal, Infineat, -<<<<<<< HEAD - InfineatColors, -======= ->>>>>>> 504f5774 (copy :( files from gitlab repo) Meow, CasioStyleG7710, }; From 896f2011b2a5811bfd26645787e88dcf88368435 Mon Sep 17 00:00:00 2001 From: ecarlett Date: Wed, 29 May 2024 15:48:35 +0200 Subject: [PATCH 030/101] fixing mess again after merge mess --- build/src/pinetime-mcuboot-app-dfu-1.14.0.zip | Bin 378895 -> 0 bytes src/CMakeLists.txt | 3 +- src/components/settings/Settings.h | 4 +- .../screens/WatchFaceInfineatColors.cpp | 516 ------------------ .../screens/WatchFaceInfineatColors.h | 124 ----- 5 files changed, 2 insertions(+), 645 deletions(-) delete mode 100644 build/src/pinetime-mcuboot-app-dfu-1.14.0.zip delete mode 100644 src/displayapp/screens/WatchFaceInfineatColors.cpp delete mode 100644 src/displayapp/screens/WatchFaceInfineatColors.h diff --git a/build/src/pinetime-mcuboot-app-dfu-1.14.0.zip b/build/src/pinetime-mcuboot-app-dfu-1.14.0.zip deleted file mode 100644 index 4904d149c01b8f34763e30cbadf8c98a7b703961..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 378895 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