Source/Gorgon/Graphics/FreeType.cpp

Thu, 17 Sep 2020 16:25:17 +0300

author
cemkalyoncu
date
Thu, 17 Sep 2020 16:25:17 +0300
branch
ComponentStackRework
changeset 1444
53e69f1cea46
parent 1376
e15204b22280
child 1451
8d05e766f523
permissions
-rwxr-xr-x

* Light hinting for better looking font

#include "FreeType.h"

#include <set>

#include "Bitmap.h"
#include "../Filesystem.h"

#include <ft2build.h>
#include FT_FREETYPE_H

namespace {
    using namespace Gorgon::Graphics;

    Bitmap* fill_bitmap(FT_Face face, int index, bool aa,
                        FT_Vector *delta = nullptr, FT_Matrix *matrix = nullptr) {

        if(delta || matrix)
            FT_Set_Transform(face, matrix, delta);

        auto error = FT_Load_Glyph(face, index, FT_LOAD_RENDER|(aa ? FT_LOAD_TARGET_LIGHT : FT_LOAD_MONOCHROME));

        if(error != FT_Err_Ok)
            return nullptr;

        auto slot = face->glyph;
        auto &ftbmp = slot->bitmap;

        auto &bmp = *new Bitmap(ftbmp.width, ftbmp.rows, ColorMode::Alpha);

        if(ftbmp.pitch < 0) {
            if(ftbmp.pixel_mode == FT_PIXEL_MODE_MONO) {
                for(unsigned y=0; y<ftbmp.rows; y++) {
                    int b = 7, B = 0; //bit, byte

                    for(unsigned x=0; x<ftbmp.width; x++) {
                        bmp(x, ftbmp.rows - y - 1, 0) = ftbmp.buffer[B + y*ftbmp.pitch]&(1<<b) ? 255 : 0;

                        b--;
                        if(b<0) {
                            b = 7;
                            B++;
                        }
                    }
                }
            }
            else {
                for(unsigned y=0; y<ftbmp.rows; y++) {
                    for(unsigned x=0; x<ftbmp.width; x++) {
                        bmp(x, ftbmp.rows - y - 1, 0) = ftbmp.buffer[x + y*ftbmp.pitch];
                    }
                }
            }
        }
        else {
            if(ftbmp.pixel_mode == FT_PIXEL_MODE_MONO) {
                for(unsigned y=0; y<ftbmp.rows; y++) {
                    int b = 7, B = 0; //bit, byte

                    for(unsigned x=0; x<ftbmp.width; x++) {
                        bmp(x, y, 0) = ftbmp.buffer[B + y*ftbmp.pitch]&(1<<b) ? 255 : 0;

                        b--;
                        if(b<0) {
                            b = 7;
                            B++;
                        }
                    }
                }
            }
            else {
                for(unsigned y=0; y<ftbmp.rows; y++) {
                    for(unsigned x=0; x<ftbmp.width; x++) {
                        bmp(x, y, 0) = ftbmp.buffer[x + y*ftbmp.pitch];
                    }
                }
            }
        }

        return &bmp;
    }
} // end of anonymous namespace

namespace Gorgon { namespace Graphics {
    
    struct ftlib {
        ftlib() {
            FT_Init_FreeType(&library);
        }
        
        void destroyface() {
            if(face)
                FT_Done_Face(face);
            
            delete[] data;
            
            face = nullptr;
            data = nullptr;
            
            if(!vecdata.empty()) {
                std::vector<Byte> n;
                std::swap(vecdata, n);
            }
        }
        
        ~ftlib() {
            FT_Done_FreeType(library);
            library = nullptr;
        }
        
        FT_Library library;
        FT_Face    face = nullptr;
        std::vector<Byte> vecdata; //if we own our data
        const Byte *data = nullptr;
    };
    
    
    FreeType::FreeType() : BasicFont(dynamic_cast<GlyphRenderer &>(*this)) {
        lib = new ftlib;
    }
    
    FreeType::~FreeType() {
        delete lib;
        
        destroylist.Destroy();
    }
    
