<commit_before>import Tool from '../../core/tool.js';
export class TextTool extends Tool {
  constructor(SL, config, Belt) {
    super(SL, config, Belt);
    this.loaded = {};
    this.State = {
      textItem: null,
      hoverItem: null,
      cursor: {
        index: null,
        line: null,
        local: null,
        targetX: null
      },
      selectCache: null
    };
    this.UI = {};
  }
  configure(config) {
    config = super.configure(config);
    this.configureFont(config.font);
    this.configureInteractive(config.interactive);
    this.configureUI(config.ui);
    return config;
  }
  configureFont(config={}) {
    this.config.font = config;
    if (config.fontFamily == null) {
      config.fontFamily = 'sans-serif';
    }
    if (config.fontWeight == null) {
      config.fontWeight = 'normal';
    }
    if (config.fontSize == null) {
      config.fontSize = 14;
    }
  }
  configureInteractive(config={}) {
    this.config.interactive = config;
    if (!config.deltaMultiplier) {
      config.deltaMultiplier = 0.5;
    }
    if (!config.allowSizes || !config.allowSizes.length) {
      config.allowSizes = [14, 16, 18, 21, 24, 28, 36];
    }
    config.allowSizes.sort();
    if (!config.minSize) {
      config.minSize = config.allowSizes[0];
    }
    if (!config.maxSize) {
      config.maxSize = config.allowSizes[config.allowSizes.length-1];
    }
  }
  configureUI(config={}) {
    this.config.ui = config;
    if (config.color == null) {
      config.color = '#000000';
    }
    if (config.cursor == null) {
      config.cursor = {};
    }
    if (config.cursor.blink == null) {
      config.cursor.blink = 0.5;
    }
    if (config.cursor.style == null) {
      config.cursor.style = {};
    }
    if (config.cursor.style.strokeWidth == null) {
      config.cursor.style.strokeWidth = 1;
    }
    if (config.cursor.style.strokeColor == null) {
      config.cursor.style.strokeColor = config.color;
    }

    if (config.target == null) {
      config.target = {};
    }
    if (config.target.padding == null) {
      config.target.padding = 2;
    }
    if (config.target.style == null) {
      config.target.style = {};
    }
    if (config.target.style.strokeColor == null) {
      config.target.style.strokeColor = '#6600FF';
    }
    if (config.target.style.strokeWidth == null) {
      config.target.style.strokeWidth = 1;
    }

    if (config.hoverTarget == null) {
      config.hoverTarget = {};
    }
    if (config.hoverTarget.padding == null) {
      config.hoverTarget.padding = config.target.padding;
    }
    if (config.hoverTarget.style == null) {
      config.hoverTarget.style = {};
    }
    if (config.hoverTarget.style.strokeColor == null) {
      config.hoverTarget.style.strokeColor = config.target.style.strokeColor;
    }
    if (config.hoverTarget.style.strokeWidth == null) {
      config.hoverTarget.style.strokeWidth = config.target.style.strokeWidth;
    }
  }
  reset() {
    super.reset();
    this.resetState();
  }
  resetState() {
    this.resetStateCursor();
    this.resetStateTargets();
    this.resetStateSelectCache();
  }
  resetStateCursor() {
    if (this.State && this.State.cursor) {
      this.State.cursor.index = null;
      this.State.cursor.line = null;
      this.State.cursor.local = null;
      this.State.cursor.targetX = null;
    }
  }
  resetStateTargets() {
    this.resetStateHoverTarget();
    this.resetStateTextItem();
  }
  resetStateHoverTarget() {
    if (this.State) {
      if (this.State.hoverItem) {
        this.removeTemporaryStraighten(this.State.hoverItem, 'hoverItem');
      }
      this.State.hoverItem = null;
    }
  }
  resetStateTextItem() {
    if (this.State) {
      if (this.State.textItem) {
        this.removeTemporaryStraighten(this.State.textItem, 'textItem');
        if (!this.State.textItem.content.trim()) {
          this.SL.Paper.destroyPaperItem(this.State.textItem);
        }
      }
      this.State.textItem = null;
    }
  }
  resetStateSelectCache() {
    if (this.State) {
      if (this.State.selectCache) {
        this.Belt.Belt.Select.Select(this.State.selectCache);
      }
      this.State.selectCache = null;
    }
  }

