1 /** 2 Copyright: Copyright (c) 2017-2018 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 module voxelman.graphics.font.bitmapfontloader; 7 8 import dlib.image.io.png; 9 import voxelman.math; 10 import voxelman.graphics.image.crop; 11 import voxelman.graphics.bitmap; 12 import voxelman.graphics.font.font; 13 import voxelman.graphics.textureatlas; 14 15 import std.stdio; 16 17 void loadBitmapFont(Font* font, TextureAtlas texAtlas, in string chars) 18 { 19 auto image = new Bitmap(loadPNG(font.filename)); 20 if (image.width == 0 || image.height == 0) return; 21 22 auto sentinelPixel = image[0,0]; 23 //writefln("sent %s", sentinelPixel); 24 25 ivec2 cursor; 26 27 int nextCursorX; 28 // find a pixel past the end of current char 29 void checkNextChar() 30 { 31 nextCursorX = cursor.x+1; 32 while (nextCursorX < image.width && image[nextCursorX, cursor.y] != sentinelPixel) 33 { 34 ++nextCursorX; 35 } 36 } 37 38 int nextLineY; 39 void checkNextLine() 40 { 41 if (cursor.x == 0) // new line 42 { 43 nextLineY = cursor.y+1; 44 while (nextLineY < image.height && image[0, nextLineY] != sentinelPixel) 45 { 46 ++nextLineY; 47 } 48 } 49 } 50 51 void advanceCursor() 52 { 53 if (nextCursorX >= image.width) 54 { 55 cursor.x = 0; 56 cursor.y = nextLineY; 57 } 58 else 59 { 60 cursor.x = nextCursorX; 61 62 // check if end of line 63 // either sentinel + end or two sentinels 64 int rightPixel = cursor.x + 1; 65 if (rightPixel == image.width || image[rightPixel, cursor.y] == sentinelPixel) 66 { 67 // newline 68 cursor.x = 0; 69 cursor.y = nextLineY; 70 } 71 } 72 } 73 74 bool isValidCursor() 75 { 76 return cursor.x < image.width && cursor.y < image.height; 77 } 78 79 int maxGlyphWidth = 0; 80 int maxGlyphHeight = 0; 81 82 void loadGlyph(dchar glyph) 83 { 84 // load glyph 85 auto glyphStart = cursor + ivec2(1,0); 86 auto glyphStartCopy = glyphStart; 87 88 int glyphWidth = nextCursorX - cursor.x - 1; 89 int glyphHeight = nextLineY - cursor.y; 90 auto glyphSize = ivec2(glyphWidth, glyphHeight); 91 92 // crop all transparent pixels 93 cropImage(image, glyphStart, glyphSize); 94 95 // write to texture 96 ivec2 atlasPos = texAtlas.insert(image, irect(glyphStart, glyphSize)); 97 98 ivec2 glyphOffset = glyphStart - glyphStartCopy; 99 100 int glyphAdvanceX = glyphSize.x + 1; 101 int glyphAdvanceY = glyphSize.y + 1; 102 103 auto metrics = GlyphMetrics( 104 glyphSize.x, glyphSize.y, 105 glyphOffset.x, -glyphOffset.y, 106 glyphAdvanceX, glyphAdvanceY); 107 108 maxGlyphWidth = max(maxGlyphWidth, glyphSize.x); 109 // use uncropped height here since there can be no glyphs 110 // that reach both top and bottom of line 111 maxGlyphHeight = max(maxGlyphHeight, glyphHeight); 112 113 //writefln("glyph '%s' img pos %s size %s", glyph, glyphStart, glyphSize); 114 115 font.glyphs[glyph] = Glyph(atlasPos, metrics); 116 } 117 118 import std.utf : byDchar; 119 foreach (dchar glyph; chars.byDchar) 120 { 121 import std.uni : isWhite; 122 if(isWhite(glyph)) continue; 123 124 checkNextChar(); 125 checkNextLine(); 126 127 if (!isValidCursor()) break; 128 129 loadGlyph(glyph); 130 131 advanceCursor(); 132 } 133 134 font.metrics.width = maxGlyphWidth; 135 font.metrics.height = maxGlyphHeight; 136 font.metrics.advanceX = maxGlyphWidth + 1; 137 font.metrics.advanceY = maxGlyphHeight + 1; 138 //writefln("font %s", font.metrics); 139 }