Skip to content

Commit

Permalink
[WIP] Use the new FreeType and FreeImage
Browse files Browse the repository at this point in the history
  • Loading branch information
harry-cpp committed Nov 22, 2024
1 parent b27e18d commit 4c38235
Show file tree
Hide file tree
Showing 11 changed files with 498 additions and 261 deletions.
4 changes: 1 addition & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,8 @@ jobs:
dotnet.exe workload install android
else
dotnet workload install android macos ios --version 8.0.402.0
brew install wine-stable p7zip freeimage freetype
brew install wine-stable p7zip
sudo mkdir -p /usr/local/lib
sudo ln -s /opt/homebrew/lib/libfreetype.dylib /usr/local/lib/libfreetype6.dylib
sudo ln -s /opt/homebrew/lib/libfreeimage.dylib /usr/local/lib/libfreeimage.dylib
wget -qO- https://monogame.net/downloads/net9_mgfxc_wine_setup.sh | bash
fi
shell: bash
Expand Down
14 changes: 12 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,23 @@
"name": "MGCB Editor (Mac)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "mgcb-editor-mac",
"preLaunchTask": "Build: mgcb-editor-mac",
"program": "${workspaceFolder}/Artifacts/MonoGame.Content.Builder.Editor/Mac/Debug/mgcb-editor-mac.app/Contents/MacOS/mgcb-editor-mac",
"args": [],
"cwd": "${workspaceFolder}/Artifacts/MonoGame.Content.Builder.Editor/Mac/Debug",
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": "MGCB",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "Build: mgcb",
"program": "${workspaceFolder}/Artifacts/MonoGame.Content.Builder/Debug/mgcb",
"cwd": "/Users/harry/Projects/ContentTest/",
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": "Attach to Process",
"type": "coreclr",
Expand All @@ -44,7 +54,7 @@
"cwd": "${workspaceFolder}/Artifacts/Tests/DesktopGL/Debug/",
"console": "internalConsole",
"stopAtEntry": false
},
}
],
"inputs": [
{
Expand Down
14 changes: 13 additions & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,19 @@
"version": "2.0.0",
"tasks": [
{
"label": "mgcb-editor-mac",
"label": "Build: mgcb",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Tools/MonoGame.Content.Builder/MonoGame.Content.Builder.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "Build: mgcb-editor-mac",
"command": "dotnet",
"type": "process",
"args": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ internal interface IFontImporter

float LineSpacing { get; }

int YOffsetMin { get; }
long YOffsetMin { get; }
}
}
144 changes: 66 additions & 78 deletions MonoGame.Framework.Content.Pipeline/Graphics/Font/SharpFontImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,126 +5,112 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using SharpFont;
using FreeTypeAPI;

namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
{
// Uses FreeType to rasterize TrueType fonts into a series of glyph bitmaps.
internal class SharpFontImporter : IFontImporter
unsafe internal class SharpFontImporter : IFontImporter
{
// Properties hold the imported font data.
public IEnumerable<Glyph> Glyphs { get; private set; }

public float LineSpacing { get; private set; }

public int YOffsetMin { get; private set; }
public long YOffsetMin { get; private set; }

// Size of the temp surface used for GDI+ rasterization.
const int MaxGlyphSize = 1024;

Library lib = null;

public void Import(FontDescription options, string fontName)
{
lib = new Library();
// Create a bunch of GDI+ objects.
var face = CreateFontFace(options, fontName);
try
{
// Which characters do we want to include?
var characters = options.Characters;
CheckError(FreeType.FT_Init_FreeType(out FT_Library* library));

var glyphList = new List<Glyph>();
var glyphMaps = new Dictionary<uint, GlyphData>();

// Rasterize each character in turn.
foreach (char character in characters)
{
uint glyphIndex = face.GetCharIndex(character);
if (!glyphMaps.TryGetValue(glyphIndex, out GlyphData glyphData))
{
glyphData = ImportGlyph(glyphIndex, face);
glyphMaps.Add(glyphIndex, glyphData);
}
// Create a bunch of GDI+ objects.
var face = CreateFontFace(library, options, fontName);

var glyph = new Glyph(character, glyphData);
glyphList.Add(glyph);
}
Glyphs = glyphList;
// Which characters do we want to include?
var characters = options.Characters;

// Store the font height.
LineSpacing = face.Size.Metrics.Height >> 6;
var glyphList = new List<Glyph>();
var glyphMaps = new Dictionary<uint, GlyphData>();

// The height used to calculate the Y offset for each character.
YOffsetMin = -face.Size.Metrics.Ascender >> 6;
}
finally
// Rasterize each character in turn.
foreach (char character in characters)
{
if (face != null)
face.Dispose();
if (lib != null)
uint glyphIndex = FreeType.FT_Get_Char_Index(face, character);
if (!glyphMaps.TryGetValue(glyphIndex, out GlyphData glyphData))
{
lib.Dispose();
lib = null;
glyphData = ImportGlyph(glyphIndex, face);
glyphMaps.Add(glyphIndex, glyphData);
}

var glyph = new Glyph(character, glyphData);
glyphList.Add(glyph);
}
Glyphs = glyphList;

// Store the font height.
LineSpacing = face->size->metrics.height >> 6;

// The height used to calculate the Y offset for each character.
YOffsetMin = -face->size->metrics.ascender >> 6;

CheckError(FreeType.FT_Done_Face(face));
CheckError(FreeType.FT_Done_FreeType(library));
}

private void CheckError(int error)
{
if (error == 0)
return;

throw new Exception("An error occured in freetype."); // TODO: Fill the error
}

// Attempts to instantiate the requested GDI+ font object.
private Face CreateFontFace(FontDescription options, string fontName)
private FT_Face* CreateFontFace(FT_Library* library, FontDescription options, string fontName)
{
try
{
const uint dpi = 96;
var face = lib.NewFace(fontName, 0);
var fixedSize = ((int)options.Size) << 6;
face.SetCharSize(0, fixedSize, dpi, dpi);
const uint dpi = 96;

if (face.FamilyName == "Microsoft Sans Serif" && options.FontName != "Microsoft Sans Serif")
throw new PipelineException(string.Format("Font {0} is not installed on this computer.", options.FontName));
CheckError(FreeType.FT_New_Face(library, fontName, 0, out FT_Face* face));

return face;
var fixedSize = ((int)options.Size) << 6;
CheckError(FreeType.FT_Set_Char_Size(face, 0, fixedSize, dpi, dpi));

// A font substitution must have occurred.
//throw new Exception(string.Format("Can't find font '{0}'.", options.FontName));
}
catch
{
throw;
}
return face;
}

// Rasterizes a single character glyph.
private GlyphData ImportGlyph(uint glyphIndex, Face face)
private GlyphData ImportGlyph(uint glyphIndex, FT_Face* face)
{
face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal);
face.Glyph.RenderGlyph(RenderMode.Normal);
CheckError(FreeType.FT_Load_Glyph(face, glyphIndex));
CheckError(FreeType.FT_Render_Glyph(face->glyph));

// Render the character.
BitmapContent glyphBitmap = null;
if (face.Glyph.Bitmap.Width > 0 && face.Glyph.Bitmap.Rows > 0)
if (face->glyph->bitmap.width > 0 && face->glyph->bitmap.rows > 0)
{
glyphBitmap = new PixelBitmapContent<byte>(face.Glyph.Bitmap.Width, face.Glyph.Bitmap.Rows);
byte[] gpixelAlphas = new byte[face.Glyph.Bitmap.Width * face.Glyph.Bitmap.Rows];
glyphBitmap = new PixelBitmapContent<byte>((int)face->glyph->bitmap.width, (int)face->glyph->bitmap.rows);
byte[] gpixelAlphas = new byte[face->glyph->bitmap.width * face->glyph->bitmap.rows];
//if the character bitmap has 1bpp we have to expand the buffer data to get the 8bpp pixel data
//each byte in bitmap.bufferdata contains the value of to 8 pixels in the row
//if bitmap is of width 10, each row has 2 bytes with 10 valid bits, and the last 6 bits of 2nd byte must be discarded
if (face.Glyph.Bitmap.PixelMode == PixelMode.Mono)
if ((FT_Pixel_Mode)face->glyph->bitmap.pixel_mode == FT_Pixel_Mode.FT_PIXEL_MODE_MONO)
{
//variables needed for the expansion, amount of written data, length of the data to write
int written = 0, length = face.Glyph.Bitmap.Width * face.Glyph.Bitmap.Rows;
int written = 0, length = (int)(face->glyph->bitmap.width * face->glyph->bitmap.rows);
for (int i = 0; written < length; i++)
{
//width in pixels of each row
int width = face.Glyph.Bitmap.Width;
int width = (int)face->glyph->bitmap.width;
while (width > 0)
{
//valid data in the current byte
int stride = MathHelper.Min(8, width);
//copy the valid bytes to pixeldata
//System.Array.Copy(ExpandByte(face.Glyph.Bitmap.BufferData[i]), 0, gpixelAlphas, written, stride);
ExpandByteAndCopy(face.Glyph.Bitmap.BufferData[i], stride, gpixelAlphas, written);
ExpandByteAndCopy(face->glyph->bitmap.buffer[i], stride, gpixelAlphas, written);
written += stride;
width -= stride;
if (width > 0)
Expand All @@ -133,35 +119,37 @@ private GlyphData ImportGlyph(uint glyphIndex, Face face)
}
}
else
Marshal.Copy(face.Glyph.Bitmap.Buffer, gpixelAlphas, 0, gpixelAlphas.Length);
{
gpixelAlphas = new Span<byte>(face->glyph->bitmap.buffer, gpixelAlphas.Length).ToArray();
}
glyphBitmap.SetPixelData(gpixelAlphas);
}

if (glyphBitmap == null)
{
var gHA = face.Glyph.Metrics.HorizontalAdvance >> 6;
var gVA = face.Size.Metrics.Height >> 6;
var gHA = face->glyph->metrics.horiAdvance >> 6;
var gVA = face->size->metrics.height >> 6;

gHA = gHA > 0 ? gHA : gVA;
gVA = gVA > 0 ? gVA : gHA;

glyphBitmap = new PixelBitmapContent<byte>(gHA, gVA);
glyphBitmap = new PixelBitmapContent<byte>((int)gHA, (int)gVA);
}

// not sure about this at all
var abc = new ABCFloat();
abc.A = face.Glyph.Metrics.HorizontalBearingX >> 6;
abc.B = face.Glyph.Metrics.Width >> 6;
abc.C = (face.Glyph.Metrics.HorizontalAdvance >> 6) - (abc.A + abc.B);
abc.A -= face.Glyph.BitmapLeft;
abc.B += face.Glyph.BitmapLeft;
abc.A = face->glyph->metrics.horiBearingX >> 6;
abc.B = face->glyph->metrics.width >> 6;
abc.C = (face->glyph->metrics.horiAdvance >> 6) - (abc.A + abc.B);
abc.A -= face->glyph->bitmap_left;
abc.B += face->glyph->bitmap_left;

// Construct the output Glyph object.
return new GlyphData(glyphIndex, glyphBitmap)
{
XOffset = -(face.Glyph.Advance.X >> 6),
XAdvance = face.Glyph.Metrics.HorizontalAdvance >> 6,
YOffset = -(face.Glyph.Metrics.HorizontalBearingY >> 6),
XOffset = -(face->glyph->advance.x >> 6),
XAdvance = face->glyph->metrics.horiAdvance >> 6,
YOffset = -(face->glyph->metrics.horiBearingY >> 6),
CharacterWidths = abc
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,6 @@
<Reference Include="CppNet">
<HintPath>..\ThirdParty\Dependencies\CppNet\CppNet.dll</HintPath>
</Reference>
<Reference Include="SharpFont">
<HintPath>..\ThirdParty\Dependencies\SharpFont\x64\SharpFont.dll</HintPath>
</Reference>
</ItemGroup>

<ItemGroup Condition="'$(SolutionName)' != 'MonoGame.Framework.WindowsDX'">
Expand All @@ -74,59 +71,22 @@

<ItemGroup>
<PackageReference Include="AssimpNet" Version="5.0.0-beta1" IncludeAssets="compile;runtime;build" ExcludeAssets="native" />
<PackageReference Include="MonoGame.Library.Assimp" Version="5.3.1.1" />
<PackageReference Include="MonoGame.Library.Assimp" Version="5.3.1.2" />
<PackageReference Include="BCnEncoder.Net" Version="2.1.0" />
<PackageReference Include="Microsoft.NETCore.App" Version="2.1.30" />
<PackageReference Include="RoyT.TrueType" Version="0.2.0" />
<PackageReference Include="MonoGame.Library.FreeImage" Version="3.18.0.3" />
<PackageReference Include="MonoGame.Library.FreeType" Version="2.13.2.2" />
<PackageReference Include="SharpDX" Version="4.0.1" />
<PackageReference Include="SharpDX.D3DCompiler" Version="4.0.1" />
<PackageReference Include="LibKTX" Version="0.9.2" />
</ItemGroup>

<ItemGroup Condition="'$(CopyContentFiles)' == 'True'">
<Content Include="..\ThirdParty\Dependencies\CppNet\CppNet.dll" PackagePath="lib\net8.0" Visible="false" />
<Content Include="..\ThirdParty\Dependencies\SharpFont\x64\SharpFont.dll" PackagePath="lib\net8.0" Visible="false" />

<Content Include="..\ThirdParty\Dependencies\FreeImage.NET\Linux\x64\libfreeimage-3.17.0.so" Visible="false">
<Link>libFreeImage.so</Link>
<PackagePath>runtimes\linux-x64\native\libFreeImage.so</PackagePath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\ThirdParty\Dependencies\SharpFont\x64\libfreetype.6.so" Visible="false">
<Link>libfreetype6.so</Link>
<PackagePath>runtimes\linux-x64\native\libfreetype6.so</PackagePath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\ThirdParty\Dependencies\SharpFont\x64\libpng16.so.16" Visible="false">
<PackagePath>runtimes\linux-x64\native</PackagePath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\ThirdParty\Dependencies\FreeImage.NET\MacOS\libfreeimage.dylib" Visible="false">
<Link>libFreeImage.dylib</Link>
<PackagePath>runtimes\osx\native\libFreeImage.dylib</PackagePath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\ThirdParty\Dependencies\SharpFont\x64\libfreetype.6.dylib" Visible="false">
<Link>libfreetype6.dylib</Link>
<PackagePath>runtimes\osx\native\libfreetype6.dylib</PackagePath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\ThirdParty\Dependencies\SharpFont\x64\libpng16.16.dylib" Visible="false">
<PackagePath>runtimes\osx\native</PackagePath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\ThirdParty\Dependencies\FreeImage.NET\Windows\FreeImage.dll" Visible="false">
<PackagePath>runtimes\win-x64\native</PackagePath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\ThirdParty\Dependencies\MojoShader\Windows\libmojoshader_64.dll" Visible="false">
<PackagePath>runtimes\win-x64\native</PackagePath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\ThirdParty\Dependencies\SharpFont\x64\freetype6.dll" Visible="false">
<PackagePath>runtimes\win-x64\native</PackagePath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>

<PropertyGroup>
Expand Down

This file was deleted.

Loading

0 comments on commit 4c38235

Please sign in to comment.