  activate() {
    super.activate();
    if (this.DOM && this.DOM.PaletteButton) {
      this.DOM.PaletteButton.addClass(this.DOM.PaletteButton.data('activeClass'));
    }
    this.SL.Paper.on('Frame', undefined, (event) => {
      this.onFrame(event);
    }, 'TextTool.Frame');
    this.State.selectCache = this.Belt.Belt.Select.Items.slice();
    this.Belt.Belt.Select.Unselect();
  }
  deactivate() {
    super.deactivate();
    this.SL.Paper.off('Frame', 'TextTool.Frame');
    if (this.DOM && this.DOM.PaletteButton) {
      this.DOM.PaletteButton.removeClass(this.DOM.PaletteButton.data('activeClass'));
    }
    this.resetState();
    this.resetUI();
  }
  get activationPriority() {
    return (this.active ? 500 : -1);
  }
  refreshUI() {
    if (this.isActive()) {
      this.SL.UI.Mouse.Cursor.activateCursor('text');
      this.refreshUICursor();
      this.refreshUITarget();
    }
  }
  refreshUICursor(resetBlink=false) {
    if (this.State.textItem && this.State.cursor.line != null && this.State.cursor.local != null) {
      let lineHeight = this.State.textItem.leading;
      let currentLine = ((this.State.cursor.line < this.State.textItem._lines.length) ? this.State.textItem._lines[this.State.cursor.line] : '') || '';
      let cursorTop = {
        x: this.State.textItem.bounds.left + this.getTextWidth(currentLine.substr(0, this.State.cursor.local)),
        y: this.State.textItem.bounds.top + this.State.cursor.line * lineHeight
      };
      let cursorBottom = {
        x: cursorTop.x,
        y: cursorTop.y+lineHeight
      };
      if (!this.UI.cursor) {
        this.UI.cursor = this.SL.Paper.generatePaperItem({Source: this, Class:['UI','Tool'], Layer:'UI_BG'}, paper.Path.Line, new paper.Point(cursorTop), new paper.Point(cursorBottom));
        this.SL.Paper.applyStyle(this.UI.cursor, this.config.ui.cursor.style);
      }
      this.UI.cursor.segments[0].point.set(cursorTop);
      this.UI.cursor.segments[1].point.set(cursorBottom);
      if (resetBlink) {
        this.UI.cursor.data.lastTick = null;
        this.UI.cursor.visible = true;
      }
    }
  }
  refreshUIHoverTarget() {
    let targetItem = this.State.hoverItem;
    if (targetItem && targetItem.content && targetItem.data && targetItem.data.Type == 'Text') {
      if (!this.UI.hoverTarget) {
        this.UI.hoverTarget = this.SL.Paper.generatePaperItem({Source: this, Class:['UI','Tool'], Layer:'UI_BG'}, paper.Shape.Rectangle, targetItem.bounds.clone().expand(this.config.ui.hoverTarget.padding*2.0));
        this.UI.hoverTarget.strokeScaling = false;
        this.SL.Paper.applyStyle(this.UI.hoverTarget, this.config.ui.hoverTarget.style);
      }
      this.UI.hoverTarget.bounds.set(targetItem.bounds.clone().expand(this.config.ui.hoverTarget.padding*2.0));
    }
    else if (this.UI.hoverTarget) {
      this.resetUIHoverTarget();
    }
  }
  refreshUITarget() {
    let targetItem = this.State.textItem;
    if (targetItem && targetItem.content) {
      if (!this.UI.target) {
        this.UI.target = this.SL.Paper.generatePaperItem({Source: this, Class:['UI','Tool'], Layer:'UI_BG'}, paper.Shape.Rectangle, targetItem.bounds.clone().expand(this.config.ui.target.padding*2.0));
        this.UI.target.strokeScaling = false;
        this.SL.Paper.applyStyle(this.UI.target, this.config.ui.target.style);
      }
      this.UI.target.bounds.set(targetItem.bounds.clone().expand(this.config.ui.target.padding*2.0));
    }
    else if (this.UI.target) {
      this.resetUITarget();
    }
  }
  resetUI() {
    this.resetUICursor();
    this.resetUITarget();
    this.resetUIHoverTarget();
  }
  resetUICursor() {
    if (this.UI.cursor) {
      this.SL.Paper.destroyPaperItem(this.UI.cursor);
      this.UI.cursor = undefined;
    }
  }
  resetUITarget() {
    if (this.UI.target) {
      this.SL.Paper.destroyPaperItem(this.UI.target);
      this.UI.target = undefined;
    }
  }
  resetUIHoverTarget() {
    if (this.UI.hoverTarget) {
      this.SL.Paper.destroyPaperItem(this.UI.hoverTarget);
      this.UI.hoverTarget = undefined;
    }
  }

