From ee220a64aaefb7acd9fbeda2594cd17cf9e7e02a Mon Sep 17 00:00:00 2001 From: Mirek Fidler Date: Tue, 28 Dec 2021 10:14:11 +0100 Subject: [PATCH] Draw: Color Emoji now prefered as replacement --- uppbox/Emoji/Emoji.upp | 11 ++ uppbox/Emoji/TypeReader.cpp | 214 ++++++++++++++++++++++++++++++++++++ uppbox/Emoji/TypeReader.h | 40 +++++++ uppbox/Emoji/main.cpp | 109 ++++++++++++++++++ uppbox/FontMaps2/main.cpp | 57 +++++++--- uppsrc/Draw/Draw.h | 7 ++ uppsrc/Draw/Font.cpp | 36 +++--- uppsrc/Draw/FontCR.cpp | 12 ++ upptst/CJK/main.cpp | 3 + 9 files changed, 459 insertions(+), 30 deletions(-) create mode 100644 uppbox/Emoji/Emoji.upp create mode 100644 uppbox/Emoji/TypeReader.cpp create mode 100644 uppbox/Emoji/TypeReader.h create mode 100644 uppbox/Emoji/main.cpp diff --git a/uppbox/Emoji/Emoji.upp b/uppbox/Emoji/Emoji.upp new file mode 100644 index 000000000..69e1a0a26 --- /dev/null +++ b/uppbox/Emoji/Emoji.upp @@ -0,0 +1,11 @@ +uses + CtrlLib; + +file + TypeReader.h, + TypeReader.cpp, + main.cpp; + +mainconfig + "" = "GUI"; + diff --git a/uppbox/Emoji/TypeReader.cpp b/uppbox/Emoji/TypeReader.cpp new file mode 100644 index 000000000..ca44f5b98 --- /dev/null +++ b/uppbox/Emoji/TypeReader.cpp @@ -0,0 +1,214 @@ +#include "TypeReader.h" + +#define LLOG(x) // LOG(x) +#define LDUMP(x) // LOG(#x << " = " << x); + +int FontTypeReader::Error() +{ + throw Fail(); return 0; +} + +int FontTypeReader::Peek8(const char *s) +{ + return s + 1 < data.end() ? (byte)*s : Error(); +} + +int FontTypeReader::Peek16(const char *s) +{ + return s + 2 < data.end() ? Peek16be(s) : Error(); +} + +int FontTypeReader::Peek32(const char *s) +{ + return s + 4 < data.end() ? Peek32be(s) : Error(); +} + +int FontTypeReader::Peek8(const char *s, int i) +{ + return Peek8(s + i); +} + +int FontTypeReader::Peek16(const char *s, int i) +{ + return Peek16(s + 2 * i); +} + +int FontTypeReader::Peek32(const char *s, int i) +{ + return Peek32(s + 4 * i); +} + +int FontTypeReader::Read8(const char *&s) +{ + int q = byte(*s); + s++; + return q; +} + +int FontTypeReader::Read16(const char *&s) +{ + int q = Peek16(s); + s += 2; + return q; +} + +int FontTypeReader::Read32(const char *&s) +{ + int q = Peek32(s); + s += 4; + return q; +} + +bool FontTypeReader::Open(Font font, bool symbol, bool justcheck) +{ +// DDUMP(fnt.GetCount()); + try { + int i; +#if 0 + for(i = 0; i < table.GetCount(); i++) + LLOG("table: " << table.GetKey(i) << " offset: " << table[i].offset << " length: " << table[i].length); + + s = Seek("hmtx"); + int aw = 0; + for(i = 0; i < hhea.numOfLongHorMetrics; i++) { + aw = glyphinfo[i].advanceWidth = (uint16)Read16(s); + glyphinfo[i].leftSideBearing = (int16)Read16(s); + } + for(; i < maxp.numGlyphs; i++) { + glyphinfo[i].advanceWidth = aw; + glyphinfo[i].leftSideBearing = (int16)Read16(s); + } + + s = Seek("loca"); + for(i = 0; i < maxp.numGlyphs; i++) + if(head.indexToLocFormat) { + glyphinfo[i].offset = Peek32(s, i); + glyphinfo[i].size = Peek32(s, i + 1) - glyphinfo[i].offset; + } + else { + glyphinfo[i].offset = 2 * (word)Peek16(s, i); + glyphinfo[i].size = 2 * (word)Peek16(s, i + 1) - glyphinfo[i].offset; + } + + for(i = 0; i < maxp.numGlyphs; i++) + LLOG(i << " advance: " << glyphinfo[i].advanceWidth << ", left: " << glyphinfo[i].leftSideBearing + << ", offset: " << glyphinfo[i].offset << ", size: " << glyphinfo[i].size); +#endif + + data = font.GetData("OS/2"); + panose.Clear(); + if(data.GetCount() > 32 + 10) + panose = data.Mid(32, 10); + + bool found = false; + data = font.GetData("cmap"); + for(int pass = 0; pass < 2; pass++) { + const char *p = data; +// LOGHEXDUMP(p, 256); + p += 2; + int n = Read16(p); +// DDUMP(n); + while(n--) { + int pid = Read16(p); + int psid = Read16(p); + int offset = Read32(p); + if(offset < 0 || offset > data.GetCount()) + Error(); + int format = Peek16(~data + offset); + LLOG("cmap pid: " << pid << " psid: " << psid << " format: " << format); + //Test with Symbol font !!!; improve - Unicode first, 256 bytes later..., symbol... + if(symbol) { + if(pid == 1 && psid == 0 && Peek16(~data + offset) == 0) { + LLOG("Reading symbol table"); + p = ~data + offset + 6; + for(int i = 0; i < 256; i++); + //SetGlyph(i, (byte)p[i]); + goto done; + } + } + else + if((pid == 3 && psid == 10) || (pid == 0 && psid == 4) && format == 12 && pass == 0) { + const char *p = ~data + offset; + int ngroups = Peek32(p + 12); + p += 16; // pointer to groups table + for(int i = 0; i < ngroups; i++) { + LLOG(Peek32(p) << " - " << Peek32(p + 4) << ", glyphi: " << Peek32(p + 8)); + ranges.Add(MakeTuple(Peek32(p), Peek32(p + 4))); + p += 12; + } + goto done; + } + else + if((pid == 3 && psid == 1) || (pid == 0 && psid == 3) && format == 4 && pass == 1) { + const char *p = ~data + offset; + int n = Peek16(p + 6) >> 1; + LLOG("Found UNICODE encoding, offset " << offset << ", segments " << n); + const char *seg_end = p + 14; + const char *seg_start = seg_end + 2 * n + 2; + const char *idDelta = seg_start + 2 * n; + const char *idRangeOffset = idDelta + 2 * n; + for(int i = 0; i < n; i++) { + int start = Peek16(seg_start, i); + int end = Peek16(seg_end, i); + ranges.Add(MakeTuple(start, end)); + #if 0 + int delta = Peek16(idDelta, i); + int ro = Peek16(idRangeOffset, i); + if (ro && delta == 0) { + LOG(start << " - " << end); + LLOG("RangeOffset start: " << start << ", end: " << end << ", delta: " << (int16)delta); + LLOG("ro " << ro); + // const char *q = idRangeOffset + 2 * i + ro; + // for(int c = start; c <= end; c++) { + // SetGlyph(c, (word)Read16(q)); + // } + } + else { + LOG(start << " - " << end); + LLOG("Delta start: " << start << ", end: " << end << ", delta: " << (int16)delta); + // for(int c = start; c <= end; c++); + // SetGlyph(c, c + delta); + } + #endif + } + break; + } + } + } + done:; + #if 0 + String names = font.GetData("name"); + if(names.GetCount()) { + const char *strings = names; + const char *s = strings + 2; + int count = Read16(s); + strings += (word)Read16(s); + for(int i = 0; i < count; i++) { + int platform = Read16(s); + s += 4; + if(Read16(s) == 6) { + int len = Read16(s); + int offset = Read16(s); + if(platform == 1) + ps_name = String(strings + offset, len); + else { + s = strings + offset; + len >>= 1; + while(len--) + ps_name.Cat(Read16(s)); + } + break; + } + s += 4; + } + } + DDUMP(ps_name); + #endif + } + catch(Fail) { + LLOG("ERROR"); + return false; + } + + return true; +} diff --git a/uppbox/Emoji/TypeReader.h b/uppbox/Emoji/TypeReader.h new file mode 100644 index 000000000..52d2ec541 --- /dev/null +++ b/uppbox/Emoji/TypeReader.h @@ -0,0 +1,40 @@ +#ifndef _FontMaps2_TypeReader_h_ +#define _FontMaps2_TypeReader_h_ + +#include + +using namespace Upp; + +class FontTypeReader { + String data; + + struct Table : Moveable { + int offset; + int length; + }; + VectorMap table; + + struct Fail {}; + static int Error(); + + int Peek8(const char *s); + int Peek16(const char *s); + int Peek32(const char *s); + int Peek8(const char *s, int i); + int Peek16(const char *s, int i); + int Peek32(const char *s, int i); + int Read8(const char *&s); + int Read16(const char *&s); + int Read32(const char *&s); + +public: + String ps_name; + + Array> ranges; + + String panose; + + bool Open(Font font, bool symbol = false, bool justcheck = false); +}; + +#endif diff --git a/uppbox/Emoji/main.cpp b/uppbox/Emoji/main.cpp new file mode 100644 index 000000000..8b49dc649 --- /dev/null +++ b/uppbox/Emoji/main.cpp @@ -0,0 +1,109 @@ +#include +#include "TypeReader.h" + +using namespace Upp; + +#define MAKE_TT_TABLE_NAME(c1, c2, c3, c4) \ + (((uint32)c4) << 24 | ((uint32)c3) << 16 | ((uint32)c2) << 8 | ((uint32)c1)) + +#define CMAP (MAKE_TT_TABLE_NAME('c','m','a','p')) + +#ifdef PLATFORM_WIN32 + +namespace Upp { +HFONT GetWin32Font(Font fnt, int angle); +}; + +String GetFontDataSys(Font font, dword table) +{ + String r; + HFONT hfont = GetWin32Font(font, 0); + if(hfont) { + HDC hdc = CreateIC("DISPLAY", NULL, NULL, NULL); + HFONT ohfont = (HFONT) ::SelectObject(hdc, hfont); + DWORD size = GetFontData(hdc, table, 0, NULL, 0); + if(size == GDI_ERROR) { + return Null; + } + else { + StringBuffer b(size); + GetFontData(hdc, table, 0, b, size); + r = b; + } + ::SelectObject(hdc, ohfont); + ::DeleteDC(hdc); + } + return r; +} + +#endif + +String ReadFontTable(Stream& in, const char *table, int fonti = 0) +{ + in.Seek(0); + int q = in.Get32be(); + if(q == 0x74746366) { // true type collection + in.Get32(); // skip major/minor version + int nfonts = in.Get32be(); + if(fonti >= nfonts) + return Null; + in.SeekCur(fonti * 4); + int offset = in.Get32be(); + if(offset < 0 || offset >= in.GetSize()) + return Null; + in.Seek(offset); + q = in.Get32be(); + } + if(q != 0x74727565 && q != 0x00010000) + return Null; + int n = in.Get16be(); + in.Get32(); + in.Get16(); + while(n--) { + if(in.IsError() || in.IsEof()) return Null; + String tab = in.Get(4); + in.Get32(); + int offset = in.Get32be(); + int length = in.Get32be(); + if(tab == table) { + if(offset < 0 || length < 0 || offset + length > in.GetSize()) + return Null; + in.Seek(offset); + return in.Get(length); + } + } + return Null; +} + +struct MyApp : TopWindow { + void Paint(Draw& w) { + String text; + text << WString(0x1F970, 1).ToString(); + text << WString(0x1F603, 1).ToString(); + text << WString(0x1F923, 1).ToString(); + text << " Quick brown fox, 訓民正音 (훈민정음) "; + + w.DrawRect(GetSize(), White()); + int y = 10; + int x = 10; + Font fnt(Font::FindFaceNameIndex("Noto Color Emoji"), 20); + FontTypeReader r; + r.Open(fnt); + for(auto r : r.ranges) { + for(int c = r.a; c <= r.b; c++) { + w.DrawText(x, y, WString(c, 1), fnt); + w.DrawText(x + fnt[c], y, WString(c, 1)); + w.DrawText(x + fnt[c] + StdFont()[c] + 4, y, Format("%x", c)); + y += 30; + if(y > GetSize().cy - 30) { + y = 10; + x += 130; + } + } + } + } +}; + +GUI_APP_MAIN { + MyApp().Sizeable().Zoomable().Run(); +} diff --git a/uppbox/FontMaps2/main.cpp b/uppbox/FontMaps2/main.cpp index 978841ed3..146f1627b 100644 --- a/uppbox/FontMaps2/main.cpp +++ b/uppbox/FontMaps2/main.cpp @@ -75,6 +75,37 @@ String ReadFontTable(Stream& in, const char *table, int fonti = 0) return Null; } +struct MyApp : TopWindow { + void Paint(Draw& w) { + String text; + text << WString(0x1F970, 1).ToString(); + text << WString(0x1F603, 1).ToString(); + text << WString(0x1F923, 1).ToString(); + text << " Quick brown fox, 訓民正音 (훈민정음) "; + + w.DrawRect(GetSize(), White()); + int y = 10; + int x = 10; + Font fnt(Font::FindFaceNameIndex("Noto Color Emoji"), 20); + FontTypeReader r; + r.Open(font); + for(auto r : r.ranges) { + for(int c = r.a; c <= r.b; c++) { + w.DrawText(x, y, WString(c, 1), fnt); + y += 30; + if(y > GetSize().cy - 30) { + y = 10; + x += 100; + } + } + } + } +}; + +GUI_APP_MAIN { + MyApp().Sizeable().Zoomable().Run(); +} + GUI_APP_MAIN { // FileIn in("C:/Windows/Fonts/arial.ttf"); @@ -85,24 +116,18 @@ GUI_APP_MAIN DDUMPHEX(ReadFontTable(in, "cmap", 2)); return; */ -#if 0 - Font font(Font::FindFaceNameIndex("Noto Serif CJK KR"), 20); - -// Font font = Roman(20); - -// SaveFile("d:/xxx/font.bin", GetFontDataSys(font, 0)); - -// String cmap = GetFontDataSys(font, CMAP); -// LOGHEXDUMP(~cmap, 256); - +#if 1 +/* for(int i = 0; i < Font::GetFaceCount(); i++) + DDUMP(Font::GetFaceName(i)); + return;*/ + Font font(Font::FindFaceNameIndex("Noto Color Emoji"), 20); FontTypeReader r; -// r.Open(Arial(20).GetData()); - DDUMP(font.GetFaceName()); r.Open(font); - DDUMPHEX(r.panose); - return; - - + for(auto r : r.ranges) { + DUMPHEX(r.a); + DUMPHEX(r.b); + RLOG("================="); + } return; #endif Progress pi; diff --git a/uppsrc/Draw/Draw.h b/uppsrc/Draw/Draw.h index da22eb8ed..5f037ccff 100644 --- a/uppsrc/Draw/Draw.h +++ b/uppsrc/Draw/Draw.h @@ -33,6 +33,13 @@ struct FontGlyphConsumer { class FontInfo; + +inline +bool PreferColorEmoji(int c) +{ // for these codepoints we prefer replacemnet color emoji even if glyphs is in the font + return c >= 0x2600 && c <= 0x27ef || c >= 0x1f004 && c <= 0x1f251 || c >= 0x1f300 && c <= 0x1faf6; +} + class Font : public ValueType >{ union { int64 data; diff --git a/uppsrc/Draw/Font.cpp b/uppsrc/Draw/Font.cpp index 8ec2d4924..aedab8de6 100644 --- a/uppsrc/Draw/Font.cpp +++ b/uppsrc/Draw/Font.cpp @@ -421,21 +421,29 @@ struct GlyphInfoMaker : ValueMaker { CharEntry& e = CreateRawValue(object); e.font = font.AsInt64(); e.chr = chr; - e.info = GetGlyphInfoSys(font, chr); - if(!e.info.IsNormal()) { - ComposedGlyph cg; - Font rfnt; - if(Compose(font, chr, cg)) { - e.info.lspc = -1; - e.info.rspc = (int16)cg.basic_char; + Font rfnt; + if(PreferColorEmoji(chr) && !(font.GetFaceInfo() & Font::COLORIMG) + && Replace(font, chr, rfnt) && rfnt != font) { + e.info.width = 0x8000; + e.info.lspc = rfnt.GetFace(); + e.info.rspc = rfnt.GetHeight(); + } + else { + e.info = GetGlyphInfoSys(font, chr); + if(!e.info.IsNormal()) { + ComposedGlyph cg; + if(Compose(font, chr, cg)) { + e.info.lspc = -1; + e.info.rspc = (int16)cg.basic_char; + } + else + if(Replace(font, chr, rfnt)) { + e.info.lspc = rfnt.GetFace(); + e.info.rspc = rfnt.GetHeight(); + } + else + e.info.lspc = -2; } - else - if(Replace(font, chr, rfnt)) { - e.info.lspc = rfnt.GetFace(); - e.info.rspc = rfnt.GetHeight(); - } - else - e.info.lspc = -2; } return sizeof(e); } diff --git a/uppsrc/Draw/FontCR.cpp b/uppsrc/Draw/FontCR.cpp index c50c5e791..ae0a233dd 100644 --- a/uppsrc/Draw/FontCR.cpp +++ b/uppsrc/Draw/FontCR.cpp @@ -338,7 +338,9 @@ int PanoseDistance(byte *a, byte *b) bool Replace(Font fnt, int chr, Font& rfnt) { + bool prefer_color = PreferColorEmoji(chr); static VectorMap rface[2]; // face index to font info + static Vector color[2]; // colorimg faces static bool all_loaded; if(rface[0].GetCount() == 0) { for(int i = 0; i < __countof(sFontReplacements); i++) { @@ -347,6 +349,9 @@ bool Replace(Font fnt, int chr, Font& rfnt) rface[0].Add(q) = &sFontReplacements[i]; } } + for(int i = 0; i < Font::GetFaceCount(); i++) + if(Font::GetFaceInfo(i) & Font::COLORIMG) + color[0].Add(i); } int face = fnt.GetFace(); @@ -399,6 +404,11 @@ bool Replace(Font fnt, int chr, Font& rfnt) distance.Add(PanoseDistance(rface[pass][i]->panose, panose)); candidate.Add(rface[pass].GetKey(i)); } + if(prefer_color) + for(int fi : color[pass]) { + distance.Add(-1); + candidate.Add(fi); + } StableIndexSort(distance, candidate); for(int fi : candidate) { f.Face(fi); @@ -444,6 +454,8 @@ bool Replace(Font fnt, int chr, Font& rfnt) rface[1].Add(i, &n); } } + if((Font::GetFaceInfo(i) & Font::COLORIMG) && FindIndex(color[0], i) < 0) + color[1].Add(i); } } } diff --git a/upptst/CJK/main.cpp b/upptst/CJK/main.cpp index 54529ab84..a6e44f607 100644 --- a/upptst/CJK/main.cpp +++ b/upptst/CJK/main.cpp @@ -11,6 +11,9 @@ struct MyApp : TopWindow { text << " Quick brown fox, 訓民正音 (훈민정음) "; w.DrawRect(GetSize(), White()); + +// w.DrawText(500, 500, "Hello!" + WString(0x1F970, 1).ToString() + " Hello again!"); + int y = 10; int x = 10; for(int i = 0; i < 3/*Font::GetFaceCount()*/; i++)