    bool FreeType::finalizeload() {
        isfixedw = (lib->face->face_flags & FT_FACE_FLAG_FIXED_WIDTH) != 0;
        
        //no unicode charmap table
        if(lib->face->charmap == nullptr) {
            auto error = FT_Select_Charmap(lib->face, FT_ENCODING_APPLE_ROMAN);
            
            if(error != FT_Err_Ok)
                error = FT_Select_Charmap(lib->face, FT_ENCODING_ADOBE_LATIN_1);
            
            if(error != FT_Err_Ok)
                error = FT_Select_Charmap(lib->face, FT_ENCODING_ADOBE_STANDARD);
            
            if(error != FT_Err_Ok)
                error = FT_Select_Charmap(lib->face, FT_ENCODING_ADOBE_EXPERT);

			if(error != FT_Err_Ok)
				error = FT_Select_Charmap(lib->face, FT_ENCODING_ADOBE_CUSTOM);

            if(error == FT_Err_Ok) {
                isascii = true;
            }
            else {
                error = FT_Select_Charmap(lib->face, FT_ENCODING_MS_SYMBOL);
                
                if(error == FT_Err_Ok) {
                    issymbol = true;
                }
                else {
					if(lib->face->num_charmaps > 0) {
						error = FT_Set_Charmap(lib->face, lib->face->charmaps[0]);

						isascii = true;
					}

					if(error != FT_Err_Ok) {
						lib->destroyface();
                    
						return false;
					}
                }
            }
        }
        
        haskerning = FT_HAS_KERNING(lib->face);
        
        filename = "";
        vecdata = nullptr;
        data = nullptr;
        
        return true;
    }

    
    
    bool FreeType::LoadFile(const std::string &filename) {
        lib->destroyface();
        
        auto error = FT_New_Face(lib->library, filename.c_str(), 0, &lib->face);
        
        if(error != FT_Err_Ok || lib->face == nullptr)
            return false;
        
        if(!finalizeload())
            return false;
        
        this->filename = Filesystem::Canonical(filename);
        
        return true;
    }
    
    
    bool FreeType::LoadFile(const std::string &filename, int size, bool loadascii) {
        if(!LoadFile(filename))
            return false;
        
        if(!LoadMetrics(size))
            return false;
        
        if(loadascii)
            return LoadGlyphs({0, {0x20, 0x7f}});
        else
            return true;
    }
    
    
    bool FreeType::Load(const std::vector<Byte> &data) {
        lib->destroyface();
        
        auto error = FT_New_Memory_Face(lib->library, &data[0], (long)data.size(), 0, &lib->face);
        
        if(error != FT_Err_Ok || lib->face == nullptr)
            return false;
        
        if(!finalizeload())
            return false;
        
        vecdata = &data;
        
        return true;
    }
    
        
    bool FreeType::Load(const Byte *data, long datasize) {
        lib->destroyface();
        
        auto error = FT_New_Memory_Face(lib->library, data, (long)datasize, 0, &lib->face);
        
        if(error != FT_Err_Ok || lib->face == nullptr)
            return false;
        
        if(!finalizeload())
            return false;
        
        this->data = data;
        this->datasize = datasize;
        
        return true;
    }
    
    
    bool FreeType::Assume(std::vector<Byte> &data) {
        lib->destroyface();
        
        std::swap(lib->vecdata, data);
        
        auto error = FT_New_Memory_Face(lib->library, &lib->vecdata[0], (long)lib->vecdata.size(), 0, &lib->face);
        
        if(error != FT_Err_Ok || lib->face == nullptr)
            return false;
        
        if(!finalizeload())
            return false;
        
        this->vecdata = &lib->vecdata;
        
        return true;
    }
    
    
    bool FreeType::Assume(const Byte *data, long datasize) {
        lib->destroyface();
        
        lib->data = data;
        
        auto error = FT_New_Memory_Face(lib->library, data, datasize, 0, &lib->face);
        
        if(error != FT_Err_Ok || lib->face == nullptr)
            return false;
        
        if(!finalizeload())
            return false;
        
        this->data = data;
        this->datasize = datasize;
        
        return true;
    }
    

