mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-16 14:16:09 -06:00
634 lines
12 KiB
C++
634 lines
12 KiB
C++
#include "Draw.h"
|
|
|
|
#define LLOG(x) // DLOG(x)
|
|
|
|
namespace Upp {
|
|
|
|
StaticMutex sFontLock;
|
|
|
|
bool Replace(Font fnt, int chr, Font& rfnt);
|
|
|
|
void Std(Font& font)
|
|
{
|
|
font.RealizeStd();
|
|
}
|
|
|
|
Size Font::StdFontSize;
|
|
Font Font::AStdFont;
|
|
|
|
INITBLOCK {
|
|
Value::Register<Font>("Font");
|
|
}
|
|
|
|
static bool sListValid;
|
|
|
|
void InvalidateFontList()
|
|
{
|
|
sListValid = false;
|
|
}
|
|
|
|
Vector<FaceInfo>& Font::FaceList()
|
|
{
|
|
static Vector<FaceInfo> list;
|
|
ONCELOCK {
|
|
list = GetAllFacesSys();
|
|
}
|
|
return list;
|
|
}
|
|
|
|
void sInitFonts()
|
|
{
|
|
Mutex::Lock __(sFontLock);
|
|
Font::FaceList();
|
|
GetStdFont();
|
|
}
|
|
|
|
INITBLOCK {
|
|
sInitFonts();
|
|
}
|
|
|
|
int Font::GetFaceCount()
|
|
{
|
|
Mutex::Lock __(sFontLock);
|
|
return FaceList().GetCount();
|
|
}
|
|
|
|
String Font::GetFaceName(int index)
|
|
{
|
|
Mutex::Lock __(sFontLock);
|
|
if(index == 0)
|
|
return "STDFONT";
|
|
const Vector<FaceInfo>& l = FaceList();
|
|
if(index >= 0 && index < l.GetCount())
|
|
return l[index].name;
|
|
return Null;
|
|
}
|
|
|
|
dword Font::GetFaceInfo(int index)
|
|
{
|
|
Mutex::Lock __(sFontLock);
|
|
const Vector<FaceInfo>& l = FaceList();
|
|
if(index >= 0 && index < l.GetCount())
|
|
return l[index].info;
|
|
return 0;
|
|
}
|
|
|
|
void Font::SetFace(int index, const String& name, dword info)
|
|
{
|
|
Mutex::Lock __(sFontLock);
|
|
FaceInfo& f = FaceList().At(index);
|
|
f.name = name;
|
|
f.info = info;
|
|
}
|
|
|
|
void Font::SetFace(int index, const String& name)
|
|
{
|
|
int q = FindFaceNameIndex(name);
|
|
q = q >= 0 ? GetFaceInfo(q) : 0;
|
|
SetFace(index, name, q);
|
|
}
|
|
|
|
int FontFilter(int c)
|
|
{
|
|
return c >= 'a' && c <= 'z' || c >= '0' && c <= '9' ? c : c >= 'A' && c <= 'Z' ? ToLower(c) : 0;
|
|
}
|
|
|
|
int Font::FindFaceNameIndex(const String& name) {
|
|
if(name == "STDFONT")
|
|
return 0;
|
|
for(int i = 1; i < GetFaceCount(); i++)
|
|
if(GetFaceName(i) == name)
|
|
return i;
|
|
String n = Filter(name, FontFilter);
|
|
for(int i = 1; i < GetFaceCount(); i++)
|
|
if(Filter(GetFaceName(i), FontFilter) == n)
|
|
return i;
|
|
if(n == "serif")
|
|
return SERIF;
|
|
if(n == "sansserif")
|
|
return SANSSERIF;
|
|
if(n == "monospace")
|
|
return MONOSPACE;
|
|
if(n == "stdfont")
|
|
return STDFONT;
|
|
return 0;
|
|
}
|
|
|
|
void Font::SyncStdFont()
|
|
{
|
|
Mutex::Lock __(sFontLock);
|
|
StdFontSize = Size(AStdFont.GetAveWidth(), AStdFont().GetCy());
|
|
LLOG("SyncStdFont " << StdFontSize);
|
|
SyncUHDMode();
|
|
}
|
|
|
|
void (*whenSetStdFont)();
|
|
|
|
void Font::SetStdFont0(Font font)
|
|
{
|
|
LLOG("SetStdFont " << font);
|
|
Mutex::Lock __(sFontLock);
|
|
static int x = 0;
|
|
if(x) return;
|
|
x++;
|
|
InitStdFont();
|
|
AStdFont = font;
|
|
LLOG("AStdFont1: " << AStdFont);
|
|
SyncStdFont();
|
|
LLOG("AStdFont2: " << AStdFont);
|
|
if(whenSetStdFont)
|
|
(*whenSetStdFont)();
|
|
LLOG("AStdFont3: " << AStdFont);
|
|
x--;
|
|
static int w = 0;
|
|
if(w) return;
|
|
w++;
|
|
if(whenSetStdFont)
|
|
(*whenSetStdFont)();
|
|
LLOG("AStdFont4: " << AStdFont);
|
|
w--;
|
|
}
|
|
|
|
bool Font::std_font_override;
|
|
|
|
void Font::SetDefaultFont(Font font)
|
|
{
|
|
LLOG("SetDefaultFont " << font);
|
|
if(!std_font_override)
|
|
SetStdFont0(font);
|
|
}
|
|
|
|
void Font::SetStdFont(Font font)
|
|
{
|
|
std_font_override = true;
|
|
SetStdFont0(font);
|
|
}
|
|
|
|
void Font::InitStdFont()
|
|
{
|
|
ONCELOCK { // TODO: This is now sort of obsolete function....
|
|
// Mutex::Lock __(sFontLock);
|
|
// FaceList();
|
|
AStdFont = Arial(12);
|
|
// SyncStdFont();
|
|
}
|
|
}
|
|
|
|
Font Font::GetStdFont()
|
|
{
|
|
InitStdFont();
|
|
return AStdFont;
|
|
}
|
|
|
|
Size Font::GetStdFontSize()
|
|
{
|
|
InitStdFont();
|
|
return StdFontSize;
|
|
}
|
|
|
|
Font StdFont()
|
|
{
|
|
return Font(0, -32000);
|
|
}
|
|
|
|
void Font::RealizeStd()
|
|
{
|
|
if(IsNullInstance())
|
|
*this = GetStdFont();
|
|
if(v.face == STDFONT)
|
|
Face(GetStdFont().GetFace());
|
|
if(v.height == -32000)
|
|
Height(GetStdFont().GetHeight());
|
|
}
|
|
|
|
int Font::GetHeight() const
|
|
{
|
|
return v.height == -32000 ? GetStdFont().GetHeight() : v.height;
|
|
}
|
|
|
|
String Font::GetFaceName() const {
|
|
if(IsNullInstance()) return String();
|
|
if(GetFace() == 0)
|
|
return "STDFONT";
|
|
return GetFaceName(GetFace());
|
|
}
|
|
|
|
dword Font::GetFaceInfo() const {
|
|
if(IsNullInstance()) return 0;
|
|
return GetFaceInfo(GetFace());
|
|
}
|
|
|
|
Font& Font::FaceName(const String& name) {
|
|
int n = FindFaceNameIndex(name);
|
|
Face(n < 0 ? 0xffff : n);
|
|
return *this;
|
|
}
|
|
|
|
void Font::Serialize(Stream& s) {
|
|
int version = 1;
|
|
s / version;
|
|
if(version >= 1) {
|
|
enum {
|
|
OLD_STDFONT, OLD_SCREEN_SERIF, OLD_SCREEN_SANS, OLD_SCREEN_FIXED,
|
|
OLD_ROMAN,
|
|
OLD_ARIAL,
|
|
OLD_COURIER,
|
|
};
|
|
int f = GetFace();
|
|
if(f > COURIER)
|
|
f = -1;
|
|
s / f;
|
|
String name;
|
|
if(f == OLD_ROMAN)
|
|
f = ROMAN;
|
|
if(f == OLD_ARIAL)
|
|
f = ARIAL;
|
|
if(f == OLD_COURIER)
|
|
f = COURIER;
|
|
if(f < 0) {
|
|
name = GetFaceName();
|
|
s % name;
|
|
}
|
|
if(s.IsLoading()) {
|
|
if(f >= 0)
|
|
Face(f);
|
|
else {
|
|
FaceName(name);
|
|
if(IsNull(name))
|
|
SetNull();
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
String name = GetFaceName();
|
|
s % name;
|
|
if(s.IsLoading()) {
|
|
FaceName(name);
|
|
if(IsNullInstance())
|
|
Face(COURIER);
|
|
}
|
|
}
|
|
s % v.flags % v.height % v.width;
|
|
}
|
|
|
|
String Font::GetTextFlags() const
|
|
{
|
|
String txt;
|
|
if(IsBold())
|
|
txt << "bold ";
|
|
if(IsItalic())
|
|
txt << "italic ";
|
|
if(IsUnderline())
|
|
txt << "underline ";
|
|
if(IsStrikeout())
|
|
txt << "strikeout ";
|
|
if(IsNonAntiAliased())
|
|
txt << "noaa ";
|
|
if(IsTrueTypeOnly())
|
|
txt << "ttonly ";
|
|
if(txt.GetCount())
|
|
txt.Trim(txt.GetCount() - 1);
|
|
return txt;
|
|
}
|
|
|
|
void Font::ParseTextFlags(const char *s)
|
|
{
|
|
CParser p(s);
|
|
v.flags = 0;
|
|
while(!p.IsEof()) {
|
|
if(p.Id("bold"))
|
|
Bold();
|
|
else
|
|
if(p.Id("italic"))
|
|
Italic();
|
|
else
|
|
if(p.Id("underline"))
|
|
Underline();
|
|
else
|
|
if(p.Id("strikeout"))
|
|
Strikeout();
|
|
else
|
|
if(p.Id("noaa"))
|
|
NonAntiAliased();
|
|
else
|
|
if(p.Id("ttonly"))
|
|
TrueTypeOnly();
|
|
else
|
|
p.SkipTerm();
|
|
}
|
|
}
|
|
|
|
String Font::GetFaceNameStd() const
|
|
{
|
|
switch(GetFace()) {
|
|
case STDFONT: return "STDFONT";
|
|
case SERIF: return "serif";
|
|
case SANSSERIF: return "sansserif";
|
|
case MONOSPACE: return "monospace";
|
|
}
|
|
return GetFaceName();
|
|
}
|
|
|
|
void Font::Jsonize(JsonIO& jio)
|
|
{
|
|
String n, tf;
|
|
if(jio.IsStoring()) {
|
|
n = GetFaceNameStd();
|
|
tf = GetTextFlags();
|
|
if(IsNullInstance())
|
|
n.Clear();
|
|
}
|
|
jio("face", n)("height", v.height)("width", v.width)("flags", tf);
|
|
if(IsNull(n))
|
|
SetNull();
|
|
else {
|
|
FaceName(n);
|
|
ParseTextFlags(tf);
|
|
}
|
|
}
|
|
|
|
void Font::Xmlize(XmlIO& xio)
|
|
{
|
|
String n, tf;
|
|
if(xio.IsStoring()) {
|
|
n = GetFaceNameStd();
|
|
tf = GetTextFlags();
|
|
if(IsNullInstance())
|
|
n.Clear();
|
|
}
|
|
xio.Attr("face", n)
|
|
.Attr("height", v.height)
|
|
.Attr("width", v.width)
|
|
.Attr("flags", tf);
|
|
if(IsNull(n))
|
|
SetNull();
|
|
else {
|
|
FaceName(n);
|
|
ParseTextFlags(tf);
|
|
}
|
|
}
|
|
|
|
template<>
|
|
String AsString(const Font& f) {
|
|
if(IsNull(f)) return "<null>";
|
|
String s = "<" + f.GetFaceName() + Format(":%d", f.GetHeight());
|
|
if(f.IsBold())
|
|
s += " Bold";
|
|
if(f.IsItalic())
|
|
s += " Italic";
|
|
if(f.IsUnderline())
|
|
s += " Underline";
|
|
if(f.IsStrikeout())
|
|
s += " Strikeout";
|
|
return s + '>';
|
|
}
|
|
|
|
struct CharEntry {
|
|
int64 font;
|
|
GlyphInfo info;
|
|
word chr;
|
|
};
|
|
|
|
CharEntry fc_cache_global[4093];
|
|
|
|
inline dword GlyphHash(Font font, int chr)
|
|
{
|
|
return FoldHash(CombineHash(font.GetHashValue(), chr));
|
|
}
|
|
|
|
bool IsNormal(Font font, int chr)
|
|
{
|
|
Mutex::Lock __(sFontLock);
|
|
font.RealizeStd();
|
|
CharEntry& e = fc_cache_global[GlyphHash(font, chr) % 4093];
|
|
if(e.font == font.AsInt64() && e.chr == chr)
|
|
return e.info.IsNormal();
|
|
return GetGlyphInfoSys(font, chr).IsNormal();
|
|
}
|
|
|
|
CharEntry GetGlyphEntry(Font font, int chr, unsigned hash)
|
|
{
|
|
Mutex::Lock __(sFontLock);
|
|
CharEntry& e = fc_cache_global[hash % 4093];
|
|
if(e.font != font.AsInt64() || e.chr != chr) {
|
|
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 = cg.basic_char;
|
|
}
|
|
else
|
|
if(Replace(font, chr, rfnt)) {
|
|
e.info.lspc = rfnt.GetFace();
|
|
e.info.rspc = rfnt.GetHeight();
|
|
}
|
|
else
|
|
e.info.lspc = -2;
|
|
}
|
|
}
|
|
return e;
|
|
}
|
|
|
|
#ifdef MINGW_TLS_PATCH
|
|
struct FontEntry {
|
|
CommonFontInfo info;
|
|
int64 font;
|
|
};
|
|
|
|
struct FontInfoTls {
|
|
CharEntry fc_cache[512];
|
|
FontEntry fi_cache[63];
|
|
int64 lastFiFont = INT_MIN;
|
|
CommonFontInfo lastFontInfo;
|
|
int64 lastStdFont = INT_MIN;
|
|
};
|
|
|
|
thread_local static FontInfoTls sFontInfoTls;
|
|
static FastMingwTls<FontInfoTls *> sFontInfoTlsPtr;
|
|
|
|
force_inline static FontInfoTls& sTls()
|
|
{
|
|
if(!sFontInfoTlsPtr)
|
|
sFontInfoTlsPtr = &sFontInfoTls;
|
|
return *sFontInfoTlsPtr;
|
|
}
|
|
|
|
GlyphInfo GetGlyphInfo(Font font, int chr)
|
|
{
|
|
FontInfoTls& tls = sTls();
|
|
font.RealizeStd();
|
|
unsigned hash = GlyphHash(font, chr);
|
|
CharEntry& e = tls.fc_cache[hash & 511];
|
|
if(e.font != font.AsInt64() || e.chr != chr)
|
|
e = GetGlyphEntry(font, chr, hash);
|
|
return e.info;
|
|
}
|
|
|
|
const CommonFontInfo& GetFontInfo(Font font)
|
|
{
|
|
FontInfoTls& tls = sTls();
|
|
font.RealizeStd();
|
|
unsigned hash = FoldHash(font.GetHashValue()) % 63;
|
|
FontEntry& e = tls.fi_cache[hash];
|
|
if(e.font != font.AsInt64()) {
|
|
Mutex::Lock __(sFontLock);
|
|
e.font = font.AsInt64();
|
|
e.info = GetFontInfoSys(font);
|
|
}
|
|
return e.info;
|
|
}
|
|
|
|
const CommonFontInfo& Font::Fi() const
|
|
{
|
|
FontInfoTls& tls = sTls();
|
|
if(tls.lastStdFont != AStdFont.AsInt64()) {
|
|
tls.lastFiFont = INT_MIN;
|
|
tls.lastStdFont = AStdFont.AsInt64();
|
|
}
|
|
if(AsInt64() == tls.lastFiFont)
|
|
return tls.lastFontInfo;
|
|
tls.lastFontInfo = GetFontInfo(*this);
|
|
tls.lastFiFont = AsInt64();
|
|
return tls.lastFontInfo;
|
|
}
|
|
#else
|
|
thread_local CharEntry fc_cache[512];
|
|
|
|
GlyphInfo GetGlyphInfo(Font font, int chr)
|
|
{
|
|
font.RealizeStd();
|
|
unsigned hash = GlyphHash(font, chr);
|
|
CharEntry& e = fc_cache[hash & 511];
|
|
if(e.font != font.AsInt64() || e.chr != chr)
|
|
e = GetGlyphEntry(font, chr, hash);
|
|
return e.info;
|
|
}
|
|
|
|
struct FontEntry {
|
|
CommonFontInfo info;
|
|
int64 font;
|
|
};
|
|
|
|
thread_local FontEntry fi_cache[63];
|
|
|
|
const CommonFontInfo& GetFontInfo(Font font)
|
|
{
|
|
font.RealizeStd();
|
|
unsigned hash = FoldHash(font.GetHashValue()) % 63;
|
|
FontEntry& e = fi_cache[hash];
|
|
if(e.font != font.AsInt64()) {
|
|
Mutex::Lock __(sFontLock);
|
|
e.font = font.AsInt64();
|
|
e.info = GetFontInfoSys(font);
|
|
}
|
|
return e.info;
|
|
}
|
|
|
|
thread_local int64 lastFiFont = INT_MIN;
|
|
thread_local CommonFontInfo lastFontInfo;
|
|
thread_local int64 lastStdFont = INT_MIN;
|
|
|
|
const CommonFontInfo& Font::Fi() const
|
|
{
|
|
if(lastStdFont != AStdFont.AsInt64()) {
|
|
lastFiFont = INT_MIN;
|
|
lastStdFont = AStdFont.AsInt64();
|
|
}
|
|
if(AsInt64() == lastFiFont)
|
|
return lastFontInfo;
|
|
lastFontInfo = GetFontInfo(*this);
|
|
lastFiFont = AsInt64();
|
|
return lastFontInfo;
|
|
}
|
|
#endif
|
|
|
|
bool Font::IsNormal(int ch) const
|
|
{
|
|
return GetGlyphInfo(*this, ch).IsNormal();
|
|
}
|
|
|
|
bool Font::IsComposed(int ch) const
|
|
{
|
|
return GetGlyphInfo(*this, ch).IsComposed();
|
|
}
|
|
|
|
bool Font::IsReplaced(int ch) const
|
|
{
|
|
return GetGlyphInfo(*this, ch).IsReplaced();
|
|
}
|
|
|
|
bool Font::IsMissing(int ch) const
|
|
{
|
|
return GetGlyphInfo(*this, ch).IsMissing();
|
|
}
|
|
|
|
int Font::HasChar(int ch) const
|
|
{
|
|
return !GetGlyphInfo(*this, ch).IsMissing();
|
|
}
|
|
|
|
void GlyphMetrics(GlyphInfo& f, Font font, int chr)
|
|
{
|
|
if(f.IsReplaced())
|
|
f = GetGlyphInfo(font().Face(f.lspc).Height(f.rspc), chr);
|
|
if(f.IsComposed()) {
|
|
f = GetGlyphInfo(font, f.rspc);
|
|
if(f.IsComposedLM())
|
|
f.rspc += f.width / 2;
|
|
}
|
|
}
|
|
|
|
GlyphInfo GetGlyphMetrics(Font font, int chr)
|
|
{
|
|
font.RealizeStd();
|
|
GlyphInfo f = GetGlyphInfo(font, chr);
|
|
if(f.IsMissing()) {
|
|
Font fnt = Arial(font.GetHeight());
|
|
wchar chr = 0x25a1;
|
|
f = GetGlyphInfo(fnt, chr);
|
|
if(!f.IsNormal()) {
|
|
chr = ' ';
|
|
f = GetGlyphInfo(fnt, chr);
|
|
}
|
|
}
|
|
else
|
|
GlyphMetrics(f, font, chr);
|
|
return f;
|
|
}
|
|
|
|
int Font::GetWidth(int c) const {
|
|
return GetGlyphMetrics(*this, c).width;
|
|
}
|
|
|
|
int Font::GetLeftSpace(int c) const {
|
|
return GetGlyphMetrics(*this, c).lspc;
|
|
}
|
|
|
|
int Font::GetRightSpace(int c) const {
|
|
return GetGlyphMetrics(*this, c).rspc;
|
|
}
|
|
|
|
String Font::GetData() const
|
|
{
|
|
Mutex::Lock __(sFontLock);
|
|
return GetFontDataSys(*this);
|
|
}
|
|
|
|
void Font::Render(FontGlyphConsumer& sw, double x, double y, int ch) const
|
|
{
|
|
Mutex::Lock __(sFontLock);
|
|
RenderCharacterSys(sw, x, y, ch, *this);
|
|
}
|
|
|
|
FontInfo Font::Info() const
|
|
{
|
|
FontInfo h;
|
|
h.font = *this;
|
|
return h;
|
|
}
|
|
|
|
}
|