diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..ee3dfdc --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +exclude: ^(writer|\w*\.py) + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.287 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + - repo: https://github.com/psf/black + rev: 22.10.0 + hooks: + - id: black diff --git a/Chinese_Japanese b/Chinese_Japanese index 6f4a933..4ce8082 100644 --- a/Chinese_Japanese +++ b/Chinese_Japanese @@ -1 +1 @@ - !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんゔァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶヷヸヹヺ一丁七万丈三上下丌不与丐丑专且丕世丘丙业丛东丝丞丢两严丧丨个丫丬中丰串临丶丸丹为主丽举丿乃久乇么义之乌乍乎乏乐乒乓乔乖乘乙乜九乞也习乡书乩买乱乳乾了予争事二亍于亏云互亓五井亘亚些亟亠亡亢交亥亦产亨亩享京亭亮亲亳亵人亻亿什仁仂仃仄仅仆仇仉今介仍从仑仓仔仕他仗付仙仝仞仟仡代令以仨仪仫们仰仲仳仵件价任份仿企伉伊伍伎伏伐休众优伙会伛伞伟传伢伤伥伦伧伪伫伯估伲伴伶伸伺似伽佃但位低住佐佑体何佗佘余佚佛作佝佞佟你佣佤佥佧佩佬佯佰佳佴佶佻佼佾使侃侄侈侉例侍侏侑侔侗供依侠侣侥侦侧侨侩侪侬侮侯侵便促俄俅俊俎俏俐俑俗俘俚俜保俞俟信俣俦俨俩俪俭修俯俱俳俸俺俾倌倍倏倒倔倘候倚倜借倡倥倦倨倩倪倬倭倮债值倾偃假偈偌偎偏偕做停健偬偶偷偻偾偿傀傅傈傍傣傥傧储傩催傲傺傻像僖僚僦僧僬僭僮僳僵僻儆儇儋儒儡儿兀允元兄充兆先光克免兑兔兕兖党兜兢入全八公六兮兰共关兴兵其具典兹养兼兽冀冁冂内冈冉册再冒冕冖冗写军农冠冢冤冥冫冬冯冰冱冲决况冶冷冻冼冽净凄准凇凉凋凌减凑凛凝几凡凤凫凭凯凰凳凵凶凸凹出击凼函凿刀刁刂刃分切刈刊刍刎刑划刖列刘则刚创初删判刨利别刭刮到刳制刷券刹刺刻刽刿剀剁剂剃削剌前剐剑剔剖剜剞剡剥剧剩剪副割剽剿劁劂劈劐劓力劝办功加务劢劣动助努劫劬劭励劲劳劾势勃勇勉勋勐勒勖勘募勤勰勹勺勾勿匀包匆匈匍匏匐匕化北匙匚匝匠匡匣匦匪匮匹区医匾匿十千卅升午卉半华协卑卒卓单卖南博卜卞卟占卡卢卣卤卦卧卩卫卮卯印危即却卵卷卸卺卿厂厄厅历厉压厌厍厕厘厚厝原厢厣厥厦厨厩厮厶去县叁参又叉及友双反发叔取受变叙叛叟叠口古句另叨叩只叫召叭叮可台叱史右叵叶号司叹叻叼叽吁吃各吆合吉吊同名后吏吐向吒吓吕吖吗君吝吞吟吠吡吣否吧吨吩含听吭吮启吱吲吴吵吸吹吻吼吾呀呃呆呈告呋呐呒呓呔呕呖呗员呙呛呜呢呤呦周呱呲味呵呶呷呸呻呼命咀咂咄咆咋和咎咏咐咒咔咕咖咙咚咛咝咣咤咦咧咨咩咪咫咬咭咯咱咳咴咸咻咽咿哀品哂哄哆哇哈哉哌响哎哏哐哑哒哓哔哕哗哙哚哜哝哞哟哥哦哧哨哩哪哭哮哲哳哺哼哽哿唁唆唇唉唏唐唑唔唛唠唢唣唤唧唪唬售唯唰唱唳唷唼唾唿啁啃啄商啉啊啐啕啖啜啡啤啥啦啧啪啬啭啮啰啵啶啷啸啻啼啾喀喁喂喃善喇喈喉喊喋喏喑喔喘喙喜喝喟喧喰喱喳喵喷喹喻喽喾嗄嗅嗉嗌嗍嗑嗒嗓嗔嗖嗜嗝嗟嗡嗣嗤嗥嗦嗨嗪嗫嗬嗯嗲嗳嗵嗷嗽嗾嘀嘁嘈嘉嘌嘎嘏嘘嘛嘞嘟嘣嘤嘧嘬嘭嘱嘲嘴嘶嘹嘻嘿噌噍噎噔噗噘噙噜噢噤器噩噪噫噬噱噶噻噼嚅嚆嚎嚏嚓嚣嚯嚷嚼囊囔囗囚四囝回囟因囡团囤囫园困囱围囵囹固国图囿圃圄圆圈圉圊圜土圣在圩圪圬圭圮圯地圳圹场圻圾址坂均坊坌坍坎坏坐坑块坚坛坜坝坞坟坠坡坤坦坨坩坪坫坭坯坳坶坷坻坼垂垃垄垅垆型垌垒垓垛垠垡垢垣垤垦垧垩垫垭垮垲垴垸埂埃埋城埏埒埔埕埘埙埚埝域埠埤埭埯埴埸培基埽堀堂堆堇堋堍堑堕堙堞堠堡堤堪堰堵塄塌塍塑塔塘塞塥填塬塾墀墁境墅墉墒墓墙墚增墟墨墩墼壁壅壑壕壤士壬壮声壳壶壹夂处备复夏夔夕外夙多夜够夤夥大天太夫夭央夯失头夷夸夹夺夼奁奂奄奇奈奉奋奎奏契奔奕奖套奘奚奠奢奥女奴奶奸她好妁如妃妄妆妇妈妊妍妒妓妖妗妙妞妣妤妥妨妩妪妫妮妯妲妹妻妾姆姊始姐姑姒姓委姗姘姚姜姝姣姥姨姬姹姻姿威娃娄娅娆娇娈娉娌娑娓娘娜娟娠娣娥娩娱娲娴娶娼婀婆婉婊婕婚婢婧婪婴婵婶婷婺婿媒媚媛媪媲媳媵媸媾嫁嫂嫉嫌嫒嫔嫖嫘嫜嫠嫡嫣嫦嫩嫫嫱嬉嬖嬗嬲嬴嬷孀子孑孓孔孕字存孙孚孛孜孝孟孢季孤孥学孩孪孬孰孱孳孵孺孽宀宁它宄宅宇守安宋完宏宓宕宗官宙定宛宜宝实宠审客宣室宥宦宪宫宰害宴宵家宸容宽宾宿寂寄寅密寇富寐寒寓寝寞察寡寤寥寨寮寰寸对寺寻导寿封射将尉尊小少尔尕尖尘尚尜尝尢尤尥尧尬就尴尸尹尺尻尼尽尾尿局屁层居屈屉届屋屎屏屐屑展屙属屠屡屣履屦屮屯山屹屺屿岁岂岈岌岍岐岑岔岖岗岘岙岚岛岜岢岣岩岫岬岭岱岳岵岷岸岽岿峁峄峋峒峙峡峤峥峦峨峪峭峰峻崂崃崆崇崎崔崖崛崞崤崦崧崩崭崮崴崽崾嵇嵊嵋嵌嵘嵛嵝嵩嵫嵬嵯嵴嶂嶙嶝嶷巅巍巛川州巡巢工左巧巨巩巫差巯己已巳巴巷巽巾币市布帅帆师希帏帐帑帔帕帖帘帙帚帛帜帝带帧席帮帱帷常帻帼帽幂幄幅幌幔幕幛幞幡幢干平年并幸幺幻幼幽广庀庄庆庇床庋序庐庑库应底庖店庙庚府庞废庠庥度座庭庳庵庶康庸庹庾廉廊廑廒廓廖廛廨廪廴延廷建廾廿开弁异弃弄弈弊弋式弑弓引弗弘弛弟张弥弦弧弩弪弭弯弱弹强弼彀彐归当录彖彗彘彝彡形彤彦彩彪彬彭彰影彳彷役彻彼往征徂径待徇很徉徊律後徐徒徕得徘徙徜御徨循徭微徵德徼徽心忄必忆忉忌忍忏忐忑忒忖志忘忙忝忠忡忤忧忪快忭忮忱念忸忻忽忾忿怀态怂怃怄怅怆怊怍怎怏怒怔怕怖怙怛怜思怠怡急怦性怨怩怪怫怯怵总怼怿恁恂恃恋恍恐恒恕恙恚恝恢恣恤恧恨恩恪恫恬恭息恰恳恶恸恹恺恻恼恽恿悃悄悉悌悍悒悔悖悚悛悝悟悠患悦您悫悬悭悯悱悲悴悸悻悼情惆惊惋惑惕惘惚惜惝惟惠惦惧惨惩惫惬惭惮惯惰想惴惶惹惺愀愁愆愈愉愍愎意愕愚感愠愣愤愦愧愫愿慈慊慌慎慑慕慝慢慧慨慰慵慷憋憎憔憝憧憨憩憬憷憾懂懈懊懋懑懒懔懦懵懿戆戈戊戋戌戍戎戏成我戒戕或戗战戚戛戟戡戢戤戥截戬戮戳戴户戽戾房所扁扃扇扈扉手扌才扎扑扒打扔托扛扣扦执扩扪扫扬扭扮扯扰扳扶批扼找承技抄抉把抑抒抓投抖抗折抚抛抟抠抡抢护报抨披抬抱抵抹抻押抽抿拂拄担拆拇拈拉拊拌拍拎拐拒拓拔拖拗拘拙拚招拜拟拢拣拥拦拧拨择括拭拮拯拱拳拴拶拷拼拽拾拿持挂指挈按挎挑挖挚挛挝挞挟挠挡挢挣挤挥挨挪挫振挲挹挺挽捂捃捅捆捉捋捌捍捎捏捐捕捞损捡换捣捧捩捭据捱捶捷捺捻掀掂掇授掉掊掌掎掏掐排掖掘掠探掣接控推掩措掬掭掮掰掳掴掷掸掺掼掾揄揆揉揍揎描提插揖揞揠握揣揩揪揭揲援揶揸揽揿搀搁搂搅搋搌搏搐搓搔搛搜搞搠搡搦搪搬搭搴携搽搿摁摄摅摆摇摈摊摒摔摘摞摧摩摭摸摹摺撂撄撅撇撑撒撕撖撙撞撤撩撬播撮撰撵撷撸撺撼擀擂擅操擎擐擒擗擘擞擢擤擦攀攉攒攘攥攫攮支攴攵收攸改攻放政故效敉敌敏救敕敖教敛敝敞敢散敦敫敬数敲整敷文斋斌斐斑斓斗料斛斜斟斡斤斥斧斩斫断斯新方於施旁旃旄旅旆旋旌旎族旒旖旗无既日旦旧旨早旬旭旮旯旰旱时旷旺昀昂昃昆昊昌明昏易昔昕昙昝星映春昧昨昭是昱昴昵昶昼显晁晃晋晌晏晒晓晔晕晖晗晚晟晡晤晦晨普景晰晴晶晷智晾暂暄暇暌暑暖暗暝暧暨暮暴暹暾曙曛曜曝曦曩曰曲曳更曷曹曼曾替最月有朊朋服朐朔朕朗望朝期朦木未末本札术朱朴朵机朽杀杂权杆杈杉杌李杏材村杓杖杜杞束杠条来杨杩杪杭杯杰杲杳杵杷杼松板极构枇枉枋析枕林枘枚果枝枞枢枣枥枧枨枪枫枭枯枰枳枵架枷枸柁柃柄柏某柑柒染柔柘柙柚柜柝柞柠柢查柩柬柯柰柱柳柴柽柿栀栅标栈栉栊栋栌栎栏树栓栖栗栝校栩株栲栳样核根格栽栾桀桁桂桃桄桅框案桉桊桌桎桐桑桓桔桕桠桡桢档桤桥桦桧桨桩桫桴桶桷梁梃梅梆梏梓梗梢梦梧梨梭梯械梳梵检棂棉棋棍棒棕棘棚棠棣森棰棱棵棹棺棼椁椅椋植椎椐椒椟椠椤椭椰椴椹椽椿楂楔楗楚楝楞楠楣楦楫楮楱楷楸楹楼榀概榄榆榇榈榉榍榔榕榘榛榜榧榨榫榭榱榴榷榻槁槊槌槎槐槔槛槟槠槭槲槽槿樊樗樘樟模樨横樯樱樵樽樾橄橇橐橘橙橛橡橥橱橹橼檀檄檎檐檑檗檠檩檫檬欠次欢欣欤欧欲欷欹欺款歃歆歇歉歌歙止正此步武歧歪歹死歼殁殂殃殄殆殇殉殊残殍殒殓殖殚殛殡殪殳殴段殷殿毁毂毅毋母每毒毓比毕毖毗毙毛毡毪毫毯毳毵毹毽氅氆氇氍氏氐民氓气氕氖氘氙氚氛氟氡氢氤氦氧氨氩氪氮氯氰氲水氵永氽汀汁求汆汇汉汊汐汔汕汗汛汜汝汞江池污汤汨汩汪汰汲汴汶汹汽汾沁沂沃沅沆沈沉沌沏沐沓沔沙沛沟没沣沤沥沦沧沩沪沫沭沮沱沲河沸油治沼沽沾沿泄泅泉泊泌泐泓泔法泖泗泛泞泠泡波泣泥注泪泫泮泯泰泱泳泵泶泷泸泺泻泼泽泾洁洄洇洋洌洎洒洗洙洚洛洞津洧洪洫洮洱洲洳洵洹活洼洽派流浃浅浆浇浈浊测浍济浏浑浒浓浔浙浚浜浞浠浣浦浩浪浮浯浴海浸浼涂涅消涉涌涎涑涓涔涕涛涝涞涟涠涡涣涤润涧涨涩涪涫涮涯液涵涸涿淀淄淅淆淇淋淌淑淖淘淙淝淞淠淡淤淦淫淬淮深淳混淹添淼清渊渌渍渎渐渑渔渖渗渚渝渠渡渣渤渥温渫渭港渲渴游渺湃湄湍湎湓湔湖湘湛湟湫湮湾湿溃溅溆溉溏源溘溜溟溢溥溧溪溯溱溲溴溶溷溺溻溽滁滂滇滋滏滑滓滔滕滗滚滞滟滠满滢滤滥滦滨滩滴滹漂漆漉漏漓演漕漠漤漩漪漫漭漯漱漳漶漾潆潇潋潍潘潜潞潢潦潭潮潲潴潸潺潼澄澈澉澌澍澎澜澡澧澳澶澹激濂濉濑濒濞濠濡濮濯瀑瀚瀛瀣瀵瀹灌灏灞火灬灭灯灰灵灶灸灼灾灿炀炅炉炊炎炒炔炕炖炙炜炝炫炬炭炮炯炱炳炷炸点炻炼炽烀烁烂烃烈烊烘烙烛烟烤烦烧烨烩烫烬热烯烷烹烽焉焊焐焓焕焖焘焙焚焦焯焰焱然煅煊煌煎煜煞煤煦照煨煮煲煳煸煺煽熄熊熏熔熘熙熟熠熨熬熳熵熹燃燎燔燕燠燥燧燮燹爆爝爨爪爬爰爱爵父爷爸爹爻爽爿片版牌牍牒牖牙牛牝牟牡牢牦牧物牮牯牲牵特牺牾牿犀犁犄犊犋犍犏犒犟犬犭犯犰犴状犷犸犹狁狂狃狄狈狍狎狐狒狗狙狞狠狡狨狩独狭狮狯狰狱狲狳狴狷狸狺狻狼猁猃猊猎猓猕猖猗猛猜猝猞猡猢猥猩猪猫猬献猱猴猷猸猹猾猿獍獐獒獗獠獬獭獯獾玄率玉王玎玑玖玛玟玢玩玫玮环现玲玳玷玺玻珀珂珈珉珊珍珏珐珑珙珞珠珥珧珩班珲球琅理琉琊琏琐琚琛琢琥琦琨琪琬琮琰琳琴琵琶琼瑁瑕瑗瑙瑚瑛瑜瑞瑟瑭瑰瑶瑷瑾璀璁璃璇璋璎璐璜璞璧璨璩璺瓒瓜瓞瓠瓢瓣瓤瓦瓮瓯瓴瓶瓷瓿甄甍甏甑甓甘甙甚甜生甥用甩甫甬甭甯田由甲申电男甸町画甾畀畅畈畋界畎畏畔留畚畛畜略畦番畲畴畸畹畿疃疆疋疏疑疒疔疖疗疙疚疝疟疠疡疣疤疥疫疬疮疯疰疱疲疳疴疵疸疹疼疽疾痂痃痄病症痈痉痊痍痒痔痕痖痘痛痞痢痣痤痦痧痨痪痫痰痱痴痹痼痿瘀瘁瘃瘅瘊瘌瘐瘕瘗瘘瘙瘛瘟瘠瘢瘤瘥瘦瘩瘪瘫瘭瘰瘳瘴瘵瘸瘼瘾瘿癀癃癌癍癔癖癜癞癣癫癯癸登白百皂的皆皇皈皋皎皑皓皖皙皤皮皱皲皴皿盂盅盆盈益盍盎盏盐监盒盔盖盗盘盛盟盥目盯盱盲直相盹盼盾省眄眇眈眉看眍眙眚真眠眢眦眨眩眭眯眵眶眷眸眺眼着睁睃睇睐睑睚睛睡睢督睥睦睨睫睬睹睽睾睿瞀瞄瞅瞌瞍瞎瞑瞒瞟瞠瞢瞥瞧瞩瞪瞬瞭瞰瞳瞵瞻瞽瞿矍矗矛矜矢矣知矧矩矫矬短矮石矶矸矽矾矿砀码砂砉砌砍砑砒研砖砗砘砚砜砝砟砣砥砦砧砩砬砭砰破砷砸砹砺砻砼砾础硅硇硌硎硐硒硕硖硗硝硪硫硬硭确硷硼碇碉碌碍碎碑碓碗碘碚碛碜碟碡碣碥碧碰碱碲碳碴碹碾磁磅磉磊磋磐磔磕磙磨磬磲磴磷磺礁礅礓礞礤礴示礻礼社祀祁祆祈祉祓祖祗祚祛祜祝神祟祠祢祥祧票祭祯祷祸祺禀禁禄禅禊福禚禧禳禹禺离禽禾秀私秃秆秉秋种科秒秕秘租秣秤秦秧秩秫秭积称秸移秽稀稂稃稆程稍税稔稗稚稞稠稣稳稷稹稻稼稽稿穆穑穗穰穴究穷穸穹空穿窀突窃窄窆窈窍窑窒窕窖窗窘窜窝窟窠窥窦窨窬窭窳窿立竖站竞竟章竣童竦竭端竹竺竽竿笃笄笆笈笊笋笏笑笔笕笙笛笞笠笤笥符笨笪笫第笮笱笳笸笺笼笾筅筇等筋筌筏筐筑筒答策筘筚筛筝筠筢筮筱筲筵筷筹筻签简箅箍箐箔箕算箜箝管箢箦箧箨箩箪箫箬箭箱箴箸篁篆篇篌篑篓篙篚篝篡篥篦篪篮篱篷篼篾簇簋簌簏簖簟簦簧簪簸簿籀籁籍米籴类籼籽粉粑粒粕粗粘粜粝粞粟粢粤粥粪粮粱粲粳粹粼粽精糁糅糇糈糊糌糍糕糖糗糙糜糟糠糨糯糸系紊素索紧紫累絮絷綦綮縻繁繇纂纛纟纠纡红纣纤纥约级纨纩纪纫纬纭纯纰纱纲纳纵纶纷纸纹纺纽纾线绀绁绂练组绅细织终绉绊绋绌绍绎经绐绑绒结绔绕绗绘给绚绛络绝绞统绠绡绢绣绥绦继绨绩绪绫续绮绯绰绱绲绳维绵绶绷绸绺绻综绽绾绿缀缁缂缃缄缅缆缇缈缉缋缌缍缎缏缑缒缓缔缕编缗缘缙缚缛缜缝缟缠缡缢缣缤缥缦缧缨缩缪缫缬缭缮缯缰缱缲缳缴缵缶缸缺罂罄罅罐网罔罕罗罘罚罟罡罢罨罩罪置罱署罴罹罾羁羊羌美羔羚羝羞羟羡群羧羯羰羲羸羹羼羽羿翁翅翊翌翎翔翕翘翟翠翡翥翦翩翮翰翱翳翻翼耀老考耄者耆耋而耍耐耒耔耕耖耗耘耙耜耠耢耥耦耧耨耩耪耱耳耵耶耷耸耻耽耿聂聃聆聊聋职聍聒联聘聚聩聪聱聿肀肃肄肆肇肉肋肌肓肖肘肚肛肜肝肟肠股肢肤肥肩肪肫肭肮肯肱育肴肷肺肼肽肾肿胀胁胂胃胄胆背胍胎胖胗胙胚胛胜胝胞胡胤胥胧胨胩胪胫胬胭胯胰胱胲胳胴胶胸胺胼能脂脆脉脊脍脎脏脐脑脒脓脔脖脘脚脞脬脯脱脲脶脸脾腆腈腊腋腌腐腑腓腔腕腙腚腠腥腧腩腭腮腰腱腴腹腺腻腼腽腾腿膀膂膈膊膏膑膘膛膜膝膣膦膨膪膳膺膻臀臁臂臃臆臊臌臣臧自臬臭至致臻臼臾舀舁舂舄舅舆舌舍舐舒舔舛舜舞舟舡舢舣舨航舫般舭舯舰舱舳舴舵舶舷舸船舻舾艄艇艉艋艏艘艚艟艨艮良艰色艳艴艹艺艽艾艿节芄芈芊芋芍芎芏芑芒芗芘芙芜芝芟芡芤芥芦芨芩芪芫芬芭芮芯芰花芳芴芷芸芹芽芾苁苄苇苈苊苋苌苍苎苏苑苒苓苔苕苗苘苛苜苞苟苠苡苣苤若苦苫苯英苴苷苹苻茁茂范茄茅茆茇茈茉茌茎茏茑茔茕茗茚茛茜茧茨茫茬茭茯茱茳茴茵茶茸茹茺茼荀荃荆荇草荏荐荑荒荔荚荛荜荞荟荠荡荣荤荥荦荧荨荩荪荫荬荭荮药荷荸荻荼荽莅莆莉莎莒莓莘莛莜莞莠莨莩莪莫莰莱莲莳莴莶获莸莹莺莼莽菀菁菅菇菊菌菏菔菖菘菜菝菟菠菡菥菩菪菰菱菲菸菹菽萁萃萄萆萋萌萍萎萏萑萘萜萝萤营萦萧萨萱萸萼落葆葑著葙葚葛葜葡董葩葫葬葭葱葳葵葶葸葺蒂蒇蒈蒉蒋蒌蒎蒗蒙蒜蒡蒯蒲蒴蒸蒹蒺蒽蒿蓁蓄蓉蓊蓍蓐蓑蓓蓖蓝蓟蓠蓣蓥蓦蓬蓰蓼蓿蔌蔑蔓蔗蔚蔟蔡蔫蔬蔷蔸蔹蔺蔻蔼蔽蕃蕈蕉蕊蕖蕙蕞蕤蕨蕲蕴蕹蕺蕻蕾薄薅薇薏薛薜薤薨薪薮薯薰薷薹藁藉藏藐藓藕藜藤藩藻藿蘅蘑蘖蘧蘩蘸蘼虍虎虏虐虑虔虚虞虢虫虬虮虱虹虺虻虼虽虾虿蚀蚁蚂蚊蚋蚌蚍蚓蚕蚜蚝蚣蚤蚧蚨蚩蚪蚬蚯蚰蚱蚴蚵蚶蚺蛀蛄蛆蛇蛉蛊蛋蛎蛏蛐蛑蛔蛘蛙蛛蛞蛟蛤蛩蛭蛮蛰蛱蛲蛳蛴蛸蛹蛾蜀蜂蜃蜇蜈蜉蜊蜍蜒蜓蜕蜗蜘蜚蜜蜞蜡蜢蜣蜥蜩蜮蜱蜴蜷蜻蜾蜿蝇蝈蝉蝌蝎蝓蝗蝙蝠蝣蝤蝥蝮蝰蝴蝶蝻蝼蝽蝾螂螃螅螈螋融螓螗螟螨螫螬螭螯螳螵螺螽蟀蟆蟊蟋蟑蟒蟓蟛蟠蟥蟪蟮蟹蟾蠃蠊蠓蠕蠖蠛蠡蠢蠲蠹蠼血衄衅行衍衔街衙衡衢衣衤补表衩衫衬衮衰衲衷衽衾衿袁袂袄袅袈袋袍袒袖袜袢袤被袭袱袷袼裁裂装裆裉裎裒裔裕裘裙裟裢裣裤裥裨裰裱裳裴裸裹裼裾褂褊褐褒褓褙褚褛褡褥褪褫褰褴褶襁襄襞襟襦襻西要覃覆见观规觅视觇览觉觊觋觌觎觏觐觑角觖觚觜觞解觥触觫觯觳言訇訾詈詹誉誊誓謇謦警譬讠计订讣认讥讦讧讨让讪讫训议讯记讲讳讴讵讶讷许讹论讼讽设访诀证诂诃评诅识诈诉诊诋诌词诎诏译诒诓诔试诖诗诘诙诚诛诜话诞诟诠诡询诣诤该详诧诨诩诫诬语诮误诰诱诲诳说诵诶请诸诹诺读诼诽课诿谀谁谂调谄谅谆谇谈谊谋谌谍谎谏谐谑谒谓谔谕谖谗谘谙谚谛谜谝谟谠谡谢谣谤谥谦谧谨谩谪谫谬谭谮谯谰谱谲谳谴谵谶谷豁豆豇豉豌豕豚象豢豪豫豳豸豹豺貂貅貉貊貌貔貘贝贞负贡财责贤败账货质贩贪贫贬购贮贯贰贱贲贳贴贵贶贷贸费贺贻贼贽贾贿赀赁赂赃资赅赆赇赈赉赊赋赌赍赎赏赐赓赔赕赖赘赙赚赛赜赝赞赠赡赢赣赤赦赧赫赭走赳赴赵赶起趁趄超越趋趑趔趟趣趱足趴趵趸趺趼趾趿跃跄跆跋跌跎跏跑跖跗跚跛距跞跟跣跤跨跪跫跬路跳践跷跸跹跺跻跽踅踉踊踌踏踔踝踞踟踢踣踩踪踬踮踯踱踵踹踺踽蹀蹁蹂蹄蹇蹈蹉蹊蹋蹑蹒蹙蹦蹩蹬蹭蹯蹰蹲蹴蹶蹼蹿躁躅躇躏躐躔躜躞身躬躯躲躺軎车轧轨轩轫转轭轮软轰轱轲轳轴轵轶轷轸轹轺轻轼载轾轿辁辂较辄辅辆辇辈辉辊辋辍辎辏辐辑输辔辕辖辗辘辙辚辛辜辞辟辣辨辩辫辰辱辶边辽达迁迂迄迅过迈迎运近迓返迕还这进远违连迟迢迤迥迦迨迩迪迫迭迮述迳迷迸迹追退送适逃逄逅逆选逊逋逍透逐逑递途逖逗通逛逝逞速造逡逢逦逭逮逯逵逶逸逻逼逾遁遂遄遇遍遏遐遑遒道遗遘遛遢遣遥遨遭遮遴遵遽避邀邂邃邈邋邑邓邕邗邙邛邝邡邢那邦邪邬邮邯邰邱邳邴邵邶邸邹邺邻邾郁郄郅郇郊郎郏郐郑郓郗郛郜郝郡郢郦郧部郫郭郯郴郸都郾鄂鄄鄙鄞鄢鄣鄯鄱鄹酃酆酉酊酋酌配酎酏酐酒酗酚酝酞酡酢酣酤酥酩酪酬酮酯酰酱酲酴酵酶酷酸酹酽酾酿醅醇醉醋醌醍醐醑醒醚醛醢醣醪醭醮醯醴醵醺采釉释里重野量金釜鉴銎銮鋈錾鍪鎏鏊鏖鐾鑫钅钆钇针钉钊钋钌钍钎钏钐钒钓钔钕钗钙钚钛钜钝钞钟钠钡钢钣钤钥钦钧钨钩钪钫钬钭钮钯钰钱钲钳钴钵钶钷钸钹钺钻钼钽钾钿铀铁铂铃铄铅铆铈铉铊铋铌铍铎铐铑铒铕铖铗铘铙铛铜铝铞铟铠铡铢铣铤铥铧铨铩铪铫铬铭铮铯铰铱铲铳铴铵银铷铸铹铺铼铽链铿销锁锂锃锄锅锆锇锈锉锊锋锌锍锎锏锐锑锒锓锔锕锖锗锘错锚锛锝锞锟锡锢锣锤锥锦锨锩锪锫锬锭键锯锰锱锲锴锵锶锷锸锹锺锻锼锾锿镀镁镂镄镅镆镇镉镊镌镍镎镏镐镑镒镓镔镖镗镘镙镛镜镝镞镟镡镢镣镤镥镦镧镨镩镪镫镬镭镯镰镱镲镳镶长门闩闪闫闭问闯闰闱闲闳间闵闶闷闸闹闺闻闼闽闾阀阁阂阃阄阅阆阈阉阊阋阌阍阎阏阐阑阒阔阕阖阗阙阚阜阝队阡阢阪阮阱防阳阴阵阶阻阼阽阿陀陂附际陆陇陈陉陋陌降限陔陕陛陟陡院除陧陨险陪陬陲陴陵陶陷隅隆隈隋隍随隐隔隗隘隙障隧隰隳隶隹隼隽难雀雁雄雅集雇雉雌雍雎雏雒雕雠雨雩雪雯雳零雷雹雾需霁霄霆震霈霉霍霎霏霓霖霜霞霪霭霰露霸霹霾青靓靖静靛非靠靡面靥革靳靴靶靼鞅鞋鞍鞑鞒鞔鞘鞠鞣鞫鞭鞯鞲鞴韦韧韩韪韫韬韭音韵韶页顶顷顸项顺须顼顽顾顿颀颁颂颃预颅领颇颈颉颊颌颍颏颐频颓颔颖颗题颚颛颜额颞颟颠颡颢颤颥颦颧风飑飒飓飕飘飙飚飞食飧飨餍餐餮饔饕饣饥饧饨饩饪饫饬饭饮饯饰饱饲饴饵饶饷饺饼饽饿馀馁馄馅馆馇馈馊馋馍馏馐馑馒馓馔馕首馗馘香馥馨马驭驮驯驰驱驳驴驵驶驷驸驹驺驻驼驽驾驿骀骁骂骄骅骆骇骈骊骋验骏骐骑骒骓骖骗骘骚骛骜骝骞骟骠骡骢骣骤骥骧骨骰骱骶骷骸骺骼髀髁髂髅髋髌髑髓高髟髡髦髫髭髯髹髻鬃鬈鬏鬓鬟鬣鬯鬲鬻鬼魁魂魃魄魅魇魈魉魍魏魑魔鱼鱿鲁鲂鲅鲆鲇鲈鲋鲍鲎鲐鲑鲒鲔鲕鲚鲛鲜鲞鲟鲠鲡鲢鲣鲤鲥鲦鲧鲨鲩鲫鲭鲮鲰鲱鲲鲳鲴鲵鲶鲷鲸鲺鲻鲼鲽鳃鳄鳅鳆鳇鳊鳋鳌鳍鳎鳏鳐鳓鳔鳕鳖鳗鳘鳙鳜鳝鳞鳟鳢鸟鸠鸡鸢鸣鸥鸦鸨鸩鸪鸫鸬鸭鸯鸱鸲鸳鸵鸶鸷鸸鸹鸺鸽鸾鸿鹁鹂鹃鹄鹅鹆鹇鹈鹉鹊鹋鹌鹎鹏鹑鹕鹗鹘鹚鹛鹜鹞鹣鹤鹦鹧鹨鹩鹪鹫鹬鹭鹰鹱鹳鹾鹿麂麇麈麋麒麓麝麟麦麴麸麻麽麾黄黉黍黎黏黑黔默黛黜黝黟黠黢黥黧黩黪黯黹黻黼黾鼋鼍鼎鼐鼓鼗鼙鼠鼢鼬鼯鼷鼹鼻鼽鼾齄齐齑齿龀龃龄龅龆龇龈龉龊龋龌龙龚龛龟龠 \ No newline at end of file + !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんゔァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶヷヸヹヺ一丁七万丈三上下丌不与丐丑专且丕世丘丙业丛东丝丞丢两严丧丨个丫丬中丰串临丶丸丹为主丽举丿乃久乇么义之乌乍乎乏乐乒乓乔乖乘乙乜九乞也习乡书乩买乱乳乾了予争事二亍于亏云互亓五井亘亚些亟亠亡亢交亥亦产亨亩享京亭亮亲亳亵人亻亿什仁仂仃仄仅仆仇仉今介仍从仑仓仔仕他仗付仙仝仞仟仡代令以仨仪仫们仰仲仳仵件价任份仿企伉伊伍伎伏伐休众优伙会伛伞伟传伢伤伥伦伧伪伫伯估伲伴伶伸伺似伽佃但位低住佐佑体何佗佘余佚佛作佝佞佟你佣佤佥佧佩佬佯佰佳佴佶佻佼佾使侃侄侈侉例侍侏侑侔侗供依侠侣侥侦侧侨侩侪侬侮侯侵便促俄俅俊俎俏俐俑俗俘俚俜保俞俟信俣俦俨俩俪俭修俯俱俳俸俺俾倌倍倏倒倔倘候倚倜借倡倥倦倨倩倪倬倭倮债值倾偃假偈偌偎偏偕做停健偬偶偷偻偾偿傀傅傈傍傣傥傧储傩催傲傺傻像僖僚僦僧僬僭僮僳僵僻儆儇儋儒儡儿兀允元兄充兆先光克免兑兔兕兖党兜兢入全八公六兮兰共关兴兵其具典兹养兼兽冀冁冂内冈冉册再冒冕冖冗写军农冠冢冤冥冫冬冯冰冱冲决况冶冷冻冼冽净凄准凇凉凋凌减凑凛凝几凡凤凫凭凯凰凳凵凶凸凹出击凼函凿刀刁刂刃分切刈刊刍刎刑划刖列刘则刚创初删判刨利别刭刮到刳制刷券刹刺刻刽刿剀剁剂剃削剌前剐剑剔剖剜剞剡剥剧剩剪副割剽剿劁劂劈劐劓力劝办功加务劢劣动助努劫劬劭励劲劳劾势勃勇勉勋勐勒勖勘募勤勰勹勺勾勿匀包匆匈匍匏匐匕化北匙匚匝匠匡匣匦匪匮匹区医匾匿十千卅升午卉半华协卑卒卓单卖南博卜卞卟占卡卢卣卤卦卧卩卫卮卯印危即却卵卷卸卺卿厂厄厅历厉压厌厍厕厘厚厝原厢厣厥厦厨厩厮厶去县叁参又叉及友双反发叔取受变叙叛叟叠口古句另叨叩只叫召叭叮可台叱史右叵叶号司叹叻叼叽吁吃各吆合吉吊同名后吏吐向吒吓吕吖吗君吝吞吟吠吡吣否吧吨吩含听吭吮启吱吲吴吵吸吹吻吼吾呀呃呆呈告呋呐呒呓呔呕呖呗员呙呛呜呢呤呦周呱呲味呵呶呷呸呻呼命咀咂咄咆咋和咎咏咐咒咔咕咖咙咚咛咝咣咤咦咧咨咩咪咫咬咭咯咱咳咴咸咻咽咿哀品哂哄哆哇哈哉哌响哎哏哐哑哒哓哔哕哗哙哚哜哝哞哟哥哦哧哨哩哪哭哮哲哳哺哼哽哿唁唆唇唉唏唐唑唔唛唠唢唣唤唧唪唬售唯唰唱唳唷唼唾唿啁啃啄商啉啊啐啕啖啜啡啤啥啦啧啪啬啭啮啰啵啶啷啸啻啼啾喀喁喂喃善喇喈喉喊喋喏喑喔喘喙喜喝喟喧喰喱喳喵喷喹喻喽喾嗄嗅嗉嗌嗍嗑嗒嗓嗔嗖嗜嗝嗟嗡嗣嗤嗥嗦嗨嗪嗫嗬嗯嗲嗳嗵嗷嗽嗾嘀嘁嘈嘉嘌嘎嘏嘘嘛嘞嘟嘣嘤嘧嘬嘭嘱嘲嘴嘶嘹嘻嘿噌噍噎噔噗噘噙噜噢噤器噩噪噫噬噱噶噻噼嚅嚆嚎嚏嚓嚣嚯嚷嚼囊囔囗囚四囝回囟因囡团囤囫园困囱围囵囹固国图囿圃圄圆圈圉圊圜土圣在圩圪圬圭圮圯地圳圹场圻圾址坂均坊坌坍坎坏坐坑块坚坛坜坝坞坟坠坡坤坦坨坩坪坫坭坯坳坶坷坻坼垂垃垄垅垆型垌垒垓垛垠垡垢垣垤垦垧垩垫垭垮垲垴垸埂埃埋城埏埒埔埕埘埙埚埝域埠埤埭埯埴埸培基埽堀堂堆堇堋堍堑堕堙堞堠堡堤堪堰堵塄塌塍塑塔塘塞塥填塬塾墀墁境墅墉墒墓墙墚增墟墨墩墼壁壅壑壕壤士壬壮声壳壶壹夂处备复夏夔夕外夙多夜够夤夥大天太夫夭央夯失头夷夸夹夺夼奁奂奄奇奈奉奋奎奏契奔奕奖套奘奚奠奢奥女奴奶奸她好妁如妃妄妆妇妈妊妍妒妓妖妗妙妞妣妤妥妨妩妪妫妮妯妲妹妻妾姆姊始姐姑姒姓委姗姘姚姜姝姣姥姨姬姹姻姿威娃娄娅娆娇娈娉娌娑娓娘娜娟娠娣娥娩娱娲娴娶娼婀婆婉婊婕婚婢婧婪婴婵婶婷婺婿媒媚媛媪媲媳媵媸媾嫁嫂嫉嫌嫒嫔嫖嫘嫜嫠嫡嫣嫦嫩嫫嫱嬉嬖嬗嬲嬴嬷孀子孑孓孔孕字存孙孚孛孜孝孟孢季孤孥学孩孪孬孰孱孳孵孺孽宀宁它宄宅宇守安宋完宏宓宕宗官宙定宛宜宝实宠审客宣室宥宦宪宫宰害宴宵家宸容宽宾宿寂寄寅密寇富寐寒寓寝寞察寡寤寥寨寮寰寸对寺寻导寿封射将尉尊小少尔尕尖尘尚尜尝尢尤尥尧尬就尴尸尹尺尻尼尽尾尿局屁层居屈屉届屋屎屏屐屑展屙属屠屡屣履屦屮屯山屹屺屿岁岂岈岌岍岐岑岔岖岗岘岙岚岛岜岢岣岩岫岬岭岱岳岵岷岸岽岿峁峄峋峒峙峡峤峥峦峨峪峭峰峻崂崃崆崇崎崔崖崛崞崤崦崧崩崭崮崴崽崾嵇嵊嵋嵌嵘嵛嵝嵩嵫嵬嵯嵴嶂嶙嶝嶷巅巍巛川州巡巢工左巧巨巩巫差巯己已巳巴巷巽巾币市布帅帆师希帏帐帑帔帕帖帘帙帚帛帜帝带帧席帮帱帷常帻帼帽幂幄幅幌幔幕幛幞幡幢干平年并幸幺幻幼幽广庀庄庆庇床庋序庐庑库应底庖店庙庚府庞废庠庥度座庭庳庵庶康庸庹庾廉廊廑廒廓廖廛廨廪廴延廷建廾廿开弁异弃弄弈弊弋式弑弓引弗弘弛弟张弥弦弧弩弪弭弯弱弹强弼彀彐归当录彖彗彘彝彡形彤彦彩彪彬彭彰影彳彷役彻彼往征徂径待徇很徉徊律後徐徒徕得徘徙徜御徨循徭微徵德徼徽心忄必忆忉忌忍忏忐忑忒忖志忘忙忝忠忡忤忧忪快忭忮忱念忸忻忽忾忿怀态怂怃怄怅怆怊怍怎怏怒怔怕怖怙怛怜思怠怡急怦性怨怩怪怫怯怵总怼怿恁恂恃恋恍恐恒恕恙恚恝恢恣恤恧恨恩恪恫恬恭息恰恳恶恸恹恺恻恼恽恿悃悄悉悌悍悒悔悖悚悛悝悟悠患悦您悫悬悭悯悱悲悴悸悻悼情惆惊惋惑惕惘惚惜惝惟惠惦惧惨惩惫惬惭惮惯惰想惴惶惹惺愀愁愆愈愉愍愎意愕愚感愠愣愤愦愧愫愿慈慊慌慎慑慕慝慢慧慨慰慵慷憋憎憔憝憧憨憩憬憷憾懂懈懊懋懑懒懔懦懵懿戆戈戊戋戌戍戎戏成我戒戕或戗战戚戛戟戡戢戤戥截戬戮戳戴户戽戾房所扁扃扇扈扉手扌才扎扑扒打扔托扛扣扦执扩扪扫扬扭扮扯扰扳扶批扼找承技抄抉把抑抒抓投抖抗折抚抛抟抠抡抢护报抨披抬抱抵抹抻押抽抿拂拄担拆拇拈拉拊拌拍拎拐拒拓拔拖拗拘拙拚招拜拟拢拣拥拦拧拨择括拭拮拯拱拳拴拶拷拼拽拾拿持挂指挈按挎挑挖挚挛挝挞挟挠挡挢挣挤挥挨挪挫振挲挹挺挽捂捃捅捆捉捋捌捍捎捏捐捕捞损捡换捣捧捩捭据捱捶捷捺捻掀掂掇授掉掊掌掎掏掐排掖掘掠探掣接控推掩措掬掭掮掰掳掴掷掸掺掼掾揄揆揉揍揎描提插揖揞揠握揣揩揪揭揲援揶揸揽揿搀搁搂搅搋搌搏搐搓搔搛搜搞搠搡搦搪搬搭搴携搽搿摁摄摅摆摇摈摊摒摔摘摞摧摩摭摸摹摺撂撄撅撇撑撒撕撖撙撞撤撩撬播撮撰撵撷撸撺撼擀擂擅操擎擐擒擗擘擞擢擤擦攀攉攒攘攥攫攮支攴攵收攸改攻放政故效敉敌敏救敕敖教敛敝敞敢散敦敫敬数敲整敷文斋斌斐斑斓斗料斛斜斟斡斤斥斧斩斫断斯新方於施旁旃旄旅旆旋旌旎族旒旖旗无既日旦旧旨早旬旭旮旯旰旱时旷旺昀昂昃昆昊昌明昏易昔昕昙昝星映春昧昨昭是昱昴昵昶昼显晁晃晋晌晏晒晓晔晕晖晗晚晟晡晤晦晨普景晰晴晶晷智晾暂暄暇暌暑暖暗暝暧暨暮暴暹暾曙曛曜曝曦曩曰曲曳更曷曹曼曾替最月有朊朋服朐朔朕朗望朝期朦木未末本札术朱朴朵机朽杀杂权杆杈杉杌李杏材村杓杖杜杞束杠条来杨杩杪杭杯杰杲杳杵杷杼松板极构枇枉枋析枕林枘枚果枝枞枢枣枥枧枨枪枫枭枯枰枳枵架枷枸柁柃柄柏某柑柒染柔柘柙柚柜柝柞柠柢查柩柬柯柰柱柳柴柽柿栀栅标栈栉栊栋栌栎栏树栓栖栗栝校栩株栲栳样核根格栽栾桀桁桂桃桄桅框案桉桊桌桎桐桑桓桔桕桠桡桢档桤桥桦桧桨桩桫桴桶桷梁梃梅梆梏梓梗梢梦梧梨梭梯械梳梵检棂棉棋棍棒棕棘棚棠棣森棰棱棵棹棺棼椁椅椋植椎椐椒椟椠椤椭椰椴椹椽椿楂楔楗楚楝楞楠楣楦楫楮楱楷楸楹楼榀概榄榆榇榈榉榍榔榕榘榛榜榧榨榫榭榱榴榷榻槁槊槌槎槐槔槛槟槠槭槲槽槿樊樗樘樟模樨横樯樱樵樽樾橄橇橐橘橙橛橡橥橱橹橼檀檄檎檐檑檗檠檩檫檬欠次欢欣欤欧欲欷欹欺款歃歆歇歉歌歙止正此步武歧歪歹死歼殁殂殃殄殆殇殉殊残殍殒殓殖殚殛殡殪殳殴段殷殿毁毂毅毋母每毒毓比毕毖毗毙毛毡毪毫毯毳毵毹毽氅氆氇氍氏氐民氓气氕氖氘氙氚氛氟氡氢氤氦氧氨氩氪氮氯氰氲水氵永氽汀汁求汆汇汉汊汐汔汕汗汛汜汝汞江池污汤汨汩汪汰汲汴汶汹汽汾沁沂沃沅沆沈沉沌沏沐沓沔沙沛沟没沣沤沥沦沧沩沪沫沭沮沱沲河沸油治沼沽沾沿泄泅泉泊泌泐泓泔法泖泗泛泞泠泡波泣泥注泪泫泮泯泰泱泳泵泶泷泸泺泻泼泽泾洁洄洇洋洌洎洒洗洙洚洛洞津洧洪洫洮洱洲洳洵洹活洼洽派流浃浅浆浇浈浊测浍济浏浑浒浓浔浙浚浜浞浠浣浦浩浪浮浯浴海浸浼涂涅消涉涌涎涑涓涔涕涛涝涞涟涠涡涣涤润涧涨涩涪涫涮涯液涵涸涿淀淄淅淆淇淋淌淑淖淘淙淝淞淠淡淤淦淫淬淮深淳混淹添淼清渊渌渍渎渐渑渔渖渗渚渝渠渡渣渤渥温渫渭港渲渴游渺湃湄湍湎湓湔湖湘湛湟湫湮湾湿溃溅溆溉溏源溘溜溟溢溥溧溪溯溱溲溴溶溷溺溻溽滁滂滇滋滏滑滓滔滕滗滚滞滟滠满滢滤滥滦滨滩滴滹漂漆漉漏漓演漕漠漤漩漪漫漭漯漱漳漶漾潆潇潋潍潘潜潞潢潦潭潮潲潴潸潺潼澄澈澉澌澍澎澜澡澧澳澶澹激濂濉濑濒濞濠濡濮濯瀑瀚瀛瀣瀵瀹灌灏灞火灬灭灯灰灵灶灸灼灾灿炀炅炉炊炎炒炔炕炖炙炜炝炫炬炭炮炯炱炳炷炸点炻炼炽烀烁烂烃烈烊烘烙烛烟烤烦烧烨烩烫烬热烯烷烹烽焉焊焐焓焕焖焘焙焚焦焯焰焱然煅煊煌煎煜煞煤煦照煨煮煲煳煸煺煽熄熊熏熔熘熙熟熠熨熬熳熵熹燃燎燔燕燠燥燧燮燹爆爝爨爪爬爰爱爵父爷爸爹爻爽爿片版牌牍牒牖牙牛牝牟牡牢牦牧物牮牯牲牵特牺牾牿犀犁犄犊犋犍犏犒犟犬犭犯犰犴状犷犸犹狁狂狃狄狈狍狎狐狒狗狙狞狠狡狨狩独狭狮狯狰狱狲狳狴狷狸狺狻狼猁猃猊猎猓猕猖猗猛猜猝猞猡猢猥猩猪猫猬献猱猴猷猸猹猾猿獍獐獒獗獠獬獭獯獾玄率玉王玎玑玖玛玟玢玩玫玮环现玲玳玷玺玻珀珂珈珉珊珍珏珐珑珙珞珠珥珧珩班珲球琅理琉琊琏琐琚琛琢琥琦琨琪琬琮琰琳琴琵琶琼瑁瑕瑗瑙瑚瑛瑜瑞瑟瑭瑰瑶瑷瑾璀璁璃璇璋璎璐璜璞璧璨璩璺瓒瓜瓞瓠瓢瓣瓤瓦瓮瓯瓴瓶瓷瓿甄甍甏甑甓甘甙甚甜生甥用甩甫甬甭甯田由甲申电男甸町画甾畀畅畈畋界畎畏畔留畚畛畜略畦番畲畴畸畹畿疃疆疋疏疑疒疔疖疗疙疚疝疟疠疡疣疤疥疫疬疮疯疰疱疲疳疴疵疸疹疼疽疾痂痃痄病症痈痉痊痍痒痔痕痖痘痛痞痢痣痤痦痧痨痪痫痰痱痴痹痼痿瘀瘁瘃瘅瘊瘌瘐瘕瘗瘘瘙瘛瘟瘠瘢瘤瘥瘦瘩瘪瘫瘭瘰瘳瘴瘵瘸瘼瘾瘿癀癃癌癍癔癖癜癞癣癫癯癸登白百皂的皆皇皈皋皎皑皓皖皙皤皮皱皲皴皿盂盅盆盈益盍盎盏盐监盒盔盖盗盘盛盟盥目盯盱盲直相盹盼盾省眄眇眈眉看眍眙眚真眠眢眦眨眩眭眯眵眶眷眸眺眼着睁睃睇睐睑睚睛睡睢督睥睦睨睫睬睹睽睾睿瞀瞄瞅瞌瞍瞎瞑瞒瞟瞠瞢瞥瞧瞩瞪瞬瞭瞰瞳瞵瞻瞽瞿矍矗矛矜矢矣知矧矩矫矬短矮石矶矸矽矾矿砀码砂砉砌砍砑砒研砖砗砘砚砜砝砟砣砥砦砧砩砬砭砰破砷砸砹砺砻砼砾础硅硇硌硎硐硒硕硖硗硝硪硫硬硭确硷硼碇碉碌碍碎碑碓碗碘碚碛碜碟碡碣碥碧碰碱碲碳碴碹碾磁磅磉磊磋磐磔磕磙磨磬磲磴磷磺礁礅礓礞礤礴示礻礼社祀祁祆祈祉祓祖祗祚祛祜祝神祟祠祢祥祧票祭祯祷祸祺禀禁禄禅禊福禚禧禳禹禺离禽禾秀私秃秆秉秋种科秒秕秘租秣秤秦秧秩秫秭积称秸移秽稀稂稃稆程稍税稔稗稚稞稠稣稳稷稹稻稼稽稿穆穑穗穰穴究穷穸穹空穿窀突窃窄窆窈窍窑窒窕窖窗窘窜窝窟窠窥窦窨窬窭窳窿立竖站竞竟章竣童竦竭端竹竺竽竿笃笄笆笈笊笋笏笑笔笕笙笛笞笠笤笥符笨笪笫第笮笱笳笸笺笼笾筅筇等筋筌筏筐筑筒答策筘筚筛筝筠筢筮筱筲筵筷筹筻签简箅箍箐箔箕算箜箝管箢箦箧箨箩箪箫箬箭箱箴箸篁篆篇篌篑篓篙篚篝篡篥篦篪篮篱篷篼篾簇簋簌簏簖簟簦簧簪簸簿籀籁籍米籴类籼籽粉粑粒粕粗粘粜粝粞粟粢粤粥粪粮粱粲粳粹粼粽精糁糅糇糈糊糌糍糕糖糗糙糜糟糠糨糯糸系紊素索紧紫累絮絷綦綮縻繁繇纂纛纟纠纡红纣纤纥约级纨纩纪纫纬纭纯纰纱纲纳纵纶纷纸纹纺纽纾线绀绁绂练组绅细织终绉绊绋绌绍绎经绐绑绒结绔绕绗绘给绚绛络绝绞统绠绡绢绣绥绦继绨绩绪绫续绮绯绰绱绲绳维绵绶绷绸绺绻综绽绾绿缀缁缂缃缄缅缆缇缈缉缋缌缍缎缏缑缒缓缔缕编缗缘缙缚缛缜缝缟缠缡缢缣缤缥缦缧缨缩缪缫缬缭缮缯缰缱缲缳缴缵缶缸缺罂罄罅罐网罔罕罗罘罚罟罡罢罨罩罪置罱署罴罹罾羁羊羌美羔羚羝羞羟羡群羧羯羰羲羸羹羼羽羿翁翅翊翌翎翔翕翘翟翠翡翥翦翩翮翰翱翳翻翼耀老考耄者耆耋而耍耐耒耔耕耖耗耘耙耜耠耢耥耦耧耨耩耪耱耳耵耶耷耸耻耽耿聂聃聆聊聋职聍聒联聘聚聩聪聱聿肀肃肄肆肇肉肋肌肓肖肘肚肛肜肝肟肠股肢肤肥肩肪肫肭肮肯肱育肴肷肺肼肽肾肿胀胁胂胃胄胆背胍胎胖胗胙胚胛胜胝胞胡胤胥胧胨胩胪胫胬胭胯胰胱胲胳胴胶胸胺胼能脂脆脉脊脍脎脏脐脑脒脓脔脖脘脚脞脬脯脱脲脶脸脾腆腈腊腋腌腐腑腓腔腕腙腚腠腥腧腩腭腮腰腱腴腹腺腻腼腽腾腿膀膂膈膊膏膑膘膛膜膝膣膦膨膪膳膺膻臀臁臂臃臆臊臌臣臧自臬臭至致臻臼臾舀舁舂舄舅舆舌舍舐舒舔舛舜舞舟舡舢舣舨航舫般舭舯舰舱舳舴舵舶舷舸船舻舾艄艇艉艋艏艘艚艟艨艮良艰色艳艴艹艺艽艾艿节芄芈芊芋芍芎芏芑芒芗芘芙芜芝芟芡芤芥芦芨芩芪芫芬芭芮芯芰花芳芴芷芸芹芽芾苁苄苇苈苊苋苌苍苎苏苑苒苓苔苕苗苘苛苜苞苟苠苡苣苤若苦苫苯英苴苷苹苻茁茂范茄茅茆茇茈茉茌茎茏茑茔茕茗茚茛茜茧茨茫茬茭茯茱茳茴茵茶茸茹茺茼荀荃荆荇草荏荐荑荒荔荚荛荜荞荟荠荡荣荤荥荦荧荨荩荪荫荬荭荮药荷荸荻荼荽莅莆莉莎莒莓莘莛莜莞莠莨莩莪莫莰莱莲莳莴莶获莸莹莺莼莽菀菁菅菇菊菌菏菔菖菘菜菝菟菠菡菥菩菪菰菱菲菸菹菽萁萃萄萆萋萌萍萎萏萑萘萜萝萤营萦萧萨萱萸萼落葆葑著葙葚葛葜葡董葩葫葬葭葱葳葵葶葸葺蒂蒇蒈蒉蒋蒌蒎蒗蒙蒜蒡蒯蒲蒴蒸蒹蒺蒽蒿蓁蓄蓉蓊蓍蓐蓑蓓蓖蓝蓟蓠蓣蓥蓦蓬蓰蓼蓿蔌蔑蔓蔗蔚蔟蔡蔫蔬蔷蔸蔹蔺蔻蔼蔽蕃蕈蕉蕊蕖蕙蕞蕤蕨蕲蕴蕹蕺蕻蕾薄薅薇薏薛薜薤薨薪薮薯薰薷薹藁藉藏藐藓藕藜藤藩藻藿蘅蘑蘖蘧蘩蘸蘼虍虎虏虐虑虔虚虞虢虫虬虮虱虹虺虻虼虽虾虿蚀蚁蚂蚊蚋蚌蚍蚓蚕蚜蚝蚣蚤蚧蚨蚩蚪蚬蚯蚰蚱蚴蚵蚶蚺蛀蛄蛆蛇蛉蛊蛋蛎蛏蛐蛑蛔蛘蛙蛛蛞蛟蛤蛩蛭蛮蛰蛱蛲蛳蛴蛸蛹蛾蜀蜂蜃蜇蜈蜉蜊蜍蜒蜓蜕蜗蜘蜚蜜蜞蜡蜢蜣蜥蜩蜮蜱蜴蜷蜻蜾蜿蝇蝈蝉蝌蝎蝓蝗蝙蝠蝣蝤蝥蝮蝰蝴蝶蝻蝼蝽蝾螂螃螅螈螋融螓螗螟螨螫螬螭螯螳螵螺螽蟀蟆蟊蟋蟑蟒蟓蟛蟠蟥蟪蟮蟹蟾蠃蠊蠓蠕蠖蠛蠡蠢蠲蠹蠼血衄衅行衍衔街衙衡衢衣衤补表衩衫衬衮衰衲衷衽衾衿袁袂袄袅袈袋袍袒袖袜袢袤被袭袱袷袼裁裂装裆裉裎裒裔裕裘裙裟裢裣裤裥裨裰裱裳裴裸裹裼裾褂褊褐褒褓褙褚褛褡褥褪褫褰褴褶襁襄襞襟襦襻西要覃覆见观规觅视觇览觉觊觋觌觎觏觐觑角觖觚觜觞解觥触觫觯觳言訇訾詈詹誉誊誓謇謦警譬讠计订讣认讥讦讧讨让讪讫训议讯记讲讳讴讵讶讷许讹论讼讽设访诀证诂诃评诅识诈诉诊诋诌词诎诏译诒诓诔试诖诗诘诙诚诛诜话诞诟诠诡询诣诤该详诧诨诩诫诬语诮误诰诱诲诳说诵诶请诸诹诺读诼诽课诿谀谁谂调谄谅谆谇谈谊谋谌谍谎谏谐谑谒谓谔谕谖谗谘谙谚谛谜谝谟谠谡谢谣谤谥谦谧谨谩谪谫谬谭谮谯谰谱谲谳谴谵谶谷豁豆豇豉豌豕豚象豢豪豫豳豸豹豺貂貅貉貊貌貔貘贝贞负贡财责贤败账货质贩贪贫贬购贮贯贰贱贲贳贴贵贶贷贸费贺贻贼贽贾贿赀赁赂赃资赅赆赇赈赉赊赋赌赍赎赏赐赓赔赕赖赘赙赚赛赜赝赞赠赡赢赣赤赦赧赫赭走赳赴赵赶起趁趄超越趋趑趔趟趣趱足趴趵趸趺趼趾趿跃跄跆跋跌跎跏跑跖跗跚跛距跞跟跣跤跨跪跫跬路跳践跷跸跹跺跻跽踅踉踊踌踏踔踝踞踟踢踣踩踪踬踮踯踱踵踹踺踽蹀蹁蹂蹄蹇蹈蹉蹊蹋蹑蹒蹙蹦蹩蹬蹭蹯蹰蹲蹴蹶蹼蹿躁躅躇躏躐躔躜躞身躬躯躲躺軎车轧轨轩轫转轭轮软轰轱轲轳轴轵轶轷轸轹轺轻轼载轾轿辁辂较辄辅辆辇辈辉辊辋辍辎辏辐辑输辔辕辖辗辘辙辚辛辜辞辟辣辨辩辫辰辱辶边辽达迁迂迄迅过迈迎运近迓返迕还这进远违连迟迢迤迥迦迨迩迪迫迭迮述迳迷迸迹追退送适逃逄逅逆选逊逋逍透逐逑递途逖逗通逛逝逞速造逡逢逦逭逮逯逵逶逸逻逼逾遁遂遄遇遍遏遐遑遒道遗遘遛遢遣遥遨遭遮遴遵遽避邀邂邃邈邋邑邓邕邗邙邛邝邡邢那邦邪邬邮邯邰邱邳邴邵邶邸邹邺邻邾郁郄郅郇郊郎郏郐郑郓郗郛郜郝郡郢郦郧部郫郭郯郴郸都郾鄂鄄鄙鄞鄢鄣鄯鄱鄹酃酆酉酊酋酌配酎酏酐酒酗酚酝酞酡酢酣酤酥酩酪酬酮酯酰酱酲酴酵酶酷酸酹酽酾酿醅醇醉醋醌醍醐醑醒醚醛醢醣醪醭醮醯醴醵醺采釉释里重野量金釜鉴銎銮鋈錾鍪鎏鏊鏖鐾鑫钅钆钇针钉钊钋钌钍钎钏钐钒钓钔钕钗钙钚钛钜钝钞钟钠钡钢钣钤钥钦钧钨钩钪钫钬钭钮钯钰钱钲钳钴钵钶钷钸钹钺钻钼钽钾钿铀铁铂铃铄铅铆铈铉铊铋铌铍铎铐铑铒铕铖铗铘铙铛铜铝铞铟铠铡铢铣铤铥铧铨铩铪铫铬铭铮铯铰铱铲铳铴铵银铷铸铹铺铼铽链铿销锁锂锃锄锅锆锇锈锉锊锋锌锍锎锏锐锑锒锓锔锕锖锗锘错锚锛锝锞锟锡锢锣锤锥锦锨锩锪锫锬锭键锯锰锱锲锴锵锶锷锸锹锺锻锼锾锿镀镁镂镄镅镆镇镉镊镌镍镎镏镐镑镒镓镔镖镗镘镙镛镜镝镞镟镡镢镣镤镥镦镧镨镩镪镫镬镭镯镰镱镲镳镶长门闩闪闫闭问闯闰闱闲闳间闵闶闷闸闹闺闻闼闽闾阀阁阂阃阄阅阆阈阉阊阋阌阍阎阏阐阑阒阔阕阖阗阙阚阜阝队阡阢阪阮阱防阳阴阵阶阻阼阽阿陀陂附际陆陇陈陉陋陌降限陔陕陛陟陡院除陧陨险陪陬陲陴陵陶陷隅隆隈隋隍随隐隔隗隘隙障隧隰隳隶隹隼隽难雀雁雄雅集雇雉雌雍雎雏雒雕雠雨雩雪雯雳零雷雹雾需霁霄霆震霈霉霍霎霏霓霖霜霞霪霭霰露霸霹霾青靓靖静靛非靠靡面靥革靳靴靶靼鞅鞋鞍鞑鞒鞔鞘鞠鞣鞫鞭鞯鞲鞴韦韧韩韪韫韬韭音韵韶页顶顷顸项顺须顼顽顾顿颀颁颂颃预颅领颇颈颉颊颌颍颏颐频颓颔颖颗题颚颛颜额颞颟颠颡颢颤颥颦颧风飑飒飓飕飘飙飚飞食飧飨餍餐餮饔饕饣饥饧饨饩饪饫饬饭饮饯饰饱饲饴饵饶饷饺饼饽饿馀馁馄馅馆馇馈馊馋馍馏馐馑馒馓馔馕首馗馘香馥馨马驭驮驯驰驱驳驴驵驶驷驸驹驺驻驼驽驾驿骀骁骂骄骅骆骇骈骊骋验骏骐骑骒骓骖骗骘骚骛骜骝骞骟骠骡骢骣骤骥骧骨骰骱骶骷骸骺骼髀髁髂髅髋髌髑髓高髟髡髦髫髭髯髹髻鬃鬈鬏鬓鬟鬣鬯鬲鬻鬼魁魂魃魄魅魇魈魉魍魏魑魔鱼鱿鲁鲂鲅鲆鲇鲈鲋鲍鲎鲐鲑鲒鲔鲕鲚鲛鲜鲞鲟鲠鲡鲢鲣鲤鲥鲦鲧鲨鲩鲫鲭鲮鲰鲱鲲鲳鲴鲵鲶鲷鲸鲺鲻鲼鲽鳃鳄鳅鳆鳇鳊鳋鳌鳍鳎鳏鳐鳓鳔鳕鳖鳗鳘鳙鳜鳝鳞鳟鳢鸟鸠鸡鸢鸣鸥鸦鸨鸩鸪鸫鸬鸭鸯鸱鸲鸳鸵鸶鸷鸸鸹鸺鸽鸾鸿鹁鹂鹃鹄鹅鹆鹇鹈鹉鹊鹋鹌鹎鹏鹑鹕鹗鹘鹚鹛鹜鹞鹣鹤鹦鹧鹨鹩鹪鹫鹬鹭鹰鹱鹳鹾鹿麂麇麈麋麒麓麝麟麦麴麸麻麽麾黄黉黍黎黏黑黔默黛黜黝黟黠黢黥黧黩黪黯黹黻黼黾鼋鼍鼎鼐鼓鼗鼙鼠鼢鼬鼯鼷鼹鼻鼽鼾齄齐齑齿龀龃龄龅龆龇龈龉龊龋龌龙龚龛龟龠 diff --git a/Chinese_Japanese.md b/Chinese_Japanese.md index f0669f3..867b988 100644 --- a/Chinese_Japanese.md +++ b/Chinese_Japanese.md @@ -3,7 +3,7 @@ The file `chinese_japanese` contains the following characters. It may be used (in full, or edited) to perpare Python font files for these languages with the `-k` or `--charset_file` option. The source TTF or OTF file must contain the -relevant glyphs. +relevant glyphs. # charset range diff --git a/FONT_TO_PY.md b/FONT_TO_PY.md index 78761ef..a419a49 100644 --- a/FONT_TO_PY.md +++ b/FONT_TO_PY.md @@ -104,7 +104,7 @@ $ font_to_py.py -k extended FreeSans.ttf 23 my_extended_font.py set. See below. * -k or --charset_file Obtain the character set from a file. Typical use is for alternative character sets such as Cyrillic: the file must contain the - character set to be included. An example file is `cyrillic`. Another is + character set to be included. An example file is `cyrillic`. Another is `extended` which adds unicode characters `°μπωϕθαβγδλΩ` to those in the original ASCII set of printable characters. At risk of stating the obvious this will only produce useful results if the source font file includes all @@ -117,7 +117,7 @@ usage but it will conserve flash. Example usage for a digital clock font: ```shell $ font_to_py.py Arial.ttf 20 arial_clock.py -c 1234567890: ``` -Example usage with the -k option: +Example usage with the -k option: ```shell font_to_py.py FreeSans.ttf 20 freesans_cyr_20.py -k cyrillic font_to_py.py -x -k extended FreeSans.ttf 17 font10.py @@ -173,7 +173,7 @@ The detailed layout of the Python file may be seen [here](./writer/DRIVERS.md). # 4. Python font files -Users of the `Writer` or `CWriter` classes or of +Users of the `Writer` or `CWriter` classes or of [nano-gui](https://github.com/peterhinch/micropython-nano-gui) do not need to study the file format. These details are provided for those wishing to access Python font files directly. @@ -191,10 +191,10 @@ They include the following functions: 6. `monospaced()` `True` if bitmaps were created with fixed pitch. 7. `min_ch()` Returns smallest ordinal value in font. 8. `max_ch()` Largest ordinal value in font. - 9. `get_ch()` Arg: a Unicode character. Returns three items: - A memoryview into the bitmap for that character. - Bitmap height in pixels. Equal to `height()` above. - Bitmap width in pixels. + 9. `get_ch()` Arg: a Unicode character. Returns three items: + A memoryview into the bitmap for that character. + Bitmap height in pixels. Equal to `height()` above. + Bitmap width in pixels. See [this link](https://stackoverflow.com/questions/27631736/meaning-of-top-ascent-baseline-descent-bottom-and-leading-in-androids-font) for an explanation of `baseline`. @@ -224,9 +224,9 @@ Only the following optional arguments are valid: The code is released under the MIT licence. The `font_to_py.py` utility requires Python 3.2 or later. -The module relies on [Freetype](https://www.freetype.org/) which is included in most Linux distributions. +The module relies on [Freetype](https://www.freetype.org/) which is included in most Linux distributions. It uses the [Freetype Python bindings](http://freetype-py.readthedocs.io/en/latest/index.html) -which will need to be installed. +which will need to be installed. My solution draws on the excellent example code written by Daniel Bader. This may be viewed [here](https://dbader.org/blog/monochrome-font-rendering-with-freetype-and-python) and [here](https://gist.github.com/dbader/5488053). diff --git a/README.md b/README.md index 047625e..0ffab8f 100644 --- a/README.md +++ b/README.md @@ -88,13 +88,13 @@ A font file is imported in the usual way e.g. `import font14`. Python font files contain the following functions. These return values defined by the arguments which were provided to `font_to_py.py`: -`height` Returns height in pixels. -`max_width` Returns maximum width of a glyph in pixels. -`baseline` Offset from top of glyph to the baseline. -`hmap` Returns `True` if font is horizontally mapped. -`reverse` Returns `True` if bit reversal was specified. -`monospaced` Returns `True` if monospaced rendering was specified. -`min_ch` Returns the ordinal value of the lowest character in the file. +`height` Returns height in pixels. +`max_width` Returns maximum width of a glyph in pixels. +`baseline` Offset from top of glyph to the baseline. +`hmap` Returns `True` if font is horizontally mapped. +`reverse` Returns `True` if bit reversal was specified. +`monospaced` Returns `True` if monospaced rendering was specified. +`min_ch` Returns the ordinal value of the lowest character in the file. `max_ch` Returns the ordinal value of the highest character in the file. Glyphs are returned with the `get_ch` function. Its argument is a Unicode diff --git a/font_to_py.py b/font_to_py.py deleted file mode 100755 index f8144d9..0000000 --- a/font_to_py.py +++ /dev/null @@ -1,722 +0,0 @@ -#! /usr/bin/env python3 -# -*- coding: utf-8 -*- -# Needs freetype-py>=1.0 - -# Implements multi-pass solution to setting an exact font height - -# Some code adapted from Daniel Bader's work at the following URL -# https://dbader.org/blog/monochrome-font-rendering-with-freetype-and-python -# With thanks to Stephen Irons @ironss for various improvements, also to -# @enigmaniac for ideas around handling `bdf` and `pcf` files. - -# The MIT License (MIT) -# -# Copyright (c) 2016-2023 Peter Hinch -# -# 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. - -import argparse -import sys -import os -try: - import freetype -except ModuleNotFoundError: - print('font_to_py requires the freetype library. Please see FONT_TO_PY.md.') - sys.exit(1) -if freetype.version()[0] < 1: - print('freetype version should be >= 1. Please see FONT_TO_PY.md') - -MINCHAR = 32 # Ordinal values of default printable ASCII set -MAXCHAR = 126 # 94 chars - -# UTILITIES FOR WRITING PYTHON SOURCECODE TO A FILE - -# ByteWriter takes as input a variable name and data values and writes -# Python source to an output stream of the form -# my_variable = b'\x01\x02\x03\x04\x05\x06\x07\x08'\ - -# Lines are broken with \ for readability. - -class ByteWriter: - bytes_per_line = 16 - - def __init__(self, stream, varname): - self.stream = stream - self.stream.write('{} =\\\n'.format(varname)) - self.bytecount = 0 # For line breaks - - def _eol(self): - self.stream.write("'\\\n") - - def _eot(self): - self.stream.write("'\n") - - def _bol(self): - self.stream.write("b'") - - # Output a single byte - def obyte(self, data): - if not self.bytecount: - self._bol() - self.stream.write('\\x{:02x}'.format(data)) - self.bytecount += 1 - self.bytecount %= self.bytes_per_line - if not self.bytecount: - self._eol() - - # Output from a sequence - def odata(self, bytelist): - for byt in bytelist: - self.obyte(byt) - - # ensure a correct final line - def eot(self): # User force EOL if one hasn't occurred - if self.bytecount: - self._eot() - self.stream.write('\n') - - -# Define a global -def var_write(stream, name, value): - stream.write('{} = {}\n'.format(name, value)) - -# FONT HANDLING - - -class Bitmap: - """ - A 2D bitmap image represented as a list of byte values. Each byte indicates - the state of a single pixel in the bitmap. A value of 0 indicates that the - pixel is `off` and any other value indicates that it is `on`. - """ - def __init__(self, width, height, pixels=None): - self.width = width - self.height = height - self.pixels = pixels or bytearray(width * height) - - def display(self): - """Print the bitmap's pixels.""" - for row in range(self.height): - for col in range(self.width): - char = '#' if self.pixels[row * self.width + col] else '.' - print(char, end='') - print() - print() - - def bitblt(self, src, top, left): - """Copy all pixels from `src` into this bitmap""" - srcpixel = 0 - dstpixel = top * self.width + left - row_offset = self.width - src.width - - for _ in range(src.height): - for _ in range(src.width): - self.pixels[dstpixel] = src.pixels[srcpixel] - srcpixel += 1 - dstpixel += 1 - dstpixel += row_offset - - # Horizontal mapping generator function - def get_hbyte(self, reverse): - for row in range(self.height): - col = 0 - while True: - bit = col % 8 - if bit == 0: - if col >= self.width: - break - byte = 0 - if col < self.width: - if reverse: - byte |= self.pixels[row * self.width + col] << bit - else: - # Normal map MSB of byte 0 is (0, 0) - byte |= self.pixels[row * self.width + col] << (7 - bit) - if bit == 7: - yield byte - col += 1 - - # Vertical mapping - def get_vbyte(self, reverse): - for col in range(self.width): - row = 0 - while True: - bit = row % 8 - if bit == 0: - if row >= self.height: - break - byte = 0 - if row < self.height: - if reverse: - byte |= self.pixels[row * self.width + col] << (7 - bit) - else: - # Normal map MSB of byte 0 is (0, 7) - byte |= self.pixels[row * self.width + col] << bit - if bit == 7: - yield byte - row += 1 - - -class Glyph: - def __init__(self, pixels, width, height, top, left, advance_width): - self.bitmap = Bitmap(width, height, pixels) - - # The glyph bitmap's top-side bearing, i.e. the vertical distance from - # the baseline to the bitmap's top-most scanline. - self.top = top - self.left = left - - # Ascent and descent determine how many pixels the glyph extends - # above or below the baseline. - self.descent = max(0, self.height - self.top) - self.ascent = max(0, max(self.top, self.height) - self.descent) - - # The advance width determines where to place the next character - # horizontally, that is, how many pixels we move to the right to - # draw the next glyph. - self.advance_width = advance_width - - @property - def width(self): - return self.bitmap.width - - @property - def height(self): - return self.bitmap.height - - @staticmethod - def from_glyphslot(slot): - """Construct and return a Glyph object from a FreeType GlyphSlot.""" - pixels = Glyph.unpack_mono_bitmap(slot.bitmap) - width, height = slot.bitmap.width, slot.bitmap.rows - top = slot.bitmap_top - left = slot.bitmap_left - - # The advance width is given in FreeType's 26.6 fixed point format, - # which means that the pixel values are multiples of 64. - advance_width = slot.advance.x / 64 - - return Glyph(pixels, width, height, top, left, advance_width) - - @staticmethod - def unpack_mono_bitmap(bitmap): - """ - Unpack a freetype FT_LOAD_TARGET_MONO glyph bitmap into a bytearray - where each pixel is represented by a single byte. - """ - # Allocate a bytearray of sufficient size to hold the glyph bitmap. - data = bytearray(bitmap.rows * bitmap.width) - - # Iterate over every byte in the glyph bitmap. Note that we're not - # iterating over every pixel in the resulting unpacked bitmap -- - # we're iterating over the packed bytes in the input bitmap. - for row in range(bitmap.rows): - for byte_index in range(bitmap.pitch): - - # Read the byte that contains the packed pixel data. - byte_value = bitmap.buffer[row * bitmap.pitch + byte_index] - - # We've processed this many bits (=pixels) so far. This - # determines where we'll read the next batch of pixels from. - num_bits_done = byte_index * 8 - - # Pre-compute where to write the pixels that we're going - # to unpack from the current byte in the glyph bitmap. - rowstart = row * bitmap.width + byte_index * 8 - - # Iterate over every bit (=pixel) that's still a part of the - # output bitmap. Sometimes we're only unpacking a fraction of - # a byte because glyphs may not always fit on a byte boundary. - # So we make sure to stop if we unpack past the current row - # of pixels. - for bit_index in range(min(8, bitmap.width - num_bits_done)): - - # Unpack the next pixel from the current glyph byte. - bit = byte_value & (1 << (7 - bit_index)) - - # Write the pixel to the output bytearray. We ensure that - # `off` pixels have a value of 0 and `on` pixels have a - # value of 1. - data[rowstart + bit_index] = 1 if bit else 0 - - return data - - -# A Font object is a dictionary of ASCII chars indexed by a character e.g. -# myfont['a'] -# Each entry comprises a list -# [0] A Bitmap instance containing the character -# [1] The width of the character data including advance (actual data stored) -# Public attributes: -# height (in pixels) of all characters -# width (in pixels) for monospaced output (advance width of widest char) -class Font(dict): - def __init__(self, filename, size, minchar, maxchar, monospaced, defchar, charset, bitmapped): - super().__init__() - self._face = freetype.Face(filename) - # .crange is the inclusive range of ordinal values spanning the character set. - self.crange = range(minchar, maxchar + 1) - self.monospaced = monospaced - self.defchar = defchar - # .charset has all defined characters with '' for those in range but undefined. - # Sort order is increasing ordinal value of the character whether defined or not, - # except that item 0 is the default char. - if defchar is None: # Binary font - self.charset = [chr(ordv) for ordv in self.crange] - elif charset == '': - self.charset = [chr(defchar)] + [chr(ordv) for ordv in self.crange] - else: - cl = [ord(x) for x in chr(defchar) + charset if self._face.get_char_index(x) != 0 ] - self.crange = range(min(cl), max(cl) + 1) # Inclusive ordinal value range - cs = [chr(ordv) if chr(ordv) in charset and self._face.get_char_index(chr(ordv)) != 0 else '' for ordv in self.crange] - # .charset has an item for all chars in range. '' if unsupported. - # item 0 is the default char. Subsequent chars are in increasing ordinal value. - self.charset = [chr(defchar)] + cs - # Populate self with defined chars only - self.update(dict.fromkeys([c for c in self.charset if c])) - self.max_width = self.bmp_dimensions(size) if bitmapped else self.get_dimensions(size) - self.width = self.max_width if monospaced else 0 - self._assign_values() # Assign values to existing keys - - def bmp_dimensions(self, height): - max_descent = 0 - # For each character in the charset string we get the glyph - # and update the overall dimensions of the resulting bitmap. - max_width = 0 - max_ascent = 0 - for char in self.keys(): - glyph = self._glyph_for_character(char) - max_ascent = max(max_ascent, glyph.ascent) - max_descent = max(max_descent, glyph.descent) - # for a few chars e.g. _ glyph.width > glyph.advance_width - max_width = int(max(max_width, glyph.advance_width, - glyph.width)) - - self.height = int(max_ascent + max_descent) - self._max_ascent = int(max_ascent) - self._max_descent = int(max_descent) - print('Requested height', height) - print('Actual height', self.height) - print('Max width', max_width) - print('Max descent', self._max_descent) - print('Max ascent', self._max_ascent) - return max_width - - # n-pass solution to setting a precise height. - def get_dimensions(self, required_height): - error = 0 - height = required_height - for npass in range(10): - height += error - self._face.set_pixel_sizes(0, height) - max_descent = 0 - - # For each character in the charset string we get the glyph - # and update the overall dimensions of the resulting bitmap. - max_width = 0 - max_ascent = 0 - for char in self.keys(): - glyph = self._glyph_for_character(char) - max_ascent = max(max_ascent, glyph.ascent) - max_descent = max(max_descent, glyph.descent) - # for a few chars e.g. _ glyph.width > glyph.advance_width - max_width = int(max(max_width, glyph.advance_width, - glyph.width)) - - new_error = required_height - (max_ascent + max_descent) - if (new_error == 0) or (abs(new_error) - abs(error) == 0): - break - error = new_error - self.height = int(max_ascent + max_descent) - st = 'Height set in {} passes. Actual height {} pixels.\nMax character width {} pixels.' - print(st.format(npass + 1, self.height, max_width)) - self._max_ascent = int(max_ascent) - self._max_descent = int(max_descent) - return max_width - - - def _glyph_for_character(self, char): - # Let FreeType load the glyph for the given character and tell it to - # render a monochromatic bitmap representation. - assert char != '' - self._face.load_char(char, freetype.FT_LOAD_RENDER | - freetype.FT_LOAD_TARGET_MONO) - return Glyph.from_glyphslot(self._face.glyph) - - def _assign_values(self): - for char in self.keys(): - glyph = self._glyph_for_character(char) - # https://github.com/peterhinch/micropython-font-to-py/issues/21 - # Handle negative glyph.left correctly (capital J), - # also glyph.width > advance (capital K and R). - if glyph.left >= 0: - char_width = int(max(glyph.advance_width, glyph.width + glyph.left)) - left = glyph.left - else: - char_width = int(max(glyph.advance_width - glyph.left, glyph.width)) - left = 0 - - width = self.width if self.width else char_width # Space required if monospaced - outbuffer = Bitmap(width, self.height) - - # The vertical drawing position should place the glyph - # on the baseline as intended. - row = self.height - int(glyph.ascent) - self._max_descent - outbuffer.bitblt(glyph.bitmap, row, left) - self[char] = [outbuffer, width, char_width] - - def stream_char(self, char, hmap, reverse): - outbuffer, _, _ = self[char] - if hmap: - gen = outbuffer.get_hbyte(reverse) - else: - gen = outbuffer.get_vbyte(reverse) - yield from gen - - def build_arrays(self, hmap, reverse): - data = bytearray() - index = bytearray() - sparse = bytearray() - def append_data(data, char): - width = self[char][1] - data += (width).to_bytes(2, byteorder='little') - data += bytearray(self.stream_char(char, hmap, reverse)) - - # self.charset is contiguous with chars having ordinal values in the - # inclusive range specified. Where the specified character set has gaps - # missing characters are empty strings. - # Charset includes default char and both max and min chars, hence +2. - if len(self.charset) <= MAXCHAR - MINCHAR + 2: - # Build normal index. Efficient for ASCII set and smaller as - # entries are 2 bytes (-> data[0] for absent glyph) - for char in self.charset: - if char == '': - index += bytearray((0, 0)) - else: - index += (len(data)).to_bytes(2, byteorder='little') # Start - append_data(data, char) - index += (len(data)).to_bytes(2, byteorder='little') # End - else: - # Sparse index. Entries are 4 bytes but only populated if the char - # has a defined glyph. - append_data(data, self.charset[0]) # data[0] is the default char - for char in sorted(self.keys()): - sparse += ord(char).to_bytes(2, byteorder='little') - pad = len(data) % 8 - if pad: # Ensure len(data) % 8 == 0 - data += bytearray(8 - pad) - try: - sparse += (len(data) >> 3).to_bytes(2, byteorder='little') # Start - except OverflowError: - raise ValueError("Total size of font bitmap exceeds 524287 bytes.") - append_data(data, char) - return data, index, sparse - - def build_binary_array(self, hmap, reverse, sig): - data = bytearray((0x3f + sig, 0xe7, self.max_width, self.height)) - for char in self.charset: - width = self[char][2] - data += bytes((width,)) - data += bytearray(self.stream_char(char, hmap, reverse)) - return data - -# PYTHON FILE WRITING -# The index only holds the start of data so can't read next_offset but must -# calculate it. - -STR01 = """# Code generated by font_to_py.py. -# Font: {}{} -# Cmd: {} -version = '0.33' - -""" - -# Code emitted for charsets spanning a small range of ordinal values -STR02 = """_mvfont = memoryview(_font) -_mvi = memoryview(_index) -ifb = lambda l : l[0] | (l[1] << 8) - -def get_ch(ch): - oc = ord(ch) - ioff = 2 * (oc - {0} + 1) if oc >= {0} and oc <= {1} else 0 - doff = ifb(_mvi[ioff : ]) - width = ifb(_mvfont[doff : ]) -""" - -# Code emiited for large charsets, assumed by build_arrays() to be sparse. -# Binary search of sorted sparse index. -# Offset into data array is saved after dividing by 8 -STRSP = """_mvfont = memoryview(_font) -_mvsp = memoryview(_sparse) -ifb = lambda l : l[0] | (l[1] << 8) - -def bs(lst, val): - while True: - m = (len(lst) & ~ 7) >> 1 - v = ifb(lst[m:]) - if v == val: - return ifb(lst[m + 2:]) - if not m: - return 0 - lst = lst[m:] if v < val else lst[:m] - -def get_ch(ch): - doff = bs(_mvsp, ord(ch)) << 3 - width = ifb(_mvfont[doff : ]) -""" - -# Code emitted for horizontally mapped fonts. -STR02H =""" - next_offs = doff + 2 + ((width - 1)//8 + 1) * {0} - return _mvfont[doff + 2:next_offs], {0}, width - -""" - -# Code emitted for vertically mapped fonts. -STR02V =""" - next_offs = doff + 2 + (({0} - 1)//8 + 1) * width - return _mvfont[doff + 2:next_offs], {0}, width - -""" - -# Extra code emitted where -i is specified. -STR03 = ''' -def glyphs(): - for c in """{}""": - yield c, get_ch(c) - -''' - -def write_func(stream, name, arg): - stream.write('def {}():\n return {}\n\n'.format(name, arg)) - -def write_font(op_path, font_path, height, monospaced, hmap, reverse, minchar, - maxchar, defchar, charset, iterate, bitmapped): - try: - fnt = Font(font_path, height, minchar, maxchar, monospaced, defchar, charset, bitmapped) - except freetype.ft_errors.FT_Exception: - print("Can't open", font_path) - return False - try: - with open(op_path, 'w', encoding='utf-8') as stream: - write_data(stream, fnt, font_path, hmap, reverse, iterate, charset) - except OSError: - print("Can't open", op_path, 'for writing') - return False - return True - -def write_data(stream, fnt, font_path, hmap, reverse, iterate, charset): - height = fnt.height # Actual height, not target height - minchar = min(fnt.crange) - maxchar = max(fnt.crange) - defchar = fnt.defchar - st = '' if charset == '' else ' Char set: {}'.format(charset) - cl = ' '.join(sys.argv) - stream.write(STR01.format(os.path.split(font_path)[1], st, cl)) - write_func(stream, 'height', height) - write_func(stream, 'baseline', fnt._max_ascent) - write_func(stream, 'max_width', fnt.max_width) - write_func(stream, 'hmap', hmap) - write_func(stream, 'reverse', reverse) - write_func(stream, 'monospaced', fnt.monospaced) - write_func(stream, 'min_ch', minchar) - write_func(stream, 'max_ch', maxchar) - if iterate: - stream.write(STR03.format(''.join(sorted(fnt.keys())))) - data, index, sparse = fnt.build_arrays(hmap, reverse) - bw_font = ByteWriter(stream, '_font') - bw_font.odata(data) - bw_font.eot() - if sparse: # build_arrays() has returned a sparse index - bw_sparse = ByteWriter(stream, '_sparse') - bw_sparse.odata(sparse) - bw_sparse.eot() - stream.write(STRSP) - print("Sparse") - else: - bw_index = ByteWriter(stream, '_index') - bw_index.odata(index) - bw_index.eot() - stream.write(STR02.format(minchar, maxchar)) - print("Normal") - if hmap: - stream.write(STR02H.format(height)) - else: - stream.write(STR02V.format(height)) - -# BINARY OUTPUT -# hmap reverse magic bytes -# 0 0 0x3f 0xe7 -# 1 0 0x40 0xe7 -# 0 1 0x41 0xe7 -# 1 1 0x42 0xe7 -def write_binary_font(op_path, font_path, height, hmap, reverse): - try: - fnt = Font(font_path, height, 32, 126, True, None, '') # All chars have same width - except freetype.ft_errors.FT_Exception: - print("Can't open", font_path) - return False - sig = 1 if hmap else 0 - if reverse: - sig += 2 - try: - with open(op_path, 'wb') as stream: - data = fnt.build_binary_array(hmap, reverse, sig) - stream.write(data) - except OSError: - print("Can't open", op_path, 'for writing') - return False - return True - -# PARSE COMMAND LINE ARGUMENTS - -def quit(msg): - print(msg) - sys.exit(1) - -DESC = """font_to_py.py V0.4.0 -Utility to convert ttf, otf, bdf and pcf font files to Python source. -Sample usage: -font_to_py.py FreeSans.ttf 23 freesans.py - -This creates a font with nominal height 23 pixels with these defaults: -Mapping is vertical, pitch variable, character set 32-126 inclusive. -Illegal characters will be rendered as "?". - -To specify monospaced rendering issue: -font_to_py.py FreeSans.ttf 23 --fixed freesans.py -""" - -BINARY = """Invalid arguments. Binary (random access) font files support the standard ASCII -character set (from 32 to 126 inclusive). This range cannot be overridden. -Random access font files don't support an error character. -""" - -if __name__ == "__main__": - parser = argparse.ArgumentParser(__file__, description=DESC, - formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument('infile', type=str, help='Input file path') - parser.add_argument('height', type=int, help='Font height in pixels') - parser.add_argument('outfile', type=str, - help='Path and name of output file') - - parser.add_argument('-x', '--xmap', action='store_true', - help='Horizontal (x) mapping') - parser.add_argument('-r', '--reverse', action='store_true', - help='Bit reversal') - parser.add_argument('-f', '--fixed', action='store_true', - help='Fixed width (monospaced) font') - parser.add_argument('-b', '--binary', action='store_true', - help='Produce binary (random access) font file.') - parser.add_argument('-i', '--iterate', action='store_true', - help='Include generator function to iterate over character set.') - - parser.add_argument('-s', '--smallest', - type = int, - default = MINCHAR, - help = 'Ordinal value of smallest character default %(default)i') - - parser.add_argument('-l', '--largest', - type = int, - help = 'Ordinal value of largest character default %(default)i', - default = MAXCHAR) - - parser.add_argument('-e', '--errchar', - type = int, - help = 'Ordinal value of error character default %(default)i ("?")', - default = 63) - - parser.add_argument('-c', '--charset', - type = str, - help = 'Character set. e.g. 1234567890: to restrict for a clock display.', - default = '') - - parser.add_argument('-k', '--charset_file', - type = str, - help = 'File containing charset e.g. cyrillic_subset.', - default = '') - - args = parser.parse_args() - if not args.outfile[0].isalpha(): - quit('Font filenames must be valid Python variable names.') - - if not os.path.isfile(args.infile): - quit("Font filename does not exist") - - if not os.path.splitext(args.infile)[1].upper() in ('.TTF', '.OTF', '.BDF', '.PCF'): - quit("Font file should be a ttf or otf file.") - - if args.binary: - if os.path.splitext(args.outfile)[1].upper() == '.PY': - quit('Binary file must not have a .py extension.') - - if args.smallest != 32 or args.largest != 126 or args.errchar != ord('?') or args.charset: - quit(BINARY) - - print('Writing binary font file.') - if not write_binary_font(args.outfile, args.infile, args.height, - args.xmap, args.reverse): - sys.exit(1) - else: - if not os.path.splitext(args.outfile)[1].upper() == '.PY': - quit('Output filename must have a .py extension.') - - if args.smallest < 0: - quit('--smallest must be >= 0') - - if args.largest > 255: - quit('--largest must be < 256') - elif args.largest > 127 and os.path.splitext(args.infile)[1].upper() == '.TTF': - print('WARNING: extended ASCII characters may not be correctly converted. See docs.') - - if args.errchar < 0 or args.errchar > 255: - quit('--errchar must be between 0 and 255') - if args.charset and (args.smallest != 32 or args.largest != 126): - print('WARNING: specified smallest and largest values ignored.') - - if args.charset_file: - try: - with open(args.charset_file, 'r', encoding='utf-8') as f: - cset = f.read() - except OSError: - print("Can't open", args.charset_file, 'for reading.') - sys.exit(1) - else: - cset = args.charset - # dedupe and remove default char. Allow chars in private use area. - # https://github.com/peterhinch/micropython-font-to-py/issues/22 - cs = {c for c in cset if c.isprintable() or (0xE000 <= ord(c) <= 0xF8FF) } - {args.errchar} - cs = sorted(list(cs)) - cset = ''.join(cs) # Back to string - bitmapped = os.path.splitext(args.infile)[1].upper() in ('.BDF', '.PCF') - if bitmapped: - if args.height != 0: - print('Warning: height arg ignored for bitmapped fonts.') - chkface = freetype.Face(args.infile) - args.height = chkface._get_available_sizes()[0].height - print("Found font with size " + str(args.height)) - - print('Writing Python font file.') - if not write_font(args.outfile, args.infile, args.height, args.fixed, - args.xmap, args.reverse, args.smallest, args.largest, - args.errchar, cset, args.iterate, bitmapped): - sys.exit(1) - - print(args.outfile, 'written successfully.') - diff --git a/font_to_py/bitmap.py b/font_to_py/bitmap.py new file mode 100755 index 0000000..69889a1 --- /dev/null +++ b/font_to_py/bitmap.py @@ -0,0 +1,101 @@ +# Some code adapted from Daniel Bader's work at the following URL +# https://dbader.org/blog/monochrome-font-rendering-with-freetype-and-python +# With thanks to Stephen Irons @ironss for various improvements, also to +# @enigmaniac for ideas around handling `bdf` and `pcf` files. + +# The MIT License (MIT) +# +# Copyright (c) 2016-2023 Peter Hinch +# +# 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. + + +class Bitmap: + """ + A 2D bitmap image represented as a list of byte values. Each byte indicates + the state of a single pixel in the bitmap. A value of 0 indicates that the + pixel is `off` and any other value indicates that it is `on`. + """ + + def __init__(self, width, height, pixels=None): + self.width = width + self.height = height + self.pixels = pixels or bytearray(width * height) + + def display(self): + """Print the bitmap's pixels.""" + for row in range(self.height): + for col in range(self.width): + char = "#" if self.pixels[row * self.width + col] else "." + print(char, end="") + print() + print() + + def bitblt(self, src, top, left): + """Copy all pixels from `src` into this bitmap""" + srcpixel = 0 + dstpixel = top * self.width + left + row_offset = self.width - src.width + + for _ in range(src.height): + for _ in range(src.width): + self.pixels[dstpixel] = src.pixels[srcpixel] + srcpixel += 1 + dstpixel += 1 + dstpixel += row_offset + + # Horizontal mapping generator function + def get_hbyte(self, reverse): + for row in range(self.height): + col = 0 + while True: + bit = col % 8 + if bit == 0: + if col >= self.width: + break + byte = 0 + if col < self.width: + if reverse: + byte |= self.pixels[row * self.width + col] << bit + else: + # Normal map MSB of byte 0 is (0, 0) + byte |= self.pixels[row * self.width + col] << (7 - bit) + if bit == 7: # noqa: PLR2004 + yield byte + col += 1 + + # Vertical mapping + def get_vbyte(self, reverse): + for col in range(self.width): + row = 0 + while True: + bit = row % 8 + if bit == 0: + if row >= self.height: + break + byte = 0 + if row < self.height: + if reverse: + byte |= self.pixels[row * self.width + col] << (7 - bit) + else: + # Normal map MSB of byte 0 is (0, 7) + byte |= self.pixels[row * self.width + col] << bit + if bit == 7: # noqa: PLR2004 + yield byte + row += 1 diff --git a/font_to_py/byte_writer.py b/font_to_py/byte_writer.py new file mode 100755 index 0000000..d0696dc --- /dev/null +++ b/font_to_py/byte_writer.py @@ -0,0 +1,65 @@ +# Some code adapted from Daniel Bader's work at the following URL +# https://dbader.org/blog/monochrome-font-rendering-with-freetype-and-python +# With thanks to Stephen Irons @ironss for various improvements, also to +# @enigmaniac for ideas around handling `bdf` and `pcf` files. + +# The MIT License (MIT) +# +# Copyright (c) 2016-2023 Peter Hinch +# +# 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. + + +class ByteWriter: + bytes_per_line = 16 + + def __init__(self, stream, varname): + self.stream = stream + self.stream.write(f"{varname} =\\\n") + self.bytecount = 0 # For line breaks + + def _eol(self): + self.stream.write("'\\\n") + + def _eot(self): + self.stream.write("'\n") + + def _bol(self): + self.stream.write("b'") + + # Output a single byte + def obyte(self, data): + if not self.bytecount: + self._bol() + self.stream.write(f"\\x{data:02x}") + self.bytecount += 1 + self.bytecount %= self.bytes_per_line + if not self.bytecount: + self._eol() + + # Output from a sequence + def odata(self, bytelist): + for byt in bytelist: + self.obyte(byt) + + # ensure a correct final line + def eot(self): # User force EOL if one hasn't occurred + if self.bytecount: + self._eot() + self.stream.write("\n") diff --git a/font_to_py/cli.py b/font_to_py/cli.py new file mode 100755 index 0000000..7d1f3cf --- /dev/null +++ b/font_to_py/cli.py @@ -0,0 +1,404 @@ +# Implements multi-pass solution to setting an exact font height + +# Some code adapted from Daniel Bader's work at the following URL +# https://dbader.org/blog/monochrome-font-rendering-with-freetype-and-python +# With thanks to Stephen Irons @ironss for various improvements, also to +# @enigmaniac for ideas around handling `bdf` and `pcf` files. + +# The MIT License (MIT) +# +# Copyright (c) 2016-2023 Peter Hinch +# +# 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. + +import importlib.metadata +import sys +from pathlib import Path + +import click +import freetype + +from .byte_writer import ByteWriter +from .font import Font + +if freetype.version()[0] < 1: + click.echo("freetype version should be >= 1. Please see FONT_TO_PY.md") + +MINCHAR = 32 # Ordinal values of default printable ASCII set +MAXCHAR = 126 # 94 chars + +UNICODE_PRIVATE_USE_AREA_START = 0xE000 +UNICODE_PRIVATE_USE_AREA_END = 0xF8FF + +VERSION = importlib.metadata.version("micropython-font-to-py") + + +# Define a global +def var_write(stream, name, value): + stream.write(f"{name} = {value}\n") + + +STR01 = """# Code generated by font_to_py. +# Font: {font:s}{charset:s} +# Cmd: {cmd:s} +version = '{version:s}' + +""" + +# Code emitted for charsets spanning a small range of ordinal values +STR02 = """_mvfont = memoryview(_font) +_mvi = memoryview(_index) +ifb = lambda l : l[0] | (l[1] << 8) + +def get_ch(ch): + oc = ord(ch) + ioff = 2 * (oc - {min:d} + 1) if oc >= {min:d} and oc <= {max:d} else 0 + doff = ifb(_mvi[ioff : ]) + width = ifb(_mvfont[doff : ]) +""" + +# Code emiited for large charsets, assumed by build_arrays() to be sparse. +# Binary search of sorted sparse index. +# Offset into data array is saved after dividing by 8 +STRSP = """_mvfont = memoryview(_font) +_mvsp = memoryview(_sparse) +ifb = lambda l : l[0] | (l[1] << 8) + +def bs(lst, val): + while True: + m = (len(lst) & ~ 7) >> 1 + v = ifb(lst[m:]) + if v == val: + return ifb(lst[m + 2:]) + if not m: + return 0 + lst = lst[m:] if v < val else lst[:m] + +def get_ch(ch): + doff = bs(_mvsp, ord(ch)) << 3 + width = ifb(_mvfont[doff : ]) +""" + +# Code emitted for horizontally mapped fonts. +STR02H = """ + next_offs = doff + 2 + ((width - 1)//8 + 1) * {height:d} + return _mvfont[doff + 2:next_offs], {height:d}, width + +""" + +# Code emitted for vertically mapped fonts. +STR02V = """ + next_offs = doff + 2 + (({height:d} - 1)//8 + 1) * width + return _mvfont[doff + 2:next_offs], {height:d}, width + +""" + +# Extra code emitted where -i is specified. +STR03 = ''' +def glyphs(): + for c in """{}""": + yield c, get_ch(c) + +''' + + +def write_func(stream, name, arg): + stream.write(f"def {name}():\n return {arg}\n\n") + + +def write_font( # noqa: PLR0913 + op_path, + font_path, + height, + monospaced, + hmap, + reverse, + minchar, + maxchar, + defchar, + charset, + iterate, + bitmapped, +): + try: + fnt = Font( + font_path, height, minchar, maxchar, monospaced, defchar, charset, bitmapped + ) + except freetype.ft_errors.FT_Exception: + click.echo(f"Can't open {font_path}") + return False + try: + with open(op_path, "w", encoding="utf-8") as stream: + write_data(stream, fnt, font_path, hmap, reverse, iterate, charset) + except OSError: + click.echo(f"Can't open {op_path} for writing") + return False + return True + + +def write_data( # noqa: PLR0913 + stream, fnt, font_path, hmap, reverse, iterate, charset +): + height = fnt.height # Actual height, not target height + minchar = min(fnt.crange) + maxchar = max(fnt.crange) + st = "" if charset == "" else f" Char set: {charset}" + cl = " ".join([str(Path(sys.argv[0]).stem), *sys.argv[1:]]) + stream.write( + STR01.format(font=Path(font_path).stem, charset=st, cmd=cl, version=VERSION) + ) + write_func(stream, "height", height) + write_func(stream, "baseline", fnt._max_ascent) + write_func(stream, "max_width", fnt.max_width) + write_func(stream, "hmap", hmap) + write_func(stream, "reverse", reverse) + write_func(stream, "monospaced", fnt.monospaced) + write_func(stream, "min_ch", minchar) + write_func(stream, "max_ch", maxchar) + if iterate: + stream.write(STR03.format("".join(sorted(fnt.keys())))) + data, index, sparse = fnt.build_arrays(hmap, reverse) + bw_font = ByteWriter(stream, "_font") + bw_font.odata(data) + bw_font.eot() + if sparse: # build_arrays() has returned a sparse index + bw_sparse = ByteWriter(stream, "_sparse") + bw_sparse.odata(sparse) + bw_sparse.eot() + stream.write(STRSP) + click.echo("Sparse") + else: + bw_index = ByteWriter(stream, "_index") + bw_index.odata(index) + bw_index.eot() + stream.write(STR02.format(min=minchar, max=maxchar)) + click.echo("Normal") + if hmap: + stream.write(STR02H.format(height=height)) + else: + stream.write(STR02V.format(height=height)) + + +# BINARY OUTPUT +# hmap reverse magic bytes +# 0 0 0x3f 0xe7 +# 1 0 0x40 0xe7 +# 0 1 0x41 0xe7 +# 1 1 0x42 0xe7 +def write_binary_font(op_path, font_path, height, hmap, reverse): + try: + fnt = Font( + font_path, height, 32, 126, True, None, "" + ) # All chars have same width + except freetype.ft_errors.FT_Exception: + click.echo(f"Can't open {font_path}") + return False + sig = 1 if hmap else 0 + if reverse: + sig += 2 + try: + with open(op_path, "wb") as stream: + data = fnt.build_binary_array(hmap, reverse, sig) + stream.write(data) + except OSError: + click.echo(f"Can't open {op_path} for writing") + return False + return True + + +# PARSE COMMAND LINE ARGUMENTS + + +def quit(msg): + click.echo(msg) + sys.exit(1) + + +BINARY = """Invalid arguments. Binary (random access) font files support the standard ASCII +character set (from 32 to 126 inclusive). This range cannot be overridden. +Random access font files don't support an error character. +""" + +CONTEXT_SETTINGS = dict(max_content_width=100) + + +@click.command(context_settings=CONTEXT_SETTINGS) +@click.option("-x", "--xmap", is_flag=True, help="Horizontal (x) mapping") +@click.option("-r", "--reverse", is_flag=True, help="Bit reversal") +@click.option("-f", "--fixed", is_flag=True, help="Fixed width (monospaced) font") +@click.option( + "-b", + "--binary", + is_flag=True, + help="Produce binary (random access) font file.", +) +@click.option( + "-i", + "--iterate", + is_flag=True, + help="Include generator function to iterate over character set.", +) +@click.option( + "-s", + "--smallest", + type=int, + default=MINCHAR, + help="Ordinal value of smallest character.", + show_default=True, +) +@click.option( + "-l", + "--largest", + type=click.IntRange(min=0, max=255), + help="Ordinal value of largest character.", + default=MAXCHAR, + show_default=True, +) +@click.option( + "-e", + "--errchar", + type=click.IntRange(min=0, max=255), + help="Ordinal value of error character.", + default=63, + show_default=True, +) +@click.option( + "-c", + "--charset", + type=str, + help="Character set. e.g. 1234567890: to restrict for a clock display.", + default="", +) +@click.option( + "-k", + "--charset_file", + type=click.Path(exists=True), + help="File containing charset e.g. cyrillic_subset.", + default=None, +) +@click.argument("infile", type=click.Path(exists=True), required=True) +@click.argument("height", type=int, required=True) +@click.argument("outfile", type=click.Path(), required=True) +def main( # noqa: C901, PLR0913, PLR0912 + xmap, + reverse, + fixed, + binary, + iterate, + smallest, + largest, + errchar, + charset, + charset_file, + infile, + height, + outfile, +): + """ + Utility to convert ttf, otf, bdf and pcf font files to Python source. + Sample usage: + font_to_py.py FreeSans.ttf 23 freesans.py + + This creates a font with nominal height 23 pixels with these defaults: + Mapping is vertical, pitch variable, character set 32-126 inclusive. + Illegal characters will be rendered as "?". + + To specify monospaced rendering issue: + font_to_py.py FreeSans.ttf 23 --fixed freesans.py + """ + + if Path(infile).suffix.upper() not in (".TTF", ".OTF", ".BDF", ".PCF"): + quit(f"Font file ({outfile}) should be a ttf or otf file.") + + if binary: + if Path(outfile).suffix.upper() == ".PY": + quit("Binary file must not have a .py extension.") + + if smallest != MINCHAR or largest != MAXCHAR or errchar != ord("?") or charset: + quit(BINARY) + + click.echo("Writing binary font file.") + if not write_binary_font(outfile, infile, height, xmap, reverse): + sys.exit(1) + else: + if Path(outfile).suffix.upper() != ".PY": + quit("Output filename must have a .py extension.") + + if smallest < 0: + quit("--smallest must be >= 0") + + elif largest > MAXCHAR + 1 and Path(outfile).suffix.upper() == ".TTF": + click.echo( + "WARNING: extended ASCII characters may not be correctly converted. See docs." + ) + + if charset and (smallest != MINCHAR or largest != MAXCHAR): + click.echo("WARNING: specified smallest and largest values ignored.") + + if charset_file is not None: + try: + with open(charset_file, encoding="utf-8") as f: + cset = f.read() + except OSError: + click.echo(f"Can't open {charset_file} for reading.") + sys.exit(1) + else: + cset = charset + # dedupe and remove default char. Allow chars in private use area. + # https://github.com/peterhinch/micropython-font-to-py/issues/22 + cs = { + c + for c in cset + if c.isprintable() + or ( + UNICODE_PRIVATE_USE_AREA_START <= ord(c) <= UNICODE_PRIVATE_USE_AREA_END + ) + } - {errchar} + cs = sorted(list(cs)) + cset = "".join(cs) # Back to string + bitmapped = Path(outfile).suffix.upper() in (".BDF", ".PCF") + if bitmapped: + if height != 0: + click.echo("Warning: height arg ignored for bitmapped fonts.") + chkface = freetype.Face(infile) + height = chkface._get_available_sizes()[0].height + click.echo(f"Found font with size {height!s}") + + click.echo("Writing Python font file.") + if not write_font( + outfile, + infile, + height, + fixed, + xmap, + reverse, + smallest, + largest, + errchar, + cset, + iterate, + bitmapped, + ): + sys.exit(1) + + click.echo(f"{outfile} written successfully.") + + +if __name__ == "__main__": + main() diff --git a/font_to_py/font.py b/font_to_py/font.py new file mode 100644 index 0000000..a0c0b1e --- /dev/null +++ b/font_to_py/font.py @@ -0,0 +1,231 @@ +# Some code adapted from Daniel Bader's work at the following URL +# https://dbader.org/blog/monochrome-font-rendering-with-freetype-and-python +# With thanks to Stephen Irons @ironss for various improvements, also to +# @enigmaniac for ideas around handling `bdf` and `pcf` files. + +# The MIT License (MIT) +# +# Copyright (c) 2016-2023 Peter Hinch +# +# 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. + +import freetype + +from .bitmap import Bitmap +from .glyph import Glyph + +if freetype.version()[0] < 1: + print("freetype version should be >= 1. Please see FONT_TO_PY.md") + + +# A Font object is a dictionary of ASCII chars indexed by a character e.g. +# myfont['a'] +# Each entry comprises a list +# [0] A Bitmap instance containing the character +# [1] The width of the character data including advance (actual data stored) +# Public attributes: +# height (in pixels) of all characters +# width (in pixels) for monospaced output (advance width of widest char) +class Font(dict): + def __init__( # noqa: PLR0913 + self, filename, size, minchar, maxchar, monospaced, defchar, charset, bitmapped + ): + super().__init__() + self._face = freetype.Face(filename) + # .crange is the inclusive range of ordinal values spanning the character set. + self.crange = range(minchar, maxchar + 1) + self.monospaced = monospaced + self.defchar = defchar + # .charset has all defined characters with '' for those in range but undefined. + # Sort order is increasing ordinal value of the character whether defined or not, + # except that item 0 is the default char. + if defchar is None: # Binary font + self.charset = [chr(ordv) for ordv in self.crange] + elif charset == "": + self.charset = [chr(defchar)] + [chr(ordv) for ordv in self.crange] + else: + cl = [ + ord(x) + for x in chr(defchar) + charset + if self._face.get_char_index(x) != 0 + ] + self.crange = range(min(cl), max(cl) + 1) # Inclusive ordinal value range + cs = [ + chr(ordv) + if chr(ordv) in charset and self._face.get_char_index(chr(ordv)) != 0 + else "" + for ordv in self.crange + ] + # .charset has an item for all chars in range. '' if unsupported. + # item 0 is the default char. Subsequent chars are in increasing ordinal value. + self.charset = [chr(defchar), *cs] + # Populate self with defined chars only + self.update(dict.fromkeys([c for c in self.charset if c])) + self.max_width = ( + self.bmp_dimensions(size) if bitmapped else self.get_dimensions(size) + ) + self.width = self.max_width if monospaced else 0 + self._assign_values() # Assign values to existing keys + + def bmp_dimensions(self, height): + max_descent = 0 + # For each character in the charset string we get the glyph + # and update the overall dimensions of the resulting bitmap. + max_width = 0 + max_ascent = 0 + for char in self.keys(): + glyph = self._glyph_for_character(char) + max_ascent = max(max_ascent, glyph.ascent) + max_descent = max(max_descent, glyph.descent) + # for a few chars e.g. _ glyph.width > glyph.advance_width + max_width = int(max(max_width, glyph.advance_width, glyph.width)) + + self.height = int(max_ascent + max_descent) + self._max_ascent = int(max_ascent) + self._max_descent = int(max_descent) + print("Requested height", height) + print("Actual height", self.height) + print("Max width", max_width) + print("Max descent", self._max_descent) + print("Max ascent", self._max_ascent) + return max_width + + # n-pass solution to setting a precise height. + def get_dimensions(self, required_height): + error = 0 + height = required_height + npass = 0 + for _ in range(10): + npass += 1 + height += error + self._face.set_pixel_sizes(0, height) + max_descent = 0 + + # For each character in the charset string we get the glyph + # and update the overall dimensions of the resulting bitmap. + max_width = 0 + max_ascent = 0 + for char in self.keys(): + glyph = self._glyph_for_character(char) + max_ascent = max(max_ascent, glyph.ascent) + max_descent = max(max_descent, glyph.descent) + # for a few chars e.g. _ glyph.width > glyph.advance_width + max_width = int(max(max_width, glyph.advance_width, glyph.width)) + + new_error = required_height - (max_ascent + max_descent) + if (new_error == 0) or (abs(new_error) - abs(error) == 0): + break + error = new_error + self.height = int(max_ascent + max_descent) + st = "Height set in {} passes. Actual height {} pixels.\nMax character width {} pixels." + print(st.format(npass + 1, self.height, max_width)) + self._max_ascent = int(max_ascent) + self._max_descent = int(max_descent) + return max_width + + def _glyph_for_character(self, char): + # Let FreeType load the glyph for the given character and tell it to + # render a monochromatic bitmap representation. + assert char != "" + self._face.load_char( + char, freetype.FT_LOAD_RENDER | freetype.FT_LOAD_TARGET_MONO + ) + return Glyph.from_glyphslot(self._face.glyph) + + def _assign_values(self): + for char in self.keys(): + glyph = self._glyph_for_character(char) + # https://github.com/peterhinch/micropython-font-to-py/issues/21 + # Handle negative glyph.left correctly (capital J), + # also glyph.width > advance (capital K and R). + if glyph.left >= 0: + char_width = int(max(glyph.advance_width, glyph.width + glyph.left)) + left = glyph.left + else: + char_width = int(max(glyph.advance_width - glyph.left, glyph.width)) + left = 0 + + width = ( + self.width if self.width else char_width + ) # Space required if monospaced + outbuffer = Bitmap(width, self.height) + + # The vertical drawing position should place the glyph + # on the baseline as intended. + row = self.height - int(glyph.ascent) - self._max_descent + outbuffer.bitblt(glyph.bitmap, row, left) + self[char] = [outbuffer, width, char_width] + + def stream_char(self, char, hmap, reverse): + outbuffer, _, _ = self[char] + if hmap: + gen = outbuffer.get_hbyte(reverse) + else: + gen = outbuffer.get_vbyte(reverse) + yield from gen + + def build_arrays(self, hmap, reverse): + data = bytearray() + index = bytearray() + sparse = bytearray() + + def append_data(data, char): + width = self[char][1] + data += (width).to_bytes(2, byteorder="little") + data += bytearray(self.stream_char(char, hmap, reverse)) + + # self.charset is contiguous with chars having ordinal values in the + # inclusive range specified. Where the specified character set has gaps + # missing characters are empty strings. + # Charset includes default char and both max and min chars, hence +2. + if len(self.charset) <= len(self.crange) + 1: + # Build normal index. Efficient for ASCII set and smaller as + # entries are 2 bytes (-> data[0] for absent glyph) + for char in self.charset: + if char == "": + index += bytearray((0, 0)) + else: + index += (len(data)).to_bytes(2, byteorder="little") # Start + append_data(data, char) + index += (len(data)).to_bytes(2, byteorder="little") # End + else: + # Sparse index. Entries are 4 bytes but only populated if the char + # has a defined glyph. + append_data(data, self.charset[0]) # data[0] is the default char + for char in sorted(self.keys()): + sparse += ord(char).to_bytes(2, byteorder="little") + pad = len(data) % 8 + if pad: # Ensure len(data) % 8 == 0 + data += bytearray(8 - pad) + try: + sparse += (len(data) >> 3).to_bytes(2, byteorder="little") # Start + except OverflowError as err: + raise ValueError( + "Total size of font bitmap exceeds 524287 bytes." + ) from err + append_data(data, char) + return data, index, sparse + + def build_binary_array(self, hmap, reverse, sig): + data = bytearray((0x3F + sig, 0xE7, self.max_width, self.height)) + for char in self.charset: + width = self[char][2] + data += bytes((width,)) + data += bytearray(self.stream_char(char, hmap, reverse)) + return data diff --git a/font_to_py/glyph.py b/font_to_py/glyph.py new file mode 100755 index 0000000..c4047bf --- /dev/null +++ b/font_to_py/glyph.py @@ -0,0 +1,113 @@ +# Some code adapted from Daniel Bader's work at the following URL +# https://dbader.org/blog/monochrome-font-rendering-with-freetype-and-python +# With thanks to Stephen Irons @ironss for various improvements, also to +# @enigmaniac for ideas around handling `bdf` and `pcf` files. + +# The MIT License (MIT) +# +# Copyright (c) 2016-2023 Peter Hinch +# +# 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. + +from .bitmap import Bitmap + + +class Glyph: + def __init__( # noqa: PLR0913 + self, pixels, width, height, top, left, advance_width + ): + self.bitmap = Bitmap(width, height, pixels) + + # The glyph bitmap's top-side bearing, i.e. the vertical distance from + # the baseline to the bitmap's top-most scanline. + self.top = top + self.left = left + + # Ascent and descent determine how many pixels the glyph extends + # above or below the baseline. + self.descent = max(0, self.height - self.top) + self.ascent = max(0, max(self.top, self.height) - self.descent) + + # The advance width determines where to place the next character + # horizontally, that is, how many pixels we move to the right to + # draw the next glyph. + self.advance_width = advance_width + + @property + def width(self): + return self.bitmap.width + + @property + def height(self): + return self.bitmap.height + + @staticmethod + def from_glyphslot(slot): + """Construct and return a Glyph object from a FreeType GlyphSlot.""" + pixels = Glyph.unpack_mono_bitmap(slot.bitmap) + width, height = slot.bitmap.width, slot.bitmap.rows + top = slot.bitmap_top + left = slot.bitmap_left + + # The advance width is given in FreeType's 26.6 fixed point format, + # which means that the pixel values are multiples of 64. + advance_width = slot.advance.x / 64 + + return Glyph(pixels, width, height, top, left, advance_width) + + @staticmethod + def unpack_mono_bitmap(bitmap): + """ + Unpack a freetype FT_LOAD_TARGET_MONO glyph bitmap into a bytearray + where each pixel is represented by a single byte. + """ + # Allocate a bytearray of sufficient size to hold the glyph bitmap. + data = bytearray(bitmap.rows * bitmap.width) + + # Iterate over every byte in the glyph bitmap. Note that we're not + # iterating over every pixel in the resulting unpacked bitmap -- + # we're iterating over the packed bytes in the input bitmap. + for row in range(bitmap.rows): + for byte_index in range(bitmap.pitch): + # Read the byte that contains the packed pixel data. + byte_value = bitmap.buffer[row * bitmap.pitch + byte_index] + + # We've processed this many bits (=pixels) so far. This + # determines where we'll read the next batch of pixels from. + num_bits_done = byte_index * 8 + + # Pre-compute where to write the pixels that we're going + # to unpack from the current byte in the glyph bitmap. + rowstart = row * bitmap.width + byte_index * 8 + + # Iterate over every bit (=pixel) that's still a part of the + # output bitmap. Sometimes we're only unpacking a fraction of + # a byte because glyphs may not always fit on a byte boundary. + # So we make sure to stop if we unpack past the current row + # of pixels. + for bit_index in range(min(8, bitmap.width - num_bits_done)): + # Unpack the next pixel from the current glyph byte. + bit = byte_value & (1 << (7 - bit_index)) + + # Write the pixel to the output bytearray. We ensure that + # `off` pixels have a value of 0 and `on` pixels have a + # value of 1. + data[rowstart + bit_index] = 1 if bit else 0 + + return data diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..a9bce66 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,330 @@ +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. + +[[package]] +name = "black" +version = "23.10.1" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.10.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:ec3f8e6234c4e46ff9e16d9ae96f4ef69fa328bb4ad08198c8cee45bb1f08c69"}, + {file = "black-23.10.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:1b917a2aa020ca600483a7b340c165970b26e9029067f019e3755b56e8dd5916"}, + {file = "black-23.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c74de4c77b849e6359c6f01987e94873c707098322b91490d24296f66d067dc"}, + {file = "black-23.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:7b4d10b0f016616a0d93d24a448100adf1699712fb7a4efd0e2c32bbb219b173"}, + {file = "black-23.10.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b15b75fc53a2fbcac8a87d3e20f69874d161beef13954747e053bca7a1ce53a0"}, + {file = "black-23.10.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:e293e4c2f4a992b980032bbd62df07c1bcff82d6964d6c9496f2cd726e246ace"}, + {file = "black-23.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d56124b7a61d092cb52cce34182a5280e160e6aff3137172a68c2c2c4b76bcb"}, + {file = "black-23.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:3f157a8945a7b2d424da3335f7ace89c14a3b0625e6593d21139c2d8214d55ce"}, + {file = "black-23.10.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:cfcce6f0a384d0da692119f2d72d79ed07c7159879d0bb1bb32d2e443382bf3a"}, + {file = "black-23.10.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:33d40f5b06be80c1bbce17b173cda17994fbad096ce60eb22054da021bf933d1"}, + {file = "black-23.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:840015166dbdfbc47992871325799fd2dc0dcf9395e401ada6d88fe11498abad"}, + {file = "black-23.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:037e9b4664cafda5f025a1728c50a9e9aedb99a759c89f760bd83730e76ba884"}, + {file = "black-23.10.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:7cb5936e686e782fddb1c73f8aa6f459e1ad38a6a7b0e54b403f1f05a1507ee9"}, + {file = "black-23.10.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:7670242e90dc129c539e9ca17665e39a146a761e681805c54fbd86015c7c84f7"}, + {file = "black-23.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed45ac9a613fb52dad3b61c8dea2ec9510bf3108d4db88422bacc7d1ba1243d"}, + {file = "black-23.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:6d23d7822140e3fef190734216cefb262521789367fbdc0b3f22af6744058982"}, + {file = "black-23.10.1-py3-none-any.whl", hash = "sha256:d431e6739f727bb2e0495df64a6c7a5310758e87505f5f8cde9ff6c0f2d7e4fe"}, + {file = "black-23.10.1.tar.gz", hash = "sha256:1f8ce316753428ff68749c65a5f7844631aa18c8679dfd3ca9dc1a289979c258"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "distlib" +version = "0.3.7" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, + {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, +] + +[[package]] +name = "filelock" +version = "3.12.4" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"}, + {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] +typing = ["typing-extensions (>=4.7.1)"] + +[[package]] +name = "freetype-py" +version = "2.4.0" +description = "Freetype python bindings" +optional = false +python-versions = ">=3.7" +files = [ + {file = "freetype-py-2.4.0.zip", hash = "sha256:8ad81195d2f8f339aba61700cebfbd77defad149c51f59b75a2a5e37833ae12e"}, + {file = "freetype_py-2.4.0-py3-none-macosx_10_9_universal2.whl", hash = "sha256:3e0f5a91bc812f42d98a92137e86bac4ed037a29e43dafdb76d716d5732189e8"}, + {file = "freetype_py-2.4.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9a3abc277f5f6d21575c0093c0c6139c161bf05b91aa6258505ab27c5001c5e"}, + {file = "freetype_py-2.4.0-py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ce931f581d5038c4fea1f3d314254e0264e92441a5fdaef6817fe77b7bb888d3"}, + {file = "freetype_py-2.4.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:c6276d92ac401c8ce02ea391fc854de413b01a8d835fb394ee5eb6f04fc947f5"}, + {file = "freetype_py-2.4.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:9614f68876e9c62e821dfa811dd6160e00279d9d98cf60118cb264be48da1472"}, + {file = "freetype_py-2.4.0-py3-none-win_amd64.whl", hash = "sha256:a2620788d4f0c00bd75fee2dfca61635ab0da856131598c96e2355d5257f70e5"}, +] + +[[package]] +name = "identify" +version = "2.5.30" +description = "File identification library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "identify-2.5.30-py2.py3-none-any.whl", hash = "sha256:afe67f26ae29bab007ec21b03d4114f41316ab9dd15aa8736a167481e108da54"}, + {file = "identify-2.5.30.tar.gz", hash = "sha256:f302a4256a15c849b91cfcdcec052a8ce914634b2f77ae87dad29cd749f2d88d"}, +] + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "nodeenv" +version = "1.8.0" +description = "Node.js virtual environment builder" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "pathspec" +version = "0.11.2" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, +] + +[[package]] +name = "platformdirs" +version = "3.11.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, + {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + +[[package]] +name = "pre-commit" +version = "3.5.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, + {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "ruff" +version = "0.0.292" +description = "An extremely fast Python linter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.0.292-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:02f29db018c9d474270c704e6c6b13b18ed0ecac82761e4fcf0faa3728430c96"}, + {file = "ruff-0.0.292-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:69654e564342f507edfa09ee6897883ca76e331d4bbc3676d8a8403838e9fade"}, + {file = "ruff-0.0.292-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c3c91859a9b845c33778f11902e7b26440d64b9d5110edd4e4fa1726c41e0a4"}, + {file = "ruff-0.0.292-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4476f1243af2d8c29da5f235c13dca52177117935e1f9393f9d90f9833f69e4"}, + {file = "ruff-0.0.292-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be8eb50eaf8648070b8e58ece8e69c9322d34afe367eec4210fdee9a555e4ca7"}, + {file = "ruff-0.0.292-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9889bac18a0c07018aac75ef6c1e6511d8411724d67cb879103b01758e110a81"}, + {file = "ruff-0.0.292-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6bdfabd4334684a4418b99b3118793f2c13bb67bf1540a769d7816410402a205"}, + {file = "ruff-0.0.292-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7c77c53bfcd75dbcd4d1f42d6cabf2485d2e1ee0678da850f08e1ab13081a8"}, + {file = "ruff-0.0.292-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e087b24d0d849c5c81516ec740bf4fd48bf363cfb104545464e0fca749b6af9"}, + {file = "ruff-0.0.292-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f160b5ec26be32362d0774964e218f3fcf0a7da299f7e220ef45ae9e3e67101a"}, + {file = "ruff-0.0.292-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ac153eee6dd4444501c4bb92bff866491d4bfb01ce26dd2fff7ca472c8df9ad0"}, + {file = "ruff-0.0.292-py3-none-musllinux_1_2_i686.whl", hash = "sha256:87616771e72820800b8faea82edd858324b29bb99a920d6aa3d3949dd3f88fb0"}, + {file = "ruff-0.0.292-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b76deb3bdbea2ef97db286cf953488745dd6424c122d275f05836c53f62d4016"}, + {file = "ruff-0.0.292-py3-none-win32.whl", hash = "sha256:e854b05408f7a8033a027e4b1c7f9889563dd2aca545d13d06711e5c39c3d003"}, + {file = "ruff-0.0.292-py3-none-win_amd64.whl", hash = "sha256:f27282bedfd04d4c3492e5c3398360c9d86a295be00eccc63914438b4ac8a83c"}, + {file = "ruff-0.0.292-py3-none-win_arm64.whl", hash = "sha256:7f67a69c8f12fbc8daf6ae6d36705037bde315abf8b82b6e1f4c9e74eb750f68"}, + {file = "ruff-0.0.292.tar.gz", hash = "sha256:1093449e37dd1e9b813798f6ad70932b57cf614e5c2b5c51005bf67d55db33ac"}, +] + +[[package]] +name = "setuptools" +version = "68.2.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, + {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "virtualenv" +version = "20.24.6" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.24.6-py3-none-any.whl", hash = "sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381"}, + {file = "virtualenv-20.24.6.tar.gz", hash = "sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<4" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "4c19ea3e9aa03c53c7f3d392efccdcce03bec01cb32a9d6f8c4336204f210c34" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..46400dc --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,41 @@ +[tool.poetry] +name = "micropython-font-to-py" +version = "0.34.0" +description = "" +authors = ["Peter Hinch"] +repository = "https://github.com/peterhinch/micropython-font-to-py" +license = "MIT" +readme = "README.md" +packages = [{ include = "font_to_py" }] +keywords = ["micropython", "font"] +include = [ + { path = "icon_fonts/**/*", format = "sdist" }, + { path = "writer/**/*", format = "sdist" }, + { path = "c_to_python_font.py", format = "sdist" }, + { path = "font_test.py", format = "sdist" }, + { path = "FONT_TO_PY.md", format = ["sdist", "wheel"] }, + { path = "Chinese_Japanese*", format = ["sdist", "wheel"] }, + { path = "cyrillic", format = ["sdist", "wheel"] }, + { path = "extended", format = ["sdist", "wheel"] }, +] + +[tool.poetry.dependencies] +python = "^3.11" +freetype-py = "^2.4.0" +click = "^8.1.7" + +[tool.poetry.group.dev.dependencies] +black = "^23.9.1" +ruff = "^0.0.292" +pre-commit = "^3.4.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.ruff] +select = ["E", "F", "B", "Q", "N", "I", "UP", "PL", "RUF", "C90"] +line-length = 120 + +[tool.poetry.scripts] +font-to-py = "font_to_py.cli:main"