  setHoverTarget(targetItem) {
    if (this.State.hoverItem != targetItem) {
      this.resetStateHoverTarget();
      this.State.hoverItem = targetItem;
      this.addTemporaryStraighten(this.State.hoverItem, 'hoverItem');
      this.refreshUIHoverTarget();
    }
  }
  setTarget(targetItem) {
    if (this.State.textItem != targetItem) {
      this.resetStateTextItem();
      this.State.textItem = targetItem;
      this.addTemporaryStraighten(this.State.textItem, 'textItem');
      this.refreshUITarget();
    }
  }

  addTemporaryStraighten(item, id) {
    if (item && item.data && item.data.Type == 'Text' && item.rotation) {
      if (!item.data) {
        item.data = {};
      }
      if (!item.data.straightened) {
        item.data.straightened = {
          angle: 0,
          by: []
        };
      }
      if (!item.data.straightened.by.length) {
        item.data.straightened.angle = item.rotation;
        item.rotate(-1.0*item.rotation);
        item.data.straightened.by.push(id);
      }
    }
    if (item && item.data && item.data.straightened && item.data.straightened.by.indexOf(id) == -1) {
      item.data.straightened.by.push(id);
    }
  }
  removeTemporaryStraighten(item, id) {
    if (item && item.data && item.data.straightened && item.data.straightened.angle && item.data.straightened.by) {
      let straightenedIndex = item.data.straightened.by.indexOf(id);
      if (straightenedIndex != -1) {
        item.data.straightened.by.splice(straightenedIndex, 1);
        if (!item.data.straightened.by.length) {
          item.rotate(item.data.straightened.angle);
          item.data.straightened = undefined;
          delete item.data.straightened;
        }
      }
    }
  }

  getLineCount(lines, index) {
    let line = 0;
    let check = 0;
    while (line < lines.length && (check + lines[line].length) < index) {
      check += lines[line++].length + 1;
    }
    return Math.max(0, Math.min(line, lines.length-1));
  }
  getLineStartIndex(lines, line) {
    let index = 0;
    for (let i=0; i < lines.length && i < line; i++) {
      index += lines[i].length + 1;
    }
    return index;
  }
  getTextWidth(text, font) {
    if (!font && this.State.textItem) {
      font = this.State.textItem.style.getFontStyle();
    }
    if (!font) {
      return 0;
    }
    return this.SL.Paper.view.getTextWidth(font, [text]);
  }

  getAnchorPoint(edge, rectangle, opposite=false) {
    let result = rectangle.center.clone();
    if (typeof edge == 'string') {
      edge = this.vector(edge);
    }
    if (!edge || edge.x == undefined || edge.y == undefined) {
      throw 'Invalid edge given';
    }
    if (edge.x < 0) {
      result.x = (opposite ? rectangle.right : rectangle.left);
    }
    else if (edge.x >= 0) {
      result.x = (opposite ? rectangle.left : rectangle.right);
    }
    if (edge.y < 0) {
      result.y = (opposite ? rectangle.bottom : rectangle.top);
    }
    else if (edge.y >= 0) {
      result.y = (opposite ? rectangle.top : rectangle.bottom);
    }
    return result;
  }

