From a72e244331d6bf63746d5d81c85c6216a3efd5ee Mon Sep 17 00:00:00 2001 From: Brandon Jones Date: Fri, 8 Mar 2024 10:13:58 -0800 Subject: [PATCH 1/2] Add an MSDF-based text renering sample This sample has been languishing in a branch for a while because react's handling of asset files prevented it from working correctly. Now that react has been removed from the sample pages, though, it works as expected! Thanks @greggman! --- public/assets/font/ya-hei-ascii-msdf.json | 1 + public/assets/font/ya-hei-ascii.png | Bin 0 -> 107021 bytes sample/textRenderingMsdf/index.html | 27 ++ sample/textRenderingMsdf/main.ts | 310 ++++++++++++++++ sample/textRenderingMsdf/meta.ts | 19 + sample/textRenderingMsdf/msdfText.ts | 421 ++++++++++++++++++++++ sample/textRenderingMsdf/msdfText.wgsl | 81 +++++ src/samples.ts | 2 + 8 files changed, 861 insertions(+) create mode 100644 public/assets/font/ya-hei-ascii-msdf.json create mode 100644 public/assets/font/ya-hei-ascii.png create mode 100644 sample/textRenderingMsdf/index.html create mode 100644 sample/textRenderingMsdf/main.ts create mode 100644 sample/textRenderingMsdf/meta.ts create mode 100644 sample/textRenderingMsdf/msdfText.ts create mode 100644 sample/textRenderingMsdf/msdfText.wgsl diff --git a/public/assets/font/ya-hei-ascii-msdf.json b/public/assets/font/ya-hei-ascii-msdf.json new file mode 100644 index 00000000..2cbc9910 --- /dev/null +++ b/public/assets/font/ya-hei-ascii-msdf.json @@ -0,0 +1 @@ +{"pages":["ya-hei-ascii.png"],"chars":[{"id":124,"index":98,"char":"|","width":8,"height":49,"xoffset":2,"yoffset":1,"xadvance":11,"chnl":15,"x":0,"y":0,"page":0},{"id":106,"index":80,"char":"j","width":16,"height":48,"xoffset":-6,"yoffset":3,"xadvance":11,"chnl":15,"x":0,"y":50,"page":0},{"id":87,"index":61,"char":"W","width":46,"height":36,"xoffset":-1,"yoffset":4,"xadvance":43,"chnl":15,"x":9,"y":0,"page":0},{"id":81,"index":55,"char":"Q","width":35,"height":45,"xoffset":0,"yoffset":4,"xadvance":34,"chnl":15,"x":0,"y":99,"page":0},{"id":36,"index":10,"char":"$","width":22,"height":44,"xoffset":1,"yoffset":0,"xadvance":25,"chnl":15,"x":17,"y":37,"page":0},{"id":40,"index":14,"char":"(","width":14,"height":43,"xoffset":1,"yoffset":4,"xadvance":14,"chnl":15,"x":0,"y":145,"page":0},{"id":41,"index":15,"char":")","width":15,"height":43,"xoffset":-2,"yoffset":4,"xadvance":14,"chnl":15,"x":0,"y":189,"page":0},{"id":91,"index":65,"char":"[","width":12,"height":43,"xoffset":2,"yoffset":4,"xadvance":14,"chnl":15,"x":15,"y":145,"page":0},{"id":93,"index":67,"char":"]","width":12,"height":43,"xoffset":-1,"yoffset":4,"xadvance":14,"chnl":15,"x":0,"y":233,"page":0},{"id":123,"index":97,"char":"{","width":15,"height":43,"xoffset":0,"yoffset":4,"xadvance":14,"chnl":15,"x":0,"y":277,"page":0},{"id":125,"index":99,"char":"}","width":15,"height":43,"xoffset":-1,"yoffset":4,"xadvance":14,"chnl":15,"x":13,"y":233,"page":0},{"id":47,"index":21,"char":"/","width":23,"height":41,"xoffset":-3,"yoffset":4,"xadvance":18,"chnl":15,"x":16,"y":189,"page":0},{"id":92,"index":66,"char":"\\","width":23,"height":41,"xoffset":-3,"yoffset":4,"xadvance":17,"chnl":15,"x":28,"y":145,"page":0},{"id":12385,"index":28668,"char":"ち","width":33,"height":41,"xoffset":3,"yoffset":2,"xadvance":42,"chnl":15,"x":36,"y":82,"page":0},{"id":64,"index":38,"char":"@","width":40,"height":40,"xoffset":2,"yoffset":4,"xadvance":43,"chnl":15,"x":40,"y":37,"page":0},{"id":12435,"index":28718,"char":"ん","width":39,"height":38,"xoffset":1,"yoffset":3,"xadvance":42,"chnl":15,"x":0,"y":321,"page":0},{"id":37,"index":11,"char":"%","width":38,"height":37,"xoffset":0,"yoffset":4,"xadvance":37,"chnl":15,"x":16,"y":277,"page":0},{"id":98,"index":72,"char":"b","width":25,"height":38,"xoffset":2,"yoffset":2,"xadvance":27,"chnl":15,"x":29,"y":231,"page":0},{"id":100,"index":74,"char":"d","width":25,"height":38,"xoffset":0,"yoffset":2,"xadvance":27,"chnl":15,"x":40,"y":187,"page":0},{"id":102,"index":76,"char":"f","width":18,"height":38,"xoffset":-1,"yoffset":2,"xadvance":15,"chnl":15,"x":52,"y":124,"page":0},{"id":103,"index":77,"char":"g","width":25,"height":38,"xoffset":0,"yoffset":13,"xadvance":27,"chnl":15,"x":70,"y":78,"page":0},{"id":104,"index":78,"char":"h","width":23,"height":38,"xoffset":2,"yoffset":2,"xadvance":26,"chnl":15,"x":81,"y":0,"page":0},{"id":107,"index":81,"char":"k","width":23,"height":38,"xoffset":2,"yoffset":2,"xadvance":23,"chnl":15,"x":81,"y":39,"page":0},{"id":108,"index":82,"char":"l","width":8,"height":38,"xoffset":2,"yoffset":2,"xadvance":11,"chnl":15,"x":0,"y":360,"page":0},{"id":112,"index":86,"char":"p","width":25,"height":38,"xoffset":2,"yoffset":13,"xadvance":27,"chnl":15,"x":0,"y":399,"page":0},{"id":113,"index":87,"char":"q","width":25,"height":38,"xoffset":0,"yoffset":13,"xadvance":27,"chnl":15,"x":9,"y":360,"page":0},{"id":12399,"index":28682,"char":"は","width":38,"height":38,"xoffset":3,"yoffset":4,"xadvance":42,"chnl":15,"x":0,"y":438,"page":0},{"id":38,"index":12,"char":"&","width":37,"height":37,"xoffset":1,"yoffset":4,"xadvance":37,"chnl":15,"x":26,"y":399,"page":0},{"id":48,"index":22,"char":"0","width":25,"height":37,"xoffset":0,"yoffset":4,"xadvance":25,"chnl":15,"x":35,"y":360,"page":0},{"id":51,"index":25,"char":"3","width":23,"height":37,"xoffset":1,"yoffset":4,"xadvance":25,"chnl":15,"x":40,"y":315,"page":0},{"id":54,"index":28,"char":"6","width":24,"height":37,"xoffset":0,"yoffset":4,"xadvance":25,"chnl":15,"x":61,"y":353,"page":0},{"id":56,"index":30,"char":"8","width":24,"height":37,"xoffset":0,"yoffset":4,"xadvance":25,"chnl":15,"x":39,"y":437,"page":0},{"id":57,"index":31,"char":"9","width":24,"height":37,"xoffset":0,"yoffset":4,"xadvance":25,"chnl":15,"x":39,"y":475,"page":0},{"id":63,"index":37,"char":"?","width":19,"height":37,"xoffset":1,"yoffset":4,"xadvance":20,"chnl":15,"x":55,"y":226,"page":0},{"id":67,"index":41,"char":"C","width":28,"height":37,"xoffset":0,"yoffset":4,"xadvance":28,"chnl":15,"x":55,"y":264,"page":0},{"id":71,"index":45,"char":"G","width":30,"height":37,"xoffset":0,"yoffset":4,"xadvance":31,"chnl":15,"x":64,"y":302,"page":0},{"id":77,"index":51,"char":"M","width":37,"height":36,"xoffset":2,"yoffset":4,"xadvance":41,"chnl":15,"x":66,"y":163,"page":0},{"id":79,"index":53,"char":"O","width":34,"height":37,"xoffset":0,"yoffset":4,"xadvance":34,"chnl":15,"x":71,"y":117,"page":0},{"id":83,"index":57,"char":"S","width":24,"height":37,"xoffset":1,"yoffset":4,"xadvance":24,"chnl":15,"x":96,"y":78,"page":0},{"id":105,"index":79,"char":"i","width":9,"height":37,"xoffset":1,"yoffset":3,"xadvance":11,"chnl":15,"x":75,"y":200,"page":0},{"id":109,"index":83,"char":"m","width":37,"height":27,"xoffset":2,"yoffset":13,"xadvance":39,"chnl":15,"x":0,"y":477,"page":0},{"id":121,"index":95,"char":"y","width":26,"height":37,"xoffset":-2,"yoffset":13,"xadvance":22,"chnl":15,"x":84,"y":238,"page":0},{"id":12395,"index":28678,"char":"に","width":37,"height":37,"xoffset":3,"yoffset":4,"xadvance":42,"chnl":15,"x":85,"y":200,"page":0},{"id":33,"index":7,"char":"!","width":9,"height":36,"xoffset":2,"yoffset":4,"xadvance":13,"chnl":15,"x":56,"y":0,"page":0},{"id":49,"index":23,"char":"1","width":22,"height":36,"xoffset":2,"yoffset":4,"xadvance":25,"chnl":15,"x":104,"y":155,"page":0},{"id":50,"index":24,"char":"2","width":24,"height":36,"xoffset":0,"yoffset":4,"xadvance":25,"chnl":15,"x":106,"y":116,"page":0},{"id":52,"index":26,"char":"4","width":27,"height":36,"xoffset":-2,"yoffset":4,"xadvance":25,"chnl":15,"x":105,"y":0,"page":0},{"id":53,"index":27,"char":"5","width":22,"height":36,"xoffset":2,"yoffset":4,"xadvance":25,"chnl":15,"x":105,"y":37,"page":0},{"id":55,"index":29,"char":"7","width":25,"height":36,"xoffset":0,"yoffset":4,"xadvance":25,"chnl":15,"x":121,"y":74,"page":0},{"id":65,"index":39,"char":"A","width":33,"height":36,"xoffset":-2,"yoffset":4,"xadvance":30,"chnl":15,"x":128,"y":37,"page":0},{"id":66,"index":40,"char":"B","width":24,"height":36,"xoffset":2,"yoffset":4,"xadvance":26,"chnl":15,"x":133,"y":0,"page":0},{"id":68,"index":42,"char":"D","width":30,"height":36,"xoffset":2,"yoffset":4,"xadvance":32,"chnl":15,"x":158,"y":0,"page":0},{"id":69,"index":43,"char":"E","width":21,"height":36,"xoffset":2,"yoffset":4,"xadvance":23,"chnl":15,"x":64,"y":391,"page":0},{"id":70,"index":44,"char":"F","width":20,"height":36,"xoffset":2,"yoffset":4,"xadvance":22,"chnl":15,"x":64,"y":428,"page":0},{"id":72,"index":46,"char":"H","width":28,"height":36,"xoffset":2,"yoffset":4,"xadvance":32,"chnl":15,"x":64,"y":465,"page":0},{"id":73,"index":47,"char":"I","width":14,"height":36,"xoffset":-1,"yoffset":4,"xadvance":12,"chnl":15,"x":66,"y":0,"page":0},{"id":74,"index":48,"char":"J","width":16,"height":36,"xoffset":-2,"yoffset":4,"xadvance":17,"chnl":15,"x":85,"y":428,"page":0},{"id":75,"index":49,"char":"K","width":27,"height":36,"xoffset":2,"yoffset":4,"xadvance":27,"chnl":15,"x":93,"y":465,"page":0},{"id":76,"index":50,"char":"L","width":21,"height":36,"xoffset":2,"yoffset":4,"xadvance":22,"chnl":15,"x":86,"y":340,"page":0},{"id":78,"index":52,"char":"N","width":30,"height":36,"xoffset":2,"yoffset":4,"xadvance":34,"chnl":15,"x":86,"y":377,"page":0},{"id":80,"index":54,"char":"P","width":24,"height":36,"xoffset":2,"yoffset":4,"xadvance":26,"chnl":15,"x":102,"y":414,"page":0},{"id":82,"index":56,"char":"R","width":27,"height":36,"xoffset":2,"yoffset":4,"xadvance":27,"chnl":15,"x":121,"y":451,"page":0},{"id":84,"index":58,"char":"T","width":26,"height":36,"xoffset":-1,"yoffset":4,"xadvance":24,"chnl":15,"x":95,"y":276,"page":0},{"id":85,"index":59,"char":"U","width":28,"height":36,"xoffset":2,"yoffset":4,"xadvance":31,"chnl":15,"x":111,"y":238,"page":0},{"id":86,"index":60,"char":"V","width":32,"height":36,"xoffset":-2,"yoffset":4,"xadvance":28,"chnl":15,"x":123,"y":192,"page":0},{"id":88,"index":62,"char":"X","width":30,"height":36,"xoffset":-1,"yoffset":4,"xadvance":27,"chnl":15,"x":127,"y":153,"page":0},{"id":89,"index":63,"char":"Y","width":29,"height":36,"xoffset":-2,"yoffset":4,"xadvance":25,"chnl":15,"x":131,"y":111,"page":0},{"id":90,"index":64,"char":"Z","width":28,"height":36,"xoffset":-1,"yoffset":4,"xadvance":26,"chnl":15,"x":147,"y":74,"page":0},{"id":119,"index":93,"char":"w","width":36,"height":27,"xoffset":-1,"yoffset":13,"xadvance":33,"chnl":15,"x":162,"y":37,"page":0},{"id":116,"index":90,"char":"t","width":18,"height":34,"xoffset":-1,"yoffset":7,"xadvance":16,"chnl":15,"x":189,"y":0,"page":0},{"id":35,"index":9,"char":"#","width":29,"height":33,"xoffset":-1,"yoffset":4,"xadvance":27,"chnl":15,"x":108,"y":313,"page":0},{"id":59,"index":33,"char":";","width":11,"height":33,"xoffset":-1,"yoffset":13,"xadvance":10,"chnl":15,"x":122,"y":275,"page":0},{"id":12371,"index":28654,"char":"こ","width":32,"height":31,"xoffset":5,"yoffset":8,"xadvance":42,"chnl":15,"x":134,"y":275,"page":0},{"id":58,"index":32,"char":":","width":9,"height":28,"xoffset":0,"yoffset":13,"xadvance":10,"chnl":15,"x":108,"y":347,"page":0},{"id":97,"index":71,"char":"a","width":22,"height":28,"xoffset":0,"yoffset":13,"xadvance":23,"chnl":15,"x":117,"y":376,"page":0},{"id":99,"index":73,"char":"c","width":21,"height":28,"xoffset":0,"yoffset":13,"xadvance":21,"chnl":15,"x":118,"y":347,"page":0},{"id":101,"index":75,"char":"e","width":24,"height":28,"xoffset":0,"yoffset":13,"xadvance":24,"chnl":15,"x":138,"y":307,"page":0},{"id":111,"index":85,"char":"o","width":27,"height":28,"xoffset":0,"yoffset":13,"xadvance":27,"chnl":15,"x":140,"y":229,"page":0},{"id":115,"index":89,"char":"s","width":19,"height":28,"xoffset":0,"yoffset":13,"xadvance":19,"chnl":15,"x":156,"y":190,"page":0},{"id":110,"index":84,"char":"n","width":23,"height":27,"xoffset":2,"yoffset":13,"xadvance":26,"chnl":15,"x":158,"y":148,"page":0},{"id":114,"index":88,"char":"r","width":16,"height":27,"xoffset":2,"yoffset":13,"xadvance":16,"chnl":15,"x":161,"y":111,"page":0},{"id":117,"index":91,"char":"u","width":23,"height":27,"xoffset":1,"yoffset":13,"xadvance":26,"chnl":15,"x":127,"y":405,"page":0},{"id":118,"index":92,"char":"v","width":26,"height":27,"xoffset":-2,"yoffset":13,"xadvance":22,"chnl":15,"x":176,"y":65,"page":0},{"id":120,"index":94,"char":"x","width":24,"height":27,"xoffset":-1,"yoffset":13,"xadvance":21,"chnl":15,"x":199,"y":35,"page":0},{"id":122,"index":96,"char":"z","width":23,"height":27,"xoffset":-1,"yoffset":13,"xadvance":21,"chnl":15,"x":208,"y":0,"page":0},{"id":60,"index":34,"char":"<","width":23,"height":26,"xoffset":4,"yoffset":12,"xadvance":31,"chnl":15,"x":178,"y":93,"page":0},{"id":62,"index":36,"char":">","width":23,"height":26,"xoffset":4,"yoffset":12,"xadvance":31,"chnl":15,"x":178,"y":120,"page":0},{"id":126,"index":100,"char":"~","width":26,"height":11,"xoffset":3,"yoffset":19,"xadvance":31,"chnl":15,"x":158,"y":176,"page":0},{"id":43,"index":17,"char":"+","width":25,"height":25,"xoffset":3,"yoffset":12,"xadvance":31,"chnl":15,"x":182,"y":147,"page":0},{"id":61,"index":35,"char":"=","width":25,"height":17,"xoffset":3,"yoffset":17,"xadvance":31,"chnl":15,"x":127,"y":433,"page":0},{"id":94,"index":68,"char":"^","width":25,"height":23,"xoffset":3,"yoffset":4,"xadvance":31,"chnl":15,"x":121,"y":488,"page":0},{"id":95,"index":69,"char":"_","width":23,"height":7,"xoffset":-2,"yoffset":40,"xadvance":19,"chnl":15,"x":0,"y":505,"page":0},{"id":42,"index":16,"char":"*","width":20,"height":20,"xoffset":0,"yoffset":4,"xadvance":19,"chnl":15,"x":147,"y":488,"page":0},{"id":45,"index":19,"char":"-","width":16,"height":7,"xoffset":1,"yoffset":22,"xadvance":18,"chnl":15,"x":71,"y":155,"page":0},{"id":44,"index":18,"char":",","width":10,"height":15,"xoffset":-1,"yoffset":31,"xadvance":10,"chnl":15,"x":84,"y":276,"page":0},{"id":34,"index":8,"char":"\"","width":14,"height":14,"xoffset":2,"yoffset":4,"xadvance":18,"chnl":15,"x":36,"y":124,"page":0},{"id":39,"index":13,"char":"'","width":8,"height":14,"xoffset":1,"yoffset":4,"xadvance":11,"chnl":15,"x":66,"y":200,"page":0},{"id":96,"index":70,"char":"`","width":13,"height":11,"xoffset":0,"yoffset":2,"xadvance":12,"chnl":15,"x":52,"y":163,"page":0},{"id":46,"index":20,"char":".","width":9,"height":9,"xoffset":0,"yoffset":31,"xadvance":10,"chnl":15,"x":156,"y":219,"page":0},{"id":32,"index":3,"char":" ","width":0,"height":0,"xoffset":-2,"yoffset":36,"xadvance":12,"chnl":15,"x":26,"y":437,"page":0}],"info":{"face":"ya-hei-ascii","size":42,"bold":0,"italic":0,"charset":[" ","!","\"","#","$","%","&","'","(",")","*","+",",","-",".","/","0","1","2","3","4","5","6","7","8","9",":",";","<","=",">","?","@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","[","\\","]","^","_","`","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","{","|","}","~","こ","ん","に","ち","は"],"unicode":1,"stretchH":100,"smooth":1,"aa":1,"padding":[2,2,2,2],"spacing":[0,0]},"common":{"lineHeight":45,"base":36,"scaleW":512,"scaleH":512,"pages":1,"packed":0,"alphaChnl":0,"redChnl":0,"greenChnl":0,"blueChnl":0},"distanceField":{"fieldType":"msdf","distanceRange":4},"kernings":[{"first":34,"second":114,"amount":-1},{"first":34,"second":115,"amount":-1},{"first":39,"second":114,"amount":-1},{"first":39,"second":115,"amount":-1},{"first":40,"second":106,"amount":5},{"first":42,"second":65,"amount":-4},{"first":42,"second":74,"amount":-3},{"first":42,"second":99,"amount":-2},{"first":42,"second":100,"amount":-2},{"first":42,"second":101,"amount":-2},{"first":42,"second":103,"amount":-2},{"first":42,"second":111,"amount":-2},{"first":42,"second":113,"amount":-2},{"first":65,"second":42,"amount":-3},{"first":65,"second":44,"amount":1},{"first":65,"second":59,"amount":1},{"first":65,"second":67,"amount":-1},{"first":65,"second":71,"amount":-1},{"first":65,"second":74,"amount":2},{"first":65,"second":79,"amount":-1},{"first":65,"second":84,"amount":-3},{"first":65,"second":85,"amount":-1},{"first":65,"second":86,"amount":-3},{"first":65,"second":87,"amount":-2},{"first":65,"second":89,"amount":-3},{"first":65,"second":90,"amount":1},{"first":65,"second":116,"amount":-1},{"first":65,"second":118,"amount":-1},{"first":65,"second":119,"amount":-1},{"first":65,"second":121,"amount":-1},{"first":66,"second":84,"amount":-2},{"first":66,"second":89,"amount":-1},{"first":67,"second":63,"amount":0},{"first":67,"second":67,"amount":-1},{"first":67,"second":71,"amount":-1},{"first":67,"second":79,"amount":-1},{"first":67,"second":81,"amount":-1},{"first":68,"second":44,"amount":-3},{"first":68,"second":46,"amount":-3},{"first":68,"second":65,"amount":-1},{"first":68,"second":84,"amount":-2},{"first":68,"second":88,"amount":-1},{"first":68,"second":90,"amount":-1},{"first":69,"second":65,"amount":0},{"first":69,"second":74,"amount":1},{"first":69,"second":84,"amount":0},{"first":69,"second":87,"amount":1},{"first":69,"second":88,"amount":0},{"first":70,"second":44,"amount":-3},{"first":70,"second":46,"amount":-3},{"first":70,"second":65,"amount":-3},{"first":70,"second":74,"amount":-1},{"first":70,"second":83,"amount":-1},{"first":70,"second":84,"amount":0},{"first":70,"second":97,"amount":-2},{"first":70,"second":102,"amount":0},{"first":71,"second":84,"amount":-1},{"first":71,"second":86,"amount":-1},{"first":71,"second":121,"amount":-1},{"first":74,"second":44,"amount":-2},{"first":74,"second":46,"amount":-2},{"first":74,"second":65,"amount":-1},{"first":74,"second":74,"amount":-1},{"first":74,"second":97,"amount":-1},{"first":75,"second":44,"amount":1},{"first":75,"second":59,"amount":1},{"first":75,"second":67,"amount":-2},{"first":75,"second":71,"amount":-2},{"first":75,"second":74,"amount":2},{"first":75,"second":79,"amount":-2},{"first":75,"second":81,"amount":-2},{"first":75,"second":88,"amount":1},{"first":75,"second":90,"amount":1},{"first":75,"second":99,"amount":-1},{"first":75,"second":100,"amount":-1},{"first":75,"second":101,"amount":-1},{"first":75,"second":103,"amount":-1},{"first":75,"second":111,"amount":-1},{"first":75,"second":113,"amount":-1},{"first":75,"second":116,"amount":-1},{"first":75,"second":118,"amount":-2},{"first":75,"second":119,"amount":-1},{"first":75,"second":121,"amount":-2},{"first":76,"second":42,"amount":-5},{"first":76,"second":63,"amount":-2},{"first":76,"second":65,"amount":1},{"first":76,"second":67,"amount":-1},{"first":76,"second":71,"amount":-1},{"first":76,"second":74,"amount":2},{"first":76,"second":79,"amount":-2},{"first":76,"second":81,"amount":-2},{"first":76,"second":84,"amount":-3},{"first":76,"second":85,"amount":-1},{"first":76,"second":86,"amount":-3},{"first":76,"second":87,"amount":-1},{"first":76,"second":89,"amount":-3},{"first":76,"second":90,"amount":1},{"first":76,"second":116,"amount":-1},{"first":76,"second":118,"amount":-2},{"first":76,"second":119,"amount":-1},{"first":76,"second":121,"amount":-2},{"first":79,"second":44,"amount":-2},{"first":79,"second":46,"amount":-2},{"first":79,"second":65,"amount":-1},{"first":79,"second":74,"amount":0},{"first":79,"second":84,"amount":-2},{"first":79,"second":88,"amount":-1},{"first":79,"second":89,"amount":-1},{"first":79,"second":90,"amount":-1},{"first":80,"second":44,"amount":-7},{"first":80,"second":46,"amount":-7},{"first":80,"second":65,"amount":-4},{"first":80,"second":71,"amount":0},{"first":80,"second":74,"amount":-3},{"first":80,"second":87,"amount":1},{"first":80,"second":88,"amount":-1},{"first":80,"second":97,"amount":-1},{"first":80,"second":99,"amount":-2},{"first":80,"second":100,"amount":-2},{"first":80,"second":101,"amount":-2},{"first":80,"second":103,"amount":-2},{"first":80,"second":111,"amount":-2},{"first":80,"second":113,"amount":-2},{"first":81,"second":44,"amount":-2},{"first":81,"second":46,"amount":-3},{"first":81,"second":65,"amount":-1},{"first":81,"second":84,"amount":-2},{"first":81,"second":88,"amount":-1},{"first":81,"second":89,"amount":0},{"first":81,"second":90,"amount":-1},{"first":82,"second":59,"amount":2},{"first":82,"second":67,"amount":-1},{"first":82,"second":71,"amount":-1},{"first":82,"second":74,"amount":1},{"first":82,"second":79,"amount":0},{"first":82,"second":81,"amount":0},{"first":82,"second":84,"amount":-1},{"first":82,"second":89,"amount":-1},{"first":82,"second":99,"amount":-1},{"first":82,"second":100,"amount":-1},{"first":82,"second":101,"amount":-1},{"first":82,"second":103,"amount":-1},{"first":82,"second":111,"amount":-1},{"first":82,"second":113,"amount":-1},{"first":83,"second":116,"amount":-1},{"first":83,"second":118,"amount":-1},{"first":83,"second":119,"amount":-1},{"first":83,"second":121,"amount":-1},{"first":84,"second":44,"amount":-3},{"first":84,"second":46,"amount":-4},{"first":84,"second":58,"amount":-1},{"first":84,"second":59,"amount":-1},{"first":84,"second":65,"amount":-3},{"first":84,"second":67,"amount":-2},{"first":84,"second":71,"amount":-2},{"first":84,"second":74,"amount":-3},{"first":84,"second":79,"amount":-2},{"first":84,"second":81,"amount":-2},{"first":84,"second":84,"amount":1},{"first":84,"second":86,"amount":1},{"first":84,"second":87,"amount":1},{"first":84,"second":88,"amount":0},{"first":84,"second":89,"amount":1},{"first":84,"second":97,"amount":-5},{"first":84,"second":99,"amount":-5},{"first":84,"second":100,"amount":-5},{"first":84,"second":101,"amount":-5},{"first":84,"second":102,"amount":-2},{"first":84,"second":103,"amount":-5},{"first":84,"second":109,"amount":-4},{"first":84,"second":110,"amount":-4},{"first":84,"second":111,"amount":-5},{"first":84,"second":112,"amount":-4},{"first":84,"second":113,"amount":-5},{"first":84,"second":114,"amount":-4},{"first":84,"second":115,"amount":-3},{"first":84,"second":117,"amount":-4},{"first":84,"second":118,"amount":-2},{"first":84,"second":119,"amount":-3},{"first":84,"second":120,"amount":-4},{"first":84,"second":121,"amount":-3},{"first":84,"second":122,"amount":-3},{"first":85,"second":65,"amount":-1},{"first":86,"second":44,"amount":-5},{"first":86,"second":46,"amount":-5},{"first":86,"second":65,"amount":-3},{"first":86,"second":67,"amount":-1},{"first":86,"second":71,"amount":-1},{"first":86,"second":74,"amount":-2},{"first":86,"second":79,"amount":0},{"first":86,"second":81,"amount":-1},{"first":86,"second":83,"amount":-1},{"first":86,"second":84,"amount":1},{"first":86,"second":97,"amount":-3},{"first":86,"second":99,"amount":-3},{"first":86,"second":100,"amount":-3},{"first":86,"second":101,"amount":-3},{"first":86,"second":103,"amount":-3},{"first":86,"second":109,"amount":-2},{"first":86,"second":110,"amount":-2},{"first":86,"second":111,"amount":-3},{"first":86,"second":112,"amount":-2},{"first":86,"second":113,"amount":-3},{"first":86,"second":114,"amount":-2},{"first":86,"second":115,"amount":-1},{"first":86,"second":117,"amount":-2},{"first":87,"second":44,"amount":-3},{"first":87,"second":46,"amount":-3},{"first":87,"second":65,"amount":-2},{"first":87,"second":84,"amount":1},{"first":87,"second":97,"amount":-2},{"first":87,"second":99,"amount":-1},{"first":87,"second":100,"amount":-1},{"first":87,"second":101,"amount":-1},{"first":87,"second":103,"amount":-1},{"first":87,"second":111,"amount":-1},{"first":87,"second":113,"amount":-1},{"first":88,"second":44,"amount":1},{"first":88,"second":46,"amount":1},{"first":88,"second":59,"amount":2},{"first":88,"second":67,"amount":-1},{"first":88,"second":71,"amount":-1},{"first":88,"second":74,"amount":2},{"first":88,"second":79,"amount":-1},{"first":88,"second":81,"amount":-1},{"first":88,"second":84,"amount":1},{"first":89,"second":44,"amount":-4},{"first":89,"second":46,"amount":-4},{"first":89,"second":65,"amount":-4},{"first":89,"second":67,"amount":-1},{"first":89,"second":71,"amount":-1},{"first":89,"second":74,"amount":-1},{"first":89,"second":79,"amount":-1},{"first":89,"second":81,"amount":-1},{"first":89,"second":83,"amount":-1},{"first":89,"second":84,"amount":1},{"first":89,"second":97,"amount":-4},{"first":89,"second":99,"amount":-4},{"first":89,"second":100,"amount":-4},{"first":89,"second":101,"amount":-4},{"first":89,"second":102,"amount":-1},{"first":89,"second":103,"amount":-4},{"first":89,"second":109,"amount":-3},{"first":89,"second":110,"amount":-3},{"first":89,"second":111,"amount":-4},{"first":89,"second":112,"amount":-3},{"first":89,"second":113,"amount":-4},{"first":89,"second":114,"amount":-3},{"first":89,"second":115,"amount":-3},{"first":89,"second":117,"amount":-3},{"first":90,"second":74,"amount":2},{"first":90,"second":84,"amount":1},{"first":90,"second":121,"amount":-1},{"first":91,"second":106,"amount":5},{"first":98,"second":97,"amount":-1},{"first":98,"second":102,"amount":0},{"first":98,"second":120,"amount":-1},{"first":99,"second":74,"amount":2},{"first":99,"second":84,"amount":-2},{"first":99,"second":89,"amount":-2},{"first":101,"second":34,"amount":-2},{"first":101,"second":39,"amount":-2},{"first":102,"second":41,"amount":3},{"first":102,"second":44,"amount":-3},{"first":102,"second":45,"amount":-2},{"first":102,"second":46,"amount":-3},{"first":102,"second":58,"amount":2},{"first":102,"second":59,"amount":2},{"first":102,"second":63,"amount":1},{"first":102,"second":93,"amount":3},{"first":102,"second":98,"amount":0},{"first":102,"second":104,"amount":0},{"first":102,"second":116,"amount":1},{"first":102,"second":118,"amount":1},{"first":102,"second":119,"amount":1},{"first":102,"second":120,"amount":0},{"first":102,"second":121,"amount":1},{"first":102,"second":125,"amount":2},{"first":103,"second":106,"amount":1},{"first":106,"second":106,"amount":1},{"first":107,"second":44,"amount":2},{"first":107,"second":45,"amount":-3},{"first":107,"second":46,"amount":2},{"first":107,"second":58,"amount":2},{"first":107,"second":59,"amount":2},{"first":107,"second":99,"amount":-1},{"first":107,"second":100,"amount":-1},{"first":107,"second":101,"amount":-1},{"first":107,"second":103,"amount":-1},{"first":107,"second":111,"amount":-1},{"first":107,"second":113,"amount":-1},{"first":107,"second":116,"amount":0},{"first":110,"second":34,"amount":-2},{"first":110,"second":39,"amount":-2},{"first":111,"second":34,"amount":-3},{"first":111,"second":39,"amount":-3},{"first":111,"second":97,"amount":-1},{"first":111,"second":102,"amount":-1},{"first":111,"second":120,"amount":-1},{"first":112,"second":97,"amount":-1},{"first":112,"second":102,"amount":-1},{"first":112,"second":120,"amount":-1},{"first":113,"second":106,"amount":2},{"first":114,"second":44,"amount":-4},{"first":114,"second":45,"amount":-3},{"first":114,"second":46,"amount":-4},{"first":114,"second":58,"amount":2},{"first":114,"second":59,"amount":2},{"first":114,"second":99,"amount":-1},{"first":114,"second":100,"amount":-1},{"first":114,"second":101,"amount":-1},{"first":114,"second":102,"amount":1},{"first":114,"second":103,"amount":-1},{"first":114,"second":109,"amount":0},{"first":114,"second":110,"amount":0},{"first":114,"second":111,"amount":-1},{"first":114,"second":113,"amount":-1},{"first":114,"second":115,"amount":0},{"first":114,"second":116,"amount":1},{"first":114,"second":118,"amount":2},{"first":114,"second":119,"amount":2},{"first":114,"second":120,"amount":1},{"first":114,"second":121,"amount":2},{"first":114,"second":122,"amount":1},{"first":116,"second":45,"amount":-3},{"first":116,"second":63,"amount":-1},{"first":116,"second":99,"amount":-1},{"first":116,"second":100,"amount":-1},{"first":116,"second":101,"amount":0},{"first":116,"second":103,"amount":0},{"first":116,"second":111,"amount":0},{"first":116,"second":113,"amount":0},{"first":116,"second":120,"amount":1},{"first":117,"second":34,"amount":-1},{"first":117,"second":39,"amount":-1},{"first":118,"second":44,"amount":-3},{"first":118,"second":46,"amount":-3},{"first":118,"second":97,"amount":-1},{"first":118,"second":99,"amount":0},{"first":118,"second":100,"amount":0},{"first":118,"second":101,"amount":0},{"first":118,"second":103,"amount":0},{"first":118,"second":111,"amount":0},{"first":118,"second":113,"amount":0},{"first":119,"second":44,"amount":-2},{"first":119,"second":46,"amount":-2},{"first":119,"second":99,"amount":0},{"first":119,"second":100,"amount":0},{"first":119,"second":101,"amount":0},{"first":119,"second":103,"amount":0},{"first":119,"second":111,"amount":0},{"first":119,"second":113,"amount":0},{"first":120,"second":99,"amount":0},{"first":120,"second":100,"amount":0},{"first":120,"second":101,"amount":0},{"first":120,"second":103,"amount":0},{"first":120,"second":111,"amount":0},{"first":120,"second":113,"amount":0},{"first":121,"second":34,"amount":1},{"first":121,"second":39,"amount":1},{"first":121,"second":44,"amount":-2},{"first":121,"second":46,"amount":-3},{"first":121,"second":63,"amount":-2},{"first":121,"second":99,"amount":0},{"first":121,"second":100,"amount":0},{"first":121,"second":101,"amount":0},{"first":121,"second":102,"amount":0},{"first":121,"second":103,"amount":0},{"first":121,"second":111,"amount":0},{"first":121,"second":113,"amount":0},{"first":121,"second":116,"amount":0},{"first":123,"second":106,"amount":4}]} \ No newline at end of file diff --git a/public/assets/font/ya-hei-ascii.png b/public/assets/font/ya-hei-ascii.png new file mode 100644 index 0000000000000000000000000000000000000000..a23980bb48a6bfb77754b5a18ccaa368bbfb9df8 GIT binary patch literal 107021 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

qho-gS|0Jn`;GT6 zbxP4}BFJRWGt;zPJ}cZ_gv%LO0H#ZZG8D4WNhren{3ykM#8Q!)XF8f^09)y+-vk=K zK>R)`D0SrDxfBP}Lt&P8vhCJSwovTaL#EqdWIzDCW`FT9HZ3K>vX`eGRNx)CT-n^U zq(nt}2LeKHJ=6Fr`%*^R41KR3gnqvOs z?2CWg(7HV>X~S3ia)hjc$L^*inKctq6|8@Ak0}_-mTk#;M;A@J>8S?A%!@mzejlkXx-*fwewa4Duuj0TA`plvR z5p>tP%am(X(7-3MRyiA8vjz;QjBE`M0(g3+ykcn7HD8yn7TrGNYVrU%{6`D%msa!oGTYKt?4)fO`X{O=ZeKj3e;nBc{* z?Ymc=9UfssU9-~FGdH;?bvXadZax@u?_8-IbJ-@k$bN?4lhnqi{}8m+eFLg z6ajhR715P17h5l0IaPG!mIyxiHIb5o$JLg9ZqyVM8_Z_^F<{6DJLHL~&UpJaPIany z#C<5g)!f_tL&tinSu?zzvr%WS#V8Aj&2GQ#QBzX(+B2ZGfhYbJbZXu<4a9Hxe&g27 zD}rv>X=xee6ZIT=<@Vlh8zRX3gr5>x_`D0aWM^gN`C&_2cl*|k@D4PzPBXNcz1*McmA+~}?)fx{ zl3xJRXQrtss>bP^sa+}}WbNVe`LoP$_hf`p^)#=WTzIdJTy;XUNDG;;9GQ^l+ldjE z>az6uq5vGeUX`!HH>k{p#QJ^v2|K(e%4}PUb&!{vY|c>D88o1>5|f zkh=G8;Cc9UX^Aknx?18r)Fs!gqjt@;?{C@j>*nTN4OCF(xlMDOGK!qSl_ANUA?l6( zRF>{#%HfhM3`>M|(Ny_Jxt*6?(&d4I9;?Fg#wwS#wp(HxcW%?%;csj{Uq!LViy^9` zvb1!qDIKApboOi>Pp(U%j8|N8)8*?eoPR(9VMPDf#NU@6rLsyERx4Vt>;Ix;pD%H0 z`!tL)`4Jj=fVgb9<7e>lC-E%Av?o8KQMwnAyq40u$-F z<`#kX@4fB>1rAPW;r5FxoWCe1x;A~i0nt!JcF;F)Tcb0(_Hqn(XBioW8_; ztA`kdm+({@4BNvl6*couNun$-x8hxJ70s7i*J-Mj-nZ56wQ*j(F~pvS&)qypoH#|4 z)stvwVctNh;nXl78u;mHhS;Z5BuDi%`7%F8jzw!{_v{azAw9D2ivQB$icikeHbciN z1S&O2%gMnQ?mfV0JsN}BPq2i6fsv8XL=XFFYdPSF`Focx-4<<|ycvV*N%Q%A$AyA3|B=d-QJ{T!*s>74TTwdea? zZ|#q7N^yyJ9oSu+gHu7OHao&JjZ4nd+^VmweUG&`rLG5BG24W0ZX)j6$WCkjQBNDN z2lnAA(cz6oIcZFp1_WIcO`SFk3qSJG*8PL35C8k)D@WBp<2mxbIDfz49xEC}u&uF9 z+9s=znI9VM4-A&fEBg-YZHQ4bmjF8@Xe*Wq)^)sEE ziErPkWrb0WTynP`rtfTyx{BDJ@zqf6nT@QVZBdn*&|njL8{}imFJ15IahX%l8VJ57 z9kxd?$?9mdDCZv_T*@$Pn{+pxq1KD3;Aw_9L>5l zw<7J8Fzv6teapSe$cdhrBJzNJPC;3X{Z0E12Q`G`1?a%0_#Jh`X+0Gd1DM%YxvX^W z$a}x0o?U$H&j_k;!EnOa-+u)6IPl|O<4c{wce%OmK;-UZ5W2eVD>C$ui2WpAa~z}o zgE;^>pZ;7j9z49pSLFQcOqQP>Cnxh*R#z(}T3HX3)UmY;Po>tSOg|yDTMXJ3Sj~z0A##AEc9- znZf_>VicJI{xjI!<d}n{Gi7!sqhgS!$~MdbOyFJjEnkOwMKbUiLjPiGAhn`xiMVQlFFPTCv3F`H5KlUe#=^2oK{Ov4^If4Fgv(g-+bv z4NCCccYax4B++*tYBk(_16_+P&m%APNB57)t_=b)R7u@x=xes`I##bhdyFrohO0wD~1Q8J=#AyJ@~28?ye1qcyT z^4J5DrpJnz1fm2ees;LZI+p>-D4#RpLykQCT29bi#8Id*ta8P3R*!F0_)PjsU*st5 zg7HsJbC`Zao8|U#kK0uB*bF(AtyJh%=aH9oeTfKarhSUfjj|uDoEM$<>hx}rH{2~= z%(K4p@z}+gLeIdUtg32pVL_%~fQOgY42>Q>Jgg1tX|@mrhM%(b!_EL(1l;ITX%kef>*-uQ}*IrjEbA?ObBU0L@`|8(@6=HUm@ zz-Os_Um{@n86yS{ly|kaf8(qbsMChLpsi!34f(bR3lF@Wk(`=whQHIq+5Inm==9h8Y%krjSZ0IDPXcpXV1P(cGKNu6P5 zbMuQwRV&aO3%o4fqYceogCzNzt>vjh#n@(~-Ga(j_S zGrgF*H`#0lftd3Cy$ZAADe}q}Z@6;x``NI4Bn*Une2A;6I=jXdNfWaD{soMFl!Xdm ztiu!>=(~GZ1BmlsK+!0djf91W(GBlx&ye-1J1h_F1FeDn-TGu~#9%l~hjZ%Q-78mc z1VyG-UY785boeX^FT4-E%uRzvVHl!p$fzC3$Zg5|GtBSw6c3GZ7pq}HIML@%Z~NE{?(Y}=a&BxQqqh93WmGXSUNJCSGBKQmSkBLsor_{A zQTNV~nK@16zkg50b3aw5M7KS9cGkSYal#!qbdGg9(ExgB;q&4ghsE;4+2d!Lc7GGCsRjc@bl$zG4;uM z^!T;FXEYCGu4iZ1A_rH*!s2fk@&Me6qgtKbUtXU~g-l`y)+Wkr`mUXr1ZLos!G6W7 zus0|HlNk?bsKvXvwM;154gDAx7}#s2U!_5MKOc1pjg_wl!_3euR z1lyy0sE&QX>FJl@9_p(@t!qP_YaV~uH9i@?-M1Hkfr!)+h?-{2JA(+H z{Ybg}iZQte74rs}*vHo`Dx7NYim=2mjK798=i>|KK>p&EpbU}oFIw<|UKNFO*wb2j zh38mWbiO+*95aB7dM5dRz?{i1*W-D9z69;>95_IbG$BS2ZSo|Ea*yx!=Y2s_)0Z!y zW97*A@?}BMZ5Z%V&T3Sb_$29RkJ&A;>FJrRm*MNcGV4qh^ByPNf18Zkia5eGV4r|^ zK?1-?&nhY=U}2%T820@hGYk-+k*%+b2nxPa*|26|!F-H+Q~ic}gA4yW4vOf!50zyH zu$bObt{6A@o<*h2Q^8^qf{Qcr(SwRuZ!Z+uwCm@tp-~KoRli^^CRPlt|!~3^Kmb#=Vu{GEyMY&6l95IALgys$NE3!z!+Pd+{I*_#7+i z>mw5r6*V=>z{E&%6{Z|>(5e`5L!+1cpz~jNcvwkE34cED)s3ha0Yp17Wgz;Td*L+C zoX`DEU#zJT1`^5>2FJ%sOhq8=t#o30w?*WMu1ZQo7!n1KAw63~+KTOI?C5_fgYBh! zu^`1>1_-m6l#rA9s95%Zu>gW`9UUmf1)KsqH+dM~G9MdPc z;NRf2>H9Gx6iQW2-Be+Ds>(O zi8%P?3*zl@ry%4~r0wq7P)R`j*!s`cuM$_mIIl`hwZK7yUA?BFh1~YiA~5wQqMJ^WUs)LB$3?Q!g8rRshruITm6*RT+iHN zdMAFWArv zeZF@Nmj`|Y#*>M&k^Q|64fNOtyOe#a?ry@AMp)girKSG71cCJ@7Xip~vuS=U8;NMd ztptC2;hR2BgZ00#6aO7Vf@mt56C3^EW1+ADK|c(%tV}FSGCZ~0xG3PVpYC`h~r^beTXl<2tpadvqwNj$qx__JrC4PI>v<-2H6*MTPUi6IWc%RO6iX zI9mtWh--L^r>|WLKsb!rg`1c=OVX>qZNRYM3cV$?guU%tG8 zc-94G_MG2ph-Z}%Rp>O0O<}?wF4jcB2G_8bYA_~#7F9fRdu6To0Q$I*!TD0l9Gsey zIXX8nKDF@Trm^c=Rp*g!ORUaqWwxW0_b5tArgI);j1ap@J!r)|rvyD#W<+$P<6cfH zG#XfTBnr78Du!pyocXa>>MG2Uq~uM3QI}(ldLx&SuF66HZ7e;RugD@JoR@lCl6f5T z@7wZT0B`MrU&O#wBqG@q{Cqu6M|i3~8QTBwlnNLT$Er*5NS^DalE zFrBBXTh_z%2W66iG*eGEmDR{VIc62AJ_tu_NX$7=$UU(|u)}P$ue-ayzu$f88xVt2 zQ)z$+A|)jy8us(&Ls0H`RF$aDIe#xcKEH;OW3?AUC%wB;HTlGGKM+~_{#=-dc2M0N zDrS&}Qdewp(-oYzttT(xIuc{V$8)j&Gx87k!F3GSt(>3VdEV#y$7hnIJ{WE?R$^9S z5|R%uALz`l|2Jo%b|HV}|9yu3>C4vCoMaa5IJRG@blf6Lz)gOg5--2PSh|axy;;4;{X^i;D|%gow(ds@xJ0 z$+>2+s4t946M#Z&OtyX|w&aPFJyU;{51u8J1)CdEVb3FK{{f$r?YHfYD5 zJv-fai99-L$K{U4?(Y0jh?P-U50~tC*YGo310GT z;0$8M-?p!xS4i(1(4H0Zxz!9tJmnX7b zYbj>?<=Iub;^H-7=c!AM`}){Z?SgpAT_nMjp&uTm8u%Fgahi&h^9teuX9PpO`JD{6 z-MHuJjt{1>@q@ay_B-r|oi#BZ0*PB&=hX}Bb2No?r8KY^CW~U{{B7ay`+{_I_?Ho^ z5c9nExMj(w{gxLV^jz(>Y%Ozn?oVAK8jj%>M1~43jd|2L=V&$M@cWPP@5RoK2L?zhST5 zo_PeNM2tl0T~ufD`KM3KxYJb=le!myf^du)8L_id$%&vxWnHdbpmoyH)xCEwRZB(1 zX^`ED-yx1tciz*R#wk#=<0BF z?cLrkgYy6-vUL8vPpthL$PBv7>kB7uw}<#h>>#(gNQpXFR=a0TH<)Z z)v*c1wLqIRZ)^gEF!26-{8fNfMncl756tYNkvQh5Hv6uzm{G{hlXb@KXVnOH_77|7 z>J!a6hME5zdOJ40QO+xsu5=2Jdp@M|-8?g^WuA;2aw&yC^^g)CVal*DAl%u8N`*kH|kRj#=Mv{9$)4UeWzBPa>Nt^MaxhH>zWSSzH`3tZj7ZNU841 z+7CO`oTBo~`N-3I=RaiQp##Fgd=GBi`2L;IB{Fb65U}jNz8}T*dbC9p>@|E|ttw86 zS8^{rp~@P8Dvf*NP+Sc+x3DlOTrmN(KxxE*Z!5q>$if`U%fjW@78@8C1aj8MPAS#< zE{hEbB&LFi@Z8@wBLTidgeNyl&r|4LmPvqmD9Rk(~AIhR5oG{ zXc9<{0b3Xja9~mESM#Q6wtmoLApsksVoaEwQzY19@+31IkBK@_dv`5<&c&!&-ij&gE!I;nKd5VjB%v zS;O(yG{Bw{2Ac_^B>x;p%%)lFap>-@#xKwKR4GU)$UQ-+$)Lcqfo}k^RWG{{U^bYy z<{RJ|iQL*Qqp500%*}oIQW_7LGkPp^3+D$!p!3mk*exbOr1N{(;8zsLP_SzfvQ=qy z&hHu=H)qXk{nxVC*#&de)tlI&V_K2vI>tV^C@Rjsk9$|tVCKdCjw>*rZc zLo=VluGNu>y!?E*H{ZZPLcah2UjRpfuduRe-ruiWT(o|8JzJgC(08Y{MOI3>#X>ei zP;7S0V{GDMXhpXi@`Rllrd zQW6B$mc?V`-A~Vm>*>*7=wt2ac^S6Zug9B!QvH;^jLws-fi z^SLr;SeS*}4`eA#M)uIN($eaD9tjYdp<84nUkkq(4KoU7w3n+}lCT>R*smXm5s3~j zFe4rHv&9Zo?7zfMKtKs>0P=zs_W^zVW|m3Og(5bSmEU(eul>&7JBsj5^iP(@+8!%0 zQ&h-On@D!w-|@|nzX|`(1c&YSiZZZu(-`;gfK!H^0bqo-M$^ki^BFOMq6(ntCo&~i z4oz~tz6K>jrO?nZGb2$j5UG@P>C&Z^`7O|dgSOipG;q*YJ%2tYop~g5@6#d+6H}>f zuMKMXMloQ0Jd7(c-`)FrM*lPMOIs@|oJ-Vl zvJk)bKsA%8Wxi#;sJiJGg^No;QMpWPR!7UW9IdCBht^M%xtPhJL@zv$DvTm?)@<5pjyu za%D6*psh41?Fm()Sec~D34fozdgW+sXxNpQP{J)Rzmufm>(<+NZe#?43#U)NZm;rN zTBfT);L)kZiJRYHAKwEXVh5+~*8j;VP#F*I!Aosa~&J!3m~j~ek0E-0SW!E74(lt;1bemX8L%)d(<4ph=dZ&$)_?}|?P zj%vXEQ1Y~#79-Uy&o=^_9R=hy)ix$x?Vrc9>2msW7njiQ{2xoQPi14VYo%eXnJv*R z{7zJBL#TeWL{GZxF%?{USO89-}+i5>dDJ{X&__mP*>=WmwK@-jVeM)mYo zR-BwZbzl8AwU=)5HH=b>!1-~x^=K*7f`DLX_`PPDuDNaKz?JuHw&q`6M-lfAKpBW; z<0cP}=wF}Cl1^33Es;>&SLmcym*N$bltyZ0AdR5A8<2H7$xpy=ppWjjOI6M zes-b60_<6 z#CL8vHiUR>&9(imF3>A9-f7ps27Mh-%}XzwQ}ppl3(X$A}IjHCyRrMfW}rz?U&zv}IxBO;O)t>@;eJR?XAC#NJk zV#2+AgJF96WeBOO1C1I=ZsaJctC z1Mm(518UuEmWRzw&C24>WL_`G_V$WaN!Dm@&pWmxxaR_&wIh+34GRj4*Tz`DT{GoC ze}g0r4SPoUO_-Q-jN#fEg|=HpVbbn~jA{a!F^tk$as`reX~lmCc9 zK`24MF+LHPW>L=`E+kCgx3>fMd0myWq;#evyUoHfsg)!V^f+UL$`Zz1misFOsdm?D zMytfjzJJGqd)eeGT1D1x34d?HkHULprR!xA;*=$<3_+{cGU)5q zWcQH+ZZavU*hG4KA#ndOp1&H@#=U|B=Q`l}4jVpdH08*P4O_p4%!~2yKnW?y%E-ve z%Yr)jy{C;Ynk-g_%3cH`^dU#0yfBBN45^DEt*Lw8%b z6Ka+)Q=mqjt%2l_e26SHmnS5WhkL5ef- z*R(@_djbeSMt!?o;i#5}rri;Sow-*0@lLKN@3+aDRf&5?^RZ2$a~J9x9DA*J2Zv2O zVl+dsGXoYE`%c}&FWK_q$P)q6e53hcKA_IS`M>ODmu9UuL_kC|GyD9$jt&P0C8e0y znR7EUiNesx%uxY%{h76JROkawbHL4Y)V4*eynOIa(0)+8o%|i6srmhyW6UtpzWjfN zgM5*|GcVK$(qd&5Cw7wubn~hdRI8RGSQ78+G zZhuo!!j`Yx)v)C~&k2IU5RF`uFv~9@oOOcsY_WwfPkrxue$~6ir_wYwg)88S6qA#W z>Y7>y18d&>mF>XbxO>3>?c=H85fSqq zRCtVYxww$}$v4MyPp+Sz@|B@_a{V6X8Tco`*XRSbUgNZ&aCDk>O{gd=uYS5!IW)F7 z7B{}Q$S4W_m53eEP=QdbUmyO2MrCH|(YeXnVDw5#M${&xMUJ_AsMmi6$&IT^HF}i& z=By9>7~)kMbf@vJDy#KDAAjbp=Pb41)(UFLoNB3B1!3r*($W%wP6}n}k>qjBxctK9 z=ZKg96NCcyQ)TFhLL|PRtwt*!q-o|d!=qgUB=2{|4Iu_KH{hvvP(}3*$!d`zjlqSr z`o(p1pyLGXK7j=`-rgg-yXC4FXyVqQz2_ldU;upS?$3*ud3;lxiAG$H^?2nh4!f-O zPD@dr*v|r8fp@+Yc&iPux3{PFq^lBVYfQxo?tb%Te)sqb72P_|b?dd|$mNx{wzg?i zahZ(D>NXUb+9(sp@H~$G{^ogT5H5x8>m?&I2Zy^1x(XZKS@?mruprnUg!hkfB~BRj0#Fjs0SSI!d=;hc_;VJ12c7hryPa26gm zO(G(q!{CXMoYd6RpUaGW!*VCm=&xG}KYny{bnIMP%U8wBLn|JA&!*?eH&W-kSyn!JV2FF=3Z=^{gE-F)i21ZbacVHLa`zrTvdEbUtvoJeo2G zFsUR{GSp!V;KCk8Ha5C6*~sc@?cDcMgV1g#C+EJ3)_ZRycjbytOkvQKQ>SrA&YoWG zugkY+zn|LWmP$iIE75$QGJbl(^m~;@*R-03!gNfZzKbGrZ;n=pg=GiDV3EZz>s;M8 zVg+v-q-IDPQAmY{gNJuI;K`G_H;HcWWgCAEu|Ip590*TAin{#8A$FkERP4+khc*R;3TshS z%OX&er@kR70HSOxnEd?wHA`!Me>tM7ff6*ID=Gs&C4UOOdy{ae@TVJC3ADWb$$y(( zOYF6dR0>?wP&=2=>bGA#`#UI7|1(+-s8_rnBtzpl7<&2(L;m(93{|Y>*8p^gY^nRH zFavnELWm)KxMZY^K}uA}3t}D+WGpUXA|tPr>9&ukVAR!FiTi9cGE^g!B9T|lE1Uf9 zRn#X@l3U@|=67!eO-SF7V@`b*YM6-$UUT?!a6uf>LBAw&flBW3&jedY-3)dksj!&2 z`SU1ZW%@`nZeBjGsg;*<&vo0$f14iOK4#;Bm^bB>2;SNEFK*sEf4*LRu!x@U0@RBx zk=M(Fk|1Nndw$=G4f*t3>s_fX6PvjnwMOe zKZV~4Kg+)V(*I#(ta{!|j4gw6ZH^`zrq85ISFUa?*zApZNHmGA9>_d?Ir~V6({N@< z!(nu=kEa5~Z)l^p=+D ztZ!T4*sNJ5ChS;Uzb1)2FPv5_ptATJ#tU~bX5%0i=?5Cd{03I%7NhJadK-o^UD>?;0>eECZdq1I7oNO4@SG#9lQ$nWXRHzD z@$AJVnEEB-5qVfGx`bqbW(Pvo#Y?9GtS^5%MM#y5*9@AOKW8nrPMIci!A1eIUj$f!(LXZ!t1 zSJp8vU2|Ehz?(LXKI(l{oLzL-Wi;C4Xh@MVC+E;OE{LGYBfLszZcb2SD>FVJ;S#9i zP*C)ajDW(2ytj8vbTrBTP6-Ny0E6nRh}L;^wT-=fdxDMi&rS2j#`C8G17UJ%68bgc z6J!~MM5edjA~bv*19NK-i#PCQOxQWX zdv0U1xp}fj`ZBLG>?5`!{7&-t2@eU|J$>OHoo;hm8xSaNq2ENtZbZ+7!4v1q5{mR(^jY^dtYcI!id- zoGQLG2Ld;|K9m_s%0favx8FuyBMg3K$Iq6~4(r)BJPbv`X051p3-b~PUOB)f?Ck(> zZ@Iw4lq|eK=9aQILDOz%q0FLIoG_#z$7L-~T*??pPnekWAm;%Fe)1=cxA)3QVG)o) zQJp{D2=tAQJ3#%gFGmZMH6Sz(3<}!W+gn~-gpEg&&@PwN0~QUOE>L6Y?v4N{EKo&( z*p&$HY?Z!}kx|OQ-a0aeTGihsaV8Qc8*zX*^osJLs-!(Rc^iOB>ADONHzQL^dO)R+ zO+bnxe0yPyG4kii%QQL?lG?7HP-p^n8qO&#hqFli{>lpp_Z3gg*z?ctKNKUv(@;Ng zNs_ciWUF9TNa zbwFup;YAwVck{~xVu&9}OB1)W%mRrc;CaElNO7L^NH0ZlO7} z@2Izc24Mi+RAf<8{hrH~krk!NlGj2XjALopYopGZn5e>8^0BkyazX<1A2f}tgWBCc zRWG;^WO{m<)3Dac%uIqYvZ$!&p}jp^`3DBxy9}AVBDBqdl+42Fs@u^YH`vCIWCsMC z0$~pji%9RW-dms8yB`_*35W4|;-ub@H=6oy?EQc;K6iH@8L84lysL^^a|jF?8ocGc zl{qIuXT$g1+}&NL*Tueo2Z!6;n}v~szcBpvh$$*O`+{Bwz{T$0^z5b3 zm0=B4Y+12DwRCy3rgFV zljr36?HJ{}(h8>RbO2)eYopHa2ckj;pbo%gAdHl*!VE|R+>1KBv7wTo`k-I{T0vVZ zN`ys33@Sm8Du#!je@@G9Rtr7^6w5&v2M%;gOG^?`Qr`|P)6>`XW1I=@?4Js!0^F;p zxu4|VATjxkOxF+KX}){~W$DqbiV+~VjSL!_Ki3aaZ2=)oLhRXioqL=Z31FZ|#=;^* zdU8Ivz7>RgVMoT$&~SX2nGL!kUvEh-T7#qdG#9=-vZkM;E^Jw5ob61d?#Z_euim!>pO zE{KVb&K#b(qX(*Nkd}?Ybq+H#=^`F$m2S(?X=!wK4u2AGaB}Jz8fvnWfXs-Hpr8{P zoo!ekpP^bj1k1w-B#|dK3*r6)0L(zIIJK%uhdWL^r!O%%nKh;r5$Brs-@8_`e|xSR z4wwc>symq~#+FU7RRbJLG z#|RSLdCtEdmFROaoj?8Xx~_o%9}mxTYxC@*FSmHPxlI7f2lc%;GJgKSQ8Skw7xW*r zg6yBx*e@dcXB13v)%IJNO>cY*w-y~7-@WEmK$idtcXrX4j5cMISy?^1yO~j~J+Z%X z*|hR2;pP_DnE)8Nf~5ro1)=^d(0QznyRDClfq);V4MCCF+uOSX=&{)_S&ohcKrewR z43G-mwwgOrc+V$!Ko|!yQ&6b^<+veWfCDW~Xe73Qea-LFq3GVa8ao=(qqa6N|K)6B z+Rv0<2pBoJITczA4e|kfNtKj}C;YIucKwhhKrfRrDSSKu8U~H^KHd$k0w# zxfyc)t4fh}X??u|9B?2C2DDVi_i1Tg5#J72=-^=NE&SCrejS}uZ8Nv19k))5l7@z2 zFkk2c>6h0v)UVP>L0jeV+ljGV;p*z^lngOxSwRpl#DC}$6atXO_c?)uD6kY}e`Lrv zHZ>X4q5I$}3&F7V_V&2AxL@7U)Nu(3E`N`GadB}CXCTgp3zGW!G@x`1bEc7_(PE}5 zFCPki68MrTtWkFM_BuvJ9`N(Z_QoV7mEPZ1*ge*ZE9Ng!Qme5RRW$vinJu;}mq#)p zGj5nc40iUOJici~?27wDB;Y35d5bf2B6L_gjOSy69lE)hEcb3L&zqouCnS`J?Ip2e zm1uk>6cJr;j3`2Wdb=ej1Do2!zCRuUzK%K1mm{>%_olN^%~lZu8H zSLt?{Om2T%zILIvPUbrI?a#BVIH!DlOUokU9^^Q?e(R?oXY-lNdUErYu<#OG>;f!l zJ7eQd1b!Q5K)j4cK)}-7yZIetv$~>yPIZli*Phl>j^Dk6Zyc#t06Kj^;vqN+b!pr&b0cci?A{av8dU z_6M>9fqODgU{q1rJVgucx%As+ac3};wV)Dzkf|M`ZKz?$uGyEdlwk7hSO%)mz7aPk zlRPTjLLol$j0iXd4J7HRSvGu^CoE0FE**yZ!CiNfa3F|^vY$VlmX-#S(iBdur?2k{ zrUkm4pgtl1$MDbNqXRS=jgOBH{-u5^X&p8Tu&1DY4sHh2`9!l}pXBK*=XRz@JSIAp{hGY;C^V0G+ElzOn(L6+S zZV(@=uM2?I3_wg+&}2zpIH`YlI6Et=2|VJ>^Do20Yd{YcE^`|iU%MsHROc<~`RQwr zI%{%-6K6eTXamw5ZIWgZ{tbF1pjxe{p`(G&xWu>us+G$scy50ND(2SZ3WuwPEMB93 z2F3KkC~b$We}tVnEKXU1|2LdiPEN4OuR(8#+IKsGIRr3VZ|Lv`doyZ;>B9PK2w$L@ z1>&^O2h%$-u?~kTh&hP{6J zO`iIBwRzIGy?OG*hK*HgG~)fZ9Q#=RhF2_8);_gPQRCzhX!?$ah&y9n6RXnLtu<{W z2YMzZOXSk;M~}b{ma~&UfzWEdTHdSi7uM{ML~XM;)Rz(ish z_PN=tQ2K{iE$Wnix2Avn(JxxK5n_w+a}xA?6`j2p5)wusE4TE+{5qRoc149293gN~ z6RcxJSs5_HfZ~=N6-5j)Rv&WW6hUhj&hEI_*sg(rEcgcyLqdj|m6hf5LMKt9uBaaM z=g-1oYEG+rl_cidIF+}zdJZaLgZ2682kN9aIG_DF&cnSe#*>+!KMGqlzb50MjQ~BH zn3z4D9kDwR8$#aRKHFFS-{gI_avoi<>Zmal`uY@R?&OK)2$etrmii7=RGKz@5@WZ}alh*nm52L>!*+jKt&CqPYh(@}{6 z_%x)mHntV~j5PS9N9Du@k@-R>HZXC9?5P-xgVXDu55K%_jj5WC@Y;e)(MULio!-et z8rI!UCBD-)G=zb)4P;1@lc|3Dj03z1T6b`O)MrDE28jpcgkxg{fV;qtK$ruuBGA$y zsM!SJ_4C)MEUaDxoU*lri$AJbcFPCcT_uPZ0Pol!e=@mFcHMY#VDWPptrp09ymVitXc#BmDKN&Y3gQjEs$Cr4&La6iHK&3uNPP#*K~ngP>fHg$1g#-tYl)Vd=N8L2i5N zTay9d3_cw=YhJW4BPK;y14F*R&Jg^uVh%zrYnc@>$9_wc2mN`W5^FWL(uWuW?e$TkVLwmaksn&fTju$wId#@Z`yLZdnc*STtk2=}Y@sAi_VQ*sg zwxTw2;qHrmW}xJsyku<0nW1TFkJ9{g^4b2o55+bE!m(h`Sb8lSs#R@koaE%WxloAy zdD_6BwCsp4u1$cK&sW^>h}@yd{EyS{(zw01H=l?IA2~TS3yJA((Qs-eJG=ebiO;;5 zV#J|{N(j>Eg}5(vP51*jXp08b&J1p-^Cxr->kjlwd5b`cfTl^S?9sQZ%fJT6 zCm!p7d(4_GN+1&M@7J8iLan*_oDg(u@KaDy!a}+G_^j;jYx5_7YTbK?_FP;_xbm`s zf-mG7n)@Wm$B6n2g8ryaGBe0ak_(|2L(ZT6^7?*&KW4AZ*DC^>1VnTAmq%}m?AsTp zGOMtw!h4ySM4Bk{0vp@mquW2nx;-utAb~HY)oTWuPZ`}|DNtek^_9A~Tng1WdDGYT z0_ba$&4K`Mi&^6iT1s~~ZP=$Pr$-I{c1j@{t;gOv-pu3^k&xai_D=|a}R z&P(d-?t#xv(9yR82+gvt?t+*YToK)#56Y(1Z^+8fx^mw|n$|1pJVu7b zwj`eox-6!#_lOOyKKLIK__tREFCHoH>m^6r9lLfR#rcKxahbKkwC{X;d_0H-$!4gk zV&3ZJWoqUGPis>&jYsR|F@~8sQ$XDf{sO`&EI?|B*M_diQ>Evtn&FG>O zkrdvX-kf9x$UxJp!pYgSvGaCwm=HQ~hJL*?*NP-tgkj35)wNK1D8#>g{rb(1T%81d z0x@-!cWXkjvfyo2$34QWvCiNi0vRx}2{gRA6FLj>H{7s%^c2|48I65M=TB$R(NW=ug<+~7ygz(5Z?Q<3qAh=`~@OwZ#}Ihw0OKtQb` z;ehnDrR8_m0g7J{4EV@D`^8n{Jkb26`RxOYPRv%0ynZz*Q`EWjV(zr>F$)X?U6&6< z(t%}IPWnh$NF2E1w#)zf)msHd=sJV)!O&0~08^cR4)!^?B9wT9T4v}g99<5|lk_S^ zlUCjLI9-5Y;PSV=U=8Q$ah396R^@XsN1NfM5N5PDH`)J=l!=ZG@7_l%dR3WvCSU`; zkif&Ejj$j2cQn?kd?D2-yveetZ(yL<&QWpr#^^`BK?({`;eLVD5x5hq%A#G^?M5j_ z(BJWGdMMcsF+wNv1IH%~K>64BQx?FISq@*P7g@19nH*B$or;PH%o_9uyEt(_&MntlDMJ-Wif3lNL4^klGok&vMTxGSURilL z9MhW`09g*%X~Ovk2NxXbVd3FPiHU+@Vmr@&STizJxdb8FxKWHu?W5$pBCRXzB_)v3 z1+07ukcM^s^a+ z@uK+fJ3>txd|bo0-;W5+vqwumH89gJUu@^ z{{gnf*I6B^hl)^gnZFEr_)6SD)|QWcwAIn@@Bj~n=W822@M$78?z=h4jxEc3vT+`= zuAh5`T{i59_`9FWd^SLI<*{R00Wkn%M9j0#3F~ulsX!O4EA`DAKHI?MuW-#JTt}WG z8_8qI%6hr*DTiZIsWOG}u-Z}VX3UjAX$zG<3d9#lG3v#0lfsoixbTr{n(vJk9&SH5Ruk4 z{Mq7g2GRLDUwQo|20j&-Jp1+7=!WwhE6oL#7d{`}-Tb$upbZ0w)Fq=g6Q5Pk5Dt}r zYViF!Bet zZZKd-<$wYGx^E23Alw1yw!MsOU!{o`SAJ;GP2vyMGv{w%1SO^P#~v`NRF#8ew{&Xjznw* zde3=0aN7}!S+V=lhA%ChthL9-7jhbJ{MvtA)tfiShYU=S^P>G}30gzG2OSydytdJA z;VKl6dicdpN$uUJ_!6BRHhY`mptneCWOTH&yd2sUb#-(eu#-H0_yN8&GO8#kF$L=Y zk$M5Z%YQ<*JAzKYXTKf@Oh#h7vkfNQ!^82APNhV)fC4`9AxEoBO~I^J-o?c|izBnJ zjEnoZKe)AIXg?G~^!v9r!PyDYlaDDUhxpSQC@#q_oeE!Eka)MVabh#E+OjudLtW(9 zk*ln@m^OqKswFTMpM#MJeiX_9$Q4-h9PnL`!84SG$RB7{)o_8vKd2qrL5}t&DnJH4 zrIU>a4ruSIWt`W0#XGC_J0^mzLJl~koMbp-ZO^U)54+v=`{Eo*&mE8DgYgIKmyz${Tb(@6`WPFc6AmR>yrII)2lc(8Qbhi$I=1?BnRyf}aDw zY%7EAe53wJ!1lJaKKG6Ck$5M@J3yE)`H$k^fgx0v$Zy=>zIE#zWOq<^K#(tCVd&CH zv~_SmVDk`KFi*quyu5ZuQA0_&ko%?ojcs|LpvM1ieM5sJ0W}IdjG33$kP9~%vioL` zO_-;o6o0gk5*ruC%_IIWI-kt%sF(c1)ZP&fb;52St;Dg9XQwx9jXMH#@%j|KW#Iyi zWP!1+xpNN2r!L0s)vf;?B$~KeF|bv)Zf%gT$!4ql^~O2q4#f9`T%|sDU8v%xkXwO@ z^cS_im+EJqQ=xo5WMx6wBnV>aIE6;IM;<#0D5`w?II*zs0rndB2*9dbpxkC_n*&}1 zTJiwoLZPrA^jNjIi%SpmE&){@xsao>@`EbIQ)!+)hVh@{KXSyyWuH!C(Xy!I4h`wd z@eXFqOggYc@vmNzcyZ`eRLc9+ak>IKpxQtfP;X|t+W>S&m-t}7u78=t6DNmTlUn~IcYg4`HvHa+J_-g`u~9#cnyK{1>AlNO-UpqBuH7LR(6zzN7muZ=X+|nxRv*Op1P^m2C!y| zS+>|7oRL5N&)=#$1;zC;ug&qGRG(sUqbJs5Hu}yh7|G!C1n{Dq8uHStMvb6sgV3YvJHL`FrJ$-{nSuUHfw;;nVhD;Aa76T^}paLL%0yxgm(GlP{?_oE=*agFqpcn$RHpuvf#>UDD3ys~}G>D+k z;RhGNbk}+B!G8l?Wu1baV9GZh&gna<;+>@geR0;~Pj@`NUZRtNYfpkMx5qQkt7|~F zguUK_ZqxA9k-htW|E&KC_gPs-V^9j{M#AXY-ih6!kHOYR&CEX{!$&GdGsb}5VU$=4 z6k}5MuMvoWy3HAa+P2bgjSb&|(u$_x8%W`!h#zHUX7%Fpy}&@v{nuZ`jpW9(uuG_` z`v`aqCnaj;5CGSJZ{9OD2F;L)kf>&6GR^~{2}=gS2%OEZ?|@+lSGoA0)_;Bt0%*8) zxYT9+03G{i4aTER+_PVPX1ej!4L;+$F`OK_^z`cLa7)VnN7Q=&a=o_yd{I_fIO?w^%YpDPF7%A z6&ejgFG4Q$xX}yV0`Z3$84*(+ozc(@cXt=TnFm`GfQ|r`3{an8NEdML!O570yb&bc zAm9c5pSEG@$osYBA1t}` z`|s<+qiKof2XM(pafpEzrha^#o}octPtW4&qb{obBYgnlGXkT3(8Y zj^-uM7C&SFv9Xz+pNA3Tn4BD7t7-wdNX>L*h2*`zbH5C{HSOLIZRliqi7y-4a_Dq8 z`s!z?6WDmO$3>EUw7X#EeWLrsMlg}>WA3vXz;AK=!~pjxfBfxo%=PL_S(k#r)`Fofj&nZvB3k0XIGjw55U>{EX}!__Q5}Cyrn^*%6reH9qmz*r*rLx3P_(p3>W$SohWi7e+d{~3`!=CyKtwQSG55CY>D?^u%)`_ zKw-|hRRRys?4W{6%d#edr!FWkP&&-I`li){Q%x}cBu|$n8+$+vkgS%za-9*BJm~HY zl7vF-@VWFkO1}Ak2VdpguV?p}hSuu^V}Ag?k+=V8U{pKv`r?bI4}n0Ro1lH`L`ik! zXjZiT#~vSD3egWs*L^6z)ZtzO#7C$~LoXnKjZ)#UfAisNjQyA!y);u@^xWgcLB6ib z-lWT%zDq(}T%vxB|9oyL;Mk~VJxz?%UiPH)`8o8apy0u?$E89!`7DyA3T52_k_)WL z&|jv*u1B7noScg2R5LPwVe8?vVoa+vU-#z|zjVZcgL7Ue!MPn?=7V`{&du ztAMjSyO&!1$jME+lYq!GvgRw}__XAmTK&_jjwX=*amYyNO-ad3k;zGj5YiF5q69(b z6@`~Fj=2m3+hNth|502Qd3^I5!I{^?&GQE-;o230Hn?enYk1dm5-eVw>gD#zW$V@M z>YPy8^nAWw_1z05qw|X9t6YFD5!7@LsjX`Ev%G!^zw!&I$)TL-DTcKH;woj`RoHG1 zp3@`u5JixeGcztz?hXzG1r82mRYcTzdu-un(fgR%xtC)^gYC0-7F5ywNfj3my$}ln!=;3I!0iUL2pn`HyXYG*N-NHb@uozi&h5R@EzJrA821JV@k{3lt$ce zu#}GKY6KT?9#0^dMUpcwOBnGcz0>Y^jRYUqS??`1H#)vrB^zMEjoZ&{Fb}Lqcw#jp z$SFPq0-U2Kj5R)G2A2}D@(qS_raoZr)B9hK;~WuU2D*4`c2?3uKum@Jshz?s(glbGJdXm04~aR!~zdQNQumACWj7MV%-JU*Ke z_qEG94=JHT@q$Cyo#!%W{VMU9Ceotv%}D^rr;L6F??QO2pAr6Mwgy!mc#jzycoX%7 zQ9d&1I)=<$C#SlbN;-!}fxd!p*=Q<7rlxq)H3kXeWRfy`=04t_U8QeCP9$9CYN1s=)e)MsBpuJ&P`RpE(NoK9XBj} zB;>okn&z6EdhQabZ^n2+m$OUp$CRQJmx_P~N=i1#GnJYhfBu}+7yo`>ZrZ6b@qAmM zia#Sa7Y%R2D~xrs9ID=-UXDDzm!FOhINyG6G{jB-s2WZaLz0Drr0?6Xgr2EYO#S=G zKeK0^^RUk*Z$8&k*=VbMF{~fzsEH{jBE7{vlRSZqZlg@NK@(xvmnKO}Y`&EbABjz4 z#Il?^=Q62p;P~#`kJ)F9xXMtw<`cr z`zK(v11^34yTf=HVlU45E{q6D>rrcS9Z9zj=^l}!C74-3GbB#p11>u zC_L0sFsHHrlC+x!2WO3YS2{(Kg*6v;@mN)g@JwDH)LilfH@0ASDR$UwIM2znmfJnw z=KdgHXXoepqc+MUZ*Eq19@(@bIe0LZtXl=@1zpYIX6Y)QLyU9t!mJUS!&_;hqP1@S z!hZ0i<~I+2ji@q(fz7@bIhwyn1m(+Ap{>g4W}scKL1LsW`htmzULAA{e`h7Tgf7U< zhoPRRf=uIc_SakfT_OHDl_l2aOC=-t%jGPIvVHZm52+PEn}2_K&1`sgOR%^pY5t~n z$jV)XgeAPae~Vw6jS2u@U)_sq6~mBBi}D$Z@|oUsZTo->A#yVKPAdGNYW>Lc|%;Udq5HQUw;NJvMfz& ztALN{!KY@~Z5Q|D?&F2EX{ROQqk?XT&6s9BS)&`Q_@i;O>OT?q*EegxUp>?%W8u0Hl--O%rDFOH>zy!TAa``_NVMm^(y8MxatcG2#XhV z+a3@lItksBsAk1qqWivJl-DnxOcW1AAg(bk6A75yy>PINDH2`*4%op6A$?atvXs``hsM ztJsDb+aZ2z5Q>R<;?31YzVB#mobHPDKtp{!&wa}KloONoyCz&6PE$hVX&Sb}vw}gE zyoi7^yZ&uML8c;|--GyK#tO2$!U-AZc`eD3{e#E#<7l0v`(awcL^rvwzR^_(y&o6X z@|e2^x;3$9V>Nk9S9gcGUWfKTEP+MZT1LNSUK)IdkkL?*N#RBiP+n30Fq8Q2G_I(&7O3yyZNqctU3h?<(-1iTrK5J!_E4Ok3SSkZ0sZ|Ij8=VGu`lMgB znd0nw%1L$=ZLCG(u_G6)^Xn0D=eDjMe*4#tQv8LPnNcui9W3*sg__tFMb0hg{_33I zaeCzn7><{VFV@0P9Q0%W-pVB!j3X=1nGC$+8z=64;knfJAKW2}hiAy1xkR-=^o%xF zCaK>0I?2b7XnmKc=)K>{wm)+FV#W=#Q&voX%i2JKfR{8w=dTf&IE9~NynIQX+-SQ2 zuAr}>ci}k09*0Kvq^@psfuEm`4`9~ZkJa(2KWHeY(xHjnbB%0v*|(WD)Nss5PoJM` za-BcUfWH*vz7jpk1TaDGbzY7iUh}6NDWLUDainnd-J8lsC@{+&mKC?oYw@tCaiHi9 zV?oJ{y8p2eT7;1?(h~k2vF=~HtN#AKI z?~$cDLFIy+!e>~!$8Q}iRc73`#~bUUxjKUjx(wl4Y=WE{p$0}?Gk!_L9$|i&`&6wD zisviW@G_%cYB@G{lKse;>;-Ey*=V*USLa+fKD_6KzfX#J2pNT_lZ)$*c{yHN!hCie zBTiA#NgHkTjd$}tm9Wv^vjLT~(ce6c#;AAg@ZrCrtMS^JG zi!Yo0SNqqQRaq9HCSR8^(@S2LXzpK{n4mfDeo2xVF`onJ0i(`F^F|+2AZ&Ot`<(p3 zW}v&s_%O?Oe6~`mpm~SMK{aWmphiQ2)ARedp34pOTe7`fh5e?XgHpS{S9{;S-gsP! zJ|nU2Eh~QKL#uwg3Tjy2&e|;)^LHJA|FJ|of>5TUxpu&=fy3)6gZhttTgUmFF74g9 zRr`Oj04qW9FuVebSERm>Tilqepqc!b>wsc{#rHp#F`x&Ecag}`@Sg;32LDe9h%tM- zj!YfhMFIdt<5y{9y|m47sR)!ke{=V(uVXRohV}K$#xv~U+D4rFLuMrg14acUg<~c5 zGxWt4Bkx)c*DUKY^5jP$lgI=M!vLtp!+Id=9J!*Sxe?9hVm}tQ0via0& z+g@61d##Hz0-OiG+nZ8EGy6$H?X0*`@FsPhcfWXkef{|cng$v+w#J9Png#E=@W{`vXmP-2xZ8`4ak=t#TmR=wROovT$M zIlY4JG7*oNA2TzPohLf4s+AK+&=|SoQ zJjUIo`w3)oXy$*kb$8ozH83e%W_w=bpQJ=gN6~HGKGf5^!(^Go_Z;*p&>4=(FY^Y zyoayQZrDa6i-`t^2lv{ID_5Evlob@{fC8$Hk%Uvzs-amU=8#676$&_!x? z;?1Q$H0?{twKc3O5qIKRZh#8}Y7WMkHbXd}PX!KziV4vN**BJo>`L#Q%%goW_mAYl zl59(M&h1!Ch(3cD#R8Tbpvlb_u9{vu&u!;(2pb+8#rF#o$K8^q$9EHT>GkwOVHlnBxrAvhC3WobVPg_v@BmO-XIrA5BJX-hi_rR*$M)pG7hFT{{}vJDA2nzUmsr z4#j_~4!s%}&wQsI0r_N63b?UfUXvOX!lIk7kh}8{6{;>(+DHOR$(d4`IosED*P3uSd zsWu3cN?FCOhI(~^)B<2ll{&skgx1H30gg7@pZDp3{S*FDkdpW$BA={MIFb~{h`R$6 zxO#O9P|%vU#_83OsT_vVeWa;a1`zqR(FyQQ)o^b2-?CG~xg{hLUvM_gEF$ngy2<@? zwJY#D>5cKw2xNhWAin}6o6nykT(Hp4mStUZeN0S5MDlPhkuvUx_|YM9hu*{FmJBmd zGt5aZO@srGwyF4afME63XJ;=Q#v>(NoiO>{@WTMMzMe&Am7^^|j3QZ)G4DmoKd1TU z4a7-vO#4eqBX}rL)<>>ey~m^zA;hVsWiS%(OoV7=BqnAq1}NmNq=wkkNkmxV2G;>7 zg4;m=xfG9^z2`k4*)S5qrtcI6*nU=sE~iNX$>U9Fc(ps2IEu#l!`*^Y`<^vy;SCZ^ zP$?*H>C3{Z0RcKvx=8I3(lE|KzrYKl*{SaOd@BT74Ie{xbi2L`>VxtfTG7h`eOgeU zIP-zBk$W`JWhT)jB~icZU04{U&(w^eApnv;&4S-ft7Sj)74YF6`T7~q92g632hUC; z@2;$~b^*lDuUhytaBX#{M4FeCmNqw6Ke%Z8yp!RUg+GhTcD3(bMz}he3JUS}gztKT zSwVRr50bdC<7)Ro@SYLt{N+Uqil9L>ah0}x)T7`LNyc}{XLr?D#RFZ?-kD>`?wAp)z^oDpUhG#9-~ang~HW=l^_L~ z9#fp?$j?z!!`{K|zzc~nK~A+NTgx_on!$``Jb!9vFQU|3@CU%?oasW8+UXKYD63-G}`&Q?H6}Bi^mhR_kG-u`{w{q3_Y({k7>=xRI@sqX&{vz<0iQ5|@D9Oo=*IaIVb%(Qg| z3m|~Cv0htm2xViwc!JP%JvJ=t_ys^7&U*T6%wGyJnF=z1ypLv&%w6>uHdVh3X)X5Z zM}w!XSJphWUf2B{jrX2v-nb#xN0` zwtmogDOr)iFC+2U;wQLx_6sfbE7uLl%F@DpMv~G`oEWUwo;EnVgA(DIWB~AgD5Aag zc>wZPB$IwWz4G9i%=NFXZ+^W(AD2PtapfM)KIbaiv?qW8La+SCv?Z3SC^>O5yRKD7 zn$)@(e6Emhh8=HC3Y$0bEIM?#;i0?tvm`r6TFd+wq#Ls9E;#coozF;BwQR-TzsSIa z2X3d1$hk@5VPAfa=!45b6@yi-!)#rPm+IC`?kQUn8ZMko8Ebh9$ z$Op#gLq+olFY}AXFP2ls(Wg>(HvKHarI6B7oDhGyHH=y2|M`_kuDJg;rDy{{g*#Hj zymoc~G8#!`IjvDot3e`4)6SLZl;YjVkJtMU)TG@*8FGgWCGk%H^*}|hZzsR!k#3|y zLB0Q1#^TFTRPwZ#vLDMoeGMno#!%N}Q9Pl`U`MC*!muEE$Tt1?=L(v1V)hnc63n^K zFv{;kx??Ua&WBg#i|;?DZV{d5cI-6DbS50E7+ksy6}R};$T0DTW=H?bLg}Nz$AkoQZMs^rtR4 z5j?zz(*Jk-yty)RWCZoclaou{1utJI^!|HNsM6Ql`cKyxg{TMp0cJxaTShPO2dg%h zT)po@P#ELyyRSjM6V2}}u%%~rEp~j+fqi!NS&srZ0+e-0UCABFXuduPVAu4)C4<#I zm(tyA$@UJaVz$+pY~085U5sdW_sZ47H5s{qLRGa*O;J~qzr6W3syzEjoy#*dn(y%{ zEVCZJeLN#C>K^B@JV9T$!-q2KqzB?oP37Hhbv9ydf_e;Us73C_sF4n(XGqjQDyN15 zPM*^BE#d^V3J0z*Jq8Ay8@d|2L$CHXAdEw7X)vY705k{mgSZigSzd-8-!jl5e3P9F zHv&98Gn2T#zR@Ym2G&S5;6(zvHKSDTVC*;#9nQrREVSZx&6Ss&wR7^b@B^LZ!igM- z9LoaJ-sJqG##%1>6_8H4x1sv!izT_Vto@=H}R8*YH>0;`+wVcLv2U5Rc zUI)$O*GT%>)2gm|Aug6Vqc=*wHctvOZr;@ma7r5!(T#oswJkRKrW&MW^iyhHUurL0 zjI_g)r`OM{|#B_9`uaoz`|`_do($TkF^zM8AWp6I9r_BLLO*(xc? z+DnR*|0mww{xCGVimcB>MLyfKACK#fIy-A?+iK7a!X-=*F1@VP2Hm*PYgDoi4X3Q* z#7#eEQcj+`e;?GPbkS6n@powoXej6KCnI<*!M=t0_d-V*sI54$|;B$z6Q1#jxxE$w~-cUT_LXlkEX83_w6klL% ztZ?VFsfTFsm=r`j#B4r1G=51by+)ZzsQ@L?{iG*5H6x5|a?|?1i~qM=!Ih}Z>*jZr zI(`@cexf%qs>iIaH-v9W9F0hvdjpGn^SIp2ofG6vQjmS)ELdONBkSvqZ*>Fnq3&3@ zXEik#XNsHo9pp{PW_zCb&+NYFf3U`)9Xi*pgxXpPzv@*AzcZ69B2DDe_McUB77Ig<@+j?4L_UNM&8*kZz=o= z3K9}tzb-D$$ROX%l9hypT@ZoaxPdDtgfE`(HJ%fJ9nEj+o@m6?+2flJNMpPBjCk_+ zN(892sr5g@g$$aT2H{%jv4e?SahvQ#`W`>cKiwG_5*~2w+HdGp1gF2fU9{LB@vH!) zz+hWjTZc`ZpOCZY=wE}Ey=~e{Z$8WHWRdfY2LN*pn|=@6C&;erN2jESzM*_EM30Q( z(pIf$TbErUPnIg3P4&$x(%?Eh)?iIm=B2MR(vt8Os2cYdK#5gzAtjZpkBC}>Zd+H^ zHEo=;Wbx*YKmJ(EaL;Fs4t5$RUPqG=H{P}pySeVLhCb25&A|)^9rpJcdwGm>fYXpz zxF=TnBA5tk|sN0aYOH z9QO_p=Cq8`yXn(S5pc>DmW*EH*V+7VdhQ^w+_9wed#&Mp*Kz`j_NkJ!aC4p<)Qa@r@LxPbHDBnIT;fZ70uU* zmD;=iCRWpW@T}Yo?JkOUHE159S|JoAV0JGHj|A#(KVGn52>8s+?7!uZNr99J4QnXe zp@IVWWbouO5`eLoOyy8!MS%zX;)!47y}G_}K9W=ycwa<2Lwptve!oWcvw^~ljB|`X z#||3Dd0Z})Ob7u+v)uJZLdgaM!0>`xeT zq3{a}gWz@g=uyj=2|7ZJtMG)pzkW2p1|$a;juoy>H1t6e0`S}^9^SNE4?n-d7u4sE z1Bh1|0-#>5LWs;Tjb>ZFF5wL`S2}#E2r?2_)JF!wS<1Ce3AILXUo%E z4gaxVq7fbz#cC7|eubD5A+Q$x#dA09DRRuCk^n#*m@?bhF9yU;@?E_1ETykLzjZUa z;9hJ$?rAop-P;4g3d-Ce$xUibDxuWw@+K%~)zQJs2%R#VbMfs^>!llye*b!F+y_Hor?ET#HQ{+n?BRhG7vt8~#w?*Js*^ zgj{Jox_GIofog%v8bB`F&?YN1A0Mo}^2e$P@bH0O2SEBF9?=EfxpRl;g1ghXr{Sz2N zN99EdEiD1;35PBqA0JwU+S;Hy1~7J+$v5`tAx-e?Ozk*pDApbXL{G>e{!HIjc15Kg z8d?Y5oV*pmjH>y&^P5SwJ=9mF;Exp8IP(T{-l(|@UcAE3t}|>5vJTcPC$>$A1kVT(3lbgtbId7A z=9JTN#nP`OiPPnscVDT08p?`bYYX#JPoGI1QLm>6t$)a>U0ChxQUBrP;`{KlWK(4e zeL~#MQn4%r;9{_vWlm88iW}m$sjBqm8GZ*hP67JKYyeS%0|d6c`QpDtwQBm2R*hnW zkitqXR}d^ZNQMrEdKj+j$JxnV7J~u)^<8J^!GP970Ahzu-;9H?l(q^tLJvZ0V##Yg zqSXp%UIBolz=OtoXnba2c>?Tkx`+LL0g}|RB+Oq}wH}3LoMAl4tFg+-^|y7RGNJRn z7oPD*W{Yr@8Izdj<>X+bX_dW{rOb0<;*_I(XI#iOvPJe zFjkNVzzl%@;78>Fz%9{EE4KvqmP9r-_+{bs*VoXrnyjpBlkU82A7C)R{3@8Sg8mGc zTYYr7r?0QgG*7^!HUN}@H0VV~!@kb;pa!q-T-)64Qv7cp7Smaoj8eMR{<&gZ9rv8$xr!0{&MWF?5_>o{yIDHaxeM|)V>=UKCe$o1u#l)Hh}ZH zNW>DGxmXGyXK)yy4{26yJ+))g=fNTai3l+(kR;v|aXUXfZBGiFG8{-E`f^cRaJ#)@ zofS5-y*|vQRYIIZoQ(hF&_I#n!k_$67&L+LQfEK_3W64Oz@ma_yLjbP9_O7O9!>+R zfX0Gt0PIA?Q{VZ81%&A#Aiy9&0H8R)YLS+f_7mFL#eK=_d*7W!gJbM^wb^<={M?-- zkkvpXIrT*(S>q6z8!?9}%E`&WNHaW}06+lvqB^KfWI!RKTl6C_cw^1j%VLh_0zSsk z5!*p^L0W2;t?WMSLS?NItxSNsj1M!rJ=Xnoi=Ii~vm68xe!%Y2Z34}dWW_Vb z=&k@tnymmO$k8!C2qN&Xgx#3|fv}GvufgfwS7^&KLDhCGyM|D!N@6}+e$FRly8|lF zO9cuJr0BD~qZm^Q_yTR<5wJZKl|WC;ED=WvxHIs4{R-XQH!t^Qjq05`PsH{dy$a34 zpT*Ao6B96xNqt@HnUPQoYxt8+ZTXHJh@tw<64kNpZ{`X(>YXu%e&{G`rc~unv;z>rHNia$LmWXwa3ZAk?qH!DDn?wv1aA_&VEGY>S zj4v>>1xDZRf#?P2rmg=fnHR(>KjIdP@kpY8AkW7Krf?x?g`^E;cth*^=p8)JE<+v_ zZ9RHaVE7pdR_MZP)4NiGM{w<$c04G6v^3-9FCgcUGF8y14-Mdhq3-|F8=yErbzY+M z-s=7PR&4LO6xnR7-INDHra^o6!KCkkmHP!!Uh^S(p}Rm2g80mETH=wE$Ta~EItUzT zSQJK4l@k$x)^Sf=?x8|FyV64iv4FyRpj44seS;cpxf{*60PD-V}m6&E+_D(^DXEH+VyK>sgHD7WAh8*J_e@t7dtP#L8IQAf! zkX&Cck_0P>o`!5aJUlQeYrOvhofrtE1~H#w`aKncFql23!fuyS`NA%8y%G0%rl=qR$d%4V?4!KJx z6~*yFPOay$X|paVDY8HtBvMnnGe9DhnA$c!W#b^9oELOM_|5N2Jee^au^k9)|Cevm zp)|X>8{{Qp8VqT6B+g?>2iIm*t0I0&+yd7w1L(un*2cyH0=%zvw5>La&@123fa*D5 z`uye_!#pL1D@CGeJ+J;bekvfq8ufJ0MMI(clfpAfxMb?P2RXBLg(Cfn;$ zz^@0&7w`}yZL9&W+C)R4t-xmUk5v`v<{#wyh7QWKNMU@BUxeSD0^lMr=PfPE6x7s* z?r&Q{zpVyfdx$8s&GK4;#GzKSL;e*&xB}q*YvkmJ4ijj85)c2$ZshmbMfIC1%#ZIP z!v}eB0B%v~V&es}O`k!THz&@@o6&e4nipYaTFkO+2_x-t;uxPTreU(p22=NPa&5Zm zdsuZVe;DXig1|q%tF2Af(E%*s8CnbszWmvS9SPFN+-@V^_0VPIG*-Rc-f4?229rFJ zHh2&e(?vKGv)FW#Lsm2pVEJS~|9~<2&$ncI`}^17npD($z6C?`*g$zq+cF~*q^<-) z!vS*FknU^(I@AjV1KOUKY30o_FsX`turxp&_^d5j%9a zXJ&w<4@pxDSB^Z zKKy@AF3O|Zhx{MR*Uai>Bd~4}JI&%bpb2L{U}kFO3#@G~a>msCZX|)Wd;EalM9OKU z*QEipT>Y0ZIXbuQOg)J;7Y-ZP+b1j1fW!=>#}Zc8fd^I}a!H2U@)=^g*qC?pqg4R- ztrRbXQkXVn-j1fZhgkZ}P1;GfE=;{Y#`rOM92G7MmN z1CEOVizR=Hds@`~u2kZAt#WT}7D*vxiEy+%MBxRf37Xc~_Rjx}%hdeszIBm&Sft)D zACBVdfXq=C@J0E`i4(#%&K~=<4-kCV-KJ`7S@tg0Cml)LN}iO291OVl3s;KZ4Kc_1 zNg2ZK2gh2s%B#Jj19L|XFI00`8kjDUXMNos*1Si;UA&FBH;g8~y*hd4@jt8D z48m_tcnpS=1$DKd0Or3(UoDdpA(A^bZLP#iaOT2sqfYD^tl&9knpj&a3P#(Ro8ORD zz=Xa3w!>>rPv|Iu_)V`N3`Wm@BzWHb6kZBA6UoWM95Zau=mhXc20EQLl@y^L*jQD2 zAf7%L2Iau?lrjGRu%fDH<+J2plWT+ep2p7*@RtKP#8y`l52gy zN0!@n3a_?idItREu|0|CdgrH!IWU5)1LQkRlK}T?^pIn(VQcW`co5Sc#my4Oxz{Eo zQpnRsPs#G%k+4j>sDg=?!y#z*7q|P3kYD@Zwq@*!ueZ1P)3JA1uG}=3F-V~K8_p%4 zuI#+lB90~{wKMA`S0Oq{PNoII?%G;U&^9%N8e&b4R3KrYdtXWEc6&RzDopZ7&B5wd zlVy?m$HOa0S4v%3ad5bB{e@vla_IFCc7Cq3FE_68Sw8E_m75Y?cs;(Xr}y_{kkdpz z_#@ckn3Y=0$ra3&_sPm+I4tfxeFl^6{$L8FIL431y^w`i<|3K*uEZN zKk<|-lP`;`^PW2wp?L$Atvl@Ymi}}xm&HTuQl7$heY%8yI^aK|rHVPiurLZgQoaFE zz?9?UOaK%-2#Lx6ZdCysohG9`Krw@2z&6-XY>?HcQ{omB|MeTulF~IctHFW&Kk>*j zRKo1+U_5}S1zn-1qXQk={N>FXQGl*@-sQ9;q42ZNcYa>8C`sYBc1eQK@4z=IfeuScb>);g1IX`)vX=&qNLT^0IB1D|y}hk3*qye=gIn1Hnh!37omr7IwS2=+|6mo#}Y88AQiCwbi@ik{#htCQ6R*!8(%IVaz||6P;1 zNWyp?OhoUGu&_26BSRP+Yl@_yVAHe78u^WXi8P}ca7Q}hual1a$P zyB0UoGc)VPEdltA0SHm-7Nd^j!8J8@%*>`c^Ias7$6xK zFg6AR8&Ar^I9`_`9*mMTspR(X8OZ2NPt(%T(bBFzq8hB?Q1vv)^LY=`>=b2q*K)D462jCwp^8vnMc5@dl;o(elZeAXC0e2M& z3$1&02kd&1>K0z$Mo*dcSky59R~Ku^-gx5H<-cBoLeWGPji(#c%q#sU8Q@zsbEn5#zq2~(kv*DJbLyrx6a)edUvZct>*_V0kG|KrV+Ez z(a}g>V|)JlgYa8P$(0qDrHGE;x0i=LkYq&!g9?x(fN3FjdkR~vEzSfa0&+X|3S7r0dY!Bt_G#MrZ34CN27;nOotM}2i}j6Usd}}adr9JohN6dersjYZfH7>?bY7m zb{Dq$ll5HsHaPGa8gxp^oCZ*{hLKD}SX<>|($m`f%}`0aDtu}Nc6x^HHlmp$W*+kR z`kP<{|g+yC>zR@aSj(bG>HUnHgBQ0F<&ZN&p}?WOCFwcz6uJ z$ihY0_K^Hqu*PZRjgZ9PFSXt&d`-8IC z9k!;a-5}Qwv%Lp#B^FiAn*wWKQrs`Z%fCH!@)(Sqjy;g5A>TTl7_m}%TuG@d0lY)B zWbC=d{J8Yh>u0Zk4fTtcLh*3vYd}(E2xop$_U=)f8y>j2hf6MQXlyL79QOpf*R;Jl ziRxJC%D^2r4s6&ERZ&R*@VbOQH*l&H6)~I_fgtS74ReS|AZG|LGic?kB_tI!$r4wy zK8R_qr@6XQ-^tp~%#)5O;jW>mc^bP~1@J|g)K#%ng1^2c?!WI^_Z5g+0_Cg_J@Jq zuGYFm__UUFbA{_Hnn$fPDw5Ho#s6-bQ~a zh>pQbgL?rH3w#rR0l-v35C-`2kQsA}InghW%8G3Rr^QA8v}&)`Aa-n9gnIJg7@n zF5(xp6GWO;xy~KFgm@xbQXhlYsBl2bhCI*OzBUBpfVV~q6;6wTJ^RI?%_u;*M&|%! zPgJ7;lyqRyDmHokyb>tUsU*%{xDXZ+Vh%r>u3Q%S}6m;D-(*t@dVs5szlY z^{ZJ^Radq6yEcCGRa5$|+-&=MaLdK%d7bfm@qd&sOR>vo{2<CQYq@x{Xi@+i zBkNW_N~2D8c2TIC6=nGO{k`n^E_&z|%m8mm?Rl4n7tc3;Fsqx~uDK}mYp~qaO=n1d zD{rgh0>1P?B&JN#o}QIbed<_F61wN8`hRM?D%aPCpA(8eSk3{;gMbYMi0au;`vAaY z1OQzRwjn16)udaDIdjw*gfE5wYda`nV4c|6DZuUmUj=oEWrKoc$hxlVcBXSV!3xP( zFcI++mne2UW( z4{R30T={eY^tX)q*vt-yT4hbgCI}JK8jbKDJF&M5`O5PG^NB8=e{0%+6<0s0Q9mJA}O+6h4a7*Lq^64-UL`TFlqlszy-cUrY@kk!Hovr z9)RzG-UGZFVyFmma|038^yV&RuT3vI1ME}GVvey1Ag}-WXWq8wi?E*F@l#`CYwJZ( z5&COImtYh+1|J66-<(v@vZ*5N(zNQg;p(^UGchwXvXFvUaZUVQqS%?Amk^!DlPNW? zVr52!boq4o{g4^%zl|}?P3+Kj=*zIv%is8Nj%k16s@!rnOAx;aP$7UtW(*Fj(?jaG z%>wXQ0dsG5#+sKm(g#y&4#x09$%^uHflx>W^MU1RKE@JGJDL=cSv-Vco&N-ySZQ-~g}Hq5^F_B;8@zRt?zNvvkeq zFB?K}AuP=26w|d7<+CWU50K2hWTaH$)MtJxG#j>!Bl0k?=pq397i81(d-8dvK z%qW8EYQ`-s^CJM%TUx;&OtMWeEa5Pbak-~VXw}HsY|7W!uF`4F8E6m+T-Bu;zDhvs zahZxh+{d`@>c8pgN{=l1OdL1(M->J?1w+TjNkV9E+a1V*i5V`y=;G*dTCmd6Dgiuc zIT&L~ro9}7j~064%*(ZEuJLA=5J0H)9dvJhL9hltLVI~fkXnKQL1aQJN0urS72x23 z)&e=9hf)CWGP)oMnO$wO>wuvdy6Kswfh4tRV92|)emIL<5?ZL%s@0tEsx550A zjWa~{=*$xmwujuYG8r@XZ9AvAK15cHK4II33zm#8maVF)f>atfSO$50=%&H7BqinG zTza1!9Sy@?MU(ao~6QS@q5#kc%4OQ_f*CleesMS9$6cQ8_ z>q8*+_2su5bhi6rG1qOUSMG-GY47d%6zml)o=8jI&b{#kBqUy4TI+uTVg?EVEdAW# zA|zQL|0MnJVF>V<0!dq{64a+tp$-6eAa$SJgC zXbt9Z`*Lka9a!vD1vxNft)dUFC9RT`=DqIn<%gi`mqgTNrjtR~QR%@$bI`9>LeNpZWENb7}ouD>!QBL1~j-pb&tL1POm4Xy1So z_2MblF51L2btM%QC76=BEiYa|at07Zfs`3QVZl`fausNTaS}9ha?&Jn1I+T@ z7HO(?u3<^$2uSV+(yM3|zH9*?EAZe{)zq?pzYNI8kHSgQD@B13nQH=_^R3g(@Qe9H z^QW7X_an^95~P_{m*b^;4rVg<){7jAw?@uED6}-yLOF?jbaU0hln1tHvp3 zLUu4Igwv$I;8u`Rd5j2T_j|t2HhqLVxTj|dGzaWtATxkOD)ey_c5>(~b0jayfhlX{ zZ@K(06349WMK3A|C}y2`Q#v;G6`Dj4O9CXeww(q54H2NHI}CaF`7c*xJ6PUs5tl^X5EU-3I6P{j+WP1~9y5asLj`McfIJ zbk@MU5pb{oKoPwmlRuRQ{L{z#BA3M=k6X812XPuIUFc}6;K7~h$(~jq;r1eKmk#Ba zjd3qF0tQdW=>BVRlJFr`Tr(dzvAc_O*qc{|fv1mNdodjTOla4E{5h!HeV4&_so=dS zaUoU#fq9Tl$X@02nSdg?{4d&B&&>mUoZ|EZaP?OVsEc@baV?>fDaDe zAjdLHy0dHdd2t$_^YRZo5&{n%D_-8GI$K<1Te)zzK;gij%n>e~*$U1UT;raeth_uZ z!#;|G+CL|>4Cq$f*>=;yasK%}9Ch{J;&)))IrcgzXi;6^Q0&;DK!PfUx9v8SQWfD7 z@J0^%79=JY7;L(6;1yPzc^PG-UWw!C?Clk%eK--rl4TBBlW( z(xZ2R?L``T5BwzY&aQmd=$e}Bd&9C31L|vUYkj3J(*WMEjr82Pzm?xHy*>{y68Kyy z)7HGuI|l{0(THlXtoz$UR#;ITdc#xr;Gn=-X+1v0tV0FY zm$72QAJrq^XvjJxDsFU ze@Fw~I26+dzNVxgb(E5FZrhG()jlDidQ#e^HX$J-?5UxrdI5mSCnTiIgwvfDA$Qq| zfo_zcp}R%en_J7e5VYSH7YBz@g68v^8_l!p&;S9dvFimpSPY2jAYRs1Y_3?#%iLCA`r%!+G?A#ju{fT^FE5=!TMVGBPfB8T)!~z4ao5`wZxg&`-wiveyk*j*!YREAs<$jvCoIJaqL}?N{Kl-3Y#h zmV&}VY0_;uC75khl5XJYhmOYixjCrDKiAiXnr`j5CHVf(viLH#YnBHCNdD4eoYG+k zAw2|(0GWIRRoezL-gW7{?0&nQmvePciC%MsYjWbe^m$tPK9SP`B^qGXGIUB7pgj)4 z>sy~|x}og4G%VsFT9)auPHzU7T!(*sk5iP>fVrSqN_cS7Vp!P73v6#;f79nTg#k=3 zwF9VKP!s~Lyt%m?z@Cei4BA;*j(|VBu#f?8K5$^D=U@frw^VS4(Q^1rRab`nZol2W z#`}MSqWALMQ}H?2t((_(05BJbGAR5m`c|!@{u+4ro1o^P`P2LtaF!RrxDuS5iK!{n z!@%f6fCC+D12AKXq#-4xfA8dqy~1{3vi3lG`|)l5996V`Fdw`a4p#<%YX$#1@%mO7 zGgeDA^Lf{GGxV8*I=4_$z7?HW6o2Y``VUa%LQjgiAPPTJ(heL*wU=-@@^kg(d& zkX}m_Y%xBhC5!)CL)5!W+BKt|J8^N|AN`tA@w=3i(lzY5v}QYUGcE!n5LwXLbN=A#9HA_n$QplkzGKJS>hW`D{Cl81S+>n28 zmGbIp>ye{yH1)BIN$$Mg#T#oA5vzf40fFeOa%3bHUJ_i-g?`+m8o!JA20VJ-$_ynmB8TRA`QSZOoYpe6v9Cf_@8$Gr~i+t?||!h@87;1s}Kz#DMZqe zhEgdsXs9%`)7I9Kwn8+FwzgXP&|)}ja7r&Lv%DE5oq}$x59%Xs(KLbjyxu} zJa|p5(4Z#r%<%EQWPeke)0?6mYky8`>~Ime9~0eTZtM2pdrqMArAu|= z2N;%=>;!W)E?qjn`2N$;%a;!^*t&5Ld-(7va)q6gF8Y5d{>1KI^!%khvJm#W4>G=M zU(c^(osV2OwsYrBNQL8i9@ZGZqu(0&hBUVMF4mDJfL-2}! z?q4zFng~p`9GzdQ({ZX1whjN=XFS~d>+#L=4lXWiWdA_m8bREcui>6^D5q`SLtM8L z)H%|qj+rzy03LFR-Jl=RB^+=AY*zq z?`Z(l0Nj8=IUkw0?YlYntlMpOd%TQy~Yae%&KBg6@a& z?{WS|+k}PrqBhn`&^L>}jDI?m|G78xjJYbuS1wL72XLSH^Fw)pd1eMCZ=W6|C+{yD zv~SMR(JIW)x~6ukSp*#-wzjs9!ZvTb1?_<~KYf%i%?b%g05Cc`8&I7&!Ur8}k;2Ru zqnG)u)>65snZ8-%<-LW7yUDW{RaHQYu-z>vxrj_V78a&-_6#`uv}|2n;5>k=h5Y z(z7ek65e^HC3g2^!_Rug-rqXfugWigLJ2&{FaP_@J;n}?RDB~JJIzLTw!$XsVMP-q zI$`QzVqw-e`ju{VE874Y*`$*o(_Sb2|P@s&tElMk=- z&CH~wy)J-S(VePX`xs%gVXcT(q2&wjxKZH`T`8TD%OafG+K|{P5i~JXWhvvgN=pGz zGj}{uHQmzkRprNlXs=Y(@KcY@ik{d-cL+)|E-}^OLJZz_93op4O-uznAq`dH7Ex2O z+q2|t-@zw`^GaArGhdWkVU>A^m_gZL^-tkcPQHB`oLor=xpNj~G*Ph@ot*UiS7PFV zwCd~Y9{SHrp+flD%f}}&@|x;ZRGrXwY?YjdDZ2h9T9AQ(g5rtDty%TUmrw4c;k|z0 z(j~R7a%a}=0p*$#Y%6OkM=w4)jQhF4ccX}oeyjl-&AxqS?cYb9ndyQIK$2W`g0896 zqX@baCl1hYJFb^H4a+qeJ$rWDHZX?a@slYBvVW9Vb2WZA@7o7Y_@YxzC5};R%dDa5 zI>uHcLQkbTwGu0r&9lJQm$z+?h|qV7*8cdX?iIleFf((G?d(Kxcx7Hh0mohNYYV40 zTe|@Xhk$`&ud$?L?PE?YHN_mO{#cRAkMj%ei*a*r-nNS_DD;f=KnUxH(Xy3ehu1cl zN2l7_`kMq>hkw$?Ww?a;?-z*{R#Pi9?<8)cB3H?`Rnf;s>iFa>IlbcRixuU)8ij>D zgX1gDtiz97zYZIk6DOWU849(I>}L)OFL93=H)cF{WEUaH_qm@%e{$RKaw_FQ><^o< zs-FS$Zd}*5O!4LAMW1gTh&C@Y?=MUOjjB?5A4l<*NOVqWg|kYMQf~C-K48==Fei+? zP&7Lv=*YI$&sSQSj^{WFBsS=HbHBWLD0z78sCmlwY%BX*g(BG#ub7pG&IrY=fBsK2kB`RS+^%~YE`nn z4*+Q_J292^kscnP&BDGK}p}l$Co@;Lp&lN z-Y7^tz$5Nfl$!s#`>k6kYR8KhQm8~sGNCwr}~ zYTi(m?RN)n7nO^FZ1>AN&azvoDiF4`>=jvdQ~nSI`tVkiDR0&@;~?$UDH{AKTjkKm z*(OVSuI9* zqV#W_Z=b@k{pn~~S;x$x3+XLq`)rYoy^Y>n2ep{+%1#kZPQrl({B}Ru;p4#6bZPkS z1MkXfv#~Lb8p~d*BCGzNpbVz`>UnbdQ7x6WK~NeX$q)3cYSz6oH4cZZ%(h z*GvkMEfG(-cg6Wl319KQy@BF@W5!8 zSQ!0YY?5bYY+M74&qId~uk}#G9e97(CD_`z>%UiVPq*duow$Vduu)rC^&Pt%ZwXaO zp}^ia4hQY;x3y6dHZOoF9uk_$j%Up!H$J%D-s8Dbz?KF-XyxKs7X!HsAp|dA-In0v zgU+yJYMQx)Yo_%IUUXM^w14hdvCR~G1LJ?(1+IMJ)YH1tvR{3{)Z;77eX%$FHdS+= zndKB0pM>+43LAXpkW#>cWI2aEmOtS9;)#JD=lC{ zy3PB8UB4r$h}sRhxLG=7Uk06qfq)z+b84QNN<|qEzz2t$H}C9>Jrh8e-3~ukYTw`1 z$K3+mVjIIXv~fP>aP!bnv#2w@j{6lrwD8f6F@x? z8895C-9J*qDT2Or)j@;G>1tv8=cJ&ZiC?^=`Yypdi#~3K?)Sm;gO}nHa;@)AKBP~ z0Am8QwaPDbJJy$c$oWCh@Rw1^kI^{Q;?MdrA0~Huk1AF<)K-T))~v$0^lss=(LaFV zHJS%V2E$M35Z<-gr_e_vp!+qDV<2cjNm`_Dpd~n zpi2N?RsYb?EtHv1Jb-ZuyeyE@z+~6Wvc5emXCvI+N8YT=VYkFKU)fk-QP>Rcb@{&Z z^?Nk;Cf5kyEbNGFDwe{WhMa2wk{&FvuZ7L%rI+#j1OgkG9Z^zss#?>@QNNPjnOjzd zl8kJ2_d==@6_3FNg`xEsx4@tZ75kTIf;iHw;j?2g%@mk2 zf70OPjTUKKUC^$yT>D)%c`wV9bTTz|TSl4Lr> z;{SU~0?aHd01UCSOyuBw6GJp7{{eF|)e7FpIg5Il!`HhTUB8WZ0i=f=#vtbzdcSxl z8%NUPd!`h7f}41R%+Cv0&xrH$-~M(dN?_`iJCXCi1Dl41253oa5DJM(Iq-nFy-Gc{9OGOnkf64gAQorDTl%}9k5ecgrGzf+s{)d2%`nug^KF1!)6v`BH%PsoW z?Ycjr!om(^qJ-ltGTe>s8PTR9*|2}lz+nN;D$F!AyTLs{(&->X^pt_dC(Uv0lWabc z8xND2nJWz~kU5~jlSfC}+ITS9+T^2e4-!|Go4fHer>Au_CRvfh(S8!l__0hn`LvFI z(IAmaT`HdGKCuqB?zU$Rmd473pCoETgVq*B{qhP5BI)f@H;Vevwze)}50rXXJBHKj z_PMXVX6-vjqqMYvG(d(^fuEJEY-5h0!Y+D#(x(1#CM+-zJ4kRaLF*vKx6XgKPAm#~ z2^?uW|F`+O=NLb9m$yIrp7;-GzuP9e66t+}a*NjyvU~a`cZusC*iqfG0GA)$ZFVEC;S=^V<}5cstOQ3hQbl<|$k@ho{Iij|wy# zu#>xcQF;lm`%@X&1{fatM7$vh56_zNkgBg56}k$j*UsJAcotBdN>E~LmPu~MQ$d*z zj|a~rCN|c*s2@c&?|%pJq=}K06?bhN0O6A{DeKVHq`?)O_vx9HZXdNNDU{N?)dIGMs=i$=f*+2ynEbF%jmCIxi@ zDzgFMOM1@e`=Ofv3m_ErQ0yDG6GRa#_6#@o(eOq(z!+iv{eLsZaGhI{G^C|B=8CFn zHgIWK*8PQ%kpTgM_8-q0zVTl8=*oZz%~TdrP!^JxA5xIt@hZRLs$N<;SEmMIrE|3z z&Eyr&TTBC1WHf&3+!O7(u$iKYTl!S^nX}JAuRK2Df9%jFG7*~(-o#@p*C(&~dHB=c zA3T5Pww=oXY6EU(dA$H4f*%Ix&%S;8W@l#+5K#HVqs27wF^2d2c^f3kY@y)LCbr(_ z>Fp(*u{dULHge*vVPu;(hNY!}Ty9CYM|u1CS@qwV6S|d=@L9Df%1O8XiItX4gGQ+2 zZpRMA?zSH_f5hWxT%?cw6ZgyMo1RP7Uc0(q?D${H^qwCa1ZKocC6a?Xr>;yne4TW7 ze8}nXz%-abU_0GCMf~!gc$h?ecgpdrdQBZhS|}82?;oHGZM#D zmliF1zV#SBIS0bjviq>){<;3{wzhVf{Z7N~T*p`@h?6~gq}C(AWxe_PgzmuKX?wBk zlM*A!o`;7Q1O<%(7joP?S}#=F3|vp(9hDKVc)|OkU9fHHQ2*?qHUE#?!1`STK(2*g zqqV#_{3%&Xfx`n|y2BL=A4y5Ujm?n|w(2f00q@+|ndZdj&)Rm@eh<-y~LL<6O1}iGBOBrF!rp6{@CdnB6*D8 zz#qqbO;gjwh}TljZtT2GNdOsSZ`B^WlKMNZL0kvHCi^eZdILun4}{f z)g%3P5jpa~=e#&ft!TX1ezw^D3UQ2on!SfMSOIbW1qc?i#d)kU;zUjJ(5mtk3B0Wh*o2=W+vvKBf_!Je%g7*A!Vo! zst7SCtiF9PZUtSI!`5#k_V2app1JL!*gnisf5`1u((^6*eo$4*WbPrjf! zh8#>FJI%cI@#gLF(UVi;TcFq6<^xyg2tKIYI4^TtZ|UCa(j>M;)Uo>V=xFYjaqW;A zs)IM4f6DS%8(?8(<|KyIaHZOd#LydN#+th7-Tvg1M>e>fHtJ#=mzXXJHI>+3QQ+l% zGnrx%Tp_Z1_t|s1+#;-tX7270eN|PJt(5vKGBOB>krJx{!df8!hml--AFE{yKkS;l z97}A;biCH3q}bNsYlrK#A$x!&QL{y}01Z*!+OZ8tI>>orwNJ1wIn56h0H4r!ZV?B| z$&Oar&vjW)m0dsM*_0#)bKo-!LJrd^3JOP_K7AU-ZzLuE@g6zdgRa4a=2u@V$SRH= z{pWDWr(*=gM&sJ&4=FFRlD}R?1oPvG6hP@Dn6z@TeDqnzwDg(#%;o>{R%a=)nBjSE z?nk61`9h~H++)noT+ME`bUBJ33#^3tdT9iqIEK!(S{E@eG9m%Qxl>e;nL-)~#Q6Mu z)^ON)S(r$M+7e~=?cb01i@f@3b~ikCo)6E%BN|Z*k^%&OK&W6#?he-|1Zy?XxH-18 zl!6{MFnm)1yjjKYn#;^gW}%dlI&gZVE>Ogk-!dh&mr}F3^cv+aPBN;S)t8B54xvS5 zw!U8VhCw*_bB`V#^*LZbip+eNi!X}4aS94}q@kgHwfRrO*z(`_@$tXS z4LfFLbY+VARWsFKQ`ih@7AfYssHLTW;rwd$$9UtA%M9my#ha^=o-w=&idhZ^UqRb`1?1aZ;t!$;C@s$tXAYhyomMD2^YH`%z7G z-nyV8JVotNXyZIUE3pLye%;KhsiZeB2^HqjRJ?`bg_OiRL*n(LOUnjMc0WHoIP8L$ z6|Eoy`+%tAnKom#e%tQ(Z9A~a3FyhHr9)r?ELOe5B$LK43i>Y}>NjffEzd(jii=sW znU!MZ+?svurBjAPD2?B8a&pD}X(&dp{iuYF4mLV*yMWxPE z24UR?{~OlW<<4gI7C{-E=xebMtUwgJ2!$A+V%w~X5Q6|iPW6NUkgfs+YU=Jj2#CC! zC=N$ws`wT{%P8RB8IunWsj%Z6pxnPK@!w)MYzQs7E8VLUE(O%pV>A#FBq=y$Ts4U} zGVs`g-Q(jp;%jI%G_Y@25sFO&osA&hXMi;N(3dp)Wr-y3eb6?8%}O~TDzjlhq)*& z0>7w`>-7pX6bHb!nGxm`mlgk0T9x&bJ!MuEB4n05@#1=yHF`>`(d!eB zqZ?rd4kS}F-f*75hC6N08uC2wN=Il896)mWcQG+8tGcHd8Arw2+Jb@%48-DNq2!^Q z44UxGkynm&sKnii@?iPjOeog0#xFWLAwfCW+#L2Uvh=5q96fsM81W83X^-7UFu1Sk z>7DFoZ}0AIZ%0tp{>ahMFkYS`iQb>cmFuEHzlD^RinxLSZf7)5eDQ8uo03v2`(mh_ z-9}2}b64MKfDqX!)UT#NrGjH0xL>5~g@jjKK1XCVR}c97o9KYl>nJN5b;hD0#CFWi z&W8e%U&hau{Jx6|bQm|5e^L_BbVM3DSkhcYQ&UMv zOiWuFw5BVd=%3>xG19N5psANk{3K)!=p7vIW?8p%l5fOCZut_3(2c_5Y6n{dat);n1(&Nyn2wx{o7V^GQ)izV&;}Urn0<)j+850KwYZ z%PlH;Dz2HpMWm`&T3ITuGmQdd?qvj$(w zt88p`CI9H95kS!mBeE(_>dLzFZ%&G3XAqB@7)0Vj1S=Xq+JHKunP~$1WTZ+&fegeJ z-RxwgROC>2_D~kNdh)F6&wuU1ffT_Js@Ltkb^dlOFyfA#kQ%*t(Ic4`iX-qY$S>dR_4*#UhK8A$ zt!;cus`BN>d}j*sNoiix(NB%DF2;E#8%Tm5(*@hJF^pEAT5wLM44AY|itHXq%Nb zjMajdkXPMGHOId$`u%ME@rYg3)d7#;=Kf*V!h-rc=ijfVPk`paZ=RA;Z|7(F8u#RlG-J9 zHLmj&M9qF^C>F~(Mjl`p#Ke2f|87Am3!HSlRFxl&0S9brXwe=8E?;N)XZ==>rA zZ_YC&Qd?N4un|4;U(r{50;%4=ubYU78k^M^RkeIMa!gjYO>gBy1m9GU?2a8+B7OPX zhTACo{AX~<<^WRm^IJaS9;;kqbS453rYd1_g|d-x&eiCRr~^vUtVAi0ogF$kOnA6J zw_(G_zu@G>QwEL`WEBxCT61G#NgUO=xuXP9bGS0I*YcN;pC7CpavS0Gsri@Fdfi*^ zPmoqovHgR!WeuIBL`_aj&cecC&$w0RefwE0;~$qk;czDR{1gS}7ubaQv7aiv(fUHr z{_lC$oBQZjV~+T&va1Dh1TC=-nbP{Z7j-DfM$t9P%xGAUeLhO|)bh@8ve^UDl+}0E zN5W^t|FfC<_vjHRgLlq6mKjv9FU>kqADGeT*)-Zic1Cx0;N{ECX_LdVY`K0lQ5nDX z&oi+bEITMTr7K9c^QNzR#%9y!HC{SuEQptcgoR^3u)Lby zi8V;j)MvUl0Tizc;5L*Oo(tdwQTv5IbzYu!)yK|sx8K~?vR;)=OH+R}bH0);S@Pir z4^(cm^qX{ebz9avVlXdyjcuG8>4&~Wb_2|-0ro~m$04DRprAlVS}}(Q91IN%41o@v zVQmmIp#$Z*q}0F&cVS}0i$B;MBb1mZ(i*Uxn)-b-_k4h}>CJ724R%8ccQv}Pc+?&Q zr9Gl6V9@ZNJI4{#gu4CBQ2;bwwt^38VrEugmJp@bhphwF`?Kc9n9e-k@_x1c%ZcA zM=lNdlhvKi-t(O*W&ac##-grQTgvsad*|8VMyGF#LvPk@A07s#d#WpQ5dZz>VC945 z@0F24tPsE}ZSc2HkUY#80&w`10y_WkiaFAZ5PLXgaM ztZ$kk4GoHY`%dnQk-Gh%_;SH-G4Feq*NawK_1^35wggTs z85u07t_=1piKhj)KOA8n(j=ithF_4-F{@^s5l zGEA$*sZQCs-CA0f*145mfw7JIdntQQhnqafuk^fyM{4ylL{P>Io$gN^Axqk_HE^D^ z=hGB$Zx9Cp=mjj)R8=)J6kBs>tQfl;ESnC8dCgA!Crr8F7}K}(WAggVpxeHo;@?zq^?wNkn>2C9=$4;*NT{ zuk`}IEyvtgEg?+5B)p-w^pGh|x~>kozGU9C>?mqLs%ks+@2$T@OOwl%CIc~Ytf04* zAUl{pVKzzztdU?_ym;*}I>Ermk~Y5rrP%lc=yM+x_ddeO$;x{4=qFm|?a4R&VwKB9 zYVQA{P3=^*?gU6^@}W^s)~O12_e-_!qt|(pA3aK3`q?)0v6ZU7|3j0rgp3Rz1$_k; z$uJcA{OxJA__Ca9B%@oSe^gDz`9;;bBX`fgo0lq1riCk~_sJd59AWaKmM0$lMaqF& zT6p>cp*Oq#1}qP@a}Q!426ML!Kw)n4+7t#ZuGNVk=zb9HNyKqep~*_#+|dTnt~Avu z>c3I0w{FrO9u7-WQP(xG4+bFXvDAsFuO>I2y6M^(R+lt}J31JhiQ+q$k*%DWTQ=i$ zZ)0r9+(-I^ttE-b;yJIHaL=T-`rNrQ;$@`7v?Y_0Pn=TnB2@gy6+c5s?J1e}bGay~ z1XviA%ny&a^9=;gZiFy0(_NXe;e0|B8FC&Zb@?i>)Uf?|Pk0(qo(J$WoP7EFk!`0_ zdihY6W93>-oXS~ru5llY~t?}o*(h~p6sdQT>%KE2;sb@O2Rvo5nfl|q`lN})}g=#0+6 z2UqIe^C_d{P^AiR`Ufn_?^GiP$BJg z*I385I*xMeIQcSQN5jbpyN2W;MNw7!P@77*DnSKp!+eTeD<2M-aQpYN)AH+TixHdTB$$mp%{Z4m{6pK1N`iiGfYA^`fir+}ziw?}vC9gI=?;2yxHx zR*j`A-n=G%Nom`rO`OL`GyCYn7p#gZl#(Y~d@!+(5G^K%IwdD6&`~k*xtVwjwSEXh zy#pg50RYR~1TEbTJz_QH=xt}^N@Yk^yg)Z@q_o<>8mgQeq9Df_$Hv|q8tQL6ZlICg z^I|;l_psDjNgQl|pRKbBSYJ(dPRrz!VLrj4p=D^V{BtoSma(*KYWl}R0xbd3cr&Aw zRYJRiW9y*Ywf$vfCg-XHSf=0Q$E`8wJu-9J`o?BQ-H|aEWqVO}?Aa~Rnm_q;vSK*h<>{AEpcq;2Sas7`{XH`OQ@k+gxIkVM?JPl`mF#S2v_U|pkofdBVo->gK~)k{cTwt zwfD(iIVl#Jd~yt&SD3)VLM6eNO7pAPk=#>1Dt)&7eHb%`zv~PX<{hS}w`gd%|Jq^& zXYarZ&7R$3qaP%PS6;>uJ{vvh;XjNWoMf-aMVxbN_?)WDraXLe+|*dBCRZ~jLp_7E z9>!YQf4fQ@dvq^ujnyRQX46h#q0yCRV}_4G$ZCVNUz?@NyI7GxDTp8r7Fvn8yDu(r zpRF4?A}gB~`+Pc8-dSp=Y~{i1)+9Mt4w$_ON&l+#!|8X`3fKGHoP&(th7QfIGymCE z`Q&fA>7UExweM{X32IrKow4$)e>Puc+RZ@0{3ypq>N7N|9UXyYB?Ne%Uxer>eRw!P zm(3k-sb2EFJoQoAVfKaR$oX2y-w#N^)^vg!9YJs5K2y^X(peL2z@4nZc5+{1VIh{d zH?htG-1p3gA2dr4`JCLNz4WT;fm!(%*3#0+*@TyOf8J7*c=_hz?lQF2iH!^m!@MNZ z8}|aKfcrk2tlrT#6!vXvzFUC_A=Ezk^t*fVAn%>|kqW-3VSUz7mwSH?94?*Ey1nO< z+(GF-Cts`%kGV^veDcG=WoA}lGgt~wgbelE;}DC3vszbXe?fZ6q_3vK6pu0F#^C_< zMCYBP_TGO(F8}EnLvz1W)}$nkM^{+?+%X6=u+@et?mxs`@URiT+={^~;%j5ft!Mkg z<+f+II)CB-z2VKLm7=*C=>)pBL^|_}J!sPf<~8WWB$Xx|ILs24eD%5J__QkL@pH0o zxdwNk@9XMK_eO+#?^C*?ZVxCQGFeN899uq4Mfr#0C?)jj&Ih#IRJcb55Yhuq4JB1o zMQ)p+XWXVSs>*pbdNK{qFS4ug@f{LGGiPwRD|`4Mm9uklOqav%{WqyJ{yo7eL15mX zPsw2ZsVAtsP&cw^Hoe5{_uso28$KI&3Gc=IqKL@e9oogOJNDAhgq2+i5lvLmcaG>( zJs*B^*pC*L=b9>n9M0|d3k3}gJEEd2ZIn68kAA9T^Eg~JpGtjWb@~Hk#tsvsr{8ye zI`@g==&LFPoDXgt*V|3S1R|DkFsrK{@l$_PJk3k)=C|$O!*j3J)sZl1Rtdx`nRnv@(O z6C$(Mf3H6yZ-D;ZtC#4Ef2%Gh61TKRc*09@%M+R7H*AuVAN=t+apJ6NShrr$?Jofq zaSl;2Fk@F@)i#B__H;PG+h#6)9bG!RWb_|e3TmKFO({hO`0QQiON{y)mfKa-O}WOV!_{Ul|W zn@>NyxcBskzF9)xrM~JUkmf7U zC60~CO;G{_Kz;KzBV!#=_lQBAe-HiZckCiUX){gQ-O2Y%%^GcSz7jvezBE>}`rYXp zL-_-JA)z{>fA8(m-57>tBOb(qK-d$~iJ!CfiN0G~8LAp_&v3==(5=&*+U^UT7E4qN z2+6SQEbF3znVy`4g#3I)k*>`2G|g8dbyfrKI0yc_u#DxdTs?XEjAZ=t+v-<2l-Ly6 z6f<2mZDtH7=AVdV--H{@8xh|vb8|8}I{bMWQI~gib^nbBJ^x3&MB9EszKULeU#Fr0 zrRCDOLQ!`DFQcTSTjk(u6yy*&r2GCoLR~8uG**a2%4JH3y{L%EIz=>JpB;F9hjq{F z^GSdD<%rYr7q-0-A^8KVYaB(G_P$`P!t8&FQztPDXY7Lro4|k;Jv|^C{hP%^W>{TNB=!S2ay1T{5e0joHfZ zW!C>3lmNSR!o$MQqy;qQzW;uQ`>(&9UPU-$Y1wk)Rng@486ZPOz|& zZZpJuYa%uAiHc$j`nHqv_~iS?&C;aok3i9LcSX2h?{LYtaBr5X!-^_-x_ZbyKeg6?n9?z5?{&5 z$m)#LwO%S7VQaBdxcBf0lj!4fpO;1Iy*5L&E=s^sT`yhX31f^E8Mb_lxhgV*QaW>o4GJsZu>N(^nUP#jTl2@1L zua|Itnk>(nGpt{CC+yStlv&qeVs7!EZ}=zD?|iOFji01{L&FzUT6bHU`xj1#lkp9W zZ~wX~61Vzb8~eBwm;-wBrAOXr^)zCAJwf38RG@9am! z<7NRzie=L666vppNMdSe}*Ly3WCft^+ za3Uh`V^U%Q5LLuuu5>4~he?rzK}`@dE$z;I8S399o3PisHn?s}d3W-S>&oNhj9!-+ zSJAAOJ|fFowiq?9E}NDDOl(wJlrtT>hcdqPh+ld1y~~G$H)2~oRxYN{7gwL-^XgfY z%CAkBluBZ$pI0j%v8AOLkgKXlUsI62)}8oL>Y(Qxk!APEN@EV!QbrLUiYPv$PRA~{ zwV~XLwz-vNq2(KR%El%qec60iJ^uzFineXnWGDNvmxwq+_^n6_qsXQ^OA3xW?MbTX zVVqh;UmGLIy=J^-Hf`qkhmC^q7Px?a}a*`%Q{Sn4?4k);C9_Vky>Qo2ngsN%XbzDH#7zjdlJ z$$T)fe!h%35(YCr-A5!gQ%F3#d7HG)=^3&%w-0^%d^|Y#ihShZSiZlTWfJeLI)9m{ zC_WYahE65t9@~_;X6dg;hou0A0`nrxf@_&dY__}GSGMc+JZ6xTbaqZxv+B7yb@WoI-~3lNDX@BYL0hX(f(c5$J*|ndnwn6_ z5)yg(Es;nG%Z2E+luuP2sVmHVYkM`@RFZ1RMRqUq0Ijzgzbx5wy!dhI0;VO2LHDs0 z5s4ch9p?0O>onx$7X>#1pevYq?yT9LCtT0s{uVyI&ppq%dxtlGTalP@uSR_6A?+@i z;G$gWTILux)ur0hpBbBTZQandGwL_|H`H@RsXROF&zxZpgf@7NAqzEz?)~q7Jlau` z+{S&E?!yR8lx|;A^OoP?`2#>eBeQ*RomD2a+n4F_B)8ti9bRH?Bv5w!SL!aGMr?`nOh^+S|J?Y^PPiKGTDO3^~AEDuGe-n zYVtdUh|9pu>PmNo&;{sFiu){11&69C)A;i7LFBbjKCkl?9nq zr8Qn%uMgtXg00Qz5jh*7`EqoJ8&I6IXmSqIt;o%FS!y7mB_2x1yKNTvW#p!utt%-> zOUucru()4P#J&%T7!f=~Fv`q~<58=~xRGl(zpU+cck|#oIlYE}%SOgqFRD?lMv#hJ zPEAI6h+a5MB^w1gJ0+PEe!N62X=CnbHv}IK{=Bg9@t~>pAHJDx*BDbHEF;6l)|F{t zUOM^W;$3)F60(os5)mA3R9FYM26Ho4HM6nCC`Iv8}2qV1W<|ahg?^ zk-7B#mHz!<_Trm=yy3Golc7^4gUB7MQqHw;*T&6e+Um7(dVK-gm6W`X%&4D!`Iu}; z=+U++{|HRN5~^a`0OEBot5o^_l_XB0%J)jK$HR}svEXBB^bVR>>Du;&-+f4uWLtNt zWgOtU@d4nm$VgFlw0=R>3o%Kbk|x3MUf=OpCuCj|QSddH4@CnFu>x&;p z^ps=B$~ssI90m@osN{;U+j3PNaVzhj1!Y@X?(F*Wx9^!TuZ)cA%BPP#e<<|A(5Y$V ziqlB#CJl*{W7NLq+6BJxlHPZujB^n2wzVI-4P|It)3zOt)3(iAkBqX0zgq2M8djKd z5Cb!ij_%?sv-V*4gjn_v#zvTaIA`EI=P>AOw9tb_ChPH5n+URiL!B` zFE7bOI`jxFUL4)rtqpnWrbx#|PR|$XA2!qT|F)<9Q^K+q(foV1c7^*-AyMe7`6}zI zjisS*>N1wctnZ#4ATSk3TvqgGos z|3LlCjuTCE+%KL(5Fd^vhi;fj%S^X1lkr=j5y>89(Nyxu$HLb_bSX7Ec%vl+*@BaP zF!tM%f9_hcy!)@k$<|b5nFimaBoWtxOFm5za!nB@cvmiv-s=e^#_-0##=z$Orinqj zb~yoj<0iN?`Qqpn1gRHep|pDgzbPV3!1G{dLYHUR>qm>Zujx3vC&#=d_b^)oqO~|t z5f_K)kRUsf^fV5PTw}uZm{ja9JTr6Jc5-GE=rd4$OmE*-*V2lDAhW%_z3A#^8UXrZ zjq9EgMn;p}T*shH%RCO_%PgHhXy!MDg@#Hpk>0V3lygtdfs#Y`L<`esefdx_R~Tho zIv$pc#vXC3-kO{4WM@Ycxi&G8b}w|^K3{qE&3(AYs6L1puG7oDT527tDgaFxfkpI$ zkwGXS0Voj5;mQqgn^tZ@hGl(FnWoeV%{)j@1UDX+3`%-oAwLWe$leen713XH!4A_7 zL&)XB!jZ@k2f3LUD=W)xhQ5OIEbAut;(D08+5RV#m02&syv93R#n-482Z__`a*!6C7!zfK2=o_>l&~eb91-tBJ;K$ z6mpc25rR;c*Jj~c9i3J^SCtn8IhT)PmGJYMfTR~?Q#pl823<*~%3}~X1ewIqmrq20 z6lY`zlz)2R4}r6W5ONxyUG)vqUEp=okdu?-f3DXA@cdZ0MhbbBp4tr)zy#tkuMQyo z|I)-F0*Z9mC5F(^xXF4r~(Q9wimR+Cv+f-z{2I`>XgN%-M?3ci+iQt6sekiLJnHFR{eH%E{kaB zdG9<`V$>K0mB;b0ert9%mvPNlRxL)@~k!7O&PVlUvW;1ZC^KJq9o; z0aC`}12fOc$_ga2fl^1vrhI?ZE-yS0?SHEDb5P?cnA7>OXO&rhjP~=Bi~2bW-j?i4 zx6OxVtjg-*a->Z_RO$R{gHxt<24eaKVoc$AJsKp#48`x-oKBf1)WGggJ!3N@;EoK8 zj;_BjF%iB}@96^Ta17W3pe``*jXm%}<3s>maJYcMyE~gM0{)?-B->Sngaj@FP_LqLgT=cdj=aZ?U@;ehQ@CKC`V`A^Lp~e6X;v z5GZaF8;_%-MVE?Ont>|a;a`5o&vnkuW&qh!QBl#?*C(N91dV=x(b3T4vS|o8vht1v z(V=qOub$d{9JhnSANctJ-kbiCAOgwW^0 zeq0@3$*3jloZeXIfu*-B-j15fC-THA(J7^VkhvDdp&DkX!hiBQwKP{k>XhN({yfvn z0(2_`TGMsP#2-vgW1D?7QV1gKs^inpB`jHDU1&SB!WJ?mt#k@-I`Id6`BNRN#|&TJ z;a#zED4v-A6-3Rr5U2mXQA!$44SH2@ZlXX5uEsAdEjKsZwwmJK*UBBQ7Iifp4=GHQ zO?F49v8=zW%GHZogHPT*y7m*ImM1yP<~E)x z`J5Dm=JFeF)pVx7&D){4E4$PC;@$T~pSgXd^_1Qk{yAk*p1~+Wu(@(`cOL>Kmau3` zA{8z@T@qK4SCZw?DlVzNe0Ws`#Rlua?6QTAWs@(usk5D5F8N>+H9TB4ou-lJ=NHD& zn>+s6i{A>pkWQ$4z`aiKQg8UvZ_m)H@W;NziOyPnz7Ek`%rWdIMFs0qVNid3WOl{y z;Q6<^OisV-J(n;Z0X-a?dsyv6`ikOrY>HOiB`%S65qz2HX(SJ?*54FPnAzCCCii$$ z(*|n_@?=1!0n>qlRB^8}-Q8_5w(m*RatmGPts;nmSz)KDz+k>neef>&u<&dAsR6lM zCqHp>YA;A85&-vvd<&<~QlxWUnb-^dILKD;%7wGI4x9cdNZtY95CJjT;hmq43p>y<6*07ycR$ANIDyFJH zW^qb#@7{lxIFth^1fZtMBKke>3HsJ~L@yMloiwf9g}2v8a9twP#L2;xaDSiXjg6gE zs)29*dE?8WW&6K_{&tC~~vS6}23 z=>-oEWN{&&-o)asL-W?tv+CfVN_>nR<`Q?`QkK%{S;rHO9+o99O^$HHS&pkm6( z2CGPSA+p^y8U03kS$ljZ@FoQV<&$w-mz?te^||)(I_Ip+s%P|@7s*hWCWOHeY!HM3 zVD#(&(ubrE5zt}#!3Bfr4T1oaW6RgRmI3ey0Te+n*Ror0sO#x1ABUqT$c%6~Jp8LA z*H{%Zph|_jN(F_J{(Zh3guhjK_nl35w>>%t9b12)@sJlU{tc+g@99>YDjp%Ndz6LV zEL}XTyVinS3{~;QP?g&d`sf&ESd1vl_}`@)HgKb z32E^LFL(~Wed~=KMMGEjC-`d+0)E*FvN{N2XCW?VS3)RI#-?=7fci@UYTh2Yvx#^{ z`*sxM@1-5TTV6fc;!C4|{5Uu`h}xI$OpK7@mk&<+Zu{2tPtVQ48606h2ocH@6qg_G zrKN>`1ezH4$l-<=VLy5Ga&3A-B7zuW=^4`IUn%9hb0P);ys+|h>@;$2nJqvC`K z70#);z@4X_o!0Mp#VAth;45+PAW_4|3@*=QEv5c`*r07@>yR%%m-|m?18dJg_7Ax5 zP~fw)w7kv61ruAHGMr64wI)q{McD}nS=PO|k&*i$;PA#9VBFE2f8h?~Uo*6y)#gUa zbF+4nrqU54$)#0x!=PTH!M*mQkqd&G>{wQ487HW)`4Tn{-+N8}`XKaw7)-;(3$!mp z;~}q#rvmxD0w5)!U{2^iqN50@v1D-D^EEj^6u{;Ko^)Owl)ef#BynFs7hI#lWofz- z%UhJFEYQ-%x0E~cngkqZO;!kpMURe-hKBA?p;hn+k;SWufjW9S%hg9>z$dduoPq*? zfS@32pw!uO=lDpsj~O3Js?FCjqFDOwq5mm1c8eQQ(sX<`y0d6l4jv2&a#@5YW?w!I zvg0>3NVm^|+yC$kCzf0!f2N)KcT7ky{s|5W5;WYx1cWF{AUqEj7i>zJphhL*KU9i-Vpe_*~6WD*T*D-z;J=26+E?@I5k75y3bBqOED2~0iVz7kODoATqhKAyZ zrFn?CB_Kd}$|3a=i+_H8SB2C8AMjBL;XxP-z^DPdV8DHWq=w>$KQQ6MUr<#w|LxmV z*m3aiFzpft@*SLFC=Rd5_yzns2;PB4o^|o3yMuc^|FpxZh~WdI=tyuq46*GTf27^N z`Q{!u4y38JRz+1+wVz_1+x;tf?$R~;m3K}2wtXbQ6kk;}42i(rUX3`9U#SvIFiwk# ziXtd&7K5dmHe;`EW2P>d%{{#0MW~Unu?ab*r2-|zZqf3b+c1gt(CA;{AjnykZlEV+ z1+VYG`fyjVql0XYy2Hku&ET;Lii$#V9o%ecA6+9b>v7bd-gRI9hK-Fv{MDz;6AXtA z-Htfv_j$p&#sGAOK&4b8m^}i!4QGz^^>uJ$+>HZqRyY8BsZ~TcRk3HSj5O|<&Hmay zGz1rLC?8y;tQ)s`d)fhF8_?YNxh^~0ohYl-AHmDathe%ZC+Dw*T_)$)e`v<7H5`Tq z3&h{c2D>czaZw0{BAb$+kKI0Xdq~GAF#*ci9fySZ2{W6W`wj`(eYqklD<*d7l4hUR z<|5jRvB3Yn^$UcK$P>m27f|I0-@|%e1TLngrnaTUuVGKXBmm z*|UT^HQp4Ar6B~Xo2bHuM{^63o1Z_w1APtTqp7*M&i?+H)XxS=BzRQ=O`wz*AZpv= z&ff%I6xd*#i}-dIDJSRV&OpvmU;ko(ChZolnf=+qj%g`32psorhF@WlCa1?z>*!Yp z1h56Ga}e8QZtjcrDbzVJA2F4HERXH(SWM(P!xs`dK5o^Az5dQ7K|!6;bFFPks*a-) zx$zXLG-)YMcUNdX>_p7I+5l1HT%OAwPk)67h*wWmivaxTKp8VNI=nXe`P z49X`s$c~UL{`?7nPHus-ru~J|!S5dlJ3ORlPWyd|#^Vm;-d_83mrmSg6mcoRX4dx8VLwq?Q^8{T1@_p@$JlOACCHF*>nC>fq{`P!5hT zF+xJr(i1@_DbU3Gb7GEaf@&+M575!n%*5sc^_09kUSR16ekAD`;M@d*Gvdl6p%_Sb zVk-VgvRU<5;ywSzw!N=Ufdl?q=%Pb*q8Wc28|0BdsSP!3edMk8-|R$&#cj~@Dx1@J znq?>x$N2uLmHUvrL6M7c3nmNHJs>BnYfQv7_*(u9y{dqKkPs9_rl%VkWMl|a6Jizi ztTB&Mz)>2!FS6f-Mm%&R#ug6Ejv2=#7ae#iWp`J|L#E&cAASc)&R770gPCPzT24+y zSy`v>H2}(i2Ip`tuz6xbGNur8J6Jk2La{Y6D$4%$ZSX&ezm{ISi{e82goSMEitq4a zREV(WSEd6$&fvmEd7Fuv6mYL`jZ@^~j3x$n0;wu8Y@tCiiRD0=9u1Tt^S(g2MK>Wm zT^8RR@igF0ScM=t_gaD$HzWb3yLkc5Ku}9be+&#XwAE<{-9};rN|k71;k-~`tDjiV z9ba68ay&SrFSJjgI1t=K)V_?|tm+khZ{1Oj7etCz-SLhIYy5LUA~Ul{ zYljL&8pvdJ8w;%{MBu6_;<5O1<4sjfR2lTZQk+k3FV4eIA$BJ>5wdu~1!yF^io;{ugJRQgkt_wNLV0zwm^S zP)bzbyPL#oN=!#Uhc|N7ia4dA*AcwN#w_w7A<;s#BxMN@5}_7Eh(a~ON+b&YEZp4s zNUIL`@EwfZPsodqG({BmB5c|#`Qqe{Q!zp$Mxq*47v58^CC(D%lY7f$u3Zbqv?aZ; z4Y~NmcQ1B%OwxZ*yxe9W{jh`E*M1P`{LDj$_?rO z5;{aiY>BvQXvhM3J8yf!9$Y3)9@GN+W>S(6$YV6=%gaS^0`B*B95wTHtfuNy_xC)Z zJUcwKwHot6{G;Vt&);wVw!y52lI`?fos{^xRl@l5b!R%QEWpK>zbk^JT2P?-DI`Sh z706G1VThG}`SO7^7EvlEM3sLpMetYiJ=r>Ki7D>1;G>Er1j9ZC&}cYj0oj90RmCvi zr~nEC@yeipGSMkKgb1QPeSBD$1@K8a3ol^!S&yDnZcek#O;_k zoe5Mb(pQG$9Oy<6Qw}VBXbpD+zJqr5v>+@&z#&|y0_MyoW5tdj3Q9_9NuKQK6!`Gy zZYT{;ow*1%-%)=fgHy^g=TSw>I$oI7iM6j`tQLcgF`yTR9C84PN-;WTMoa6mD;{Cn zA&*clY;B{s5bY{4>04jV1EUJE5)kQMqq^shBsYFEX75RY~_4>I$;D&Go2vfj-b(vEN3%kJf zgURb~)Txeti!7q_4L(0;syV_w2Ai)TE0E7%ox8Te85oJgK^c5Jjh0w-CAN{Euc_)+dT_J5|b`}B0s4-r8R{?Vj-UoDK+kn{( zlP{-Dxo`mfL;wk>q0}+?3W{x$GBQfjAopcD0uJ5{vlHYN3uHSsjoU;D2Pt-Q1!ml~ z(=;41@*Sk%clhDiyZW%tUMJxi|^!I=IEf$ImX8hV* z9d;c^CIZ1OfsYh+?#7AvD+|S(mTkOS^d$zZr{qe7;c!grfKw3!;ozkl9}c?A8bDXC z(`Cj{o}h>A`1CivQh4^u6A%97+mBm#hXH>KQ8c-SdSJBzQ4GwT3-5p%8w2g= z5YIs155SqmtS8*0>r8i?&(^8ccu-<{Zz_YA-nn5PmN-%t_{72~$@pg=>Ze7m06w$> z8MSF-CB=^^N3x$e3F;Fz&F{{{f)K^2v+HvX|M#dyRi013rKF?-h5&LhIRe&G*1^HO zy{V}XY|AyE=+0u!ifdFiyW-eVTDXHe+zvb>H#dgi>G+SkBYcbSwbP%YhtK<9`MJD2 zYIA%7f?-j7oskP2pBI7}C&zO*7jYSG6!`?VQej41+&9qdgh=A$I(mxdyZ;yWmt?>YPwD+HrYjRD+w|HzXqQ4Vm59DgmpUFEQ(Jy} zLRw~{zG6d7&5@q-zWa_NvR@(htlbddz1135cNBb*R{(!lV0LooQ&Vs~{AvnrR*2HW z%H_)En>IbQf9g=6>X4|aSm4Kvi>9E3cLESOl^XvG9=jWjb;8JVrg2-vT_u?BdosKM zqBfv00$Sf-=h)gJ{?Wf))HL`GjUTkYTO)}BIM z8v&c5(4&u7j*xB4))^@FA}wCR1P3dM^otAsc@QP>ApIkyzyI<~(jO>lL2jER^sP%* z9VueQ;FH5>(i{p2KC76%T~)QdA&8K>ad*#Cs&yd=Lc?At6BWM@B^CWM{`Bju;RUj%g0? zf53W#Xc9O+KqdwsIc>hzZdUHuuK*IH01Z%xNHB|4PL6uK!qUGd9(sLpLhaZ_~va|Cdk^}l}g{&4@`VBA_a_xKJnwHId>WA~$H zt|RvY-57B2g=7Y^0|GKnV?I0cEgh?EfQ$$dK4N3jgFp_5X9BFIv9S@|F)(1}>N*J7 z9@tnR%!Y(O=jXd|!bM9GQ5-7?Oh8ojzIN*D`1k`#L2^92BuU6naY)N=KqD941%b*> zj{qorTzB%^-M-Hvy`*GvccU&r3{b=E1QZmPue7!au&aJ&ZwpHLD*p?ub!H zqmm2~!jo^ub+PaUul8Mqu-+VCco5GI!V%EY4a^bVhbQ&=*gW2RX$38N8E*fcIB>3e(W!jt|9zFu-JiQ_|8-#J$4)dlZ{L4nZn4Mo0E^+ATe-M+T zmZ);G-53#^X+LC3HS|T!dKCSH`uWnQ<4;!LAL8ZZg;QGa)pwO2#g-%Qpjo#98u>x& z4%`>;v*5Ktgazw%Xail`hy()sS{y3R956>E{2}LG4;Wuz+6n4<2pH+ zWkD5}_W8b)N}dqIK=4W*n5uwQpni96`2~UCU;<^iEwN+y^?R#*dBvhYbb~igR|$vv z@!gS{dB(d`F8*1WWPU>r(b(8~h;c&1x(H-=!6yI-=R9sGR$!ip^UILWEZUxqlTG^4 zZzpklKqQ4En3yH6_yU<4uU|K>O z#12UAtirKtKD7B#Qj#W=hLDrvwN~s9?8FdTdqqG5dxoPX%llLRz0y|7fkTz`!V>Dt{i4=j)M&iN3#ZPI(N1M z;onl4cL|-{A?8IK|7{j}EuYEjA8S%Re0X$mV=gQx2u{RTvcU0`1|?*f6XcnpNJ=tO zgNR5(1OfNoyeTNCr^mFuG=jfe_-M)G=)|EWJxZtKJdX~R<>U)j)y~fI$f@z*ayZg{ z1o!Ktw~1Z7-LGv!Fnqxx&COrKizY3g2@_6Eu>O*7%%Eo#_M9sk8a|XbP!FJfPLHsP zC7T`FTMzT6&wBD?`nZi8fg-jot66*UiBmZ_*ROMNflZ0@Mf4Te&Gq&UD#Y-C#f{Kk z%3Y6#YGamCw0!u!k2C&MYZsu{Gi0Dcu{``M*{vkf?UES(@n@%G z{P)I_N8>3}UzSs#PzV@lkF*fj5aI!+rUnP|qDWDuzrFVs0j&I)i>c4g$JjWDx0sEnBdm= zfA+)MldBp4f_wR1#%ICorO?bAMf7Iq%lFpdRRS#>cD z+)JhPo8M#K-w(AG1V2@r3R0-Ffnf^?o1g&m_5{>@+nV-KRn*dqo);9?{^!)~r%fd{ zcX|V}KO`JQQ0DE|{5yYM zYwr+FsykzV+nTzb&YnZtRY~ctoVtWrf&q2!KQ-po%gi#^u$6}(7H~`qnTJBUd zF`oej?9AGR&z5+f{X~x-M)PJ@>gJ2Zj8#0 zyr7|oj~{D?w~+b}rptMuufOk|eeCUGLAiWA$btI#uxfMjf9DXEFCJ$0^tNDZyzKP! zX%bTEQpvRHNvg<5%=Ocl>w>Pd%k8`Oui#c&rhACRPRX=ghdkN-%sKm`a`iwmetS#H zZ!#%xO2hGpDA|Ca2`LtcVwy*l+`kTjLRR2obfkuHPDSCWHB2(X`=i$VxXUd;bQ=yP zFjRqc4bjTmo{VZgzgauYzW=?YMq+}K-GB9PtEHyCQ~!Ok<mqt~{0v;TLa-{$=c>wKowV2MC@v%GVK{4&(+gcVtsJwRUq`o~g_S+(MwWI>9Cd@< zf15jMNXNvFTPKQBAZ97mA&Bga&rN!i$AiNPzlFW2LtH@Na8=Z?8Pb@q4nFg*e9mV> z3_B23fNDQ9#M6-qI>s;%j)%7lf#;f}+K)sm+JJviE#QGwotO8=`sJo%XU_slDECe3 zLaFhkNjqDfv^Mh+JLhyqr`Z^=JxlnuMeEPC8dno?emwVVtSr5x%ZFWYvXN`GBVEIf+u+{U+g zU~~LI&`%&XKh!ipkslzEko>V8gnXgu52|N@ZB20TjACLYRA5)UJ#osFVR?$YpR$<5 zXYoaU4GBG}NUq^PH~7W77tfI@A2ERc9ZiS%3I+sl?5c*;{PaFSikCIGbmp*bjgWWc zf*ZaY{v{0~eAJ-(dG}i?>I*k;WYbq*e16?+*krH!7IGYy z@5`WVa7xU=vC@DNU9yAq1~rw(koxZlknD~Zl9YV;Qlcq$_8F9ELJcxY+od)q1=RX1 zn_)}gWqdy>>I(vA&k~@3iKeG_^Ck(&(S=1s-b$L$W|vzjtE)=mi)kk@k@N4?{-6Yk zFP3xI(pL1XzU3%93$3bLQObYk#COuTMvKXFu0L)mZe@*f3y(4%!#OfNL#Ci^25d11 zk3f>f4uS*_NrB%(Mo2%Gb^yZ3NOsbkjSUN7cKh^X8m1*MJCLexRuct{L7hMsJ!=`; ziHl)OEKh3NLq4tp?(oW88k#q=#hR&IMy$zQM*g(6AE#%5MjrI7ZN*s3)d-YzuWhT0 zFVH*INR@^kojJ?|+g9-%%Dsd98>r@co`!9(lqYja0 zT3QhirW}hQM6(Ibk96g|O%m?TS!0pFMBa$`E-r_F9mfm}=v>|+JPG0;`c0rQcCHT- z+ZNk~bWmED)n}Rx2PJ`%*B`98koU%`9;cY&#+ z!rSCDzYNCW;`Wl__BmD{rwsO|7)0$db~-q$cx?+mc*Kx{cX2HSyZ`O zU-KZc`T{%2lzTKG8n{MY9)iS(B)LtJj^#uL)jm{QI^(^+i6?O-+gp%+G>3odn)zP7 z@b~!m*x2J@!|W~*N6G*4P!OQb?OA^a5if{wK|pU7QnIbqv+bb4*xcS;3QW|x##5HyW-BmM#np)UQ4)zJb#khk z03>5*V@P8Z+KKzxNhSiY|3kB?& zHrAE(J`N9;OZnjxvaGc*e|46B-3b|>`T!!|1$Df=(2|0S$HGZvsq;&(2e0fU{wJ$5dZ$jQTXPndh)@+dvb31)_AWoFs=AD4gm>j( z#w&UmdTZP9h0_`ot5d5=tL6+&P7SzA@~$eqm{;u&{7yeqQ|QX(*9^ zJ-x_wCK#}-$bSGQ7s!)?v^#R!%gH%c_1|3s8IM2tCZx?z`facbh5gozE?Z{3-#sLN z)>kf-EnFmCW}ey+yuW?cDNltpvfa3!KT_X1*yln`?ZU!F@6gon+bRJI>ai|n^ex9| zl_u`OaUWFoRw>fWu2J?pmR2^SNWcH5>w%0+Z*Ox{(M%$4wacIj2vk`u^s-fB;?9u*sw_x|6g5u_agvD9e$M6?MGS{gRSdNE zp;6|=Mnf12Z*m1i5XDkS7})&%=HbM9d`!~cX_+)DrwEz)m|VNXyKQX&iHY-vQOA;k zTTvBa2PE|Wm9IqZuaGSD2PZ9R4dn(0Gc!Z&8=G-wdR!b}({#v8j8c+mXu5N!D%^3i zW#h|gDP_6bW{W_~K7P!$*0umtl0hs%fsn{=Y0 znERKBNc5beQfR$=pavh3JLV9-VG70CtWiCC`cRO|>|vyQ*=|sqY{P^jwacxL$w>}} z=E+|lelXbEclX40tom2b{cpuhWcPSr8A=RohrfqLy2yaL-TK{SuQ^IYbhkZ;kRH_6 zk;)Dz(SpJOgm?3T{27kqG80-{ES!McxS(d{myjujod$IW%Eap#;%Y1Pzo_FrL*@o@ z#vqjmkvQcy&`Aw!#_3pqI?iRVnP3t!0}ow68I{ytTS7$S*)R7~neq>PI#Ye=)?IY@@JbAnZckD{_4~Cq54~8Jvg)90=%yn@WcRwN&D)CH zD`lX+DKGbc5-e2n{yZZ{0o`HKei6nff%H(o#lX;Ngxt(Tne9VT7EHXny16*7N>Qv~ zZS_24Gp@1T6{@wTviBE<+B6EviI!Ns#(&f19jWI?L3izzC=|!m>DTLbKzomzd^Fd_ zLwC*kDUgCjlkV=?9v#j9QudEJ_wM%OQs(a5^q#KfgUGE{3_&VCSh*vhxJ`2O-i7^7 z-OKOa^Zb7Lw)C{;+NXc6m_F$47Wq_4#QFC2%baF`vvu0{YoEQo*E7F)_s++u?w}Xh z5xSoTn)HVTIy%%{I(Cj;`H>6Cd^fL;n>xZQnkQIDK=&y6$L1x`DQsjUF)KgW*BApW z_bP$Foux+}wD|oS$z>KSqw=M`N2eWJ;w1XlLjA67zfmoKrTXm|RK|5%HbAZx>W34= zpt3>F&JNPBaB*<~A*Lj9P$Nfq){gAjX@7sq`P&Bv>zkrbjq-bqa{aOEOjzd+VzyMx za{GnWRv-zGq$Wd+0rXid!Vw-WLyO$4MncD?fu?^&t*X+u64jPAow&lTq4~KruPH_v zJB@WkS=#dAwR4O8Ml4Sp3|$9xT-|A8R|$M5R#U% z9IkLjScvLk=PgL zi%(OGWH&NmV33zDu?HPC;|^+Lb|d6w)wNOP7 z!GZaeePRs1#_F8rQA&9DrN#b~uhA~5xihi1%hy?D14?Ihu2C$TW-B4eq=?l4;^+x* zkr)~EK!H#JgDSh(ojb_t&1c)(tkb6rHDs-uA|jpKcWeULCSEu6fV%5ilG=U%vebC^ zpJT71AWa3`*`FYd;;irGb@y&oId%}es8-150v(tviRtr?v-eI0&a}^HFU0w9CzWwL zn)moc%5-^hV82uFiOtQz)e`;Q&2)Q`EUq!(d0J+oJ9y%bZRPv855%vU6vvXgNk_0LZ}$kqWGJYa<{)nE*67nxl7w7w~lhi z78i;fEua2)nN~fYUc0`_t^33XW5tMbNmwwMn9MJ>8V5DCwv8=LCr294E$%Zp*h*nM%=QhCJ!@tEyo);f z69^3Id7!pzKL&7hxkw5M%F1dCe!3sYjwpyALO5MN`re+PSn>WW3Ef+l7=Py3)R#NB zG^cfC%q+SnDs~+$-tusJ0DeIJc>ya;ve1iK!czDG0+*iD{FOuvG3^<^|09< zz{<;ANw|>Y@OI3lLalis5VY9TL^T@ovDB)Rjq4`fX z-XCd7?VPp}LbYsWOqv9mKEesp^W)p&8%Zvs_T>8lqN1=WbAW_^MGFnsNNEg0hYq{xZRYPK@v0dV z)K>vfpR824Xl~iHu-IpO$b87K^c6>(pAws%W91fe<0m2caQ~yH-xPL^=#ZD`=eR|%NBDlcyQUlN!R~VjIn4sj-{Dn z<~!g`>w}>N;0G##h$S_(p#c`eqiFH3apoImwO-eE76^X0j#?2|7i!RYuuFw#Q zBpI>y^y%2NEw7Au2I8fxt4SLT$u=nnlBaM`s$yrmms)aVubG?YY}ydsDnfes}KvKW@YBuKVs3?jRIfV(+r+PXwyC(7sqt2`TVCGn+|zr7Ye0T(H9;*s451 zOg%;R<;#05?RgeM3R!wh5&l~Ly>{8|)em2=)C+Ar{o_x{M}~2DqL)HMw_A|H!?I{` z&wKZLle;)MzR75r_-)vM@Tx;#5$EI-iQ^W?nkx9cKh@dEVNahBuk-Y(bXs-^D)4$| z*5oNg-cPkpXA&he+vQ`bm2I86YQxu`@>B6%J^$hPLhr`(W8J!+R#Q>A+SPVl5|Q?e z47vKy2TQQ+U0jR;xleSUjZ(Pc4zz0FUi96INj#&qn6GY4Nx5ZBOs>CnxnQ!hzHsAL zSI^52s=s;~2rE!@H@_&un4A4;_MxAjTpFn%yW^f&s(e9#lG3;Gm;&~9ZxJvNAZ;>q znq>E}0i}KtL-d~m)^%FrPr6GdOGKIvoW(OX{5gVjz0V}hWx&3GMv>ahcazbGYs1Q3 zu4d4>DEn1qbF8nB5(YLJNgcK7PPR9i*uS;qoWr4S9#F&?KoXxB*eto~$V~L^Elu2C z*h`U=L{I-Z{j1;WtaNmj@!R^u_@u4^MyOn>=uZud8@;^b)LzoP-t>5+?n9DK_3Z5C zLdl3Hn+YKUq(%z*fXDWDES?H4emE`5UyuI_R0jH3#920=T<04%T=st_6=wgc@x+oZ z(}QA0_hte1b$c7~beClV_6a1Ue5>!=fug?x5L4D`B&7e!29)@uNL$_8XEYP_=($%l zaR>R6SKDndJI1FM{mPh@?Jhbyxd-OXUO1txSwf2se@h_lZ-IlIhE_S>_-|ZdqJhcU z(@5lu+GoG8L*(jt)T>Coyi?7m;C01i7y86M-?&%}{l5sSK3lJmG(tRdn;4Gj=R z$TO@DW|A1jba(0BaTgj`u9ir7EsAg(UHd*@-ZM9>hvlA~wS&m;-GVF+7pWEQy8G}~ ze<(^F!gl0MrCJw7IAbIjyQsF-*g3~P30#41Rhld4^Vi6+wup$cL$47OI^1dhp&H2^ zD-B?ULK8)i`W!un0XChQJR=DS&4>yCw+pg{yR*N~J3Bs1{P&ULTeh+#ilf-24>R0X zGu*9vMm7t6SSxb|m^pfq-9w&Ewn<*zMm}@G_@#|}ky>Q`$5J)bNHsnt%^wZO5O1%b zhhAS~$m8j_saQIE2DX4sW$I{FUEwABOhYD+I|B*SqJSt-&FW%1KSDerz#J%ast&fc zt_uF>0x~J8Nw0R0y#U4ONredAq4;GLUUS^eGIEO;4z|1y%14{86O{-X%uE zlE0H-ZRP6se>TgAa8gKLQ8a}Mp=qR*{QOIQ_y)g zp`u)*mgs+B$&^4@WR;3u&2$L7!$ zGqK&&j#YcE*IdU_qUoIAX^UzDjjkEuv{ zJmZcGYvTBbX(Duj&Me5<0ie0|-5E$so`%@(c4KnvBzP`Xw=Y(HvW<`vWf^D}XJZqJ zxYPCAph}v+GtO3J`~$Ln;I)7GqR`&z9})3Nme-iv`T@a3vDmEJ4dMo@zIYqj>So20 zmMhW%Rk9>^f6AvBbwC*NxD((syth|)ltU#v6DZ8o(rqisH#%^ z#r82n)D;in*z|ORhKV}e{wo5N(uhX`b9x|G+!0G`xUcxNvc%iPmA?|-*3gF4$k|-0Ak~qn47I9b9M<&x-2A)ON}#gGlhmDmF})K~$b7O?cxOQ04z*NZH4+B+881_Lj*tFv-&XkZTU-lc~CNe&pY7|-#i(H zS6n%*#t}&5UxKx4i9Q?T8%637;t3uxyS@IOnff--BNGvcrUEm35`IuWjN+>9hQd9< zfdb zuELO{(3qkp^TpLN`Ab@gp7CsWF%(9*NJt=E8O}U}QZ>6&j(g|A^2r##I+-=L%MIiG zP9Z?zMh_<*MHhsK`o;OvG~63|m8QU;Y~7_U;JKeMWY^4z@JRG@`|bOl_nl3Wv^s>c z*f2(`#VF!a#5hrTLI#!WQDyUc%ifWs*Y0iFDJh3fd2PpLHlIDb?M9Cqv0i1tHwg_5 z#CfMz1!|Q0DLlN8cH?u#`2u_Sr}$Il<@ZvN-5gQqiHJ~9iMw|lzjjnXd@kopUXY=+y81~pNYp5yO+_oY<6^!FprwtQA~-?A1KRQl9?}>Zo1m2er53T29#V z-+`fKUOn2WtTMHK?#Gwc4?kU$k@;n3`biY3kE4!9ANPpBBNlntB8B}B)lg9pYs9!1 zL~o=jZn$L*bx(ht2nr4zt39wtl2qnIb?&>#S)*nPB0olElw>3ucBC>bMy1a^7&o3uTO zD(zL~UaLfKtqET=MqTuK(gtTL*R43^{57By40i z->3oDb`?ele-g&x3vTW1tppcZc;3>p3ug!`U6&ZK*E5Ugv8eNn}&f!;QLcot~k$7l`#c& zfng5=9DW;30`UVC;GJXm5H2ikhygG}BBEGn=#4_I0heoR?a~5GV<*XO){b&eL1qqV z_WBJiGrzTkPX6`=orgvWZTy6Uva6-T!D1jDlGlcFo!tt`%u1A4^LihxQd7LdxYaMI z78-ZN3C6#Kh&@o72G*5-FJ5v!n`u2l$ijl2?S6dr?DSlG(z(9A`FZIS>6GnO^$ls# zk1{?f@(^_}A^jZm_?vpB#*b)86BBxR*a}hHGpueax8xq;JgTURpw89$98_?QFE8Wd zSkH*PlUM>FSOIr$)E=Krqi-v8R19N1z9azfWWBEYM9K?b4v}#7g&j7 zNk(g%`j2P@kW{sDA^6gXoXp-3iYHmKE+@3{^>l5gp|+a`e5E?u>>_u_;$8O6(CmlK57Ku;qcCp86VoS)ipY$N0O z*W1dk|M@eGj%3Npdi|zI-_-8U_EMng-kwQ;86C}ZTarX%NC<3`dxZ4crP`+hPH(3K zzPUM2+A5(*Ha91irppY0&mCq1EHj^^W^v^zGaVhE@v}$L*eBeHl{PT{fV9@jmr0Vf zcWoMI`wG+-ie{^Cp*oD`=d&=)qvR$gxEFu-B9L?7I(`5Z71A}G{q7suTW8P&_ckxP z*G5(Fzr4f{2)%phxs1ioq2o(e<4Xx5_6|dOPAU`4B%wQwh4}c0zC z*vjVh6xIH=WFcOM?@c^Bu%s~+OJhr@KXlXjd0s)+8uW}I7kua{8IveITzN)(NZ%EW z+EP*(U2ah{Ylt_0-LZG_9ml7jzwx!nuOitY2{i6pGS(*N<`+r~C+^{Or}=ma7+mNo$70HB;sN2mt```qwR3jSeeeJgmN?mzVdFlI4=SAP6<1 zW$JG;716ucKU{+c)!@;3u7h1#r`(0ioM@inR8QpP^Nm;xTP{#v#wUBiA*@MOR`%~4 z=OqVOI+)4B@!I%Mmfa_Y;ts!@B1i9g^5fYKfg!QDCm;1m#h5gkx+~HqQGcg}vpYWEl~1(q3l}+`R=wu_?NG z6XW9(`9|?76Uz{{|FxJQC8MD5D%)acq(`8~Cw=~sGJOB@qQ(V@wTrC-bKDf|uFl1E zULelfI5e81vkPKWJ<9A)4*ae|AU;SK_ZEeaV%q>89o)Ws#RIW`Rh|J}6r%GU&jV-Y z;-!ZhZo)|WIX%4^i^p=*@*mz5jC&-9ow%#(Yj4jU(#X#oCONll{-ES;xRSpuQ%+Vz z<;lLj!5e$Y5<A}8-@PFxl=>~cT@mucrGBuwpNUBQdtun7 zedQ;bIE8qbSUGLGj&ehH1Mjj-0WWs5)m3Zj0H`-IhI{Vc-2xgeB2=uzsOrvpAVecl zyIF#%W~&`$#mr#ITL+`N=_bU)86>2;{PEt82*%f%9qc3K7ukFEQtQP7%a#~-nq_p2 zXswq>YXe!>2_s_+%mY-KZcN@aGCtM|ZR>oADhcD}o^W#NzaS7Hfo+YM>0ob6TP&p8P$w$plduQn=D)MG$h zh2h^943%hb^>EM;BLebzye)r!)t#r7wA^JiGb=pU?dmFBTP(?7V03bD)4VG9;l7!f ztvxhOXu4y@^o1fMqxR=&YL8@V1GnxF>*#(!jtU+7FA0=nU#rp)`NO_KL_gw(a}Bf$ zO**`MIW8F;Oc&b!6%9TrHDGBt_^k$gd~SGBd*X>-?vCiraj{nwtj2L@cJ=e_)$8HI;sdi)h(AG{tGMg6v#1XVE=s z>M8dOMklA~IUPfqoV>hoc!)ulDUvQI)N-U_$nC$D?dIC;R$~ss>8v8M2EXB6dGEB`YH%5upV#GBMqn8h>AU zuJV((r|B293}b+6G&cu8o5)(B1xhA!X}UcnzOd@hyrirw#?fZ!d%}Cl#)U@7+BC*1 zF(A34eCpKtR$G^l#Zr`Nq5h8_O-@i13i^Ic#H`6C%X&;w;&s*8&71Z>8y{ZKcIN{@U3%I0G(bP9hWIx;oAt{54GNvf}k9^Qn?BWUu4{5J^L zwixGsz=h$U9}RT8xg8dxdKLlm_sFc5q3}<{M#cAYt8bcui2iI7P`RWpQ7rJ%DXp~p z_ro`yGGeZ4;wyWsF#1b@$4E-0^?X!QL7cj$)w4``50k z>FbIW6^Uby(C^bHLE*~{V`w&pB@lCiAo$?uXd&lzRe5N=!-^v5P1@dxfd_bSM`*^1_#w2lZw=JSY(9w#4#6Oa0xsApS46 zAS{oEhh)7&L%|g?{2K+(%pAkNYcx!mIBnE#-ykbiSegJG;?u=*zP@}SfqmwC$x%^! zE&-gASbCKrQl%)zrv2T=jg4)Dgp+3GxCBD^>B{%J?gzUV_zqt|z1{&%UdLb2PaL*^ z1@7+dt|WBs*(dvv%BAhiEicm}Zpw2D=g5P^qyFZ1QRY6LUmVbY=C3s>hsEEMQ>XCL zl^}CaU=}JhbsKQ9hcSPnRUK8?MMafLt@30yOx(ej3K;=xAh=tt22I(S)c7 zm>uKqQJXycCj%eS{CEAFj6i1T94uSkYQCNj!T!yk>}F>#L+Wpd)4Y1?&w;KaRzL9D z-QJc;Ufel$=&=&0eQx8_UcO7(2JfW}$r-1lW|gF7Cu+N;zTP;c2j@9>zRQg+4m@^o z(dq3KPDo}~=Kdz9?HJypoR}1jrt)>BfzwlcsW)=Kh69z%?jY`BuTUnpib9(;N1+x0 z44_#q84VD#R_$zLXK>lGpQoWsN!_(RGkELXCgmlblyBdz5)h2y7F(n_K!G0?FBZqe zMMOv$Gz7sD#BP2Yu;iJKvVok@Z0cd|K@N+ugal|D^yZtX;_OI^SlZ256nrHbqh>eq zoXTX&22*l7MM#bw9{MLFJk7t;H>j`Y5qlqs38zF74B0t(bWxG{Y>R^vn`WC^yaPOI zxogI`Y#Ti`v$?sm$$M`kBoG$w=2KBou&rF!PDxV?Wca?veojw`O6>TdcDEJ^aXvJ5Nm2II<@zpDZO_Oq5Mr%sd(6?+C{baUec7r#+ z7mXWdg2qyuXR#Nei$F+t`}VhzNVNbYWm+Zh@FqJuO(;(vss7{IhI!vzQ}wdH(n160 zPR6J_Snasr99Q9z&D50q>J0Ak`1YEAP!Z8(ma`oI<2OdVQm7?s~^NuZ6EZ{eqe+drBlNEj`1DCY0RQ*(vvHHN8G9 zLs+Q?*DT-FJtZ9UD^@^30ioT?%L`T~O5ovP3APDN#)!P_o+5bjO)BQUHCnBs*}=9BK}|9wbN}i^e#0hvLa@Qi-+0AL`-SJRO6iHv;Vcn( z=MJA=db$`R=TfPxBoUi&#ar_MXjYerh}c}9qf^b+{`Ky}Df6FNwE^%%9;dxsuDJg< zOj$|CMpa}g0pzykviqL4=2QQQ&kE^ZpIFo(aPP~U#4%7fdJOo^(?+7(577LvVN^3~ z1bD8SVz^j?`w2u^$D91h6);sD!*?9n*?Arrwt(Gs8_JQBBx9xNK?4$@*z#`mTM5x` zima|?6dPB5fFY0h-;liR>$x6+YR8`o00>scU}}?P8-gmen+XoxC@7SdgHI!j{AouL z!O#Ep&~L-)OUP5{s`!5+E@1vp;>fspTj+7GIT8*MJz=;0}CPguuDupo;5eY#De9i(3XT=@Z_I(O9bZO!UG5uG-0xqW@1j0ijD+6$=@go405wH+q4&UV|Mje;6ycqV&Ip=c`&0(!!t&P6jI0f}zofXW;u**}MtsjBt&} znKK?oX~V-u7Wf+4V?0-@{&Az=V->hUDKq{@nq^-^tW!rk^Ycc5GU^ibHESBD=>(cj z1?OAF<-VQiMXQ_^#OEb=_MY4;PWU1#%_p(!x>#k_^DTMQ`VJdB=aj@i<7Nm7lIi>4$FCV#tsZLfouKg|D>05(J-60HCRj4*c4~gI^D+6C! z*#N-Z$o%Llz;%H&#t*%Bz2O37+Z3Ca$L8UYatWF71>L46%sCDLi>RP$$_B}KFAHRKr?L}`5l ztc!}8n(fA{Ji?7BE}nG-eipEq&YlH6-)%CoC$e9&jpa24jgkhe8{^eS?P7Ap7C9Bg zIW&1P-8O$V{?%I>iQ6&!n{WB6bM^~X2>-%XTAFGBJ)*Bo2kN}+h%z4{l_x5SD<7R` zS$Y=s^_NAUr1cJSS4MW}pXLD6;r8zf`uc|Xw{H)Ri?ls^t*=)dPCrG}yd$zho$+(j zlNgbi9Sdlz?nx!| z!vhY=O2eyqlT!y#WxPQCF#7vBx>Z0(|U-vM5#}9KJW|Ly|5-@gKtJt_<1%kYIM*nLa0{M`7t=2v@s6U>F? z**~Z67g~JA=3>nCO&V zg4UDHx@Q{EVq}avb^Kc|_@7>^qW>;T1uY4Oz4QokK=4EC?}^6#vN@ymPs{F{WRA6M zJ0z2bzu$|B0!ZzwZcw$dsrISJ=Hl5@B!20#vVm&ZWRHvl9ofvb1t;N>rp1iGXzux? zHq+jnJH%_moi$zUH73R#5kgR1cq|VCH2z-7}WD zXK2X5(sCG(8UV+3 z1Nk75y4Kn!org)Vy3GOsca;n1P2>qVOF!gxSKvzbU6j{REx%4QQK3-t^KYcFLRavi zIPTE{Usl0QH=Cisee}YL+1ZxOPbG^GqKg&&E)|2(h~a|@f_)$0DWNe5>YMiawslG^3LeQezfxM00bU>8c+9OOf8%a@c6U`=F+O z$wX(Li*e-oV3nrU6^p(V8+%;+y$$R@Wu(YGkdf2@^dPwQ+R*rvJQ~;=9?u=Xy;b+R zf`^X}r~x?n5vCh(cqEg?tdQRU%@EhHg%bmRd*Wc%C6)BL>j_@$+@{&TA1`(IgN=`E zlFcs>OAksjLK@NI!AXyppI^P|{E=styIqBz{>>eJ`*M=8Ed2u;bHY$CYFPb7*-52u2#cs)-WEAd#{as zUw=uV&nJ_GWMgj0oXBB?OU|1)?)@G&YpD_mRLEwR5ykU9BpeyFB>q|z2@`9Sy2f~+ zr>HF0BTsCo&6ZlG6=uhpg9WE;q^w`7`G=oEUa%T zW>3z*V!iByR>i+SHoF0Go8b>apaKvDMvqJ|dP~Re(oMsZjN>>}`0NWe0Kkim!xe+|r zfkwVmq@=L2b35T5r2eico8&90i3=w zA057;U&aU-T)4BdM?^HLb}Qb#Oq3uzI_I_*mfy$w>IJ;rZ>a~ONXuX32mDgMrTfrE zZdJ6v!1)K)Q|;HfbSWbhgju7ako_HHw<$kht_ahwUKb$G91x*eut$~+NclnYD-c^` z58kKt5;5+FGsD#F8cZg7l60`YrDuf zJe~pz^~|{RzMMUNx*CR_pVur=AzmX%!$7C%bE(Hm_Ou|EdMAB#iayI)l2;)u=56L% zu$2S}hj{1Z4}2DtsGDH6F1k=u=HI{hJ+H-YZIuXR!|f{ znrNty?=nDNLFNc-|7t~};ipeLdwSLIwEL_BVdx$q0Y=buQW39x4woV@=4(tg)_)`@YvPE=l+RbDG`s z>yu2#4h4MqA`vMm70nLFz2|f976(8?G>x+*?{Ut^Y<3!WWQUujU1b@X<(L~zIzeCCh&oOzz#IltdI=X<_itah!kv?;Epb}-ux z2M`9V9d5O~4Er^f!Ujw}zwBydy$#m&9!GHrS3aqWU#%`nn;Ku&)IQ58W0!HtIWtQp zy)cofn45;&`_GrsgDouD;^Idg8uz9s0-@)$`TjVK#kzgYxo_{S1L0!L*Us zt>*X0I%)l082usER=4Eo9Wl0rB@RL3`6A^YfuI5_eD2i4V@(ei6HzNM=z{mOghWhY zi5421)1~4uWg8@&etW5u6TKB$#-&9 z=x;KD^(TkdWB|f<&#wJRk5IHMK792WR&~8QsJ@j1ylNnw;--(#UE0_{Y@0f5`!WOS zbsBE#g!9=r++Bc|+z@K3FIUDqO9`l%uyHd9W{9|%+>(1B@pZu?OP;u{YDxVTk_UfQ z4!Uv|iG6z#aDm|PQ|zuMMWry?J*@!V1{e%Zfe&LQ-C)#zZQ@^F4mU$>vGy!_0P&<_~X z%a=ZAhyBhgF3HbZE%yvnFwoMRM{-hL2NviS-@U4ND_8x~Q59f>ts(>R5|j$&8v~Hj zSsE{aprIUse4P2Rxn)u$QV^)`;RWF`*HTQ4XnkkeL>PgBLt=}B*qk{R7m`IPl7@D1 zNeJmtLZb5pf2#)Y)e!}1@D1`>c9;_vwwBDVgF#a?)=&Px;hF&9AQ=he@HcI+7ZnzRnP&Xrc)unGsc>b z0V6sP=zrjzsWak}`99j6Zj7G+sVQ}1k7A*lRkWo+5RFE~wT zHT$oVC=?ovX7~1DBXYs$Jo5|f@9Z96(vj;|5y@3SH1&3}4r`_oD}|Vxdh<%Z0SiUl zsGT0Fdg(uYt&Pjspw3lYv#l!OaZ9eP5pqFhZ6MJ;b-Y~1t(3<797!ns9F0pY9@}gQ6wbbt#w&fbhE## zoC}J{bg5@;PJPw;#4Ko zN1utWmzPG*0$>fyC7nVFZ74L5lr96W6vZ6TV>30oc%{SvXnw7#+6CnN<+XpV%@PSF zz-)w+k&Z3w`0Pvfi;ZPDsvUIiR-+BoVgjpnRPA8TN+j`RzZfTN)O}stGXW0bdd$78 zME<9|gRwG(cbiIzV6>$)dH>Ey zmqS@Fj!VokGYEl>FWkR?Xby8F;JEc-vJE2=#rTMlU(;@=Pv~!5G%RM_b`E?M1~zrX zQuWH&s|2@Hw>g?yQ0L!&@1C$;&umF;5zcSR7q<92^hDk{!Z&=%4{W-z>%h?&dxTUW zS>i0wnf-Kja@<(H!sSHgQ1i0cWDFse{mcW(S)Qb4er`&H8th zu1g3H*5XPaJr}|*a#d6a)E?H?7S%Ua)Kd%VsgsKWGPlsXn3dr?LyX5u;<@;U2wOvX zF&>mV1~C|zr(|Kg*{wnarFiTreagL`PpwThtXrROj@gne>ZePjUINr0n8;?AGPCwx zY_}umhP=Kl&hdty(BmA&4EDW8*G`HEJtpJkNp6_WQVj_@Armqq$GIXS!-$`QmxHIj zO;@m3GD0?qCyGz=RJ0~0cqJ3P8LI*^3*N&S&Vet+#>nwTX0wMz4|i&Er|@oeTAFIW zodk^R0B|P>Yax$5MRuNONiog8b)uWjZ&_iiUzBw`xoHcqq*nN_2W7>gqLyZ|amRv( zOYAvbFa?BDO?`^uNOJ&O>dt=id^1>@B~jl{O$IATq^kJ~k)}iQ&O&z@aDR5i?1{o` z)NIrlRg|ogC1v14HUst2*ci{8Kj$;XAWMGEVi%)}|4^kD+h=nryHWtb_J6~MUi7XW zZd#x&>WdwEc)dwZMuK>Sh48jQHfe2r2xg>cAs`Kg9=$qZZ*VXEl5{hL2d5cdO<&Dt zdakYz%1^u|uAwV{aG{s#{H(e#BM$#sw-{&kNi=B-7I@ zQsBWLY2!W5o%&O0i%{ZQs zOM=)&ncMBB?@I|_SZ`?|!EV?pF9aL`dWv9Fz*$gBJ>TYf;w4ccTX`!06pNbSSn zpqs_Z0TJsdWGq%c#3<~qy^v;dz39@uVw<))o!WxVjAr}_T$UR976SYot-X=7r`d1S zy~ksTixfx1myLNjWO6B-PTjkAzs*cIkeIf6Qc}1(+P>S^lo?%B!g7qs`3_z-CU&j0`b literal 0 HcmV?d00001 diff --git a/sample/textRenderingMsdf/index.html b/sample/textRenderingMsdf/index.html new file mode 100644 index 00000000..5c58503b --- /dev/null +++ b/sample/textRenderingMsdf/index.html @@ -0,0 +1,27 @@ + + + + + + webgpu-samples: textRenderingMsdf + + + + + + + + diff --git a/sample/textRenderingMsdf/main.ts b/sample/textRenderingMsdf/main.ts new file mode 100644 index 00000000..7ffba76d --- /dev/null +++ b/sample/textRenderingMsdf/main.ts @@ -0,0 +1,310 @@ +import { mat4, vec3 } from 'wgpu-matrix'; + +import { + cubeVertexArray, + cubeVertexSize, + cubeUVOffset, + cubePositionOffset, + cubeVertexCount, +} from '../../meshes/cube'; +import { MsdfTextRenderer } from './msdfText'; + +import basicVertWGSL from '../../shaders/basic.vert.wgsl'; +import vertexPositionColorWGSL from '../../shaders/vertexPositionColor.frag.wgsl'; + +const canvas = document.querySelector('canvas') as HTMLCanvasElement; +const adapter = await navigator.gpu.requestAdapter(); +const device = await adapter.requestDevice(); + +const context = canvas.getContext('webgpu') as GPUCanvasContext; + +const devicePixelRatio = window.devicePixelRatio || 1; +canvas.width = canvas.clientWidth * devicePixelRatio; +canvas.height = canvas.clientHeight * devicePixelRatio; +const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); +const depthFormat = 'depth24plus'; + +context.configure({ + device, + format: presentationFormat, + alphaMode: 'premultiplied', +}); + +const textRenderer = new MsdfTextRenderer(device, presentationFormat, depthFormat); +const font = await textRenderer.createFont(new URL( + '../../assets/font/ya-hei-ascii-msdf.json', + import.meta.url +).toString()); + +function getTextTransform(position: [number, number, number], rotation?: [number, number, number]) { + const textTransform = mat4.create(); + mat4.identity(textTransform); + mat4.translate(textTransform, position, textTransform); + if (rotation && rotation[0] != 0) { + mat4.rotateX(textTransform, rotation[0], textTransform); + } + if (rotation && rotation[1] != 0) { + mat4.rotateY(textTransform, rotation[1], textTransform); + } + if (rotation && rotation[2] != 0) { + mat4.rotateZ(textTransform, rotation[2], textTransform); + } + return textTransform; +} + +const textTransforms = [ + getTextTransform([0, 0, 1.1]), + getTextTransform([0, 0, -1.1], [0, Math.PI, 0]), + getTextTransform([1.1, 0, 0], [0, Math.PI/2, 0]), + getTextTransform([-1.1, 0, 0], [0, -Math.PI/2, 0]), + getTextTransform([0, 1.1, 0], [-Math.PI/2, 0, 0]), + getTextTransform([0, -1.1, 0], [Math.PI/2, 0, 0]), +]; + +const titleText = textRenderer.formatText(font, `WebGPU`, +{ centered: true, pixelScale: 1/128 }); +const largeText = textRenderer.formatText(font, ` +WebGPU exposes an API for performing operations, such as rendering +and computation, on a Graphics Processing Unit. + +Graphics Processing Units, or GPUs for short, have been essential +in enabling rich rendering and computational applications in personal +computing. WebGPU is an API that exposes the capabilities of GPU +hardware for the Web. The API is designed from the ground up to +efficiently map to (post-2014) native GPU APIs. WebGPU is not related +to WebGL and does not explicitly target OpenGL ES. + +WebGPU sees physical GPU hardware as GPUAdapters. It provides a +connection to an adapter via GPUDevice, which manages resources, and +the device’s GPUQueues, which execute commands. GPUDevice may have +its own memory with high-speed access to the processing units. +GPUBuffer and GPUTexture are the physical resources backed by GPU +memory. GPUCommandBuffer and GPURenderBundle are containers for +user-recorded commands. GPUShaderModule contains shader code. The +other resources, such as GPUSampler or GPUBindGroup, configure the +way physical resources are used by the GPU. + +GPUs execute commands encoded in GPUCommandBuffers by feeding data +through a pipeline, which is a mix of fixed-function and programmable +stages. Programmable stages execute shaders, which are special +programs designed to run on GPU hardware. Most of the state of a +pipeline is defined by a GPURenderPipeline or a GPUComputePipeline +object. The state not included in these pipeline objects is set +during encoding with commands, such as beginRenderPass() or +setBlendConstant().`, +{ pixelScale: 1/256 }); + +const text = [ + textRenderer.formatText(font, "Front", { centered: true, pixelScale: 1/128, color: [1, 0, 0, 1] }), + textRenderer.formatText(font, "Back", { centered: true, pixelScale: 1/128, color: [0, 1, 1, 1] }), + textRenderer.formatText(font, "Right", { centered: true, pixelScale: 1/128, color: [0, 1, 0, 1] }), + textRenderer.formatText(font, "Left", { centered: true, pixelScale: 1/128, color: [1, 0, 1, 1] }), + textRenderer.formatText(font, "Top", { centered: true, pixelScale: 1/128, color: [0, 0, 1, 1] }), + textRenderer.formatText(font, "Bottom", { centered: true, pixelScale: 1/128, color: [1, 1, 0, 1] }), + + titleText, + largeText, +]; + +// Create a vertex buffer from the cube data. +const verticesBuffer = device.createBuffer({ + size: cubeVertexArray.byteLength, + usage: GPUBufferUsage.VERTEX, + mappedAtCreation: true, +}); +new Float32Array(verticesBuffer.getMappedRange()).set(cubeVertexArray); +verticesBuffer.unmap(); + +const pipeline = device.createRenderPipeline({ + layout: 'auto', + vertex: { + module: device.createShaderModule({ + code: basicVertWGSL, + }), + entryPoint: 'main', + buffers: [ + { + arrayStride: cubeVertexSize, + attributes: [ + { + // position + shaderLocation: 0, + offset: cubePositionOffset, + format: 'float32x4', + }, + { + // uv + shaderLocation: 1, + offset: cubeUVOffset, + format: 'float32x2', + }, + ], + }, + ], + }, + fragment: { + module: device.createShaderModule({ + code: vertexPositionColorWGSL, + }), + entryPoint: 'main', + targets: [ + { + format: presentationFormat, + }, + ], + }, + primitive: { + topology: 'triangle-list', + + // Backface culling since the cube is solid piece of geometry. + // Faces pointing away from the camera will be occluded by faces + // pointing toward the camera. + cullMode: 'back', + }, + + // Enable depth testing so that the fragment closest to the camera + // is rendered in front. + depthStencil: { + depthWriteEnabled: true, + depthCompare: 'less', + format: depthFormat, + }, +}); + +const depthTexture = device.createTexture({ + size: [canvas.width, canvas.height], + format: depthFormat, + usage: GPUTextureUsage.RENDER_ATTACHMENT, +}); + +const uniformBufferSize = 4 * 16; // 4x4 matrix +const uniformBuffer = device.createBuffer({ + size: uniformBufferSize, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, +}); + +const uniformBindGroup = device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { + binding: 0, + resource: { + buffer: uniformBuffer, + }, + }, + ], +}); + +const renderPassDescriptor: GPURenderPassDescriptor = { + colorAttachments: [ + { + view: undefined, // Assigned later + + clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, + loadOp: 'clear', + storeOp: 'store', + }, + ], + depthStencilAttachment: { + view: depthTexture.createView(), + + depthClearValue: 1.0, + depthLoadOp: 'clear', + depthStoreOp: 'store', + }, +}; + +const aspect = canvas.width / canvas.height; +const projectionMatrix = mat4.perspective( + (2 * Math.PI) / 5, + aspect, + 1, + 100.0 +); +const modelViewProjectionMatrix = mat4.create(); + +const frameBindGroup = device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { + binding: 0, + resource: { + buffer: uniformBuffer, + }, + }, + ], +}); + +const start = Date.now(); +function getTransformationMatrix() { + const now = Date.now() / 5000; + const viewMatrix = mat4.identity(); + mat4.translate(viewMatrix, vec3.fromValues(0, 0, -5), viewMatrix); + + const modelMatrix = mat4.identity(); + mat4.translate(modelMatrix, vec3.fromValues(0, 2, -3), modelMatrix); + mat4.rotate( + modelMatrix, + vec3.fromValues(Math.sin(now), Math.cos(now), 0), + 1, + modelMatrix + ); + + // Update the matrix for the cube + mat4.multiply(projectionMatrix, viewMatrix, modelViewProjectionMatrix); + mat4.multiply(modelViewProjectionMatrix, modelMatrix, modelViewProjectionMatrix); + + // Update the projection and view matrices for the text + textRenderer.updateCamera(projectionMatrix, viewMatrix); + + // Update the transform of all the text surrounding the cube + const textMatrix = mat4.create(); + for (let [index, transform] of textTransforms.entries()) { + mat4.multiply(modelMatrix, transform, textMatrix); + text[index].setTransform(textMatrix); + } + + // Update the transform of the larger block of text + const crawl = ((Date.now() - start) / 2500) % 14; + mat4.identity(textMatrix); + mat4.rotateX( + textMatrix, + -Math.PI / 8, + textMatrix + ); + mat4.translate(textMatrix, [0, crawl - 3, 0], textMatrix); + titleText.setTransform(textMatrix); + mat4.translate(textMatrix, [-3, -0.1, 0], textMatrix); + largeText.setTransform(textMatrix); + + return modelViewProjectionMatrix as Float32Array; +} + +function frame() { + const transformationMatrix = getTransformationMatrix(); + device.queue.writeBuffer( + uniformBuffer, + 0, + transformationMatrix.buffer, + transformationMatrix.byteOffset, + transformationMatrix.byteLength + ); + renderPassDescriptor.colorAttachments[0].view = context + .getCurrentTexture() + .createView(); + + const commandEncoder = device.createCommandEncoder(); + const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); + passEncoder.setPipeline(pipeline); + passEncoder.setBindGroup(0, uniformBindGroup); + passEncoder.setVertexBuffer(0, verticesBuffer); + passEncoder.draw(cubeVertexCount, 1, 0, 0); + + textRenderer.render(passEncoder, ...text); + + passEncoder.end(); + device.queue.submit([commandEncoder.finish()]); + + requestAnimationFrame(frame); +} +requestAnimationFrame(frame); diff --git a/sample/textRenderingMsdf/meta.ts b/sample/textRenderingMsdf/meta.ts new file mode 100644 index 00000000..0ed0fb59 --- /dev/null +++ b/sample/textRenderingMsdf/meta.ts @@ -0,0 +1,19 @@ +export default { + name: 'Text Rendering - MSDF', + description: `This example uses multichannel signed distance fields (MSDF) to render text. MSDF + fonts are more complex to implement than using Canvas 2D to generate text, but the resulting + text looks smoother while using less memory than the Canvas 2D approach, especially at high + zoom levels. They can be used to render larger amounts of text efficiently. + + The font texture is generated using Don + McCurdy's MSDF font generation tool`, + filename: __DIRNAME__, + sources: [ + { path: 'main.ts' }, + { path: 'msdfText.ts' }, + { path: 'msdfText.wgsl' }, + { path: '../../shaders/basic.vert.wgsl' }, + { path: '../../shaders/vertexPositionColor.frag.wgsl' }, + { path: '../../meshes/cube.ts' }, + ], +}; diff --git a/sample/textRenderingMsdf/msdfText.ts b/sample/textRenderingMsdf/msdfText.ts new file mode 100644 index 00000000..abfb0c3f --- /dev/null +++ b/sample/textRenderingMsdf/msdfText.ts @@ -0,0 +1,421 @@ +import { mat4 } from 'wgpu-matrix'; + +import msdfTextWGSL from './msdfText.wgsl'; + +type Mat4 = mat4.default; + +// The kerning map stores a spare map of character ID pairs with an associated +// X offset that should be applied to the character spacing when the second +// character ID is rendered after the first. +type KerningMap = Map>; + +export class MsdfFont { + charCount: number; + defaultChar: any; + constructor(public pipeline: GPURenderPipeline, public bindGroup: GPUBindGroup, + public lineHeight: number, public chars: { [x: number]: any }, + public kernings: KerningMap) { + const charArray = Object.values(chars); + this.charCount = charArray.length; + this.defaultChar = charArray[0]; + } + + getChar(charCode: number): any { + let char = this.chars[charCode]; + if (!char) { char = this.defaultChar; } + return char; + } + + // Gets the distance in pixels a line should advance for a given character code. If the upcoming + // character code is given any kerning between the two characters will be taken into account. + getXAdvance(charCode: number, nextCharCode: number = -1): number { + const char = this.getChar(charCode); + if (nextCharCode >= 0) { + const kerning = this.kernings.get(charCode); + if (kerning) { + return char.xadvance + (kerning.get(nextCharCode) ?? 0); + } + } + return char.xadvance; + } +}; + +export interface MsdfTextMeasurements { + width: number, + height: number, + lineWidths: number[], + printedCharCount: number, +}; + +export class MsdfText { + private bufferArray = new Float32Array(24); + private bufferArrayDirty = true; + + constructor(public device: GPUDevice, private renderBundle: GPURenderBundle, public measurements: MsdfTextMeasurements, public font: MsdfFont, public textBuffer: GPUBuffer) { + mat4.identity(this.bufferArray); + this.setColor(1, 1, 1, 1); + this.setPixelScale(1/512); + this.bufferArrayDirty = true; + } + + getRenderBundle() { + if (this.bufferArrayDirty) { + this.bufferArrayDirty = false; + this.device.queue.writeBuffer(this.textBuffer, 0, this.bufferArray, 0, this.bufferArray.length); + } + return this.renderBundle; + } + + setTransform(matrix: Mat4) { + mat4.copy(matrix, this.bufferArray); + this.bufferArrayDirty = true; + } + + setColor(r: number, g: number, b: number, a: number = 1.0) { + this.bufferArray[16] = r; + this.bufferArray[17] = g; + this.bufferArray[18] = b; + this.bufferArray[19] = a; + this.bufferArrayDirty = true; + } + + setPixelScale(pixelScale: number) { + this.bufferArray[20] = pixelScale; + this.bufferArrayDirty = true; + } +}; + +export interface MsdfTextFormattingOptions { + centered?: boolean, + pixelScale?: number, + color?: [number, number, number, number], +}; + +export class MsdfTextRenderer { + fontBindGroupLayout: GPUBindGroupLayout; + textBindGroupLayout: GPUBindGroupLayout; + pipelinePromise: Promise; + sampler: GPUSampler; + cameraUniformBuffer: GPUBuffer; + + renderBundleDescriptor: GPURenderBundleEncoderDescriptor; + cameraArray: Float32Array = new Float32Array(16 * 2); + + constructor(public device: GPUDevice, colorFormat: GPUTextureFormat, depthFormat: GPUTextureFormat) { + this.renderBundleDescriptor = { + colorFormats: [colorFormat], + depthStencilFormat: depthFormat, + }; + + this.sampler = device.createSampler({ + label: 'MSDF text sampler', + minFilter: 'linear', + magFilter: 'linear', + mipmapFilter: 'linear', + maxAnisotropy: 16, + }); + + this.cameraUniformBuffer = device.createBuffer({ + label: 'MSDF camera uniform buffer', + size: this.cameraArray.byteLength, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM + }); + + this.fontBindGroupLayout = device.createBindGroupLayout({ + label: 'MSDF font group layout', + entries: [{ + binding: 0, + visibility: GPUShaderStage.FRAGMENT, + texture: {} + }, { + binding: 1, + visibility: GPUShaderStage.FRAGMENT, + sampler: {} + }, { + binding: 2, + visibility: GPUShaderStage.VERTEX, + buffer: { type: 'read-only-storage' } + }] + }); + + this.textBindGroupLayout = device.createBindGroupLayout({ + label: 'MSDF text group layout', + entries: [{ + binding: 0, + visibility: GPUShaderStage.VERTEX, + buffer: {} + }, { + binding: 1, + visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, + buffer: { type: 'read-only-storage' } + }] + }); + + const shaderModule = device.createShaderModule({ + label: 'MSDF text shader', + code: msdfTextWGSL, + }); + + this.pipelinePromise = device.createRenderPipelineAsync({ + label: `msdf text pipeline`, + layout: device.createPipelineLayout({ + bindGroupLayouts: [ + this.fontBindGroupLayout, + this.textBindGroupLayout + ] + }), + vertex: { + module: shaderModule, + entryPoint: 'vertexMain' + }, + fragment: { + module: shaderModule, + entryPoint: 'fragmentMain', + targets: [{ + format: colorFormat, + blend: { + color: { + srcFactor: 'src-alpha', + dstFactor: 'one-minus-src-alpha' + }, + alpha: { + srcFactor: 'one', + dstFactor: 'one' + } + } + }], + }, + primitive: { + topology: 'triangle-strip', + stripIndexFormat: 'uint32' + }, + depthStencil: { + depthWriteEnabled: false, + depthCompare: 'less', + format: depthFormat, + } + }); + } + + async loadTexture(url: string) { + const response = await fetch(url); + const imageBitmap = await createImageBitmap(await response.blob()); + + const texture = this.device.createTexture({ + label: `MSDF font texture ${url}`, + size: [imageBitmap.width, imageBitmap.height, 1], + format: 'rgba8unorm', + usage: + GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.COPY_DST | + GPUTextureUsage.RENDER_ATTACHMENT, + }); + this.device.queue.copyExternalImageToTexture( + { source: imageBitmap }, + { texture }, + [imageBitmap.width, imageBitmap.height] + ); + return texture; + } + + async createFont(fontJsonUrl: string): Promise { + const response = await fetch(fontJsonUrl); + const json = await response.json(); + + const i = fontJsonUrl.lastIndexOf('/'); + const baseUrl = (i !== -1) ? fontJsonUrl.substring(0, i + 1) : undefined; + + const pagePromises = []; + for (const pageUrl of json.pages) { + pagePromises.push(this.loadTexture(baseUrl + pageUrl)); + } + + const charCount = json.chars.length; + const charsBuffer = this.device.createBuffer({ + label: 'MSDF character layout buffer', + size: charCount * Float32Array.BYTES_PER_ELEMENT * 8, + usage: GPUBufferUsage.STORAGE, + mappedAtCreation: true, + }); + + const charsArray = new Float32Array(charsBuffer.getMappedRange()); + + const u = 1 / json.common.scaleW; + const v = 1 / json.common.scaleH; + + const chars: { [x: number]: any } = {}; + + let offset = 0; + for (const [i, char] of json.chars.entries()) { + chars[char.id] = char; + chars[char.id].charIndex = i; + charsArray[offset] = char.x * u; // texOffset.x + charsArray[offset+1] = char.y * v; // texOffset.y + charsArray[offset+2] = char.width * u; // texExtent.x + charsArray[offset+3] = char.height * v; // texExtent.y + charsArray[offset+4] = char.width; // size.x + charsArray[offset+5] = char.height; // size.y + charsArray[offset+6] = char.xoffset; // offset.x + charsArray[offset+7] = -char.yoffset; // offset.y + offset += 8; + } + + charsBuffer.unmap(); + + const pageTextures = await Promise.all(pagePromises); + + const bindGroup = this.device.createBindGroup({ + label: 'msdf font bind group', + layout: this.fontBindGroupLayout, + entries: [{ + binding: 0, + // TODO: Allow multi-page fonts + resource: pageTextures[0].createView(), + }, { + binding: 1, + resource: this.sampler, + }, { + binding: 2, + resource: { buffer: charsBuffer }, + }] + }); + + const kernings = new Map(); + + if (json.kernings) { + for (const kearning of json.kernings) { + let charKerning = kernings.get(kearning.first); + if (!charKerning) { + charKerning = new Map(); + kernings.set(kearning.first, charKerning); + } + charKerning.set(kearning.second, kearning.amount); + } + } + + return new MsdfFont(await this.pipelinePromise, bindGroup, json.common.lineHeight, chars, kernings); + } + + formatText(font: MsdfFont, text: string, options: MsdfTextFormattingOptions = {}): MsdfText { + const textBuffer = this.device.createBuffer({ + label: 'msdf text buffer', + size: (text.length + 6) * Float32Array.BYTES_PER_ELEMENT * 4, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, + mappedAtCreation: true, + }); + + const textArray = new Float32Array(textBuffer.getMappedRange()); + let offset = 24; // Accounts for the values managed by MsdfText internally. + + let measurements: MsdfTextMeasurements; + if (options.centered) { + measurements = this.measureText(font, text); + + this.measureText(font, text, (textX: number, textY: number, line: number, char: any) => { + const lineOffset = measurements.width * -0.5 - (measurements.width - measurements.lineWidths[line]) * -0.5; + + textArray[offset] = textX + lineOffset; + textArray[offset+1] = textY + measurements.height * 0.5; + textArray[offset+2] = char.charIndex; + offset += 4; + }); + } else { + measurements = this.measureText(font, text, (textX: number, textY: number, line: number, char: any) => { + textArray[offset] = textX; + textArray[offset+1] = textY; + textArray[offset+2] = char.charIndex; + offset += 4; + }); + } + + textBuffer.unmap(); + + const bindGroup = this.device.createBindGroup({ + label: 'msdf text bind group', + layout: this.textBindGroupLayout, + entries: [{ + binding: 0, + resource: { buffer: this.cameraUniformBuffer }, + }, { + binding: 1, + resource: { buffer: textBuffer }, + }] + }); + + const encoder = this.device.createRenderBundleEncoder(this.renderBundleDescriptor); + encoder.setPipeline(font.pipeline); + encoder.setBindGroup(0, font.bindGroup); + encoder.setBindGroup(1, bindGroup); + encoder.draw(4, measurements.printedCharCount); + const renderBundle = encoder.finish(); + + var msdfText = new MsdfText(this.device, renderBundle, measurements, font, textBuffer); + if (options.pixelScale !== undefined) { + msdfText.setPixelScale(options.pixelScale); + } + + if (options.color !== undefined) { + msdfText.setColor(...options.color); + } + + return msdfText; + } + + measureText(font: MsdfFont, text: string, charCallback?: (x: number, y: number, line: number, char: any) => void ): MsdfTextMeasurements { + let maxWidth = 0; + const lineWidths: number[] = []; + + let textOffsetX = 0; + let textOffsetY = 0; + let line = 0; + let printedCharCount = 0; + let nextCharCode = text.charCodeAt(0); + for (let i = 0; i < text.length; ++i) { + let charCode = nextCharCode; + nextCharCode = i < text.length - 1 ? text.charCodeAt(i+1) : -1; + + switch(charCode) { + case 10: // Newline + lineWidths.push(textOffsetX); + line++; + maxWidth = Math.max(maxWidth, textOffsetX); + textOffsetX = 0; + textOffsetY -= font.lineHeight + case 13: // CR + break; + case 32: // Space + // For spaces, advance the offset without actually adding a character. + textOffsetX += font.getXAdvance(charCode); + break; + default: { + if (charCallback) { + charCallback(textOffsetX, textOffsetY, line, font.getChar(charCode)); + } + textOffsetX += font.getXAdvance(charCode, nextCharCode); + printedCharCount++; + } + } + } + + lineWidths.push(textOffsetX); + maxWidth = Math.max(maxWidth, textOffsetX); + + return { + width: maxWidth, + height: lineWidths.length * font.lineHeight, + lineWidths, + printedCharCount, + }; + } + + updateCamera(projection: Mat4, view: Mat4) { + this.cameraArray.set(projection, 0); + this.cameraArray.set(view, 16); + this.device.queue.writeBuffer(this.cameraUniformBuffer, 0, this.cameraArray); + } + + render(renderPass: GPURenderPassEncoder, ...text: MsdfText[]) { + const renderBundles = text.map(t => t.getRenderBundle()); + renderPass.executeBundles(renderBundles); + } +} diff --git a/sample/textRenderingMsdf/msdfText.wgsl b/sample/textRenderingMsdf/msdfText.wgsl new file mode 100644 index 00000000..9671a5a1 --- /dev/null +++ b/sample/textRenderingMsdf/msdfText.wgsl @@ -0,0 +1,81 @@ +// Positions for simple quad geometry +const pos : array = array( + vec2f(0, -1), vec2f(1, -1), vec2f(0, 0), vec2f(1, 0) +); + +struct VertexInput { + @builtin(vertex_index) vertex : u32, + @builtin(instance_index) instance : u32, +}; + +struct VertexOutput { + @builtin(position) position : vec4f, + @location(0) texcoord : vec2f, +}; + +struct Char { + texOffset: vec2f, + texExtent: vec2f, + size: vec2f, + offset: vec2f, +}; + +struct FormattedText { + transform: mat4x4f, + color: vec4f, + scale: f32, + chars: array, +}; + +struct Camera { + projection: mat4x4f, + view: mat4x4f, +}; + +// Font bindings +@group(0) @binding(0) var fontTexture: texture_2d; +@group(0) @binding(1) var fontSampler: sampler; +@group(0) @binding(2) var chars: array; + +// Text bindings +@group(1) @binding(0) var camera: Camera; +@group(1) @binding(1) var text: FormattedText; + +@vertex +fn vertexMain(input : VertexInput) -> VertexOutput { + let textElement = text.chars[input.instance]; + let char = chars[u32(textElement.z)]; + let charPos = (pos[input.vertex] * char.size + textElement.xy + char.offset) * text.scale; + + var output : VertexOutput; + output.position = camera.projection * camera.view * text.transform * vec4f(charPos, 0, 1); + + output.texcoord = pos[input.vertex] * vec2f(1, -1); + output.texcoord *= char.texExtent; + output.texcoord += char.texOffset; + return output; +} + +fn sampleMsdf(texcoord: vec2f) -> f32 { + let c = textureSample(fontTexture, fontSampler, texcoord); + return max(min(c.r, c.g), min(max(c.r, c.g), c.b)); +} + +// Antialiasing technique from https://drewcassidy.me/2020/06/26/sdf-antialiasing/ +@fragment +fn fragmentMain(input : VertexOutput) -> @location(0) vec4f { + let dist = 0.5 - sampleMsdf(input.texcoord); + + // sdf distance per pixel (gradient vector) + let ddist = vec2f(dpdx(dist), dpdy(dist)); + + // distance to edge in pixels (scalar) + let pixelDist = dist / length(ddist); + + let alpha = saturate(0.5 - pixelDist); + if (alpha < 0.001) { + discard; + } + + return vec4f(text.color.rgb, text.color.a * alpha); +} \ No newline at end of file diff --git a/src/samples.ts b/src/samples.ts index 22dd81ff..6d247d63 100644 --- a/src/samples.ts +++ b/src/samples.ts @@ -27,6 +27,7 @@ import samplerParameters from '../sample/samplerParameters/meta'; import shadowMapping from '../sample/shadowMapping/meta'; import skinnedMesh from '../sample/skinnedMesh/meta'; import spookyball from '../sample/spookyball/meta'; +import textRenderingMsdf from '../sample/textRenderingMsdf/meta'; import texturedCube from '../sample/texturedCube/meta'; import twoCubes from '../sample/twoCubes/meta'; import videoUploading from '../sample/videoUploading/meta'; @@ -115,6 +116,7 @@ export const pageCategories: PageCategory[] = [ cornell, 'a-buffer': aBuffer, skinnedMesh, + textRenderingMsdf, }, }, From 3c77902bb6b76b7a81dcd3bad37af043d3b3347e Mon Sep 17 00:00:00 2001 From: Brandon Jones Date: Fri, 8 Mar 2024 13:25:37 -0800 Subject: [PATCH 2/2] Address review feedback --- sample/textRenderingMsdf/main.ts | 117 +++++---- sample/textRenderingMsdf/meta.ts | 9 +- sample/textRenderingMsdf/msdfText.ts | 324 ++++++++++++++++--------- sample/textRenderingMsdf/msdfText.wgsl | 4 +- 4 files changed, 280 insertions(+), 174 deletions(-) diff --git a/sample/textRenderingMsdf/main.ts b/sample/textRenderingMsdf/main.ts index 7ffba76d..e83e2116 100644 --- a/sample/textRenderingMsdf/main.ts +++ b/sample/textRenderingMsdf/main.ts @@ -30,13 +30,22 @@ context.configure({ alphaMode: 'premultiplied', }); -const textRenderer = new MsdfTextRenderer(device, presentationFormat, depthFormat); -const font = await textRenderer.createFont(new URL( - '../../assets/font/ya-hei-ascii-msdf.json', - import.meta.url -).toString()); +const textRenderer = new MsdfTextRenderer( + device, + presentationFormat, + depthFormat +); +const font = await textRenderer.createFont( + new URL( + '../../assets/font/ya-hei-ascii-msdf.json', + import.meta.url + ).toString() +); -function getTextTransform(position: [number, number, number], rotation?: [number, number, number]) { +function getTextTransform( + position: [number, number, number], + rotation?: [number, number, number] +) { const textTransform = mat4.create(); mat4.identity(textTransform); mat4.translate(textTransform, position, textTransform); @@ -55,15 +64,19 @@ function getTextTransform(position: [number, number, number], rotation?: [number const textTransforms = [ getTextTransform([0, 0, 1.1]), getTextTransform([0, 0, -1.1], [0, Math.PI, 0]), - getTextTransform([1.1, 0, 0], [0, Math.PI/2, 0]), - getTextTransform([-1.1, 0, 0], [0, -Math.PI/2, 0]), - getTextTransform([0, 1.1, 0], [-Math.PI/2, 0, 0]), - getTextTransform([0, -1.1, 0], [Math.PI/2, 0, 0]), + getTextTransform([1.1, 0, 0], [0, Math.PI / 2, 0]), + getTextTransform([-1.1, 0, 0], [0, -Math.PI / 2, 0]), + getTextTransform([0, 1.1, 0], [-Math.PI / 2, 0, 0]), + getTextTransform([0, -1.1, 0], [Math.PI / 2, 0, 0]), ]; -const titleText = textRenderer.formatText(font, `WebGPU`, -{ centered: true, pixelScale: 1/128 }); -const largeText = textRenderer.formatText(font, ` +const titleText = textRenderer.formatText(font, `WebGPU`, { + centered: true, + pixelScale: 1 / 128, +}); +const largeText = textRenderer.formatText( + font, + ` WebGPU exposes an API for performing operations, such as rendering and computation, on a Graphics Processing Unit. @@ -92,15 +105,40 @@ pipeline is defined by a GPURenderPipeline or a GPUComputePipeline object. The state not included in these pipeline objects is set during encoding with commands, such as beginRenderPass() or setBlendConstant().`, -{ pixelScale: 1/256 }); + { pixelScale: 1 / 256 } +); const text = [ - textRenderer.formatText(font, "Front", { centered: true, pixelScale: 1/128, color: [1, 0, 0, 1] }), - textRenderer.formatText(font, "Back", { centered: true, pixelScale: 1/128, color: [0, 1, 1, 1] }), - textRenderer.formatText(font, "Right", { centered: true, pixelScale: 1/128, color: [0, 1, 0, 1] }), - textRenderer.formatText(font, "Left", { centered: true, pixelScale: 1/128, color: [1, 0, 1, 1] }), - textRenderer.formatText(font, "Top", { centered: true, pixelScale: 1/128, color: [0, 0, 1, 1] }), - textRenderer.formatText(font, "Bottom", { centered: true, pixelScale: 1/128, color: [1, 1, 0, 1] }), + textRenderer.formatText(font, 'Front', { + centered: true, + pixelScale: 1 / 128, + color: [1, 0, 0, 1], + }), + textRenderer.formatText(font, 'Back', { + centered: true, + pixelScale: 1 / 128, + color: [0, 1, 1, 1], + }), + textRenderer.formatText(font, 'Right', { + centered: true, + pixelScale: 1 / 128, + color: [0, 1, 0, 1], + }), + textRenderer.formatText(font, 'Left', { + centered: true, + pixelScale: 1 / 128, + color: [1, 0, 1, 1], + }), + textRenderer.formatText(font, 'Top', { + centered: true, + pixelScale: 1 / 128, + color: [0, 0, 1, 1], + }), + textRenderer.formatText(font, 'Bottom', { + centered: true, + pixelScale: 1 / 128, + color: [1, 1, 0, 1], + }), titleText, largeText, @@ -121,7 +159,6 @@ const pipeline = device.createRenderPipeline({ module: device.createShaderModule({ code: basicVertWGSL, }), - entryPoint: 'main', buffers: [ { arrayStride: cubeVertexSize, @@ -146,7 +183,6 @@ const pipeline = device.createRenderPipeline({ module: device.createShaderModule({ code: vertexPositionColorWGSL, }), - entryPoint: 'main', targets: [ { format: presentationFormat, @@ -154,8 +190,6 @@ const pipeline = device.createRenderPipeline({ ], }, primitive: { - topology: 'triangle-list', - // Backface culling since the cube is solid piece of geometry. // Faces pointing away from the camera will be occluded by faces // pointing toward the camera. @@ -200,7 +234,7 @@ const renderPassDescriptor: GPURenderPassDescriptor = { { view: undefined, // Assigned later - clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, + clearValue: [0, 0, 0, 1], loadOp: 'clear', storeOp: 'store', }, @@ -215,26 +249,9 @@ const renderPassDescriptor: GPURenderPassDescriptor = { }; const aspect = canvas.width / canvas.height; -const projectionMatrix = mat4.perspective( - (2 * Math.PI) / 5, - aspect, - 1, - 100.0 -); +const projectionMatrix = mat4.perspective((2 * Math.PI) / 5, aspect, 1, 100.0); const modelViewProjectionMatrix = mat4.create(); -const frameBindGroup = device.createBindGroup({ - layout: pipeline.getBindGroupLayout(0), - entries: [ - { - binding: 0, - resource: { - buffer: uniformBuffer, - }, - }, - ], -}); - const start = Date.now(); function getTransformationMatrix() { const now = Date.now() / 5000; @@ -252,14 +269,18 @@ function getTransformationMatrix() { // Update the matrix for the cube mat4.multiply(projectionMatrix, viewMatrix, modelViewProjectionMatrix); - mat4.multiply(modelViewProjectionMatrix, modelMatrix, modelViewProjectionMatrix); + mat4.multiply( + modelViewProjectionMatrix, + modelMatrix, + modelViewProjectionMatrix + ); // Update the projection and view matrices for the text textRenderer.updateCamera(projectionMatrix, viewMatrix); // Update the transform of all the text surrounding the cube const textMatrix = mat4.create(); - for (let [index, transform] of textTransforms.entries()) { + for (const [index, transform] of textTransforms.entries()) { mat4.multiply(modelMatrix, transform, textMatrix); text[index].setTransform(textMatrix); } @@ -267,11 +288,7 @@ function getTransformationMatrix() { // Update the transform of the larger block of text const crawl = ((Date.now() - start) / 2500) % 14; mat4.identity(textMatrix); - mat4.rotateX( - textMatrix, - -Math.PI / 8, - textMatrix - ); + mat4.rotateX(textMatrix, -Math.PI / 8, textMatrix); mat4.translate(textMatrix, [0, crawl - 3, 0], textMatrix); titleText.setTransform(textMatrix); mat4.translate(textMatrix, [-3, -0.1, 0], textMatrix); diff --git a/sample/textRenderingMsdf/meta.ts b/sample/textRenderingMsdf/meta.ts index 0ed0fb59..d05a5590 100644 --- a/sample/textRenderingMsdf/meta.ts +++ b/sample/textRenderingMsdf/meta.ts @@ -1,12 +1,11 @@ export default { name: 'Text Rendering - MSDF', description: `This example uses multichannel signed distance fields (MSDF) to render text. MSDF - fonts are more complex to implement than using Canvas 2D to generate text, but the resulting - text looks smoother while using less memory than the Canvas 2D approach, especially at high - zoom levels. They can be used to render larger amounts of text efficiently. +fonts are more complex to implement than using Canvas 2D to generate text, but the resulting +text looks smoother while using less memory than the Canvas 2D approach, especially at high +zoom levels. They can be used to render larger amounts of text efficiently. - The font texture is generated using Don - McCurdy's MSDF font generation tool`, +The font texture is generated using [Don McCurdy's MSDF font generation tool](https://msdf-bmfont.donmccurdy.com/)`, filename: __DIRNAME__, sources: [ { path: 'main.ts' }, diff --git a/sample/textRenderingMsdf/msdfText.ts b/sample/textRenderingMsdf/msdfText.ts index abfb0c3f..065213a7 100644 --- a/sample/textRenderingMsdf/msdfText.ts +++ b/sample/textRenderingMsdf/msdfText.ts @@ -9,20 +9,42 @@ type Mat4 = mat4.default; // character ID is rendered after the first. type KerningMap = Map>; +interface MsdfChar { + id: number; + index: number; + char: string; + width: number; + height: number; + xoffset: number; + yofsset: number; + xadvance: number; + chnl: number; + x: number; + y: number; + page: number; + charIndex: number; +} + export class MsdfFont { charCount: number; - defaultChar: any; - constructor(public pipeline: GPURenderPipeline, public bindGroup: GPUBindGroup, - public lineHeight: number, public chars: { [x: number]: any }, - public kernings: KerningMap) { + defaultChar: MsdfChar; + constructor( + public pipeline: GPURenderPipeline, + public bindGroup: GPUBindGroup, + public lineHeight: number, + public chars: { [x: number]: MsdfChar }, + public kernings: KerningMap + ) { const charArray = Object.values(chars); this.charCount = charArray.length; this.defaultChar = charArray[0]; } - getChar(charCode: number): any { + getChar(charCode: number): MsdfChar { let char = this.chars[charCode]; - if (!char) { char = this.defaultChar; } + if (!char) { + char = this.defaultChar; + } return char; } @@ -38,30 +60,42 @@ export class MsdfFont { } return char.xadvance; } -}; +} export interface MsdfTextMeasurements { - width: number, - height: number, - lineWidths: number[], - printedCharCount: number, -}; + width: number; + height: number; + lineWidths: number[]; + printedCharCount: number; +} export class MsdfText { private bufferArray = new Float32Array(24); private bufferArrayDirty = true; - constructor(public device: GPUDevice, private renderBundle: GPURenderBundle, public measurements: MsdfTextMeasurements, public font: MsdfFont, public textBuffer: GPUBuffer) { + constructor( + public device: GPUDevice, + private renderBundle: GPURenderBundle, + public measurements: MsdfTextMeasurements, + public font: MsdfFont, + public textBuffer: GPUBuffer + ) { mat4.identity(this.bufferArray); this.setColor(1, 1, 1, 1); - this.setPixelScale(1/512); + this.setPixelScale(1 / 512); this.bufferArrayDirty = true; } getRenderBundle() { if (this.bufferArrayDirty) { this.bufferArrayDirty = false; - this.device.queue.writeBuffer(this.textBuffer, 0, this.bufferArray, 0, this.bufferArray.length); + this.device.queue.writeBuffer( + this.textBuffer, + 0, + this.bufferArray, + 0, + this.bufferArray.length + ); } return this.renderBundle; } @@ -83,13 +117,13 @@ export class MsdfText { this.bufferArray[20] = pixelScale; this.bufferArrayDirty = true; } -}; +} export interface MsdfTextFormattingOptions { - centered?: boolean, - pixelScale?: number, - color?: [number, number, number, number], -}; + centered?: boolean; + pixelScale?: number; + color?: [number, number, number, number]; +} export class MsdfTextRenderer { fontBindGroupLayout: GPUBindGroupLayout; @@ -101,7 +135,11 @@ export class MsdfTextRenderer { renderBundleDescriptor: GPURenderBundleEncoderDescriptor; cameraArray: Float32Array = new Float32Array(16 * 2); - constructor(public device: GPUDevice, colorFormat: GPUTextureFormat, depthFormat: GPUTextureFormat) { + constructor( + public device: GPUDevice, + colorFormat: GPUTextureFormat, + depthFormat: GPUTextureFormat + ) { this.renderBundleDescriptor = { colorFormats: [colorFormat], depthStencilFormat: depthFormat, @@ -118,37 +156,44 @@ export class MsdfTextRenderer { this.cameraUniformBuffer = device.createBuffer({ label: 'MSDF camera uniform buffer', size: this.cameraArray.byteLength, - usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM, }); this.fontBindGroupLayout = device.createBindGroupLayout({ label: 'MSDF font group layout', - entries: [{ - binding: 0, - visibility: GPUShaderStage.FRAGMENT, - texture: {} - }, { - binding: 1, - visibility: GPUShaderStage.FRAGMENT, - sampler: {} - }, { - binding: 2, - visibility: GPUShaderStage.VERTEX, - buffer: { type: 'read-only-storage' } - }] + entries: [ + { + binding: 0, + visibility: GPUShaderStage.FRAGMENT, + texture: {}, + }, + { + binding: 1, + visibility: GPUShaderStage.FRAGMENT, + sampler: {}, + }, + { + binding: 2, + visibility: GPUShaderStage.VERTEX, + buffer: { type: 'read-only-storage' }, + }, + ], }); this.textBindGroupLayout = device.createBindGroupLayout({ label: 'MSDF text group layout', - entries: [{ - binding: 0, - visibility: GPUShaderStage.VERTEX, - buffer: {} - }, { - binding: 1, - visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, - buffer: { type: 'read-only-storage' } - }] + entries: [ + { + binding: 0, + visibility: GPUShaderStage.VERTEX, + buffer: {}, + }, + { + binding: 1, + visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, + buffer: { type: 'read-only-storage' }, + }, + ], }); const shaderModule = device.createShaderModule({ @@ -159,41 +204,40 @@ export class MsdfTextRenderer { this.pipelinePromise = device.createRenderPipelineAsync({ label: `msdf text pipeline`, layout: device.createPipelineLayout({ - bindGroupLayouts: [ - this.fontBindGroupLayout, - this.textBindGroupLayout - ] + bindGroupLayouts: [this.fontBindGroupLayout, this.textBindGroupLayout], }), vertex: { module: shaderModule, - entryPoint: 'vertexMain' + entryPoint: 'vertexMain', }, fragment: { module: shaderModule, entryPoint: 'fragmentMain', - targets: [{ - format: colorFormat, - blend: { - color: { - srcFactor: 'src-alpha', - dstFactor: 'one-minus-src-alpha' + targets: [ + { + format: colorFormat, + blend: { + color: { + srcFactor: 'src-alpha', + dstFactor: 'one-minus-src-alpha', + }, + alpha: { + srcFactor: 'one', + dstFactor: 'one', + }, }, - alpha: { - srcFactor: 'one', - dstFactor: 'one' - } - } - }], + }, + ], }, primitive: { topology: 'triangle-strip', - stripIndexFormat: 'uint32' + stripIndexFormat: 'uint32', }, depthStencil: { depthWriteEnabled: false, depthCompare: 'less', format: depthFormat, - } + }, }); } @@ -223,7 +267,7 @@ export class MsdfTextRenderer { const json = await response.json(); const i = fontJsonUrl.lastIndexOf('/'); - const baseUrl = (i !== -1) ? fontJsonUrl.substring(0, i + 1) : undefined; + const baseUrl = i !== -1 ? fontJsonUrl.substring(0, i + 1) : undefined; const pagePromises = []; for (const pageUrl of json.pages) { @@ -243,20 +287,20 @@ export class MsdfTextRenderer { const u = 1 / json.common.scaleW; const v = 1 / json.common.scaleH; - const chars: { [x: number]: any } = {}; + const chars: { [x: number]: MsdfChar } = {}; let offset = 0; for (const [i, char] of json.chars.entries()) { chars[char.id] = char; chars[char.id].charIndex = i; charsArray[offset] = char.x * u; // texOffset.x - charsArray[offset+1] = char.y * v; // texOffset.y - charsArray[offset+2] = char.width * u; // texExtent.x - charsArray[offset+3] = char.height * v; // texExtent.y - charsArray[offset+4] = char.width; // size.x - charsArray[offset+5] = char.height; // size.y - charsArray[offset+6] = char.xoffset; // offset.x - charsArray[offset+7] = -char.yoffset; // offset.y + charsArray[offset + 1] = char.y * v; // texOffset.y + charsArray[offset + 2] = char.width * u; // texExtent.x + charsArray[offset + 3] = char.height * v; // texExtent.y + charsArray[offset + 4] = char.width; // size.x + charsArray[offset + 5] = char.height; // size.y + charsArray[offset + 6] = char.xoffset; // offset.x + charsArray[offset + 7] = -char.yoffset; // offset.y offset += 8; } @@ -267,17 +311,21 @@ export class MsdfTextRenderer { const bindGroup = this.device.createBindGroup({ label: 'msdf font bind group', layout: this.fontBindGroupLayout, - entries: [{ - binding: 0, - // TODO: Allow multi-page fonts - resource: pageTextures[0].createView(), - }, { - binding: 1, - resource: this.sampler, - }, { - binding: 2, - resource: { buffer: charsBuffer }, - }] + entries: [ + { + binding: 0, + // TODO: Allow multi-page fonts + resource: pageTextures[0].createView(), + }, + { + binding: 1, + resource: this.sampler, + }, + { + binding: 2, + resource: { buffer: charsBuffer }, + }, + ], }); const kernings = new Map(); @@ -293,10 +341,20 @@ export class MsdfTextRenderer { } } - return new MsdfFont(await this.pipelinePromise, bindGroup, json.common.lineHeight, chars, kernings); + return new MsdfFont( + await this.pipelinePromise, + bindGroup, + json.common.lineHeight, + chars, + kernings + ); } - formatText(font: MsdfFont, text: string, options: MsdfTextFormattingOptions = {}): MsdfText { + formatText( + font: MsdfFont, + text: string, + options: MsdfTextFormattingOptions = {} + ): MsdfText { const textBuffer = this.device.createBuffer({ label: 'msdf text buffer', size: (text.length + 6) * Float32Array.BYTES_PER_ELEMENT * 4, @@ -311,21 +369,31 @@ export class MsdfTextRenderer { if (options.centered) { measurements = this.measureText(font, text); - this.measureText(font, text, (textX: number, textY: number, line: number, char: any) => { - const lineOffset = measurements.width * -0.5 - (measurements.width - measurements.lineWidths[line]) * -0.5; - - textArray[offset] = textX + lineOffset; - textArray[offset+1] = textY + measurements.height * 0.5; - textArray[offset+2] = char.charIndex; - offset += 4; - }); + this.measureText( + font, + text, + (textX: number, textY: number, line: number, char: MsdfChar) => { + const lineOffset = + measurements.width * -0.5 - + (measurements.width - measurements.lineWidths[line]) * -0.5; + + textArray[offset] = textX + lineOffset; + textArray[offset + 1] = textY + measurements.height * 0.5; + textArray[offset + 2] = char.charIndex; + offset += 4; + } + ); } else { - measurements = this.measureText(font, text, (textX: number, textY: number, line: number, char: any) => { - textArray[offset] = textX; - textArray[offset+1] = textY; - textArray[offset+2] = char.charIndex; - offset += 4; - }); + measurements = this.measureText( + font, + text, + (textX: number, textY: number, line: number, char: MsdfChar) => { + textArray[offset] = textX; + textArray[offset + 1] = textY; + textArray[offset + 2] = char.charIndex; + offset += 4; + } + ); } textBuffer.unmap(); @@ -333,23 +401,34 @@ export class MsdfTextRenderer { const bindGroup = this.device.createBindGroup({ label: 'msdf text bind group', layout: this.textBindGroupLayout, - entries: [{ - binding: 0, - resource: { buffer: this.cameraUniformBuffer }, - }, { - binding: 1, - resource: { buffer: textBuffer }, - }] + entries: [ + { + binding: 0, + resource: { buffer: this.cameraUniformBuffer }, + }, + { + binding: 1, + resource: { buffer: textBuffer }, + }, + ], }); - const encoder = this.device.createRenderBundleEncoder(this.renderBundleDescriptor); + const encoder = this.device.createRenderBundleEncoder( + this.renderBundleDescriptor + ); encoder.setPipeline(font.pipeline); encoder.setBindGroup(0, font.bindGroup); encoder.setBindGroup(1, bindGroup); encoder.draw(4, measurements.printedCharCount); const renderBundle = encoder.finish(); - var msdfText = new MsdfText(this.device, renderBundle, measurements, font, textBuffer); + const msdfText = new MsdfText( + this.device, + renderBundle, + measurements, + font, + textBuffer + ); if (options.pixelScale !== undefined) { msdfText.setPixelScale(options.pixelScale); } @@ -361,7 +440,11 @@ export class MsdfTextRenderer { return msdfText; } - measureText(font: MsdfFont, text: string, charCallback?: (x: number, y: number, line: number, char: any) => void ): MsdfTextMeasurements { + measureText( + font: MsdfFont, + text: string, + charCallback?: (x: number, y: number, line: number, char: MsdfChar) => void + ): MsdfTextMeasurements { let maxWidth = 0; const lineWidths: number[] = []; @@ -371,16 +454,16 @@ export class MsdfTextRenderer { let printedCharCount = 0; let nextCharCode = text.charCodeAt(0); for (let i = 0; i < text.length; ++i) { - let charCode = nextCharCode; - nextCharCode = i < text.length - 1 ? text.charCodeAt(i+1) : -1; + const charCode = nextCharCode; + nextCharCode = i < text.length - 1 ? text.charCodeAt(i + 1) : -1; - switch(charCode) { + switch (charCode) { case 10: // Newline lineWidths.push(textOffsetX); line++; maxWidth = Math.max(maxWidth, textOffsetX); textOffsetX = 0; - textOffsetY -= font.lineHeight + textOffsetY -= font.lineHeight; case 13: // CR break; case 32: // Space @@ -389,7 +472,12 @@ export class MsdfTextRenderer { break; default: { if (charCallback) { - charCallback(textOffsetX, textOffsetY, line, font.getChar(charCode)); + charCallback( + textOffsetX, + textOffsetY, + line, + font.getChar(charCode) + ); } textOffsetX += font.getXAdvance(charCode, nextCharCode); printedCharCount++; @@ -411,11 +499,15 @@ export class MsdfTextRenderer { updateCamera(projection: Mat4, view: Mat4) { this.cameraArray.set(projection, 0); this.cameraArray.set(view, 16); - this.device.queue.writeBuffer(this.cameraUniformBuffer, 0, this.cameraArray); + this.device.queue.writeBuffer( + this.cameraUniformBuffer, + 0, + this.cameraArray + ); } render(renderPass: GPURenderPassEncoder, ...text: MsdfText[]) { - const renderBundles = text.map(t => t.getRenderBundle()); + const renderBundles = text.map((t) => t.getRenderBundle()); renderPass.executeBundles(renderBundles); } } diff --git a/sample/textRenderingMsdf/msdfText.wgsl b/sample/textRenderingMsdf/msdfText.wgsl index 9671a5a1..87dfeb36 100644 --- a/sample/textRenderingMsdf/msdfText.wgsl +++ b/sample/textRenderingMsdf/msdfText.wgsl @@ -1,7 +1,5 @@ // Positions for simple quad geometry -const pos : array = array( - vec2f(0, -1), vec2f(1, -1), vec2f(0, 0), vec2f(1, 0) -); +const pos = array(vec2f(0, -1), vec2f(1, -1), vec2f(0, 0), vec2f(1, 0)); struct VertexInput { @builtin(vertex_index) vertex : u32,