fonts refactoring; support tabs

This commit is contained in:
Vadim Lopatin 2014-04-24 10:56:06 +04:00
parent 31794b9df6
commit ca094497a1
3 changed files with 147 additions and 329 deletions

View File

@ -26,11 +26,17 @@ import std.algorithm;
/// font family
enum FontFamily : ubyte {
/// Unknown / not set / does not matter
Unspecified,
/// Sans Serif font, e.g. Arial
SansSerif,
/// Serif font, e.g. Times New Roman
Serif,
/// Fantasy font
Fantasy,
/// Cursive font
Cursive,
/// Monospace font (fixed pitch font), e.g. Courier New
MonoSpace
}
@ -40,11 +46,12 @@ enum FontWeight : int {
Bold = 800
}
const dchar UNICODE_SOFT_HYPHEN_CODE = 0x00ad;
const dchar UNICODE_ZERO_WIDTH_SPACE = 0x200b;
const dchar UNICODE_NO_BREAK_SPACE = 0x00a0;
const dchar UNICODE_HYPHEN = 0x2010;
const dchar UNICODE_NB_HYPHEN = 0x2011;
immutable dchar UNICODE_SOFT_HYPHEN_CODE = 0x00ad;
immutable dchar UNICODE_ZERO_WIDTH_SPACE = 0x200b;
immutable dchar UNICODE_NO_BREAK_SPACE = 0x00a0;
immutable dchar UNICODE_HYPHEN = 0x2010;
immutable dchar UNICODE_NB_HYPHEN = 0x2011;
version (USE_OPENGL) {
private __gshared void function(uint id) _glyphDestroyCallback;
@ -54,15 +61,27 @@ version (USE_OPENGL) {
@property void glyphDestroyCallback(void function(uint id) callback) { _glyphDestroyCallback = callback; }
private __gshared uint _nextGlyphId;
/// ID generator for glyphs
uint nextGlyphId() { return _nextGlyphId++; }
}
/// glyph image cache
/***************************************
* Glyph image cache
*
*
* Recently used glyphs are marked with glyph.lastUsage = 1
*
* checkpoint() call clears usage marks
*
* cleanup() removes all items not accessed since last checkpoint()
*
***************************************/
struct GlyphCache
{
alias glyph_ptr = Glyph*;
private glyph_ptr[][1024] _glyphs;
/// try to find glyph for character in cache, returns null if not found
Glyph * find(dchar ch) {
ch = ch & 0xF_FFFF;
//if (_array is null)
@ -78,7 +97,8 @@ struct GlyphCache
res.lastUsage = 1;
return res;
}
/// put glyph to cache
/// put character glyph to cache
Glyph * put(dchar ch, Glyph * glyph) {
ch = ch & 0xF_FFFF;
uint p = ch >> 8;
@ -108,7 +128,7 @@ struct GlyphCache
}
}
// clear usage flags for all entries
/// clear usage flags for all entries
void checkpoint() {
foreach(part; _glyphs) {
if (part !is null)
@ -119,7 +139,7 @@ struct GlyphCache
}
}
/// removes all entries
/// removes all entries (notify OpenGL cache about removed glyphs)
void clear() {
foreach(part; _glyphs) {
if (part !is null)
@ -136,174 +156,12 @@ struct GlyphCache
}
}
}
/// on destroy, destroy all items (notify OpenGL cache about removed glyphs)
~this() {
clear();
}
}
/*
/// font glyph cache
struct GlyphCache
{
//Glyph[ushort] _map;
Glyph[][256] _glyphs;
//Glyph[] _array;
// find glyph in cache
Glyph * find(ushort glyphIndex) {
//if (_array is null)
// _array = new Glyph[0x10000];
ushort p = glyphIndex >> 8;
if (_glyphs[p] is null)
return null;
ushort i = glyphIndex & 0xFF;
if (_glyphs[p][i].glyphIndex == 0)
return null;
return &_glyphs[p][i];
//if (_array[glyphIndex].glyphIndex)
// return &_array[glyphIndex];
//return null;
//Glyph * res = (glyphIndex in _map);
//if (res !is null)
// res.lastUsage = 1;
//return res;
}
/// put glyph to cache
Glyph * put(ushort glyphIndex, Glyph * glyph) {
ushort p = glyphIndex >> 8;
ushort i = glyphIndex & 0xFF;
if (_glyphs[p] is null)
_glyphs[p] = new Glyph[256];
_glyphs[p][i] = *glyph;
return &_glyphs[p][i]; // = *glyph;
//_array[glyphIndex] = *glyph;
//return &_array[glyphIndex];
//_map[glyphIndex] = *glyph;
//Glyph * res = glyphIndex in _map;
//res.lastUsage = 1;
//return res;
}
// clear usage flags for all entries
void checkpoint() {
//foreach(ref Glyph item; _map) {
// item.lastUsage = 0;
//}
foreach(ref Glyph[] part; _glyphs) {
if (part !is null)
foreach(ref Glyph item; part) {
item.lastUsage = 0;
}
}
//foreach(ref Glyph item; _array) {
// item.lastUsage = 0;
//}
}
/// removes entries not used after last call of checkpoint() or cleanup()
void cleanup() {
//uint dst = 0;
// notify about destroyed glyphs
version (USE_OPENGL) {
if (_glyphDestroyCallback !is null) {
foreach(ref Glyph[] part; _glyphs) {
if (part !is null)
foreach(ref Glyph item; part) {
if (item.lastUsage == 0 && item.glyphIndex)
_glyphDestroyCallback(item.id);
}
}
//foreach(ref Glyph item; _map) {
// if (item.lastUsage == 0)
// _glyphDestroyCallback(item.id);
//}
}
}
//ushort[] forDelete;
//foreach(ref Glyph item; _map)
// if (item.lastUsage == 0)
// forDelete ~= item.glyphIndex;
//foreach(ushort index; forDelete)
// _map.remove(index);
foreach(ref Glyph[] part; _glyphs) {
if (part !is null)
foreach(ref Glyph item; part) {
if (item.lastUsage == 0 && item.glyphIndex) {
item.glyphIndex = 0;
item.glyph = null;
version (USE_OPENGL) {
item.id = 0;
}
}
}
}
//foreach(ref Glyph item; _array) {
// if (item.lastUsage == 0 && item.glyphIndex) {
// item.glyphIndex = 0;
// item.glyph = null;
// item.id = 0;
// }
//}
}
/// removes all entries
void clear() {
// notify about destroyed glyphs
version (USE_OPENGL) {
if (_glyphDestroyCallback !is null) {
foreach(ref Glyph[] part; _glyphs) {
if (part !is null)
foreach(ref Glyph item; part) {
if (item.glyphIndex)
_glyphDestroyCallback(item.id);
}
}
}
}
foreach(ref Glyph[] part; _glyphs) {
if (part !is null)
foreach(ref Glyph item; part) {
if (item.glyphIndex) {
item.glyphIndex = 0;
item.glyph = null;
version (USE_OPENGL) {
item.id = 0;
}
}
}
}
//version (USE_OPENGL) {
// if (_glyphDestroyCallback !is null) {
// foreach(ref Glyph item; _array) {
// if (item.glyphIndex)
// _glyphDestroyCallback(item.id);
// }
// //foreach(ref Glyph item; _map) {
// // if (item.lastUsage == 0)
// // _glyphDestroyCallback(item.id);
// //}
// }
//}
////_map.clear();
//foreach(ref Glyph item; _array) {
// if (item.glyphIndex) {
// item.glyphIndex = 0;
// item.glyph = null;
// item.id = 0;
// }
//}
}
~this() {
clear();
}
}
*/
/// Font object
class Font : RefCountedObject {
@ -350,23 +208,130 @@ class Font : RefCountedObject {
return !g ? 0 : g.width;
}
/// measure text string, return accumulated widths[] (distance to end of n-th character), returns number of measured chars.
abstract int measureText(const dchar[] text, ref int[] widths, int maxWidth);
/*******************************************************************************************
* Measure text string, return accumulated widths[] (distance to end of n-th character), returns number of measured chars.
*
* Params:
* text = text string to measure
* widths = output buffer to put measured widths (widths[i] will be set to cumulative widths text[0..i])
* maxWidth = maximum width - measure is stopping if max width is reached
* tabSize = tabulation size, in number of spaces
* tabOffset = when string is drawn not from left position, use to move tab stops left/right
******************************************************************************************/
int measureText(const dchar[] text, ref int[] widths, int maxWidth=int.max, int tabSize = 4, int tabOffset = 0) {
if (text.length == 0)
return 0;
const dchar * pstr = text.ptr;
uint len = cast(uint)text.length;
int x = 0;
int charsMeasured = 0;
int * pwidths = widths.ptr;
int tabWidth = spaceWidth * tabSize; // width of full tab in pixels
tabOffset = tabOffset % tabWidth;
if (tabOffset < 0)
tabOffset += tabWidth;
for (int i = 0; i < len; i++) {
//auto measureStart = std.datetime.Clock.currAppTick;
dchar ch = pstr[i];
if (ch == '\t') {
// measure tab
int tabPosition = (x + tabWidth - tabOffset) / tabWidth * tabWidth + tabOffset;
while (tabPosition < x + spaceWidth)
tabPosition += tabWidth;
pwidths[i] = tabPosition;
charsMeasured = i + 1;
x = tabPosition;
continue;
}
Glyph * glyph = getCharGlyph(pstr[i], true); // TODO: what is better
//auto measureEnd = std.datetime.Clock.currAppTick;
//auto duration = measureEnd - measureStart;
//if (duration.length > 10)
// Log.d("ft measureText took ", duration.length, " ticks");
if (glyph is null) {
// if no glyph, use previous width - treat as zero width
pwidths[i] = x;
continue;
}
int w = x + glyph.width; // using advance
int w2 = x + glyph.originX + glyph.blackBoxX; // using black box
if (w < w2) // choose bigger value
w = w2;
pwidths[i] = w;
x += glyph.width;
charsMeasured = i + 1;
if (x > maxWidth)
break;
}
return charsMeasured;
}
private int[] _textSizeBuffer; // buffer to reuse while measuring strings - to avoid GC
/// measure text string as single line, returns width and height
/*************************************************************************
* Measure text string as single line, returns width and height
*
* Params:
* text = text string to measure
* maxWidth = maximum width - measure is stopping if max width is reached
************************************************************************/
Point textSize(const dchar[] text, int maxWidth = int.max) {
if (_textSizeBuffer.length < text.length + 1)
_textSizeBuffer.length = text.length + 1;
//int[] widths = new int[text.length + 1];
int charsMeasured = measureText(text, _textSizeBuffer, maxWidth);
if (charsMeasured < 1)
return Point(0,0);
return Point(_textSizeBuffer[charsMeasured - 1], height);
}
/// draw text string to buffer
abstract void drawText(DrawBuf buf, int x, int y, const dchar[] text, uint color);
/*****************************************************************************************
* Draw text string to buffer.
*
* Params:
* buf = graphics buffer to draw text to
* x = x coordinate to draw first character at
* y = y coordinate to draw first character at
* text = text string to draw
* color = color for drawing of glyphs
* tabSize = tabulation size, in number of spaces
* tabOffset = when string is drawn not from left position, use to move tab stops left/right
****************************************************************************************/
void drawText(DrawBuf buf, int x, int y, const dchar[] text, uint color, int tabSize = 4, int tabOffset = 0) {
if (text.length == 0)
return; // nothing to draw - empty text
if (_textSizeBuffer.length < text.length)
_textSizeBuffer.length = text.length;
int charsMeasured = measureText(text, _textSizeBuffer, int.max, tabSize, tabOffset);
Rect clip = buf.clipOrFullRect;
if (clip.empty)
return; // not visible - clipped out
if (y + height < clip.top || y >= clip.bottom)
return; // not visible - fully above or below clipping rectangle
int _baseline = baseline;
for (int i = 0; i < charsMeasured; i++) {
int xx = (i > 0) ? _textSizeBuffer[i - 1] : 0;
if (x + xx > clip.right)
break;
if (x + xx + 255 < clip.left)
continue; // far at left of clipping region
dchar ch = text[i];
if (ch == ' ' || ch == '\t')
continue;
Glyph * glyph = getCharGlyph(ch);
if (glyph is null)
continue;
if ( glyph.blackBoxX && glyph.blackBoxY ) {
int gx = x + xx + glyph.originX;
if (gx + glyph.blackBoxX < clip.left)
continue;
buf.drawGlyph( gx,
y + _baseline - glyph.originY,
glyph,
color);
}
}
}
/// get character glyph information
abstract Glyph * getCharGlyph(dchar ch, bool withImage = true);

View File

@ -368,64 +368,7 @@ class FreeTypeFont : Font {
return glyph;
}
// draw text string to buffer
override void drawText(DrawBuf buf, int x, int y, const dchar[] text, uint color) {
int[] widths;
int bl = baseline;
int xx = 0;
for (int i = 0; i < text.length; i++) {
Glyph * glyph = getCharGlyph(text[i], true);
if (glyph is null)
continue;
if ( glyph.blackBoxX && glyph.blackBoxY ) {
int x0 = x + xx + glyph.originX;
int y0 = y + bl - glyph.originY;
if (x0 > buf.width)
break; // outside right bound
Rect rc = Rect(x0, y0, x0 + glyph.blackBoxX, y0 + glyph.blackBoxY);
if (buf.applyClipping(rc))
buf.drawGlyph( x0,
y0,
glyph,
color);
}
xx += glyph.width;
}
}
override int measureText(const dchar[] text, ref int[] widths, int maxWidth) {
if (text.length == 0)
return 0;
const dchar * pstr = text.ptr;
uint len = cast(uint)text.length;
int x = 0;
int charsMeasured = 0;
int * pwidths = widths.ptr;
for (int i = 0; i < len; i++) {
//auto measureStart = std.datetime.Clock.currAppTick;
Glyph * glyph = getCharGlyph(pstr[i], true); // TODO: what is better
//auto measureEnd = std.datetime.Clock.currAppTick;
//auto duration = measureEnd - measureStart;
//if (duration.length > 10)
// Log.d("ft measureText took ", duration.length, " ticks");
if (glyph is null) {
// if no glyph, use previous width - treat as zero width
pwidths[i] = i > 0 ? pwidths[i-1] : 0;
continue;
}
int w = x + glyph.width; // using advance
int w2 = x + glyph.originX + glyph.blackBoxX; // using black box
if (w < w2) // choose bigger value
w = w2;
pwidths[i] = w;
x += glyph.width;
charsMeasured = i + 1;
if (x > maxWidth)
break;
}
return charsMeasured;
}
/// load font files
bool create() {
if (!isNull())
clear();
@ -438,12 +381,12 @@ class FreeTypeFont : Font {
return _files.length > 0;
}
// clear usage flags for all entries
/// clear usage flags for all entries
override void checkpoint() {
_glyphCache.checkpoint();
}
// removes entries not used after last call of checkpoint() or cleanup()
/// removes entries not used after last call of checkpoint() or cleanup()
override void cleanup() {
_glyphCache.cleanup();
}

View File

@ -185,98 +185,8 @@ class Win32Font : Font {
return _glyphCache.put(ch, g);
}
// draw text string to buffer
override void drawText(DrawBuf buf, int x, int y, const dchar[] text, uint color) {
int[] widths;
int charsMeasured = measureText(text, widths, 3000);
Rect clip = buf.clipOrFullRect;
if (y + height < clip.top || y >= clip.bottom)
return;
for (int i = 0; i < charsMeasured; i++) {
int xx = (i > 0) ? widths[i - 1] : 0;
if (x + xx > clip.right)
break;
Glyph * glyph = getCharGlyph(text[i]);
if (glyph is null)
continue;
if ( glyph.blackBoxX && glyph.blackBoxY ) {
int gx = x + xx + glyph.originX;
if (gx + glyph.blackBoxX < clip.left)
continue;
buf.drawGlyph( gx,
y + _baseline - glyph.originY,
glyph,
color);
}
}
}
static if (true) {
override int measureText(const dchar[] text, ref int[] widths, int maxWidth) {
if (text.length == 0)
return 0;
const dchar * pstr = text.ptr;
uint len = cast(uint)text.length;
if (widths.length < len)
widths.length = len;
int x = 0;
int charsMeasured = 0;
for (int i = 0; i < len; i++) {
Glyph * glyph = getCharGlyph(text[i], true); // TODO: what is better
if (glyph is null) {
// if no glyph, use previous width - treat as zero width
widths[i] = i > 0 ? widths[i-1] : 0;
continue;
}
int w = x + glyph.width; // using advance
int w2 = x + glyph.originX + glyph.blackBoxX; // using black box
if (w < w2) // choose bigger value
w = w2;
widths[i] = w;
x += glyph.width;
charsMeasured = i + 1;
if (x > maxWidth)
break;
}
return charsMeasured;
}
} else {
override int measureText(const dchar[] text, ref int[] widths, int maxWidth) {
if (_hfont is null || _drawbuf is null || text.length == 0)
return 0;
wstring utf16text = toUTF16(text);
const wchar * pstr = utf16text.ptr;
uint len = cast(uint)utf16text.length;
GCP_RESULTSW gcpres;
gcpres.lStructSize = gcpres.sizeof;
if (widths.length < len + 1)
widths.length = len + 1;
gcpres.lpDx = widths.ptr;
gcpres.nMaxFit = len;
gcpres.nGlyphs = len;
uint res = GetCharacterPlacementW(
_drawbuf.dc,
pstr,
len,
maxWidth,
&gcpres,
GCP_MAXEXTENT); //|GCP_USEKERNING
if (!res) {
widths[0] = 0;
return 0;
}
uint measured = gcpres.nMaxFit;
int total = 0;
for (int i = 0; i < measured; i++) {
int w = widths[i];
total += w;
widths[i] = total;
}
return measured;
}
}
/// init from font definition
bool create(FontDef * def, int size, int weight, bool italic) {
if (!isNull())
clear();