  calculateCursor(set) {
    let checkString = ((this.State.textItem && this.State.textItem.content) || '');
    if (set.position != null) {
      this.State.cursor.index = 0;
      this.State.cursor.line = 0;
      this.State.cursor.local = 0;
      if (this.State.textItem) {
        if (this.State.textItem.leading) {
          this.State.cursor.line = Math.min((this.State.textItem._lines.length-1), Math.floor(set.position.y / this.State.textItem.leading));
        }
        let line = this.State.textItem._lines[this.State.cursor.line] || '';
        let checkIndex = 0;
        let charWidth = this.getTextWidth('A');
        if (charWidth) {
          // make a guess at the index based on width of A
          checkIndex = Math.floor(set.position.x / charWidth);
        }

        // make sure index is to the left of set.position.x
        let indexLeft = this.getTextWidth(line.substr(0, checkIndex));
        while (indexLeft > set.position.x && checkIndex > 0) {
          checkIndex--;
          indexLeft -= this.getTextWidth(line.substr(checkIndex, 1));
        }

        // make sure index+1 is to the right of set.position.x
        let indexRight = indexLeft + this.getTextWidth(line.substr(checkIndex, 1));
        while (indexRight < set.position.x && checkIndex < line.length) {
          indexLeft += this.getTextWidth(line.substr(checkIndex, 1));
          checkIndex++;
          indexRight = indexLeft + this.getTextWidth(line.substr(checkIndex, 1));
        }

        // clicking on right half of character bumps up to next index (append)
        if (set.position.x > (indexLeft + (indexRight-indexLeft)*0.5) && checkIndex < line.length) {
          checkIndex++;
        }

        // make sure it stays on the line (line selection by set.position.y is quite reliable)
        if (checkIndex > line.length) {
          checkIndex = line.length;
        }

        // assign the calculated index
        this.State.cursor.local = checkIndex;

        // initialize index to the start of current line
        if (this.State.cursor.line) {
          this.State.cursor.index = this.getLineStartIndex(this.State.textItem._lines, this.State.cursor.line);
        }
      }
      // advance index to the "local" position (position on currecnt line)
      this.State.cursor.index += this.State.cursor.local;
    }
    else if (set.index != null) {
      this.State.cursor.index = set.index;
      this.State.cursor.line = this.getLineCount(this.State.textItem._lines, set.index);
      let lineStart = this.getLineStartIndex(this.State.textItem._lines, this.State.cursor.line);
      this.State.cursor.local = set.index - lineStart;
    }
    this.refreshUICursor(true);
  }

