diff --git a/simple/writing/build.sh b/simple/writing/build.sh new file mode 100755 index 0000000..66dc266 --- /dev/null +++ b/simple/writing/build.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -e + +cc -O3 -Wall src/writing.c -o writing + +echo "build success" \ No newline at end of file diff --git a/simple/writing/src/letters.inc.c b/simple/writing/src/letters.inc.c new file mode 100644 index 0000000..cb785f5 --- /dev/null +++ b/simple/writing/src/letters.inc.c @@ -0,0 +1,239 @@ + + +#define MSPACE "m 60 0" +#define MDOTSP "m 20 0" +#define MUP "m 10 -25 " +#define MUP3 "m 30 -75 " +#define MDN "m -10 25 " +#define MUPE "m 4 -10 " +#define MDNE "m -4 10 " + +#define Co1 "q 6 -15 -4 -15 " +#define Co2 "q -10 0 -16 15 " +#define Co3 "q -6 15 4 15 " +#define Co4 "q 10 0 16 -15 " + +#define CO1 "q 10 -25 -5 -25 " +#define CO2 "q -15 0 -25 25 " +#define CO3 "q -10 25 5 25 " +#define CO4 "q 15 0 25 -25 " + +#define CO1B "q 15 0 5 25 " +#define CO2B "q 10 -25 25 -25 " +#define CO3B "q -15 0 -5 -25 " +#define CO4B "q -10 25 -25 25 " + +#define CFO1 CO1 CO2 CO3 CO4 +#define CFO2 CO2 CO3 CO4 CO1 +#define CFO3 CO3 CO4 CO1 CO2 +#define CFO4 CO4 CO1 CO2 CO3 + +#define CLU "q 50 -50 30 0 " +#define CLD "q -20 50 30 0 " +#define CLLD "q -10 25 0 25 q 15 0 40 -50 " + +#define CKD1 "c 14 -35 44 -35 38 -20 " +#define CKD2 "q -18 45 32 -5 " + +#define CJL0 "q -10 25 0 25 " +#define CJL CJL0 "q 15 0 50 -50 " +#define CJR CJL0 "c 15 0 40 -25 44 -35 " Co1 Co2 Co3 Co4 "l 6 -15 " + +#define CGD0 CLD CDN "q -10 25 -20 25 " +#define CGD CGD0 "c -20 0 20 -25 45 -50 " +#define CGDE CGD0 "c -20 0 19 -25 39 -35 " + +#define CQU1 "c 25 -25 51 -40 61 -65 " +#define CQU2 "q 4 -10 -6 -10 q -10 0 -20 25 l -20 50 " +#define CQU CQU1 CQU2 + +#define CQD1 CDN2 +#define CQD2 "q -10 25 -20 25 q -10 0 -6 -10 " +#define CQD3 "c 10 -25 36 -40 61 -65 " +#define CQD3E "c 10 -25 35 -40 55 -50 " +#define CQD CQD1 CQD2 CQD3 + +#define CC1 "q 5 0 8 5 " +#define CC2 "q -3 -5 -8 -5 " CO2 CO3 +#define CC CC1 CC2 +#define CXL CO1B CO4B "q -5 0 -8 -5 q 3 5 8 5 " + +#define Cb0 "l -4 10 " Co3 Co4 Co1 +#define Cb Cb0 Co2 Co3 + +#define CUP "l 10 -25 " +#define CDN "l -10 25 " +#define CUP2 "l 20 -50 " +#define CDN2 "l -20 50 " + +#define CEE "m -18 -65 l 2 -2 m 10 0 l -2 2 m 8 65 " +#define CYY "m -28 -65 q 3 5 17 -5 m 11 70 " + +#define CDot "m 2 0 q 0 2 -2 2 q -2 0 -2 -2 q 0 -2 2 -2 q 2 0 2 2 " +#define CComma CDot "q -1 5 -8 10 m 8 -10 " + + +#define C_O1 "q 20 -50 0 -50 " +#define C_O2 "q -20 0 -40 50 " +#define C_O3 "q -20 50 0 50 " +#define C_O4 "q 20 0 40 -50 " + +#define C_O1B "q 20 0 0 50 " +#define C_O2B "q 20 -50 40 -50 " +#define C_O3B "q -20 0 0 -50 " +#define C_O4B "q -20 50 -40 50 " + +#define C_XL1 "m 36 -90 q 10 -10 18 -10 " +#define C_XL2 C_O1B C_O4B +#define C_XL3 "q -7.5 0 -14 -10 q 3.5 10 14 10 " +#define C_XL C_XL1 C_XL2 C_XL3 + +#define C_XR1 "q 7.5 0 14 10 " +#define C_XR2 "q -3.5 -10 -14 -10 " +#define C_XR3 C_O2 C_O3 +#define C_XR C_XR1 C_XR2 C_XR3 + +#define C_AL0 "m 4 -10 q 1 10 11 10 " +#define C_AL C_AL0 "q 30 0 95 -100 " + +#define C_LU CLU +#define C_LD CLD +#define C_LLD "q -10 25 0 25 q 15 0 70 -100 " + +#define C_J1 "m 64 -85 l -24 60 " +#define C_J2 "l -4 10 q -6 15 -21 15 q -5 0 -11 -10 m 11 10 " +#define C_JJ2 "l -4 10 q -6 15 -21 15 q -5 0 -11 -10 q 1 10 11 10 " +#define C_J C_J1 C_J2 + +#define C_T "m 28 -70 c -10 -10 -8 -30 12 -30 " + +#define C_E "m 69 -90 " C_XR2 "q -15 0 -23 20 q -3 25 15 25 q -25 0 -35 25 q -12 30 13 30 " + +#define C_3 C_XL1 "q 15 0 7 20 q -10 25 -25 25 q 15 0 5 25 q -12 30 -32 30 " C_XL3 + +#define C_B1 "m 60 -100 " CDN CDN2 CQD2 "q 56 -90 71 -90 " +#define C_B2 "q 15 0 7 20 q -10 25 -30 25 " +#define C_B3 "q 25 0 15 25 q -12 30 -32 30 q -5 0 -8 -5 q 3 5 8 5 " +#define C_B C_B1 C_B2 C_B3 + +#define C_EE "m 31 -115 l 2 -2 m 10 0 l -2 2 m -41 115 " +#define C_YY "m -26.5 -15 q 3 5 17 -5 m 9.5 20 " + + +#define LLBO MUP "q 25 -25 35 -25 " + + +#define LBL { MUP CLU } +#define LBO { "m 35 -50 ", LLBO } +#define LBA { "m 40 -25 ", LLBO CO1B } +#define LBQ { MUP CQU } +#define LBE { MUPE "c 46 -23 56 -40 41 -40 " } +#define LBN { "m 20 -50 " CDN, MUP "l 25 -25 " CDN } +#define LBX { "m 18 -45 q 7 -5 12 -5 ", LLBO } +#define LBC { "m 38 -45 ", LLBO CC1 } +#define LBJ { MUP CJL } +#define LBr { MUP "l 25 -25 q 5 25 30 0 " } +#define LBR { MUP CJR } + +#define LEL { CLD MDN, NULL, "c -14 35 4 25 24 15 " MDNE } +#define LELY { CLD MDN CYY, NULL, "c -14 35 4 25 24 15 " MDNE CYY } +#define LEO { "m 15 0 ", "q 15 0 40 -25 " MDN, "q 19 0 39 -10 " MDNE } +#define LEE { "q 19 0 39 -10 " MDNE, "q 15 0 40 -25 " MDN } +#define LEEE { "q 19 0 39 -10 " MDNE CEE, "q 15 0 40 -25 " MDN CEE } +#define LEQ { CQD2 CQD3 MDN, NULL, CQD2 CQD3E MDNE } +#define LEX { "q 15 0 40 -25 " MDN, NULL, "q 19 0 39 -10 " MDNE } +#define LEK { CKD2 MDN, NULL, "c -12 30 2 20 22 10 " MDNE } +#define LEG { CGD MDN, NULL, CGDE MDNE } + + +#define L_EO1 { "m 30 0 ", "q 15 0 40 -25 " MDN, "q 19 0 39 -10 " MDNE } +#define L_EO2 { "m 60 0 ", "q 15 0 40 -25 " MDN, "q 19 0 39 -10 " MDNE } +#define L_EO3 { "m 75 0 ", "q 15 0 40 -25 " MDN, "q 19 0 39 -10 " MDNE } + +#define L_EA { "q 20 -50 -10 -50 c -20 0 -16 40 14 40 q 21 0 36 -15 " MDN, NULL, \ + "q 20 -50 -10 -50 c -20 0 -18 45 12 45 q 22 0 32 -5 " MDNE } + + +Letter letters[] = { + { "а", LBA, CFO1 CUP CDN, LEL }, + { "б", LBA, CFO1 CUP2 "q 10 -25 25 -25 l 15 0 m -85 100", LEO }, + { "в", LBQ, CFO3 CO3, LEO }, + { "г", LBL, "", LEL }, + { "д", LBA, CFO1 CUP CDN CQD1, LEQ }, + { "е", LBE, CO2 CO3, LEE, 1 }, + { "ё", LBE, CO2 CO3, LEEE, 1 }, + { "ж", LBX, CXL "q 15 0 55 -50 " CDN2 "q 40 -50 55 -50 " CC, LEX }, + { "з", LBX, CO1B "q -3 7.5 -19 15 q 18 -7.5 1 35 ", LEQ }, + { "и", LBN, CLLD CDN, LEL }, + { "й", LBN, CLLD CDN, LELY }, + { "к", LBN, CDN "q 48 -70 46 -40 m -46 40 " CKD1, LEK }, + { "л", LBJ, CDN, LEL }, + { "м", LBJ, CDN CLLD CDN, LEL }, + { "н", LBN, CDN CUP "q 30 0 40 -25" CDN, LEL }, + { "о", LBO, CFO2 CO2 CO3, LEO }, + { "п", LBN, CDN CUP CLU, LEL }, + { "р", LBN, "l -30 75 l 30 -75 " CLU, LEL }, + { "с", LBC, CC2, LEX }, + { "т", LBN, CDN CUP CLU CDN CUP CLU, LEL }, + { "у", LBN, CLLD CDN CQD1, LEQ }, + { "ф", LBA, CFO1 CUP CDN CFO3 "l -30 75 m 35 -50 ", LEO }, + { "х", LBX, CXL CO4 CO2B CC, LEX }, + { "ц", LBN, CLLD CDN, LEG }, + { "ч", LBr, CDN, LEL }, + { "ш", LBN, CLLD CDN CLLD CDN, LEL }, + { "щ", LBN, CLLD CDN CLLD CDN, LEG }, + { "ъ", LBr, CDN Cb, LEO }, + { "ы", LBN, Cb0 "q 20 0 28 -20 " CDN, LEL }, + { "ь", LBN, Cb, LEO }, + { "э", LBX, CXL "m 10 -25 l 15 0 m -25 25 ", LEO }, + { "ю", LBN, CDN CUP "c 30 0 40 -25 55 -25 " CFO2 CO2 CO3, LEO }, + { "я", LBR, CDN, LEL }, + + + //{ "А", {""}, C_AL "m -64 60 l 50 0 m 14 -60 " CDN2 CDN, LEL, 3 }, + { "А", {""}, C_AL CDN2 CDN2, L_EA, 3 }, + { "Б", {""}, "m 53 -85 l -24 60 " CQD2 "q 33 -45 48 -45 " C_B3 "m -30 0 " C_T "l 50 0 m -60 100 ", L_EO2, 3 }, + { "В", {""}, C_B, L_EO1, 3 }, + { "Г", {""}, C_J "m -10 0 " C_T "l 60 0 m -90 100 ", L_EO3, 3 }, + { "Д", {""}, C_J1 CO4B "c -15 0 -9 -15 1 -15 c 10 0 14 15 24 15 " C_O4 "q 20 -50 -15 -50 q -25 0 -37 30 m 12 70 ", L_EO1, 3 }, + { "Е", {""}, C_E, LEX, 3 }, + { "Ё", {""}, C_E C_EE, LEX, 3 }, + { "Ж", {""}, C_XL "q 30 0 85 -100 " CDN2 CDN2 "q 55 -100 85 -100 " C_XR, LEX, 3 }, + { "З", {""}, C_3, L_EO1, 3 }, + { "И", {""}, MUP3 C_LU CDN2 C_LLD CDN2 CDN, LEL, 3 }, + { "Й", {""}, MUP3 C_LU CDN2 C_LLD C_YY CDN2 CDN, LEL, 3 }, + { "К", {""}, "m 40 -75 l 25 -25 " CDN CDN2 CQD2 "c 4 -10 76 -90 86 -90 q 2.5 0 1 10 m -56 55 " CO2B CO1B "l -4 10 ", LEL, 3 }, + { "Л", {""}, C_AL CDN2 CDN, LEL, 3 }, + { "М", {""}, C_AL CDN2 CDN C_LLD CDN2 CDN, LEL, 3 }, + { "Н", {""}, "m 40 -75 l 25 -25 " CDN CDN2 CQD2 "c 8 -20 96 -60 102 -80 " CQU2, LEL, 3 }, + { "О", {""}, "m 20 0 " C_O4 C_O1 C_O2 C_O3, L_EO1, 3 }, + { "П", {""}, C_J "m -5 0 " C_T "l 60 0 m -90 100 " C_J1, LEL, 3 }, + { "Р", {""}, C_J "m 18 -70 c -5 -5 7 -30 32 -30 c 35 0 25 30 15 35 m -65 65 ", L_EO2, 3 }, + { "С", {""}, "m 74 -90 " C_XR2 C_XR3, LEX, 3 }, + { "Т", {""}, C_J "m -5 0 " C_T "l 80 0 m -110 100 " C_J1 CDN "m -10 0 " C_J1, LEL, 3 }, + { "У", {""}, MUP3 C_LU CLLD CDN2 CDN C_JJ2, LEO, 3 }, + { "Ф", {""}, "m 60 -75 " CFO1 CUP CDN CFO3 CDN2 C_J2, L_EO2, 3 }, + { "Х", {""}, C_XL C_O4 C_O2B C_XR, LEX, 3 }, + { "Ц", {""}, MUP3 C_LU CDN2 C_LLD CDN2 CDN, LEG, 3 }, + { "Ч", {""}, MUP3 C_LU CLLD CDN2 CDN, LEL, 3 }, + { "Ш", {""}, MUP3 C_LU CDN2 C_LLD CDN2 CDN C_LLD CDN2 CDN, LEL, 3 }, + { "Щ", {""}, MUP3 C_LU CDN2 C_LLD CDN2 CDN C_LLD CDN2 CDN, LEG, 3 }, + + { "Э", {""}, C_XL "m 20 -50 l 20 0 m -40 50 ", L_EO1, 3 }, + { "Ю", {""}, "m 40 -75 l 25 -25 " CDN CDN2 CQD2 "c 8 -20 46 -35 56 -40 " C_O3 C_O4 C_O1 C_O2 C_O3, L_EO1, 3 }, + { "Я", {""}, C_AL0 "c 15 0 55 -50 65 -75 " CFO1 CUP CDN2 CDN, LEL, 3 }, + + { "-", {""}, MDOTSP "m 10 -25 l 40 0 m -10 25 " MDOTSP, {""}, 2 }, + { ".", {""}, MDOTSP CDot MDOTSP, {""}, 2 }, + { ",", {""}, MDOTSP CComma MDOTSP, {""}, 2 }, + { "\"",{""}, "m 40 -100 " CComma "m 10 0 " CComma "m -40 100 " MDOTSP, {""}, 2 }, + { "»", {}, NULL, {}, 0, "\"" }, + { "“", {}, NULL, {}, 0, "\"" }, + { "«", {""}, MDOTSP CComma "m 10 0 " CComma MDOTSP, {""}, 2 }, + { "„", {}, NULL, {}, 0, "«" }, + + { "!", {""}, MDOTSP "m 40 -100 l -34 85 m -6 15 " CDot MDOTSP, {""}, 2 }, + { "?", {""}, MDOTSP "m 15 -75 " CO2B CO1B "c -10 25 -29 35 -39 60 m -6 15 " CDot MDOTSP, {""}, 2 }, +}; + + diff --git a/simple/writing/src/svg-save.inc.c b/simple/writing/src/svg-save.inc.c new file mode 100644 index 0000000..3838bd2 --- /dev/null +++ b/simple/writing/src/svg-save.inc.c @@ -0,0 +1,32 @@ + + +FILE* svgBegin(const char *filename, double widthMM, double heightMM, double unitsPerMM) { + FILE *f = fopen(filename, "w"); + if (f) { + fprintf(f, "\n", + widthMM, heightMM, widthMM*unitsPerMM, heightMM*unitsPerMM ); + } + return f; +} + + +void svgEnd(FILE *f) { + if (!f) return; + fprintf(f, "\n"); + fclose(f); +} + + +void svgAddText(FILE *f, double x, double y, double size, const char *text) { + if (!f) return; + fprintf(f, "%s\n", x, y, size, text); +} + + +void svgAddPath(FILE *f, const char *path, double width, int round) { + if (!f) return; + fprintf(f, "\n", + (round ? "stroke-linecap:round;stroke-linejoin:round;" : ""), + width, path ); +} diff --git a/simple/writing/src/svg-track.inc.c b/simple/writing/src/svg-track.inc.c new file mode 100644 index 0000000..36f1c09 --- /dev/null +++ b/simple/writing/src/svg-track.inc.c @@ -0,0 +1,64 @@ + + +void spSkipSpace(const char **pos) + { while(**pos && isspace(**pos)) ++*pos; } + + +double spReadNum(const char **pos) { + spSkipSpace(pos); + char *end = ""; + double x = strtod(*pos, &end); + *pos = end; + return x; +} + + +void spTrack(const char *path, double *x, double *y) { + double sx = *x, sy = *y; + const char *c = path; + spSkipSpace(&c); + while(*c) { + switch(*c++) { + case 'M': + sx = (*x = spReadNum(&c)); + sy = (*y = spReadNum(&c)); + break; + case 'm': + sx = (*x += spReadNum(&c)); + sy = (*y += spReadNum(&c)); + break; + + case 'C': + spReadNum(&c); + spReadNum(&c); + case 'Q': + spReadNum(&c); + spReadNum(&c); + case 'L': + *x = spReadNum(&c); + *y = spReadNum(&c); + break; + + case 'c': + spReadNum(&c); + spReadNum(&c); + case 'q': + spReadNum(&c); + spReadNum(&c); + case 'l': + *x += spReadNum(&c); + *y += spReadNum(&c); + break; + + case 'Z': + case 'z': + *x = sx; + *y = sy; + break; + default: + printf("unknown path command \'%c\'\n", *(c - 1)); + } + spSkipSpace(&c); + } +} + diff --git a/simple/writing/src/writing.c b/simple/writing/src/writing.c new file mode 100644 index 0000000..70a7063 --- /dev/null +++ b/simple/writing/src/writing.c @@ -0,0 +1,177 @@ + + +#include +#include +#include + +#include "svg-track.inc.c" +#include "svg-save.inc.c" + + +typedef struct { + const char *base; + const char *link0; + const char *link1; +} Link; + +typedef struct { + const char *name; + Link begin; + const char *mid; + Link end; + int linkMode; + const char *alias; +} Letter; + + +#include "letters.inc.c" + + +char path[1024*1024]; +double pathX0 = 60, pathRowStep = 200, pathWidth = 5; +char *pathEnd; +double pathX, pathY; +FILE *pathFile; +int pathMode; + + +void putPath(const char *str) { + if (!str || !*str) return; + if (!*path) { + char buf[256] = {}; + sprintf(buf, "M %g %g ", pathX, pathY); + char *c = buf; + while(*c) *pathEnd++ = *c++; + } + spTrack(str, &pathX, &pathY); + while(*str) *pathEnd++ = *str++; + *pathEnd = 0; +} + + +void splitPath(int mode) { + if (mode >= 2) { + if (pathMode >= 2) { + if (*path) svgAddPath(pathFile, path, pathWidth, 1); + *(pathEnd = path) = 0; + } + } else + if (mode >= 1) { + pathX = pathX0; + pathY += pathRowStep; + if (pathMode >= 1) { + if (*path) svgAddPath(pathFile, path, pathWidth, 1); + *(pathEnd = path) = 0; + } else + if (*path) { + char buf[256] = {}; + sprintf(buf, "M %g %g ", pathX, pathY); + putPath(buf); + } + } else { + if (*path) svgAddPath(pathFile, path, pathWidth, 1); + //*(pathEnd = path) = 0; + } +} + + +void textToPath(const char *text) { + pathEnd = path; + *pathEnd = 0; + + int cnt = sizeof(letters)/sizeof(*letters); + Letter *prev = NULL; + while(*text) { + Letter *curr = NULL; + do { + for(int i = 0; i < cnt; ++i) { + Letter *l = &letters[i]; + const char *t = curr ? curr->alias : text; + const char *ln = l->name; + while(*t && *ln && *t == *ln) ++t, ++ln; + if (!*ln) { + if (!curr) text = t; + curr = l; + break; + } + } + } while(curr && curr->alias); + + + if (prev) { + const char *link = curr && curr->linkMode == 1 ? prev->end.link1 : prev->end.link0; + putPath(curr && curr->linkMode != 2 && curr->linkMode != 3 && link ? link : prev->end.base); + } + + splitPath(2); + + if (curr) { + const char *link = curr->linkMode == 1 ? curr->begin.link1 : curr->begin.link0; + putPath(prev && prev->linkMode != 2 && link ? link : curr->begin.base); + putPath(curr->mid); + } else + if (*text == '\n') { + splitPath(1); + ++text; + } else { + putPath(MSPACE); + ++text; + } + + prev = curr; + } + if (prev) putPath(prev->end.base); + splitPath(0); +} + + +int textFileToSVG(const char *textfile, const char *svgfile) { + FILE *sf = fopen(textfile, "r"); + if (!sf) { + fprintf(stderr, "cannot open file '%s' for read\n", textfile); + return 1; + } + char buf[1024*1024] = {}; + char *str = buf, *end = buf + sizeof(buf) - 1; + while(1) { + int c = fgetc(sf); + if (c <= 0) break; + *str++ = c; + if (str == end) { + fprintf(stderr, "input file '%s' too large\n", textfile); + return 1; + } + } + fclose(sf); + + pathX = pathX0; + pathY = pathRowStep; + pathEnd = path; + pathFile = svgBegin(svgfile, 300, 300, 10); + if (!pathFile) { + fprintf(stderr, "cannot open file '%s' for write\n", svgfile); + return 1; + } + + pathMode = 2; + textToPath(buf); + svgEnd(pathFile); + + pathX = pathX0; + pathY = pathRowStep; + pathEnd = path; + pathFile = NULL; + + return 0; +} + + +int main(int argc, char **argv) { + if (argc != 3) { + printf("usage: writing \n"); + return 1; + } + + return textFileToSVG(argv[1], argv[2]); +} + diff --git a/simple/writing/test/test-input.txt b/simple/writing/test/test-input.txt new file mode 100644 index 0000000..b6c9055 --- /dev/null +++ b/simple/writing/test/test-input.txt @@ -0,0 +1,20 @@ +х х х х х х х х х х х х +ж ж ж ж ж ж ж ж ж ж ж ж +Х Х Х Х Х Х Х Х Х Х Х Х +Ж Ж Ж Ж Ж Ж Ж Ж Ж Ж Ж Ж +й й й й й й й й й й й й +Й Й Й Й Й Й Й Й Й Й Й Й +ч ч ч ч ч ч ч ч ч ч ч ч +Ч Ч Ч Ч Ч Ч Ч Ч Ч Ч Ч Ч +ц ц ц ц ц ц ц ц ц ц ц ц +Ц Ц Ц Ц Ц Ц Ц Ц Ц Ц Ц Ц +щ щ щ щ щ щ щ щ щ щ щ щ +Щ Щ Щ Щ Щ Щ Щ Щ Щ Щ Щ Щ +ф ф ф ф ф ф ф ф ф ф ф ф +Ф Ф Ф Ф Ф Ф Ф Ф Ф Ф Ф Ф +э э э э э э э э э э э э +Э Э Э Э Э Э Э Э Э Э Э Э +ю ю ю ю ю ю ю ю ю ю ю ю +Ю Ю Ю Ю Ю Ю Ю Ю Ю Ю Ю Ю +ь ь ь ь ь ь ь ь ь ь ь ь +ъ ъ ъ ъ ъ ъ ъ ъ ъ ъ ъ ъ diff --git a/simple/writing/test/test-output.svg b/simple/writing/test/test-output.svg new file mode 100644 index 0000000..d847dd9 --- /dev/null +++ b/simple/writing/test/test-output.svg @@ -0,0 +1,922 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +