mirror of
https://github.com/beefytech/Beef.git
synced 2025-07-08 17:25:59 +02:00
FontEffect support - outlined fonts
This commit is contained in:
parent
474bad09b2
commit
89bf475045
10 changed files with 385 additions and 8 deletions
|
@ -36,6 +36,104 @@ namespace Beefy.gfx
|
|||
public float mMaxWidth;
|
||||
}
|
||||
|
||||
public class FontEffect
|
||||
{
|
||||
public enum Kind
|
||||
{
|
||||
case None;
|
||||
case Outline(float thickness, uint32 color);
|
||||
}
|
||||
|
||||
public class EffectCharData
|
||||
{
|
||||
public Font.CharData mSrcCharData;
|
||||
public Font.CharData mEffectCharData ~ delete _;
|
||||
}
|
||||
|
||||
public class FontEntry
|
||||
{
|
||||
public Font mFont;
|
||||
public ImageAtlas mAtlas = new ImageAtlas() ~ delete _;
|
||||
public List<EffectCharData> mCharData = new .() ~ delete _;
|
||||
}
|
||||
|
||||
public Kind mKind;
|
||||
public String mEffectOptions = new .() ~ delete _;
|
||||
public Dictionary<Font, FontEntry> mFontEntries = new .() ~ DeleteDictionaryAndValues!(_);
|
||||
public Dictionary<Font.CharData, EffectCharData> mCharData = new .() ~ DeleteDictionaryAndValues!(_);
|
||||
|
||||
public this(Kind kind)
|
||||
{
|
||||
mKind = kind;
|
||||
switch (mKind)
|
||||
{
|
||||
case .Outline(let thickness, let color):
|
||||
mEffectOptions..Clear().AppendF(
|
||||
$"""
|
||||
Effect=Stroke
|
||||
Size={thickness}
|
||||
Color=#{color:X}
|
||||
""");
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
public ~this()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Font.CharData Apply(Font font, Font.CharData charData)
|
||||
{
|
||||
EffectCharData effectCharData = null;
|
||||
if (mCharData.TryAdd(charData, ?, var effectCharDataPtr))
|
||||
{
|
||||
effectCharData = new EffectCharData();
|
||||
*effectCharDataPtr = effectCharData;
|
||||
|
||||
effectCharData.mSrcCharData = charData;
|
||||
effectCharData.mEffectCharData = new .(charData);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (*effectCharDataPtr).mEffectCharData;
|
||||
}
|
||||
|
||||
FontEntry fontEntry = null;
|
||||
if (mFontEntries.TryAdd(font, ?, var fontEntryPtr))
|
||||
{
|
||||
fontEntry = new .();
|
||||
*fontEntryPtr = fontEntry;
|
||||
}
|
||||
else
|
||||
fontEntry = *fontEntryPtr;
|
||||
|
||||
int32 ofsX = 0;
|
||||
int32 ofsY = 0;
|
||||
if (mKind case .Outline(let thickness, ?))
|
||||
{
|
||||
ofsX += (.)Math.Ceiling(thickness) + 2;
|
||||
ofsY += (.)Math.Ceiling(thickness) + 2;
|
||||
}
|
||||
|
||||
effectCharData.mEffectCharData.mXOffset -= ofsX;
|
||||
effectCharData.mEffectCharData.mYOffset -= ofsY;
|
||||
var effectImage = fontEntry.mAtlas.Alloc(charData.mImageSegment.mSrcWidth + ofsX * 2, charData.mImageSegment.mSrcHeight + ofsY * 2);
|
||||
effectCharData.mEffectCharData.mImageSegment = effectImage;
|
||||
effectCharData.mSrcCharData.mImageSegment.ApplyEffect(effectImage, mEffectOptions);
|
||||
|
||||
/*uint32 color = 0xFFFFFFFF;
|
||||
effectImage.SetBits(0, 0, 1, 1, 1, &color);*/
|
||||
|
||||
return effectCharData.mEffectCharData;
|
||||
}
|
||||
|
||||
public void Prepare(Font font, StringView str)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class Font
|
||||
{
|
||||
[CallingConvention(.Stdcall), CLink]
|
||||
|
@ -89,6 +187,24 @@ namespace Beefy.gfx
|
|||
public int32 mYOffset;
|
||||
public int32 mXAdvance;
|
||||
public bool mIsCombiningMark;
|
||||
|
||||
public this()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public this(CharData copyFrom)
|
||||
{
|
||||
mImageSegment = copyFrom.mImageSegment;
|
||||
mX = copyFrom.mX;
|
||||
mY = copyFrom.mY;
|
||||
mWidth = copyFrom.mWidth;
|
||||
mHeight = copyFrom.mHeight;
|
||||
mXOffset = copyFrom.mXOffset;
|
||||
mYOffset = copyFrom.mYOffset;
|
||||
mXAdvance = copyFrom.mXAdvance;
|
||||
mIsCombiningMark = copyFrom.mIsCombiningMark;
|
||||
}
|
||||
}
|
||||
|
||||
public class Page
|
||||
|
@ -139,6 +255,9 @@ namespace Beefy.gfx
|
|||
//BitmapFont mBMFont ~ delete _;
|
||||
public StringView mEllipsis = "...";
|
||||
|
||||
List<FontEffect> mFontEffectStack ~ delete _;
|
||||
DisposeProxy mFontEffectDisposeProxy = new .(new () => { PopFontEffect(); }) ~ delete _;
|
||||
|
||||
public this()
|
||||
{
|
||||
}
|
||||
|
@ -153,6 +272,19 @@ namespace Beefy.gfx
|
|||
FTFont_ClearCache();
|
||||
}
|
||||
|
||||
public DisposeProxy PushFontEffect(FontEffect fontEffect)
|
||||
{
|
||||
if (mFontEffectStack == null)
|
||||
mFontEffectStack = new .();
|
||||
mFontEffectStack.Add(fontEffect);
|
||||
return mFontEffectDisposeProxy;
|
||||
}
|
||||
|
||||
public void PopFontEffect()
|
||||
{
|
||||
mFontEffectStack.PopBack();
|
||||
}
|
||||
|
||||
static void BuildFontNameCache()
|
||||
{
|
||||
#if BF_PLATFORM_WINDOWS
|
||||
|
@ -666,13 +798,23 @@ namespace Beefy.gfx
|
|||
mLowCharData[(int)checkChar] = charData;
|
||||
else
|
||||
mCharData[checkChar] = charData;
|
||||
return charData;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (charData == null)
|
||||
{
|
||||
if (checkChar == (char32)'?')
|
||||
return null;
|
||||
return GetCharData((char32)'?');
|
||||
}
|
||||
|
||||
if (mFontEffectStack?.IsEmpty == false)
|
||||
{
|
||||
var fontEffect = mFontEffectStack.Back;
|
||||
charData = fontEffect.Apply(this, charData);
|
||||
}
|
||||
|
||||
return charData;
|
||||
}
|
||||
|
||||
|
@ -839,7 +981,10 @@ namespace Beefy.gfx
|
|||
|
||||
uint32 color = g.mColor;
|
||||
|
||||
g.PushTextRenderState();
|
||||
bool usingTextRenderState = mFontEffectStack?.IsEmpty != false;
|
||||
|
||||
if (usingTextRenderState)
|
||||
g.PushTextRenderState();
|
||||
|
||||
float markTopOfs = 0;
|
||||
float markBotOfs = 0;
|
||||
|
@ -943,7 +1088,8 @@ namespace Beefy.gfx
|
|||
prevChar = c;
|
||||
}
|
||||
|
||||
g.PopRenderState();
|
||||
if (usingTextRenderState)
|
||||
g.PopRenderState();
|
||||
|
||||
if (fontMetrics != null)
|
||||
fontMetrics.mMaxX = Math.Max(fontMetrics.mMaxX, curX);
|
||||
|
|
|
@ -71,6 +71,9 @@ namespace Beefy.gfx
|
|||
[CallingConvention(.Stdcall), CLink]
|
||||
static extern int32 Gfx_Texture_GetHeight(void* textureSegment);
|
||||
|
||||
[CallingConvention(.Stdcall), CLink]
|
||||
static extern void Gfx_ApplyEffect(void* destTextureSegment, void* srcTextureSegment, char8* options);
|
||||
|
||||
public this()
|
||||
{
|
||||
}
|
||||
|
@ -189,6 +192,11 @@ namespace Beefy.gfx
|
|||
Gfx_ModifyTextureSegment(mNativeTextureSegment, srcImage.mNativeTextureSegment, (int32)srcX, (int32)srcY, (int32)srcWidth, (int32)srcHeight);
|
||||
}
|
||||
|
||||
public void ApplyEffect(Image destImage, StringView options)
|
||||
{
|
||||
Gfx_ApplyEffect(destImage.mNativeTextureSegment, mNativeTextureSegment, options.ToScopeCStr!());
|
||||
}
|
||||
|
||||
public void SetBits(int destX, int destY, int destWidth, int destHeight, int srcPitch, uint32* bits)
|
||||
{
|
||||
Gfx_Texture_SetBits(mNativeTextureSegment, (.)destX, (.)destY, (.)destWidth, (.)destHeight, (.)srcPitch, bits);
|
||||
|
|
66
BeefLibs/Beefy2D/src/gfx/ImageAtlas.bf
Normal file
66
BeefLibs/Beefy2D/src/gfx/ImageAtlas.bf
Normal file
|
@ -0,0 +1,66 @@
|
|||
using System.Collections;
|
||||
using System;
|
||||
namespace Beefy.gfx;
|
||||
|
||||
class ImageAtlas
|
||||
{
|
||||
public class Page
|
||||
{
|
||||
public Image mImage ~ delete _;
|
||||
public int32 mCurX;
|
||||
public int32 mCurY;
|
||||
public int32 mMaxRowHeight;
|
||||
}
|
||||
|
||||
public List<Page> mPages = new .() ~ DeleteContainerAndItems!(_);
|
||||
public bool mAllowMultiplePages = true;
|
||||
|
||||
public int32 mImageWidth = 1024;
|
||||
public int32 mImageHeight = 1024;
|
||||
|
||||
public this()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Image Alloc(int32 width, int32 height)
|
||||
{
|
||||
Page page = null;
|
||||
if (!mPages.IsEmpty)
|
||||
{
|
||||
page = mPages.Back;
|
||||
if (page.mCurX + (int)width > page.mImage.mSrcWidth)
|
||||
{
|
||||
// Move down to next row
|
||||
page.mCurX = 0;
|
||||
page.mCurY += page.mMaxRowHeight;
|
||||
page.mMaxRowHeight = 0;
|
||||
}
|
||||
|
||||
if (page.mCurY + height > page.mImage.mSrcHeight)
|
||||
{
|
||||
// Doesn't fit
|
||||
page = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (page == null)
|
||||
{
|
||||
page = new .();
|
||||
page.mImage = Image.CreateDynamic(mImageWidth, mImageHeight);
|
||||
|
||||
uint32* colors = new uint32[mImageWidth*mImageHeight]*;
|
||||
defer delete colors;
|
||||
for (int i < mImageWidth*mImageHeight)
|
||||
colors[i] = 0xFF000000 | (.)i;
|
||||
|
||||
page.mImage.SetBits(0, 0, mImageWidth, mImageHeight, mImageWidth, colors);
|
||||
mPages.Add(page);
|
||||
}
|
||||
|
||||
Image image = page.mImage.CreateImageSegment(page.mCurX, page.mCurY, width, height);
|
||||
page.mCurX += width;
|
||||
page.mMaxRowHeight = Math.Max(page.mMaxRowHeight, height);
|
||||
return image;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue