commit 67ed949d8af6cb04329bb68c47b7cdfafb6eb0b6 Author: James Brumond Date: Sun Jul 2 23:46:40 2023 -0700 first commit, basic http server rending a sample svg is functioning diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5b49d35 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ + +root = true + +indent_style = tab +indent_size = 4 + +[*.{md,yaml,yml,json}] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dd87e2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +build diff --git a/image-templates/Disaster-Girl.jpg b/image-templates/Disaster-Girl.jpg new file mode 100644 index 0000000..e2e50f0 Binary files /dev/null and b/image-templates/Disaster-Girl.jpg differ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1edb77a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,47 @@ +{ + "name": "mini-macro-service", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "mini-macro-service", + "devDependencies": { + "@types/node": "^20.3.3", + "typescript": "^5.1.6" + } + }, + "node_modules/@types/node": { + "version": "20.3.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.3.tgz", + "integrity": "sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==", + "dev": true + }, + "node_modules/typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + }, + "dependencies": { + "@types/node": { + "version": "20.3.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.3.tgz", + "integrity": "sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==", + "dev": true + }, + "typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..12ae447 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "mini-macro-service", + "private": true, + "scripts": { + "tsc": "tsc --build" + }, + "engines": { + "node": ">=17" + }, + "devDependencies": { + "@types/node": "^20.3.3", + "typescript": "^5.1.6" + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..c40a76b --- /dev/null +++ b/readme.md @@ -0,0 +1,209 @@ + +# Minimal Image Macro Server (mini-macro) + +Self-hostable image macro creation and hosting service. + +- Add and use whatever template images you want +- Add as many pieces of text, formatted as you want, to each image +- Images are stored and transferred efficiently as SVGs +- SVG markup is screen-reader accessible +- Web UI and [REST API](#rest-api) for managing image macros and templates + + + +## Efficient, Accessible SVG Image Macros + +Rather than render new PNG (or other raster) formatted images, the renderer used here actually generates SVG files that look something like this: + +```xml + + + Image Macro Title Text + + + + Image Template Title Text + + + + Top Text + + Bottom Text + +``` + +That sample XML above is about 800 bytes in total with the comments removed; The raster formats that might otherwise be used would typically be several kilobytes _at least_. + +Obviously, the externally referenced template image still has to be downloaded. However that external image (the largest part of an image macro, and the part that gets reused over and over) can be cached separately by clients so that it does not need to be downloaded separately each time it gets used to make a new image. Additionally, only one copy of it actually gets stored on the server, regardless of how many times its used. + +Additionally, since an SVG is markup rather than a raster image, they can be read by screen readers (including the additional `` text for both the whole image, and the template). + + + +## Embedding in HTML + +The one drawback to this approach is that, in web browsers, [SVGs do not load external resources when rendered in an image context](https://developer.mozilla.org/en-US/docs/Web/SVG/SVG_as_an_Image) (e.g. when used in an `<img>` tag). That said, you can embed it using either the `<embed>` or `<object>` elements, which will both render it as a document. + +```html +<!-- NOT This: <img> doesn't work with external resources --> +<img type="image/svg+xml" src="http://example.com/FrztglsLexLRWg"> + +<!-- Either of these will work: --> +<embed type="image/svg+xml" src="http://example.com/FrztglsLexLRWg"></embed> +<object type="image/svg+xml" data="http://example.com/FrztglsLexLRWg"></object> +``` + + + + +## REST API + +### Image Template List + +#### Media Type: `application/vnd.mini-macro.image-templates+json` + +_todo_ + +Supports: `GET` + +```json +{ + "items": [ + { + "id": "<template_id>", + "title": "Image Template Title", + "width": 800, + "height": 600, + "links": { + "self": { + "href": "/templates/<template_id>", + "type": "application/vnd.mini-macro.image-template+json" + }, + "alternate": { + "href": "/templates/<template_id>", + "type": "image/png" + } + } + } + ], + "links": { + "next": { + "href": "/templates?anchor=<template_id>", + "type": "application/vnd.mini-macro.image-templates+json" + } + } +} +``` + +### Image Template + +General Support: `DELETE` + +#### Media Type: `application/vnd.mini-macro.image-template+json` + +_todo_ + +Supports: `GET`, `POST`, `PUT` + +```json +{ + "id": "<template_id>", + "title": "Image Template Title", + "width": 800, + "height": 600, + "links": { + "self": { + "href": "/templates/<template_id>", + "type": "application/vnd.mini-macro.image-template+json" + }, + "alternate": { + "href": "/templates/<template_id>", + "type": "image/png" + } + } +} +``` + +#### Other Media Types: `image/png`, `image/jpeg` + +Supports: `GET`, `PUT` + + + +### Image Macro + +General Support: `DELETE` + +#### Media Type: `application/vnd.mini-macro.image-macro+json` + +_todo_ + +Supports: `GET`, `POST`, `PUT` + +```json +{ + "id": "<image_id>", + "name": "Image Macro Name", + "text_style": { + "fill": "#fff", + "stroke": "#000", + "stroke_width": 2, + "font_size": 48, + "font_family": "sans-serif", + "font_weight": 600 + }, + "text": [ + { + "text": "Top Text", + "top": 50, + "left": 400, + }, + { + "text": "Bottom Text", + "top": 550, + "left": 400, + }, + ] + "links": { + "self": { + "href": "/images/<template_id>", + "type": "application/vnd.mini-macro.image-macro+json" + }, + "alternate": { + "href": "/images/<template_id>", + "type": "image/png" + }, + "/rel/template": { + "href": "/templates/<template_id>", + "type": "application/vnd.mini-macro.image-template+json" + } + } +} +``` + +#### Other Media Types: `image/svg+xml` + +Supports: `GET` + diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..6cdf8c1 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,19 @@ + +export namespace http { + export namespace images { + export const port = 54320; + export const address = '0.0.0.0'; + export const public_url = 'http://me.local.jbrumond.me:54320'; + } + + export namespace api { + export const port = 54321; + export const address = '0.0.0.0'; + export const public_url = 'http://me.local.jbrumond.me:54321'; + } +} + +export namespace storage { + export type Mode = 'memory' | 'file' | 'sqlite'; + export const mode: Mode = 'memory'; +} diff --git a/src/image-id.ts b/src/image-id.ts new file mode 100644 index 0000000..09cec75 --- /dev/null +++ b/src/image-id.ts @@ -0,0 +1,6 @@ + +import { pseudoRandomBytes } from 'crypto'; + +export function generate_image_id() { + return pseudoRandomBytes(10).toString('base64url'); +} diff --git a/src/render-svg.ts b/src/render-svg.ts new file mode 100644 index 0000000..227fa82 --- /dev/null +++ b/src/render-svg.ts @@ -0,0 +1,53 @@ + +export interface ImageParams { + title: string; + image: { + url: string; + title: string; + width: number; + height: number; + }; + text: TextParams[]; + text_style: { + font_size: number; + font_family: string; + font_weight: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900; + fill: string; + stroke: string; + stroke_width: number; + } +} + +export interface TextParams { + text: string; + top: number; + left: number; +} + +export const render_svg = (params: ImageParams) => ` +<?xml version="1.0" encoding="UTF-8"?> +<svg version="1.1" + width="${params.image.width}" height="${params.image.height}" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + style="user-select: none"> + <title>${params.title} + + + ${params.image.title} + + ${params.text.map((text) => ` + ${text.text}` + ).join('')} +`.trimStart(); diff --git a/src/start.ts b/src/start.ts new file mode 100644 index 0000000..4cdb766 --- /dev/null +++ b/src/start.ts @@ -0,0 +1,9 @@ + +import { init_http_servers } from './web/server'; +// import { } from './api/server'; + +main(); + +async function main() { + init_http_servers(); +} diff --git a/src/storage/interface.ts b/src/storage/interface.ts new file mode 100644 index 0000000..0b4a7d9 --- /dev/null +++ b/src/storage/interface.ts @@ -0,0 +1,33 @@ + +import { TextParams } from '../render-svg'; + +export interface Store { + get_image_by_id(image_id: string) : Promise; + get_image_data_by_id(image_id: string) : Promise; +} + +export type ImageMacroMediaType = 'image/svg+xml'; +export type ImageTemplateMediaType = 'image/jpeg' | 'image/png'; + +export interface ImageTemplate { + id: string; + title: string; + width: number; + height: number; + media_type: ImageTemplateMediaType; + content: Buffer; +} + +export interface ImageMacro { + id: string; + media_type: ImageMacroMediaType; + content: string; +} + +export interface ImageMacroData { + id: string; + title: string; + template_id: string; + text_nodes: TextParams[]; + content: string; +} diff --git a/src/storage/memory/images.ts b/src/storage/memory/images.ts new file mode 100644 index 0000000..5e07312 --- /dev/null +++ b/src/storage/memory/images.ts @@ -0,0 +1,80 @@ + +import { http } from '../../config'; +import { render_svg } from '../../render-svg'; +import { ImageMacro, ImageMacroData, ImageTemplate } from '../interface'; + +const images_by_id: Record = Object.create(null); + +export async function get_image_by_id(image_id: string) : Promise { + const image_data = images_by_id[image_id]; + + if (! image_data) { + return null; + } + + if ('media_type' in image_data) { + return structuredClone(image_data); + } + + return { + id: image_id, + media_type: 'image/svg+xml', + content: image_data.content, + }; +} + +export async function get_image_data_by_id(image_id: string) : Promise { + return structuredClone(images_by_id[image_id]); +} + +// ===== Test Data ===== + +const template_id = 'QF9ci-R-RfqUBA'; +const image_id = 'FrztglsLexLRWg'; + +images_by_id['QF9ci-R-RfqUBA'] = { + id: 'QF9ci-R-RfqUBA', + title: 'Disaster Girl', + media_type: 'image/jpeg', + width: 500, + height: 375, + content: Buffer.from('/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDIBCQkJDAsMGA0NGDIhHCEyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMv/CABEIAXcB9AMBIgACEQEDEQH/xAA0AAABBQEBAQAAAAAAAAAAAAAEAAIDBQYBBwgBAAMBAQEBAAAAAAAAAAAAAAABAgMEBQb/2gAMAwEAAhADEAAAALm/z0Pbx7srAuDXi5u5Za2eW4jU0hD1VFWaF0aZOr1tNF5sK1oc9LrRZa6clAZwtroRFam7oc4jzgFScCkIuzqTAtbOv0TSrdNVXI9PPbJ5/fYL0MVJSTDBa72vFpBMorwDxDgwNuwjmi3RDA1qr2Wahehz4p2Pla8XXNUjBTYaVMrBaLDm1/ZImaAsMULsRk8oZdjJvuswWPTDVwqC6WzzGeizthUZbnSu4insq4wIBzhEV65E1emUt1UMBOEY4oG0C+1VNcXBoBFU5rJu2Y6HUZDcNYl+lsgBo9OM5ZbUpwceBOy1sqMNrUV4ww5+V9jLJMGNpIiJ4iO8chvVwFFKKxqaqWPHLKShGhumEEZyYLNkxDKOt0TJqiLdURT8zKNj0Lkdvnqq3cZCoqOEPz0Grb6qFTNkTTyYiKiRveueWMN7Ss7ulLuJ4rAepB13YRFVEXVWpz+UYGY9EoSnF/pqO1on5yRDaK/zbGc51DCxkzRFUGjaZxhQnpqG7iQIadoBpypZKxIohFMOMAOM0xmLtbwJA7uSzdLWOosOpkolhy9s5JBW/GPQ2+ejScnPWs6cqLuoc56djUjpW21ZhEWR1wDqaq1vMyKJXOUuigwp/PdzQR04reZSkUfQQ3h2pno9dPq7Ho86s0VTjx+sFefEK97lq8smsuwDxMjkma0jxz6l3JqdFg6olatWiPHPzjQFUqox5FbXSaEOKMLy3x7w1wlbWKjgKetx6SQUuH10YLYTdnZ5+/6vNix+up41oJjB8t3hTVwgGzvvOe6qitOe3hnWnPLJVspXT6eVlpUk+dNU9tVrLrvG1d5rx4rQWsufV6CaKb0cEOKucirMoLuhx9C/3nlvo18xthTnXzmyixC0BeULc7LP0lCq3BXnZBe0ZRW1Zl2VBaNEKsTQw9KYMNhxEXl4NE2NKzva/LoNsKnQeX7kVbY1nP1V8xs+/MFaRAOLpgF5z9FAHc122QLbQzPbFFlk3PBOP7PL4VH3fjgjXLzInqapzPsKzaaZ+VV/rufH5/e+oXzjyLQ6KClnrmmsya+Ehly6msh5uXtfDlrfl47sVr4sZwLcGGAD358gd5Pnix6fSef2VxYkMurQKMVw60jkcckBgz3NpDp8OnJTUl3xenYmKx8X080PfidCbzQi55iea+z+S64ZL3byb2wzoOag/wAy8haW1XjGBz17Q+32BlxFeh5cpQk2mUcsnbwErrBt5aKyzbOjltb3zqMNdTZRgtyvOkXpNB4upPaYcrcAbHXV2O19WZsIL+lAc1GUQYKAazTdNXaqrAi1yNsi87XsHoLfDFNeoLGK8/Y2S3OXRnBNSLydYIG2ovN0wFvrTsejPq5bCpHW5UOggviBR+T+y+W3li/cPGfdNNDO8r+HzjK3E1K9S7zO7A7OvFj2wPqcwxMXN+YwqqJrGvzO+wG+GlLdDtyCi1wDnW2OFgKA3mcOl5naqDPYgJylVVlFnnOirKmyZ183GOdF2plfA+amIrnIlq7uNlQNYBBHPUzCPXUz06w8Dnx39vq/IiA9fi8zBm/SNV43d8m51hUWr3tKoGv0x1ur8pt4197zOHwvCtpvPnWbrXuj8NufC77C2820UPaYTl/eNRX+n+e900AO2bvWd565Jhx+eh+q81x+YM19H+Vezz4aPavusLD6h5PfLaacaHLU+OuBHcU7SgHKLIHDK4oYo1pWiG5GyoOkCeHWpiZc4HANrp4Gu0t0ypqE9Dg1VJsEshBtcvxep6h45635FjVzeMsfR8sra4HQ+f7Zx1Bb4Vbg6TLzh5rnvY/LfZ83PSWdlh1VPPVLPy+7xc0Tvp8mpPxeg5ur0/A9hw0aSBe9/C05z4L++wLue7CiDb6XHjrTX5rfHQY21rVAY8QeGkZnLKkw0ouNRpzoywyTE0HQ6bLExVN/QVm6elNcHcBcM+OONBSD40pwo2rFVKtXG1xW0RQ521bltZ53SViNSrYPbmGw3rvn0d/pdv51ovN64PMrDJ656G+x+v35Z9hTX+HXtshv/OPm+nzt3efTc0Gozmvx3KpNtiMNsvZZO29Xx97sMTJz9W7wTRXDZquHt4975YZ3TnMAss5h0wxPkgKvq7VzoOS5poiZ3VNdx1cnJmNDm0TVNhA4zDC6nTG54A9Fk2J4EV7wGjY2DCIXEyx9D849SJxsXqrHPmgvqEYw9gHfVPmeB9a8k9DA/bYv0zl38WKprvh6DjabfS49JQCLf2LD4R3l90Ekxvqcdb6Hmpsb3PmOgyc1gDg2en5nptvV6Xm6gKnS1+kY4QwjTPP6EiG8uZ2+zOG6sq3SRd4fA9aTTxvpTpjWhau2rlVVW2NPJx9ecTX0emoqmtlHdphYkhSzUIc49TK1iDqahWPs/nheefr68ytLW4bWedt+r2fkdnJpfHt1k+/C72uMpuXenZTw56aTceb+jRWstclq9IrK66KToaXWUrWfH0Aau/x/ovnI8EbWzaZ+52WGv8NL0BgFLFFdZ0ZHh2zk8vR3Oc5+grYZDaRpaEtdRM+N1LsbogaK+BUFV3cKMabfVCTK3ZgtecQ3dTeTpxZVKGnHoc1PJSYmfS9biHeZvoMmZT9U6H0TwDdZaWdF6pUZZ4dk4/ucLI7gPz+rzSK1n2gvRh22GgLyJ5dfFYQtD9N4gB5krBQraFrDRGmdUHHHv5dBel9ZVS2bKDpavO07PCWVMqvNngdSr03aaUu1jDaBnasYL/uViZrnZQ8WgqSymqDpMEmNqbytFWKRt5vGmaTF3iqepyT20245k8dXegSi8vsPShgyvqGbiw0vM1f66V5JSev1fXn5JD60qnBG7OKLx0e0FKy0OpAi6t9kwYPS5BV8hYjWXMteXMsjVFkQc4iR0T2oRZQFWZq7So0m6IDATu7TLNo9GbAdOlJXH1goh2taOsqQqleHZu1i7uCGZrNVt1WIpoyWuQuEQOIeOV5pOSr02LXZ7HSksOcFHNGQS7kcQzSat0v0UzymPnr0wbCko1zM3KndwDEzTRrglPNRbCYMLB6Em8BF6CgwC3cJPnz9tBTya0ojKg54jR+YIoNUNSWlbvFnPFrnVVl9865F3cN6ngqLdVxWEsNCCFFkPS6u8stYaS5jTN3N9LNY6k2ePVV3CGkgRFQuAE9t5JJB6i5k++DHd7UcTkDev4EalQ4OypEKmQM5L0cERjUAx2STqJLHida205JVsteKqdtw2XTNt2Kqpp750AIf2oWdvM9NRhmwqz/VfPPWC691uRTn5bgVOfimaDanUdRjitQ4unOOjcwV5gCdDhd3gYrnHdJBbJGprGSxaY8Tk16g6HvTyyugcE3YOhM6FBMo0N6ag7xIEyTiEm8By5wOtd0I07gRwTpMRFRBBGRGOBnWI5zjRqntQMNxU983qPRfL/QTa3Lywwb0TEjutyHU8Fd2GP0Lmz4I11PBFENByBJVWF22Ngjla8muZ3ikKM2JoNTKo9GUfevic+FycqZwUvYeNz8HcE3InIkUbgcmoHRv4DE6MJuwoJucQMhK4A6mjDvYkEjGzjBguYwpwbgTDelJlljQPf4W7Wlld5rQO6mk2IIs1bWpbICYgA0TaFLW4HrWDPhCQmZm9ophcmGJCgm4pibHylGmKo9GaPzp4yUN0JnRcCVsSBPZ0JnQPCd0HAnbC8JuDMGZyDop2McNksTgl7AkT8T2MiIiCCE/qKwiQcDogSRjBaGLPSoiunzbxTyY3pSX0Z03ZGXha1YVIYZS2gJyHP70tvXwjr66eGZ6AcIs4GmVzkOR4jgZcVx6G3kXRzEdGmG7sDRF9FQyoo0iTrHg6Zjw65sYSdjcEjgpQK5AgmgTgli5wI5OCga6vQWHazoWTAYQsoR3sj4TEhd5GOwt6PmO+mzW3Lnp82O19YtKwoxhmNCWxMPksJfBJRkw4SoCInSMIEhnGSBD6zTFJqa9D47vRzCcJjRF2Hg3t6QEfXoOSRxgXyu6BsUDQnijQEPg4BLxGgTNXzoJhQbdigGAfJVIVsIK4czJHgL2RCjlchkERoJ60xTV9pMNdZ9d+2kInU4BDpQwkRtjQFtTCUYiUPevSZFO9KvhtxnOP7ZjXkErVC0jQedPKZBFxORRoJHQOCZw8oMlgaBLhUD286HHP6ET5YU2864GdfGCkjJZEiQw47nESTCdAkitcB48DWEcicCauC65iZIfWuT05WPfn07llVHGx1a0dEY06QxSdCJ5BM1DOeczPV+uzDmvBvq5RVKFVFy1Lp5W9SSXEm+8SQnpA3iQcSQd6kNNSF3iSanSYokgXUg7xJC4k0upDSSB0aQOKSATqTXepA5yQTW6SvR55LPsdClmxmJA8hJlzepdOd1ZpbzyqSF53ElxaUySvL//xAAjEAACAgIDAAIDAQEAAAAAAAABAgMEABEFEhMQFAYVIDAW/9oACAEBAAECAY7cXLR8l7yXftpdWwXnrGu0TRyxkFocjV53Z2MhYEOkk0m4yMjxQotR1lmHDsp5d1koVly0QQXkqhAMJxmHyABr4IdWjEjTeTrXuJM4F2KQysekqyJKkgqvYn9Q7GTOynWNgCZ2gEUaRXIEkngoZBnND3gSxZgkRAkagodvJtH7bwYP5YFYZvr2aMUU9dSpDQzKdOWyR5XmMImzYLM5OK0bdXj0uLlOOJdzMscE0B1z00UUtuTOOrLFMY2TASWNiaMgrmgNfJ+Gy0kTV55saL67U5HWGKMuXMzzyyERGVuoHnIG+ImjZ3ORmHK5Vy8prmaGKKCOXifpT1pakMLRewsi6vK/bEq5GIwPgEfz2ZXqZ5RJHaOdGQ0zUU97AIKR1/pvH8y4fgYrg5qDIiCWK1UjiMUMoXmMXkK96qgzXTpeh1omOVQAcjX+WwmK49hJImNRLsMshhkkJNrHl7g0pJTMOixMkuHAAAuLiLDWEaTmOrEqVeVtc+nLXeVqSiOpTrOJQd5ydhZTIjS36ln41/Jza4qTV60ePEAlrs6ukxJ6LDEjtIdjHywCYycVVjEUEMSgTVGzjuS/ILvH3JbHF843KxX4T6rJXCtvfMR1q4GhFGTiKz994fl45oTHBdMSYokriHJks2HsDIcUIJo5W9YpGycOqhMiQL0TA4cyMRWt254b1fzim4qavhCLf5Hh+Rtc7x3P8g8GbyJYh56YvIkok9A2+zchHckhjuiyrtN6y2JbtifceKUyMWHsIIlGSlwqoIA8KPv0EpkBu3OQl4Sryq14p14avNXXEk5qzxNqK1xL2GUq242Wy3ITO07XY8eH1S52z6X0hiv6G4kksjmR9CKMOIZEkkMo6EMWGlUJGY5pFOK6uDu9YscbSS7FFnMVaPG9zlqWTk61Ss1GHbPE3f0Mgks8o9tBXmfkKdyPkO31ox9yW2olikWGyzCYIa0sYwYirhiaB1II8WWvDPGcDCRwp7FrFqOje5B7jpT4WT8d+sBYIzxyXjYK+wVf0M8/I/d+z7q2w9QzxccluVc+oK/1GhIMEixQR07BDeXmsct6Lm0MwKLE9Ywdmm0F8ycczWuIoS1V4i7x9QK8pIsLZIBwyNMZDKbHZpPYymQ2I32lhZorfvSqT0li81h6yZZqFokWIl1MH1DGkHJRwwRwtB9avVnilydhm0+OumRKlBbV6DmLEVYWa8fINzlrkp7nt3OEMWtS8lLYEqSd2VpWkrX+xIkiswzRcqHc9+wdjbyoI4zA8XmiyhE5MVECR1xDJFMLcTqqaUDNENKLickeXe498yOe0HDzU4a9Oj6NK92Sbq8YxY+jxrGIJ6njXkWZz6CaPlPExuGFajLDZgqwrGxYxRuekMHMR0zWUKQckbkgIZINbUg9bFEyU6lijEloUo5YjFxcq1oY/SSyI0qSu9vPH4BwntJA0c5Sdikj5DIA1eKuFtZUtxB5PNIjnkI85hYVroTPdtc1FGlSRZmKZvuliUfpqV+9zFq39qryb8j+3rWpJ+rlYpuSmuLXSJVb4BwkZFICVYPWEhwMn5NH+Ry/ki86vNWOUp8l/wBRb5erzlnnv3EfNRchDavQ7H5HLyI4qrC0EKz134xKVqksa0V42vSucJb4dl3hPpXjkm2MkuvKI1gZNKsmdgc3jln7SBGnhZVsMYgEXIka1XlyGoeKsV+qRpYuc9Lbearch/I7PNV+XTl2TibFjEu/sYgknbXXk4pYp46QsV5BGEySWSwseRxdXABWQHO3ZCpJUxkST4krY9bx4mG7hHCU7I4iK2nFNu0nFUF4+zxl6lJjJEjQLWAFiLmK/KRc4U7JgyKapytnl552HIrwsMkktd3eXaRqiRpGVWMJKrFwSHjbbNCYyW7dvRJDnBD8irtJx9x84CPkBxxe/ZtcNdt2oub5qKSHwg47iuB/IK5Toq068EUgOUskkSJo1MrQi3RSyRYbDiCNI4dLH1iQiTHyUSgSB3Ias0TTZ2YiQnpwkfLyrVswmP8AHo+RFCK3IKtehyNm1PBJXHjCtWP8lbxePrxuV45jfucLas5RksMGJrm80B72DLJ1jSGMIsYXrhbdvFaZXwP33VMbTGJ9qzZHLw7Sn0lue3AHkV4qxYejNBb5u5EZl4k6M3Hn8pUTM7niqtardFmTjHmaa1DOWMv2JeQrpJE40mVa6jQUYwbJGY3SMlEqHFYtXIE7AliRJVPFjy62M1wIapz1ZjBYarPJFDM3E4riTjOT/JpiFjlj4qzHd5SbdZmlkYoxbGaN6+TTuyiIVoS6pvZMmSZ3tZCTk6TKCDCclOE5uCfinGakiWvV+OfDjh6N3I8774yBV5N0uz3kspMr9JU5EPHHnH1WoLTmqE0693ioHtza7VEGAD43t8YSpLkEhJeZPiM7kOH5QcOe/YYDGAebHfgVtC7ACp4vEiapYpLxU3GtSkggRY+RUyVHrydt3C68eeQvYXkKiuFAAzWbJfHEkcyRBq0td0xTjfxv6axR1a7ScichvDmbHKivVt3JZm7Uciv1+R++11bnI2fdGOQvdeUViLsVjvblbK5hrcjSlpkw5XC4MB32bCThHV68aq8y2kYZs/O8ZZOSl5zl+R4nlfG7w/l9dI2ieNwV4yPz8vMp06dfMxqzxTxLAkKn3ayz1DVs35ecmkNVYM79/Tv6em86+ZjsQJkuWRIDgIP8Gx3Ml1qmVr9K9f4+QwtuxhqfWow+hkMxmE3tsHsS2WRAioEIVdddT5LjZC8Nn3EnYku07WvtLZSzHZJdXyyJVII238DNrk8aVkWtNQvX+Hho260tT9b+njoil9XzMfmIxH9fxEJitClCIfreJhEQgZWZ8cwg4s8U6l8kkaQt6RlSrI/ZhcEw1rD/ABHZe4vIff8AvG4J5bvH8uBO7cueb/ejl25z9y/Lm99v7C2ftrYM32WC4JzN6eiuXkkuSHHxSztiWaTyJLG4cK3cYtmOYSE3wwTNa/jwI80Ro1jJLSNDyFbmXRuK/Rfom/H/ANF+mbihxxpGu0arhU4zDApjQeK1V4/lI5BJlcNVaI1Px2OSCeGSCzBUh5CuMjniMbotpUxAwbHHzNWd/jQYnXToYwntHd/Zpzf/AEC/kS/kQ58c0OU/Y/e+7903DbNw325VuYPOt+QtzFmawXyhnTyEPHQSrLGa8sWnxqUVKLjloNBZSHArBgw+YuV7b387ze9/GtdPH65r+AhNf6/h4+Hia/0v1360URVZHyUuOKVIPqiGtWlV1EZhel9RKaVBCRLlzIsfNMNEfCQ61rWta1rWta1rXzr53ve+3f1M3p6F9nNcDFGNV48mxypUdfPz2WZmy5iF80Tj4M673ve+3be+3bt27du4P860R1+CuiNZtsAfAOAxD3ilNhpfOUQyA7wgg42WyuaOMdOhXNa1rX8b38b+Nb3873377Y9m/lgA+AcPIJHsezXByEPLT260ySdzIZDK0jvaK4uNjYCQw1/W83m99u3btts3ve/gp5ZvOvQxGDydUGlEDmQSakRokhjrdI5vYytM0xl9LDJkYkxs369z/jvNddfJO/jYf51op036B8IlTrKsKypUl3XlkRYVQBixEpk7kkgzGHIhJjYVYICc3snfp6GTv37D/HZPqH+N733KnBOLAZoZaseO0ckiwx/Ya4Lf3DZksrdBwD4slcQMVRy7H479u2dNZvfbtvetaOa11668vLoGB69OgGGE1+iS9ghVcXEFuExGPzWqnH/ViHzIbBkEjdWxsWN2+N73muusI0B1CdddQPjfxrCvlrN73vsX7swm9xMrQ5LEw2qqApXpm+zMSTrTYRNJ8bJEgkMgbN7LBuwO+5cNs4G3veb2W9vQS+nphAABbvrpGXuQnxkpGgICMOEkl80q4w05Yr8dcJU9ixfAnmIum+3fO+xIJfbuG2cIEfXQYt6+nffZigVR8Sx8W4fvtixb4OHOmHDmiCpSRQOnycIJU77+vq0m/ne9g7L943lk7d97+Bir4+YjChN+nr7Q2fUTLMX3o4QQ87SgltBerpaiYKPT1Lk73vNFdjAfQneazr/OgNa0BoYshl9fb07fxDaFiOTRd7TXTZI0c2qBPNhLIW8y2973m83vvv8AnXx33hGAoCSfjfbYDLgwp/RESQSMSCOvUDSRqiRfXaoY5VkjYD/Xfxo/BGd++a+dfwC3y0/8aA6VqTwuPNlI6gBIuPh45a5j6XYVqyUG4z//xAA9EAABAwIEBAMGBAQGAwEBAAABAAIRAyEEEjFBECJRYRMycSAjQoGRoQUUUsEzQ7HwJDBictHhFVOC8UD/2gAIAQEAAz8BqzGreyp02gOZCp1PKU/WQiO6cTeUMvVBwlB6l0xZADRHNopUKFDlJWVslM0lCLK6KnjZSUVdSERZFS3ug+nBJEIVKBpnoopPZo4Jzqrs3wpzHB7NNwg9kjons/C4I1CJYwu06JoaA0IPxttALovdpyhAjsjMxCkZiLqdUFZZnEoCygf5EqyusMWXGU9xCp5ImZ7KgQC05XdlXYJbWlvRNY/JU17qhWdEgfNU2HlespyQgRYhOmCjKETHEoh65hKLaXKUS6USOEFX4WVkQeMLOiFOyJYY1RpZXd4KFen4g1WXFVQB6prmJ9GJPKShWoUaYFiRKDKbRGyyTl2VSvUee6qOERAQY2TdeJXkjlCAGkKVCgQg1izOJ43/AMmmXeFVZB6wqBtkAncIgTTMj0UMiq53zTGnM069pTWC7T6hOa6QS7tKIvVEKkSC2ot5kIHRQiVzQgECUdUSFzIBiupHC6kIESECFlMKFKKIUCya8d02oH0z9UaR8NwMBM/8i5zDY6hFrc7fonVGUwRuvDw9ExuFFMAKrVaSN1lw0kDMnkydAiSWwYTaTe6LiCUAFupcF8IVlJ/yZV1722p0IVcjK4g+iY0w5UK210xwytavC8zJaqLznpWITmNipTlvVUniRZHar9VVZ6Kpm50wMKBcVKkrI1SEcyMcI4XUIEa8MwRmChnhBo4d1eVmruBRAzAT1TjjiWNN9bJxYQ9MxLwapkDQJgY0Wyjqm5QbFB7MwMQnRlbVcCsUwDLVn1CxjJmkD6FVBd9ApsS6k4fJBzyII6SpbcKZKJJc75KGyt+N/aymFJVRzcxMu2WUe8lrh8k2pSnfqqlEyWy1UqggWKJtqFzQ0BOFnNsvEHIjS3I+adFyszYKaGHRcxsgVJXLKyyr8CiT7BRV0SuYKCuVZT2WYS26c7EmAjoRZNptLmsErFVq3kysQCccE7wqmV3ZfiFO/iOc0HyuQxGHAm5Fwi58g6KyB2QOyBEQqIDTkEyrcIGsJpIFzwsoaoHtXuoMJ9EDMMzfRYfFtiAHdwn4cljgMuxT6lIiBlWRxcx0FGjy1b914l2kIuYbJzZEIE8yAdGyLTymFUdurXR1QzcwTfDV0UVAUKTxCtwkom6qN2UCHiFnpzY+ie4uAMBU8LSLnECNSsJiw7wqjXZTBgrCYar4ZdJXjAeDTnvCxrZgZR1Kq1qJfUJdKmQGiCjTr5tim0i4Ewg7ymfYpsLQ611Tc0EOCEWTHvAKo4Z/hNp5j2CqYmpzUy0d1CLndvbBahuvBdD2yFQxF2WKe0HVw6ou8lSAnAw64TarDy3VSieWRCqERl+aIInReINU4HVWXMg+yhkINGilikoQgWq6kKCpKuiUSiNlJQAQdZZ2GDdfiGGDi2qS0bG6JZlcIqdFihQ8Np5XawjhPELbAhOqPLsxBJRoU20yAY3Kw9alGUHrColhptGX5QqOQlx+6Aw4dTEheM52YESm02Q1X4sqhgcE1rtSQgBogm+KHRJTA2wUnVbINMIHjHCylyDk7DvJGiY8ZHfdNYM7Psg8z9kNIhAtmE2NLrLUym4Q8OWGOycwI1ApUKQgRCc0nossoyrKUVdGVKugDKEKCrcD1U63Qz+JAELD+JkqGVg612QEyQKWyqi2UqpSdqQn1cXl1VNktqWJQFOBogGF0LGUTLGZWA6lPxNImpaEKLyykw1HDWFTxtTwyC142KzVGBXPHNVDeqgmmgFkYSi8mF4IGdB4lpRUoFXjgWsGa/yVDENiRKYxuZl1Uo3cJYqdV4dSN1LedNDdVF1D1ZBwTXNhXhEPhFQgRoiXIDhAQhSVdBAqWmEQIOoQUIIHg6ly/ChiK8gqliqrjXqHK3aVgmvH5R99xKJouL4Rzk6qu7FNNIgHuixmaqZI6L3QPZcsKqWeGKfu9yj4NSkOlin4bEPJgmbzumP/ABE1HGCdFmrBECVI78IeCF79MbVayCSVygLwTmO5VBxDXlUnCKZCqAkseq9MczZTSYIIKtMoFMfT5SZ9VldzC/VPoiCUZh4lpVCmctMwd0bNme6bk5lEwbJ5vqnA3C8Xssi50CFBQIQQKsoRhEq/EsctigbhcsyjHDdchMwq1Sv4FM5y6yfQptzAmo7ZVMPiocC0HUFB+JmmE0mlSecrTqsJTDBhiMx1hV6D6dUVCOyz4U5jKig30T6WHc+mJKfiMOaLqTs57KpQwj6mjiFRqOczEWceqP58eGSWg6r3igZQJJ+ygXBQ1lAHlmU0vadCV4b/ABXNBjqVWeYYwNHqnupwWEE/ECSD6rxQ1rhfqvytWrzGJCq06rQLyE6tTeXt8qwtTWAQqdVsNeLqoNKiYGF2ZU4Idsg60W6p4GZhkfpJTn1c5EeiqUrsMp1QZTZHSVeCmV7QExrdLoBvdEWhEq6MQngKyi3EZVdFztEGwrKFmCM2Kg3QlNA1hVKtQYekZk3IWHwVGnVLQXAXKbUxZfTbOUrxqoc+nCrPJNKk4nayxWLAfV5O0IsfmDyb9E4UgO0I0cPlcZcSsmGHopYqeacgn0QiNlh6rpLYVPDt5GomogCZIuOqaNwFTmMwB9UACNdxCykjKR3hV6hkSB6KD8f2Q7z3Q1n9kCeaD6oPguiwQbh6kW/dU6dGTEkqa9MtcYjqsQ3EODHwE6AJ5fVU3XgIZoAsjHKbdE9pg3VMdFJlhhOaUapEmEWaOTqYhZjzKTKkaIqnh3w7ULD1BlLgCszc2xUlHop9UfC0V0KIXiG6shCtZOCJ2UCei/MN8OjOdZacuu7qvHphiw9EfwxdU6VQPyDLKoGm0wNFh8pAIQ2XKpqtvF17gBco4AHqeydoI/qnHcz2CI3jtupMC59ET8I/+moNP8OmfR5BX6qFQDrn/wC04CG1Hf8A3dPg+U29EGuIfTBb1BuFTq/wng9tD/2nUzr9QgbGzvRZTYhpRb8+mhWHrtDK4gfdUab/ABaNQOZ2OirVKxcHWTTVAhMamgyg26B7oPE6FeC3KSvE1QBRywiX811L0Q2yLQuWVGLqIOxDBGrgg2i30WZ+inZNzXCb4dgmh5UlXRCJVoV0E0heE8uaNVlpo0KfJTJI6BfmqgblIPQoV6MIMzUHOjun4atnbiuXoSqIpjPVErBMsaoVCrXYWVLSqD6LQKg+qplohwKzaG3XqjECzU3SSe0ohsDKBveAFQpg3zeht9Ub+GGj5wq5bJeO8TCqfCBpc5T/AMLESObKOrbJ7zDnA3iDZPZdhdA1AKfk5p+e6Iv5eyc9kVHR0d/z1HdScr2wfsUIAcSOjuiew6j/AJUiJEjUdkQc9BxbU0idf+VUDIfZwTHv7oEd04NsnHVAW3QLU78wRsnGkCpVpRRDxCtdQ9ZqXKoxdQboHE0v9wXumqTJTUMvKEfDhFtQlHhIUIyjOnCm3zOAVEbyqbGcrCUdBQn1VV1TOxrWrFEXrAeiLn5jVcXdlmuQ93qulP6p34jVcQYgJ1Kq9mpaYKrPdDS75FVWNl7+bodAqdORLqh7lPc3M94Yzb/pZRFJt9yVVrXLiGjc/wBwqhPu6VSrO5sPqi5zQ7KSNmjdQ/Ix1mWM79U5h95cbH90DbKJ7oPaC0267z0WYzo4WMf1H/CB5SNdI39E4jNcgWFk5keuqFQeC6zm+U9O3oiDkqiO+xRpXbJZuBq30Uw9pB3KMS3mjUbwmmmM9IVOhMTCadoRmAspjgKjMxWSoQES6YR8ICLrIO6OqkKbwjKc9wHVeFThAY19tbqMZT9VnYCo4s0kJurUag5RKdT1UcZRITCTUMyspIEfMqpiKWcuAHZChT8QklNq38Mx3TG0jygEJrqQMBNNFwETCxvikBpiU7AYao+owyQsXicY9zWwHuLjOgVPCsF8zt3FVMRZginuTZU6TfDo875u8iw9FUxD8znPeTvoPqmi5bmP2CpUzzOYD9YTX21teb/JQCZiLaQAgzK74t7IWA6yO9kMlxEfZAgyJGhCj4rbE7dihHNIB+x6rXP5SOYj7OQmMt9Y2WpbYjftsVnpCpHZ3YqOVzp2BTmHM0A9QEHjMwAEdtOy5z+yzvgpjbplTm3THPIN4TWNytsm0gXO9VhsfhjkggWIITAPLComplJEqkSqZqX2VNpy2TJ7pmtpXKh+ansh+Yb6hBlJvChhmzVqtaO5VfEOjCUzl/WdFj34mX1bHWy8Uc+ipYdxDYTqrr24QiiETYo1KRAGoVbxS7xFQwzG0HEZgqQd4QvO6ytBoVInoqbqEVHEuRoWN2qoa+dpt0VX4aYWJxbodApi5Ky8lJlyhrUILW6fuUa/LfJ+kW+qo025naDoYb9f+Exgim2Tt/8AixFUw57i47AwAiTLrkWv90G7WDZUOjW+vVXLd5UgX2/v7qb9bH+/70XMDvossbtdyunboszck+hXwn6FS0tBPLdvog4A2ugx0Ecpt8kDIBv9j3RYcjwbazsi0+IPqEHAGVQpOOp9AmVQTBHQJzW5WEDunZ83jHMqhfm/Mme6dXvUrl3bQL8hXc9vMDqJVOoyMrm+qqvxmenUdZVRVBc4kdFiHumjDAN1iRV8Quuq76kudHYKrla/PPZU6tMOmFh6znPLtlSw1YOLgYMrBNpiH5nfpAusfjf4IFGmdzcoMYatZxqvjVxlU3MsEG05Ca+jcqu7GOysmnMAqs1uZwRq1S3RVKVSIkIhVaw5KbifRYgG9Mj1VSILZCfUaXBzm+ixNElwJciDDwZ7pgGiadlGyBFgvHqhgsNz0/7TKIbSYPQfuUGAuefl1TqhzPsBoOiZTGWmA4/39VVrv5iXE6BZTl81Q79EGjcnr1QDQBuI/wCVLDG8baotcL+qAfLbHb1V839jZQZ03QIUtk+ih4PWxVw8WJRytqjUFCZHlf8A1QeCCoJpP8w0PUJlcTo7Y/sqmHMsNjtsqYF2PaejTZX8yqPtJ+q54JMoNKeQ51NhcN1FrrxDCyr8w7lCfRExMqpTHlTi2IKfmsCfknUnNmbdUTQFOkwh3UnRVSTzkfNPJkvJRw+IFSMyaSxjqZbe5tCwzcP/ABBJCy3aUXM94WgHS6xWQuo4hwadkXe7qeYbr3DjrZN8bM0rDOdzkT3WGr4jNIhUWUxlLQFTfuE2EHCCExhOVU31SS1YegyXMWFxLSQxYdmHcfD2WWtEWJsE3DYWY5o07lQ51R5ubn9ggHZn7XDOnc9Sn1bCcqtJsCjOVojrCFPzDa4TiQSPQLmi3/Skf0UAFT6zIUHsdVEf36qDB01Ul47LNI7ypDhOn7qaTwd1molp1B/v91LQTqLFZ2hws5u6D2g6SgekHZc3LooIKFWo+1gvBxLrHVFzM/VN/wDFZrS5TiKtvjKz11kqq5CLoCjzBUsSHFwEoYao/l1NkPxBojli6qYSuWOv0KKJRJVlUyl2U5eqIi6qQBnMDusQxmXMC3unsqZ2vhydicKWy0OiJVRjnEVN0ReUX6KoNyFWZ/McPmqjKgFV5LVT8OKJlx7JzxmLpTLmyxNeqWgQ1VKAIq6KmWQTKpufngTO6NRxGoB33KDG6zf6p1Qz1M3QBg3PQK8zJ/ooHLr1WUi0k3A/dBjZPzPVTBjXVQob6he8Cy+iyqfl9wuYd7KHuHUStHdQFOZQag+f7oMq9j/RRvvlK8OpGxsrOHRCNZ9Vor1imUsMHjUvA+yp/kmtHmsmYf8ACeZ8H1Rc5zjuVNUoDEKHkqmxwBcJXiNEGUKFaHGxTKhGUqhQxhwtc5TAIJ3WFr4c121OYXF1mEoqq+qGMFyJTapc7FfCbNVLDfh0U2gaAK6JR0TSJIU1XdkRUIk2RKGSDqmsMFVajM7Wy1PZdzYTUfENzC8SoGyVTZSz5ri6p1OUahSVkBy7BQAeo2/qnF36WhSJDeX9WqaNif3Um+23RAaBZRLvUo1HZosNB17oXbsNFEHoUMkdFFWO6lkq6LY/06qCB3Cku9CFmotO0XXO4eq98O//AApYDvCzsnqEKjJ6j7qWydRyldCnEiyIpVj3VXFe7EEB06qoB5V/gm8plEN8p+iOZxhf4p3onvY9zQTCecS/XWFhmfgzMsZso+qouwHjfHEr8qwVJjcIY+oXu10CFKlDiV4lFe8aO6NLH0gNxdEh5XuGU+rlJWQKEMjgUGFxQNV3SV4UNbqjVeQ4rmC8DDN8TRYeth3kETCOYhXK99rCoMwnnBML3rj3RQPmOoJsnOytYIkR/wBLTkLzsEcwNR1/0NOizeXlb1UcoHyUXsjWg/y9h+rv6LZZcp0n7FAv7OH3QnKfi09Vlqj1UgtKIJCIurdwrlQANICgn1j7L3tM+i5BHVEEDuoD29LhAuudde6LXQRKNQTKd+TqmdyneJM7lVJF/sopNZF7onYLO1xiEfzTrWhYfB4N5qmCTN1Tq4qrVyw17yQFneGlxgCwTqWemanJOi/MVwxvlajnhFrZus1BDxG+qp0cZTc/om1cPnboURXYdgUQs5ChZ6BqFB4LV+XxFVp+EoVquibRxA7qXBVPCDJ5U6MuxUVHK5QpnMVTry1gKDgA0XKdSdDrFE1HGYtAlAAjMSdLW+6HlpgDqVndG25UCGD5oNEkwNyUa5l7S2ls06u7nt2Uq3dAiNijlndpv3Qez10RnuszA4fNZxPZTPZEGFqOqvPqtf8AcP6KHiNjKmkB3n+ih699I3b+3Axqi1pRH4c+2sqvM+GVUDhNM6q7eU6LsUTSOyD9RKNGiHN0COUJrHtMwhUw5rg7IOe7e6gB0LxKcQngOgWRdiGDusM3Ff4iJy8s6LCGkKLXZSNAvExDAPLMo9FIFoRBsFRpfh/huMP6Klh6c9Ua9WrVNs2ihxU1233UvYJVJzGsdEpoe0NOy53oklBrSX6Ki58UwiwS3zBVqryXiIGqvJOuwRqvmwY0wAg4gN2+/dBjLrQC52AUkF1yL9gVP14WUE9CjM7rKcu23bsszZ37rl6FX0Uc4+a34WHqob/9LnN+slbdp+6uuYFbdFCAAEJ7sNYBECMgQm9MKm8/wgqAN6QVIPim2OE4ErkVPE4k+LcBCh+GVBT0AWd6dEJ5CFLBX1KP5ttrSntxYIBNk5hkyEcQ8F5mFS0JCoE6hUHnzBUzcEIFrVlYuYlOFVobe6NSDUEFMcZQY6Qixr3yolU8ZU8N+io/h5mmCJRY3RF1JtMWc8x8hqUXwXWtbsFmHRgsB1WVrepuVbsvlwjjKhSjlg/VQ8sd8l3BQcIcLoCRJ7cbDtdS31UweFlfgA8Km3CCXAFU/wBQVLqCVqXCG9VQfo4FNbVtHDNgHR0TS2FNd0Jr8M6i54BKZgsQ1rSCpEgpxMTKquBFWwGiGcQmuu4SUxwy5AqYeJaqFNs5FS1yhUW+XVHP5z6IvpSNk7KROi5ig3EtedkyowOb0Q4f4ZyJCdQrZtwqmLOUsAheHS0uhnqv6NyNH9UZAjXX0RcQNpVvY6cLqylSiU+m7MEajQQ7VVOkp8aD0IWVxB4Q35LksjlB4W4QgPhKrxDA/wCS/EH+XOPVP/D684wm+ip4ugRTBNtlVoVDlLm33VemZmVVGydXpGnl1TTNkMI2Gtuq+IreIHuaOycapzGT3RG6f+YDtQEAYiFTFWCVQcPOFRzjnCoSDmCplgyu+iZGqokk2+abTr5hGUqmcLIIXiOfdQ5e9bOkpmHa0BCowOlGNURRjqoCzVSvFqwhSwj6k2aNOp2X5Wj4jj/2US+VMeqsrexPAqeEoOBTqFUgCRNwg9shWQ8f5SocrK0Lk4XVo4YSi3myysNTs1oKLPJTVTFhpywAqdNoY5oWE/EaWgT8LLmy5n9FaUMmaUMqIKIY49lzu9VCkEwj0V/KiOoXqp3Kd+o2Tv1J02cVUjzkqqxmUOMIkarmhOBF7olgnVVWWBsq43VUi909w0QpvJeE019P+1TxD6NFx5c4cUD4bGdyVEDeZKsPVQwKOEcO6lBBA7hQrKb7hfJw1UtXvx3aVoryrlSFdXV+Bf5iSmgTCa4WC9zEKKydRdYmFSxdKHwVkmpSbI3Chh2UuTSU3I4J4qA7Sg98TFkcO0iVBRnZW0uuyaXaKnpCZ0TAUwBNClHx4Cz1R2QhCdV3Q7IToE07AoA9IVV7y9kzNkWmHGX79uyk5tpR1lQ0SgVKtwhQj1R6p3VHqgRdNeJBCIMgXH3HRSyRde/p/NchUNB6q08Lz7BI0To3QjS6e8xBj0VRpkNd9FUGx+iq0nS0GPRCrTy1B9UyoTUp77KnTfzAhPZU90HEHssbUbDabl+IzemV+IeJIpkLGMYAaZlYrXwyq4/lFVZ/hkfJGdCr2aUf0p+mVVOiq9FUJ0VSLNTqNSXNJlF/MFVmE/unZYVXaVVG6qGTKq5CvDpSwd5RfVcOi+my1UbwiDqD81JUhQijwb+oIHRwPzR6ohFT6qCRsbhQ6kej1LSPmppMXLw5QPYwRZ5B9Fg2jyfZYSPJ9lhif4dlhY8gCwx+AKh8LQhSbytupGWoqFcZgBKFA3pSOoWHZ8MfJUf/AF/ZUD8H2VN3w/ZU2mMn2TD/ACvsqZ/lKm8fw4Qc4gNRA0TomFUOyeVUIVaYTqh5xKfT0EBVJhVJ8yqEeZVI1TifMqtM6ynOgE/JZKMKKbr+pC0UUwbaLMf1dlUbTzmnTA9EWO0t0Qr0Q5pRjRGVlReYVFtrk9gsOXAOaW9ym/yK3yN0aZiq2O4uECAWn6IIET0uvck9CIU5ypDR0JWoUq5Ctxc02lGSHBcydF0c+qJGqIsDZaDVAN5bFV8PEPlU6rYqxKwOJFw1YN3lIHzVA6VD9UNqx+qzfzCq7Ry1B9FiQNWysSB5QVXaOakq/wD6zCqNEeG76KpECm4fJVGnyO+ieT5T9Ec3lP0TgJylEss0pw2KebwngTBVR3wH6KudKZVeowSII1TzXYHmLEoMqtY0k7lBmGHU/ZXQfTaD1WStpaU2rhywkaWKrgx4TvkLJ48Vr2kCREqyuVLoRDhTbadXHRNNOwFlno5mCS3UIg2VQ2PMEWmQC2dtkSiQi6g7/aVmw89QveO/3L+qy37rm9h9IEjmagXRCh1gnSjM3QDUSDGqdGiB1QlE+VYhvlqFYto/iFYwGC4rFNPVYgG4KqA6FdQVT3VE7hUD0WHP6VhejVhejVhOgWE6BYQ7BYQbBYPoFhBsFhRsFhhoAqI2CA8oVTEOloIiR9U974lvSSoa1trK6BeAVN3BNjT7JzjFwFkJKspUuBhOaPLI7qPK0T9EY09bqi8k5Gz6lAizQPQIbhMGyyN0XJ8l/hL/AAyPuor1R6Fcs+pUtcO1lmY0rm416lPIdFOy7Lsh0Q6IdF2XbgUfYB2TY0CYdkzomdE3uu5RPxFO/WUR8ZVT9RVT9ZTx8RTyfMU4/GU4/wAxf600a3VIfCmDyiOqzYkD5rM75wv6LPi8u2qBbop2QbtdeHTFr78L8MwiFewRnfgAgFAViEMpXuKo/wBSjEVT/pC5Wjsv6FTT00M8N+DWiw//AIwgghwCCCCAQUIRHGdl7xzlopB7BZsW93RqtwDn5joFZQCpPCRwCACjhPDkKjxR6KcQ8dco4X+qhjlfv7fdDr/kFFFT7BRRR4FFFHiONiZtC5T8lZWd6KPH6yFYIAICnrdABCpaUInVAIEcB7MLkXNU+SP5p3YDhzqxb0EIyoHthDgPbB4EKfbHsCEfY5D6LlI31Uheb0WSvUZ+pocoCnsqlNt9OqcbSm0j7yo0diVTqjke13oUMhMqwKlWXdHqu6CkdlyKalX1AU16vyVlf7qXTvF1HC59f8gIIIIIIIcZCIF/ZupOvAhBNKCBQV0VFOFJciWqQT1XgYmnU2Fj6Ll1WZ8IGnCLSYTiTCe10sAB6qpUINV5PbZZRZEbqeEcbLlhQah/1H7Bc9T1j7LljrZXKIugdRCBI1UOPr7ZRXfiEEEEEEECgiFPtAqERwBQ4E2WSoP0mxWSq4Tym6lgHRXI0govw+Q+Zlvkgwym1GaqR6qNkEArKDPA9SrLvx17KWT1/dWeerir+llt1QUIl09FzH14X4d13QCHCdl2RKPEIBAoIIIIBBAjXhb2AgU5qIKBQcs0olvLNtJTmAWPyU3yzIgrwaoc243CDm9iqjZyOIT2v5nw4dVVIAkJw8wTY3CYfiTG6uA+apudAcpE8LK6gKKTusErw6c/oElZKTR2UmUbuQAQnRZKcfFvwtwKJQQTQh0XZdkRw7qd+AUI9V3U8ZXdPCizk02lDqraoQrIFNK6FPZunbqd00K8heitCgq+aLbhfoJb6J5/nO/onf8Asd9U551d9UwXIzHug3ZQyON+GYAdSB+65Az9ZupsP/xZiOigQig05nLMew4BCeE7I8Aggrq3CDwIQQhBBBDi6dVAQdqoNioXf2QgE0ojRPGy7IynAzsp1siW2RY69N0dlSJuXD1CojQA90DoLKOEKOJMr3vYNJ+qL6vTKPuVI/09t1AQ6woUnKFeOAXbhGyvKJKcPRTur6oIQtkUeA43lW4zvxCA34AqE7onpxGqf1QITQmppTSjsjIDk6lWbAloN/RBwB1BTCbgKk74QmAy2yLe6HRf2eA/sIIAIkaWQBe9x0MfZcmd/wARmETwEz/YW3sBCNEOiPRAFRoiUUTw6oAruu6kIIo9UQrronEaJ0o9V3U7oRcpippibCIKKJ3RG6PXgCrzwn1Vb4QE8YYMqEZmW+XAFAoIHgeHorSSEFLQNS65+akzBKPb5rqiR+6j1UPI7qGqeFlCndX1QQlBQhwkIooqOLUJQlFFMnnVMiGBFOR68TwaV3RRQOyIPZAC6am9UE1j+gOqPDup4wuyDRLiB6qkJykuPZFzDYiVlO89lF7om5t2Q6KVdXDo9VlEdLLIMo21QQQPsnqrqODYQCClT7B9qQijxMotCvKCHAolE7+y6mMp5m9OioFuYuLR3Co1PJWpu+aHUfVU2/GPkqY6/IIjyU/mSqz/AI8voIUnmknvfjECJd0V5Nzxjt3TaTsskk7JrxofmssudtoOpUHX/Jvwk+0IQAQIR4niCNUGCEZXdWRRNkVmMItMHgITgJgx19vMIXh7STuvEY+1xEnr7Hdd1Nkdt1Hr1UlbQnj+W76KrB9076L8vXzPEB9pOxUvAAglCAdmozc/5VvZngVdbKOFuEngR/kEG2qJueLnU8hHs2U8KlbmEBnUqhRoinRku+JxGq5z7WgVR97Ad1SYfeEkqnTLC1gHcIFmimypOoONQAtAvZZ3EMtPwrEVB5Wwf9Se0wQwfNf/xAAmEAEAAgICAgICAwEBAQAAAAABABEhMUFRYXGBkaGxwdHw4RDx/9oACAEBAAE/EFUGbFgs83LUjVylCt4qA2gONQegteguNwQPhIctWuA7UVLMM+o6gW8xhQuHSWjpsqWGo6bmBGNIucQEzGLjlaPbMI0sVbjjuVOGU5OJiBhZdy8W4hApqNKOHuFRFTqFUnyIncLDcCEs28kcFb1eZchpZHUcnJ9EY1Wr3daYyYNIv7lzZi/9RDwA1FFwkZzTR2wY0U0EcgAa8zNi2riodIRqXK4Wjo1HAM3eWb9y8yz/AMDCEdRYvUycQqjXiyzCpMLFhPYwVx9KohBLcI2FRpQXLZJ00qUK7mhhAlnF3DAh3uMRYPBHWqz1FeZBVoqC3czK1LlmUJ2nMCoNJbWbIwudxWRgSlw3S3A3DZdQEviCmyCQkdlQ4av3N54cSyYU+4Xddex/2HzwLa37jxvnCtMYacpSMI0xX8RwbgjqD4ACGSjDiEFeEKKjrO47XA5no5EJABHp1E4ENLZcQZV4uXbocQsEMIZtHZKVBly46llwL1Gxz9D8wI1mlFvpj/kSGnyTFY9aS3KsM0P3G6RdBceXRoUUzRvAkSAOskupHwQHgfMzqQqMVmVxhTL4VcdQ4juK1MpEYzGbSyvcxcRDeUwxISmZBuOV0wKxqHXmNBOJQXmLsGejEZi1yIFO9eXEfNYBTZ4/qVGAB5OIvrQt9JkfZZYC6tRozM88HqBOoHJvxHqxcXU0R0VAgHKxhC29AgW6HIQ0Exq4guXNxGkteCYw8R6uCGzMMpUQwlRIbmkHK4i8azZbIr7lMsc5WQEeuaxExRR1zGYDiy8/EahNN1X6lCOgEu10IIpz/DVQoDUaIKAbXO4SwQdpmGmbCV13CKFjHmZCGZfAcQdNYiAMU5YZi6gsOplMYnMJU7gG9EAC0sJAZj4sqOFmEJrfZDDfEMmHDvzALAVxXBqodS+ILsxfqKSjRaiBKBzTLM6piGOnEoiuZgIpY4sLgItzimEpQMXB7KNUVf3AOIKTKPNSkFrB3MvUVyKuCtQrcNoTKMWXHJGytMSwcVMUDnl+IVkYAYtnnhbcYMHTyHjuFqgx1GwwTs/cXiT/ALmXZl8malw4eSXSoOqQtmQcSpAXxAQLPiGMipdpXxNNLVRqXQZImWKlKwacwogppgZmLAFEQW2kb0ZcL+5cBzUYFavTMAeZjFTyjpfTMclrl4nwUjtAL1C90nlz/UMt3GbVmFZIcM4vI46YwKdASJMRQ4XHcH1EWQwUQrqoTQ1IQxH4jXEWpo7gzoeQxKA8/wDkIj9RLHbBoiy5WYjxBZOXMbMcRURN1aMygYs3GwMskTFryRrwOmU3hCI6WB4hgprVYhNmuNRggpxW5Spl1FZ9AmOUhFiRAiBSAJsmtNkqaO5eqR2UgYNwgV3HZUVZTiEMwq1Q25iCQgG9kABw4T+SMV1oeGPjgHLMMvbWcVDiAtGNBiJZSblJyF4LxL44fAfmMETLyP6j+Hlojm1jeMfmOMzEFYIMnLlhYiPEtgIOYi1uTqOADwNwuxhJ4fMM4LZkY9QeqVFBOFpDD/zf/l4xFQxCq91G6kWYjvqesZCViVaRpj4ozQLCFIobDj7iKJDkoh4o8i4xHc1xDWYeTdxPAt0RwoYmZYGowlbmecXAIIaUl+ciC1OoWWADUyxDaCWCM99DoYNCawY2iajmIrVGHwIBZx3qFmdMPMNyeoqJ46ilScjfqKqo0lkI1erKushuXBAVt5h3jDQ1LyYmwlce4b/gxLpUC24aZhuNWjBBhJmjtl4hJaoeC7M1HKAaFsIsAX6ijrJGrAOVghEK2EWIFQ1KYNx70hpMAliMIwRyYhW2yIqOIu3VOoiaPtx/yPDVrIMkaCK5ho0eVqDLhPZK8N9x1ClraNTNIfbEq6DaKBbTcul1Gmwz3OfNse6OqZlAYUZ2Zbu1csFwFkmVHE8wgSHETMYyMQODDBwgVGFiHrUyLc1RnzKKUDuXhCMViHYqvJKSgeDFwoORn1MYP6I2LBsWDroS0huISgYGcpaRxWrNEeY6bIaYlSl0+ZqrIrj7jTfw6Z1KJMYOxNu/5hNaVRbEQ5gYLbhlF4liMkxBh5qZoSA0jKCkzBKuFFtcQNQgtbmCj4jTEvYcRm+G64IAcRnOGFgV52R1RhZVG4LrthYC/ePiJLG2NKC46gAZV8xgAbmCpMQhdumE5IFUYgOZl1dQk6hbSvnDCAKnAiZgTHghWNlwoq5idxEBzDullGRNmqzKrhN+YABpFLlP7egsfTplTVC7SszL4L4I8Xb2gw1JvtE1Lpwhi83EaKpmjUQWDn4JbkGDLaFEyB/MxlsP6jobuEKsJ5X6uXALM7hUXDzLxxoDiZRgS2EhKUXD40hVstabmotZvhgo1jdQEsNagaaPMVLs+5dIlkLX6gsqDSKr6jQhHBbk/uKRciqyQRC+lkC/BJjxanMZfDLFm2XG5QkPiKVaQwt3GW1iBkwnMchmQ1FDgZlazEWxiLrLipcXGSOo2BKuCuu4CwK59sIVKziNsuY627Iq2cxzllbuO70pWiGykYbtlGzUDDA2ePGLiUFC98R8agCuEFqvLH1BJtuIwlRTR7QIOtgr+0MI2gaie2HOGYcLDcqjc3sK/EpkZWDA7epm9K1oX8S0aUiE2b1WJeFwkcW+9SgOaU4f39RFQBmi/nUbu7KQ5oyqpKVbKF8wSAUgWwxx7jHlppa1KPy0TeoslgpgdiFFMUUle4Ra4uiL1orClXA7tvjY9w8Wi3gQZQuHSLGOy2XcaqLTTuGdrfMqGGoVaw5mGjyqMKi67JRrI+YwR7mg2wYumNqix7oyQ61LzUWalMrYahccO5RKCtxdLmM1VdwlHENb4op6ZgtXF8QAVVcsaEKMgCEYJYZt2zhLQrGJmQ1QG48aNo/vGJV4LgJ4BR2lIOSBtopn3BSmwRgCWPDmB08y4TQLYriV2LbVSmkeUqXCFBy3H5FGabOPph+e5f6i1BDsokcoPKUU8y8QBmwzwhK8QUqbfaX8QSCFcX25upbavgTHYZG8xVADYZZ96gQAsDyac+IdCzdN2DiIysFXenUZCFCZF4hMeEhSGp0I+4nHYmYf4cRsD+TUCig4qX9lnjX+YTEF/ExYWt0wwuQcVfmEQ2vzErWZWnmUswTxGcZjxuwgXQOWYAKFkBaPxHOFUxSovnHHUpa5piWgDFqgHKNyIoSvzHKS6jeLR7ish5WQUqaKMXFI2rctvIVedwgNmS0QFsBZULhgLalSNDQx1tKuCmiZNUB3BUZwEq9Ewf8AkqV+BW/PUJYB92PcLyy4HMfpKvCb+hmPgPQs/mJ1SfLn83G1UcUw+Fg0ODUFQYLLoED1mmvmVxSywPypv9s3IVhfB/UozBVkh5DUI1s3wfXEOr8Gn1MKALocexeJX0gDIvk6hdCYVKD60/FepjDFP7BxK9S0eJYmBiuIhqqvrEYmswc3AixAeRKBXwS+yOG4QKWObZWisQlS2ag5wSqApeoA2c8w0JcZRM1AIgtEdVgGvMJqKgwXCJtX4yiakYR0YJVBzcRy8EK+UCAyhnUVxQW2WBGTMdCbgCuVxrmy3mF3ZmSInYZCo5aCkYARgtWI2arKFIYMG6ZVEPuBtoFRiy7iAAhRzEOqNacuj+4bAowo7/3coXR0pjyxxT3NqPLtfEuBc9CeLX6CHBmNYPld+qjty7yIfq61mLFQ4WH9PzM06uAnrT9RYCZAw6zVdZhjozCnkTH+zKAKRrA4YzruJ4LMgyX3jnzBF0IIrXp6Ox5ilCOi7BLsT7slGQa21ea4+x8MZZkFU0O9VFqJsKz2PWmDQrJaPbgeHPkl7lCJaRomBH2cNMUHJlZvRcf5RX+ajatMk1sZaKu3cEqc1GtJRxC1LfMdA68QaGqmQHEZYwQfqGKWytAbalZp6gFaGWyA9y2V6hUHLOpfmOKuJWiIaGX0MygVCbRjqKHFzEC9CCyMexWgq3I+rOhEHNimEPeLTZ1+UIRmNA88QcInQ1qDwc8kKMqXkfInNGi/LCgEmXBjg4+NQoybN2+OS/H1uEzkOUK/H8RpSGQKPXD4PmKVxayQOrZy+dQYyLwtra48W/8AJQlhK2AbomEzz0NQU37XNcBKvO4ABUbFWdv9mdX1HplsLA5Lp3W7jEjWRV9ezlcjFgVpQqKf0vHio6FlgpTf7lvNBscg5/UsXe67dHt1w6q2NCLIIydPTAULWx7Ox2fXUBgwUDT7E+odVYG3yXXiXDQVTDgLzXfVQbYYrSs+4Q1B6qLk7i53ojXJBqMS17qUAhGqjG0x2GJk0lpNqmEDOouo5bm5LZzG/gF+pQHAGA9kAhArUDdRmlMy/aw2XFLRnqMCxAsKj8kFeJgvUuY3PQREEC6uoxEg1A8kaBEac4CXoodqhiXis3cctydTqEEeuh/U1Z2K94iWGQsC3n+oWGjWoHwH4IrxbOEfn+seY/pLkh0OflrwzUACCg6tx4KIfaI5fyUD9SyAMFCvR49EvC40QoHhavnEQCSBkENXQZvP5ijdl6DR9VCmUABdB0TkSzOqI2RZ0o2vVcckqAY9LpOS/rHUExMBSth+jXn+cYELQ3/hd879mGwAARODyOE9PMS6LYDCeQ94SG2KQivMAMO2hDOi4oMxstvj3AJhbAFvZxfZz+imAFYPb06jT+RiOB47gyKuoa9WI1YNg5hYAdQrItMURSapScVBwCh1qIQKauaJE6uUAFDA6YahCmiUtsV3BFLcGUlbzUvEZf5QlJlLHGYTHISgiQgOAiPOaBRejbClzNah6IwBKG3mU6EOItRS8BGX1FHmF2DEV4MximLnui42cha0sCKwXcx8ODTBEcVsCWCz2xBs4DLLcC14Yi6CsbxmVE8Kg6O4+OclOUNW9bP+sVQkoMtvJ7b7+YUKtLEr7yuutfUAoJVovHJ9QKOGEiHxs+2o4zQHA7ajJtA6AZXq8ecy4aIEPK6X5zL0bK+67a+PmWxLsrN2W2fTHAMSjygVk+B8Pib+jgt7/wDh8+UEOYbBprg8/sZjEBgZFjLyNZ6+IXlApFXwX9ZlRQlKKXXZ6YDIcg34f7mHNRamsZ2Pjk+eSMrQWO1j7xXxHwrZyX0dtVH4lqSfS3k8yvVMW5LO3s87itApxVPk8RYNFBYxypbsSJfNjcKuiUQD4lQqewh8ROkVQE+gls19hS40RNbG4YGBlvD4qF/W1ghINgi1gOt6q0wPUdmPzDNosW2/EZmjI4hILKXcSJB/KD+Jq026qCU6UfjTR+Yta2bw9dfEAhWYxKMUhYwC0KZR1M9lQNw2AOiMG6eJmdwIRtsVHJUs9Iv/AOomalzyj7BDdVHJPaqEQTN6Y/BHZhVtn1B25epXwkPEGLF8EewYEi6deVwfwTBHq7r/ALM/eCDTqFo2u3jvErORcI76X3M0LulsHz29w44SDRFoKikZPD3517mOCEVOzLigoKXZdrrmBAMryf4wQb2WN5rP7YUMo5PTeY4AVhHi2x+oCB5DNHn6ajYDFnG8a+tQtIWH2vT6f3KRVGyH4fuoWQyGvq/umANo19Z4/wB3FwmCw62/3m+2EmZw05H/AAfjzEIb0j2fH9w6VEsMPtFhNyjL2cPmZABt/BRSor8Q9GvhRbUx0rNvfF2wEItRmHdARqk1DEFvUAxQhrQeUhhgKsEpgi5mhPdSoKTpTWAhoVccmzJK9Am/N5Rws8txuvjS/qDw0CKB7h0UwAy/iJikVQWsRvqxcvmEVvddfEBuH1rhoBYtUQwUbLzFcEMQuFRLBPzCgSUUkKKL8wLGviKok6YEE1kgorviU2WF1iGXx4hqK2qiXNxZ5E/gillqwK9HxjvcrYTXmaOD+PvmYlAMrFvsODiWDWZQar3CAIjOVW9HeejLKGSOFg11caKFLA0+DwQCJVsNGNx0HJKFLrtMY2lbcZmMjBl6/wAyk1SMLjs+v1M5BpQnCcwWSwG086+xn4YFLmmXIXTXrDB9g1WZkDZgR4H/AH1DyxV30CyGW0H1lH+4zKq3dXk/NPmNenkbXpP91Arxag5/4/zCjlYp4Tf5iJUSJlWeTPf9QGyt5G4XRtf6gy2AImgQMGoARQCKFFmbLzBUgcgcZYbiWWR6DErZu43BuBRCqxEWhgKiamrEddQgsW2A6qo2SPslS3YxZcvzHQ8SoI2yjEGwYhdGG6uES+gcCEyAVtLnUjauSG9bqOkbV1zFVrPNxlYxK9AuhYsIA8p5yJLlG3lWA+40GnYxSFneuYVIdK7ieAmymNaCtSn2ozkVf/yGJLK8qGhv5VYea0VPY2vz3jEVneQdl58sCObTMHl/39RSjBpa/B/mMgA7OPMPIKscvb/sftbycqcEzlWP+D9Ri3lW5pArBKkOkS/LTMQNsWcPcfJ7qv8AfXzC5K5HkK/X6j2XgP4b/EDgxRdb/wCfcINAhfTX6YWS6bHN4GvzUczdo88P2xXZe/pWf96gKjhbw5w/f7i+Eyh0Ox/3UaKs2QbC+Zt6Las2ePu4Th3Pn4fiV0/sjUPS0AezcXABajSsYm3Civaty5JGYdRTcEHIt3CcI8TBwefcEC553iCFuAoD0weaTQHPUQxM9d4l2+wvQTOFgBx7ZYV3QKq01E0hrXL2WI2uJ3mEUsIJlHC47K6heJ2hINwcNNF5hikG/EeZckfRmdxUCHbcY2ocoU4ErkgVdzGlqG9ug/MsfdmcDOP+PuNd1ULTNeL0edyyrWqy2+Xf6g4CFrGVde/1D4HRr/qGLd178+f3DSoeR0eWORhxY9LH6IpqvJ/nzHAGdnjI/qBU6SD1G28aD8wa5mshwyjLSP5M/m4xtlQN13L8SzVovr/YiCH/AJLuCsFr/aW0BL064mSvKB+afxGiYDB9ZPwwvjdtdp/8hhurPQsly7gHvr+SLZaYznb3EwaXzHxQvB8RIJD8FkHXl4YtNgeDmV5shiGeyOYsEHZOhLEGFHiE9lUbtcL/AJhYVnC7E4gXsDQ/qGWAKOQi1p4FWGCZgrF5MoVBzyqChQXRCb2Qx6gyg08sAzGe43S35iRTMcyArBi6C9R3dDpDHGOGUNRUjkeIC5oj5u4aBo7j1EdLnuKSGoLXiUxWoHDOCKAq5ygWUYUAkZLNPbe38THGQQC9LTF+fqM7sGHZDoe/UPM5+T5f6hZUUXfB5/7HFHknLt4cd+tnGNcLyuRgr0rFvZj4RSFRFQh1TJ41EA+EWenHzx9QZmsWpkWXkf8AfccGxprk7jS15Kwcef4j5ryecSq18UPVXDOwU/z/ADMpbsrjkzFGlR9XHEytmHsxGRop8PEGvm3wf4hAopVuHfs3AxiMWR6SJ4jlu6jEXVe00dygqOQ+0d0qNo1rb5l7AW8x7DeQMQHdgeGMVDiaBNCw2JSRwQhssS2rjoMV4cLFAXuH6U3cx65IXbWka0C4Lwwwe9E5gBwDHlIxVQ0dblwqNbAVqo6hsziZdGi+uIveDUzRViGu8JAOVPzLhjRtj0GFk7Znqw3UKQKZtsj9VK1zKFtFlczFeAjooyvm1/ETTuHBxZbXGAuMXTO1VeLc1NRaN8y+e3xAplBTTB48sHyha+se4QIGx10hx0+T1DRrBwVs8xLVpkfMLUXgS+df74h7bTIGsOa8h8MqiiZLVjmvek8/MUlum0/cdptqgdwqGRGeT3KBdrPZEpPiK+hV+pjcR+lVKqYwH5RnIPoY/ud4YPgH8svG95KnGMDXblKjBkRJQ56lJC3KvdpVLkufUBZynHmG7QRcQy5PUIS16qNKCeWbKAuARY6OpzEqhkZ8QSFSjMLctzIcQl9RKmC48ysYlBcLGxnPzqCeCiiYg05SCcoVX5jyxozPEuYUsQlNQXaabYNuKKm8cw6Aojnt2xtVRhB2aJRgQGYWZ2c3G9TUaHmXsMqMuuCV+ZLKzESBNOGXC+ZjVRxb+5fDSgYvx32sFLC4zbs8sAfcvb5hmUuKBXvohIhlCvgXt8vxULERQVwI/mPtjDiId5GuHv8AuCy4Ap53+z/kDdxZW8uadf3CEKDh/wCoZcaGt4rceHBuq4vZ/MYNNGD2eSANap1UY1wS3YnBlwrY28uouChhdHH6l4C6UDq/6EHCupl3FDF2uwMKqm1MlApOdQsT/EAy/wAXLE/VBzU9R0J6kbNajt6zGt6lBLFg6lSgSH1G2pbdsABCiCg1kl8rC8j2XUNWHEg4lGxpKZVtDBX5mH8pK1JIFhDOKumJnUzg2OJcDmEgU4EvGAwMLK7NYjtrUrPEdm2YQtHNyuDmAfNHPqGaGem2H3gFez6MfMLDtocUa/uIoBwQVbz2f3AtmLJxcdgt4UQxwU8S6HUMvHPiUDHqNht46hol+o67C/iEWrv8n9kDWB44Qw1WWv8Avy/UGsAfE4oFXW5i0HNi/iOFIKJ7uD1CV5bhaFC4L43Lo9V9MDJeJkGXs8xw1U9wQ5qGrgpZV7ijR1BBkEOkXR43qXV/CCrbLyLpMc0isWBw8Q9A1lzAziIo+cMUglwwCnQQ31oLj0ot7uYuodw9cGAApH7I8KXBWQPBEEIPpjaNA3bMGo4dQjIYZ4haCLUCasMQugXmanplKNxyQclPMPAcLG7hgBWOrmSLrrc7Xyv4gAs28hx95fiKeyi7+2MhoWph+JwQiwqLg8ykKrHniNlWGsjOU5mBpGDuX5qENDBADTdf/I+KhjBGCp+BL1KPIiWEegjXF+2Cg5/sxXJZKNa8wqo1brpjV0Z7l2jklWhWYUU1GggPqVRJ0GGBQ7VQ0BS0tieId4cEQn2yQtc2heZXXf5ik0VBaALjZflUfAA0KpYuTVq5SoVe48k7BNWVVDyA7YEBZXcNZ9kWj7oOi2nlG2VfeYqW3hKgh4KiS6ruHRsWw1TuFmQbARO4QAD5gX9kSb6TOszKoXAu4xbE26VB2ND5UhHe0Pot+LuMHgAx7bhWoKqliSjc+Yq7uAFtnubmY+C4c3dfMI7b/cu21uN6IhpVm8QmS8dS9EW035PMuxms3uGYBS/UsMZcR4WH4EsoiADlpmnKwy+ZZRnC4v8A8dqglcP/AAYhD5jSsMahpTY9Rb7pxOGavEKBvTqkjPoLY/mVCL+SZmFrSRlBBWbXeI1UrsS7utLGYhNjMb6HnqKLCvwTbBvFy3K28xVALrMGS04WzKg66hhE7RiaIPLHhS43Gq2XcQWqsZDZiLrWnLChIJbLSQIBEzRx5hfcdJQMF4hygQbw0tPnUPcIGVeXAV6JhrkU8uvwQDPlTnqEyKNQtAdeZVyYiqN+ou91HKoNBFnLTwS2NNR9433AISsfqA0SYIK0NZgbd6Qdnfpm41rqJlzQt9wKXTT6ZkFq4mR3p88QEurd/qIlOdR2g1b5ldR4VOriJSw7TZBVWcRlzohJIOoQZErMx2XIfxBvD2HEIr1caVM+ZirsozGRS7X4mLFEJSxe6iwBKfEEaFTLQVekxNs+YUGleI2bX1F2ynxHiVcqBLXzKxuEbWvc60u5VFxtD7mDr/yJ1VMeJcFUeoogQhaLkEkPJ1ODLp+iZOgbtj2h5f17mQjA158fiBQUbzDAyVSkSHhZC+2P3KSlhq1zFxGWb9+oVoC67inDD3UYZXVMoDvi1z9Ril2dRVeBoPk/p5lQwksa3HKTaP1K06PyM8bMy+njcF1lIbQ0l/8Al68Q1DiZLiNERHqGoUo0FR8W4ZRi98puiPKhAcOQgu4sppCXKFo0/wDYoFrSmJNsBsfMaeLUhWEhrEtV2ctzuzis2klaIvqA50sZULlm2VLfxUzQV9xq2ZlWRjFgePiXBr1UusEdsOhcJQRc0kEAYhbdp+Y4IbgqEPmN5sQ0cholYKtwrgL7jMTSueeuuIwWKJd88szAOOB35g0Gq2dZlXZV5cQUHcqDIVH9xwvUaPMsItkY44xHbVlaCT4YvTEErr9sKxad3Me3UEru+1wwdHo7P5+4dbpH0k+Y19suisXX0wVy3pO4lNtpv+4/mX/4S+pqQnEKMWvCZit9IgQ08qIHf6BH0AvxHXS/EWtXCncpjrjMOp03E748v8R+krVNIdxYOSCwGcZCpD694gLdoQoT6CGl+giIQHqKLWFdFcaiJwr3HKqLEKTULCbl1GncubjgZjjZ7jcAHuN0g+Ybn5yuOTxcWBUlhoIeVa1TV6iKFoycX0H0S3JK9xrX3FWmKoPEcNtQBxbUJcKuhaPnmBQjWN8w805UcEw5nFdPUErV+IBf1G4bhC9HNRqlHZZ+YHpXSFVCs21yU/uZyP8Ag5IGVEwrEuYfmUgF4P5/FwxRlFPCSq1pT+Vf1C0uf0rixTtIBU83/wBiZW+Ja0rXMrNEDHH1OCuJWJrcGNHA4lZY8EbtDjzAVaSqVTkgqlh2waI7JmUPLB/UNwwtXlCfgEpG3U9SHij1lONi7CKQjq4GSxeIuBFdxpZfhhMPrQ4K4pgXtHthXN4pAA5pQ4TH5ZjEvnGbsPJUaKl8TIVXVMzLeKWX118WoLKNc1USLU1XZwnqUrSxMarH5lYFFFKs4vxHKjO5x8vMFb2n9x98CXHBBZEZDmDML6DwxhZRS7F6ZTYEArKI/ohN0j4ox05hMANor09wUMLWO5WXDYGU5itJHhJgKjdlsoqOi0yjMrCqjTZmVjY4GvEzOg/AB/cxzilj3UqbrkMNAuv2/wDkMKc4/wCRKUdyqa51P9qGFyZwkbJ3rJEX0NRtgoGQ1F7rH7mpN+DmNRyN8wAG3fMuja4lYGuc7hjZF5Jr2e4VQj7l6Q8y+pRNO3cDhl1yNq1eybv5CY5XMmilm/sIvlyeSZP65gX9cIz+OHf0wv8Axh9FHioHSlev/FRLV0GQB8EDMKCnyz8QsgdqFmYEjggEWpcITYLiY1ucrX4hrZbxbMpB0AuGsaw+WEAxdQbKSsMLjqw/S8QN8Brp9QVXV5K/ETp3cYn4AgA6zATFpXm5rhYagrQEFwQyqYaV7Ib8BPj/AKmIK/kpl3kfyTsCiPI/9jZLBXyf8hmjruU298wa7j6nCrd1ApX5VM8xRi+Ub3Fgx+EpmFzDjqHRDliq6IHiUrITrYrld6jwteoq6zuvhiThHzMABfMbvNHiD5jTpPmLXX3TE39kxrT5mtXzKZi+YhyvuXG1PzH8mkN0WEiKM4c6EjENXat0ZlwhjBjglyYpE/mKYzSxDJ0JRaH8QdqywYmaZC/aWXqz6h2vLASq/EK3a+493BsrcBwaczsW/RChq07JnQPqGlldRCjGKxzKLsgdRopbF+af5njj+r/kRicD8Si7w/ZX/wAhmZFr1WT/AHcMR7bPB5go03KlGDEDeCp4Q8ZefGGP/mtRPU9Z+Eb8QcFDtERoqyoJ4iU1HoIB4n4RSohuNVYiWqj4I7UaHP5jYlsQRY8sMm1ZTEqKJt64IXbumq+obLayzBO1/ZGpMEX7f+QwGU7gQa115nOdwPcG+ng3KKaPiXFagiDCsJVsDm5c6phYALKHgh3Q5mZkjrGzi+Ir8ZzEHcr3g/qDkj6Gf6ikbMjUId6r9IGXmwoec/8AY8rMluY3eplkceoOtwd7gzmB7gf/AAKdwKbiTmAjBhglupkiM8NRAZjbtlPLM1vEYps8n/gvbEdXUW+RhYqMM3E8uYJuPBECBMJecS9DEsPEGztHfMxHWal3gkl/Ivilm4vUaA3C1hXU3wjFsI0gUWXiFVmEKbqA/wAxItvBxFK3vREbpxG1cOc/3KeupQjeJdFyDh81MBwh+khwXyv7lVnIIZOLihDsXrhlvfGKldnnMLLKdy1bhyuYO4BN5gO5VpWGYIS1KEp8yyWXAHuCJ7jRkiLsYsLhRmXemW6g9x/2o0L5iDDOxLlL4gF1ULGTEswMx8GK9MtjZbGtLGwSuthUYnBX8SsfLj9S5HQFbrD+4d0ZJdvEN7ywMifwwpCua+udj6JRyDZYnxuNSAC2UodwAU67iXJNz/gljpC0/SKd3WqisZPKHeqzi4XlanjLFe4w/HExt5iGzi1+HH6iUa0e2ozrnGNx0ryYmJ4wiFVuLncHMEjXmYYDqCJZLTvMS6nqiGLgl5xGdUxEkTg3AjK4NYXjBfcDsTPQ5XbcbSxSDubIFFJFYbwRzRqZlMRfUZXVvNbgjrYB6qLCjjD+Y9ZhVvxHbK0HwfxColhyPcoVnMLMCUE48ceJUgpeof8AFKBDRvCvpCuYhHEVfOYAXi/cz4gKzYbv9z5WpivTG2HMdVuFlRM3+o/bKGy1+AExA2qf71GKGjVdagVFZ3fP+3LYut4zEcFLvUVHG3MtxmWlxVcS03BeYre5k7zGnOIgaVTCkwxvzDsnMsKcRPWXqJAhqYosmrYxJaWQdRg3C5juNjcq7/8AGbrLYn1Cm8MKzFRARqBBLFtQ0ShCgRuupWtsJ+cwRjyfmC1aL8CbhNL2O+D/ABC6FZ3KiCzEJir8JkXS8y3gITeJmWnqVgckSvJEYangxHUsX0dZjVR6qVNMsy0LWDJzZfan+Ame5wPNL/UsseFPb/yWXtwJUzp5IbwnjiMHgGPfEDa7QcbijLULNxEwIAc2jtZlriHqsxGW0HW0BWFRZXiF1lhXcL7biMZzHAWQD1AV6jXNExApErDMbKSpYAXLE3L4RAZcRDzEB4i7TZcbwTgCky4jWdwIRIWYHhK3EqrWf4JYK8FItP7gsbFCFemMCswTsliKQsrkmbR0aYhCsxwfmXojsJX2vEMxs8kNaNPeGFKR5pClh1YNSuEymIL4QLpzEsX3Lvd2nusEJaqkbyGD8S0SkC+/8sJktZfb/sRlhx8VAGsHBCULro/5MJOy+3Aesy+7hdcRfIsQKDEAG8XBBV+ZWVioWrE2y75gdkjcAIbCQubgGoYPFihniXGGDtxejlM2WNK8Qu0mZRWu4nhDAjTZBTgjeCW0AiOCIJAWGC9xAqo7dVDMytWKHxc4lRi0vkEtWrxFHRXUNjYvYWMDdca9QwQ9yii3Q4hWqu8sfTBC1eiEsO2bUGPN43YTlXEVgvxAJmTEo4qG6+4GCVUrwLnL/vqMLaqB7t+pcHKPsXb+CKmm3PgQwGmBDfmUo6MXClBt7uWCtPxDevN5eCO9wqWkcYJXgg8BXEajjxAnuHIQLAEaYxC01AuGY64MRoNYjbuDvETMtMuteJSoZjtIaQ6iXbZFa8xSxjBQF1GoRZRBYKsvJl9zTDLcswhjuJOUhVqMX+fEbUtTeKXd0YYqYcYhQNrUsymRYRMi00dxz7oCQEMprTDRY4tliOCAQoQ3jcWrjaz+IgbjbV9RxfXruYCuEd2qH8MKGBZjK6D6GJqoNlwt76/cbAFdeIwU7MVnMoZM8DEVbDbx/wDImBcD9sxGi3MG7FUAcn4m8cY7gbZ1cio2wWYozgfpKtXD0vcGYOGUMuYXlaj2YFpbfERwalQu6gtWl1LzMpfvEyYjXmNt8RLkgF0jVeYZC5ijAsvdpbQGJxkQ2XCZ8xnIXBFYiqiiI8tsTbNhHlUOL6mPFC5ntULqAsfHEWrx6hzf1TMhdQzOHjEE5tW4UWLTwQlWuJQq6+U7hR4lSBvio5RTbOYW/AWd44fKyqgH5vV/AYiJbfgmDLRHIZaxjEM2C15/iKa3y8rLWvNcxgBguUloQmC7KbdTzDIBmEjnMFdwG3qZdWWDBmENqGQZUxQJUIog7lneeiLXTRF4xrkgCaxHKhdxuChC9Lg2E3AJk3FVXgLW/Mw5qBOUgcFTqhjgDEXWsXEVQaygjbAOm4ZkzCMDUQPiPTCxcLtLzjP3qOcM2G9rP6ghS4jzMRRoT9TX0xtGrlsg4YhxVnuPOSr6zCt0rzcvolYFJg3RRtdEZT2lOAV0QLJH4PUadA74SpVX5dEx6q7alwcqKt/2Iz3CP6ijilV+CCunDUHGI05bjVrwwaUCH3EAlGSXKn3FS8EqKMVGCCDQTNtzFOxgAsdxXZIyC8wqbIXJimGJdocQ5W5kNIIyO5c3GqrZ2wu9sFI2OYHkaiIXTFbPpEWq+YDLWIrmj6gOoMoIhtzCB58wkroeHmUc4iOLbYgVpK1jsilxF1bMDEPbFQ0A5cI6PAuL9s1xFBfeIaV4PCLkxcAtr4IJPhG37loT5rcWi+4yKKrceja6KS7BkGHe0+5ShaMvPMaYhqZ1xHXChLdssM23UyqBotMwxYzBRgVlzEs6jRgxHWHEVymTcG5jd1CjBESNkd5jqG5ewYgsGNVlRS4qKsGo6glRxlIyrZRAFXqZ8wyFzoI7zHaUzywWoP8A8g1BQh6TT6MYgRlxHyRy/GBH6cwO7rN44mWU8BYU1c/xuXVF+VXxGsgaNT53FsStqt+YV3RX3AoduQOCNIDgHXb1F5bi+DwR8Y3Chpjk7NPSEUWvC0ImoEyA2yzBh2Bs1/vEBhy778wh7S3mCYr1BcxVgzcvR4jcLI+F/wCKzWY4yy8hB73mGYvxHOGmIYvEveYjhDcFUcYlbSR0YSuaYhU/EwGcpa2VuUaWJhOIBpcxwDl7lDKZ4SKyzMEsnqmGCS5Sxs7hxADefUz9wdMqDVFoxVaqAQJSDk3/AFEFF5lg3nxDvBVeIh4fqFMoXxmWhYO7ydxTILVZ4g3drLyZjESMoGroC1jXsRwK8xiCjwWuEj6BtKNFuh89Q27Yv/nEqGxlDt7lrh6PErSsfEvEuiWpD/wSXbUQPKKFVuXcb4lkzxBVljRjv/wWnEFcGiIt3Dh4hUFailuN2f8AgLKZl2xF3HCVxcqVq4mICVQ5IspbAu54YJJRyc/+VAqbYgCSwQYXdTPhml7/AAZizDVqLOvjcUDqFgXUMuZY+EXSy5qH1jLAhXu1v4hw0ytH4mTdNhSlOIzAFFj5NRAE8ZOJdfJa1+KiCBppNgX3/sEMNoWaFeSovQdXfzU//8QAKREAAgIBBAIBAwUBAQAAAAAAAAECEQMQEiExBEFREyAiBRQwQGEjcf/aAAgBAgEBPwBlIVDoY4jiMizdZZYmWWIo7Ij7070SKL+2LLLKQ0VWjJOiTF0WWJieiEXoiuShaWiy/tWq5P8A3RpDdEpWLljVFjExMsQkULgasv0R1vRfItK0WvGrJzSN/JGibFIsTEIiJiJTSfA5P0LI26YhulZuTViafWti5f28osscqJZaM+VNcMjO4qzHJV2ZHZuoUxSIuxFiYlxybalyOiMXuESaqmNOkmY00nZZZuNyQpikmxNaWxsckiU+Dyc6fFmJN8s3pEcjfESWfLGVPgjltWyWeK4Pr2qRhtR5FKxMiRHFv2ON9iSXQhEpRXY8vpI3s3s3X2WXRGddid8rRySJSRKSZOfaMrSlyRk1HgWRt0zw53laP1KdSSJeU1wj6jySTs8eCVOyMhMixWRdI3DkKTfQpkst8IcrYk2VpX2J0bzyPIjFcdkvJ/G36JeXNT46J+ZFOm6Y5p8slmSVRI5EvR4km8zZ+pPo/CtqRDxqW6Tow+QktseWYpNrk3CkQbZaNy6NyoUlHkbvnRf4WJl6WXZ3pZLBBu3yVBeiMcUnwlZn/TsOWW5rkXj4YLYyHj4IrhIy4MbVRSMHg5HJzujP4iyR55aM+LPCSUVVkvHzXyY4SwpyXJjy5Ha9oWaaX5Il5k0uDH5mRSSfTMclOKaIyi3SZKNDdvWzcJiY/sTs2j6I5nOKaXujxYyWabZGNs8hPe0jJuWFtdn6clOf/Tsl+PBLIlFsl+oR+qsa5bNsX2ZcWNRbaMGOMZNR9lcE4QSuSR+2xydxMUKikLEomR8CG6HIvRMT++0+DHCMY0kQjTdmNcnj4JZs0rVJezzPDyYINXbuzxcKjFSa5JU0Zo/8ZHjRk/JTa4E2eVJLHbPGkpZaXdDg1E8jE8kEjxcDxppuxOuTepcoyHQ+RIrVMi7+2xJIpDoXR47/ABPJam6a6HxwJLoy492Jx+TF4Uo5U7tI20ZsKyw2Mw+KseTcuxp1yZIqapOjDFRjTdsSvgSrgm7Y3olo1rFlaLrSiWSiMt3NcGRqPQsiR4+R7XRlyJydjjfKNtMjtrkajY69DViiuxjTsjF2ba5Gm02O750X2NDQkhMYtXinLmbMbcI7V0eQpzpxfRizvqXZ40ksTY5XK2bkSdssstlyE5Iv8bHKTFJr2LI07YsrfDG7d6WWWWWcMXB7LFw9LKj8lR+TbH5MuCMuU+Tx/Kli/CfQpY5O0xOPyVC+xqPyfivZwcHA5qqODgTjfOkRKxwaEISscSq0ZYnq4STqjZL4Ppy+BYpfBLA5KmiXhTTuLH4/kL2OHkL0bs67R9bIu0fuZLtH7r/D91/gvK/w/dX6Fnk+kLJN9IhGd2yPB0xzsSvkoTpjknyOmUPRPWMa70rWitKHFP0PHB+h4YP0fQh8H7eHwPx4+j6LXsUGhqmLsfei4RYxISQ2S1XRZf2X/DRRRRJciEWbmWhOxaMloxMv+jRJci7GqEtNtjVCestFyyiv6TV9ksfwbH0xxp6L7X2MQuf6rVmR1wyinrY39iVIr+SxO/4JRTHGtdptKrVFm7+s1ZJV6N3+G5vRlCg2OLQjb/SX2M77MiSY9EQxxEkhrgUU+zaj/8QAKREAAgICAQQCAgMBAAMAAAAAAAECEQMhMQQQEkETUSAiMDJhQBQjcf/aAAgBAwEBPwCiiihNoUhSFskhRKGhooofb2M9D7t9q/ForumWuyI7Ioe2eJQ0NCGMrsz0X+FfyWJWQjQ9Ii7ZRQ0NFDGWNiaKGkuBfg2X+Nl97EYsTm9CxJRpck7ujFGyeNoSHEaGyWyjxvgh0kpK2QxxjKpEsMPjckN2Qi5Oh4pJuL5Q4tafZ9uCy+9IoojC2QwOTpHRYJRb8lonjUJtrgyxfldGGLXocbFibJQ9GSLWmMocaFJxdo+duDaexNt29mWS+NVoZhvyTFOHySkt64+zqnFyTj9cfRWhopig29jxL0ODWxxa7URiRhZDC7OkwOCuSMk60jwbJ4YrcuCGDFKNoeGpUiOH7P8Ax1F+UuDqqnNuI8bQ1Q3XJMU0lwKVcDk3yPtFSfAsXts8EeCPFLu4tobqNMuJGLZGEuCGNqrMWJ6dDyRVJjUXsjNXSM0/ODX0zE/jx/8A1kutUXSI53kmmnRnyNxpEobJIkiVElbKFEeOUVbHhklbRHFW2KNIbSL713as8EYsTW2tEVBWzDkU501r0PqvBNONNCzt7kiXU1+qQ+pUVpHTycm2zNNRxV7sUZTelZh6KSXlJ0RcW/G7oyxV6HElAmoLlDi+DxftiVPkk5T0zylVPs+zRXdrvQuoyJUqF5y22P5cdOzH1T05JOvsebNNeSWh5ct1wYpyf9+BvHhk2t/4LLDJNKapCcY0sVV7MuOcpXF2hwlg8p3f+GDK8sW2tjiuT5t/10Tyxm9olDejL0mTFFSktMX+EVSHoSs8RRHGhoX4cFiez4IwbUnwrOsmnigkOVI6Vp4k2ybfm1HmtHVTyxwJ1X2Y25q/ZjxtySrkeHHF+L5FOUXSMWSeR+LJwjCFpUSk0Y5zk6Q4xtOapmef7WjJ1U8v9nZjjbGxKyMSj/BrQ4/nTjTM2WUp3J7Msk0q+ibM2WOPBBJ7OnzrPkuGtHW9Q5ScE9IxvZgleaJ8nllkn6HR0j/9h1MqxiyXIwTjFuzqsibSRLaoUaZjerOWRjQ2X2Y0SVb7vtQ5NqjybexMbJvZibgrTIu9sTraMGXxypsk8UbmntnnZiy+DbMudTg9F7MGb45NtWmdRk+SdpUjyrbHJN2iOo0RiIbELvKNrsh91ZtujadND2ZEk9kV+qQm0hMbd6PKQpMU2hTbTTEJok9Hm2qI+KdMTXoiX2Xa+zslGhD7+cVwiKUppocYxk0/ZLElwZY/ukfHSEnQotqzwZ4s8ZI8Wb7Jv6OfQ4p+hwjyhKlRFiRRXajaGySGh8dqHLVUJyjtDlJu2RnLhrRkwp7Q/KqZ4yYnJEZSvg/Z+i5GzaFB3YkxpjTSEtEuBtJEciLsbHKhSE7KKKJLtR+taG41yfr9nnFexZa9jyxfIskBShxZcX7KXpiRRTHBsUWhr/R0vY2q0S2iKtHxJCVaHwOHlGhYpIimuTQkUSVldpTvRf52WxTkuGecvs+WX2fLIjm+0LJBrgckyPB6If1GUNURtFjTFE9dmNbK/mss8iyHAyP9Svs8TxfsqmJFFD47McdFf8NkOBvRil6JOzY5MixI0MY+zk+Dy/mr8E64I5Ps+RLaRGXlG0R3Gxxv0NURky+zGxj1sv8A5E6MT5fohKS4HkbLsSEWNjGN2y/5ErGq/gjJxMUk1soqiyzy7VY0eNM8f+aMnF2iOVtEXast90eSQ2nwNFv8V+C/hYu67Y+Pwk2fR7JFn//Z', 'base64'), +}; + +images_by_id[image_id] = { + id: image_id, + title: 'Example Image', + template_id, + text_nodes: [ + // + ], + content: render_svg({ + title: 'Example Image', + image: { + url: `${http.images.public_url}/${template_id}`, + title: images_by_id[template_id].title, + width: images_by_id[template_id].width, + height: images_by_id[template_id].height, + }, + text_style: { + fill: '#fff', + stroke: '#000', + stroke_width: 2, + font_size: 48, + font_family: 'sans-serif', + font_weight: 600, + }, + text: [ + { + text: 'Top Text', + top: 50, + left: 250, + }, + { + text: 'Bottom Text', + top: 320, + left: 250, + }, + ] + }) +}; diff --git a/src/storage/memory/store.ts b/src/storage/memory/store.ts new file mode 100644 index 0000000..1eed87c --- /dev/null +++ b/src/storage/memory/store.ts @@ -0,0 +1,8 @@ + +import { Store } from '../interface'; +import { get_image_by_id, get_image_data_by_id } from './images'; + +export const memory_store: Store = { + get_image_by_id, + get_image_data_by_id, +} diff --git a/src/storage/store.ts b/src/storage/store.ts new file mode 100644 index 0000000..ea85be9 --- /dev/null +++ b/src/storage/store.ts @@ -0,0 +1,24 @@ + +import * as conf from '../config'; +import { Store } from './interface'; +import { memory_store } from './memory/store'; + +export let store: Store; + +switch (conf.storage.mode) { + case 'memory': + store = memory_store; + break; + + case 'file': + // + // break; + + case 'sqlite': + // + // break; + + default: + console.error('Unknown storage mode configured'); + process.exit(1); +} diff --git a/src/web/request-handlers/image-request.ts b/src/web/request-handlers/image-request.ts new file mode 100644 index 0000000..a717fa9 --- /dev/null +++ b/src/web/request-handlers/image-request.ts @@ -0,0 +1,85 @@ + +import { ImageMacro, ImageTemplate } from '../../storage/interface'; +import { store } from '../../storage/store'; +import { IncomingMessage, ServerResponse } from 'http'; + +export async function handle_image_request(req: IncomingMessage, res: ServerResponse) { + if (req.url === '/' && req.method === 'GET') { + res.writeHead(200, { + 'content-type': 'text/html', + }); + res.end(` + + + +

<embed>

+ + +

<object>

+ + +

<iframe>

+ + + + `); + return; + } + + if (! req.url.startsWith('/')) { + return send_404_not_found(res); + } + + switch (req.method) { + case 'OPTIONS': return send_options_response(res); + case 'GET': return send_image_response(res, await get_image(req)); + case 'HEAD': return send_image_response(res, await get_image(req), false); + } + + return send_415_method_not_allowed(res); +} + +function send_options_response(res: ServerResponse) { + res.writeHead(200, { + 'access-control-allow-origin': '*', + 'access-control-allow-methods': 'GET, HEAD, OPTIONS', + }); + res.end(); +} + +function send_image_response(res: ServerResponse, image: ImageTemplate | ImageMacro, send_content = true) { + if (! image) { + return send_404_not_found(res); + } + + const buf = image.media_type === 'image/svg+xml' + ? Buffer.from(image.content, 'utf8') + : image.content; + + res.writeHead(200, { + 'content-type': image.media_type, + 'content-length': buf.byteLength, + 'cache-control': 'public, max-age=31536000', + }); + + res.end(send_content ? buf : void 0); +} + +function send_404_not_found(res: ServerResponse) { + res.writeHead(404, { + 'content-type': 'text/plain' + }); + res.end('Image not found'); +} + +function send_415_method_not_allowed(res: ServerResponse) { + res.writeHead(415, { + 'content-type': 'text/plain' + }); + res.end('Method not allowed'); +} + +function get_image(req: IncomingMessage) { + const image_id = req.url.slice(1); + return store.get_image_by_id(image_id); +} diff --git a/src/web/server.ts b/src/web/server.ts new file mode 100644 index 0000000..80d5b77 --- /dev/null +++ b/src/web/server.ts @@ -0,0 +1,12 @@ + +import * as conf from '../config'; +import { createServer } from 'http'; +import { handle_image_request } from './request-handlers/image-request'; + +export function init_http_servers() { + const image_server = createServer(handle_image_request); + + image_server.listen(conf.http.images.port, conf.http.images.address, () => { + console.log('HTTP image server listening at %s:%d', conf.http.images.address, conf.http.images.port); + }); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..16c5890 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "outDir": "./build", + "rootDir": "./src" + }, + "include": [ + "./src/**/*.ts" + ] +} \ No newline at end of file