mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 06:05:58 -06:00
615 lines
12 KiB
C++
615 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;
|
|
Size Font::StdFontSizeA;
|
|
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());
|
|
|
|
StdFontSizeA = Size(0, 0);
|
|
for(int italic = 0; italic < 2; italic++)
|
|
for(int bold = 0; bold < 2; bold++) {
|
|
Font fnt = AStdFont().Bold(bold).Italic(italic);
|
|
StdFontSizeA.cx = max(StdFontSizeA.cx, fnt.GetAveWidth());
|
|
StdFontSizeA.cy = max(StdFontSizeA.cy, fnt.GetCy());
|
|
}
|
|
|
|
LLOG("SyncStdFont " << StdFontSize);
|
|
SyncDPIScale();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
Size Font::GetStdFontSizeA()
|
|
{
|
|
InitStdFont();
|
|
return StdFontSizeA;
|
|
}
|
|
|
|
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;
|
|
wchar chr;
|
|
};
|
|
|
|
CharEntry fc_cache_global[16384];
|
|
|
|
inline hash_t GlyphHash(Font font, int chr)
|
|
{
|
|
return FoldHash(CombineHash(font.GetHashValue(), chr));
|
|
}
|
|
|
|
bool IsNormal_nc(Font font, int chr)
|
|
{ // do not change cache - to be used in Replace
|
|
Mutex::Lock __(sFontLock);
|
|
font.RealizeStd();
|
|
CharEntry& e = fc_cache_global[GlyphHash(font, chr) & 16383];
|
|
if(e.font == font.AsInt64() && e.chr == chr)
|
|
return e.info.IsNormal();
|
|
return GetGlyphInfoSys(font, chr).IsNormal();
|
|
}
|
|
|
|
struct GlyphInfoMaker : ValueMaker {
|
|
Font font;
|
|
int chr;
|
|
|
|
virtual String Key() const {
|
|
StringBuffer s;
|
|
int64 h = font.AsInt64();
|
|
RawCat(s, h);
|
|
RawCat(s, chr);
|
|
return String(s);
|
|
}
|
|
virtual int Make(Value& object) const {
|
|
CharEntry& e = CreateRawValue<CharEntry>(object);
|
|
e.font = font.AsInt64();
|
|
e.chr = chr;
|
|
Font rfnt;
|
|
if(PreferColorEmoji(chr) && !(font.GetFaceInfo() & Font::COLORIMG) && !font.IsNoColor()
|
|
&& Replace(font, chr, rfnt) && rfnt != font) {
|
|
e.info.width = (int16)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;
|
|
}
|
|
}
|
|
return sizeof(e);
|
|
}
|
|
};
|
|
|
|
CharEntry GetGlyphEntry(Font font, int chr, hash_t hash)
|
|
{
|
|
Mutex::Lock __(sFontLock);
|
|
GlyphInfoMaker m;
|
|
#ifdef flagX11
|
|
font.NoColor();
|
|
#endif
|
|
m.font = font;
|
|
m.chr = chr;
|
|
return MakeValue(m).To<CharEntry>();
|
|
}
|
|
|
|
thread_local CharEntry fc_cache[512];
|
|
|
|
GlyphInfo GetGlyphInfo(Font font, int chr)
|
|
{
|
|
font.RealizeStd();
|
|
hash_t 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();
|
|
dword 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;
|
|
}
|
|
|
|
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 char *table, int offset, int size) const
|
|
{
|
|
Mutex::Lock __(sFontLock);
|
|
ASSERT(!table || strlen(table) == 4);
|
|
return GetFontDataSys(*this, table, offset, size);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
}
|