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)
pull/18/head
Nick Black 5 years ago committed by GitHub
parent 0d10fdad79
commit b93bcebf0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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;
}

@ -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

@ -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;
}

@ -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;
}

@ -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;
}

@ -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_);
}

@ -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…
Cancel
Save