1 /* 2 * Copyright (C) 2015-2017, by Laszlo Szeremi under the Boost license. 3 * 4 * Pixel Perfect Engine, graphics.fontsets module 5 */ 6 7 module pixelperfectengine.graphics.fontsets; 8 public import pixelperfectengine.graphics.bitmap; 9 public import pixelperfectengine.system.exc; 10 import bmfont; 11 import dimage; 12 //public import pixelperfectengine.system.binarySearchTree; 13 import collections.treemap; 14 import collections.sortedlist; 15 static import std.stdio; 16 /** 17 * Stores the letters and all the data associated with the font, also has functions related to text lenght and line formatting. Supports variable letter width. 18 * TODO: Add ability to load from dpk files through the use of vfiles. 19 */ 20 public class Fontset(T) 21 if(T.stringof == Bitmap8Bit.stringof || T.stringof == Bitmap16Bit.stringof || T.stringof == Bitmap32Bit.stringof) { 22 //If the kerning map will cost too much memory etc, this will be used instead. 23 protected struct KerningInfo { 24 align(2): 25 dchar a; 26 dchar b; 27 short amount; 28 } 29 //public Font fontinfo; ///BMFont information on drawing the letters (might be removed later on) 30 //alias CharMap = TreeMap!(dchar, Font.Char, true); 31 alias CharMap = Font.Char[]; 32 /* alias KerningMapB = TreeMap!(dchar, short, true); 33 alias KerningMap = TreeMap!(dchar, KerningMapB, true); */ 34 alias KerningMap = KerningInfo[]; 35 protected CharMap _chars; ///Contains character information in a fast lookup form 36 protected KerningMap _kerning; ///Contains kerning information 37 public T[] pages; ///Character pages 38 private Fontset!T[] fallbacks; ///Provides fallbacks to other fontsets in case of missing characters 39 private string name; ///Name of the font 40 private int _size; ///Height of the font 41 ///Empty constructor, primarily for testing purposes 42 public this () {} 43 /** 44 * Loads a fontset from disk. 45 */ 46 public this (std.stdio.File file, string basepath = "") { 47 import std.path : extension; 48 import bitleveld.reinterpret; 49 50 ubyte[] buffer; 51 buffer.length = cast(size_t)file.size; 52 file.rawRead(buffer); 53 Font fontinfo = parseFnt(buffer); 54 /* foreach(ch; fontinfo.chars){ 55 _chars[ch.id] = ch; 56 } */ 57 _chars = fontinfo.chars; 58 foreach(krn; fontinfo.kernings){ 59 _kerning ~= KerningInfo(krn.first, krn.second, krn.amount); 60 /* KerningMapB* mapB = _kerning.ptrOf(krn.first); 61 if (mapB !is null) { 62 (*mapB)[krn.second] = krn.amount; 63 } else { 64 KerningMapB newMap; 65 newMap[krn.second] = krn.amount; 66 _kerning[krn.first] = newMap; 67 } */ 68 //_kerning[krn.first][krn.second] = krn.amount; 69 } 70 _size = fontinfo.info.fontSize; 71 name = fontinfo.info.fontName; 72 foreach (path; fontinfo.pages) { 73 std.stdio.File pageload = std.stdio.File(basepath ~ path); 74 switch (extension(path)) { 75 case ".tga", ".TGA": 76 TGA fontPage = TGA.load(pageload); 77 if(!fontPage.getHeader.topOrigin){ 78 fontPage.flipVertical; 79 } 80 static if (T.stringof == Bitmap8Bit.stringof) { 81 if(fontPage.getBitdepth != 8) 82 throw new BitmapFormatException("Bitdepth mismatch exception!"); 83 pages ~= new Bitmap8Bit(fontPage.imageData.raw, fontPage.width, fontPage.height); 84 } else static if (T.stringof == Bitmap16Bit.stringof) { 85 if(fontPage.getBitdepth != 16) 86 throw new BitmapFormatException("Bitdepth mismatch exception!"); 87 pages ~= new Bitmap16Bit(reinterpretCast!ushort(fontPage.imageData.raw), fontPage.width, fontPage.height); 88 } else static if (T.stringof == Bitmap32Bit.stringof) { 89 if(fontPage.getBitdepth != 32) 90 throw new BitmapFormatException("Bitdepth mismatch exception!"); 91 pages ~= new Bitmap32Bit(reinterpretCast!Color(fontPage.imageData.raw), fontPage.width, fontPage.height); 92 } 93 break; 94 case ".png", ".PNG": 95 PNG fontPage = PNG.load(pageload); 96 static if(T.stringof == Bitmap8Bit.stringof) { 97 if(fontPage.getBitdepth != 8) 98 throw new BitmapFormatException("Bitdepth mismatch exception!"); 99 pages ~= new Bitmap8Bit(fontPage.imageData.raw, fontPage.width, fontPage.height); 100 } else static if(T.stringof == Bitmap32Bit.stringof) { 101 if(fontPage.getBitdepth != 32) 102 throw new BitmapFormatException("Bitdepth mismatch exception!"); 103 pages ~= new Bitmap32Bit(reinterpretCast!Color(fontPage.imageData.raw), fontPage.width, fontPage.height); 104 } 105 break; 106 case ".bmp", ".BMP": 107 BMP fontPage = BMP.load(pageload); 108 static if(T.stringof == Bitmap8Bit.stringof) { 109 if(fontPage.getBitdepth != 8) 110 throw new BitmapFormatException("Bitdepth mismatch exception!"); 111 pages ~= new Bitmap8Bit(fontPage.imageData.raw, fontPage.width, fontPage.height); 112 } else static if(T.stringof == Bitmap32Bit.stringof) { 113 if(fontPage.getBitdepth != 32) 114 throw new BitmapFormatException("Bitdepth mismatch exception!"); 115 pages ~= new Bitmap32Bit(fontPage.imageData.raw, fontPage.width, fontPage.height); 116 } 117 break; 118 default: 119 throw new Exception("Unsupported file format!"); 120 } 121 } 122 } 123 ///Returns the name of the font 124 public string getName() @nogc @safe nothrow pure @property const { 125 return name; 126 } 127 /* ///Returns the height of the font. 128 ///WILL BE DEPRECATED! 129 public deprecated int getSize() @nogc @safe nothrow pure const { 130 return _size; 131 } */ 132 ///returns the height of the font. 133 public int size() @nogc @safe nothrow pure @property const { 134 return _size; 135 } 136 ///Returns the width of the text in pixels. 137 public int getTextLength(dstring text) @nogc @safe nothrow pure { 138 int length; 139 foreach(c; text){ 140 length += chars(c).xadvance; 141 } 142 return length; 143 } 144 /** 145 * Returns the character info if present, or a substitute from either a fallback font if it found in them or 146 * the default substitute character (0xFFFD) 147 */ 148 public Font.Char chars(dchar c) @nogc @trusted nothrow pure { 149 foreach (key; _chars) { 150 if (key.id == c) 151 return key; 152 } 153 assert(c != 0xFFFD); 154 foreach(fntSt ; fallbacks) { 155 Font.Char result = fntSt.chars(c); 156 if(result.id != dchar.init) return result; 157 } 158 159 return chars(0xFFFD); 160 } 161 /** 162 * Returns the kerning for the given character pair if there's any, or 0. 163 * Should be called through CharacterFormattingInfo, which can bypass it if formatting flag is enabled. 164 */ 165 public final short getKerning(const dchar first, const dchar second) @nogc @safe pure nothrow { 166 foreach (KerningInfo key ; _kerning) { 167 if (key.a == first && key.b == second) 168 return key.amount; 169 } 170 return 0; 171 } 172 /** 173 * Breaks the input text into multiple lines according to the parameters. 174 */ 175 public dstring[] breakTextIntoMultipleLines(dstring input, int maxWidth, bool ignoreNewLineChars = false){ 176 dstring[] output; 177 dstring currentWord, currentLine; 178 int currentWordLength, currentLineLength; 179 180 foreach(character; input){ 181 currentWordLength += chars(character).xadvance; 182 if(!ignoreNewLineChars && currentWordLength && (character == FormattingCharacters.newLine || 183 character == FormattingCharacters.carriageReturn || character == FormattingCharacters.newParagraph)) { 184 //initialize new line on symbols indicating new lines 185 if(currentWordLength + currentLineLength <= maxWidth){ 186 currentLine ~= currentWord; 187 output ~= currentLine; 188 }else{ 189 output ~= currentLine; 190 currentLine = currentWord; 191 } 192 currentLine.length = 0; 193 currentLineLength = 0; 194 currentWord.length = 0; 195 currentWordLength = 0; 196 }else if(character == FormattingCharacters.space){ 197 //add new word to the current line if it has enough space, otherwise break the line and initialize next one 198 if(currentWordLength + currentLineLength <= maxWidth){ 199 currentLineLength += currentWordLength; 200 currentLine ~= currentWord ~ ' '; 201 }else{ 202 output ~= currentLine; 203 } 204 }else{ 205 if(currentWordLength > maxWidth){ //Flush current word if it will be too long for a single line 206 output ~= currentWord; 207 currentLine.length = 0; 208 currentWordLength = 0; 209 } 210 currentWord ~= character; 211 212 } 213 } 214 215 return output; 216 } 217 } 218 /** 219 * Specifies formatting flags. 220 * They usually can be mixed with others, see documentation for further info. 221 */ 222 public enum FormattingFlags : uint { 223 reset = 0b0000_0000_0000_0000_0000_0000_0000_0000, 224 //Mask used for detecting complex formatting flags 225 justifyMask = 0b0000_0000_0000_0000_0000_0000_0000_0111, 226 ulMask = 0b0000_0000_0000_0000_0000_0000_0111_0000, 227 ulLineMultiplier = 0b0000_0000_0000_0000_0000_0001_1000_0000, 228 ulLineStyle = 0b0000_0000_0000_0000_0000_1110_0000_0000, 229 forceItalicsMask = 0b0000_0000_0000_0000_1100_0000_0000_0000, 230 231 leftJustify = 0b0000_0000_0000_0000_0000_0000_0000_0000, 232 rightJustify = 0b0000_0000_0000_0000_0000_0000_0000_0001, 233 centerJustify = 0b0000_0000_0000_0000_0000_0000_0000_0010, 234 fillEntireLine = 0b0000_0000_0000_0000_0000_0000_0000_0100, 235 slHorizCenter = 0b0000_0000_0000_0000_0000_0000_0000_1000, 236 237 underline = 0b0000_0000_0000_0000_0000_0000_0001_0000, 238 underlinePerWord = 0b0000_0000_0000_0000_0000_0000_0010_0000, 239 240 underlineDouble = 0b0000_0000_0000_0000_0000_0000_1000_0000, 241 underlineTriple = 0b0000_0000_0000_0000_0000_0001_0000_0000, 242 underlineQuadruple = 0b0000_0000_0000_0000_0000_0001_1000_0000, 243 244 underlineDotted = 0b0000_0000_0000_0000_0000_0010_0000_0000, 245 underlineWavy = 0b0000_0000_0000_0000_0000_0100_0000_0000, 246 underlineWavySoft = 0b0000_0000_0000_0000_0000_0110_0000_0000, 247 underlineStripes = 0b0000_0000_0000_0000_0000_1000_0000_0000, 248 249 overline = 0b0000_0000_0000_0000_0001_0000_0000_0000, 250 strikeThrough = 0b0000_0000_0000_0000_0010_0000_0000_0000, 251 //Forced italic fonts. The upper portions of the letters get shifted to the right by a certain amount set by the flags. 252 //If all clear, then the text will be displayed normally from its bitmap form. 253 forceItalics1per4 = 0b0000_0000_0000_0000_0100_0000_0000_0000, 254 forceItalics1per3 = 0b0000_0000_0000_0000_1000_0000_0000_0000, 255 forceItalics1per2 = 0b0000_0000_0000_0000_1100_0000_0000_0000, 256 257 disableKerning = 0b0000_0000_0000_0001_0000_0000_0000_0000, 258 } 259 /** 260 * Stores character formatting info. 261 */ 262 public class CharacterFormattingInfo(T) { 263 public Fontset!T font; ///The type of the font 264 static if(T.stringof == Bitmap8Bit.stringof) 265 public ubyte color; ///The displayed color 266 else static if(T.stringof == Bitmap16Bit.stringof) 267 public ushort color; ///The displayed color 268 else static if(T.stringof == Bitmap32Bit.stringof) 269 public Color color; ///The displayed color 270 public uint formatFlags; ///Styleflags to be set for different purposes (eg, orientation, understrike style) 271 public ushort paragraphSpace; ///The space between paragraphs in pixels 272 public short rowHeight; ///Modifies the space between rows of text within a single formatting unit 273 public ushort offsetV; ///Upper-part offseting. The amount of lines which should be skipped (single line), or moved upwards (multi line) 274 static if(T.stringof == Bitmap8Bit.stringof) { 275 /** 276 * Creates character formatting from the supplied data. 277 * Params: 278 * font = The fontset to be used for the formatting. 279 * color = The color of the text. 280 * formatFlags = Formatting flags, some combination of FormattingFlags ORed together. 281 * paragraphSpace = Spaces between new paragraphs. 282 * rowHeight = Total height of a row (fontsize + space between lines). 283 * offsetV = Vertical offset on singleline texts. 284 */ 285 public this(Fontset!T font, ubyte color, uint formatFlags, ushort paragraphSpace, short rowHeight, ushort offsetV) 286 @safe { 287 this.font = font; 288 this.color = color; 289 this.formatFlags = formatFlags; 290 this.paragraphSpace = paragraphSpace; 291 this.rowHeight = rowHeight; 292 this.offsetV = offsetV; 293 } 294 } 295 ///Copy constructor 296 public this(CharacterFormattingInfo!T source) @safe { 297 this.font = source.font; 298 this.color = source.color; 299 this.formatFlags = source.formatFlags; 300 this.paragraphSpace = source.paragraphSpace; 301 this.rowHeight = source.rowHeight; 302 this.offsetV = source.offsetV; 303 } 304 /** 305 * Returns the kerning amount, or zero if disabled by formatting. 306 */ 307 public short getKerning(const dchar first, const dchar second) @nogc @safe pure nothrow { 308 if (!(formatFlags & FormattingFlags.disableKerning)) 309 return font.getKerning(first, second); 310 else 311 return short.init; 312 } 313 /** 314 * Checks if two instances hold the same character formatting information. 315 */ 316 public bool opEquals(CharacterFormattingInfo!T rhs) { 317 return this.color == rhs.color && this.font == rhs.font && this.formatFlags == rhs.formatFlags && 318 this.paragraphSpace == rhs.paragraphSpace && this.rowHeight == rhs.rowHeight; 319 } 320 public int getItalicsAm() @nogc @safe pure nothrow { 321 switch (formatFlags & FormattingFlags.forceItalicsMask) { 322 case FormattingFlags.forceItalics1per2: 323 return 2; 324 case FormattingFlags.forceItalics1per3: 325 return 3; 326 case FormattingFlags.forceItalics1per4: 327 return 4; 328 default: 329 return 1; 330 } 331 } 332 alias opEquals = Object.opEquals; 333 } 334 ///Defines formatting characters. 335 ///DC1 is repurposed to initialize binary embedded CharacterFormattingInfo. 336 ///DC2 is repurposed to initialize length of formatted text blocks. 337 public enum FormattingCharacters : dchar{ 338 horizontalTab = 0x09, 339 newLine = 0x0A, 340 newParagraph = 0x0B, 341 carriageReturn = 0x0D, 342 binaryDLE = 0x10, /// 343 binaryCFI = 0x11, ///Use DC1 to initialize binary embedded CFI 344 binaryLI = 0x12, ///Use DC2 to initialize binary length indicator of text blocks in bytes 345 space = 0x20, 346 } 347 ///Defines what kind of encoding the text use 348 public enum TextType : ubyte { 349 ASCII = 1, 350 UTF8 = 2, 351 UTF16 = 3, 352 UTF32 = 4, 353 }