1
0
mirror of https://github.com/octoleo/plantuml.git synced 2024-10-03 01:09:04 +00:00

Wip on gtile

This commit is contained in:
Arnaud Roques 2021-11-01 12:38:32 +01:00
parent 5bc2b96592
commit c94e204240
9 changed files with 404 additions and 52 deletions

View File

@ -35,6 +35,7 @@
*/
package net.sourceforge.plantuml.activitydiagram3;
import java.util.Collection;
import java.util.Objects;
import net.sourceforge.plantuml.ISkinParam;
@ -46,6 +47,7 @@ import net.sourceforge.plantuml.activitydiagram3.ftile.FtileKilled;
import net.sourceforge.plantuml.activitydiagram3.ftile.Swimlane;
import net.sourceforge.plantuml.activitydiagram3.gtile.Gtile;
import net.sourceforge.plantuml.activitydiagram3.gtile.GtileBox;
import net.sourceforge.plantuml.activitydiagram3.gtile.GtileWithNoteOpale;
import net.sourceforge.plantuml.command.CommandExecutionResult;
import net.sourceforge.plantuml.cucadiagram.Display;
import net.sourceforge.plantuml.cucadiagram.Stereotype;
@ -80,7 +82,16 @@ public class InstructionSimple extends MonoSwimable implements Instruction {
@Override
public Gtile createGtile(ISkinParam skinParam, StringBounder stringBounder) {
return GtileBox.create(stringBounder, colors.mute(skinParam), label, getSwimlaneIn(), style, stereotype);
GtileBox result = GtileBox.create(stringBounder, colors.mute(skinParam), label, getSwimlaneIn(), style,
stereotype);
if (hasNotes()) {
final Collection<PositionedNote> notes = getPositionedNotes();
if (notes.size() != 1)
throw new UnsupportedOperationException("wip");
return new GtileWithNoteOpale(result, notes.iterator().next(), skinParam, false);
}
return result;
}
@Override

View File

@ -94,21 +94,21 @@ public abstract class AbstractGtile extends AbstractTextBlock implements Gtile {
@Override
public UTranslate getCoord(String name) {
final Dimension2D dim = calculateDimension(stringBounder);
if (name.equals(GPoint.NORTH))
if (name.equals(GPoint.NORTH_HOOK))
return new UTranslate(dim.getWidth() / 2, 0);
if (name.equals(GPoint.SOUTH))
if (name.equals(GPoint.SOUTH_HOOK))
return new UTranslate(dim.getWidth() / 2, dim.getHeight());
if (name.equals(GPoint.WEST))
if (name.equals(GPoint.WEST_HOOK))
return new UTranslate(0, dim.getHeight() / 2);
if (name.equals(GPoint.EAST))
if (name.equals(GPoint.EAST_HOOK))
return new UTranslate(dim.getWidth(), dim.getHeight() / 2);
throw new UnsupportedOperationException();
}
@Override
public GPoint getGPoint(String name) {
if (name.equals(GPoint.NORTH) || name.equals(GPoint.SOUTH) || name.equals(GPoint.WEST)
|| name.equals(GPoint.EAST))
if (name.equals(GPoint.NORTH_HOOK) || name.equals(GPoint.SOUTH_HOOK) || name.equals(GPoint.WEST_HOOK)
|| name.equals(GPoint.EAST_HOOK))
return new GPoint(this, name);
throw new UnsupportedOperationException();
}

View File

@ -0,0 +1,137 @@
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2020, Arnaud Roques
*
* Project Info: http://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* http://plantuml.com/patreon (only 1$ per month!)
* http://plantuml.com/paypal
*
* This file is part of PlantUML.
*
* PlantUML is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PlantUML distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
* License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
*
* Original Author: Arnaud Roques
*
*
*/
package net.sourceforge.plantuml.activitydiagram3.gtile;
import java.awt.geom.Point2D;
import net.sourceforge.plantuml.Direction;
import net.sourceforge.plantuml.activitydiagram3.ftile.Arrows;
import net.sourceforge.plantuml.activitydiagram3.ftile.Snake;
import net.sourceforge.plantuml.graphic.HorizontalAlignment;
import net.sourceforge.plantuml.graphic.Rainbow;
import net.sourceforge.plantuml.graphic.TextBlock;
import net.sourceforge.plantuml.ugraphic.UGraphic;
import net.sourceforge.plantuml.ugraphic.UTranslate;
public class GConnectionLeftThenDownThenRight extends GAbstractConnection implements GConnectionTranslatable {
private final TextBlock textBlock;
private final UTranslate pos1;
private final UTranslate pos2;
private final double xright;
public GConnectionLeftThenDownThenRight(UTranslate pos1, GPoint gpoint1, UTranslate pos2, GPoint gpoint2,
double xright, TextBlock textBlock) {
super(gpoint1, gpoint2);
this.textBlock = textBlock;
this.pos1 = pos1;
this.pos2 = pos2;
this.xright = xright;
// See FtileFactoryDelegatorAssembly
}
@Override
public void drawTranslate(UGraphic ug, UTranslate translate1, UTranslate translate2) {
throw new UnsupportedOperationException();
}
@Override
public void drawU(UGraphic ug) {
ug.draw(getSimpleSnake());
}
// public double getMaxX(StringBounder stringBounder) {
// return getSimpleSnake().getMaxX(stringBounder);
// }
private Rainbow getInLinkRenderingColor() {
Rainbow color;
color = Rainbow.build(gpoint1.getGtile().skinParam());
// final LinkRendering linkRendering = tile.getInLinkRendering();
// if (linkRendering == null) {
// if (UseStyle.useBetaStyle()) {
// final Style style = getDefaultStyleDefinitionArrow()
// .getMergedStyle(skinParam().getCurrentStyleBuilder());
// return Rainbow.build(style, skinParam().getIHtmlColorSet(), skinParam().getThemeStyle());
// } else {
// color = Rainbow.build(skinParam());
// }
// } else {
// color = linkRendering.getRainbow();
// }
// if (color.size() == 0) {
// if (UseStyle.useBetaStyle()) {
// final Style style = getDefaultStyleDefinitionArrow()
// .getMergedStyle(skinParam().getCurrentStyleBuilder());
// return Rainbow.build(style, skinParam().getIHtmlColorSet(), skinParam().getThemeStyle());
// } else {
// color = Rainbow.build(skinParam());
// }
// }
return color;
}
private Snake getSimpleSnake() {
final Snake snake = Snake.create(getInLinkRenderingColor(), Arrows.asToLeft())
.emphasizeDirection(Direction.DOWN).withLabel(textBlock, HorizontalAlignment.LEFT);
final Point2D p1 = pos1.getTranslated(gpoint1.getPoint2D());
final Point2D p2 = pos2.getTranslated(gpoint2.getPoint2D());
// final double maxX = Math.max(p1.getX(), p2.getX());
final double maxX = xright;
snake.addPoint(p1);
snake.addPoint(new Point2D.Double(maxX, p1.getY()));
snake.addPoint(new Point2D.Double(maxX, p2.getY()));
snake.addPoint(p2);
return snake;
}
// @Override
// public void drawTranslate(UGraphic ug, UTranslate translate1, UTranslate translate2) {
// final Snake snake = Snake.create(color, Arrows.asToDown()).withLabel(textBlock, HorizontalAlignment.LEFT);
// final Point2D mp1a = translate1.getTranslated(p1);
// final Point2D mp2b = translate2.getTranslated(p2);
// final double middle = (mp1a.getY() + mp2b.getY()) / 2.0;
// snake.addPoint(mp1a);
// snake.addPoint(mp1a.getX(), middle);
// snake.addPoint(mp2b.getX(), middle);
// snake.addPoint(mp2b);
// ug.draw(snake);
//
// }
}

View File

@ -42,10 +42,15 @@ import net.sourceforge.plantuml.ugraphic.UTranslate;
public class GPoint {
public static final String NORTH = "NORTH";
public static final String SOUTH = "SOUTH";
public static final String WEST = "WEST";
public static final String EAST = "EAST";
public static final String NORTH_HOOK = "NORTH_HOOK";
public static final String SOUTH_HOOK = "SOUTH_HOOK";
public static final String WEST_HOOK = "WEST_HOOK";
public static final String EAST_HOOK = "EAST_HOOK";
public static final String NORTH_BORDER = "NORTH_BORDER";
public static final String SOUTH_BORDER = "SOUTH_BORDER";
public static final String WEST_BORDER = "WEST_BORDER";
public static final String EAST_BORDER = "EAST_BORDER";
private final Gtile gtile;
private final String name;

View File

@ -89,8 +89,8 @@ public class GtileAssembly extends GtileAssemblySimple {
@Override
public Collection<GConnection> getInnerConnections() {
final GConnection arrow = new GConnectionVerticalDown(getPos1(), tile1.getGPoint(GPoint.SOUTH), getPos2(),
tile2.getGPoint(GPoint.NORTH), textBlock);
final GConnection arrow = new GConnectionVerticalDown(getPos1(), tile1.getGPoint(GPoint.SOUTH_HOOK), getPos2(),
tile2.getGPoint(GPoint.NORTH_HOOK), textBlock);
return Collections.singletonList(arrow);
}

View File

@ -74,8 +74,8 @@ public class GtileAssemblySimple extends AbstractGtile {
this.dim1 = tile1.calculateDimension(stringBounder);
this.dim2 = tile2.calculateDimension(stringBounder);
final UTranslate vector1 = tile1.getCoord(GPoint.SOUTH);
final UTranslate vector2 = tile2.getCoord(GPoint.NORTH);
final UTranslate vector1 = tile1.getCoord(GPoint.SOUTH_HOOK);
final UTranslate vector2 = tile2.getCoord(GPoint.NORTH_HOOK);
// final UTranslate diff = vector1.compose(vector2.reverse());
// this.pos1 = diff.getDx() > 0 ? UTranslate.none() : UTranslate.dx(-diff.getDx());
@ -97,9 +97,9 @@ public class GtileAssemblySimple extends AbstractGtile {
@Override
public UTranslate getCoord(String name) {
if (name.equals(GPoint.NORTH))
if (name.equals(GPoint.NORTH_HOOK))
return getPos1().compose(tile1.getCoord(name));
if (name.equals(GPoint.SOUTH))
if (name.equals(GPoint.SOUTH_HOOK))
return getPos2().compose(tile2.getCoord(name));
throw new UnsupportedOperationException();
}

View File

@ -147,9 +147,20 @@ public class GtileIfHexagon extends GtileIfSimple {
it.set(tmp.compose(UTranslate.dy(height1)));
}
this.positionShape1 = this.getCoord(GPoint.NORTH).compose(shape1.getCoord(GPoint.NORTH).reverse());
this.positionShape2 = this.getCoord(GPoint.SOUTH).compose(shape2.getCoord(GPoint.SOUTH).reverse());
if (branches.size() == 1) {
final UTranslate tmp = positions.get(0);
positions.set(0, tmp.compose(UTranslate.dx(missingSpace())));
}
this.positionShape1 = this.getCoord(GPoint.NORTH_HOOK).compose(shape1.getCoord(GPoint.NORTH_HOOK).reverse());
this.positionShape2 = this.getCoord(GPoint.SOUTH_HOOK).compose(shape2.getCoord(GPoint.SOUTH_HOOK).reverse());
}
private double missingSpace() {
if (branches.size() != 1)
throw new IllegalStateException();
return 25;
}
private double getSuppHeightMargin() {
@ -161,8 +172,16 @@ public class GtileIfHexagon extends GtileIfSimple {
@Override
public Dimension2D calculateDimension(StringBounder stringBounder) {
final double height2 = shape2.calculateDimension(stringBounder).getHeight() + getSuppHeightMargin();
final Dimension2D result = super.calculateDimension(stringBounder);
return Dimension2DDouble.delta(result, 0, height2);
final Dimension2D nude = super.calculateDimension(stringBounder);
if (branches.size() > 1)
return Dimension2DDouble.delta(nude, 0, height2);
return Dimension2DDouble.delta(nude, missingSpace(), height2);
}
@Override
public UTranslate getCoord(String name) {
final UTranslate result = super.getCoord(name);
return result;
}
private HColor fontColor(FontParam param) {
@ -192,25 +211,32 @@ public class GtileIfHexagon extends GtileIfSimple {
@Override
public Collection<GConnection> getInnerConnections() {
if (branches.size() == 1) {
final GConnection arrow1 = new GConnectionVerticalDown(positionShape1, shape1.getGPoint(GPoint.SOUTH),
positions.get(0), gtiles.get(0).getGPoint(GPoint.NORTH), TextBlockUtils.EMPTY_TEXT_BLOCK);
final GConnection arrow1 = new GConnectionVerticalDown(positionShape1, shape1.getGPoint(GPoint.SOUTH_HOOK),
positions.get(0), gtiles.get(0).getGPoint(GPoint.NORTH_HOOK), TextBlockUtils.EMPTY_TEXT_BLOCK);
final GConnection arrow2 = new GConnectionVerticalDown(positions.get(0),
gtiles.get(0).getGPoint(GPoint.SOUTH), positionShape2, shape2.getGPoint(GPoint.NORTH),
gtiles.get(0).getGPoint(GPoint.SOUTH_HOOK), positionShape2, shape2.getGPoint(GPoint.NORTH_HOOK),
TextBlockUtils.EMPTY_TEXT_BLOCK);
return Arrays.asList(arrow1, arrow2);
final Dimension2D totalDim = calculateDimension(stringBounder);
final GConnection arrow3 = new GConnectionLeftThenDownThenRight(positionShape1,
shape1.getGPoint(GPoint.EAST_HOOK), positionShape2, shape2.getGPoint(GPoint.EAST_HOOK), totalDim.getWidth(),
TextBlockUtils.EMPTY_TEXT_BLOCK);
return Arrays.asList(arrow1, arrow2, arrow3);
// return Arrays.asList(arrow3);
} else if (branches.size() == 2) {
final GConnection arrow1 = new GConnectionHorizontalThenVerticalDown(positionShape1,
shape1.getGPoint(GPoint.WEST), positions.get(0), gtiles.get(0).getGPoint(GPoint.NORTH),
shape1.getGPoint(GPoint.WEST_HOOK), positions.get(0), gtiles.get(0).getGPoint(GPoint.NORTH_HOOK),
TextBlockUtils.EMPTY_TEXT_BLOCK);
final GConnection arrow2 = new GConnectionHorizontalThenVerticalDown(positionShape1,
shape1.getGPoint(GPoint.EAST), positions.get(1), gtiles.get(1).getGPoint(GPoint.NORTH),
shape1.getGPoint(GPoint.EAST_HOOK), positions.get(1), gtiles.get(1).getGPoint(GPoint.NORTH_HOOK),
TextBlockUtils.EMPTY_TEXT_BLOCK);
final GConnection arrow3 = new GConnectionVerticalDownThenHorizontal(positions.get(0),
gtiles.get(0).getGPoint(GPoint.SOUTH), positionShape2, shape2.getGPoint(GPoint.WEST),
gtiles.get(0).getGPoint(GPoint.SOUTH_HOOK), positionShape2, shape2.getGPoint(GPoint.WEST_HOOK),
TextBlockUtils.EMPTY_TEXT_BLOCK);
final GConnection arrow4 = new GConnectionVerticalDownThenHorizontal(positions.get(1),
gtiles.get(1).getGPoint(GPoint.SOUTH), positionShape2, shape2.getGPoint(GPoint.EAST),
gtiles.get(1).getGPoint(GPoint.SOUTH_HOOK), positionShape2, shape2.getGPoint(GPoint.EAST_HOOK),
TextBlockUtils.EMPTY_TEXT_BLOCK);
return Arrays.asList(arrow1, arrow2, arrow3, arrow4);

View File

@ -0,0 +1,173 @@
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2020, Arnaud Roques
*
* Project Info: http://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* http://plantuml.com/patreon (only 1$ per month!)
* http://plantuml.com/paypal
*
* This file is part of PlantUML.
*
* PlantUML is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PlantUML distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
* License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
*
* Original Author: Arnaud Roques
*
*
*/
package net.sourceforge.plantuml.activitydiagram3.gtile;
import java.awt.geom.Dimension2D;
import net.sourceforge.plantuml.AlignmentParam;
import net.sourceforge.plantuml.ColorParam;
import net.sourceforge.plantuml.Dimension2DDouble;
import net.sourceforge.plantuml.FontParam;
import net.sourceforge.plantuml.ISkinParam;
import net.sourceforge.plantuml.LineBreakStrategy;
import net.sourceforge.plantuml.UseStyle;
import net.sourceforge.plantuml.activitydiagram3.PositionedNote;
import net.sourceforge.plantuml.activitydiagram3.ftile.Swimlane;
import net.sourceforge.plantuml.creole.CreoleMode;
import net.sourceforge.plantuml.creole.Parser;
import net.sourceforge.plantuml.creole.Sheet;
import net.sourceforge.plantuml.creole.SheetBlock1;
import net.sourceforge.plantuml.creole.SheetBlock2;
import net.sourceforge.plantuml.creole.Stencil;
import net.sourceforge.plantuml.graphic.FontConfiguration;
import net.sourceforge.plantuml.graphic.HorizontalAlignment;
import net.sourceforge.plantuml.graphic.StringBounder;
import net.sourceforge.plantuml.graphic.TextBlock;
import net.sourceforge.plantuml.sequencediagram.NotePosition;
import net.sourceforge.plantuml.sequencediagram.NoteType;
import net.sourceforge.plantuml.skin.rose.Rose;
import net.sourceforge.plantuml.style.PName;
import net.sourceforge.plantuml.style.SName;
import net.sourceforge.plantuml.style.Style;
import net.sourceforge.plantuml.style.StyleSignature;
import net.sourceforge.plantuml.style.Styleable;
import net.sourceforge.plantuml.svek.image.Opale;
import net.sourceforge.plantuml.ugraphic.UGraphic;
import net.sourceforge.plantuml.ugraphic.UStroke;
import net.sourceforge.plantuml.ugraphic.UTranslate;
import net.sourceforge.plantuml.ugraphic.color.HColor;
public class GtileWithNoteOpale extends AbstractGtile implements Stencil, Styleable {
private final Gtile tile;
private final Opale opale;
private final NotePosition notePosition;
private final double suppSpace = 20;
private final Swimlane swimlaneNote;
private final UTranslate positionNote;
private final UTranslate positionTile;
private final Dimension2D dimNote;
private final Dimension2D dimTile;
public StyleSignature getDefaultStyleDefinition() {
return StyleSignature.of(SName.root, SName.element, SName.activityDiagram, SName.note);
}
public GtileWithNoteOpale(Gtile tile, PositionedNote note, ISkinParam skinParam, boolean withLink) {
super(tile.getStringBounder(), tile.skinParam());
this.swimlaneNote = note.getSwimlaneNote();
if (note.getColors() != null) {
skinParam = note.getColors().mute(skinParam);
}
this.tile = tile;
this.notePosition = note.getNotePosition();
if (note.getType() == NoteType.FLOATING_NOTE) {
withLink = false;
}
final Rose rose = new Rose();
final HColor noteBackgroundColor;
final HColor borderColor;
final FontConfiguration fc;
final double shadowing;
final LineBreakStrategy wrapWidth;
if (UseStyle.useBetaStyle()) {
final Style style = getDefaultStyleDefinition().getMergedStyle(skinParam.getCurrentStyleBuilder())
.eventuallyOverride(note.getColors());
noteBackgroundColor = style.value(PName.BackGroundColor).asColor(skinParam.getThemeStyle(),
getIHtmlColorSet());
borderColor = style.value(PName.LineColor).asColor(skinParam.getThemeStyle(), getIHtmlColorSet());
fc = style.getFontConfiguration(skinParam.getThemeStyle(), getIHtmlColorSet());
shadowing = style.value(PName.Shadowing).asDouble();
wrapWidth = style.wrapWidth();
} else {
noteBackgroundColor = rose.getHtmlColor(skinParam, ColorParam.noteBackground);
borderColor = rose.getHtmlColor(skinParam, ColorParam.noteBorder);
fc = new FontConfiguration(skinParam, FontParam.NOTE, null);
shadowing = skinParam.shadowing(null) ? 4 : 0;
wrapWidth = skinParam.wrapWidth();
}
final HorizontalAlignment align = skinParam.getHorizontalAlignment(AlignmentParam.noteTextAlignment, null,
false, null);
final Sheet sheet = Parser.build(fc, align, skinParam, CreoleMode.FULL).createSheet(note.getDisplay());
final TextBlock text = new SheetBlock2(new SheetBlock1(sheet, wrapWidth, skinParam.getPadding()), this,
new UStroke(1));
this.opale = new Opale(shadowing, borderColor, noteBackgroundColor, text, withLink);
this.dimNote = opale.calculateDimension(stringBounder);
this.dimTile = tile.calculateDimension(stringBounder);
final Dimension2D dimTotal = calculateDimension(stringBounder);
this.positionNote = new UTranslate(0, (dimTotal.getHeight() - dimNote.getHeight()) / 2);
this.positionTile = new UTranslate(dimNote.getWidth() + suppSpace,
(dimTotal.getHeight() - dimTile.getHeight()) / 2);
}
@Override
public UTranslate getCoord(String name) {
return tile.getCoord(name).compose(positionTile);
}
@Override
public Dimension2D calculateDimension(StringBounder stringBounder) {
final double height = Math.max(dimNote.getHeight(), dimTile.getHeight());
return new Dimension2DDouble(dimTile.getWidth() + dimNote.getWidth() + suppSpace, height);
}
@Override
public void drawU(UGraphic ug) {
ug.apply(positionNote).draw(opale);
ug.apply(positionTile).draw(tile);
}
@Override
public double getStartingX(StringBounder stringBounder, double y) {
return -opale.getMarginX1();
}
@Override
public double getEndingX(StringBounder stringBounder, double y) {
return opale.calculateDimension(stringBounder).getWidth() - opale.getMarginX1();
}
}

View File

@ -70,29 +70,29 @@ public class PSystemDonors extends PlainDiagram {
private static final int COLS = 6;
private static final int FREE_LINES = 6;
public static final String DONORS = "6mOE03mSRygAqa-2sUB1zgHs3y67oAViE8BbUK9nOX6dku3u0zmxFUutqbz4LtRlcsP4hTScvGoY-Y3e"
+ "0Uzk2-r3wNzz4EOiUH7ScH9SG0g8ukWI-P5kP3jzKcL82RxyZVJFg8c_kaepeSDd0_UtWMiJ21dDARPx"
+ "cJAlLqYb59-_yX7w138rLbrJbICmjvO2rb-oUYLzo_zDdQtBGo7CqjqAZC2eQ8JBFFOO_vygg70ATWur"
+ "xFHZEfLKBOTgLMApxKqHhQIvOrQeR_9Hg-QY62r5YdyqKl2BP8R6dCm-ILxqcyOVTHCwN9yAoN9FG6TR"
+ "FJgVz7QkAx7y8j67W2VuWcm6Hi1of2wY-RdfHiF30mxrkwZ6bfRSyoG9Wane8H_-KPYINf9dLy1Dmwi6"
+ "8FpUXWAI7DIeXhELkm6LbDYYqy9L5_k5kMKTbgTod3GyXLXucQW5L0aI7tH1PzGyRqtkxWPRfAT4Ozeg"
+ "bCy2EsYR2_h6bXB1V6qVcYNuAqQvbDGupdlltxKNWDtIygg7CKSuPw0kMoZ4U6k1-X7eiJbXYRwct5d8"
+ "WgYFqvi-TAuvgLM4Kt9BMQpJbx3bjPmVkgJ91YLv2YpwU-ANOWjui4bNKwe1NsYLf5dd2vYqpD-N7Yt_"
+ "VI-ufaJith1e8rmXwQ_V4PiP2W4wE8NPq4q136MLLXt51YBOBNHiW7O4qWdbo779H_oIFl8J06yk8Rh2"
+ "HaWJ-FFBaeSEBc8WKzUcS06m-PIYBXe-3x1GeIdeLbG9fRVny9qvMlOvRMnQ9ktcURl16FandQLOcAzG"
+ "uH669Nf6SyxRsaVO9NWM7wTvrNkMSnNWKk4zQkFQBpBWvmmP4IawNV5UpsoPXmd3BFQSbodTyyZWuA5R"
+ "3jpDeZdCA9E7B1i6XnDHhO-4_hOO_fOXcvtN6bIVOEjqdH7SpCY2rrkJMwevek6noI9EDWb8CnUlE2KB"
+ "5n2iPTd971Kb8j-nUX8ov7UgJy9IfIuqZTXOxYeyetiKvE6y9rOzvayVw5n0bMeJXAG5Crj2-SpyW0oi"
+ "LVJCjYHs__U51MFhsnCJX-BB9jf-SWF159WUmddW81B9G5gYbpKn0lZkZxlLqsiTvl6tBwSO1qaBIGHK"
+ "GQOptb1cwgazmiPwY_wVj6MB2t_-jKURd9hisdi8edKG5u08Bz1HBWWXdW9V7pPC1vnWYOiECbovPkbP"
+ "8B9W2trm4tLvcz6uunVBZSj5QyyFLVBTt0uNyy4tfBW85ULzJIcPjfb1DT8ltdMuoK7nTr5bM2INlJcP"
+ "Z8HDe8fpT-pMDjHZoG0pc0EL2UpT_LbPQ6ZC_Ggaod02vFijaaf6XHPMQreH2u4rSk7stfQJyepwJ4Rq"
+ "axNE6Nlrq3Z8rVKN8GhV3WbvGPYuNIHE1EZDDISMTFDD0FcYMYY9fHqTTkvWXVmF_WZUI3Xc6nzOv0cb"
+ "UPODENIJx8EpG7qWxo4ia8d5Ibr5B9HoF90YAPYpMiQpIDjf2yxe7K2dA1Z_tziGBYVH_8fcbrxLIEPo"
+ "pBsuWiY6-xj8R56oDI0T-rhcigqpSZld8Xrswd_WEyvtFg9kNKH00b5BWjUMWtHtOvzQ6ZQrvteLnt6P"
+ "fuF9BsHNjFELWQtnOWkodCr0YIS1X1q85P9NuLVx1wuvvyYlzwbI6AZXUUOMTEEvDp0MAvR4KzA0waqM"
+ "vTht1-xanGc54ui5BYmLRlGmgLihd6h7JPlrf569NALYl8Snkxcav9yg1fNIDRcze6P9Q5rtbj4Pd6nX"
+ "Bi_xl7jX1fNw9UfJmHu33TU7RvmXlRAiOXyX50RLk31POW00";
public static final String DONORS = "6sOE03mSS7Dt1Og5J5Uh3hcJits4ZufNcNRmsK-DFu8Mfx004WGrllMtt9JI0K46z1ewUipjEptkDz9V"
+ "H5UwzqqpHgtN9kKCeleWw07lRWljG-b_VH3cB7aHt9aIN40AYEBeOlcHRkGuVL9bI2baDAVOxcNAl5uX"
+ "bL9y_if7w1Ducpuwpvr6qQZ_14uVjNqKUr_pyskkhY4MYkLvWX8ewJ8XtKt-nzoQwNi_RQJZ9_3xf44L"
+ "Yw8jtPQiaJOTOzxNjG0TqwxLSYOVJMfMnRYhIB6FHXfsaKneoyNaS-H5dpJ-g9lm4VN8a8ow0ZhBwkZy"
+ "kRUlxudvbui-ucG9lg5eWH6m7bcBQ74UUj2mSC3GiejYR5NXo9DqIEH12tdpZy8KHahEBeERXbK38EpU"
+ "WGeaEQXD3MSx3mEgJB55NmWtNUeFbixxibAgq9M6Pn8RAnErW2f5OeCkwCwSjw-cQlK0HwIdH2riFShd"
+ "EFRI3olwffSImVhQEYnBy5UKiakfwRXzf_hTfmHmXBHyvyEO8fGJQCfW2cNyMe76LUYkjM2BZgiSBAH3"
+ "rCSjDdtOt7XeHOHZCgL2hUENiPrgXJzqAS7693m95lAUzgNK0WussQf92-0BhKfwx7m1oL3mSj3ZE_w-"
+ "TxYcKfAy8T56E4FIJy-p7SrG2D2nPSo6RgR0OjnbLM-n1H7iKJewG3j2qWbdyt7PctsGVkKd0Dv2GdIx"
+ "JHpjuDzVkZ9rv29D97HJ6Yk0jQSLTT6mUGI3Mar5DsDACBsDSzpfex7F6SUQPT4cpriDniXlrEcAXUKo"
+ "5PbkMAcdDnU-OzjBknJtoHYykjqdHrq5k9HxJzcuzuiCnBcQ35IAJ7Uy-vktB3C8YrdySxs2qhDc3suU"
+ "kZFOpcBHCcQsClMG5S3WK99gVIGQIuYHtWx85lYr4Jq2oAznf0OFC_BWzMF9DTKSqx3OP14ZcuJ8jZQl"
+ "s3k7SXwmv-LFScIKIBduwb58oE_OM4IqojeeRTYOxmeyGr8KvEMyUrOzmRywC7K1rQcY82WjMAiajA-U"
+ "3v21UsSzIos9CrvnOK7TwdPWcg3uIXHjFRc1OWhC314-CF14Cf2LiakV617uxe-xpIEb3ktusvTTZ8Ea"
+ "kIG6gY3Xr3TK4mtM7k78Uel_VsxTO2V-_5zhskJKRi_UGn0zWOG0X7k18UC24E8fyFLcmnp0AIm9qK72"
+ "bQlMdWUY6Ena37PHTompgZT-PxaPkMdjzKDLdffIL3Zf1x9V18lskgPKwJO4dMgoFxmHBd8mjd-9AhDI"
+ "tBlAQ4Tb97mIYZY-u_HoG3EM3j22DL17WNztVrOMQXfhlqAfF9m0-MP3KgcPheNLbjR6Gc2ZNDZVMxTJ"
+ "pipFoi0-XzM5uweUsWOfRgGOKVWsA2xF21CpYQI927PFoOa78tpZGRwe5afYQLkxdJbO8VzZFuOt4WxP"
+ "8GrqwZ6bFDs07BP9zx5Le3-Gzn0UoCHYdQui5aebF90YAPYhM2Oof4sq6UVs1Z0e2iR_znQKAqmqFw7P"
+ "fLOsahaCQpSf8FAX_iuIM-HihKZ7lhlciitJS_lM9XFisF_0ZvzlVrJTV1C1TKHt9NZTFiY76zjKLx15"
+ "ovibtCTbiMuclunBqIvH3MpUTpPgA9k1Gaf2Y7WWL4XVutvDVyAMSu5yUlrbBWpKy3nH3JruRWPMa6qq"
+ "l4eQKBt8ujpwVijDtZcGEV7OuC8gOwU7EJrQPgXjDsssa44bSkUAiPx1x1ubLJwLCQYKhgaQ2sgMXDPb"
+ "PmSw0-Cjv8hvlNrhOGQN-YNcCy4f1fgINXh8X0Pa4Kb13KHNoNxLYZEaPwPLuYrMjfA6gMvKGwFTO57S" + "aIUUpER31W00";
/*
* Special thanks to our sponsors and donors: