First Commit
This commit is contained in:
commit
ae0fab9400
424 files changed
+71006
No files matched your search
@@ -0,0 +1,92 @@
|
||||
// 10 february 2017
|
||||
#include "../ui.h"
|
||||
#include "uipriv.h"
|
||||
|
||||
// TODO this doesn't handle the case where nLines == 0
|
||||
// TODO this should never happen even if there are no characters??
|
||||
// TODO figure out how to make this work on GTK+
|
||||
void uiDrawCaret(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout, size_t pos, int *line)
|
||||
{
|
||||
double xoff;
|
||||
uiDrawTextLayoutLineMetrics m;
|
||||
struct caretDrawParams cdp;
|
||||
uiDrawPath *path;
|
||||
uiDrawBrush brush;
|
||||
|
||||
if (*line < 0)
|
||||
*line = 0;
|
||||
if (*line > (uiDrawTextLayoutNumLines(layout) - 1))
|
||||
*line = (uiDrawTextLayoutNumLines(layout) - 1);
|
||||
// TODO cap pos
|
||||
xoff = uiDrawTextLayoutByteLocationInLine(layout, pos, *line);
|
||||
while (xoff < 0) {
|
||||
size_t start, end;
|
||||
|
||||
uiDrawTextLayoutLineByteRange(layout, *line, &start, &end);
|
||||
if (end < pos) // too far up
|
||||
(*line)++;
|
||||
else
|
||||
(*line)--;
|
||||
xoff = uiDrawTextLayoutByteLocationInLine(layout, pos, *line);
|
||||
}
|
||||
uiDrawTextLayoutLineGetMetrics(layout, *line, &m);
|
||||
|
||||
caretDrawParams(c, m.Height, &cdp);
|
||||
|
||||
uiDrawSave(c);
|
||||
|
||||
path = uiDrawNewPath(uiDrawFillModeWinding);
|
||||
uiDrawPathAddRectangle(path,
|
||||
// TODO add m.X?
|
||||
x + xoff - cdp.xoff, y + m.Y,
|
||||
cdp.width, m.Height);
|
||||
uiDrawPathEnd(path);
|
||||
brush.Type = uiDrawBrushTypeSolid;
|
||||
brush.R = cdp.r;
|
||||
brush.G = cdp.g;
|
||||
brush.B = cdp.b;
|
||||
brush.A = cdp.a;
|
||||
uiDrawFill(c, path, &brush);
|
||||
uiDrawFreePath(path);
|
||||
|
||||
uiDrawRestore(c);
|
||||
}
|
||||
|
||||
void drawTextBackground(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout, size_t start, size_t end, uiDrawBrush *brush, int isSelection)
|
||||
{
|
||||
int line, nLines;
|
||||
size_t lstart, lend;
|
||||
double layoutwid, layoutht;
|
||||
|
||||
uiDrawTextLayoutExtents(layout, &layoutwid, &layoutht);
|
||||
nLines = uiDrawTextLayoutNumLines(layout);
|
||||
for (line = 0; line < nLines; line++) {
|
||||
uiDrawTextLayoutLineByteRange(layout, line, &lstart, &lend);
|
||||
if (start >= lstart && start < lend)
|
||||
break;
|
||||
}
|
||||
while (end - start > 0) {
|
||||
uiDrawTextLayoutLineMetrics m;
|
||||
double startx, endx;
|
||||
uiDrawPath *path;
|
||||
|
||||
uiDrawTextLayoutLineByteRange(layout, line, &lstart, &lend);
|
||||
if (lend > end) // don't cross lines
|
||||
lend = end;
|
||||
startx = uiDrawTextLayoutByteLocationInLine(layout, start, line);
|
||||
// TODO explain this; note the use of start with lend
|
||||
endx = layoutwid;
|
||||
if (!isSelection || end <= lend)
|
||||
endx = uiDrawTextLayoutByteLocationInLine(layout, lend, line);
|
||||
uiDrawTextLayoutLineGetMetrics(layout, line, &m);
|
||||
path = uiDrawNewPath(uiDrawFillModeWinding);
|
||||
uiDrawPathAddRectangle(path,
|
||||
x + startx, y + m.Y,
|
||||
endx - startx, m.Height);
|
||||
uiDrawPathEnd(path);
|
||||
uiDrawFill(c, path, brush);
|
||||
uiDrawFreePath(path);
|
||||
line++;
|
||||
start = lend;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
|
||||
// TODO split these into a separate header file?
|
||||
|
||||
// drawtext.c
|
||||
struct caretDrawParams {
|
||||
double r;
|
||||
double g;
|
||||
double b;
|
||||
double a;
|
||||
double xoff;
|
||||
double width;
|
||||
};
|
||||
extern void caretDrawParams(uiDrawContext *c, double height, struct caretDrawParams *p);
|
||||
extern void drawTextBackground(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout, size_t start, size_t end, uiDrawBrush *brush, int isSelection);
|
||||
@@ -0,0 +1,347 @@
|
||||
// 2 january 2017
|
||||
#import "uipriv_darwin.h"
|
||||
#import "draw.h"
|
||||
|
||||
@interface lineInfo : NSObject
|
||||
@property NSRange glyphRange;
|
||||
@property NSRange characterRange;
|
||||
@property NSRect lineRect;
|
||||
@property NSRect lineUsedRect;
|
||||
@property NSRect glyphBoundingRect;
|
||||
@property CGFloat baselineOffset;
|
||||
@property double ascent;
|
||||
@property double descent;
|
||||
@property double leading;
|
||||
@end
|
||||
|
||||
@implementation lineInfo
|
||||
@end
|
||||
|
||||
struct uiDrawTextLayout {
|
||||
// NSTextStorage is subclassed from NSMutableAttributedString
|
||||
NSTextStorage *attrstr;
|
||||
NSTextContainer *container;
|
||||
NSLayoutManager *layoutManager;
|
||||
|
||||
// the width as passed into uiDrawTextLayout constructors
|
||||
double width;
|
||||
|
||||
#if 0 /* TODO */
|
||||
// the *actual* size of the frame
|
||||
// note: technically, metrics returned from frame are relative to CGPathGetPathBoundingBox(tl->path)
|
||||
// however, from what I can gather, for a path created by CGPathCreateWithRect(), like we do (with a NULL transform), CGPathGetPathBoundingBox() seems to just return the standardized form of the rect used to create the path
|
||||
// (this I confirmed through experimentation)
|
||||
// so we can just use tl->size for adjustments
|
||||
// we don't need to adjust coordinates by any origin since our rect origin is (0, 0)
|
||||
CGSize size;
|
||||
#endif
|
||||
|
||||
NSMutableArray<lineInfo *> *lineInfo;
|
||||
|
||||
// for converting CFAttributedString indices to byte offsets
|
||||
size_t *u16tou8;
|
||||
size_t nu16tou8; // TODO I don't like the casing of this name
|
||||
};
|
||||
|
||||
static NSFont *fontdescToNSFont(uiDrawFontDescriptor *fd)
|
||||
{
|
||||
NSFontDescriptor *desc;
|
||||
NSFont *font;
|
||||
|
||||
desc = fontdescToNSFontDescriptor(fd);
|
||||
font = [NSFont fontWithDescriptor:desc size:fd->Size];
|
||||
[desc release];
|
||||
return font;
|
||||
}
|
||||
|
||||
static NSTextStorage *attrstrToTextStorage(uiAttributedString *s, uiDrawFontDescriptor *defaultFont)
|
||||
{
|
||||
NSString *nsstr;
|
||||
NSMutableDictionary *defaultAttrs;
|
||||
NSTextStorage *attrstr;
|
||||
|
||||
nsstr = [[NSString alloc] initWithCharacters:attrstrUTF16(s)
|
||||
length:attrstrUTF16Len(s)];
|
||||
|
||||
defaultAttrs = [NSMutableDictionary new];
|
||||
[defaultAttrs setObject:fontdescToNSFont(defaultFont)
|
||||
forKey:NSFontAttributeName];
|
||||
|
||||
attrstr = [[NSTextStorage alloc] initWithString:nsstr
|
||||
attributes:defaultAttrs];
|
||||
[defaultAttrs release];
|
||||
[nsstr release];
|
||||
|
||||
[attrstr beginEditing];
|
||||
// TODO copy in the attributes
|
||||
[attrstr endEditing];
|
||||
|
||||
return attrstr;
|
||||
}
|
||||
|
||||
// TODO fine-tune all the properties
|
||||
uiDrawTextLayout *uiDrawNewTextLayout(uiAttributedString *s, uiDrawFontDescriptor *defaultFont, double width)
|
||||
{
|
||||
uiDrawTextLayout *tl;
|
||||
CGFloat cgwidth;
|
||||
// TODO correct type?
|
||||
NSUInteger index;
|
||||
|
||||
tl = uiNew(uiDrawTextLayout);
|
||||
tl->attrstr = attrstrToTextStorage(s, defaultFont);
|
||||
tl->width = width;
|
||||
|
||||
// TODO the documentation on the size property implies this might not be necessary?
|
||||
cgwidth = (CGFloat) width;
|
||||
if (cgwidth < 0)
|
||||
cgwidth = CGFLOAT_MAX;
|
||||
// TODO rename to tl->textContainer
|
||||
tl->container = [[NSTextContainer alloc] initWithSize:NSMakeSize(cgwidth, CGFLOAT_MAX)];
|
||||
// TODO pull the reference for this
|
||||
[tl->container setLineFragmentPadding:0];
|
||||
|
||||
tl->layoutManager = [[NSLayoutManager alloc] init];
|
||||
[tl->layoutManager setTypesetterBehavior:NSTypesetterLatestBehavior];
|
||||
|
||||
[tl->layoutManager addTextContainer:tl->container];
|
||||
[tl->attrstr addLayoutManager:tl->layoutManager];
|
||||
// and force a re-layout (TODO get source
|
||||
[tl->layoutManager glyphRangeForTextContainer:tl->container];
|
||||
|
||||
// TODO equivalent of CTFrameProgression for RTL/LTR?
|
||||
|
||||
// now collect line information; see https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/TextLayout/Tasks/CountLines.html
|
||||
tl->lineInfo = [NSMutableArray<lineInfo *> new];
|
||||
index = 0;
|
||||
while (index < [tl->layoutManager numberOfGlyphs]) {
|
||||
NSRange glyphRange;
|
||||
__block lineInfo *li;
|
||||
|
||||
li = [lineInfo new];
|
||||
li.lineRect = [tl->layoutManager lineFragmentRectForGlyphAtIndex:index effectiveRange:&glyphRange];
|
||||
li.glyphRange = glyphRange;
|
||||
li.characterRange = [tl->layoutManager characterRangeForGlyphRange:li.glyphRange actualGlyphRange:NULL];
|
||||
li.lineUsedRect = [tl->layoutManager lineFragmentUsedRectForGlyphAtIndex:index effectiveRange:NULL];
|
||||
li.glyphBoundingRect = [tl->layoutManager boundingRectForGlyphRange:li.glyphRange inTextContainer:tl->container];
|
||||
// and this is from http://www.cocoabuilder.com/archive/cocoa/308568-how-to-get-baseline-info.html and http://www.cocoabuilder.com/archive/cocoa/199283-height-and-location-of-text-within-line-in-nslayoutmanager-ignoring-spacing.html
|
||||
li.baselineOffset = [[tl->layoutManager typesetter] baselineOffsetInLayoutManager:tl->layoutManager glyphIndex:index];
|
||||
li.ascent = 0;
|
||||
li.descent = 0;
|
||||
li.leading = 0;
|
||||
// imitate what AppKit actually does (or seems to)
|
||||
[tl->attrstr enumerateAttributesInRange:li.characterRange options:0 usingBlock:^(NSDictionary<NSString *,id> *attrs, NSRange range, BOOL *stop) {
|
||||
NSFont *f;
|
||||
NSRect boundingRect;
|
||||
double v, realAscent, realDescent, realLeading;
|
||||
BOOL skipAdjust, doAdjust;
|
||||
|
||||
f = (NSFont *) [attrs objectForKey:NSFontAttributeName];
|
||||
if (f == nil) {
|
||||
f = [NSFont fontWithName:@"Helvetica" size:12.0];
|
||||
if (f == nil)
|
||||
f = [NSFont systemFontOfSize:12.0];
|
||||
}
|
||||
|
||||
boundingRect = [f boundingRectForFont];
|
||||
realAscent = [f ascender];
|
||||
realDescent = -[f descender];
|
||||
realLeading = [f leading];
|
||||
skipAdjust = NO;
|
||||
doAdjust = NO;
|
||||
if (NSMaxY(boundingRect) <= realAscent) {
|
||||
// ascent entirely within bounding box
|
||||
// don't do anything if there's leading; I'm guessing it's a combination of both of the reasons to skip below... (least sure of this one)
|
||||
if (realLeading != 0)
|
||||
skipAdjust = YES;
|
||||
// does the descent slip outside the bounding box?
|
||||
if (-realDescent <= NSMinY(boundingRect))
|
||||
// yes — I guess we should assume accents don't collide with the previous line's descent, though I'm not as sure of that as I am about the else clause below...
|
||||
skipAdjust = YES;
|
||||
} else {
|
||||
// ascent outside bounding box — ascent does not include accents
|
||||
// only skip adjustment if there isn't leading (apparently some fonts use the previous line's leading for accents? :/ )
|
||||
if (realLeading != 0)
|
||||
skipAdjust = YES;
|
||||
}
|
||||
if (!skipAdjust) {
|
||||
UniChar ch = 0xC0;
|
||||
CGGlyph glyph;
|
||||
|
||||
// there does not seem to be an AppKit API for this...
|
||||
if (CTFontGetGlyphsForCharacters((CTFontRef) f, &ch, &glyph, 1) != false) {
|
||||
NSRect bbox;
|
||||
|
||||
bbox = [f boundingRectForGlyph:glyph];
|
||||
if (NSMaxY(bbox) > realAscent)
|
||||
doAdjust = YES;
|
||||
if (-realDescent > NSMinY(bbox))
|
||||
doAdjust = YES;
|
||||
}
|
||||
}
|
||||
// TODO vertical
|
||||
|
||||
v = [f ascender];
|
||||
// TODO get this one back out
|
||||
if (doAdjust)
|
||||
v += 0.2 * ([f ascender] + [f descender]);
|
||||
//v = floor(v + 0.5);
|
||||
if (li.ascent < v)
|
||||
li.ascent = v;
|
||||
|
||||
v = -[f descender];// floor(-[f descender] + 0.5);
|
||||
if (li.descent < v)
|
||||
li.descent = v;
|
||||
|
||||
v = [f leading];//floor([f leading] + 0.5);
|
||||
if (li.leading < v)
|
||||
li.leading = v;
|
||||
}];
|
||||
li.ascent = floor(li.ascent + 0.5);
|
||||
li.descent = floor(li.descent + 0.5);
|
||||
li.leading = floor(li.leading + 0.5);
|
||||
[tl->lineInfo addObject:li];
|
||||
[li release];
|
||||
index = glyphRange.location + glyphRange.length;
|
||||
}
|
||||
|
||||
// and finally copy the UTF-16 to UTF-8 index conversion table
|
||||
tl->u16tou8 = attrstrCopyUTF16ToUTF8(s, &(tl->nu16tou8));
|
||||
|
||||
return tl;
|
||||
}
|
||||
|
||||
void uiDrawFreeTextLayout(uiDrawTextLayout *tl)
|
||||
{
|
||||
uiFree(tl->u16tou8);
|
||||
[tl->lineInfo release];
|
||||
[tl->layoutManager release];
|
||||
[tl->container release];
|
||||
[tl->attrstr release];
|
||||
uiFree(tl);
|
||||
}
|
||||
|
||||
// TODO document that (x,y) is the top-left corner of the *entire frame*
|
||||
void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)
|
||||
{
|
||||
NSGraphicsContext *gc;
|
||||
|
||||
CGContextFlush(c->c); // just to be safe
|
||||
[NSGraphicsContext saveGraphicsState];
|
||||
gc = [NSGraphicsContext graphicsContextWithGraphicsPort:c->c flipped:YES];
|
||||
[NSGraphicsContext setCurrentContext:gc];
|
||||
|
||||
// TODO is this the right point?
|
||||
// TODO does this draw with the proper default styles?
|
||||
[tl->layoutManager drawGlyphsForGlyphRange:[tl->layoutManager glyphRangeForTextContainer:tl->container]
|
||||
atPoint:NSMakePoint(x, y)];
|
||||
|
||||
[gc flushGraphics]; // just to be safe
|
||||
[NSGraphicsContext restoreGraphicsState];
|
||||
// TODO release gc?
|
||||
}
|
||||
|
||||
// TODO update all of these {
|
||||
// TODO document that the width and height of a layout is not necessarily the sum of the widths and heights of its constituent lines; this is definitely untrue on OS X, where lines are placed in such a way that the distance between baselines is always integral
|
||||
// TODO width doesn't include trailing whitespace...
|
||||
// TODO figure out how paragraph spacing should play into this
|
||||
// }
|
||||
void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)
|
||||
{
|
||||
NSRect r;
|
||||
|
||||
// see https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/TextLayout/Tasks/StringHeight.html
|
||||
r = [tl->layoutManager usedRectForTextContainer:tl->container];
|
||||
*width = r.size.width;
|
||||
*height = r.size.height;
|
||||
}
|
||||
|
||||
int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl)
|
||||
{
|
||||
return [tl->lineInfo count];
|
||||
}
|
||||
|
||||
void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end)
|
||||
{
|
||||
lineInfo *li;
|
||||
|
||||
li = (lineInfo *) [tl->lineInfo objectAtIndex:line];
|
||||
*start = tl->u16tou8[li.characterRange.location];
|
||||
*end = tl->u16tou8[li.characterRange.location + li.characterRange.length];
|
||||
}
|
||||
|
||||
void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m)
|
||||
{
|
||||
lineInfo *li;
|
||||
|
||||
li = (lineInfo *) [tl->lineInfo objectAtIndex:line];
|
||||
|
||||
m->X = li.lineRect.origin.x;
|
||||
m->Y = li.lineRect.origin.y;
|
||||
// if we use li.lineRect here we get the whole line, not just the part with stuff in it
|
||||
m->Width = li.lineUsedRect.size.width;
|
||||
m->Height = li.lineRect.size.height;
|
||||
|
||||
// TODO is this correct?
|
||||
m->BaselineY = (m->Y + m->Height) - li.baselineOffset;
|
||||
m->Ascent = li.ascent;
|
||||
m->Descent = li.descent;
|
||||
m->Leading = li.leading;
|
||||
|
||||
// TODO
|
||||
m->ParagraphSpacingBefore = 0;
|
||||
m->LineHeightSpace = 0;
|
||||
m->LineSpacing = 0;
|
||||
m->ParagraphSpacing = 0;
|
||||
}
|
||||
|
||||
void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, uiDrawTextLayoutHitTestResult *result)
|
||||
{
|
||||
#if 0 /* TODO */
|
||||
CFIndex i;
|
||||
CTLineRef line;
|
||||
CFIndex pos;
|
||||
|
||||
if (y >= 0) {
|
||||
for (i = 0; i < tl->nLines; i++) {
|
||||
double ltop, lbottom;
|
||||
|
||||
ltop = tl->lineMetrics[i].Y;
|
||||
lbottom = ltop + tl->lineMetrics[i].Height;
|
||||
if (y >= ltop && y < lbottom)
|
||||
break;
|
||||
}
|
||||
result->YPosition = uiDrawTextLayoutHitTestPositionInside;
|
||||
if (i == tl->nLines) {
|
||||
i--;
|
||||
result->YPosition = uiDrawTextLayoutHitTestPositionAfter;
|
||||
}
|
||||
} else {
|
||||
i = 0;
|
||||
result->YPosition = uiDrawTextLayoutHitTestPositionBefore;
|
||||
}
|
||||
result->Line = i;
|
||||
|
||||
result->XPosition = uiDrawTextLayoutHitTestPositionInside;
|
||||
if (x < tl->lineMetrics[i].X) {
|
||||
result->XPosition = uiDrawTextLayoutHitTestPositionBefore;
|
||||
// and forcibly return the first character
|
||||
x = tl->lineMetrics[i].X;
|
||||
} else if (x > (tl->lineMetrics[i].X + tl->lineMetrics[i].Width)) {
|
||||
result->XPosition = uiDrawTextLayoutHitTestPositionAfter;
|
||||
// and forcibly return the last character
|
||||
x = tl->lineMetrics[i].X + tl->lineMetrics[i].Width;
|
||||
}
|
||||
|
||||
line = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, i);
|
||||
// TODO copy the part from the docs about this point
|
||||
pos = CTLineGetStringIndexForPosition(line, CGPointMake(x, 0));
|
||||
if (pos == kCFNotFound) {
|
||||
// TODO
|
||||
}
|
||||
result->Pos = tl->u16tou8[pos];
|
||||
#endif
|
||||
}
|
||||
|
||||
void uiDrawTextLayoutByteRangeToRectangle(uiDrawTextLayout *tl, size_t start, size_t end, uiDrawTextLayoutByteRangeRectangle *r)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
// 3 january 2017
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// Stupidity: Core Text requires an **exact match for the entire traits dictionary**, otherwise it will **drop ALL the traits**.
|
||||
// This seems to be true for every function in Core Text that advertises that it performs font matching; I can confirm at the time of writing this is the case for
|
||||
// - CTFontDescriptorCreateMatchingFontDescriptors() (though the conditions here seem odd)
|
||||
// - CTFontCreateWithFontDescriptor()
|
||||
// - CTFontCreateCopyWithAttributes()
|
||||
// And as a bonus prize, this also applies to Cocoa's NSFontDescriptor methods as well!
|
||||
// We have to implement the closest match ourselves.
|
||||
// This needs to be done early in the matching process; in particular, we have to do this before adding any features (such as small caps), because the matching descriptors won't have those.
|
||||
|
||||
struct closeness {
|
||||
NSUInteger index;
|
||||
double weight;
|
||||
double italic;
|
||||
double stretch;
|
||||
double distance;
|
||||
};
|
||||
|
||||
static double doubleAttr(NSDictionary *traits, NSString *attr)
|
||||
{
|
||||
NSNumber *n;
|
||||
|
||||
n = (NSNumber *) [traits objectForKey:attr];
|
||||
return [n doubleValue];
|
||||
}
|
||||
|
||||
struct italicCloseness {
|
||||
double normal;
|
||||
double oblique;
|
||||
double italic;
|
||||
};
|
||||
|
||||
// remember that in closeness, 0 means exact
|
||||
// in this case, since we define the range, we use 0.5 to mean "close enough" (oblique for italic and italic for oblique) and 1 to mean "not a match"
|
||||
static const struct italicCloseness italicClosenesses[] = {
|
||||
[uiDrawTextItalicNormal] = { 0, 1, 1 },
|
||||
[uiDrawTextItalicOblique] = { 1, 0, 0.5 },
|
||||
[uiDrawTextItalicItalic] = { 1, 0.5, 0 },
|
||||
};
|
||||
|
||||
// Italics are hard because Core Text does NOT distinguish between italic and oblique.
|
||||
// All Core Text provides is a slant value and the italic bit of the symbolic traits mask.
|
||||
// However, Core Text does seem to guarantee (from experimentation; see below) that the slant will be nonzero if and only if the italic bit is set, so we don't need to use the slant value.
|
||||
// Core Text also seems to guarantee that if a font lists itself as Italic or Oblique by name (font subfamily name, font style name, whatever), it will also have that bit set, so testing this bit does cover all fonts that name themselves as Italic and Oblique. (Again, this is from the below experimentation.)
|
||||
// TODO there is still one catch that might matter from a user's POV: the reverse is not true — the italic bit can be set even if the style of the font face/subfamily/style isn't named as Italic (for example, script typefaces like Adobe's Palace Script MT Std); I don't know what to do about this... I know how to start: find a script font that has an italic form (Adobe's Palace Script MT Std does not; only Regular and Semibold)
|
||||
// TODO see if the above applies to Cocoa as well
|
||||
static double italicCloseness(NSFontDescriptor *desc, NSDictionary *traits, uiDrawTextItalic italic)
|
||||
{
|
||||
const struct italicCloseness *ic = &(italicClosenesses[italic]);
|
||||
NSNumber *num;
|
||||
NSFontSymbolicTraits symbolic;
|
||||
NSString *styleName;
|
||||
NSRange range;
|
||||
BOOL isOblique;
|
||||
|
||||
num = (NSNumber *) [traits objectForKey:NSFontSymbolicTrait];
|
||||
// TODO this should really be a uint32_t-specific one
|
||||
symbolic = (NSFontSymbolicTraits) [num unsignedIntegerValue];
|
||||
if ((symbolic & NSFontItalicTrait) == 0)
|
||||
return ic->normal;
|
||||
|
||||
// Okay, now we know it's either Italic or Oblique
|
||||
// Pango's Core Text code just does a g_strrstr() (backwards case-sensitive search) for "Oblique" in the font's style name (see https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c); let's do that too I guess
|
||||
// note that NSFontFaceAttribute is the Cocoa equivalent of the style name
|
||||
isOblique = NO; // default value
|
||||
styleName = (NSString *) [desc objectForKey:NSFontFaceAttribute];
|
||||
// TODO is styleName guaranteed?
|
||||
// TODO NSLiteralSearch?
|
||||
range = [styleName rangeOfString:@"Oblique" options:NSCaseInsensitiveSearch];
|
||||
if (range.location != NSNotFound)
|
||||
return ic->oblique;
|
||||
return ic->italic;
|
||||
}
|
||||
|
||||
static NSFontDescriptor *matchTraits(NSFontDescriptor *against, double targetWeight, uiDrawTextItalic targetItalic, double targetStretch)
|
||||
{
|
||||
NSArray<NSFontDescriptor *> *matching;
|
||||
NSUInteger i, n;
|
||||
struct closeness *closeness;
|
||||
NSFontDescriptor *current;
|
||||
NSFontDescriptor *out;
|
||||
|
||||
matching = [against matchingFontDescriptorsWithMandatoryKeys:nil];
|
||||
if (matching == nil)
|
||||
// no matches; give the original back and hope for the best
|
||||
return against;
|
||||
n = [matching count];
|
||||
if (n == 0) {
|
||||
// likewise
|
||||
//TODO [matching release];
|
||||
return against;
|
||||
}
|
||||
|
||||
closeness = (struct closeness *) uiAlloc(n * sizeof (struct closeness), "struct closeness[]");
|
||||
for (i = 0; i < n; i++) {
|
||||
NSDictionary *traits;
|
||||
|
||||
closeness[i].index = i;
|
||||
current = (NSFontDescriptor *) [matching objectAtIndex:i];
|
||||
traits = (NSDictionary *) [current objectForKey:NSFontTraitsAttribute];
|
||||
if (traits == nil) {
|
||||
// couldn't get traits; be safe by ranking it lowest
|
||||
// LONGTERM figure out what the longest possible distances are
|
||||
closeness[i].weight = 3;
|
||||
closeness[i].italic = 2;
|
||||
closeness[i].stretch = 3;
|
||||
continue;
|
||||
}
|
||||
closeness[i].weight = doubleAttr(traits, NSFontWeightTrait) - targetWeight;
|
||||
closeness[i].italic = italicCloseness(current, traits, targetItalic);
|
||||
closeness[i].stretch = doubleAttr(traits, NSFontWidthTrait) - targetStretch;
|
||||
// TODO release traits?
|
||||
}
|
||||
|
||||
// now figure out the 3-space difference between the three and sort by that
|
||||
// TODO merge this loop with the previous loop?
|
||||
for (i = 0; i < n; i++) {
|
||||
double weight, italic, stretch;
|
||||
|
||||
weight = closeness[i].weight;
|
||||
weight *= weight;
|
||||
italic = closeness[i].italic;
|
||||
italic *= italic;
|
||||
stretch = closeness[i].stretch;
|
||||
stretch *= stretch;
|
||||
closeness[i].distance = sqrt(weight + italic + stretch);
|
||||
}
|
||||
qsort_b(closeness, n, sizeof (struct closeness), ^(const void *aa, const void *bb) {
|
||||
const struct closeness *a = (const struct closeness *) aa;
|
||||
const struct closeness *b = (const struct closeness *) bb;
|
||||
|
||||
// via http://www.gnu.org/software/libc/manual/html_node/Comparison-Functions.html#Comparison-Functions
|
||||
// LONGTERM is this really the best way? isn't it the same as if (*a < *b) return -1; if (*a > *b) return 1; return 0; ?
|
||||
return (a->distance > b->distance) - (a->distance < b->distance);
|
||||
});
|
||||
// and the first element of the sorted array is what we want
|
||||
out = (NSFontDescriptor *) [matching objectAtIndex:closeness[0].index];
|
||||
// TODO is this correct?
|
||||
[out retain]; // get rule
|
||||
|
||||
// release everything
|
||||
uiFree(closeness);
|
||||
//TODO [matching release];
|
||||
// and release the original descriptor since we no longer need it
|
||||
[against release];
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// since uiDrawTextWeight effectively corresponds to OS/2 weights (which roughly correspond to GDI, Pango, and DirectWrite weights, and to a lesser(? TODO) degree, CSS weights), let's just do what Core Text does with OS/2 weights
|
||||
// TODO this will not be correct for system fonts, which use cached values that have no relation to the OS/2 weights; we need to figure out how to reconcile these
|
||||
// for more information, see https://bugzilla.gnome.org/show_bug.cgi?id=766148 and TODO_put_blog_post_here_once_I_write_it (TODO keep this line when resolving the above TODO)
|
||||
static const double weightsToCTWeights[] = {
|
||||
-1.0, // 0..99
|
||||
-0.7, // 100..199
|
||||
-0.5, // 200..299
|
||||
-0.23, // 300..399
|
||||
0.0, // 400..499
|
||||
0.2, // 500..599
|
||||
0.3, // 600..699
|
||||
0.4, // 700..799
|
||||
0.6, // 800..899
|
||||
0.8, // 900..999
|
||||
1.0, // 1000
|
||||
};
|
||||
|
||||
static double weightToCTWeight(uiDrawTextWeight weight)
|
||||
{
|
||||
int weightClass;
|
||||
double ctclass;
|
||||
double rest, weightFloor, nextFloor;
|
||||
|
||||
if (weight <= 0)
|
||||
return -1.0;
|
||||
if (weight >= 1000)
|
||||
return 1.0;
|
||||
|
||||
weightClass = weight / 100;
|
||||
rest = (double) weight;
|
||||
weightFloor = (double) (weightClass * 100);
|
||||
nextFloor = (double) ((weightClass + 1) * 100);
|
||||
rest = (rest - weightFloor) / (nextFloor - weightFloor);
|
||||
|
||||
ctclass = weightsToCTWeights[weightClass];
|
||||
return fma(rest,
|
||||
weightsToCTWeights[weightClass + 1] - ctclass,
|
||||
ctclass);
|
||||
}
|
||||
|
||||
// based on what Core Text says about actual fonts (system fonts, system fonts in another folder to avoid using cached values, Adobe Font Folio 11, Google Fonts archive, fonts in Windows 7/8.1/10)
|
||||
static const double stretchesToCTWidths[] = {
|
||||
[uiDrawTextStretchUltraCondensed] = -0.400000,
|
||||
[uiDrawTextStretchExtraCondensed] = -0.300000,
|
||||
[uiDrawTextStretchCondensed] = -0.200000,
|
||||
[uiDrawTextStretchSemiCondensed] = -0.100000,
|
||||
[uiDrawTextStretchNormal] = 0.000000,
|
||||
[uiDrawTextStretchSemiExpanded] = 0.100000,
|
||||
[uiDrawTextStretchExpanded] = 0.200000,
|
||||
[uiDrawTextStretchExtraExpanded] = 0.300000,
|
||||
// this one isn't present in any of the fonts I tested, but it follows naturally from the pattern of the rest, so... (TODO verify by checking the font files directly)
|
||||
[uiDrawTextStretchUltraExpanded] = 0.400000,
|
||||
};
|
||||
|
||||
NSFontDescriptor *fontdescToNSFontDescriptor(uiDrawFontDescriptor *fd)
|
||||
{
|
||||
NSMutableDictionary *attrs;
|
||||
NSFontDescriptor *basedesc;
|
||||
|
||||
attrs = [NSMutableDictionary new];
|
||||
[attrs setObject:[NSString stringWithUTF8String:fd->Family]
|
||||
forKey:NSFontFamilyAttribute];
|
||||
[attrs setObject:[NSNumber numberWithDouble:fd->Size]
|
||||
forKey:NSFontSizeAttribute];
|
||||
|
||||
basedesc = [[NSFontDescriptor alloc] initWithFontAttributes:attrs];
|
||||
[attrs release];
|
||||
return matchTraits(basedesc,
|
||||
weightToCTWeight(fd->Weight),
|
||||
fd->Italic,
|
||||
stretchesToCTWidths[fd->Stretch]);
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
// 6 september 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// TODO double-check that we are properly handling allocation failures (or just toll free bridge from cocoa)
|
||||
struct uiDrawFontFamilies {
|
||||
CFArrayRef fonts;
|
||||
};
|
||||
|
||||
uiDrawFontFamilies *uiDrawListFontFamilies(void)
|
||||
{
|
||||
uiDrawFontFamilies *ff;
|
||||
|
||||
ff = uiNew(uiDrawFontFamilies);
|
||||
ff->fonts = CTFontManagerCopyAvailableFontFamilyNames();
|
||||
if (ff->fonts == NULL)
|
||||
implbug("error getting available font names (no reason specified) (TODO)");
|
||||
return ff;
|
||||
}
|
||||
|
||||
int uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff)
|
||||
{
|
||||
return CFArrayGetCount(ff->fonts);
|
||||
}
|
||||
|
||||
char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, int n)
|
||||
{
|
||||
CFStringRef familystr;
|
||||
char *family;
|
||||
|
||||
familystr = (CFStringRef) CFArrayGetValueAtIndex(ff->fonts, n);
|
||||
// toll-free bridge
|
||||
family = uiDarwinNSStringToText((NSString *) familystr);
|
||||
// Get Rule means we do not free familystr
|
||||
return family;
|
||||
}
|
||||
|
||||
void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff)
|
||||
{
|
||||
CFRelease(ff->fonts);
|
||||
uiFree(ff);
|
||||
}
|
||||
|
||||
struct uiDrawTextFont {
|
||||
CTFontRef f;
|
||||
};
|
||||
|
||||
uiDrawTextFont *mkTextFont(CTFontRef f, BOOL retain)
|
||||
{
|
||||
uiDrawTextFont *font;
|
||||
|
||||
font = uiNew(uiDrawTextFont);
|
||||
font->f = f;
|
||||
if (retain)
|
||||
CFRetain(font->f);
|
||||
return font;
|
||||
}
|
||||
|
||||
uiDrawTextFont *mkTextFontFromNSFont(NSFont *f)
|
||||
{
|
||||
// toll-free bridging; we do retain, though
|
||||
return mkTextFont((CTFontRef) f, YES);
|
||||
}
|
||||
|
||||
static CFMutableDictionaryRef newAttrList(void)
|
||||
{
|
||||
CFMutableDictionaryRef attr;
|
||||
|
||||
attr = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
if (attr == NULL)
|
||||
complain("error creating attribute dictionary in newAttrList()()");
|
||||
return attr;
|
||||
}
|
||||
|
||||
static void addFontFamilyAttr(CFMutableDictionaryRef attr, const char *family)
|
||||
{
|
||||
CFStringRef cfstr;
|
||||
|
||||
cfstr = CFStringCreateWithCString(NULL, family, kCFStringEncodingUTF8);
|
||||
if (cfstr == NULL)
|
||||
complain("error creating font family name CFStringRef in addFontFamilyAttr()");
|
||||
CFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, cfstr);
|
||||
CFRelease(cfstr); // dictionary holds its own reference
|
||||
}
|
||||
|
||||
static void addFontSizeAttr(CFMutableDictionaryRef attr, double size)
|
||||
{
|
||||
CFNumberRef n;
|
||||
|
||||
n = CFNumberCreate(NULL, kCFNumberDoubleType, &size);
|
||||
CFDictionaryAddValue(attr, kCTFontSizeAttribute, n);
|
||||
CFRelease(n);
|
||||
}
|
||||
|
||||
#if 0
|
||||
TODO
|
||||
// See http://stackoverflow.com/questions/4810409/does-coretext-support-small-caps/4811371#4811371 and https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c for what these do
|
||||
// And fortunately, unlike the traits (see below), unmatched features are simply ignored without affecting the other features :D
|
||||
static void addFontSmallCapsAttr(CFMutableDictionaryRef attr)
|
||||
{
|
||||
CFMutableArrayRef outerArray;
|
||||
CFMutableDictionaryRef innerDict;
|
||||
CFNumberRef numType, numSelector;
|
||||
int num;
|
||||
|
||||
outerArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
|
||||
if (outerArray == NULL)
|
||||
complain("error creating outer CFArray for adding small caps attributes in addFontSmallCapsAttr()");
|
||||
|
||||
// Apple's headers say these are deprecated, but a few fonts still rely on them
|
||||
num = kLetterCaseType;
|
||||
numType = CFNumberCreate(NULL, kCFNumberIntType, &num);
|
||||
num = kSmallCapsSelector;
|
||||
numSelector = CFNumberCreate(NULL, kCFNumberIntType, &num);
|
||||
innerDict = newAttrList();
|
||||
CFDictionaryAddValue(innerDict, kCTFontFeatureTypeIdentifierKey, numType);
|
||||
CFRelease(numType);
|
||||
CFDictionaryAddValue(innerDict, kCTFontFeatureSelectorIdentifierKey, numSelector);
|
||||
CFRelease(numSelector);
|
||||
CFArrayAppendValue(outerArray, innerDict);
|
||||
CFRelease(innerDict); // and likewise for CFArray
|
||||
|
||||
// these are the non-deprecated versions of the above; some fonts have these instead
|
||||
num = kLowerCaseType;
|
||||
numType = CFNumberCreate(NULL, kCFNumberIntType, &num);
|
||||
num = kLowerCaseSmallCapsSelector;
|
||||
numSelector = CFNumberCreate(NULL, kCFNumberIntType, &num);
|
||||
innerDict = newAttrList();
|
||||
CFDictionaryAddValue(innerDict, kCTFontFeatureTypeIdentifierKey, numType);
|
||||
CFRelease(numType);
|
||||
CFDictionaryAddValue(innerDict, kCTFontFeatureSelectorIdentifierKey, numSelector);
|
||||
CFRelease(numSelector);
|
||||
CFArrayAppendValue(outerArray, innerDict);
|
||||
CFRelease(innerDict); // and likewise for CFArray
|
||||
|
||||
CFDictionaryAddValue(attr, kCTFontFeatureSettingsAttribute, outerArray);
|
||||
CFRelease(outerArray);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
// Named constants for these were NOT added until 10.11, and even then they were added as external symbols instead of macros, so we can't use them directly :(
|
||||
// kode54 got these for me before I had access to El Capitan; thanks to him.
|
||||
#define ourNSFontWeightUltraLight -0.800000
|
||||
#define ourNSFontWeightThin -0.600000
|
||||
#define ourNSFontWeightLight -0.400000
|
||||
#define ourNSFontWeightRegular 0.000000
|
||||
#define ourNSFontWeightMedium 0.230000
|
||||
#define ourNSFontWeightSemibold 0.300000
|
||||
#define ourNSFontWeightBold 0.400000
|
||||
#define ourNSFontWeightHeavy 0.560000
|
||||
#define ourNSFontWeightBlack 0.620000
|
||||
#endif
|
||||
|
||||
// Now remember what I said earlier about having to add the small caps traits after calling the above? This gets a dictionary back so we can do so.
|
||||
CFMutableDictionaryRef extractAttributes(CTFontDescriptorRef desc)
|
||||
{
|
||||
CFDictionaryRef dict;
|
||||
CFMutableDictionaryRef mdict;
|
||||
|
||||
dict = CTFontDescriptorCopyAttributes(desc);
|
||||
// this might not be mutable, so make a mutable copy
|
||||
mdict = CFDictionaryCreateMutableCopy(NULL, 0, dict);
|
||||
CFRelease(dict);
|
||||
return mdict;
|
||||
}
|
||||
|
||||
uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc)
|
||||
{
|
||||
CTFontRef f;
|
||||
CFMutableDictionaryRef attr;
|
||||
CTFontDescriptorRef cfdesc;
|
||||
|
||||
attr = newAttrList();
|
||||
addFontFamilyAttr(attr, desc->Family);
|
||||
addFontSizeAttr(attr, desc->Size);
|
||||
|
||||
// now we have to do the traits matching, so create a descriptor, match the traits, and then get the attributes back
|
||||
cfdesc = CTFontDescriptorCreateWithAttributes(attr);
|
||||
// TODO release attr?
|
||||
cfdesc = matchTraits(cfdesc, desc->Weight, desc->Italic, desc->Stretch);
|
||||
|
||||
// specify the initial size again just to be safe
|
||||
f = CTFontCreateWithFontDescriptor(cfdesc, desc->Size, NULL);
|
||||
// TODO release cfdesc?
|
||||
|
||||
return mkTextFont(f, NO); // we hold the initial reference; no need to retain again
|
||||
}
|
||||
|
||||
void uiDrawFreeTextFont(uiDrawTextFont *font)
|
||||
{
|
||||
CFRelease(font->f);
|
||||
uiFree(font);
|
||||
}
|
||||
|
||||
uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font)
|
||||
{
|
||||
return (uintptr_t) (font->f);
|
||||
}
|
||||
|
||||
void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
// text sizes and user space points are identical:
|
||||
// - https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/TypoFeatures/TextSystemFeatures.html#//apple_ref/doc/uid/TP40009459-CH6-51627-BBCCHIFF text points are 72 per inch
|
||||
// - https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Transforms/Transforms.html#//apple_ref/doc/uid/TP40003290-CH204-SW5 user space points are 72 per inch
|
||||
void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics)
|
||||
{
|
||||
metrics->Ascent = CTFontGetAscent(font->f);
|
||||
metrics->Descent = CTFontGetDescent(font->f);
|
||||
metrics->Leading = CTFontGetLeading(font->f);
|
||||
metrics->UnderlinePos = CTFontGetUnderlinePosition(font->f);
|
||||
metrics->UnderlineThickness = CTFontGetUnderlineThickness(font->f);
|
||||
}
|
||||
|
||||
// LONGTERM allow line separation and leading to be factored into a wrapping text layout
|
||||
|
||||
// TODO reconcile differences in character wrapping on platforms
|
||||
void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height)
|
||||
{
|
||||
struct framesetter fs;
|
||||
|
||||
mkFramesetter(layout, &fs);
|
||||
*width = fs.extents.width;
|
||||
*height = fs.extents.height;
|
||||
freeFramesetter(&fs);
|
||||
}
|
||||
|
||||
// LONGTERM provide an equivalent to CTLineGetTypographicBounds() on uiDrawTextLayout?
|
||||
|
||||
// LONGTERM keep this for later features and documentation purposes
|
||||
#if 0
|
||||
|
||||
// LONGTERM provide a way to get the image bounds as a separate function later
|
||||
bounds = CTLineGetImageBounds(line, c);
|
||||
// though CTLineGetImageBounds() returns CGRectNull on error, it also returns CGRectNull on an empty string, so we can't reasonably check for error
|
||||
|
||||
// CGContextSetTextPosition() positions at the baseline in the case of CTLineDraw(); we need the top-left corner instead
|
||||
CTLineGetTypographicBounds(line, &yoff, NULL, NULL);
|
||||
// remember that we're flipped, so we subtract
|
||||
y -= yoff;
|
||||
CGContextSetTextPosition(c, x, y);
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, int startChar, int endChar, double r, double g, double b, double a)
|
||||
{
|
||||
CGColorSpaceRef colorspace;
|
||||
CGFloat components[4];
|
||||
CGColorRef color;
|
||||
|
||||
// for consistency with windows, use sRGB
|
||||
colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
|
||||
components[0] = r;
|
||||
components[1] = g;
|
||||
components[2] = b;
|
||||
components[3] = a;
|
||||
color = CGColorCreate(colorspace, components);
|
||||
CGColorSpaceRelease(colorspace);
|
||||
|
||||
CFAttributedStringSetAttribute(layout->mas,
|
||||
rangeToCFRange(),
|
||||
kCTForegroundColorAttributeName,
|
||||
color);
|
||||
CGColorRelease(color); // TODO safe?
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,314 @@
|
||||
// 2 january 2017
|
||||
#import "uipriv_darwin.h"
|
||||
#import "draw.h"
|
||||
|
||||
// TODO on an empty string nLines == 0
|
||||
// we must prevent this somehow
|
||||
// TODO in general, every function could be more robust, but we cannot have a situation where there are zero lines
|
||||
// TODO what happens to extents if only whitespace?
|
||||
|
||||
struct uiDrawTextLayout {
|
||||
CFAttributedStringRef attrstr;
|
||||
|
||||
// the width as passed into uiDrawTextLayout constructors
|
||||
double width;
|
||||
|
||||
CTFramesetterRef framesetter;
|
||||
|
||||
// the *actual* size of the frame
|
||||
// note: technically, metrics returned from frame are relative to CGPathGetPathBoundingBox(tl->path)
|
||||
// however, from what I can gather, for a path created by CGPathCreateWithRect(), like we do (with a NULL transform), CGPathGetPathBoundingBox() seems to just return the standardized form of the rect used to create the path
|
||||
// (this I confirmed through experimentation)
|
||||
// so we can just use tl->size for adjustments
|
||||
// we don't need to adjust coordinates by any origin since our rect origin is (0, 0)
|
||||
CGSize size;
|
||||
|
||||
CGPathRef path;
|
||||
CTFrameRef frame;
|
||||
|
||||
CFArrayRef lines;
|
||||
CFIndex nLines;
|
||||
// we compute this once when first creating the layout
|
||||
uiDrawTextLayoutLineMetrics *lineMetrics;
|
||||
|
||||
NSArray *backgroundBlocks;
|
||||
|
||||
// for converting CFAttributedString indices from/to byte offsets
|
||||
size_t *u8tou16;
|
||||
size_t nUTF8;
|
||||
size_t *u16tou8;
|
||||
size_t nUTF16;
|
||||
};
|
||||
|
||||
// TODO document that lines may or may not overlap because ours do in the case of multiple combining characters
|
||||
static uiDrawTextLayoutLineMetrics *computeLineMetrics(CTFrameRef frame, CGSize size)
|
||||
{
|
||||
uiDrawTextLayoutLineMetrics *metrics;
|
||||
CFArrayRef lines;
|
||||
CTLineRef line;
|
||||
CFIndex i, n;
|
||||
CGFloat ypos;
|
||||
CGRect bounds, boundsNoLeading;
|
||||
CGFloat ascent, descent, leading;
|
||||
CGPoint *origins;
|
||||
|
||||
lines = CTFrameGetLines(frame);
|
||||
n = CFArrayGetCount(lines);
|
||||
metrics = (uiDrawTextLayoutLineMetrics *) uiAlloc(n * sizeof (uiDrawTextLayoutLineMetrics), "uiDrawTextLayoutLineMetrics[] (text layout)");
|
||||
|
||||
origins = (CGPoint *) uiAlloc(n * sizeof (CGPoint), "CGPoint[] (text layout)");
|
||||
CTFrameGetLineOrigins(frame, CFRangeMake(0, n), origins);
|
||||
|
||||
ypos = size.height;
|
||||
for (i = 0; i < n; i++) {
|
||||
line = (CTLineRef) CFArrayGetValueAtIndex(lines, i);
|
||||
bounds = CTLineGetBoundsWithOptions(line, 0);
|
||||
boundsNoLeading = CTLineGetBoundsWithOptions(line, kCTLineBoundsExcludeTypographicLeading);
|
||||
|
||||
// this is equivalent to boundsNoLeading.size.height + boundsNoLeading.origin.y (manually verified)
|
||||
ascent = bounds.size.height + bounds.origin.y;
|
||||
descent = -boundsNoLeading.origin.y;
|
||||
leading = -bounds.origin.y - descent;
|
||||
|
||||
// Core Text always rounds these up for paragraph style calculations; there is a flag to control it but it's inaccessible (and this behavior is turned off for old versions of iPhoto)
|
||||
ascent = floor(ascent + 0.5);
|
||||
descent = floor(descent + 0.5);
|
||||
if (leading > 0)
|
||||
leading = floor(leading + 0.5);
|
||||
|
||||
metrics[i].X = origins[i].x;
|
||||
metrics[i].Y = origins[i].y - descent - leading;
|
||||
metrics[i].Width = bounds.size.width;
|
||||
metrics[i].Height = ascent + descent + leading;
|
||||
|
||||
metrics[i].BaselineY = origins[i].y;
|
||||
metrics[i].Ascent = ascent;
|
||||
metrics[i].Descent = descent;
|
||||
metrics[i].Leading = leading;
|
||||
|
||||
// TODO
|
||||
metrics[i].ParagraphSpacingBefore = 0;
|
||||
metrics[i].LineHeightSpace = 0;
|
||||
metrics[i].LineSpacing = 0;
|
||||
metrics[i].ParagraphSpacing = 0;
|
||||
|
||||
// and finally advance to the next line
|
||||
ypos += metrics[i].Height;
|
||||
}
|
||||
|
||||
// okay, but now all these metrics are unflipped
|
||||
// we need to flip them
|
||||
for (i = 0; i < n; i++) {
|
||||
metrics[i].Y = size.height - metrics[i].Y;
|
||||
// go from bottom-left corner to top-left
|
||||
metrics[i].Y -= metrics[i].Height;
|
||||
metrics[i].BaselineY = size.height - metrics[i].BaselineY;
|
||||
}
|
||||
|
||||
uiFree(origins);
|
||||
return metrics;
|
||||
}
|
||||
|
||||
uiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p)
|
||||
{
|
||||
uiDrawTextLayout *tl;
|
||||
CGFloat cgwidth;
|
||||
CFRange range, unused;
|
||||
CGRect rect;
|
||||
|
||||
tl = uiNew(uiDrawTextLayout);
|
||||
tl->attrstr = attrstrToCoreFoundation(p, &(tl->backgroundBlocks));
|
||||
range.location = 0;
|
||||
range.length = CFAttributedStringGetLength(tl->attrstr);
|
||||
tl->width = p->Width;
|
||||
|
||||
// TODO kCTParagraphStyleSpecifierMaximumLineSpacing, kCTParagraphStyleSpecifierMinimumLineSpacing, kCTParagraphStyleSpecifierLineSpacingAdjustment for line spacing
|
||||
tl->framesetter = CTFramesetterCreateWithAttributedString(tl->attrstr);
|
||||
if (tl->framesetter == NULL) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
cgwidth = (CGFloat) (tl->width);
|
||||
if (cgwidth < 0)
|
||||
cgwidth = CGFLOAT_MAX;
|
||||
tl->size = CTFramesetterSuggestFrameSizeWithConstraints(tl->framesetter,
|
||||
range,
|
||||
// TODO kCTFramePathWidthAttributeName?
|
||||
NULL,
|
||||
CGSizeMake(cgwidth, CGFLOAT_MAX),
|
||||
&unused); // not documented as accepting NULL (TODO really?)
|
||||
|
||||
rect.origin = CGPointZero;
|
||||
rect.size = tl->size;
|
||||
tl->path = CGPathCreateWithRect(rect, NULL);
|
||||
tl->frame = CTFramesetterCreateFrame(tl->framesetter,
|
||||
range,
|
||||
tl->path,
|
||||
// TODO kCTFramePathWidthAttributeName?
|
||||
NULL);
|
||||
if (tl->frame == NULL) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
tl->lines = CTFrameGetLines(tl->frame);
|
||||
tl->nLines = CFArrayGetCount(tl->lines);
|
||||
tl->lineMetrics = computeLineMetrics(tl->frame, tl->size);
|
||||
|
||||
// and finally copy the UTF-8/UTF-16 conversion tables
|
||||
tl->u8tou16 = attrstrCopyUTF8ToUTF16(p->String, &(tl->nUTF8));
|
||||
tl->u16tou8 = attrstrCopyUTF16ToUTF8(p->String, &(tl->nUTF16));
|
||||
|
||||
return tl;
|
||||
}
|
||||
|
||||
void uiDrawFreeTextLayout(uiDrawTextLayout *tl)
|
||||
{
|
||||
uiFree(tl->u16tou8);
|
||||
uiFree(tl->u8tou16);
|
||||
[tl->backgroundBlocks release];
|
||||
uiFree(tl->lineMetrics);
|
||||
CFRelease(tl->frame);
|
||||
CFRelease(tl->path);
|
||||
CFRelease(tl->framesetter);
|
||||
CFRelease(tl->attrstr);
|
||||
uiFree(tl);
|
||||
}
|
||||
|
||||
// TODO document that (x,y) is the top-left corner of the *entire frame*
|
||||
void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)
|
||||
{
|
||||
backgroundBlock b;
|
||||
CGAffineTransform textMatrix;
|
||||
|
||||
CGContextSaveGState(c->c);
|
||||
// save the text matrix because it's not part of the graphics state
|
||||
textMatrix = CGContextGetTextMatrix(c->c);
|
||||
|
||||
for (b in tl->backgroundBlocks)
|
||||
b(c, tl, x, y);
|
||||
|
||||
// Core Text doesn't draw onto a flipped view correctly; we have to pretend it was unflipped
|
||||
// see the iOS bits of the first example at https://developer.apple.com/library/mac/documentation/StringsTextFonts/Conceptual/CoreText_Programming/LayoutOperations/LayoutOperations.html#//apple_ref/doc/uid/TP40005533-CH12-SW1 (iOS is naturally flipped)
|
||||
// TODO how is this affected by a non-identity CTM?
|
||||
CGContextTranslateCTM(c->c, 0, c->height);
|
||||
CGContextScaleCTM(c->c, 1.0, -1.0);
|
||||
CGContextSetTextMatrix(c->c, CGAffineTransformIdentity);
|
||||
|
||||
// wait, that's not enough; we need to offset y values to account for our new flipping
|
||||
// TODO explain this calculation
|
||||
y = c->height - tl->size.height - y;
|
||||
|
||||
// CTFrameDraw() draws in the path we specified when creating the frame
|
||||
// this means that in our usage, CTFrameDraw() will draw at (0,0)
|
||||
// so move the origin to be at (x,y) instead
|
||||
// TODO are the signs correct?
|
||||
CGContextTranslateCTM(c->c, x, y);
|
||||
|
||||
CTFrameDraw(tl->frame, c->c);
|
||||
|
||||
CGContextSetTextMatrix(c->c, textMatrix);
|
||||
CGContextRestoreGState(c->c);
|
||||
}
|
||||
|
||||
// TODO document that the width and height of a layout is not necessarily the sum of the widths and heights of its constituent lines
|
||||
// TODO width doesn't include trailing whitespace...
|
||||
// TODO figure out how paragraph spacing should play into this
|
||||
void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)
|
||||
{
|
||||
*width = tl->size.width;
|
||||
*height = tl->size.height;
|
||||
}
|
||||
|
||||
int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl)
|
||||
{
|
||||
return tl->nLines;
|
||||
}
|
||||
|
||||
void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end)
|
||||
{
|
||||
CTLineRef lr;
|
||||
CFRange range;
|
||||
|
||||
lr = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, line);
|
||||
range = CTLineGetStringRange(lr);
|
||||
*start = tl->u16tou8[range.location];
|
||||
*end = tl->u16tou8[range.location + range.length];
|
||||
}
|
||||
|
||||
void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m)
|
||||
{
|
||||
*m = tl->lineMetrics[line];
|
||||
}
|
||||
|
||||
// in the case of overlapping lines, we read lines first to last and use their bottommost point (Y + Height) to determine where the next line should start for hit-testing
|
||||
// TODO should we document this?
|
||||
void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *pos, int *line)
|
||||
{
|
||||
int i;
|
||||
CTLineRef ln;
|
||||
CFIndex p;
|
||||
|
||||
for (i = 0; i < tl->nLines; i++) {
|
||||
double ltop, lbottom;
|
||||
|
||||
ltop = tl->lineMetrics[i].Y;
|
||||
lbottom = ltop + tl->lineMetrics[i].Height;
|
||||
// y will already >= ltop at this point since the past lbottom should == (or at least >=, see above) ltop
|
||||
if (y < lbottom)
|
||||
break;
|
||||
}
|
||||
if (i == tl->nLines)
|
||||
i--;
|
||||
*line = i;
|
||||
|
||||
ln = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, i);
|
||||
// note: according to the docs, we pass a y of 0 for this since the is the baseline of that line (the point is relative to the line)
|
||||
// note: x is relative to the line origin
|
||||
x -= tl->lineMetrics[*line].X;
|
||||
p = CTLineGetStringIndexForPosition(ln, CGPointMake(x, 0));
|
||||
if (p == kCFNotFound) {
|
||||
// TODO
|
||||
}
|
||||
*pos = tl->u16tou8[p];
|
||||
}
|
||||
|
||||
double uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line)
|
||||
{
|
||||
CTLineRef lr;
|
||||
CFRange range;
|
||||
|
||||
pos = tl->u8tou16[pos];
|
||||
if (line < 0 || line >= tl->nLines)
|
||||
return -1;
|
||||
lr = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, line);
|
||||
range = CTLineGetStringRange(lr);
|
||||
// note: >, not >=, because the position at end is valid!
|
||||
if (pos < range.location || pos > (range.location + range.length))
|
||||
return -1;
|
||||
// no point in checking the return; we already validated everything and 0 is a valid return for the first index :/
|
||||
// note: the result is relative to the line origin (TODO find documentation to support this)
|
||||
// TODO document that these functions do this
|
||||
return CTLineGetOffsetForStringIndex(lr, pos, NULL) + tl->lineMetrics[line].X;
|
||||
}
|
||||
|
||||
void caretDrawParams(uiDrawContext *c, double height, struct caretDrawParams *p)
|
||||
{
|
||||
NSColor *cc;
|
||||
CGFloat cr, cg, cb, ca;
|
||||
|
||||
// Interface Builder sets this as the insertion point color for a NSTextView by default
|
||||
cc = [NSColor controlTextColor];
|
||||
// the given color may not be an RGBA color, which will cause the -getRed:green:blue:alpha: call to throw an exception
|
||||
cc = [cc colorUsingColorSpace:[NSColorSpace sRGBColorSpace]];
|
||||
[cc getRed:&cr green:&cg blue:&cb alpha:&ca];
|
||||
p->r = cr;
|
||||
p->g = cg;
|
||||
p->b = cb;
|
||||
p->a = ca;
|
||||
// both cc and the controlTextColor it was made from will be autoreleased since they aren't new or init calls
|
||||
// TODO disabled carets have some blending applied...
|
||||
|
||||
// okay there doesn't seem to be any defined metrics for these, argh...
|
||||
p->width = 1;
|
||||
p->xoff = 0;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
darwin
|
||||
int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl)
|
||||
{
|
||||
return CFArrayGetCount([tl->forLines lines]);
|
||||
}
|
||||
|
||||
void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end)
|
||||
{
|
||||
CTLineRef lr;
|
||||
CFRange range;
|
||||
|
||||
lr = (CTLineRef) CFArrayGetValueAtIndex([tl->forLines lines], line);
|
||||
range = CTLineGetStringRange(lr);
|
||||
*start = tl->u16tou8[range.location];
|
||||
if (tl->empty)
|
||||
*end = *start;
|
||||
else
|
||||
*end = tl->u16tou8[range.location + range.length];
|
||||
}
|
||||
|
||||
|
||||
unix
|
||||
int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl)
|
||||
{
|
||||
return pango_layout_get_line_count(tl->layout);
|
||||
}
|
||||
|
||||
void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end)
|
||||
{
|
||||
PangoLayoutLine *pll;
|
||||
|
||||
pll = pango_layout_get_line_readonly(tl->layout, line);
|
||||
*start = pll->start_index;
|
||||
*end = pll->start_index + pll->length;
|
||||
// TODO unref pll?
|
||||
}
|
||||
|
||||
|
||||
windows
|
||||
int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl)
|
||||
{
|
||||
return 0;
|
||||
#if 0
|
||||
TODO
|
||||
return tl->nLines;
|
||||
#endif
|
||||
}
|
||||
|
||||
// DirectWrite doesn't provide a direct way to do this, so we have to do this manually
|
||||
// TODO does that comment still apply here or to the code at the top of this file?
|
||||
void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end)
|
||||
{
|
||||
#if 0
|
||||
TODO
|
||||
*start = tl->lineInfo[line].startPos;
|
||||
*start = tl->u16tou8[*start];
|
||||
*end = tl->lineInfo[line].endPos - tl->lineInfo[line].newlineCount;
|
||||
*end = tl->u16tou8[*end];
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
|
||||
typedef struct uiDrawTextLayoutLineMetrics uiDrawTextLayoutLineMetrics;
|
||||
|
||||
// Height will equal ParagraphSpacingBefore + LineHeightSpace + Ascent + Descent + Leading + LineSpacing + ParagraphSpacing.
|
||||
// The above values are listed in vertical order, from top to bottom.
|
||||
// Ascent + Descent + Leading will give you the typographic bounds
|
||||
// of the text. BaselineY is the boundary between Ascent and Descent.
|
||||
// X, Y, and BaselineY are all in the layout's coordinate system, so the
|
||||
// start point of the baseline will be at (X, BaselineY). All values are
|
||||
// nonnegative.
|
||||
struct uiDrawTextLayoutLineMetrics {
|
||||
// This describes the overall bounding box of the line.
|
||||
double X;
|
||||
double Y;
|
||||
double Width;
|
||||
double Height;
|
||||
|
||||
// This describes the typographic bounds of the line.
|
||||
double BaselineY;
|
||||
double Ascent;
|
||||
double Descent;
|
||||
double Leading;
|
||||
|
||||
// This describes any additional whitespace.
|
||||
// TODO come up with better names for these.
|
||||
double ParagraphSpacingBefore;
|
||||
double LineHeightSpace;
|
||||
double LineSpacing;
|
||||
double ParagraphSpacing;
|
||||
|
||||
// TODO trailing whitespace?
|
||||
};
|
||||
|
||||
// uiDrawTextLayoutNumLines() returns the number of lines in tl.
|
||||
// This number will always be greater than or equal to 1; a text
|
||||
// layout with no text only has one line.
|
||||
_UI_EXTERN int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl);
|
||||
|
||||
// uiDrawTextLayoutLineByteRange() returns the byte indices of the
|
||||
// text that falls into the given line of tl as [start, end).
|
||||
_UI_EXTERN void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end);
|
||||
|
||||
_UI_EXTERN void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m);
|
||||
|
||||
// TODO rewrite this documentation
|
||||
|
||||
// uiDrawTextLayoutHitTest() returns the byte offset and line closest
|
||||
// to the given point. The point is relative to the top-left of the layout.
|
||||
// If the point is outside the layout itself, the closest point is chosen;
|
||||
// this allows the function to be used for cursor positioning with the
|
||||
// mouse. Do keep the returned line in mind if used in this way; the
|
||||
// user might click on the end of a line, at which point the cursor
|
||||
// might be at the trailing edge of the last grapheme on the line
|
||||
// (subject to the operating system's APIs).
|
||||
_UI_EXTERN void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *pos, int *line);
|
||||
|
||||
// uiDrawTextLayoutByteLocationInLine() returns the point offset
|
||||
// into the given line that the given byte position stands. This is
|
||||
// relative to the line's X position (as returned by
|
||||
// uiDrawTextLayoutLineGetMetrics()), which in turn is relative to
|
||||
// the top-left of the layout. This function can be used for cursor
|
||||
// positioning: if start and end are the start and end of the line
|
||||
// (as returned by uiDrawTextLayoutLineByteRange()), you will get
|
||||
// the correct offset, even if pos is at the end of the line. If pos is not
|
||||
// in the range [start, end], a negative value will be returned,
|
||||
// indicating you need to move the cursor to another line.
|
||||
// TODO make sure this works right for right-aligned and center-aligned lines and justified lines and RTL text
|
||||
_UI_EXTERN double uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line);
|
||||
|
||||
_UI_EXTERN void uiDrawCaret(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout, size_t pos, int *line);
|
||||
// TODO allow blinking
|
||||
// TODO allow secondary carets
|
||||
@@ -0,0 +1,171 @@
|
||||
diff --git a/darwin/attrstr.m b/darwin/attrstr.m
|
||||
index fd45ec25..86039fad 100644
|
||||
--- a/darwin/attrstr.m
|
||||
+++ b/darwin/attrstr.m
|
||||
@@ -403,8 +403,13 @@ static CTParagraphStyleRef mkParagraphStyle(uiDrawTextLayoutParams *p)
|
||||
return ps;
|
||||
}
|
||||
|
||||
-CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, NSArray **backgroundBlocks)
|
||||
+static const UniChar emptyChars[] = { 0x20, 0x0 };
|
||||
+static const CFIndex emptyCharCount = 1;
|
||||
+
|
||||
+CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, BOOL *isEmpty, NSArray **backgroundBlocks)
|
||||
{
|
||||
+ const UniChar *chars;
|
||||
+ CFIndex charCount;
|
||||
CFStringRef cfstr;
|
||||
CFMutableDictionaryRef defaultAttrs;
|
||||
CTFontRef defaultCTFont;
|
||||
@@ -413,7 +418,15 @@ CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, NSArray
|
||||
CFMutableAttributedStringRef mas;
|
||||
struct foreachParams fep;
|
||||
|
||||
- cfstr = CFStringCreateWithCharacters(NULL, attrstrUTF16(p->String), attrstrUTF16Len(p->String));
|
||||
+ *isEmpty = NO;
|
||||
+ chars = attrstrUTF16(p->String);
|
||||
+ charCount = attrstrUTF16Len(p->String);
|
||||
+ if (charCount == 0) {
|
||||
+ *isEmpty = YES;
|
||||
+ chars = emptyChars;
|
||||
+ charCount = emptyCharCount;
|
||||
+ }
|
||||
+ cfstr = CFStringCreateWithCharacters(NULL, chars, charCount);
|
||||
if (cfstr == NULL) {
|
||||
// TODO
|
||||
}
|
||||
diff --git a/darwin/drawtext.m b/darwin/drawtext.m
|
||||
index 1fa5920e..65912383 100644
|
||||
--- a/darwin/drawtext.m
|
||||
+++ b/darwin/drawtext.m
|
||||
@@ -2,13 +2,16 @@
|
||||
#import "uipriv_darwin.h"
|
||||
#import "draw.h"
|
||||
|
||||
-// TODO on an empty string nLines == 0
|
||||
-// we must prevent this somehow
|
||||
-// TODO in general, every function could be more robust, but we cannot have a situation where there are zero lines
|
||||
+// TODO in general, every function could be more robust
|
||||
// TODO what happens to extents if only whitespace?
|
||||
+// TODO for empty layouts:
|
||||
+// - check if alignment correct compared to other OSs, or expected behavior at all
|
||||
+// - double-check if uiAttributedString allows zero-length attributes; I forget if I did
|
||||
|
||||
struct uiDrawTextLayout {
|
||||
CFAttributedStringRef attrstr;
|
||||
+ // this is needed because Core Text will give us an empty line array on a frame made with an empty string
|
||||
+ BOOL isEmpty;
|
||||
|
||||
// the width as passed into uiDrawTextLayout constructors
|
||||
double width;
|
||||
@@ -41,7 +44,7 @@
|
||||
};
|
||||
|
||||
// TODO document that lines may or may not overlap because ours do in the case of multiple combining characters
|
||||
-static uiDrawTextLayoutLineMetrics *computeLineMetrics(CTFrameRef frame, CGSize size)
|
||||
+static uiDrawTextLayoutLineMetrics *computeLineMetrics(CTFrameRef frame, CGSize size, BOOL isEmpty)
|
||||
{
|
||||
uiDrawTextLayoutLineMetrics *metrics;
|
||||
CFArrayRef lines;
|
||||
@@ -79,6 +82,8 @@
|
||||
metrics[i].X = origins[i].x;
|
||||
metrics[i].Y = origins[i].y - descent - leading;
|
||||
metrics[i].Width = bounds.size.width;
|
||||
+ if (isEmpty)
|
||||
+ metrics[i].Width = 0;
|
||||
metrics[i].Height = ascent + descent + leading;
|
||||
|
||||
metrics[i].BaselineY = origins[i].y;
|
||||
@@ -117,7 +122,7 @@
|
||||
CGRect rect;
|
||||
|
||||
tl = uiNew(uiDrawTextLayout);
|
||||
- tl->attrstr = attrstrToCoreFoundation(p, &(tl->backgroundBlocks));
|
||||
+ tl->attrstr = attrstrToCoreFoundation(p, &(tl->isEmpty), &(tl->backgroundBlocks));
|
||||
range.location = 0;
|
||||
range.length = CFAttributedStringGetLength(tl->attrstr);
|
||||
tl->width = p->Width;
|
||||
@@ -152,7 +157,7 @@
|
||||
|
||||
tl->lines = CTFrameGetLines(tl->frame);
|
||||
tl->nLines = CFArrayGetCount(tl->lines);
|
||||
- tl->lineMetrics = computeLineMetrics(tl->frame, tl->size);
|
||||
+ tl->lineMetrics = computeLineMetrics(tl->frame, tl->size, tl->isEmpty);
|
||||
|
||||
// and finally copy the UTF-8/UTF-16 conversion tables
|
||||
tl->u8tou16 = attrstrCopyUTF8ToUTF16(p->String, &(tl->nUTF8));
|
||||
@@ -180,6 +185,9 @@ void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)
|
||||
backgroundBlock b;
|
||||
CGAffineTransform textMatrix;
|
||||
|
||||
+ if (tl->isEmpty)
|
||||
+ return;
|
||||
+
|
||||
CGContextSaveGState(c->c);
|
||||
// save the text matrix because it's not part of the graphics state
|
||||
textMatrix = CGContextGetTextMatrix(c->c);
|
||||
@@ -216,6 +224,8 @@ void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)
|
||||
void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)
|
||||
{
|
||||
*width = tl->size.width;
|
||||
+ if (tl->isEmpty)
|
||||
+ *width = 0;
|
||||
*height = tl->size.height;
|
||||
}
|
||||
|
||||
@@ -233,6 +243,8 @@ void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start
|
||||
range = CTLineGetStringRange(lr);
|
||||
*start = tl->u16tou8[range.location];
|
||||
*end = tl->u16tou8[range.location + range.length];
|
||||
+ if (tl->isEmpty)
|
||||
+ *start = *end;
|
||||
}
|
||||
|
||||
void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m)
|
||||
@@ -262,14 +274,17 @@ void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *p
|
||||
*line = i;
|
||||
|
||||
ln = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, i);
|
||||
- // note: according to the docs, we pass a y of 0 for this since the is the baseline of that line (the point is relative to the line)
|
||||
- // note: x is relative to the line origin
|
||||
x -= tl->lineMetrics[*line].X;
|
||||
- p = CTLineGetStringIndexForPosition(ln, CGPointMake(x, 0));
|
||||
- if (p == kCFNotFound) {
|
||||
- // TODO
|
||||
+ *pos = 0;
|
||||
+ if (!tl->isEmpty) {
|
||||
+ // note: according to the docs, we pass a y of 0 for this since the is the baseline of that line (the point is relative to the line)
|
||||
+ // note: x is relative to the line origin
|
||||
+ p = CTLineGetStringIndexForPosition(ln, CGPointMake(x, 0));
|
||||
+ if (p == kCFNotFound) {
|
||||
+ // TODO
|
||||
+ }
|
||||
+ *pos = tl->u16tou8[p];
|
||||
}
|
||||
- *pos = tl->u16tou8[p];
|
||||
}
|
||||
|
||||
double uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line)
|
||||
@@ -282,6 +297,9 @@ void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *p
|
||||
return -1;
|
||||
lr = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, line);
|
||||
range = CTLineGetStringRange(lr);
|
||||
+ // TODO is the behavior of this part correct?
|
||||
+ if (tl->isEmpty)
|
||||
+ range.length = 0;
|
||||
// note: >, not >=, because the position at end is valid!
|
||||
if (pos < range.location || pos > (range.location + range.length))
|
||||
return -1;
|
||||
diff --git a/darwin/uipriv_darwin.h b/darwin/uipriv_darwin.h
|
||||
index 0303c32c..d44ee410 100644
|
||||
--- a/darwin/uipriv_darwin.h
|
||||
+++ b/darwin/uipriv_darwin.h
|
||||
@@ -151,7 +151,7 @@ extern void fontdescFromCTFontDescriptor(CTFontDescriptorRef ctdesc, uiDrawFontD
|
||||
extern void initUnderlineColors(void);
|
||||
extern void uninitUnderlineColors(void);
|
||||
typedef void (^backgroundBlock)(uiDrawContext *c, uiDrawTextLayout *layout, double x, double y);
|
||||
-extern CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, NSArray **backgroundBlocks);
|
||||
+extern CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, BOOL *isEmpty, NSArray **backgroundBlocks);
|
||||
|
||||
// aat.m
|
||||
typedef void (^aatBlock)(uint16_t type, uint16_t selector);
|
||||
@@ -0,0 +1,217 @@
|
||||
// 6 september 2015
|
||||
#include "uipriv_unix.h"
|
||||
#include "draw.h"
|
||||
|
||||
struct uiDrawFontFamilies {
|
||||
PangoFontFamily **f;
|
||||
int n;
|
||||
};
|
||||
|
||||
uiDrawFontFamilies *uiDrawListFontFamilies(void)
|
||||
{
|
||||
uiDrawFontFamilies *ff;
|
||||
PangoFontMap *map;
|
||||
|
||||
ff = uiNew(uiDrawFontFamilies);
|
||||
map = pango_cairo_font_map_get_default();
|
||||
pango_font_map_list_families(map, &(ff->f), &(ff->n));
|
||||
// do not free map; it's a shared resource
|
||||
return ff;
|
||||
}
|
||||
|
||||
int uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff)
|
||||
{
|
||||
return ff->n;
|
||||
}
|
||||
|
||||
char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, int n)
|
||||
{
|
||||
PangoFontFamily *f;
|
||||
|
||||
f = ff->f[n];
|
||||
return uiUnixStrdupText(pango_font_family_get_name(f));
|
||||
}
|
||||
|
||||
void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff)
|
||||
{
|
||||
g_free(ff->f);
|
||||
uiFree(ff);
|
||||
}
|
||||
|
||||
struct uiDrawTextFont {
|
||||
PangoFont *f;
|
||||
};
|
||||
|
||||
uiDrawTextFont *mkTextFont(PangoFont *f, gboolean ref)
|
||||
{
|
||||
uiDrawTextFont *font;
|
||||
|
||||
font = uiNew(uiDrawTextFont);
|
||||
font->f = f;
|
||||
if (ref)
|
||||
g_object_ref(font->f);
|
||||
return font;
|
||||
}
|
||||
|
||||
|
||||
|
||||
PangoFont *pangoDescToPangoFont(PangoFontDescription *pdesc)
|
||||
{
|
||||
PangoFont *f;
|
||||
PangoContext *context;
|
||||
|
||||
// in this case, the context is necessary for the metrics to be correct
|
||||
context = mkGenericPangoCairoContext();
|
||||
f = pango_font_map_load_font(pango_cairo_font_map_get_default(), context, pdesc);
|
||||
if (f == NULL) {
|
||||
// LONGTERM
|
||||
g_error("[libui] no match in pangoDescToPangoFont(); report to andlabs");
|
||||
}
|
||||
g_object_unref(context);
|
||||
return f;
|
||||
}
|
||||
|
||||
uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc)
|
||||
{
|
||||
PangoFont *f;
|
||||
PangoFontDescription *pdesc;
|
||||
|
||||
pdesc = pango_font_description_new();
|
||||
pango_font_description_set_family(pdesc,
|
||||
desc->Family);
|
||||
pango_font_description_set_size(pdesc,
|
||||
(gint) (desc->Size * PANGO_SCALE));
|
||||
pango_font_description_set_weight(pdesc,
|
||||
pangoWeights[desc->Weight]);
|
||||
pango_font_description_set_style(pdesc,
|
||||
pangoItalics[desc->Italic]);
|
||||
pango_font_description_set_stretch(pdesc,
|
||||
pangoStretches[desc->Stretch]);
|
||||
f = pangoDescToPangoFont(pdesc);
|
||||
pango_font_description_free(pdesc);
|
||||
return mkTextFont(f, FALSE); // we hold the initial reference; no need to ref
|
||||
}
|
||||
|
||||
void uiDrawFreeTextFont(uiDrawTextFont *font)
|
||||
{
|
||||
g_object_unref(font->f);
|
||||
uiFree(font);
|
||||
}
|
||||
|
||||
uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font)
|
||||
{
|
||||
return (uintptr_t) (font->f);
|
||||
}
|
||||
|
||||
void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc)
|
||||
{
|
||||
PangoFontDescription *pdesc;
|
||||
|
||||
// this creates a copy; we free it later
|
||||
pdesc = pango_font_describe(font->f);
|
||||
|
||||
// TODO
|
||||
|
||||
pango_font_description_free(pdesc);
|
||||
}
|
||||
|
||||
// See https://developer.gnome.org/pango/1.30/pango-Cairo-Rendering.html#pango-Cairo-Rendering.description
|
||||
// Note that we convert to double before dividing to make sure the floating-point stuff is right
|
||||
#define pangoToCairo(pango) (((double) (pango)) / PANGO_SCALE)
|
||||
#define cairoToPango(cairo) ((gint) ((cairo) * PANGO_SCALE))
|
||||
|
||||
void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics)
|
||||
{
|
||||
PangoFontMetrics *pm;
|
||||
|
||||
pm = pango_font_get_metrics(font->f, NULL);
|
||||
metrics->Ascent = pangoToCairo(pango_font_metrics_get_ascent(pm));
|
||||
metrics->Descent = pangoToCairo(pango_font_metrics_get_descent(pm));
|
||||
// Pango doesn't seem to expose this :( Use 0 and hope for the best.
|
||||
metrics->Leading = 0;
|
||||
metrics->UnderlinePos = pangoToCairo(pango_font_metrics_get_underline_position(pm));
|
||||
metrics->UnderlineThickness = pangoToCairo(pango_font_metrics_get_underline_thickness(pm));
|
||||
pango_font_metrics_unref(pm);
|
||||
}
|
||||
|
||||
// note: PangoCairoLayouts are tied to a given cairo_t, so we can't store one in this device-independent structure
|
||||
struct uiDrawTextLayout {
|
||||
char *s;
|
||||
ptrdiff_t *graphemes;
|
||||
PangoFont *defaultFont;
|
||||
double width;
|
||||
PangoAttrList *attrs;
|
||||
};
|
||||
|
||||
uiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultFont, double width)
|
||||
{
|
||||
uiDrawTextLayout *layout;
|
||||
PangoContext *context;
|
||||
|
||||
layout = uiNew(uiDrawTextLayout);
|
||||
layout->s = g_strdup(text);
|
||||
context = mkGenericPangoCairoContext();
|
||||
layout->graphemes = graphemes(layout->s, context);
|
||||
g_object_unref(context);
|
||||
layout->defaultFont = defaultFont->f;
|
||||
g_object_ref(layout->defaultFont); // retain a copy
|
||||
uiDrawTextLayoutSetWidth(layout, width);
|
||||
layout->attrs = pango_attr_list_new();
|
||||
return layout;
|
||||
}
|
||||
|
||||
void uiDrawFreeTextLayout(uiDrawTextLayout *layout)
|
||||
{
|
||||
pango_attr_list_unref(layout->attrs);
|
||||
g_object_unref(layout->defaultFont);
|
||||
uiFree(layout->graphemes);
|
||||
g_free(layout->s);
|
||||
uiFree(layout);
|
||||
}
|
||||
|
||||
void uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width)
|
||||
{
|
||||
layout->width = width;
|
||||
}
|
||||
|
||||
static void prepareLayout(uiDrawTextLayout *layout, PangoLayout *pl)
|
||||
{
|
||||
// again, this makes a copy
|
||||
desc = pango_font_describe(layout->defaultFont);
|
||||
|
||||
pango_layout_set_attributes(pl, layout->attrs);
|
||||
}
|
||||
|
||||
void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout)
|
||||
{
|
||||
PangoLayout *pl;
|
||||
|
||||
pl = pango_cairo_create_layout(c->cr);
|
||||
}
|
||||
|
||||
static void addAttr(uiDrawTextLayout *layout, PangoAttribute *attr, int startChar, int endChar)
|
||||
{
|
||||
attr->start_index = layout->graphemes[startChar];
|
||||
attr->end_index = layout->graphemes[endChar];
|
||||
pango_attr_list_insert(layout->attrs, attr);
|
||||
// pango_attr_list_insert() takes attr; we don't free it
|
||||
}
|
||||
|
||||
void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, int startChar, int endChar, double r, double g, double b, double a)
|
||||
{
|
||||
PangoAttribute *attr;
|
||||
guint16 rr, gg, bb, aa;
|
||||
|
||||
rr = (guint16) (r * 65535);
|
||||
gg = (guint16) (g * 65535);
|
||||
bb = (guint16) (b * 65535);
|
||||
aa = (guint16) (a * 65535);
|
||||
|
||||
attr = pango_attr_foreground_new(rr, gg, bb);
|
||||
addAttr(layout, attr, startChar, endChar);
|
||||
|
||||
// TODO what if aa == 0?
|
||||
attr = FUTURE_pango_attr_foreground_alpha_new(aa);
|
||||
if (attr != NULL)
|
||||
addAttr(layout, attr, startChar, endChar);
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
// 17 january 2017
|
||||
#include "uipriv_unix.h"
|
||||
#include "draw.h"
|
||||
|
||||
// TODO
|
||||
// - if the RTL override is at the beginning of a line, the preceding space is included?
|
||||
// - nLines == 0: mostly works, except the width is wrong if the paragraph alignment is center or right...
|
||||
// - TODO check whitespace and line bounds
|
||||
|
||||
struct uiDrawTextLayout {
|
||||
PangoLayout *layout;
|
||||
GPtrArray *backgroundClosures;
|
||||
uiDrawTextLayoutLineMetrics *lineMetrics;
|
||||
// TODO change everything to use this
|
||||
int nLines;
|
||||
};
|
||||
|
||||
// TODO neither these nor the overall extents seem to include trailing whitespace... we need to figure that out too
|
||||
static void computeLineMetrics(uiDrawTextLayout *tl)
|
||||
{
|
||||
PangoLayoutIter *iter;
|
||||
PangoLayoutLine *pll;
|
||||
PangoRectangle lineStartPos, lineExtents;
|
||||
int i, n;
|
||||
uiDrawTextLayoutLineMetrics *m;
|
||||
|
||||
n = tl->nLines; // TODO remove this variable
|
||||
tl->lineMetrics = (uiDrawTextLayoutLineMetrics *) uiAlloc(n * sizeof (uiDrawTextLayoutLineMetrics), "uiDrawTextLayoutLineMetrics[] (text layout)");
|
||||
iter = pango_layout_get_iter(tl->layout);
|
||||
|
||||
m = tl->lineMetrics;
|
||||
for (i = 0; i < n; i++) {
|
||||
int baselineY;
|
||||
|
||||
// TODO we use this instead of _get_yrange() because of the block of text in that function's description about how line spacing is distributed in Pango; we have to worry about this when we start adding line spacing...
|
||||
baselineY = pango_layout_iter_get_baseline(iter);
|
||||
pll = pango_layout_iter_get_line_readonly(iter);
|
||||
pango_layout_index_to_pos(tl->layout, pll->start_index, &lineStartPos);
|
||||
pango_layout_line_get_extents(pll, NULL, &lineExtents);
|
||||
// TODO unref pll?
|
||||
|
||||
// TODO is this correct for RTL glyphs?
|
||||
m->X = pangoToCairo(lineStartPos.x);
|
||||
// TODO fix the whole combined not being updated shenanigans in the static build (here because ugh)
|
||||
m->Y = pangoToCairo(baselineY - PANGO_ASCENT(lineExtents));
|
||||
// TODO this does not include the last space if any
|
||||
m->Width = pangoToCairo(lineExtents.width);
|
||||
m->Height = pangoToCairo(lineExtents.height);
|
||||
|
||||
m->BaselineY = pangoToCairo(baselineY);
|
||||
m->Ascent = pangoToCairo(PANGO_ASCENT(lineExtents));
|
||||
m->Descent = pangoToCairo(PANGO_DESCENT(lineExtents));
|
||||
m->Leading = 0; // TODO
|
||||
|
||||
m->ParagraphSpacingBefore = 0; // TODO
|
||||
m->LineHeightSpace = 0; // TODO
|
||||
m->LineSpacing = 0; // TODO
|
||||
m->ParagraphSpacing = 0; // TODO
|
||||
|
||||
// don't worry about the return value; we're not using this after the last line
|
||||
pango_layout_iter_next_line(iter);
|
||||
m++;
|
||||
}
|
||||
|
||||
pango_layout_iter_free(iter);
|
||||
}
|
||||
|
||||
uiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p)
|
||||
{
|
||||
uiDrawTextLayout *tl;
|
||||
PangoContext *context;
|
||||
PangoFontDescription *desc;
|
||||
PangoAttrList *attrs;
|
||||
int pangoWidth;
|
||||
|
||||
tl = uiNew(uiDrawTextLayout);
|
||||
|
||||
// in this case, the context is necessary to create the layout
|
||||
// the layout takes a ref on the context so we can unref it afterward
|
||||
context = mkGenericPangoCairoContext();
|
||||
tl->layout = pango_layout_new(context);
|
||||
g_object_unref(context);
|
||||
|
||||
// this is safe; pango_layout_set_text() copies the string
|
||||
pango_layout_set_text(tl->layout, uiAttributedStringString(p->String), -1);
|
||||
|
||||
desc = pango_font_description_new();
|
||||
pango_font_description_set_family(desc, p->DefaultFont->Family);
|
||||
pango_font_description_set_style(desc, pangoItalics[p->DefaultFont->Italic]);
|
||||
// for the most part, pango weights correlate to ours
|
||||
// the differences:
|
||||
// - Book — libui: 350, Pango: 380
|
||||
// - Ultra Heavy — libui: 950, Pango: 1000
|
||||
// TODO figure out what to do about this misalignment
|
||||
pango_font_description_set_weight(desc, p->DefaultFont->Weight);
|
||||
pango_font_description_set_stretch(desc, pangoStretches[p->DefaultFont->Stretch]);
|
||||
// see https://developer.gnome.org/pango/1.30/pango-Fonts.html#pango-font-description-set-size and https://developer.gnome.org/pango/1.30/pango-Glyph-Storage.html#pango-units-from-double
|
||||
pango_font_description_set_size(desc, pango_units_from_double(p->DefaultFont->Size));
|
||||
pango_layout_set_font_description(tl->layout, desc);
|
||||
// this is safe; the description is copied
|
||||
pango_font_description_free(desc);
|
||||
|
||||
pangoWidth = cairoToPango(p->Width);
|
||||
if (p->Width < 0)
|
||||
pangoWidth = -1;
|
||||
pango_layout_set_width(tl->layout, pangoWidth);
|
||||
|
||||
pango_layout_set_alignment(tl->layout, pangoAligns[p->Align]);
|
||||
|
||||
attrs = attrstrToPangoAttrList(p, &(tl->backgroundClosures));
|
||||
pango_layout_set_attributes(tl->layout, attrs);
|
||||
pango_attr_list_unref(attrs);
|
||||
|
||||
tl->nLines = pango_layout_get_line_count(tl->layout);
|
||||
computeLineMetrics(tl);
|
||||
|
||||
return tl;
|
||||
}
|
||||
|
||||
void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m)
|
||||
{
|
||||
*m = tl->lineMetrics[line];
|
||||
}
|
||||
|
||||
// TODO
|
||||
#if 0
|
||||
{
|
||||
PangoLayoutLine *pll;
|
||||
|
||||
pll = pango_layout_get_line_readonly(tl->layout, line);
|
||||
// TODO unref?
|
||||
}
|
||||
#endif
|
||||
|
||||
// note: Pango will not let us place the cursor at the end of a line the same way other OSs do; see https://git.gnome.org/browse/pango/tree/pango/pango-layout.c?id=f4cbd27f4e5bf8490ea411190d41813e14f12165#n4204
|
||||
// ideally there'd be a way to say "I don't need this hack; I'm well behaved" but GTK+ 2 and 3 AND Qt 4 and 5 all behave like this, with the behavior seeming to date back to TkTextView, so...
|
||||
void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *pos, int *line)
|
||||
{
|
||||
int p, trailing;
|
||||
int i;
|
||||
|
||||
// this is layout-global, so it takes line origins into account
|
||||
pango_layout_xy_to_index(tl->layout,
|
||||
cairoToPango(x), cairoToPango(y),
|
||||
&p, &trailing);
|
||||
// on a trailing hit, align to the nearest cluster
|
||||
// fortunately Pango provides that info directly
|
||||
if (trailing != 0)
|
||||
p += trailing;
|
||||
*pos = p;
|
||||
|
||||
for (i = 0; i < tl->nLines; i++) {
|
||||
double ltop, lbottom;
|
||||
|
||||
ltop = tl->lineMetrics[i].Y;
|
||||
lbottom = ltop + tl->lineMetrics[i].Height;
|
||||
// y will already >= ltop at this point since the past lbottom should == ltop
|
||||
if (y < lbottom)
|
||||
break;
|
||||
}
|
||||
if (i == pango_layout_get_line_count(tl->layout))
|
||||
i--;
|
||||
*line = i;
|
||||
}
|
||||
|
||||
double uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line)
|
||||
{
|
||||
PangoLayoutLine *pll;
|
||||
gboolean trailing;
|
||||
int pangox;
|
||||
|
||||
if (line < 0 || line >= tl->nLines)
|
||||
return -1;
|
||||
pll = pango_layout_get_line_readonly(tl->layout, line);
|
||||
// note: >, not >=, because the position at end is valid!
|
||||
if (pos < pll->start_index || pos > (pll->start_index + pll->length))
|
||||
return -1;
|
||||
// this behavior seems correct
|
||||
// there's also PadWrite's TextEditor::GetCaretRect() but that requires state...
|
||||
// TODO where does this fail?
|
||||
// TODO optimize everything to avoid calling strlen()
|
||||
trailing = 0;
|
||||
if (pos != 0 && pos != strlen(pango_layout_get_text(tl->layout)) && pos == (pll->start_index + pll->length)) {
|
||||
pos--;
|
||||
trailing = 1;
|
||||
}
|
||||
pango_layout_line_index_to_x(pll, pos, trailing, &pangox);
|
||||
// TODO unref pll?
|
||||
// this is relative to the beginning of the line
|
||||
return pangoToCairo(pangox) + tl->lineMetrics[line].X;
|
||||
}
|
||||
|
||||
// note: we can't use gtk_render_insertion_cursor() because that doesn't take information about what line to render on
|
||||
// we'll just recreate what it does
|
||||
void caretDrawParams(uiDrawContext *c, double height, struct caretDrawParams *p)
|
||||
{
|
||||
GdkColor *color;
|
||||
GdkRGBA rgba;
|
||||
gfloat aspectRatio;
|
||||
gint width, xoff;
|
||||
|
||||
gtk_style_context_get_style(c->style,
|
||||
"cursor-color", &color,
|
||||
"cursor-aspect-ratio", &aspectRatio,
|
||||
NULL);
|
||||
if (color != NULL) {
|
||||
p->r = ((double) (color->red)) / 65535.0;
|
||||
p->g = ((double) (color->green)) / 65535.0;
|
||||
p->b = ((double) (color->blue)) / 65535.0;
|
||||
p->a = 1.0;
|
||||
gdk_color_free(color);
|
||||
} else {
|
||||
gtk_style_context_get_color(c->style, GTK_STATE_FLAG_NORMAL, &rgba);
|
||||
p->r = rgba.red;
|
||||
p->g = rgba.green;
|
||||
p->b = rgba.blue;
|
||||
p->a = rgba.alpha;
|
||||
}
|
||||
|
||||
// GTK+ itself uses integer arithmetic here; let's do the same
|
||||
width = height * aspectRatio + 1;
|
||||
// TODO this is for LTR
|
||||
xoff = width / 2;
|
||||
|
||||
p->xoff = xoff;
|
||||
p->width = width;
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
// 22 december 2015
|
||||
#include "uipriv_windows.hpp"
|
||||
#include "draw.hpp"
|
||||
// TODO really migrate
|
||||
|
||||
// notes:
|
||||
// only available in windows 8 and newer:
|
||||
// - character spacing
|
||||
// - kerning control
|
||||
// - justficiation (how could I possibly be making this up?!)
|
||||
// - vertical text (SERIOUSLY?! WHAT THE ACTUAL FUCK, MICROSOFT?!?!?!? DID YOU NOT THINK ABOUT THIS THE FIRST TIME, TRYING TO IMPROVE THE INTERNATIONALIZATION OF WINDOWS 7?!?!?! bonus: some parts of MSDN even say 8.1 and up only!)
|
||||
|
||||
struct uiDrawFontFamilies {
|
||||
fontCollection *fc;
|
||||
};
|
||||
|
||||
uiDrawFontFamilies *uiDrawListFontFamilies(void)
|
||||
{
|
||||
struct uiDrawFontFamilies *ff;
|
||||
|
||||
ff = uiNew(struct uiDrawFontFamilies);
|
||||
ff->fc = loadFontCollection();
|
||||
return ff;
|
||||
}
|
||||
|
||||
int uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff)
|
||||
{
|
||||
return ff->fc->fonts->GetFontFamilyCount();
|
||||
}
|
||||
|
||||
char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, int n)
|
||||
{
|
||||
IDWriteFontFamily *family;
|
||||
WCHAR *wname;
|
||||
char *name;
|
||||
HRESULT hr;
|
||||
|
||||
hr = ff->fc->fonts->GetFontFamily(n, &family);
|
||||
if (hr != S_OK)
|
||||
logHRESULT(L"error getting font out of collection", hr);
|
||||
wname = fontCollectionFamilyName(ff->fc, family);
|
||||
name = toUTF8(wname);
|
||||
uiFree(wname);
|
||||
family->Release();
|
||||
return name;
|
||||
}
|
||||
|
||||
void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff)
|
||||
{
|
||||
fontCollectionFree(ff->fc);
|
||||
uiFree(ff);
|
||||
}
|
||||
|
||||
struct uiDrawTextFont {
|
||||
IDWriteFont *f;
|
||||
WCHAR *family; // save for convenience in uiDrawNewTextLayout()
|
||||
double size;
|
||||
};
|
||||
|
||||
uiDrawTextFont *mkTextFont(IDWriteFont *df, BOOL addRef, WCHAR *family, BOOL copyFamily, double size)
|
||||
{
|
||||
uiDrawTextFont *font;
|
||||
WCHAR *copy;
|
||||
HRESULT hr;
|
||||
|
||||
font = uiNew(uiDrawTextFont);
|
||||
font->f = df;
|
||||
if (addRef)
|
||||
font->f->AddRef();
|
||||
if (copyFamily) {
|
||||
copy = (WCHAR *) uiAlloc((wcslen(family) + 1) * sizeof (WCHAR), "WCHAR[]");
|
||||
wcscpy(copy, family);
|
||||
font->family = copy;
|
||||
} else
|
||||
font->family = family;
|
||||
font->size = size;
|
||||
return font;
|
||||
}
|
||||
|
||||
// TODO MinGW-w64 is missing this one
|
||||
#define DWRITE_FONT_WEIGHT_SEMI_LIGHT (DWRITE_FONT_WEIGHT(350))
|
||||
|
||||
uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc)
|
||||
{
|
||||
uiDrawTextFont *font;
|
||||
IDWriteFontCollection *collection;
|
||||
UINT32 index;
|
||||
BOOL exists;
|
||||
struct dwriteAttr attr;
|
||||
IDWriteFontFamily *family;
|
||||
WCHAR *wfamily;
|
||||
IDWriteFont *match;
|
||||
HRESULT hr;
|
||||
|
||||
// always get the latest available font information
|
||||
hr = dwfactory->GetSystemFontCollection(&collection, TRUE);
|
||||
if (hr != S_OK)
|
||||
logHRESULT(L"error getting system font collection", hr);
|
||||
|
||||
wfamily = toUTF16(desc->Family);
|
||||
hr = collection->FindFamilyName(wfamily, &index, &exists);
|
||||
if (hr != S_OK)
|
||||
logHRESULT(L"error finding font family", hr);
|
||||
if (!exists)
|
||||
implbug("LONGTERM family not found in uiDrawLoadClosestFont()", hr);
|
||||
hr = collection->GetFontFamily(index, &family);
|
||||
if (hr != S_OK)
|
||||
logHRESULT(L"error loading font family", hr);
|
||||
|
||||
attr.weight = desc->Weight;
|
||||
attr.italic = desc->Italic;
|
||||
attr.stretch = desc->Stretch;
|
||||
attrToDWriteAttr(&attr);
|
||||
hr = family->GetFirstMatchingFont(
|
||||
attr.dweight,
|
||||
attr.dstretch,
|
||||
attr.ditalic,
|
||||
&match);
|
||||
if (hr != S_OK)
|
||||
logHRESULT(L"error loading font", hr);
|
||||
|
||||
font = mkTextFont(match,
|
||||
FALSE, // we own the initial reference; no need to add another one
|
||||
wfamily, FALSE, // will be freed with font
|
||||
desc->Size);
|
||||
|
||||
family->Release();
|
||||
collection->Release();
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
void uiDrawFreeTextFont(uiDrawTextFont *font)
|
||||
{
|
||||
font->f->Release();
|
||||
uiFree(font->family);
|
||||
uiFree(font);
|
||||
}
|
||||
|
||||
uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font)
|
||||
{
|
||||
return (uintptr_t) (font->f);
|
||||
}
|
||||
|
||||
void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc)
|
||||
{
|
||||
// TODO
|
||||
|
||||
desc->Size = font->size;
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
// text sizes are 1/72 of an inch
|
||||
// points in Direct2D are 1/96 of an inch (https://msdn.microsoft.com/en-us/library/windows/desktop/ff684173%28v=vs.85%29.aspx, https://msdn.microsoft.com/en-us/library/windows/desktop/hh447022%28v=vs.85%29.aspx)
|
||||
// As for the actual conversion from design units, see:
|
||||
// - http://cboard.cprogramming.com/windows-programming/136733-directwrite-font-height-issues.html
|
||||
// - https://sourceforge.net/p/vstgui/mailman/message/32483143/
|
||||
// - http://xboxforums.create.msdn.com/forums/t/109445.aspx
|
||||
// - https://msdn.microsoft.com/en-us/library/dd183564%28v=vs.85%29.aspx
|
||||
// - http://www.fontbureau.com/blog/the-em/
|
||||
// TODO make points here about how DIPs in DirectWrite == DIPs in Direct2D; if not, figure out what they really are? for the width and layout functions later
|
||||
static double scaleUnits(double what, double designUnitsPerEm, double size)
|
||||
{
|
||||
return (what / designUnitsPerEm) * (size * (96.0 / 72.0));
|
||||
}
|
||||
|
||||
void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics)
|
||||
{
|
||||
DWRITE_FONT_METRICS dm;
|
||||
|
||||
font->f->GetMetrics(&dm);
|
||||
metrics->Ascent = scaleUnits(dm.ascent, dm.designUnitsPerEm, font->size);
|
||||
metrics->Descent = scaleUnits(dm.descent, dm.designUnitsPerEm, font->size);
|
||||
// TODO what happens if dm.xxx is negative?
|
||||
// TODO remember what this was for
|
||||
metrics->Leading = scaleUnits(dm.lineGap, dm.designUnitsPerEm, font->size);
|
||||
metrics->UnderlinePos = scaleUnits(dm.underlinePosition, dm.designUnitsPerEm, font->size);
|
||||
metrics->UnderlineThickness = scaleUnits(dm.underlineThickness, dm.designUnitsPerEm, font->size);
|
||||
}
|
||||
|
||||
// some attributes, such as foreground color, can't be applied until after we establish a Direct2D context :/ so we have to prepare all attributes in advance
|
||||
// also since there's no way to clear the attributes from a layout en masse (apart from overwriting them all), we'll play it safe by creating a new layout each time
|
||||
enum layoutAttrType {
|
||||
layoutAttrColor,
|
||||
};
|
||||
|
||||
struct layoutAttr {
|
||||
enum layoutAttrType type;
|
||||
int start;
|
||||
int end;
|
||||
double components[4];
|
||||
};
|
||||
|
||||
struct uiDrawTextLayout {
|
||||
WCHAR *text;
|
||||
size_t textlen;
|
||||
size_t *graphemes;
|
||||
double width;
|
||||
IDWriteTextFormat *format;
|
||||
std::vector<struct layoutAttr> *attrs;
|
||||
};
|
||||
|
||||
uiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultFont, double width)
|
||||
{
|
||||
uiDrawTextLayout *layout;
|
||||
HRESULT hr;
|
||||
|
||||
layout = uiNew(uiDrawTextLayout);
|
||||
|
||||
hr = dwfactory->CreateTextFormat(defaultFont->family,
|
||||
NULL,
|
||||
defaultFont->f->GetWeight(),
|
||||
defaultFont->f->GetStyle(),
|
||||
defaultFont->f->GetStretch(),
|
||||
|
||||
if (hr != S_OK)
|
||||
logHRESULT(L"error creating IDWriteTextFormat", hr);
|
||||
|
||||
layout->text = toUTF16(text);
|
||||
layout->textlen = wcslen(layout->text);
|
||||
layout->graphemes = graphemes(layout->text);
|
||||
|
||||
uiDrawTextLayoutSetWidth(layout, width);
|
||||
|
||||
layout->attrs = new std::vector<struct layoutAttr>;
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
void uiDrawFreeTextLayout(uiDrawTextLayout *layout)
|
||||
{
|
||||
delete layout->attrs;
|
||||
layout->format->Release();
|
||||
uiFree(layout->graphemes);
|
||||
uiFree(layout->text);
|
||||
uiFree(layout);
|
||||
}
|
||||
|
||||
|
||||
IDWriteTextLayout *prepareLayout(uiDrawTextLayout *layout, ID2D1RenderTarget *rt)
|
||||
{
|
||||
IDWriteTextLayout *dl;
|
||||
DWRITE_TEXT_RANGE range;
|
||||
IUnknown *unkBrush;
|
||||
HRESULT hr;
|
||||
|
||||
for (const struct layoutAttr &attr : *(layout->attrs)) {
|
||||
range.startPosition = layout->graphemes[attr.start];
|
||||
range.length = layout->graphemes[attr.end] - layout->graphemes[attr.start];
|
||||
switch (attr.type) {
|
||||
case layoutAttrColor:
|
||||
if (rt == NULL) // determining extents, not drawing
|
||||
break;
|
||||
unkBrush = mkSolidBrush(rt,
|
||||
attr.components[0],
|
||||
attr.components[1],
|
||||
attr.components[2],
|
||||
attr.components[3]);
|
||||
hr = dl->SetDrawingEffect(unkBrush, range);
|
||||
unkBrush->Release(); // associated with dl
|
||||
break;
|
||||
default:
|
||||
hr = E_FAIL;
|
||||
logHRESULT(L"invalid text attribute type", hr);
|
||||
}
|
||||
if (hr != S_OK)
|
||||
logHRESULT(L"error adding attribute to text layout", hr);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return dl;
|
||||
}
|
||||
|
||||
|
||||
void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout)
|
||||
{
|
||||
IDWriteTextLayout *dl;
|
||||
D2D1_POINT_2F pt;
|
||||
ID2D1Brush *black;
|
||||
HRESULT hr;
|
||||
|
||||
// TODO document that fully opaque black is the default text color; figure out whether this is upheld in various scenarios on other platforms
|
||||
black = mkSolidBrush(c->rt, 0.0, 0.0, 0.0, 1.0);
|
||||
|
||||
dl = prepareLayout(layout, c->rt);
|
||||
pt.x = x;
|
||||
pt.y = y;
|
||||
// TODO D2D1_DRAW_TEXT_OPTIONS_NO_SNAP?
|
||||
// TODO D2D1_DRAW_TEXT_OPTIONS_CLIP?
|
||||
// TODO when setting 8.1 as minimum, D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT?
|
||||
c->rt->DrawTextLayout(pt, dl, black, D2D1_DRAW_TEXT_OPTIONS_NONE);
|
||||
dl->Release();
|
||||
|
||||
black->Release();
|
||||
}
|
||||
|
||||
void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, int startChar, int endChar, double r, double g, double b, double a)
|
||||
{
|
||||
struct layoutAttr attr;
|
||||
|
||||
attr.type = layoutAttrColor;
|
||||
attr.start = startChar;
|
||||
attr.end = endChar;
|
||||
attr.components[0] = r;
|
||||
attr.components[1] = g;
|
||||
attr.components[2] = b;
|
||||
attr.components[3] = a;
|
||||
layout->attrs->push_back(attr);
|
||||
}
|
||||
@@ -0,0 +1,667 @@
|
||||
// 17 january 2017
|
||||
#include "uipriv_windows.hpp"
|
||||
#include "draw.hpp"
|
||||
|
||||
// TODO
|
||||
// - consider the warnings about antialiasing in the PadWrite sample
|
||||
// - if that's not a problem, do we have overlapping rects in the hittest sample? I can't tell...
|
||||
// - empty string: nLines == 1 and all checks out except extents has x == 0 when not left aligned
|
||||
// - paragraph alignment is subject to RTL mirroring; see if it is on other platforms
|
||||
// - add overhang info to metrics?
|
||||
|
||||
// TODO verify our renderer is correct, especially with regards to snapping
|
||||
|
||||
struct uiDrawTextLayout {
|
||||
IDWriteTextFormat *format;
|
||||
IDWriteTextLayout *layout;
|
||||
std::vector<backgroundFunc> *backgroundFuncs;
|
||||
UINT32 nLines;
|
||||
struct lineInfo *lineInfo;
|
||||
// for converting DirectWrite indices from/to byte offsets
|
||||
size_t *u8tou16;
|
||||
size_t nUTF8;
|
||||
size_t *u16tou8;
|
||||
size_t nUTF16;
|
||||
};
|
||||
|
||||
// TODO copy notes about DirectWrite DIPs being equal to Direct2D DIPs here
|
||||
|
||||
// typographic points are 1/72 inch; this parameter is 1/96 inch
|
||||
// fortunately Microsoft does this too, in https://msdn.microsoft.com/en-us/library/windows/desktop/dd371554%28v=vs.85%29.aspx
|
||||
#define pointSizeToDWriteSize(size) (size * (96.0 / 72.0))
|
||||
|
||||
struct lineInfo {
|
||||
size_t startPos; // in UTF-16 points
|
||||
size_t endPos;
|
||||
size_t newlineCount;
|
||||
double x;
|
||||
double y;
|
||||
double width;
|
||||
double height;
|
||||
double baseline;
|
||||
};
|
||||
|
||||
// this function is deeply indebted to the PadWrite sample: https://github.com/Microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/multimedia/DirectWrite/PadWrite/TextEditor.cpp
|
||||
static void computeLineInfo(uiDrawTextLayout *tl)
|
||||
{
|
||||
DWRITE_LINE_METRICS *dlm;
|
||||
size_t nextStart;
|
||||
UINT32 i, j;
|
||||
DWRITE_HIT_TEST_METRICS *htm;
|
||||
UINT32 nFragments, unused;
|
||||
HRESULT hr;
|
||||
|
||||
// TODO make sure this is legal; if not, switch to GetMetrics() and use its line count field instead
|
||||
hr = tl->layout->GetLineMetrics(NULL, 0, &(tl->nLines));
|
||||
// ugh, HRESULT_TO_WIN32() is an inline function and is not constexpr so we can't use switch here
|
||||
if (hr == S_OK) {
|
||||
// TODO what do we do here
|
||||
} else if (hr != E_NOT_SUFFICIENT_BUFFER)
|
||||
logHRESULT(L"error getting number of lines in IDWriteTextLayout", hr);
|
||||
tl->lineInfo = (struct lineInfo *) uiAlloc(tl->nLines * sizeof (struct lineInfo), "struct lineInfo[] (text layout)");
|
||||
|
||||
dlm = new DWRITE_LINE_METRICS[tl->nLines];
|
||||
// we can't pass NULL here; it outright crashes if we do
|
||||
// TODO verify the numbers haven't changed
|
||||
hr = tl->layout->GetLineMetrics(dlm, tl->nLines, &unused);
|
||||
if (hr != S_OK)
|
||||
logHRESULT(L"error getting IDWriteTextLayout line metrics", hr);
|
||||
|
||||
// assume the first line starts at position 0 and the string flow is incremental
|
||||
nextStart = 0;
|
||||
for (i = 0; i < tl->nLines; i++) {
|
||||
tl->lineInfo[i].startPos = nextStart;
|
||||
tl->lineInfo[i].endPos = nextStart + dlm[i].length;
|
||||
tl->lineInfo[i].newlineCount = dlm[i].newlineLength;
|
||||
nextStart = tl->lineInfo[i].endPos;
|
||||
|
||||
// a line can have multiple fragments; for example, if there's a bidirectional override in the middle of a line
|
||||
hr = tl->layout->HitTestTextRange(tl->lineInfo[i].startPos, (tl->lineInfo[i].endPos - tl->lineInfo[i].newlineCount) - tl->lineInfo[i].startPos,
|
||||
0, 0,
|
||||
NULL, 0, &nFragments);
|
||||
if (hr != S_OK && hr != E_NOT_SUFFICIENT_BUFFER)
|
||||
logHRESULT(L"error getting IDWriteTextLayout line fragment count", hr);
|
||||
htm = new DWRITE_HIT_TEST_METRICS[nFragments];
|
||||
// TODO verify unused == nFragments?
|
||||
hr = tl->layout->HitTestTextRange(tl->lineInfo[i].startPos, (tl->lineInfo[i].endPos - tl->lineInfo[i].newlineCount) - tl->lineInfo[i].startPos,
|
||||
0, 0,
|
||||
htm, nFragments, &unused);
|
||||
// TODO can this return E_NOT_SUFFICIENT_BUFFER again?
|
||||
if (hr != S_OK)
|
||||
logHRESULT(L"error getting IDWriteTextLayout line fragment metrics", hr);
|
||||
// TODO verify htm.textPosition and htm.length against dtm[i]/tl->lineInfo[i]?
|
||||
tl->lineInfo[i].x = htm[0].left;
|
||||
tl->lineInfo[i].y = htm[0].top;
|
||||
// TODO does this not include trailing whitespace? I forget
|
||||
tl->lineInfo[i].width = htm[0].width;
|
||||
tl->lineInfo[i].height = htm[0].height;
|
||||
for (j = 1; j < nFragments; j++) {
|
||||
// this is correct even if the leftmost fragment on the line is RTL
|
||||
if (tl->lineInfo[i].x > htm[j].left)
|
||||
tl->lineInfo[i].x = htm[j].left;
|
||||
tl->lineInfo[i].width += htm[j].width;
|
||||
// TODO verify y and height haven't changed?
|
||||
}
|
||||
// TODO verify dlm[i].height == htm.height?
|
||||
delete[] htm;
|
||||
|
||||
// TODO on Windows 8.1 and/or 10 we can use DWRITE_LINE_METRICS1 to get specific info about the ascent and descent; do we have an alternative?
|
||||
// TODO and even on those platforms can we somehow split tyographic leading from spacing?
|
||||
// TODO and on that note, can we have both line spacing proportionally above and uniformly below?
|
||||
tl->lineInfo[i].baseline = dlm[i].baseline;
|
||||
}
|
||||
|
||||
delete[] dlm;
|
||||
}
|
||||
|
||||
// TODO should be const but then I can't operator[] on it; the real solution is to find a way to do designated array initializers in C++11 but I do not know enough C++ voodoo to make it work (it is possible but no one else has actually done it before)
|
||||
static std::map<uiDrawTextAlign, DWRITE_TEXT_ALIGNMENT> dwriteAligns = {
|
||||
{ uiDrawTextAlignLeft, DWRITE_TEXT_ALIGNMENT_LEADING },
|
||||
{ uiDrawTextAlignCenter, DWRITE_TEXT_ALIGNMENT_CENTER },
|
||||
{ uiDrawTextAlignRight, DWRITE_TEXT_ALIGNMENT_TRAILING },
|
||||
};
|
||||
|
||||
uiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p)
|
||||
{
|
||||
uiDrawTextLayout *tl;
|
||||
WCHAR *wDefaultFamily;
|
||||
DWRITE_WORD_WRAPPING wrap;
|
||||
FLOAT maxWidth;
|
||||
HRESULT hr;
|
||||
|
||||
tl = uiNew(uiDrawTextLayout);
|
||||
|
||||
wDefaultFamily = toUTF16(p->DefaultFont->Family);
|
||||
hr = dwfactory->CreateTextFormat(
|
||||
wDefaultFamily, NULL,
|
||||
uiprivWeightToDWriteWeight(p->DefaultFont->Weight),
|
||||
uiprivItalicToDWriteStyle(p->DefaultFont->Italic),
|
||||
uiprivStretchToDWriteStretch(p->DefaultFont->Stretch),
|
||||
pointSizeToDWriteSize(p->DefaultFont->Size),
|
||||
// see http://stackoverflow.com/questions/28397971/idwritefactorycreatetextformat-failing and https://msdn.microsoft.com/en-us/library/windows/desktop/dd368203.aspx
|
||||
// TODO use the current locale?
|
||||
L"",
|
||||
&(tl->format));
|
||||
if (hr != S_OK)
|
||||
logHRESULT(L"error creating IDWriteTextFormat", hr);
|
||||
hr = tl->format->SetTextAlignment(dwriteAligns[p->Align]);
|
||||
if (hr != S_OK)
|
||||
logHRESULT(L"error applying text layout alignment", hr);
|
||||
|
||||
hr = dwfactory->CreateTextLayout(
|
||||
(const WCHAR *) attrstrUTF16(p->String), attrstrUTF16Len(p->String),
|
||||
tl->format,
|
||||
// FLOAT is float, not double, so this should work... TODO
|
||||
FLT_MAX, FLT_MAX,
|
||||
&(tl->layout));
|
||||
if (hr != S_OK)
|
||||
logHRESULT(L"error creating IDWriteTextLayout", hr);
|
||||
|
||||
// and set the width
|
||||
// this is the only wrapping mode (apart from "no wrap") available prior to Windows 8.1 (TODO verify this fact) (TODO this should be the default anyway)
|
||||
wrap = DWRITE_WORD_WRAPPING_WRAP;
|
||||
maxWidth = (FLOAT) (p->Width);
|
||||
if (p->Width < 0) {
|
||||
// TODO is this wrapping juggling even necessary?
|
||||
wrap = DWRITE_WORD_WRAPPING_NO_WRAP;
|
||||
// setting the max width in this case technically isn't needed since the wrap mode will simply ignore the max width, but let's do it just to be safe
|
||||
maxWidth = FLT_MAX; // see TODO above
|
||||
}
|
||||
hr = tl->layout->SetWordWrapping(wrap);
|
||||
if (hr != S_OK)
|
||||
logHRESULT(L"error setting IDWriteTextLayout word wrapping mode", hr);
|
||||
hr = tl->layout->SetMaxWidth(maxWidth);
|
||||
if (hr != S_OK)
|
||||
logHRESULT(L"error setting IDWriteTextLayout max layout width", hr);
|
||||
|
||||
attrstrToIDWriteTextLayoutAttrs(p, tl->layout, &(tl->backgroundFuncs));
|
||||
|
||||
computeLineInfo(tl);
|
||||
|
||||
// and finally copy the UTF-8/UTF-16 index conversion tables
|
||||
tl->u8tou16 = attrstrCopyUTF8ToUTF16(p->String, &(tl->nUTF8));
|
||||
tl->u16tou8 = attrstrCopyUTF16ToUTF8(p->String, &(tl->nUTF16));
|
||||
|
||||
// TODO can/should this be moved elsewhere?
|
||||
uiFree(wDefaultFamily);
|
||||
return tl;
|
||||
}
|
||||
|
||||
void uiDrawFreeTextLayout(uiDrawTextLayout *tl)
|
||||
{
|
||||
uiFree(tl->u16tou8);
|
||||
uiFree(tl->u8tou16);
|
||||
uiFree(tl->lineInfo);
|
||||
delete tl->backgroundFuncs;
|
||||
tl->layout->Release();
|
||||
tl->format->Release();
|
||||
uiFree(tl);
|
||||
}
|
||||
|
||||
static HRESULT mkSolidBrush(ID2D1RenderTarget *rt, double r, double g, double b, double a, ID2D1SolidColorBrush **brush)
|
||||
{
|
||||
D2D1_BRUSH_PROPERTIES props;
|
||||
D2D1_COLOR_F color;
|
||||
|
||||
ZeroMemory(&props, sizeof (D2D1_BRUSH_PROPERTIES));
|
||||
props.opacity = 1.0;
|
||||
// identity matrix
|
||||
props.transform._11 = 1;
|
||||
props.transform._22 = 1;
|
||||
color.r = r;
|
||||
color.g = g;
|
||||
color.b = b;
|
||||
color.a = a;
|
||||
return rt->CreateSolidColorBrush(
|
||||
&color,
|
||||
&props,
|
||||
brush);
|
||||
}
|
||||
|
||||
static ID2D1SolidColorBrush *mustMakeSolidBrush(ID2D1RenderTarget *rt, double r, double g, double b, double a)
|
||||
{
|
||||
ID2D1SolidColorBrush *brush;
|
||||
HRESULT hr;
|
||||
|
||||
hr = mkSolidBrush(rt, r, g, b, a, &brush);
|
||||
if (hr != S_OK)
|
||||
logHRESULT(L"error creating solid brush", hr);
|
||||
return brush;
|
||||
}
|
||||
|
||||
// some of the stuff we want to do isn't possible with what DirectWrite provides itself; we need to do it ourselves
|
||||
// this is based on http://www.charlespetzold.com/blog/2014/01/Character-Formatting-Extensions-with-DirectWrite.html
|
||||
class textRenderer : public IDWriteTextRenderer {
|
||||
ULONG refcount;
|
||||
ID2D1RenderTarget *rt;
|
||||
BOOL snap;
|
||||
ID2D1SolidColorBrush *black;
|
||||
public:
|
||||
textRenderer(ID2D1RenderTarget *rt, BOOL snap, ID2D1SolidColorBrush *black)
|
||||
{
|
||||
this->refcount = 1;
|
||||
this->rt = rt;
|
||||
this->snap = snap;
|
||||
this->black = black;
|
||||
}
|
||||
|
||||
// IUnknown
|
||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)
|
||||
{
|
||||
if (ppvObject == NULL)
|
||||
return E_POINTER;
|
||||
if (riid == IID_IUnknown ||
|
||||
riid == __uuidof (IDWritePixelSnapping) ||
|
||||
riid == __uuidof (IDWriteTextRenderer)) {
|
||||
this->AddRef();
|
||||
*ppvObject = this;
|
||||
return S_OK;
|
||||
}
|
||||
*ppvObject = NULL;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
virtual ULONG STDMETHODCALLTYPE AddRef(void)
|
||||
{
|
||||
this->refcount++;
|
||||
return this->refcount;
|
||||
}
|
||||
|
||||
virtual ULONG STDMETHODCALLTYPE Release(void)
|
||||
{
|
||||
this->refcount--;
|
||||
if (this->refcount == 0) {
|
||||
delete this;
|
||||
return 0;
|
||||
}
|
||||
return this->refcount;
|
||||
}
|
||||
|
||||
// IDWritePixelSnapping
|
||||
virtual HRESULT STDMETHODCALLTYPE GetCurrentTransform(void *clientDrawingContext, DWRITE_MATRIX *transform)
|
||||
{
|
||||
D2D1_MATRIX_3X2_F d2dtf;
|
||||
|
||||
if (transform == NULL)
|
||||
return E_POINTER;
|
||||
this->rt->GetTransform(&d2dtf);
|
||||
transform->m11 = d2dtf._11;
|
||||
transform->m12 = d2dtf._12;
|
||||
transform->m21 = d2dtf._21;
|
||||
transform->m22 = d2dtf._22;
|
||||
transform->dx = d2dtf._31;
|
||||
transform->dy = d2dtf._32;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetPixelsPerDip(void *clientDrawingContext, FLOAT *pixelsPerDip)
|
||||
{
|
||||
FLOAT dpix, dpiy;
|
||||
|
||||
if (pixelsPerDip == NULL)
|
||||
return E_POINTER;
|
||||
this->rt->GetDpi(&dpix, &dpiy);
|
||||
*pixelsPerDip = dpix / 96;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE IsPixelSnappingDisabled(void *clientDrawingContext, BOOL *isDisabled)
|
||||
{
|
||||
if (isDisabled == NULL)
|
||||
return E_POINTER;
|
||||
*isDisabled = !this->snap;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// IDWriteTextRenderer
|
||||
virtual HRESULT STDMETHODCALLTYPE DrawGlyphRun(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_MEASURING_MODE measuringMode, const DWRITE_GLYPH_RUN *glyphRun, const DWRITE_GLYPH_RUN_DESCRIPTION *glyphRunDescription, IUnknown *clientDrawingEffect)
|
||||
{
|
||||
D2D1_POINT_2F baseline;
|
||||
textDrawingEffect *t = (textDrawingEffect *) clientDrawingEffect;
|
||||
ID2D1SolidColorBrush *brush;
|
||||
|
||||
baseline.x = baselineOriginX;
|
||||
baseline.y = baselineOriginY;
|
||||
brush = this->black;
|
||||
if (t != NULL && t->hasColor) {
|
||||
HRESULT hr;
|
||||
|
||||
hr = mkSolidBrush(this->rt, t->r, t->g, t->b, t->a, &brush);
|
||||
if (hr != S_OK)
|
||||
return hr;
|
||||
}
|
||||
this->rt->DrawGlyphRun(
|
||||
baseline,
|
||||
glyphRun,
|
||||
brush,
|
||||
measuringMode);
|
||||
if (t != NULL && t->hasColor)
|
||||
brush->Release();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE DrawInlineObject(void *clientDrawingContext, FLOAT originX, FLOAT originY, IDWriteInlineObject *inlineObject, BOOL isSideways, BOOL isRightToLeft, IUnknown *clientDrawingEffect)
|
||||
{
|
||||
if (inlineObject == NULL)
|
||||
return E_POINTER;
|
||||
return inlineObject->Draw(clientDrawingContext, this,
|
||||
originX, originY,
|
||||
isSideways, isRightToLeft,
|
||||
clientDrawingEffect);
|
||||
}
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE DrawStrikethrough(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_STRIKETHROUGH *strikethrough, IUnknown *clientDrawingEffect)
|
||||
{
|
||||
// we don't support strikethrough
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE DrawUnderline(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_UNDERLINE *underline, IUnknown *clientDrawingEffect)
|
||||
{
|
||||
textDrawingEffect *t = (textDrawingEffect *) clientDrawingEffect;
|
||||
ID2D1SolidColorBrush *brush;
|
||||
D2D1_RECT_F rect;
|
||||
D2D1::Matrix3x2F pixeltf;
|
||||
FLOAT dpix, dpiy;
|
||||
D2D1_POINT_2F pt;
|
||||
|
||||
if (underline == NULL)
|
||||
return E_POINTER;
|
||||
if (t == NULL) // we can only get here through an underline
|
||||
return E_UNEXPECTED;
|
||||
brush = this->black;
|
||||
if (t->hasUnderlineColor) {
|
||||
HRESULT hr;
|
||||
|
||||
hr = mkSolidBrush(this->rt, t->ur, t->ug, t->ub, t->ua, &brush);
|
||||
if (hr != S_OK)
|
||||
return hr;
|
||||
} else if (t->hasColor) {
|
||||
// TODO formalize this rule
|
||||
HRESULT hr;
|
||||
|
||||
hr = mkSolidBrush(this->rt, t->r, t->g, t->b, t->a, &brush);
|
||||
if (hr != S_OK)
|
||||
return hr;
|
||||
}
|
||||
rect.left = baselineOriginX;
|
||||
rect.top = baselineOriginY + underline->offset;
|
||||
rect.right = rect.left + underline->width;
|
||||
rect.bottom = rect.top + underline->thickness;
|
||||
switch (t->u) {
|
||||
case uiDrawUnderlineStyleSingle:
|
||||
this->rt->FillRectangle(&rect, brush);
|
||||
break;
|
||||
case uiDrawUnderlineStyleDouble:
|
||||
// TODO do any of the matrix methods return errors?
|
||||
// TODO standardize double-underline shape across platforms? wavy underline shape?
|
||||
this->rt->GetTransform(&pixeltf);
|
||||
this->rt->GetDpi(&dpix, &dpiy);
|
||||
pixeltf = pixeltf * D2D1::Matrix3x2F::Scale(dpix / 96, dpiy / 96);
|
||||
pt.x = 0;
|
||||
pt.y = rect.top;
|
||||
pt = pixeltf.TransformPoint(pt);
|
||||
rect.top = (FLOAT) ((int) (pt.y + 0.5));
|
||||
pixeltf.Invert();
|
||||
pt = pixeltf.TransformPoint(pt);
|
||||
rect.top = pt.y;
|
||||
// first line
|
||||
rect.top -= underline->thickness;
|
||||
// and it seems we need to recompute this
|
||||
rect.bottom = rect.top + underline->thickness;
|
||||
this->rt->FillRectangle(&rect, brush);
|
||||
// second line
|
||||
rect.top += 2 * underline->thickness;
|
||||
rect.bottom = rect.top + underline->thickness;
|
||||
this->rt->FillRectangle(&rect, brush);
|
||||
break;
|
||||
case uiDrawUnderlineStyleSuggestion:
|
||||
{ // TODO get rid of the extra block
|
||||
// TODO properly clean resources on failure
|
||||
// TODO use fully qualified C overloads for all methods
|
||||
// TODO ensure all methods properly have errors handled
|
||||
ID2D1PathGeometry *path;
|
||||
ID2D1GeometrySink *sink;
|
||||
double amplitude, period, xOffset, yOffset;
|
||||
double t;
|
||||
bool first = true;
|
||||
HRESULT hr;
|
||||
|
||||
hr = d2dfactory->CreatePathGeometry(&path);
|
||||
if (hr != S_OK)
|
||||
return hr;
|
||||
hr = path->Open(&sink);
|
||||
if (hr != S_OK)
|
||||
return hr;
|
||||
amplitude = underline->thickness;
|
||||
period = 5 * underline->thickness;
|
||||
xOffset = baselineOriginX;
|
||||
yOffset = baselineOriginY + underline->offset;
|
||||
for (t = 0; t < underline->width; t++) {
|
||||
double x, angle, y;
|
||||
D2D1_POINT_2F pt;
|
||||
|
||||
x = t + xOffset;
|
||||
angle = 2 * uiPi * fmod(x, period) / period;
|
||||
y = amplitude * sin(angle) + yOffset;
|
||||
pt.x = x;
|
||||
pt.y = y;
|
||||
if (first) {
|
||||
sink->BeginFigure(pt, D2D1_FIGURE_BEGIN_HOLLOW);
|
||||
first = false;
|
||||
} else
|
||||
sink->AddLine(pt);
|
||||
}
|
||||
sink->EndFigure(D2D1_FIGURE_END_OPEN);
|
||||
hr = sink->Close();
|
||||
if (hr != S_OK)
|
||||
return hr;
|
||||
sink->Release();
|
||||
this->rt->DrawGeometry(path, brush, underline->thickness);
|
||||
path->Release();
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (t->hasUnderlineColor || t->hasColor)
|
||||
brush->Release();
|
||||
return S_OK;
|
||||
}
|
||||
};
|
||||
|
||||
// TODO this ignores clipping?
|
||||
void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)
|
||||
{
|
||||
D2D1_POINT_2F pt;
|
||||
ID2D1SolidColorBrush *black;
|
||||
textRenderer *renderer;
|
||||
HRESULT hr;
|
||||
|
||||
for (const auto &f : *(tl->backgroundFuncs))
|
||||
f(c, tl, x, y);
|
||||
|
||||
// TODO document that fully opaque black is the default text color; figure out whether this is upheld in various scenarios on other platforms
|
||||
// TODO figure out if this needs to be cleaned out
|
||||
black = mustMakeSolidBrush(c->rt, 0.0, 0.0, 0.0, 1.0);
|
||||
|
||||
#define renderD2D 0
|
||||
#define renderOur 1
|
||||
#if renderD2D
|
||||
pt.x = x;
|
||||
pt.y = y;
|
||||
// TODO D2D1_DRAW_TEXT_OPTIONS_NO_SNAP?
|
||||
// TODO D2D1_DRAW_TEXT_OPTIONS_CLIP?
|
||||
// TODO LONGTERM when setting 8.1 as minimum (TODO verify), D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT?
|
||||
// TODO what is our pixel snapping setting related to the OPTIONS enum values?
|
||||
c->rt->DrawTextLayout(pt, tl->layout, black, D2D1_DRAW_TEXT_OPTIONS_NONE);
|
||||
#endif
|
||||
#if renderD2D && renderOur
|
||||
// draw ours semitransparent so we can check
|
||||
// TODO get the actual color Charles Petzold uses and use that
|
||||
black->Release();
|
||||
black = mustMakeSolidBrush(c->rt, 1.0, 0.0, 0.0, 0.75);
|
||||
#endif
|
||||
#if renderOur
|
||||
renderer = new textRenderer(c->rt,
|
||||
TRUE, // TODO FALSE for no-snap?
|
||||
black);
|
||||
hr = tl->layout->Draw(NULL,
|
||||
renderer,
|
||||
x, y);
|
||||
if (hr != S_OK)
|
||||
logHRESULT(L"error drawing IDWriteTextLayout", hr);
|
||||
renderer->Release();
|
||||
#endif
|
||||
|
||||
black->Release();
|
||||
}
|
||||
|
||||
// TODO for a single line the height includes the leading; should it? TextEdit on OS X always includes the leading and/or paragraph spacing, otherwise Klee won't work...
|
||||
// TODO width does not include trailing whitespace
|
||||
void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)
|
||||
{
|
||||
DWRITE_TEXT_METRICS metrics;
|
||||
HRESULT hr;
|
||||
|
||||
hr = tl->layout->GetMetrics(&metrics);
|
||||
if (hr != S_OK)
|
||||
logHRESULT(L"error getting IDWriteTextLayout layout metrics", hr);
|
||||
*width = metrics.width;
|
||||
// TODO make sure the behavior of this on empty strings is the same on all platforms (ideally should be 0-width, line height-height; TODO note this in the docs too)
|
||||
*height = metrics.height;
|
||||
}
|
||||
|
||||
int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl)
|
||||
{
|
||||
return tl->nLines;
|
||||
}
|
||||
|
||||
// DirectWrite doesn't provide a direct way to do this, so we have to do this manually
|
||||
// TODO does that comment still apply here or to the code at the top of this file?
|
||||
void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end)
|
||||
{
|
||||
*start = tl->lineInfo[line].startPos;
|
||||
*start = tl->u16tou8[*start];
|
||||
*end = tl->lineInfo[line].endPos - tl->lineInfo[line].newlineCount;
|
||||
*end = tl->u16tou8[*end];
|
||||
}
|
||||
|
||||
void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m)
|
||||
{
|
||||
m->X = tl->lineInfo[line].x;
|
||||
m->Y = tl->lineInfo[line].y;
|
||||
m->Width = tl->lineInfo[line].width;
|
||||
m->Height = tl->lineInfo[line].height;
|
||||
|
||||
// TODO rename tl->lineInfo[line].baseline to .baselineOffset or something of the sort to make its meaning more clear
|
||||
m->BaselineY = tl->lineInfo[line].y + tl->lineInfo[line].baseline;
|
||||
m->Ascent = tl->lineInfo[line].baseline;
|
||||
m->Descent = tl->lineInfo[line].height - tl->lineInfo[line].baseline;
|
||||
m->Leading = 0; // TODO
|
||||
|
||||
m->ParagraphSpacingBefore = 0; // TODO
|
||||
m->LineHeightSpace = 0; // TODO
|
||||
m->LineSpacing = 0; // TODO
|
||||
m->ParagraphSpacing = 0; // TODO
|
||||
}
|
||||
|
||||
// this algorithm comes from Microsoft's PadWrite sample, following TextEditor::SetSelectionFromPoint()
|
||||
// TODO go back through all of these and make sure we convert coordinates properly
|
||||
// TODO same for OS X
|
||||
void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *pos, int *line)
|
||||
{
|
||||
DWRITE_HIT_TEST_METRICS m;
|
||||
BOOL trailing, inside;
|
||||
size_t p;
|
||||
UINT32 i;
|
||||
HRESULT hr;
|
||||
|
||||
hr = tl->layout->HitTestPoint(x, y,
|
||||
&trailing, &inside,
|
||||
&m);
|
||||
if (hr != S_OK)
|
||||
logHRESULT(L"error hit-testing IDWriteTextLayout", hr);
|
||||
p = m.textPosition;
|
||||
// on a trailing hit, align to the nearest cluster
|
||||
if (trailing) {
|
||||
DWRITE_HIT_TEST_METRICS m2;
|
||||
FLOAT x, y; // crashes if I skip these :/
|
||||
|
||||
hr = tl->layout->HitTestTextPosition(m.textPosition, trailing,
|
||||
&x, &y, &m2);
|
||||
if (hr != S_OK)
|
||||
logHRESULT(L"error aligning trailing hit to nearest cluster", hr);
|
||||
p = m2.textPosition + m2.length;
|
||||
}
|
||||
*pos = tl->u16tou8[p];
|
||||
|
||||
for (i = 0; i < tl->nLines; i++) {
|
||||
double ltop, lbottom;
|
||||
|
||||
ltop = tl->lineInfo[i].y;
|
||||
lbottom = ltop + tl->lineInfo[i].height;
|
||||
// y will already >= ltop at this point since the past lbottom should == ltop
|
||||
if (y < lbottom)
|
||||
break;
|
||||
}
|
||||
if (i == tl->nLines)
|
||||
i--;
|
||||
*line = i;
|
||||
}
|
||||
|
||||
double uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line)
|
||||
{
|
||||
BOOL trailing;
|
||||
DWRITE_HIT_TEST_METRICS m;
|
||||
FLOAT x, y;
|
||||
HRESULT hr;
|
||||
|
||||
if (line < 0 || line >= tl->nLines)
|
||||
return -1;
|
||||
pos = tl->u8tou16[pos];
|
||||
// note: >, not >=, because the position at endPos is valid!
|
||||
if (pos < tl->lineInfo[line].startPos || pos > tl->lineInfo[line].endPos)
|
||||
return -1;
|
||||
// this behavior seems correct
|
||||
// there's also PadWrite's TextEditor::GetCaretRect() but that requires state...
|
||||
// TODO where does this fail?
|
||||
trailing = FALSE;
|
||||
if (pos != 0 && pos != tl->nUTF16 && pos == tl->lineInfo[line].endPos) {
|
||||
pos--;
|
||||
trailing = TRUE;
|
||||
}
|
||||
hr = tl->layout->HitTestTextPosition(pos, trailing,
|
||||
&x, &y, &m);
|
||||
if (hr != S_OK)
|
||||
logHRESULT(L"error calling IDWriteTextLayout::HitTestTextPosition()", hr);
|
||||
return x;
|
||||
}
|
||||
|
||||
void caretDrawParams(uiDrawContext *c, double height, struct caretDrawParams *p)
|
||||
{
|
||||
DWORD caretWidth;
|
||||
|
||||
// there seems to be no defined caret color
|
||||
// the best I can come up with is "inverts colors underneath" (according to https://msdn.microsoft.com/en-us/library/windows/desktop/ms648397(v=vs.85).aspx) which I have no idea how to do (TODO)
|
||||
// just return black for now
|
||||
p->r = 0.0;
|
||||
p->g = 0.0;
|
||||
p->b = 0.0;
|
||||
p->a = 1.0;
|
||||
|
||||
if (SystemParametersInfoW(SPI_GETCARETWIDTH, 0, &caretWidth, 0) == 0)
|
||||
// don't log the failure, fall back gracefully
|
||||
// the instruction to use this comes from https://msdn.microsoft.com/en-us/library/windows/desktop/ms648399(v=vs.85).aspx
|
||||
// and we have to assume GetSystemMetrics() always succeeds, so
|
||||
caretWidth = GetSystemMetrics(SM_CXBORDER);
|
||||
// TODO make this a function and split it out of areautil.cpp
|
||||
{
|
||||
FLOAT dpix, dpiy;
|
||||
|
||||
// TODO can we pass NULL for dpiy?
|
||||
c->rt->GetDpi(&dpix, &dpiy);
|
||||
// see https://msdn.microsoft.com/en-us/library/windows/desktop/dd756649%28v=vs.85%29.aspx (and others; search "direct2d mouse")
|
||||
p->width = ((double) (caretWidth * 96)) / dpix;
|
||||
}
|
||||
// and there doesn't seem to be this either... (TODO check what PadWrite does?)
|
||||
p->xoff = 0;
|
||||
}
|
||||
@@ -0,0 +1,912 @@
|
||||
// 11 february 2017
|
||||
#include "drawtext.h"
|
||||
|
||||
static uiAttributedString *attrstr;
|
||||
|
||||
#define nFeatures 256
|
||||
static uiOpenTypeFeatures *features[nFeatures];
|
||||
static int curFeature = 0;
|
||||
|
||||
static uiOpenTypeFeatures *addFeature(const char tag[4], uint32_t value)
|
||||
{
|
||||
uiOpenTypeFeatures *otf;
|
||||
|
||||
if (curFeature >= nFeatures) {
|
||||
fprintf(stderr, "TODO (also TODO is there a panic function?)\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
otf = uiNewOpenTypeFeatures();
|
||||
uiOpenTypeFeaturesAdd(otf, tag[0], tag[1], tag[2], tag[3], value);
|
||||
features[curFeature] = otf;
|
||||
curFeature++;
|
||||
return otf;
|
||||
}
|
||||
|
||||
// some of these examples come from Microsoft's and Apple's lists of typographic features and also https://www.fontfont.com/staticcontent/downloads/FF_OT_User_Guide.pdf
|
||||
static void setupAttributedString(void)
|
||||
{
|
||||
uiAttributeSpec spec;
|
||||
size_t start, end;
|
||||
const char *next;
|
||||
uiOpenTypeFeatures *otf;
|
||||
int i;
|
||||
|
||||
attrstr = uiNewAttributedString("uiAttributedString isn't just for plain text! It supports ");
|
||||
|
||||
next = "multiple fonts";
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFamily;
|
||||
spec.Family = "Courier New";
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "multiple sizes";
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeSize;
|
||||
spec.Double = 18;
|
||||
uiAttributedStringSetAttribute(attrstr,
|
||||
&spec, start, end);
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "multiple weights";
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeWeight;
|
||||
spec.Value = (uintptr_t) uiDrawTextWeightBold;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "multiple italics";
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeItalic;
|
||||
spec.Value = (uintptr_t) uiDrawTextItalicItalic;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "multiple stretches";
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeStretch;
|
||||
spec.Value = (uintptr_t) uiDrawTextStretchCondensed;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "multiple colors";
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeColor;
|
||||
// Direct2D "Crimson" (#DC143C)
|
||||
spec.R = 0.8627450980392156;
|
||||
spec.G = 0.0784313725490196;
|
||||
spec.B = 0.2352941176470588;
|
||||
spec.A = 0.75;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "multiple backgrounds";
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeBackground;
|
||||
// Direct2D "Peach Puff" (#FFDAB9)
|
||||
// TODO choose a darker color
|
||||
spec.R = 1.0;
|
||||
spec.G = 0.85490196078431372;
|
||||
spec.B = 0.7254901960784313;
|
||||
spec.A = 0.5;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "multiple";
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeUnderline;
|
||||
spec.Value = uiDrawUnderlineStyleSingle;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " ");
|
||||
next = "underlines";
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeUnderline;
|
||||
spec.Value = uiDrawUnderlineStyleDouble;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
spec.Type = uiAttributeUnderlineColor;
|
||||
spec.Value = uiDrawUnderlineColorCustom;
|
||||
spec.R = 0.5;
|
||||
spec.G = 0.0;
|
||||
spec.B = 1.0;
|
||||
spec.A = 1.0;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " (");
|
||||
next = "including underlines for spelling correction and the like";
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeUnderline;
|
||||
spec.Value = uiDrawUnderlineStyleSuggestion;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
spec.Type = uiAttributeUnderlineColor;
|
||||
spec.Value = uiDrawUnderlineColorSpelling;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
// TODO randomize these ranges better
|
||||
// TODO make some overlap to test that
|
||||
// TODO also change colors to light foreground dark background
|
||||
next = "or any combination of the above";
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeWeight;
|
||||
spec.Value = (uintptr_t) uiDrawTextWeightBold;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end - 8);
|
||||
spec.Type = uiAttributeItalic;
|
||||
spec.Value = (uintptr_t) uiDrawTextItalicItalic;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start + 3, end - 4);
|
||||
spec.Type = uiAttributeColor;
|
||||
spec.R = 0.8627450980392156;
|
||||
spec.G = 0.0784313725490196;
|
||||
spec.B = 0.2352941176470588;
|
||||
spec.A = 0.75;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start + 12, end);
|
||||
spec.Type = uiAttributeFamily;
|
||||
spec.Family = "Helvetica";
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start + 8, end - 1);
|
||||
spec.Type = uiAttributeBackground;
|
||||
spec.R = 1.0;
|
||||
spec.G = 0.85490196078431372;
|
||||
spec.B = 0.7254901960784313;
|
||||
spec.A = 0.5;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start + 5, end - 7);
|
||||
spec.Type = uiAttributeUnderline;
|
||||
spec.Value = uiDrawUnderlineStyleSingle;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start + 9, end - 1);
|
||||
|
||||
// TODO rewrite this to talk about OpenTpe instead
|
||||
// TODO also shorten this to something more useful and that covers the general gist of things (and combines features arbitrarily like the previous demo) when we add a general OpenType demo (see the last TODO in this function)
|
||||
uiAttributedStringAppendUnattributed(attrstr, ". In addition, a variety of typographical features are available (depending on the chosen font) that can be switched on (or off, if the font enables them by default): ");
|
||||
|
||||
next = "fi";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "standard ligatures like f+i (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("liga", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
// note the use of LTR marks and RTL embeds to make sure the bidi algorithm doesn't kick in for our demonstration (it will produce incorrect results)
|
||||
// see also: https://www.w3.org/International/articles/inline-bidi-markup/#nomarkup
|
||||
next = "\xD9\x84\xD8\xA7";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "required ligatures like \xE2\x80\xAB\xD9\x84\xE2\x80\xAC+\xE2\x80\xAB\xD8\xA7\xE2\x80\xAC (\xE2\x80\x8E\xE2\x80\xAB");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("rlig", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, "\xE2\x80\xAC)");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "ct";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "discretionary/rare ligatures like c+t (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("dlig", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "the";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "contextual ligatures like h+e in the (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("clig", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
otf = addFeature("hlig", 1);
|
||||
// This technically isn't what is meant by "historical ligatures", but Core Text's internal AAT-to-OpenType mapping says to include it, so we include it too
|
||||
uiOpenTypeFeaturesAdd(otf, 'h', 'i', 's', 't', 1);
|
||||
next = "\xC3\x9F";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "historical ligatures like the decomposition of \xC3\x9F (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = otf;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
// TODO a different word than "writing"?
|
||||
next = "UnICasE wRITInG";
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("unic", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "316";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "proportional (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("pnum", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ") and tabular/monospaced (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("tnum", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ") numbers");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "123";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "superscipts (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("sups", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "123";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "subscripts (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("subs", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "1st";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "ordinals (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("ordn", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "H2O";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "scientific inferiors (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("sinf", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "2/3";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "fraction forms (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
#if 0 /* TODO */
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFractionForms;
|
||||
spec.Value = uiAttributeFractionFormNone;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
#endif
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("afrc", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("frac", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "0";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "slashed zeroes (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("zero", 0);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("zero", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "\xCE\xA0\xCE\xA3";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "mathematical greek (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("mgrk", 0);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("mgrk", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "qwertyuiop\xE2\x80\xA2";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "ornamental forms (");
|
||||
for (i = 1; i < 11; i++) {
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("ornm", i);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
next = "\xE2\x80\xA2";
|
||||
}
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "g";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "specific forms/alternates (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("aalt", 0);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("aalt", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "ABCDEFGQWERTY";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "titling capital forms (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("titl", 0);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("titl", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "\xE7\x80\x86";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "alternate Han character forms (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("jp78", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("jp83", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
otf = addFeature("onum", 0);
|
||||
// Core Text's internal AAT-to-OpenType mapping says to include this, so we include it too
|
||||
// TODO is it always set?
|
||||
uiOpenTypeFeaturesAdd(otf, 'l', 'n', 'u', 'm', 0);
|
||||
next = "0123456789";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "lowercase numbers (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = otf;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
otf = addFeature("onum", 1);
|
||||
uiOpenTypeFeaturesAdd(otf, 'l', 'n', 'u', 'm', 1);
|
||||
spec.Features = otf;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "\xE4\xBC\xBD";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "hanja to hangul translation (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("hngl", 0);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("hngl", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "\xE3\x81\x82";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "annotated glyph forms (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("nalt", 0);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("nalt", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("nalt", 4); // AAT inverted circle
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "\xE3\x81\x82";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "ruby forms of kana (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("ruby", 0);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("ruby", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "now is the time";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "italic forms of Latin letters in CJK fonts (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("ital", 0);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("ital", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "{I} > {J}";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "case-sensitive character forms, such as punctuation (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("case", 0);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("case", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "ABC";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "specialized spacing between uppercase letters (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("cpsp", 0);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("cpsp", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "\xE3\x82\xB9\xE3\x83\x98\xE3\x83\x88";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "alternate horizontal (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("hkna", 0);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("hkna", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ") and vertical (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("vkna", 0);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("vkna", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ") kana forms");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "g";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "stylistic alternates (");
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
for (i = 1; i <= 20; i++) {
|
||||
char tag[4];
|
||||
|
||||
tag[0] = 's';
|
||||
tag[1] = 's';
|
||||
tag[2] = '0';
|
||||
if (i >= 10)
|
||||
tag[2] = '1';
|
||||
tag[3] = (i % 10) + '0'; // TODO see how I wrote this elsewhere
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Features = addFeature(tag, 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
}
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "first";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "contextual alternates (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("calt", 0);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("calt", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "FONT";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "swashes (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("swsh", 0);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("swsh", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "Font";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "contextual swashes (");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("cswh", 0);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("cswh", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "Small Caps";
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("smcp", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
next = "Petite Caps";
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("pcap", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
|
||||
next = "SMALL UPPERCASES";
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("c2sp", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", and ");
|
||||
next = "PETITE UPPERCASES";
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = addFeature("c2pc", 1);
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ".");
|
||||
|
||||
// TODO write a dedicated example for experimenting with typographic features like the one in gtk3-demo
|
||||
}
|
||||
|
||||
static char fontFamily[] = "Times New Roman";
|
||||
// TODO should be const; look at constructor function?
|
||||
static uiDrawFontDescriptor defaultFont = {
|
||||
.Family = fontFamily,
|
||||
.Size = 12,
|
||||
.Weight = uiDrawTextWeightNormal,
|
||||
.Italic = uiDrawTextItalicNormal,
|
||||
.Stretch = uiDrawTextStretchNormal,
|
||||
};
|
||||
static uiDrawTextLayoutParams params;
|
||||
|
||||
#define margins 10
|
||||
|
||||
static uiBox *panel;
|
||||
static uiCheckbox *showLineBounds;
|
||||
static uiFontButton *fontButton;
|
||||
|
||||
// TODO should be const?
|
||||
static uiDrawBrush fillBrushes[4] = {
|
||||
{
|
||||
.Type = uiDrawBrushTypeSolid,
|
||||
.R = 1.0,
|
||||
.G = 0.0,
|
||||
.B = 0.0,
|
||||
.A = 0.5,
|
||||
},
|
||||
{
|
||||
.Type = uiDrawBrushTypeSolid,
|
||||
.R = 0.0,
|
||||
.G = 1.0,
|
||||
.B = 0.0,
|
||||
.A = 0.5,
|
||||
},
|
||||
{
|
||||
.Type = uiDrawBrushTypeSolid,
|
||||
.R = 0.0,
|
||||
.G = 0.0,
|
||||
.B = 1.0,
|
||||
.A = 0.5,
|
||||
},
|
||||
{
|
||||
.Type = uiDrawBrushTypeSolid,
|
||||
.R = 0.0,
|
||||
.G = 1.0,
|
||||
.B = 1.0,
|
||||
.A = 0.5,
|
||||
},
|
||||
};
|
||||
|
||||
static void draw(uiAreaDrawParams *p)
|
||||
{
|
||||
uiDrawPath *path;
|
||||
uiDrawTextLayout *layout;
|
||||
uiDrawBrush b;
|
||||
|
||||
b.Type = uiDrawBrushTypeSolid;
|
||||
|
||||
// only clip the text, not the guides
|
||||
uiDrawSave(p->Context);
|
||||
|
||||
path = uiDrawNewPath(uiDrawFillModeWinding);
|
||||
uiDrawPathAddRectangle(path, margins, margins,
|
||||
p->AreaWidth - 2 * margins,
|
||||
p->AreaHeight - 2 * margins);
|
||||
uiDrawPathEnd(path);
|
||||
uiDrawClip(p->Context, path);
|
||||
uiDrawFreePath(path);
|
||||
|
||||
params.Width = p->AreaWidth - 2 * margins;
|
||||
layout = uiDrawNewTextLayout(¶ms);
|
||||
uiDrawText(p->Context, layout, margins, margins);
|
||||
|
||||
uiDrawRestore(p->Context);
|
||||
|
||||
if (uiCheckboxChecked(showLineBounds)) {
|
||||
uiDrawTextLayoutLineMetrics m;
|
||||
int i, n;
|
||||
int fill = 0;
|
||||
|
||||
n = uiDrawTextLayoutNumLines(layout);
|
||||
for (i = 0; i < n; i++) {
|
||||
uiDrawTextLayoutLineGetMetrics(layout, i, &m);
|
||||
|
||||
path = uiDrawNewPath(uiDrawFillModeWinding);
|
||||
uiDrawPathAddRectangle(path, margins + m.X, margins + m.Y,
|
||||
m.Width, m.Height);
|
||||
uiDrawPathEnd(path);
|
||||
uiDrawFill(p->Context, path, fillBrushes + fill);
|
||||
uiDrawFreePath(path);
|
||||
fill = (fill + 1) % 4;
|
||||
}
|
||||
}
|
||||
|
||||
uiDrawFreeTextLayout(layout);
|
||||
}
|
||||
|
||||
static struct example attributesExample;
|
||||
|
||||
static void changeFont(uiFontButton *b, void *data)
|
||||
{
|
||||
if (defaultFont.Family != fontFamily)
|
||||
uiFreeText(defaultFont.Family);
|
||||
// TODO rename defaultFont
|
||||
uiFontButtonFont(fontButton, &defaultFont);
|
||||
redraw();
|
||||
}
|
||||
|
||||
// TODO share?
|
||||
static void checkboxChecked(uiCheckbox *c, void *data)
|
||||
{
|
||||
redraw();
|
||||
}
|
||||
|
||||
static uiCheckbox *newCheckbox(const char *text)
|
||||
{
|
||||
uiCheckbox *c;
|
||||
|
||||
c = uiNewCheckbox(text);
|
||||
uiCheckboxOnToggled(c, checkboxChecked, NULL);
|
||||
uiBoxAppend(panel, uiControl(c), 0);
|
||||
return c;
|
||||
}
|
||||
|
||||
struct example *mkAttributesExample(void)
|
||||
{
|
||||
panel = uiNewVerticalBox();
|
||||
showLineBounds = newCheckbox("Show Line Bounds");
|
||||
fontButton = uiNewFontButton();
|
||||
uiFontButtonOnChanged(fontButton, changeFont, NULL);
|
||||
// TODO set the font button to the current defaultFont
|
||||
uiBoxAppend(panel, uiControl(fontButton), 0);
|
||||
|
||||
attributesExample.name = "Attributed Text";
|
||||
attributesExample.panel = uiControl(panel);
|
||||
attributesExample.draw = draw;
|
||||
attributesExample.mouse = NULL;
|
||||
attributesExample.key = NULL;
|
||||
|
||||
setupAttributedString();
|
||||
params.String = attrstr;
|
||||
params.DefaultFont = &defaultFont;
|
||||
params.Align = uiDrawTextAlignLeft;
|
||||
|
||||
return &attributesExample;
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
// 17 january 2017
|
||||
#include "drawtext.h"
|
||||
|
||||
static const char *text =
|
||||
"It is with a kind of fear that I begin to write the history of my life. "
|
||||
"I have, as it were, a superstitious hesitation in lifting the veil that "
|
||||
"clings about my childhood like a golden mist. The task of writing an "
|
||||
"autobiography is a difficult one. When I try to classify my earliest "
|
||||
"impressions, I find that fact and fancy look alike across the years that "
|
||||
"link the past with the present. The woman paints the child's experiences "
|
||||
"in her own fantasy. A few impressions stand out vividly from the first "
|
||||
"years of my life; but \"the shadows of the prison-house are on the rest.\" "
|
||||
"Besides, many of the joys and sorrows of childhood have lost their "
|
||||
"poignancy; and many incidents of vital importance in my early education "
|
||||
"have been forgotten in the excitement of great discoveries. In order, "
|
||||
"therefore, not to be tedious I shall try to present in a series of "
|
||||
"sketches only the episodes that seem to me to be the most interesting "
|
||||
"and important."
|
||||
"";
|
||||
static char fontFamily[] = "Palatino";
|
||||
// TODO should be const; look at constructor function?
|
||||
static uiDrawFontDescriptor defaultFont = {
|
||||
.Family = fontFamily,
|
||||
.Size = 18,
|
||||
.Weight = uiDrawTextWeightNormal,
|
||||
.Italic = uiDrawTextItalicNormal,
|
||||
.Stretch = uiDrawTextStretchNormal,
|
||||
};
|
||||
static uiAttributedString *attrstr;
|
||||
static uiDrawTextLayoutParams params;
|
||||
|
||||
#define margins 10
|
||||
|
||||
static uiBox *panel;
|
||||
static uiCheckbox *showExtents;
|
||||
static uiCheckbox *showLineBounds;
|
||||
static uiCheckbox *showLineGuides;
|
||||
|
||||
// TODO should this be const?
|
||||
static double strokeDashes[] = { 5, 2 };
|
||||
// TODO this should be const
|
||||
static uiDrawStrokeParams strokeParams = {
|
||||
.Cap = uiDrawLineCapFlat,
|
||||
.Join = uiDrawLineJoinMiter,
|
||||
.Thickness = 1,
|
||||
.MiterLimit = uiDrawDefaultMiterLimit,
|
||||
.Dashes = strokeDashes,
|
||||
.NumDashes = 2,
|
||||
.DashPhase = 0,
|
||||
};
|
||||
|
||||
// TODO should be const?
|
||||
static uiDrawBrush fillBrushes[4] = {
|
||||
{
|
||||
.Type = uiDrawBrushTypeSolid,
|
||||
.R = 1.0,
|
||||
.G = 0.0,
|
||||
.B = 0.0,
|
||||
.A = 0.5,
|
||||
},
|
||||
{
|
||||
.Type = uiDrawBrushTypeSolid,
|
||||
.R = 0.0,
|
||||
.G = 1.0,
|
||||
.B = 0.0,
|
||||
.A = 0.5,
|
||||
},
|
||||
{
|
||||
.Type = uiDrawBrushTypeSolid,
|
||||
.R = 0.0,
|
||||
.G = 0.0,
|
||||
.B = 1.0,
|
||||
.A = 0.5,
|
||||
},
|
||||
{
|
||||
.Type = uiDrawBrushTypeSolid,
|
||||
.R = 0.0,
|
||||
.G = 1.0,
|
||||
.B = 1.0,
|
||||
.A = 0.5,
|
||||
},
|
||||
};
|
||||
// TODO come up with better colors
|
||||
static uiDrawBrush strokeBrushes[3] = {
|
||||
// baseline
|
||||
{
|
||||
.Type = uiDrawBrushTypeSolid,
|
||||
.R = 0.5,
|
||||
.G = 0.5,
|
||||
.B = 0.0,
|
||||
.A = 0.75,
|
||||
},
|
||||
// ascent
|
||||
{
|
||||
.Type = uiDrawBrushTypeSolid,
|
||||
.R = 1.0,
|
||||
.G = 0.0,
|
||||
.B = 1.0,
|
||||
.A = 0.75,
|
||||
},
|
||||
// descent
|
||||
{
|
||||
.Type = uiDrawBrushTypeSolid,
|
||||
.R = 0.5,
|
||||
.G = 0.75,
|
||||
.B = 1.0,
|
||||
.A = 0.75,
|
||||
},
|
||||
};
|
||||
|
||||
static void draw(uiAreaDrawParams *p)
|
||||
{
|
||||
uiDrawPath *path;
|
||||
uiDrawTextLayout *layout;
|
||||
uiDrawBrush b;
|
||||
|
||||
b.Type = uiDrawBrushTypeSolid;
|
||||
|
||||
// only clip the text, not the guides
|
||||
uiDrawSave(p->Context);
|
||||
|
||||
path = uiDrawNewPath(uiDrawFillModeWinding);
|
||||
uiDrawPathAddRectangle(path, margins, margins,
|
||||
p->AreaWidth - 2 * margins,
|
||||
p->AreaHeight - 2 * margins);
|
||||
uiDrawPathEnd(path);
|
||||
uiDrawClip(p->Context, path);
|
||||
uiDrawFreePath(path);
|
||||
|
||||
// TODO get rid of this later
|
||||
#if 0
|
||||
path = uiDrawNewPath(uiDrawFillModeWinding);
|
||||
uiDrawPathAddRectangle(path, -100, -100,
|
||||
p->AreaWidth * 2,
|
||||
p->AreaHeight * 2);
|
||||
uiDrawPathEnd(path);
|
||||
b.R = 0.0;
|
||||
b.G = 1.0;
|
||||
b.B = 0.0;
|
||||
b.A = 1.0;
|
||||
uiDrawFill(p->Context, path, &b);
|
||||
uiDrawFreePath(path);
|
||||
#endif
|
||||
|
||||
params.Width = p->AreaWidth - 2 * margins;
|
||||
layout = uiDrawNewTextLayout(¶ms);
|
||||
uiDrawText(p->Context, layout, margins, margins);
|
||||
|
||||
uiDrawRestore(p->Context);
|
||||
|
||||
if (uiCheckboxChecked(showExtents)) {
|
||||
double width, height;
|
||||
|
||||
uiDrawTextLayoutExtents(layout, &width, &height);
|
||||
path = uiDrawNewPath(uiDrawFillModeWinding);
|
||||
uiDrawPathAddRectangle(path, margins, margins,
|
||||
width, height);
|
||||
uiDrawPathEnd(path);
|
||||
b.R = 1.0;
|
||||
b.G = 0.0;
|
||||
b.B = 1.0;
|
||||
b.A = 0.5;
|
||||
uiDrawStroke(p->Context, path, &b, &strokeParams);
|
||||
uiDrawFreePath(path);
|
||||
}
|
||||
|
||||
if (uiCheckboxChecked(showLineBounds) || uiCheckboxChecked(showLineGuides)) {
|
||||
uiDrawTextLayoutLineMetrics m;
|
||||
int i, n;
|
||||
int fill = 0;
|
||||
|
||||
n = uiDrawTextLayoutNumLines(layout);
|
||||
for (i = 0; i < n; i++) {
|
||||
uiDrawTextLayoutLineGetMetrics(layout, i, &m);
|
||||
|
||||
if (uiCheckboxChecked(showLineBounds)) {
|
||||
path = uiDrawNewPath(uiDrawFillModeWinding);
|
||||
uiDrawPathAddRectangle(path, margins + m.X, margins + m.Y,
|
||||
m.Width, m.Height);
|
||||
uiDrawPathEnd(path);
|
||||
uiDrawFill(p->Context, path, fillBrushes + fill);
|
||||
uiDrawFreePath(path);
|
||||
fill = (fill + 1) % 4;
|
||||
}
|
||||
if (uiCheckboxChecked(showLineGuides)) {
|
||||
// baseline
|
||||
path = uiDrawNewPath(uiDrawFillModeWinding);
|
||||
uiDrawPathNewFigure(path,
|
||||
margins + m.X,
|
||||
margins + m.BaselineY);
|
||||
uiDrawPathLineTo(path,
|
||||
margins + m.X + m.Width,
|
||||
margins + m.BaselineY);
|
||||
uiDrawPathEnd(path);
|
||||
uiDrawStroke(p->Context, path, &(strokeBrushes[0]), &strokeParams);
|
||||
uiDrawFreePath(path);
|
||||
|
||||
// ascent line
|
||||
path = uiDrawNewPath(uiDrawFillModeWinding);
|
||||
uiDrawPathNewFigure(path,
|
||||
margins + m.X,
|
||||
margins + m.BaselineY - m.Ascent);
|
||||
uiDrawPathLineTo(path,
|
||||
margins + m.X + m.Width,
|
||||
margins + m.BaselineY - m.Ascent);
|
||||
uiDrawPathEnd(path);
|
||||
uiDrawStroke(p->Context, path, &(strokeBrushes[1]), &strokeParams);
|
||||
uiDrawFreePath(path);
|
||||
|
||||
// descent line
|
||||
path = uiDrawNewPath(uiDrawFillModeWinding);
|
||||
uiDrawPathNewFigure(path,
|
||||
margins + m.X,
|
||||
margins + m.BaselineY + m.Descent);
|
||||
uiDrawPathLineTo(path,
|
||||
margins + m.X + m.Width,
|
||||
margins + m.BaselineY + m.Descent);
|
||||
uiDrawPathEnd(path);
|
||||
uiDrawStroke(p->Context, path, &(strokeBrushes[2]), &strokeParams);
|
||||
uiDrawFreePath(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uiDrawFreeTextLayout(layout);
|
||||
}
|
||||
|
||||
static struct example basicExample;
|
||||
|
||||
// TODO share?
|
||||
static void checkboxChecked(uiCheckbox *c, void *data)
|
||||
{
|
||||
redraw();
|
||||
}
|
||||
|
||||
static uiCheckbox *newCheckbox(const char *text)
|
||||
{
|
||||
uiCheckbox *c;
|
||||
|
||||
c = uiNewCheckbox(text);
|
||||
uiCheckboxOnToggled(c, checkboxChecked, NULL);
|
||||
uiBoxAppend(panel, uiControl(c), 0);
|
||||
return c;
|
||||
}
|
||||
|
||||
struct example *mkBasicExample(void)
|
||||
{
|
||||
panel = uiNewVerticalBox();
|
||||
showExtents = newCheckbox("Show Layout Extents");
|
||||
showLineBounds = newCheckbox("Show Line Bounds");
|
||||
showLineGuides = newCheckbox("Show Line Guides");
|
||||
|
||||
basicExample.name = "Basic Paragraph of Text";
|
||||
basicExample.panel = uiControl(panel);
|
||||
basicExample.draw = draw;
|
||||
basicExample.mouse = NULL;
|
||||
basicExample.key = NULL;
|
||||
|
||||
attrstr = uiNewAttributedString(text);
|
||||
params.String = attrstr;
|
||||
params.DefaultFont = &defaultFont;
|
||||
params.Align = uiDrawTextAlignLeft;
|
||||
|
||||
return &basicExample;
|
||||
}
|
||||
|
||||
// TODO on GTK+ an area by itself in a window doesn't get expand properties set properly?
|
||||
@@ -0,0 +1,29 @@
|
||||
// 20 january 2017
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "../../ui.h"
|
||||
|
||||
struct example {
|
||||
const char *name;
|
||||
uiControl *panel;
|
||||
void (*draw)(uiAreaDrawParams *p);
|
||||
void (*mouse)(uiAreaMouseEvent *e);
|
||||
int (*key)(uiAreaKeyEvent *e);
|
||||
// TODO key?
|
||||
};
|
||||
|
||||
// main.c
|
||||
extern void redraw(void);
|
||||
|
||||
// basic.c
|
||||
extern struct example *mkBasicExample(void);
|
||||
|
||||
// hittest.c
|
||||
extern struct example *mkHitTestExample(void);
|
||||
|
||||
// attributes.c
|
||||
extern struct example *mkAttributesExample(void);
|
||||
|
||||
// emptystr_hittest.c
|
||||
extern struct example *mkEmptyStringExample(void);
|
||||
@@ -0,0 +1,254 @@
|
||||
// 20 january 2017
|
||||
#include "drawtext.h"
|
||||
|
||||
// TODO FOR THIS FILE
|
||||
// get rid of it once we rewrite this example (which requires having more fine-grained scrolling control)
|
||||
|
||||
// TODO double-check ligatures on all platforms to make sure we can place the cursor at the right place
|
||||
// TODO the hiding and showing does not work properly on GTK+
|
||||
// TODO using the arrow keys allows us to walk back to the end of the line on some platforms (TODO which?); IIRC arrow keys shouldn't do that
|
||||
// TODO make sure to check the cursor positions of RTL on all platforms
|
||||
|
||||
static const char *text = "";
|
||||
static char fontFamily[] = "Helvetica";
|
||||
static uiDrawFontDescriptor defaultFont = {
|
||||
.Family = fontFamily,
|
||||
.Size = 14,
|
||||
.Weight = uiDrawTextWeightNormal,
|
||||
.Italic = uiDrawTextItalicNormal,
|
||||
.Stretch = uiDrawTextStretchNormal,
|
||||
};
|
||||
static uiAttributedString *attrstr;
|
||||
static uiDrawTextLayoutParams params;
|
||||
|
||||
#define margins 10
|
||||
|
||||
static uiBox *panel;
|
||||
static uiBox *vbox;
|
||||
static uiLabel *caretLabel;
|
||||
static uiCheckbox *showLineBounds;
|
||||
static uiFontButton *fontButton;
|
||||
static uiCombobox *textAlign;
|
||||
|
||||
static int caretLine = -1;
|
||||
static size_t caretPos;
|
||||
|
||||
// TODO should be const?
|
||||
static uiDrawBrush fillBrushes[4] = {
|
||||
{
|
||||
.Type = uiDrawBrushTypeSolid,
|
||||
.R = 1.0,
|
||||
.G = 0.0,
|
||||
.B = 0.0,
|
||||
.A = 0.5,
|
||||
},
|
||||
{
|
||||
.Type = uiDrawBrushTypeSolid,
|
||||
.R = 0.0,
|
||||
.G = 1.0,
|
||||
.B = 0.0,
|
||||
.A = 0.5,
|
||||
},
|
||||
{
|
||||
.Type = uiDrawBrushTypeSolid,
|
||||
.R = 0.0,
|
||||
.G = 0.0,
|
||||
.B = 1.0,
|
||||
.A = 0.5,
|
||||
},
|
||||
{
|
||||
.Type = uiDrawBrushTypeSolid,
|
||||
.R = 0.0,
|
||||
.G = 1.0,
|
||||
.B = 1.0,
|
||||
.A = 0.5,
|
||||
},
|
||||
};
|
||||
|
||||
static void draw(uiAreaDrawParams *p)
|
||||
{
|
||||
uiDrawPath *path;
|
||||
uiDrawTextLayout *layout;
|
||||
uiDrawTextLayoutLineMetrics m;
|
||||
|
||||
// only clip the text, not the guides
|
||||
uiDrawSave(p->Context);
|
||||
|
||||
path = uiDrawNewPath(uiDrawFillModeWinding);
|
||||
uiDrawPathAddRectangle(path, margins, margins,
|
||||
p->AreaWidth - 2 * margins,
|
||||
p->AreaHeight - 2 * margins);
|
||||
uiDrawPathEnd(path);
|
||||
uiDrawClip(p->Context, path);
|
||||
uiDrawFreePath(path);
|
||||
|
||||
params.Width = p->AreaWidth - 2 * margins;
|
||||
layout = uiDrawNewTextLayout(¶ms);
|
||||
uiDrawText(p->Context, layout, margins, margins);
|
||||
|
||||
uiDrawRestore(p->Context);
|
||||
|
||||
if (caretLine == -1) {
|
||||
caretLine = uiDrawTextLayoutNumLines(layout) - 1;
|
||||
caretPos = uiAttributedStringLen(attrstr);
|
||||
}
|
||||
uiDrawCaret(p->Context, margins, margins,
|
||||
layout, caretPos, &caretLine);
|
||||
|
||||
if (uiCheckboxChecked(showLineBounds)) {
|
||||
int i, n;
|
||||
int fill = 0;
|
||||
|
||||
n = uiDrawTextLayoutNumLines(layout);
|
||||
for (i = 0; i < n; i++) {
|
||||
uiDrawTextLayoutLineGetMetrics(layout, i, &m);
|
||||
path = uiDrawNewPath(uiDrawFillModeWinding);
|
||||
uiDrawPathAddRectangle(path, margins + m.X, margins + m.Y,
|
||||
m.Width, m.Height);
|
||||
uiDrawPathEnd(path);
|
||||
uiDrawFill(p->Context, path, fillBrushes + fill);
|
||||
uiDrawFreePath(path);
|
||||
fill = (fill + 1) % 4;
|
||||
}
|
||||
}
|
||||
|
||||
uiDrawFreeTextLayout(layout);
|
||||
}
|
||||
|
||||
static void mouse(uiAreaMouseEvent *e)
|
||||
{
|
||||
uiDrawTextLayout *layout;
|
||||
char labelText[128];
|
||||
|
||||
if (e->Down != 1)
|
||||
return;
|
||||
|
||||
params.Width = e->AreaWidth - 2 * margins;
|
||||
layout = uiDrawNewTextLayout(¶ms);
|
||||
uiDrawTextLayoutHitTest(layout,
|
||||
e->X - margins, e->Y - margins,
|
||||
&caretPos, &caretLine);
|
||||
uiDrawFreeTextLayout(layout);
|
||||
|
||||
// TODO move this into the draw handler so it is reflected by keyboard-based position changes
|
||||
// urgh %zd is not supported by MSVC with sprintf()
|
||||
// TODO get that part in test/ about having no other option
|
||||
sprintf(labelText, "pos %d line %d",
|
||||
(int) caretPos, caretLine);
|
||||
uiLabelSetText(caretLabel, labelText);
|
||||
|
||||
redraw();
|
||||
}
|
||||
|
||||
static int key(uiAreaKeyEvent *e)
|
||||
{
|
||||
size_t grapheme;
|
||||
|
||||
if (e->Up)
|
||||
return 0;
|
||||
if (e->Key != 0)
|
||||
return 0;
|
||||
switch (e->ExtKey) {
|
||||
case uiExtKeyUp:
|
||||
// TODO
|
||||
return 1;
|
||||
case uiExtKeyDown:
|
||||
// TODO
|
||||
return 1;
|
||||
case uiExtKeyLeft:
|
||||
grapheme = uiAttributedStringByteIndexToGrapheme(attrstr, caretPos);
|
||||
if (grapheme == 0)
|
||||
return 0;
|
||||
grapheme--;
|
||||
caretPos = uiAttributedStringGraphemeToByteIndex(attrstr, grapheme);
|
||||
redraw();
|
||||
return 1;
|
||||
case uiExtKeyRight:
|
||||
grapheme = uiAttributedStringByteIndexToGrapheme(attrstr, caretPos);
|
||||
if (grapheme == uiAttributedStringNumGraphemes(attrstr))
|
||||
return 0;
|
||||
grapheme++;
|
||||
caretPos = uiAttributedStringGraphemeToByteIndex(attrstr, grapheme);
|
||||
redraw();
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct example hitTestExample;
|
||||
|
||||
// TODO share?
|
||||
static void checkboxChecked(uiCheckbox *c, void *data)
|
||||
{
|
||||
redraw();
|
||||
}
|
||||
|
||||
static void changeFont(uiFontButton *b, void *data)
|
||||
{
|
||||
if (defaultFont.Family != fontFamily)
|
||||
uiFreeText(defaultFont.Family);
|
||||
// TODO rename defaultFont
|
||||
uiFontButtonFont(fontButton, &defaultFont);
|
||||
printf("{\n\tfamily: %s\n\tsize: %g\n\tweight: %d\n\titalic: %d\n\tstretch: %d\n}\n",
|
||||
defaultFont.Family,
|
||||
defaultFont.Size,
|
||||
(int) (defaultFont.Weight),
|
||||
(int) (defaultFont.Italic),
|
||||
(int) (defaultFont.Stretch));
|
||||
redraw();
|
||||
}
|
||||
|
||||
static void changeTextAlign(uiCombobox *c, void *data)
|
||||
{
|
||||
// note the order of the items added below
|
||||
params.Align = (uiDrawTextAlign) uiComboboxSelected(textAlign);
|
||||
redraw();
|
||||
}
|
||||
|
||||
// TODO share?
|
||||
static uiCheckbox *newCheckbox(uiBox *box, const char *text)
|
||||
{
|
||||
uiCheckbox *c;
|
||||
|
||||
c = uiNewCheckbox(text);
|
||||
uiCheckboxOnToggled(c, checkboxChecked, NULL);
|
||||
uiBoxAppend(box, uiControl(c), 0);
|
||||
return c;
|
||||
}
|
||||
|
||||
struct example *mkEmptyStringExample(void)
|
||||
{
|
||||
panel = uiNewHorizontalBox();
|
||||
vbox = uiNewVerticalBox();
|
||||
// TODO the second vbox causes this not to stretch at least on OS X
|
||||
uiBoxAppend(panel, uiControl(vbox), 1);
|
||||
caretLabel = uiNewLabel("Caret information is shown here");
|
||||
uiBoxAppend(vbox, uiControl(caretLabel), 0);
|
||||
showLineBounds = newCheckbox(vbox, "Show Line Bounds (for debugging metrics)");
|
||||
vbox = uiNewVerticalBox();
|
||||
uiBoxAppend(panel, uiControl(vbox), 0);
|
||||
fontButton = uiNewFontButton();
|
||||
uiFontButtonOnChanged(fontButton, changeFont, NULL);
|
||||
// TODO set the font button to the current defaultFont
|
||||
uiBoxAppend(vbox, uiControl(fontButton), 0);
|
||||
textAlign = uiNewCombobox();
|
||||
// note that these are in the order in the enum
|
||||
uiComboboxAppend(textAlign, "Left");
|
||||
uiComboboxAppend(textAlign, "Center");
|
||||
uiComboboxAppend(textAlign, "Right");
|
||||
uiComboboxOnSelected(textAlign, changeTextAlign, NULL);
|
||||
uiBoxAppend(vbox, uiControl(textAlign), 0);
|
||||
|
||||
hitTestExample.name = "Empty String";
|
||||
hitTestExample.panel = uiControl(panel);
|
||||
hitTestExample.draw = draw;
|
||||
hitTestExample.mouse = mouse;
|
||||
hitTestExample.key = key;
|
||||
|
||||
attrstr = uiNewAttributedString(text);
|
||||
params.String = attrstr;
|
||||
params.DefaultFont = &defaultFont;
|
||||
params.Align = uiDrawTextAlignLeft;
|
||||
|
||||
return &hitTestExample;
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
// 20 january 2017
|
||||
#include "drawtext.h"
|
||||
|
||||
// TODO double-check ligatures on all platforms to make sure we can place the cursor at the right place
|
||||
// TODO using the arrow keys allows us to walk back to the end of the line on some platforms (TODO which?); IIRC arrow keys shouldn't do that
|
||||
// TODO make sure to check the cursor positions of RTL on all platforms
|
||||
|
||||
static const char *text =
|
||||
"Each of the glyphs an end user interacts with are called graphemes. "
|
||||
"If you enter a byte range in the text boxes below and click the button, you can see the blue box move to surround that byte range, as well as what the actual byte range necessary is. "
|
||||
// TODO rephrase this; I don't think this code will use those grapheme functions...
|
||||
"You'll also see the index of the first grapheme; uiAttributedString has facilities for converting between UTF-8 code points and grapheme indices. "
|
||||
"Additionally, click on the string to move the caret. Watch the status text at the bottom change too. "
|
||||
"That being said: "
|
||||
"\xC3\x93O\xCC\x81 (combining accents) "
|
||||
"A\xCC\xAA\xEF\xB8\xA0 (multiple combining characters) "
|
||||
"\xE2\x80\xAE#\xE2\x80\xAC (RTL glyph) "
|
||||
"\xF0\x9F\x92\xBB (non-BMP character) "
|
||||
"\xF0\x9F\x92\xBB\xCC\x80 (combined non-BMP character; may render strangely) "
|
||||
"";
|
||||
static char fontFamily[] = "Helvetica";
|
||||
static uiDrawFontDescriptor defaultFont = {
|
||||
.Family = fontFamily,
|
||||
.Size = 14,
|
||||
.Weight = uiDrawTextWeightNormal,
|
||||
.Italic = uiDrawTextItalicNormal,
|
||||
.Stretch = uiDrawTextStretchNormal,
|
||||
};
|
||||
static uiAttributedString *attrstr;
|
||||
static uiDrawTextLayoutParams params;
|
||||
|
||||
#define margins 10
|
||||
|
||||
static uiBox *panel;
|
||||
static uiBox *vbox;
|
||||
static uiLabel *caretLabel;
|
||||
static uiCheckbox *showLineBounds;
|
||||
static uiFontButton *fontButton;
|
||||
static uiCombobox *textAlign;
|
||||
|
||||
static int caretLine = -1;
|
||||
static size_t caretPos;
|
||||
|
||||
// TODO should be const?
|
||||
static uiDrawBrush fillBrushes[4] = {
|
||||
{
|
||||
.Type = uiDrawBrushTypeSolid,
|
||||
.R = 1.0,
|
||||
.G = 0.0,
|
||||
.B = 0.0,
|
||||
.A = 0.5,
|
||||
},
|
||||
{
|
||||
.Type = uiDrawBrushTypeSolid,
|
||||
.R = 0.0,
|
||||
.G = 1.0,
|
||||
.B = 0.0,
|
||||
.A = 0.5,
|
||||
},
|
||||
{
|
||||
.Type = uiDrawBrushTypeSolid,
|
||||
.R = 0.0,
|
||||
.G = 0.0,
|
||||
.B = 1.0,
|
||||
.A = 0.5,
|
||||
},
|
||||
{
|
||||
.Type = uiDrawBrushTypeSolid,
|
||||
.R = 0.0,
|
||||
.G = 1.0,
|
||||
.B = 1.0,
|
||||
.A = 0.5,
|
||||
},
|
||||
};
|
||||
|
||||
static void draw(uiAreaDrawParams *p)
|
||||
{
|
||||
uiDrawPath *path;
|
||||
uiDrawTextLayout *layout;
|
||||
uiDrawTextLayoutLineMetrics m;
|
||||
|
||||
// only clip the text, not the guides
|
||||
uiDrawSave(p->Context);
|
||||
|
||||
path = uiDrawNewPath(uiDrawFillModeWinding);
|
||||
uiDrawPathAddRectangle(path, margins, margins,
|
||||
p->AreaWidth - 2 * margins,
|
||||
p->AreaHeight - 2 * margins);
|
||||
uiDrawPathEnd(path);
|
||||
uiDrawClip(p->Context, path);
|
||||
uiDrawFreePath(path);
|
||||
|
||||
params.Width = p->AreaWidth - 2 * margins;
|
||||
layout = uiDrawNewTextLayout(¶ms);
|
||||
uiDrawText(p->Context, layout, margins, margins);
|
||||
|
||||
uiDrawRestore(p->Context);
|
||||
|
||||
if (caretLine == -1) {
|
||||
caretLine = uiDrawTextLayoutNumLines(layout) - 1;
|
||||
caretPos = uiAttributedStringLen(attrstr);
|
||||
}
|
||||
uiDrawCaret(p->Context, margins, margins,
|
||||
layout, caretPos, &caretLine);
|
||||
|
||||
if (uiCheckboxChecked(showLineBounds)) {
|
||||
int i, n;
|
||||
int fill = 0;
|
||||
|
||||
n = uiDrawTextLayoutNumLines(layout);
|
||||
for (i = 0; i < n; i++) {
|
||||
uiDrawTextLayoutLineGetMetrics(layout, i, &m);
|
||||
path = uiDrawNewPath(uiDrawFillModeWinding);
|
||||
uiDrawPathAddRectangle(path, margins + m.X, margins + m.Y,
|
||||
m.Width, m.Height);
|
||||
uiDrawPathEnd(path);
|
||||
uiDrawFill(p->Context, path, fillBrushes + fill);
|
||||
uiDrawFreePath(path);
|
||||
fill = (fill + 1) % 4;
|
||||
}
|
||||
}
|
||||
|
||||
uiDrawFreeTextLayout(layout);
|
||||
}
|
||||
|
||||
static void mouse(uiAreaMouseEvent *e)
|
||||
{
|
||||
uiDrawTextLayout *layout;
|
||||
char labelText[128];
|
||||
|
||||
if (e->Down != 1)
|
||||
return;
|
||||
|
||||
params.Width = e->AreaWidth - 2 * margins;
|
||||
layout = uiDrawNewTextLayout(¶ms);
|
||||
uiDrawTextLayoutHitTest(layout,
|
||||
e->X - margins, e->Y - margins,
|
||||
&caretPos, &caretLine);
|
||||
uiDrawFreeTextLayout(layout);
|
||||
|
||||
// TODO move this into the draw handler so it is reflected by keyboard-based position changes
|
||||
// urgh %zd is not supported by MSVC with sprintf()
|
||||
// TODO get that part in test/ about having no other option
|
||||
sprintf(labelText, "pos %d line %d",
|
||||
(int) caretPos, caretLine);
|
||||
uiLabelSetText(caretLabel, labelText);
|
||||
|
||||
redraw();
|
||||
}
|
||||
|
||||
static int key(uiAreaKeyEvent *e)
|
||||
{
|
||||
size_t grapheme;
|
||||
|
||||
if (e->Up)
|
||||
return 0;
|
||||
if (e->Key != 0)
|
||||
return 0;
|
||||
switch (e->ExtKey) {
|
||||
case uiExtKeyUp:
|
||||
// TODO
|
||||
return 1;
|
||||
case uiExtKeyDown:
|
||||
// TODO
|
||||
return 1;
|
||||
case uiExtKeyLeft:
|
||||
grapheme = uiAttributedStringByteIndexToGrapheme(attrstr, caretPos);
|
||||
if (grapheme == 0)
|
||||
return 0;
|
||||
grapheme--;
|
||||
caretPos = uiAttributedStringGraphemeToByteIndex(attrstr, grapheme);
|
||||
redraw();
|
||||
return 1;
|
||||
case uiExtKeyRight:
|
||||
grapheme = uiAttributedStringByteIndexToGrapheme(attrstr, caretPos);
|
||||
if (grapheme == uiAttributedStringNumGraphemes(attrstr))
|
||||
return 0;
|
||||
grapheme++;
|
||||
caretPos = uiAttributedStringGraphemeToByteIndex(attrstr, grapheme);
|
||||
redraw();
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct example hitTestExample;
|
||||
|
||||
// TODO share?
|
||||
static void checkboxChecked(uiCheckbox *c, void *data)
|
||||
{
|
||||
redraw();
|
||||
}
|
||||
|
||||
static void changeFont(uiFontButton *b, void *data)
|
||||
{
|
||||
if (defaultFont.Family != fontFamily)
|
||||
uiFreeText(defaultFont.Family);
|
||||
// TODO rename defaultFont
|
||||
uiFontButtonFont(fontButton, &defaultFont);
|
||||
printf("{\n\tfamily: %s\n\tsize: %g\n\tweight: %d\n\titalic: %d\n\tstretch: %d\n}\n",
|
||||
defaultFont.Family,
|
||||
defaultFont.Size,
|
||||
(int) (defaultFont.Weight),
|
||||
(int) (defaultFont.Italic),
|
||||
(int) (defaultFont.Stretch));
|
||||
redraw();
|
||||
}
|
||||
|
||||
static void changeTextAlign(uiCombobox *c, void *data)
|
||||
{
|
||||
// note the order of the items added below
|
||||
params.Align = (uiDrawTextAlign) uiComboboxSelected(textAlign);
|
||||
redraw();
|
||||
}
|
||||
|
||||
// TODO share?
|
||||
static uiCheckbox *newCheckbox(uiBox *box, const char *text)
|
||||
{
|
||||
uiCheckbox *c;
|
||||
|
||||
c = uiNewCheckbox(text);
|
||||
uiCheckboxOnToggled(c, checkboxChecked, NULL);
|
||||
uiBoxAppend(box, uiControl(c), 0);
|
||||
return c;
|
||||
}
|
||||
|
||||
struct example *mkHitTestExample(void)
|
||||
{
|
||||
panel = uiNewHorizontalBox();
|
||||
vbox = uiNewVerticalBox();
|
||||
// TODO the second vbox causes this not to stretch at least on OS X
|
||||
uiBoxAppend(panel, uiControl(vbox), 1);
|
||||
caretLabel = uiNewLabel("Caret information is shown here");
|
||||
uiBoxAppend(vbox, uiControl(caretLabel), 0);
|
||||
showLineBounds = newCheckbox(vbox, "Show Line Bounds (for debugging metrics)");
|
||||
vbox = uiNewVerticalBox();
|
||||
uiBoxAppend(panel, uiControl(vbox), 0);
|
||||
fontButton = uiNewFontButton();
|
||||
uiFontButtonOnChanged(fontButton, changeFont, NULL);
|
||||
// TODO set the font button to the current defaultFont
|
||||
uiBoxAppend(vbox, uiControl(fontButton), 0);
|
||||
textAlign = uiNewCombobox();
|
||||
// note that these are in the order in the enum
|
||||
uiComboboxAppend(textAlign, "Left");
|
||||
uiComboboxAppend(textAlign, "Center");
|
||||
uiComboboxAppend(textAlign, "Right");
|
||||
uiComboboxOnSelected(textAlign, changeTextAlign, NULL);
|
||||
uiBoxAppend(vbox, uiControl(textAlign), 0);
|
||||
|
||||
hitTestExample.name = "Hit-Testing and Grapheme Boundaries";
|
||||
hitTestExample.panel = uiControl(panel);
|
||||
hitTestExample.draw = draw;
|
||||
hitTestExample.mouse = mouse;
|
||||
hitTestExample.key = key;
|
||||
|
||||
attrstr = uiNewAttributedString(text);
|
||||
params.String = attrstr;
|
||||
params.DefaultFont = &defaultFont;
|
||||
params.Align = uiDrawTextAlignLeft;
|
||||
|
||||
return &hitTestExample;
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,136 @@
|
||||
// 17 january 2017
|
||||
#include "drawtext.h"
|
||||
|
||||
static uiWindow *mainwin;
|
||||
static uiBox *box;
|
||||
static uiCombobox *exampleList;
|
||||
static uiArea *area;
|
||||
static uiAreaHandler handler;
|
||||
|
||||
#define nExamples 20
|
||||
static struct example *examples[nExamples];
|
||||
static int curExample = 0;
|
||||
|
||||
static void onExampleChanged(uiCombobox *c, void *data)
|
||||
{
|
||||
uiControlHide(examples[curExample]->panel);
|
||||
curExample = uiComboboxSelected(exampleList);
|
||||
uiControlShow(examples[curExample]->panel);
|
||||
redraw();
|
||||
}
|
||||
|
||||
void redraw(void)
|
||||
{
|
||||
uiAreaQueueRedrawAll(area);
|
||||
}
|
||||
|
||||
static void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p)
|
||||
{
|
||||
examples[curExample]->draw(p);
|
||||
}
|
||||
|
||||
static void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e)
|
||||
{
|
||||
if (examples[curExample]->mouse != NULL)
|
||||
examples[curExample]->mouse(e);
|
||||
}
|
||||
|
||||
static void handlerMouseCrossed(uiAreaHandler *ah, uiArea *a, int left)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
static void handlerDragBroken(uiAreaHandler *ah, uiArea *a)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
static int handlerKeyEvent(uiAreaHandler *ah, uiArea *a, uiAreaKeyEvent *e)
|
||||
{
|
||||
if (examples[curExample]->key != NULL)
|
||||
return examples[curExample]->key(e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int onClosing(uiWindow *w, void *data)
|
||||
{
|
||||
uiControlDestroy(uiControl(mainwin));
|
||||
uiQuit();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int shouldQuit(void *data)
|
||||
{
|
||||
uiControlDestroy(uiControl(mainwin));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
uiInitOptions o;
|
||||
const char *err;
|
||||
int n;
|
||||
|
||||
handler.Draw = handlerDraw;
|
||||
handler.MouseEvent = handlerMouseEvent;
|
||||
handler.MouseCrossed = handlerMouseCrossed;
|
||||
handler.DragBroken = handlerDragBroken;
|
||||
handler.KeyEvent = handlerKeyEvent;
|
||||
|
||||
memset(&o, 0, sizeof (uiInitOptions));
|
||||
err = uiInit(&o);
|
||||
if (err != NULL) {
|
||||
fprintf(stderr, "error initializing ui: %s\n", err);
|
||||
uiFreeInitError(err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
uiOnShouldQuit(shouldQuit, NULL);
|
||||
|
||||
mainwin = uiNewWindow("libui Text-Drawing Example", 640, 480, 1);
|
||||
uiWindowOnClosing(mainwin, onClosing, NULL);
|
||||
|
||||
box = uiNewVerticalBox();
|
||||
uiWindowSetChild(mainwin, uiControl(box));
|
||||
|
||||
exampleList = uiNewCombobox();
|
||||
uiBoxAppend(box, uiControl(exampleList), 0);
|
||||
|
||||
area = uiNewArea(&handler);
|
||||
uiBoxAppend(box, uiControl(area), 1);
|
||||
|
||||
n = 0;
|
||||
examples[n] = mkBasicExample();
|
||||
uiComboboxAppend(exampleList, examples[n]->name);
|
||||
uiControlHide(examples[n]->panel);
|
||||
uiBoxAppend(box, examples[n]->panel, 0);
|
||||
n++;
|
||||
examples[n] = mkHitTestExample();
|
||||
uiComboboxAppend(exampleList, examples[n]->name);
|
||||
uiControlHide(examples[n]->panel);
|
||||
uiBoxAppend(box, examples[n]->panel, 0);
|
||||
n++;
|
||||
examples[n] = mkAttributesExample();
|
||||
uiComboboxAppend(exampleList, examples[n]->name);
|
||||
uiControlHide(examples[n]->panel);
|
||||
uiBoxAppend(box, examples[n]->panel, 0);
|
||||
n++;
|
||||
examples[n] = mkEmptyStringExample();
|
||||
uiComboboxAppend(exampleList, examples[n]->name);
|
||||
uiControlHide(examples[n]->panel);
|
||||
uiBoxAppend(box, examples[n]->panel, 0);
|
||||
n++;
|
||||
// and set things up for the initial state
|
||||
uiComboboxSetSelected(exampleList, 0);
|
||||
uiComboboxOnSelected(exampleList, onExampleChanged, NULL);
|
||||
// and set up the first one
|
||||
onExampleChanged(NULL, NULL);
|
||||
|
||||
uiControlShow(uiControl(mainwin));
|
||||
uiMain();
|
||||
|
||||
// TODO free examples
|
||||
|
||||
uiUninit();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# 3 june 2016
|
||||
|
||||
_add_example(drawtext
|
||||
drawtext/attributes.c
|
||||
drawtext/basic.c
|
||||
drawtext/emptystr_hittest.c
|
||||
drawtext/hittest.c
|
||||
drawtext/main.c
|
||||
${_EXAMPLE_RESOURCES_RC}
|
||||
)
|
||||
target_include_directories(drawtext
|
||||
PRIVATE drawtext)
|
||||
|
||||
_add_example(opentype
|
||||
opentype/main.c
|
||||
${_EXAMPLE_RESOURCES_RC}
|
||||
)
|
||||
target_include_directories(opentype
|
||||
PRIVATE opentype)
|
||||
@@ -0,0 +1,201 @@
|
||||
// 10 june 2017
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include "../../ui.h"
|
||||
|
||||
// TODO the grid simply flat out does not work on OS X
|
||||
|
||||
uiWindow *mainwin;
|
||||
uiFontButton *fontButton;
|
||||
uiEntry *textEntry;
|
||||
uiCheckbox *nullFeatures;
|
||||
uiArea *area;
|
||||
|
||||
uiAttributedString *attrstr = NULL;
|
||||
|
||||
static void remakeAttrStr(void)
|
||||
{
|
||||
char *text;
|
||||
uiOpenTypeFeatures *otf;
|
||||
uiAttributeSpec spec;
|
||||
|
||||
if (attrstr != NULL)
|
||||
uiFreeAttributedString(attrstr);
|
||||
|
||||
text = uiEntryText(textEntry);
|
||||
attrstr = uiNewAttributedString(text);
|
||||
uiFreeText(text);
|
||||
|
||||
if (!uiCheckboxChecked(nullFeatures)) {
|
||||
otf = uiNewOpenTypeFeatures();
|
||||
// TODO
|
||||
spec.Type = uiAttributeFeatures;
|
||||
spec.Features = otf;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec,
|
||||
0, uiAttributedStringLen(attrstr));
|
||||
// and uiAttributedString copied otf
|
||||
uiFreeOpenTypeFeatures(otf);
|
||||
}
|
||||
|
||||
uiAreaQueueRedrawAll(area);
|
||||
}
|
||||
|
||||
// TODO make a variable of main()? in all programs?
|
||||
static uiAreaHandler handler;
|
||||
|
||||
static void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p)
|
||||
{
|
||||
uiDrawTextLayout *layout;
|
||||
uiDrawTextLayoutParams lp;
|
||||
uiDrawFontDescriptor desc;
|
||||
|
||||
memset(&lp, 0, sizeof (uiDrawTextLayoutParams));
|
||||
lp.String = attrstr;
|
||||
uiFontButtonFont(fontButton, &desc);
|
||||
lp.DefaultFont = &desc;
|
||||
lp.Width = p->AreaWidth;
|
||||
lp.Align = uiDrawTextAlignLeft;
|
||||
layout = uiDrawNewTextLayout(&lp);
|
||||
uiDrawText(p->Context, layout, 0, 0);
|
||||
uiDrawFreeTextLayout(layout);
|
||||
}
|
||||
|
||||
static void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
static void handlerMouseCrossed(uiAreaHandler *ah, uiArea *a, int left)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
static void handlerDragBroken(uiAreaHandler *ah, uiArea *a)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
static int handlerKeyEvent(uiAreaHandler *ah, uiArea *a, uiAreaKeyEvent *e)
|
||||
{
|
||||
// reject all keys
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void onFontChanged(uiFontButton *b, void *data)
|
||||
{
|
||||
remakeAttrStr();
|
||||
}
|
||||
|
||||
static void onTextChanged(uiEntry *e, void *data)
|
||||
{
|
||||
remakeAttrStr();
|
||||
}
|
||||
|
||||
static void onNULLToggled(uiCheckbox *c, void *data)
|
||||
{
|
||||
remakeAttrStr();
|
||||
}
|
||||
|
||||
static int onClosing(uiWindow *w, void *data)
|
||||
{
|
||||
// TODO change the others to be like this? (the others destroy here rather than later)
|
||||
// TODO move this below uiQuit()?
|
||||
uiControlHide(uiControl(w));
|
||||
uiQuit();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int shouldQuit(void *data)
|
||||
{
|
||||
uiControlDestroy(uiControl(mainwin));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
uiInitOptions o;
|
||||
const char *err;
|
||||
uiGrid *grid;
|
||||
uiBox *vbox;
|
||||
|
||||
handler.Draw = handlerDraw;
|
||||
handler.MouseEvent = handlerMouseEvent;
|
||||
handler.MouseCrossed = handlerMouseCrossed;
|
||||
handler.DragBroken = handlerDragBroken;
|
||||
handler.KeyEvent = handlerKeyEvent;
|
||||
|
||||
memset(&o, 0, sizeof (uiInitOptions));
|
||||
err = uiInit(&o);
|
||||
if (err != NULL) {
|
||||
fprintf(stderr, "error initializing ui: %s\n", err);
|
||||
uiFreeInitError(err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
uiOnShouldQuit(shouldQuit, NULL);
|
||||
|
||||
// TODO 800x600? the size of the GTK+ example?
|
||||
mainwin = uiNewWindow("libui OpenType Features Example", 640, 480, 1);
|
||||
uiWindowSetMargined(mainwin, 1);
|
||||
uiWindowOnClosing(mainwin, onClosing, NULL);
|
||||
|
||||
grid = uiNewGrid();
|
||||
uiGridSetPadded(grid, 1);
|
||||
uiWindowSetChild(mainwin, uiControl(grid));
|
||||
|
||||
fontButton = uiNewFontButton();
|
||||
uiFontButtonOnChanged(fontButton, onFontChanged, NULL);
|
||||
uiGridAppend(grid, uiControl(fontButton),
|
||||
0, 0, 1, 1,
|
||||
// TODO are these Y values correct?
|
||||
0, uiAlignFill, 0, uiAlignCenter);
|
||||
|
||||
textEntry = uiNewEntry();
|
||||
uiEntrySetText(textEntry, "afford afire aflight");
|
||||
uiEntryOnChanged(textEntry, onTextChanged, NULL);
|
||||
uiGridAppend(grid, uiControl(textEntry),
|
||||
1, 0, 1, 1,
|
||||
// TODO are these Y values correct too?
|
||||
// TODO add a baseline align? or a form align?
|
||||
1, uiAlignFill, 0, uiAlignCenter);
|
||||
|
||||
vbox = uiNewVerticalBox();
|
||||
uiBoxSetPadded(vbox, 1);
|
||||
uiGridAppend(grid, uiControl(vbox),
|
||||
0, 1, 1, 1,
|
||||
0, uiAlignFill, 1, uiAlignFill);
|
||||
|
||||
nullFeatures = uiNewCheckbox("NULL uiOpenTypeFeatures");
|
||||
uiCheckboxOnToggled(nullFeatures, onNULLToggled, NULL);
|
||||
uiBoxAppend(vbox, uiControl(nullFeatures), 0);
|
||||
|
||||
// TODO separator (if other stuff isn't a tab)
|
||||
|
||||
// TODO needed for this to be testable on os x without rewriting everything again
|
||||
{
|
||||
int x;
|
||||
|
||||
for (x = 0; x < 10; x++)
|
||||
uiBoxAppend(vbox, uiControl(uiNewEntry()), 0);
|
||||
}
|
||||
|
||||
// TODO other stuff
|
||||
|
||||
area = uiNewArea(&handler);
|
||||
uiGridAppend(grid, uiControl(area),
|
||||
1, 1, 1, 1,
|
||||
1, uiAlignFill, 1, uiAlignFill);
|
||||
|
||||
// and set up the initial draw
|
||||
remakeAttrStr();
|
||||
|
||||
uiControlShow(uiControl(mainwin));
|
||||
uiMain();
|
||||
|
||||
uiControlDestroy(uiControl(mainwin));
|
||||
uiFreeAttributedString(attrstr);
|
||||
uiUninit();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
every rule in ui_darwin.h
|
||||
SetParent must be followed by SetSuperview and SyncEnableState
|
||||
TODO can child cache it?
|
||||
adding a child must be followed by a call to SyncEnableState
|
||||
SyncEnableState() must call ShouldStopSyncEnableState() first thing
|
||||
Enable() and Disable() must call SyncEnableState() AFTER CHANGING WHAT Enabled() WILL RETURN
|
||||
@@ -0,0 +1,3 @@
|
||||
every rule in ui_unix.h
|
||||
SetParent must be followed by SetContainer
|
||||
TODO can child cache it?
|
||||
@@ -0,0 +1,25 @@
|
||||
2016-05-26 22:38:12.877 svtest[81790:544681] backgroundColor NSNamedColorSpace System controlColor
|
||||
2016-05-26 22:38:12.877 svtest[81790:544681] drawsBackground 1
|
||||
2016-05-26 22:38:12.877 svtest[81790:544681] borderType 2
|
||||
2016-05-26 22:38:12.877 svtest[81790:544681] documentCursor (null)
|
||||
2016-05-26 22:38:12.877 svtest[81790:544681] hasHorizontalScroller 1
|
||||
2016-05-26 22:38:12.877 svtest[81790:544681] hasVerticalScroller 1
|
||||
2016-05-26 22:38:12.877 svtest[81790:544681] autohidesScrollers 0
|
||||
2016-05-26 22:38:12.877 svtest[81790:544681] hasHorizontalRuler 0
|
||||
2016-05-26 22:38:12.878 svtest[81790:544681] hasVerticalRuler 0
|
||||
2016-05-26 22:38:12.878 svtest[81790:544681] rulersVisible 0
|
||||
2016-05-26 22:38:12.878 svtest[81790:544681] 10.10 autoAdjContentInsets 1
|
||||
2016-05-26 22:38:12.878 svtest[81790:544681] scrollerKnobStyle 0
|
||||
2016-05-26 22:38:12.878 svtest[81790:544681] scrollerStyle 1
|
||||
2016-05-26 22:38:12.878 svtest[81790:544681] horizontalLineScroll 10
|
||||
2016-05-26 22:38:12.878 svtest[81790:544681] verticalLineScroll 10
|
||||
2016-05-26 22:38:12.886 svtest[81790:544681] horizontalPageScroll 10
|
||||
2016-05-26 22:38:12.886 svtest[81790:544681] verticalPageScroll 10
|
||||
2016-05-26 22:38:12.886 svtest[81790:544681] scrollsDynamically 1
|
||||
2016-05-26 22:38:12.886 svtest[81790:544681] findBarPosition 1
|
||||
2016-05-26 22:38:12.886 svtest[81790:544681] usesPredomAxisScroll 0
|
||||
2016-05-26 22:38:12.886 svtest[81790:544681] horizontalElasticity 0
|
||||
2016-05-26 22:38:12.886 svtest[81790:544681] verticalElasticity 0
|
||||
2016-05-26 22:38:12.887 svtest[81790:544681] 10.8 allowsMagnification 0
|
||||
2016-05-26 22:38:12.887 svtest[81790:544681] 10.8 maxMagnification 4
|
||||
2016-05-26 22:38:12.887 svtest[81790:544681] 10.8 minMagnification 0.25
|
||||
@@ -0,0 +1,25 @@
|
||||
backgroundColor NSNamedColorSpace System controlColor
|
||||
drawsBackground 1
|
||||
borderType 2
|
||||
documentCursor (null)
|
||||
hasHorizontalScroller 1
|
||||
hasVerticalScroller 1
|
||||
autohidesScrollers 0
|
||||
hasHorizontalRuler 0
|
||||
hasVerticalRuler 0
|
||||
rulersVisible 0
|
||||
10.10 autoAdjContentInsets 1
|
||||
scrollerKnobStyle 0
|
||||
scrollerStyle 1
|
||||
horizontalLineScroll 10
|
||||
verticalLineScroll 10
|
||||
horizontalPageScroll 10
|
||||
verticalPageScroll 10
|
||||
scrollsDynamically 1
|
||||
findBarPosition 1
|
||||
usesPredomAxisScroll 0
|
||||
horizontalElasticity 0
|
||||
verticalElasticity 0
|
||||
10.8 allowsMagnification 0
|
||||
10.8 maxMagnification 4
|
||||
10.8 minMagnification 0.25
|
||||
@@ -0,0 +1,25 @@
|
||||
2016-05-26 22:42:16.208 svtest[82103:547159] backgroundColor NSNamedColorSpace System controlBackgroundColor
|
||||
2016-05-26 22:42:16.208 svtest[82103:547159] drawsBackground 1
|
||||
2016-05-26 22:42:16.208 svtest[82103:547159] borderType 2
|
||||
2016-05-26 22:42:16.208 svtest[82103:547159] documentCursor (null)
|
||||
2016-05-26 22:42:16.209 svtest[82103:547159] hasHorizontalScroller 1
|
||||
2016-05-26 22:42:16.209 svtest[82103:547159] hasVerticalScroller 1
|
||||
2016-05-26 22:42:16.209 svtest[82103:547159] autohidesScrollers 1
|
||||
2016-05-26 22:42:16.209 svtest[82103:547159] hasHorizontalRuler 0
|
||||
2016-05-26 22:42:16.209 svtest[82103:547159] hasVerticalRuler 0
|
||||
2016-05-26 22:42:16.209 svtest[82103:547159] rulersVisible 0
|
||||
2016-05-26 22:42:16.209 svtest[82103:547159] 10.10 autoAdjContentInsets 1
|
||||
2016-05-26 22:42:16.209 svtest[82103:547159] scrollerKnobStyle 0
|
||||
2016-05-26 22:42:16.209 svtest[82103:547159] scrollerStyle 1
|
||||
2016-05-26 22:42:16.209 svtest[82103:547159] horizontalLineScroll 19
|
||||
2016-05-26 22:42:16.209 svtest[82103:547159] verticalLineScroll 19
|
||||
2016-05-26 22:42:16.217 svtest[82103:547159] horizontalPageScroll 10
|
||||
2016-05-26 22:42:16.218 svtest[82103:547159] verticalPageScroll 10
|
||||
2016-05-26 22:42:16.218 svtest[82103:547159] scrollsDynamically 1
|
||||
2016-05-26 22:42:16.218 svtest[82103:547159] findBarPosition 1
|
||||
2016-05-26 22:42:16.218 svtest[82103:547159] usesPredomAxisScroll 0
|
||||
2016-05-26 22:42:16.218 svtest[82103:547159] horizontalElasticity 0
|
||||
2016-05-26 22:42:16.218 svtest[82103:547159] verticalElasticity 0
|
||||
2016-05-26 22:42:16.218 svtest[82103:547159] 10.8 allowsMagnification 0
|
||||
2016-05-26 22:42:16.218 svtest[82103:547159] 10.8 maxMagnification 4
|
||||
2016-05-26 22:42:16.218 svtest[82103:547159] 10.8 minMagnification 0.25
|
||||
@@ -0,0 +1,25 @@
|
||||
backgroundColor NSNamedColorSpace System controlBackgroundColor
|
||||
drawsBackground 1
|
||||
borderType 2
|
||||
documentCursor (null)
|
||||
hasHorizontalScroller 1
|
||||
hasVerticalScroller 1
|
||||
autohidesScrollers 1
|
||||
hasHorizontalRuler 0
|
||||
hasVerticalRuler 0
|
||||
rulersVisible 0
|
||||
10.10 autoAdjContentInsets 1
|
||||
scrollerKnobStyle 0
|
||||
scrollerStyle 1
|
||||
horizontalLineScroll 19
|
||||
verticalLineScroll 19
|
||||
horizontalPageScroll 10
|
||||
verticalPageScroll 10
|
||||
scrollsDynamically 1
|
||||
findBarPosition 1
|
||||
usesPredomAxisScroll 0
|
||||
horizontalElasticity 0
|
||||
verticalElasticity 0
|
||||
10.8 allowsMagnification 0
|
||||
10.8 maxMagnification 4
|
||||
10.8 minMagnification 0.25
|
||||
@@ -0,0 +1,25 @@
|
||||
2016-05-26 22:43:58.600 svtest[82237:548359] backgroundColor (null)
|
||||
2016-05-26 22:43:58.600 svtest[82237:548359] drawsBackground 0
|
||||
2016-05-26 22:43:58.600 svtest[82237:548359] borderType 2
|
||||
2016-05-26 22:43:58.600 svtest[82237:548359] documentCursor (null)
|
||||
2016-05-26 22:43:58.600 svtest[82237:548359] hasHorizontalScroller 1
|
||||
2016-05-26 22:43:58.600 svtest[82237:548359] hasVerticalScroller 1
|
||||
2016-05-26 22:43:58.600 svtest[82237:548359] autohidesScrollers 1
|
||||
2016-05-26 22:43:58.600 svtest[82237:548359] hasHorizontalRuler 0
|
||||
2016-05-26 22:43:58.600 svtest[82237:548359] hasVerticalRuler 0
|
||||
2016-05-26 22:43:58.600 svtest[82237:548359] rulersVisible 0
|
||||
2016-05-26 22:43:58.601 svtest[82237:548359] 10.10 autoAdjContentInsets 1
|
||||
2016-05-26 22:43:58.601 svtest[82237:548359] scrollerKnobStyle 0
|
||||
2016-05-26 22:43:58.601 svtest[82237:548359] scrollerStyle 1
|
||||
2016-05-26 22:43:58.601 svtest[82237:548359] horizontalLineScroll 19
|
||||
2016-05-26 22:43:58.601 svtest[82237:548359] verticalLineScroll 19
|
||||
2016-05-26 22:43:58.645 svtest[82237:548359] horizontalPageScroll 10
|
||||
2016-05-26 22:43:58.645 svtest[82237:548359] verticalPageScroll 10
|
||||
2016-05-26 22:43:58.645 svtest[82237:548359] scrollsDynamically 1
|
||||
2016-05-26 22:43:58.645 svtest[82237:548359] findBarPosition 1
|
||||
2016-05-26 22:43:58.645 svtest[82237:548359] usesPredomAxisScroll 0
|
||||
2016-05-26 22:43:58.645 svtest[82237:548359] horizontalElasticity 0
|
||||
2016-05-26 22:43:58.646 svtest[82237:548359] verticalElasticity 0
|
||||
2016-05-26 22:43:58.646 svtest[82237:548359] 10.8 allowsMagnification 0
|
||||
2016-05-26 22:43:58.646 svtest[82237:548359] 10.8 maxMagnification 4
|
||||
2016-05-26 22:43:58.646 svtest[82237:548359] 10.8 minMagnification 0.25
|
||||
@@ -0,0 +1,25 @@
|
||||
backgroundColor (null)
|
||||
drawsBackground 0
|
||||
borderType 2
|
||||
documentCursor (null)
|
||||
hasHorizontalScroller 1
|
||||
hasVerticalScroller 1
|
||||
autohidesScrollers 1
|
||||
hasHorizontalRuler 0
|
||||
hasVerticalRuler 0
|
||||
rulersVisible 0
|
||||
10.10 autoAdjContentInsets 1
|
||||
scrollerKnobStyle 0
|
||||
scrollerStyle 1
|
||||
horizontalLineScroll 19
|
||||
verticalLineScroll 19
|
||||
horizontalPageScroll 10
|
||||
verticalPageScroll 10
|
||||
scrollsDynamically 1
|
||||
findBarPosition 1
|
||||
usesPredomAxisScroll 0
|
||||
horizontalElasticity 0
|
||||
verticalElasticity 0
|
||||
10.8 allowsMagnification 0
|
||||
10.8 maxMagnification 4
|
||||
10.8 minMagnification 0.25
|
||||
@@ -0,0 +1,25 @@
|
||||
2016-05-26 22:41:26.514 svtest[82032:546554] backgroundColor NSNamedColorSpace System controlBackgroundColor
|
||||
2016-05-26 22:41:26.514 svtest[82032:546554] drawsBackground 1
|
||||
2016-05-26 22:41:26.514 svtest[82032:546554] borderType 2
|
||||
2016-05-26 22:41:26.514 svtest[82032:546554] documentCursor (null)
|
||||
2016-05-26 22:41:26.515 svtest[82032:546554] hasHorizontalScroller 1
|
||||
2016-05-26 22:41:26.515 svtest[82032:546554] hasVerticalScroller 1
|
||||
2016-05-26 22:41:26.515 svtest[82032:546554] autohidesScrollers 1
|
||||
2016-05-26 22:41:26.515 svtest[82032:546554] hasHorizontalRuler 0
|
||||
2016-05-26 22:41:26.516 svtest[82032:546554] hasVerticalRuler 0
|
||||
2016-05-26 22:41:26.516 svtest[82032:546554] rulersVisible 0
|
||||
2016-05-26 22:41:26.516 svtest[82032:546554] 10.10 autoAdjContentInsets 1
|
||||
2016-05-26 22:41:26.516 svtest[82032:546554] scrollerKnobStyle 0
|
||||
2016-05-26 22:41:26.516 svtest[82032:546554] scrollerStyle 1
|
||||
2016-05-26 22:41:26.516 svtest[82032:546554] horizontalLineScroll 19
|
||||
2016-05-26 22:41:26.516 svtest[82032:546554] verticalLineScroll 19
|
||||
2016-05-26 22:41:26.528 svtest[82032:546554] horizontalPageScroll 10
|
||||
2016-05-26 22:41:26.528 svtest[82032:546554] verticalPageScroll 10
|
||||
2016-05-26 22:41:26.528 svtest[82032:546554] scrollsDynamically 1
|
||||
2016-05-26 22:41:26.528 svtest[82032:546554] findBarPosition 1
|
||||
2016-05-26 22:41:26.528 svtest[82032:546554] usesPredomAxisScroll 0
|
||||
2016-05-26 22:41:26.528 svtest[82032:546554] horizontalElasticity 0
|
||||
2016-05-26 22:41:26.528 svtest[82032:546554] verticalElasticity 0
|
||||
2016-05-26 22:41:26.528 svtest[82032:546554] 10.8 allowsMagnification 0
|
||||
2016-05-26 22:41:26.528 svtest[82032:546554] 10.8 maxMagnification 4
|
||||
2016-05-26 22:41:26.528 svtest[82032:546554] 10.8 minMagnification 0.25
|
||||
@@ -0,0 +1,25 @@
|
||||
backgroundColor NSNamedColorSpace System controlBackgroundColor
|
||||
drawsBackground 1
|
||||
borderType 2
|
||||
documentCursor (null)
|
||||
hasHorizontalScroller 1
|
||||
hasVerticalScroller 1
|
||||
autohidesScrollers 1
|
||||
hasHorizontalRuler 0
|
||||
hasVerticalRuler 0
|
||||
rulersVisible 0
|
||||
10.10 autoAdjContentInsets 1
|
||||
scrollerKnobStyle 0
|
||||
scrollerStyle 1
|
||||
horizontalLineScroll 19
|
||||
verticalLineScroll 19
|
||||
horizontalPageScroll 10
|
||||
verticalPageScroll 10
|
||||
scrollsDynamically 1
|
||||
findBarPosition 1
|
||||
usesPredomAxisScroll 0
|
||||
horizontalElasticity 0
|
||||
verticalElasticity 0
|
||||
10.8 allowsMagnification 0
|
||||
10.8 maxMagnification 4
|
||||
10.8 minMagnification 0.25
|
||||
@@ -0,0 +1,25 @@
|
||||
2016-05-26 22:40:02.050 svtest[81927:545793] backgroundColor NSCalibratedWhiteColorSpace 1 1
|
||||
2016-05-26 22:40:02.050 svtest[81927:545793] drawsBackground 1
|
||||
2016-05-26 22:40:02.051 svtest[81927:545793] borderType 2
|
||||
2016-05-26 22:40:02.052 svtest[81927:545793] documentCursor <NSCursor: 0x608000041f20>
|
||||
2016-05-26 22:40:02.052 svtest[81927:545793] hasHorizontalScroller 0
|
||||
2016-05-26 22:40:02.052 svtest[81927:545793] hasVerticalScroller 1
|
||||
2016-05-26 22:40:02.052 svtest[81927:545793] autohidesScrollers 0
|
||||
2016-05-26 22:40:02.052 svtest[81927:545793] hasHorizontalRuler 0
|
||||
2016-05-26 22:40:02.052 svtest[81927:545793] hasVerticalRuler 0
|
||||
2016-05-26 22:40:02.052 svtest[81927:545793] rulersVisible 0
|
||||
2016-05-26 22:40:02.052 svtest[81927:545793] 10.10 autoAdjContentInsets 1
|
||||
2016-05-26 22:40:02.052 svtest[81927:545793] scrollerKnobStyle 0
|
||||
2016-05-26 22:40:02.052 svtest[81927:545793] scrollerStyle 1
|
||||
2016-05-26 22:40:02.054 svtest[81927:545793] horizontalLineScroll 10
|
||||
2016-05-26 22:40:02.055 svtest[81927:545793] verticalLineScroll 10
|
||||
2016-05-26 22:40:02.062 svtest[81927:545793] horizontalPageScroll 10
|
||||
2016-05-26 22:40:02.062 svtest[81927:545793] verticalPageScroll 10
|
||||
2016-05-26 22:40:02.062 svtest[81927:545793] scrollsDynamically 1
|
||||
2016-05-26 22:40:02.062 svtest[81927:545793] findBarPosition 1
|
||||
2016-05-26 22:40:02.062 svtest[81927:545793] usesPredomAxisScroll 0
|
||||
2016-05-26 22:40:02.062 svtest[81927:545793] horizontalElasticity 0
|
||||
2016-05-26 22:40:02.062 svtest[81927:545793] verticalElasticity 0
|
||||
2016-05-26 22:40:02.062 svtest[81927:545793] 10.8 allowsMagnification 0
|
||||
2016-05-26 22:40:02.062 svtest[81927:545793] 10.8 maxMagnification 4
|
||||
2016-05-26 22:40:02.063 svtest[81927:545793] 10.8 minMagnification 0.25
|
||||
@@ -0,0 +1,25 @@
|
||||
backgroundColor NSCalibratedWhiteColorSpace 1 1
|
||||
drawsBackground 1
|
||||
borderType 2
|
||||
documentCursor <NSCursor: 0x608000041f20>
|
||||
hasHorizontalScroller 0
|
||||
hasVerticalScroller 1
|
||||
autohidesScrollers 0
|
||||
hasHorizontalRuler 0
|
||||
hasVerticalRuler 0
|
||||
rulersVisible 0
|
||||
10.10 autoAdjContentInsets 1
|
||||
scrollerKnobStyle 0
|
||||
scrollerStyle 1
|
||||
horizontalLineScroll 10
|
||||
verticalLineScroll 10
|
||||
horizontalPageScroll 10
|
||||
verticalPageScroll 10
|
||||
scrollsDynamically 1
|
||||
findBarPosition 1
|
||||
usesPredomAxisScroll 0
|
||||
horizontalElasticity 0
|
||||
verticalElasticity 0
|
||||
10.8 allowsMagnification 0
|
||||
10.8 maxMagnification 4
|
||||
10.8 minMagnification 0.25
|
||||
@@ -0,0 +1,80 @@
|
||||
// 18 october 2015
|
||||
#include "test.h"
|
||||
|
||||
// TODO manage the memory of the uiTableModel
|
||||
|
||||
static intmax_t nColumns = 4;
|
||||
static uiTableColumnType coltypes[] = {
|
||||
uiTableColumnText,
|
||||
uiTableColumnText,
|
||||
uiTableColumnCheckbox,
|
||||
uiTableColumnCheckbox,
|
||||
};
|
||||
|
||||
static intmax_t nRows = 6;
|
||||
|
||||
static intmax_t modelNumRows(uiTableModel *m, void *mData)
|
||||
{
|
||||
return nRows;
|
||||
}
|
||||
|
||||
void *modelCellValue(uiTableModel *m, void *mData, intmax_t row, intmax_t column)
|
||||
{
|
||||
char line[20];
|
||||
|
||||
line[0] = 'R';
|
||||
line[1] = 'o';
|
||||
line[2] = 'w';
|
||||
line[3] = ' ';
|
||||
line[4] = row + '0';
|
||||
line[5] = '\0';
|
||||
switch (column) {
|
||||
case 0:
|
||||
case 1:
|
||||
return uiTableModelFromString(line);
|
||||
case 2:
|
||||
return uiTableModelFromBool(row % 2 == 0);
|
||||
case 3:
|
||||
return uiTableModelFromBool(row % 3 == 0);
|
||||
}
|
||||
// TODO
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// TODO make this not need to be static
|
||||
uiTableModelSpec spec;
|
||||
|
||||
void modelSetCellValue(uiTableModel *m, void *mData, intmax_t row, intmax_t column, void *value)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
uiBox *makePage9(void)
|
||||
{
|
||||
uiBox *page9;
|
||||
uiTable *table;
|
||||
uiTableModel *model;
|
||||
uiTableColumnParams p;
|
||||
intmax_t i;
|
||||
|
||||
page9 = newVerticalBox();
|
||||
|
||||
table = uiNewTable();
|
||||
uiBoxAppend(page9, uiControl(table), 1);
|
||||
|
||||
spec.NumRows = modelNumRows;
|
||||
spec.CellValue = modelCellValue;
|
||||
spec.SetCellValue = modelSetCellValue;
|
||||
model = uiNewTableModel(nColumns, coltypes, &spec, NULL);
|
||||
uiTableSetModel(table, model);
|
||||
|
||||
for (i = 0; i < nColumns; i++) {
|
||||
p.Name = "Column";
|
||||
p.Type = coltypes[i];
|
||||
p.Mutable = i % 2 == 1;
|
||||
p.ValueColumn = i;
|
||||
uiTableAppendColumn(table, &p);
|
||||
}
|
||||
|
||||
return page9;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
typedef struct uiTable uiTable;
|
||||
typedef struct uiTableModel uiTableModel;
|
||||
typedef struct uiTableModelSpec uiTableModelSpec;
|
||||
typedef struct uiTableColumnParams uiTableColumnParams;
|
||||
typedef enum uiTableColumnType uiTableColumnType;
|
||||
typedef enum uiTableNotification uiTableNotification;
|
||||
|
||||
_UI_EXTERN uintmax_t uiTableType(void);
|
||||
#define uiTable(this) ((uiTable *) uiIsA((this), uiTableType(), 1))
|
||||
_UI_EXTERN void uiTableSetModel(uiTable *t, uiTableModel *m);
|
||||
_UI_EXTERN void uiTableAppendColumn(uiTable *t, uiTableColumnParams *p);
|
||||
_UI_EXTERN uiTable *uiNewTable(void);
|
||||
|
||||
enum uiTableColumnType {
|
||||
uiTableColumnText,
|
||||
//TODO uiTableColumnImage,
|
||||
uiTableColumnCheckbox,
|
||||
};
|
||||
|
||||
struct uiTableModelSpec {
|
||||
intmax_t (*NumRows)(uiTableModel *m, void *mData);
|
||||
void *(*CellValue)(uiTableModel *m, void *mData, intmax_t row, intmax_t column);
|
||||
void (*SetCellValue)(uiTableModel *m, void *mData, intmax_t row, intmax_t column, void *value);
|
||||
};
|
||||
|
||||
enum uiTableNotification {
|
||||
uiTableRowInserted,
|
||||
uiTableRowDeleted,
|
||||
uiTableCellChanged,
|
||||
};
|
||||
|
||||
_UI_EXTERN uiTableModel *uiNewTableModel(uintmax_t nCols, uiTableColumnType *types, uiTableModelSpec *spec, void *mData);
|
||||
_UI_EXTERN void uiFreeTableModel(uiTableModel *m);
|
||||
_UI_EXTERN void uiTableModelNotify(uiTableModel *m, uiTableNotification notification, intmax_t row, intmax_t column);
|
||||
|
||||
#define uiTableModelFromBool(b) ((void *) ((intptr_t) (b)))
|
||||
_UI_EXTERN void *uiTableModelFromString(const char *str);
|
||||
|
||||
struct uiTableColumnParams {
|
||||
const char *Name;
|
||||
// TODO make this unnecessary
|
||||
uiTableColumnType Type;
|
||||
int Mutable; // TODO move to the model?
|
||||
intmax_t ValueColumn;
|
||||
// TODO background color
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
// 18 october 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiTable {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkContainer *scontainer;
|
||||
GtkScrolledWindow *sw;
|
||||
GtkWidget *treeWidget;
|
||||
GtkTreeView *treeview;
|
||||
GtkTreeSelection *selection;
|
||||
uiTableModel *model;
|
||||
};
|
||||
|
||||
uiUnixDefineControl(
|
||||
uiTable // type name
|
||||
)
|
||||
|
||||
void uiTableSetModel(uiTable *t, uiTableModel *m)
|
||||
{
|
||||
t->model = m;
|
||||
gtk_tree_view_set_model(t->treeview, GTK_TREE_MODEL(t->model));
|
||||
}
|
||||
|
||||
void uiTableAppendColumn(uiTable *t, uiTableColumnParams *p)
|
||||
{
|
||||
GtkTreeViewColumn *col;
|
||||
GtkCellRenderer *r;
|
||||
const char *attribute;
|
||||
const char *mutableAttr;
|
||||
gboolean mutable;
|
||||
|
||||
switch (p->Type) {
|
||||
case uiTableColumnText:
|
||||
r = gtk_cell_renderer_text_new();
|
||||
attribute = "text";
|
||||
mutableAttr = "editable";
|
||||
break;
|
||||
//TODO case uiTableColumnImage:
|
||||
// TODO
|
||||
case uiTableColumnCheckbox:
|
||||
r = gtk_cell_renderer_toggle_new();
|
||||
attribute = "active";
|
||||
mutableAttr = "activatable";
|
||||
break;
|
||||
default:
|
||||
complain("unknown table column type %d in uiTableAppendColumn()", p->Type);
|
||||
}
|
||||
mutable = FALSE;
|
||||
if (p->Mutable)
|
||||
mutable = TRUE;
|
||||
g_object_set(r,
|
||||
mutableAttr, mutable,
|
||||
NULL);
|
||||
col = gtk_tree_view_column_new_with_attributes(p->Name, r,
|
||||
attribute, p->ValueColumn,
|
||||
NULL);
|
||||
// allow columns to be resized
|
||||
gtk_tree_view_column_set_resizable(col, TRUE);
|
||||
gtk_tree_view_append_column(t->treeview, col);
|
||||
}
|
||||
|
||||
uiTable *uiNewTable(void)
|
||||
{
|
||||
uiTable *t;
|
||||
|
||||
t = (uiTable *) uiNewControl(uiTableType());
|
||||
|
||||
t->widget = gtk_scrolled_window_new(NULL, NULL);
|
||||
t->scontainer = GTK_CONTAINER(t->widget);
|
||||
t->sw = GTK_SCROLLED_WINDOW(t->widget);
|
||||
|
||||
t->treeWidget = gtk_tree_view_new();
|
||||
t->treeview = GTK_TREE_VIEW(t->treeWidget);
|
||||
|
||||
t->selection = gtk_tree_view_get_selection(t->treeview);
|
||||
|
||||
// give a border and add the table
|
||||
gtk_scrolled_window_set_shadow_type(t->sw, GTK_SHADOW_IN);
|
||||
gtk_container_add(t->scontainer, t->treeWidget);
|
||||
// and make the table visible; only the scrolled window's visibility is controlled by libui
|
||||
gtk_widget_show(t->treeWidget);
|
||||
|
||||
uiUnixFinishNewControl(t, uiTable);
|
||||
|
||||
return t;
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
// 18 october 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
// On GTK+, uiTableModel is a GtkTreeModel.
|
||||
|
||||
#define uiTableModelType (uiTableModel_get_type())
|
||||
#define uiTableModel(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), uiTableModelType, uiTableModel))
|
||||
#define isAreaWidget(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), uiTableModelType))
|
||||
#define uiTableModelClass(class) (G_TYPE_CHECK_CLASS_CAST((class), uiTableModelType, uiTableModelClass))
|
||||
#define isAreaWidgetClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), uiTableModel))
|
||||
#define getAreaWidgetClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), uiTableModelType, uiTableModelClass))
|
||||
|
||||
typedef struct uiTableModel uiTableModel;
|
||||
typedef struct uiTableModelClass uiTableModelClass;
|
||||
|
||||
struct uiTableModel {
|
||||
GObject parent_instance;
|
||||
uiTableModelSpec *spec;
|
||||
void *mData;
|
||||
intmax_t nColumns;
|
||||
GType *coltypes;
|
||||
};
|
||||
|
||||
struct uiTableModelClass {
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
static void uiTableModel_treeModel_init(GtkTreeModelIface *);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE(uiTableModel, uiTableModel, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_MODEL, uiTableModel_treeModel_init))
|
||||
|
||||
static void uiTableModel_init(uiTableModel *m)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
static void uiTableModel_dispose(GObject *obj)
|
||||
{
|
||||
G_OBJECT_CLASS(uiTableModel_parent_class)->dispose(obj);
|
||||
}
|
||||
|
||||
static void uiTableModel_finalize(GObject *obj)
|
||||
{
|
||||
uiTableModel *m = uiTableModel(obj);
|
||||
|
||||
uiFree(m->coltypes);
|
||||
G_OBJECT_CLASS(uiTableModel_parent_class)->finalize(obj);
|
||||
}
|
||||
|
||||
static GtkTreeModelFlags uiTableModel_get_flags(GtkTreeModel *mb)
|
||||
{
|
||||
return GTK_TREE_MODEL_LIST_ONLY;
|
||||
}
|
||||
|
||||
static gint uiTableModel_get_n_columns(GtkTreeModel *mb)
|
||||
{
|
||||
uiTableModel *m = uiTableModel(mb);
|
||||
|
||||
return m->nColumns;
|
||||
}
|
||||
|
||||
static GType uiTableModel_get_column_type(GtkTreeModel *mb, gint index)
|
||||
{
|
||||
uiTableModel *m = uiTableModel(mb);
|
||||
|
||||
return m->coltypes[index];
|
||||
}
|
||||
|
||||
/*
|
||||
how our GtkTreeIters are stored:
|
||||
stamp: either GOOD_STAMP or BAD_STAMP
|
||||
user_data: row index
|
||||
Thanks to Company in irc.gimp.net/#gtk+ for suggesting the GSIZE_TO_POINTER() t
|
||||
rick.
|
||||
*/
|
||||
#define GOOD_STAMP 0x1234
|
||||
#define BAD_STAMP 0x5678
|
||||
#define FROM(x) ((gint) GPOINTER_TO_SIZE((x)))
|
||||
#define TO(x) GSIZE_TO_POINTER((gsize) (x))
|
||||
|
||||
#define numRows(m) ((*((m)->spec->NumRows))((m), (m)->mData))
|
||||
#define cellValue(m, row, col) ((*((m)->spec->CellValue))((m), (m)->mData, row, column))
|
||||
|
||||
static gboolean uiTableModel_get_iter(GtkTreeModel *mb, GtkTreeIter *iter, GtkTreePath *path)
|
||||
{
|
||||
uiTableModel *m = uiTableModel(mb);
|
||||
gint index;
|
||||
|
||||
if (gtk_tree_path_get_depth(path) != 1)
|
||||
goto bad;
|
||||
index = gtk_tree_path_get_indices(path)[0];
|
||||
if (index < 0)
|
||||
goto bad;
|
||||
if (index >= numRows(m))
|
||||
goto bad;
|
||||
iter->stamp = GOOD_STAMP;
|
||||
iter->user_data = TO(index);
|
||||
return TRUE;
|
||||
bad:
|
||||
iter->stamp = BAD_STAMP;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static GtkTreePath *uiTableModel_get_path(GtkTreeModel *mb, GtkTreeIter *iter)
|
||||
{
|
||||
// note: from this point forward, the GOOD_STAMP checks ensure that the index stored in iter is nonnegative
|
||||
if (iter->stamp != GOOD_STAMP)
|
||||
return NULL; // this is what both GtkListStore and GtkTreeStore do
|
||||
return gtk_tree_path_new_from_indices(FROM(iter->user_data), -1);
|
||||
}
|
||||
|
||||
void *uiTableModelFromString(const char *str)
|
||||
{
|
||||
return g_strdup(str);
|
||||
}
|
||||
|
||||
#define toBool(v) ((int) ((intptr_t) (v)))
|
||||
#define toStr(v) ((char *) (v))
|
||||
|
||||
static void uiTableModel_get_value(GtkTreeModel *mb, GtkTreeIter *iter, gint column, GValue *value)
|
||||
{
|
||||
uiTableModel *m = uiTableModel(mb);
|
||||
void *v;
|
||||
GType type;
|
||||
|
||||
if (iter->stamp != GOOD_STAMP)
|
||||
return; // this is what both GtkListStore and GtkTreeStore do
|
||||
v = cellValue(m, FROM(iter->user_data), column);
|
||||
type = m->coltypes[column];
|
||||
g_value_init(value, type);
|
||||
if (type == G_TYPE_STRING)
|
||||
g_value_take_string(value, toStr(v));
|
||||
// the GValue now manages the memory of the string that was g_strdup()'d before
|
||||
// TODO image
|
||||
else if (type == G_TYPE_BOOLEAN)
|
||||
g_value_set_boolean(value, toBool(v));
|
||||
else
|
||||
complain("unknown GType in uiTableModel_get_value()");
|
||||
}
|
||||
|
||||
static gboolean uiTableModel_iter_next(GtkTreeModel *mb, GtkTreeIter *iter)
|
||||
{
|
||||
uiTableModel *m = uiTableModel(mb);
|
||||
gint index;
|
||||
|
||||
if (iter->stamp != GOOD_STAMP)
|
||||
return FALSE; // this is what both GtkListStore and GtkTreeStore do
|
||||
index = FROM(iter->user_data);
|
||||
index++;
|
||||
if (index >= numRows(m)) {
|
||||
iter->stamp = BAD_STAMP;
|
||||
return FALSE;
|
||||
}
|
||||
iter->user_data = TO(index);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean uiTableModel_iter_previous(GtkTreeModel *mb, GtkTreeIter *iter)
|
||||
{
|
||||
uiTableModel *m = uiTableModel(mb);
|
||||
gint index;
|
||||
|
||||
if (iter->stamp != GOOD_STAMP)
|
||||
return FALSE; // this is what both GtkListStore and GtkTreeStore do
|
||||
index = FROM(iter->user_data);
|
||||
if (index <= 0) {
|
||||
iter->stamp = BAD_STAMP;
|
||||
return FALSE;
|
||||
}
|
||||
index--;
|
||||
iter->user_data = TO(index);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean uiTableModel_iter_children(GtkTreeModel *mb, GtkTreeIter *iter, GtkTreeIter *parent)
|
||||
{
|
||||
uiTableModel *m = uiTableModel(mb);
|
||||
|
||||
if (parent == NULL && numRows(m) > 0) {
|
||||
iter->stamp = GOOD_STAMP;
|
||||
iter->user_data = 0;
|
||||
return TRUE;
|
||||
}
|
||||
iter->stamp = BAD_STAMP;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean uiTableModel_iter_has_child(GtkTreeModel *mb, GtkTreeIter *iter)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gint uiTableModel_iter_n_children(GtkTreeModel *mb, GtkTreeIter *iter)
|
||||
{
|
||||
uiTableModel *m = uiTableModel(mb);
|
||||
|
||||
if (iter == NULL)
|
||||
return numRows(m);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static gboolean uiTableModel_iter_nth_child(GtkTreeModel *mb, GtkTreeIter *iter, GtkTreeIter *parent, gint n)
|
||||
{
|
||||
uiTableModel *m = uiTableModel(mb);
|
||||
|
||||
if (parent == NULL && n >= 0 && n < numRows(m)) {
|
||||
iter->stamp = GOOD_STAMP;
|
||||
iter->user_data = TO(n);
|
||||
return TRUE;
|
||||
}
|
||||
iter->stamp = BAD_STAMP;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean uiTableModel_iter_parent(GtkTreeModel *mb, GtkTreeIter *iter, GtkTreeIter *child)
|
||||
{
|
||||
iter->stamp = BAD_STAMP;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void uiTableModel_class_init(uiTableModelClass *class)
|
||||
{
|
||||
G_OBJECT_CLASS(class)->dispose = uiTableModel_dispose;
|
||||
G_OBJECT_CLASS(class)->finalize = uiTableModel_finalize;
|
||||
}
|
||||
|
||||
static void uiTableModel_treeModel_init(GtkTreeModelIface *iface)
|
||||
{
|
||||
iface->get_flags = uiTableModel_get_flags;
|
||||
iface->get_n_columns = uiTableModel_get_n_columns;
|
||||
iface->get_column_type = uiTableModel_get_column_type;
|
||||
iface->get_iter = uiTableModel_get_iter;
|
||||
iface->get_path = uiTableModel_get_path;
|
||||
iface->get_value = uiTableModel_get_value;
|
||||
iface->iter_next = uiTableModel_iter_next;
|
||||
iface->iter_previous = uiTableModel_iter_previous;
|
||||
iface->iter_children = uiTableModel_iter_children;
|
||||
iface->iter_has_child = uiTableModel_iter_has_child;
|
||||
iface->iter_n_children = uiTableModel_iter_n_children;
|
||||
iface->iter_nth_child = uiTableModel_iter_nth_child;
|
||||
iface->iter_parent = uiTableModel_iter_parent;
|
||||
// no need for ref_node or unref_node
|
||||
}
|
||||
|
||||
uiTableModel *uiNewTableModel(uintmax_t nCols, uiTableColumnType *types, uiTableModelSpec *spec, void *mData)
|
||||
{
|
||||
uiTableModel *m;
|
||||
intmax_t i;
|
||||
|
||||
m = uiTableModel(g_object_new(uiTableModelType, NULL));
|
||||
m->spec = spec;
|
||||
m->mData = mData;
|
||||
m->nColumns = nCols;
|
||||
m->coltypes = (GType *) uiAlloc(m->nColumns * sizeof (GType), "GType[]");
|
||||
for (i = 0; i < m->nColumns; i++)
|
||||
switch (types[i]) {
|
||||
case uiTableColumnText:
|
||||
m->coltypes[i] = G_TYPE_STRING;
|
||||
break;
|
||||
//TODO case uiTableColumnImage:
|
||||
// TODO
|
||||
case uiTableColumnCheckbox:
|
||||
m->coltypes[i] = G_TYPE_BOOLEAN;
|
||||
break;
|
||||
default:
|
||||
complain("unknown column type %d in uiNewTableModel()", types[i]);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
// TODO ensure no tables are subscribed
|
||||
void uiFreeTableModel(uiTableModel *m)
|
||||
{
|
||||
g_object_unref(m);
|
||||
}
|
||||
|
||||
void uiTableModelNotify(uiTableModel *m, uiTableNotification notification, intmax_t row, intmax_t column)
|
||||
{
|
||||
GtkTreeModel *model = GTK_TREE_MODEL(m);
|
||||
GtkTreePath *path;
|
||||
GtkTreeIter iter;
|
||||
|
||||
path = gtk_tree_path_new_from_indices(row, -1);
|
||||
switch (notification) {
|
||||
case uiTableRowInserted:
|
||||
if (gtk_tree_model_get_iter(model, &iter, path) == FALSE)
|
||||
complain("invalid row given to row inserted in uiTableModelNotify()");
|
||||
gtk_tree_model_row_inserted(model, path, &iter);
|
||||
break;
|
||||
case uiTableRowDeleted:
|
||||
gtk_tree_model_row_deleted(model, path);
|
||||
break;
|
||||
case uiTableCellChanged:
|
||||
if (gtk_tree_model_get_iter(model, &iter, path) == FALSE)
|
||||
complain("invalid row given to row changed in uiTableModelNotify()");
|
||||
gtk_tree_model_row_changed(model, path, &iter);
|
||||
break;
|
||||
default:
|
||||
complain("unknown uiTable notification %d in uiTableModelNotify()", notification);
|
||||
}
|
||||
gtk_tree_path_free(path);
|
||||
}
|
||||
Reference in new issue
Block a user