Compare commits
37 Commits
greenkeepe
...
greenkeepe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd385bb43d | ||
|
|
6fe4c33365 | ||
|
|
6035ea29d0 | ||
|
|
bdde0168ae | ||
|
|
0d1d0d3552 | ||
|
|
f10f18042e | ||
|
|
ebd5378936 | ||
|
|
a4b8460cf0 | ||
|
|
18d4567d66 | ||
|
|
ac8ab4e495 | ||
|
|
eb95e2e663 | ||
|
|
9b869e0da7 | ||
|
|
d7e39a35d7 | ||
|
|
44c30d76ad | ||
|
|
d7b26ea1be | ||
|
|
7f53a4a19e | ||
|
|
d8f82b21ec | ||
|
|
13a75b6a7b | ||
|
|
4a737352d6 | ||
|
|
75ad498f3c | ||
|
|
de6b2dea79 | ||
|
|
d9f2d14306 | ||
|
|
5009692426 | ||
|
|
7cd92cd125 | ||
|
|
88d78b5f9f | ||
|
|
59413a4c2b | ||
|
|
8abffc7f0b | ||
|
|
9d84dfdd0e | ||
|
|
5205f9cb87 | ||
|
|
f9e2fe5c80 | ||
|
|
ce4296be82 | ||
|
|
2dac278f90 | ||
|
|
39ce51c6f0 | ||
|
|
71ecef29a2 | ||
|
|
528ae873d7 | ||
|
|
b7ac54293d | ||
|
|
2a4f6f9f02 |
@@ -1,3 +1,4 @@
|
||||
dist/*
|
||||
node_modules/*
|
||||
playground/*
|
||||
tap-snapshots/*
|
||||
|
||||
16
.npmignore
16
.npmignore
@@ -0,0 +1,16 @@
|
||||
# Development files
|
||||
.eslintrc.js
|
||||
/.editorconfig
|
||||
/.eslintignore
|
||||
/.gitattributes
|
||||
/.github
|
||||
/.jsdoc.json
|
||||
/.travis.yml
|
||||
/test
|
||||
/webpack.config.js
|
||||
|
||||
# Build created files
|
||||
/playground
|
||||
|
||||
# Exclude already built packages from testing with npm pack
|
||||
/scratch-render-*.{tar,tgz}
|
||||
|
||||
@@ -1,7 +1 @@
|
||||
The Scratch trademarks, including the Scratch name, logo, the Scratch Cat, Gobo, Pico, Nano, Tera and Giga graphics (the "Marks"), are property of the Massachusetts Institute of Technology (MIT), and the use of the Marks is governed by this policy.
|
||||
|
||||
You may use the Marks to refer to Scratch in Substantially Unmodified form.
|
||||
|
||||
"Substantially Unmodified" means the source code provided by MIT, possibly with minor modifications including but not limited to: bug fixes (including security), changing the locations of files for better integration with the host operating system, adding documentation, and changes to the dynamic linking of libraries.
|
||||
|
||||
A version is not "Substantially Unmodified" if it incorporates features not present in a release of Scratch by MIT. If you do make a substantial modification, to avoid confusion with versions of Scratch produced by MIT you must remove all Marks from your version of the software and refrain from using any of the Marks to refer to your version.
|
||||
The Scratch trademarks, including the Scratch name, logo, the Scratch Cat, Gobo, Pico, Nano, Tera and Giga graphics (the "Marks"), are property of the Massachusetts Institute of Technology (MIT). Marks may not be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
28
package.json
28
package.json
@@ -10,7 +10,7 @@
|
||||
"url": "git+ssh://git@github.com/LLK/scratch-render.git"
|
||||
},
|
||||
"main": "./dist/node/scratch-render.js",
|
||||
"browser": "./dist/web/scratch-render.js",
|
||||
"browser": "./src/index.js",
|
||||
"scripts": {
|
||||
"build": "webpack --progress --colors",
|
||||
"docs": "jsdoc -c .jsdoc.json",
|
||||
@@ -35,23 +35,25 @@
|
||||
"eslint": "^4.6.1",
|
||||
"eslint-config-scratch": "^5.0.0",
|
||||
"gh-pages": "^1.0.0",
|
||||
"jsdoc": "^3.5.5",
|
||||
"json": "^9.0.4",
|
||||
"scratch-vm": "0.1.0-prerelease.1527254075",
|
||||
"tap": "^11.0.0",
|
||||
"travis-after-all": "^1.4.4",
|
||||
"uglifyjs-webpack-plugin": "^1.2.5",
|
||||
"webpack": "^4.15.1",
|
||||
"webpack-cli": "^2.0.15",
|
||||
"webpack-dev-server": "^3.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"grapheme-breaker": "0.3.2",
|
||||
"hull.js": "0.2.10",
|
||||
"ify-loader": "1.0.4",
|
||||
"jsdoc": "^3.5.5",
|
||||
"json": "^9.0.4",
|
||||
"linebreak": "0.3.0",
|
||||
"minilog": "3.1.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"scratch-storage": "^0.4.0",
|
||||
"scratch-svg-renderer": "0.1.0-prerelease.20180514170126",
|
||||
"scratch-vm": "0.1.0-prerelease.1525975472",
|
||||
"tap": "^11.0.0",
|
||||
"travis-after-all": "^1.4.4",
|
||||
"twgl.js": "4.4.0",
|
||||
"uglifyjs-webpack-plugin": "^1.2.5",
|
||||
"webpack": "^4.8.0",
|
||||
"webpack-cli": "^2.0.15",
|
||||
"webpack-dev-server": "^3.1.4",
|
||||
"xml-escape": "1.1.0"
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20180613184320",
|
||||
"twgl.js": "4.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ const ShaderManager = require('./ShaderManager');
|
||||
const SVGSkin = require('./SVGSkin');
|
||||
const SVGTextBubble = require('./util/svg-text-bubble');
|
||||
const EffectTransform = require('./EffectTransform');
|
||||
const log = require('./util/log');
|
||||
|
||||
const __isTouchingDrawablesPoint = twgl.v3.create();
|
||||
const __candidatesBounds = new Rectangle();
|
||||
@@ -120,6 +121,22 @@ class RenderWebGL extends EventEmitter {
|
||||
/** @type {Array<int>} */
|
||||
this._drawList = [];
|
||||
|
||||
// A list of layer group names in the order they should appear
|
||||
// from furthest back to furthest in front.
|
||||
/** @type {Array<String>} */
|
||||
this._groupOrdering = [];
|
||||
|
||||
/**
|
||||
* @typedef LayerGroup
|
||||
* @property {int} groupIndex The relative position of this layer group in the group ordering
|
||||
* @property {int} drawListOffset The absolute position of this layer group in the draw list
|
||||
* This number gets updated as drawables get added to or deleted from the draw list.
|
||||
*/
|
||||
|
||||
// Map of group name to layer group
|
||||
/** @type {Object.<string, LayerGroup>} */
|
||||
this._layerGroups = {};
|
||||
|
||||
/** @type {int} */
|
||||
this._nextDrawableId = RenderConstants.ID_NONE + 1;
|
||||
|
||||
@@ -360,31 +377,100 @@ class RenderWebGL extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Create a new Drawable and add it to the scene.
|
||||
* @param {string} group Layer group to add the drawable to
|
||||
* @returns {int} The ID of the new Drawable.
|
||||
*/
|
||||
createDrawable () {
|
||||
createDrawable (group) {
|
||||
if (!group || !this._layerGroups.hasOwnProperty(group)) {
|
||||
log.warn('Cannot create a drawable without a known layer group');
|
||||
return;
|
||||
}
|
||||
const drawableID = this._nextDrawableId++;
|
||||
const drawable = new Drawable(drawableID);
|
||||
this._allDrawables[drawableID] = drawable;
|
||||
this._drawList.push(drawableID);
|
||||
this._addToDrawList(drawableID, group);
|
||||
|
||||
drawable.skin = null;
|
||||
|
||||
return drawableID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the layer group ordering for the renderer.
|
||||
* @param {Array<string>} groupOrdering The ordered array of layer group
|
||||
* names
|
||||
*/
|
||||
setLayerGroupOrdering (groupOrdering) {
|
||||
this._groupOrdering = groupOrdering;
|
||||
for (let i = 0; i < this._groupOrdering.length; i++) {
|
||||
this._layerGroups[this._groupOrdering[i]] = {
|
||||
groupIndex: i,
|
||||
drawListOffset: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
_addToDrawList (drawableID, group) {
|
||||
const currentLayerGroup = this._layerGroups[group];
|
||||
const currentGroupOrderingIndex = currentLayerGroup.groupIndex;
|
||||
|
||||
const drawListOffset = this._endIndexForKnownLayerGroup(currentLayerGroup);
|
||||
this._drawList.splice(drawListOffset, 0, drawableID);
|
||||
|
||||
this._updateOffsets('add', currentGroupOrderingIndex);
|
||||
}
|
||||
|
||||
_updateOffsets (updateType, currentGroupOrderingIndex) {
|
||||
for (let i = currentGroupOrderingIndex + 1; i < this._groupOrdering.length; i++) {
|
||||
const laterGroupName = this._groupOrdering[i];
|
||||
if (updateType === 'add') {
|
||||
this._layerGroups[laterGroupName].drawListOffset++;
|
||||
} else if (updateType === 'delete'){
|
||||
this._layerGroups[laterGroupName].drawListOffset--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Given a layer group, return the index where it ends (non-inclusive),
|
||||
// e.g. the returned index does not have a drawable from this layer group in it)
|
||||
_endIndexForKnownLayerGroup (layerGroup) {
|
||||
const groupIndex = layerGroup.groupIndex;
|
||||
if (groupIndex === this._groupOrdering.length - 1) {
|
||||
return this._drawList.length;
|
||||
}
|
||||
return this._layerGroups[this._groupOrdering[groupIndex + 1]].drawListOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy a Drawable, removing it from the scene.
|
||||
* @param {int} drawableID The ID of the Drawable to remove.
|
||||
* @param {string} group Group name that the drawable belongs to
|
||||
*/
|
||||
destroyDrawable (drawableID) {
|
||||
destroyDrawable (drawableID, group) {
|
||||
if (!group || !this._layerGroups.hasOwnProperty(group)) {
|
||||
log.warn('Cannot destroy drawable without known layer group.');
|
||||
return;
|
||||
}
|
||||
const drawable = this._allDrawables[drawableID];
|
||||
drawable.dispose();
|
||||
delete this._allDrawables[drawableID];
|
||||
|
||||
let index;
|
||||
while ((index = this._drawList.indexOf(drawableID)) >= 0) {
|
||||
const currentLayerGroup = this._layerGroups[group];
|
||||
const endIndex = this._endIndexForKnownLayerGroup(currentLayerGroup);
|
||||
|
||||
let index = currentLayerGroup.drawListOffset;
|
||||
while (index < endIndex) {
|
||||
if (this._drawList[index] === drawableID) {
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
if (index < endIndex) {
|
||||
this._drawList.splice(index, 1);
|
||||
this._updateOffsets('delete', currentLayerGroup.groupIndex);
|
||||
} else {
|
||||
log.warn('Could not destroy drawable that could not be found in layer group.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,28 +483,55 @@ class RenderWebGL extends EventEmitter {
|
||||
* "go to front": setDrawableOrder(id, Infinity);
|
||||
* @param {int} drawableID ID of Drawable to reorder.
|
||||
* @param {number} order New absolute order or relative order adjusment.
|
||||
* @param {string=} group Name of layer group drawable belongs to.
|
||||
* Reordering will not take place if drawable cannot be found within the bounds
|
||||
* of the layer group.
|
||||
* @param {boolean=} optIsRelative If set, `order` refers to a relative change.
|
||||
* @param {number=} optMin If set, order constrained to be at least `optMin`.
|
||||
* @return {?number} New order if changed, or null.
|
||||
*/
|
||||
setDrawableOrder (drawableID, order, optIsRelative, optMin) {
|
||||
const oldIndex = this._drawList.indexOf(drawableID);
|
||||
if (oldIndex >= 0) {
|
||||
setDrawableOrder (drawableID, order, group, optIsRelative, optMin) {
|
||||
if (!group || !this._layerGroups.hasOwnProperty(group)) {
|
||||
log.warn('Cannot set the order of a drawable without a known layer group.');
|
||||
return;
|
||||
}
|
||||
|
||||
const currentLayerGroup = this._layerGroups[group];
|
||||
const startIndex = currentLayerGroup.drawListOffset;
|
||||
const endIndex = this._endIndexForKnownLayerGroup(currentLayerGroup);
|
||||
|
||||
let oldIndex = startIndex;
|
||||
while (oldIndex < endIndex) {
|
||||
if (this._drawList[oldIndex] === drawableID) {
|
||||
break;
|
||||
}
|
||||
oldIndex++;
|
||||
}
|
||||
|
||||
if (oldIndex < endIndex) {
|
||||
// Remove drawable from the list.
|
||||
const drawable = this._drawList.splice(oldIndex, 1)[0];
|
||||
if (order === 0) {
|
||||
return oldIndex;
|
||||
}
|
||||
|
||||
const _ = this._drawList.splice(oldIndex, 1)[0];
|
||||
// Determine new index.
|
||||
let newIndex = order;
|
||||
if (optIsRelative) {
|
||||
newIndex += oldIndex;
|
||||
}
|
||||
if (optMin) {
|
||||
newIndex = Math.max(newIndex, optMin);
|
||||
}
|
||||
newIndex = Math.max(newIndex, 0);
|
||||
|
||||
const possibleMin = (optMin || 0) + startIndex;
|
||||
const min = (possibleMin >= startIndex && possibleMin < endIndex) ? possibleMin : startIndex;
|
||||
newIndex = Math.max(newIndex, min);
|
||||
|
||||
newIndex = Math.min(newIndex, endIndex);
|
||||
|
||||
// Insert at new index.
|
||||
this._drawList.splice(newIndex, 0, drawable);
|
||||
return this._drawList.indexOf(drawable);
|
||||
this._drawList.splice(newIndex, 0, drawableID);
|
||||
return newIndex;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,11 +59,11 @@ class Silhouette {
|
||||
const height = this._height = canvas.height = bitmapData.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
ctx.drawImage(bitmapData, 0, 0, width, height);
|
||||
if (!(width && height)) {
|
||||
return;
|
||||
}
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
ctx.drawImage(bitmapData, 0, 0, width, height);
|
||||
const imageData = ctx.getImageData(0, 0, width, height);
|
||||
|
||||
this._data = new Uint8ClampedArray(imageData.data.length / 4);
|
||||
|
||||
@@ -38,15 +38,16 @@
|
||||
var canvas = document.getElementById('scratch-stage');
|
||||
var fudge = 90;
|
||||
var renderer = new ScratchRender(canvas);
|
||||
renderer.setLayerGroupOrdering(['group1']);
|
||||
|
||||
var drawableID = renderer.createDrawable();
|
||||
var drawableID = renderer.createDrawable('group1');
|
||||
renderer.updateDrawableProperties(drawableID, {
|
||||
position: [0, 0],
|
||||
scale: [100, 100],
|
||||
direction: 90
|
||||
});
|
||||
|
||||
var drawableID2 = renderer.createDrawable();
|
||||
var drawableID2 = renderer.createDrawable('group1');
|
||||
var wantBitmapSkin = false;
|
||||
|
||||
// Bitmap (squirrel)
|
||||
|
||||
4
src/util/log.js
Normal file
4
src/util/log.js
Normal file
@@ -0,0 +1,4 @@
|
||||
const minilog = require('minilog');
|
||||
minilog.enable();
|
||||
|
||||
module.exports = minilog('scratch-render');
|
||||
@@ -1,17 +1,30 @@
|
||||
const SVGTextWrapper = require('./svg-text-wrapper');
|
||||
const SvgRenderer = require('scratch-svg-renderer').SVGRenderer;
|
||||
const xmlescape = require('xml-escape');
|
||||
|
||||
const MAX_LINE_LENGTH = 170;
|
||||
const MIN_WIDTH = 50;
|
||||
const STROKE_WIDTH = 4;
|
||||
|
||||
class SVGTextBubble {
|
||||
constructor () {
|
||||
this.svgRenderer = new SvgRenderer();
|
||||
this.svgTextWrapper = new SVGTextWrapper();
|
||||
this.svgTextWrapper = new SVGTextWrapper(this.makeSvgTextElement);
|
||||
this._textSizeCache = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {SVGElement} an SVG text node with the properties that we want for speech bubbles.
|
||||
*/
|
||||
makeSvgTextElement () {
|
||||
const svgText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||
svgText.setAttribute('alignment-baseline', 'text-before-edge');
|
||||
svgText.setAttribute('font-size', '14');
|
||||
svgText.setAttribute('fill', '#575E75');
|
||||
// TODO Do we want to use the new default sans font instead of Helvetica?
|
||||
svgText.setAttribute('font-family', 'Helvetica');
|
||||
return svgText;
|
||||
}
|
||||
|
||||
_speechBubble (w, h, radius, pointsLeft) {
|
||||
let pathString = `
|
||||
M 0 ${radius}
|
||||
@@ -45,7 +58,7 @@ class SVGTextBubble {
|
||||
<path
|
||||
d="${pathString}"
|
||||
stroke="rgba(0, 0, 0, 0.15)"
|
||||
stroke-width="4"
|
||||
stroke-width="${STROKE_WIDTH}"
|
||||
fill="rgba(0, 0, 0, 0.15)"
|
||||
stroke-line-join="round"
|
||||
/>
|
||||
@@ -101,7 +114,7 @@ class SVGTextBubble {
|
||||
rx="${rx}" ry="${ry}"
|
||||
fill="rgba(0, 0, 0, 0.15)"
|
||||
stroke="rgba(0, 0, 0, 0.15)"
|
||||
stroke-width="4"
|
||||
stroke-width="${STROKE_WIDTH}"
|
||||
/>
|
||||
<ellipse
|
||||
cx="${cx}" cy="${cy}"
|
||||
@@ -125,7 +138,8 @@ class SVGTextBubble {
|
||||
|
||||
return `
|
||||
<g>
|
||||
<path d="${pathString}" stroke="rgba(0, 0, 0, 0.15)" stroke-width="4" fill="rgba(0, 0, 0, 0.15)" />
|
||||
<path d="${pathString}" stroke="rgba(0, 0, 0, 0.15)" stroke-width="${STROKE_WIDTH}"
|
||||
fill="rgba(0, 0, 0, 0.15)" />
|
||||
<path d="${pathString}" stroke="none" fill="white" />
|
||||
${ellipses.join('\n')}
|
||||
</g>`;
|
||||
@@ -133,30 +147,38 @@ class SVGTextBubble {
|
||||
|
||||
|
||||
_getTextSize () {
|
||||
const svgString = this._wrapSvgFragment(this._textFragment());
|
||||
const svgString = this._wrapSvgFragment(this._textFragment);
|
||||
if (!this._textSizeCache[svgString]) {
|
||||
this._textSizeCache[svgString] = this.svgRenderer.measure(svgString);
|
||||
}
|
||||
return this._textSizeCache[svgString];
|
||||
}
|
||||
|
||||
_wrapSvgFragment (fragment) {
|
||||
return `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
${fragment}
|
||||
</svg>
|
||||
`;
|
||||
_wrapSvgFragment (fragment, width, height) {
|
||||
let svgString = `<svg xmlns="http://www.w3.org/2000/svg" version="1.1"`;
|
||||
if (width && height) {
|
||||
const fullWidth = width + STROKE_WIDTH;
|
||||
const fullHeight = height + STROKE_WIDTH + 12;
|
||||
svgString = `${svgString} viewBox="
|
||||
${-STROKE_WIDTH / 2} ${-STROKE_WIDTH / 2} ${fullWidth} ${fullHeight}"
|
||||
width="${fullWidth}" height="${fullHeight}">`;
|
||||
} else {
|
||||
svgString = `${svgString}>`;
|
||||
}
|
||||
svgString = `${svgString} ${fragment} </svg>`;
|
||||
return svgString;
|
||||
}
|
||||
|
||||
_textFragment () {
|
||||
const attrs = `font-family="Helvetica, Arial, sans-serif" font-size="14px" fill="#575E75"`;
|
||||
return `<text ${attrs}>${xmlescape(this.lines.join('\n'))}</text>`;
|
||||
_buildTextFragment (text) {
|
||||
const textNode = this.svgTextWrapper.wrapText(MAX_LINE_LENGTH, text);
|
||||
const serializer = new XMLSerializer();
|
||||
return serializer.serializeToString(textNode);
|
||||
}
|
||||
|
||||
buildString (type, text, pointsLeft) {
|
||||
this.type = type;
|
||||
this.pointsLeft = pointsLeft;
|
||||
this.lines = this.svgTextWrapper.wrapText(MAX_LINE_LENGTH, text);
|
||||
this._textFragment = this._buildTextFragment(text);
|
||||
|
||||
let fragment = '';
|
||||
|
||||
@@ -170,8 +192,8 @@ class SVGTextBubble {
|
||||
} else {
|
||||
fragment += this._thinkBubble(fullWidth, fullHeight, radius, this.pointsLeft);
|
||||
}
|
||||
fragment += `<g transform="translate(${padding - x}, ${padding - y})">${this._textFragment()}</g>`;
|
||||
return this._wrapSvgFragment(fragment);
|
||||
fragment += `<g transform="translate(${padding - x}, ${padding - y})">${this._textFragment}</g>`;
|
||||
return this._wrapSvgFragment(fragment, fullWidth, fullHeight);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,14 @@ const TextWrapper = require('./text-wrapper');
|
||||
* For use with TextWrapper.
|
||||
*/
|
||||
class SVGMeasurementProvider {
|
||||
constructor () {
|
||||
/**
|
||||
* @param {function} makeTextElement - provides a text node of an SVGElement
|
||||
* with the style of the text to be wrapped.
|
||||
*/
|
||||
constructor (makeTextElement) {
|
||||
this._svgRoot = null;
|
||||
this._cache = {};
|
||||
this.makeTextElement = makeTextElement;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,16 +66,7 @@ class SVGMeasurementProvider {
|
||||
|
||||
const svgRoot = document.createElementNS(svgNamespace, 'svg');
|
||||
const svgGroup = document.createElementNS(svgNamespace, 'g');
|
||||
const svgText = document.createElementNS(svgNamespace, 'text');
|
||||
|
||||
// Normalize text attributes to match what the svg-renderer does.
|
||||
// @TODO This code should be shared with the svg-renderer.
|
||||
svgText.setAttribute('alignment-baseline', 'text-before-edge');
|
||||
svgText.setAttribute('font-size', '14');
|
||||
|
||||
// TODO Do we want to use the new default sans font instead of Helvetica?
|
||||
// This change intentionally subverts the svg-renderer auto font conversion.
|
||||
svgText.setAttribute('font-family', 'Helvetica, Arial, sans-serif');
|
||||
const svgText = this.makeTextElement();
|
||||
|
||||
// hide from the user, including screen readers
|
||||
svgRoot.setAttribute('style', 'position:absolute;visibility:hidden');
|
||||
@@ -99,8 +95,32 @@ class SVGMeasurementProvider {
|
||||
* TextWrapper specialized for SVG text.
|
||||
*/
|
||||
class SVGTextWrapper extends TextWrapper {
|
||||
constructor () {
|
||||
super(new SVGMeasurementProvider());
|
||||
/**
|
||||
* @param {function} makeTextElement - provides a text node of an SVGElement
|
||||
* with the style of the text to be wrapped.
|
||||
*/
|
||||
constructor (makeTextElement) {
|
||||
super(new SVGMeasurementProvider(makeTextElement));
|
||||
this.makeTextElement = makeTextElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the provided text into lines restricted to a maximum width. See Unicode Standard Annex (UAX) #14.
|
||||
* @param {number} maxWidth - the maximum allowed width of a line.
|
||||
* @param {string} text - the text to be wrapped. Will be split on whitespace.
|
||||
* @returns {SVGElement} wrapped text node
|
||||
*/
|
||||
wrapText (maxWidth, text) {
|
||||
const lines = super.wrapText(maxWidth, text);
|
||||
const textElement = this.makeTextElement();
|
||||
for (const line of lines) {
|
||||
const tspanNode = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
|
||||
tspanNode.setAttribute('x', '0');
|
||||
tspanNode.setAttribute('dy', '1.2em');
|
||||
tspanNode.textContent = line;
|
||||
textElement.appendChild(tspanNode);
|
||||
}
|
||||
return textElement;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const LineBreaker = require('linebreak');
|
||||
const GraphemeBreaker = require('grapheme-breaker');
|
||||
const LineBreaker = require('!ify-loader!linebreak');
|
||||
const GraphemeBreaker = require('!ify-loader!grapheme-breaker');
|
||||
|
||||
/**
|
||||
* Tell this text wrapper to use a specific measurement provider.
|
||||
|
||||
10
tap-snapshots/test-integration-scratch-tests.js-TAP.test.js
Normal file
10
tap-snapshots/test-integration-scratch-tests.js-TAP.test.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/* IMPORTANT
|
||||
* This snapshot file is auto-generated, but designed for humans.
|
||||
* It should be checked into source control and tracked carefully.
|
||||
* Re-generate by setting TAP_SNAPSHOT=1 and running tests.
|
||||
* Make sure to inspect the output below. Do not ignore changes!
|
||||
*/
|
||||
'use strict'
|
||||
exports[`test/integration/scratch-tests.js TAP bubble snapshot > bubble-text-snapshot 1`] = `
|
||||
<text xmlns="http://www.w3.org/2000/svg" alignment-baseline="text-before-edge" font-size="14" fill="#575E75" font-family="Helvetica"><tspan x="0" dy="1.2em"><e*&%$&^$></!abc'></tspan></text>
|
||||
`
|
||||
@@ -1,4 +1,4 @@
|
||||
/* global vm, Promise */
|
||||
/* global vm, render, Promise */
|
||||
const {Chromeless} = require('chromeless');
|
||||
const test = require('tap').test;
|
||||
const path = require('path');
|
||||
@@ -101,6 +101,16 @@ const testFile = file => test(file, async t => {
|
||||
}
|
||||
});
|
||||
|
||||
const testBubbles = () => test('bubble snapshot', async t => {
|
||||
const bubbleSvg = await chromeless.goto(`file://${indexHTML}`)
|
||||
.evaluate(() => {
|
||||
const testString = '<e*&%$&^$></!abc\'>';
|
||||
return render._svgTextBubble._buildTextFragment(testString);
|
||||
});
|
||||
t.matchSnapshot(bubbleSvg, 'bubble-text-snapshot');
|
||||
t.end();
|
||||
});
|
||||
|
||||
// immediately invoked async function to let us wait for each test to finish before starting the next.
|
||||
(async () => {
|
||||
const files = fs.readdirSync(testDir())
|
||||
@@ -110,6 +120,8 @@ const testFile = file => test(file, async t => {
|
||||
await testFile(file);
|
||||
}
|
||||
|
||||
await testBubbles();
|
||||
|
||||
// close the browser window we used
|
||||
await chromeless.end();
|
||||
})();
|
||||
|
||||
@@ -21,10 +21,6 @@ const base = {
|
||||
options: {
|
||||
presets: [['env', {targets: {browsers: ['last 3 versions', 'Safari >= 8', 'iOS >= 8']}}]]
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /node_modules[\\/](linebreak|grapheme-breaker)[\\/].*\.js$/,
|
||||
loader: 'ify-loader'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -84,6 +80,14 @@ module.exports = [
|
||||
libraryTarget: 'commonjs2',
|
||||
path: path.resolve('dist', 'node'),
|
||||
filename: '[name].js'
|
||||
},
|
||||
externals: {
|
||||
'!ify-loader!grapheme-breaker': 'grapheme-breaker',
|
||||
'!ify-loader!linebreak': 'linebreak',
|
||||
'hull.js': true,
|
||||
'scratch-svg-renderer': true,
|
||||
'twgl.js': true,
|
||||
'xml-escape': true
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user