EM_Task/UnrealEd/Private/Factories/TTFontImport.cpp
Boshuang Zhao 5144a49c9b add
2026-02-13 16:18:33 +08:00

2299 lines
84 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
TTFontImport.cpp: True-type Font Importing
=============================================================================*/
#include "CoreMinimal.h"
#include "HAL/FileManager.h"
#include "Misc/FileHelper.h"
#include "HAL/ThreadSafeCounter.h"
#include "Stats/Stats.h"
#include "Async/AsyncWork.h"
#include "Misc/FeedbackContext.h"
#include "Modules/ModuleManager.h"
#include "Widgets/SWindow.h"
#include "Engine/FontImportOptions.h"
#include "Engine/Font.h"
#include "Factories/TrueTypeFontFactory.h"
#include "RenderUtils.h"
#include "Engine/Texture2D.h"
#include "Interfaces/IMainFrameModule.h"
#include "IDesktopPlatform.h"
#include "DesktopPlatformModule.h"
#ifndef WITH_FREETYPE
#define WITH_FREETYPE 0
#endif // WITH_FREETYPE
#if PLATFORM_WINDOWS
#include "Windows/WindowsHWrapper.h"
#include "Windows/AllowWindowsPlatformTypes.h"
namespace TTFConstants
{
uint32 WIN_SRCCOPY = SRCCOPY;
}
#include "Windows/HideWindowsPlatformTypes.h"
#endif // PLATFORM_WINDOWS
#define USE_FREETYPE (!PLATFORM_WINDOWS && WITH_FREETYPE) // @todo: Enable for Windows when support for bitmap fonts is fixed
#if USE_FREETYPE
#include "ft2build.h"
// Freetype style include
#include FT_FREETYPE_H
#include FT_GLYPH_H
#endif // USE_FREETYPE
DEFINE_LOG_CATEGORY_STATIC(LogTTFontImport, Log, All);
#define LOCTEXT_NAMESPACE "TTFontImport"
UTrueTypeFontFactory::UTrueTypeFontFactory(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
SupportedClass = UFont::StaticClass();
bCreateNew = true;
bEditorImport = false;
bEditAfterNew = true;
ImportPriority = -1;
LODGroup = TEXTUREGROUP_UI;
}
void UTrueTypeFontFactory::PostInitProperties()
{
Super::PostInitProperties();
if (!HasAnyFlags(RF_ClassDefaultObject | RF_NeedLoad))
{
SetupFontImportOptions();
}
}
int32 FromHex(TCHAR Ch)
{
if (Ch >= '0' && Ch <= '9')
return Ch - '0';
else if (Ch >= 'a' && Ch <= 'f')
return 10 + Ch - 'a';
else if (Ch >= 'A' && Ch <= 'F')
return 10 + Ch - 'A';
UE_LOG(LogTTFontImport, Fatal, TEXT("Expecting digit, got character %c"), Ch);
return 0;
}
#if !PLATFORM_WINDOWS // !!! FIXME
#define FW_NORMAL 400
#endif
void UTrueTypeFontFactory::SetupFontImportOptions()
{
// Allocate our import options object if it hasn't been created already!
ImportOptions = NewObject<UFontImportOptions>(this, NAME_None);
}
bool UTrueTypeFontFactory::ConfigureProperties()
{
// Set bFontSelected to false so we can see test selection
bFontSelected = false;
// Show the dialog to let them choose the font
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
if (DesktopPlatform)
{
void* ParentWindowWindowHandle = NULL;
IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>(TEXT("MainFrame"));
const TSharedPtr<SWindow>& MainFrameParentWindow = MainFrameModule.GetParentWindow();
if (MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid())
{
ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle();
}
if (ImportOptions == NULL)
{
SetupFontImportOptions();
}
EFontImportFlags FontFlags;
bFontSelected = DesktopPlatform->OpenFontDialog(
ParentWindowWindowHandle,
ImportOptions->Data.FontName,
ImportOptions->Data.Height,
FontFlags);
if (bFontSelected)
{
if (!!(FontFlags & EFontImportFlags::EnableUnderline))
{
ImportOptions->Data.bEnableUnderline = true;
}
if (!!(FontFlags & EFontImportFlags::EnableItalic))
{
ImportOptions->Data.bEnableItalic = true;
}
if (!!(FontFlags & EFontImportFlags::EnableBold))
{
ImportOptions->Data.bEnableBold = true;
}
}
}
bPropertiesConfigured = true;
return bFontSelected;
}
UObject* UTrueTypeFontFactory::FactoryCreateNew(
UClass* Class,
UObject* InParent,
FName Name,
EObjectFlags Flags,
UObject* Context,
FFeedbackContext* Warn)
{
#if !PLATFORM_WINDOWS && !PLATFORM_MAC && !PLATFORM_LINUX
STUBBED("Windows/Mac TTF code");
return NULL;
#else
check(Class == UFont::StaticClass());
if (bPropertiesConfigured && !bFontSelected)
{
// If the font dialog was shown but no font was selected, don't create a font object.
return NULL;
}
// Create font and its texture.
auto Font = NewObject<UFont>(InParent, Name, Flags);
if (ImportOptions->Data.bUseDistanceFieldAlpha)
{
// high res font bitmap should only contain a mask
ImportOptions->Data.bEnableAntialiasing = false;
// drop shadows can be generated dynamically during rendering of distance fields
ImportOptions->Data.bEnableDropShadow = false;
// scale factor should always be a power of two
ImportOptions->Data.DistanceFieldScaleFactor = FMath::RoundUpToPowerOfTwo(FMath::Max<int32>(ImportOptions->Data.DistanceFieldScaleFactor, 2));
ImportOptions->Data.DistanceFieldScanRadiusScale = FMath::Clamp<float>(ImportOptions->Data.DistanceFieldScanRadiusScale, 0.f, 8.f);
// need a minimum padding of 4,4 to prevent bleeding of distance values across characters
ImportOptions->Data.XPadding = FMath::Max<int32>(4, ImportOptions->Data.XPadding);
ImportOptions->Data.YPadding = FMath::Max<int32>(4, ImportOptions->Data.YPadding);
}
// Copy the import settings into the font for later reference
Font->ImportOptions = ImportOptions->Data;
// For a single-resolution font, we'll create a one-element array and pass that along to our import function
TArray<float> ResHeights;
ResHeights.Add(ImportOptions->Data.Height);
GWarn->BeginSlowTask(NSLOCTEXT("UnrealEd", "FontFactory_ImportingTrueTypeFont", "Importing TrueType Font..."), true);
// Import the font
const bool bSuccess = ImportTrueTypeFont(Font, Warn, ResHeights.Num(), ResHeights);
if (!bSuccess)
{
Font = NULL;
}
GWarn->EndSlowTask();
return bSuccess ? Font : NULL;
#endif
}
bool UTrueTypeFontFactory::CanReimport(UObject* Obj, TArray<FString>& OutFilenames)
{
UFont* FontToReimport = Cast<UFont>(Obj);
if (FontToReimport && FontToReimport->FontCacheType == EFontCacheType::Offline)
{
OutFilenames.Add(TEXT("None"));
return true;
}
return false;
}
void UTrueTypeFontFactory::SetReimportPaths(UObject* Obj, const TArray<FString>& NewReimportPaths)
{
// No path is needed
}
EReimportResult::Type UTrueTypeFontFactory::Reimport(UObject* InObject)
{
UFont* FontToReimport = Cast<UFont>(InObject);
if (FontToReimport == nullptr)
{
return EReimportResult::Failed;
}
SetupFontImportOptions();
this->ImportOptions->Data = FontToReimport->ImportOptions;
bool OutCanceled = false;
if (ImportObject(InObject->GetClass(), InObject->GetOuter(), *InObject->GetName(), RF_Public | RF_Standalone, TEXT(""), nullptr, OutCanceled) != nullptr)
{
return EReimportResult::Succeeded;
}
return EReimportResult::Failed;
}
int32 UTrueTypeFontFactory::GetPriority() const
{
return ImportPriority;
}
/**
* Utility to convert texture alpha mask to a signed distance field
*
* Based on the following paper:
* http://www.valvesoftware.com/publications/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf
*/
class FTextureAlphaToDistanceField
{
/** Container for the input image from which we build the distance field */
struct FTaskSrcData
{
/**
* Creates a source data with the following parameters:
*
* @param SrcTexture A pointer to the raw data
* @param InSrcSizeX The Width of the data in pixels
* @param InSrcSizeY The Height of the data in pixels
* @param InSrcFormat The format of each pixel
*/
FTaskSrcData(const uint8* InSrcTexture, int32 InSrcSizeX, int32 InSrcSizeY, uint8 InSrcFormat)
: SrcSizeX(InSrcSizeX), SrcSizeY(InSrcSizeY), SrcTexture(InSrcTexture), SrcFormat(InSrcFormat)
{
check(SrcFormat == PF_B8G8R8A8);
}
/**
* Get the color for the source texture at the specified coordinates
*
* @param PointX - x coordinate
* @param PointY - y coordinate
* @return texel color
*/
FORCEINLINE FColor GetSourceColor(int32 PointX, int32 PointY) const
{
checkSlow(PointX < SrcSizeX && PointY < SrcSizeY);
return FColor(
SrcTexture[4 * (PointX + PointY * SrcSizeX) + 2],
SrcTexture[4 * (PointX + PointY * SrcSizeX) + 1],
SrcTexture[4 * (PointX + PointY * SrcSizeX) + 0],
SrcTexture[4 * (PointX + PointY * SrcSizeX) + 3]);
}
/**
* Get just the alpha for the source texture at the specified coordinates
*
* @param PointX - x coordinate
* @param PointY - y coordinate
* @return texel alpha
*/
FORCEINLINE uint8 GetSourceAlpha(int32 PointX, int32 PointY) const
{
checkSlow(PointX < SrcSizeX && PointY < SrcSizeY);
return SrcTexture[4 * (PointX + PointY * SrcSizeX) + 3];
}
/** Width of the source texture */
const int32 SrcSizeX;
/** Height of the source texture */
const int32 SrcSizeY;
private:
/** Source texture used for silhouette determination. Alpha channel contains mask */
const uint8* SrcTexture;
/** Format of the source texture. Assumed to be PF_B8G8R8A8 */
const uint8 SrcFormat;
};
/** The source data */
const FTaskSrcData TaskData;
/** Downsampled destination texture. Populated by Generate(). Alpha channel contains distance field */
TArray<uint8> DstTexture;
/** Width of the destination texture */
int32 DstSizeX;
/** Height of the destination texture */
int32 DstSizeY;
/** Format of the destination texture */
uint8 DstFormat;
/** A task that builds the distance field for a strip of the image. */
class FBuildDistanceFieldTask: public FNonAbandonableTask
{
public:
/**
* Initialize the task as follows:
*
* @param InThreadScaleCounter Used by the invoker to track when tasks are completed.
* @param InTaskData Source data from which to build the distance field
* @param InStartRow Row on which this task should start.
* @param InNumRowsToProcess How many rows this task should process
* @param InScanRadius Radius of area to scan for nearest border
* @param InScaleFactor The input image and resulting distance field
* @param WorkRemainingCounter Counter used for providing feedback (i.e. updating the progress bar)
*/
FBuildDistanceFieldTask(
FThreadSafeCounter* InThreadScaleCounter,
TArray<float>* OutSignedDistances,
const FTaskSrcData* InTaskData,
int32 InStartRow,
int32 InDstRowWidth,
int32 InNumRowsToProcess,
int32 InScanRadius,
int32 InScaleFactor,
FThreadSafeCounter* InWorkRemainingCounter)
: ThreadScaleCounter(InThreadScaleCounter), SignedDistances(*OutSignedDistances), TaskData(InTaskData), StartRow(InStartRow), DstRowWidth(InDstRowWidth), NumRowsToProcess(InNumRowsToProcess), ScanRadius(InScanRadius), ScaleFactor(InScaleFactor), WorkRemainingCounter(InWorkRemainingCounter)
{
}
/**
* Calculate the signed distance at the given coordinate to the
* closest silhouette edge of the source texture.
*
* If the current point is solid then the closest non-solid
* pixel is the edge, and if the current point is non-solid
* then the closest solid pixel is the edge.
*
* @param PointX - x coordinate
* @param PointY - y coordinate
* @param ScanRadius - radius (in texels) to scan the source texture for a silhouette edge
* @return distance to silhouette edge
*/
float CalcSignedDistanceToSrc(int32 PointX, int32 PointY, int32 ScanRadius) const;
/** Called by the thread pool to do the work in this task */
void DoWork(void);
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FBuildDistanceFieldTask, STATGROUP_ThreadPoolAsyncTasks);
}
private:
FThreadSafeCounter* ThreadScaleCounter;
TArray<float>& SignedDistances;
const FTaskSrcData* TaskData;
const int32 StartRow;
const int32 DstRowWidth;
const int32 NumRowsToProcess;
const int32 ScanRadius;
const int32 ScaleFactor;
FThreadSafeCounter* WorkRemainingCounter;
};
public:
/**
* Constructor
*
* @param InSrcTexture - source texture data
* @param InSrcSizeX - source texture width
* @param InSrcSizeY - source texture height
* @param InSrcFormat - source texture format
*/
FTextureAlphaToDistanceField(const uint8* InSrcTexture, int32 InSrcSizeX, int32 InSrcSizeY, uint8 InSrcFormat)
: TaskData(InSrcTexture, InSrcSizeX, InSrcSizeY, InSrcFormat), DstSizeX(0), DstSizeY(0), DstFormat(PF_B8G8R8A8)
{
}
// Accessors
const uint8* GetResultTexture() const
{
return DstTexture.GetData();
}
const int32 GetResultTextureSize() const
{
return DstTexture.Num();
}
const int32 GetResultSizeX() const
{
return DstSizeX;
}
const int32 GetResultSizeY() const
{
return DstSizeY;
}
/**
* Generates the destination texture from the source texture.
* The alpha channel of the destination texture contains the
* signed distance field.
*
* The destination texture size is scaled based on the ScaleFactor.
* Eg. a scale factor of 4 creates a destination texture 4x smaller.
*
* @param ScaleFactor - downsample scale from source to destination texture
* @param ScanRadius - distance in texels to scan high res font for the silhouette
*/
void Generate(int32 ScaleFactor, int32 ScanRadius);
/**
* Calculate the distance between 2 coordinates
*
* @param X1 - 1st x coordinate
* @param Y1 - 1st y coordinate
* @param X2 - 2nd x coordinate
* @param Y2 - 2nd y coordinate
* @return 2d distance between the coordinates
*/
static FORCEINLINE float CalcDistance(int32 X1, int32 Y1, int32 X2, int32 Y2)
{
int32 DX = X1 - X2;
int32 DY = Y1 - Y2;
return FMath::Sqrt(DX * DX + DY * DY);
}
};
/**
* Generates the destination texture from the source texture.
* The alpha channel of the destination texture contains the
* signed distance field.
*
* The destination texture size is scaled based on the ScaleFactor.
* Eg. a scale factor of 4 creates a destination texture 4x smaller.
*
* @param ScaleFactor - downsample scale from source to destination texture
* @param ScanRadius - distance in texels to scan high res font for the silhouette
*/
void FTextureAlphaToDistanceField::Generate(int32 ScaleFactor, int32 ScanRadius)
{
// restart progress bar for distance field generation as this can be slow
GWarn->StatusUpdate(0, 0, NSLOCTEXT("TextureAlphaToDistanceField", "BeginGeneratingDistanceFieldTask", "Generating Distance Field"));
// need to maintain pow2 sizing for textures
ScaleFactor = FMath::RoundUpToPowerOfTwo(ScaleFactor);
DstSizeX = TaskData.SrcSizeX / ScaleFactor;
DstSizeY = TaskData.SrcSizeY / ScaleFactor;
// destination texture
// note that destination format can be different from source format
SIZE_T NumBytes = CalculateImageBytes(DstSizeX, DstSizeY, 0, DstFormat);
DstTexture.Empty(NumBytes);
DstTexture.AddZeroed(NumBytes);
// array of signed distance values for the downsampled texture
TArray<float> SignedDistance;
SignedDistance.Empty(DstSizeX * DstSizeY);
SignedDistance.AddUninitialized(DstSizeX * DstSizeY);
// We want to run the distance field computation as 16 tasks for a speed boost on multi-core machines.
const int32 NumTasks = 16;
FThreadSafeCounter BuildTasksCounter;
const int32 DstStripHeight = DstSizeY / NumTasks;
// We need to report the progress, and all the threads must be able to update it.
const int32 TotalDistFieldWork = DstStripHeight * NumTasks;
FThreadSafeCounter WorkProgressCounter;
int32 TotalProgress = DstSizeY - 1;
// calculate distances
for (int32 y = 0; y < DstSizeY; y += DstStripHeight)
{
// The tasks will clean themselves up when they are completed; no need to call delete elsewhere.
BuildTasksCounter.Increment();
(new FAutoDeleteAsyncTask<FBuildDistanceFieldTask>(
&BuildTasksCounter,
&SignedDistance,
&TaskData,
y,
DstSizeX,
DstStripHeight,
ScanRadius,
ScaleFactor,
&WorkProgressCounter))
->StartBackgroundTask();
}
while (BuildTasksCounter.GetValue() > 0)
{
// Waiting for Distance Field to finish generating.
GWarn->UpdateProgress(WorkProgressCounter.GetValue(), TotalDistFieldWork);
FPlatformProcess::Sleep(.1f);
}
// find min,max range of distances
const float BadMax = CalcDistance(0, 0, TaskData.SrcSizeX, TaskData.SrcSizeY);
;
const float BadMin = -BadMax;
float MaxDistance = BadMin;
float MinDistance = BadMax;
for (int32 i = 0; i < SignedDistance.Num(); i++)
{
if (SignedDistance[i] > BadMin &&
SignedDistance[i] < BadMax)
{
MinDistance = FMath::Min<float>(MinDistance, SignedDistance[i]);
MaxDistance = FMath::Max<float>(MaxDistance, SignedDistance[i]);
}
}
// normalize distances
float RangeDistance = MaxDistance - MinDistance;
for (int32 i = 0; i < SignedDistance.Num(); i++)
{
// clamp edge cases that were never found due to limited scan radius
if (SignedDistance[i] <= MinDistance)
{
SignedDistance[i] = 0.0f;
}
else if (SignedDistance[i] >= MaxDistance)
{
SignedDistance[i] = 1.0f;
}
else
{
// normalize and remap from [-1,+1] to [0,+1]
SignedDistance[i] = SignedDistance[i] / RangeDistance * 0.5f + 0.5f;
}
}
// copy results to the destination texture
if (DstFormat == PF_G8)
{
for (int32 x = 0; x < DstSizeX; x++)
{
for (int32 y = 0; y < DstSizeY; y++)
{
DstTexture[x + y * DstSizeX] = SignedDistance[x + y * DstSizeX] * 255;
}
}
}
else if (DstFormat == PF_B8G8R8A8)
{
for (int32 x = 0; x < DstSizeX; x++)
{
for (int32 y = 0; y < DstSizeY; y++)
{
const FColor SrcColor(TaskData.GetSourceColor((x * ScaleFactor) + (ScaleFactor / 2), (y * ScaleFactor) + (ScaleFactor / 2)));
DstTexture[4 * (x + y * DstSizeX) + 0] = SrcColor.B;
DstTexture[4 * (x + y * DstSizeX) + 1] = SrcColor.G;
DstTexture[4 * (x + y * DstSizeX) + 2] = SrcColor.R;
DstTexture[4 * (x + y * DstSizeX) + 3] = SignedDistance[x + y * DstSizeX] * 255;
}
}
}
else
{
checkf(0, TEXT("unsupported format specified"));
}
}
/**
* Calculate the signed distance at the given coordinate to the
* closest silhouette edge of the source texture.
*
* If the current point is solid then the closest non-solid
* pixel is the edge, and if the current point is non-solid
* then the closest solid pixel is the edge.
*
* @param PointX - x coordinate
* @param PointY - y coordinate
* @param ScanRadius - radius (in texels) to scan the source texture for a silhouette edge
*
* @return distance to silhouette edge
*/
float FTextureAlphaToDistanceField::FBuildDistanceFieldTask::CalcSignedDistanceToSrc(int32 PointX, int32 PointY, int32 InScanRadius) const
{
// determine whether or not the source point is solid
const bool BaseIsSolid = TaskData->GetSourceAlpha(PointX, PointY) > 0;
float ClosestDistance = FTextureAlphaToDistanceField::CalcDistance(0, 0, TaskData->SrcSizeX, TaskData->SrcSizeY);
// If the current point is solid then the closest non-solid
// pixel is the edge, and if the current point is non-solid
// then the closest solid pixel is the edge.
// Search pattern:
// Use increasing ring sizes allows us to early out.
// In the picture below 1s indicate the first ring
// while 2s indicate the 2nd ring.
//
// 2 2 2 2 2
// 2 1 1 1 2
// 2 1 * 1 2
// 2 1 1 1 2
// 2 2 2 2 2
//
// Note that the "rings" are not actually circular, so
// we may find a sample that is up to Sqrt(2*(RingSize^2)) away.
// Once we have found such a sample, we must search a few more
// rings in case a nearer sample can be found.
bool bFoundClosest = false;
int32 RequiredRadius = InScanRadius;
for (int RingSize = 1; RingSize <= RequiredRadius; ++RingSize)
{
const int32 StartX = FMath::Clamp<int32>(PointX - RingSize, 0, TaskData->SrcSizeX);
const int32 EndX = FMath::Clamp<int32>(PointX + RingSize, 0, TaskData->SrcSizeX - 1);
const int32 StartY = FMath::Clamp<int32>(PointY - RingSize, 0, TaskData->SrcSizeY);
const int32 EndY = FMath::Clamp<int32>(PointY + RingSize, 0, TaskData->SrcSizeY - 1);
// - - - <-- Search this top line
// . * .
// . . .
for (int x = StartX; x <= EndX; ++x)
{
const int32 y = StartY;
const uint8 SrcAlpha(TaskData->GetSourceAlpha(x, y));
if ((BaseIsSolid && SrcAlpha == 0) || (!BaseIsSolid && SrcAlpha > 0))
{
const float Dist = CalcDistance(PointX, PointY, x, y);
ClosestDistance = FMath::Min<float>(Dist, ClosestDistance);
bFoundClosest = true;
}
}
// . . .
// . * .
// - - - <-- Search the bottom line
for (int x = StartX; x <= EndX; ++x)
{
const int32 y = EndY;
const uint8 SrcAlpha(TaskData->GetSourceAlpha(x, y));
if ((BaseIsSolid && SrcAlpha == 0) || (!BaseIsSolid && SrcAlpha > 0))
{
const float Dist = CalcDistance(PointX, PointY, x, y);
ClosestDistance = FMath::Min<float>(Dist, ClosestDistance);
bFoundClosest = true;
}
}
// . . .
// - * - <-- Search the left and right two vertical lines
// . . .
for (int y = StartY + 1; y <= EndY - 1; ++y)
{
{
const int32 x = StartX;
const uint8 SrcAlpha(TaskData->GetSourceAlpha(x, y));
if ((BaseIsSolid && SrcAlpha == 0) || (!BaseIsSolid && SrcAlpha > 0))
{
const float Dist = CalcDistance(PointX, PointY, x, y);
ClosestDistance = FMath::Min<float>(Dist, ClosestDistance);
bFoundClosest = true;
}
}
{
const int32 x = EndX;
const uint8 SrcAlpha(TaskData->GetSourceAlpha(x, y));
if ((BaseIsSolid && SrcAlpha == 0) || (!BaseIsSolid && SrcAlpha > 0))
{
const float Dist = CalcDistance(PointX, PointY, x, y);
ClosestDistance = FMath::Min<float>(Dist, ClosestDistance);
bFoundClosest = true;
}
}
}
// We have found a sample on the edge, but we might have to search
// a few more rings to guarantee that we found the closest sample
// on the edge.
if (bFoundClosest && RequiredRadius >= InScanRadius)
{
RequiredRadius = FMath::CeilToInt(FMath::Sqrt(RingSize * RingSize * 2));
RequiredRadius = FMath::Min(RequiredRadius, InScanRadius);
}
}
// positive distance if inside and negative if outside
return BaseIsSolid ? ClosestDistance : -ClosestDistance;
}
/** Called by the thread pool to do the work in this task */
void FTextureAlphaToDistanceField::FBuildDistanceFieldTask::DoWork(void)
{
// Build the distance field for the strip specified for this task.
for (int32 y = StartRow; y < (StartRow + NumRowsToProcess); y++)
{
if (y % 16 == 0)
{
// Update the user about our progress
WorkRemainingCounter->Add(16);
}
// Build distance field for a single line
for (int32 x = 0; x < DstRowWidth; x++)
{
SignedDistances[x + y * DstRowWidth] = CalcSignedDistanceToSrc(
(x * ScaleFactor) + (ScaleFactor / 2),
(y * ScaleFactor) + (ScaleFactor / 2),
ScanRadius);
}
}
ThreadScaleCounter->Decrement();
}
#if PLATFORM_WINDOWS
UTexture2D* UTrueTypeFontFactory::CreateTextureFromDC(UFont* Font, HDC dc, int32 Height, int32 TextureNum)
{
FString TextureString = FString::Printf(TEXT("%s_Page"), *Font->GetName());
if (TextureNum < 26)
{
TextureString = TextureString + FString::Printf(TEXT("%c"), 'A' + TextureNum);
}
else
{
TextureString = TextureString + FString::Printf(TEXT("%c%c"), 'A' + TextureNum / 26, 'A' + TextureNum % 26);
}
if (StaticFindObject(NULL, Font, *TextureString))
{
UE_LOG(LogTTFontImport, Warning, TEXT("A texture named %s already exists!"), *TextureString);
}
int32 BitmapWidth = ImportOptions->Data.TexturePageWidth;
int32 BitmapHeight = FMath::RoundUpToPowerOfTwo(Height);
if (ImportOptions->Data.bUseDistanceFieldAlpha)
{
// scale original bitmap width by scale factor to generate high res font
// note that height is already scaled during font bitmap generation
BitmapWidth *= ImportOptions->Data.DistanceFieldScaleFactor;
}
// Create texture for page.
auto Texture = NewObject<UTexture2D>(Font, *TextureString);
// note RF_Public because font textures can be referenced directly by material expressions
Texture->SetFlags(RF_Public);
Texture->Source.Init(BitmapWidth, BitmapHeight, 1, 1, TSF_BGRA8);
// Copy the LODGroup from the font factory to the new texture
// By default, this should be TEXTUREGROUP_UI for fonts!
Texture->LODGroup = LODGroup;
// Also, we never want to stream in font textures since that always looks awful
Texture->NeverStream = true;
// Copy bitmap data to texture page.
FColor FontColor8Bit(ImportOptions->Data.ForegroundColor.ToFColor(true));
// restart progress bar for font bitmap generation since this takes a while
int32 TotalProgress = BitmapWidth - 1;
FFormatNamedArguments Args;
Args.Add(TEXT("TextureNum"), TextureNum);
GWarn->StatusUpdate(0, 0, FText::Format(NSLOCTEXT("TrueTypeFontImport", "GeneratingFontPageStatusUpdate", "Generating font page {TextureNum}"), Args));
TArray<int32> SourceData;
// Copy the data from the Device Context to our SourceData array.
// This approach is much faster than using GetPixel()!
{
// We must make a new bitmap to populate with data from the DC
BITMAPINFO BitmapInfo;
BitmapInfo.bmiHeader.biBitCount = 32;
BitmapInfo.bmiHeader.biCompression = BI_RGB;
BitmapInfo.bmiHeader.biPlanes = 1;
BitmapInfo.bmiHeader.biSize = sizeof(BitmapInfo.bmiHeader);
BitmapInfo.bmiHeader.biWidth = BitmapWidth;
BitmapInfo.bmiHeader.biHeight = -BitmapHeight; // Having a positive height results in an upside-down bitmap
// Initialize the Bitmap and the Device Context in a way that they are automatically cleaned up.
struct CleanupResourcesScopeGuard
{
HDC BitmapDC;
HBITMAP BitmapHandle;
~CleanupResourcesScopeGuard()
{
DeleteDC(BitmapDC);
DeleteObject(BitmapHandle);
}
} Resources;
Resources.BitmapDC = CreateCompatibleDC(dc);
void* BitsPtr;
Resources.BitmapHandle = CreateDIBSection(Resources.BitmapDC, &BitmapInfo, DIB_RGB_COLORS, &BitsPtr, 0, 0);
if (Resources.BitmapHandle)
{
// Bind the bitmap to the Device Context
SelectObject(Resources.BitmapDC, Resources.BitmapHandle);
// Copy from the Device Context to the Bitmap we created
BitBlt(Resources.BitmapDC, 0, 0, BitmapWidth, BitmapHeight, dc, 0, 0, TTFConstants::WIN_SRCCOPY);
// Finally copy the data from the Bitmap into an Unreal data array.
SourceData.AddUninitialized(BitmapWidth * BitmapHeight);
GetDIBits(Resources.BitmapDC, Resources.BitmapHandle, 0, BitmapHeight, SourceData.GetData(), &BitmapInfo, DIB_RGB_COLORS);
}
}
uint8* MipData = Texture->Source.LockMip(0);
if (!ImportOptions->Data.bEnableAntialiasing)
{
int32 SizeX = Texture->Source.GetSizeX();
int32 SizeY = Texture->Source.GetSizeY();
for (int32 i = 0; i < SizeX; i++)
{
// update progress bar
GWarn->UpdateProgress(i, TotalProgress);
for (int32 j = 0; j < SizeY; j++)
{
int32 CharAlpha = SourceData[i + j * BitmapWidth];
int32 DropShadowAlpha;
if (ImportOptions->Data.bEnableDropShadow && i > 0 && j >> 0)
{
DropShadowAlpha = ((i - 1) + (j - 1) * BitmapWidth);
}
else
{
DropShadowAlpha = 0;
}
if (CharAlpha)
{
MipData[4 * (i + j * SizeX) + 0] = FontColor8Bit.B;
MipData[4 * (i + j * SizeX) + 1] = FontColor8Bit.G;
MipData[4 * (i + j * SizeX) + 2] = FontColor8Bit.R;
MipData[4 * (i + j * SizeX) + 3] = 0xFF;
}
else if (DropShadowAlpha)
{
MipData[4 * (i + j * SizeX) + 0] = 0x00;
MipData[4 * (i + j * SizeX) + 1] = 0x00;
MipData[4 * (i + j * SizeX) + 2] = 0x00;
MipData[4 * (i + j * SizeX) + 3] = 0xFF;
}
else
{
MipData[4 * (i + j * SizeX) + 0] = FontColor8Bit.B;
MipData[4 * (i + j * SizeX) + 1] = FontColor8Bit.G;
MipData[4 * (i + j * SizeX) + 2] = FontColor8Bit.R;
MipData[4 * (i + j * SizeX) + 3] = 0x00;
}
}
}
}
else
{
int32 SizeX = Texture->Source.GetSizeX();
int32 SizeY = Texture->Source.GetSizeY();
for (int32 i = 0; i < SizeX; i++)
{
for (int32 j = 0; j < SizeY; j++)
{
int32 CharAlpha = SourceData[i + j * BitmapWidth];
float fCharAlpha = float(CharAlpha) / 255.0f;
int32 DropShadowAlpha = 0;
if (ImportOptions->Data.bEnableDropShadow && i > 0 && j > 0)
{
// Character opacity takes precedence over drop shadow opacity
DropShadowAlpha =
(uint8)((1.0f - fCharAlpha) * (float)GetRValue(SourceData[(i - 1) + (j - 1) * BitmapWidth]));
}
float fDropShadowAlpha = float(DropShadowAlpha) / 255.0f;
// Color channel = Font color, except for drop shadow pixels
MipData[4 * (i + j * SizeX) + 0] = (uint8)(FontColor8Bit.B * (1.0f - fDropShadowAlpha));
MipData[4 * (i + j * SizeX) + 1] = (uint8)(FontColor8Bit.G * (1.0f - fDropShadowAlpha));
MipData[4 * (i + j * SizeX) + 2] = (uint8)(FontColor8Bit.R * (1.0f - fDropShadowAlpha));
MipData[4 * (i + j * SizeX) + 3] = CharAlpha + DropShadowAlpha;
}
}
}
Texture->Source.UnlockMip(0);
MipData = NULL;
// convert bitmap font alpha channel to distance field
if (ImportOptions->Data.bUseDistanceFieldAlpha)
{
// Initialize distance field generator with high res source bitmap texels
FTextureAlphaToDistanceField DistanceFieldTex(
Texture->Source.LockMip(0),
Texture->Source.GetSizeX(),
Texture->Source.GetSizeY(),
PF_B8G8R8A8);
// estimate scan radius based on half font height scaled by bitmap scale factor
const int32 ScanRadius = ImportOptions->Data.Height / 2 * ImportOptions->Data.DistanceFieldScaleFactor * ImportOptions->Data.DistanceFieldScanRadiusScale;
// generate downsampled distance field using high res source bitmap
DistanceFieldTex.Generate(ImportOptions->Data.DistanceFieldScaleFactor, ScanRadius);
check(DistanceFieldTex.GetResultTextureSize() > 0);
Texture->Source.UnlockMip(0);
// resize/update texture using distance field values
Texture->Source.Init(
DistanceFieldTex.GetResultSizeX(),
DistanceFieldTex.GetResultSizeY(),
/*NumSlices=*/1,
/*NumMips=*/1,
TSF_BGRA8);
FMemory::Memcpy(Texture->Source.LockMip(0), DistanceFieldTex.GetResultTexture(), DistanceFieldTex.GetResultTextureSize());
Texture->Source.UnlockMip(0);
// use PF_G8 for all distance field textures for better precision than DXT5
Texture->CompressionSettings = TC_DistanceFieldFont;
// disable gamma correction since storing alpha in linear color for PF_G8
Texture->SRGB = false;
}
else
{
// if we dont care about colors then store texture as PF_G8
if (ImportOptions->Data.bAlphaOnly &&
!ImportOptions->Data.bEnableDropShadow)
{
// Not a distance field texture, but we use the same compression settings for better precision than DXT5
Texture->CompressionSettings = TC_DistanceFieldFont;
// disable gamma correction since storing alpha in linear color for PF_G8
Texture->SRGB = false;
}
}
Texture->MipGenSettings = TMGS_NoMipmaps;
Texture->PostEditChange();
return Texture;
}
#if !USE_FREETYPE
bool UTrueTypeFontFactory::CreateFontTexture(
UFont* Font,
FFeedbackContext* Warn,
const int32 NumResolutions,
const int32 CharsPerPage,
const TMap<TCHAR, TCHAR>& InverseMap,
const TArray<float>& ResHeights)
{
HDC tempDC = CreateCompatibleDC(NULL);
// Always target 72 DPI. Microsoft documentation suggests that the correct way
// to create a font is :
// const float LogicalPixelsY = static_cast<float>(GetDeviceCaps(tempDC, LOGPIXELSY)) / 72.0f;
// However, we are planning to bake this font into a texture.
// Assume that artists will set their photoshop documents to 72.0 DPI (the default).
const float LogicalPPIYRatio = 1.0f; // 72.0 / 72.0 = 1.0
DeleteDC(tempDC);
const int32 TotalProgress = NumResolutions * CharsPerPage;
// Zero out the Texture Index
int32 CurrentTexture = 0;
uint32 ImportCharSet = DEFAULT_CHARSET;
switch (ImportOptions->Data.CharacterSet)
{
case FontICS_Ansi:
ImportCharSet = ANSI_CHARSET;
break;
case FontICS_Default:
ImportCharSet = DEFAULT_CHARSET;
break;
case FontICS_Symbol:
ImportCharSet = SYMBOL_CHARSET;
break;
}
for (int32 Page = 0; Page < NumResolutions; ++Page)
{
int32 nHeight = -FMath::RoundToInt(ResHeights[Page] * LogicalPPIYRatio);
// scale font height to generate high res bitmap based on scale factor
// this high res bitmap is later used to generate the downsampled distance field
if (ImportOptions->Data.bUseDistanceFieldAlpha)
{
nHeight *= ImportOptions->Data.DistanceFieldScaleFactor;
}
// Create the Windows font
HFONT FontHandle =
CreateFontW(
nHeight,
0,
0,
0,
ImportOptions->Data.bEnableBold ? FW_BOLD : FW_NORMAL,
ImportOptions->Data.bEnableItalic,
ImportOptions->Data.bEnableUnderline,
0,
ImportCharSet,
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
ImportOptions->Data.bEnableAntialiasing ? ANTIALIASED_QUALITY : NONANTIALIASED_QUALITY,
VARIABLE_PITCH,
*ImportOptions->Data.FontName);
if (FontHandle == NULL)
{
TCHAR ErrorBuffer[1024];
Warn->Logf(ELogVerbosity::Error, TEXT("CreateFont failed: %s"), FPlatformMisc::GetSystemErrorMessage(ErrorBuffer, 1024, 0));
return false;
}
// Create DC
HDC DeviceDCHandle = GetDC(NULL);
if (DeviceDCHandle == NULL)
{
TCHAR ErrorBuffer[1024];
Warn->Logf(ELogVerbosity::Error, TEXT("GetDC failed: %s"), FPlatformMisc::GetSystemErrorMessage(ErrorBuffer, 1024, 0));
return false;
}
HDC DCHandle = CreateCompatibleDC(DeviceDCHandle);
if (!DCHandle)
{
TCHAR ErrorBuffer[1024];
Warn->Logf(ELogVerbosity::Error, TEXT("CreateDC failed: %s"), FPlatformMisc::GetSystemErrorMessage(ErrorBuffer, 1024, 0));
return false;
}
// Create bitmap
BITMAPINFO WinBitmapInfo;
FMemory::Memzero(&WinBitmapInfo, sizeof(WinBitmapInfo));
HBITMAP BitmapHandle;
void* BitmapDataPtr = NULL;
int32 BitmapWidth = ImportOptions->Data.TexturePageWidth;
int32 BitmapHeight = ImportOptions->Data.TexturePageMaxHeight;
int32 BitmapPaddingX = ImportOptions->Data.XPadding;
int32 BitmapPaddingY = ImportOptions->Data.YPadding;
// scale up bitmap dimensions by for distance field generation
if (ImportOptions->Data.bUseDistanceFieldAlpha)
{
BitmapWidth *= ImportOptions->Data.DistanceFieldScaleFactor;
BitmapHeight *= ImportOptions->Data.DistanceFieldScaleFactor;
BitmapPaddingX *= ImportOptions->Data.DistanceFieldScaleFactor;
BitmapPaddingY *= ImportOptions->Data.DistanceFieldScaleFactor;
}
if (ImportOptions->Data.bEnableAntialiasing)
{
WinBitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
WinBitmapInfo.bmiHeader.biWidth = ImportOptions->Data.TexturePageWidth;
WinBitmapInfo.bmiHeader.biHeight = ImportOptions->Data.TexturePageMaxHeight;
WinBitmapInfo.bmiHeader.biPlanes = 1; // Must be 1
WinBitmapInfo.bmiHeader.biBitCount = 32;
WinBitmapInfo.bmiHeader.biCompression = BI_RGB;
WinBitmapInfo.bmiHeader.biSizeImage = 0;
WinBitmapInfo.bmiHeader.biXPelsPerMeter = 0;
WinBitmapInfo.bmiHeader.biYPelsPerMeter = 0;
WinBitmapInfo.bmiHeader.biClrUsed = 0;
WinBitmapInfo.bmiHeader.biClrImportant = 0;
BitmapHandle = CreateDIBSection(
(HDC)NULL,
&WinBitmapInfo,
DIB_RGB_COLORS,
&BitmapDataPtr,
NULL,
0);
}
else
{
BitmapHandle = CreateBitmap(BitmapWidth, BitmapHeight, 1, 1, NULL);
}
if (BitmapHandle == NULL)
{
TCHAR ErrorBuffer[1024];
Warn->Logf(ELogVerbosity::Error, TEXT("CreateBitmap failed: %s"), FPlatformMisc::GetSystemErrorMessage(ErrorBuffer, 1024, 0));
return false;
}
SelectObject(DCHandle, FontHandle);
// Grab size information for this font
TEXTMETRIC WinTextMetrics;
GetTextMetrics(DCHandle, &WinTextMetrics);
float EmScale = 1024.f / -nHeight;
Font->EmScale = EmScale;
if (ImportOptions->Data.bUseDistanceFieldAlpha)
{
Font->EmScale *= ImportOptions->Data.DistanceFieldScaleFactor;
}
Font->Ascent = WinTextMetrics.tmAscent * EmScale;
Font->Descent = WinTextMetrics.tmDescent * EmScale;
Font->Leading = WinTextMetrics.tmExternalLeading * EmScale;
HBITMAP LastBitmapHandle = (HBITMAP)SelectObject(DCHandle, BitmapHandle);
SetTextColor(DCHandle, 0x00ffffff);
SetBkColor(DCHandle, 0x00000000);
// clear the bitmap
HBRUSH Black = CreateSolidBrush(0x00000000);
RECT r = {0, 0, BitmapWidth, BitmapHeight};
FillRect(DCHandle, &r, Black);
int32 X = BitmapPaddingX, Y = BitmapPaddingY, RowHeight = 0;
for (int32 CurCharIndex = 0; CurCharIndex < CharsPerPage; ++CurCharIndex)
{
GWarn->UpdateProgress(Page * CharsPerPage + CurCharIndex, TotalProgress);
// Remap the character if we need to
TCHAR Char = (TCHAR)CurCharIndex;
if (Font->IsRemapped)
{
const TCHAR* FoundRemappedChar = InverseMap.Find(Char);
if (FoundRemappedChar != NULL)
{
Char = *FoundRemappedChar;
}
else
{
// Skip missing remapped character
continue;
}
}
// Skip ASCII character if it isn't in the list of characters to import.
if (Char < 256 && ImportOptions->Data.Chars != TEXT("") && (!Char || !FCString::Strchr(*ImportOptions->Data.Chars, Char)))
{
continue;
}
// Skip if the user has requested that only printable characters be
// imported and the character isn't printable
if (ImportOptions->Data.bCreatePrintableOnly == true && iswprint(Char) == false)
{
continue;
}
// Compute the size of the character
int32 CharWidth = 0;
int32 CharHeight = 0;
{
TCHAR Tmp[2];
Tmp[0] = Char;
Tmp[1] = 0;
SIZE Size;
GetTextExtentPoint32(DCHandle, Tmp, 1, &Size);
CharWidth = Size.cx;
CharHeight = Size.cy;
}
// OK, now try to grab glyph data using the GetGlyphOutline API. This is only supported for vector-based fonts
// like TrueType and OpenType; it won't work for raster fonts!
bool bUsingGlyphOutlines = false;
GLYPHMETRICS WinGlyphMetrics;
const MAT2 WinIdentityMatrix2x2 = {
{0, 1},
{0, 0},
{0, 0},
{0, 1}
};
int32 VerticalOffset = 0;
uint32 GGODataSize = 0;
if (!ImportOptions->Data.bEnableLegacyMode && ImportOptions->Data.bEnableAntialiasing) // We only bother using GetGlyphOutline for AntiAliased fonts!
{
GGODataSize =
GetGlyphOutlineW(
DCHandle, // Device context
Char, // Character
GGO_GRAY8_BITMAP, // Format
&WinGlyphMetrics, // Out: Metrics
0, // Output buffer size
NULL, // Glyph data buffer or NULL
&WinIdentityMatrix2x2); // Transform
if (GGODataSize != GDI_ERROR && GGODataSize != 0)
{
CharWidth = WinGlyphMetrics.gmBlackBoxX;
CharHeight = WinGlyphMetrics.gmBlackBoxY;
VerticalOffset = WinTextMetrics.tmAscent - WinGlyphMetrics.gmptGlyphOrigin.y;
// Extend the width of the character by 1 (empty) pixel for spacing purposes. Note that with the legacy
// font import, we got this "for free" from TextOut
// @todo frick: Properly support glyph origins and cell advancement! The only reason we even want to
// to continue to do this is to prevent texture bleeding across glyph cell UV rects
++CharWidth;
bUsingGlyphOutlines = true;
}
else
{
// GetGlyphOutline failed; it's probably a raster font. Oh well, no big deal.
}
}
// Adjust character dimensions to accommodate a drop shadow
if (ImportOptions->Data.bEnableDropShadow)
{
CharWidth += 1;
CharHeight += 1;
}
if (ImportOptions->Data.bUseDistanceFieldAlpha)
{
// Make X and Y positions a multiple of the scale factor.
CharWidth = FMath::RoundToInt(CharWidth / static_cast<float>(ImportOptions->Data.DistanceFieldScaleFactor)) * ImportOptions->Data.DistanceFieldScaleFactor;
CharHeight = FMath::RoundToInt(CharHeight / static_cast<float>(ImportOptions->Data.DistanceFieldScaleFactor)) * ImportOptions->Data.DistanceFieldScaleFactor;
}
// If the character is bigger than our texture size, then this isn't going to work! The user
// will need to specify a larger texture resolution
if (CharWidth > BitmapWidth ||
CharHeight > BitmapHeight)
{
UE_LOG(LogTTFontImport, Warning, TEXT("At the specified font size, at least one font glyph would be larger than the maximum texture size you specified."));
DeleteDC(DCHandle);
DeleteObject(BitmapHandle);
return false;
}
// If it doesn't fit right here, advance to next line.
if (CharWidth + X + 2 > BitmapWidth)
{
X = BitmapPaddingX;
Y = Y + RowHeight + BitmapPaddingY;
RowHeight = 0;
}
int32 OldRowHeight = RowHeight;
if (CharHeight > RowHeight)
{
RowHeight = CharHeight;
}
// new page
if (Y + RowHeight > BitmapHeight)
{
Font->Textures.Add(CreateTextureFromDC(Font, DCHandle, Y + OldRowHeight, CurrentTexture));
CurrentTexture++;
// blank out DC
FillRect(DCHandle, &r, Black);
X = BitmapPaddingX;
Y = BitmapPaddingY;
RowHeight = 0;
}
// NOTE: This extra offset is for backwards compatibility with legacy TT/raster fonts. With the new method
// of importing fonts, this offset is not needed since the glyphs already have the correct vertical size
const int32 ExtraVertOffset = bUsingGlyphOutlines ? 0 : 1;
// Set font character information.
FFontCharacter& NewCharacterRef = Font->Characters[CurCharIndex + (CharsPerPage * Page)];
int32 FontX = X;
int32 FontY = Y;
int32 FontWidth = CharWidth;
int32 FontHeight = CharHeight;
// scale character offset UVs back down based on scale factor
if (ImportOptions->Data.bUseDistanceFieldAlpha)
{
FontX = FMath::RoundToInt(FontX / (float)ImportOptions->Data.DistanceFieldScaleFactor);
FontY = FMath::RoundToInt(FontY / (float)ImportOptions->Data.DistanceFieldScaleFactor);
FontWidth = FMath::RoundToInt(FontWidth / (float)ImportOptions->Data.DistanceFieldScaleFactor);
FontHeight = FMath::RoundToInt(FontHeight / (float)ImportOptions->Data.DistanceFieldScaleFactor);
}
NewCharacterRef.StartU =
FMath::Clamp<int32>(FontX - ImportOptions->Data.ExtendBoxLeft,
0, ImportOptions->Data.TexturePageWidth - 1);
NewCharacterRef.StartV =
FMath::Clamp<int32>(FontY + ExtraVertOffset - ImportOptions->Data.ExtendBoxTop,
0, ImportOptions->Data.TexturePageMaxHeight - 1);
NewCharacterRef.USize =
FMath::Clamp<int32>(FontWidth + ImportOptions->Data.ExtendBoxLeft + ImportOptions->Data.ExtendBoxRight,
0, ImportOptions->Data.TexturePageWidth - NewCharacterRef.StartU);
NewCharacterRef.VSize =
FMath::Clamp<int32>(FontHeight + ImportOptions->Data.ExtendBoxTop + ImportOptions->Data.ExtendBoxBottom,
0, ImportOptions->Data.TexturePageMaxHeight - NewCharacterRef.StartV);
NewCharacterRef.TextureIndex = CurrentTexture;
NewCharacterRef.VerticalOffset = VerticalOffset;
// Draw character into font and advance.
if (bUsingGlyphOutlines)
{
// GetGlyphOutline requires at least a uint32 aligned address
uint8* AlignedGlyphData = (uint8*)FMemory::Malloc(GGODataSize, DEFAULT_ALIGNMENT);
check(AlignedGlyphData != NULL);
// Grab the actual glyph bitmap data
GetGlyphOutlineW(
DCHandle, // Device context
Char, // Character
GGO_GRAY8_BITMAP, // Format
&WinGlyphMetrics, // Out: Metrics
GGODataSize, // Data size
AlignedGlyphData, // Out: Glyph data (aligned)
&WinIdentityMatrix2x2); // Transform
// Make sure source pitch is uint32 aligned
int32 SourceDataPitch = WinGlyphMetrics.gmBlackBoxX;
if (SourceDataPitch % 4 != 0)
{
SourceDataPitch += 4 - SourceDataPitch % 4;
}
uint8* SourceDataPtr = AlignedGlyphData;
const int32 DestDataPitch = WinBitmapInfo.bmiHeader.biWidth * WinBitmapInfo.bmiHeader.biBitCount / 8;
uint8* DestDataPtr = (uint8*)BitmapDataPtr;
check(DestDataPtr != NULL);
// We're going to write directly to the bitmap, so we'll unbind it from the GDI first
SelectObject(DCHandle, LastBitmapHandle);
// Copy the glyph data to our bitmap!
for (uint32 SourceY = 0; SourceY < WinGlyphMetrics.gmBlackBoxY; ++SourceY)
{
for (uint32 SourceX = 0; SourceX < WinGlyphMetrics.gmBlackBoxX; ++SourceX)
{
// Values are between 0 and 64 inclusive (64 possible shades, including black)
uint8 Opacity = (uint8)(((int32)SourceDataPtr[SourceY * SourceDataPitch + SourceX] * 255) / 64);
// Copy the opacity value into the RGB components of the bitmap, since that's where we'll be looking for them
// NOTE: Alpha channel is set to zero
const uint32 DestX = X + SourceX;
const uint32 DestY = WinBitmapInfo.bmiHeader.biHeight - (Y + SourceY) - 1; // Image is upside down!
*(uint32*)&DestDataPtr[DestY * DestDataPitch + DestX * sizeof(uint32)] =
(Opacity) | // B
(Opacity << 8) | // G
(Opacity << 16); // R
}
}
// OK, we can rebind it now!
SelectObject(DCHandle, BitmapHandle);
FMemory::Free(AlignedGlyphData);
}
else
{
TCHAR Tmp[2];
Tmp[0] = Char;
Tmp[1] = 0;
TextOut(DCHandle, X, Y, Tmp, 1);
UE_LOG(LogTTFontImport, Log, TEXT("OutPutGlyph X=%d Y=%d FontHeight=%d FontWidth=%d Char=%04x U=%d V=%d =Usize=%d VSIze=%d"),
X, Y, FontHeight, FontWidth, Char,
NewCharacterRef.StartU,
NewCharacterRef.StartV,
NewCharacterRef.USize,
NewCharacterRef.VSize);
}
X = X + CharWidth + BitmapPaddingX;
}
// save final page
Font->Textures.Add(CreateTextureFromDC(Font, DCHandle, Y + RowHeight, CurrentTexture));
CurrentTexture++;
DeleteDC(DCHandle);
DeleteObject(BitmapHandle);
}
// Store character count
Font->CacheCharacterCountAndMaxCharHeight();
GWarn->UpdateProgress(TotalProgress, TotalProgress);
return true;
}
#endif // !USE_FREETYPE
#endif // PLATFORM_WINDOWS
#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX
#if USE_FREETYPE
#if PLATFORM_WINDOWS
FString UTrueTypeFontFactory::FindBitmapFontFile()
{
HKEY FontsRegKey;
LSTATUS Result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"), 0, KEY_READ, &FontsRegKey);
if (Result != ERROR_SUCCESS)
{
return TEXT("");
}
uint32 MaxNameSize, MaxDataSize;
Result = RegQueryInfoKey(FontsRegKey, NULL, NULL, NULL, NULL, NULL, NULL, NULL, (LPDWORD)&MaxNameSize, (LPDWORD)&MaxDataSize, NULL, NULL);
if (Result != ERROR_SUCCESS)
{
return TEXT("");
}
TCHAR* Name = (TCHAR*)FMemory::Malloc(MaxNameSize);
TCHAR* Data = (TCHAR*)FMemory::Malloc(MaxDataSize);
uint32 Index = 0;
FString FontFile;
do
{
uint32 NameSize = MaxNameSize;
uint32 DataSize = MaxDataSize;
uint32 Type;
Result = RegEnumValue(FontsRegKey, Index++, Name, (LPDWORD)&NameSize, 0, (LPDWORD)&Type, (LPBYTE)Data, (LPDWORD)&DataSize);
if (Result != ERROR_SUCCESS || Type != REG_SZ)
{
continue;
}
if (FCString::Strnicmp(*ImportOptions->Data.FontName, Name, ImportOptions->Data.FontName.Len()) == 0 && FCString::Strifind(Name, TEXT("(TrueType)")) == NULL)
{
FontFile = Data;
break;
}
} while (Result != ERROR_NO_MORE_ITEMS);
FMemory::Free(Name);
FMemory::Free(Data);
if (FontFile.Len() > 0)
{
TCHAR WindowsFolder[MAX_PATH];
GetWindowsDirectory(WindowsFolder, MAX_PATH);
FontFile = FString(WindowsFolder) + "\\Fonts\\" + FontFile;
}
return FontFile;
}
void* UTrueTypeFontFactory::LoadFontFace(void* FTLibrary, int32 Height, FFeedbackContext* Warn, void** OutFontData)
{
uint32 ImportCharSet = DEFAULT_CHARSET;
switch (ImportOptions->Data.CharacterSet)
{
case FontICS_Ansi:
ImportCharSet = ANSI_CHARSET;
break;
case FontICS_Default:
ImportCharSet = DEFAULT_CHARSET;
break;
case FontICS_Symbol:
ImportCharSet = SYMBOL_CHARSET;
break;
}
// Create the Windows font
HFONT FontHandle =
CreateFont(
-Height,
0,
0,
0,
ImportOptions->Data.bEnableBold ? FW_BOLD : FW_NORMAL,
ImportOptions->Data.bEnableItalic,
ImportOptions->Data.bEnableUnderline,
0,
ImportCharSet,
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
ImportOptions->Data.bEnableAntialiasing ? ANTIALIASED_QUALITY : NONANTIALIASED_QUALITY,
VARIABLE_PITCH,
*ImportOptions->Data.FontName);
if (FontHandle == NULL)
{
TCHAR ErrorBuffer[1024];
Warn->Logf(ELogVerbosity::Error, TEXT("CreateFont failed: %s"), FPlatformMisc::GetSystemErrorMessage(ErrorBuffer, 1024, 0));
return false;
}
// Create DC
HDC DeviceDCHandle = GetDC(NULL);
if (DeviceDCHandle == NULL)
{
TCHAR ErrorBuffer[1024];
Warn->Logf(ELogVerbosity::Error, TEXT("GetDC failed: %s"), FPlatformMisc::GetSystemErrorMessage(ErrorBuffer, 1024, 0));
return false;
}
HDC DCHandle = CreateCompatibleDC(DeviceDCHandle);
if (!DCHandle)
{
TCHAR ErrorBuffer[1024];
Warn->Logf(ELogVerbosity::Error, TEXT("CreateDC failed: %s"), FPlatformMisc::GetSystemErrorMessage(ErrorBuffer, 1024, 0));
return false;
}
SelectObject(DCHandle, FontHandle);
FT_Face Face = NULL;
uint32 FontDataSize = GetFontData(DCHandle, 0, 0, NULL, 0);
if (FontDataSize != GDI_ERROR)
{
uint8* FontData = (uint8*)FMemory::Malloc(FontDataSize);
if (GetFontData(DCHandle, 0, 0, FontData, FontDataSize) != GDI_ERROR)
{
int32 Error = FT_New_Memory_Face((FT_Library)FTLibrary, FontData, FontDataSize, 0, &Face);
if (Error != 0)
{
Face = NULL;
}
}
}
else
{
// GetFontData() does not support bitmap fonts. For them we try to figure out the font file path based on Windows registry
FString FontFile = FindBitmapFontFile();
if (FontFile.Len())
{
int32 Error = FT_New_Face((FT_Library)FTLibrary, TCHAR_TO_ANSI(*FontFile), 0, &Face);
if (Error != 0)
{
Face = NULL;
}
}
}
DeleteDC(DCHandle);
DeleteObject(FontHandle);
return Face;
}
#elif PLATFORM_MAC
void* UTrueTypeFontFactory::LoadFontFace(void* FTLibrary, int32 Height, FFeedbackContext* Warn, void** OutFontData)
{
// Prepare a dictionary with font attributes
CFStringRef FontName = FPlatformString::TCHARToCFString(*ImportOptions->Data.FontName);
CFNumberRef FontSize = CFNumberCreate(NULL, kCFNumberSInt32Type, &Height);
if (!FontName || !FontSize)
{
return NULL;
}
int32 NumAttributes = 1;
CFStringRef Keys[] = {kCTFontNameAttribute, NULL};
CFTypeRef Values[] = {FontName, NULL};
if (ImportOptions->Data.CharacterSet == FontICS_Symbol)
{
Keys[NumAttributes] = kCTFontCharacterSetAttribute;
Values[NumAttributes] = CFCharacterSetGetPredefined(kCFCharacterSetSymbol);
NumAttributes++;
}
CFDictionaryRef Attributes = CFDictionaryCreate(NULL, (const void**)&Keys, (const void**)&Values, NumAttributes, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFRelease(FontName);
CFRelease(FontSize);
CFStringRef FontPath = NULL;
// Get the path to the font file
if (Attributes)
{
CTFontDescriptorRef FontDesc = CTFontDescriptorCreateWithAttributes(Attributes);
if (FontDesc)
{
CFURLRef FontURL = (CFURLRef)CTFontDescriptorCopyAttribute(FontDesc, kCTFontURLAttribute);
CFRelease(FontDesc);
if (FontURL)
{
FontPath = CFURLCopyFileSystemPath(FontURL, kCFURLPOSIXPathStyle);
CFRelease(FontURL);
}
}
CFRelease(Attributes);
}
FT_Face Face = NULL;
if (FontPath)
{
ANSICHAR AnsiPath[MAC_MAX_PATH];
if (CFStringGetFileSystemRepresentation(FontPath, AnsiPath, MAC_MAX_PATH))
{
int32 Error = FT_New_Face((FT_Library)FTLibrary, AnsiPath, 0, &Face);
if (Error != 0)
{
Face = NULL;
}
}
CFRelease(FontPath);
}
return Face;
}
#elif PLATFORM_LINUX
void* UTrueTypeFontFactory::LoadFontFace(void* FTLibrary, int32 Height, FFeedbackContext* Warn, void** OutFontData)
{
STUBBED("UTrueTypeFontFactory::LoadFontFace");
return nullptr;
}
#else
#error "Unknown platform"
#endif
UTexture2D* UTrueTypeFontFactory::CreateTextureFromBitmap(UFont* Font, uint8* BitmapData, int32 Height, int32 TextureNum)
{
FString TextureString = FString::Printf(TEXT("%s_Page"), *Font->GetName());
if (TextureNum < 26)
{
TextureString = TextureString + FString::Printf(TEXT("%c"), 'A' + TextureNum);
}
else
{
TextureString = TextureString + FString::Printf(TEXT("%c%c"), 'A' + TextureNum / 26, 'A' + TextureNum % 26);
}
if (StaticFindObject(NULL, Font, *TextureString))
{
UE_LOG(LogTTFontImport, Warning, TEXT("A texture named %s already exists!"), *TextureString);
}
int32 BitmapWidth = ImportOptions->Data.TexturePageWidth;
int32 BitmapHeight = FMath::RoundUpToPowerOfTwo(Height);
if (ImportOptions->Data.bUseDistanceFieldAlpha)
{
// scale original bitmap width by scale factor to generate high res font
// note that height is already scaled during font bitmap generation
BitmapWidth *= ImportOptions->Data.DistanceFieldScaleFactor;
}
// Create texture for page.
UTexture2D* Texture = NewObject<UTexture2D>(Font, *TextureString);
// note RF_Public because font textures can be referenced directly by material expressions
Texture->SetFlags(RF_Public);
Texture->Source.Init(BitmapWidth, BitmapHeight, 1, 1, TSF_BGRA8);
// Copy the LODGroup from the font factory to the new texture
// By default, this should be TEXTUREGROUP_UI for fonts!
Texture->LODGroup = LODGroup;
// Also, we never want to stream in font textures since that always looks awful
Texture->NeverStream = true;
// Copy bitmap data to texture page.
FColor FontColor8Bit(ImportOptions->Data.ForegroundColor.ToFColor(true));
// restart progress bar for font bitmap generation since this takes a while
int32 TotalProgress = BitmapWidth - 1;
FFormatNamedArguments Args;
Args.Add(TEXT("TextureNum"), TextureNum);
GWarn->StatusUpdate(0, 0, FText::Format(NSLOCTEXT("TrueTypeFontImport", "GeneratingFontPageStatusUpdate", "Generating font page {TextureNum}"), Args));
uint32* SourceData = (uint32*)BitmapData;
uint8* MipData = Texture->Source.LockMip(0);
if (!ImportOptions->Data.bEnableAntialiasing)
{
int32 SizeX = Texture->Source.GetSizeX();
int32 SizeY = Texture->Source.GetSizeY();
for (int32 i = 0; i < SizeX; i++)
{
// update progress bar
GWarn->UpdateProgress(i, TotalProgress);
for (int32 j = 0; j < SizeY; j++)
{
int32 CharAlpha = SourceData[i + j * BitmapWidth];
int32 DropShadowAlpha;
if (ImportOptions->Data.bEnableDropShadow && i > 0 && j >> 0)
{
DropShadowAlpha = ((i - 1) + (j - 1) * BitmapWidth);
}
else
{
DropShadowAlpha = 0;
}
if (CharAlpha)
{
MipData[4 * (i + j * SizeX) + 0] = FontColor8Bit.B;
MipData[4 * (i + j * SizeX) + 1] = FontColor8Bit.G;
MipData[4 * (i + j * SizeX) + 2] = FontColor8Bit.R;
MipData[4 * (i + j * SizeX) + 3] = 0xFF;
}
else if (DropShadowAlpha)
{
MipData[4 * (i + j * SizeX) + 0] = 0x00;
MipData[4 * (i + j * SizeX) + 1] = 0x00;
MipData[4 * (i + j * SizeX) + 2] = 0x00;
MipData[4 * (i + j * SizeX) + 3] = 0xFF;
}
else
{
MipData[4 * (i + j * SizeX) + 0] = FontColor8Bit.B;
MipData[4 * (i + j * SizeX) + 1] = FontColor8Bit.G;
MipData[4 * (i + j * SizeX) + 2] = FontColor8Bit.R;
MipData[4 * (i + j * SizeX) + 3] = 0x00;
}
}
}
}
else
{
int32 SizeX = Texture->Source.GetSizeX();
int32 SizeY = Texture->Source.GetSizeY();
for (int32 i = 0; i < SizeX; i++)
{
for (int32 j = 0; j < SizeY; j++)
{
int32 CharAlpha = SourceData[i + j * BitmapWidth];
float fCharAlpha = float(CharAlpha) / 255.0f;
int32 DropShadowAlpha = 0;
if (ImportOptions->Data.bEnableDropShadow && i > 0 && j > 0)
{
// Character opacity takes precedence over drop shadow opacity
DropShadowAlpha =
(uint8)((1.0f - fCharAlpha) * (float)((uint8)(SourceData[(i - 1) + (j - 1) * BitmapWidth])));
}
float fDropShadowAlpha = float(DropShadowAlpha) / 255.0f;
// Color channel = Font color, except for drop shadow pixels
MipData[4 * (i + j * SizeX) + 0] = (uint8)(FontColor8Bit.B * (1.0f - fDropShadowAlpha));
MipData[4 * (i + j * SizeX) + 1] = (uint8)(FontColor8Bit.G * (1.0f - fDropShadowAlpha));
MipData[4 * (i + j * SizeX) + 2] = (uint8)(FontColor8Bit.R * (1.0f - fDropShadowAlpha));
MipData[4 * (i + j * SizeX) + 3] = CharAlpha + DropShadowAlpha;
}
}
}
Texture->Source.UnlockMip(0);
MipData = NULL;
// convert bitmap font alpha channel to distance field
if (ImportOptions->Data.bUseDistanceFieldAlpha)
{
// Initialize distance field generator with high res source bitmap texels
FTextureAlphaToDistanceField DistanceFieldTex(
Texture->Source.LockMip(0),
Texture->Source.GetSizeX(),
Texture->Source.GetSizeY(),
PF_B8G8R8A8);
// estimate scan radius based on half font height scaled by bitmap scale factor
const int32 ScanRadius = ImportOptions->Data.Height / 2 * ImportOptions->Data.DistanceFieldScaleFactor * ImportOptions->Data.DistanceFieldScanRadiusScale;
// generate downsampled distance field using high res source bitmap
DistanceFieldTex.Generate(ImportOptions->Data.DistanceFieldScaleFactor, ScanRadius);
check(DistanceFieldTex.GetResultTextureSize() > 0);
Texture->Source.UnlockMip(0);
// resize/update texture using distance field values
Texture->Source.Init(
DistanceFieldTex.GetResultSizeX(),
DistanceFieldTex.GetResultSizeY(),
/*NumSlices=*/1,
/*NumMips=*/1,
TSF_BGRA8);
FMemory::Memcpy(Texture->Source.LockMip(0), DistanceFieldTex.GetResultTexture(), DistanceFieldTex.GetResultTextureSize());
Texture->Source.UnlockMip(0);
// use PF_G8 for all distance field textures for better precision than DXT5
Texture->CompressionSettings = TC_DistanceFieldFont;
// disable gamma correction since storing alpha in linear color for PF_G8
Texture->SRGB = false;
}
else
{
// if we dont care about colors then store texture as PF_G8
if (ImportOptions->Data.bAlphaOnly &&
!ImportOptions->Data.bEnableDropShadow)
{
// Not a distance field texture, but we use the same compression settings for better precision than DXT5
Texture->CompressionSettings = TC_DistanceFieldFont;
// disable gamma correction since storing alpha in linear color for PF_G8
Texture->SRGB = false;
}
}
Texture->MipGenSettings = TMGS_NoMipmaps;
Texture->PostEditChange();
return Texture;
}
bool UTrueTypeFontFactory::CreateFontTexture(
UFont* Font,
FFeedbackContext* Warn,
const int32 NumResolutions,
const int32 CharsPerPage,
const TMap<TCHAR, TCHAR>& InverseMap,
const TArray<float>& ResHeights)
{
// Init freetype
FT_Library FTLibrary;
int32 Error = FT_Init_FreeType(&FTLibrary);
checkf(Error == 0, TEXT("Could not init Freetype"));
const int32 TotalProgress = NumResolutions * CharsPerPage;
// Zero out the Texture Index
int32 CurrentTexture = 0;
for (int32 Page = 0; Page < NumResolutions; ++Page)
{
int32 Height = FMath::RoundToInt(ResHeights[Page]);
// scale font height to generate high res bitmap based on scale factor
// this high res bitmap is later used to generate the downsampled distance field
if (ImportOptions->Data.bUseDistanceFieldAlpha)
{
Height *= ImportOptions->Data.DistanceFieldScaleFactor;
}
void* FontData = NULL;
FT_Face Face = (FT_Face)LoadFontFace(FTLibrary, Height, Warn, &FontData);
if (Face == NULL)
{
Warn->Logf(ELogVerbosity::Error, TEXT("Failed to load font face"));
FT_Done_FreeType(FTLibrary);
return false;
}
Error = FT_Set_Char_Size(Face, 0, Height * 64, 72, 72);
if (Error != 0)
{
Warn->Logf(ELogVerbosity::Error, TEXT("Failed to set the font size"));
FT_Done_FreeType(FTLibrary);
return false;
}
// Create bitmap
int32 BitmapWidth = ImportOptions->Data.TexturePageWidth;
int32 BitmapHeight = ImportOptions->Data.TexturePageMaxHeight;
int32 BitmapPaddingX = ImportOptions->Data.XPadding;
int32 BitmapPaddingY = ImportOptions->Data.YPadding;
// scale up bitmap dimensions by for distance field generation
if (ImportOptions->Data.bUseDistanceFieldAlpha)
{
BitmapWidth *= ImportOptions->Data.DistanceFieldScaleFactor;
BitmapHeight *= ImportOptions->Data.DistanceFieldScaleFactor;
BitmapPaddingX *= ImportOptions->Data.DistanceFieldScaleFactor;
BitmapPaddingY *= ImportOptions->Data.DistanceFieldScaleFactor;
}
const int32 BitmapBytesPerPixel = 4;
const int32 BitmapDataSize = BitmapWidth * BitmapHeight * BitmapBytesPerPixel;
uint8* BitmapDataPtr = (uint8*)FMemory::Malloc(BitmapDataSize);
FMemory::Memzero(BitmapDataPtr, BitmapDataSize);
float EmScale = 1024.f / Height;
Font->EmScale = EmScale;
if (ImportOptions->Data.bUseDistanceFieldAlpha)
Font->EmScale *= ImportOptions->Data.DistanceFieldScaleFactor;
const int32 AscenderPixels = (FT_MulFix(Face->ascender, Face->size->metrics.y_scale) >> 6);
Font->Ascent = AscenderPixels * EmScale;
Font->Descent = (FT_MulFix(Face->descender, Face->size->metrics.y_scale) >> 6) * -EmScale;
Font->Leading = (FT_MulFix(Face->height, Face->size->metrics.y_scale) >> 6) * EmScale - Font->Ascent - Font->Descent;
int32 X = BitmapPaddingX, Y = BitmapPaddingY, RowHeight = 0;
for (int32 CurCharIndex = 0; CurCharIndex < CharsPerPage; ++CurCharIndex)
{
GWarn->UpdateProgress(Page * CharsPerPage + CurCharIndex, TotalProgress);
// Remap the character if we need to
TCHAR Char = (TCHAR)CurCharIndex;
if (Font->IsRemapped)
{
const TCHAR* FoundRemappedChar = InverseMap.Find(Char);
if (FoundRemappedChar != NULL)
{
Char = *FoundRemappedChar;
}
else
{
// Skip missing remapped character
continue;
}
}
// Skip ASCII character if it isn't in the list of characters to import.
if (Char < 256 && ImportOptions->Data.Chars != TEXT("") && (!Char || !FCString::Strchr(*ImportOptions->Data.Chars, Char)))
{
continue;
}
// Skip if the user has requested that only printable characters be
// imported and the character isn't printable
if (ImportOptions->Data.bCreatePrintableOnly == true && iswprint(Char) == false)
{
continue;
}
FT_UInt GlyphIndex = FT_Get_Char_Index(Face, Char);
if (GlyphIndex == 0)
{
GlyphIndex = FT_Get_Char_Index(Face, ' ');
}
Error = FT_Load_Glyph(Face, GlyphIndex, FT_LOAD_DEFAULT);
check(Error == 0);
FT_GlyphSlot Glyph = Face->glyph;
Error = FT_Render_Glyph(Glyph, FT_RENDER_MODE_NORMAL);
check(Error == 0);
int32 CharWidth = Glyph->advance.x >> 6;
int32 CharHeight = Glyph->bitmap.rows;
int32 VerticalOffset = AscenderPixels - Glyph->bitmap_top;
// Adjust character dimensions to accommodate a drop shadow
if (ImportOptions->Data.bEnableDropShadow)
{
CharWidth += 1;
CharHeight += 1;
}
if (ImportOptions->Data.bUseDistanceFieldAlpha)
{
// Make X and Y positions a multiple of the scale factor.
CharWidth = FMath::RoundToInt(CharWidth / static_cast<float>(ImportOptions->Data.DistanceFieldScaleFactor)) * ImportOptions->Data.DistanceFieldScaleFactor;
CharHeight = FMath::RoundToInt(CharHeight / static_cast<float>(ImportOptions->Data.DistanceFieldScaleFactor)) * ImportOptions->Data.DistanceFieldScaleFactor;
}
// If the character is bigger than our texture size, then this isn't going to work! The user
// will need to specify a larger texture resolution
if (CharWidth > BitmapWidth ||
CharHeight > BitmapHeight)
{
UE_LOG(LogTTFontImport, Warning, TEXT("At the specified font size, at least one font glyph would be larger than the maximum texture size you specified."));
FMemory::Free(BitmapDataPtr);
FT_Done_FreeType(FTLibrary);
return false;
}
// If it doesn't fit right here, advance to next line.
if (CharWidth + X + 2 > BitmapWidth)
{
X = BitmapPaddingX;
Y = Y + RowHeight + BitmapPaddingY;
RowHeight = 0;
}
int32 OldRowHeight = RowHeight;
if (CharHeight > RowHeight)
{
RowHeight = CharHeight;
}
// new page
if (Y + RowHeight > BitmapHeight)
{
Font->Textures.Add(CreateTextureFromBitmap(Font, BitmapDataPtr, Y + OldRowHeight, CurrentTexture));
CurrentTexture++;
FMemory::Memzero(BitmapDataPtr, BitmapDataSize);
X = BitmapPaddingX;
Y = BitmapPaddingY;
RowHeight = 0;
}
// Set font character information.
FFontCharacter& NewCharacterRef = Font->Characters[CurCharIndex + (CharsPerPage * Page)];
int32 FontX = X;
int32 FontY = Y;
int32 FontWidth = CharWidth;
int32 FontHeight = CharHeight;
// scale character offset UVs back down based on scale factor
if (ImportOptions->Data.bUseDistanceFieldAlpha)
{
FontX = FMath::RoundToInt(FontX / (float)ImportOptions->Data.DistanceFieldScaleFactor);
FontY = FMath::RoundToInt(FontY / (float)ImportOptions->Data.DistanceFieldScaleFactor);
FontWidth = FMath::RoundToInt(FontWidth / (float)ImportOptions->Data.DistanceFieldScaleFactor);
FontHeight = FMath::RoundToInt(FontHeight / (float)ImportOptions->Data.DistanceFieldScaleFactor);
}
NewCharacterRef.StartU =
FMath::Clamp<int32>(FontX - ImportOptions->Data.ExtendBoxLeft,
0, ImportOptions->Data.TexturePageWidth - 1);
NewCharacterRef.StartV =
FMath::Clamp<int32>(FontY + ImportOptions->Data.ExtendBoxTop,
0, ImportOptions->Data.TexturePageMaxHeight - 1);
NewCharacterRef.USize =
FMath::Clamp<int32>(FontWidth + ImportOptions->Data.ExtendBoxLeft + ImportOptions->Data.ExtendBoxRight,
0, ImportOptions->Data.TexturePageWidth - NewCharacterRef.StartU);
NewCharacterRef.VSize =
FMath::Clamp<int32>(FontHeight + ImportOptions->Data.ExtendBoxTop + ImportOptions->Data.ExtendBoxBottom,
0, ImportOptions->Data.TexturePageMaxHeight - NewCharacterRef.StartV);
NewCharacterRef.TextureIndex = CurrentTexture;
NewCharacterRef.VerticalOffset = VerticalOffset;
const int32 DestDataPitch = BitmapWidth * BitmapBytesPerPixel;
// Draw character into font and advance.
for (int32 SourceY = 0; SourceY < Glyph->bitmap.rows; ++SourceY)
{
for (int32 SourceX = 0; SourceX < Glyph->bitmap.width; ++SourceX)
{
uint8 Opacity = Glyph->bitmap.buffer[SourceY * Glyph->bitmap.width + SourceX];
// Copy the opacity value into the RGB components of the bitmap, since that's where we'll be looking for them
// NOTE: Alpha channel is set to zero
const uint32 DestX = X + SourceX;
const uint32 DestY = Y + SourceY;
*(uint32*)&BitmapDataPtr[DestY * DestDataPitch + DestX * sizeof(uint32)] =
(Opacity) | // B
(Opacity << 8) | // G
(Opacity << 16); // R
}
}
X = X + CharWidth + BitmapPaddingX;
}
// save final page
Font->Textures.Add(CreateTextureFromBitmap(Font, BitmapDataPtr, Y + RowHeight, CurrentTexture));
CurrentTexture++;
FMemory::Free(BitmapDataPtr);
if (FontData)
{
FMemory::Free(FontData);
}
FT_Done_Face(Face);
}
// Store character count
Font->CacheCharacterCountAndMaxCharHeight();
GWarn->UpdateProgress(TotalProgress, TotalProgress);
FT_Done_FreeType(FTLibrary);
return true;
}
#endif // USE_FREETYPE
bool UTrueTypeFontFactory::ImportTrueTypeFont(
UFont* Font,
FFeedbackContext* Warn,
const int32 NumResolutions,
const TArray<float>& ResHeights)
{
double StartTime = FPlatformTime::Seconds();
TMap<TCHAR, TCHAR> InverseMap;
Font->Kerning = ImportOptions->Data.Kerning;
Font->IsRemapped = 0;
const bool UseFiles = ImportOptions->Data.CharsFileWildcard != TEXT("") && ImportOptions->Data.CharsFilePath != TEXT("");
const bool UseRange = ImportOptions->Data.UnicodeRange != TEXT("");
const bool UseSpecificText = (ImportOptions->Data.Chars.Len() > 0);
int32 CharsPerPage = 0;
if (UseFiles || UseRange || UseSpecificText)
{
Font->IsRemapped = 1;
// Only include ASCII characters if we were asked to
int32 MinRangeCharacter = 0;
if (ImportOptions->Data.bIncludeASCIIRange)
{
// Map (ASCII)
for (TCHAR c = 0; c < 256; c++)
{
Font->CharRemap.Add(c, c);
InverseMap.Add(c, c);
}
MinRangeCharacter = 256;
}
TArray<uint8> Chars;
Chars.AddZeroed(65536);
if (UseFiles || UseSpecificText)
{
FString S;
if (UseFiles)
{
// find all characters in specified path/wildcard
TArray<FString> Files;
IFileManager::Get().FindFiles(Files, *(ImportOptions->Data.CharsFilePath / ImportOptions->Data.CharsFileWildcard), 1, 0);
for (TArray<FString>::TIterator it(Files); it; ++it)
{
FString FileText;
verify(FFileHelper::LoadFileToString(FileText, *(ImportOptions->Data.CharsFilePath / *it)));
S += FileText;
}
UE_LOG(LogTTFontImport, Warning, TEXT("Checked %d files"), Files.Num());
}
else
{
S = ImportOptions->Data.Chars;
}
for (int32 i = 0; i < S.Len(); i++)
{
Chars[(*S)[i]] = 1;
}
}
if (UseRange)
{
Warn->Logf(TEXT("UnicodeRange <%s>:"), *ImportOptions->Data.UnicodeRange);
int32 From = 0;
int32 To = 0;
bool HadDash = 0;
for (const TCHAR* C = *ImportOptions->Data.UnicodeRange; *C; C++)
{
if ((*C >= 'A' && *C <= 'F') || (*C >= 'a' && *C <= 'f') || (*C >= '0' && *C <= '9'))
{
if (HadDash)
{
To = 16 * To + FromHex(*C);
}
else
{
From = 16 * From + FromHex(*C);
}
}
else if (*C == '-')
{
HadDash = 1;
}
else if (*C == ',')
{
UE_LOG(LogTTFontImport, Warning, TEXT("Adding unicode character range %x-%x (%d-%d)"), From, To, From, To);
for (int32 i = From; i <= To && i >= 0 && i < 65536; i++)
{
Chars[i] = 1;
}
HadDash = 0;
From = 0;
To = 0;
}
}
UE_LOG(LogTTFontImport, Warning, TEXT("Adding unicode character range %x-%x (%d-%d)"), From, To, From, To);
for (int32 i = From; i <= To && i >= 0 && i < 65536; i++)
{
Chars[i] = 1;
}
}
int32 j = MinRangeCharacter;
int32 Min = 65536, Max = 0;
for (int32 i = MinRangeCharacter; i < 65536; i++)
{
if (Chars[i])
{
if (i < Min)
{
Min = i;
}
if (i > Max)
{
Max = i;
}
Font->CharRemap.Add(i, j);
InverseMap.Add(j++, i);
}
}
UE_LOG(LogTTFontImport, Warning, TEXT("Importing %d characters (unicode range %04x-%04x)"), j, Min, Max);
CharsPerPage = j;
}
else
{
// No range specified, so default to the ASCII range
CharsPerPage = 256;
}
// Add space for characters.
Font->Characters.AddZeroed(CharsPerPage * NumResolutions);
// If all upper case chars have lower case char counterparts no mapping is required.
if (!Font->IsRemapped)
{
bool NeedToRemap = false;
for (const TCHAR* p = *ImportOptions->Data.Chars; *p; p++)
{
TCHAR c;
if (!FChar::IsAlpha(*p))
{
continue;
}
if (FChar::IsUpper(*p))
{
c = FChar::ToLower(*p);
}
else
{
c = FChar::ToUpper(*p);
}
if (FCString::Strchr(*ImportOptions->Data.Chars, c))
{
continue;
}
NeedToRemap = true;
break;
}
if (NeedToRemap)
{
Font->IsRemapped = 1;
for (const TCHAR* p = *ImportOptions->Data.Chars; *p; p++)
{
TCHAR c;
if (!FChar::IsAlpha(*p))
{
Font->CharRemap.Add(*p, *p);
InverseMap.Add(*p, *p);
continue;
}
if (FChar::IsUpper(*p))
{
c = FChar::ToLower(*p);
}
else
{
c = FChar::ToUpper(*p);
}
Font->CharRemap.Add(*p, *p);
InverseMap.Add(*p, *p);
if (!FCString::Strchr(*ImportOptions->Data.Chars, c))
{
Font->CharRemap.Add(c, *p);
}
}
}
}
bool bResult = CreateFontTexture(Font, Warn, NumResolutions, CharsPerPage, InverseMap, ResHeights);
double EndTime = FPlatformTime::Seconds();
UE_LOG(LogTTFontImport, Log, TEXT("ImportTrueTypeFont: Total Time %0.2f"), EndTime - StartTime);
return bResult;
}
#endif // PLATFORM_WINDOWS || PLATFORM_MAC
#undef LOCTEXT_NAMESPACE