/* Starshatter OpenSource Distribution Copyright (c) 1997-2004, Destroyer Studios LLC. All Rights Reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name "Destroyer Studios" nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SUBSYSTEM: nGenEx.lib FILE: Font.cpp AUTHOR: John DiCamillo OVERVIEW ======== Font Resource class implementation */ #include "MemDebug.h" #include "Font.h" #include "Polygon.h" #include "Bitmap.h" #include "DataLoader.h" #include "ParseUtil.h" #include "Video.h" DWORD GetRealTime(); // +--------------------------------------------------------------------+ Font::Font() : flags(0), height(0), baseline(0), interspace(0), spacewidth(0), imagewidth(0), image(0), expansion(0), alpha(1), blend(Video::BLEND_ALPHA), scale(1), material(0), vset(0), polys(0), npolys(0), caret_index(-1), caret_x(0), caret_y(0), tgt_bitmap(0) { ZeroMemory(name, sizeof(name)); ZeroMemory(glyph, sizeof(glyph)); ZeroMemory(kern, sizeof(kern)); } Font::Font(const char* n) : flags(0), height(0), baseline(0), interspace(0), spacewidth(4), imagewidth(0), image(0), expansion(0), alpha(1), blend(Video::BLEND_ALPHA), scale(1), material(0), vset(0), polys(0), npolys(0), caret_index(-1), caret_x(0), caret_y(0), tgt_bitmap(0) { ZeroMemory(glyph, sizeof(glyph)); ZeroMemory(kern, sizeof(kern)); CopyMemory(name, n, sizeof(name)); if (!Load(name)) { flags = 0; height = 0; baseline = 0; interspace = 0; spacewidth = 0; imagewidth = 0; image = 0; ZeroMemory(glyph, sizeof(glyph)); ZeroMemory(kern, sizeof(kern)); } } // +--------------------------------------------------------------------+ Font::~Font() { if (image) delete [] image; if (vset) delete vset; if (polys) delete [] polys; if (material) delete material; } // +--------------------------------------------------------------------+ static char kern_tweak[256][256]; bool Font::Load(const char* name) { if (!name || !name[0]) return false; char imgname[256]; char defname[256]; wsprintf(defname, "%s.def", name); wsprintf(imgname, "%s.pcx", name); DataLoader* loader = DataLoader::GetLoader(); if (!loader) return false; LoadDef(defname, imgname); for (int i = 0; i < 256; i++) { glyph[i].offset = GlyphOffset(i); glyph[i].width = 0; } if (loader->LoadBitmap(imgname, bitmap)) { if (!bitmap.Pixels() && !bitmap.HiPixels()) return false; scale = bitmap.Width() / 256; imagewidth = bitmap.Width(); if (height > bitmap.Height()) height = bitmap.Height(); int imgsize = bitmap.Width() * bitmap.Height(); image = new(__FILE__,__LINE__) BYTE[imgsize]; if (image) { if (bitmap.Pixels()) { CopyMemory(image, bitmap.Pixels(), imgsize); } else { for (int i = 0; i < imgsize; i++) image[i] = (BYTE) bitmap.HiPixels()[i].Alpha(); } } material = new(__FILE__,__LINE__) Material; material->tex_diffuse = &bitmap; } else { return false; } for (int i = 0; i < 256; i++) { glyph[i].width = CalcWidth(i); } color = Color::White; if (!(flags & (FONT_FIXED_PITCH | FONT_NO_KERN))) AutoKern(); for (int i = 0; i < 256; i++) { for (int j = 0; j < 256; j++) { if (kern_tweak[i][j] < 100) { kern[i][j] = kern_tweak[i][j]; } } } return true; } void Font::LoadDef(char* defname, char* imgname) { for (int i = 0; i < 256; i++) for (int j = 0; j < 256; j++) kern_tweak[i][j] = 111; DataLoader* loader = DataLoader::GetLoader(); if (!loader) return; BYTE* block; int blocklen = loader->LoadBuffer(defname, block, true); if (!block || blocklen < 4) return; Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block, blocklen)); Term* term = parser.ParseTerm(); if (!term) { Print("WARNING: could not parse '%s'\n", defname); return; } else { TermText* file_type = term->isText(); if (!file_type || file_type->value() != "FONT") { Print("WARNING: invalid font def file '%s'\n", defname); return; } } do { delete term; term = parser.ParseTerm(); if (term) { TermDef* def = term->isDef(); if (def) { if (def->name()->value().indexOf("image") == 0) { GetDefText(imgname, def, defname); } else if (def->name()->value() == "height") { int h=0; GetDefNumber(h, def, defname); if (h >= 0 && h <= 32) height = (BYTE) h; } else if (def->name()->value() == "baseline") { int b=0; GetDefNumber(b, def, defname); if (b >= 0 && b <= 32) baseline = (BYTE) b; } else if (def->name()->value() == "flags") { if (def->term()->isText()) { Text buf; GetDefText(buf, def, defname); buf.setSensitive(false); flags = 0; if (buf.contains("caps")) flags = flags | FONT_ALL_CAPS; if (buf.contains("kern")) flags = flags | FONT_NO_KERN; if (buf.contains("fixed")) flags = flags | FONT_FIXED_PITCH; } else { int f=0; GetDefNumber(f, def, defname); flags = (WORD) f; } } else if (def->name()->value() == "interspace") { int n=0; GetDefNumber(n, def, defname); if (n >= 0 && n <= 100) interspace = (BYTE) n; } else if (def->name()->value() == "spacewidth") { int n=0; GetDefNumber(n, def, defname); if (n >= 0 && n <= 100) spacewidth = (BYTE) n; } else if (def->name()->value() == "expansion") { GetDefNumber(expansion, def, defname); } else if (def->name()->value() == "kern") { TermStruct* val = def->term()->isStruct(); char a[8], b[8]; int k=111; a[0] = 0; b[0] = 0; for (int i = 0; i < val->elements()->size(); i++) { TermDef* pdef = val->elements()->at(i)->isDef(); if (pdef) { if (pdef->name()->value() == "left" || pdef->name()->value() == "a") GetDefText(a, pdef, defname); else if (pdef->name()->value() == "right" || pdef->name()->value() == "b") GetDefText(b, pdef, defname); else if (pdef->name()->value() == "kern" || pdef->name()->value() == "k") GetDefNumber(k, pdef, defname); } } if (k < 100) kern_tweak[a[0]][b[0]] = k; } else { Print("WARNING: unknown object '%s' in '%s'\n", def->name()->value().data(), defname); } } else { Print("WARNING: term ignored in '%s'\n", defname); term->print(); } } } while (term); loader->ReleaseBuffer(block); } // +--------------------------------------------------------------------+ static const int pipe_width = 16; static const int char_width = 16; static const int char_height = 16; static const int row_items = 16; static const int row_width = row_items * char_width; static const int row_size = char_height * row_width; int Font::GlyphOffset(BYTE c) const { if (flags & FONT_ALL_CAPS) if (islower(c)) c = toupper(c); return (c/row_items * row_size * scale * scale + c%row_items * char_width * scale); } int Font::GlyphLocationX(BYTE c) const { if (flags & FONT_ALL_CAPS) if (islower(c)) c = toupper(c); return c%row_items * char_width; } int Font::GlyphLocationY(BYTE c) const { if (flags & FONT_ALL_CAPS) if (islower(c)) c = toupper(c); return c/row_items * char_height; } // +--------------------------------------------------------------------+ int Font::CalcWidth(BYTE c) const { if (c >= PIPE_NBSP && c <= ARROW_RIGHT) return pipe_width; if (c >= 128 || !image) return 0; // all digits should be same size: if (isdigit(c)) c = '0'; int result = 0; int w = 16 * scale; int h = 16 * scale; BYTE* src = image + GlyphOffset(c); for (int y = 0; y < h; y++) { BYTE* pleft = src; for (int x = 0; x < w; x++) { if (*pleft++ > 0 && x > result) result = x; } src += imagewidth; } return result + 2; } // +--------------------------------------------------------------------+ struct FontKernData { double l[32]; double r[32]; }; void Font::FindEdges(BYTE c, double* l, double* r) { if (!image) return; int w = glyph[c].width; int h = height; if (h > 32) h = 32; BYTE* src = image + GlyphOffset(c); for (int y = 0; y < h; y++) { BYTE* pleft = src; BYTE* pright = src+w-1; *l = -1; *r = -1; for (int x = 0; x < w; x++) { if (*l == -1 && *pleft != 0) *l = x + 1 - (double) *pleft/255.0; if (*r == -1 && *pright != 0) *r = x + 1 - (double) *pright/255.0; pleft++; pright--; } src += imagewidth; l++; r++; } } static bool nokern(char c) { if (c <= Font::ARROW_RIGHT) return true; const char* nokernchars = "0123456789+=<>-.,:;?'\""; if (strchr(nokernchars, c)) return true; return false; } void Font::AutoKern() { FontKernData* data = new(__FILE__,__LINE__) FontKernData[256]; if (!data) return; int h = height; if (h > 32) h = 32; int i, j; // first, compute row edges for each glyph: for (i = 0; i < 256; i++) { ZeroMemory(&data[i], sizeof(FontKernData)); char c = i; if ((flags & FONT_ALL_CAPS) && islower(c)) c = toupper(c); if (glyph[(BYTE) c].width > 0) { FindEdges((BYTE) c, data[i].l, data[i].r); } } // then, compute the appropriate kern for each pair. // use a desired average distance of one pixel, // with a desired minimum distance of more than half a pixel: double desired_avg = 2.5 + expansion; double desired_min = 1; for (i = 0; i < 256; i++) { for (j = 0; j < 256; j++) { // no kerning between digits or dashes: if (nokern(i) || nokern(j)) { kern[i][j] = (char) 0; } else { double delta = 0; double avg = 0; double min = 2500; int n = 0; for (int y = 0; y < h; y++) { if (data[i].r[y] >= 0 && data[j].l[y] >= 0) { delta = data[i].r[y] + data[j].l[y]; avg += delta; if (delta < min) min = delta; n++; } } if (n > 0) { avg /= n; delta = desired_avg - avg; if (delta < desired_min - min) { delta = ceil(desired_min - min); if (i == 'T' && islower(j) && !(flags & FONT_ALL_CAPS)) delta += 1; } } else { delta = 0; } kern[i][j] = (char) delta; } } } delete [] data; } // +--------------------------------------------------------------------+ int Font::CharWidth(char c) const { if (flags & FONT_ALL_CAPS) if (islower(c)) c = toupper(c); int result = 0; if (c >= PIPE_NBSP && c <= ARROW_RIGHT) result = pipe_width; else if (c < 0 || isspace(c)) result = spacewidth; else result = glyph[c].width + interspace; return result; } int Font::SpaceWidth() const { return spacewidth; } int Font::KernWidth(char a, char b) const { if (flags & FONT_ALL_CAPS) { if (islower(a)) a = toupper(a); if (islower(b)) b = toupper(b); } return kern[a][b]; } void Font::SetKern(char a, char b, int k) { if (k < -100 || k > 100) return; if (flags & FONT_ALL_CAPS) { if (islower(a)) a = toupper(a); if (islower(b)) b = toupper(b); } kern[a][b] = (char) k; } // +--------------------------------------------------------------------+ int Font::StringWidth(const char* str, int len) const { int result = 0; if (!str) return result; if (!len) len = strlen(str); const char* c = str; for (int i = 0; i < len; i++) { if (isspace(*c) && (*c < PIPE_NBSP || *c > ARROW_RIGHT)) result += spacewidth; else { int cc = *c; if (flags & FONT_ALL_CAPS) if (islower(cc)) cc = toupper(cc); int k = 0; if (i < len-1) k = kern[cc][str[i+1]]; result += glyph[cc].width + interspace + k; } c++; } return result; } // +--------------------------------------------------------------------+ void Font::DrawText(const char* text, int count, Rect& text_rect, DWORD flags, Bitmap* tgt) { Rect clip_rect = text_rect; if (clip_rect.w < 1 || clip_rect.h < 1) return; tgt_bitmap = tgt; if (text && text[0]) { if (count < 1) count = strlen(text); // single line: if (flags & DT_SINGLELINE) { DrawTextSingle(text, count, text_rect, clip_rect, flags); } // multi-line with word wrap: else if (flags & DT_WORDBREAK) { DrawTextWrap(text, count, text_rect, clip_rect, flags); } // multi-line with clip: else { DrawTextMulti(text, count, text_rect, clip_rect, flags); } } else { caret_x = text_rect.x + 2; caret_y = text_rect.y + 2; } // if calc only, update the rectangle: if (flags & DT_CALCRECT) { text_rect.h = clip_rect.h; text_rect.w = clip_rect.w; } // otherwise, draw caret if requested: else if (caret_index >= 0 && caret_y >= text_rect.y && caret_y <= text_rect.y + text_rect.h) {//caret_y + height < text_rect.y + text_rect.h) { Video* video = Video::GetInstance(); if (video && (GetRealTime() / 500) & 1) { float v[4]; v[0] = (float) (caret_x + 1); v[1] = (float) (caret_y); v[2] = (float) (caret_x + 1); v[3] = (float) (caret_y + height); video->DrawScreenLines(1, v, color, blend); } caret_index = -1; } tgt_bitmap = 0; } // +--------------------------------------------------------------------+ static int find_next_word_start(const char* text, int index) { // step through intra-word space: while (text[index] && isspace(text[index]) && text[index] != '\n') index++; return index; } static int find_next_word_end(const char* text, int index) { if (index < 0) return index; // check for leading newline: if (text[index] == '\n') return index; // step through intra-word space: while (text[index] && isspace(text[index])) index++; // step through word: while (text[index] && !isspace(text[index])) index++; return index-1; } // +--------------------------------------------------------------------+ void Font::DrawTextSingle(const char* text, int count, const Rect& text_rect, Rect& clip_rect, DWORD flags) { // parse the format flags: bool nodraw = (flags & DT_CALCRECT) ?true:false; int align = DT_LEFT; if (flags & DT_RIGHT) align = DT_RIGHT; else if (flags & DT_CENTER) align = DT_CENTER; int max_width = 0; int valign = DT_TOP; if (flags & DT_BOTTOM) valign = DT_BOTTOM; else if (flags & DT_VCENTER) valign = DT_VCENTER; int xoffset = 0; int yoffset = 0; int length = StringWidth(text, count); if (length < text_rect.w) { switch (align) { default: case DT_LEFT: break; case DT_RIGHT: xoffset = text_rect.w - length; break; case DT_CENTER: xoffset = (text_rect.w - length)/2; break; } } if (Height() < text_rect.h) { switch (valign) { default: case DT_TOP: break; case DT_BOTTOM: yoffset = text_rect.h - Height(); break; case DT_VCENTER: yoffset = (text_rect.h - Height())/2; break; } } max_width = length; // if calc only, update the rectangle: if (nodraw) { clip_rect.h = Height(); clip_rect.w = max_width; } // otherwise, draw the string now: else { int x1 = text_rect.x + xoffset; int y1 = text_rect.y + yoffset; DrawString(text, count, x1, y1, text_rect); } if (caret_index >= 0 && caret_index <= count) { caret_x = text_rect.x + xoffset; caret_y = text_rect.y + yoffset; if (caret_index > 0) caret_x += StringWidth(text, caret_index); } else { caret_x = text_rect.x + 0; caret_y = text_rect.y + 0; } } // +--------------------------------------------------------------------+ void Font::DrawTextWrap(const char* text, int count, const Rect& text_rect, Rect& clip_rect, DWORD flags) { // parse the format flags: bool nodraw = (flags & DT_CALCRECT) ?true:false; int align = DT_LEFT; if (flags & DT_RIGHT) align = DT_RIGHT; else if (flags & DT_CENTER) align = DT_CENTER; int nlines = 0; int max_width = 0; int line_start = 0; int line_count = 0; int count_remaining = count; int curr_word_end = -1; int next_word_end = 0; int eol_index = 0; int xoffset = 0; int yoffset = 0; caret_x = -1; caret_y = -1; // repeat for each line of text: while (count_remaining > 0) { int length = 0; // find the end of the last whole word that fits on the line: for (;;) { next_word_end = find_next_word_end(text, curr_word_end+1); if (next_word_end < 0 || next_word_end == curr_word_end) break; if (text[next_word_end] == '\n') { eol_index = curr_word_end = next_word_end; break; } int word_len = next_word_end - line_start + 1; length = StringWidth(text+line_start, word_len); if (length < text_rect.w) { curr_word_end = next_word_end; // check for a newline in the next block of white space: eol_index = 0; const char* eol = &text[curr_word_end+1]; while (*eol && isspace(*eol) && *eol != '\n') eol++; if (*eol == '\n') { eol_index = eol - text; break; } } else break; } line_count = curr_word_end - line_start + 1; if (line_count > 0) { length = StringWidth(text+line_start, line_count); } // there was a single word longer than the entire line: else { line_count = next_word_end - line_start + 1; length = StringWidth(text+line_start, line_count); curr_word_end = next_word_end; } xoffset = 0; if (length < text_rect.w) { switch (align) { default: case DT_LEFT: break; case DT_RIGHT: xoffset = text_rect.w - length; break; case DT_CENTER: xoffset = (text_rect.w - length)/2; break; } } if (length > max_width) max_width = length; if (eol_index > 0) curr_word_end = eol_index; int next_line_start = find_next_word_start(text, curr_word_end+1); if (length > 0 && !nodraw) { int x1 = text_rect.x + xoffset; int y1 = text_rect.y + yoffset; DrawString(text+line_start, line_count, x1, y1, text_rect); if (caret_index == line_start) { caret_x = x1 - 2; caret_y = y1; } else if (caret_index > line_start && caret_index < next_line_start) { caret_x = text_rect.x + xoffset + StringWidth(text+line_start, caret_index-line_start) - 2; caret_y = text_rect.y + yoffset; } else if (caret_index == count) { if (text[count-1] == '\n') { caret_x = x1 - 2; caret_y = y1 + height; } else { caret_x = text_rect.x + xoffset + StringWidth(text+line_start, caret_index-line_start) - 2; caret_y = text_rect.y + yoffset; } } } nlines++; yoffset += Height(); if (eol_index > 0) curr_word_end = eol_index; line_start = find_next_word_start(text, curr_word_end+1); count_remaining = count - line_start; } // if calc only, update the rectangle: if (nodraw) { clip_rect.h = nlines * Height(); clip_rect.w = max_width; } } // +--------------------------------------------------------------------+ void Font::DrawTextMulti(const char* text, int count, const Rect& text_rect, Rect& clip_rect, DWORD flags) { // parse the format flags: bool nodraw = (flags & DT_CALCRECT) ?true:false; int align = DT_LEFT; if (flags & DT_RIGHT) align = DT_RIGHT; else if (flags & DT_CENTER) align = DT_CENTER; int max_width = 0; int line_start = 0; int count_remaining = count; int xoffset = 0; int yoffset = 0; int nlines = 0; // repeat for each line of text: while (count_remaining > 0) { int length = 0; int line_count = 0; // find the end of line: while (line_count < count_remaining) { char c = text[line_start+line_count]; if (!c || c == '\n') break; line_count++; } if (line_count > 0) { length = StringWidth(text+line_start, line_count); } xoffset = 0; if (length < text_rect.w) { switch (align) { default: case DT_LEFT: break; case DT_RIGHT: xoffset = text_rect.w - length; break; case DT_CENTER: xoffset = (text_rect.w - length)/2; break; } } if (length > max_width) max_width = length; if (length && !nodraw) { int x1 = text_rect.x + xoffset; int y1 = text_rect.y + yoffset; DrawString(text+line_start, line_count, x1, y1, text_rect); } nlines++; yoffset += Height(); if (line_start+line_count+1 < count) { line_start = find_next_word_start(text, line_start+line_count+1); count_remaining = count - line_start; } else { count_remaining = 0; } } // if calc only, update the rectangle: if (nodraw) { clip_rect.h = nlines * Height(); clip_rect.w = max_width; } } // +--------------------------------------------------------------------+ int Font::DrawString(const char* str, int len, int x1, int y1, const Rect& clip, Bitmap* tgt) { Video* video = Video::GetInstance(); int count = 0; int maxw = clip.w; int maxh = clip.h; if (len < 1 || !video) return count; // vertical clip if ((y1 < clip.y) || (y1 > clip.y + clip.h)) return count; // RENDER TO BITMAP if (!tgt) tgt = tgt_bitmap; if (tgt) { for (int i = 0; i < len; i++) { char c = str[i]; if ((flags & FONT_ALL_CAPS) && islower(c)) c = toupper(c); int cw = glyph[c].width + interspace; int ch = height; int k = 0; if (i < len-1) k = kern[c][str[i+1]]; // horizontal clip: if (x1 < clip.x) { if (isspace(c) && (c < PIPE_NBSP || c > ARROW_RIGHT)) { x1 += spacewidth; maxw -= spacewidth; } else { x1 += cw+k; maxw -= cw+k; } } else if (x1+cw > clip.x+clip.w) { return count; } else { if (isspace(c) && (c < PIPE_NBSP || c > ARROW_RIGHT)) { x1 += spacewidth; maxw -= spacewidth; } else { int sx = GlyphLocationX(c); int sy = GlyphLocationY(c); Color* srcpix = bitmap.HiPixels(); Color* dstpix = tgt->HiPixels(); if (srcpix && dstpix) { int spitch = bitmap.Width(); int dpitch = tgt->Width(); Color* dst = dstpix + (y1*dpitch) + x1; Color* src = srcpix + (sy*spitch) + sx; for (int i = 0; i < ch; i++) { Color* ps = src; Color* pd = dst; for (int n = 0; n < cw; n++) { DWORD alpha = ps->Alpha(); if (alpha) { *pd = color.dim(alpha / 240.0); } ps++; pd++; } dst += dpitch; src += spitch; } } else { // this probably won't work... tgt->BitBlt(x1, y1, bitmap, sx, sy, cw, ch, true); } x1 += cw + k; maxw -= cw + k; } count++; } } return count; } // RENDER TO VIDEO // allocate verts, if necessary int nverts = 4*len; if (!vset) { vset = new(__FILE__,__LINE__) VertexSet(nverts); if (!vset) return false; vset->space = VertexSet::SCREEN_SPACE; for (int v = 0; v < vset->nverts; v++) { vset->s_loc[v].z = 0.0f; vset->rw[v] = 1.0f; } } else if (vset->nverts < nverts) { vset->Resize(nverts); for (int v = 0; v < vset->nverts; v++) { vset->s_loc[v].z = 0.0f; vset->rw[v] = 1.0f; } } if (vset->nverts < nverts) return count; if (alpha < 1) color.SetAlpha((BYTE) (alpha * 255.0f)); else color.SetAlpha(255); for (int i = 0; i < len; i++) { char c = str[i]; if ((flags & FONT_ALL_CAPS) && islower(c)) c = toupper(c); int cw = glyph[c].width + interspace; int k = 0; if (i < len-1) k = kern[c][str[i+1]]; // horizontal clip: if (x1 < clip.x) { if (isspace(c) && (c < PIPE_NBSP || c > ARROW_RIGHT)) { x1 += spacewidth; maxw -= spacewidth; } else { x1 += cw+k; maxw -= cw+k; } } else if (x1+cw > clip.x+clip.w) { break; } else { if (isspace(c) && (c < PIPE_NBSP || c > ARROW_RIGHT)) { x1 += spacewidth; maxw -= spacewidth; } else { // create four verts for this character: int v = count*4; double char_x = GlyphLocationX(c); double char_y = GlyphLocationY(c); double char_w = glyph[c].width; double char_h = height; if (y1 + char_h > clip.y + clip.h) { char_h = clip.y + clip.h - y1; } vset->s_loc[v+0].x = (float) (x1 - 0.5); vset->s_loc[v+0].y = (float) (y1 - 0.5); vset->tu[v+0] = (float) (char_x / 256); vset->tv[v+0] = (float) (char_y / 256); vset->diffuse[v+0] = color.Value(); vset->s_loc[v+1].x = (float) (x1 + char_w - 0.5); vset->s_loc[v+1].y = (float) (y1 - 0.5); vset->tu[v+1] = (float) (char_x / 256 + char_w / 256); vset->tv[v+1] = (float) (char_y / 256); vset->diffuse[v+1] = color.Value(); vset->s_loc[v+2].x = (float) (x1 + char_w - 0.5); vset->s_loc[v+2].y = (float) (y1 + char_h - 0.5); vset->tu[v+2] = (float) (char_x / 256 + char_w / 256); vset->tv[v+2] = (float) (char_y / 256 + char_h / 256); vset->diffuse[v+2] = color.Value(); vset->s_loc[v+3].x = (float) (x1 - 0.5); vset->s_loc[v+3].y = (float) (y1 + char_h - 0.5); vset->tu[v+3] = (float) (char_x / 256); vset->tv[v+3] = (float) (char_y / 256 + char_h / 256); vset->diffuse[v+3] = color.Value(); x1 += cw + k; maxw -= cw + k; count++; } } } if (count) { // this small hack is an optimization to reduce the // size of vertex buffer needed for font rendering: int old_nverts = vset->nverts; vset->nverts = 4 * count; // create a larger poly array, if necessary: if (count > npolys) { if (polys) delete [] polys; npolys = count; polys = new(__FILE__,__LINE__) Poly[npolys]; Poly* p = polys; int index = 0; for (int i = 0; i < npolys; i++) { p->nverts = 4; p->vertex_set = vset; p->material = material; p->verts[0] = index++; p->verts[1] = index++; p->verts[2] = index++; p->verts[3] = index++; p++; } } video->DrawScreenPolys(count, polys, blend); // remember to restore the proper size of the vertex set: vset->nverts = old_nverts; } return count; }