ultimatepp/uppsrc/Draw/FontCoco.mm
Mirek Fidler 34ff691308 sizeof(wchar) is changed to 4 (32 bits) to support non BMP unicode characters
This might bring some incompatibilities in the code that expects wchar to be 16 bit, which
  escpecially involves dealing with Win32 (and to lesser extend MacOS) APIs, so if your application
  is doing that, please check all instances of WCHAR (UniChar on MacOS) or even wchar
  especially type casts.

  To support host APIs, char16 is introduced (but there is no 16-bit String varian).

  Use ToSystemCharsetW, FromSystemCharsetW to convert texts to Win32 API.

- Support of drawing non-BMP characters in GUI
- Vastly improved character font replacement code (when drawing characters missing with requested font, replacement font is used)
- Last instances of Win32 ANSI calls (those ending with A) are removed
- UTF handling routines are refactored and their's naming is unified
- RTF is now being able to handle non-BMP characters (RTF is used as clipboard format for RichText)

Other minor changes:

- fixed TryRealloc issue
- improved MemoryCheck
- Removed MemoryAlloc48/MemoryFree48
- In theide Background parsing should less often cause delays in the main thread
2021-12-02 12:03:19 +01: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)
#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