123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750 |
- /**
- * @license
- * Visual Blocks Editor
- *
- * Copyright 2012 Google Inc.
- * https://developers.google.com/blockly/
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- /**
- * @fileoverview Utility methods.
- * These methods are not specific to Blockly, and could be factored out into
- * a JavaScript framework such as Closure.
- * @author fraser@google.com (Neil Fraser)
- */
- 'use strict';
- goog.provide('Blockly.utils');
- goog.require('Blockly.Touch');
- goog.require('goog.dom');
- goog.require('goog.events.BrowserFeature');
- goog.require('goog.math.Coordinate');
- goog.require('goog.userAgent');
- /**
- * Add a CSS class to a element.
- * Similar to Closure's goog.dom.classes.add, except it handles SVG elements.
- * @param {!Element} element DOM element to add class to.
- * @param {string} className Name of class to add.
- * @private
- */
- Blockly.addClass_ = function(element, className) {
- var classes = element.getAttribute('class') || '';
- if ((' ' + classes + ' ').indexOf(' ' + className + ' ') == -1) {
- if (classes) {
- classes += ' ';
- }
- element.setAttribute('class', classes + className);
- }
- };
- /**
- * Remove a CSS class from a element.
- * Similar to Closure's goog.dom.classes.remove, except it handles SVG elements.
- * @param {!Element} element DOM element to remove class from.
- * @param {string} className Name of class to remove.
- * @private
- */
- Blockly.removeClass_ = function(element, className) {
- var classes = element.getAttribute('class');
- if ((' ' + classes + ' ').indexOf(' ' + className + ' ') != -1) {
- var classList = classes.split(/\s+/);
- for (var i = 0; i < classList.length; i++) {
- if (!classList[i] || classList[i] == className) {
- classList.splice(i, 1);
- i--;
- }
- }
- if (classList.length) {
- element.setAttribute('class', classList.join(' '));
- } else {
- element.removeAttribute('class');
- }
- }
- };
- /**
- * Checks if an element has the specified CSS class.
- * Similar to Closure's goog.dom.classes.has, except it handles SVG elements.
- * @param {!Element} element DOM element to check.
- * @param {string} className Name of class to check.
- * @return {boolean} True if class exists, false otherwise.
- * @private
- */
- Blockly.hasClass_ = function(element, className) {
- var classes = element.getAttribute('class');
- return (' ' + classes + ' ').indexOf(' ' + className + ' ') != -1;
- };
- /**
- * Bind an event to a function call. When calling the function, verifies that
- * it belongs to the touch stream that is currently being processsed, and splits
- * multitouch events into multiple events as needed.
- * @param {!Node} node Node upon which to listen.
- * @param {string} name Event name to listen to (e.g. 'mousedown').
- * @param {Object} thisObject The value of 'this' in the function.
- * @param {!Function} func Function to call when event is triggered.
- * @param {boolean} opt_noCaptureIdentifier True if triggering on this event
- * should not block execution of other event handlers on this touch or other
- * simultaneous touches.
- * @return {!Array.<!Array>} Opaque data that can be passed to unbindEvent_.
- * @private
- */
- Blockly.bindEventWithChecks_ = function(node, name, thisObject, func,
- opt_noCaptureIdentifier) {
- var handled = false;
- var wrapFunc = function(e) {
- var captureIdentifier = !opt_noCaptureIdentifier;
- // Handle each touch point separately. If the event was a mouse event, this
- // will hand back an array with one element, which we're fine handling.
- var events = Blockly.Touch.splitEventByTouches(e);
- for (var i = 0, event; event = events[i]; i++) {
- if (captureIdentifier && !Blockly.Touch.shouldHandleEvent(event)) {
- continue;
- }
- Blockly.Touch.setClientFromTouch(event);
- if (thisObject) {
- func.call(thisObject, event);
- } else {
- func(event);
- }
- handled = true;
- }
- };
- node.addEventListener(name, wrapFunc, false);
- var bindData = [[node, name, wrapFunc]];
- // Add equivalent touch event.
- if (name in Blockly.Touch.TOUCH_MAP) {
- var touchWrapFunc = function(e) {
- wrapFunc(e);
- // Stop the browser from scrolling/zooming the page.
- if (handled) {
- e.preventDefault();
- }
- };
- for (var i = 0, eventName;
- eventName = Blockly.Touch.TOUCH_MAP[name][i]; i++) {
- node.addEventListener(eventName, touchWrapFunc, false);
- bindData.push([node, eventName, touchWrapFunc]);
- }
- }
- return bindData;
- };
- /**
- * Bind an event to a function call. Handles multitouch events by using the
- * coordinates of the first changed touch, and doesn't do any safety checks for
- * simultaneous event processing.
- * @deprecated in favor of bindEventWithChecks_, but preserved for external
- * users.
- * @param {!Node} node Node upon which to listen.
- * @param {string} name Event name to listen to (e.g. 'mousedown').
- * @param {Object} thisObject The value of 'this' in the function.
- * @param {!Function} func Function to call when event is triggered.
- * @return {!Array.<!Array>} Opaque data that can be passed to unbindEvent_.
- * @private
- */
- Blockly.bindEvent_ = function(node, name, thisObject, func) {
- var wrapFunc = function(e) {
- if (thisObject) {
- func.call(thisObject, e);
- } else {
- func(e);
- }
- };
- node.addEventListener(name, wrapFunc, false);
- var bindData = [[node, name, wrapFunc]];
- // Add equivalent touch event.
- if (name in Blockly.Touch.TOUCH_MAP) {
- var touchWrapFunc = function(e) {
- // Punt on multitouch events.
- if (e.changedTouches.length == 1) {
- // Map the touch event's properties to the event.
- var touchPoint = e.changedTouches[0];
- e.clientX = touchPoint.clientX;
- e.clientY = touchPoint.clientY;
- }
- wrapFunc(e);
- // Stop the browser from scrolling/zooming the page.
- e.preventDefault();
- };
- for (var i = 0, eventName;
- eventName = Blockly.Touch.TOUCH_MAP[name][i]; i++) {
- node.addEventListener(eventName, touchWrapFunc, false);
- bindData.push([node, eventName, touchWrapFunc]);
- }
- }
- return bindData;
- };
- /**
- * Unbind one or more events event from a function call.
- * @param {!Array.<!Array>} bindData Opaque data from bindEvent_. This list is
- * emptied during the course of calling this function.
- * @return {!Function} The function call.
- * @private
- */
- Blockly.unbindEvent_ = function(bindData) {
- while (bindData.length) {
- var bindDatum = bindData.pop();
- var node = bindDatum[0];
- var name = bindDatum[1];
- var func = bindDatum[2];
- node.removeEventListener(name, func, false);
- }
- return func;
- };
- /**
- * Don't do anything for this event, just halt propagation.
- * @param {!Event} e An event.
- */
- Blockly.noEvent = function(e) {
- // This event has been handled. No need to bubble up to the document.
- e.preventDefault();
- e.stopPropagation();
- };
- /**
- * Is this event targeting a text input widget?
- * @param {!Event} e An event.
- * @return {boolean} True if text input.
- * @private
- */
- Blockly.isTargetInput_ = function(e) {
- return e.target.type == 'textarea' || e.target.type == 'text' ||
- e.target.type == 'number' || e.target.type == 'email' ||
- e.target.type == 'password' || e.target.type == 'search' ||
- e.target.type == 'tel' || e.target.type == 'url' ||
- e.target.isContentEditable;
- };
- /**
- * Return the coordinates of the top-left corner of this element relative to
- * its parent. Only for SVG elements and children (e.g. rect, g, path).
- * @param {!Element} element SVG element to find the coordinates of.
- * @return {!goog.math.Coordinate} Object with .x and .y properties.
- * @private
- */
- Blockly.getRelativeXY_ = function(element) {
- var xy = new goog.math.Coordinate(0, 0);
- // First, check for x and y attributes.
- var x = element.getAttribute('x');
- if (x) {
- xy.x = parseInt(x, 10);
- }
- var y = element.getAttribute('y');
- if (y) {
- xy.y = parseInt(y, 10);
- }
- // Second, check for transform="translate(...)" attribute.
- var transform = element.getAttribute('transform');
- var r = transform && transform.match(Blockly.getRelativeXY_.XY_REGEXP_);
- if (r) {
- xy.x += parseFloat(r[1]);
- if (r[3]) {
- xy.y += parseFloat(r[3]);
- }
- }
- return xy;
- };
- /**
- * Static regex to pull the x,y values out of an SVG translate() directive.
- * Note that Firefox and IE (9,10) return 'translate(12)' instead of
- * 'translate(12, 0)'.
- * Note that IE (9,10) returns 'translate(16 8)' instead of 'translate(16, 8)'.
- * Note that IE has been reported to return scientific notation (0.123456e-42).
- * @type {!RegExp}
- * @private
- */
- Blockly.getRelativeXY_.XY_REGEXP_ =
- /translate\(\s*([-+\d.e]+)([ ,]\s*([-+\d.e]+)\s*\))?/;
- /**
- * Return the absolute coordinates of the top-left corner of this element,
- * scales that after canvas SVG element, if it's a descendant.
- * The origin (0,0) is the top-left corner of the Blockly SVG.
- * @param {!Element} element Element to find the coordinates of.
- * @param {!Blockly.Workspace} workspace Element must be in this workspace.
- * @return {!goog.math.Coordinate} Object with .x and .y properties.
- * @private
- */
- Blockly.getSvgXY_ = function(element, workspace) {
- var x = 0;
- var y = 0;
- var scale = 1;
- if (goog.dom.contains(workspace.getCanvas(), element) ||
- goog.dom.contains(workspace.getBubbleCanvas(), element)) {
- // Before the SVG canvas, scale the coordinates.
- scale = workspace.scale;
- }
- do {
- // Loop through this block and every parent.
- var xy = Blockly.getRelativeXY_(element);
- if (element == workspace.getCanvas() ||
- element == workspace.getBubbleCanvas()) {
- // After the SVG canvas, don't scale the coordinates.
- scale = 1;
- }
- x += xy.x * scale;
- y += xy.y * scale;
- element = element.parentNode;
- } while (element && element != workspace.getParentSvg());
- return new goog.math.Coordinate(x, y);
- };
- /**
- * Helper method for creating SVG elements.
- * @param {string} name Element's tag name.
- * @param {!Object} attrs Dictionary of attribute names and values.
- * @param {Element} parent Optional parent on which to append the element.
- * @param {Blockly.Workspace=} opt_workspace Optional workspace for access to
- * context (scale...).
- * @return {!SVGElement} Newly created SVG element.
- */
- Blockly.createSvgElement = function(name, attrs, parent, opt_workspace) {
- var e = /** @type {!SVGElement} */ (
- document.createElementNS(Blockly.SVG_NS, name));
- for (var key in attrs) {
- e.setAttribute(key, attrs[key]);
- }
- // IE defines a unique attribute "runtimeStyle", it is NOT applied to
- // elements created with createElementNS. However, Closure checks for IE
- // and assumes the presence of the attribute and crashes.
- if (document.body.runtimeStyle) { // Indicates presence of IE-only attr.
- e.runtimeStyle = e.currentStyle = e.style;
- }
- if (parent) {
- parent.appendChild(e);
- }
- return e;
- };
- /**
- * Is this event a right-click?
- * @param {!Event} e Mouse event.
- * @return {boolean} True if right-click.
- */
- Blockly.isRightButton = function(e) {
- if (e.ctrlKey && goog.userAgent.MAC) {
- // Control-clicking on Mac OS X is treated as a right-click.
- // WebKit on Mac OS X fails to change button to 2 (but Gecko does).
- return true;
- }
- return e.button == 2;
- };
- /**
- * Convert between HTML coordinates and SVG coordinates.
- * @param {number} x X input coordinate.
- * @param {number} y Y input coordinate.
- * @param {boolean} toSvg True to convert to SVG coordinates.
- * False to convert to mouse/HTML coordinates.
- * @param {!Element} svg SVG element.
- * @return {!Object} Object with x and y properties in output coordinates.
- */
- Blockly.convertCoordinates = function(x, y, toSvg, svg) {
- if (toSvg) {
- x -= window.scrollX || window.pageXOffset;
- y -= window.scrollY || window.pageYOffset;
- }
- var svgPoint = svg.createSVGPoint();
- svgPoint.x = x;
- svgPoint.y = y;
- var matrix = svg.getScreenCTM();
- if (toSvg) {
- matrix = matrix.inverse();
- }
- var xy = svgPoint.matrixTransform(matrix);
- if (!toSvg) {
- xy.x += window.scrollX || window.pageXOffset;
- xy.y += window.scrollY || window.pageYOffset;
- }
- return xy;
- };
- /**
- * Return the converted coordinates of the given mouse event.
- * The origin (0,0) is the top-left corner of the Blockly svg.
- * @param {!Event} e Mouse event.
- * @param {!Element} svg SVG element.
- * @param {SVGMatrix} matrix Inverted screen CTM to use.
- * @return {!Object} Object with .x and .y properties.
- */
- Blockly.mouseToSvg = function(e, svg, matrix) {
- var svgPoint = svg.createSVGPoint();
- svgPoint.x = e.clientX;
- svgPoint.y = e.clientY;
- if (!matrix) {
- matrix = svg.getScreenCTM().inverse();
- }
- return svgPoint.matrixTransform(matrix);
- };
- /**
- * Given an array of strings, return the length of the shortest one.
- * @param {!Array.<string>} array Array of strings.
- * @return {number} Length of shortest string.
- */
- Blockly.shortestStringLength = function(array) {
- if (!array.length) {
- return 0;
- }
- var len = array[0].length;
- for (var i = 1; i < array.length; i++) {
- len = Math.min(len, array[i].length);
- }
- return len;
- };
- /**
- * Given an array of strings, return the length of the common prefix.
- * Words may not be split. Any space after a word is included in the length.
- * @param {!Array.<string>} array Array of strings.
- * @param {number=} opt_shortest Length of shortest string.
- * @return {number} Length of common prefix.
- */
- Blockly.commonWordPrefix = function(array, opt_shortest) {
- if (!array.length) {
- return 0;
- } else if (array.length == 1) {
- return array[0].length;
- }
- var wordPrefix = 0;
- var max = opt_shortest || Blockly.shortestStringLength(array);
- for (var len = 0; len < max; len++) {
- var letter = array[0][len];
- for (var i = 1; i < array.length; i++) {
- if (letter != array[i][len]) {
- return wordPrefix;
- }
- }
- if (letter == ' ') {
- wordPrefix = len + 1;
- }
- }
- for (var i = 1; i < array.length; i++) {
- var letter = array[i][len];
- if (letter && letter != ' ') {
- return wordPrefix;
- }
- }
- return max;
- };
- /**
- * Given an array of strings, return the length of the common suffix.
- * Words may not be split. Any space after a word is included in the length.
- * @param {!Array.<string>} array Array of strings.
- * @param {number=} opt_shortest Length of shortest string.
- * @return {number} Length of common suffix.
- */
- Blockly.commonWordSuffix = function(array, opt_shortest) {
- if (!array.length) {
- return 0;
- } else if (array.length == 1) {
- return array[0].length;
- }
- var wordPrefix = 0;
- var max = opt_shortest || Blockly.shortestStringLength(array);
- for (var len = 0; len < max; len++) {
- var letter = array[0].substr(-len - 1, 1);
- for (var i = 1; i < array.length; i++) {
- if (letter != array[i].substr(-len - 1, 1)) {
- return wordPrefix;
- }
- }
- if (letter == ' ') {
- wordPrefix = len + 1;
- }
- }
- for (var i = 1; i < array.length; i++) {
- var letter = array[i].charAt(array[i].length - len - 1);
- if (letter && letter != ' ') {
- return wordPrefix;
- }
- }
- return max;
- };
- /**
- * Is the given string a number (includes negative and decimals).
- * @param {string} str Input string.
- * @return {boolean} True if number, false otherwise.
- */
- Blockly.isNumber = function(str) {
- return !!str.match(/^\s*-?\d+(\.\d+)?\s*$/);
- };
- /**
- * Parse a string with any number of interpolation tokens (%1, %2, ...).
- * '%' characters may be self-escaped (%%).
- * @param {string} message Text containing interpolation tokens.
- * @return {!Array.<string|number>} Array of strings and numbers.
- */
- Blockly.utils.tokenizeInterpolation = function(message) {
- var tokens = [];
- var chars = message.split('');
- chars.push(''); // End marker.
- // Parse the message with a finite state machine.
- // 0 - Base case.
- // 1 - % found.
- // 2 - Digit found.
- var state = 0;
- var buffer = [];
- var number = null;
- for (var i = 0; i < chars.length; i++) {
- var c = chars[i];
- if (state == 0) {
- if (c == '%') {
- state = 1; // Start escape.
- } else {
- buffer.push(c); // Regular char.
- }
- } else if (state == 1) {
- if (c == '%') {
- buffer.push(c); // Escaped %: %%
- state = 0;
- } else if ('0' <= c && c <= '9') {
- state = 2;
- number = c;
- var text = buffer.join('');
- if (text) {
- tokens.push(text);
- }
- buffer.length = 0;
- } else {
- buffer.push('%', c); // Not an escape: %a
- state = 0;
- }
- } else if (state == 2) {
- if ('0' <= c && c <= '9') {
- number += c; // Multi-digit number.
- } else {
- tokens.push(parseInt(number, 10));
- i--; // Parse this char again.
- state = 0;
- }
- }
- }
- var text = buffer.join('');
- if (text) {
- tokens.push(text);
- }
- return tokens;
- };
- /**
- * Generate a unique ID. This should be globally unique.
- * 87 characters ^ 20 length > 128 bits (better than a UUID).
- * @return {string} A globally unique ID string.
- */
- Blockly.genUid = function() {
- var length = 20;
- var soupLength = Blockly.genUid.soup_.length;
- var id = [];
- for (var i = 0; i < length; i++) {
- id[i] = Blockly.genUid.soup_.charAt(Math.random() * soupLength);
- }
- return id.join('');
- };
- /**
- * Legal characters for the unique ID. Should be all on a US keyboard.
- * No characters that conflict with XML or JSON. Requests to remove additional
- * 'problematic' characters from this soup will be denied. That's your failure
- * to properly escape in your own environment. Issues #251, #625, #682.
- * @private
- */
- Blockly.genUid.soup_ = '!#$%()*+,-./:;=?@[]^_`{|}~' +
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
- /**
- * Wrap text to the specified width.
- * @param {string} text Text to wrap.
- * @param {number} limit Width to wrap each line.
- * @return {string} Wrapped text.
- */
- Blockly.utils.wrap = function(text, limit) {
- var lines = text.split('\n');
- for (var i = 0; i < lines.length; i++) {
- lines[i] = Blockly.utils.wrap_line_(lines[i], limit);
- }
- return lines.join('\n');
- };
- /**
- * Wrap single line of text to the specified width.
- * @param {string} text Text to wrap.
- * @param {number} limit Width to wrap each line.
- * @return {string} Wrapped text.
- * @private
- */
- Blockly.utils.wrap_line_ = function(text, limit) {
- if (text.length <= limit) {
- // Short text, no need to wrap.
- return text;
- }
- // Split the text into words.
- var words = text.trim().split(/\s+/);
- // Set limit to be the length of the largest word.
- for (var i = 0; i < words.length; i++) {
- if (words[i].length > limit) {
- limit = words[i].length;
- }
- }
- var lastScore;
- var score = -Infinity;
- var lastText;
- var lineCount = 1;
- do {
- lastScore = score;
- lastText = text;
- // Create a list of booleans representing if a space (false) or
- // a break (true) appears after each word.
- var wordBreaks = [];
- // Seed the list with evenly spaced linebreaks.
- var steps = words.length / lineCount;
- var insertedBreaks = 1;
- for (var i = 0; i < words.length - 1; i++) {
- if (insertedBreaks < (i + 1.5) / steps) {
- insertedBreaks++;
- wordBreaks[i] = true;
- } else {
- wordBreaks[i] = false;
- }
- }
- wordBreaks = Blockly.utils.wrapMutate_(words, wordBreaks, limit);
- score = Blockly.utils.wrapScore_(words, wordBreaks, limit);
- text = Blockly.utils.wrapToText_(words, wordBreaks);
- lineCount++;
- } while (score > lastScore);
- return lastText;
- };
- /**
- * Compute a score for how good the wrapping is.
- * @param {!Array.<string>} words Array of each word.
- * @param {!Array.<boolean>} wordBreaks Array of line breaks.
- * @param {number} limit Width to wrap each line.
- * @return {number} Larger the better.
- * @private
- */
- Blockly.utils.wrapScore_ = function(words, wordBreaks, limit) {
- // If this function becomes a performance liability, add caching.
- // Compute the length of each line.
- var lineLengths = [0];
- var linePunctuation = [];
- for (var i = 0; i < words.length; i++) {
- lineLengths[lineLengths.length - 1] += words[i].length;
- if (wordBreaks[i] === true) {
- lineLengths.push(0);
- linePunctuation.push(words[i].charAt(words[i].length - 1));
- } else if (wordBreaks[i] === false) {
- lineLengths[lineLengths.length - 1]++;
- }
- }
- var maxLength = Math.max.apply(Math, lineLengths);
- var score = 0;
- for (var i = 0; i < lineLengths.length; i++) {
- // Optimize for width.
- // -2 points per char over limit (scaled to the power of 1.5).
- score -= Math.pow(Math.abs(limit - lineLengths[i]), 1.5) * 2;
- // Optimize for even lines.
- // -1 point per char smaller than max (scaled to the power of 1.5).
- score -= Math.pow(maxLength - lineLengths[i], 1.5);
- // Optimize for structure.
- // Add score to line endings after punctuation.
- if ('.?!'.indexOf(linePunctuation[i]) != -1) {
- score += limit / 3;
- } else if (',;)]}'.indexOf(linePunctuation[i]) != -1) {
- score += limit / 4;
- }
- }
- // All else being equal, the last line should not be longer than the
- // previous line. For example, this looks wrong:
- // aaa bbb
- // ccc ddd eee
- if (lineLengths.length > 1 && lineLengths[lineLengths.length - 1] <=
- lineLengths[lineLengths.length - 2]) {
- score += 0.5;
- }
- return score;
- };
- /**
- * Mutate the array of line break locations until an optimal solution is found.
- * No line breaks are added or deleted, they are simply moved around.
- * @param {!Array.<string>} words Array of each word.
- * @param {!Array.<boolean>} wordBreaks Array of line breaks.
- * @param {number} limit Width to wrap each line.
- * @return {!Array.<boolean>} New array of optimal line breaks.
- * @private
- */
- Blockly.utils.wrapMutate_ = function(words, wordBreaks, limit) {
- var bestScore = Blockly.utils.wrapScore_(words, wordBreaks, limit);
- var bestBreaks;
- // Try shifting every line break forward or backward.
- for (var i = 0; i < wordBreaks.length - 1; i++) {
- if (wordBreaks[i] == wordBreaks[i + 1]) {
- continue;
- }
- var mutatedWordBreaks = [].concat(wordBreaks);
- mutatedWordBreaks[i] = !mutatedWordBreaks[i];
- mutatedWordBreaks[i + 1] = !mutatedWordBreaks[i + 1];
- var mutatedScore =
- Blockly.utils.wrapScore_(words, mutatedWordBreaks, limit);
- if (mutatedScore > bestScore) {
- bestScore = mutatedScore;
- bestBreaks = mutatedWordBreaks;
- }
- }
- if (bestBreaks) {
- // Found an improvement. See if it may be improved further.
- return Blockly.utils.wrapMutate_(words, bestBreaks, limit);
- }
- // No improvements found. Done.
- return wordBreaks;
- };
- /**
- * Reassemble the array of words into text, with the specified line breaks.
- * @param {!Array.<string>} words Array of each word.
- * @param {!Array.<boolean>} wordBreaks Array of line breaks.
- * @return {string} Plain text.
- * @private
- */
- Blockly.utils.wrapToText_ = function(words, wordBreaks) {
- var text = [];
- for (var i = 0; i < words.length; i++) {
- text.push(words[i]);
- if (wordBreaks[i] !== undefined) {
- text.push(wordBreaks[i] ? '\n' : ' ');
- }
- }
- return text.join('');
- };
|