Files
tlib/oversampling/WDL/lice/lice_textnew.cpp
2024-05-24 13:28:31 +02:00

1357 lines
37 KiB
C++

#ifndef WDL_NO_DEFINE_MINMAX
#define WDL_NO_DEFINE_MINMAX
#endif
#include "lice_text.h"
#include <math.h>
#include "lice_combine.h"
#include "lice_extended.h"
#define IGNORE_SCALING(mode) ((mode)&LICE_BLIT_IGNORE_SCALING)
#if defined(_WIN32) && defined(WDL_SUPPORT_WIN9X)
static char __1ifNT2if98=0; // 2 for iswin98
#endif
// for very large fonts, fallback to native and avoid making a huge glyph cache, which would be terrible for performance
#define USE_NATIVE_RENDERING_FOR_FONTS_HIGHER_THAN 256
// if other methods fail, at this point just flat out refuse to render glyphs (since they would use a ridiculous amount of memory)
#ifndef ABSOLUTELY_NO_GLYPHS_HIGHER_THAN
#define ABSOLUTELY_NO_GLYPHS_HIGHER_THAN 1024
#endif
static int utf8makechar(char *ptrout, unsigned short charIn)
{
unsigned char *pout = (unsigned char *)ptrout;
if (charIn < 128) { *pout = (unsigned char)charIn; return 1; }
if (charIn < 2048) { pout[0] = 0xC0 + (charIn>>6); pout[1] = 0x80 + (charIn&0x3f); return 2; }
pout[0] = 0xE0 + (charIn>>12);
pout[1] = 0x80 + ((charIn>>6)&0x3f);
pout[2] = 0x80 + (charIn&0x3f);
return 3;
}
static int utf8char(const char *ptr, unsigned short *charOut) // returns char length
{
const unsigned char *p = (const unsigned char *)ptr;
unsigned char tc = *p;
if (tc < 128)
{
if (charOut) *charOut = (unsigned short) tc;
return 1;
}
else if (tc < 0xC2) // invalid chars (subsequent in sequence, or overlong which we disable for)
{
}
else if (tc < 0xE0) // 2 char seq
{
if (p[1] >= 0x80 && p[1] <= 0xC0)
{
if (charOut) *charOut = ((tc&0x1f)<<6) | (p[1]&0x3f);
return 2;
}
}
else if (tc < 0xF0) // 3 char seq
{
if (p[1] >= 0x80 && p[1] <= 0xC0 && p[2] >= 0x80 && p[2] <= 0xC0)
{
if (charOut) *charOut = ((tc&0xf)<<12) | ((p[1]&0x3f)<<6) | ((p[2]&0x3f));
return 3;
}
}
else if (tc < 0xF5) // 4 char seq
{
if (p[1] >= 0x80 && p[1] <= 0xC0 && p[2] >= 0x80 && p[2] <= 0xC0 && p[3] >= 0x80 && p[3] <= 0xC0)
{
if (charOut) *charOut = (unsigned short)' '; // dont support 4 byte sequences yet(ever?)
return 4;
}
}
if (charOut) *charOut = (unsigned short) tc;
return 1;
}
//not threadsafe ----
static LICE_SysBitmap *s_tempbitmap; // keep a sysbitmap around for rendering fonts
static LICE_SysBitmap *s_nativerender_tempbitmap;
static int s_tempbitmap_refcnt;
int LICE_CachedFont::_charSortFunc(const void *a, const void *b)
{
charEnt *aa = (charEnt *)a;
charEnt *bb = (charEnt *)b;
return aa->charid - bb->charid;
}
LICE_CachedFont::LICE_CachedFont() : m_cachestore(65536)
{
s_tempbitmap_refcnt++;
m_fg=0;
m_effectcol=m_bg=LICE_RGBA(255,255,255,255);
m_comb=0;
m_alpha=1.0f;
m_bgmode = TRANSPARENT;
m_flags=0;
m_line_height=0;
m_lsadj=0;
m_font=0;
memset(m_lowchars,0,sizeof(m_lowchars));
}
LICE_CachedFont::~LICE_CachedFont()
{
if ((m_flags&LICE_FONT_FLAG_OWNS_HFONT) && m_font) {
DeleteObject(m_font);
}
if (!--s_tempbitmap_refcnt)
{
delete s_tempbitmap;
s_tempbitmap=0;
delete s_nativerender_tempbitmap;
s_nativerender_tempbitmap=0;
}
}
void LICE_CachedFont::SetFromHFont(HFONT font, int flags)
{
if ((m_flags&LICE_FONT_FLAG_OWNS_HFONT) && m_font && m_font != font)
{
DeleteObject(m_font);
}
m_flags=flags;
m_font=font;
if (font)
{
if (!s_tempbitmap) s_tempbitmap=new LICE_SysBitmap;
if (s_tempbitmap->getWidth() < 256 || s_tempbitmap->getHeight() < 256)
{
s_tempbitmap->resize(256,256);
::SetTextColor(s_tempbitmap->getDC(),RGB(255,255,255));
::SetBkMode(s_tempbitmap->getDC(),OPAQUE);
::SetBkColor(s_tempbitmap->getDC(),RGB(0,0,0));
}
TEXTMETRIC tm;
HGDIOBJ oldFont = 0;
if (font) oldFont = SelectObject(s_tempbitmap->getDC(),font);
GetTextMetrics(s_tempbitmap->getDC(),&tm);
if (oldFont) SelectObject(s_tempbitmap->getDC(),oldFont);
m_line_height = tm.tmHeight;
}
memset(m_lowchars,0,sizeof(m_lowchars));
m_extracharlist.Resize(0,false);
m_cachestore.Resize(0);
if (flags&LICE_FONT_FLAG_PRECALCALL)
{
int x;
for(x=0;x<128;x++)
RenderGlyph(x);
}
}
bool LICE_CachedFont::RenderGlyph(unsigned short idx) // return TRUE if ok
{
if (m_line_height >= ABSOLUTELY_NO_GLYPHS_HIGHER_THAN) return false;
bool needSort=false;
charEnt *ent;
if (idx>=128)
{
#if defined(_WIN32) && defined(WDL_SUPPORT_WIN9X)
if (!__1ifNT2if98) __1ifNT2if98 = GetVersion()<0x80000000 ? 1 : 2;
if (__1ifNT2if98==2) return false;
#endif
ent=findChar(idx);
if (!ent)
{
if (m_flags & LICE_FONT_FLAG_PRECALCALL) return false;
int oldsz=m_extracharlist.GetSize();
ent = m_extracharlist.Resize(oldsz+1) + oldsz;
memset(ent,0,sizeof(*ent));
ent->charid = idx;
needSort=true;
}
}
else ent = m_lowchars+idx;
const int bmsz=lice_max(m_line_height,1) * 2 + 8;
if (!s_tempbitmap) s_tempbitmap=new LICE_SysBitmap;
if (s_tempbitmap->getWidth() < bmsz || s_tempbitmap->getHeight() < bmsz)
{
s_tempbitmap->resize(bmsz,bmsz);
::SetTextColor(s_tempbitmap->getDC(),RGB(255,255,255));
::SetBkMode(s_tempbitmap->getDC(),OPAQUE);
::SetBkColor(s_tempbitmap->getDC(),RGB(0,0,0));
}
HGDIOBJ oldFont=0;
if (m_font) oldFont = SelectObject(s_tempbitmap->getDC(),m_font);
RECT r={0,0,0,0,};
int advance;
// overrender sides and check to see if it was updated
const int right_extra_pad = 2+wdl_max(m_line_height/8,0);
const int left_extra_pad = 2+wdl_max(m_line_height/16,0);
#ifdef _WIN32
#if defined(WDL_SUPPORT_WIN9X)
if (__1ifNT2if98==1)
#endif
{
WCHAR tmpstr[2]={(WCHAR)idx,0};
::DrawTextW(s_tempbitmap->getDC(),tmpstr,1,&r,DT_CALCRECT|DT_SINGLELINE|DT_NOPREFIX);
advance=r.right;
r.right += right_extra_pad+left_extra_pad;
LICE_FillRect(s_tempbitmap,0,0,r.right,r.bottom,0,1.0f,LICE_BLIT_MODE_COPY);
r.left+=left_extra_pad;
::DrawTextW(s_tempbitmap->getDC(),tmpstr,1,&r,DT_SINGLELINE|DT_LEFT|DT_TOP|DT_NOPREFIX|DT_NOCLIP);
}
#if defined(WDL_SUPPORT_WIN9X)
else
#endif
#endif
#if !defined(_WIN32) || defined(WDL_SUPPORT_WIN9X)
{
char tmpstr[6]={(char)idx,0};
#ifndef _WIN32
if (idx>=128) utf8makechar(tmpstr,idx);
#endif
::DrawText(s_tempbitmap->getDC(),tmpstr,-1,&r,DT_CALCRECT|DT_SINGLELINE|DT_NOPREFIX);
advance=r.right;
r.right += right_extra_pad+left_extra_pad;
LICE_FillRect(s_tempbitmap,0,0,r.right,r.bottom,0,1.0f,LICE_BLIT_MODE_COPY);
r.left += left_extra_pad;
::DrawText(s_tempbitmap->getDC(),tmpstr,-1,&r,DT_SINGLELINE|DT_LEFT|DT_TOP|DT_NOPREFIX|DT_NOCLIP);
}
#endif
if (advance > s_tempbitmap->getWidth()) advance=s_tempbitmap->getWidth();
if (r.right > s_tempbitmap->getWidth()) r.right=s_tempbitmap->getWidth();
if (r.bottom > s_tempbitmap->getHeight()) r.bottom=s_tempbitmap->getHeight();
if (oldFont) SelectObject(s_tempbitmap->getDC(),oldFont);
if (advance < 1 || r.bottom < 1)
{
ent->base_offset=-1;
ent->left_extra=ent->advance=ent->width=ent->height=0;
}
else
{
ent->advance=advance;
int flags=m_flags;
if (flags&LICE_FONT_FLAG_FX_BLUR)
{
LICE_Blur(s_tempbitmap,s_tempbitmap,0,0,0,0,r.right,r.bottom);
}
LICE_pixel *srcbuf = s_tempbitmap->getBits();
int span=s_tempbitmap->getRowSpan();
ent->base_offset=m_cachestore.GetSize()+1;
int newsz = ent->base_offset-1+r.right*r.bottom;
unsigned char *destbuf = m_cachestore.Resize(newsz) + ent->base_offset-1;
if (m_cachestore.GetSize() != newsz)
{
ent->base_offset=-1;
ent->advance=ent->width=ent->height=0;
}
else
{
if (s_tempbitmap->isFlipped())
{
srcbuf += (s_tempbitmap->getHeight()-1)*span;
span=-span;
}
int x,y;
int min_x=left_extra_pad;
int max_x=advance + min_x;
for(y=0;y<r.bottom;y++)
{
if (flags&LICE_FONT_FLAG_FX_INVERT)
{
for (x=0;x<min_x;x++)
{
const unsigned char v=((unsigned char*)(srcbuf+x))[LICE_PIXEL_R];
*destbuf++ = 255-v;
if (v) min_x=x;
}
for (;x<max_x;x++) *destbuf++ = 255-((unsigned char*)(srcbuf+x))[LICE_PIXEL_R];
for (;x<r.right;x++)
{
const unsigned char v=((unsigned char*)(srcbuf+x))[LICE_PIXEL_R];
if (v) max_x=x+1;
*destbuf++ = 255-v;
}
}
else
{
for (x=0;x<min_x;x++)
{
const unsigned char v=((unsigned char*)(srcbuf+x))[LICE_PIXEL_R];
*destbuf++ = v;
if (v) min_x=x;
}
for (;x<max_x;x++) *destbuf++ = ((unsigned char*)(srcbuf+x))[LICE_PIXEL_R];
for (;x<r.right;x++)
{
const unsigned char v=((unsigned char*)(srcbuf+x))[LICE_PIXEL_R];
if (v) max_x=x+1;
*destbuf++ = v;
}
}
srcbuf += span;
}
destbuf -= r.right*r.bottom;
if (max_x < r.right || min_x > 0)
{
const int neww = max_x-min_x;
const unsigned char *rdptr=destbuf + min_x;
// trim down destbuf
for (y=0;y<r.bottom;y++)
{
if (destbuf != rdptr) memmove(destbuf,rdptr,neww);
destbuf+=neww;
rdptr += r.right;
}
r.right = neww;
newsz = ent->base_offset-1+r.right*r.bottom;
destbuf = m_cachestore.Resize(newsz,false) + ent->base_offset-1;
}
if (flags&LICE_FONT_FLAG_VERTICAL)
{
int a=r.bottom; r.bottom=r.right; r.right=a;
unsigned char *tmpbuf = (unsigned char *)s_tempbitmap->getBits();
memcpy(tmpbuf,destbuf,r.right*r.bottom);
int dptr=r.bottom;
int dtmpbuf=1;
if (!(flags&LICE_FONT_FLAG_VERTICAL_BOTTOMUP))
{
tmpbuf += (r.right-1)*dptr;
dptr=-dptr;
}
else
{
tmpbuf+=r.bottom-1;
dtmpbuf=-1;
}
for(y=0;y<r.bottom;y++)
{
unsigned char *ptr=tmpbuf;
tmpbuf+=dtmpbuf;
for(x=0;x<r.right;x++)
{
*destbuf++=*ptr;
ptr+=dptr;
}
}
destbuf -= r.right*r.bottom;
}
if (flags&LICE_FONT_FLAG_FX_MONO)
{
for(y=0;y<r.bottom;y++) for (x=0;x<r.right;x++)
{
*destbuf = *destbuf>130 ? 255:0;
destbuf++;
}
destbuf -= r.right*r.bottom;
}
if (flags&(LICE_FONT_FLAG_FX_SHADOW|LICE_FONT_FLAG_FX_OUTLINE))
{
for(y=0;y<r.bottom;y++)
for (x=0;x<r.right;x++)
{
*destbuf = *destbuf>130 ? 255:0;
destbuf++;
}
destbuf -= r.right*r.bottom;
if (flags&LICE_FONT_FLAG_FX_SHADOW)
{
for(y=0;y<r.bottom;y++)
for (x=0;x<r.right;x++)
{
if (!*destbuf)
{
if (x>0)
{
if (destbuf[-1]==255) *destbuf=128;
else if (y>0 && destbuf[-r.right-1]==255) *destbuf=128;
}
if (y>0 && destbuf[-r.right]==255) *destbuf=128;
}
destbuf++;
}
}
else
{
for(y=0;y<r.bottom;y++)
for (x=0;x<r.right;x++)
{
if (!*destbuf)
{
if (y>0 && destbuf[-r.right]==255) *destbuf=128;
else if (y<r.bottom-1 && destbuf[r.right]==255) *destbuf=128;
else if (x>0)
{
if (destbuf[-1]==255) *destbuf=128;
// else if (y>0 && destbuf[-r.right-1]==255) *destbuf=128;
// else if (y<r.bottom-1 && destbuf[r.right-1]==255) *destbuf=128;
}
if (x<r.right-1)
{
if (destbuf[1]==255) *destbuf=128;
// else if (y>0 && destbuf[-r.right+1]==255) *destbuf=128;
// else if (y<r.bottom-1 && destbuf[r.right+1]==255) *destbuf=128;
}
}
destbuf++;
}
}
}
ent->left_extra=left_extra_pad-min_x;
ent->width = r.right;
ent->height = r.bottom;
}
}
if (needSort&&m_extracharlist.GetSize()>1) qsort(m_extracharlist.Get(),m_extracharlist.GetSize(),sizeof(charEnt),_charSortFunc);
return true;
}
template<class T> class GlyphRenderer
{
public:
static void Normal(unsigned char *gsrc, LICE_pixel *pout,
int src_span, int dest_span, int width, int height,
int red, int green, int blue, int pxa, int a256)
{
int y;
if (a256==256)
{
for(y=0;y<height;y++)
{
int x;
for(x=0;x<width;x++)
{
unsigned char v=gsrc[x];
if (v) T::doPix((unsigned char *)(pout+x),red,green,blue,pxa,(int)v+1);
}
gsrc += src_span;
pout += dest_span;
}
}
else
{
for(y=0;y<height;y++)
{
int x;
for(x=0;x<width;x++)
{
unsigned char v=gsrc[x];
if (v)
{
int a=(v*a256)/256;
if (a>256)a=256;
T::doPix((unsigned char *)(pout+x),red,green,blue,pxa,a);
}
}
gsrc += src_span;
pout += dest_span;
}
}
}
static void Mono(unsigned char *gsrc, LICE_pixel *pout,
int src_span, int dest_span, int width, int height,
int red, int green, int blue, int pxa, int alpha)
{
int y;
for(y=0;y<height;y++)
{
int x;
for(x=0;x<width;x++)
if (gsrc[x]) T::doPix((unsigned char *)(pout+x),red,green,blue,pxa,alpha);
gsrc += src_span;
pout += dest_span;
}
}
static void Effect(unsigned char *gsrc, LICE_pixel *pout,
int src_span, int dest_span, int width, int height,
int red, int green, int blue, int pxa, int alpha, int r2, int g2, int b2, int pxa2)
{
int y;
for(y=0;y<height;y++)
{
int x;
for(x=0;x<width;x++)
{
unsigned char v=gsrc[x];
if (v)
{
if (v==255) T::doPix((unsigned char *)(pout+x),red,green,blue,pxa,alpha);
else T::doPix((unsigned char *)(pout+x),r2,g2,b2,pxa2,alpha);
}
}
gsrc += src_span;
pout += dest_span;
}
}
};
LICE_CachedFont::charEnt *LICE_CachedFont::findChar(unsigned short c)
{
if (c<128) return m_lowchars+c;
if (!m_extracharlist.GetSize()) return 0;
charEnt a={0,};
a.charid=c;
return (charEnt *)bsearch(&a,m_extracharlist.Get(),m_extracharlist.GetSize(),sizeof(charEnt),_charSortFunc);
}
bool LICE_CachedFont::DrawGlyph(LICE_IBitmap *bm, unsigned short c,
int xpos, int ypos, const RECT *clipR)
{
charEnt *ch = findChar(c);
if (!ch) return false;
if (m_flags&LICE_FONT_FLAG_VERTICAL)
{
if ((m_flags&(LICE_FONT_FLAG_VERTICAL|LICE_FONT_FLAG_VERTICAL_BOTTOMUP)) == (LICE_FONT_FLAG_VERTICAL|LICE_FONT_FLAG_VERTICAL_BOTTOMUP))
ypos -= ch->left_extra;
else
ypos += ch->left_extra;
}
else
{
xpos -= ch->left_extra;
}
if (xpos >= clipR->right ||
ypos >= clipR->bottom ||
xpos+ch->width <= clipR->left ||
ypos+ch->height <= clipR->top) return true; // would have drawn but out of bounds
unsigned char *gsrc = m_cachestore.Get() + ch->base_offset-1;
int src_span = ch->width;
int width = ch->width;
int height = ch->height;
#ifndef DISABLE_LICE_EXTENSIONS
if (bm->Extended(LICE_EXT_SUPPORTS_ID, (void*) LICE_EXT_DRAWGLYPH_ACCEL))
{
LICE_Ext_DrawGlyph_acceldata data(xpos, ypos, m_fg, gsrc, width, height, m_alpha, m_comb);
if (bm->Extended(LICE_EXT_DRAWGLYPH_ACCEL, &data)) return true;
}
#endif
if (xpos < clipR->left)
{
width += (xpos-clipR->left);
gsrc += clipR->left-xpos;
xpos=clipR->left;
}
if (ypos < clipR->top)
{
gsrc += src_span*(clipR->top-ypos);
height += (ypos-clipR->top);
ypos=clipR->top;
}
int dest_span = bm->getRowSpan();
LICE_pixel *pout = bm->getBits();
if (bm->isFlipped())
{
int bm_h = bm->getHeight();
const int __sc = bm ? (int)bm->Extended(LICE_EXT_GET_SCALING,NULL) : 0;
if (__sc>0)
{
__LICE_SCU(bm_h);
}
pout += (bm_h-1)*dest_span;
dest_span=-dest_span;
}
pout += xpos + ypos * dest_span;
if (width >= clipR->right-xpos) width = clipR->right-xpos;
if (height >= clipR->bottom-ypos) height = clipR->bottom-ypos;
if (width < 1 || height < 1) return false; // this could be an assert, it is guaranteed by the xpos/ypos checks at the top of this function
int mode=m_comb&~LICE_BLIT_USE_ALPHA;
float alpha=m_alpha;
if (m_bgmode==OPAQUE)
LICE_FillRect(bm,xpos,ypos,width,height,m_bg,alpha,mode|LICE_BLIT_IGNORE_SCALING);
int red=LICE_GETR(m_fg);
int green=LICE_GETG(m_fg);
int blue=LICE_GETB(m_fg);
int pxa=LICE_GETA(m_fg);
if (m_flags&LICE_FONT_FLAG_FX_MONO)
{
if (alpha==1.0 && (mode&LICE_BLIT_MODE_MASK)==LICE_BLIT_MODE_COPY) // fast simple
{
LICE_pixel col=m_fg;
int y;
for(y=0;y<height;y++)
{
int x;
for(x=0;x<width;x++) if (gsrc[x]) pout[x]=col;
gsrc += src_span;
pout += dest_span;
}
}
else
{
int avalint = (int) (alpha*256.0);
if (avalint>256)avalint=256;
#define __LICE__ACTION(comb) GlyphRenderer<comb>::Mono(gsrc,pout,src_span,dest_span,width,height,red,green,blue,pxa,avalint)
__LICE_ACTION_NOSRCALPHA(mode,avalint, false);
#undef __LICE__ACTION
}
}
else if (m_flags&(LICE_FONT_FLAG_FX_SHADOW|LICE_FONT_FLAG_FX_OUTLINE))
{
LICE_pixel col=m_fg;
LICE_pixel bkcol=m_effectcol;
if (alpha==1.0 && (mode&LICE_BLIT_MODE_MASK)==LICE_BLIT_MODE_COPY)
{
int y;
for(y=0;y<height;y++)
{
int x;
for(x=0;x<width;x++)
{
unsigned char cv=gsrc[x];
if (cv) pout[x]=cv==255? col : bkcol;
}
gsrc += src_span;
pout += dest_span;
}
}
else
{
int avalint = (int) (alpha*256.0);
if (avalint>256)avalint=256;
int r2=LICE_GETR(bkcol);
int g2=LICE_GETG(bkcol);
int b2=LICE_GETB(bkcol);
int pxa2=LICE_GETA(bkcol);
#define __LICE__ACTION(comb) GlyphRenderer<comb>::Effect(gsrc,pout,src_span,dest_span,width,height,red,green,blue,pxa,avalint,r2,g2,b2,pxa2)
__LICE_ACTION_NOSRCALPHA(mode,avalint, false);
#undef __LICE__ACTION
}
}
else
{
int avalint = (int) (alpha*256.0);
#define __LICE__ACTION(comb) GlyphRenderer<comb>::Normal(gsrc,pout,src_span,dest_span,width,height,red,green,blue,pxa,avalint)
__LICE_ACTION_NOSRCALPHA(mode,avalint, false);
#undef __LICE__ACTION
}
return true; // drew glyph at all (for updating max extents)
}
#ifndef LICE_TEXT_NONATIVE
static int LICE_Text_IsWine()
{
static int isWine=-1;
#ifdef _WIN32
if (isWine<0)
{
HKEY hk;
if (RegOpenKey(HKEY_LOCAL_MACHINE,"Software\\Wine",&hk)==ERROR_SUCCESS)
{
isWine=1;
RegCloseKey(hk);
}
else isWine=0;
}
#endif
return isWine>0;
}
#endif
#ifdef _WIN32
static BOOL LICE_Text_HasUTF8(const char *_str)
{
const unsigned char *str = (const unsigned char *)_str;
if (!str) return FALSE;
while (*str)
{
unsigned char c = *str;
if (c >= 0xC2) // fuck overlongs
{
if (c <= 0xDF && str[1] >=0x80 && str[1] <= 0xBF) return TRUE;
else if (c <= 0xEF && str[1] >=0x80 && str[1] <= 0xBF && str[2] >=0x80 && str[2] <= 0xBF) return TRUE;
else if (c <= 0xF4 && str[1] >=0x80 && str[1] <= 0xBF && str[2] >=0x80 && str[2] <= 0xBF) return TRUE;
}
str++;
}
return FALSE;
}
#endif
#define __LICE_SC_DRAWTEXT_RESTORE_RECT \
if (__sc > 0 && rect) { \
rect->left = (rect->left * 256) / __sc; \
rect->top = (rect->top * 256) / __sc; \
rect->right = (rect->right * 256) / __sc; \
rect->bottom = (rect->bottom * 256) / __sc; \
}
static const char *adv_str(const char *str, int *strcnt, unsigned short *c)
{
int charlen=utf8char(str, c);
if (strcnt && *strcnt > 0) *strcnt=wdl_max(*strcnt-charlen, 0);
return str+charlen;
}
const char *LICE_CachedFont::NextWordBreak(const char *str, int strcnt, int w)
{
// returns the first character of the next line
const char *next_break=NULL;
while (*str && strcnt)
{
unsigned short c;
str=adv_str(str, &strcnt, &c);
if (c == '\n') return str;
if (c != '\r')
{
charEnt *ent=findChar(c);
if (ent && ent->base_offset > 0 && ent->base_offset < m_cachestore.GetSize())
{
w -= ent->advance;
if (w < 0) return next_break ? next_break : str;
}
}
if (c == ' ' || c == '\t' || c == '\r') next_break=str;
}
return str;
}
int LICE_CachedFont::DrawTextImpl(LICE_IBitmap *bm, const char *str, int strcnt,
RECT *rect, UINT dtFlags)
{
WDL_ASSERT((dtFlags & DT_SINGLELINE) || !(dtFlags & (DT_BOTTOM|DT_VCENTER))); // if DT_BOTTOM or DT_VCENTER used, must have DT_SINGLELINE
if (!bm && !(dtFlags&DT_CALCRECT)) return 0;
const int __sc = bm ? (int)bm->Extended(LICE_EXT_GET_SCALING,NULL) : 0;
int bm_w = bm ? bm->getWidth() : 0;
int bm_h = bm ? bm->getHeight() : 0;
if (__sc>0 && rect)
{
if (!IGNORE_SCALING(m_comb))
{
__LICE_SC(rect->left);
__LICE_SC(rect->top);
__LICE_SC(rect->right);
__LICE_SC(rect->bottom);
}
__LICE_SC(bm_w);
__LICE_SC(bm_h);
}
bool forceWantAlpha=false;
if (dtFlags & LICE_DT_NEEDALPHA)
{
forceWantAlpha=true;
dtFlags &= ~LICE_DT_NEEDALPHA;
}
if (dtFlags&DT_SINGLELINE) dtFlags &= ~DT_WORDBREAK;
#ifndef _WIN32
const int lsadj = m_lsadj+3;
#else
const int lsadj = m_lsadj;
#endif
// if using line-spacing adjustments (m_lsadj), don't allow native rendering
// todo: split rendering up into invidual lines and DrawText calls
#ifndef LICE_TEXT_NONATIVE
int ret=0;
if (!bm || !bm->Extended('YUVx',NULL)) if (((m_flags&LICE_FONT_FLAG_FORCE_NATIVE) && m_font && !forceWantAlpha &&!LICE_Text_IsWine() &&
#ifndef _WIN32
// swell does not support DT_WORDBREAK at the moment
!(dtFlags & DT_WORDBREAK) &&
#endif
!(dtFlags & LICE_DT_USEFGALPHA) &&
!(m_flags&LICE_FONT_FLAG_PRECALCALL) && !LICE_FONT_FLAGS_HAS_FX(m_flags) &&
(!lsadj || (dtFlags&DT_SINGLELINE))) ||
(m_line_height >= USE_NATIVE_RENDERING_FOR_FONTS_HIGHER_THAN) )
{
#ifdef _WIN32
WCHAR wtmpbuf[1024];
WCHAR *wtmp=NULL;
#ifdef WDL_SUPPORT_WIN9X
static int win9x;
if (!win9x) win9x = GetVersion() < 0x80000000 ? -1 : 1; //>0 if win9x
if (win9x<0 && LICE_Text_HasUTF8(str))
#else
if (LICE_Text_HasUTF8(str))
#endif
{
int req = MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,str,strcnt,NULL,0);
if (req < 1000)
{
int cnt=0;
if ((cnt=MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,str,strcnt,wtmpbuf,1024)))
{
wtmp=wtmpbuf;
wtmp[cnt]=0;
}
}
else
{
wtmp = (WCHAR *)malloc((req + 32)*sizeof(WCHAR));
int cnt=-1;
if (wtmp && !(cnt=MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,str,strcnt,wtmp,req+1)))
free(wtmp);
else if (cnt>0) wtmp[cnt]=0;
}
}
#endif
HDC hdc = (bm ? bm->getDC() : 0);
HGDIOBJ oldfont = 0;
RECT dt_rect={0,};
bool isTmp=false;
RECT tmp_rect={0,};
#ifdef _WIN32
HRGN rgn=NULL;
#endif
RECT nr_subbitmap_clip = {0,};
bool nr_subbitmap_clip_use=false;
if (!hdc && bm)
{
LICE_IBitmap *bmt = bm;
while (bmt->Extended(LICE_SubBitmap::LICE_GET_SUBBITMAP_VERSION,NULL) == (INT_PTR)LICE_SubBitmap::LICE_SUBBITMAP_VERSION)
{
LICE_SubBitmap *sb = (LICE_SubBitmap *)bmt;
int sub_x = sb->m_x, sub_y = sb->m_y;
if (__sc>0)
{
__LICE_SC(sub_x);
__LICE_SC(sub_y);
}
nr_subbitmap_clip.left += sub_x;
nr_subbitmap_clip.top += sub_y;
bmt = sb->m_parent;
if (!bmt) break; // ran out of parents
hdc=bmt->getDC();
if (hdc)
{
int sub_w = ((LICE_SubBitmap*)bm)->m_w, sub_h = ((LICE_SubBitmap*)bm)->m_h;
if (__sc>0)
{
__LICE_SC(sub_w);
__LICE_SC(sub_h);
}
nr_subbitmap_clip.right = nr_subbitmap_clip.left + sub_w;
nr_subbitmap_clip.bottom = nr_subbitmap_clip.top + sub_h;
nr_subbitmap_clip_use=!(dtFlags & DT_CALCRECT);
bm = bmt;
break;
}
}
}
if (!hdc)
{
// use temp buffer -- we could optionally enable this if non-1 alpha was desired, though this only works for BLIT_MODE_COPY anyway (since it will end up compositing the whole rectangle, ugh)
isTmp=true;
if (!s_nativerender_tempbitmap)
s_nativerender_tempbitmap = new LICE_SysBitmap;
if (s_nativerender_tempbitmap->getWidth() < 4 || s_nativerender_tempbitmap->getHeight() < 4) s_nativerender_tempbitmap->resize(4,4);
hdc = s_nativerender_tempbitmap->getDC();
if (!hdc) goto finish_up_native_render;
oldfont = SelectObject(hdc, m_font);
RECT text_size = {0,0};
ret =
#ifdef _WIN32
wtmp ? ::DrawTextW(hdc,wtmp,-1,&text_size,(dtFlags&~(DT_CENTER|DT_VCENTER|DT_TOP|DT_BOTTOM|DT_LEFT|DT_RIGHT))|DT_CALCRECT|DT_NOPREFIX) :
#endif
::DrawText(hdc,str,strcnt,&text_size,(dtFlags&~(DT_CENTER|DT_VCENTER|DT_TOP|DT_BOTTOM|DT_LEFT|DT_RIGHT))|DT_CALCRECT|DT_NOPREFIX);
if (dtFlags & DT_CALCRECT)
{
rect->right = rect->left + text_size.right - text_size.left;
rect->bottom = rect->top + text_size.bottom - text_size.top;
goto finish_up_native_render;
}
if (!bm) goto finish_up_native_render;
if (dtFlags & DT_RIGHT) tmp_rect.left = rect->right - text_size.right;
else if (dtFlags & DT_CENTER) tmp_rect.left = (rect->right+rect->left- text_size.right)/2;
else tmp_rect.left = rect->left;
tmp_rect.right = tmp_rect.left + text_size.right;
if (dtFlags & DT_BOTTOM) tmp_rect.top = rect->bottom - text_size.bottom;
else if (dtFlags & DT_VCENTER) tmp_rect.top = (rect->bottom+rect->top- text_size.bottom)/2;
else tmp_rect.top = rect->top;
tmp_rect.bottom = tmp_rect.top + text_size.bottom;
// tmp_rect is the desired rect of drawing, now clip to bitmap (adjusting dt_rect.top/left if starting offscreen)
if (tmp_rect.right > bm_w) tmp_rect.right=bm_w;
if (tmp_rect.bottom > bm_h) tmp_rect.bottom=bm_h;
int lclip = 0, tclip = 0;
// clip tmp_rect to rect if not DT_NOCLIP
if (!(dtFlags&DT_NOCLIP))
{
if (tmp_rect.right > rect->right) tmp_rect.right=rect->right;
if (tmp_rect.bottom > rect->bottom) tmp_rect.bottom=rect->bottom;
if (rect->left > 0) lclip = rect->left;
if (rect->top > 0) tclip = rect->top;
}
if (tmp_rect.left < lclip)
{
dt_rect.left = tmp_rect.left - lclip;
tmp_rect.left = lclip;
}
if (tmp_rect.top < tclip)
{
dt_rect.top = tmp_rect.top - tclip;
tmp_rect.top = tclip;
}
if (tmp_rect.bottom <= tmp_rect.top || tmp_rect.right <= tmp_rect.left) goto finish_up_native_render;
dtFlags &= ~(DT_BOTTOM|DT_RIGHT|DT_CENTER|DT_VCENTER);
int left_overrender = 2+(m_line_height>=16 ? m_line_height/16 : 0);
if (left_overrender>tmp_rect.left) left_overrender=tmp_rect.left;
tmp_rect.left -= left_overrender;
if (tmp_rect.right-tmp_rect.left > s_nativerender_tempbitmap->getWidth() ||
tmp_rect.bottom-tmp_rect.top > s_nativerender_tempbitmap->getHeight())
{
SelectObject(hdc,oldfont);
s_nativerender_tempbitmap->resize(tmp_rect.right-tmp_rect.left, tmp_rect.bottom-tmp_rect.top);
hdc = s_nativerender_tempbitmap->getDC();
oldfont = SelectObject(hdc, m_font);
}
LICE_Blit(s_nativerender_tempbitmap, bm, 0, 0, &tmp_rect, 1.0f, LICE_BLIT_MODE_COPY);
dt_rect.left += left_overrender;
dt_rect.right=tmp_rect.right;
dt_rect.bottom=tmp_rect.bottom;
}
else
{
oldfont = SelectObject(hdc, m_font);
dt_rect = *rect;
}
::SetTextColor(hdc,RGB(LICE_GETR(m_fg),LICE_GETG(m_fg),LICE_GETB(m_fg)));
::SetBkMode(hdc,m_bgmode);
if (m_bgmode==OPAQUE) ::SetBkColor(hdc,RGB(LICE_GETR(m_bg),LICE_GETG(m_bg),LICE_GETB(m_bg)));
if (nr_subbitmap_clip_use)
{
#ifdef _WIN32
rgn=CreateRectRgn(0,0,0,0);
if (!GetClipRgn(hdc,rgn))
{
DeleteObject(rgn);
rgn=NULL;
}
IntersectClipRect(hdc,nr_subbitmap_clip.left,nr_subbitmap_clip.top,nr_subbitmap_clip.right,nr_subbitmap_clip.bottom);
#else
SWELL_PushClipRegion(hdc);
SWELL_SetClipRegion(hdc,&nr_subbitmap_clip);
#endif
dt_rect.left += nr_subbitmap_clip.left;
dt_rect.top += nr_subbitmap_clip.top;
dt_rect.right += nr_subbitmap_clip.left;
dt_rect.bottom += nr_subbitmap_clip.top;
}
ret =
#ifdef _WIN32
wtmp ? ::DrawTextW(hdc,wtmp,-1,&dt_rect,dtFlags|DT_NOPREFIX) :
#endif
::DrawText(hdc,str,strcnt,&dt_rect,dtFlags|DT_NOPREFIX);
if (nr_subbitmap_clip_use)
{
#ifdef _WIN32
SelectClipRgn(hdc,rgn); // if rgn is NULL, clears region
if (rgn) DeleteObject(rgn);
#else
SWELL_PopClipRegion(hdc);
#endif
}
if (isTmp)
LICE_Blit(bm, s_nativerender_tempbitmap, tmp_rect.left, tmp_rect.top,
0,0, tmp_rect.right-tmp_rect.left, tmp_rect.bottom-tmp_rect.top,
m_alpha, // this is a slight improvement over the non-tempbuffer version, but might not be necessary...
LICE_BLIT_MODE_COPY);
else if (dtFlags & DT_CALCRECT) *rect = dt_rect;
finish_up_native_render:
if (hdc) SelectObject(hdc, oldfont);
#ifdef _WIN32
if (wtmp!=wtmpbuf) free(wtmp);
#endif
__LICE_SC_DRAWTEXT_RESTORE_RECT
if (__sc>0) ret = (ret * 256) / __sc;
return ret;
}
#endif
// ensure all glyphs rendered
const char *tstr=str;
int tcnt=strcnt;
while (*tstr && tcnt)
{
unsigned short c;
tstr=adv_str(tstr, &tcnt, &c);
if (c == '\r') continue;
if (c == '\n')
{
if (dtFlags & DT_SINGLELINE) c=' ';
else continue;
}
charEnt *ent=findChar(c);
if (!ent)
{
const int os=m_extracharlist.GetSize();
RenderGlyph(c);
if (m_extracharlist.GetSize() != os) ent=findChar(c);
}
if (ent && ent->base_offset == 0) RenderGlyph(c);
}
if (dtFlags & DT_CALCRECT)
{
int xpos=0;
int ypos=0;
int max_xpos=0;
int max_ypos=0;
const char *next_break=NULL;
while (*str && strcnt)
{
unsigned short c;
str=adv_str(str, &strcnt, &c);
if (c == '\r') continue;
if (c == '\n')
{
if (dtFlags & DT_SINGLELINE)
{
c=' '; // different from win32 native behavior, which skips the character
}
else
{
if (m_flags&LICE_FONT_FLAG_VERTICAL)
{
xpos+=m_line_height+lsadj;
ypos=0;
}
else
{
ypos+=m_line_height+lsadj;
xpos=0;
}
if (dtFlags&DT_WORDBREAK) next_break=NULL;
continue;
}
}
charEnt *ent = findChar(c);
if (ent && ent->base_offset > 0 && ent->base_offset < m_cachestore.GetSize())
{
if (m_flags&LICE_FONT_FLAG_VERTICAL)
{
const int yext = ypos + ent->height - ent->left_extra;
ypos += ent->advance;
if (xpos+ent->width>max_xpos) max_xpos=xpos+ent->width;
if (ypos>max_ypos) max_ypos=ypos;
if (yext>max_ypos) max_ypos=yext;
}
else
{
const int xext = xpos + ent->width - ent->left_extra;
xpos += ent->advance;
if (ypos+ent->height>max_ypos) max_ypos=ypos+ent->height;
if (xpos>max_xpos) max_xpos=xpos;
if (xext>max_xpos) max_xpos=xext;
}
if (dtFlags&DT_WORDBREAK)
{
if (m_flags&LICE_FONT_FLAG_VERTICAL)
{
if (str == next_break)
{
xpos += m_line_height+lsadj;
ypos=0;
next_break=NULL;
}
if (!next_break)
{
next_break=NextWordBreak(str, strcnt, rect->bottom-rect->top-ypos);
}
}
else
{
if (str == next_break)
{
ypos += m_line_height+lsadj;
xpos=0;
next_break=NULL;
}
if (!next_break)
{
next_break=NextWordBreak(str, strcnt, rect->right-rect->left-xpos);
}
}
}
}
}
rect->right = rect->left+max_xpos;
rect->bottom = rect->top+max_ypos;
int retval = (m_flags&LICE_FONT_FLAG_VERTICAL) ? max_xpos : max_ypos;
__LICE_SC_DRAWTEXT_RESTORE_RECT
if (__sc>0) return (retval * 256) / __sc;
return retval;
}
float alphaSave = m_alpha;
if (dtFlags & LICE_DT_USEFGALPHA)
{
m_alpha *= LICE_GETA(m_fg)/255.0;
}
if (m_alpha==0.0)
{
m_alpha=alphaSave;
__LICE_SC_DRAWTEXT_RESTORE_RECT
return 0;
}
RECT use_rect=*rect;
int xpos=use_rect.left;
int ypos=use_rect.top;
bool isVertRev = false;
if ((m_flags&(LICE_FONT_FLAG_VERTICAL|LICE_FONT_FLAG_VERTICAL_BOTTOMUP)) == (LICE_FONT_FLAG_VERTICAL|LICE_FONT_FLAG_VERTICAL_BOTTOMUP))
isVertRev=true;
if (dtFlags & (DT_CENTER|DT_VCENTER|DT_RIGHT|DT_BOTTOM))
{
RECT tr={0,};
DrawTextImpl(bm,str,strcnt,&tr,DT_CALCRECT|(dtFlags & DT_SINGLELINE)|(forceWantAlpha?LICE_DT_NEEDALPHA:0));
if (__sc > 0)
{
__LICE_SC(tr.right);
__LICE_SC(tr.bottom);
}
if (dtFlags & DT_CENTER)
{
xpos += (use_rect.right-use_rect.left-tr.right)/2;
}
else if (dtFlags & DT_RIGHT)
{
xpos = use_rect.right - tr.right;
}
if (dtFlags & DT_VCENTER)
{
ypos += (use_rect.bottom-use_rect.top-tr.bottom)/2;
}
else if (dtFlags & DT_BOTTOM)
{
ypos = use_rect.bottom - tr.bottom;
}
if (isVertRev)
ypos += tr.bottom;
}
else if (isVertRev)
{
RECT tr={0,};
DrawTextImpl(bm,str,strcnt,&tr,DT_CALCRECT|(dtFlags & DT_SINGLELINE)|(forceWantAlpha?LICE_DT_NEEDALPHA:0));
if (__sc > 0) __LICE_SC(tr.bottom);
ypos += tr.bottom;
}
int start_y=ypos;
int start_x=xpos;
int max_ypos=ypos;
int max_xpos=xpos;
if (!(dtFlags & DT_NOCLIP))
{
if (use_rect.left<0)use_rect.left=0;
if (use_rect.top<0) use_rect.top=0;
if (use_rect.right > bm_w) use_rect.right = bm_w;
if (use_rect.bottom > bm_h) use_rect.bottom = bm_h;
if (use_rect.right <= use_rect.left || use_rect.bottom <= use_rect.top)
{
m_alpha=alphaSave;
__LICE_SC_DRAWTEXT_RESTORE_RECT
return 0;
}
}
else
{
use_rect.left=use_rect.top=0;
use_rect.right = bm_w;
use_rect.bottom = bm_h;
}
// todo: handle DT_END_ELLIPSIS etc
// thought: calculate length of "...", then when pos+length+widthofnextchar >= right, switch
// might need to precalc size to make sure it's needed, though
const char *next_break=NULL;
while (*str && strcnt)
{
unsigned short c;
str=adv_str(str, &strcnt, &c);
if (c == '\r') continue;
if (c == '\n')
{
if (dtFlags & DT_SINGLELINE)
{
c=' '; // different from win32 native behavior, which skips the character
}
else
{
if (m_flags&LICE_FONT_FLAG_VERTICAL)
{
xpos+=m_line_height+lsadj;
ypos=start_y;
}
else
{
ypos+=m_line_height+lsadj;
xpos=start_x;
}
if (dtFlags&DT_WORDBREAK) next_break=NULL;
continue;
}
}
charEnt *ent = findChar(c);
if (ent && ent->base_offset > 0 && ent->base_offset < m_cachestore.GetSize())
{
if (isVertRev) ypos -= ent->height;
bool drawn = DrawGlyph(bm,c,xpos,ypos,&use_rect);
if (m_flags&LICE_FONT_FLAG_VERTICAL)
{
if (!isVertRev)
{
ypos += ent->advance;
}
else ypos += ent->height - ent->advance;
if (drawn && xpos+ent->width > max_xpos) max_xpos=xpos;
}
else
{
xpos += ent->advance;
if (drawn && ypos+ent->height>max_ypos) max_ypos=ypos+ent->height;
}
if (dtFlags&DT_WORDBREAK)
{
if (m_flags&LICE_FONT_FLAG_VERTICAL)
{
if (str == next_break)
{
xpos += m_line_height+lsadj;
ypos=start_y;
next_break=NULL;
}
if (!next_break)
{
next_break=NextWordBreak(str, strcnt, use_rect.bottom-ypos);
}
}
else
{
if (str == next_break)
{
ypos += m_line_height+lsadj;
xpos=start_x;
next_break=NULL;
}
if (!next_break)
{
next_break=NextWordBreak(str, strcnt, use_rect.right-xpos);
}
}
}
}
}
m_alpha=alphaSave;
int retv = (m_flags&LICE_FONT_FLAG_VERTICAL) ? max_xpos - start_x : max_ypos - start_y;
__LICE_SC_DRAWTEXT_RESTORE_RECT
if (__sc>0) return (retv*256)/__sc;
return retv;
}