ultimatepp/uppsrc/Draw/FontCoco.mm
2023-04-04 14:49:12 +02:00

360 lines
No EOL
10 KiB
Text

#include "Draw.h"
#define LLOG(x) // LOG(x)
#define LTIMING(x) // TIMING(x)
#if !defined(CUSTOM_FONTSYS) && defined(PLATFORM_COCOA) && !defined(flagNOMM)
#define Point NS_Point
#define Rect NS_Rect
#define Size NS_Size
#include <AppKit/AppKit.h>
#undef Point
#undef Rect
#undef Size
namespace Upp {
namespace Detail { // TODO: following utilities are normally defined in CtrlCore, rename to avoid name clash
template <class T>
struct CFRef {
T ptr;
T operator~() { return ptr; }
operator T() { return ptr; }
T operator->() { return ptr; }
T Detach() { T h = ptr; ptr = NULL; return h; }
CFRef(T p) { ptr = p; }
~CFRef() { if(ptr) CFRelease(ptr); }
};
struct AutoreleasePool {
NSAutoreleasePool *pool;
AutoreleasePool() {
pool = [[NSAutoreleasePool alloc] init];
}
~AutoreleasePool() {
[pool release];
}
};
};
using namespace Detail;
WString ToWString(CFStringRef s)
{
if(!s) return Null;
CFIndex l = CFStringGetLength(s);
if(!l) return Null;
Buffer<char16> h(l);
CFStringGetCharacters(s, CFRangeMake(0, l), (UniChar *)~h);
return ToUtf32(~h, l);
}
String ToString(CFStringRef s)
{
return ToWString(s).ToString();
}
CTFontRef CT_Font0(Font fnt, bool& synth)
{
CFRef<CFStringRef> s = CFStringCreateWithCString(NULL, ~fnt.GetFaceName(), kCFStringEncodingUTF8);
CFRef<CTFontRef> ctfont0 = CTFontCreateWithName(s, fnt.GetHeight(), NULL);
synth = false;
if(fnt.IsItalic() || fnt.IsBold()) {
CTFontSymbolicTraits t = 0;
if(fnt.IsBold())
t |= kCTFontBoldTrait;
if(fnt.IsItalic())
t |= kCTFontItalicTrait;
CFRef<CTFontRef> ctfont = CTFontCreateCopyWithSymbolicTraits(ctfont0, fnt.GetHeight(),
NULL, t, t);
if(ctfont)
return ctfont.Detach();
synth = true;
}
return ctfont0.Detach();
}
CTFontRef CT_Font(Font fnt, bool& synth)
{
struct Entry {
Font font;
CTFontRef ctfont = NULL;
bool synth = false;
void Free() { if(ctfont) CFRelease(ctfont); ctfont = NULL; }
Entry() { font.Height(-22222); }
~Entry() { Free(); }
};
const int FONTCACHE = 64;
static Entry cache[FONTCACHE];
for(int i = 0; i < FONTCACHE; i++)
if(cache[i].font == fnt) {
synth = cache[i].synth;
return cache[i].ctfont;
}
Entry& e = cache[Random(FONTCACHE)];
e.Free();
e.font = fnt;
e.ctfont = CT_Font0(fnt, synth);
e.synth = synth;
return e.ctfont;
}
CGGlyph GetCharGlyph(CTFontRef ctfont, int chr)
{
CGGlyph glyph_index[2];
Vector<char16> h = ToUtf16(chr);
CTFontGetGlyphsForCharacters(ctfont, (UniChar *)h.begin(), glyph_index, h.GetCount());
return glyph_index[0];
}
GlyphInfo GetGlyphInfoSys(CTFontRef ctfont, int chr, bool bold_synth, CGRect *bounds = NULL)
{
GlyphInfo gi;
gi.lspc = gi.rspc = 0;
gi.width = 0x8000;
if(ctfont) {
LTIMING("GetGlyphInfoSys 2");
CGGlyph glyph_index = GetCharGlyph(ctfont, chr);
if(glyph_index) {
CGSize advance;
CTFontGetAdvancesForGlyphs(ctfont, kCTFontOrientationHorizontal, &glyph_index, &advance, 1);
gi.width = ceil(advance.width);
gi.lspc = gi.rspc = 0; // TODO! (using bounding box?)
gi.glyphi = glyph_index;
if(bold_synth)
gi.width++;
if(bounds)
CTFontGetBoundingRectsForGlyphs(ctfont, kCTFontOrientationHorizontal,
&glyph_index, bounds, 1);
}
}
return gi;
}
CommonFontInfo GetFontInfoSys(Font font)
{
CommonFontInfo fi;
String path;
bool synth;
CTFontRef ctfont = CT_Font(font, synth);
if(ctfont) {
#if 0
DDUMP(font);
DDUMP(CTFontGetSize(ctfont));
DDUMP(CTFontGetAscent(ctfont));
DDUMP(CTFontGetDescent(ctfont));
DDUMP();
DDUMP(CTFontGetXHeight(ctfont));
DDUMP(CTFontGetUnderlinePosition(ctfont));
DDUMP(MakeRect(CTFontGetBoundingBox(ctfont)));
DDUMPHEX(CTFontGetSymbolicTraits(ctfont));
DDUMP(font);
CGRect cr = CTFontGetBoundingBox(ctfont);
DDUMP(cr.origin.y);
DDUMP(cr.size.height);
DDUMP(cr.origin.y + cr.size.height);
#endif
fi.descent = ceil(CTFontGetDescent(ctfont));
fi.ascent = ceil(CTFontGetAscent(ctfont));
fi.external = ceil(CTFontGetLeading(ctfont));
// Some MacOS fonts have really weird ascent/descents (namely stadard GUI font...)
// let us fix it by testing typical charactes bounding boxes
static WString descent_test = "yjgp";
CGRect bb;
for(int i = 0; i < descent_test.GetCount(); i++)
if(GetGlyphInfoSys(ctfont, descent_test[i], synth && font.IsBold(), &bb).IsNormal())
fi.descent = max(fi.descent, (int)ceil(-bb.origin.y));
int ascent = fi.ascent;
static WString ascent_test = "ÀÁÂÃÄË";
for(int i = 0; i < ascent_test.GetCount(); i++)
if(GetGlyphInfoSys(ctfont, ascent_test[i], synth && font.IsBold(), &bb).IsNormal())
ascent = max(ascent, (int)ceil(bb.origin.y + bb.size.height));
fi.ascent = ascent;
fi.internal = 0;
fi.overhang = 0;
fi.maxwidth = GetGlyphInfoSys(ctfont, 'W', synth && font.IsBold()).width; // TODO?
fi.avewidth = GetGlyphInfoSys(ctfont, 'e', synth && font.IsBold()).width;
fi.default_char = '?';
fi.fixedpitch = CTFontGetSymbolicTraits(ctfont) & kCTFontMonoSpaceTrait;
fi.ttf = true;
CFRef<CTFontDescriptorRef> fd = CTFontCopyFontDescriptor(ctfont);
CFRef<CFURLRef> url = (CFURLRef)CTFontDescriptorCopyAttribute(fd, kCTFontURLAttribute);
CFRef<CFStringRef> path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
String p = ToString(path);
if(p.GetCount() < 250)
strcpy(fi.path, ~p);
else
*fi.path = 0;
}
return fi;
}
GlyphInfo GetGlyphInfoSys(Font font, int chr)
{
LTIMING("GetGlyphInfoSys");
bool synth;
CTFontRef ctfont = CT_Font(font, synth);
return GetGlyphInfoSys(ctfont, chr, synth && font.IsBold());
}
Vector<FaceInfo> GetAllFacesSys()
{
Index<String> facename;
facename.Add("Arial"); // This is default GUI font, changed afterward
facename.Add("Times New Roman");
facename.Add("Arial");
facename.Add("Courier New");
int oi = facename.GetCount();
AutoreleasePool __;
CFRef<CTFontCollectionRef> collection = CTFontCollectionCreateFromAvailableFonts(0);
if(collection) {
CFRef<CFArrayRef> fonts = CTFontCollectionCreateMatchingFontDescriptors(collection);
if(fonts) {
int count = CFArrayGetCount(fonts);
for(int i = 0; i < count; ++i) {
CTFontDescriptorRef font = (CTFontDescriptorRef)CFArrayGetValueAtIndex(fonts, i);
CFRef<CFStringRef> family_name = (CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontFamilyNameAttribute);
facename.FindAdd(ToString(family_name));
}
}
}
Vector<String> h = facename.PickKeys();
Sort(SubRange(h, oi, h.GetCount() - oi));
Vector<FaceInfo> r;
for(String s : h) {
FaceInfo& fi = r.Add();
fi.name = s;
fi.info = Font::TTF;
CFRef<CFStringRef> fs = CFStringCreateWithCString(NULL, ~s, kCFStringEncodingUTF8);
CFRef<CTFontRef> ctfont = CTFontCreateWithName(fs, 12, NULL);
dword traits = CTFontGetSymbolicTraits(ctfont);
fi.info = Font::SCALEABLE;
if(traits & kCTFontMonoSpaceTrait)
fi.info |= Font::FIXEDPITCH;
switch(traits & kCTFontClassMaskTrait) {
case kCTFontOldStyleSerifsClass:
case kCTFontTransitionalSerifsClass:
case kCTFontModernSerifsClass:
case kCTFontClarendonSerifsClass:
case kCTFontSlabSerifsClass:
case kCTFontFreeformSerifsClass:
fi.info |= Font::SERIFSTYLE;
break;
case kCTFontScriptsClass:
fi.info |= Font::SCRIPTSTYLE;
break;
}
}
return r;
}
String GetFontDataSys(Font font, const char *table, int offset, int size)
{ // read truetype or opentype table
FileIn in(font.Fi().path);
int q = in.Get32be();
if(q == 0x74746366) { // font collection
in.Get32(); // skip major/minor version
int nfonts = in.Get32be();
if(font.Fi().fonti >= nfonts)
return Null;
in.SeekCur(font.Fi().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 && q != 0x4f54544f) // 0x4f54544f means CCF font!
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 off = in.Get32be();
int len = in.Get32be();
if(tab == table) {
if(off < 0 || len < 0 || off + len > in.GetSize())
return Null;
len = min(len - offset, size);
if(len < 0)
return Null;
in.Seek(off + offset);
return in.Get(len);
}
}
return Null;
}
struct sCGPathTarget {
double x, y;
FontGlyphConsumer *sw;
};
static void convertCGPathToQPainterPath(void *info, const CGPathElement *e)
{
auto t = (sCGPathTarget *)info;
switch(e->type) {
case kCGPathElementMoveToPoint:
t->sw->Move(Pointf(e->points[0].x + t->x, e->points[0].y + t->y));
break;
case kCGPathElementAddLineToPoint:
t->sw->Line(Pointf(e->points[0].x + t->x, e->points[0].y + t->y));
break;
case kCGPathElementAddQuadCurveToPoint:
t->sw->Quadratic(Pointf(e->points[0].x + t->x, e->points[0].y + t->y),
Pointf(e->points[1].x + t->x, e->points[1].y + t->y));
break;
case kCGPathElementAddCurveToPoint:
t->sw->Cubic(Pointf(e->points[0].x + t->x, e->points[0].y + t->y),
Pointf(e->points[1].x + t->x, e->points[1].y + t->y),
Pointf(e->points[2].x + t->x, e->points[2].y + t->y));
break;
case kCGPathElementCloseSubpath:
t->sw->Close();
break;
}
}
void RenderCharacterSys(FontGlyphConsumer& sw, double x, double y, int chr, Font font)
{
CGAffineTransform cgMatrix = CGAffineTransformIdentity;
cgMatrix = CGAffineTransformScale(cgMatrix, 1, -1);
bool synth;
CTFontRef ctfont = CT_Font(font, synth);
CGGlyph glyph_index = GetCharGlyph(ctfont, chr);
CFRef<CGPathRef> cgpath = CTFontCreatePathForGlyph(ctfont, glyph_index, &cgMatrix);
sCGPathTarget t;
t.x = x;
t.y = y + font.GetAscent();
t.sw = &sw;
CGPathApply(cgpath, &t, convertCGPathToQPainterPath);
}
};
#endif