	bool FreeType::savedata(std::ostream &stream) {
		if(!vecdata && !data && filename == "")
			return false;

		if(vecdata)
			IO::WriteVector(stream, *vecdata);
		else if(data)
			IO::WriteArray(stream, data, datasize);
		else {
			std::ifstream file(filename, std::ios::binary);

			if(!file.is_open())
				return false;

			char buffer[1024];
			while(!file.read(buffer, 1024).bad()) {
				if(file.gcount() <= 0)
					break;

				IO::WriteArray(stream, buffer, (unsigned long)file.gcount());
			}
		}

		return true;
	}


    bool FreeType::LoadMetrics(int size) {
        if(!lib->face)
            return false;
        
        if(IsScalable()) {            
            auto error = FT_Set_Pixel_Sizes(lib->face, 0, size);
            
            if(error != FT_Err_Ok)
                return false;
        
            auto xscale = lib->face->size->metrics.x_scale;
            auto yscale = lib->face->size->metrics.y_scale;
            
            maxadvance = (int)std::round(FT_MulFix(lib->face->bbox.xMax, xscale)/64.f);
            maxwidth = (int)std::round(FT_MulFix((lib->face->bbox.xMax-lib->face->bbox.xMin), xscale)/64.f);
            height   = (int)std::round(FT_MulFix((lib->face->bbox.yMax-lib->face->bbox.yMin), yscale)/64.f);
            
            baseline = std::round(lib->face->size->metrics.ascender/64.f);
            
            linegap = std::round(lib->face->size->metrics.height/64.f);
            
            underlinepos  = (int)std::round(baseline - FT_MulFix((lib->face->underline_position),yscale)/64.f);
            linethickness = FT_MulFix((lib->face->underline_thickness),yscale)/64.f;
         }
        else {
            //search bitmap size table and find values that is closest to the given one.
            
            int mindiff = INT_MAX;
            int minind  = -1;
            
            for(int i=0; i<lib->face->num_fixed_sizes; i++) {
                auto s = lib->face->available_sizes[i];
                auto diff = abs(s.height - size);
                
                if(diff < mindiff) {
                    mindiff = diff;
                    minind  = i;
                }
            }
            
            if(minind == -1)
                return false;
            
            auto s = lib->face->available_sizes[minind];

            auto error = FT_Set_Pixel_Sizes(lib->face, s.width, s.height);
            
            if(error != FT_Err_Ok)
                return false;
            
            baseline = std::round(lib->face->size->metrics.ascender/64.f);
        
            maxwidth = s.width;
            height   = s.height;
            
            underlinepos = (int)std::round((baseline + height) / 2.f);
            
            linethickness = (float)height / 10;
            
            linegap = std::round(lib->face->size->metrics.height/64.f);
            
            if(linethickness < 1) linethickness = 1;
        }
        
		this->size = (float)size;
        return true;
    }
    
    
    bool FreeType::loadglyphs(GlyphRange range, bool prepare) const {
        if(!lib->face)
            return false;

        auto slot = lib->face->glyph;

        int done = 0;
        for(Glyph g = range.Start; g <= range.End; g++) {
            done++; //already loaded glyphs are also counted as done

            //we already have this glyph
            if(glyphmap.count(g)) continue;

            auto index = FT_Get_Char_Index(lib->face, g);

            //check if glyph is already loaded. if so use the same
            if(ft_to_map.count(index) && glyphmap.count(ft_to_map[index])) {
                glyphmap[g] = glyphmap[ft_to_map[index]];
                continue;
            }

            auto bmp = fill_bitmap(lib->face, index, aa);
            FT_Vector penpos = {32 /* 0.5 * 64 */, 0};
            auto withoffset = fill_bitmap(lib->face, index, aa, &penpos);

            if(!bmp || !withoffset) {
                if(range.Count() > 1) {
                    //error, this one is not done
                    done--;
                    continue;
                }
                else
                    return false;
            }

            destroylist.Add(*bmp);
            destroylist.Add(*withoffset);

            glyphmap[g] = {{bmp, withoffset},
                           slot->linearHoriAdvance/float(1<<16),
                           {(int)slot->bitmap_left, (int)-slot->bitmap_top},
                           (unsigned int)index};
            ft_to_map[index] = g;

            if(g < 127 && isdigit(g) && digw < bmp->GetWidth())
                digw = bmp->GetWidth();

            if(prepare) {
                bmp->Prepare();
                withoffset->Prepare();
            }
        }

        return done > 0;
    }

    
    bool FreeType::IsScalable() const {
        if(!lib->face)
            return false;
        
        return (lib->face->face_flags & FT_FACE_FLAG_SCALABLE) != 0;
    }
    
    
    std::vector<int> FreeType::GetPresetSizes() const {
        if(!lib->face)
            return {};
        
        std::vector<int> ret;
        ret.reserve(lib->face->num_fixed_sizes);
     
        for(int i=0; i<lib->face->num_fixed_sizes; i++) {
            ret.push_back(lib->face->available_sizes[i].height);
        }
        
        return ret;
    }
    
    
    std::string FreeType::GetFamilyName() const {
        if(!lib->face)
            return {};
        
        return lib->face->family_name;
    }
    
    
    std::string FreeType::GetStyleName() const {
        if(!lib->face)
            return {};
        
        return lib->face->style_name;
    }
    
    
    bool FreeType::IsFileLoaded() const {
        return lib->face != nullptr;
    }
    
    
    bool FreeType::IsReady() const {
        return height != 0 && (lib->face != nullptr || !glyphmap.empty());
    }

    Geometry::Size FreeType::GetSize(Glyph chr) const {
		if(glyphmap.count(chr))
			return glyphmap.at(chr).images.regular->GetSize();
		else if(glyphmap.count(0) && !internal::isspace(chr) && !internal::isnewline(chr) && chr != '\t')
			return glyphmap.at(0).images.regular->GetSize();
		else
			return{0, 0};
	}

    float FreeType::GetCursorAdvance(Glyph chr) const  {
		if(glyphmap.count(chr))
			return glyphmap.at(chr).advance;
        else if(chr == '\t')
            return (float)height;
        else if(internal::isspace(chr))
            return float(height / 4);
		else if(glyphmap.count(0))
			return glyphmap.at(0).advance;
		else
			return 0;
	}
	
	bool FreeType::Exists(Glyph g) const {
        return glyphmap.count(g);
    }
    
    bool FreeType::Available(Glyph g) const {
        if(glyphmap.count(g))
			return true;
        
        if(!lib->face)
            return false;
        
        return FT_Get_Char_Index(lib->face, g) != 0;
    }
    
    void FreeType::Render(Glyph chr, TextureTarget &target, Geometry::Pointf location, RGBAf color) const {
        if(glyphmap.count(chr)) {
            auto glyph = glyphmap.at(chr);
            glyph.images.regular->Draw(target, location + glyph.offset + Geometry::Pointf(0.f, (float)baseline), color);
        }
		else if(glyphmap.count(0) && !internal::isspace(chr) && !internal::isnewline(chr) && chr != '\t') {
			auto glyph = glyphmap.at(0);
			glyph.images.regular->Draw(target, location + glyph.offset + Geometry::Pointf(0.f, (float)baseline), color);
		}
    }
    
    Geometry::Pointf FreeType::KerningDistance(Glyph chr1, Glyph chr2) const {
        if(!lib->face || !haskerning || !glyphmap.count(chr1) || !glyphmap.count(chr2))
            return {0.f, 0.f};
        
        FT_Vector p;
        FT_Get_Kerning(lib->face, glyphmap.at(chr1).ftindex, glyphmap.at(chr2).ftindex, FT_KERNING_DEFAULT, &p);
        
        if(p.x != 0)
            p.x = p.x;

        return {std::round(p.x / 64.f), std::round(p.y / 64.f)};
    }