  createTextItem(point) {
    let Grid = this.SL.Utils.get('Grid');
    if (Grid) {
      // force rounding to left edge of whatever grid cell was clicked in - same trick as classic int(float(val + 0.5))
      let grid = Grid.getCurrentDefinition();
      point = point.subtract(new paper.Point({x: grid.cell.width/2.0, y: grid.cell.height/2.0}));
    }
    let textItem = this.SL.Paper.generatePaperItem({Source: this, Type: 'Text'}, paper.PointText, point);
    this.SL.Paper.applyStyle(textItem, this.config.font);
    this.SL.Paper.Item.addCustomMethod(textItem, 'SnapItem', this.snapTextItem, this);
    this.SL.Paper.Item.addCustomMethod(textItem, 'ScaleItem', this.scaleTextItem, this);
    this.snapTextItem(textItem, { context: 'create' });
    this.setTarget(textItem);
    this.calculateCursor({index: 0});
    this.refreshUITarget();
  }
  editTextItem(item, point) {
    this.setTarget(item);
    this.calculateCursor({position: {x: point.x - item.bounds.left, y: point.y - item.bounds.top}});
    this.refreshUITarget();
  }
  snapTextItem(item, args={}) {
    if (item && item.data && item.data.Type == 'Text') {
      let Snap = this.SL.Utils.get('Snap');
      if (Snap) {
        let pointArgs = {
          context: args.context,
          type: 'text-point',
          item: item,
          interactive: args.interactive,
          target: args.target,
          offset: args.offset
        };
        let refItem = args.original || item;
        let point = Snap.Point(refItem.bounds.topLeft, pointArgs);
        let delta = point.subtract(refItem.bounds.topLeft);
        item.translate(delta);
      }
      if (!args.interactive) {
        var snapSizes = this.config.interactive.allowSizes;
        let fontSize = parseFloat(item.fontSize);<commit_msg>TextTool emits CreateItem and EditItem events<commit_after>import Tool from '../../core/tool.js';
export class TextTool extends Tool {
  constructor(SL, config, Belt) {
    super(SL, config, Belt);
    this.loaded = {};
    this.State = {
      textItem: null,
      hoverItem: null,
      cursor: {
        index: null,
        line: null,
        local: null,
        targetX: null
      },
      selectCache: null
    };
    this.UI = {};
  }
  configure(config) {
    config = super.configure(config);
    this.configureFont(config.font);
    this.configureInteractive(config.interactive);
    this.configureUI(config.ui);
    return config;
  }
  configureFont(config={}) {
    this.config.font = config;
    if (config.fontFamily == null) {
      config.fontFamily = 'sans-serif';
    }
    if (config.fontWeight == null) {
      config.fontWeight = 'normal';
    }
    if (config.fontSize == null) {
      config.fontSize = 14;
    }
  }
  configureInteractive(config={}) {
    this.config.interactive = config;
    if (!config.deltaMultiplier) {
      config.deltaMultiplier = 0.5;
    }
    if (!config.allowSizes || !config.allowSizes.length) {
      config.allowSizes = [14, 16, 18, 21, 24, 28, 36];
    }
    config.allowSizes.sort();
    if (!config.minSize) {
      config.minSize = config.allowSizes[0];
    }
    if (!config.maxSize) {
      config.maxSize = config.allowSizes[config.allowSizes.length-1];
    }
  }
  configureUI(config={}) {
    this.config.ui = config;
    if (config.color == null) {
      config.color = '#000000';
    }
    if (config.cursor == null) {
      config.cursor = {};
    }
    if (config.cursor.blink == null) {
      config.cursor.blink = 0.5;
    }
    if (config.cursor.style == null) {
      config.cursor.style = {};
    }
    if (config.cursor.style.strokeWidth == null) {
      config.cursor.style.strokeWidth = 1;
    }
    if (config.cursor.style.strokeColor == null) {
      config.cursor.style.strokeColor = config.color;
    }

    if (config.target == null) {
      config.target = {};
    }
    if (config.target.padding == null) {
      config.target.padding = 2;
    }
    if (config.target.style == null) {
      config.target.style = {};
    }
    if (config.target.style.strokeColor == null) {
      config.target.style.strokeColor = '#6600FF';
    }
    if (config.target.style.strokeWidth == null) {
      config.target.style.strokeWidth = 1;
    }

    if (config.hoverTarget == null) {
      config.hoverTarget = {};
    }
    if (config.hoverTarget.padding == null) {
      config.hoverTarget.padding = config.target.padding;
    }
    if (config.hoverTarget.style == null) {
      config.hoverTarget.style = {};
    }
    if (config.hoverTarget.style.strokeColor == null) {
      config.hoverTarget.style.strokeColor = config.target.style.strokeColor;
    }
    if (config.hoverTarget.style.strokeWidth == null) {
      config.hoverTarget.style.strokeWidth = config.target.style.strokeWidth;
    }
  }
  reset() {
    super.reset();
    this.resetState();
  }
  resetState() {
    this.resetStateCursor();
    this.resetStateTargets();
    this.resetStateSelectCache();
  }
  resetStateCursor() {
    if (this.State && this.State.cursor) {
      this.State.cursor.index = null;
      this.State.cursor.line = null;
      this.State.cursor.local = null;
      this.State.cursor.targetX = null;
    }
  }
  resetStateTargets() {
    this.resetStateHoverTarget();
    this.resetStateTextItem();
  }
  resetStateHoverTarget() {
    if (this.State) {
      if (this.State.hoverItem) {
        this.removeTemporaryStraighten(this.State.hoverItem, 'hoverItem');
      }
      this.State.hoverItem = null;
    }
  }
  resetStateTextItem() {
    if (this.State) {
      if (this.State.textItem) {
        this.removeTemporaryStraighten(this.State.textItem, 'textItem');
        if (!this.State.textItem.content.trim()) {
          this.SL.Paper.destroyPaperItem(this.State.textItem);
        }
      }
      this.State.textItem = null;
    }
  }
  resetStateSelectCache() {
    if (this.State) {
      if (this.State.selectCache) {
        this.Belt.Belt.Select.Select(this.State.selectCache);
      }
      this.State.selectCache = null;
    }
  }

  activate() {
    super.activate();
    if (this.DOM && this.DOM.PaletteButton) {
      this.DOM.PaletteButton.addClass(this.DOM.PaletteButton.data('activeClass'));
    }
    this.SL.Paper.on('Frame', undefined, (event) => {
      this.onFrame(event);
    }, 'TextTool.Frame');
    this.State.selectCache = this.Belt.Belt.Select.Items.slice();
    this.Belt.Belt.Select.Unselect();
  }
  deactivate() {
    super.deactivate();
    this.SL.Paper.off('Frame', 'TextTool.Frame');
    if (this.DOM && this.DOM.PaletteButton) {
      this.DOM.PaletteButton.removeClass(this.DOM.PaletteButton.data('activeClass'));
    }
    this.resetState();
    this.resetUI();
  }
  get activationPriority() {
    return (this.active ? 500 : -1);
  }
  refreshUI() {
    if (this.isActive()) {
      this.SL.UI.Mouse.Cursor.activateCursor('text');
      this.refreshUICursor();
      this.refreshUITarget();
    }
  }
  refreshUICursor(resetBlink=false) {
    if (this.State.textItem && this.State.cursor.line != null && this.State.cursor.local != null) {
      let lineHeight = this.State.textItem.leading;
      let currentLine = ((this.State.cursor.line < this.State.textItem._lines.length) ? this.State.textItem._lines[this.State.cursor.line] : '') || '';
      let cursorTop = {
        x: this.State.textItem.bounds.left + this.getTextWidth(currentLine.substr(0, this.State.cursor.local)),
        y: this.State.textItem.bounds.top + this.State.cursor.line * lineHeight
      };
      let cursorBottom = {
        x: cursorTop.x,
        y: cursorTop.y+lineHeight
      };
      if (!this.UI.cursor) {
        this.UI.cursor = this.SL.Paper.generatePaperItem({Source: this, Class:['UI','Tool'], Layer:'UI_BG'}, paper.Path.Line, new paper.Point(cursorTop), new paper.Point(cursorBottom));
        this.SL.Paper.applyStyle(this.UI.cursor, this.config.ui.cursor.style);
      }
      this.UI.cursor.segments[0].point.set(cursorTop);
      this.UI.cursor.segments[1].point.set(cursorBottom);
      if (resetBlink) {
        this.UI.cursor.data.lastTick = null;
        this.UI.cursor.visible = true;
      }
    }
  }
  refreshUIHoverTarget() {
    let targetItem = this.State.hoverItem;
    if (targetItem && targetItem.content && targetItem.data && targetItem.data.Type == 'Text') {
      if (!this.UI.hoverTarget) {
        this.UI.hoverTarget = this.SL.Paper.generatePaperItem({Source: this, Class:['UI','Tool'], Layer:'UI_BG'}, paper.Shape.Rectangle, targetItem.bounds.clone().expand(this.config.ui.hoverTarget.padding*2.0));
        this.UI.hoverTarget.strokeScaling = false;
        this.SL.Paper.applyStyle(this.UI.hoverTarget, this.config.ui.hoverTarget.style);
      }
      this.UI.hoverTarget.bounds.set(targetItem.bounds.clone().expand(this.config.ui.hoverTarget.padding*2.0));
    }
    else if (this.UI.hoverTarget) {
      this.resetUIHoverTarget();
    }
  }
  refreshUITarget() {
    let targetItem = this.State.textItem;
    if (targetItem && targetItem.content) {
      if (!this.UI.target) {
        this.UI.target = this.SL.Paper.generatePaperItem({Source: this, Class:['UI','Tool'], Layer:'UI_BG'}, paper.Shape.Rectangle, targetItem.bounds.clone().expand(this.config.ui.target.padding*2.0));
        this.UI.target.strokeScaling = false;
        this.SL.Paper.applyStyle(this.UI.target, this.config.ui.target.style);
      }
      this.UI.target.bounds.set(targetItem.bounds.clone().expand(this.config.ui.target.padding*2.0));
    }
    else if (this.UI.target) {
      this.resetUITarget();
    }
  }
  resetUI() {
    this.resetUICursor();
    this.resetUITarget();
    this.resetUIHoverTarget();
  }
  resetUICursor() {
    if (this.UI.cursor) {
      this.SL.Paper.destroyPaperItem(this.UI.cursor);
      this.UI.cursor = undefined;
    }
  }
  resetUITarget() {
    if (this.UI.target) {
      this.SL.Paper.destroyPaperItem(this.UI.target);
      this.UI.target = undefined;
    }
  }
  resetUIHoverTarget() {
    if (this.UI.hoverTarget) {
      this.SL.Paper.destroyPaperItem(this.UI.hoverTarget);
      this.UI.hoverTarget = undefined;
    }
  }

  setHoverTarget(targetItem) {
    if (this.State.hoverItem != targetItem) {
      this.resetStateHoverTarget();
      this.State.hoverItem = targetItem;
      this.addTemporaryStraighten(this.State.hoverItem, 'hoverItem');
      this.refreshUIHoverTarget();
    }
  }
  setTarget(targetItem) {
    if (this.State.textItem != targetItem) {
      this.resetStateTextItem();
      this.State.textItem = targetItem;
      this.addTemporaryStraighten(this.State.textItem, 'textItem');
      this.refreshUITarget();
    }
  }

  addTemporaryStraighten(item, id) {
    if (item && item.data && item.data.Type == 'Text' && item.rotation) {
      if (!item.data) {
        item.data = {};
      }
      if (!item.data.straightened) {
        item.data.straightened = {
          angle: 0,
          by: []
        };
      }
      if (!item.data.straightened.by.length) {
        item.data.straightened.angle = item.rotation;
        item.rotate(-1.0*item.rotation);
        item.data.straightened.by.push(id);
      }
    }
    if (item && item.data && item.data.straightened && item.data.straightened.by.indexOf(id) == -1) {
      item.data.straightened.by.push(id);
    }
  }
  removeTemporaryStraighten(item, id) {
    if (item && item.data && item.data.straightened && item.data.straightened.angle && item.data.straightened.by) {
      let straightenedIndex = item.data.straightened.by.indexOf(id);
      if (straightenedIndex != -1) {
        item.data.straightened.by.splice(straightenedIndex, 1);
        if (!item.data.straightened.by.length) {
          item.rotate(item.data.straightened.angle);
          item.data.straightened = undefined;
          delete item.data.straightened;
        }
      }
    }
  }

  getLineCount(lines, index) {
    let line = 0;
    let check = 0;
    while (line < lines.length && (check + lines[line].length) < index) {
      check += lines[line++].length + 1;
    }
    return Math.max(0, Math.min(line, lines.length-1));
  }
  getLineStartIndex(lines, line) {
    let index = 0;
    for (let i=0; i < lines.length && i < line; i++) {
      index += lines[i].length + 1;
    }
    return index;
  }
  getTextWidth(text, font) {
    if (!font && this.State.textItem) {
      font = this.State.textItem.style.getFontStyle();
    }
    if (!font) {
      return 0;
    }
    return this.SL.Paper.view.getTextWidth(font, [text]);
  }

  getAnchorPoint(edge, rectangle, opposite=false) {
    let result = rectangle.center.clone();
    if (typeof edge == 'string') {
      edge = this.vector(edge);
    }
    if (!edge || edge.x == undefined || edge.y == undefined) {
      throw 'Invalid edge given';
    }
    if (edge.x < 0) {
      result.x = (opposite ? rectangle.right : rectangle.left);
    }
    else if (edge.x >= 0) {
      result.x = (opposite ? rectangle.left : rectangle.right);
    }
    if (edge.y < 0) {
      result.y = (opposite ? rectangle.bottom : rectangle.top);
    }
    else if (edge.y >= 0) {
      result.y = (opposite ? rectangle.top : rectangle.bottom);
    }
    return result;
  }

  calculateCursor(set) {
    let checkString = ((this.State.textItem && this.State.textItem.content) || '');
    if (set.position != null) {
      this.State.cursor.index = 0;
      this.State.cursor.line = 0;
      this.State.cursor.local = 0;
      if (this.State.textItem) {
        if (this.State.textItem.leading) {
          this.State.cursor.line = Math.min((this.State.textItem._lines.length-1), Math.floor(set.position.y / this.State.textItem.leading));
        }
        let line = this.State.textItem._lines[this.State.cursor.line] || '';
        let checkIndex = 0;
        let charWidth = this.getTextWidth('A');
        if (charWidth) {
          // make a guess at the index based on width of A
          checkIndex = Math.floor(set.position.x / charWidth);
        }

        // make sure index is to the left of set.position.x
        let indexLeft = this.getTextWidth(line.substr(0, checkIndex));
        while (indexLeft > set.position.x && checkIndex > 0) {
          checkIndex--;
          indexLeft -= this.getTextWidth(line.substr(checkIndex, 1));
        }

        // make sure index+1 is to the right of set.position.x
        let indexRight = indexLeft + this.getTextWidth(line.substr(checkIndex, 1));
        while (indexRight < set.position.x && checkIndex < line.length) {
          indexLeft += this.getTextWidth(line.substr(checkIndex, 1));
          checkIndex++;
          indexRight = indexLeft + this.getTextWidth(line.substr(checkIndex, 1));
        }

        // clicking on right half of character bumps up to next index (append)
        if (set.position.x > (indexLeft + (indexRight-indexLeft)*0.5) && checkIndex < line.length) {
          checkIndex++;
        }

        // make sure it stays on the line (line selection by set.position.y is quite reliable)
        if (checkIndex > line.length) {
          checkIndex = line.length;
        }

        // assign the calculated index
        this.State.cursor.local = checkIndex;

        // initialize index to the start of current line
        if (this.State.cursor.line) {
          this.State.cursor.index = this.getLineStartIndex(this.State.textItem._lines, this.State.cursor.line);
        }
      }
      // advance index to the "local" position (position on currecnt line)
      this.State.cursor.index += this.State.cursor.local;
    }
    else if (set.index != null) {
      this.State.cursor.index = set.index;
      this.State.cursor.line = this.getLineCount(this.State.textItem._lines, set.index);
      let lineStart = this.getLineStartIndex(this.State.textItem._lines, this.State.cursor.line);
      this.State.cursor.local = set.index - lineStart;
    }
    this.refreshUICursor(true);
  }

  createTextItem(point) {
    let Grid = this.SL.Utils.get('Grid');
    if (Grid) {
      // force rounding to left edge of whatever grid cell was clicked in - same trick as classic int(float(val + 0.5))
      let grid = Grid.getCurrentDefinition();
      point = point.subtract(new paper.Point({x: grid.cell.width/2.0, y: grid.cell.height/2.0}));
    }
    let textItem = this.SL.Paper.generatePaperItem({Source: this, Type: 'Text'}, paper.PointText, point);
    this.SL.Paper.applyStyle(textItem, this.config.font);
    this.SL.Paper.Item.addCustomMethod(textItem, 'SnapItem', this.snapTextItem, this);
    this.SL.Paper.Item.addCustomMethod(textItem, 'ScaleItem', this.scaleTextItem, this);
    this.snapTextItem(textItem, { context: 'create' });
    this.setTarget(textItem);
    this.calculateCursor({index: 0});
    this.refreshUITarget();
    this.SL.Paper.emit('TextTool.CreateItem', {item: textItem}, textItem);
  }
  editTextItem(item, point) {
    this.setTarget(item);
    this.calculateCursor({position: {x: point.x - item.bounds.left, y: point.y - item.bounds.top}});
    this.refreshUITarget();
    this.SL.Paper.emit('TextTool.EditItem', {item: item}, item);
  }
  snapTextItem(item, args={}) {
    if (item && item.data && item.data.Type == 'Text') {
      let Snap = this.SL.Utils.get('Snap');
      if (Snap) {
        let pointArgs = {
          context: args.context,
          type: 'text-point',
          item: item,
          interactive: args.interactive,
          target: args.target,
          offset: args.offset
        };
        let refItem = args.original || item;
        let point = Snap.Point(refItem.bounds.topLeft, pointArgs);
        let delta = point.subtract(refItem.bounds.topLeft);
        item.translate(delta);
      }
      if (!args.interactive) {
        var snapSizes = this.config.interactive.allowSizes;
        let fontSize = parseFloat(item.fontSize);
{'fraction_non_alphanumeric': 0.14909733619492488, 'fraction_numerical': 0.005681100871102134, 'mean_word_length': 2.5593125140417885, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6019587', 'n_tokens_mistral': 10444, 'n_tokens_neox': 10225, 'n_words': 1722}