// 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(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(TEXT("MainFrame")); const TSharedPtr& 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(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(ImportOptions->Data.DistanceFieldScaleFactor, 2)); ImportOptions->Data.DistanceFieldScanRadiusScale = FMath::Clamp(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(4, ImportOptions->Data.XPadding); ImportOptions->Data.YPadding = FMath::Max(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 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& OutFilenames) { UFont* FontToReimport = Cast(Obj); if (FontToReimport && FontToReimport->FontCacheType == EFontCacheType::Offline) { OutFilenames.Add(TEXT("None")); return true; } return false; } void UTrueTypeFontFactory::SetReimportPaths(UObject* Obj, const TArray& NewReimportPaths) { // No path is needed } EReimportResult::Type UTrueTypeFontFactory::Reimport(UObject* InObject) { UFont* FontToReimport = Cast(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 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* 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& 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 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( &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(MinDistance, SignedDistance[i]); MaxDistance = FMath::Max(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(PointX - RingSize, 0, TaskData->SrcSizeX); const int32 EndX = FMath::Clamp(PointX + RingSize, 0, TaskData->SrcSizeX - 1); const int32 StartY = FMath::Clamp(PointY - RingSize, 0, TaskData->SrcSizeY); const int32 EndY = FMath::Clamp(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(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(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(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(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(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 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& InverseMap, const TArray& 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(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(ImportOptions->Data.DistanceFieldScaleFactor)) * ImportOptions->Data.DistanceFieldScaleFactor; CharHeight = FMath::RoundToInt(CharHeight / static_cast(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(FontX - ImportOptions->Data.ExtendBoxLeft, 0, ImportOptions->Data.TexturePageWidth - 1); NewCharacterRef.StartV = FMath::Clamp(FontY + ExtraVertOffset - ImportOptions->Data.ExtendBoxTop, 0, ImportOptions->Data.TexturePageMaxHeight - 1); NewCharacterRef.USize = FMath::Clamp(FontWidth + ImportOptions->Data.ExtendBoxLeft + ImportOptions->Data.ExtendBoxRight, 0, ImportOptions->Data.TexturePageWidth - NewCharacterRef.StartU); NewCharacterRef.VSize = FMath::Clamp(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(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& InverseMap, const TArray& 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(ImportOptions->Data.DistanceFieldScaleFactor)) * ImportOptions->Data.DistanceFieldScaleFactor; CharHeight = FMath::RoundToInt(CharHeight / static_cast(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(FontX - ImportOptions->Data.ExtendBoxLeft, 0, ImportOptions->Data.TexturePageWidth - 1); NewCharacterRef.StartV = FMath::Clamp(FontY + ImportOptions->Data.ExtendBoxTop, 0, ImportOptions->Data.TexturePageMaxHeight - 1); NewCharacterRef.USize = FMath::Clamp(FontWidth + ImportOptions->Data.ExtendBoxLeft + ImportOptions->Data.ExtendBoxRight, 0, ImportOptions->Data.TexturePageWidth - NewCharacterRef.StartU); NewCharacterRef.VSize = FMath::Clamp(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& ResHeights) { double StartTime = FPlatformTime::Seconds(); TMap 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 Chars; Chars.AddZeroed(65536); if (UseFiles || UseSpecificText) { FString S; if (UseFiles) { // find all characters in specified path/wildcard TArray Files; IFileManager::Get().FindFiles(Files, *(ImportOptions->Data.CharsFilePath / ImportOptions->Data.CharsFileWildcard), 1, 0); for (TArray::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