changeset 50:f68ae1d667f9

Options: impl template & new OptionsFont class. Options: impl template to ease creating Options sub-classes. New OptionsFont class (currently only controls render mode and hinting). committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Sun, 01 Jun 2008 18:22:54 +0100
parents bca7e2342d77
children 387a80724c35
files codeDoc/jobs.txt data/L10n/OptionsFont.mtt data/conf/options.mtt mde/Options.d mde/resource/FontTexture.d mde/resource/font.d mde/sdl.d
diffstat 7 files changed, 224 insertions(+), 72 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/jobs.txt	Sat May 31 13:10:06 2008 +0100
+++ b/codeDoc/jobs.txt	Sun Jun 01 18:22:54 2008 +0100
@@ -9,6 +9,7 @@
 To do (importance 0-5: 0 pointless, 1 no obvious impact now, 2 todo sometime, 3 useful, 4 important, 5 urgent):
 Also see todo.txt and FIXME/NOTE comment marks.
 5   mergetag crashes with no ending > on a tag and doesn't add header data when comments are included!
+4   LCD filtering / fonts from Options. Get yMax for font not all glyphs on line?
 4   Not guaranteed to catch up-click ending callback! Appears not to be a problem...
 4   OutOfMemoryException is not currently checked for − it should be at least in critical places (use high-level catching of all errors?).
 3   on-event draw support (mde.events and GUI need to tell mde.mde)
@@ -51,4 +52,5 @@
 
 
 Done (for git log message):
-Enabled getting the size of a text block (before or after rendering).
\ No newline at end of file
+Options: impl template to ease creating Options sub-classes.
+New OptionsFont class (currently only controls render mode and hinting).
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/L10n/OptionsFont.mtt	Sun Jun 01 18:22:54 2008 +0100
@@ -0,0 +1,4 @@
+{MT01}
+{en-GB}
+<entry|lcdFilter=["LCD filtering","Enable or disable LCD filtering. Note that the FreeType library may be compiled with this support disabled due to patents."]>
+<entry|renderMode=["Font rendering mode","Controls how fonts are rendered: in gray-scale, or for LCDs (with a horizontal (normal) or vertical layout, with an RGB (normal) or BGR sub-pixel mode."]>
--- a/data/conf/options.mtt	Sat May 31 13:10:06 2008 +0100
+++ b/data/conf/options.mtt	Sun Jun 01 18:22:54 2008 +0100
@@ -6,6 +6,10 @@
 <int|logLevel=1>
 <double|pollInterval=0.01>
 
+{font}
+<int|lcdFilter=0>
+<int|hinting=0>
+
 {video}
 <bool|noFrame=false>
 <bool|resizable=true>
--- a/mde/Options.d	Sat May 31 13:10:06 2008 +0100
+++ b/mde/Options.d	Sun Jun 01 18:22:54 2008 +0100
@@ -44,7 +44,8 @@
 * Each sub-class provides named variables for maximal-speed reading. Local sub-class references
 * should be used for reading variables, and via the addOptionsClass() hook will be loaded from
 * files during pre-init (init0 stage). Do not write changes directly to the subclasses or they will
-* not be saved; use, for example, Options.setBool(...).
+* not be saved; use, for example, Options.setBool(...). Use an example like OptionsMisc as a
+* template for creating a new Options sub-class.
 *
 * Details: Options sub-classes hold associative arrays of pointers to all option variables, with a
 * char[] id. This list is used for saving, loading and to provide generic GUI options screens. The
@@ -224,41 +225,174 @@
     //END Static
     
     //BEGIN Templates
