Add beginnings of sketch theme [#18]

This commit is contained in:
David Evans 2018-01-05 23:19:54 +00:00
parent dc6a741ea4
commit eaf4a956d4
23 changed files with 2672 additions and 390 deletions

View File

@ -146,6 +146,21 @@ state over Foo: 'Newlines here,
too!'
```
### Themes
<img src="screenshots/Themes.png" alt="Themes preview" width="200" align="right" />
```
theme sketch
title Mockup
A thing -> +Another thing: Something happens
-Another thing --> A thing: With some response
note right of Another thing: Find out what happens here
end Another thing
```
### Short-Lived Agents
<img src="screenshots/ShortLivedAgents.png" alt="Short Lived Agents preview" width="200" align="right" />

View File

@ -5,7 +5,11 @@
base-uri 'self';
default-src 'none';
script-src 'self' https://cdnjs.cloudflare.com https://unpkg.com;
style-src 'self' https://cdnjs.cloudflare.com;
style-src 'self'
https://cdnjs.cloudflare.com
'sha256-ru2GY2rXeOf7PQX5LzK3ckNo21FCDUoRc2f3i0QcD1g='
;
font-src 'self' data:;
img-src 'self' blob:;
form-action 'none';
">

View File

@ -5,7 +5,11 @@
base-uri 'self';
default-src 'none';
script-src 'self' https://cdnjs.cloudflare.com https://unpkg.com;
style-src 'self' https://cdnjs.cloudflare.com;
style-src 'self'
https://cdnjs.cloudflare.com
'sha256-ru2GY2rXeOf7PQX5LzK3ckNo21FCDUoRc2f3i0QcD1g='
;
font-src 'self' data:;
img-src 'self' blob:;
form-action 'none';
">

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -5,8 +5,12 @@
base-uri 'self';
default-src 'none';
script-src 'self' 'unsafe-inline' cdnjs.cloudflare.com;
style-src 'self' cdnjs.cloudflare.com https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
style-src 'self'
https://cdnjs.cloudflare.com
https://fonts.googleapis.com
'sha256-ru2GY2rXeOf7PQX5LzK3ckNo21FCDUoRc2f3i0QcD1g='
;
font-src 'self' data: https://fonts.gstatic.com;
img-src 'self';
form-action 'none';
">
@ -244,6 +248,26 @@ state over Foo: 'Newlines here,
too!'
</pre>
<h3 id="Themes">Themes</h3>
<pre class="example" data-lang="sequence">
theme sketch
title Mockup
A thing -> +Another thing: Something happens
-Another thing --> A thing: With some response
note right of Another thing: Find out what happens here
end Another thing
</pre>
<h4>Available themes</h4>
<ul>
<li>basic</li>
<li>chunky</li>
<li>sketch</li>
<li>sketch left handed</li>
</ul>
<h3 id="ShortLivedAgents">Short-Lived Agents</h3>
<pre class="example" data-lang="sequence">
title "Baz doesn't live long"
@ -522,8 +546,14 @@ and
<a href="https://github.com/bramp/js-sequence-diagrams" target="_blank">js-sequence-diagrams</a>
for inspiring the syntax of this project.
</p>
<p>
Also thanks to
<a href="https://fonts.google.com/specimen/Handlee" target="_blank">the Handlee font</a>
which is used in the sketch theme.
</p>
<pre class="sequence-diagram fancy">
<pre class="sequence-diagram">
theme sketch
begin User, SequenceDiagram as SD, Parser, Generator, Renderer
User -> +SD: code

View File

@ -5,7 +5,10 @@
base-uri 'self';
default-src 'none';
script-src 'self' https://cdnjs.cloudflare.com;
style-src 'self';
style-src 'self'
'sha256-ru2GY2rXeOf7PQX5LzK3ckNo21FCDUoRc2f3i0QcD1g='
;
font-src 'self' data:;
connect-src 'self';
img-src 'self' blob:;
form-action 'none';

BIN
screenshots/Themes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -204,6 +204,11 @@
code: 'theme chunky',
preview: 'headers box\ntheme chunky\nA -> B',
},
{
title: 'Sketch theme',
code: 'theme sketch',
preview: 'headers box\ntheme sketch\nA -> B',
},
{
title: 'Cross terminators',
code: 'terminators cross',

View File

@ -240,25 +240,13 @@ define([
return;
}
const r = agentInfo.currentRad;
if(r > 0) {
this.agentLines.appendChild(svg.make('rect', Object.assign({
'x': agentInfo.x - r,
'y': agentInfo.latestYStart,
'width': r * 2,
'height': toY - agentInfo.latestYStart,
'class': 'agent-' + agentInfo.index + '-line',
}, this.theme.agentLineAttrs)));
} else {
this.agentLines.appendChild(svg.make('line', Object.assign({
'x1': agentInfo.x,
'y1': agentInfo.latestYStart,
'x2': agentInfo.x,
'y2': toY,
'class': 'agent-' + agentInfo.index + '-line',
}, this.theme.agentLineAttrs)));
}
this.theme.drawAgentLine(this.agentLines, {
x: agentInfo.x,
y0: agentInfo.latestYStart,
y1: toY,
width: agentInfo.currentRad * 2,
className: 'agent-' + agentInfo.index + '-line',
});
}
addHighlightObject(line, o) {
@ -475,6 +463,9 @@ define([
this.theme = this.themes.get('');
}
this.theme.reset();
this.theme.addDefs(this.addDef);
this.title.set({
attrs: this.theme.titleAttrs,
text: sequence.meta.title,

View File

@ -7,6 +7,7 @@ define([
'./Exporter',
'./themes/Basic',
'./themes/Chunky',
'./themes/Sketch',
], (
EventObject,
Parser,
@ -14,7 +15,8 @@ define([
Renderer,
Exporter,
BasicTheme,
ChunkyTheme
ChunkyTheme,
SketchTheme
) => {
/* jshint +W072 */
'use strict';
@ -22,6 +24,8 @@ define([
const themes = [
new BasicTheme(),
new ChunkyTheme(),
new SketchTheme(SketchTheme.RIGHT),
new SketchTheme(SketchTheme.LEFT),
];
const SharedParser = new Parser();

View File

@ -41,7 +41,7 @@ defineDescribe('SequenceDiagram', [
});
it('provides default themes', () => {
expect(SequenceDiagram.themes.length).toEqual(2);
expect(SequenceDiagram.themes.length).toBeGreaterThan(1);
});
it('renders empty diagrams without error', () => {

View File

@ -45,6 +45,7 @@ define([
y,
padding: config.padding,
boxAttrs: config.boxAttrs,
boxRenderer: config.boxRenderer,
labelAttrs: config.labelAttrs,
boxLayer: env.shapeLayer,
labelLayer: clickable,
@ -85,14 +86,7 @@ define([
const config = env.theme.agentCap.cross;
const d = config.size / 2;
env.shapeLayer.appendChild(svg.make('path', Object.assign({
'd': (
'M ' + (x - d) + ' ' + y +
' L ' + (x + d) + ' ' + (y + d * 2) +
' M ' + (x + d) + ' ' + y +
' L ' + (x - d) + ' ' + (y + d * 2)
),
}, config.attrs)));
env.shapeLayer.appendChild(config.render({x, y: y + d, radius: d}));
env.makeRegion().appendChild(svg.make('rect', {
'x': x - d,
@ -128,7 +122,7 @@ define([
topShift(agentInfo, env) {
const config = env.theme.agentCap.bar;
return config.attrs.height / 2;
return config.height / 2;
}
render(y, {x, label}, env) {
@ -139,25 +133,27 @@ define([
configB.padding.left +
configB.padding.right
);
const height = config.height;
env.shapeLayer.appendChild(svg.make('rect', Object.assign({
'x': x - width / 2,
'y': y,
'width': width,
}, config.attrs)));
env.shapeLayer.appendChild(config.render({
x: x - width / 2,
y,
width,
height,
}));
env.makeRegion().appendChild(svg.make('rect', {
'x': x - width / 2,
'y': y,
'width': width,
'height': config.attrs.height,
'height': height,
'fill': 'transparent',
}));
return {
lineTop: 0,
lineBottom: config.attrs.height,
height: config.attrs.height,
lineBottom: height,
height: height,
};
}
}

View File

@ -32,7 +32,7 @@ define(['./BaseComponent', 'svg/SVGUtilities'], (BaseComponent, svg) => {
mode,
label,
}, env) {
const config = env.theme.note[mode];
const config = env.theme.getNote(mode);
const clickable = env.makeRegion();
@ -106,7 +106,7 @@ define(['./BaseComponent', 'svg/SVGUtilities'], (BaseComponent, svg) => {
class NoteOver extends NoteComponent {
separation({agentNames, mode, label}, env) {
const config = env.theme.note[mode];
const config = env.theme.getNote(mode);
const width = (
env.textSizer.measure(config.labelAttrs, label).width +
config.padding.left +
@ -133,7 +133,7 @@ define(['./BaseComponent', 'svg/SVGUtilities'], (BaseComponent, svg) => {
}
render({agentNames, mode, label}, env) {
const config = env.theme.note[mode];
const config = env.theme.getNote(mode);
const {left, right} = findExtremes(env.agentInfos, agentNames);
const infoL = env.agentInfos.get(left);
@ -165,7 +165,7 @@ define(['./BaseComponent', 'svg/SVGUtilities'], (BaseComponent, svg) => {
}
separation({agentNames, mode, label}, env) {
const config = env.theme.note[mode];
const config = env.theme.getNote(mode);
const {left, right} = findExtremes(env.agentInfos, agentNames);
const width = (
env.textSizer.measure(config.labelAttrs, label).width +
@ -191,7 +191,7 @@ define(['./BaseComponent', 'svg/SVGUtilities'], (BaseComponent, svg) => {
}
render({agentNames, mode, label}, env) {
const config = env.theme.note[mode];
const config = env.theme.getNote(mode);
const {left, right} = findExtremes(env.agentInfos, agentNames);
if(this.isRight) {
const info = env.agentInfos.get(right);
@ -217,7 +217,7 @@ define(['./BaseComponent', 'svg/SVGUtilities'], (BaseComponent, svg) => {
class NoteBetween extends NoteComponent {
separation({agentNames, mode, label}, env) {
const config = env.theme.note[mode];
const config = env.theme.getNote(mode);
const {left, right} = findExtremes(env.agentInfos, agentNames);
const infoL = env.agentInfos.get(left);
const infoR = env.agentInfos.get(right);

View File

@ -1,6 +1,15 @@
define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
define([
'core/ArrayUtilities',
'svg/SVGUtilities',
'svg/SVGShapes',
], (
array,
svg,
SVGShapes
) => {
'use strict';
const FONT = 'sans-serif';
const LINE_HEIGHT = 1.3;
const SETTINGS = {
@ -26,7 +35,7 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
'stroke-width': 1,
},
labelAttrs: {
'font-family': 'sans-serif',
'font-family': FONT,
'font-size': 12,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
@ -34,18 +43,32 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
},
cross: {
size: 20,
attrs: {
render: ({x, y, radius}) => {
return svg.make('path', {
'd': (
'M' + (x - radius) + ' ' + (y - radius) +
'l' + (radius * 2) + ' ' + (radius * 2) +
'm0 ' + (-radius * 2) +
'l' + (-radius * 2) + ' ' + (radius * 2)
),
'fill': 'none',
'stroke': '#000000',
'stroke-width': 1,
});
},
},
bar: {
attrs: {
height: 4,
render: ({x, y, width, height}) => {
return svg.make('rect', {
'x': x,
'y': y,
'width': width,
'height': height,
'fill': '#000000',
'stroke': '#000000',
'stroke-width': 1,
'height': 4,
});
},
},
fade: {
@ -106,13 +129,13 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
padding: 6,
margin: {top: 2, bottom: 1},
attrs: {
'font-family': 'sans-serif',
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
},
loopbackAttrs: {
'font-family': 'sans-serif',
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
},
@ -172,7 +195,7 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
'ry': 2,
},
labelAttrs: {
'font-family': 'sans-serif',
'font-family': FONT,
'font-weight': 'bold',
'font-size': 9,
'line-height': LINE_HEIGHT,
@ -187,7 +210,7 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
bottom: 0,
},
labelAttrs: {
'font-family': 'sans-serif',
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
'text-anchor': 'left',
@ -203,7 +226,22 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
},
},
note: {
titleAttrs: {
'font-family': FONT,
'font-size': 20,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
'class': 'title',
},
agentLineAttrs: {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 1,
},
};
const NOTES = {
'text': {
margin: {top: 0, left: 2, right: 2, bottom: 0},
padding: {top: 2, left: 2, right: 2, bottom: 2},
@ -212,7 +250,7 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
'fill': '#FFFFFF',
}),
labelAttrs: {
'font-family': 'sans-serif',
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
},
@ -231,7 +269,7 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
'stroke-width': 1,
}),
labelAttrs: {
'font-family': 'sans-serif',
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
},
@ -248,26 +286,11 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
'ry': 10,
}),
labelAttrs: {
'font-family': 'sans-serif',
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
},
},
},
titleAttrs: {
'font-family': 'sans-serif',
'font-size': 20,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
'class': 'title',
},
agentLineAttrs: {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 1,
},
};
return class BasicTheme {
@ -275,5 +298,35 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
this.name = 'basic';
Object.assign(this, SETTINGS);
}
reset() {
}
addDefs() {
}
getNote(type) {
return NOTES[type];
}
drawAgentLine(container, {x, y0, y1, width, className}) {
if(width > 0) {
container.appendChild(svg.make('rect', Object.assign({
'x': x - width / 2,
'y': y0,
'width': width,
'height': y1 - y0,
'class': className,
}, this.agentLineAttrs)));
} else {
container.appendChild(svg.make('line', Object.assign({
'x1': x,
'y1': y0,
'x2': x,
'y2': y1,
'class': className,
}, this.agentLineAttrs)));
}
}
};
});

View File

@ -1,6 +1,15 @@
define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
define([
'core/ArrayUtilities',
'svg/SVGUtilities',
'svg/SVGShapes',
], (
array,
svg,
SVGShapes
) => {
'use strict';
const FONT = 'sans-serif';
const LINE_HEIGHT = 1.3;
const SETTINGS = {
@ -28,7 +37,7 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
'ry': 4,
},
labelAttrs: {
'font-family': 'sans-serif',
'font-family': FONT,
'font-weight': 'bold',
'font-size': 14,
'line-height': LINE_HEIGHT,
@ -37,21 +46,36 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
},
cross: {
size: 20,
attrs: {
render: ({x, y, radius}) => {
return svg.make('path', Object.assign({
'd': (
'M' + (x - radius) + ' ' + (y - radius) +
'l' + (radius * 2) + ' ' + (radius * 2) +
'm0 ' + (-radius * 2) +
'l' + (-radius * 2) + ' ' + (radius * 2)
),
}, {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 3,
'stroke-linecap': 'round',
}));
},
},
bar: {
attrs: {
height: 4,
render: ({x, y, width, height}) => {
return svg.make('rect', {
'x': x,
'y': y,
'width': width,
'height': height,
'fill': '#000000',
'stroke': '#000000',
'stroke-width': 3,
'height': 4,
'rx': 2,
'ry': 2,
});
},
},
fade: {
@ -114,13 +138,13 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
padding: 7,
margin: {top: 2, bottom: 3},
attrs: {
'font-family': 'sans-serif',
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
},
loopbackAttrs: {
'font-family': 'sans-serif',
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
},
@ -180,7 +204,7 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
'ry': 3,
},
labelAttrs: {
'font-family': 'sans-serif',
'font-family': FONT,
'font-weight': 'bold',
'font-size': 9,
'line-height': LINE_HEIGHT,
@ -195,7 +219,7 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
bottom: 0,
},
labelAttrs: {
'font-family': 'sans-serif',
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
'text-anchor': 'left',
@ -211,7 +235,23 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
},
},
note: {
titleAttrs: {
'font-family': FONT,
'font-weight': 'bolder',
'font-size': 20,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
'class': 'title',
},
agentLineAttrs: {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 3,
},
};
const NOTES = {
'text': {
margin: {top: 0, left: 2, right: 2, bottom: 0},
padding: {top: 2, left: 2, right: 2, bottom: 2},
@ -220,7 +260,7 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
'fill': '#FFFFFF',
}),
labelAttrs: {
'font-family': 'sans-serif',
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
},
@ -240,7 +280,7 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
'stroke-width': 1,
}),
labelAttrs: {
'font-family': 'sans-serif',
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
},
@ -257,27 +297,11 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
'ry': 10,
}),
labelAttrs: {
'font-family': 'sans-serif',
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
},
},
},
titleAttrs: {
'font-family': 'sans-serif',
'font-weight': 'bolder',
'font-size': 20,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
'class': 'title',
},
agentLineAttrs: {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 3,
},
};
return class ChunkyTheme {
@ -285,5 +309,35 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
this.name = 'chunky';
Object.assign(this, SETTINGS);
}
reset() {
}
addDefs() {
}
getNote(type) {
return NOTES[type];
}
drawAgentLine(container, {x, y0, y1, width, className}) {
if(width > 0) {
container.appendChild(svg.make('rect', Object.assign({
'x': x - width / 2,
'y': y0,
'width': width,
'height': y1 - y0,
'class': className,
}, this.agentLineAttrs)));
} else {
container.appendChild(svg.make('line', Object.assign({
'x1': x,
'y1': y0,
'x2': x,
'y2': y1,
'class': className,
}, this.agentLineAttrs)));
}
}
};
});

View File

@ -0,0 +1,434 @@
define(() => {
'use strict';
// Handlee font, by Joe Prince
// Downloaded from Google Fonts and converted to Base64 for embedding in
// generated SVGs
// https://fonts.google.com/specimen/Handlee
/* License
SIL OPEN FONT LICENSE
Version 1.1 - 26 February 2007
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting in part or in whole any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
*/
return {
name: 'Handlee',
woff2: (
'd09GMgABAAAAAD3EAA4AAAAAi4QAAD1qAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
'GhYbxV4cKgZgAIF8EQgKgbpIgZNlC4MGAAE2AiQDhggEIAWEDgeDKxuhdSXsmCEe' +
'ByC2jBgVNYuS+mQUpZq0k+z/vyY3BgzxIa3ugqhFRlV1T4WqHliFgkLvX+Fguaf/' +
'tXTReCMU5hvZIfpErawhdoJhvGi60udSmcpUmZV+33txIHLMbEhRhomyZs7N2ods' +
'iOl7OmXseNPFL9fnzsBxPmouBP/3/Wbmvt+Om/2FCihaDcM063gCEX9NLAt4u0Mw' +
't27RRQwYMGqMWgUxxtioahM7sANfMfL9svpDv1r7QwmKw9y7bx3k5w1Kx2CplAiG' +
'qIJEAx6gUICjf39+y0xK/qS6vwbT8sAy+yrlEHe3vNdJ+jauiqvOOWRpffhe8mec' +
'SXiGF4IGTtgLKLV2M6lzDvAXO0QkRtk8v62KREEBwaihHQPHQUZ7YfchZmHtcm8+' +
'Z6+az/v4cS/auKr51LlyEhygfzOnr94kmRQnbJcIXjvUGdVV1xbM/AtMwQWbE4Nk' +
'WTgj7VNM7tsSphi6w7Xer64GAF9ZWaEQhamQGZvYSbJ32eOle7QPGCaZ8v8BQACc' +
'a4LR5sPtg6JABnLj1wL8AT7Ig0UXwD88/7/XfvbvhPOLI9BloMsIxZsiDEJjTHLf' +
'vMw6c9alqsNd71f1JrRMfmuZUDJl+RESNELxW1Gh1Kq+QyiUQwiLBqH532XN5lbd' +
'0eXiwCGbNynX8me2JbTMXE2omWuTmaWEddCFA4eSW9ospRR3QvI8/vf70bJLSBva' +
'prCxqOq7/933bfWsOurVtGuIZKolhswQGbJVhtI2JyxEQs+omyBJQJ8+owwHUkQk' +
'9Jq/DeT7/htzRkREiHgi8hDpxlIJr0M6CN3euB9bfbmXPn+2CXsKDDCEMYJ7/34I' +
'cAYeiOz8vQ8DOCFHDXS5TvYA4vNtWwMgggPgFUc9PbY0ADRwdw846HXQ9qjp/yfP' +
'OqIoWmIoLqQrIZ6SqqBHYVhYHuwXuBjuB6+DD8Cvwm8h4Ag1woQoQNQhBhDLkELk' +
'EPJTVBHqK/QJ9DOMB3MNm4x9hJuNe49vIxAJfYTnRDKxhrSKLCU/oRRQvqE+o1XR' +
'ntOL6AMMPGOC8Q3Ty2KzVrCesC3srRwkh85xcJo4/3D7eEjeVj6Xr+EfFrgEt4Ry' +
'4VzhXyKtyCRyiLJEJaI+0YholbhEXCVuEfeJDaikK5mTGlM7t1SUSKl6LLDOJUBR' +
'QUACHMOBIhCAIEYMExlJsDBRRCQllJBOGWW4qaWODJppJpN22vEyYoSPMeOyWGyx' +
'HJZbLpeNNstj2oxCzjqvHATKXNhGKJv4sVkAM92BP3MEW+Ire+UeHEou02UugSF/' +
'Rs/TIybjDymoqi7KSkiF6oAu1aMNYyukNRbNnNoSLHVMXEt37sm9Y7bg5hdVaCbs' +
'i/upZWaddaJAqWgRL6FlVUXVott4B+lhffVQrslH4jEyVTyVfQSAYVb8waSwLLM8' +
'lCJTlUVxSVrBOnhX1cOGOUw6YRhaoC1hu9k5e/ToQ7yNH5zSuXZ9Z+6+7Un407GQ' +
'SJOH0uPsndyp/KgsbYABNaImzMyhwcMcf9/0Z2zgmLtn3ntcEcqFVFwkT6TkY+O7' +
'OCirR7zNn7Z//Pv7/3yfhr7CluKlI3OuXUNRTscMuL1AKYjzN0Ae6LtWimNVo6hT' +
'clTRajlvSzraXc2eRl8+cK7xR/jY6eTC49n92zeszJcQcIXM0BGeuqtkki1yWT6T' +
'Elmqowa+D+34DJgKSwUPcGccS++aWAi1OMar1CLtuRSePK/6ut9vZHYEDEVDpTW5' +
'jKNMJZghGbqPVUzlRunA+JHtSjqM0zxcWjT7qY49n08atNHzpffVINN4Sq/G5oZr' +
'DyLo07HU38/J87ubnQy1SGHDkest3yN4eqbkt2+w16d440jae0Jdv779BDgORYvk' +
'OjdUeiSZsjnjUOuomIiEfN/GMVP2ZXp+spVRjBi0OHXFqNqouqloUb7TM2lYgCpX' +
'4spEhauytaSujarNNy1tg472oP2zGjLaao7U4+aEmzJf3Cy3Ws0ZVf64WnKYneVC' +
'PornpBypzk3RdtK3weAEo4HAoJsRumuZG366FYc1HApypjYODbVCFJNEa/JZqktf' +
'LNNmX+RaqWA0l3U96YoNC0yRp+gLfE6Jhxv0kzvpAdHTOSmVJwreSIFVQRGWmApk' +
'URDAYnFuuw49BuWYtUs6ny/TD1g7cR5nLxyVN+dm7ktapYkWT+tMKaMisSAsTAQA' +
'FAb33mwfHyIaPhKOyQk9RdnVO98TgFlCHKpmpX8FX9uhTlNd+7fkyXx79JB5zvUC' +
'IdTnPykMxMl1l5immsxKMVsAqpWi9ZJ5H9QKrMoXBs0GB2wz81mt9nUG9ocqTWOk' +
'N7Y24U0dzpzeaoXXJvcWbO5u7IoHtQmyRp24X2fnOVpekShDlQUBzGvKyytUgVHJ' +
'Il0yChoEQRAAQdfdpKd92cAsGCwGbC9mOtflVkzWIH5wQP17NTlxkZe5KC+mAlkA' +
'gJkUWlBAoG7unUMaRvbNdgqQpqtpgZH7CggKAlgcsNkWNgY5WTxFgyP5CWjMqTeb' +
'3PNQv/BC7LPnUoXWzyZ3RmELJHQgIeRG1VM2MbOhni57avjglIAW+aSBntktvobY' +
'TcKALMrfG/Qn+N/YBGfmuO6ee49RhiTEp9U8TH7N22n/jWTq3GXpREGtUuZEPRzq' +
'JyxZiOa8xx+mTw79qklH/htTU8mt8HWfbf6y/cvu6oPSB80VCqAwUWrlBatUzHSY' +
'hSwYODGrRWbEAy8IWFpwLk2f0AioQrUB4wDAJ6NPSQTgsFhQrFgQJw4HRBtMyY0V' +
'3HQ6Qgl8Noc84AJ5jD7CC7pmIv+v7CJvaK0ERwZELmUt/qzFCYo68lgwA7WNecRJ' +
'al+RD1+HElgzOTaUCgwjcDqLxYnNuhqevCOoFljO3ijgFI6QfCEH0cMtmUB7qZ8d' +
'Wq6Rx+uOKrfVfPDl7tDFBzQOhcNE0Nhkdgmx4rcWUA1IIH4ZZ2Zw0ZY5Iojjd1J5' +
'S99pihV9irnNXQ+wF7oVjvL7fyfciJfiTiT9MJgJEoAcQfFwQaJ6uOiRkgfL9ioe' +
'qYprSIOnZR7mUR7hAR7m9Wv5qaF9zWMjP449MPHu1EMzL+ghbsWGv+b3X/z04y99' +
'8rVPv/j5z776/jefW4hZVevhd7///v9+/OpPX/3tzh9fu/vN7uyDBQonj0RGaB9X' +
'II6cPIJhQdFJVeTCjhtgAUEsgHxEQ1rUXJ1pZUjnvcHl/694s5dHAWaCYNhfIbfR' +
'nMyV+mZ0n1lyaQDgQnr94jaFQiwznRxOcag6KmqU7JXZihO7nL3V1uk47jpJNESp' +
'MYwemgialHToM5PChX4B8HrQ3WtwtyAgkH5kwLR8mnI0r40K4XY1VcSxABaNEY2G' +
'o0B0RFcPem91hmxe4qk8CCZjX/gZzpWXXivjRqGhkC6OEUkYPg0rEGJin9ZlfUGj' +
'ixecSAxtco4Ql8/1LG6p3VgOyH0VCb4yGY5zhnPhNN6pa+ApfBv0XeYptxuoLlrQ' +
'BRniAiQGsccaORvFX77Mf4ye7rcyWTFS9Q9Bxny2s77Irv/1DPbtD/w5xDRjI8HY' +
'xERzampmL9q/m8Lw43znxYGvDix4rSDWSN9f+u3m3endzgNKAvj3zFEO9SbcKpRN' +
'YSnOoey4g28cFhoRY8dd7KG7YXKsGCo0YmE02GO3GIQfvoai2BaJNi9nSmdZARih' +
'NqIH2lT4VZjkBhpBSpGSssW917pp2/WOVPSpIoNpiSwBjsGnyIIleD+4h+mSLcd6' +
'jrGrdI+9O2zNjdAA16dbSs3S9zM1QWoX0MfLZu90fXoo1LAKZM4MPbY9A4TVmeSX' +
'mhq03uuKNJMz00PRTAFMoBKp83+ilmbt/Efu+XrxroJ1WBqB8ouoXmZrrLyq4nZd' +
'+mD8wZTK9GZq5OjIkfxM6uX+wbJLXVlvnG6zt038QJVYEn8VtWKbFRdD2Rppo91l' +
'qJfiVVbdGO/yKnciaQvYoFdfXnouYwOXmstJWjyT3NR2cDOpkwS4sODUhg1rZGuU' +
'XBLjCoE4ol2nSCDZfOppxa25+IAlvNgHOgbD88HiAAAD4IC6K5Pv2IsGloJhbO2s' +
'YPS7UDHHSCYOIBwkA+4hKTrx5ZQoesVUrJOdLRRuGjQbp+KfXdJNaU/Kist5XF6M' +
'xEnucQqiIkV1yR8HWS1Qq1SEQZVIzLtVz3LQyzIEC92WDq1g1p50V3hsFT4ji93q' +
'HFRxlZthwaXhQoUGS+ZKzC8xGHRlhBbRCBLUiONaao4UyDjWTqysQ5QjWLkMcsQR' +
'S2woVNzq9Lge5TuG3z4aZiY517CJI11C7p0ynbnlbjBdxPXnJS8IO25VJSC25goi' +
'Mu2Snu2AM5361usYnnUz2ayNRl017ABzf+cE70ZbnZCTa27zAYw8ykqsWSouI8vb' +
'WsHr5mmdqtkEkJsGE5Ox3eyQ3DE9wOsiiPnJrM4g3Gg2yW+m2rSVqw1pZGN1m3un' +
'968zQOI31gnCHtyD0ZbNhJ9uXpFyupDQY+5yOFFTzMkOhxCJAbgWJcc0GaCiSc/l' +
'COUMOB0AaQhCKSjWQxYQRKGyEgDluuyJDEsr5L9xOx12OkOBaivgYs5i09nZ2ukQ' +
'K1dyiGFEICug/fIe4N+wAA/7Qo7Bt8tfOxHG6JJ+B2eXAh/RC+3g/+CaxWXumDkY' +
'5ZQ+Sqepw0dzT/NP0l2RZFUCg6MKcM4lGDGZPjLt38KzFTgbnM6A3IqSJiQ7SI3r' +
'6VNwZRo/Vz0U38luR39+jyashWIExGBWQ+lUQwwod8k5DeYpCGOPCRMyUWMlI6g6' +
'vsAo4kQ5xxwreaSM7wc3GNjALXxeLhfToKSo4CAwEZQLOmkXBWwULhIva1+47RnK' +
'ak3fgmZDei4ux42tUeioCccNSGniMxYlQTA4XGSWPrfEHMGAzlj52YeTDxobyYMz' +
'Vb8WcjGvlPxdepTrSVkl0EHzntW/yQVm1pjmloETsSMiZBQVUNZFox7mLrl7kvoT' +
'SOAYQUHBkZAiAIDcA6cJRBeRR3ooc207xTV6rkO9Uy4JZVNYiGG9XpZdGWqpEKFi' +
'dPG0Pm0EOUgcyWYfGcOZx+b04/XYR3Ksj1d6MqHRhyVKzw0cuc+VH8MKMDs8ZL53' +
'99OmSMDM2AW3DCwuFsHlioQ1spv886heJjG7AnYncSByPXcGjQPHSo8y0qw012vN' +
'jxX9gqpLi4ylyKgqnajeqpqsoaopb21t97RD7TnWh6tj06FaU5yRyBKbOX3y93Wo' +
'B99eikR4hT9BSLiJIOCUaB4+AlnI6qjAQVSEsxh16mdqNFINuWBQoDgVgUByM/9H' +
'/7FhH8TlGIRCAciF6XltWFgcsTkmMImBYCgWhVyquTTCNTv1rBC9U7ed2gjYSkEd' +
'wbqqCJC1SeT0ORIPOUYh94BX1sLUBxH5q6M2rm1dskL+xKT84SP6dF6dvVfDwJLF' +
'Cm8tygb5I3qK3mC09LQKNZckZJwSUkIpIW/s3xWfNc6u3FfvzNEPOWCuh+zwg3bB' +
'vZRx4PhLTbkdzjadznJ7QLXLOAVn7JnW2sLMUnvQwypQ3Vl6sx9seTAnYwRsYZ90' +
'v7+YE4sgfnt/51yA96vn8Jeaf4BcvvwNuOa9z+TltaUV6BBv/xjbzo2A8RmLF0Fm' +
'AHiI4wDbRtKhO8+BgTTgfvJbxPO3yGSALbbZY79DTjjlunue+NN/Plhk5JV8lE8W' +
'dMJN+AmK4IgM8cf8ALBls2122O+go04666YHvvZ3j7jvpBFOwkv6b4MP9TcX58Kc' +
'd9qMkz5y3DFHHXbIAQOz4K/8/curjTZYa5UJDADxcFCQikTsHV9y0+U+6w/AzUJT' +
'76y7DwWDAFkN+ibCWvN73b6CRjwS6GwhQFeDjBvufWF9+Y1iqQi/ssyNCHsEfGjl' +
'4sXQF0/+RBYyDn93PfCe+Rv5jZEXAElXlutg/6DzqCnQ/7D5EwCKxJ4jc2oEIbAK' +
'0t5stXSyUpE6lYCYtIxqlHFCVRUYXYv0dtbvyaQTjbtMFXRx3yyKqj/1q9UKtx3b' +
'snJcq2gntl61tZHF1J26qGPtdG00r+f7/rY2MLNC01ge7lOFBJQn5D1KpxDqIU9H' +
'UAbIszJWlU3ERYxi5OtINELipnRc2iUv4yUSh4FBJq90PJNNEKMY+R1IPZLVLTEw' +
'diAty8pqxChGHgfYvVHiXImDE1YhRvEIJG0161gjdT22WCJGMZGvgaQAmwh1W+d6' +
'XEfarhzxxpYWsUQfRZIDRcxGAN4v9u7HjJTYKkfyCrDEgNoTFqflHMxQlcBqaHIk' +
'xofGhhwEKtSqI25KAo6X0f5A16QZfMx0EG2XU3bNakAsRinrn1mtqNFqFX3uHG08' +
'qiat72zxPP8u7Wzc3dlgq7zsxoEnIjkR8IGGWA2aySuoonsKQ117x5u/iwK9XL5U' +
'b+8Sc1N7R/dSIErgsZCRoWoW3b3wXUiVDUQIYlSfmDjAVL2/eT5HKxYvSQ2Xr8zj' +
'DXUWlvosujQxsuMX/P2EMlIzuE6VmcdFtQC1MUmel/BCGe2thONf9wKdYP/mw9NU' +
'3V9Va2wV3Vi2K6IGJSch6u4Y8Q54yenE5S3HdoJk3VCV1I1QM4yBUsJu0eGn5/pJ' +
'dP2pAMcuBlf+LHyFnJrVpp+jnNKOPuRh/E3pZuO9kKUKTVfjXG0KCn6v9cN9RjWx' +
'XC7XXSgU46eB/j4ShpyFkpkz1ENrsTRBBbAAqDqQ3wsvFXzHkQgQKsrn+IX6BHKt' +
'IU0N0+5IbmbUItbl3MAmsUOfa4/45ZWktKE5OYMnlLWq827prUQYcy1RRC9OMfCq' +
'qkUVg6MIHti+tXcJJ8rIFDJYcwqg6soB7ubGcoAVStktBWrlC3jeiIXHzaeOOG60' +
'mBI62FB+A5cHANhXIIR0tFylJqtQL2Y8wQwWaRGryAj7DT7bLb15NeyQbnvx2sBL' +
'manJgK0x185xSmRnObotg3GUBVApFq2GKucqVOO1lpbbWGRalI8HqihQ6QwyeELZ' +
'dT0aDPSX5xKYxdFXXt84wp7zL48wxHLEgDJG6qMU6OnydD1O3C6hveCtRVD2yf0o' +
'8za1NGjVM61jU/dy94m8QvfJDXSKTEU8NKkmeMwQcuQ8R2uNZD6FRuyV6Oxi3OeE' +
'IqqeGMMu5s3i6MuUQpWXia19PTggagERm87qa/VL2a19WtYYeOvp5bShFm1zsZmf' +
'wRhZzkhZzyuYRAdfhGVQFswKaAqmavO5kEbG/d/BXJq+5s49qOWcrs3VMgUkmMJB' +
'Y1slGK27sMRY1tGw1qrz8GSqbplioBbjEasQBbupS1RuXhqORZmVlqYj77aYRMxV' +
'obLWN/K+wzcjBt5w2hEgiHXb5GZn3x7ecoJm8h+ug95JAWNo0lR8hC9htWePN/IR' +
'YhDvcLSmm+AeIc1JiCa+NufJFa298UMsVUbOesegNWFQ2MVmOUsadqpx5+RzudlQ' +
'u573AHEPGSENgbgp29fMLx54oTZFUyyCnibJdI1xgWWCRWNrae0afmJ/4UORLg0x' +
'pvt3TOmgKMXGwOOdezd1Y/Y09HjyDJpBTrj6C2O67iAVMfBAtdR6uHvD8n5AQv6j' +
'MqGfSydi1yFMLIAGBMDPE0xHIDOqf+g4ul7e7ErAa1ORQkBkaCLV6vxnr4rU5Ilh' +
'QyU/3Cnfsk0cELfgLt7ei/X6MlqVqiYmL0QXwH9nEbOkgy2zzYcFpGm6H3VGBAUr' +
'0KTIMXktwjUupr6Ykk/jJscYIjAKxx09JHepX9FEf0kYFImON2azl10zxOd58M1P' +
'Fl1PJsiFeWakMXJ2JD2bbg+RP8U+49dRL2bTTf7W/z39gNz8YVOE3wW+agdT0Cei' +
'kSXk8duJYlJpA3uK6UCFCVfPh8B/VwRdV0/XyR7Ob37kW5TL+p7pfYI5+0sZfDg0' +
'Ouwd4oWogUeqJWAFbMWBYw9SMBV2jahWzetBNH08RNiPhUM9QjssuPtgBd/5a6t0' +
'CUY8w8A1C50Lb5z+tYkR+hv6ViqL1oYbL7QHojdhL9FYrc9gbZL0ZnXbhKicIupV' +
'khSAl+wOVCVmTm6ZcEMgVbmFjsoMoqCTSHcNmponknf6smbMEQgu2qzzsqUzkb6/' +
'PUS+UyjvWI7qVMU5/pACb7bNeMtp2sHPetzNYqzvXOVozLHS2hsc+519257bUnLM' +
'TnY4X9o9QHQaz0Qm+Yb87g71UjDG+3Bfk301REZ/F/8HwWyohGqLMgGxqSVsfkYE' +
'WG9v5XMiMb5V/PF/hsJB8MK/xCvltp+SGhiovEXLuRoeHnY1ed8mSxMsAN/lbYzM' +
'10tru3dTQ6HfFwsb3vY+HXq0B+bqLqUeeo6q6oAmIZCGs8BXot//XGyqcPRzM7Su' +
'rqYe3ZNAaiHJ0Bdavqkxs70HtNJti5yqo2/P+HaeyKeqpZ5UO2au4qUuIg6l/QWc' +
'fWmBvaO1tUbdrlNRgYguIEKh/emPU2Exgc2+2sZDdq7Ucruzdk0V35yBRTbgc44Q' +
'UYBqueAvcHSbumWIVgXdizJfQMdgLJS3q47TMFIZWXkUccy6119+vfhy0+/Fa7vc' +
'rcPAWag5ESGJsru+o51IGnk6VmG/RQAgI4tX1jBVkUCnrR357Bv3qngHHTm3QmA7' +
'4OGszU+44iJS6LaAPdskku2o1Weoou+wF7ZYzBLp/QkzllWoogp0aj9St6t0jMsk' +
'ZI8eTFBVrZaTX75w7Yzt5k3aibTe6MP+bf7eAq6n2wODOWKtJDZ7XfymoUrAdmbQ' +
'dQ6f4OqAcmNwLSs4ViTkefZtv+RowjH7pMHvRhc/LQQVQrk3gyIjvfb4h++kIG4r' +
'mBdosa+QSQe0MTHSpLA6R7d2q93Z1+3bV/jys9RCo9FWo+Wrb05ztDAJH+Kruf6Z' +
'sjuYm0nEcnKVdjDh74uhkasiFqsTtZ6CxKiEbAiX4aoNkY3MBhY3Fdftsw6LbsW0' +
'syg1/9ISFAoNBgH1V8+DlpfWOMzsg/Y45meoupym15eviQxGCrHsgGb1gLOdvCX0' +
'LjUH7nkKPoZPgUWbeBbLXN40akcYDxLhEO4YoeqOaqHKMawpOWRrI1NslmP3UaEP' +
'P126qZ5uOu7ISMELfTEkfHBCYpcN2cW3rd+2CtnZWOZjwiihW+HsyFlv2Ovll8zU' +
'ZdzLTbHIDV/rtv75Q1Mc/XJ81DOyGd84PQVew3aGtB+mjJC3CG6X+HAWVBXhC/Fx' +
'TgEzZ8+mrwG1Nkma84OFEljYLglooOrxZws3VoL0p9Cui/+UULM4Lj9lcD1N081G' +
'+TOWUrAXOI6PObQ20q8sZbbW6RjXgCrJf7jV1x0hFr21Mxk0Lbl+Xhmdyth8udPR' +
'SHWP+7wPDEqlEaoudNlsa/Y7DnKQrnJ1QfkrepJ4jnTEtCXImL59BWGF/lbTRL9g' +
'4NHafItFqfd6SbqYguqUJv9SUV454KzvkNy60uUA5YsvnsmyXZPo/pcinGArd7jp' +
'0ZeToAVOHQn0w72rW85alQ7FXoyaxIVXEdPGz2WlYk+uGEjPUpWPrXWzbSNvj8W6' +
'dAaDsGrhrqLt1TpUsGhZ1xVq6daxaJSH6JsBAR9+OkeEDiDu6X50ERpU5XfM1Dq+' +
'P1D18MLD08FPtevemdThe5lHcx5PrphtbaW58o3uSSHdGG+c0s8I42fL+qRV4BzG' +
'4yFYWC17mFzB0Z8gX2zTCQunrpf0kjyDdo1psG9uhTPp+crlyvX1r4vtmnBGUVkN' +
'tslLC+nmf5fcUtInBvu8OaXZc30v3rSVpBGviI3SZtqNK2lu68edxhFXuLyIsOQd' +
'R6/YaDHpOjVn8IZagCXxiqCxI5a3AAtJ1wbW5aaxVNKN4FU6JciRlUMTHaZzXq3Y' +
'Jr5LlPRBDJjaf3eKL+3jqKqW3aGwZe5NZcnbBrUJoW9q5GoHUGHjjMUy6aa0oluB' +
'DkZyr5C35FqDbQilWGlrBPefmvQ4wVp7Qx7efbp5pxQguoSzkpx1Rv0lal1WFHvn' +
'1XQaF9WMEcY7hcxy+VL52vD+MWrkcz+vCArx+LzZ9C9z0mIZkWJ7Nfq96xpb5Txt' +
'j7XWchmfyhpqlqOc2wucidonSszBySNP+O0D/7p7cnpdzPH3bOCjUuXeu9JewOn6' +
'vK38z96BNIcXPwXvvO0dRCsZFgV6jfq203Lk5bYzIeLe8biN59yzvofidHluqzxB' +
'1/GcaniTpBPkfT7SKUN6r6QCNOQyKdCg3pes6MWGzR5iJiQ36We/vT3U36RNnqo5' +
'ahlD+1TjbHegXTStQlqmH9hhRRThhYPwlk4yFEXWclImNj03A6ENcJeWf0ICNw+L' +
'pzVZnGCWyAEBWB9bJ5RCUJLTG9tI2lGTJENGkjZddJfL8VFUhNCiWwu7Wxwv/TfV' +
'VCzlWVpn3i+Ixm5mLam0lqSIGttOfDxuixikxB+m0K5xH9bGnAIaSiQCyzs5f9Iz' +
'3TY35/V1efrqn9DePuSWCCca2+/0m0GSIWofGIpwyLxPTTKtzSxUMiWtMJnulPzX' +
'ECpMOVIVZYtX4s6sGTEvxeaGLRyDOEkHXkxHF4NRqDQo2WQJOidUBdMgGhSuEWu1' +
'LSYOlJqZetLR3XRFNOPWZ02eUbaTR0QeL6eLKY3hT+wMJ/ei1VRtCXtdG7v2wvRi' +
'wHBjHhucYYjZZ8ZmKGlDM/SjzJMUJ5iOZYvFcha7giZh4BaU9LOgnnrm2Nva/dqz' +
'pn2vuNLSSiiSlpKWTUu3CY7GSsFiiBPgpibhXvBd8QhJTdwfyztVVPMDfzovG3tv' +
'4DgaHzy3u5fFdG9qA3N7iCg0oTC59sKoJVmSSpScFAGNCtJNgZwkkkORhcVP8+mr' +
'dbV7io9IdnRaON+Ejex6xbeEeX4RFgSxTIjaRdzFeKjxT3YwOnBZr8x5ZKy0O7M3' +
'k+9eZgvjof3kz/RElKWpB00Vn2RF/FkuC7OX59n0AG9MywR7CJvJ3BeoND4quFB1' +
'stdExMRU/JbZsu4RfTkw+ouLtdtHlgm6V99hlD9xX973XOZySlX3HywWj1iuE62r' +
'Wc6NWpMcPSWIuSR63R8OfuLGYVSnL/ZGMm9BztMLcP/Yyd6O20Dm0SF++KXYa0QO' +
'nbAEpd0sYimnFGtIYUCbEpBJ2kIVtyLxOKbDMjeCMXcraQZ3E07eSvoCdx5OAfvd' +
'lBaxoowmiBV1wUiZLKcniIwmK3r0L9NwrBSClfs5jkXiyZqVN4/Nozze2uzXxHEN' +
'fvpUg0U/TMHeUlNS0mbwLLKA4WSumqrYiZ4h6PAduLQGKIGvhxLVj8cKcId9uJQi' +
'Pe5f8O86UIdaw6K1evQm+0FomwUHOQSyDV87w3xPD4Rya38M7HkDWQhs6NVsOmQl' +
'xDE7OTYcilzMMHzmQ8HgdoXL3p6bzR6Pz9K6IOuRsdZZ3w8LdBwVhzZy+z1kLZBq' +
'moylT3Iqcur+eIpMUie9Tm496xmMEeVs6T5cu7H/6IGwxLjisFNNLFWyOqskLc6f' +
'IRtJXIgI2Z8YFXU2noIAzT9TNLwgAi1NSkMiKzG/oZgMdL1/NRBGrCTLo5MXmH+g' +
'v+GS7xrmRVOwLTzKkUoheehpK5ZMJOvc24BlDuVyEYJGJpaWE/biFr0glYF775uO' +
'XXmx40IEHEPCEXx7Pv73i7dOTUahYQCyFAkwe6jyus/ON19VvOPImj49Da5de8/3' +
'J2XCJOP4I9s2ykgOd/RejzI1HsuiES2KPCoUnYcK1CFQyeKKtbOjGeeqOny236wx' +
'q48QXDTf2fhXGQgVEnQQwyGdWrxIhhFDBkUjg2S+omaqmACHNMRgftPhEbshcCs0' +
'iSIYbwGvMO4ggtBPXLi1zj3NjCV95DWAocCzSMNcpIjMNLFyoJEGUI5E83EUHQrk' +
'Ctb4HVa9858C85+PBFPZhjn3E7K2bVZSERBK1rb7CWCk9EhecHzH1muTeanbV9Ze' +
'bjpY8eBbR0dJ/JJJ3VhzdmKwIY4oySnRUDp4yV+AvdtyHpUxrBgyihWIrCLQTPMk' +
'rHefC3xumwlcTv0VTkD43VUj/g4seWVcNTESTRMra5oa6tsn5+Y26PWSu/+TWfP8' +
'u/Md9k+eP9GBvmB3TL83LqF83KvD5iRrvGsGc0OLg6MiENgIZnm+J7bQYYxwGCIl' +
'kMu/83+4XlYqvRHu0UszyFNeIrCvQH2DFcjQNPR0uRFCXfQXhMLesuwvJHKlHxpl' +
'MP4chK5HzdELV0H7uFBo6KR0d0LcEO1LjciQ8Gf5T5AUcMX7DCZxrW1lW83IWoiT' +
'mCwmBWVtnhhzNekJeiwExdJuKo5Myq+pthb/iUY3wkkh++gbVEDA2x2d5G2F8d5a' +
'wf6fo9ZPiqKNR0YsSpO1obRmTtzA61zb3MK0maWDPgmL0nWHpCf5SSqDs1yB/D/Z' +
'2u2z3CsBP5sKiVpDLc5SR8sD0i+uHFGwSEiFcVXpKWhE9PQTf22dbC4pBaTtlRVz' +
'vFqz0jyiiymOzziRfHJYadnrzw1eHF/9ZVOwLcOmXuXbd3DoKi2hOGp/BkmXbvL5' +
'z6rihWWUci8ETg753MuDpcMhIa66FJBJxKeuogM3q6CgVqMzmvM7Vq5pSoNYEVXG' +
'64Q6agAb80KSnR4MVyYvqPxDou3oA4n5h/wOKYTylthSlkUZFjREDwBjR2iz8Izs' +
'eEE0UUJjk59HdnanUVGh2LW4EWJjx0xOOphAYM3GkFPDROKbq/dT26KV8V9+R5ym' +
'ocOTKhP62+szsOqRnKtuHR6pAf1uHdL0hsKboF2DXpQFf9H7FkgaS2AcIvX6fdw0' +
'9CKLoARpCqzZED4zyapdXd9gSWEWtyOnS5ZYbp08xpdiPXOawqQ7//X/LnJmbTtt' +
'Ie1jvV/TnF9vmeMqrJ7EUgrhOh4NAyEJ1aZUTaHBt1Angizz/+AOxLaBYs+eEmdf' +
'UY1qfoPKWMaVp1lilqc3aJhHMzg2Cu5XVmH5Ra06vVS5dKmk1mNN8icOIwlYSj5N' +
'UdU9+mFmAV1m4nssV0lCZVY6WAZ/JUQ3p9OPJ9fBHmWiZ9OnnPGX/HGVaBulkewS' +
'lImsKvv2W0uKGsOPCaZ5Z0+HH89KzxxUdmDLSrGf5iMSGY1MD/AloGw6WM/WXG+s' +
'r7zeRC9LGyqqzntyQtd43ymTRa0oCNJtdFw4RjVDN+y+4DvlX8jz8qegHb3rOvOI' +
'eaIDYCzy91x7e5FMtXl+ReaO3KZlk2/yTL8EDheblRlX894fmJzliVlX0rXM6lsL' +
'UAg31GL4hNpMaqgIXBnu23JveqlD3Z+Zs7A58bfq4f3755nyg9NJhl5Mj27qxnqQ' +
'/MVqaMr5oHDpfai7bUc1XgDuOzqAKQHtV/wMicJAuupfaA5twaQ/ouGvLnu2WbwJ' +
'Q7yIjtmmguR5GJRpXRn8W3Dyi3O8Z1sM/3Zr0aigI5C6k82Y5KUChYosdaKbTCgy' +
'YkEoLUgfW/hGDye8wVVR3Puj2JicEsAFU2EaGnMOe8GbEMLRPbuMx9fPO/f9H3uc' +
'4wnfPOY601OkQeMXnq3pOTyna47GE5XS3cEl3+wkAPWeH0X45ci6O2gzaVDY4olB' +
'fJuJutNCR10uGBWNiQ+z15AuibGjQV7cQWkX7VoqUr4ITJ3FXAT//+loawiEfIPZ' +
'EssHA6ecjNi09oz2BkNzTMGY4qfJnyqV2btp/3YmScD689Bf1CqfdHaR3RudPCnH' +
'B1QsW9ac7Cgo+dM75tRg9LkVKf2lHWtbdELFLngLJVRQC5Mek+DAhp3wk5WI3CPh' +
'N2HhfMmtiI0YLLz6L3jhUjIP5E1OkccZ2sIG32hWg5+nzT23qC2OjO65AOlCIi5Q' +
'vuPiliN6UqGHk2xtxpbMPEdBbcsJJjzjGAxshI+8lGJaABhIQdd+kH+KHPOirtGp' +
'DOahZVuZ2fq+qp0f5cxalFysi4OPKBE7D8VEwUYkcszGD9V3zhDA2cjEob3m5b0t' +
'2azLR9fnaBfyKnMXzK91s8XDS1b3TO7ei9mz/vXUJuDTr8N3iBL2ZDIqx1e1Pb84' +
'n7SlCtLcgI2xHDMshqajKRBHvPkjW9hnYF7f8YXZnySHwHXg4ezPuqp9H6okjXxU' +
'+lBxzR3evnjX0fEi06H5XTlLmlLKDX3nwxmZbtXWhXOT6svilgxx82Nc5aq1z73e' +
's2U7CEFg+VnoCtEx9hS+5nM8BDhc95pLVTF6b21opl3zMlVWhPuntuJ9cVgk4mW6' +
'kijNLC12Hhgu8/gY6DVUzwpPm/3PNMw5G1iKdbG87K0p4NBH2aysZKyLEyUnLhD9' +
'0aBIHvI12K0BbJeoiFMkcgWFp8a41jV25h1ICcQt4RR8tN0ZWmDod5lUxvhyboek' +
'XGZMa1S5AX5NWFbaG3wcP51Vi0mh3MfsSg3EJcAPpNL1xkmsIAaUHHnZdUQiuLyc' +
'XBfQ4bMlZY3dC7Us0ktG0lpOrEyZo9D1umflaSK9GH5p50GA2z9Lgm2VOHyknF9j' +
'XI2Jv3CKos4aR+XEN6ti98R8mnjFOxpu8n2v6gydm3gFfHQlkDpoI/ZswmaxUn8X' +
'qyyl/uv4G7krT23mlxTi/2POkxfbdcUJ2QQHe1zAShtWCvRubO4K3krtSFWy+YZN' +
'eVLEMVFqApd8bJnoBa5uGTrfP2JHFHSDCCRdzdrfui8+6+Q3b7BfkSVlLf0fnYPF' +
'ZEVn6DPS1AE4lyGlvm4kuVutn1xvcIt+J3OcBbVr5qzWDfo514jQIC1ptbKxx8+3' +
'gvF9KAae2fhr3v8V18SE7wNMh4fmLlv2dRx2QJgutARnFY+CpunDWM4MBhnduvcm' +
'vLQvxj6SEIQ0bHF7urHjEkyPrd4yK6WDjF0fxBaDaV5YJpNMYy0pwfeziFXiryid' +
'BE7mOhgTT15zBJ+zmxAeLQoDvA8nY/fYmeq10i5hMuruPbZcmCztAlbSEIy1WER3' +
'seuJfJjQP6tEUkmkixH6YQPVg2aPmLG7w9V9EBoBaFtB2bH+MDxMFikaDawv+UzY' +
'sya2zr2RwViCI+ROVFxh7FSdAhOpDsymC+hzZDQMOTy1DYlgYAeAMvUSBpVCQUrS' +
'+WWucrxjmINyl6aCDhG8xZdwhIGcMj8uSQq6vRvjUul6P/3dHZy6Knx7YUusm2us' +
'hjYRipzfc6JLP8uKvROjj3QAy7VjYlQx/QUgRbwf++doy3d0R3bPqpDZheWDnvxu' +
'gmXh/F7/7xJtE7aYhgjBiTPMrPo8DwbM7ZD6Fd3T8UuTKgS99fGpkfGxT8+/42Lg' +
'iomuDTllrsrwiuFMXY3OAngREoVPwV0cIVxKt8O87Y1ngixRqpK10vmZFUl+H/7m' +
'whrh3jxsoR+huW+Z+Z42eUmoIE+gTKsPWhDLA/Wf0lDxNn3Zr1/HC3HfLxpxlVrK' +
'stbEL2qp6LSFhfrWNPlYx0LtcUJ99Cq+2jOHYj8BxpHU0CEsnfjX0Lct35Noibx0' +
'GJEqqT0cfrWoVp74lI4qFp1XD9f/3org4jwoNYv0zga5w8vFXKpygp9f5Ertfixz' +
'PCLv96b5D63miFB3QMGjH9imchK0kJteWjOgbVrkPzu7WHMRl5ImLhQnFc8USeLd' +
'KjEO6K6hz0a6RQpizU41+pjqv+4gVQ5uGKbDrFVBGL1m3A1usCoHOwwfYuRwxZFz' +
'BTuUc5kyMPw1UhPyNVsYBuXCQyrDNC8u5WvoNXBPGYA8Ee1kHYMPg54hnv/fmVgs' +
'RkIb3YV+MZwXn4z9lqoMYHFxPCd+ehG+SlRm1ITxz+CJfPFzMO97YDYDI+ICOru6' +
'brsx/uOdMcsscYXOszZMkFVMDvEWRERK5FaZ0hvLc9O6Ym+REelZKPgktqY8NJQH' +
'cjNLN18QI4tmd+kRmbjlltN2YMH4ZI1xBmKZAZKKspOWg+qjuDEGyIVimujpOdff' +
'KqMiq4GPfbRWpcjXQ1eu0M9G6Da4uBHqHFiZHJIVjE1jSRP5yEHIInTJKHjOWqjk' +
'C/hFzM8L5XrmU8L2N5vBXR6VFRSYixyFjFJzubKwfkwy795qNneEnov4BtJIThGO' +
'OAAGZiLmz3DS/2qRhSo9sH4wLKwFC38717L24/45F3x1pcMuAj7FuKLtvcSYU7/O' +
'vSReng7ahFAXeamUi3AhubSllNqC4KJuZ+GWem/jcGlC3uFMno/lGAtDZlHFWmOe' +
'sVTbD7qXMXuh1Wmoqg3/3fVgWuuzIMPefPWEXvy1ny7cN5teEZSTEtVGaGWmWhif' +
'sF3xE5HBeuNzcSlYVxzAHOJcZLQys5m/Sy7Gwy4HEDJkNo0uzJlo4GF7BK9yoigh' +
'IldYF4haOMFx6IIcn3oZPvZVlbHBnKpaFi7jBhPS3hvla19HiC57GT7O56EJ9c70' +
'mCxNtDyQsocI4qZCXpE3vwxXAg8BmqqfxWiGWkmI5WSMFZrKlh67IKtihwXHQj34' +
'9iCPlPKIo0QxPH7CBu71Xtji52D+nUXFS6K9z7P3v6sYiv/PzmMkUPvdslxZjrqv' +
'QTs3OX2bjea8FzEKcN+MCAnjvGRuinpBkJve3fVGTfqhZUX429XTV/m/1u6Rl58B' +
'1tb/c5JqtTWF+DTss9C9DN+EYpmMKlAz9H4/x8NEAtPqYEUTHeskqXyLU2S+6ES6' +
'zuhwrqdeMI0xPrNe2WLKs0fkaAD3LpKLXUYfNzQGpScs6xOM0HRRjaH52w5XKb4M' +
'ZHRuVlrM0ctml/d+pqZypv5KbN4anqKdAIFbbCrMML6Gl5A24Cu8nWdkCDb1WwJD' +
'LbG+Kg58DXQm//sPd43N2+w4XTk0BRYQv4gr5yKKBqOmBUwRxYtLoUFTwVLjKnyP' +
'gFFKbd6Lj6Owf5r5YiDaJww0SwZYOib/KF/fag11jyzZAX7BY+XMv8U2fpiNmo3L' +
'l+R890D8wCQwi/l5Qvf/WvKaX58aEDcWQqpW5i1Iu8yG+VBOiQ4k3MILGQIC9SrU' +
'R3Ggbt8DUB84/EuRv0qAnRWCWhhAqAu2BDKOyPHS4/T0w2ENUUCf3ccLLAgB6DKl' +
'BHmM/C+ZcVlgTkerrODTRTIGpoCUYHZrZfVuhSYio190hxQdkKKLlrdlxc0Go3cz' +
'PX/PUtbQ/TkfLBR+9qeKGJYvmgfJkMGwc7EMoigODYHatNk6j38a5ICXBfEQ6DEM' +
'WAbGV/BgD4ch3XDbDU3G36I9VDWCRQ0jEBd1yI8kWJ9tVy8OZBDg9KjsjMD1Fam+' +
'2OTYaDKhI7WFsSD3ecA+oXgjBlUWahlzK/ubSzlpdNYUx8lzF0RU0z+3QbKZODK4' +
'UlMCo4fHE1mcIB2EYdvbjaBk6qi1r3Lvr8tY/tW9einA0//rPnJTL+DkSRM+WYwk' +
'SHGCHgNAf2BduCQ8Knm/A1Ikwv1BqENQ/oKA8oWkEP/haF7QePhIZHKg06Y55Zc0' +
'uWx7OiMAjUdE13ergtMYbE4aKyKgbvjH8gi9yZlQxA9ibvdvPN6pSpReDtz7XbMt' +
'BstuDwELhwhhBEGgg5X4QAoLMRE5P7NCmOBB8tTu15fv/tmzBxzbOhtdNtn7aPvQ' +
'12ObSnr27S7YufI3IjNNIIhw6RtS8kJYNj866hE9kySMSrX+aAU/5UGd/rAXGg5+' +
'ASlUG2RWCWp8ugwl7XYd7EQYWRHq2jrlwoLKCeL5jIRUPmYDk/jT/3B6e94qn7RJ' +
'Dse9Jh5rp8ov1xxXEN2u7pBtme4TEfXug7Sf+zfvxzqosk+HQUz6/MkT5PMH8Wem' +
'18PRsrbp/d8eWFGTVBC+Oyrzefcoagj2K8nCyLEEhemq+OFuTTPnHva39rR/lFie' +
'KFauYeAiL+FoKA/hJTswgX1ShwcBuQXeWnt8RV1uW0iwkdKh7dTGxO0Cx5+h7QY4' +
'30PA+mywIYRNQiDiVaqPGRnUiu+elfzVjIA2CgktHscLpGulH4LkQqFvIFnpy6KR' +
'RBcGNGVMeA7zlGhk4ywvp52bi4Zngu9XZmUHxpbiHLRxN2fBvjgYOIIMq4tmC2Mv' +
'/RZbhDaFnCq2eyZLmCXMaXqRmCLMhsFod3Cz0ZmMb4Nn0mTrCYD34RSMoJjGpD5F' +
'P0ac1l6ktOHC9mjgHHQOEbo2sgp2Qpk7ryTTZC943R64wdGNOXcYTWOCEkX26rP1' +
'G1WyXqwuiZh7A3OTikJqk7lKAeQpHRdY0iEmI4jmalByvjIlKsP7e+7Zmjhm1HT8' +
'6tDVIHTaZFeVx9t+Pz1AVVysOOz3cGVGSXCPW5+0IqLuIpR/NgDyq5MKUYmARcQB' +
'3+mhSRzMDn/Od/SysO8KXbdSzNXM6VV3x/QrPu5evn5UzRQs3qpTxdWH9sjpR/Yf' +
'9jBOSGFIh1+qi3VcoNEiaBlcP76XtuzVEhBRSU6NODOC/llU77aFAij0ICUgKDuF' +
'6w4nMPmD2ZciiXKePzQY8sIGttphihTrJBy1R49tjxJ/JnU8vaYE1iwk/L04EsE+' +
'HoMkN5sQ/4LFqijo2oBDOCvI7wp39Up8ZEusbd/rqfsuL6RJTuJu90eQ2tKxB09B' +
'ReRS2Hwrut2tYLFVZAsSi9bkzbwFN3cHuM7ltUIVSRrZgMg9AoNOUHr6t7Zhl87r' +
'S//MRj0vU1QCQifTOxUTwMYsQkIP17/wNiLT4UGidsMU2sdF1cZUzqLkEDTsxq1K' +
'huDfw5hVWGHsTbyq5AhZt3G0eeATYnZO6ZiI/KKF1QqiWgFNo9BEhdVLBaE6wZjl' +
'oxtwiEOnHj6MfJ0humz1bmtt/VRl4V0+Q7y66NRwrBa9riypNKelbK/MeI9KJKVN' +
'LKY2nRV71qcWyCNjEofmdT7C9nQ1sAwEFzzQcFT4fIfCWPoehAMy4eFMkz5e0NlN' +
'rcVjhV27pou2/2oOeDZHzi1iJFgHPGvtglTqGa5eQZxjTikviDMP+M/M5RdRf2Qm' +
'y+aU2fc7Rwrj4lL+SNW77cGX0xbJ6zwpSek/W7XOM+KmS/MSUXu6qajTFjD/uVH4' +
'+oPLH3TDVuE9PPPwDYfH2NB+F3FK5p+NhSB3dzZuw2zy0BEkriC9qsoYrdKnVxGX' +
'4pcjLMECOQYGryaML0z2LTyaW6gbKfCY6bmeJGPvpmr8WMZSXT7noHz3JDkEYI/B' +
'VcAF48kwq+A0F8QZ11DBE2DJhlTCZ3ZzChdn8x1JrbcIz3fswc7BkjMrQqFGPOOb' +
'N8bZCIUpZGLr2Mq0yJTXTBnGM00vnZsEu/VOsM/JxMj/0KOSS+ypODsVKbsHiRvr' +
'LXiFyCZNEatzImbP6/sdrHxHSb/Bx5XY42YCYNW4Y64waTXmuLlA1GJcZI8Is3Rv' +
'8ioyJ+v7kgC4x/OZ9GQn0H9DPKrC9W+nT7ZDPf+PUFFybS6BZB+Tyr7B8zMwSA0A' +
'2POQyFQY+Rs8Un0WErdFOIj8Q9kzVHMvMoOOjRe6v/2AFUaEPenk3pFOhdaS/2qt' +
'jZtECzkBFirMmraFE/WGDZGI611QluzrtOzKijxHYcWcjkXDnTqppURqT2sUNIyn' +
'8+BMnQZ4+gJKpyFYKszaqhOeoZCwVbXqs39w3we22fpX6feYYmXN1LWj6VVdzUSe' +
'vLGE02UF2uba3Ysvdvbzjlv9TExU9uXqh/6weHjJatjIFvrY2r+nNoET0cj7enL7' +
'a6LimLKBbMzdvGxtkTU3i2LjW0ty2mLTjHl9ljhT/bajkFmsHFL97D6dXpbmNKj2' +
'hnupQ0RkZi6rE2rygUEk8n+A+vvtR6bng0Cw6d/XaGItgXpqwUok4ipY8j2yrvdR' +
'5qgdcj5DxG/mk7YohEVg4RSfU8cUCD1sUnVsHktkYr3yyGT2kNuHZczgztPJPE4d' +
'n/QF7hA9WL3HwXc/yR1BwiLFFhKf3yxiNH3EFa0ZeaGXc87yxKbo9S59cJZd89qm' +
'rG/gtz5PUBg0J+ve5ZIoBO6H0OlWquJiOVBN17duYVDehB5uxHO8VAWuHL0F5zbx' +
'dX32LGdoEfPpcLE5KHNXztdrCzi5L7lKKkQYYYs4hIx8ReFkYATjB3fMcWVb2Kad' +
'9Sdiz547l1dteAv/Vqb8fJ4ANCdo7W5wUBhnRPkJAFvQgN6G/5EBP+HV7PdWAwn8' +
'5p8tzy/w5+q52mCISx67cg0IgMDDz+leoYn/R3PYTwB8ti59OaJ++aZlZWM7Q6A5' +
'0dyuf+q5EbB81VYHd+XkPezjJTXw9Kl9AXyL1CSqDIp4+H1NplsoJ2mk8ucPdN/8' +
'mq4QMV1fJqHjGbW/YjUmWjdKGpTwICAxTgaKzCL8gaAsGt+KeCWJlwQ+JCxH6otd' +
'niD130lwx/l1gqtnwnqVUgd3OZJ7p003PwFeyvBLmBzjjSVz5EseBOAZocLbyDmI' +
'1GOd/3qtAhqvF51lbhN8k8XYKNAFWmM41on1Kb6lyM4I6a4Syzg9L8BL4jwjwIeC' +
'fMhfLWjcgouf6LlKxyf1yzyQyB/0rZPkE1p/EeR9UV4X7AJqj+JYUR9iIZ6Hz01i' +
'HMXzZL13CksnLp9IVI6fShD3hDlXaHfJChTgLX4+ZeqhaGuFtIaoz2ksFugRGjvY' +
'Wy+2GWJ+E+uQEGWCW0nifgIORz05bDwEz2+EcTpEt/ESbltZDh9u8bk8JQE1DVEy' +
'0XA2irYO2lZPBw6H6HauFnDO3MMz/C1LhyUQVQzZlSEK1O60MCxHhA/5uzlDr5PB' +
'x+JaSjdQwMgpQZwWptFdOijlYbr3JAOS+5nabThuIu4DmSmae14QhnVwI+Dp23Sw' +
'AhhIE9MUwZu7IYguAJUbd8Po+OpuOKHCuxG4xivyJt5PiijSx2kzNjZ1t1RXVrWJ' +
'L24CCSF29dEIWx+X1Vd39ZTy1tYbGlpDT0fNJu1l/d0dqpfaV5Rd71wbN40WOkHf' +
'DW+rCus0VOUtHaFlh6uNos0bxbaWDy8Vn1VZXTlWUb2b91eV7XWsWhyNNNdm3lDK' +
'1bXdq4nbx7Rbv5Am864ZdqJRxa5GO9PHpglZjqn0ZulqqamNlzDq6nWqV1sqQ5uW' +
'ZG99lRlTP3z4/iVw968DnxaQhFS/xRpWdHxXGG53OF1uj9fnPydIimZYLnARDIUj' +
'fPRyp3+9Qly8TiRT6Uw2l38kyXqD0WS2WG12hzM5JTXNle7O8GR6fVnZObl5+QWF' +
'RcVbxsZRWmHCAvOst8tco5aatNJsFy++s9uUfT5Z/09J6SJln916dWt/Z3fvttsH' +
'h/9Tudi9k8Ydc9RU19bXNWzS2NzU0tre1tHZ1dPd2993wmaDA0OGnXR/wCEPzjtD' +
'jjhu2qURx1x+3R5nnI2W/z0OAAA='
),
};
});

View File

@ -0,0 +1,564 @@
define([
'core/ArrayUtilities',
'svg/SVGUtilities',
'svg/SVGShapes',
'./HandleeFontData',
], (
array,
svg,
SVGShapes,
Handlee
) => {
'use strict';
// TODO:
// * arrows
// * fade starter/terminator sometimes does not fully cover line
// * blocks (if/else/repeat/ref)
const FONT = Handlee.name;
const FONT_FAMILY = '"' + FONT + '",cursive';
const LINE_HEIGHT = 1.5;
const SETTINGS = {
titleMargin: 10,
outerMargin: 5,
agentMargin: 10,
actionMargin: 10,
minActionMargin: 3,
agentLineHighlightRadius: 4,
agentCap: {
box: {
padding: {
top: 5,
left: 10,
right: 10,
bottom: 5,
},
arrowBottom: 5 + 12 * 1.3 / 2,
labelAttrs: {
'font-family': FONT_FAMILY,
'font-size': 12,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
},
boxRenderer: null,
},
cross: {
size: 15,
render: null,
},
bar: {
height: 6,
render: null,
},
fade: {
width: 8,
height: 6,
},
none: {
height: 10,
},
},
connect: {
loopbackRadius: 6,
lineAttrs: {
'solid': {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 1,
},
'dash': {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 1,
'stroke-dasharray': '4, 2',
},
'wave': {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 1,
'stroke-linejoin': 'round',
'stroke-linecap': 'round',
'wave-width': 6,
'wave-height': 0.5,
},
},
arrow: {
single: {
width: 5,
height: 10,
attrs: {
'fill': '#000000',
'stroke-width': 0,
'stroke-linejoin': 'miter',
},
},
double: {
width: 4,
height: 6,
attrs: {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 1,
'stroke-linejoin': 'miter',
},
},
},
label: {
padding: 6,
margin: {top: 2, bottom: 1},
attrs: {
'font-family': FONT_FAMILY,
'font-size': 8,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
},
loopbackAttrs: {
'font-family': FONT_FAMILY,
'font-size': 8,
'line-height': LINE_HEIGHT,
},
},
mask: {
padding: {
top: 0,
left: 3,
right: 3,
bottom: 1,
},
},
},
block: {
margin: {
top: 0,
bottom: 0,
},
modes: {
'ref': {
boxAttrs: {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 1.5,
'rx': 2,
'ry': 2,
},
},
'': {
boxAttrs: {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 1.5,
'rx': 2,
'ry': 2,
},
},
},
section: {
padding: {
top: 3,
bottom: 2,
},
mode: {
padding: {
top: 1,
left: 3,
right: 3,
bottom: 0,
},
boxAttrs: {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 1,
'rx': 2,
'ry': 2,
},
labelAttrs: {
'font-family': FONT_FAMILY,
'font-weight': 'bold',
'font-size': 9,
'line-height': LINE_HEIGHT,
'text-anchor': 'left',
},
},
label: {
padding: {
top: 1,
left: 5,
right: 3,
bottom: 0,
},
labelAttrs: {
'font-family': FONT_FAMILY,
'font-size': 8,
'line-height': LINE_HEIGHT,
'text-anchor': 'left',
},
},
},
separator: {
attrs: {
'stroke': '#000000',
'stroke-width': 1.5,
'stroke-dasharray': '4, 2',
},
},
},
titleAttrs: {
'font-family': FONT_FAMILY,
'font-size': 20,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
'class': 'title',
},
agentLineAttrs: {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 1,
},
};
const NOTES = {
'text': {
margin: {top: 0, left: 2, right: 2, bottom: 0},
padding: {top: 2, left: 2, right: 2, bottom: 2},
overlap: {left: 10, right: 10},
boxRenderer: SVGShapes.renderBox.bind(null, {
'fill': '#FFFFFF',
}),
labelAttrs: {
'font-family': FONT_FAMILY,
'font-size': 8,
'line-height': LINE_HEIGHT,
},
},
'note': {
margin: {top: 0, left: 5, right: 5, bottom: 0},
padding: {top: 5, left: 5, right: 10, bottom: 5},
overlap: {left: 10, right: 10},
boxRenderer: null,
labelAttrs: {
'font-family': FONT_FAMILY,
'font-size': 8,
'line-height': LINE_HEIGHT,
},
},
'state': {
margin: {top: 0, left: 5, right: 5, bottom: 0},
padding: {top: 7, left: 7, right: 7, bottom: 7},
overlap: {left: 10, right: 10},
boxRenderer: null,
labelAttrs: {
'font-family': FONT_FAMILY,
'font-size': 8,
'line-height': LINE_HEIGHT,
},
},
};
class Random {
// xorshift+ 64-bit random generator
// https://en.wikipedia.org/wiki/Xorshift
constructor() {
this.s = new Uint32Array(4);
}
reset() {
// Initial seed: 0x00010203
for(let i = 0; i < 4; ++ i) {
this.s[i] = i;
}
}
nextFloat() {
/* jshint -W016 */ // bit-operations are part of the algorithm
const range = 0x100000000;
let x0 = this.s[0];
let x1 = this.s[1];
const y0 = this.s[2];
const y1 = this.s[3];
this.s[0] = y0;
this.s[1] = y1;
x0 ^= (x0 << 23) | (x1 >>> 9);
x1 ^= (x1 << 23);
this.s[2] = x0 ^ y0 ^ (x0 >>> 17) ^ (y0 >>> 26);
this.s[3] = (
x1 ^ y1 ^
(x0 << 15 | x1 >>> 17) ^
(y0 << 6 | y1 >>> 26)
);
return (((this.s[3] + y1) >>> 0) % range) / range;
}
}
const RIGHT = {};
const LEFT = {};
class SketchTheme {
constructor(handedness = RIGHT) {
if(handedness === RIGHT) {
this.name = 'sketch';
this.handedness = 1;
} else {
this.name = 'sketch left handed';
this.handedness = -1;
}
this.random = new Random();
Object.assign(this, SETTINGS);
// TODO: these mutate the global, not our copy
// (fine for now since we're a singleton for all practical purposes)
this.agentCap.cross.render = this.renderCross.bind(this);
this.agentCap.bar.render = this.renderBar.bind(this);
this.agentCap.box.boxRenderer = this.renderBox.bind(this);
NOTES.note.boxRenderer = this.renderNote.bind(this);
NOTES.state.boxRenderer = this.renderState.bind(this);
}
reset() {
this.random.reset();
}
addDefs(builder) {
builder('sketch_font', () => {
const style = document.createElement('style');
// For some uses, it is fine to load this font externally,
// but this fails when exporting as SVG / PNG (svg tags must
// have no external dependencies).
// const url = 'https://fonts.googleapis.com/css?family=' + FONT;
// style.innerText = '@import url("' + url + '")';
style.innerText = (
'@font-face{' +
'font-family:"' + Handlee.name + '";' +
'src:url("data:font/woff2;base64,' + Handlee.woff2 + '");' +
'}'
);
return style;
});
}
vary(range) {
if(!range) {
return 0;
}
// cosine distribution [-pi/2 pi/2] scaled to [-range range]
// int(cos(x))dx = sin(x)
// from -pi/2: sin(x) - sin(-pi/2) = sin(x) + 1
// total: sin(pi/2) + 1 = 2
// normalise to area 1: /2
// (sin(x) + 1) / 2
// inverse: p = (sin(x) + 1) / 2
// asin(2p - 1) = x
// normalise range
// x = asin(2p - 1) * range / (pi/2)
const rand = this.random.nextFloat(); // [0 1)
return Math.asin(rand * 2 - 1) * 2 * range / Math.PI;
}
lineNodes(p1, p2, {var1 = 1, var2 = 1, move = true}) {
const length = Math.sqrt(
(p2.x - p1.x) * (p2.x - p1.x) +
(p2.y - p1.y) * (p2.y - p1.y)
);
const rough = Math.min(Math.sqrt(length) * 0.2, 5);
const x1 = p1.x + this.vary(var1 * rough);
const y1 = p1.y + this.vary(var1 * rough);
const x2 = p2.x + this.vary(var2 * rough);
const y2 = p2.y + this.vary(var2 * rough);
// -1 = p1 higher, 1 = p2 higher
const upper = Math.max(-1, Math.min(1,
(y1 - y2) / (Math.abs(x1 - x2) + 0.001)
));
const frac = upper / 6 + 0.5;
// Line curve is to top / left (simulates right-handed drawing)
// or top / right (left-handed)
const curveX = (0.5 + this.vary(0.5)) * rough;
const curveY = (0.5 + this.vary(0.5)) * rough;
const xc = x1 * (1 - frac) + x2 * frac - curveX * this.handedness;
const yc = y1 * (1 - frac) + y2 * frac - curveY;
const nodes = (
(move ? ('M' + x1 + ' ' + y1) : '') +
'C' + xc + ' ' + yc +
',' + x2 + ' ' + y2 +
',' + x2 + ' ' + y2
);
return {
nodes,
p1: {x: x1, y: y1},
p2: {x: x2, y: y2},
};
}
renderLine(p1, p2, {var1 = 1, var2 = 1}) {
const line = this.lineNodes(p1, p2, {var1, var2});
const shape = svg.make('path', {
'd': line.nodes,
'stroke': 'rgba(0,0,0,0.7)',
'stroke-width': 0.8,
'fill': 'none',
});
return shape;
}
renderBox({x, y, width, height}, {fill = null} = {}) {
const lT = this.lineNodes(
{x, y},
{x: x + width, y},
{}
);
const lB = this.lineNodes(
{x: x + width, y: y + height},
{x, y: y + height},
{move: false}
);
const lR = this.lineNodes(
lT.p2,
lB.p1,
{var1: 0, var2: 0, move: false}
);
const lL = this.lineNodes(
lB.p2,
lT.p1,
{var1: 0, var2: 0.3, move: false}
);
const shape = svg.make('path', {
'd': lT.nodes + lR.nodes + lB.nodes + lL.nodes,
'stroke': 'rgba(0,0,0,0.7)',
'stroke-width': 0.8,
'fill': fill || '#FFFFFF',
});
return shape;
}
renderNote({x, y, width, height}) {
const flickSize = 5;
const lT = this.lineNodes(
{x, y},
{x: x + width - flickSize, y},
{}
);
const lF = this.lineNodes(
lT.p2,
{x: x + width, y: y + flickSize},
{move: false, var1: 0}
);
const lB = this.lineNodes(
{x: x + width, y: y + height},
{x, y: y + height},
{move: false}
);
const lR = this.lineNodes(
lF.p2,
lB.p1,
{var1: 0, var2: 0, move: false}
);
const lL = this.lineNodes(
lB.p2,
lT.p1,
{var1: 0, var2: 0.3, move: false}
);
const lF1 = this.lineNodes(
lF.p1,
{x: x + width - flickSize, y: y + flickSize},
{var1: 0.3}
);
const lF2 = this.lineNodes(
lF1.p2,
lF.p2,
{var1: 0, move: false}
);
const g = svg.make('g');
g.appendChild(svg.make('path', {
'd': (
lT.nodes +
lF.nodes +
lR.nodes +
lB.nodes +
lL.nodes
),
'stroke': 'rgba(0,0,0,0.7)',
'stroke-width': 0.8,
'fill': '#FFFFFF',
}));
g.appendChild(svg.make('path', {
'd': lF1.nodes + lF2.nodes,
'stroke': 'rgba(0,0,0,0.7)',
'stroke-width': 0.8,
'fill': 'none',
}));
return g;
}
renderState({x, y, width, height}) {
// TODO: rounded corners
return this.renderBox({x, y, width, height});
}
renderBar({x, y, width, height}) {
return this.renderBox({x, y, width, height}, {fill: '#000000'});
}
renderCross({x, y, radius}) {
const r1 = (this.vary(0.2) + 1) * radius;
const l1 = this.lineNodes(
{x: x - r1, y: y - r1},
{x: x + r1, y: y + r1},
{}
);
const r2 = (this.vary(0.2) + 1) * radius;
const l2 = this.lineNodes(
{x: x + r2, y: y - r2},
{x: x - r2, y: y + r2},
{}
);
return svg.make('path', {
'd': l1.nodes + l2.nodes,
'stroke': 'rgba(0,0,0,0.7)',
'stroke-width': 0.8,
'fill': 'none',
});
}
getNote(type) {
return NOTES[type];
}
drawAgentLine(container, {x, y0, y1, width, className}) {
if(width > 0) {
const shape = this.renderBox({
x: x - width / 2,
y: y0,
width,
height: y1 - y0,
}, {fill: 'none'});
shape.setAttribute('class', className);
container.appendChild(shape);
} else {
const shape = this.renderLine(
{x, y: y0},
{x, y: y1},
{}
);
shape.setAttribute('class', className);
container.appendChild(shape);
}
}
}
SketchTheme.RIGHT = RIGHT;
SketchTheme.LEFT = LEFT;
return SketchTheme;
});

View File

@ -0,0 +1,18 @@
defineDescribe('Sketch Theme', ['./Sketch'], (SketchTheme) => {
'use strict';
const theme = new SketchTheme(SketchTheme.RIGHT);
const themeL = new SketchTheme(SketchTheme.LEFT);
it('has a name', () => {
expect(theme.name).toEqual('sketch');
});
it('has a left-handed variant', () => {
expect(themeL.name).toEqual('sketch left handed');
});
it('contains settings for the theme', () => {
expect(theme.outerMargin).toEqual(5);
});
});

View File

@ -13,6 +13,7 @@ define([
'sequence/Renderer_spec',
'sequence/themes/Basic_spec',
'sequence/themes/Chunky_spec',
'sequence/themes/Sketch_spec',
'sequence/components/AgentCap_spec',
'sequence/components/AgentHighlight_spec',
'sequence/components/Block_spec',

View File

@ -35,13 +35,6 @@ article > header > .sequence-diagram {
filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.2));
}
svg.fancy {
filter:
sepia(1)
hue-rotate(140deg)
drop-shadow(0 5px 5px rgba(0, 64, 128, 0.5));
}
article > h2 {
margin: 35px -20px 10px;
padding: 5px 20px 0;
@ -56,12 +49,23 @@ article > h3 {
clear: both;
}
article > h4 {
margin: 15px -20px 10px;
padding: 5px 20px 0;
font: 1.1em 'Vollkorn', serif;
}
article > p {
margin: 20px 0;
padding: 0;
text-align: justify;
}
article > ul {
margin: 10px 0;
padding: 0 0 0 30px;
}
a:link, a:visited {
color: #556688;
text-decoration: underline;

View File

@ -6,7 +6,11 @@
default-src 'none';
script-src 'self' https://cdnjs.cloudflare.com;
connect-src 'self';
style-src 'self' https://cdnjs.cloudflare.com;
style-src 'self'
https://cdnjs.cloudflare.com
'sha256-ru2GY2rXeOf7PQX5LzK3ckNo21FCDUoRc2f3i0QcD1g='
;
font-src 'self' data:;
img-src 'self' data: blob:;
form-action 'none';
">