    void FreeType::Prepare(const std::string& text) const { 
        std::set<Glyph> list;
        
        for(auto it=text.begin(); it!=text.end(); it++) {
            auto g = internal::decode(it, text.end());
            if(!Exists(g))
                list.insert(g);
        }
        
        auto it = list.begin();
        auto prev = list.begin();
        
        std::vector<GlyphRange> ranges;
        
        while(it != list.end()) {
            it = std::adjacent_find(it, list.end(), [](int l, int r) { return l+1<r; });
            
            if(it == list.end()) {
                ranges.push_back({*prev, *list.rbegin()});
                break;
            }
            else {
                ranges.push_back({*prev, *(it)});
            }
            
            it++;
            prev = it;
        }
        
        for(auto &range : ranges) 
            loadglyphs(range, true);
        
        if(keeppacked)
            pack();
    }
    
    
    BitmapFont FreeType::CopyToBitmap(bool prepare) const {
        BitmapFont font;

		//determine glyph spacing
		GlyphDescriptor d;
		int gs = int(GetHeight() / 10);

		if(glyphmap.count('0') && glyphmap['0'].advance != 0 && glyphmap['0'].images.regular != nullptr)
			d = glyphmap['0'];
		else if(glyphmap.count('A') && glyphmap['A'].advance != 0 && glyphmap['A'].images.regular != nullptr)
			d = glyphmap['A'];
		else if(glyphmap.count('_') && glyphmap['_'].advance != 0 && glyphmap['_'].images.regular != nullptr)
			d = glyphmap['_'];
		
		if(d.advance != 0 && d.images.regular != nullptr)
			gs = (int)std::round(d.advance - d.images.regular->GetWidth() + glyphmap['0'].offset.X);

		if(gs < 1)
			gs = 1;
        
        font.SetBaseline(baseline);
        font.SetLineThickness((int)std::round(linethickness));
        font.SetUnderlineOffset(underlinepos);
        font.SetLineGap(linegap);
		font.SetGlyphSpacing(gs);
        
        //copy kerning table
        if(haskerning) {
            for(auto &l : glyphmap) {
                for(auto &r : glyphmap) {
                    auto p = KerningDistance(l.first, r.first);
                    
                    if(p.X != 0 || p.Y != 0) {
                        font.SetKerning(l.first, r.first, p);
                    }
                }
            }
        }
        
        std::map<const RectangularDrawable*, Bitmap*> newmapping;
        
        for(auto &g : glyphmap) {
            if(!newmapping.count(g.second.images.regular)) {
                if(dynamic_cast<const Bitmap*>(g.second.images.regular)) {
                    auto img = dynamic_cast<const Bitmap*>(g.second.images.regular);
                    
                    newmapping[img] = new Bitmap(img->Duplicate());
                    
                    if(prepare)
                        newmapping[img]->Prepare();
                }
                else if(dynamic_cast<const TextureImage*>(g.second.images.regular) && atlasdata.GetTotalSize() > 0 && 
                        dynamic_cast<const TextureImage*>(g.second.images.regular)->GetID() == atlas.GetID()) {
                    auto img = dynamic_cast<const TextureImage*>(g.second.images.regular);
                    
                    //create a new bitmap
                    auto bmp = new Bitmap(g.second.images.regular->GetSize(), atlasdata.GetMode());
                    
                    //copy atlas data back to it
                    auto c = img->GetCoordinates();
                    int left   = (int)std::round(c[0].X * atlasdata.GetWidth());
                    int right  = (int)std::round(c[2].X * atlasdata.GetWidth());
                    int top    = (int)std::round(c[0].Y * atlasdata.GetHeight());
                    int bottom = (int)std::round(c[2].Y * atlasdata.GetHeight());
                    
                    atlasdata.CopyTo(bmp->GetData(), {left, top, right, bottom});
                    if(prepare)
                        bmp->Prepare();
                    
                    newmapping.insert({img, bmp});
                }
            }
            
            if(newmapping.count(g.second.images.regular)) {
                font.AssumeGlyph(g.first, *newmapping[g.second.images.regular], g.second.offset + Geometry::Pointf(0, baseline), g.second.advance);
            }
        }
        
        return font;
    }
    
    
    BitmapFont FreeType::MoveOutBitmap(bool unpack, bool prepare) {
        BitmapFont font;

		//determine glyph spacing
		GlyphDescriptor d;
		int gs = int(GetHeight() / 10);

		if(glyphmap.count('0') && glyphmap['0'].advance != 0 && glyphmap['0'].images.regular != nullptr)
			d = glyphmap['0'];
		else if(glyphmap.count('A') && glyphmap['A'].advance != 0 && glyphmap['A'].images.regular != nullptr)
			d = glyphmap['A'];
		else if(glyphmap.count('_') && glyphmap['_'].advance != 0 && glyphmap['_'].images.regular != nullptr)
			d = glyphmap['_'];

		if(d.advance != 0 && d.images.regular != nullptr)
			gs = (int)std::round(d.advance - d.images.regular->GetWidth() + glyphmap['0'].offset.X);

		if(gs < 1)
			gs = 1;

        font.SetBaseline(baseline);
        font.SetLineThickness((int)std::round(linethickness));
		font.SetUnderlineOffset(underlinepos);
		font.SetLineGap(linegap);
		font.SetGlyphSpacing(gs);
        
        //copy kerning table
        if(haskerning) {
            for(auto &l : glyphmap) {
                for(auto &r : glyphmap) {
                    auto p = KerningDistance(l.first, r.first);
                    
                    if(p.X != 0 || p.Y != 0) {
                        font.SetKerning(l.first, r.first, p);
                    }
                }
            }
        }

        //if there is no atlasdata, there is no way or reason to unpack
        if(unpack && atlasdata.GetTotalSize() != 0) {
            std::map<const RectangularDrawable*, Bitmap *> map;

            for(auto &g : glyphmap) {
                // already bitmap no need to do anything
                if(dynamic_cast<const Bitmap*>(g.second.images.regular)) {
                    font.AddGlyph(g.first, *g.second.images.regular, g.second.offset + Geometry::Pointf(0, baseline), g.second.advance);
                }
                else {
                    //just to be sure
                    auto img = dynamic_cast<const TextureImage*>(g.second.images.regular);
                    
                    //if the glyph is not transformed and can be transformed
                    if(!map.count(g.second.images.regular) && img && img->GetID() == atlas.GetID()) {
                        //create a new bitmap
                        auto bmp = new Bitmap(g.second.images.regular->GetSize(), atlasdata.GetMode());
                        
                        font.Adopt(*bmp);
                        
                        //copy atlas data back to it
                        auto c = img->GetCoordinates();
                        int left   = (int)std::round(c[0].X * atlasdata.GetWidth());
                        int right  = (int)std::round(c[2].X * atlasdata.GetWidth());
                        int top    = (int)std::round(c[0].Y * atlasdata.GetHeight());
                        int bottom = (int)std::round(c[2].Y * atlasdata.GetHeight());
                        
                        atlasdata.CopyTo(bmp->GetData(), {left, top, right, bottom}, {0, 0});
                        if(prepare)
                            bmp->Prepare();
                        
                        map.insert({img, bmp});
                        
                        delete img;
                    }
                    
                    if(map.count(g.second.images.regular)) {
                        font.AddGlyph(g.first, *map[g.second.images.regular], g.second.offset + Geometry::Pointf(0, baseline), g.second.advance);
                    }
                }
            }
        }
        else {
            //add glpyhs
            for(auto &g : glyphmap) {
                font.AddGlyph(g.first, *g.second.images.regular, g.second.offset + Geometry::Pointf(0, baseline), g.second.advance);
            }
            
            //transfer ownership
            for(const auto &i : destroylist) {
                font.Adopt(i);
            }
            
            //if we have atlas
            if(atlas.GetID()) {
                //adopt and release it
                font.Adopt(*new TextureImage(atlas.GetID(), atlas.GetMode(), atlas.GetImageSize()));
                atlas.Release();
            }
        }
        
        //clear to ensure they wont be destroyed
        destroylist.Clear(); 
        
        Clear();
        
        return font;
    }
    