-    template store(A...) {
-        alias A a;
-    }
-    template decRecurse(char[] A, B...) {
-        static if (B.length) const char[] decRecurse = A ~ ", " ~ decRecurse!(B);
-        else                 const char[] decRecurse = A;
-    }
-    template decBool(A...) {
-        const char[] decBool = "bool " ~ decRecurse!(A) ~ ";\n";
-    }
-    template decInt(A...) {
-        const char[] decInt = "int " ~ decRecurse!(A) ~ ";\n";
-    }
-    template decDouble(A...) {
-        const char[] decDouble = "double " ~ decRecurse!(A) ~ ";\n";
+    private {
+        // Return index of first comma, or halts if not found.
+        template cIndex(char[] A) {
+            static if (A.length == 0)
+                static assert (false, "Error in implementation");
+            else static if (A[0] == ',')
+                const size_t cIndex = 0;
+            else
+                const size_t cIndex = 1 + cIndex!(A[1..$]);
+        }
+        // Return index of first semi-colon, or halts if not found.
+        template scIndex(char[] A) {
+            static if (A.length == 0)
+                static assert (false, "Error: no trailing semi-colon");
+            else static if (A[0] == ';')
+                const size_t scIndex = 0;
+            else
+                const size_t scIndex = 1 + scIndex!(A[1..$]);
+        }
+        // Look for "type symbols;" in A and return symbols as a comma separated list of names
+        // (even if type is encountered more than once). Output may contain spaces and, if
+        // non-empty, will contain a trailing comma. Assumes scIndex always returns less than A.$.
+        template parseT(char[] type, char[] A) {
+            static if (A.length < type.length + 1)	// end of input, no match
+                const char[] parseT = "";
+            else static if (A[0] == ' ')		// leading whitespace: skip
+                const char[] parseT = parseT!(type, A[1..$]);
+            else static if (A[0..type.length] == type && A[type.length] == ' ')	// match
+                const char[] parseT = A[type.length+1 .. scIndex!(A)] ~ "," ~
+                        parseT!(type, A[scIndex!(A)+1 .. $]);
+            else					// no match
+                const char[] parseT = parseT!(type, A[scIndex!(A)+1 .. $]);
+        }
+        // May have a trailing comma. Assumes cIndex always returns less than A.$.
+        template aaVars(char[] A) {
+            static if (A.length == 0)
+                const char[] aaVars = "";
+            else static if (A[0] == ' ')
+                const char[] aaVars = aaVars!(A[1..$]);
+            else
+                const char[] aaVars = "\""~A[0..cIndex!(A)]~"\"[]:&"~A[0..cIndex!(A)] ~ "," ~
+                        aaVars!(A[cIndex!(A)+1..$]);
+        }
+        // strip Trailing Comma
+        template sTC(char[] A) {
+            static if (A.length && A[$-1] == ',')
+                const char[] sTC = A[0..$-1];
+            else
+                const char[] sTC = A;
+        }
+        // if string is empty (other than space) return null, otherwise enclose: [A]
+        template listOrNull(char[] A) {
+            static if (A.length == 0)
+                const char[] listOrNull = "null";
+            else static if (A[0] == ' ')
+                const char[] listOrNull = listOrNull!(A[1..$]);
+            else
+                const char[] listOrNull = "["~A~"]";
+        }
+    } protected {
+        /** Produces the implementation code to go in the constuctor. */
+        template aaDefs(char[] A) {
+            const char[] aaDefs =
+                    "optsBool = "  ~ listOrNull!(sTC!(aaVars!(parseT!("bool"  , A)))) ~ ";\n" ~
+                    "optsInt = "   ~ listOrNull!(sTC!(aaVars!(parseT!("int"   , A)))) ~ ";\n" ~
+                    "optsDouble = "~ listOrNull!(sTC!(aaVars!(parseT!("double", A)))) ~ ";\n" ~
+                    "optsCharA = " ~ listOrNull!(sTC!(aaVars!(parseT!("char[]", A)))) ~ ";\n";
+        }
+        /+/** Produces the implementation code to go in the static constuctor. */
+        template optClassAdd(char[] symb) {
+            const char[] optClassAdd = symb ~ "=new "~classinfo(this).name~";\n";//Options.addOptionsClass("~symb~", );\n";
+        }+/
+        /** mixin impl("type symbol[, symbol[...]];[type symbol[...];][...]")
+         *
+         * E.g.
+         * ---
+         * mixin (impl ("bool a, b; int i;"));
+         * ---
+         *
+         * In case this() needs to be customized, mixin(impl!(A)) is equivalent to:
+         * ---
+         * mixin (A~"\nthis(){\n"~aaDefs!(A)~"}");
+         * ---
+         *
+         * Notes: Only use space as whitespace (no new-lines or tabs). Make sure to add a trailing
+         * semi-colon (;) or you'll get told off! :D
+         *
+         * In general errors aren't reported well. Trial with pragma (msg, impl!(...)); if
+         * necessary.
+         *
+         * Extending: mixins could also be used for the static this() {...} or even the whole
+         * class, but doing would rather decrease readability of any implementation. */
+        template impl(char[] A /+, char[] symb+/) {
+            const char[] impl = A~"\nthis(){\n"~aaDefs!(A)~"}";
+            // ~"\nstatic this(){\n"~optClassAdd!(symb)~"}"
+        }
     }
-    template decCharA(A...) {
-        const char[] decCharA = "char[] " ~ decRecurse!(A) ~ ";\n";
-    }
-    template aaRecurse(char[] A, B...) {
-        static if (B.length) const char[] aaRecurse = "\""~A~"\"[]:&"~A ~ ", " ~ aaRecurse!(B);
-        else                 const char[] aaRecurse = "\""~A~"\"[]:&"~A;
-    }
-    template aaBool(A...) {
-        const char[] aaBool = "optsBool = [" ~ aaRecurse!(A) ~ "];\n";
-    }
-    template aaInt(A...) {
-        const char[] aaInt = "optsInt = [" ~ aaRecurse!(A) ~ "];\n";
-    }
-    template aaDouble(A...) {
-        const char[] aaDouble = "optsDouble = [" ~ aaRecurse!(A) ~ "];\n";
-    }
-    template aaCharA(A...) {
-        const char[] aaCharA = "optsCharA = [" ~ aaRecurse!(A) ~ "];\n";
-    }
+    /+/** mixin impl("type symbol[, symbol[...]];[type symbol[...];][...]")
+     *
+     * E.g.
+     * ---
+     * mixin (impl ("bool a, b; int i;"));
+     * ---
+     * The parser isn't highly accurate. */
+    // Try using templates instead? See std.metastrings
+    static char[] impl (char[] A) {
+        char[] bools;
+        char[] ints;
+        
+        while (A.length) {
+            // Trim whitespace
+            {
+                size_t i = 0;
+                while (i < A.length && (A[i] == ' ' || (A[i] >= 9u && A[i] <= 0xD)))
+                    ++i;
+                A = A[i..$];
+            }
+            if (A.length == 0) break;
+            
+            char[] type;
+            for (size_t i = 0; i < A.length; ++i) {
+                if (A[i] == ' ' || (A[i] >= 9u && A[i] <= 0xD)) {
+                    type = A[0..i];
+                    A = A[i+1..$];
+                    break;
+                }
+            }
+            
+            char[] symbols;
+            for (size_t i = 0; i < A.length; ++i) {
+                if (A[i] == ';') {
+                    symbols = A[0..i];
+                    A = A[i+1..$];
+                    break;
+                }
+            }
+            
+            if (type == "bool") {
+                if (bools.length)
+                    bools = bools ~ "," ~ symbols;
+                else
+                    bools = symbols;
+            }
+            else if (type == "int") {
+                if (ints.length)
+                    ints = ints ~ "," ~ symbols;
+                else
+                    ints = symbols;
+            }
+            else {
+                // Unfortunately, we cannot output non-const strings (even though func is compile-time)
+                // We also cannot use pragma(msg) because the message gets printed even if the code isn't run.
+                //pragma(msg, "Warning: impl failed to parse whole input string");
+                // Cannot use Cout / logger either.
+                break;
+            }
+        }
+        
+        char[] ret;
+        if (bools.length)
+            ret = "bool "~bools~";\n";
+        if (ints.length)
+            ret = ret ~ "int "~ints~";\n";
+        
+        
+        
+        return ret;
+    }+/
     //END Templates
 }
 
@@ -341,22 +475,7 @@
 /** A home for all miscellaneous options, at least for now. */
 OptionsMisc miscOpts;
 class OptionsMisc : Options {
-    alias store!("useThreads", "exitImmediately") BOOL;
-    alias store!("logLevel") INT;
-    alias store!("pollInterval") DOUBLE;
-    alias store!("L10n") CHARA;
-    
-    mixin (decBool!(BOOL.a));
-    mixin (decInt!(INT.a));
-    mixin (decDouble!(DOUBLE.a));
-    mixin (decCharA!(CHARA.a));
-    
-    this () {
-        mixin (aaBool!(BOOL.a));
-        mixin (aaInt!(INT.a));
-        mixin (aaDouble!(DOUBLE.a));
-        mixin (aaCharA!(CHARA.a));
-    }
+    mixin (impl!("bool useThreads, exitImmediately; int logLevel; double pollInterval; char[] L10n;"));
     
     static this() {
         miscOpts = new OptionsMisc;
--- a/mde/resource/FontTexture.d	Sat May 31 13:10:06 2008 +0100
+++ b/mde/resource/FontTexture.d	Sun Jun 01 18:22:54 2008 +0100
@@ -15,12 +15,16 @@
 
 /** Font caching system.
  *
+ * This module also serves as the internals to the font module and shouldn't be used except through
+ * the font module. The two modules could be combined, at a cost to readability.
+ *
  * Three types of coordinates get used in the system: FreeType coordinates for each glyph, texture
  * coordinates, and OpenGL's model/world coordinates (for rendering). The freetype and texture
  * coords are cartesian (i.e. y increases upwards), although largely this is too abstract to
  * matter. However, for the model/world coords, y increases downwards. */
 module mde.resource.FontTexture;
 
+import mde.Options;
 import mde.resource.exception;
 
 import derelict.freetype.ft;
@@ -34,9 +38,6 @@
     logger = Log.getLogger ("mde.resource.FontTexture");
 }
 
-auto hinting = FT_LOAD_TARGET_NORMAL; //or FT_LOAD_TARGET_LCD (or others)
-//FIXME: allow setting lcd filtering
-auto lcdFilter = FT_LcdFilter.FT_LCD_FILTER_DEFAULT;	//altertives: NONE, LIGHT
 static const int dimW = 256, dimH = 256;	// Texture size
 const wFactor = 1f / dimW;
 const hFactor = 1f / dimH;
@@ -201,7 +202,8 @@
         auto gi = FT_Get_Char_Index (face, chr);
         auto g = face.glyph;
         
-        if (FT_Load_Glyph (face, gi, FT_LOAD_RENDER | hinting))
+        // Use renderMode from options, masking bits which are allowable:
+        if (FT_Load_Glyph (face, gi, FT_LOAD_RENDER | (fontOpts.renderMode & 0xF0000)))
             throw new fontGlyphException ("Unable to render glyph");
         
         auto b = g.bitmap;
@@ -237,9 +239,12 @@
         if (b.pixel_mode == FT_Pixel_Mode.FT_PIXEL_MODE_GRAY && b.num_grays == 256)
             format = GL_LUMINANCE;
         else if (b.pixel_mode == FT_Pixel_Mode.FT_PIXEL_MODE_LCD ||
-                 b.pixel_mode == FT_Pixel_Mode.FT_PIXEL_MODE_LCD_V)
-            format = GL_RGB;
-        else
+                 b.pixel_mode == FT_Pixel_Mode.FT_PIXEL_MODE_LCD_V) {
+            if (fontOpts.renderMode & RENDER_LCD_BGR)
+                format = GL_BGR;
+            else
+                format = GL_RGB;
+        } else
             throw new fontGlyphException ("Unsupported freetype bitmap format");
         
         glTexSubImage2D(GL_TEXTURE_2D, 0,
@@ -374,6 +379,24 @@
     int nextYPos = 0;	// y position for next created line (0 for first line)
 }
 
