zotica 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/exec/zotica +99 -0
- data/exec/zoticaf +52 -0
- data/source/zotica.rb +14 -0
- data/source/zotica/builder.rb +979 -0
- data/source/zotica/parser.rb +543 -0
- data/source/zotica/resource/font.otf +0 -0
- data/source/zotica/resource/font/main.json +1472 -0
- data/source/zotica/resource/font/main.ttf +0 -0
- data/source/zotica/resource/font/math.json +5040 -0
- data/source/zotica/resource/font/math.ttf +0 -0
- data/source/zotica/resource/math.json +652 -0
- data/source/zotica/resource/script/accent.js +42 -0
- data/source/zotica/resource/script/diagram.js +481 -0
- data/source/zotica/resource/script/fence.js +163 -0
- data/source/zotica/resource/script/main.js +172 -0
- data/source/zotica/resource/script/radical.js +43 -0
- data/source/zotica/resource/script/subsuper.js +90 -0
- data/source/zotica/resource/script/tree.js +68 -0
- data/source/zotica/resource/script/underover.js +9 -0
- data/source/zotica/resource/script/wide.js +121 -0
- data/source/zotica/resource/style/math.scss +680 -0
- data/source/zotica/resource/style/times.scss +11 -0
- metadata +117 -0
| @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            //
         | 
| 2 | 
            +
             | 
| 3 | 
            +
             | 
| 4 | 
            +
            class AccentModifier extends Modifier {
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              modify(element) {
         | 
| 7 | 
            +
                let baseWrapperElement = this.findChild(element, "math-basewrap");
         | 
| 8 | 
            +
                let overElement = this.findChild(element, "math-over");
         | 
| 9 | 
            +
                let contentElement = baseWrapperElement.children[0];
         | 
| 10 | 
            +
                let parentElements = [baseWrapperElement.children[1], overElement];
         | 
| 11 | 
            +
                for (let position of [0, 1]) {
         | 
| 12 | 
            +
                  let parentElement = parentElements[position];
         | 
| 13 | 
            +
                  if (parentElement) {
         | 
| 14 | 
            +
                    this.modifyPosition(contentElement, parentElement, position);
         | 
| 15 | 
            +
                  }
         | 
| 16 | 
            +
                }
         | 
| 17 | 
            +
              }
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              modifyPosition(contentElement, parentElement, position) {
         | 
| 20 | 
            +
                let charElement = contentElement.children[0];
         | 
| 21 | 
            +
                let string = charElement && charElement.textContent;
         | 
| 22 | 
            +
                if (string && string.length == 1 && contentElement.children.length == 1) {
         | 
| 23 | 
            +
                  let char = string.substring(0, 1);
         | 
| 24 | 
            +
                  let shift = DATA["shift"][position][char];
         | 
| 25 | 
            +
                  if (shift) {
         | 
| 26 | 
            +
                    let marginString;
         | 
| 27 | 
            +
                    if (position == 0) {
         | 
| 28 | 
            +
                      marginString = window.getComputedStyle(parentElement).marginTop;
         | 
| 29 | 
            +
                    } else {
         | 
| 30 | 
            +
                      marginString = window.getComputedStyle(parentElement).marginBottom;
         | 
| 31 | 
            +
                    }
         | 
| 32 | 
            +
                    let margin = parseFloat(marginString) / this.getFontSize(parentElement);
         | 
| 33 | 
            +
                    if (position == 0) {
         | 
| 34 | 
            +
                      parentElement.style.marginTop = "" + (margin + shift) + "em";
         | 
| 35 | 
            +
                    } else {
         | 
| 36 | 
            +
                      parentElement.style.marginBottom = "" + (margin - shift) + "em";
         | 
| 37 | 
            +
                    }
         | 
| 38 | 
            +
                  }
         | 
| 39 | 
            +
                }
         | 
| 40 | 
            +
              }
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            }
         | 
| @@ -0,0 +1,481 @@ | |
| 1 | 
            +
            //
         | 
| 2 | 
            +
             | 
| 3 | 
            +
             | 
| 4 | 
            +
            const ANGLE_EPSILON = Math.PI / 90;
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            const UNIT = 1 / 18;
         | 
| 7 | 
            +
            const ARROW_MARGIN = 8 * UNIT;
         | 
| 8 | 
            +
            const LABEL_DISTANCE = 5 * UNIT;
         | 
| 9 | 
            +
             | 
| 10 | 
            +
             | 
