mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-09 17:19:03 -04:00
Extended grapheme clusters (#15)
Introduce egcpool for attached storage Hook up style support Switch entirely to UTF-8 char from wchar_t (#14) Pull out next EGC in cell_load (#14)
This commit is contained in:
parent
0d10fdad79
commit
b93bcebf0b
@ -42,7 +42,6 @@ target_compile_definitions(notcurses
|
||||
_DEFAULT_SOURCE _XOPEN_SOURCE=600
|
||||
)
|
||||
|
||||
|
||||
file(GLOB BINSRCS CONFIGURE_DEPENDS src/bin/*.c)
|
||||
add_executable(notcurses-demo ${BINSRCS})
|
||||
target_include_directories(notcurses-demo PRIVATE include)
|
||||
@ -57,7 +56,11 @@ target_compile_options(notcurses-demo PRIVATE
|
||||
file(GLOB TESTSRCS CONFIGURE_DEPENDS tests/*.cpp)
|
||||
add_executable(notcurses-tester ${TESTSRCS})
|
||||
find_package(GTest 1.9 REQUIRED)
|
||||
target_include_directories(notcurses-tester PRIVATE include)
|
||||
target_include_directories(notcurses-tester
|
||||
PRIVATE
|
||||
include
|
||||
src/lib
|
||||
)
|
||||
target_link_libraries(notcurses-tester
|
||||
GTest::GTest
|
||||
notcurses
|
||||
|
@ -172,8 +172,8 @@ int ncplane_getc(const struct ncplane* n, cell* c, char** gclust);
|
||||
int ncplane_putstr(struct ncplane* n, const char* gclustarr);
|
||||
|
||||
// The ncplane equivalents of printf(3) and vprintf(3).
|
||||
int ncplane_printf(struct ncplane* n, const wchar_t* format, ...);
|
||||
int ncplane_vprintf(struct ncplane* n, const wchar_t* format, va_list ap);
|
||||
int ncplane_printf(struct ncplane* n, const char* format, ...);
|
||||
int ncplane_vprintf(struct ncplane* n, const char* format, va_list ap);
|
||||
|
||||
// Draw horizontal or vertical lines using the specified cell, starting at the
|
||||
// current cursor position. The cursor will end at the cell following the last
|
||||
@ -199,6 +199,16 @@ void ncplane_erase(struct ncplane* n);
|
||||
int ncplane_fg_rgb8(struct ncplane* n, int r, int g, int b);
|
||||
int ncplane_bg_rgb8(struct ncplane* n, int r, int g, int b);
|
||||
|
||||
// Set the specified style bits for the ncplane 'n', whether they're actively
|
||||
// supported or not.
|
||||
void ncplane_set_style(struct ncplane* n, unsigned stylebits);
|
||||
|
||||
// Add the specified styles to the ncplane's existing spec.
|
||||
void ncplane_enable_styles(struct ncplane* n, unsigned stylebits);
|
||||
|
||||
// Remove the specified styles from the ncplane's existing spec.
|
||||
void ncplane_disable_styles(struct ncplane* n, unsigned stylebits);
|
||||
|
||||
// Fine details about terminal
|
||||
|
||||
// Returns a 16-bit bitmask in the LSBs of supported NCURSES-style attributes
|
||||
@ -215,6 +225,29 @@ int notcurses_palette_size(const struct notcurses* nc);
|
||||
// Breaks the UTF-8 string in 'gcluster' down, setting up the cell 'c'.
|
||||
int cell_load(struct ncplane* n, cell* c, const char* gcluster);
|
||||
|
||||
#define CELL_STYLE_MASK 0xffff0000ul
|
||||
#define CELL_ALPHA_MASK 0x0000fffful
|
||||
|
||||
// Set the specified style bits for the cell 'c', whether they're actively
|
||||
// supported or not.
|
||||
static inline void
|
||||
cell_set_style(cell* c, unsigned stylebits){
|
||||
c->attrword = (c->attrword & ~CELL_STYLE_MASK) |
|
||||
((stylebits & 0xffff) << 16u);
|
||||
}
|
||||
|
||||
// Add the specified styles to the cell's existing spec.
|
||||
static inline void
|
||||
cell_enable_styles(cell* c, unsigned stylebits){
|
||||
c->attrword |= ((stylebits & 0xffff) << 16u);
|
||||
}
|
||||
|
||||
// Remove the specified styles from the cell's existing spec.
|
||||
static inline void
|
||||
cell_disable_styles(cell* c, unsigned stylebits){
|
||||
c->attrword &= ~((stylebits & 0xffff) << 16u);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
cell_fg_rgb(uint64_t channel){
|
||||
return (channel & 0x00ffffff00000000ull) >> 32u;
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <getopt.h>
|
||||
#include <stdlib.h>
|
||||
#include <notcurses.h>
|
||||
#include "demo.h"
|
||||
|
||||
static void
|
||||
usage(const char* exe, int status){
|
||||
@ -92,6 +93,9 @@ int main(int argc, char** argv){
|
||||
goto err;
|
||||
}
|
||||
sleep(1);
|
||||
/*if(widecolor_demo(nc, ncp)){
|
||||
goto err;
|
||||
}*/
|
||||
if(notcurses_stop(nc)){
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
18
src/bin/demo.h
Normal file
18
src/bin/demo.h
Normal file
@ -0,0 +1,18 @@
|
||||
#ifndef NOTCURSES_DEMO
|
||||
#define NOTCURSES_DEMO
|
||||
|
||||
#include <notcurses.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define FADE_MILLISECONDS 500
|
||||
|
||||
int widecolor_demo(struct notcurses* nc, struct ncplane* n);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
256
src/bin/widecolor.c
Normal file
256
src/bin/widecolor.c
Normal file
@ -0,0 +1,256 @@
|
||||
#include <curses.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "demo.h"
|
||||
|
||||
// Much of this text comes from http://kermitproject.org/utf8.html
|
||||
int widecolor_demo(struct notcurses* nc, struct ncplane* n){
|
||||
static const char* strs[] = {
|
||||
"Война и мир",
|
||||
"Бра́тья Карама́зовы",
|
||||
"Час сэканд-хэнд",
|
||||
"ஸீரோ டிகிரி",
|
||||
"Tonio Kröger",
|
||||
"بين القصرين",
|
||||
"قصر الشوق",
|
||||
"السكرية",
|
||||
"三体",
|
||||
"血的神话: 公元1967年湖南道县文革大屠杀纪实",
|
||||
"三国演义",
|
||||
"紅樓夢",
|
||||
"Hónglóumèng",
|
||||
"红楼梦",
|
||||
"महाभारतम्",
|
||||
"Mahābhāratam",
|
||||
" रामायणम्",
|
||||
"Rāmāyaṇam",
|
||||
"القرآن",
|
||||
"תּוֹרָה",
|
||||
"תָּנָ״ךְ",
|
||||
"Osudy dobrého vojáka Švejka za světové války",
|
||||
"Σίβνλλα τί ϴέλεις; respondebat illa: άπο ϴανεΐν ϴέλω",
|
||||
"काचं शक्नोम्यत्तुम् । नोपहिनस्ति माम्",
|
||||
"kācaṃ śaknomyattum; nopahinasti mām",
|
||||
"ὕαλον ϕαγεῖν δύναμαι· τοῦτο οὔ με βλάπτει",
|
||||
"Μπορώ να φάω σπασμένα γυαλιά χωρίς να πάθω τίποτα",
|
||||
"Μπορῶ νὰ φάω σπασμένα γυαλιὰ χωρὶς νὰ πάθω τίποτα",
|
||||
"Vitrum edere possum; mihi non nocet",
|
||||
"Je puis mangier del voirre. Ne me nuit",
|
||||
"Je peux manger du verre, ça ne me fait pas mal",
|
||||
"Pòdi manjar de veire, me nafrariá pas",
|
||||
"J'peux manger d'la vitre, ça m'fa pas mal",
|
||||
"Dji pou magnî do vêre, çoula m' freut nén må",
|
||||
"Ch'peux mingi du verre, cha m'foé mie n'ma",
|
||||
"Mwen kap manje vè, li pa blese'm",
|
||||
"Kristala jan dezaket, ez dit minik ematen",
|
||||
"Puc menjar vidre, que no em fa mal",
|
||||
"Puedo comer vidrio, no me hace daño",
|
||||
"Puedo minchar beire, no me'n fa mal",
|
||||
"Eu podo xantar cristais e non cortarme",
|
||||
"Posso comer vidro, não me faz mal",
|
||||
"Posso comer vidro, não me machuca",
|
||||
"M' podê cumê vidru, ca ta maguâ-m'",
|
||||
"Ami por kome glas anto e no ta hasimi daño",
|
||||
"Posso mangiare il vetro e non mi fa male",
|
||||
"Sôn bôn de magnà el véder, el me fa minga mal",
|
||||
"Me posso magna' er vetro, e nun me fa male",
|
||||
"M' pozz magna' o'vetr, e nun m' fa mal",
|
||||
"Mi posso magnare el vetro, no'l me fa mae",
|
||||
"Pòsso mangiâ o veddro e o no me fà mâ",
|
||||
"Puotsu mangiari u vitru, nun mi fa mali",
|
||||
"Jau sai mangiar vaider, senza che quai fa donn a mai",
|
||||
"Pot să mănânc sticlă și ea nu mă rănește",
|
||||
"Mi povas manĝi vitron, ĝi ne damaĝas min",
|
||||
"Mý a yl dybry gwéder hag éf ny wra ow ankenya",
|
||||
"Dw i'n gallu bwyta gwydr, 'dyw e ddim yn gwneud dolur i mi",
|
||||
"Foddym gee glonney agh cha jean eh gortaghey mee",
|
||||
"᚛᚛ᚉᚑᚅᚔᚉᚉᚔᚋ ᚔᚈᚔ ᚍᚂᚐᚅᚑ ᚅᚔᚋᚌᚓᚅᚐ",
|
||||
"Con·iccim ithi nglano. Ním·géna",
|
||||
"Is féidir liom gloinne a ithe. Ní dhéanann sí dochar ar bith dom",
|
||||
"Ithim-sa gloine agus ní miste damh é",
|
||||
"S urrainn dhomh gloinne ithe; cha ghoirtich i mi",
|
||||
"ᛁᚳ᛫ᛗᚨᚷ᛫ᚷᛚᚨᛋ᛫ᛖᚩᛏᚪᚾ᛫ᚩᚾᛞ᛫ᚻᛁᛏ᛫ᚾᛖ᛫ᚻᛖᚪᚱᛗᛁᚪᚧ᛫ᛗᛖ",
|
||||
"Ic mæg glæs eotan ond hit ne hearmiað me",
|
||||
"Ich canne glas eten and hit hirtiþ me nouȝt",
|
||||
"I can eat glass and it doesn't hurt me",
|
||||
"aɪ kæn iːt glɑːs ænd ɪt dɐz nɒt hɜːt mi",
|
||||
"⠊⠀⠉⠁⠝⠀⠑⠁⠞⠀⠛⠇⠁⠎⠎⠀⠁⠝⠙⠀⠊⠞⠀⠙⠕⠑⠎⠝⠞⠀⠓⠥⠗⠞⠀⠍",
|
||||
"Mi kian niam glas han i neba hot mi",
|
||||
"Ah can eat gless, it disnae hurt us",
|
||||
"𐌼𐌰𐌲 𐌲𐌻𐌴𐍃 𐌹̈𐍄𐌰𐌽, 𐌽𐌹 𐌼𐌹𐍃 𐍅𐌿 𐌽𐌳𐌰𐌽 𐌱𐍂𐌹𐌲𐌲𐌹𐌸",
|
||||
"ᛖᚴ ᚷᛖᛏ ᛖᛏᛁ ᚧ ᚷᛚᛖᚱ ᛘᚾ ᚦᛖᛋᛋ ᚨᚧ ᚡᛖ ᚱᚧᚨ ᛋᚨ",
|
||||
"Ek get etið gler án þess að verða sár",
|
||||
"Eg kan eta glas utan å skada meg",
|
||||
"Jeg kan spise glass uten å skade meg",
|
||||
"Eg kann eta glas, skaðaleysur",
|
||||
"Ég get etið gler án þess að meiða mig",
|
||||
"Jag kan äta glas utan att skada mig",
|
||||
"Jeg kan spise glas, det gør ikke ondt på mig",
|
||||
"Æ ka æe glass uhen at det go mæ naue",
|
||||
"က္ယ္ဝန္တော္၊က္ယ္ဝန္မ မ္ယက္စားနုိင္သည္။ ၎က္ရောင့္ ထိခုိက္မ္ဟု မရ္ဟိပာ။ (9",
|
||||
"ကျွန်တော် ကျွန်မ မှန်စားနိုင်တယ်။ ၎င်းကြောင့် ထိခိုက်မှုမရှိပါ။ (",
|
||||
"Tôi có thể ăn thủy tinh mà không hại gì",
|
||||
"些 𣎏 世 咹 水 晶 𦓡 空 𣎏 害",
|
||||
"ខ្ញុំអាចញុំកញ្ចក់បាន ដោយគ្មានបញ្ហា",
|
||||
"ຂອ້ຍກິນແກ້ວໄດ້ໂດຍທີ່ມັນບໍ່ໄດ້ເຮັດໃຫ້ຂອ້ຍເຈັບ",
|
||||
"ฉันกินกระจกได้ แต่มันไม่ทำให้ฉันเจ็",
|
||||
"Би шил идэй чадна, надад хортой би",
|
||||
"ᠪᠢ ᠰᠢᠯᠢ ᠢᠳᠡᠶᠦ ᠴᠢᠳᠠᠨᠠ ᠂ ᠨᠠᠳᠤᠷ ᠬᠣᠤᠷᠠᠳᠠᠢ ᠪᠢᠰ",
|
||||
"म काँच खान सक्छू र मलाई केहि नी हुन्न्",
|
||||
"ཤེལ་སྒོ་ཟ་ནས་ང་ན་གི་མ་རེད",
|
||||
"我能吞下玻璃而不伤身体",
|
||||
"我能吞下玻璃而不傷身體",
|
||||
"Góa ē-tàng chia̍h po-lê, mā bē tio̍h-siong",
|
||||
"私はガラスを食べられます。それは私を傷つけません",
|
||||
"나는 유리를 먹을 수 있어요. 그래도 아프지 않아",
|
||||
"Mi save kakae glas, hemi no save katem mi",
|
||||
"Hiki iaʻu ke ʻai i ke aniani; ʻaʻole nō lā au e ʻeha",
|
||||
"E koʻana e kai i te karahi, mea ʻā, ʻaʻe hauhau",
|
||||
"ᐊᓕᒍᖅ ᓂᕆᔭᕌᖓᒃᑯ ᓱᕋᙱᑦᑐᓐᓇᖅᑐ",
|
||||
"Naika məkmək kakshət labutay, pi weyk ukuk munk-sik nay",
|
||||
"Tsésǫʼ yishą́ągo bííníshghah dóó doo shił neezgai da",
|
||||
"mi kakne le nu citka le blaci .iku'i le se go'i na xrani m",
|
||||
"Ljœr ye caudran créneþ ý jor cẃran",
|
||||
"Ik kin glês ite, it docht me net sear",
|
||||
"Ik kan glas eten, het doet mij geen kwaad",
|
||||
"Iech ken glaas èèse, mer 't deet miech jing pieng",
|
||||
"Ek kan glas eet, maar dit doen my nie skade nie",
|
||||
"Ech kan Glas iessen, daat deet mir nët wei",
|
||||
"Ich kann Glas essen, ohne mir zu schaden",
|
||||
"Ich kann Glas verkasematuckeln, ohne dattet mich wat jucken tut",
|
||||
"Isch kann Jlaas kimmeln, uuhne datt mich datt weh dääd",
|
||||
"Ich koann Gloos assn und doas dudd merr ni wii",
|
||||
"Iech konn glaasch voschbachteln ohne dass es mir ebbs daun doun dud",
|
||||
"'sch kann Glos essn, ohne dass'sch mer wehtue",
|
||||
"Isch konn Glass fresse ohne dasses mer ebbes ausmache dud",
|
||||
"I kå Glas frässa, ond des macht mr nix",
|
||||
"I ka glas eassa, ohne dass mar weh tuat",
|
||||
"I koh Glos esa, und es duard ma ned wei",
|
||||
"I kaun Gloos essen, es tuat ma ned weh",
|
||||
"Ich chan Glaas ässe, das schadt mir nöd",
|
||||
"Ech cha Glâs ässe, das schadt mer ned",
|
||||
"Meg tudom enni az üveget, nem lesz tőle bajom",
|
||||
"Voin syödä lasia, se ei vahingoita minua",
|
||||
"Sáhtán borrat lása, dat ii leat bávččas",
|
||||
"Мон ярсан суликадо, ды зыян эйстэнзэ а ули",
|
||||
"Mie voin syvvä lasie ta minla ei ole kipie",
|
||||
"Minä voin syvvä st'oklua dai minule ei ole kibie",
|
||||
"Ma võin klaasi süüa, see ei tee mulle midagi",
|
||||
"Es varu ēst stiklu, tas man nekaitē",
|
||||
"Aš galiu valgyti stiklą ir jis manęs nežeidži",
|
||||
"Mohu jíst sklo, neublíží mi",
|
||||
"Môžem jesť sklo. Nezraní ma",
|
||||
"Mogę jeść szkło i mi nie szkodzi",
|
||||
"Lahko jem steklo, ne da bi mi škodovalo",
|
||||
"Ja mogu jesti staklo, i to mi ne šteti",
|
||||
"Ја могу јести стакло, и то ми не штети",
|
||||
"Можам да јадам стакло, а не ме штета",
|
||||
"Я могу есть стекло, оно мне не вредит",
|
||||
"Я магу есці шкло, яно мне не шкодзіць",
|
||||
"Ja mahu jeści škło, jano mne ne škodzić",
|
||||
"Я можу їсти скло, і воно мені не зашкодить",
|
||||
"Мога да ям стъкло, то не ми вреди",
|
||||
"მინას ვჭამ და არა მტკივა",
|
||||
"Կրնամ ապակի ուտել և ինծի անհանգիստ չըներ",
|
||||
"Unë mund të ha qelq dhe nuk më gjen gjë",
|
||||
"Cam yiyebilirim, bana zararı dokunmaz",
|
||||
"جام ييه بلورم بڭا ضررى طوقونم",
|
||||
"Алам да бар, пыяла, әмма бу ранит мине",
|
||||
"Men shisha yeyishim mumkin, ammo u menga zarar keltirmaydi",
|
||||
"Мен шиша ейишим мумкин, аммо у менга зарар келтирмайди",
|
||||
"আমি কাঁচ খেতে পারি, তাতে আমার কোনো ক্ষতি হয় না",
|
||||
"मी काच खाऊ शकतो, मला ते दुखत नाही",
|
||||
"ನನಗೆ ಹಾನಿ ಆಗದೆ, ನಾನು ಗಜನ್ನು ತಿನಬಹು",
|
||||
"मैं काँच खा सकता हूँ और मुझे उससे कोई चोट नहीं पहुंचती",
|
||||
"എനിക്ക് ഗ്ലാസ് തിന്നാം. അതെന്നെ വേദനിപ്പിക്കില്ല",
|
||||
"நான் கண்ணாடி சாப்பிடுவேன், அதனால் எனக்கு ஒரு கேடும் வராது",
|
||||
"నేను గాజు తినగలను మరియు అలా చేసినా నాకు ఏమి ఇబ్బంది లే",
|
||||
"මට වීදුරු කෑමට හැකියි. එයින් මට කිසි හානියක් සිදු නොවේ",
|
||||
"میں کانچ کھا سکتا ہوں اور مجھے تکلیف نہیں ہوتی",
|
||||
"زه شيشه خوړلې شم، هغه ما نه خوږو",
|
||||
".من می توانم بدونِ احساس درد شيشه بخور",
|
||||
"أنا قادر على أكل الزجاج و هذا لا يؤلمني",
|
||||
"Nista' niekol il-ħġieġ u ma jagħmilli xejn",
|
||||
"אני יכול לאכול זכוכית וזה לא מזיק לי",
|
||||
"איך קען עסן גלאָז און עס טוט מיר נישט װײ",
|
||||
"Metumi awe tumpan, ɜnyɜ me hwee",
|
||||
"Inā iya taunar gilāshi kuma in gamā lāfiyā",
|
||||
"إِنا إِىَ تَونَر غِلَاشِ كُمَ إِن غَمَا لَافِىَ",
|
||||
"Mo lè je̩ dígí, kò ní pa mí lára",
|
||||
"Nakokí kolíya biténi bya milungi, ekosála ngáí mabé tɛ́",
|
||||
"Naweza kula bilauri na sikunyui",
|
||||
"Saya boleh makan kaca dan ia tidak mencederakan saya",
|
||||
"Kaya kong kumain nang bubog at hindi ako masaktan",
|
||||
"Siña yo' chumocho krestat, ti ha na'lalamen yo'",
|
||||
"Au rawa ni kana iloilo, ia au sega ni vakacacani kina",
|
||||
"Aku isa mangan beling tanpa lara",
|
||||
"① На всей земле был один язык и одно наречие.",
|
||||
"② А кад отидоше од истока, нађоше равницу у земљи сенарској, и населише се онде.",
|
||||
"③ І сказалі адно аднаму: наробім цэглы і абпалім агнём. І стала ў іх цэгла замест камянёў, а земляная смала замест вапны.",
|
||||
"④ І сказали вони: Тож місто збудуймо собі, та башту, а вершина її аж до неба. І вчинімо для себе ймення, щоб ми не розпорошилися по поверхні всієї землі.",
|
||||
"⑤ Господ слезе да ги види градот и кулата, што луѓето ги градеа.",
|
||||
"⑥ И҆ речѐ гдⷭ҇ь: сѐ, ро́дъ є҆ди́нъ, и҆ ѹ҆стнѣ̀ є҆ди҄нѣ всѣ́хъ, и҆ сїѐ нача́ша твори́ти: и҆ нн҃ѣ не ѡ҆скꙋдѣ́ютъ ѿ ни́хъ всѧ҄, є҆ли҄ка а́҆ще восхотѧ́тъ твори́ти.",
|
||||
"⑦ Ⱂⱃⰻⰻⰴⱑⱅⰵ ⰺ ⰺⰸⱎⰵⰴⱎⰵ ⱄⰿⱑⱄⰻⰿⱏ ⰺⰿⱏ ⱅⱆ ⱔⰸⱏⰹⰽⰻ ⰺⱈⱏ · ⰴⰰ ⱀⰵ ⱆⱄⰾⱏⰹⱎⰰⱅⱏ ⰽⱁⰶⰴⱁ ⰴⱃⱆⰳⰰ ⱄⰲⱁⰵⰳⱁ ⁖⸏",
|
||||
NULL
|
||||
};
|
||||
const char** s;
|
||||
int count = notcurses_palette_size(nc);
|
||||
//int key;
|
||||
const int steps[] = { 1, 16, count, count + 16, };
|
||||
const int starts[] = { 0, 48 * count, 48 * count, 48 * count, };
|
||||
|
||||
size_t i;
|
||||
for(i = 0 ; i < sizeof(steps) / sizeof(*steps) ; ++i){
|
||||
const int start = starts[i];
|
||||
const int step = steps[i];
|
||||
//do{
|
||||
int y, x, maxy, maxx;
|
||||
ncplane_dimyx(n, &maxy, &maxx);
|
||||
--maxy;
|
||||
--maxx;
|
||||
int cpair = start;
|
||||
ncplane_cursor_move_yx(n, 0, 0);
|
||||
y = 0;
|
||||
x = 0;
|
||||
do{ // we fill up the entire screen, however large
|
||||
for(s = strs ; *s ; ++s){
|
||||
cell wch;
|
||||
cell_set_style(&wch, WA_NORMAL);
|
||||
cell_set_fg(&wch, cpair, cpair, cpair);
|
||||
cell_load(n, &wch, " ");
|
||||
ncplane_putc(n, &wch, " ");
|
||||
size_t idx;
|
||||
for(idx = 0 ; idx < strlen(*s) ; ++idx){
|
||||
cell_load(n, &wch, &(*s)[idx]);
|
||||
ncplane_putc(n, &wch, &(*s)[idx]);
|
||||
ncplane_cursor_yx(n, &y, &x);
|
||||
if(y >= maxy && x >= maxx){
|
||||
break;
|
||||
}
|
||||
if((cpair += step) >= 256){
|
||||
cpair = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}while(y != maxy || x != maxx);
|
||||
ncplane_fg_rgb8(n, 255, 255, 255);
|
||||
ncplane_set_style(n, WA_BOLD);
|
||||
ncplane_cursor_move_yx(n, 2, 2);
|
||||
ncplane_printf(n, " %dx%d (%d/%d) ", maxx, maxy, i, sizeof(steps) / sizeof(*steps));
|
||||
ncplane_set_style(n, WA_NORMAL);
|
||||
ncplane_fg_rgb8(n, cpair, cpair, cpair);
|
||||
ncplane_putstr(n, "wide chars, multiple colors, resize awareness ");//…");
|
||||
/*if(i){ FIXME
|
||||
fadein(w, count, palette, FADE_MILLISECONDS);
|
||||
}
|
||||
do{
|
||||
key = wgetch(w);
|
||||
}while(key == ERR);
|
||||
*/
|
||||
sleep(1); // FIXME
|
||||
ncplane_cursor_move_yx(n, 0, 0);
|
||||
//}while(key == KEY_RESIZE);
|
||||
}
|
||||
return 0;
|
||||
}
|
34
src/lib/egcpool.c
Normal file
34
src/lib/egcpool.c
Normal file
@ -0,0 +1,34 @@
|
||||
#include <stdio.h>
|
||||
#include "egcpool.h"
|
||||
|
||||
#define POOL_MINIMUM_ALLOC BUFSIZ
|
||||
|
||||
int egcpool_grow(egcpool* pool, size_t len, bool force){
|
||||
const size_t poolfree = pool->poolsize - pool->poolused;
|
||||
// proactively get more space if we have less than 10% free. this doesn't
|
||||
// guarantee that we'll have enough space to insert the string -- we could
|
||||
// theoretically have every 10th byte free, and be unable to write even a
|
||||
// two-byte egc -- so we might have to allocate after an expensive search :/.
|
||||
if(poolfree >= len && poolfree * 10 > pool->poolsize && !force){
|
||||
return 0;
|
||||
}
|
||||
size_t newsize = pool->poolsize * 2;
|
||||
if(newsize < POOL_MINIMUM_ALLOC){
|
||||
newsize = POOL_MINIMUM_ALLOC;
|
||||
}
|
||||
while(len > newsize - pool->poolsize){ // ensure we make enough space
|
||||
newsize *= 2;
|
||||
}
|
||||
// offsets only have 24 bits available...
|
||||
if(newsize >= 1u << 23u){
|
||||
return -1;
|
||||
}
|
||||
typeof(*pool->pool)* tmp = realloc(pool->pool, newsize);
|
||||
if(tmp == NULL){
|
||||
return -1;
|
||||
}
|
||||
pool->pool = tmp;
|
||||
memset(pool->pool + pool->poolsize, 0, newsize - pool->poolsize);
|
||||
pool->poolsize = newsize;
|
||||
return 0;
|
||||
}
|
120
src/lib/egcpool.h
Normal file
120
src/lib/egcpool.h
Normal file
@ -0,0 +1,120 @@
|
||||
#ifndef NOTCURSES_EGCPOOL
|
||||
#define NOTCURSES_EGCPOOL
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// cells only provide storage for a single 7-bit character. if there's anything
|
||||
// more than that, it's spilled into the egcpool, and the cell is given an
|
||||
// offset. when a cell is released, the memory it owned is zeroed out, and
|
||||
// recognizable as use for another cell.
|
||||
|
||||
typedef struct egcpool {
|
||||
char* pool; // ringbuffer of attached extension storage
|
||||
size_t poolsize; // total number of bytes in pool
|
||||
size_t poolused; // bytes actively used, grow when this gets too large
|
||||
size_t poolwrite; // next place to *look for* a place to write
|
||||
} egcpool;
|
||||
|
||||
static inline void
|
||||
egcpool_init(egcpool* p){
|
||||
memset(p, 0, sizeof(*p));
|
||||
}
|
||||
|
||||
int egcpool_grow(egcpool* pool, size_t len, bool force);
|
||||
|
||||
// stash away the provided UTF8, NUL-terminated grapheme cluster. the cluster
|
||||
// should not be less than 2 bytes (such a cluster should be directly stored in
|
||||
// the cell). returns -1 on error, and otherwise a non-negative 24-bit offset.
|
||||
static inline int
|
||||
egcpool_stash(egcpool* pool, const char* egc){
|
||||
size_t len = strlen(egc) + 1; // count the NUL terminator
|
||||
if(len <= 2){ // should never be empty, nor a single byte + NUL
|
||||
return -1;
|
||||
}
|
||||
// the first time through, we don't force a grow unless we expect ourselves
|
||||
// to have too little space. once we've done a search, we do force the grow.
|
||||
// we should thus never have more than two iterations of this loop.
|
||||
bool searched = false;
|
||||
do{
|
||||
if(egcpool_grow(pool, len, false)){
|
||||
return -1;
|
||||
}
|
||||
// we now look for a place to lay out this egc. we need |len| zeroes in a
|
||||
// row. starting at pool->poolwrite, look for such a range of unused
|
||||
// memory. if we find it, write it out, and update used count. if we come
|
||||
// back to where we started, force a growth and try again.
|
||||
size_t curpos = pool->poolwrite;
|
||||
do{
|
||||
if(curpos == pool->poolsize){
|
||||
curpos = 0;
|
||||
}
|
||||
if(pool->pool[curpos]){ // can't write if there's stuff here
|
||||
++curpos;
|
||||
}else{ // promising! let's see if there's enough space
|
||||
size_t need = len;
|
||||
size_t trial = curpos;
|
||||
while(--need){
|
||||
if(++trial == pool->poolsize){
|
||||
trial = 0;
|
||||
}
|
||||
if(pool->pool[trial]){ // alas, not enough space here
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(need == 0){ // found a suitable space, copy it!
|
||||
if(pool->poolsize - len > curpos){ // one chunk
|
||||
memcpy(pool->pool + curpos, egc, len);
|
||||
pool->poolwrite = curpos + len;
|
||||
}else{ // two chunks
|
||||
// FIXME are clients prepared for split egcs? i doubt it...
|
||||
size_t fchunk = pool->poolsize - curpos - 1;
|
||||
memcpy(pool->pool + curpos, egc, fchunk);
|
||||
memcpy(pool->pool, egc + fchunk, len - fchunk);
|
||||
pool->poolwrite = len - fchunk;
|
||||
}
|
||||
pool->poolused += len;
|
||||
return curpos;
|
||||
}
|
||||
curpos += len - need; // do we always hit pool->poolwrite properly?
|
||||
}
|
||||
}while(curpos != pool->poolwrite);
|
||||
}while( (searched = !searched) );
|
||||
return -1; // should never get here
|
||||
}
|
||||
|
||||
// remove the egc from the pool. start at offset, and zero out everything until
|
||||
// we find a zero (our own NUL terminator). remove that number of bytes from
|
||||
// the usedcount.
|
||||
static inline void
|
||||
egcpool_release(egcpool* pool, size_t offset){
|
||||
size_t freed = 1; // account for free(d) NUL terminator
|
||||
while(pool->pool[offset]){
|
||||
pool->pool[offset] = '\0';
|
||||
++freed;
|
||||
if(++offset == pool->poolsize){
|
||||
offset = 0;
|
||||
}
|
||||
}
|
||||
pool->poolused -= freed;
|
||||
// FIXME ought we update pool->poolwrite?
|
||||
}
|
||||
|
||||
static inline void
|
||||
egcpool_dump(egcpool* pool){
|
||||
free(pool->pool);
|
||||
pool->poolsize = 0;
|
||||
pool->poolwrite = 0;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
@ -9,6 +9,7 @@
|
||||
#include <sys/ioctl.h>
|
||||
#include "notcurses.h"
|
||||
#include "version.h"
|
||||
#include "egcpool.h"
|
||||
|
||||
// Some capabilities are so fundamental that we don't attempt to run without
|
||||
// them. Essentially, we require a two-dimensional, random-access terminal.
|
||||
@ -34,16 +35,15 @@ static const char* required_caps[] = {
|
||||
// screen is resized, for example. Offscreen portions will not be rendered.
|
||||
// Accesses beyond the borders of a panel, however, are errors.
|
||||
typedef struct ncplane {
|
||||
cell* fb; // "framebuffer" of character cells
|
||||
int x, y; // current location within this plane
|
||||
int absx, absy; // origin of the plane relative to the screen
|
||||
int lenx, leny; // size of the plane, [0..len{x,y}) is addressable
|
||||
struct ncplane* z; // plane below us
|
||||
cell* fb; // "framebuffer" of character cells
|
||||
int x, y; // current location within this plane
|
||||
int absx, absy; // origin of the plane relative to the screen
|
||||
int lenx, leny; // size of the plane, [0..len{x,y}) is addressable
|
||||
struct ncplane* z; // plane below us
|
||||
struct notcurses* nc; // our parent nc, kinda lame waste of memory FIXME
|
||||
uint64_t channels; // colors when not provided an active style
|
||||
char* pool; // storage pool for multibyte grapheme clusters
|
||||
size_t poolsize; // bytes allocated for gcluster pool
|
||||
size_t poolwrite;// where next to write into the pool
|
||||
egcpool pool; // attached storage pool for UTF-8 EGCs
|
||||
uint64_t channels; // works the same way as cells
|
||||
uint32_t attrword; // same deal as in a cell
|
||||
} ncplane;
|
||||
|
||||
typedef struct notcurses {
|
||||
@ -128,7 +128,7 @@ term_verify_seq(char** gseq, const char* name){
|
||||
static void
|
||||
free_plane(ncplane* p){
|
||||
if(p){
|
||||
free(p->pool);
|
||||
egcpool_dump(&p->pool);
|
||||
free(p->fb);
|
||||
free(p);
|
||||
}
|
||||
@ -168,15 +168,6 @@ create_ncplane(notcurses* nc, int rows, int cols){
|
||||
return p;
|
||||
}
|
||||
|
||||
static int
|
||||
init_gcluster_pool(ncplane* p){
|
||||
// FIXME VERY rough proof of concept, do not merge!
|
||||
p->poolsize = BUFSIZ * p->leny;
|
||||
p->pool = malloc(p->poolsize);
|
||||
p->poolwrite = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Call this on initialization, or when the screen size changes. Takes a flat
|
||||
// array of *rows * *cols cells (may be NULL if *rows == *cols == 0). Gets the
|
||||
// new size, and copies what can be copied from the old stdscr. Assumes that
|
||||
@ -197,9 +188,9 @@ alloc_stdscr(notcurses* nc){
|
||||
if((p = create_ncplane(nc, rows, cols)) == NULL){
|
||||
goto err;
|
||||
}
|
||||
if(init_gcluster_pool(p)){
|
||||
goto err;
|
||||
}
|
||||
egcpool_init(&p->pool);
|
||||
p->attrword = 0;
|
||||
p->channels = 0;
|
||||
ncplane** oldscr;
|
||||
ncplane* preserve;
|
||||
// if we ever make this a doubly-linked list, turn this into o(1)
|
||||
@ -531,7 +522,7 @@ simple_cell_p(const cell* c){
|
||||
static inline const char*
|
||||
extended_gcluster(const ncplane* n, const cell* c){
|
||||
uint32_t idx = c->gcluster - 0x80;
|
||||
return n->pool + idx;
|
||||
return n->pool.pool + idx;
|
||||
}
|
||||
|
||||
// Write the cell's UTF-8 grapheme cluster to the physical terminal.
|
||||
@ -556,6 +547,7 @@ term_putc(const notcurses* nc, const ncplane* n, const cell* c){
|
||||
if((w = write(nc->ttyfd, ext, len)) < 0 || (size_t)w != len){
|
||||
return -1;
|
||||
}
|
||||
fprintf(stderr, "WROTE %zu\n", len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -640,6 +632,7 @@ int ncplane_getc(const ncplane* n, cell* c, char** gclust){
|
||||
return 0;
|
||||
}
|
||||
|
||||
// loads the cell with the next EGC from gcluster.
|
||||
int cell_load(ncplane* n, cell* c, const char* gcluster){
|
||||
if(simple_gcluster_p(gcluster)){
|
||||
c->gcluster = *gcluster;
|
||||
@ -649,11 +642,11 @@ int cell_load(ncplane* n, cell* c, const char* gcluster){
|
||||
while(*(const unsigned char*)end >= 0x80){ // FIXME broken broken broken
|
||||
++end;
|
||||
}
|
||||
// FIXME enlarge pool on demand
|
||||
memcpy(n->pool + n->poolwrite, gcluster, end - gcluster);
|
||||
c->gcluster = n->poolwrite + 0x80;
|
||||
n->poolwrite += end - gcluster;
|
||||
n->pool[n->poolwrite++] = '\0';
|
||||
int eoffset = egcpool_stash(&n->pool, gcluster);
|
||||
if(eoffset < 0){
|
||||
return -1;
|
||||
}
|
||||
c->gcluster = eoffset + 0x80;
|
||||
return end - gcluster;
|
||||
}
|
||||
|
||||
@ -668,13 +661,6 @@ int ncplane_putstr(ncplane* n, const char* gcluster){
|
||||
cell_set_bg(&c, cell_rgb_red(rgb), cell_rgb_green(rgb), cell_rgb_blue(rgb));
|
||||
int wcs = 0;
|
||||
while(*gcluster){
|
||||
wcs = cell_load(n, &c, gcluster);
|
||||
if(wcs < 0){
|
||||
return -ret;
|
||||
}
|
||||
if(wcs == 0){
|
||||
break;
|
||||
}
|
||||
wcs = ncplane_putc(n, &c, gcluster);
|
||||
if(wcs < 0){
|
||||
return -ret;
|
||||
@ -703,3 +689,29 @@ unsigned notcurses_supported_styles(const notcurses* nc){
|
||||
int notcurses_palette_size(const notcurses* nc){
|
||||
return nc->colors;
|
||||
}
|
||||
|
||||
void ncplane_enable_styles(ncplane* n, unsigned stylebits){
|
||||
n->attrword |= ((stylebits & 0xffff) << 16u);
|
||||
}
|
||||
|
||||
void ncplane_disable_styles(ncplane* n, unsigned stylebits){
|
||||
n->attrword &= ~((stylebits & 0xffff) << 16u);
|
||||
}
|
||||
|
||||
void ncplane_set_style(ncplane* n, unsigned stylebits){
|
||||
n->attrword = (n->attrword & ~CELL_STYLE_MASK) |
|
||||
((stylebits & 0xffff) << 16u);
|
||||
}
|
||||
|
||||
int ncplane_printf(ncplane* n, const char* format, ...){
|
||||
int ret;
|
||||
va_list va;
|
||||
va_start(va, format);
|
||||
ret = ncplane_vprintf(n, format, va);
|
||||
va_end(va);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ncplane_vprintf(ncplane* n, const char* format, va_list ap){
|
||||
return 0;
|
||||
}
|
||||
|
41
tests/cell.cpp
Normal file
41
tests/cell.cpp
Normal file
@ -0,0 +1,41 @@
|
||||
#include <notcurses.h>
|
||||
#include "main.h"
|
||||
|
||||
class CellTest : public :: testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
if(getenv("TERM") == nullptr){
|
||||
GTEST_SKIP();
|
||||
}
|
||||
notcurses_options nopts{};
|
||||
nopts.outfd = STDIN_FILENO;
|
||||
nc_ = notcurses_init(&nopts);
|
||||
ASSERT_NE(nullptr, nc_);
|
||||
n_ = notcurses_stdplane(nc_);
|
||||
ASSERT_NE(nullptr, n_);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
if(nc_){
|
||||
EXPECT_EQ(0, ncplane_fg_rgb8(n_, 255, 255, 255));
|
||||
EXPECT_EQ(0, notcurses_render(nc_));
|
||||
EXPECT_EQ(0, notcurses_stop(nc_));
|
||||
}
|
||||
}
|
||||
|
||||
struct notcurses* nc_{};
|
||||
struct ncplane* n_{};
|
||||
};
|
||||
|
||||
TEST_F(CellTest, SetStyles) {
|
||||
cell c;
|
||||
memset(&c, 0, sizeof(c));
|
||||
cell_set_style(&c, WA_ITALIC);
|
||||
cell_load(n_, &c, "s");
|
||||
EXPECT_EQ(1, ncplane_putc(n_, &c, "s"));
|
||||
int x, y;
|
||||
ncplane_cursor_yx(n_, &y, &x);
|
||||
EXPECT_EQ(1, x);
|
||||
EXPECT_EQ(0, y);
|
||||
notcurses_render(nc_);
|
||||
}
|
75
tests/egcpool.cpp
Normal file
75
tests/egcpool.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
#include <notcurses.h>
|
||||
#include "egcpool.h"
|
||||
#include "main.h"
|
||||
|
||||
class EGCPoolTest : public :: testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
egcpool_dump(&pool_);
|
||||
}
|
||||
|
||||
egcpool pool_{};
|
||||
|
||||
};
|
||||
|
||||
TEST_F(EGCPoolTest, Initialized) {
|
||||
EXPECT_EQ(nullptr, pool_.pool);
|
||||
EXPECT_EQ(0, pool_.poolsize);
|
||||
EXPECT_EQ(0, pool_.poolwrite);
|
||||
EXPECT_EQ(0, pool_.poolused);
|
||||
}
|
||||
|
||||
TEST_F(EGCPoolTest, AddAndRemove) {
|
||||
const char* wstr = "﷽";
|
||||
ASSERT_EQ(0, egcpool_stash(&pool_, wstr));
|
||||
EXPECT_NE(nullptr, pool_.pool);
|
||||
EXPECT_STREQ(pool_.pool, wstr);
|
||||
EXPECT_LT(0, pool_.poolsize);
|
||||
EXPECT_EQ(strlen(wstr) + 1, pool_.poolused);
|
||||
EXPECT_LT(0, pool_.poolwrite);
|
||||
EXPECT_LE(pool_.poolused, pool_.poolsize);
|
||||
egcpool_release(&pool_, 0);
|
||||
EXPECT_EQ('\0', *pool_.pool);
|
||||
EXPECT_LT(0, pool_.poolsize);
|
||||
EXPECT_EQ(0, pool_.poolused);
|
||||
EXPECT_LT(0, pool_.poolwrite);
|
||||
}
|
||||
|
||||
TEST_F(EGCPoolTest, AddTwiceRemoveFirst) {
|
||||
const char* wstr = "血的神话: 公元1967年湖南道县文革大屠杀纪实";
|
||||
int o1 = egcpool_stash(&pool_, wstr);
|
||||
int o2 = egcpool_stash(&pool_, wstr);
|
||||
ASSERT_LT(o1, o2);
|
||||
EXPECT_NE(nullptr, pool_.pool);
|
||||
EXPECT_STREQ(pool_.pool, wstr);
|
||||
EXPECT_STREQ(pool_.pool + strlen(wstr) + 1, wstr);
|
||||
EXPECT_LT(0, pool_.poolsize);
|
||||
EXPECT_EQ(2 * (strlen(wstr) + 1), pool_.poolused);
|
||||
EXPECT_EQ(2 * (strlen(wstr) + 1), pool_.poolwrite);
|
||||
EXPECT_LE(pool_.poolused, pool_.poolsize);
|
||||
egcpool_release(&pool_, o1);
|
||||
EXPECT_EQ('\0', pool_.pool[o1]);
|
||||
EXPECT_EQ(strlen(wstr) + 1, pool_.poolused);
|
||||
EXPECT_LT(0, pool_.poolwrite);
|
||||
}
|
||||
|
||||
TEST_F(EGCPoolTest, AddTwiceRemoveSecond) {
|
||||
const char* wstr = "血的神话: 公元1967年湖南道县文革大屠杀纪实";
|
||||
int o1 = egcpool_stash(&pool_, wstr);
|
||||
int o2 = egcpool_stash(&pool_, wstr);
|
||||
ASSERT_LT(o1, o2);
|
||||
EXPECT_NE(nullptr, pool_.pool);
|
||||
EXPECT_STREQ(pool_.pool, wstr);
|
||||
EXPECT_STREQ(pool_.pool + strlen(wstr) + 1, wstr);
|
||||
EXPECT_LT(0, pool_.poolsize);
|
||||
EXPECT_EQ(2 * (strlen(wstr) + 1), pool_.poolused);
|
||||
EXPECT_EQ(2 * (strlen(wstr) + 1), pool_.poolwrite);
|
||||
EXPECT_LE(pool_.poolused, pool_.poolsize);
|
||||
egcpool_release(&pool_, o2);
|
||||
EXPECT_EQ('\0', pool_.pool[o2]);
|
||||
EXPECT_EQ(strlen(wstr) + 1, pool_.poolused);
|
||||
EXPECT_LT(0, pool_.poolwrite);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user