+// this bit of renderMode, if set, means read glyph as BGR not RGB when using LCD rendering
+enum { RENDER_LCD_BGR = 1 << 30 }
+OptionsFont fontOpts;
+class OptionsFont : Options {
+    /* renderMode should be FT_LOAD_TARGET_NORMAL, FT_LOAD_TARGET_LIGHT, FT_LOAD_TARGET_LCD or
+     * FT_LOAD_TARGET_LCD_V, possibly with bit 31 set (see RENDER_LCD_BGR).
+     * FT_LOAD_TARGET_MONO is unsupported.
+     *
+     * lcdFilter should come from enum FT_LcdFilter:
+     * FT_LCD_FILTER_NONE, FT_LCD_FILTER_DEFAULT, FT_LCD_FILTER_LIGHT */
+    mixin (impl!("int renderMode, lcdFilter;"));
+    
+    static this() {
+        fontOpts = new OptionsFont;
+        Options.addOptionsClass (fontOpts, "font");
+    }
+}
+
 struct GlyphAttribs {
     int x, y;	// position within texture
     int w, h;	// bitmap size
--- a/mde/resource/font.d	Sat May 31 13:10:06 2008 +0100
+++ b/mde/resource/font.d	Sun Jun 01 18:22:54 2008 +0100
@@ -16,6 +16,7 @@
 /// Sets up freetype (in a basic way).
 module mde.resource.font;
 
+import mde.Options;
 import mde.resource.FontTexture;
 import mde.resource.exception;
 
@@ -70,6 +71,14 @@
                 logger.warn (logger.format (tmp, "Using an untested FreeType version: {}.{}.{}", maj, min, patch));
             }
             