| 11 | 
            +
            class DiagramModifier extends Modifier {
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              modify(element) {
         | 
| 14 | 
            +
                let arrowElements = this.findChildren(element, "math-arrow");
         | 
| 15 | 
            +
                let cellElements = this.findChildren(element, "math-cellwrap").map((child) => child.children[0]);
         | 
| 16 | 
            +
                let backgroundColor = this.getBackgroundColor(element);
         | 
| 17 | 
            +
                let graphic = this.createGraphic(element);
         | 
| 18 | 
            +
                element.appendChild(graphic);
         | 
| 19 | 
            +
                for (let arrowElement of arrowElements) {
         | 
| 20 | 
            +
                  let arrowSpec = this.determineArrowSpec(graphic, arrowElement, cellElements, arrowElements);
         | 
| 21 | 
            +
                  let arrows = this.createArrows(arrowSpec, backgroundColor);
         | 
| 22 | 
            +
                  graphic.append(...arrows);
         | 
| 23 | 
            +
                  let labelPoint = this.determineLabelPoint(graphic, arrowElement, arrowSpec);
         | 
| 24 | 
            +
                  let fontRatio = this.getFontSize(graphic) / this.getFontSize(arrowElement);
         | 
| 25 | 
            +
                  arrowElement.style.left = "" + (labelPoint[0] * fontRatio) + "em";
         | 
| 26 | 
            +
                  arrowElement.style.top = "" + (labelPoint[1] * fontRatio) + "em";
         | 
| 27 | 
            +
                }
         | 
| 28 | 
            +
                let pathElements = Array.from(graphic.children).filter((child) => child.localName == "path");
         | 
| 29 | 
            +
                let extrusion = this.calcExtrusion(graphic, arrowElements.concat(pathElements));
         | 
| 30 | 
            +
                element.style.marginTop = "" + extrusion.top + "em";
         | 
| 31 | 
            +
                element.style.marginBottom = "" + extrusion.bottom + "em";
         | 
| 32 | 
            +
                element.style.marginLeft = "" + extrusion.left + "em";
         | 
| 33 | 
            +
                element.style.marginRight = "" + extrusion.right + "em";
         | 
| 34 | 
            +
              }
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              determineArrowSpec(graphic, arrowElement, cellElements, arrowElements) {
         | 
| 37 | 
            +
                let spec = {};
         | 
| 38 | 
            +
                let startConfigString = arrowElement.getAttribute("data-start");
         | 
| 39 | 
            +
                let endConfigString = arrowElement.getAttribute("data-end");
         | 
| 40 | 
            +
                let startConfig = this.parseEdgeConfig(startConfigString, graphic, cellElements, arrowElements);
         | 
| 41 | 
            +
                let endConfig = this.parseEdgeConfig(endConfigString, graphic, cellElements, arrowElements);
         | 
| 42 | 
            +
                if (startConfig && endConfig) {
         | 
| 43 | 
            +
                  let bendAngleString = arrowElement.getAttribute("data-bend");
         | 
| 44 | 
            +
                  if (bendAngleString) {
         | 
| 45 | 
            +
                    spec.bendAngle = parseFloat(bendAngleString) * Math.PI / 180;
         | 
| 46 | 
            +
                  }
         | 
| 47 | 
            +
                  let shiftString = arrowElement.getAttribute("data-shift");
         | 
| 48 | 
            +
                  if (shiftString) {
         | 
| 49 | 
            +
                    spec.shift = parseFloat(shiftString) * UNIT;
         | 
| 50 | 
            +
                  }
         | 
| 51 | 
            +
                  let startElement = startConfig.element;
         | 
| 52 | 
            +
                  let endElement = endConfig.element;
         | 
| 53 | 
            +
                  let startDimension = startConfig.dimension;
         | 
| 54 | 
            +
                  let endDimension = endConfig.dimension;
         | 
| 55 | 
            +
                  if (startConfig.point) {
         | 
| 56 | 
            +
                    spec.startPoint = startConfig.point;
         | 
| 57 | 
            +
                  } else {
         | 
| 58 | 
            +
                    spec.startPoint = this.calcEdgePoint(startDimension, endDimension, spec.bendAngle, spec.shift);
         | 
| 59 | 
            +
                  }
         | 
| 60 | 
            +
                  if (endConfig.point) {
         | 
| 61 | 
            +
                    spec.endPoint = endConfig.point;
         | 
| 62 | 
            +
                  } else {
         | 
| 63 | 
            +
                    spec.endPoint = this.calcEdgePoint(endDimension, startDimension, -spec.bendAngle, -spec.shift);
         | 
| 64 | 
            +
                  }
         | 
| 65 | 
            +
                } else {
         | 
| 66 | 
            +
                  spec.startPoint = [0, 0];
         | 
| 67 | 
            +
                  spec.endPoint = [0, 0];
         | 
| 68 | 
            +
                }
         | 
| 69 | 
            +
                let labelPositionString = arrowElement.getAttribute("data-pos");
         | 
| 70 | 
            +
                if (labelPositionString) {
         | 
| 71 | 
            +
                  spec.labelPosition = parseFloat(labelPositionString) / 100;
         | 
| 72 | 
            +
                }
         | 
| 73 | 
            +
                let lineCountString = arrowElement.getAttribute("data-line");
         | 
| 74 | 
            +
                if (lineCountString) {
         | 
| 75 | 
            +
                  spec.lineCount = parseInt(lineCountString);
         | 
| 76 | 
            +
                }
         | 
| 77 | 
            +
                let dashed = !!arrowElement.getAttribute("data-dash");
         | 
| 78 | 
            +
                if (dashed) {
         | 
| 79 | 
            +
                  spec.dashed = true;
         | 
| 80 | 
            +
                }
         | 
| 81 | 
            +
                let inverted = !!arrowElement.getAttribute("data-inv");
         | 
| 82 | 
            +
                if (inverted) {
         | 
| 83 | 
            +
                  spec.inverted = true;
         | 
| 84 | 
            +
                }
         | 
| 85 | 
            +
                let mark = !!arrowElement.getAttribute("data-mark");
         | 
| 86 | 
            +
                if (mark) {
         | 
| 87 | 
            +
                  spec.mark = true;
         | 
| 88 | 
            +
                }
         | 
| 89 | 
            +
                let tipKindsString = arrowElement.getAttribute("data-tip");
         | 
| 90 | 
            +
                spec.tipKinds = this.parseTipKinds(tipKindsString, spec.lineCount);
         | 
| 91 | 
            +
                spec.intrudedStartPoint = this.calcIntrudedPoint(spec.startPoint, spec.endPoint, spec.bendAngle, spec.tipKinds.start);
         | 
| 92 | 
            +
                spec.intrudedEndPoint = this.calcIntrudedPoint(spec.endPoint, spec.startPoint, -spec.bendAngle, spec.tipKinds.end);
         | 
| 93 | 
            +
                return spec;
         | 
| 94 | 
            +
              }
         | 
| 95 | 
            +
             | 
| 96 | 
            +
              determineLabelPoint(graphic, labelElement, arrowSpec) {
         | 
| 97 | 
            +
                let labelDimension = this.calcDimension(graphic, labelElement);
         | 
| 98 | 
            +
                let startPoint = arrowSpec.startPoint;
         | 
| 99 | 
            +
                let endPoint = arrowSpec.endPoint;
         | 
| 100 | 
            +
                let bendAngle = arrowSpec.bendAngle;
         | 
| 101 | 
            +
                let position = (arrowSpec.labelPosition == undefined) ? 0.5 : arrowSpec.labelPosition;
         | 
| 102 | 
            +
                let basePoint = [0, 0];
         | 
| 103 | 
            +
                let angle = 0;
         | 
| 104 | 
            +
                if (bendAngle != undefined) {
         | 
| 105 | 
            +
                  let controlPoint = this.calcControlPoint(startPoint, endPoint, bendAngle);
         | 
| 106 | 
            +
                  let basePointX = (1 - position) * (1 - position) * startPoint[0] + 2 * (1 - position) * position * controlPoint[0] + position * position * endPoint[0];
         | 
| 107 | 
            +
                  let basePointY = (1 - position) * (1 - position) * startPoint[1] + 2 * (1 - position) * position * controlPoint[1] + position * position * endPoint[1];
         | 
| 108 | 
            +
                  let speedX = -2 * (1 - position) * startPoint[0] + 2 * (1 - 2 * position) * controlPoint[0] + 2 * position * endPoint[0];
         | 
| 109 | 
            +
                  let speedY = -2 * (1 - position) * startPoint[1] + 2 * (1 - 2 * position) * controlPoint[1] + 2 * position * endPoint[1];
         | 
| 110 | 
            +
                  basePoint = [basePointX, basePointY];
         | 
| 111 | 
            +
                  angle = this.calcAngle([0, 0], [speedX, speedY]) + Math.PI / 2;
         | 
| 112 | 
            +
                } else {
         | 
| 113 | 
            +
                  let basePointX = (1 - position) * startPoint[0] + position * endPoint[0];
         | 
| 114 | 
            +
                  let basePointY = (1 - position) * startPoint[1] + position * endPoint[1];
         | 
| 115 | 
            +
                  basePoint = [basePointX, basePointY];
         | 
| 116 | 
            +
                  angle = this.calcAngle(startPoint, endPoint) + Math.PI / 2;
         | 
| 117 | 
            +
                }
         | 
| 118 | 
            +
                if (arrowSpec.inverted) {
         | 
| 119 | 
            +
                  angle += Math.PI;
         | 
| 120 | 
            +
                }
         | 
| 121 | 
            +
                angle = this.normalizeAngle(angle);
         | 
| 122 | 
            +
                let point;
         | 
| 123 | 
            +
                if (arrowSpec.mark) {
         | 
| 124 | 
            +
                  let pointX = basePoint[0] + labelDimension.northWest[0] - labelDimension.center[0];
         | 
| 125 | 
            +
                  let pointY = basePoint[1] + labelDimension.northWest[0] - labelDimension.center[1];
         | 
| 126 | 
            +
                  point = [pointX, pointY];
         | 
| 127 | 
            +
                } else {
         | 
| 128 | 
            +
                  point = this.calcLabelPoint(basePoint, labelDimension, angle, arrowSpec.lineCount);
         | 
| 129 | 
            +
                }
         | 
| 130 | 
            +
                return point;
         | 
| 131 | 
            +
              }
         | 
| 132 | 
            +
             | 
| 133 | 
            +
              calcEdgePoint(baseDimension, destinationDimension, bendAngle, shift) {
         | 
| 134 | 
            +
                let margin = ARROW_MARGIN;
         | 
| 135 | 
            +
                let angle = this.calcAngle(baseDimension.center, destinationDimension.center) + (bendAngle || 0);
         | 
| 136 | 
            +
                let shiftAngle = angle + Math.PI / 2;
         | 
| 137 | 
            +
                let southWestAngle = this.calcAngle(baseDimension.center, baseDimension.southWestMargined);
         | 
| 138 | 
            +
                let southEastAngle = this.calcAngle(baseDimension.center, baseDimension.southEastMargined);
         | 
| 139 | 
            +
                let northEastAngle = this.calcAngle(baseDimension.center, baseDimension.northEastMargined);
         | 
| 140 | 
            +
                let northWestAngle = this.calcAngle(baseDimension.center, baseDimension.northWestMargined);
         | 
| 141 | 
            +
                let x = 0;
         | 
| 142 | 
            +
                let y = 0;
         | 
| 143 | 
            +
                angle = this.normalizeAngle(angle);
         | 
| 144 | 
            +
                shiftAngle = this.normalizeAngle(shiftAngle);
         | 
| 145 | 
            +
                if (angle >= southWestAngle && angle <= southEastAngle) {
         | 
| 146 | 
            +
                  x = baseDimension.center[0] + (baseDimension.center[1] - baseDimension.southMargined[1]) / Math.tan(angle);
         | 
| 147 | 
            +
                  y = baseDimension.southMargined[1];
         | 
| 148 | 
            +
                } else if (angle >= southEastAngle && angle <= northEastAngle) {
         | 
| 149 | 
            +
                  x = baseDimension.eastMargined[0];
         | 
| 150 | 
            +
                  y = baseDimension.center[1] + (baseDimension.center[0] - baseDimension.eastMargined[0]) * Math.tan(angle);
         | 
| 151 | 
            +
                } else if (angle >= northEastAngle && angle <= northWestAngle) {
         | 
| 152 | 
            +
                  x = baseDimension.center[0] + (baseDimension.center[1] - baseDimension.northMargined[1]) / Math.tan(angle);
         | 
| 153 | 
            +
                  y = baseDimension.northMargined[1];
         | 
| 154 | 
            +
                } else if (angle >= northWestAngle || angle <= southWestAngle) {
         | 
| 155 | 
            +
                  x = baseDimension.westMargined[0];
         | 
| 156 | 
            +
                  y = baseDimension.center[1] + (baseDimension.center[0] - baseDimension.westMargined[0]) * Math.tan(angle);
         | 
| 157 | 
            +
                }
         | 
| 158 | 
            +
                if (shift) {
         | 
| 159 | 
            +
                  x += Math.cos(shiftAngle) * shift;
         | 
| 160 | 
            +
                  y -= Math.sin(shiftAngle) * shift;
         | 
| 161 | 
            +
                }
         | 
| 162 | 
            +
                return [x, y];
         | 
| 163 | 
            +
              }
         | 
| 164 | 
            +
             | 
| 165 | 
            +
              calcIntrudedPoint(basePoint, destinationPoint, bendAngle, tipKind) {
         | 
| 166 | 
            +
                if (tipKind != "none") {
         | 
| 167 | 
            +
                  let angle = this.calcAngle(basePoint, destinationPoint) + (bendAngle || 0);
         | 
| 168 | 
            +
                  let distance = DATA["arrow"][tipKind]["extrusion"];
         | 
| 169 | 
            +
                  angle = this.normalizeAngle(angle);
         | 
| 170 | 
            +
                  let intrudedPointX = basePoint[0] + distance * Math.cos(angle);
         | 
| 171 | 
            +
                  let intrudedPointY = basePoint[1] - distance * Math.sin(angle);
         | 
| 172 | 
            +
                  let intrudedPoint = [intrudedPointX, intrudedPointY];
         | 
| 173 | 
            +
                  return intrudedPoint;
         | 
| 174 | 
            +
                } else {
         | 
| 175 | 
            +
                  return basePoint;
         | 
| 176 | 
            +
                }
         | 
| 177 | 
            +
              }
         | 
| 178 | 
            +
             | 
| 179 | 
            +
              calcLabelPoint(basePoint, labelDimension, angle, lineCount) {
         | 
| 180 | 
            +
                let distance = LABEL_DISTANCE + ((lineCount || 1) - 1) * 0.09;
         | 
| 181 | 
            +
                let direction = "east";
         | 
| 182 | 
            +
                if (angle <= -Math.PI + ANGLE_EPSILON) {
         | 
| 183 | 
            +
                  direction = "east";
         | 
| 184 | 
            +
                } else if (angle <= -Math.PI / 2 - ANGLE_EPSILON) {
         | 
| 185 | 
            +
                  direction = "northEast";
         | 
| 186 | 
            +
                } else if (angle <= -Math.PI / 2 + ANGLE_EPSILON) {
         | 
| 187 | 
            +
                  direction = "north";
         | 
| 188 | 
            +
                } else if (angle <= -ANGLE_EPSILON) {
         | 
| 189 | 
            +
                  direction = "northWest";
         | 
| 190 | 
            +
                } else if (angle <= ANGLE_EPSILON) {
         | 
| 191 | 
            +
                  direction = "west";
         | 
| 192 | 
            +
                } else if (angle <= Math.PI / 2 - ANGLE_EPSILON) {
         | 
| 193 | 
            +
                  direction = "southWest";
         | 
| 194 | 
            +
                } else if (angle <= Math.PI / 2 + ANGLE_EPSILON) {
         | 
| 195 | 
            +
                  direction = "south";
         | 
| 196 | 
            +
                } else if (angle <= Math.PI - ANGLE_EPSILON) {
         | 
| 197 | 
            +
                  direction = "southEast";
         | 
| 198 | 
            +
                } else {
         | 
| 199 | 
            +
                  direction = "east";
         | 
| 200 | 
            +
                }
         | 
| 201 | 
            +
                let x = basePoint[0] + Math.cos(angle) * distance + labelDimension.northWest[0] - labelDimension[direction][0];
         | 
| 202 | 
            +
                let y = basePoint[1] - Math.sin(angle) * distance + labelDimension.northWest[1] - labelDimension[direction][1];
         | 
| 203 | 
            +
                return [x, y];
         | 
| 204 | 
            +
              }
         | 
| 205 | 
            +
             | 
| 206 | 
            +
              parseEdgeConfig(string, graphic, cellElements, arrowElements) {
         | 
| 207 | 
            +
                let config = null;
         | 
| 208 | 
            +
                let match = string.match(/(?:(\d+)|([A-Za-z]\w*))(?:\:(\w+))?/);
         | 
| 209 | 
            +
                if (match) {
         | 
| 210 | 
            +
                  let element = null;
         | 
| 211 | 
            +
                  if (match[1]) {
         | 
| 212 | 
            +
                    let number = parseInt(match[1]) - 1;
         | 
| 213 | 
            +
                    element = cellElements[number];
         | 
| 214 | 
            +
                  } else if (match[2]) {
         | 
| 215 | 
            +
                    let candidates = cellElements.map((candidate) => candidate.parentNode).concat(arrowElements);
         | 
| 216 | 
            +
                    let name = match[2];
         | 
| 217 | 
            +
                    element = candidates.find((candidate) => candidate.getAttribute("data-name") == name);
         | 
| 218 | 
            +
                  }
         | 
| 219 | 
            +
                  if (element) {
         | 
| 220 | 
            +
                    let dimension = this.calcDimension(graphic, element);
         | 
| 221 | 
            +
                    let point = null;
         | 
| 222 | 
            +
                    if (match[3]) {
         | 
| 223 | 
            +
                      point = this.parsePoint(match[3], dimension);
         | 
| 224 | 
            +
                    }
         | 
| 225 | 
            +
                    config = {element, dimension, point};
         | 
| 226 | 
            +
                  }
         | 
| 227 | 
            +
                }
         | 
| 228 | 
            +
                return config;
         | 
| 229 | 
            +
              }
         | 
| 230 | 
            +
             | 
| 231 | 
            +
              parsePoint(string, dimension) {
         | 
| 232 | 
            +
                let point = null;
         | 
| 233 | 
            +
                let match;
         | 
| 234 | 
            +
                if (match = string.match(/^n(|w|e)|s(|w|e)|w|e|c$/)) {
         | 
| 235 | 
            +
                  if (string == "nw") {
         | 
| 236 | 
            +
                    point = dimension.northWestMargined;
         | 
| 237 | 
            +
                  } else if (string == "n") {
         | 
| 238 | 
            +
                    point = dimension.northMargined;
         | 
| 239 | 
            +
                  } else if (string == "ne") {
         | 
| 240 | 
            +
                    point = dimension.northEastMargined;
         | 
| 241 | 
            +
                  } else if (string == "e") {
         | 
| 242 | 
            +
                    point = dimension.eastMargined;
         | 
| 243 | 
            +
                  } else if (string == "se") {
         | 
| 244 | 
            +
                    point = dimension.southEastMargined;
         | 
| 245 | 
            +
                  } else if (string == "s") {
         | 
| 246 | 
            +
                    point = dimension.southMargined;
         | 
| 247 | 
            +
                  } else if (string == "sw") {
         | 
| 248 | 
            +
                    point = dimension.southWestMargined;
         | 
| 249 | 
            +
                  } else if (string == "w") {
         | 
| 250 | 
            +
                    point = dimension.westMargined;
         | 
| 251 | 
            +
                  } else if (string == "c") {
         | 
| 252 | 
            +
                    point = dimension.center;
         | 
| 253 | 
            +
                  }
         | 
| 254 | 
            +
                } else if (match = string.match(/^(t|r|b|l)([\d.]+)$/)) {
         | 
| 255 | 
            +
                  let direction = match[1];
         | 
| 256 | 
            +
                  let position = parseFloat(match[2]) / 100;
         | 
| 257 | 
            +
                  let pointX = null;
         | 
| 258 | 
            +
                  let pointY = null;
         | 
| 259 | 
            +
                  if (direction == "t") {
         | 
| 260 | 
            +
                    pointX = (1 - position) * dimension.northWestMargined[0] + position * dimension.northEastMargined[0];
         | 
| 261 | 
            +
                    pointY = dimension.northMargined[1];
         | 
| 262 | 
            +
                  } else if (direction == "r") {
         | 
| 263 | 
            +
                    pointX = dimension.eastMargined[0];
         | 
| 264 | 
            +
                    pointY = (1 - position) * dimension.northEastMargined[1] + position * dimension.southEastMargined[1];
         | 
| 265 | 
            +
                  } else if (direction == "b") {
         | 
| 266 | 
            +
                    pointX = (1 - position) * dimension.southWestMargined[0] + position * dimension.southEastMargined[0];
         | 
| 267 | 
            +
                    pointY = dimension.southMargined[1];
         | 
| 268 | 
            +
                  } else if (direction == "l") {
         | 
| 269 | 
            +
                    pointX = dimension.westMargined[0];
         | 
| 270 | 
            +
                    pointY = (1 - position) * dimension.northWestMargined[1] + position * dimension.southWestMargined[1];
         | 
| 271 | 
            +
                  }
         | 
| 272 | 
            +
                  if (pointX != null && pointY != null) {
         | 
| 273 | 
            +
                    point = [pointX, pointY];
         | 
| 274 | 
            +
                  }
         | 
| 275 | 
            +
                }
         | 
| 276 | 
            +
                return point;
         | 
| 277 | 
            +
              }
         | 
| 278 | 
            +
             | 
| 279 | 
            +
              parseTipKinds(string, lineCount) {
         | 
| 280 | 
            +
                let tipKinds = {start: "none", end: "normal"};
         | 
| 281 | 
            +
                if (string != null) {
         | 
| 282 | 
            +
                  let specifiedTipKinds = string.split(/\s*,\s*/);
         | 
| 283 | 
            +
                  for (let specifiedTipKind of specifiedTipKinds) {
         | 
| 284 | 
            +
                    let spec = DATA["arrow"][specifiedTipKind];
         | 
| 285 | 
            +
                    if (spec) {
         | 
| 286 | 
            +
                      tipKinds[spec.edge] = specifiedTipKind;
         | 
| 287 | 
            +
                    }
         | 
| 288 | 
            +
                    if (specifiedTipKind == "none") {
         | 
| 289 | 
            +
                      tipKinds.end = "none";
         | 
| 290 | 
            +
                    }
         | 
| 291 | 
            +
                  }
         | 
| 292 | 
            +
                }
         | 
| 293 | 
            +
                if (lineCount == 2) {
         | 
| 294 | 
            +
                  if (tipKinds.start != "none") {
         | 
| 295 | 
            +
                    tipKinds.start = "d" + tipKinds.start;
         | 
| 296 | 
            +
                  }
         | 
| 297 | 
            +
                  if (tipKinds.end != "none") {
         | 
| 298 | 
            +
                    tipKinds.end = "d" + tipKinds.end;
         | 
| 299 | 
            +
                  }
         | 
| 300 | 
            +
                } else if (lineCount == 3) {
         | 
| 301 | 
            +
                  if (tipKinds.start != "none") {
         | 
| 302 | 
            +
                    tipKinds.start = "t" + tipKinds.start;
         | 
| 303 | 
            +
                  }
         | 
| 304 | 
            +
                  if (tipKinds.end != "none") {
         | 
| 305 | 
            +
                    tipKinds.end = "t" + tipKinds.end;
         | 
| 306 | 
            +
                  }
         | 
| 307 | 
            +
                }
         | 
| 308 | 
            +
                return tipKinds;
         | 
| 309 | 
            +
              }
         | 
| 310 | 
            +
             | 
| 311 | 
            +
              calcAngle(basePoint, destinationPoint) {
         | 
| 312 | 
            +
                let x = destinationPoint[0] - basePoint[0];
         | 
| 313 | 
            +
                let y = destinationPoint[1] - basePoint[1];
         | 
| 314 | 
            +
                let angle = -Math.atan2(y, x);
         | 
| 315 | 
            +
                return angle;
         | 
| 316 | 
            +
              }
         | 
| 317 | 
            +
             | 
| 318 | 
            +
              normalizeAngle(angle) {
         | 
| 319 | 
            +
                let normalizedAngle = (angle + Math.PI) % (Math.PI * 2) - Math.PI;
         | 
| 320 | 
            +
                return normalizedAngle;
         | 
| 321 | 
            +
              }
         | 
| 322 | 
            +
             | 
| 323 | 
            +
              createArrows(arrowSpec, backgroundColor) {
         | 
| 324 | 
            +
                let startPoint = arrowSpec.intrudedStartPoint;
         | 
| 325 | 
            +
                let endPoint = arrowSpec.intrudedEndPoint;
         | 
| 326 | 
            +
                let bendAngle = arrowSpec.bendAngle;
         | 
| 327 | 
            +
                let lineCount = (arrowSpec.lineCount == undefined) ? 1 : arrowSpec.lineCount;
         | 
| 328 | 
            +
                let command = "M " + startPoint[0] + " " + startPoint[1];
         | 
| 329 | 
            +
                if (bendAngle != undefined) {
         | 
| 330 | 
            +
                  let controlPoint = this.calcControlPoint(startPoint, endPoint, bendAngle)
         | 
| 331 | 
            +
                  command += " Q " + controlPoint[0] + " " + controlPoint[1] + ", " + endPoint[0] + " " + endPoint[1];
         | 
| 332 | 
            +
                } else {
         | 
| 333 | 
            +
                  command += " L " + endPoint[0] + " " + endPoint[1];
         | 
| 334 | 
            +
                }
         | 
| 335 | 
            +
                let arrows = [];
         | 
| 336 | 
            +
                for (let i = 0 ; i < lineCount ; i ++) {
         | 
| 337 | 
            +
                  let arrow = this.createSvgElement("path");
         | 
| 338 | 
            +
                  arrow.setAttribute("d", command);
         | 
| 339 | 
            +
                  if (arrowSpec.tipKinds.start != "none" && i == lineCount - 1) {
         | 
| 340 | 
            +
                    arrow.setAttribute("marker-start", "url(#tip-" + arrowSpec.tipKinds.start +")");
         | 
| 341 | 
            +
                  }
         | 
| 342 | 
            +
                  if (arrowSpec.tipKinds.end != "none" && i == lineCount - 1) {
         | 
| 343 | 
            +
                    arrow.setAttribute("marker-end", "url(#tip-" + arrowSpec.tipKinds.end + ")");
         | 
| 344 | 
            +
                  }
         | 
| 345 | 
            +
                  if (arrowSpec.dashed && i % 2 == 0) {
         | 
| 346 | 
            +
                    arrow.classList.add("dashed");
         | 
| 347 | 
            +
                  }
         | 
| 348 | 
            +
                  if (i == 0) {
         | 
| 349 | 
            +
                    arrow.classList.add("base");
         | 
| 350 | 
            +
                  } else if (i == 1) {
         | 
| 351 | 
            +
                    arrow.classList.add("cover");
         | 
| 352 | 
            +
                    arrow.style.stroke = backgroundColor;
         | 
| 353 | 
            +
                  } else if (i == 2) {
         | 
| 354 | 
            +
                    arrow.classList.add("front");
         | 
| 355 | 
            +
                  }
         | 
| 356 | 
            +
                  if (lineCount == 2) {
         | 
| 357 | 
            +
                    arrow.classList.add("double");
         | 
| 358 | 
            +
                  } else if (lineCount == 3) {
         | 
| 359 | 
            +
                    arrow.classList.add("triple");
         | 
| 360 | 
            +
                  }
         | 
| 361 | 
            +
                  arrows.push(arrow);
         | 
| 362 | 
            +
                }
         | 
| 363 | 
            +
                return arrows;
         | 
| 364 | 
            +
              }
         | 
| 365 | 
            +
             | 
| 366 | 
            +
              calcControlPoint(startPoint, endPoint, bendAngle) {
         | 
| 367 | 
            +
                let x = (endPoint[0] + startPoint[0] + (endPoint[1] - startPoint[1]) * Math.tan(bendAngle)) / 2;
         | 
| 368 | 
            +
                let y = (endPoint[1] + startPoint[1] - (endPoint[0] - startPoint[0]) * Math.tan(bendAngle)) / 2;
         | 
| 369 | 
            +
                return [x, y];
         | 
| 370 | 
            +
              }
         | 
| 371 | 
            +
             | 
| 372 | 
            +
              calcExtrusion(graphic, elements) {
         | 
| 373 | 
            +
                let fontSize = this.getFontSize(graphic);
         | 
| 374 | 
            +
                let xOffset =  window.pageXOffset;
         | 
| 375 | 
            +
                let yOffset =  window.pageYOffset;
         | 
| 376 | 
            +
                let graphicRect = graphic.getBoundingClientRect();
         | 
| 377 | 
            +
                let graphicTop = graphicRect.top + yOffset
         | 
| 378 | 
            +
                let graphicBottom = graphicRect.bottom + yOffset;
         | 
| 379 | 
            +
                let graphicLeft = graphicRect.left + xOffset;
         | 
| 380 | 
            +
                let graphicRight = graphicRect.right + xOffset;
         | 
| 381 | 
            +
                let extrusion = {top: 0, bottom: 0, left: 0, right: 0};
         | 
| 382 | 
            +
                for (let element of elements) {
         | 
| 383 | 
            +
                  let rect = element.getBoundingClientRect();
         | 
| 384 | 
            +
                  let topExtrusion = -(rect.top + yOffset - graphicTop) / fontSize;
         | 
| 385 | 
            +
                  let bottomExtrusion = (rect.bottom + yOffset - graphicBottom) / fontSize;
         | 
| 386 | 
            +
                  let leftExtrusion = -(rect.left + xOffset - graphicLeft) / fontSize;
         | 
| 387 | 
            +
                  let rightExtrusion = (rect.right + xOffset - graphicRight) / fontSize;
         | 
| 388 | 
            +
                  if (topExtrusion > extrusion.top) {
         | 
| 389 | 
            +
                    extrusion.top = topExtrusion;
         | 
| 390 | 
            +
                  }
         | 
| 391 | 
            +
                  if (bottomExtrusion > extrusion.bottom) {
         | 
| 392 | 
            +
                    extrusion.bottom = bottomExtrusion;
         | 
| 393 | 
            +
                  }
         | 
| 394 | 
            +
                  if (leftExtrusion > extrusion.left) {
         | 
| 395 | 
            +
                    extrusion.left = leftExtrusion;
         | 
| 396 | 
            +
                  }
         | 
| 397 | 
            +
                  if (rightExtrusion > extrusion.right) {
         | 
| 398 | 
            +
                    extrusion.right = rightExtrusion;
         | 
| 399 | 
            +
                  }
         | 
| 400 | 
            +
                }
         | 
| 401 | 
            +
                return extrusion;
         | 
| 402 | 
            +
              }
         | 
| 403 | 
            +
             | 
| 404 | 
            +
              createGraphic(element) {
         | 
| 405 | 
            +
                let width = this.getWidth(element);
         | 
| 406 | 
            +
                let height = this.getHeight(element);
         | 
| 407 | 
            +
                let graphic = this.createSvgElement("svg");
         | 
| 408 | 
            +
                graphic.setAttribute("viewBox", "0 0 " + width + " " + height);
         | 
| 409 | 
            +
                let definitionElement = this.createSvgElement("defs");
         | 
| 410 | 
            +
                let tipSpecKeys = Object.keys(DATA["arrow"]);
         | 
| 411 | 
            +
                for (let tipSpecKey of tipSpecKeys) {
         | 
| 412 | 
            +
                  let tipSpec = DATA["arrow"][tipSpecKey];
         | 
| 413 | 
            +
                  let markerElement = this.createSvgElement("marker");
         | 
| 414 | 
            +
                  let markerPathElement = this.createSvgElement("path");
         | 
| 415 | 
            +
                  markerElement.setAttribute("id", "tip-" + tipSpecKey);
         | 
| 416 | 
            +
                  markerElement.setAttribute("refX", tipSpec["x"]);
         | 
| 417 | 
            +
                  markerElement.setAttribute("refY", tipSpec["y"]);
         | 
| 418 | 
            +
                  markerElement.setAttribute("markerWidth", tipSpec["width"]);
         | 
| 419 | 
            +
                  markerElement.setAttribute("markerHeight", tipSpec["height"]);
         | 
| 420 | 
            +
                  markerElement.setAttribute("markerUnits", "userSpaceOnUse");
         | 
| 421 | 
            +
                  markerElement.setAttribute("orient", "auto");
         | 
| 422 | 
            +
                  markerPathElement.setAttribute("d", tipSpec["command"]);
         | 
| 423 | 
            +
                  markerElement.appendChild(markerPathElement);
         | 
| 424 | 
            +
                  definitionElement.appendChild(markerElement);
         | 
| 425 | 
            +
                }
         | 
| 426 | 
            +
                graphic.appendChild(definitionElement);
         | 
| 427 | 
            +
                return graphic;
         | 
| 428 | 
            +
              }
         | 
| 429 | 
            +
             | 
| 430 | 
            +
              createSvgElement(name) {
         | 
| 431 | 
            +
                let element = document.createElementNS("http://www.w3.org/2000/svg", name);
         | 
| 432 | 
            +
                return element;
         | 
| 433 | 
            +
              }
         | 
| 434 | 
            +
             | 
| 435 | 
            +
              calcDimension(graphic, element) {
         | 
| 436 | 
            +
                let dimension = {};
         | 
| 437 | 
            +
                let margin = ARROW_MARGIN;
         | 
| 438 | 
            +
                let fontSize = this.getFontSize(graphic)
         | 
| 439 | 
            +
                let graphicTop = graphic.getBoundingClientRect().top + window.pageYOffset;
         | 
| 440 | 
            +
                let graphicLeft = graphic.getBoundingClientRect().left + window.pageXOffset;
         | 
| 441 | 
            +
                let top = (element.getBoundingClientRect().top + window.pageYOffset - graphicTop) / fontSize;
         | 
| 442 | 
            +
                let left = (element.getBoundingClientRect().left + window.pageXOffset - graphicLeft) / fontSize;
         | 
| 443 | 
            +
                let width = this.getWidth(element, graphic);
         | 
| 444 | 
            +
                let height = this.getHeight(element, graphic);
         | 
| 445 | 
            +
                let lowerHeight = this.getLowerHeight(element, graphic);
         | 
| 446 | 
            +
                dimension.northWest = [left, top];
         | 
| 447 | 
            +
                dimension.north = [left + width / 2, top];
         | 
| 448 | 
            +
                dimension.northEast = [left + width, top];
         | 
| 449 | 
            +
                dimension.west = [left, top + height - lowerHeight];
         | 
| 450 | 
            +
                dimension.center = [left + width / 2, top + height - lowerHeight];
         | 
| 451 | 
            +
                dimension.east = [left + width, top + height - lowerHeight];
         | 
| 452 | 
            +
                dimension.southWest = [left, top + height];
         | 
| 453 | 
            +
                dimension.south = [left + width / 2, top + height];
         | 
| 454 | 
            +
                dimension.southEast = [left + width, top + height];
         | 
| 455 | 
            +
                dimension.northWestMargined = [left - margin, top - margin];
         | 
| 456 | 
            +
                dimension.northMargined = [left + width / 2, top - margin];
         | 
| 457 | 
            +
                dimension.northEastMargined = [left + width + margin, top - margin];
         | 
| 458 | 
            +
                dimension.westMargined = [left - margin, top + height - lowerHeight];
         | 
| 459 | 
            +
                dimension.centerMargined = [left + width / 2, top + height - lowerHeight];
         | 
| 460 | 
            +
                dimension.eastMargined = [left + width + margin, top + height - lowerHeight];
         | 
| 461 | 
            +
                dimension.southWestMargined = [left - margin, top + height + margin];
         | 
| 462 | 
            +
                dimension.southMargined = [left + width / 2, top + height + margin];
         | 
| 463 | 
            +
                dimension.southEastMargined = [left + width + margin, top + height + margin];
         | 
| 464 | 
            +
                return dimension;
         | 
| 465 | 
            +
              }
         | 
| 466 | 
            +
             | 
| 467 | 
            +
              getBackgroundColor(element) {
         | 
| 468 | 
            +
                let currentElement = element;
         | 
| 469 | 
            +
                let color = "white";
         | 
| 470 | 
            +
                while (currentElement && currentElement instanceof Element) {
         | 
| 471 | 
            +
                  let currentColor = window.getComputedStyle(currentElement).backgroundColor;
         | 
| 472 | 
            +
                  if (currentColor != "rgba(0, 0, 0, 0)" && currentColor != "transparent") {
         | 
| 473 | 
            +
                    color = currentColor;
         | 
| 474 | 
            +
                    break;
         | 
| 475 | 
            +
                  }
         | 
| 476 | 
            +
                  currentElement = currentElement.parentNode;
         | 
| 477 | 
            +
                }
         | 
| 478 | 
            +
                return color;
         | 
| 479 | 
            +
              }
         | 
| 480 | 
            +
             | 
| 481 | 
            +
            }
         |