Implemented Pango text metrics and expanded the drawtext basic page to draw metrics. Works on both Pango and OS X; DirectWrite comes next.
This commit is contained in:
parent
6ef6ed8cde
commit
6ccf436206
|
@ -246,6 +246,8 @@ void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 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)
|
void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)
|
||||||
{
|
{
|
||||||
*width = tl->size.width;
|
*width = tl->size.width;
|
||||||
|
|
|
@ -30,10 +30,93 @@ static uiAttributedString *attrstr;
|
||||||
|
|
||||||
#define margins 10
|
#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)
|
static void draw(uiAreaDrawParams *p)
|
||||||
{
|
{
|
||||||
uiDrawPath *path;
|
uiDrawPath *path;
|
||||||
uiDrawTextLayout *layout;
|
uiDrawTextLayout *layout;
|
||||||
|
uiDrawBrush b;
|
||||||
|
|
||||||
|
b.Type = uiDrawBrushTypeSolid;
|
||||||
|
|
||||||
|
// only clip the text, not the guides
|
||||||
|
uiDrawSave(p->Context);
|
||||||
|
|
||||||
path = uiDrawNewPath(uiDrawFillModeWinding);
|
path = uiDrawNewPath(uiDrawFillModeWinding);
|
||||||
uiDrawPathAddRectangle(path, margins, margins,
|
uiDrawPathAddRectangle(path, margins, margins,
|
||||||
|
@ -44,33 +127,131 @@ static void draw(uiAreaDrawParams *p)
|
||||||
uiDrawFreePath(path);
|
uiDrawFreePath(path);
|
||||||
|
|
||||||
// TODO get rid of this later
|
// TODO get rid of this later
|
||||||
|
#if 0
|
||||||
path = uiDrawNewPath(uiDrawFillModeWinding);
|
path = uiDrawNewPath(uiDrawFillModeWinding);
|
||||||
uiDrawPathAddRectangle(path, -100, -100,
|
uiDrawPathAddRectangle(path, -100, -100,
|
||||||
p->AreaWidth * 2,
|
p->AreaWidth * 2,
|
||||||
p->AreaHeight * 2);
|
p->AreaHeight * 2);
|
||||||
uiDrawPathEnd(path);
|
uiDrawPathEnd(path);
|
||||||
uiDrawBrush b;
|
|
||||||
b.Type = uiDrawBrushTypeSolid;
|
|
||||||
b.R = 0.0;
|
b.R = 0.0;
|
||||||
b.G = 1.0;
|
b.G = 1.0;
|
||||||
b.B = 0.0;
|
b.B = 0.0;
|
||||||
b.A = 1.0;
|
b.A = 1.0;
|
||||||
uiDrawFill(p->Context, path, &b);
|
uiDrawFill(p->Context, path, &b);
|
||||||
uiDrawFreePath(path);
|
uiDrawFreePath(path);
|
||||||
|
#endif
|
||||||
|
|
||||||
layout = uiDrawNewTextLayout(attrstr,
|
layout = uiDrawNewTextLayout(attrstr,
|
||||||
&defaultFont,
|
&defaultFont,
|
||||||
p->AreaWidth - 2 * margins);
|
p->AreaWidth - 2 * margins);
|
||||||
uiDrawText(p->Context, layout, margins, margins);
|
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);
|
uiDrawFreeTextLayout(layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct example basicExample;
|
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)
|
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.name = "Basic Paragraph of Text";
|
||||||
basicExample.panel = uiControl(uiNewVerticalBox());
|
basicExample.panel = uiControl(panel);
|
||||||
basicExample.draw = draw;
|
basicExample.draw = draw;
|
||||||
|
|
||||||
attrstr = uiNewAttributedString(text);
|
attrstr = uiNewAttributedString(text);
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
struct uiDrawTextLayout {
|
struct uiDrawTextLayout {
|
||||||
PangoLayout *layout;
|
PangoLayout *layout;
|
||||||
|
uiDrawTextLayoutLineMetrics *lineMetrics;
|
||||||
};
|
};
|
||||||
|
|
||||||
// See https://developer.gnome.org/pango/1.30/pango-Cairo-Rendering.html#pango-Cairo-Rendering.description
|
// See https://developer.gnome.org/pango/1.30/pango-Cairo-Rendering.html#pango-Cairo-Rendering.description
|
||||||
|
@ -35,6 +36,55 @@ static const PangoStretch pangoStretches[] = {
|
||||||
[uiDrawTextStretchUltraExpanded] = PANGO_STRETCH_ULTRA_EXPANDED,
|
[uiDrawTextStretchUltraExpanded] = PANGO_STRETCH_ULTRA_EXPANDED,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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 = pango_layout_get_line_count(tl->layout);
|
||||||
|
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));
|
||||||
|
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(uiAttributedString *s, uiDrawFontDescriptor *defaultFont, double width)
|
uiDrawTextLayout *uiDrawNewTextLayout(uiAttributedString *s, uiDrawFontDescriptor *defaultFont, double width)
|
||||||
{
|
{
|
||||||
uiDrawTextLayout *tl;
|
uiDrawTextLayout *tl;
|
||||||
|
@ -76,11 +126,14 @@ uiDrawTextLayout *uiDrawNewTextLayout(uiAttributedString *s, uiDrawFontDescripto
|
||||||
|
|
||||||
// TODO attributes
|
// TODO attributes
|
||||||
|
|
||||||
|
computeLineMetrics(tl);
|
||||||
|
|
||||||
return tl;
|
return tl;
|
||||||
}
|
}
|
||||||
|
|
||||||
void uiDrawFreeTextLayout(uiDrawTextLayout *tl)
|
void uiDrawFreeTextLayout(uiDrawTextLayout *tl)
|
||||||
{
|
{
|
||||||
|
uiFree(tl->lineMetrics);
|
||||||
g_object_unref(tl->layout);
|
g_object_unref(tl->layout);
|
||||||
uiFree(tl);
|
uiFree(tl);
|
||||||
}
|
}
|
||||||
|
@ -112,15 +165,12 @@ void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start
|
||||||
pll = pango_layout_get_line_readonly(tl->layout, line);
|
pll = pango_layout_get_line_readonly(tl->layout, line);
|
||||||
*start = pll->start_index;
|
*start = pll->start_index;
|
||||||
*end = pll->start_index + pll->length;
|
*end = pll->start_index + pll->length;
|
||||||
// TODO unref?
|
// TODO unref pll?
|
||||||
}
|
}
|
||||||
|
|
||||||
void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m)
|
void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m)
|
||||||
{
|
{
|
||||||
PangoLayoutLine *pll;
|
*m = tl->lineMetrics[line];
|
||||||
|
|
||||||
pll = pango_layout_get_line_readonly(tl->layout, line);
|
|
||||||
// TODO unref?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
|
|
Loading…
Reference in New Issue