    void FreeType::Clear(){
        glyphmap.clear();
        destroylist.DeleteAll();

        atlasdata.Destroy();
        
        std::vector<bool> e;
        std::swap(used, e);
        rowsused = 0;
            
		digw = 0;
    }
    
    
    void FreeType::setatlassize(unsigned size) const {
        if(size < 1) return;
        
        int w = CeilToPowerOf2(unsigned(sqrt(float(size))));
        int h = CeilToPowerOf2((int)ceil(size / w));
        
        if(atlas.GetID()) {
            //resize and copy everything
            
            std::vector<bool> nu(w*h);
            int ow = atlasdata.GetWidth(), oh = atlasdata.GetHeight();
            for(int y=0; y<oh; y++)
                for(int x=0; x<ow; x++)
                    nu[y*w+x] = used[y*ow+x];
                
            std::swap(used, nu);
            
            for(auto &g : glyphmap) {
                if(g.second.images.regular && dynamic_cast<const TextureImage*>(g.second.images.regular)) {
                    //cast away constness, we need to update them as their base is modified
                    auto im = dynamic_cast<TextureImage*>(g.second.images.regular);
                    auto c = im->GetCoordinates();
                        
                    int l = (int)std::round(c[0].X*ow);
                    int t = (int)std::round(c[0].Y*oh);
                    int r = (int)std::round(c[2].X*ow);
                    int b = (int)std::round(c[2].Y*oh);
                    
                    im->Set(im->GetID(), im->GetMode(), {w, h}, {l, t, r, b});
                }
            }
            
            
            Containers::Image ni({w, h}, ColorMode::Alpha);
            atlasdata.CopyTo(ni, {0, 0});
            atlasdata.Swap(ni);

            GL::UpdateTexture(atlas.GetID(), atlasdata);
            atlas.Set(atlas.GetID(), atlas.GetMode(), {w, h});
        }
        else {
            used.resize(w*h);
            atlas.CreateEmpty({w, h}, ColorMode::Alpha);
            atlasdata.Resize ({w, h}, ColorMode::Alpha);
        }
    }
    
    
    void FreeType::pack(float extrasize) const {
        int cursize = 0;
        bool cpeach = true;
        if(atlas.GetID()) {
            cursize = rowsused * atlas.GetImageSize().Width;
        }
        
        //collect images
        Containers::Collection<const Bitmap> images;
        for(auto &g : glyphmap) {
            if(dynamic_cast<const Bitmap*>(g.second.images.regular))
                images.Add(dynamic_cast<const Bitmap*>(g.second.images.regular));
        }
        
        if(images.GetSize() == 0)
            return;
        
        //determine collective size, add 2% extra for inefficiency
        //of packing
        for(auto &i : images) {
            cursize += (int)ceil(i.GetSize().Area() * 1.2 + (tightpack ? 0 : i.GetWidth() + i.GetHeight()));
        }
        
        //Copying each image separately is not working properly, thus it is disabled for now
        //if(images.GetCount() > 5)
            cpeach = false;
        
        if(atlas.GetID()) {
            if(cursize > atlas.GetImageSize().Area()) {
                //enlarge for future glyphs
                cursize = (int)std::ceil(cursize * (1 + extrasize));
                
                setatlassize(cursize);
            }
        }
        else {
			cursize = (int)std::ceil(cursize * (1 + extrasize));

            setatlassize(cursize);
        }
        
        //sort by height then width
        images.Sort([](const Bitmap &l, const Bitmap &r) {
            if(l.GetHeight() == r.GetHeight())
                return l.GetWidth() < r.GetWidth();
            else
                return l.GetHeight() < r.GetHeight();
        });
        
        auto aw = atlasdata.GetWidth(), ah = atlasdata.GetHeight();
        
        std::map<const RectangularDrawable *, TextureImage *> replacements;
        
        //this is not the best algorithm for the job, but it gets the deed done
        for(auto &b : images) {
        retry:
            Geometry::Point cur = {0, 0};
            
            auto w = b.GetWidth(), h = b.GetHeight();
            
            if(!tightpack) {
                w++;
                h++;
            }
            
            bool found = false;
            while(!found) {
                //run out of width, start over from the next row
                if(cur.X + w > aw) { 
                    cur.Y++;
                    cur.X = 0;
                }

                //run out of space, resize and retry
                if(cur.Y + h > ah) { 
                    cursize = (int)std::ceil(cursize * 1.2);
                    setatlassize(cursize);
                    
                    for(auto &img : replacements) {
                        auto im = img.second;
                        auto c = im->GetCoordinates();
                        
                        int l = (int)std::round(c[0].X*aw);
                        int t = (int)std::round(c[0].Y*ah);
                        int r = (int)std::round(c[2].X*aw);
                        int b = (int)std::round(c[2].Y*ah);
                        im->Set(im->GetID(), im->GetMode(), atlasdata.GetSize(), {l, t, r, b});
                    }
                    aw = atlasdata.GetWidth();
                    ah = atlasdata.GetHeight();
                    
                    
                    goto retry;
                }
                
                bool full = false;
                //check horizontal free
                for(int x=cur.X; x<cur.X+w; x++) {
                    if(used[x + cur.Y * aw]) {
                        full = true;
                        break;
                    }
                }
                
                if(full) {
                    cur.X++;
                    continue;
                }
                
                //check vertical free
                for(int y=cur.Y; y<cur.Y+h; y++) {
                    if(used[cur.X + y * aw]) {
                        full = true;
                        break;
                    }
                }
                
                if(full) {
                    cur.X++;
                    continue;
                }
                
                for(int y=cur.Y; y<cur.Y+h; y++) {
                    for(int x=cur.X; x<cur.X+w; x++) {
                        used[x+y*aw] = true;
                    }
                }
                
                //found our place
                b.GetData().CopyTo(atlasdata, cur);
                if(cpeach) {
                    GL::CopyToTexture(atlas.GetID(), b.GetData(), cur);
                }
                
                auto tex = new TextureImage(atlas.GetID(), ColorMode::Alpha, atlas.GetImageSize(), {cur, b.GetSize()});
                
                if(cur.Y + h > rowsused)
                    rowsused = cur.Y + h;
                
                replacements.insert({&b, tex});
                
                destroylist.Add(tex);
                
                found = true;
            }
            
            if(!found) //useless
                return;
        }
        
        if(!cpeach) {
            GL::UpdateTexture(atlas.GetID(), atlasdata);
        }
        
        //to test if the atlas is good
        //Bitmap ad;
        //ad.Assign(atlasdata);
        //ad.ExportPNG("test.png");
        
        //replace images
        for(auto &g : glyphmap) {
            if(replacements.count(g.second.images.regular))
                g.second.images.regular = replacements[g.second.images.regular];
        }
        
        //destroy old images
        for(auto &im : images) {
            destroylist.Remove(im);
        }
        images.Destroy();
    }