+            // Set LCD filtering method
+            if (FT_Library_SetLcdFilter(library, cast(FT_LcdFilter)fontOpts.lcdFilter)) {
+                // If setting failed, leave at default (disabled status). Note: it is disabled by
+                // default because the code isn't compiled in by default, to avoid patents.
+                logger.warn ("Bad/unsupported LCD filter option; disabling.");
+                Options.setInt ("font", "lcdFilter", FT_LcdFilter.FT_LCD_FILTER_NONE);
+            }
+            
             /* Load font settings
              *
              * Each mergetag section corresponds to a font; each is loaded whether used or not
@@ -260,3 +269,6 @@
     
     FT_Face	face;
 }
+
+/+class OptionsFont : Options {
+    alias store!(+/
\ No newline at end of file
--- a/mde/sdl.d	Sat May 31 13:10:06 2008 +0100
+++ b/mde/sdl.d	Sun Jun 01 18:22:54 2008 +0100
@@ -179,19 +179,7 @@
 /** All video options. */
 OptionsVideo vidOpts;
 class OptionsVideo : Options {
-    alias store!("fullscreen","hardware","resizable","noFrame") BOOL;
-    alias store!("screenW","screenH","windowW","windowH") INT;
-    //alias store!() CHARA;
-    
-    mixin (decBool!(BOOL.a));
-    mixin (decInt!(INT.a));
-    //mixin (decCharA!(CHARA.a));
-    
-    this () {
-        mixin (aaBool!(BOOL.a));
-        mixin (aaInt!(INT.a));
-        //mixin (aaCharA!(CHARA.a));
-    }
+    mixin (impl!("bool fullscreen,hardware,resizable,noFrame; int screenW,screenH,windowW,windowH;"));
     
     static this() {
         vidOpts = new OptionsVideo;