mirror of https://github.com/adamdruppe/arsd.git
moar fixes
This commit is contained in:
parent
21c6700b73
commit
05e496430a
5
dom.d
5
dom.d
|
@ -1252,7 +1252,6 @@ class Document : FileResource {
|
|||
}
|
||||
|
||||
/// ditto
|
||||
@scriptable
|
||||
deprecated("use querySelectorAll instead")
|
||||
Element[] getElementsBySelector(string selector) {
|
||||
return root.getElementsBySelector(selector);
|
||||
|
@ -1598,8 +1597,8 @@ class Element {
|
|||
assert(tagName !is null);
|
||||
}
|
||||
out(e) {
|
||||
assert(e.parentNode is this);
|
||||
assert(e.parentDocument is this.parentDocument);
|
||||
//assert(e.parentNode is this);
|
||||
//assert(e.parentDocument is this.parentDocument);
|
||||
}
|
||||
body {
|
||||
auto e = Element.make(tagName, childInfo, childInfo2);
|
||||
|
|
|
@ -126,6 +126,13 @@ class GameHelperBase {
|
|||
}
|
||||
}
|
||||
|
||||
protected bool redrawForced;
|
||||
|
||||
/// Forces a redraw even if update returns false
|
||||
final public void forceRedraw() {
|
||||
redrawForced = true;
|
||||
}
|
||||
|
||||
/// These functions help you handle user input. It offers polling functions for
|
||||
/// keyboard, mouse, joystick, and virtual controller input.
|
||||
///
|
||||
|
@ -282,6 +289,11 @@ void runGame(T : GameHelperBase)(T game, int maxUpdateRate = 20, int maxRedrawRa
|
|||
bool changed = game.update(now - lastUpdate);
|
||||
lastUpdate = now;
|
||||
|
||||
if(game.redrawForced) {
|
||||
changed = true;
|
||||
game.redrawForced = false;
|
||||
}
|
||||
|
||||
// FIXME: rate limiting
|
||||
if(changed)
|
||||
window.redrawOpenGlSceneNow();
|
||||
|
@ -497,6 +509,12 @@ final class OpenGlTexture {
|
|||
}
|
||||
}
|
||||
|
||||
/+
|
||||
FIXME: i want to do stbtt_GetBakedQuad for ASCII and use that
|
||||
for simple cases especially numbers. for other stuff you can
|
||||
create the texture for the text above.
|
||||
+/
|
||||
|
||||
///
|
||||
void clearOpenGlScreen(SimpleWindow window) {
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_ACCUM_BUFFER_BIT);
|
||||
|
|
176
jsvar.d
176
jsvar.d
|
@ -527,6 +527,21 @@ struct var {
|
|||
this.opAssign(t);
|
||||
}
|
||||
|
||||
public var _copy_new() {
|
||||
if(payloadType() == Type.Object) {
|
||||
var cp;
|
||||
if(this._payload._object !is null)
|
||||
cp._object = this._payload._object.new_;
|
||||
return cp;
|
||||
} else if(payloadType() == Type.Array) {
|
||||
var cp;
|
||||
cp = this._payload._array.dup;
|
||||
return cp;
|
||||
} else {
|
||||
return this._copy();
|
||||
}
|
||||
}
|
||||
|
||||
public var _copy() {
|
||||
final switch(payloadType()) {
|
||||
case Type.Integral:
|
||||
|
@ -1622,6 +1637,50 @@ class PrototypeObject {
|
|||
return n;
|
||||
}
|
||||
|
||||
bool isSpecial() { return false; }
|
||||
|
||||
PrototypeObject new_() {
|
||||
// if any of the prototypes are D objects, we need to try to copy them.
|
||||
auto p = prototype;
|
||||
|
||||
PrototypeObject[32] stack;
|
||||
PrototypeObject[] fullStack = stack[];
|
||||
int stackPos;
|
||||
|
||||
while(p !is null) {
|
||||
|
||||
if(p.isSpecial()) {
|
||||
auto proto = p.new_();
|
||||
|
||||
while(stackPos) {
|
||||
stackPos--;
|
||||
auto pr = fullStack[stackPos].copy();
|
||||
pr.prototype = proto;
|
||||
proto = pr;
|
||||
}
|
||||
|
||||
auto n = new PrototypeObject();
|
||||
n.prototype = proto;
|
||||
n.name = this.name;
|
||||
foreach(k, v; _properties) {
|
||||
n._properties[k] = v._copy;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
if(stackPos >= fullStack.length)
|
||||
fullStack ~= p;
|
||||
else
|
||||
fullStack[stackPos] = p;
|
||||
stackPos++;
|
||||
|
||||
p = p.prototype;
|
||||
}
|
||||
|
||||
return copy();
|
||||
}
|
||||
|
||||
PrototypeObject copyPropertiesFrom(PrototypeObject p) {
|
||||
foreach(k, v; p._properties) {
|
||||
this._properties[k] = v._copy;
|
||||
|
@ -1871,13 +1930,19 @@ var subclassable(T)() if(is(T == class) || is(T == interface)) {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
// I don't want to necessarily call a constructor but I need an object t use as the prototype
|
||||
// hence this faked one. hopefully the new operator will see void[] and assume it can have GC ptrs...
|
||||
static ScriptableT _allocate_() {
|
||||
void[] store = new void[](__traits(classInstanceSize, ScriptableT));
|
||||
store[] = typeid(ScriptableT).initializer[];
|
||||
ScriptableT dummy = cast(ScriptableT) store.ptr;
|
||||
//import std.stdio; writeln("Allocating new ", cast(ulong) store.ptr);
|
||||
return dummy;
|
||||
}
|
||||
}
|
||||
|
||||
// I don't want to necessarily call a constructor but I need an object t use as the prototype
|
||||
// hence this faked one. hopefully the new operator will see void[] and assume it can have GC ptrs...
|
||||
void[] store = new void[](__traits(classInstanceSize, ScriptableT));
|
||||
store[] = typeid(ScriptableT).initializer[];
|
||||
ScriptableT dummy = cast(ScriptableT) store.ptr;
|
||||
ScriptableT dummy = ScriptableT._allocate_();
|
||||
|
||||
var proto = wrapNativeObject!(ScriptableT, true)(dummy);
|
||||
|
||||
|
@ -1899,9 +1964,12 @@ unittest {
|
|||
// is written in a unittest; it shouldn't actually
|
||||
// be necessary under normal circumstances.
|
||||
static class Foo : IFoo {
|
||||
ulong handle() { return cast(ulong) cast(void*) this; }
|
||||
string method() { return "Foo"; }
|
||||
int method2() { return 10; }
|
||||
int args(int a, int b) { return a+b; }
|
||||
int args(int a, int b) {
|
||||
//import std.stdio; writeln(a, " + ", b, " + ", member_, " on ", cast(ulong) cast(void*) this);
|
||||
return member_+a+b; }
|
||||
|
||||
int member_;
|
||||
@property int member(int i) { return member_ = i; }
|
||||
|
@ -1940,6 +2008,14 @@ unittest {
|
|||
|
||||
foo.member(55);
|
||||
|
||||
// proves the new operator actually creates new D
|
||||
// objects as well to avoid sharing instance state.
|
||||
var foo2 = new Foo();
|
||||
assert(foo2.handle() != foo.handle());
|
||||
|
||||
// passing arguments works
|
||||
assert(foo.args(2, 4) == 6 + 55); // (and sanity checks operator precedence)
|
||||
|
||||
var bar = new Bar();
|
||||
assert(bar.method() == "Bar");
|
||||
assert(bar.method2() == 10);
|
||||
|
@ -1950,21 +2026,34 @@ unittest {
|
|||
// the script can even subclass D classes!
|
||||
class Amazing : Bar {
|
||||
// and override its methods
|
||||
var inst = 99;
|
||||
function method() {
|
||||
return "Amazing";
|
||||
}
|
||||
|
||||
// note: to access instance members or virtual call lookup you MUST use the `this` keyword
|
||||
// otherwise the function will be called with scope limited to this class itself (similar to javascript)
|
||||
function other() {
|
||||
// this.inst is needed to get the instance variable (otherwise it would only look for a static var)
|
||||
// and this.method triggers dynamic lookup there, so it will get children's overridden methods if there is one
|
||||
return this.inst ~ this.method();
|
||||
}
|
||||
|
||||
function args(a, b) {
|
||||
// calling parent class method still possible
|
||||
// (the script may get the `super` keyword soon btw)
|
||||
return Bar.args(a*2, b*2);
|
||||
return super.args(a*2, b*2);
|
||||
}
|
||||
}
|
||||
|
||||
var amazing = new Amazing();
|
||||
assert(amazing.method() == "Amazing");
|
||||
assert(amazing.method2() == 10); // calls back to the parent class
|
||||
assert(amazing.args(2, 4) == 12);
|
||||
amazing.member(5);
|
||||
|
||||
// this line I can paste down to interactively debug the test btw.
|
||||
//}, globals); repl!true(globals); interpret(q{
|
||||
|
||||
assert(amazing.args(2, 4) == 12+5);
|
||||
|
||||
var wc = new WithCtor(5); // argument passed to constructor
|
||||
assert(wc.getValue() == 5);
|
||||
|
@ -1973,6 +2062,20 @@ unittest {
|
|||
assert(wc.arg == 5);
|
||||
|
||||
// but property WRITING is currently not working though.
|
||||
|
||||
|
||||
class DoubleChild : Amazing {
|
||||
function method() {
|
||||
return "DoubleChild";
|
||||
}
|
||||
}
|
||||
|
||||
// can also do a child of a child class
|
||||
var dc = new DoubleChild();
|
||||
assert(dc.method() == "DoubleChild");
|
||||
assert(dc.other() == "99DoubleChild"); // the `this.method` means it uses the replacement now
|
||||
assert(dc.method2() == 10); // back to the D grandparent
|
||||
assert(dc.args(2, 4) == 12); // but the args impl from above
|
||||
}, globals);
|
||||
|
||||
Foo foo = globals.foo.get!Foo; // get the native object back out
|
||||
|
@ -2008,16 +2111,27 @@ template helper(alias T) { alias helper = T; }
|
|||
|
||||
By default, it will wrap all methods and members with a public or greater protection level. The second template parameter can filter things differently. FIXME implement this
|
||||
|
||||
That may be done automatically with `opAssign` in the future.
|
||||
History:
|
||||
This became the default after April 24, 2020. Previously, [var.opAssign] would [wrapOpaquely] instead.
|
||||
+/
|
||||
WrappedNativeObject wrapNativeObject(Class, bool special = false)(Class obj) if(is(Class == class)) {
|
||||
import std.meta;
|
||||
return new class WrappedNativeObject {
|
||||
static class WrappedNativeObjectImpl : WrappedNativeObject {
|
||||
override Object getObject() {
|
||||
return obj;
|
||||
}
|
||||
|
||||
this() {
|
||||
override bool isSpecial() { return special; }
|
||||
|
||||
static if(special)
|
||||
override WrappedNativeObject new_() {
|
||||
return new WrappedNativeObjectImpl(obj._allocate_());
|
||||
}
|
||||
|
||||
Class obj;
|
||||
|
||||
this(Class objIn) {
|
||||
this.obj = objIn;
|
||||
wrappedType = typeid(obj);
|
||||
// wrap the other methods
|
||||
// and wrap members as scriptable properties
|
||||
|
@ -2025,14 +2139,42 @@ WrappedNativeObject wrapNativeObject(Class, bool special = false)(Class obj) if(
|
|||
foreach(memberName; __traits(allMembers, Class)) static if(is(typeof(__traits(getMember, obj, memberName)) type)) {
|
||||
static if(is(type == function)) {
|
||||
foreach(idx, overload; AliasSeq!(__traits(getOverloads, obj, memberName))) static if(.isScriptable!(__traits(getAttributes, overload))()) {
|
||||
auto helper = &__traits(getOverloads, obj, memberName)[idx];
|
||||
_properties[memberName] = (Parameters!helper args) {
|
||||
var gen;
|
||||
gen._function = delegate (var vthis_, var[] vargs) {
|
||||
Parameters!(__traits(getOverloads, Class, memberName)) args;
|
||||
|
||||
foreach(idx, ref arg; args)
|
||||
if(idx < vargs.length)
|
||||
arg = vargs[idx].get!(typeof(arg));
|
||||
|
||||
static if(special) {
|
||||
Class obj;
|
||||
if(vthis_.payloadType() != var.Type.Object) { import std.stdio; writeln("getwno on ", vthis_); }
|
||||
while(vthis_ != null) {
|
||||
obj = vthis_.getWno!Class;
|
||||
if(obj !is null)
|
||||
break;
|
||||
vthis_ = vthis_.prototype;
|
||||
}
|
||||
|
||||
if(obj is null) throw new Exception("null native object");
|
||||
}
|
||||
|
||||
static if(special) {
|
||||
obj._next_devirtualized = true;
|
||||
scope(exit) obj._next_devirtualized = false;
|
||||
}
|
||||
return __traits(getOverloads, obj, memberName)[idx](args);
|
||||
|
||||
var ret;
|
||||
|
||||
static if(!is(typeof(__traits(getOverloads, obj, memberName)[idx](args)) == void))
|
||||
ret = __traits(getOverloads, obj, memberName)[idx](args);
|
||||
else
|
||||
__traits(getOverloads, obj, memberName)[idx](args);
|
||||
|
||||
return ret;
|
||||
};
|
||||
_properties[memberName] = gen;
|
||||
}
|
||||
} else {
|
||||
static if(.isScriptable!(__traits(getAttributes, __traits(getMember, Class, memberName)))())
|
||||
|
@ -2047,7 +2189,9 @@ WrappedNativeObject wrapNativeObject(Class, bool special = false)(Class obj) if(
|
|||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return new WrappedNativeObjectImpl(obj);
|
||||
}
|
||||
|
||||
import std.traits;
|
||||
|
|
444
script.d
444
script.d
|
@ -21,10 +21,6 @@
|
|||
I kinda like the javascript foo`blargh` template literals too.
|
||||
*/
|
||||
|
||||
// FIXME: add switch!!!!!!!!!!!!!!!
|
||||
// FIXME: process super keyword.
|
||||
// FIXME: maybe some kind of splat operator too. choose([1,2,3]...) expands to choose(1,2,3)
|
||||
|
||||
/++
|
||||
A small script interpreter that builds on [arsd.jsvar] to be easily embedded inside and to have has easy
|
||||
two-way interop with the host D program. The script language it implements is based on a hybrid of D and Javascript.
|
||||
|
@ -35,6 +31,17 @@
|
|||
See the [#examples] to quickly get the feel of the script language as well as the interop.
|
||||
|
||||
|
||||
$(TIP
|
||||
A goal of this language is to blur the line between D and script, but
|
||||
in the examples below, which are generated from D unit tests,
|
||||
the non-italics code is D, and the italics is the script. Notice
|
||||
how it is a string passed to the [interpret] function.
|
||||
|
||||
In some smaller, stand-alone code samples, there will be a tag "adrscript"
|
||||
in the upper right of the box to indicate it is script. Otherwise, it
|
||||
is D.
|
||||
)
|
||||
|
||||
Installation_instructions:
|
||||
This script interpreter is contained entirely in two files: jsvar.d and script.d. Download both of them
|
||||
and add them to your project. Then, `import arsd.script;`, declare and populate a `var globals = var.emptyObject;`,
|
||||
|
@ -71,8 +78,10 @@
|
|||
* try/catch/finally/throw
|
||||
You can use try as an expression without any following catch to return the exception:
|
||||
|
||||
```adrscript
|
||||
var a = try throw "exception";; // the double ; is because one closes the try, the second closes the var
|
||||
// a is now the thrown exception
|
||||
```
|
||||
* for/while/foreach
|
||||
* D style operators: +-/* on all numeric types, ~ on strings and arrays, |&^ on integers.
|
||||
Operators can coerce types as needed: 10 ~ "hey" == "10hey". 10 + "3" == 13.
|
||||
|
@ -81,17 +90,21 @@
|
|||
|
||||
So you can do some type coercion like this:
|
||||
|
||||
```adrscript
|
||||
a = a|0; // forces to int
|
||||
a = "" ~ a; // forces to string
|
||||
a = a+0.0; // coerces to float
|
||||
```
|
||||
|
||||
Though casting is probably better.
|
||||
* Type coercion via cast, similarly to D.
|
||||
```adrscript
|
||||
var a = "12";
|
||||
a.typeof == "String";
|
||||
a = cast(int) a;
|
||||
a.typeof == "Integral";
|
||||
a == 12;
|
||||
```
|
||||
|
||||
Supported types for casting to: int/long (both actually an alias for long, because of how var works), float/double/real, string, char/dchar (these return *integral* types), and arrays, int[], string[], and float[].
|
||||
|
||||
|
@ -113,8 +126,9 @@
|
|||
Variable names that start with __ are reserved and you shouldn't use them.
|
||||
* int, float, string, array, bool, and json!q{} literals
|
||||
* var.prototype, var.typeof. prototype works more like Mozilla's __proto__ than standard javascript prototype.
|
||||
* the |> pipeline operator
|
||||
* the `|>` pipeline operator
|
||||
* classes:
|
||||
```adrscript
|
||||
// inheritance works
|
||||
class Foo : bar {
|
||||
// constructors, D style
|
||||
|
@ -138,6 +152,7 @@
|
|||
var foo = new Foo(12);
|
||||
|
||||
foo.newFunc = function() { this.derived = 0; }; // this is ok too, and scoping, including 'this', works like in Javascript
|
||||
```
|
||||
|
||||
You can also use 'new' on another object to get a copy of it.
|
||||
* return, break, continue, but currently cannot do labeled breaks and continues
|
||||
|
@ -171,12 +186,16 @@
|
|||
)
|
||||
|
||||
|
||||
FIXME:
|
||||
* make sure superclass ctors are called
|
||||
Todo_list:
|
||||
|
||||
I also have a wishlist here that I may do in the future, but don't expect them any time soon.
|
||||
|
||||
FIXME: maybe some kind of splat operator too. choose([1,2,3]...) expands to choose(1,2,3)
|
||||
|
||||
make sure superclass ctors are called
|
||||
|
||||
FIXME: prettier stack trace when sent to D
|
||||
|
||||
FIXME: interpolated string: "$foo" or "#{expr}" or something.
|
||||
FIXME: support more escape things in strings like \n, \t etc.
|
||||
|
||||
FIXME: add easy to use premade packages for the global object.
|
||||
|
@ -185,15 +204,22 @@
|
|||
|
||||
FIXME: the debugger statement from javascript might be cool to throw in too.
|
||||
|
||||
FIXME: add continuations or something too
|
||||
FIXME: add continuations or something too - actually doing it with fibers works pretty well
|
||||
|
||||
FIXME: Also ability to get source code for function something so you can mixin.
|
||||
FIXME: add COM support on Windows
|
||||
|
||||
FIXME: add COM support on Windows ????
|
||||
|
||||
|
||||
Might be nice:
|
||||
varargs
|
||||
lambdas - maybe without function keyword and the x => foo syntax from D.
|
||||
|
||||
|
||||
History:
|
||||
April 26, 2020: added `switch`, fixed precedence bug, fixed doc issues and added some unittests
|
||||
|
||||
Started writing it in July 2013. Yes, a basic precedence issue was there for almost SEVEN YEARS. You can use this as a toy but please don't use it for anything too serious, it really is very poorly written and not intelligently designed at all.
|
||||
+/
|
||||
module arsd.script;
|
||||
|
||||
|
@ -288,6 +314,113 @@ unittest {
|
|||
}, globals);
|
||||
}
|
||||
|
||||
/++
|
||||
$(H3 Classes demo)
|
||||
|
||||
See also: [arsd.jsvar.subclassable] for more interop with D classes.
|
||||
+/
|
||||
unittest {
|
||||
var globals = var.emptyObject;
|
||||
interpret(q{
|
||||
class Base {
|
||||
function foo() { return "Base"; }
|
||||
function set() { this.a = 10; }
|
||||
function get() { return this.a; } // this MUST be used for instance variables though as they do not exist in static lookup
|
||||
function test() { return foo(); } // I did NOT use `this` here which means it does STATIC lookup!
|
||||
// kinda like mixin templates in D lol.
|
||||
var a = 5;
|
||||
static var b = 10; // static vars are attached to the class specifically
|
||||
}
|
||||
class Child : Base {
|
||||
function foo() {
|
||||
assert(super.foo() == "Base");
|
||||
return "Child";
|
||||
};
|
||||
function set() { this.a = 7; }
|
||||
function get2() { return this.a; }
|
||||
var a = 9;
|
||||
}
|
||||
|
||||
var c = new Child();
|
||||
assert(c.foo() == "Child");
|
||||
|
||||
assert(c.test() == "Base"); // static lookup of methods if you don't use `this`
|
||||
|
||||
/*
|
||||
// these would pass in D, but do NOT pass here because of dynamic variable lookup in script.
|
||||
assert(c.get() == 5);
|
||||
assert(c.get2() == 9);
|
||||
c.set();
|
||||
assert(c.get() == 5); // parent instance is separate
|
||||
assert(c.get2() == 7);
|
||||
*/
|
||||
|
||||
// showing the shared vars now.... I personally prefer the D way but meh, this lang
|
||||
// is an unholy cross of D and Javascript so that means it sucks sometimes.
|
||||
assert(c.get() == c.get2());
|
||||
c.set();
|
||||
assert(c.get2() == 7);
|
||||
assert(c.get() == c.get2());
|
||||
|
||||
// super, on the other hand, must always be looked up statically, or else this
|
||||
// next example with infinite recurse and smash the stack.
|
||||
class Third : Child { }
|
||||
var t = new Third();
|
||||
assert(t.foo() == "Child");
|
||||
}, globals);
|
||||
}
|
||||
|
||||
/++
|
||||
$(H3 Properties from D)
|
||||
|
||||
Note that it is not possible yet to define a property function from the script language.
|
||||
+/
|
||||
unittest {
|
||||
static class Test {
|
||||
// the @scriptable is required to make it accessible
|
||||
@scriptable int a;
|
||||
|
||||
@scriptable @property int ro() { return 30; }
|
||||
|
||||
int _b = 20;
|
||||
@scriptable @property int b() { return _b; }
|
||||
@scriptable @property int b(int val) { return _b = val; }
|
||||
}
|
||||
|
||||
Test test = new Test;
|
||||
|
||||
test.a = 15;
|
||||
|
||||
var globals = var.emptyObject;
|
||||
globals.test = test;
|
||||
// but once it is @scriptable, both read and write works from here:
|
||||
interpret(q{
|
||||
assert(test.a == 15);
|
||||
test.a = 10;
|
||||
assert(test.a == 10);
|
||||
|
||||
assert(test.ro == 30); // @property functions from D wrapped too
|
||||
test.ro = 40;
|
||||
assert(test.ro == 30); // setting it does nothing though
|
||||
|
||||
assert(test.b == 20); // reader still works if read/write available too
|
||||
test.b = 25;
|
||||
assert(test.b == 25); // writer action reflected
|
||||
|
||||
// however other opAssign operators are not implemented correctly on properties at this time so this fails!
|
||||
//test.b *= 2;
|
||||
//assert(test.b == 50);
|
||||
}, globals);
|
||||
|
||||
// and update seen back in D
|
||||
assert(test.a == 10); // on the original native object
|
||||
assert(test.b == 25);
|
||||
|
||||
assert(globals.test.a == 10); // and via the var accessor for member var
|
||||
assert(globals.test.b == 25); // as well as @property func
|
||||
}
|
||||
|
||||
|
||||
public import arsd.jsvar;
|
||||
|
||||
import std.stdio;
|
||||
|
@ -367,8 +500,8 @@ private enum string[] keywords = [
|
|||
"__FILE__", "__LINE__", // these two are special to the lexer
|
||||
"foreach", "json!q{", "default", "finally",
|
||||
"return", "static", "struct", "import", "module", "assert", "switch",
|
||||
"while", "catch", "throw", "scope", "break", "class", "false", "mixin", "macro",
|
||||
// "this" and "super" are treated as just a magic identifier.....
|
||||
"while", "catch", "throw", "scope", "break", "class", "false", "mixin", "macro", "super",
|
||||
// "this" is just treated as just a magic identifier.....
|
||||
"auto", // provided as an alias for var right now, may change later
|
||||
"null", "else", "true", "eval", "goto", "enum", "case", "cast",
|
||||
"var", "for", "try", "new",
|
||||
|
@ -1055,7 +1188,6 @@ class FunctionLiteralExpression : Expression {
|
|||
argumentsScope.prototype = scToUse;
|
||||
|
||||
argumentsScope._getMember("this", false, false) = _this;
|
||||
//argumentsScope._getMember("super", false, false) = _this.prototype.prototype.prototype;
|
||||
argumentsScope._getMember("_arguments", false, false) = args;
|
||||
argumentsScope._getMember("_thisfunc", false, false) = v;
|
||||
|
||||
|
@ -1310,6 +1442,41 @@ class VariableExpression : Expression {
|
|||
}
|
||||
}
|
||||
|
||||
class SuperExpression : Expression {
|
||||
VariableExpression dot;
|
||||
string origDot;
|
||||
this(VariableExpression dot) {
|
||||
if(dot !is null) {
|
||||
origDot = dot.identifier;
|
||||
//dot.identifier = "__super_" ~ dot.identifier; // omg this is so bad
|
||||
}
|
||||
this.dot = dot;
|
||||
}
|
||||
|
||||
override string toString() {
|
||||
if(dot is null)
|
||||
return "super";
|
||||
else
|
||||
return "super." ~ origDot;
|
||||
}
|
||||
|
||||
override InterpretResult interpret(PrototypeObject sc) {
|
||||
var a = sc._getMember("super", true, true);
|
||||
if(a._object is null)
|
||||
throw new Exception("null proto for super");
|
||||
PrototypeObject proto = a._object.prototype;
|
||||
if(proto is null)
|
||||
throw new Exception("no super");
|
||||
//proto = proto.prototype;
|
||||
|
||||
if(dot !is null)
|
||||
a = proto._getMember(dot.identifier, true, true);
|
||||
else
|
||||
a = proto._getMember("__ctor", true, true);
|
||||
return InterpretResult(a, sc);
|
||||
}
|
||||
}
|
||||
|
||||
class DotVarExpression : VariableExpression {
|
||||
Expression e1;
|
||||
VariableExpression e2;
|
||||
|
@ -1349,9 +1516,9 @@ class DotVarExpression : VariableExpression {
|
|||
return *(new var(val.toJson()));
|
||||
}
|
||||
|
||||
if(auto ve = cast(VariableExpression) e1)
|
||||
if(auto ve = cast(VariableExpression) e1) {
|
||||
return this.getVarFrom(sc, ve.getVar(sc, recurse));
|
||||
else if(cast(StringLiteralExpression) e1 && e2.identifier == "interpolate") {
|
||||
} else if(cast(StringLiteralExpression) e1 && e2.identifier == "interpolate") {
|
||||
auto se = cast(StringLiteralExpression) e1;
|
||||
var* functor = new var;
|
||||
//if(!se.allowInterpolation)
|
||||
|
@ -1379,6 +1546,10 @@ class DotVarExpression : VariableExpression {
|
|||
override ref var getVarFrom(PrototypeObject sc, ref var v) {
|
||||
return e2.getVarFrom(sc, v);
|
||||
}
|
||||
|
||||
override string toInterpretedString(PrototypeObject sc) {
|
||||
return getVar(sc).get!string;
|
||||
}
|
||||
}
|
||||
|
||||
class IndexExpression : VariableExpression {
|
||||
|
@ -1525,6 +1696,143 @@ class ScopeExpression : Expression {
|
|||
}
|
||||
}
|
||||
|
||||
class SwitchExpression : Expression {
|
||||
Expression expr;
|
||||
CaseExpression[] cases;
|
||||
CaseExpression default_;
|
||||
|
||||
override InterpretResult interpret(PrototypeObject sc) {
|
||||
auto e = expr.interpret(sc);
|
||||
|
||||
bool hitAny;
|
||||
bool fallingThrough;
|
||||
bool secondRun;
|
||||
|
||||
var last;
|
||||
|
||||
again:
|
||||
foreach(c; cases) {
|
||||
if(!secondRun && !fallingThrough && c is default_) continue;
|
||||
if(fallingThrough || (secondRun && c is default_) || c.condition.interpret(sc) == e) {
|
||||
fallingThrough = false;
|
||||
if(!secondRun)
|
||||
hitAny = true;
|
||||
InterpretResult ret;
|
||||
expr_loop: foreach(exp; c.expressions) {
|
||||
ret = exp.interpret(sc);
|
||||
with(InterpretResult.FlowControl)
|
||||
final switch(ret.flowControl) {
|
||||
case Normal:
|
||||
last = ret.value;
|
||||
break;
|
||||
case Return:
|
||||
case Goto:
|
||||
return ret;
|
||||
case Continue:
|
||||
fallingThrough = true;
|
||||
break expr_loop;
|
||||
case Break:
|
||||
return InterpretResult(last, sc);
|
||||
}
|
||||
}
|
||||
|
||||
if(!fallingThrough)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!hitAny && !secondRun) {
|
||||
secondRun = true;
|
||||
goto again;
|
||||
}
|
||||
|
||||
return InterpretResult(last, sc);
|
||||
}
|
||||
}
|
||||
|
||||
class CaseExpression : Expression {
|
||||
this(Expression condition) {
|
||||
this.condition = condition;
|
||||
}
|
||||
Expression condition;
|
||||
Expression[] expressions;
|
||||
|
||||
override string toString() {
|
||||
string code;
|
||||
if(condition is null)
|
||||
code = "default:";
|
||||
else
|
||||
code = "case " ~ condition.toString() ~ ":";
|
||||
|
||||
foreach(expr; expressions)
|
||||
code ~= "\n" ~ expr.toString() ~ ";";
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
override InterpretResult interpret(PrototypeObject sc) {
|
||||
// I did this inline up in the SwitchExpression above. maybe insane?!
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
unittest {
|
||||
interpret(q{
|
||||
var a = 10;
|
||||
// case and break should work
|
||||
var brk;
|
||||
|
||||
// var brk = switch doesn't parse, but this will.....
|
||||
// (I kinda went everything is an expression but not all the way. this code SUX.)
|
||||
brk = switch(a) {
|
||||
case 10:
|
||||
a = 30;
|
||||
break;
|
||||
case 30:
|
||||
a = 40;
|
||||
break;
|
||||
default:
|
||||
a = 0;
|
||||
}
|
||||
|
||||
assert(a == 30);
|
||||
assert(brk == 30); // value of switch set to last expression evaled inside
|
||||
|
||||
// so should default
|
||||
switch(a) {
|
||||
case 20:
|
||||
a = 40;
|
||||
break;
|
||||
default:
|
||||
a = 40;
|
||||
}
|
||||
|
||||
assert(a == 40);
|
||||
|
||||
switch(a) {
|
||||
case 40:
|
||||
a = 50;
|
||||
case 60: // no implicit fallthrough in this lang...
|
||||
a = 60;
|
||||
}
|
||||
|
||||
assert(a == 50);
|
||||
|
||||
var ret;
|
||||
|
||||
ret = switch(a) {
|
||||
case 50:
|
||||
a = 60;
|
||||
continue; // request fallthrough. D uses "goto case", but I haven't implemented any goto yet so continue is best fit
|
||||
case 90:
|
||||
a = 70;
|
||||
}
|
||||
|
||||
assert(a == 70); // the explicit `continue` requests fallthrough behavior
|
||||
assert(ret == 70);
|
||||
});
|
||||
}
|
||||
|
||||
class ForeachExpression : Expression {
|
||||
VariableDeclaration decl;
|
||||
Expression subject;
|
||||
|
@ -1750,7 +2058,7 @@ class NewExpression : Expression {
|
|||
args ~= arg.interpret(sc).value;
|
||||
|
||||
var original = what.interpret(sc).value;
|
||||
var n = original._copy;
|
||||
var n = original._copy_new;
|
||||
if(n.payloadType() == var.Type.Object) {
|
||||
var ctor = original.prototype ? original.prototype._getOwnProperty("__ctor") : var(null);
|
||||
if(ctor)
|
||||
|
@ -1885,6 +2193,10 @@ class CallExpression : Expression {
|
|||
this.func = func;
|
||||
}
|
||||
|
||||
override string toInterpretedString(PrototypeObject sc) {
|
||||
return interpret(sc).value.get!string;
|
||||
}
|
||||
|
||||
override InterpretResult interpret(PrototypeObject sc) {
|
||||
if(auto asrt = cast(AssertKeyword) func) {
|
||||
auto assertExpression = arguments[0];
|
||||
|
@ -1926,6 +2238,10 @@ class CallExpression : Expression {
|
|||
_this = dve.e1.interpret(sc).value;
|
||||
} else if(auto ide = cast(IndexExpression) func) {
|
||||
_this = ide.interpret(sc).value;
|
||||
} else if(auto se = cast(SuperExpression) func) {
|
||||
// super things are passed this object despite looking things up on the prototype
|
||||
// so it calls the correct instance
|
||||
_this = sc._getMember("this", true, true);
|
||||
}
|
||||
|
||||
return InterpretResult(f.apply(_this, args), sc);
|
||||
|
@ -1967,7 +2283,17 @@ Expression parsePart(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
|
|||
auto token = tokens.front;
|
||||
|
||||
Expression e;
|
||||
if(token.type == ScriptToken.Type.identifier)
|
||||
|
||||
if(token.str == "super") {
|
||||
tokens.popFront();
|
||||
VariableExpression dot;
|
||||
if(!tokens.empty && tokens.front.str == ".") {
|
||||
tokens.popFront();
|
||||
dot = parseVariableName(tokens);
|
||||
}
|
||||
e = new SuperExpression(dot);
|
||||
}
|
||||
else if(token.type == ScriptToken.Type.identifier)
|
||||
e = parseVariableName(tokens);
|
||||
else if(token.type == ScriptToken.Type.symbol && (token.str == "-" || token.str == "+")) {
|
||||
auto op = token.str;
|
||||
|
@ -2391,6 +2717,11 @@ Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens, bool
|
|||
new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("prototype")),
|
||||
new DotVarExpression(new VariableExpression(inheritFrom.str), new VariableExpression("prototype")));
|
||||
|
||||
expressions ~= new AssignExpression(
|
||||
new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("super")),
|
||||
new VariableExpression(inheritFrom.str)
|
||||
);
|
||||
|
||||
// and copying the instance initializer from the parent
|
||||
expressions ~= new ShallowCopyExpression(new VariableExpression("__obj"), new VariableExpression(inheritFrom.str));
|
||||
}
|
||||
|
@ -2486,6 +2817,51 @@ Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens, bool
|
|||
e.ifFalse = parseExpression(tokens);
|
||||
}
|
||||
ret = e;
|
||||
} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "switch")) {
|
||||
tokens.popFront();
|
||||
auto e = new SwitchExpression();
|
||||
tokens.requireNextToken(ScriptToken.Type.symbol, "(");
|
||||
e.expr = parseExpression(tokens);
|
||||
tokens.requireNextToken(ScriptToken.Type.symbol, ")");
|
||||
|
||||
tokens.requireNextToken(ScriptToken.Type.symbol, "{");
|
||||
|
||||
while(!tokens.peekNextToken(ScriptToken.Type.symbol, "}")) {
|
||||
|
||||
if(tokens.peekNextToken(ScriptToken.Type.keyword, "case")) {
|
||||
auto start = tokens.front;
|
||||
tokens.popFront();
|
||||
auto c = new CaseExpression(parseExpression(tokens));
|
||||
e.cases ~= c;
|
||||
tokens.requireNextToken(ScriptToken.Type.symbol, ":");
|
||||
|
||||
while(!tokens.peekNextToken(ScriptToken.Type.keyword, "default") && !tokens.peekNextToken(ScriptToken.Type.keyword, "case") && !tokens.peekNextToken(ScriptToken.Type.symbol, "}")) {
|
||||
c.expressions ~= parseStatement(tokens);
|
||||
while(tokens.peekNextToken(ScriptToken.Type.symbol, ";"))
|
||||
tokens.popFront();
|
||||
}
|
||||
} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "default")) {
|
||||
tokens.popFront();
|
||||
tokens.requireNextToken(ScriptToken.Type.symbol, ":");
|
||||
|
||||
auto c = new CaseExpression(null);
|
||||
|
||||
while(!tokens.peekNextToken(ScriptToken.Type.keyword, "case") && !tokens.peekNextToken(ScriptToken.Type.symbol, "}")) {
|
||||
c.expressions ~= parseStatement(tokens);
|
||||
while(tokens.peekNextToken(ScriptToken.Type.symbol, ";"))
|
||||
tokens.popFront();
|
||||
}
|
||||
|
||||
e.cases ~= c;
|
||||
e.default_ = c;
|
||||
} else throw new ScriptCompileException("A switch statement must consists of cases and a default, nothing else ", tokens.front.lineNumber);
|
||||
}
|
||||
|
||||
tokens.requireNextToken(ScriptToken.Type.symbol, "}");
|
||||
expectedEnd = "";
|
||||
|
||||
ret = e;
|
||||
|
||||
} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "foreach")) {
|
||||
tokens.popFront();
|
||||
auto e = new ForeachExpression();
|
||||
|
@ -2741,11 +3117,14 @@ Expression parseStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, strin
|
|||
case "class":
|
||||
case "new":
|
||||
|
||||
case "super":
|
||||
|
||||
// flow control
|
||||
case "if":
|
||||
case "while":
|
||||
case "for":
|
||||
case "foreach":
|
||||
case "switch":
|
||||
|
||||
// exceptions
|
||||
case "try":
|
||||
|
@ -2953,15 +3332,38 @@ var interpretFile(File file, var globals) {
|
|||
(globals.payloadType() == var.Type.Object && globals._payload._object !is null) ? globals._payload._object : new PrototypeObject());
|
||||
}
|
||||
|
||||
///
|
||||
void repl(var globals) {
|
||||
import std.stdio;
|
||||
/// Enhanced repl uses arsd.terminal for better ux. Added April 26, 2020. Default just uses std.stdio.
|
||||
void repl(bool enhanced = false)(var globals) {
|
||||
static if(enhanced) {
|
||||
import arsd.terminal;
|
||||
Terminal terminal = Terminal(ConsoleOutputMode.linear);
|
||||
auto lines() {
|
||||
struct Range {
|
||||
string line;
|
||||
string front() { return line; }
|
||||
bool empty() { return line is null; }
|
||||
void popFront() { line = terminal.getline(": "); terminal.writeln(); }
|
||||
}
|
||||
Range r;
|
||||
r.popFront();
|
||||
return r;
|
||||
|
||||
}
|
||||
|
||||
void writeln(T...)(T t) {
|
||||
terminal.writeln(t);
|
||||
terminal.flush();
|
||||
}
|
||||
} else {
|
||||
import std.stdio;
|
||||
auto lines() { return stdin.byLine; }
|
||||
}
|
||||
import std.algorithm;
|
||||
auto variables = (globals.payloadType() == var.Type.Object && globals._payload._object !is null) ? globals._payload._object : new PrototypeObject();
|
||||
|
||||
// we chain to ensure the priming popFront succeeds so we don't throw here
|
||||
auto tokens = lexScript(
|
||||
chain(["var __skipme = 0;"], map!((a) => a.idup)(stdin.byLine))
|
||||
chain(["var __skipme = 0;"], map!((a) => a.idup)(lines))
|
||||
, "stdin");
|
||||
auto expressions = parseScript(tokens);
|
||||
|
||||
|
|
|
@ -13445,6 +13445,11 @@ extern(System) nothrow @nogc {
|
|||
void glClearColor(float, float, float, float);
|
||||
|
||||
|
||||
void glPixelStorei(uint, int);
|
||||
|
||||
enum GL_RED = 0x1903;
|
||||
enum GL_ALPHA = 0x1906;
|
||||
enum GL_UNPACK_ALIGNMENT = 0x0CF5;
|
||||
|
||||
void glGenTextures(uint, uint*);
|
||||
void glBindTexture(int, int);
|
||||
|
|
315
ttf.d
315
ttf.d
|
@ -138,6 +138,302 @@ struct TtfFont {
|
|||
// ~this() {}
|
||||
}
|
||||
|
||||
/// Version of OpenGL you want it to use. Currently only one option.
|
||||
enum OpenGlFontGLVersion {
|
||||
old /// old style glBegin/glEnd stuff
|
||||
}
|
||||
|
||||
/+
|
||||
This is mostly there if you want to draw different pieces together in
|
||||
different colors or across different boxes (see what text didn't fit, etc.).
|
||||
|
||||
Used only with [OpenGlLimitedFont] right now.
|
||||
+/
|
||||
struct DrawingTextContext {
|
||||
const(char)[] text; /// remaining text
|
||||
float x; /// current position of the baseline
|
||||
float y; /// ditto
|
||||
|
||||
const int left; /// bounding box, if applicable
|
||||
const int top; /// ditto
|
||||
const int right; /// ditto
|
||||
const int bottom; /// ditto
|
||||
}
|
||||
|
||||
/++
|
||||
Note that the constructor calls OpenGL functions and thus this must be called AFTER
|
||||
the context creation, e.g. on simpledisplay's window first visible delegate.
|
||||
|
||||
Any text with characters outside the range you bake in the constructor are simply
|
||||
ignored - that's why it is called "limited" font. The [TtfFont] struct can generate
|
||||
any string on-demand which is more flexible, and even faster for strings repeated
|
||||
frequently, but slower for frequently-changing or one-off strings. That's what this
|
||||
class is made for.
|
||||
|
||||
History:
|
||||
Added April 24, 2020
|
||||
+/
|
||||
class OpenGlLimitedFont(OpenGlFontGLVersion ver = OpenGlFontGLVersion.old) {
|
||||
|
||||
import arsd.simpledisplay;
|
||||
|
||||
static private int nextPowerOfTwo(int v) {
|
||||
v--;
|
||||
v |= v >> 1;
|
||||
v |= v >> 2;
|
||||
v |= v >> 4;
|
||||
v |= v >> 8;
|
||||
v |= v >> 16;
|
||||
v++;
|
||||
return v;
|
||||
}
|
||||
|
||||
uint _tex;
|
||||
stbtt_bakedchar[] charInfo;
|
||||
|
||||
import arsd.color;
|
||||
|
||||
/++
|
||||
|
||||
Tip: if color == Color.transparent, it will not actually attempt to draw to OpenGL. You can use this
|
||||
to help plan pagination inside the bounding box.
|
||||
|
||||
+/
|
||||
public final DrawingTextContext drawString(int x, int y, in char[] text, Color color = Color.white, Rectangle boundingBox = Rectangle.init) {
|
||||
if(boundingBox == Rectangle.init) {
|
||||
// if you hit a newline, at least keep it aligned here if something wasn't
|
||||
// explicitly given.
|
||||
boundingBox.left = x;
|
||||
boundingBox.top = y;
|
||||
boundingBox.right = int.max;
|
||||
boundingBox.bottom = int.max;
|
||||
}
|
||||
DrawingTextContext dtc = DrawingTextContext(text, x, y, boundingBox.tupleof);
|
||||
drawString(dtc, color);
|
||||
return dtc;
|
||||
}
|
||||
|
||||
/++
|
||||
It won't attempt to draw partial characters if it butts up against the bounding box, unless
|
||||
word wrap was impossible. Use clipping if you need to cover that up and guarantee it never goes
|
||||
outside the bounding box ever.
|
||||
|
||||
+/
|
||||
public final void drawString(ref DrawingTextContext context, Color color = Color.white) {
|
||||
bool actuallyDraw = color != Color.transparent;
|
||||
|
||||
if(actuallyDraw) {
|
||||
glBindTexture(GL_TEXTURE_2D, _tex);
|
||||
|
||||
glColor4f(cast(float)color.r/255.0, cast(float)color.g/255.0, cast(float)color.b/255.0, cast(float)color.a / 255.0);
|
||||
}
|
||||
|
||||
bool newWord = true;
|
||||
bool atStartOfLine = true;
|
||||
float currentWordLength;
|
||||
int currentWordCharsRemaining;
|
||||
|
||||
void calculateWordInfo() {
|
||||
const(char)[] copy = context.text;
|
||||
currentWordLength = 0.0;
|
||||
currentWordCharsRemaining = 0;
|
||||
|
||||
while(copy.length) {
|
||||
auto ch = copy[0];
|
||||
copy = copy[1 .. $];
|
||||
|
||||
currentWordCharsRemaining++;
|
||||
|
||||
if(ch <= 32)
|
||||
break; // not in a word anymore
|
||||
|
||||
if(ch < firstChar || ch > lastChar)
|
||||
continue;
|
||||
|
||||
const b = charInfo[cast(int) ch - cast(int) firstChar];
|
||||
|
||||
currentWordLength += b.xadvance;
|
||||
}
|
||||
}
|
||||
|
||||
bool newline() {
|
||||
context.x = context.left;
|
||||
context.y += lineHeight;
|
||||
atStartOfLine = true;
|
||||
|
||||
if(context.y + descent > context.bottom)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
while(context.text.length) {
|
||||
if(newWord) {
|
||||
calculateWordInfo();
|
||||
newWord = false;
|
||||
|
||||
if(context.x + currentWordLength > context.right) {
|
||||
if(!newline())
|
||||
break; // ran out of space
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME i should prolly UTF-8 decode....
|
||||
dchar ch = context.text[0];
|
||||
context.text = context.text[1 .. $];
|
||||
|
||||
if(currentWordCharsRemaining) {
|
||||
currentWordCharsRemaining--;
|
||||
|
||||
if(currentWordCharsRemaining == 0)
|
||||
newWord = true;
|
||||
}
|
||||
|
||||
if(ch == '\t') {
|
||||
context.x += 20;
|
||||
continue;
|
||||
}
|
||||
if(ch == '\n') {
|
||||
if(newline())
|
||||
continue;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if(ch < firstChar || ch > lastChar) {
|
||||
if(ch == ' ')
|
||||
context.x += lineHeight / 4; // fake space if not available in formal font (I recommend you do include it though)
|
||||
continue;
|
||||
}
|
||||
|
||||
const b = charInfo[cast(int) ch - cast(int) firstChar];
|
||||
|
||||
int round_x = STBTT_ifloor((context.x + b.xoff) + 0.5f);
|
||||
int round_y = STBTT_ifloor((context.y + b.yoff) + 0.5f);
|
||||
|
||||
// box to draw on the screen
|
||||
auto x0 = round_x;
|
||||
auto y0 = round_y;
|
||||
auto x1 = round_x + b.x1 - b.x0;
|
||||
auto y1 = round_y + b.y1 - b.y0;
|
||||
|
||||
// is that outside the bounding box we should draw in?
|
||||
// if so on x, wordwrap to the next line. if so on y,
|
||||
// return prematurely and let the user context handle it if needed.
|
||||
|
||||
// box to fetch off the surface
|
||||
auto s0 = b.x0 * ipw;
|
||||
auto t0 = b.y0 * iph;
|
||||
auto s1 = b.x1 * ipw;
|
||||
auto t1 = b.y1 * iph;
|
||||
|
||||
if(actuallyDraw) {
|
||||
glBegin(GL_QUADS);
|
||||
glTexCoord2f(s0, t0); glVertex2i(x0, y0);
|
||||
glTexCoord2f(s1, t0); glVertex2i(x1, y0);
|
||||
glTexCoord2f(s1, t1); glVertex2i(x1, y1);
|
||||
glTexCoord2f(s0, t1); glVertex2i(x0, y1);
|
||||
glEnd();
|
||||
}
|
||||
|
||||
context.x += b.xadvance;
|
||||
}
|
||||
|
||||
if(actuallyDraw)
|
||||
glBindTexture(GL_TEXTURE_2D, 0); // unbind the texture
|
||||
}
|
||||
|
||||
private {
|
||||
const dchar firstChar;
|
||||
const dchar lastChar;
|
||||
const int pw;
|
||||
const int ph;
|
||||
const float ipw;
|
||||
const float iph;
|
||||
const int lineHeight;
|
||||
}
|
||||
|
||||
public const int ascent; /// metrics
|
||||
public const int descent; /// ditto
|
||||
public const int line_gap; /// ditto;
|
||||
|
||||
/++
|
||||
|
||||
+/
|
||||
public this(const ubyte[] ttfData, float fontPixelHeight, dchar firstChar = 32, dchar lastChar = 127) {
|
||||
|
||||
assert(lastChar > firstChar);
|
||||
assert(fontPixelHeight > 0);
|
||||
|
||||
this.firstChar = firstChar;
|
||||
this.lastChar = lastChar;
|
||||
|
||||
int numChars = 1 + cast(int) lastChar - cast(int) firstChar;
|
||||
|
||||
lineHeight = cast(int) (fontPixelHeight + 0.5);
|
||||
|
||||
import std.math;
|
||||
// will most likely be 512x512ish; about 256k likely
|
||||
int height = cast(int) (fontPixelHeight + 1) * cast(int) (sqrt(cast(float) numChars) + 1);
|
||||
height = nextPowerOfTwo(height);
|
||||
int width = height;
|
||||
|
||||
this.pw = width;
|
||||
this.ph = height;
|
||||
|
||||
ipw = 1.0f / pw;
|
||||
iph = 1.0f / ph;
|
||||
|
||||
int len = width * height;
|
||||
|
||||
//import std.stdio; writeln(len);
|
||||
|
||||
import core.stdc.stdlib;
|
||||
ubyte[] buffer = (cast(ubyte*) malloc(len))[0 .. len];
|
||||
if(buffer is null) assert(0);
|
||||
scope(exit) free(buffer.ptr);
|
||||
|
||||
charInfo.length = numChars;
|
||||
|
||||
int ascent, descent, line_gap;
|
||||
|
||||
if(stbtt_BakeFontBitmap(
|
||||
ttfData.ptr, 0,
|
||||
fontPixelHeight,
|
||||
buffer.ptr, width, height,
|
||||
cast(int) firstChar, numChars,
|
||||
charInfo.ptr,
|
||||
&ascent, &descent, &line_gap
|
||||
) == 0)
|
||||
throw new Exception("bake font didn't work");
|
||||
|
||||
this.ascent = ascent;
|
||||
this.descent = descent;
|
||||
this.line_gap = line_gap;
|
||||
|
||||
glGenTextures(1, &_tex);
|
||||
glBindTexture(GL_TEXTURE_2D, _tex);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
GL_ALPHA,
|
||||
width,
|
||||
height,
|
||||
0,
|
||||
GL_ALPHA,
|
||||
GL_UNSIGNED_BYTE,
|
||||
buffer.ptr);
|
||||
|
||||
assert(!glGetError());
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// test program
|
||||
/+
|
||||
|
@ -401,7 +697,8 @@ STBTT_DEF int stbtt_BakeFontBitmap(const(ubyte)* data, int offset, // font loca
|
|||
float pixel_height, // height of font in pixels
|
||||
ubyte *pixels, int pw, int ph, // bitmap to be filled in
|
||||
int first_char, int num_chars, // characters to bake
|
||||
stbtt_bakedchar *chardata); // you allocate this, it's num_chars long
|
||||
stbtt_bakedchar *chardata, // you allocate this, it's num_chars long
|
||||
int* ascent, int * descent, int* line_gap); // metrics for use later too
|
||||
+/
|
||||
// if return is positive, the first unused row of the bitmap
|
||||
// if return is negative, returns the negative of the number of characters that fit
|
||||
|
@ -3511,7 +3808,9 @@ private int stbtt_BakeFontBitmap_internal(ubyte *data, int offset, // font loca
|
|||
float pixel_height, // height of font in pixels
|
||||
ubyte *pixels, int pw, int ph, // bitmap to be filled in
|
||||
int first_char, int num_chars, // characters to bake
|
||||
stbtt_bakedchar *chardata)
|
||||
stbtt_bakedchar *chardata,
|
||||
int* ascent, int* descent, int* line_gap
|
||||
)
|
||||
{
|
||||
float scale;
|
||||
int x,y,bottom_y, i;
|
||||
|
@ -3525,6 +3824,12 @@ private int stbtt_BakeFontBitmap_internal(ubyte *data, int offset, // font loca
|
|||
|
||||
scale = stbtt_ScaleForPixelHeight(&f, pixel_height);
|
||||
|
||||
stbtt_GetFontVMetrics(&f, ascent, descent, line_gap);
|
||||
|
||||
if(ascent) *ascent = cast(int) (*ascent * scale);
|
||||
if(descent) *descent = cast(int) (*descent * scale);
|
||||
if(line_gap) *line_gap = cast(int) (*line_gap * scale);
|
||||
|
||||
for (i=0; i < num_chars; ++i) {
|
||||
int advance, lsb, x0,y0,x1,y1,gw,gh;
|
||||
int g = stbtt_FindGlyphIndex(&f, first_char + i);
|
||||
|
@ -4599,9 +4904,11 @@ private int stbtt_FindMatchingFont_internal(ubyte *font_collection, char *name_u
|
|||
|
||||
public int stbtt_BakeFontBitmap(const(ubyte)* data, int offset,
|
||||
float pixel_height, ubyte *pixels, int pw, int ph,
|
||||
int first_char, int num_chars, stbtt_bakedchar *chardata)
|
||||
int first_char, int num_chars, stbtt_bakedchar *chardata,
|
||||
int* ascent = null, int* descent = null, int* line_gap = null
|
||||
)
|
||||
{
|
||||
return stbtt_BakeFontBitmap_internal(cast(ubyte *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata);
|
||||
return stbtt_BakeFontBitmap_internal(cast(ubyte *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata, ascent, descent, line_gap);
|
||||
}
|
||||
|
||||
public int stbtt_GetFontOffsetForIndex(const(ubyte)* data, int index)
|
||||
|
|
Loading…
Reference in New Issue