    bool FreeType::LoadGlyphs(const std::vector< Gorgon::Graphics::GlyphRange >& ranges, bool prepare) {
        for(auto r : ranges) {
            if(!loadglyphs(r, prepare))
                return false;
        }
        
        if(keeppacked)
            pack();
        
        return true;
    }

    bool FreeType::LoadGlyphs(GlyphRange range, bool prepare) { 
        if(!loadglyphs(range, prepare))
            return false;
        
        if(keeppacked)
            pack();
        
        return true;
    }
    
    void FreeType::Discard() {
        atlasdata.Destroy();
        
        for(auto &g : glyphmap) {
            auto bmp = dynamic_cast<Bitmap*>(g.second.images.regular);
            
            if(bmp)
                bmp->Discard();
        }
        
        lib->destroyface();
        filename = "";
        vecdata  = nullptr;
        data     = nullptr;
        datasize = 0;
    }
    
    std::pair<int, int> FreeType::GetLetterHeight(bool asciionly) const {
        Char c[3];

        if(asciionly) {
            c[0] = 'A';

            loadglyphs('A', true);
            loadglyphs('f', true);
            loadglyphs('j', true);
        }
        else {
            loadglyphs('A', true);
            loadglyphs(0xc2, true);
            loadglyphs('f', true);
            loadglyphs('j', true);

            if(glyphmap.count(0xc2)) {
                c[0] = 0xc2;
            }
            else
                c[0] = 'A';
        }
        c[1] = 'j';
        c[2] = 'f';

        for(int i = 0; i<3; i++) {
            if(!glyphmap.count(c[i]) && !glyphmap.at(c[i]).images.regular)
                return {0, GetHeight()};
        }

        int miny = GetHeight(), maxy = 0;

        for(int i = 0; i<3; i++) {
            const auto &g = glyphmap.at(c[i]);

            //we trust freetype to give us trimmed images

            if(g.offset.Y < miny)
                miny = g.offset.Y;

            if(g.offset.Y + g.images.regular->GetHeight() > maxy)
                maxy = g.offset.Y + g.images.regular->GetHeight();
        }

        return {miny + baseline, maxy - miny};
    }
} }

mercurial