├── 1.jpeg ├── 20240225-P1.jpg ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── examples ├── GXEPD2_Text │ ├── GXEPD2_Text.ino │ ├── data │ │ └── hkjgxjh.ttf │ └── sleep.ino ├── LirygoEPD47 │ ├── LirygoEPD47.ino │ └── data │ │ └── AlexBrush.ttf └── truetypeToSerialTest │ └── truetypeToSerialTest.ino ├── library.properties └── src ├── truetype_Arduino.cpp └── truetype_Arduino.h /1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jie326513988/truetype_Arduino/26a5b85d3eb519cb4d453d245d9ed4d704d85a1b/1.jpeg -------------------------------------------------------------------------------- /20240225-P1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jie326513988/truetype_Arduino/26a5b85d3eb519cb4d453d245d9ed4d704d85a1b/20240225-P1.jpg -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at kosubadress0823@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 k-omura 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [观看视频](www.bilibili.com/video/BV1dx421Z7rG) 4 | 5 | ### 2024-02-25 6 | * 修复修复大部分字体标点符号异常,字符显示空白和位置异常的BUG, 7 | * fillGlyph()传入负值会导致系统崩溃 8 | * 以空格字符的2倍advanceWidth作为advanceWidthMAX值(从hmet表获取的advanceWidth异常时) 9 | * advanceWidth值异常时使用advanceWidthMAX值 10 | * 对leftSideBearing和glyph.xMin进行判断,以便能以正常的数值进行后续计算(从hmet表获取的leftSideBearing异常时) 11 | * 修复检测到换行符无法换行的BUG 12 | * 添加一个获取字符宽度的函数,就是在textDraw()中获取_x值,可以少计算一次宽度。 13 | 14 | 15 | #### 旧的消息 16 | * ~修复原版ascii符号会导致系统崩溃的问题~ 17 | * 修复原版内存泄漏问题 18 | * 添加与GXEPD2联动的例程,适用于库支持的墨水屏和esp8266&esp32 19 | * ~目前已知BUG,部分字体无法正常显示,部分字体会导致系统崩溃~ 20 | # Display truetype font for Arduino 21 | Read truetype(.ttf) from FS(ex. SD/SPIFFS/FATFS) and write framebuffer. 22 | 23 | - Read TrueType files ('cmap' format4). 24 | - Write any string to the user's framebuffer. 25 | - Supports "Simple glyphs" and part of "Compound glyphs". 26 | - Set font size, position, color, spacing, Rotate. 27 | - Centered and right aligned strings. By getting the length of the string. 28 | - Read the 'kern' table (format0) and kerning. 29 | - Beautifully arrange characters based on the 'hmtx' table. 30 | 31 | https://github.com/jie326513988/truetype_Arduino/blob/master/1.jpeg 32 | TrueType™ Reference Manual 33 | https://developer.apple.com/fonts/TrueType-Reference-Manual/ 34 | 35 | The native library for STM32 is [here](https://github.com/k-omura/STM32_UIKit). 36 | 37 | # Standard code 38 | ``` 39 | //TrueType class declaration 40 | truetypeClass truetype = truetypeClass(); 41 | uint8_t *framebuffer; 42 | 43 | void setup() { 44 | //Prepare a frame buffer 45 | framebuffer = (uint8_t *)calloc(sizeof(uint8_t), FRAMEBUFFER_SIZE); 46 | 47 | //Read TrueType file 48 | //Example in SPIFFS 49 | //I think that SD, FATFS and other codes will be almost the same 50 | SPIFFS.begin(true); 51 | File fontFile = SPIFFS.open("/FONTFILE.ttf", "r"); 52 | 53 | //Set framebuffer array in TrueType class 54 | //Pay attention to the format of the framebuffer 55 | truetype.setFramebuffer(DISPLAY_WIDTH, DISPLAY_HEIGHT, 4, 0, framebuffer); 56 | 57 | //Initial reading of ttf files 58 | if (!truetype.setTtfFile(fontFile)) { 59 | Serial.println("read ttf failed"); 60 | return; 61 | } 62 | 63 | //TrueType class string parameter settings 64 | truetype.setCharacterSize(100); 65 | truetype.setCharacterSpacing(0); 66 | truetype.setTextBoundary(10, DISPLAY_WIDTH, DISPLAY_HEIGHT); 67 | truetype.setTextColor(0x00, 0x00); 68 | 69 | //Write a string to the framebuffer 70 | truetype.textDraw(10, 10, "The quick brown fox jumps over the lazy dog"); 71 | 72 | //Export framebuffer to screen 73 | FLASH_TO_SCREEN(); 74 | 75 | //end 76 | truetype.end(); 77 | } 78 | ``` 79 | 80 | # API 81 | - uint8_t setTtfFile(File _file, uint8_t _checkCheckSum = 0); 82 | - Set the ttf file read from SD, SPIFFS, FATFS, etc. 83 | - File _file : ttf file. 84 | - Return : 1 = read successful, 0 = read failure. 85 | 86 | - void setFramebuffer(uint16_t _framebufferWidth, uint16_t _framebufferHeight, uint16_t _framebuffer_bit, uint8_t _framebufferDirection, uint8_t *_framebuffer); 87 | - Framebuffer settings. 88 | - uint16_t _framebufferWidth : Framebuffer width. 89 | - uint16_t _framebufferHeight : Framebuffer eight. 90 | - uint16_t _framebuffer_bit : The number of bits per pixel. (1,4,8bit implemented) 91 | - uint8_t _framebufferDirection : Bit orientation. 92 | - [See Framebuffer format](#Framebuffer-format) 93 | - uint8_t *_framebuffer : Framebuffer pointer. 94 | - If you want it to correspond to your own framebuffer, edit the addPixel function. If you add code, please share it! 95 | 96 | - void setCharacterSpacing(int16_t _characterSpace, uint8_t _kerning = 1); 97 | - Setting the width between characters. 98 | - int16_t _characterSpace : Width value between characters. 99 | - uint8_t _kerning : Read and use ttf 'kern' table. 1:'kern' + _characterSpace. 0: _characterSpace. 100 | 101 | - void setCharacterSize(uint16_t _characterSize); 102 | - Font size setting. 103 | - uint16_t _characterSize : Character height. 104 | 105 | - void setTextBoundary(uint16_t _start_x, uint16_t _end_x, uint16_t _end_y); 106 | - Setting the string range. 107 | - Coordinate axes rotate with "setTextRotation" 108 | - uint16_t _start_x : The starting point x of the character string when a line break occurs. 109 | - uint16_t _end_x : The final point x when breaking a line. 110 | - uint16_t _end_y : The final point y when breaking a line. 111 | 112 | - void setTextColor(uint8_t _onLine, uint8_t _inside); 113 | - Text color setting. 114 | - uint8_t _onLine : Character outline color. 115 | - uint8_t _inside : Text fill color. 116 | 117 | - void setTextRotation(uint8_t _rotation); 118 | - Text rotation 119 | - Rotate along with the coordinate axes. The image is the following image. 120 | 121 | 122 | - uint8_t _rotation : rotation angle. 123 | - (ROTATE_0/ROTATE_90/ROTATE_180/ROTATE_270) 124 | 125 | - void textDraw(uint16_t _x, uint16_t _y, const wchar_t _character[]); 126 | - Write a string to the framebuffer. 127 | - uint16_t _x : String start point x. 128 | - uint16_t _y : String start point y. 129 | - const wchar_t _character[] : String pointer (double-byte character). 130 | 131 | - void textDraw(uint16_t _x, uint16_t _y, const char _character[]); 132 | - Write a string to the framebuffer. 133 | - uint16_t _x : String start point x. 134 | - uint16_t _y : String start point y. 135 | - const char _character[] : String pointer (single-byte character). 136 | 137 | - void textDraw(uint16_t _x, uint16_t _y, const String _string); 138 | - Write a string to the framebuffer. 139 | - uint16_t _x : String start point x. 140 | - uint16_t _y : String start point y. 141 | - const String _string : String pointer (String type). 142 | 143 | - uint16_t getStringWidth(const wchar_t _character[]); 144 | - const wchar_t _character[] : String pointer (double-byte character). 145 | - Return : The length of the string. Automatic line breaks are not considered. 146 | - Can be used for text align center/right. 147 | 148 | - uint16_t getStringWidth(const char _character[]); 149 | - const char _character[] : String pointer (single-byte character). 150 | - Return : The length of the string. Automatic line breaks are not considered. 151 | - Can be used for text align center/right. 152 | 153 | - uint16_t getStringWidth(const String _string); 154 | - const String _string : String pointer (String type). 155 | - Return : The length of the string. Automatic line breaks are not considered. 156 | - Can be used for text align center/right. 157 | 158 | - void end(); 159 | - Close font file. 160 | 161 | # Framebuffer format 162 | Bit orientation when storing information for multiple pixels per byte of the framebuffer. 163 | The types of framebuffers are broadly divided according to this direction. 164 | Currently supported: Horizontal - 1,4,8bit 165 | ## Horizontal 166 | Example with 1bit / 1pixel 167 | 168 | 169 | ## Vertical 170 | Example with 1bit / 1pixel 171 | 172 | 173 | # Originality 174 | - Fixed problem that some font files could not be read. 175 | - Handling Bezier curves. 176 | - It supports up to 3rd order Bezier curves, but 4th order and above are drawn as straight lines. 177 | - Outline color and the fill color can be set individually. 178 | - Supports writing to arrays. Fonts are drawn in some form of framebuffer(uint8_t array). 179 | - Kerning by reading the 'kern' table. 180 | - Read 'hmtx' table and adjust layout. 181 | - Efficient filling process. 182 | 183 | # Future work 184 | ## TrueType 185 | - Full support for "Compound glyphs" 186 | - Diversification of supported framebuffer formats. 187 | - Only support for 'cmap' format 4 and 'kern' format0 is supported. 188 | - Correction that some files can not be read. 189 | - Unable to read ttf file if file name is long(STM32F103). 190 | - Faster glyph reading. 191 | - Decrease usage of SRAM. 192 | - Handling of Bezier curve(When exceeding 3 dimensions. Currently, provisional processing). 193 | ## Draw framebuffer 194 | - Text from the right 195 | - Underline 196 | 197 | # Confirmed controller 198 | - ESP32([Board](https://github.com/espressif/arduino-esp32)) 199 | - STM32F103C8T6([Board](https://github.com/stm32duino/Arduino_Core_STM32)) 200 | 201 | # Demo 202 | - Full color 203 | - ILI9341 [Demo](https://youtu.be/_-4tfssNTYE "ILI9341") 204 | - SSD1331 [Demo](https://youtu.be/wlubShLcMqE "SSD1331") 205 | - Mono 206 | - SSD1306 [Demo](https://youtu.be/WLiS6KDrS6Q "SSD1306") 207 | - Waveshare e-Paper 2.9inch [Demo](https://youtu.be/qs_nOYCx91o "e-Paper") 208 | - Waveshare e-Paper 7.5inch(B) [Demo](https://youtu.be/n9_DJ3ugalQ "e-Paper") 209 | 210 | # Note 211 | Feel free to post any bugs or ideas for fixes and improvements! 212 | Confirm the copyright of the font file. I did not distribute font files. 213 | 214 | It is based on the code by garretlab and changed. 215 | https://github.com/garretlab/truetype 216 | https://garretlab.web.fc2.com/arduino/lab/truetype/ 217 | -------------------------------------------------------------------------------- /examples/GXEPD2_Text/GXEPD2_Text.ino: -------------------------------------------------------------------------------- 1 | // 修复原版ascii符号会导致系统崩溃的问题 2 | // 修复原版内存泄漏问题 3 | // 添加与GXEPD2联动的例程,适用于墨水屏和esp8266 4 | // 目前已知BUG,部分字体无法正常显示,部分字体会导致系统崩溃 5 | // 目前测试成功的中文字体有造字工房系列、方正兰亭细黑、苹方黑体等 6 | 7 | #include 8 | #include // 官方要求的新文件系统库 #include "FS.h"未来将不会得官方支持,已弃用 9 | #include 10 | #define SPI_SPEED SD_SCK_MHZ(20) // SD卡频率 11 | #define SD_CS 5 // SD卡片选开关 12 | #define SD_POW 10 // SD卡电源开关 高电平打开低电平关闭 13 | 14 | #include "SdFat.h" 15 | SdFat32 sd; 16 | cid_t m_cid; 17 | csd_t m_csd; 18 | uint32_t m_eraseSize; 19 | uint32_t m_ocr; 20 | //#define SD_CONFIG SdSpiConfig(SD_CS, SHARED_SPI, SPI_SPEED) //DEDICATED_SPI(专用) SHARED_SPI(共享) 21 | SPISettings spi_settings(10000000, MSBFIRST, SPI_MODE0); 22 | 23 | 24 | #define ENABLE_GxEPD2_GFX 1 25 | #include 26 | //#include 27 | //#include 28 | #include 29 | 30 | //GxEPD2_BW display(GxEPD2_290(/*CS*/ 15, /*DC*/ 0, /*RST*/ 2, /*BUSY*/ 4)); // GDEM029A01 31 | GxEPD2_BW display(GxEPD2_154(/*CS=D8*/ 15, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 4)); // GDEP015OC1 200x200, IL3829, no longer available 32 | //GxEPD2_BW display(GxEPD2_420_M01(/*CS=D8*/ 15, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 4)); // GDEW042M01 33 | //GxEPD2_BW display(GxEPD2_420_Z96 (/*CS=D8*/ 15, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 4)); // GDEH042Z96 34 | 35 | U8G2_FOR_ADAFRUIT_GFX u8g2Fonts; 36 | #define baise GxEPD_WHITE //白色 37 | #define heise GxEPD_BLACK //黑色 38 | 39 | #include "truetype_Arduino.h" 40 | //#define MY_TTF "方正中等线_GBK.ttf" //简繁都有 但太粗 41 | //#define MY_TTF "方正黑体_GBK.ttf" //简繁都有 但太粗 42 | //#define MY_TTF "方正细等线_GBK.ttf" //无繁体 43 | //#define MY_TTF "4110_方正细黑一_GBK.ttf" // 复杂字不行 44 | //#define MY_TTF "文泉驿等宽正黑.ttf" // 标点符号不行 45 | //#define MY_TTF "造字工房典黑超细体.ttf" // 无繁体 46 | //#define MY_TTF "文鼎PL细上海宋.ttf" // 复杂字不行 47 | //#define MY_TTF "等线细.ttf" // 较粗 渲染快 48 | //#define MY_TTF "苹方黑体.ttf" // 适中 渲染最快 49 | //#define MY_TTF "华康金刚黑极细.ttf" // 最细 渲染慢 无繁体 50 | #define MY_TTF "方正兰亭细黑.ttf" // 最好 51 | 52 | //TrueType类声明 53 | truetypeClass truetype = truetypeClass(); 54 | 55 | void setup() 56 | { 57 | Serial.begin(74880); 58 | 59 | //屏幕初始化 60 | //display.init(); 61 | display.init(0, 0, 10, 1); // 串口使能 初始化完全刷新使能 复位时间 ret上拉使能 62 | u8g2Fonts.begin(display); // 将u8g2过程连接到Adafruit GFX 63 | u8g2Fonts.setFontMode(1); // 使用u8g2透明模式(这是默认设置) 64 | u8g2Fonts.setFontDirection(0); // 从左到右(这是默认设置) 65 | u8g2Fonts.setForegroundColor(heise); // 设置前景色 66 | u8g2Fonts.setBackgroundColor(baise); // 设置背景色 67 | display.setRotation(1); // 设置方向 68 | 69 | Serial.println(""); 70 | 71 | /* 72 | if (!LittleFS.begin()) { 73 | Serial.println("LittleFS挂载失败"); 74 | } 75 | else Serial.println("LittleFS挂载成功");*/ 76 | 77 | //SD卡挂载检查 78 | if (!sdBeginCheck()) ESP.deepSleep(0); 79 | 80 | clearScreen(); 81 | 82 | display.init(0, 0, 10, 1); // 串口使能 初始化完全刷新使能 复位时间 ret上拉使能 83 | display.setPartialWindow(0, 0, display.width(), display.height()); 84 | display.firstPage(); 85 | uint16_t ttf_y = 0; 86 | 87 | for (uint16_t i = 24; i <= 30; i++) 88 | { 89 | uint32_t time1 = millis(); 90 | String cs[7] = {"你,好 好", 91 | "He,ll o", 92 | "よし,よ し", 93 | "你,好", 94 | "He,llo", 95 | "よし,よし", 96 | "你,好", 97 | }; 98 | GXEPD2_truetype(0, ttf_y + i, String(i) + cs[i - 24], i); 99 | ttf_y = ttf_y + i + 0; 100 | Serial.println(millis() - time1); 101 | } 102 | 103 | /*String cs[2] = {"二愣子睁大着双眼,直直望着茅草和烂泥糊成的黑屋顶,身上盖着的旧棉被,已呈", 104 | "深黄色,看不出原来的本来面目,还若有若无的散发着淡淡的霉味123456。" 105 | }; 106 | uint32_t time1 = millis(); 107 | GXEPD2_truetype(0, 25, cs[0], 25); 108 | GXEPD2_truetype(0, 125, cs[1], 25); 109 | Serial.println(millis() - time1); 110 | Serial.print("中文大小:"); Serial.println(getTTFWidth(25, "甘", MY_TTF));*/ 111 | display.nextPage(); 112 | } 113 | 114 | void loop() 115 | { 116 | esp_sleep(0); 117 | } 118 | 119 | void GXEPD2_truetype(uint16_t x, uint16_t y, String zf, uint8_t fontSize) 120 | { 121 | //***** 检查有几个字符 122 | uint16_t zf_count; 123 | uint16_t zf_length = zf.length(); 124 | for (uint16_t i = 0; i < zf_length; i++) 125 | { 126 | if ((zf[i]& B11100000) == B11100000) { 127 | zf_count++; 128 | i += 2; 129 | } 130 | else if ((zf[i]& B11000000) == B11000000) { 131 | zf_count++; 132 | i += 1; 133 | } 134 | else zf_count++; 135 | } 136 | 137 | //Serial.println(" "); 138 | //Serial.print("显示字符:"); Serial.println(zf); 139 | //Serial.print("字符数量:"); Serial.println(zf_count); 140 | //Serial.print("字号大小:"); Serial.println(fontSize); 141 | 142 | //***** ttf文件位置 143 | //File fontFile = LittleFS.open(MY_TTF, "r"); 144 | File fontFile = SDFS.open(MY_TTF, "r"); 145 | //Serial.print("字体名称:"); Serial.println(fontFile.name()); 146 | //Serial.print("字体大小:"); Serial.println(fontFile.size()); 147 | 148 | //在TrueType类中设置帧缓冲区数组 149 | //请注意帧缓冲区的格式 150 | //ttf文件的初始读取 151 | if (!truetype.setTtfFile(fontFile)) Serial.println("读取ttf失败"); 152 | else 153 | { 154 | // 字体大小、边界范围等参数 155 | truetype.setCharacterSize(fontSize); //设置字号大小 156 | truetype.setCharacterSpacing(0); //设置间距 157 | truetype.setTextColor(0x01, 0x01); //设置文本颜色 158 | truetype.setTextBoundary(0, display.width(), 1000); //设置字符边界 159 | uint8_t BITS_PER_PIXEL = 1; // 任何一个 1, 4, or 8 //每像素位 160 | //uint16_t DISPLAY_WIDTH = zf_count * fontSize; // 显示宽度 快速 161 | uint16_t DISPLAY_WIDTH = truetype.getStringWidth(zf); // 显示宽度 慢速 162 | uint16_t DISPLAY_HEIGHT = fontSize; // 显示高度 高度像素 163 | 164 | //display.width(), display.height() 165 | //Serial.print("getStringWidth:"); Serial.println(truetype.getStringWidth(zf)); 166 | //Serial.print("DISPLAY_WIDTH:"); Serial.println(DISPLAY_WIDTH); 167 | //Serial.print("DISPLAY_HEIGHT:"); Serial.println(DISPLAY_HEIGHT); 168 | 169 | //***** 准备一个帧缓冲区 170 | uint32_t FRAMEBUFFER_SIZE = DISPLAY_WIDTH * DISPLAY_HEIGHT; //帧缓冲区大小 171 | uint8_t *framebuffer; 172 | framebuffer = (uint8_t *)calloc(sizeof(uint8_t), 20000); 173 | 174 | //Serial.print("可用堆大小1:"); Serial.println(ESP.getFreeHeap()); 175 | if (!framebuffer) 176 | { 177 | Serial.println("分配内存失败"); 178 | truetype.end(); 179 | return; 180 | } 181 | //Serial.print("分配内存成功:"); Serial.println(FRAMEBUFFER_SIZE); 182 | truetype.setFramebuffer(DISPLAY_WIDTH, DISPLAY_HEIGHT, BITS_PER_PIXEL, 0, framebuffer); 183 | 184 | //将字符串写入帧缓冲区 185 | truetype.textDraw(0, 0, zf); 186 | 187 | //显示至屏幕 188 | display.drawBitmap(x, y - fontSize, framebuffer, DISPLAY_WIDTH, DISPLAY_HEIGHT, heise); 189 | truetype.end(); 190 | 191 | //一定要释放内存 192 | delete framebuffer; 193 | framebuffer = NULL; 194 | 195 | //Serial.print("可用堆大小2:"); Serial.println(ESP.getFreeHeap()); 196 | } 197 | } 198 | 199 | //SD卡挂载失败会导致软看门狗重启 200 | boolean sdBeginCheck() //SD挂载检查 201 | { 202 | //#define SdSpiConfig(SD_CS, SHARED_SPI, sdFrequency) //DEDICATED_SPI(专用) SHARED_SPI(共享) 203 | 204 | pinMode(SD_POW, OUTPUT); 205 | digitalWrite(SD_POW, 1); 206 | delay(1); 207 | 208 | SDFS.setConfig(SDFSConfig(SD_CS, SPI_SPEED)); 209 | 210 | SDFS.end(); //SDFS.end(); 211 | ESP.wdtDisable(); //ESP.wdtEnable(8000); 212 | //yield(); 213 | 214 | if (sd.cardBegin(SdSpiConfig(SD_CS, SHARED_SPI, SPI_SPEED))) //DEDICATED_SPI(专用) SHARED_SPI(共享) 215 | { 216 | SDFS.begin(); 217 | //sd.cardBegin(SD_CONFIG); 218 | Serial.println("SD OK"); 219 | ESP.wdtEnable(8000); 220 | return 1; 221 | } 222 | else 223 | { 224 | Serial.println("SD fail"); 225 | ESP.wdtEnable(8000); 226 | return 0; 227 | } 228 | return 0; 229 | } 230 | 231 | uint16_t getTTFWidth(uint8_t fontSize, String zf, String myttf) 232 | { 233 | File fontFile = SDFS.open(myttf, "r"); 234 | //Serial.print("字体名称:"); Serial.println(fontFile.name()); 235 | uint16_t zf_width; 236 | //ttf文件的初始读取 237 | if (!truetype.setTtfFile(fontFile)) return 0; //读取ttf文件失败 238 | else 239 | { 240 | // 字体大小、边界范围等参数 241 | truetype.setCharacterSize(fontSize); //设置字号大小 242 | truetype.setCharacterSpacing(0); //设置间距 243 | return truetype.getStringWidth(zf); 244 | } 245 | return 0; 246 | } 247 | -------------------------------------------------------------------------------- /examples/GXEPD2_Text/data/hkjgxjh.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jie326513988/truetype_Arduino/26a5b85d3eb519cb4d453d245d9ed4d704d85a1b/examples/GXEPD2_Text/data/hkjgxjh.ttf -------------------------------------------------------------------------------- /examples/GXEPD2_Text/sleep.ino: -------------------------------------------------------------------------------- 1 | //系统休眠 2 | void esp_sleep(uint32_t minutes) 3 | { 4 | digitalWrite(12, 0); //关闭电池测量 5 | pinMode(12, INPUT); //改为输入状态避免漏电 6 | display.hibernate(); //关闭电源屏幕并进入深度睡眠 display.powerOff()为仅关闭电源 7 | pinMode(SD_POW, OUTPUT); 8 | digitalWrite(SD_POW, 0); 9 | ESP.deepSleep(minutes * 1000, WAKE_RF_DEFAULT); //WAKE_RF_DEFAULT WAKE_RFCAL WAKE_NO_RFCAL WAKE_RF_DISABLED RF_DISABLED 10 | } 11 | 12 | void clearScreen() //清空屏幕 13 | { 14 | display.init(0, 0, 10, 1); 15 | display.setFullWindow(); 16 | display.firstPage(); 17 | display.nextPage(); 18 | } 19 | -------------------------------------------------------------------------------- /examples/LirygoEPD47/LirygoEPD47.ino: -------------------------------------------------------------------------------- 1 | /* 2 | LilyGo T5 4.7 inch E-paper - ESP32 3 | based on https://github.com/Xinyuan-LilyGO/LilyGo-EPD47 4 | 5 | Settings 6 | PSRAM:enable 7 | Flash size:16MB(128Mb) 8 | Partition Scheme:16MB Flash (2MB APP/12.5MB FAT) (Set according to the font file and program size) 9 | 10 | "ESP32 Sketch Data Upload" before writing the program 11 | https://github.com/me-no-dev/arduino-esp32fs-plugin 12 | */ 13 | 14 | #ifndef BOARD_HAS_PSRAM 15 | #error "Please enable PSRAM !!!" 16 | #endif 17 | 18 | #include "epd_driver.h" 19 | #include 20 | 21 | #include "truetype_Arduino.h" 22 | 23 | #define BATT_PIN 36 24 | 25 | //960x540 26 | uint8_t *framebuffer; 27 | bool EPD_done = LOW; 28 | Rect_t area1 = { 29 | .x = 0, 30 | .y = 60, 31 | .width = EPD_WIDTH, 32 | .height = EPD_HEIGHT - 60 33 | }; 34 | Rect_t area2 = { 35 | .x = 0, 36 | .y = 0, 37 | .width = EPD_WIDTH, 38 | .height = 60 39 | }; 40 | 41 | truetypeClass truetype = truetypeClass(); 42 | 43 | void setup() { 44 | Serial.begin(115200); 45 | 46 | //frameBuffer init 47 | framebuffer = (uint8_t *)ps_calloc(sizeof(uint8_t), EPD_WIDTH * EPD_HEIGHT / 2); 48 | if (!framebuffer) { 49 | Serial.println("alloc memory failed !!!"); 50 | while (1); 51 | } 52 | memset(framebuffer, 0xff, EPD_WIDTH * EPD_HEIGHT / 2); 53 | 54 | //trueType 55 | if (!FFat.begin(true)) { 56 | Serial.println("Mount Failed"); 57 | return; 58 | } 59 | Serial.println("File system mounted"); 60 | 61 | File fontFile = FFat.open("/AlexBrush.ttf", "r"); 62 | if (!fontFile) { 63 | Serial.println("Error opening the file"); 64 | return; 65 | } 66 | Serial.println(fontFile.name()); 67 | Serial.println(fontFile.size()); 68 | if (!truetype.setTtfFile(fontFile)) { 69 | Serial.println("read ttf failed"); 70 | return; 71 | } 72 | truetype.setFramebuffer(EPD_WIDTH, EPD_HEIGHT, 4, 0, framebuffer); 73 | truetype.setCharacterSize(100); 74 | truetype.setCharacterSpacing(0); 75 | truetype.setTextBoundary(10, EPD_WIDTH, EPD_HEIGHT); 76 | truetype.setTextColor(0x00, 0x00); 77 | 78 | truetype.textDraw(10, 100, "The quick brown fox jumps over the lazy dog"); 79 | 80 | epd_init(); 81 | epd_poweron(); 82 | epd_clear(); 83 | epd_draw_grayscale_image(epd_full_screen(), framebuffer); 84 | epd_poweroff(); 85 | } 86 | 87 | void loop() { 88 | } 89 | 90 | void clearBitmap(uint16_t _x1, uint16_t _y1, uint16_t _x2, uint16_t _y2, uint8_t _colorCode, uint16_t _epd_width, uint8_t _bitmap[]) { 91 | for (uint16_t x = _x1; x <= _x2; x++) { 92 | for (uint16_t y = _y1; y <= _y2; y++) { 93 | bitmap_pixel(x, y, _colorCode, _epd_width, _bitmap); 94 | } 95 | } 96 | } 97 | 98 | void bitmap_pixel(uint16_t _x, uint16_t _y, uint16_t _colorCode, uint16_t _epd_width, uint8_t _bitmap[]) { 99 | uint8_t *buf_ptr = &_bitmap[(_x / 2) + _y * (_epd_width / 2)]; 100 | _colorCode = _colorCode & 0b00001111; 101 | 102 | if (_x % 2) { 103 | *buf_ptr = (*buf_ptr & 0b00001111) + (_colorCode << 4); 104 | } else { 105 | *buf_ptr = (*buf_ptr & 0b11110000) + _colorCode; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /examples/LirygoEPD47/data/AlexBrush.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jie326513988/truetype_Arduino/26a5b85d3eb519cb4d453d245d9ed4d704d85a1b/examples/LirygoEPD47/data/AlexBrush.ttf -------------------------------------------------------------------------------- /examples/truetypeToSerialTest/truetypeToSerialTest.ino: -------------------------------------------------------------------------------- 1 | #include "FS.h" 2 | #include "SPIFFS.h" 3 | #include "truetype_Arduino.h" 4 | /* 5 | this example simply creates a bitmap with your text written on it using the ttf file you supply 6 | it the prints the output to Serial 7 | 8 | dont forget to 9 | create a folder in your sketch folder called data 10 | add the ttf to a folder called data in your sketch folder and call it by a short name SPIFFS has name length limitations 11 | then upload it to SPIFFS 12 | 13 | */ 14 | 15 | // just to be clear 16 | #define WIDTH_BYTES 8 17 | #define HEIGHT_PIXELS 40 18 | #define DISPLAY_HEIGHT HEIGHT_PIXELS 19 | #define FRAMEBUFFER_SIZE (WIDTH_BYTES * HEIGHT_PIXELS) 20 | #define BITS_PER_PIXEL 1 // either 1, 4, or 8 21 | 22 | #define DISPLAY_WIDTH (WIDTH_BYTES * (8 / BITS_PER_PIXEL)) 23 | #define MY_TTF "/FONTFILE.ttf" 24 | 25 | //TrueType class declaration 26 | truetypeClass truetype = truetypeClass(); 27 | 28 | void print_bitmap(uint8_t *framebuffer, uint16_t width_in_bytes, uint16_t height_in_pixels) { 29 | for (int i = 0; i < (width_in_bytes * height_in_pixels); i++) { 30 | if ((i % width_in_bytes) == 0) 31 | Serial.println(); 32 | for (uint8_t bits = 8; bits > 0; bits--) { 33 | if (_BV(bits - 1) & framebuffer[i]) 34 | Serial.print(" "); 35 | else 36 | Serial.print("*"); 37 | } 38 | // Serial.print(" "); // uncomment to show individual bytes 39 | } 40 | Serial.println(); 41 | } 42 | 43 | void setup() { 44 | Serial.begin(115200); 45 | delay(1000); 46 | 47 | //Prepare a frame buffer 48 | uint8_t *framebuffer; 49 | framebuffer = (uint8_t *)calloc(sizeof(uint8_t), FRAMEBUFFER_SIZE); 50 | if (!framebuffer) { 51 | Serial.println("alloc memory failed !!!"); 52 | while (1); 53 | } 54 | 55 | Serial.println(FRAMEBUFFER_SIZE); 56 | //Read TrueType file 57 | //Example in SPIFFS 58 | //I think that SD, FATFS and other codes will be almost the same 59 | SPIFFS.begin(true); 60 | File fontFile = SPIFFS.open(MY_TTF, "r"); 61 | Serial.println(fontFile.name()); 62 | Serial.println(fontFile.size()); 63 | 64 | //Set framebuffer array in TrueType class 65 | //Pay attention to the format of the framebuffer 66 | truetype.setFramebuffer(DISPLAY_WIDTH, BITS_PER_PIXEL, framebuffer); 67 | 68 | //Initial reading of ttf files 69 | if (!truetype.setTtfFile(fontFile)) { 70 | Serial.println("read ttf failed"); 71 | } else { 72 | //TrueType class string parameter settings 73 | truetype.setCharacterSize(20); 74 | truetype.setCharacterSpacing(0); 75 | truetype.setStringWidth(10, DISPLAY_WIDTH, DISPLAY_HEIGHT); 76 | truetype.setStringColor(0x01, 0x01); 77 | 78 | //Write a string to the framebuffer 79 | truetype.string(10, 10, L"Hello"); 80 | //Export framebuffer to screen 81 | //FLASH_TO_SCREEN(); 82 | print_bitmap(framebuffer, WIDTH_BYTES, HEIGHT_PIXELS); 83 | 84 | //end 85 | truetype.end(); 86 | } 87 | } 88 | 89 | void loop() { 90 | } 91 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=truetype_Arduino 2 | version=1 3 | author=k-omura 4 | maintainer=omura 5 | sentence=This is the Arduino library for display truetype. 6 | paragraph=This is the Arduino library for display truetype. 7 | category=Communication 8 | url=https://github.com/k-omura/truetype_Arduino 9 | architectures=* 10 | -------------------------------------------------------------------------------- /src/truetype_Arduino.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Read truetype(.ttf) from SD and generate bitmap. 3 | 4 | TrueType™ Reference Manual 5 | https://developer.apple.com/fonts/TrueType-Reference-Manual/ 6 | */ 7 | 8 | #include "truetype_Arduino.h" 9 | 10 | /* constructor */ 11 | 12 | truetypeClass::truetypeClass() {} 13 | 14 | /* ----------------public---------------- */ 15 | 16 | void truetypeClass::end() 17 | { 18 | file.close(); 19 | 20 | delete table; 21 | table = NULL; 22 | 23 | delete cmapEncoding; 24 | cmapEncoding = NULL; 25 | 26 | delete points; 27 | points = NULL; 28 | 29 | delete pointsToFill; 30 | pointsToFill = NULL; 31 | 32 | 33 | beginPoints = NULL; 34 | endPoints = NULL; 35 | userFrameBuffer = NULL; 36 | delete beginPoints; 37 | delete endPoints; 38 | delete userFrameBuffer; 39 | 40 | glyph.endPtsOfContours = NULL; 41 | glyph.points = NULL; 42 | delete glyph.endPtsOfContours; 43 | delete glyph.points; 44 | 45 | //free(glyph.points); 46 | //free(glyph.endPtsOfContours); 47 | //this->freePointsAll(); 48 | //this->freeGlyph(); 49 | } 50 | 51 | /* initialize */ 52 | uint8_t truetypeClass::setTtfFile(File _file, uint8_t _checkCheckSum) 53 | { 54 | if (_file == 0) 55 | { 56 | return 0; 57 | } 58 | this->file = _file; 59 | if (this->readTableDirectory(_checkCheckSum) == 0) 60 | { 61 | file.close(); 62 | return 0; 63 | } 64 | if (this->readCmap() == 0) 65 | { 66 | file.close(); 67 | return 0; 68 | } 69 | if (this->readHMetric() == 0) 70 | { 71 | file.close(); 72 | return 0; 73 | } 74 | 75 | // 获取advanceWidthMax的值,使用空格的两倍为MAX 76 | uint16_t code = 32; 77 | uint32_t charCodeID = this->codeToGlyphId(code); //将字符代码转换为字形id 78 | uint32_t charCodeID_pos = this->hmtxTablePos + (charCodeID * 4); 79 | _file.seek(charCodeID_pos); 80 | advanceWidthMax = this->getInt16t() * 2; 81 | 82 | //Serial.print("advanceWidthMax:");Serial.println(advanceWidthMax); 83 | this->readKern(); 84 | this->readHeadTable(); 85 | return 1; 86 | } 87 | 88 | //设置帧缓冲区 89 | void truetypeClass::setFramebuffer(uint16_t _framebufferWidth, uint16_t _framebufferHeight, uint16_t _framebuffer_bit, uint8_t _framebufferDirection, uint8_t *_framebuffer) { 90 | this->displayWidth = _framebufferWidth; 91 | this->displayHeight = _framebufferHeight; 92 | this->framebufferBit = _framebuffer_bit; 93 | this->framebufferDirection = _framebufferDirection; 94 | this->userFrameBuffer = _framebuffer; 95 | 96 | if (this->framebufferDirection) { 97 | //Framebuffer bit direction: Vertical 98 | } else { 99 | //Framebuffer bit direction: Horizontal 100 | switch (this->framebufferBit) { 101 | case 8: //8bit Horizontal 102 | this->displayWidthFrame = this->displayWidth; 103 | break; 104 | case 4: //4bit Horizontal 105 | this->displayWidthFrame = (this->displayWidth % 2 == 0) ? (this->displayWidth / 2 ) : (this->displayWidth / 2 + 1); 106 | break; 107 | case 1: //1bit Horizontal 108 | default: 109 | this->displayWidthFrame = (this->displayWidth % 8 == 0) ? (this->displayWidth / 8 ) : (this->displayWidth / 8 + 1); 110 | break; 111 | } 112 | } 113 | return; 114 | } 115 | 116 | void truetypeClass::setCharacterSize(uint16_t _characterSize) { 117 | this->characterSize = _characterSize; 118 | } 119 | 120 | void truetypeClass::setCharacterSpacing(int16_t _characterSpace, uint8_t _kerning) { 121 | this->characterSpace = _characterSpace; 122 | this->kerningOn = _kerning; 123 | } 124 | 125 | void truetypeClass::setTextBoundary(uint16_t _start_x, uint16_t _end_x, uint16_t _end_y) { 126 | this->start_x = _start_x; 127 | this->end_x = _end_x; 128 | this->end_y = _end_y; 129 | } 130 | 131 | void truetypeClass::setTextColor(uint8_t _onLine, uint8_t _inside) { 132 | this->colorLine = _onLine; 133 | this->colorInside = _inside; 134 | } 135 | 136 | void truetypeClass::setTextRotation(uint16_t _rotation) { 137 | switch (_rotation) { 138 | case ROTATE_90: 139 | case 90: 140 | _rotation = 1; 141 | break; 142 | case ROTATE_180: 143 | case 180: 144 | _rotation = 2; 145 | break; 146 | case ROTATE_270: 147 | case 270: 148 | _rotation = 3; 149 | break; 150 | default: 151 | _rotation = 0; 152 | break; 153 | } 154 | this->stringRotation = _rotation; 155 | } 156 | 157 | /* ----------------private---------------- */ 158 | /* calculate checksum */ 159 | uint32_t truetypeClass::calculateCheckSum(uint32_t offset, uint32_t length) { 160 | uint32_t checksum = 0L; 161 | 162 | length = (length + 3) / 4; 163 | file.seek(offset); 164 | 165 | while (length-- > 0) { 166 | checksum += this->getUInt32t(); 167 | } 168 | return checksum; 169 | } 170 | 171 | /* read table directory */ 172 | int truetypeClass::readTableDirectory(int checkCheckSum) 173 | { 174 | file.seek(numTablesPos); 175 | numTables = this->getUInt16t(); 176 | this->table = (ttTable_t *)malloc(sizeof(ttTable_t) * numTables); 177 | 178 | file.seek(tablePos); 179 | // Serial.println("---table list---"); 180 | for (int i = 0; i < numTables; i++) 181 | { 182 | for (int j = 0; j < 4; j++) 183 | { 184 | this->table[i].name[j] = this->getUInt8t(); 185 | // Serial.printf("%c", table[i].name[j]); 186 | } 187 | this->table[i].name[4] = '\0'; 188 | this->table[i].checkSum = this->getUInt32t(); 189 | this->table[i].offset = this->getUInt32t(); 190 | this->table[i].length = this->getUInt32t(); 191 | 192 | // Serial.printf("--%X", table[i].offset); 193 | // Serial.println(); 194 | } 195 | 196 | if (checkCheckSum) 197 | { 198 | for (int i = 0; i < numTables; i++) 199 | { 200 | if (strcmp(this->table[i].name, "head") != 0) 201 | { /* checksum of "head" is invalid */ 202 | uint32_t c = this->calculateCheckSum(this->table[i].offset, this->table[i].length); 203 | if (this->table[i].checkSum != c) 204 | { 205 | return 0; 206 | } 207 | } 208 | } 209 | } 210 | return 1; 211 | } 212 | 213 | /* read head table */ 214 | void truetypeClass::readHeadTable() { 215 | for (int i = 0; i < numTables; i++) { 216 | if (strcmp(table[i].name, "head") == 0) { 217 | file.seek(table[i].offset); 218 | 219 | headTable.version = this->getUInt32t(); 220 | headTable.revision = this->getUInt32t(); 221 | headTable.checkSumAdjustment = this->getUInt32t(); 222 | headTable.magicNumber = this->getUInt32t(); 223 | headTable.flags = this->getUInt16t(); 224 | headTable.unitsPerEm = this->getUInt16t(); 225 | for (int j = 0; j < 8; j++) { 226 | headTable.created[i] = this->getUInt8t(); 227 | } 228 | for (int j = 0; j < 8; j++) { 229 | headTable.modified[i] = this->getUInt8t(); 230 | } 231 | xMin = headTable.xMin = this->getInt16t(); 232 | yMin = headTable.yMin = this->getInt16t(); 233 | xMax = headTable.xMax = this->getInt16t(); 234 | yMax = headTable.yMax = this->getInt16t(); 235 | headTable.macStyle = this->getUInt16t(); 236 | headTable.lowestRecPPEM = this->getUInt16t(); 237 | headTable.fontDirectionHint = this->getInt16t(); 238 | headTable.indexToLocFormat = this->getInt16t(); 239 | headTable.glyphDataFormat = this->getInt16t(); 240 | } 241 | } 242 | } 243 | 244 | /* cmap */ 245 | /* read cmap format 4 */ 246 | uint8_t truetypeClass::readCmapFormat4() { 247 | file.seek(cmapFormat4.offset); 248 | if ((cmapFormat4.format = this->getUInt16t()) != 4) { 249 | return 0; 250 | } 251 | 252 | cmapFormat4.length = this->getUInt16t(); 253 | cmapFormat4.language = this->getUInt16t(); 254 | cmapFormat4.segCountX2 = this->getUInt16t(); 255 | cmapFormat4.searchRange = this->getUInt16t(); 256 | cmapFormat4.entrySelector = this->getUInt16t(); 257 | cmapFormat4.rangeShift = this->getUInt16t(); 258 | cmapFormat4.endCodeOffset = cmapFormat4.offset + 14; 259 | cmapFormat4.startCodeOffset = cmapFormat4.endCodeOffset + cmapFormat4.segCountX2 + 2; 260 | cmapFormat4.idDeltaOffset = cmapFormat4.startCodeOffset + cmapFormat4.segCountX2; 261 | cmapFormat4.idRangeOffsetOffset = cmapFormat4.idDeltaOffset + cmapFormat4.segCountX2; 262 | cmapFormat4.glyphIndexArrayOffset = cmapFormat4.idRangeOffsetOffset + cmapFormat4.segCountX2; 263 | 264 | return 1; 265 | } 266 | 267 | /* read cmap */ 268 | uint8_t truetypeClass::readCmap() { 269 | uint16_t platformId, platformSpecificId; 270 | uint32_t cmapOffset, tableOffset; 271 | uint8_t foundMap = 0; 272 | 273 | if ((cmapOffset = this->seekToTable("cmap")) == 0) { 274 | return 0; 275 | } 276 | 277 | cmapIndex.version = this->getUInt16t(); 278 | cmapIndex.numberSubtables = this->getUInt16t(); 279 | 280 | for (uint16_t i = 0; i < cmapIndex.numberSubtables; i++) { 281 | platformId = this->getUInt16t(); 282 | platformSpecificId = this->getUInt16t(); 283 | tableOffset = this->getUInt32t(); 284 | if ((platformId == 3) && (platformSpecificId == 1)) { 285 | cmapFormat4.offset = cmapOffset + tableOffset; 286 | this->readCmapFormat4(); 287 | foundMap = 1; 288 | break; 289 | } 290 | } 291 | 292 | if (foundMap == 0) { 293 | return 0; 294 | } 295 | 296 | return 1; 297 | } 298 | 299 | /* 将字符代码转换为字形id */ 300 | uint16_t truetypeClass::codeToGlyphId(uint16_t _code) 301 | { 302 | uint16_t start, end, idRangeOffset; 303 | int16_t idDelta; 304 | uint8_t found = 0; 305 | uint16_t offset, glyphId; 306 | //Serial.print("_code:");Serial.println(_code,HEX); 307 | //Serial.print("cmapFormat4.segCountX2:");Serial.println(cmapFormat4.segCountX2); 308 | for (int i = 0; i < cmapFormat4.segCountX2 >> 1; i++) 309 | { 310 | //yield(); 311 | file.seek(cmapFormat4.endCodeOffset + 2 * i); 312 | end = this->getUInt16t(); 313 | //Serial.print("end:");Serial.println(end,HEX); 314 | if (_code <= end) 315 | { 316 | file.seek(cmapFormat4.startCodeOffset + 2 * i); 317 | start = this->getUInt16t(); 318 | if (_code >= start) 319 | { 320 | //Serial.print("start:");Serial.println(start,HEX); 321 | file.seek(cmapFormat4.idDeltaOffset + 2 * i); 322 | idDelta = this->getInt16t(); 323 | file.seek(cmapFormat4.idRangeOffsetOffset + 2 * i); 324 | idRangeOffset = this->getUInt16t(); 325 | if (idRangeOffset == 0) 326 | { 327 | glyphId = (idDelta + _code) % 65536; 328 | //Serial.print("idDelta:");Serial.println(idDelta,HEX); 329 | //Serial.print("glyphId1:");Serial.println(glyphId,HEX); 330 | } 331 | else 332 | { 333 | offset = ((idRangeOffset >> 1) + i + _code - start - (cmapFormat4.segCountX2 >> 1)) * 2; 334 | file.seek(cmapFormat4.glyphIndexArrayOffset + offset); 335 | glyphId = this->getUInt16t(); 336 | //Serial.print("glyphId2:");Serial.println(glyphId,HEX); 337 | } 338 | 339 | found = 1; 340 | break; 341 | } 342 | } 343 | } 344 | if (!found) return 0; 345 | 346 | return glyphId; 347 | } 348 | 349 | /* kerning */ 350 | /* 读取字距表 */ 351 | uint8_t truetypeClass::readKern() { 352 | uint32_t nextTable; 353 | 354 | if (this->seekToTable("kern") == 0) { 355 | //Serial.println("kern not found"); 356 | return 0; 357 | } 358 | 359 | kernHeader.nTables = this->getUInt32t(); 360 | 361 | //only support up to 32 sub-tables 362 | if (kernHeader.nTables > 32) { 363 | kernHeader.nTables = 32; 364 | } 365 | 366 | for (uint8_t i = 0; i < kernHeader.nTables; i++) { 367 | uint16_t format; 368 | 369 | kernSubtable.length = this->getUInt32t(); 370 | nextTable = file.position() + kernSubtable.length; 371 | kernSubtable.coverage = this->getUInt16t(); 372 | 373 | format = (uint16_t)(kernSubtable.coverage >> 8); 374 | 375 | // only support format0 376 | if (format != 0) { 377 | file.seek(nextTable); 378 | continue; 379 | } 380 | 381 | // only use horizontal kerning tables 382 | if ((kernSubtable.coverage & 0x0003) != 0x0001) { 383 | file.seek(nextTable); 384 | continue; 385 | } 386 | 387 | //format0 388 | kernFormat0.nPairs = this->getUInt16t(); 389 | kernFormat0.searchRange = this->getUInt16t(); 390 | kernFormat0.entrySelector = this->getUInt16t(); 391 | kernFormat0.rangeShift = this->getUInt16t(); 392 | this->kernTablePos = file.position(); 393 | 394 | break; 395 | } 396 | 397 | return 1; 398 | } 399 | 400 | int16_t truetypeClass::getKerning(uint16_t _left_glyph, uint16_t _right_glyph) { 401 | //int16_t result = this->characterSpace; 402 | int16_t result = 0; 403 | uint32_t key0 = ((uint32_t)(_left_glyph) << 16) | (_right_glyph); 404 | 405 | file.seek(this->kernTablePos); 406 | for (uint16_t i = 0; i < kernFormat0.nPairs; i++) { 407 | uint32_t key1 = this->getUInt32t(); 408 | if (key0 == key1) { 409 | result = this->getInt16t(); 410 | break; 411 | } 412 | file.seek(file.position() + 2); 413 | } 414 | 415 | return result; 416 | } 417 | 418 | //hmtx. 每个字形的水平布局的度量信息 419 | uint8_t truetypeClass::readHMetric() 420 | { 421 | if (this->seekToTable("hmtx") == 0) 422 | { 423 | // Serial.println("hmtx not found"); 424 | return 0; 425 | } 426 | 427 | this->hmtxTablePos = file.position(); 428 | return 1; 429 | } 430 | 431 | ttHMetric_t truetypeClass::getHMetric(uint16_t _code) 432 | { 433 | ttHMetric_t result; 434 | result.advanceWidth = 0; 435 | file.seek(this->hmtxTablePos + (_code * 4)); 436 | result.advanceWidth = getUInt16t(); 437 | result.leftSideBearing = getInt16t(); 438 | 439 | // 限制advanceWidth,从hmet表获取的值不一定正确 440 | if (result.advanceWidth > advanceWidthMax) 441 | result.advanceWidth = advanceWidthMax; 442 | 443 | // 限制leftSideBearing,从hmet表获取的值不一定正确 444 | if (result.leftSideBearing > 0 && (result.leftSideBearing > glyph.xMin)) 445 | result.leftSideBearing = glyph.xMin; 446 | else if (result.leftSideBearing < 0 && glyph.xMin > 0) 447 | result.leftSideBearing = glyph.xMin; 448 | 449 | // left Side Bearing 450 | // 简称LSB从原点到字形bbox左侧边缘距离。这个值对于水平排版通常是正值,但是对于垂直排版通常则是负值。 451 | int32_t difference = this->yMax - this->yMin; 452 | result.advanceWidth = (result.advanceWidth * this->characterSize) / difference; 453 | result.leftSideBearing = (result.leftSideBearing * this->characterSize) / difference; 454 | return result; 455 | } 456 | 457 | /* get glyph offset */ 458 | uint32_t truetypeClass::getGlyphOffset(uint16_t index) { 459 | uint32_t offset = 0; 460 | 461 | for (int i = 0; i < numTables; i++) { 462 | if (strcmp(table[i].name, "loca") == 0) { 463 | if (headTable.indexToLocFormat == 1) { 464 | file.seek(table[i].offset + index * 4); 465 | offset = this->getUInt32t(); 466 | } else { 467 | file.seek(table[i].offset + index * 2); 468 | offset = this->getUInt16t() * 2; 469 | } 470 | break; 471 | } 472 | } 473 | 474 | for (int i = 0; i < numTables; i++) { 475 | if (strcmp(table[i].name, "glyf") == 0) { 476 | return (offset + table[i].offset); 477 | } 478 | } 479 | 480 | return 0; 481 | } 482 | 483 | /* 读取坐标 */ 484 | void truetypeClass::readCoords(char _xy, uint16_t _startPoint) { 485 | int16_t value = 0; 486 | uint8_t shortFlag, sameFlag; 487 | 488 | if (_xy == 'x') { 489 | shortFlag = FLAG_XSHORT; 490 | sameFlag = FLAG_XSAME; 491 | } else { 492 | shortFlag = FLAG_YSHORT; 493 | sameFlag = FLAG_YSAME; 494 | } 495 | 496 | for (uint16_t i = _startPoint; i < glyph.numberOfPoints; i++) { 497 | if (glyph.points[i].flag & shortFlag) { 498 | if (glyph.points[i].flag & sameFlag) { 499 | value += this->getUInt8t(); 500 | } else { 501 | value -= this->getUInt8t(); 502 | } 503 | } else if (~glyph.points[i].flag & sameFlag) { 504 | value += this->getUInt16t(); 505 | } 506 | 507 | if (_xy == 'x') { 508 | if (this->glyphTransformation.enableScale) { 509 | glyph.points[i].x = value + this->glyphTransformation.dx; 510 | } else { 511 | glyph.points[i].x = value + this->glyphTransformation.dx; 512 | } 513 | } else { 514 | if (this->glyphTransformation.enableScale) { 515 | glyph.points[i].y = value + this->glyphTransformation.dy; 516 | } else { 517 | glyph.points[i].y = value + this->glyphTransformation.dy; 518 | } 519 | } 520 | } 521 | } 522 | 523 | /* read simple glyph */ 524 | uint8_t truetypeClass::readSimpleGlyph(uint8_t _addGlyph) { 525 | uint8_t repeatCount; 526 | uint8_t flag; 527 | static uint16_t counterContours; 528 | static uint16_t counterPoints; 529 | 530 | if (glyph.numberOfContours <= 0) { 531 | return 0; 532 | } 533 | 534 | if (!_addGlyph) { 535 | counterContours = 0; 536 | counterPoints = 0; 537 | } 538 | 539 | if (_addGlyph) { 540 | glyph.endPtsOfContours = (uint16_t *)realloc(glyph.endPtsOfContours, (sizeof(uint16_t) * glyph.numberOfContours)); 541 | } else { 542 | glyph.endPtsOfContours = (uint16_t *)malloc((sizeof(uint16_t) * glyph.numberOfContours)); 543 | } 544 | 545 | for (uint16_t i = counterContours; i < glyph.numberOfContours; i++) { 546 | glyph.endPtsOfContours[i] = counterPoints + this->getUInt16t(); 547 | } 548 | 549 | file.seek(this->getUInt16t() + file.position()); 550 | 551 | for (uint16_t i = counterContours; i < glyph.numberOfContours; i++) { 552 | if (glyph.endPtsOfContours[i] > glyph.numberOfPoints) { 553 | glyph.numberOfPoints = glyph.endPtsOfContours[i]; 554 | } 555 | } 556 | glyph.numberOfPoints++; 557 | 558 | if (_addGlyph) { 559 | glyph.points = (ttPoint_t *)realloc(glyph.points, sizeof(ttPoint_t) * (glyph.numberOfPoints + glyph.numberOfContours)); 560 | } else { 561 | glyph.points = (ttPoint_t *)malloc(sizeof(ttPoint_t) * (glyph.numberOfPoints + glyph.numberOfContours)); 562 | } 563 | 564 | for (uint16_t i = counterPoints; i < glyph.numberOfPoints; i++) { 565 | flag = this->getUInt8t(); 566 | glyph.points[i].flag = flag; 567 | if (flag & FLAG_REPEAT) { 568 | repeatCount = this->getUInt8t(); 569 | while (repeatCount--) { 570 | glyph.points[++i].flag = flag; 571 | } 572 | } 573 | } 574 | 575 | this->readCoords('x', counterPoints); 576 | this->readCoords('y', counterPoints); 577 | 578 | counterContours = glyph.numberOfContours; 579 | counterPoints = glyph.numberOfPoints; 580 | 581 | return 1; 582 | } 583 | 584 | /* read Compound glyph */ 585 | uint8_t truetypeClass::readCompoundGlyph() { 586 | uint16_t glyphIndex; 587 | uint16_t flags; 588 | uint8_t numberOfGlyphs = 0; 589 | uint32_t offset; 590 | int32_t arg1, arg2; 591 | 592 | glyph.numberOfContours = 0; 593 | 594 | //Serial.println("---CompoundGlyph---"); 595 | 596 | do { 597 | flags = this->getUInt16t(); 598 | glyphIndex = this->getUInt16t(); 599 | 600 | this->glyphTransformation.enableScale = (flags & 0b00000001000) ? (1) : (0); 601 | 602 | if (flags & 0b00000000001) { 603 | arg1 = this->getInt16t(); 604 | arg2 = this->getInt16t(); 605 | } else { 606 | arg1 = this->getUInt8t(); 607 | arg2 = this->getUInt8t(); 608 | } 609 | 610 | if (flags & 0b00000000010) { 611 | this->glyphTransformation.dx = arg1; 612 | this->glyphTransformation.dy = arg2; 613 | } 614 | 615 | if (flags & 0b01000000000) { 616 | this->charCode = glyphIndex; 617 | } 618 | 619 | //Serial.printf("--%d: flag: 0x%04X index: %4d\n", numberOfGlyphs, flags, glyphIndex); 620 | //Serial.printf("dx: %3d, dy: %3d\n", this->glyphTransformation.dx, this->glyphTransformation.dy); 621 | //Serial.printf("Scaling: %d\n", this->glyphTransformation.enableScale); 622 | 623 | offset = file.position(); 624 | 625 | uint32_t glyphOffset = this->getGlyphOffset(glyphIndex); 626 | file.seek(glyphOffset); 627 | glyph.numberOfContours += this->getInt16t(); 628 | file.seek(glyphOffset + 10); 629 | 630 | if (numberOfGlyphs == 0) { 631 | this->readSimpleGlyph(); 632 | } else { 633 | this->readSimpleGlyph(1); 634 | } 635 | file.seek(offset); 636 | 637 | numberOfGlyphs++; 638 | this->glyphTransformation = {0, 0, 0, 1, 1}; //init 639 | } while (flags & 0b00000100000); 640 | 641 | return 1; 642 | } 643 | 644 | /* 读取字形 */ 645 | uint8_t truetypeClass::readGlyph(uint16_t _code, uint8_t _justSize) //justSize 仅限大小 646 | { 647 | //Serial.print("读取字形_code:"); Serial.println(_code, HEX); 648 | //Serial.print("读取字形_justSize:"); Serial.println(_justSize, HEX); 649 | //Serial.println(""); 650 | 651 | //glyph.numberOfContours = 0; //等高线数量 652 | //glyph.xMin = 0; 653 | //glyph.yMin = 0; 654 | //glyph.xMax = 0; 655 | //glyph.yMax = 0; 656 | //if (_code != 0) 657 | //{ 658 | uint32_t offset = this->getGlyphOffset(_code); 659 | file.seek(offset); 660 | //Serial.println("glyph.numberOfContours"); 661 | glyph.numberOfContours = this->getInt16t(); 662 | //Serial.println("glyph.xMin"); 663 | glyph.xMin = this->getInt16t(); 664 | //Serial.println("glyph.yMin"); 665 | glyph.yMin = this->getInt16t(); 666 | //Serial.println("glyph.xMax"); 667 | glyph.xMax = this->getInt16t(); 668 | //Serial.println("glyph.yMax"); 669 | glyph.yMax = this->getInt16t(); 670 | 671 | /*Serial.print("glyph.numberOfContours:");Serial.println(glyph.numberOfContours); 672 | Serial.print("glyph.xMin:");Serial.println(glyph.xMin); 673 | Serial.print("glyph.yMin:");Serial.println(glyph.yMin); 674 | Serial.print("glyph.xMax:");Serial.println(glyph.xMax); 675 | Serial.print("glyph.yMax:");Serial.println(glyph.yMax);*/ 676 | //} 677 | 678 | this->glyphTransformation = {0, 0, 0, 1, 1}; //init 字形变换 679 | 680 | if (_justSize) { 681 | return 0; 682 | } 683 | 684 | if (glyph.numberOfContours >= 0) { 685 | return this->readSimpleGlyph(); //读取简单字形 686 | } 687 | else return this->readCompoundGlyph(); //读取复合字形 688 | 689 | return 0; 690 | } 691 | 692 | /* 释放Glyph内存 */ 693 | void truetypeClass::freeGlyph() 694 | { 695 | 696 | //Serial.println("glyph.points1:"); 697 | /*Serial.print("glyph.points->flag:");Serial.println(glyph.points->flag); 698 | Serial.print("glyph.points->x:");Serial.println(glyph.points->x); 699 | Serial.print("glyph.points->y:");Serial.println(glyph.points->y); 700 | Serial.println("");*/ 701 | free(glyph.points); 702 | free(glyph.endPtsOfContours); 703 | glyph.numberOfPoints = 0; 704 | } 705 | 706 | //生成位图 707 | void truetypeClass::generateOutline(int16_t _x, int16_t _y, uint16_t _width) 708 | { 709 | this->points = NULL; 710 | this->numPoints = 0; 711 | this->numBeginPoints = 0; 712 | this->numEndPoints = 0; 713 | 714 | int16_t x0, y0, x1, y1; 715 | 716 | uint16_t j = 0; 717 | 718 | for (uint16_t i = 0; i < glyph.numberOfContours; i++) 719 | { 720 | uint8_t firstPointOfContour = j; 721 | uint8_t lastPointOfContour = glyph.endPtsOfContours[i]; 722 | // Serial.print("---Contour--- "); 723 | // Serial.print(j); 724 | // Serial.print(" , "); 725 | // Serial.println(lastPointOfContour); 726 | 727 | // 旋转到曲线上的第一个点 728 | uint16_t numberOfRotations = 0; 729 | while ((firstPointOfContour + numberOfRotations) <= lastPointOfContour) 730 | { 731 | if (glyph.points[(firstPointOfContour + numberOfRotations)].flag & FLAG_ONCURVE) 732 | { 733 | break; 734 | } 735 | numberOfRotations++; 736 | } 737 | if ((j + numberOfRotations) <= lastPointOfContour) 738 | { 739 | for (uint16_t ii = 0; ii < numberOfRotations; ii++) 740 | { 741 | ttPoint_t tmp = glyph.points[firstPointOfContour]; 742 | for (uint16_t jj = firstPointOfContour; jj < lastPointOfContour; jj++) 743 | { 744 | glyph.points[jj] = glyph.points[jj + 1]; 745 | } 746 | glyph.points[lastPointOfContour] = tmp; 747 | } 748 | } 749 | 750 | while (j <= lastPointOfContour) 751 | { 752 | ttCoordinate_t pointsOfCurve[4]; 753 | 754 | // Serial.printf("%3d 0x%02X %5d %5d - deg - ", j, glyph.points[j].flag, glyph.points[j].x, glyph.points[j].y); 755 | 756 | // Examine the number of dimensions of a curve 757 | pointsOfCurve[0].x = glyph.points[j].x; 758 | pointsOfCurve[0].y = glyph.points[j].y; 759 | uint16_t searchPoint = (j == lastPointOfContour) ? (firstPointOfContour) : (j + 1); 760 | uint8_t degree = 1; 761 | while (searchPoint != j) 762 | { 763 | // Serial.printf("%5d 0x%02X %5d %5d - ", searchPoint, glyph.points[searchPoint].flag, glyph.points[searchPoint].x, glyph.points[searchPoint].y); 764 | if (degree < 4) 765 | { 766 | pointsOfCurve[degree].x = glyph.points[searchPoint].x; 767 | pointsOfCurve[degree].y = glyph.points[searchPoint].y; 768 | } 769 | if (glyph.points[searchPoint].flag & FLAG_ONCURVE) 770 | { 771 | break; 772 | } 773 | searchPoint = (searchPoint == lastPointOfContour) ? (firstPointOfContour) : (searchPoint + 1); 774 | degree++; 775 | } 776 | 777 | // Serial.printf(" ---- degree: %5d ", degree); 778 | // Replace Bezier curves of 4 dimensions or more with straight lines 779 | if (degree >= 4) 780 | { 781 | uint16_t tmp_j = j; 782 | uint16_t tmp_degree = 0; 783 | while (tmp_degree < degree) 784 | { 785 | if (tmp_j > lastPointOfContour) 786 | { 787 | tmp_j = firstPointOfContour; 788 | } 789 | glyph.points[tmp_j].flag |= FLAG_ONCURVE; 790 | tmp_j++; 791 | tmp_degree++; 792 | } 793 | } 794 | // Serial.println(); 795 | 796 | // Generate outline according to degree 797 | switch (degree) 798 | { 799 | case 3: // third-order Bezier curve 800 | x0 = pointsOfCurve[0].x; 801 | y0 = pointsOfCurve[0].y; 802 | 803 | for (float t = 0; t <= 1; t += 0.2) 804 | { 805 | x1 = (1 - t) * (1 - t) * (1 - t) * pointsOfCurve[0].x + 3 * (1 - t) * (1 - t) * t * pointsOfCurve[1].x + 3 * (1 - t) * t * t * pointsOfCurve[2].x + t * t * t * pointsOfCurve[3].x; 806 | y1 = (1 - t) * (1 - t) * (1 - t) * pointsOfCurve[0].y + 3 * (1 - t) * (1 - t) * t * pointsOfCurve[1].y + 3 * (1 - t) * t * t * pointsOfCurve[2].y + t * t * t * pointsOfCurve[3].y; 807 | 808 | this->addLine(map(x0, glyph.xMin, glyph.xMax, _x, _x + _width - 1), 809 | map(y0, this->yMin, this->yMax, _y + this->characterSize - 1, _y), 810 | map(x1, glyph.xMin, glyph.xMax, _x, _x + _width - 1), 811 | map(y1, this->yMin, this->yMax, _y + this->characterSize - 1, _y)); 812 | x0 = x1; 813 | y0 = y1; 814 | } 815 | break; 816 | case 2: // Second-order Bezier curve 817 | x0 = pointsOfCurve[0].x; 818 | y0 = pointsOfCurve[0].y; 819 | 820 | for (float t = 0; t <= 1; t += 0.2) 821 | { 822 | x1 = (1 - t) * (1 - t) * pointsOfCurve[0].x + 2 * t * (1 - t) * pointsOfCurve[1].x + t * t * pointsOfCurve[2].x; 823 | y1 = (1 - t) * (1 - t) * pointsOfCurve[0].y + 2 * t * (1 - t) * pointsOfCurve[1].y + t * t * pointsOfCurve[2].y; 824 | 825 | this->addLine(map(x0, glyph.xMin, glyph.xMax, _x, _x + _width - 1), 826 | map(y0, this->yMin, this->yMax, _y + this->characterSize - 1, _y), 827 | map(x1, glyph.xMin, glyph.xMax, _x, _x + _width - 1), 828 | map(y1, this->yMin, this->yMax, _y + this->characterSize - 1, _y)); 829 | x0 = x1; 830 | y0 = y1; 831 | } 832 | 833 | break; 834 | default: 835 | degree = 1; 836 | case 1: // straight line 837 | this->addLine(map(pointsOfCurve[0].x, glyph.xMin, glyph.xMax, _x, _x + _width - 1), 838 | map(pointsOfCurve[0].y, this->yMin, this->yMax, _y + this->characterSize - 1, _y), 839 | map(pointsOfCurve[1].x, glyph.xMin, glyph.xMax, _x, _x + _width - 1), 840 | map(pointsOfCurve[1].y, this->yMin, this->yMax, _y + this->characterSize - 1, _y)); 841 | break; 842 | } 843 | j += degree; 844 | } 845 | // Serial.println(this->numPoints); 846 | this->addEndPoint(this->numPoints - 1); 847 | this->addBeginPoint(this->numPoints); 848 | // Serial.println("---Contour end---"); 849 | } 850 | return; 851 | } 852 | 853 | /* Bresenham's line algorithm */ 854 | void truetypeClass::addLine(int16_t _x0, int16_t _y0, int16_t _x1, int16_t _y1) 855 | { 856 | // Serial.printf("addLine(%3d, %3d) -> (%3d, %3d)\n", _x0, _y0, _x1, _y1); 857 | uint16_t dx = abs(_x1 - _x0); 858 | uint16_t dy = abs(_y1 - _y0); 859 | int16_t sx, sy, err, e2; 860 | 861 | if (this->numPoints == 0) 862 | { 863 | this->addPoint(_x0, _y0); 864 | this->addBeginPoint(0); 865 | } 866 | this->addPoint(_x1, _y1); 867 | 868 | if (_x0 < _x1) 869 | { 870 | sx = 1; 871 | } 872 | else 873 | { 874 | sx = -1; 875 | } 876 | if (_y0 < _y1) 877 | { 878 | sy = 1; 879 | } 880 | else 881 | { 882 | sy = -1; 883 | } 884 | err = dx - dy; 885 | 886 | for (;;) 887 | { 888 | this->addPixel(_x0, _y0, this->colorLine); 889 | if ((_x0 == _x1) && (_y0 == _y1)) 890 | { 891 | break; 892 | } 893 | e2 = 2 * err; 894 | if (e2 > -dy) 895 | { 896 | err -= dy; 897 | _x0 += sx; 898 | } 899 | if (e2 < dx) 900 | { 901 | err += dx; 902 | _y0 += sy; 903 | } 904 | } 905 | } 906 | 907 | bool truetypeClass::isInside(int16_t _x, int16_t _y) { 908 | int16_t windingNumber = 0; 909 | uint16_t bpCounter = 0, epCounter = 0; 910 | ttCoordinate_t point = {_x, _y}; 911 | ttCoordinate_t point1; 912 | ttCoordinate_t point2; 913 | 914 | for (uint16_t i = 0; i < this->numPoints; i++) { 915 | point1 = this->points[i]; 916 | // Wrap? 917 | if (i == this->endPoints[epCounter]) { 918 | point2 = this->points[this->beginPoints[bpCounter]]; 919 | epCounter++; 920 | bpCounter++; 921 | } else { 922 | point2 = this->points[i + 1]; 923 | } 924 | 925 | if (point1.y <= point.y) { 926 | if (point2.y > point.y) { 927 | if (this->isLeft(&point1, &point2, &point) > 0) { 928 | windingNumber++; 929 | } 930 | } 931 | } else { 932 | // start y > point.y (no test needed) 933 | if (point2.y <= point.y) { 934 | if (this->isLeft(&point1, &point2, &point) < 0) { 935 | windingNumber--; 936 | } 937 | } 938 | } 939 | } 940 | 941 | return (windingNumber != 0); 942 | } 943 | 944 | void truetypeClass::fillGlyph(int16_t _x_min, int16_t _y_min, int16_t _width) 945 | { 946 | for (uint16_t y = _y_min; y < (_y_min + this->characterSize); y++) 947 | { 948 | ttCoordinate_t point1, point2; 949 | ttCoordinate_t point; 950 | point.y = y; 951 | 952 | uint16_t intersectPointsNum = 0; 953 | uint16_t bpCounter = 0; 954 | uint16_t epCounter = 0; 955 | uint16_t p2Num = 0; 956 | 957 | for (uint16_t i = 0; i < numPoints; i++) 958 | { 959 | point1 = this->points[i]; 960 | // Wrap? 961 | if (i == endPoints[epCounter]) 962 | { 963 | p2Num = beginPoints[bpCounter]; 964 | epCounter++; 965 | bpCounter++; 966 | } 967 | else 968 | { 969 | p2Num = i + 1; 970 | } 971 | point2 = this->points[p2Num]; 972 | 973 | if (point1.y <= y) 974 | { 975 | if (point2.y > y) 976 | { 977 | // Have a valid up intersect 有一个有效的上交叉 978 | intersectPointsNum++; 979 | pointsToFill = (ttWindIntersect_t *)realloc(pointsToFill, sizeof(ttWindIntersect_t) * intersectPointsNum); 980 | pointsToFill[intersectPointsNum - 1].p1 = i; 981 | pointsToFill[intersectPointsNum - 1].p2 = p2Num; 982 | pointsToFill[intersectPointsNum - 1].up = 1; 983 | } 984 | } 985 | else 986 | { 987 | // start y > point.y (no test needed) 开始y>point.y(无需测试) 988 | if (point2.y <= y) 989 | { 990 | // Have a valid down intersect 有一个有效的下交点 991 | intersectPointsNum++; 992 | pointsToFill = (ttWindIntersect_t *)realloc(pointsToFill, sizeof(ttWindIntersect_t) * intersectPointsNum); 993 | pointsToFill[intersectPointsNum - 1].p1 = i; 994 | pointsToFill[intersectPointsNum - 1].p2 = p2Num; 995 | pointsToFill[intersectPointsNum - 1].up = 0; 996 | } 997 | } 998 | } 999 | 1000 | //Serial.print("_x_min:");Serial.println(_x_min); 1001 | //Serial.print("_width:");Serial.println(_width); 1002 | for (int16_t x = _x_min; x < (_x_min + _width); x++) 1003 | { 1004 | int16_t windingNumber = 0; 1005 | point.x = x; 1006 | 1007 | for (uint16_t i = 0; i < intersectPointsNum; i++) 1008 | { 1009 | point1 = this->points[pointsToFill[i].p1]; 1010 | point2 = this->points[pointsToFill[i].p2]; 1011 | 1012 | if (pointsToFill[i].up == 1) 1013 | { 1014 | if (isLeft(&point1, &point2, &point) > 0) 1015 | { 1016 | windingNumber++; 1017 | } 1018 | } 1019 | else 1020 | { 1021 | if (isLeft(&point1, &point2, &point) < 0) 1022 | { 1023 | windingNumber--; 1024 | } 1025 | } 1026 | } 1027 | 1028 | if (windingNumber != 0) 1029 | { 1030 | this->addPixel(x, y, this->colorInside); 1031 | } 1032 | } 1033 | 1034 | free(pointsToFill); 1035 | pointsToFill = NULL; 1036 | } 1037 | } 1038 | 1039 | int32_t truetypeClass::isLeft(ttCoordinate_t *_p0, ttCoordinate_t *_p1, ttCoordinate_t *_point) { 1040 | return ((_p1->x - _p0->x) * (_point->y - _p0->y) - (_point->x - _p0->x) * (_p1->y - _p0->y)); 1041 | } 1042 | 1043 | void truetypeClass::textDraw(int16_t _x, int16_t _y, const wchar_t _character[]) 1044 | { 1045 | uint8_t c = 0; 1046 | uint16_t prev_code = 0; 1047 | // Serial.print("_character[c]:");Serial.println(_character[c], HEX); 1048 | while (_character[c] != '\0') 1049 | { 1050 | /*if ((_character[c] > 0 && _character[c] <= 32) || (_character[c] == L' ')) // 处理ASCII码 1051 | { 1052 | prev_code = 0; 1053 | if ((_character[c] == ' ') || (_character[c] == L' ')) // 半角空格和全角空格 1054 | { 1055 | _x += this->characterSize / 4; // 字符大小 1056 | } 1057 | c += 1; 1058 | continue; // 跳过 1059 | }*/ 1060 | 1061 | if ((_character[c] == ' ') || (_character[c] == L' ')) 1062 | { 1063 | prev_code = 0; 1064 | _x += this->characterSize / 4; 1065 | c++; 1066 | continue; // 跳过 1067 | } 1068 | 1069 | // Serial.printf("%c\n", _character[c]); 1070 | // Serial.print("_character[c]:");Serial.println(_character[c],HEX); 1071 | this->charCode = this->codeToGlyphId(_character[c]); // 将字符代码转换为字形id 1072 | // Serial.print("this->charCode:"); Serial.println(this->charCode, HEX); 1073 | // Serial.print("this->charCode:"); Serial.println(this->charCode); 1074 | this->readGlyph(this->charCode); // 读取字形 1075 | _x += this->characterSpace; 1076 | if (prev_code != 0 && this->kerningOn) 1077 | { 1078 | int16_t kern = this->getKerning(prev_code, this->charCode); // space between charctor 1079 | _x += (kern * (int16_t)this->characterSize) / (this->yMax - this->yMin); 1080 | } 1081 | prev_code = this->charCode; 1082 | ttHMetric_t hMetric = getHMetric(this->charCode); // 获取H度量 1083 | uint16_t width = this->characterSize * (glyph.xMax - glyph.xMin) / (this->yMax - this->yMin); 1084 | 1085 | // 到达显示器边缘和发现换行符时换下一行 1086 | if ((hMetric.leftSideBearing + width + _x) > this->end_x || _character[c] == '\n') 1087 | { 1088 | _x = this->start_x; 1089 | _y += this->characterSize; 1090 | if (_y > this->end_y) break; 1091 | 1092 | if (_character[c] == '\n') 1093 | { 1094 | //Serial.println("换行"); 1095 | //Serial.print("_x:"); Serial.println(_x); 1096 | //Serial.print("_y:"); Serial.println(_y); 1097 | c++; 1098 | continue; 1099 | } 1100 | } 1101 | 1102 | // Line breaks with line feed code 换行符和换行代码 1103 | /*if (_character[c] == '\n') 1104 | { 1105 | _x = this->start_x; 1106 | _y += this->characterSize; 1107 | if (_y > this->end_y) 1108 | break; 1109 | continue; 1110 | }*/ 1111 | 1112 | // Not compatible with Compound glyphs now 现在与复合字形不兼容 1113 | if (glyph.numberOfContours >= 0) 1114 | { 1115 | // Serial.print("hMetric.leftSideBearing:"); Serial.println(hMetric.leftSideBearing); 1116 | // Serial.print("width:"); Serial.println(width); 1117 | // 写入帧缓冲区 1118 | this->generateOutline(hMetric.leftSideBearing + _x, _y, width); 1119 | // 填充容器 1120 | this->fillGlyph(hMetric.leftSideBearing + _x, _y, width); 1121 | // Serial.println(); 1122 | } 1123 | this->freePointsAll(); 1124 | this->freeGlyph(); 1125 | 1126 | //_x += (hMetric.advanceWidth) ? (hMetric.advanceWidth) : (width); 1127 | // 使用哪个宽度? 1128 | if (hMetric.advanceWidth >= width) 1129 | _x += hMetric.advanceWidth; 1130 | else 1131 | _x += width; 1132 | lastWidth = _x; //记录宽度,下次调用时可节省计算时间 1133 | c++; 1134 | // Serial.println(); 1135 | } 1136 | } 1137 | 1138 | void truetypeClass::textDraw(int16_t _x, int16_t _y, const char _character[]) { 1139 | uint16_t length = 0; 1140 | while (_character[length] != '\0') { 1141 | length++; 1142 | } 1143 | wchar_t *wcharacter = (wchar_t *)calloc(sizeof(wchar_t), length + 1); 1144 | for (uint16_t i = 0; i < length; i++) { 1145 | wcharacter[i] = _character[i]; 1146 | } 1147 | this->textDraw(_x, _y, wcharacter); 1148 | free(wcharacter); 1149 | wcharacter = NULL; 1150 | } 1151 | 1152 | void truetypeClass::textDraw(int16_t _x, int16_t _y, const String _string) 1153 | { 1154 | uint16_t length = _string.length(); 1155 | wchar_t *wcharacter = (wchar_t *)calloc(sizeof(wchar_t), length + 1); 1156 | this->stringToWchar(_string, wcharacter); 1157 | this->textDraw(_x, _y, wcharacter); 1158 | free(wcharacter); 1159 | wcharacter = NULL; 1160 | } 1161 | 1162 | void truetypeClass::addPixel(int16_t _x, int16_t _y, uint8_t _colorCode) { 1163 | //Serial.printf("addPix(%3d, %3d)\n", _x, _y); 1164 | uint8_t *buf_ptr; 1165 | 1166 | //limit to boundary co-ordinates the boundary is always in the same orientation as the string not the buffer 1167 | if ((_x < this->start_x) || (_x >= this->end_x) || (_y >= this->end_y)) { 1168 | return; 1169 | } 1170 | 1171 | //Rotate co-ordinates relative to the buffer 1172 | uint16_t temp = _x; 1173 | switch (this->stringRotation) { 1174 | case ROTATE_270: 1175 | _x = _y; 1176 | _y = this->displayHeight - 1 - temp; 1177 | break; 1178 | case ROTATE_180: 1179 | _x = this->displayWidth - 1 - _x; 1180 | _y = this->displayHeight - 1 - _y; 1181 | break; 1182 | case ROTATE_90: 1183 | _x = this->displayWidth - 1 - _y; 1184 | _y = temp; 1185 | break; 1186 | case 0: 1187 | default: 1188 | break; 1189 | } 1190 | 1191 | //out of range 1192 | if ((_x < 0) || ((uint16_t)_x >= this->displayWidth) || ((uint16_t)_y >= this->displayHeight) || (_y < 0)) { 1193 | return; 1194 | } 1195 | 1196 | if (this->framebufferDirection) { 1197 | //Framebuffer bit direction: Vertical 1198 | } else { 1199 | //Framebuffer bit direction: Horizontal 1200 | switch (this->framebufferBit) { 1201 | case 8: //8bit Horizontal 1202 | { 1203 | this->userFrameBuffer[(uint16_t)_x + (uint16_t)_y * this->displayWidthFrame] = _colorCode; 1204 | } 1205 | break; 1206 | case 4: //4bit Horizontal 1207 | { 1208 | buf_ptr = &this->userFrameBuffer[((uint16_t)_x / 2) + (uint16_t)_y * this->displayWidthFrame]; 1209 | _colorCode = _colorCode & 0b00001111; 1210 | 1211 | if ((uint16_t)_x % 2) { 1212 | *buf_ptr = (*buf_ptr & 0b00001111) + (_colorCode << 4); 1213 | } else { 1214 | *buf_ptr = (*buf_ptr & 0b11110000) + _colorCode; 1215 | } 1216 | } 1217 | break; 1218 | case 1: //1bit Horizontal 1219 | default: 1220 | { 1221 | buf_ptr = &this->userFrameBuffer[((uint16_t)_x / 8) + (uint16_t)_y * this->displayWidthFrame]; 1222 | uint8_t bitMask = 0b10000000 >> ((uint16_t)_x % 8); 1223 | uint8_t bit = (_colorCode) ? (bitMask) : (0b00000000); 1224 | *buf_ptr = (*buf_ptr & ~bitMask) + bit; 1225 | } 1226 | break; 1227 | } 1228 | } 1229 | return; 1230 | } 1231 | //获取上次的宽度 1232 | uint16_t truetypeClass::getLastWidth() 1233 | { 1234 | return lastWidth; 1235 | } 1236 | uint16_t truetypeClass::getStringWidth(const wchar_t _character[]) 1237 | { 1238 | uint16_t prev_code = 0; 1239 | uint16_t c = 0; 1240 | uint16_t output = 0; 1241 | 1242 | while (_character[c] != '\0') 1243 | { 1244 | /*if ((_character[c] > 0 && _character[c] <= 32) || (_character[c] == L' ')) // 处理ASCII码 1245 | { 1246 | prev_code = 0; 1247 | if ((_character[c] == ' ') || (_character[c] == L' ')) // 半角空格和全角空格 1248 | { 1249 | output += this->characterSize / 4; // 字符大小 1250 | } 1251 | c += 1; 1252 | continue; // 跳过 1253 | }*/ 1254 | 1255 | //space (half-width, full-width) 1256 | if ((_character[c] == ' ') || (_character[c] == L' ')) 1257 | { 1258 | prev_code = 0; 1259 | output += this->characterSize / 4; 1260 | c++; 1261 | continue; 1262 | } 1263 | 1264 | uint16_t code = this->codeToGlyphId(_character[c]); 1265 | this->readGlyph(code, 1); 1266 | 1267 | output += this->characterSpace; 1268 | if (prev_code != 0 && this->kerningOn) { 1269 | int16_t kern = this->getKerning(prev_code, code); //space between charctor 1270 | output += (kern * (int16_t)this->characterSize) / (this->yMax - this->yMin); 1271 | } 1272 | prev_code = code; 1273 | 1274 | ttHMetric_t hMetric = getHMetric(code); 1275 | uint16_t width = this->characterSize * (glyph.xMax - glyph.xMin) / (this->yMax - this->yMin); 1276 | //output += (hMetric.advanceWidth) ? (hMetric.advanceWidth) : (width); 1277 | //使用哪个宽度? 1278 | if (hMetric.advanceWidth >= width) output += hMetric.advanceWidth; 1279 | else output += width; 1280 | c++; 1281 | } 1282 | 1283 | return output; 1284 | } 1285 | 1286 | uint16_t truetypeClass::getStringWidth(const char _character[]) { 1287 | uint16_t length = 0; 1288 | while (_character[length] != '\0') { 1289 | length++; 1290 | } 1291 | wchar_t *wcharacter = (wchar_t *)calloc(sizeof(wchar_t), length + 1); 1292 | for (uint16_t i = 0; i < length; i++) { 1293 | wcharacter[i] = _character[i]; 1294 | } 1295 | return this->getStringWidth(wcharacter); 1296 | free(wcharacter); 1297 | wcharacter = NULL; 1298 | } 1299 | 1300 | uint16_t truetypeClass::getStringWidth(const String _string) { 1301 | uint16_t length = _string.length(); 1302 | wchar_t *wcharacter = (wchar_t *)calloc(sizeof(wchar_t), length + 1); 1303 | this->stringToWchar(_string, wcharacter); 1304 | return this->getStringWidth(wcharacter); 1305 | free(wcharacter); 1306 | wcharacter = NULL; 1307 | } 1308 | 1309 | /* Points 添加点*/ 1310 | void truetypeClass::addPoint(int16_t _x, int16_t _y) 1311 | { 1312 | this->numPoints++; 1313 | this->points = (ttCoordinate_t *)realloc(this->points, sizeof(ttCoordinate_t) * this->numPoints); 1314 | this->points[(this->numPoints - 1)].x = _x; 1315 | this->points[(this->numPoints - 1)].y = _y; 1316 | } 1317 | //添加起点 1318 | void truetypeClass::addBeginPoint(uint16_t _bp) { 1319 | this->numBeginPoints++; 1320 | this->beginPoints = (uint16_t *)realloc(this->beginPoints, sizeof(uint16_t) * this->numBeginPoints); 1321 | this->beginPoints[(this->numBeginPoints - 1)] = _bp; 1322 | } 1323 | 1324 | void truetypeClass::addEndPoint(uint16_t _ep) { 1325 | this->numEndPoints++; 1326 | this->endPoints = (uint16_t *)realloc(this->endPoints, sizeof(uint16_t) * this->numEndPoints); 1327 | this->endPoints[(this->numEndPoints - 1)] = _ep; 1328 | } 1329 | 1330 | void truetypeClass::freePointsAll() { 1331 | this->freePoints(); 1332 | this->freeBeginPoints(); 1333 | this->freeEndPoints(); 1334 | } 1335 | 1336 | void truetypeClass::freePoints() 1337 | { 1338 | free(this->points); 1339 | this->points = NULL; 1340 | this->numPoints = 0; 1341 | } 1342 | 1343 | void truetypeClass::freeBeginPoints() 1344 | { 1345 | free(this->beginPoints); 1346 | this->beginPoints = NULL; 1347 | this->numBeginPoints = 0; 1348 | } 1349 | 1350 | void truetypeClass::freeEndPoints() 1351 | { 1352 | free(this->endPoints); 1353 | this->endPoints = NULL; 1354 | this->numEndPoints = 0; 1355 | } 1356 | 1357 | /* file */ 1358 | /* 查找指定表名的第一个位置 */ 1359 | uint32_t truetypeClass::seekToTable(const char *name) 1360 | { 1361 | //Serial.print("name:");Serial.println(name); 1362 | //Serial.print("this->numTables:");Serial.println(this->numTables); 1363 | for (uint32_t i = 0; i < this->numTables; i++) 1364 | { 1365 | //Serial.print("table[" + String(i) + "].name:"); 1366 | //Serial.println(table[i].name); 1367 | if (strcmp(table[i].name, name) == 0) 1368 | { 1369 | file.seek(table[i].offset); 1370 | //Serial.print("table[i].offset:");Serial.println(table[i].offset); 1371 | return table[i].offset; 1372 | } 1373 | } 1374 | return 0; 1375 | } 1376 | 1377 | /* calculate */ 1378 | void truetypeClass::stringToWchar(String _string, wchar_t _charctor[]) { 1379 | uint16_t s = 0; 1380 | uint8_t c = 0; 1381 | uint32_t codeu32; 1382 | //Serial.print("_string:");Serial.println(_string); 1383 | while (_string[s] != '\0') 1384 | { 1385 | int numBytes = GetU8ByteCount(_string[s]); 1386 | if (numBytes == 0) break; //防止无限循环 触发看门狗 1387 | //Serial.print("numBytes:");Serial.println(numBytes); 1388 | switch (numBytes) 1389 | { 1390 | case 1: 1391 | codeu32 = char32_t(uint8_t(_string[s])); 1392 | s += 1; 1393 | break; 1394 | case 2: 1395 | if (!IsU8LaterByte(_string[s + 1])) { 1396 | continue; 1397 | } 1398 | if ((uint8_t(_string[s]) & 0x1E) == 0) { 1399 | continue; 1400 | } 1401 | 1402 | codeu32 = char32_t(_string[s] & 0x1F) << 6; 1403 | codeu32 |= char32_t(_string[s + 1] & 0x3F); 1404 | s += 2; 1405 | break; 1406 | case 3: 1407 | if (!IsU8LaterByte(_string[s + 1]) || !IsU8LaterByte(_string[s + 2])) { 1408 | continue; 1409 | } 1410 | if ((uint8_t(_string[s]) & 0x0F) == 0 && 1411 | (uint8_t(_string[s + 1]) & 0x20) == 0) { 1412 | continue; 1413 | } 1414 | 1415 | codeu32 = char32_t(_string[s] & 0x0F) << 12; 1416 | codeu32 |= char32_t(_string[s + 1] & 0x3F) << 6; 1417 | codeu32 |= char32_t(_string[s + 2] & 0x3F); 1418 | s += 3; 1419 | break; 1420 | case 4: 1421 | if (!IsU8LaterByte(_string[s + 1]) || !IsU8LaterByte(_string[s + 2]) || 1422 | !IsU8LaterByte(_string[s + 3])) { 1423 | continue; 1424 | } 1425 | if ((uint8_t(_string[s]) & 0x07) == 0 && 1426 | (uint8_t(_string[s + 1]) & 0x30) == 0) { 1427 | continue; 1428 | } 1429 | 1430 | codeu32 = char32_t(_string[s] & 0x07) << 18; 1431 | codeu32 |= char32_t(_string[s + 1] & 0x3F) << 12; 1432 | codeu32 |= char32_t(_string[s + 2] & 0x3F) << 6; 1433 | codeu32 |= char32_t(_string[s + 3] & 0x3F); 1434 | s += 4; 1435 | break; 1436 | default: 1437 | continue; 1438 | } 1439 | 1440 | if (codeu32 < 0 || codeu32 > 0x10FFFF) { 1441 | continue; 1442 | } 1443 | 1444 | if (codeu32 < 0x10000) { 1445 | _charctor[c] = char16_t(codeu32); 1446 | } else { 1447 | _charctor[c] = ((char16_t((codeu32 - 0x10000) % 0x400 + 0xDC00)) << 8) || (char16_t((codeu32 - 0x10000) / 0x400 + 0xD800)); 1448 | } 1449 | c++; 1450 | } 1451 | _charctor[c] = 0; 1452 | } 1453 | 1454 | uint8_t truetypeClass::GetU8ByteCount(char _ch) 1455 | { 1456 | if (0 <= uint8_t(_ch) && uint8_t(_ch) < 0x80) { 1457 | return 1; 1458 | } 1459 | if (0xC2 <= uint8_t(_ch) && uint8_t(_ch) < 0xE0) { 1460 | return 2; 1461 | } 1462 | if (0xE0 <= uint8_t(_ch) && uint8_t(_ch) < 0xF0) { 1463 | return 3; 1464 | } 1465 | if (0xF0 <= uint8_t(_ch) && uint8_t(_ch) < 0xF8) { 1466 | return 4; 1467 | } 1468 | return 0; 1469 | } 1470 | 1471 | bool truetypeClass::IsU8LaterByte(char _ch) { 1472 | return 0x80 <= uint8_t(_ch) && uint8_t(_ch) < 0xC0; 1473 | } 1474 | 1475 | /* get uint8_t at the current position */ 1476 | uint8_t truetypeClass::getUInt8t() 1477 | { 1478 | uint8_t x; 1479 | 1480 | file.read(&x, 1); 1481 | return x; 1482 | } 1483 | 1484 | /* get int16_t at the current position 在当前位置获取int16_t */ 1485 | int16_t truetypeClass::getInt16t() 1486 | { 1487 | byte x[2]; 1488 | file.read(x, 2); 1489 | /*Serial.print("x[0]:"); Serial.println(x[0]); 1490 | Serial.print("x[1]:"); Serial.println(x[1]); 1491 | Serial.print("x:"); Serial.println((x[0] << 8) | x[1]);*/ 1492 | return (x[0] << 8) | x[1]; 1493 | } 1494 | 1495 | /* 获取uint16_t至当前位置 */ 1496 | uint16_t truetypeClass::getUInt16t() 1497 | { 1498 | byte x[2]; 1499 | file.read(x, 2); 1500 | /*Serial.print("x[0]:"); Serial.println(x[0]); 1501 | Serial.print("x[1]:"); Serial.println(x[1]); 1502 | Serial.print("x:"); Serial.println((x[0] << 8) | x[1]);*/ 1503 | return (x[0] << 8) | x[1]; 1504 | } 1505 | 1506 | /* get uint32_t at the current position */ 1507 | uint32_t truetypeClass::getUInt32t() 1508 | { 1509 | byte x[4]; 1510 | 1511 | file.read(x, 4); 1512 | return (x[0] << 24) | (x[1] << 16) | (x[2] << 8) | x[3]; 1513 | } 1514 | -------------------------------------------------------------------------------- /src/truetype_Arduino.h: -------------------------------------------------------------------------------- 1 | /* 2 | Read truetype(.ttf) from SD and generate bitmap. 3 | 4 | TrueType™ Reference Manual 5 | https://developer.apple.com/fonts/TrueType-Reference-Manual/ 6 | */ 7 | 8 | #define TRUETYPE_H 9 | 10 | #if !defined _SPI_H_INCLUDED 11 | #include "SPI.h" 12 | #endif /*_SPI_H_INCLUDED*/ 13 | 14 | #if defined ESP32 || ESP8266 15 | #include "FS.h" 16 | #endif /*FS_H*/ 17 | 18 | #define FLAG_ONCURVE (1 << 0) // 曲线 19 | #define FLAG_XSHORT (1 << 1) // X短 20 | #define FLAG_YSHORT (1 << 2) // Y短 21 | #define FLAG_REPEAT (1 << 3) // 重复 22 | #define FLAG_XSAME (1 << 4) // X相同 23 | #define FLAG_YSAME (1 << 5) // Y相同 24 | 25 | #define TEXT_ALIGN_LEFT 0 26 | #define TEXT_ALIGN_CENTER 1 27 | #define TEXT_ALIGN_RIGHT 2 28 | 29 | #define ROTATE_0 0 30 | #define ROTATE_90 1 31 | #define ROTATE_180 2 32 | #define ROTATE_270 3 33 | 34 | typedef struct { 35 | char name[5]; 36 | uint32_t checkSum; 37 | uint32_t offset; 38 | uint32_t length; 39 | } ttTable_t; 40 | 41 | typedef struct { 42 | uint32_t version; 43 | uint32_t revision; 44 | uint32_t checkSumAdjustment; 45 | uint32_t magicNumber; 46 | uint16_t flags; 47 | uint16_t unitsPerEm; 48 | char created[8]; 49 | char modified[8]; 50 | int16_t xMin; 51 | int16_t yMin; 52 | int16_t xMax; 53 | int16_t yMax; 54 | uint16_t macStyle; 55 | uint16_t lowestRecPPEM; 56 | int16_t fontDirectionHint; 57 | int16_t indexToLocFormat; 58 | int16_t glyphDataFormat; 59 | } ttHeadttTable_t; 60 | 61 | typedef struct { 62 | uint16_t flag; 63 | int16_t x; 64 | int16_t y; 65 | } ttPoint_t; 66 | 67 | typedef struct { 68 | int16_t numberOfContours; 69 | int16_t xMin; 70 | int16_t yMin; 71 | int16_t xMax; 72 | int16_t yMax; 73 | uint16_t *endPtsOfContours; 74 | uint16_t numberOfPoints; 75 | ttPoint_t *points; 76 | } ttGlyph_t; 77 | 78 | typedef struct { 79 | int16_t dx; 80 | int16_t dy; 81 | uint8_t enableScale; // 启用缩放 82 | uint16_t scale_x; // 缩放X 83 | uint16_t scale_y; // 缩放Y 84 | } ttGlyphTransformation_t; 85 | 86 | /* currently only support format4 cmap tables */ 87 | typedef struct { 88 | uint16_t version; 89 | uint16_t numberSubtables; 90 | } ttCmapIndex_t; 91 | 92 | typedef struct { 93 | uint16_t platformId; 94 | uint16_t platformSpecificId; 95 | uint16_t offset; 96 | } ttCmapEncoding_t; 97 | 98 | typedef struct 99 | { 100 | uint16_t format; // 子表格式,固定值为4 101 | uint16_t length; // 子表长度 102 | uint16_t language; // 语言码 103 | 104 | uint16_t segCountX2; // 2 x 查找段数 105 | uint16_t searchRange; // 2^(floor(log2(segCount))) * 2 106 | uint16_t entrySelector; // log2(searchRange/2) 107 | uint16_t rangeShift; // segCountX2 - searchRange 108 | 109 | uint32_t offset; // 子表偏移量 110 | 111 | uint32_t endCodeOffset; // 终止码偏移 112 | uint32_t startCodeOffset; // 起始码偏移 113 | uint32_t idDeltaOffset; // ID增量偏移 114 | uint32_t idRangeOffsetOffset; // ID范围偏移量偏移 115 | uint32_t glyphIndexArrayOffset; // 字形索引数组偏移 116 | } ttCmapFormat4_t; 117 | 118 | /* currently only support format0 kerning tables */ 119 | typedef struct { 120 | uint32_t version; //The version number of the kerning table (0x00010000 for the current version). 121 | uint32_t nTables; //The number of subtables included in the kerning table. 122 | } ttKernHeader_t; 123 | 124 | typedef struct { 125 | uint32_t length; //The length of this subtable in bytes, including this header. 126 | uint16_t coverage; //Circumstances under which this table is used. See below for description. 127 | } ttKernSubtable_t; 128 | 129 | typedef struct { 130 | uint16_t nPairs; //The number of kerning pairs in this subtable. 131 | uint16_t searchRange; //The largest power of two less than or equal to the value of nPairs, multiplied by the size in bytes of an entry in the subtable. 132 | uint16_t entrySelector; //This is calculated as log2 of the largest power of two less than or equal to the value of nPairs. This value indicates how many iterations of the search loop have to be made. For example, in a list of eight items, there would be three iterations of the loop. 133 | uint16_t rangeShift; //The value of nPairs minus the largest power of two less than or equal to nPairs. This is multiplied by the size in bytes of an entry in the table. 134 | } ttKernFormat0_t; 135 | 136 | typedef struct { 137 | int16_t x; 138 | int16_t y; 139 | } ttCoordinate_t; 140 | 141 | typedef struct { 142 | uint16_t advanceWidth; 143 | int16_t leftSideBearing; 144 | } ttHMetric_t; 145 | 146 | typedef struct { 147 | uint16_t p1; 148 | uint16_t p2; 149 | uint8_t up; 150 | } ttWindIntersect_t; 151 | 152 | class truetypeClass { 153 | public: 154 | truetypeClass(); 155 | 156 | uint8_t setTtfFile(File _file, uint8_t _checkCheckSum = 0); 157 | void setFramebuffer(uint16_t _framebufferWidth, uint16_t _framebufferHeight, uint16_t _framebuffer_bit, uint8_t _framebufferDirection, uint8_t *_framebuffer); 158 | void setCharacterSpacing(int16_t _characterSpace, uint8_t _kerning = 1); 159 | void setCharacterSize(uint16_t _characterSize); 160 | void setTextBoundary(uint16_t _start_x, uint16_t _end_x, uint16_t _end_y); 161 | void setTextColor(uint8_t _onLine, uint8_t _inside); 162 | #define setTextColour setTextColor //to satisfy a pedantic old Australian 163 | void setTextRotation(uint16_t _rotation); 164 | 165 | uint16_t getLastWidth(); //获取上一次的字符宽度 166 | uint16_t getStringWidth(const wchar_t _character[]); 167 | uint16_t getStringWidth(const char _character[]); 168 | uint16_t getStringWidth(const String _string); 169 | 170 | void textDraw(int16_t _x, int16_t _y, const wchar_t _character[]); 171 | void textDraw(int16_t _x, int16_t _y, const char _character[]); 172 | void textDraw(int16_t _x, int16_t _y, const String _string); 173 | 174 | void end(); 175 | 176 | private: 177 | File file; 178 | 179 | uint16_t charCode; 180 | int16_t xMin, xMax, yMin, yMax; 181 | 182 | const int numTablesPos = 4; 183 | const int tablePos = 12; 184 | 185 | uint16_t numTables; 186 | ttTable_t *table; 187 | ttHeadttTable_t headTable; 188 | 189 | uint8_t getUInt8t(); 190 | int16_t getInt16t(); 191 | uint16_t getUInt16t(); 192 | uint32_t getUInt32t(); 193 | int16_t swap_int16(int16_t _val); 194 | uint16_t swap_uint16(uint16_t _val); 195 | uint32_t swap_uint32(uint32_t _val); 196 | 197 | //basic 198 | uint32_t calculateCheckSum(uint32_t offset, uint32_t length); 199 | uint32_t seekToTable(const char *name); 200 | int readTableDirectory(int checkCheckSum); 201 | void readHeadTable(); 202 | void readCoords(char _xy, uint16_t _startPoint = 0); 203 | 204 | //Glyph 205 | ttGlyphTransformation_t glyphTransformation; 206 | uint32_t getGlyphOffset(uint16_t index); 207 | uint16_t codeToGlyphId(uint16_t code); 208 | uint8_t readSimpleGlyph(uint8_t _addGlyph = 0); 209 | uint8_t readCompoundGlyph(); 210 | 211 | //cmap. maps character codes to glyph indices 212 | // cmap. 将字符代码映射到图示符索引 213 | ttCmapIndex_t cmapIndex; 214 | ttCmapEncoding_t *cmapEncoding; 215 | ttCmapFormat4_t cmapFormat4; 216 | uint8_t readCmapFormat4(); 217 | uint8_t readCmap(); 218 | 219 | //hmtx. 每个字形的水平布局的度量信息 220 | uint32_t hmtxTablePos = 0; // 初始地址 221 | uint16_t advanceWidthMax = 0; // 水平宽度最大值,由特殊的字形 index 0得出 222 | uint16_t lastWidth = 0; // 上一次的宽度 223 | uint8_t readHMetric(); 224 | ttHMetric_t getHMetric(uint16_t _code); 225 | 226 | //kerning. 227 | ttKernHeader_t kernHeader; 228 | ttKernSubtable_t kernSubtable; 229 | ttKernFormat0_t kernFormat0; 230 | uint32_t kernTablePos = 0; 231 | uint8_t readKern(); 232 | int16_t getKerning(uint16_t _left_glyph, uint16_t _right_glyph); 233 | 234 | //生成点 235 | ttCoordinate_t *points; 236 | uint16_t numPoints; 237 | uint16_t *beginPoints; 238 | uint16_t numBeginPoints; 239 | uint16_t *endPoints; 240 | uint16_t numEndPoints; 241 | 242 | //glyf 243 | ttGlyph_t glyph; 244 | ttWindIntersect_t *pointsToFill; 245 | void generateOutline(int16_t _x, int16_t _y, uint16_t _width); 246 | void freePointsAll(); 247 | bool isInside(int16_t _x, int16_t _y); 248 | void fillGlyph(int16_t _x_min, int16_t _y_min, int16_t _width); 249 | uint8_t readGlyph(uint16_t code, uint8_t _justSize = 0); 250 | void freeGlyph(); 251 | 252 | void addLine(int16_t _x0, int16_t _y0, int16_t _x1, int16_t _y1); 253 | void addPoint(int16_t _x, int16_t _y); 254 | void freePoints(); 255 | void addBeginPoint(uint16_t _bp); 256 | void freeBeginPoints(); 257 | void addEndPoint(uint16_t _ep); 258 | void freeEndPoints(); 259 | int32_t isLeft(ttCoordinate_t *_p0, ttCoordinate_t *_p1, ttCoordinate_t *_point); 260 | 261 | //write user framebuffer 262 | uint16_t characterSize = 28; 263 | uint8_t kerningOn = 1; 264 | int16_t characterSpace = 0; 265 | int16_t start_x = 10; 266 | int16_t end_x = 300; 267 | int16_t end_y = 300; 268 | uint16_t displayWidth = 400; 269 | uint16_t displayHeight = 400; 270 | uint16_t displayWidthFrame = 400; 271 | uint16_t framebufferBit = 1; 272 | uint8_t framebufferDirection = 0; 273 | uint8_t stringRotation = 0x00; 274 | uint8_t colorLine = 0x00; 275 | uint8_t colorInside = 0x00; 276 | uint8_t *userFrameBuffer; 277 | void addPixel(int16_t _x, int16_t _y, uint8_t _colorCode); 278 | void stringToWchar(String _string, wchar_t _charctor[]); 279 | 280 | uint8_t GetU8ByteCount(char _ch); 281 | bool IsU8LaterByte(char _ch); 282 | }; 283 | --------------------------------------------------------------------------------