This commit is contained in:
Rick Companje 2018-01-25 15:40:32 +01:00
commit 908050bbd0
42 changed files with 573 additions and 165 deletions

BIN
img/contextmenu/btnFont.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

65
package-lock.json generated
View File

@ -1932,6 +1932,11 @@
"isarray": "1.0.0"
}
},
"buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI="
},
"buffer-from": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.1.tgz",
@ -4286,6 +4291,14 @@
}
}
},
"fd-slicer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
"integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
"requires": {
"pend": "1.2.0"
}
},
"figures": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
@ -5666,6 +5679,33 @@
"pinkie-promise": "2.0.1"
}
},
"google-fonts-webpack-plugin": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/google-fonts-webpack-plugin/-/google-fonts-webpack-plugin-0.4.4.tgz",
"integrity": "sha512-+e2D9/DVBG9EDydRovzoqMZ658SsTBGbC0c65GyZqkwNvdj8vRSYQKXqbz7/yt7QaXsCPT1MpH45r3ivWOitcw==",
"requires": {
"lodash": "4.17.4",
"node-fetch": "1.7.3",
"webpack-sources": "0.2.3",
"yauzl": "2.9.1"
},
"dependencies": {
"source-list-map": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-1.1.2.tgz",
"integrity": "sha1-mIkBnRAkzOVc3AaUmDN+9hhqEaE="
},
"webpack-sources": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-0.2.3.tgz",
"integrity": "sha1-F8Yr+vE8cH+dAsR54Nzd6DgGl/s=",
"requires": {
"source-list-map": "1.1.2",
"source-map": "0.5.7"
}
}
}
},
"got": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/got/-/got-3.3.1.tgz",
@ -8398,6 +8438,11 @@
"sort-keys": "1.1.2"
}
},
"normalize-wheel": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz",
"integrity": "sha1-rsiGr/2wRQcNhWRH32Ls+GFG7EU="
},
"npm": {
"version": "2.15.12",
"resolved": "https://registry.npmjs.org/npm/-/npm-2.15.12.tgz",
@ -10550,6 +10595,11 @@
"integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0=",
"dev": true
},
"pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
},
"pepjs": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/pepjs/-/pepjs-0.4.3.tgz",
@ -11747,12 +11797,6 @@
"prop-types": "15.6.0"
}
},
"react-router-redux": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/react-router-redux/-/react-router-redux-4.0.8.tgz",
"integrity": "sha1-InQDWWtRUeGCN32rg1tdRfD4BU4=",
"dev": true
},
"react-svg-inline": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/react-svg-inline/-/react-svg-inline-2.0.1.tgz",
@ -14370,6 +14414,15 @@
}
}
},
"yauzl": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.9.1.tgz",
"integrity": "sha1-qBmB6nCleUYTOIPwKcWCGok1mn8=",
"requires": {
"buffer-crc32": "0.2.13",
"fd-slicer": "1.0.1"
}
},
"yml-loader": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/yml-loader/-/yml-loader-2.1.0.tgz",

View File

@ -27,11 +27,13 @@
"blueimp-canvas-to-blob": "^3.14.0",
"bowser": "^1.8.1",
"fit-curve": "^0.1.6",
"google-fonts-webpack-plugin": "^0.4.4",
"imports-loader": "^0.7.1",
"jss": "^9.4.0",
"keycode": "^2.1.9",
"lodash": "^4.17.4",
"memoizee": "^0.3.9",
"normalize-wheel": "^1.0.1",
"pouchdb": "^6.3.4",
"proptypes": "^1.1.0",
"raf": "^3.4.0",
@ -82,7 +84,6 @@
"normalize-jss": "^4.0.0",
"raw-loader": "^0.5.1",
"react-dom": "^16.1.1",
"react-router-redux": "^4.0.8",
"react-tap-event-plugin": "^3.0.2",
"redux": "^3.7.2",
"redux-action-wrapper": "^1.0.1",

View File

@ -0,0 +1,36 @@
uniform sampler2D mapLeft;
uniform sampler2D mapRight;
varying vec2 vUv;
uniform mat3 colorMatrixLeft;
uniform mat3 colorMatrixRight;
float lin( float c ) {
return c <= 0.04045 ? c * 0.0773993808 :
pow( c * 0.9478672986 + 0.0521327014, 2.4 );
}
vec4 lin( vec4 c ) {
return vec4( lin( c.r ), lin( c.g ), lin( c.b ), c.a );
}
float dev( float c ) {
return c <= 0.0031308 ? c * 12.92 : pow( c, 0.41666 ) * 1.055 - 0.055;
}
void main() {
vec2 uv = vUv;
vec4 colorL = lin( texture2D( mapLeft, uv ) );
vec4 colorR = lin( texture2D( mapRight, uv ) );
vec3 color = clamp(
colorMatrixLeft * colorL.rgb +
colorMatrixRight * colorR.rgb, 0., 1. );
gl_FragColor = vec4(
dev( color.r ), dev( color.g ), dev( color.b ),
max( colorL.a, colorR.a ) );
}

View File

@ -0,0 +1,5 @@
varying vec2 vUv;
void main() {
vUv = vec2( uv.x, uv.y );
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}

View File

@ -1,6 +1,5 @@
import { ActionCreators as undo } from 'redux-undo';
import * as notification from 'react-notification-system-redux';
import { routerActions as router } from 'react-router-redux';
import * as selectionUtils from '../utils/selectionUtils.js';
import { calculatePointInImage, decomposeMatrix } from '../utils/matrixUtils.js';
import { loadImage, prepareImage } from '../utils/imageUtils.js';
@ -74,7 +73,6 @@ export const ALIGN = 'ALIGN';
export const ADD_IMAGE = 'ADD_IMAGE';
export const D2_TEXT_INIT = 'D2_TEXT_INIT';
export const D2_TEXT_INPUT_CHANGE = 'D2_TEXT_INPUT_CHANGE';
export const D2_TEXT_ADD = 'D2_TEXT_ADD';
export const UNION = 'UNION';
export const INTERSECT = 'INTERSECT';
export const MOVE_SELECTION = 'MOVE_SELECTION';
@ -390,14 +388,10 @@ export function addImage(file) {
export function d2textInit(position, textId, screenMatrixContainer, screenMatrixZoom) {
return (dispatch) => {
dispatch({ type: D2_TEXT_INIT, position, textId, screenMatrixContainer, screenMatrixZoom });
dispatch(router.push('/sketch/inputtext'));
};
}
export function d2textInputChange(text, family, weight, style, fill) {
return { type: D2_TEXT_INPUT_CHANGE, text, family, weight, style, fill };
}
export function d2textAdd() {
return { type: D2_TEXT_ADD };
export function d2textInputChange(text) {
return { type: D2_TEXT_INPUT_CHANGE, text };
}
const traceDragThrottle = createThrottle();

View File

@ -23,6 +23,7 @@ import ShapesManager from '../d2/ShapesManager.js';
import EventGroup from '../d2/EventGroup.js';
import ReactResizeDetector from 'react-resize-detector';
import { load as loadPattern } from '../d2/Shape.js';
import InputText from './InputText.js';
// import createDebug from 'debug';
// const debug = createDebug('d3d:d2');
@ -63,6 +64,9 @@ class D2Panel extends React.Component {
dispatch: PropTypes.func.isRequired,
classes: PropTypes.objectOf(PropTypes.string)
};
state = {
screenMatrix: new CAL.Matrix()
};
activeNeedRender = false;
inactiveNeedRender = false;
@ -117,7 +121,7 @@ class D2Panel extends React.Component {
}
switchTool(toolName) {
if (this.state && toolName === this.state.d2.tool) return;
if (this.state.state && toolName === this.state.state.d2.tool) return;
// cleanup & remove previous tool
if (this.tool) {
this.tool.destroy();
@ -135,18 +139,18 @@ class D2Panel extends React.Component {
this.objectContainerActive.add(this.tool);
}
update(newState) {
if (this.state === newState) return;
update(state) {
if (this.state.state === state) return;
this.updateTool(newState);
this.updateTool(state);
const shapesNeedRender = this.shapesManager.update(newState);
const shapesNeedRender = this.shapesManager.update(state);
if (shapesNeedRender.active) this.activeNeedRender = true;
if (shapesNeedRender.inactive) this.inactiveNeedRender = true;
// Update Objects Container Space with zoom & panning
const newCanvasMatrix = newState.d2.canvasMatrix;
if (this.state && newCanvasMatrix !== this.state.d2.canvasMatrix) {
const newCanvasMatrix = state.d2.canvasMatrix;
if (this.state.state && newCanvasMatrix !== this.state.state.d2.canvasMatrix) {
this.objectContainerActive.copyMatrix(newCanvasMatrix);
this.objectContainerInactive.copyMatrix(newCanvasMatrix);
@ -154,9 +158,9 @@ class D2Panel extends React.Component {
this.inactiveNeedRender = true;
}
const selection = (this.state) ? this.state.selection : null;
const newSelection = newState.selection;
if (!this.state || newSelection !== selection) {
const selection = (this.state.state) ? this.state.state.selection : null;
const newSelection = state.selection;
if (!this.state.state || newSelection !== selection) {
const newSelectedObjects = newSelection.objects;
if (!selection || selection.objects !== newSelectedObjects) {
const selected = newSelectedObjects.map((object) => object.id);
@ -167,25 +171,29 @@ class D2Panel extends React.Component {
}
}
const dragSelect = (this.state) ? this.state.d2.transform.dragSelect : null;
const newDragSelect = newState.d2.transform.dragSelect;
const dragSelect = (this.state.state) ? this.state.state.d2.transform.dragSelect : null;
const newDragSelect = state.d2.transform.dragSelect;
if (!dragSelect || dragSelect !== newDragSelect) {
this.activeNeedRender = true;
}
this.state = newState;
this.setState({ state });
}
resizeHandler = (width, height) => {
this.sceneActive.setSize(width, height, PIXEL_RATIO);
this.sceneInactive.setSize(width, height, PIXEL_RATIO);
this.sceneInactive.x = this.sceneActive.x = Math.round(width / 2 * PIXEL_RATIO);
this.sceneInactive.y = this.sceneActive.y = Math.round(height / 2 * PIXEL_RATIO);
const x = Math.round(width / 2 * PIXEL_RATIO);
const y = Math.round(height / 2 * PIXEL_RATIO);
const scale = Math.min(width * PIXEL_RATIO / CANVAS_WIDTH, height * PIXEL_RATIO / CANVAS_HEIGHT);
this.sceneInactive.scale = this.sceneActive.scale = scale;
const screenMatrix = new CAL.Matrix({ sx: scale, sy: scale, x, y });
this.sceneInactive.copyMatrix(screenMatrix);
this.sceneActive.copyMatrix(screenMatrix);
this.setState({ screenMatrix });
this.inactiveNeedRender = this.activeNeedRender = true;
this.renderRequest();
@ -210,12 +218,14 @@ class D2Panel extends React.Component {
render() {
// debug('this.props.state: ', this.props.state);
const { state, classes } = this.props;
const { screenMatrix } = this.state;
this.update(state);
this.renderCanvas();
return (
<div className={classes.container}>
<ReactResizeDetector handleWidth handleHeight onResize={this.resizeHandler} />
<div className={classes.canvasContainer} ref="canvasContainer" />
<InputText screenMatrix={screenMatrix} />
</div>
);
}

View File

@ -16,7 +16,7 @@ import TwistTransformer from '../d3/transformers/TwistTransformer.js';
import SculptTransformer from '../d3/transformers/SculptTransformer.js';
import StampTransformer from '../d3/transformers/StampTransformer.js';
import SelectionBox from '../d3/SelectionBox.js';
import RenderChain from '../d3/RenderChain';
import RenderChain, { TOONSHADER_OUTLINE, TOONSHADER } from '../d3/RenderChain';
import BaseTransformer from '../d3/transformers/BaseTransformer.js';
import Camera from '../d3/Camera.js';
import ReactResizeDetector from 'react-resize-detector';
@ -62,8 +62,8 @@ class D3Panel extends React.Component {
componentWillMount() {
this.createScene();
const toonShader = hasExtensionsFor.toonShaderPreview;
this.renderChain = new RenderChain(this.renderer, this.scene, this.camera, toonShader, {
const shader = hasExtensionsFor.toonShaderPreview ? TOONSHADER_OUTLINE : TOONSHADER;
this.renderChain = new RenderChain(this.renderer, this.scene, this.camera, shader, {
UI: this.UIContainer,
shapes: this.shapesManager,
boundingBox: this.selectionBox,

View File

@ -45,7 +45,7 @@ class DoodlePreview extends React.Component {
scene: null
};
async componentDidMount() {
async componentWillMount() {
let { docData, sketchData } = this.props;
if (docData) sketchData = await JSONToSketchData(this.props.docData);

101
src/components/InputText.js Normal file
View File

@ -0,0 +1,101 @@
import React from 'react';
import injectSheet from 'react-jss';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import * as actions from '../actions/index.js';
import * as CAL from 'cal';
import { TEXT_TOOL_FONT_SIZE } from '../constants/d2Constants.js';
const CONTEXT = document.createElement('canvas').getContext('2d');
const styles = {
textInputContainer: {
position: 'absolute',
display: 'flex'
},
textInput: {
position: 'absolute',
fontSize: `${TEXT_TOOL_FONT_SIZE}px`,
background: 'transparent',
border: 'none',
color: 'black',
textFillColor: 'transparent',
outline: 'none'
}
};
class InputText extends React.Component {
static propTypes = {
state: PropTypes.object.isRequired,
classes: PropTypes.objectOf(PropTypes.string),
changeText: PropTypes.func.isRequired,
screenMatrix: PropTypes.instanceOf(CAL.Matrix).isRequired
};
onInputChange = () => {
const shapeData = this.getShapeData();
if (!shapeData) return;
const { changeText } = this.props;
const text = this.refs.text.value;
changeText(text);
};
getShapeData = () => {
const { state } = this.props;
if (!state.d2.activeShape) return null;
const shapeData = state.objectsById[state.d2.activeShape];
if (shapeData.type !== 'TEXT') return null;
return shapeData;
}
componentWillUpdate() {
if (this.refs.text) this.refs.text.focus();
}
render() {
const { classes, state, screenMatrix } = this.props;
const shapeData = this.getShapeData();
if (shapeData) {
const { _matrix: m } = shapeData.transform
.multiplyMatrix(state.d2.canvasMatrix)
.multiplyMatrix(screenMatrix);
const { family, style, weight, text } = shapeData.text;
CONTEXT.font = `${style} normal ${weight} ${TEXT_TOOL_FONT_SIZE}px ${family}`;
const width = Math.max(10, CONTEXT.measureText(shapeData.text.text).width);
return (
<div
className={classes.textInputContainer}
style={{ transform: `matrix(${m[0]}, ${m[3]}, ${m[1]}, ${m[4]}, ${m[2]}, ${m[5]})` }}
>
<input
className={classes.textInput}
style={{
fontFamily: family,
fontStyle: style,
fontWeight: weight,
width: `${width}px`
}}
value={text}
ref="text"
spellCheck="false"
autoFocus
onChange={this.onInputChange}
/>
</div>
);
}
return null;
}
}
export default injectSheet(styles)(connect(state => ({
state: state.sketcher.present
}), {
changeText: actions.d2textInputChange
})(InputText));

View File

@ -1,7 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import initialMenuStructure from '../constants/menu.js';
import { connect } from 'react-redux';
// import createDebug from 'debug';
// const debug = createDebug('d3d:menu');
@ -14,7 +12,7 @@ class Menu extends React.Component {
value: PropTypes.string,
className: PropTypes.string,
children: PropTypes.node,
stateMenu: PropTypes.object
id: PropTypes.string
};
onSelect = (event) => {
const { onSelect, value } = this.props;
@ -23,7 +21,7 @@ class Menu extends React.Component {
if (onSelect) onSelect({ ...event, menuValue });
};
render() {
const { className = '', id, selectedValue, onOpen, onClose, value, children, stateMenu } = this.props;
const { className = '', id, selectedValue, onOpen, onClose, children } = this.props;
return (
<ul id={id} className={`menu ${className}`}>
{React.Children.map(children, (child) => {
@ -39,6 +37,4 @@ class Menu extends React.Component {
}
}
export default connect(state => ({
stateMenu: state.sketcher.present.menus
}))(Menu);
export default Menu;

View File

@ -9,6 +9,8 @@ import * as contextTools from '../constants/contextTools.js';
import * as d2Tools from '../constants/d2Tools.js';
import { createSelector } from 'reselect';
import injectSheet from 'react-jss';
import { FONT_TOOLS } from '../constants/contextTools.js';
import { FONT_FACE } from '../constants/general.js';
// TODO move this to jss instead of css
import '../../styles/styles.css';
// import createDebug from 'debug';
@ -120,6 +122,7 @@ function renderChildren(children) {
for (const child of children) {
let component;
if (child.children.length > 0) {
component = (
<SubMenu
@ -135,6 +138,18 @@ function renderChildren(children) {
{renderChildren(child.children)}
</SubMenu>
);
} else if (FONT_TOOLS.includes(child.value)) {
component = (
<MenuItem
id={child.value}
value={child.value}
key={child.value}
svg={child.svg}
disabled={child.disabled}
>
<p style={{ fontFamily: FONT_FACE[child.value] }}>{FONT_FACE[child.value]}</p>
</MenuItem>
);
} else {
component = (
<MenuItem
@ -151,7 +166,7 @@ function renderChildren(children) {
return components;
}
function filterMenus(activeTool, numSelectedObjects, numFilledObjects, numSolidObjects, menus) {
function filterMenus(activeTool, numSelectedObjects, numFilledObjects, numSolidObjects, selectionIncludesText, menus) {
const showUnion = activeTool === d2Tools.TRANSFORM && numFilledObjects && numSelectedObjects >= 2;
const showIntersect = activeTool === d2Tools.TRANSFORM && numSelectedObjects >= 1;
return {
@ -191,11 +206,14 @@ function filterMenus(activeTool, numSelectedObjects, numFilledObjects, numSolidO
case contextTools.HOLE_TOGGLE:
return numSelectedObjects > 0;
case contextTools.FONT:
return selectionIncludesText || activeTool === d2Tools.TEXT;
default:
return true;
}
}).map(child => {
return filterMenus(activeTool, numSelectedObjects, numFilledObjects, numSolidObjects, child);
return filterMenus(activeTool, numSelectedObjects, numFilledObjects, numSolidObjects, selectionIncludesText, child);
})
};
}
@ -216,12 +234,19 @@ const getMenus = createSelector([
state => state.sketcher.present.menus,
state => state.sketcher.present.d2.tool,
state => state.sketcher.present.selection.objects.length,
state => state.sketcher.present.selection.objects.filter(({ id }) => state.sketcher.present.objectsById[id].fill).length,
state => state.sketcher.present.selection.objects.filter(({ id }) => state.sketcher.present.objectsById[id].solid).length
], (menus, activeTool, numSelectedObjects, numFilledObjects, numSolidObjects) => ({
toolbar2d: filterMenus(activeTool, numSelectedObjects, numFilledObjects, numSolidObjects, nestChildren(menus, menus[TOOLBAR2D])),
toolbar3d: filterMenus(activeTool, numSelectedObjects, numFilledObjects, numSolidObjects, nestChildren(menus, menus[TOOLBAR3D])),
context: filterMenus(activeTool, numSelectedObjects, numFilledObjects, numSolidObjects, nestChildren(menus, menus[CONTEXT]))
state => state.sketcher.present.selection.objects.filter(({ id }) => {
return state.sketcher.present.objectsById[id].fill;
}).length,
state => state.sketcher.present.selection.objects.filter(({ id }) => {
return state.sketcher.present.objectsById[id].solid;
}).length,
state => state.sketcher.present.selection.objects.some(({ id }) => {
return state.sketcher.present.objectsById[id].type === 'TEXT';
})
], (menus, ...args) => ({
toolbar2d: filterMenus(...args, nestChildren(menus, menus[TOOLBAR2D])),
toolbar3d: filterMenus(...args, nestChildren(menus, menus[TOOLBAR3D])),
context: filterMenus(...args, nestChildren(menus, menus[CONTEXT]))
}));
export default injectSheet(styles)(connect(getMenus)(SketcherToolbars));

View File

@ -67,7 +67,9 @@ class SubMenu extends React.Component {
}
};
render() {
const { id, value, selected, open, selectedValue, children, svg, toggleBehavior, color, solid } = this.props;
const {
id, value, selected, open, selectedValue, children, svg, toggleBehavior, color, solid
} = this.props;
const style = {};
if (id === 'color-picker-tool') {

View File

@ -8,6 +8,7 @@ export const HOLE_TOGGLE = 'hole-toggle-tool';
export const ALIGN = 'align-tool';
export const UNION = 'union-tool';
export const INTERSECT = 'intersect-tool';
export const FONT = 'font-tool';
export const LIGHT_BLUE_A = 'color-light-blue-a';
export const LIGHT_BLUE_B = 'color-light-blue-b';
@ -79,3 +80,23 @@ export const ALIGN_TOOLS = [
ALIGN_VERTICAL,
ALIGN_BOTTOM
];
export const OSWALD = 'oswald';
export const RANGA = 'ranga';
export const JOTI_ONE = 'joti-one';
export const BELLEFAIR = 'bellefair';
export const LOBSTER = 'lobster';
export const ABRIL_FATFACE = 'abril-fatface';
export const PLAY = 'play';
export const FASCINATE = 'fascinate';
export const FONT_TOOLS = [
OSWALD,
RANGA,
JOTI_ONE,
BELLEFAIR,
LOBSTER,
ABRIL_FATFACE,
PLAY,
FASCINATE
];

View File

@ -41,3 +41,4 @@ export const BRUSH_SIZES = {
};
export const CLIPPER_PRECISION = 100; // accurate to the hundredth
export const TEXT_TOOL_FONT_SIZE = 40;

View File

@ -37,3 +37,14 @@ export const COLOR_STRING_TO_HEX = {
[contextTools.BLACK_B]: 0xAAAAAA,
[contextTools.BLACK_C]: 0x444444
};
export const FONT_FACE = {
[contextTools.OSWALD]: 'Oswald',
[contextTools.RANGA]: 'Ranga',
[contextTools.JOTI_ONE]: 'Joti One',
[contextTools.BELLEFAIR]: 'Bellefair',
[contextTools.LOBSTER]: 'Lobster',
[contextTools.ABRIL_FATFACE]: 'Abril Fatface',
[contextTools.PLAY]: 'Play',
[contextTools.FASCINATE]: 'Fascinate'
};

View File

@ -109,6 +109,11 @@ const context = {
selected: contextTools.ALIGN_HORIZONTAL,
children: contextTools.ALIGN_TOOLS.map(value => ({ value })),
...selectorBehavior
}, {
value: contextTools.FONT,
selected: contextTools.OSWALD,
children: contextTools.FONT_TOOLS.map(value => ({ value })),
...selectorBehavior
},
{ value: contextTools.UNION },
{ value: contextTools.INTERSECT }

View File

@ -1,6 +1,8 @@
import { Vector, Matrix } from '@doodle3d/cal';
import * as d2Tools from './d2Tools';
import * as d3Tools from './d3Tools';
import * as contextTools from './contextTools';
import { FONT_FACE } from '../constants/general.js';
const SHAPE = {
D3Visible: true,
@ -58,7 +60,7 @@ export const SHAPE_TYPE_PROPERTIES = {
...SHAPE,
defaultProperties: {
...defaultProperties,
text: { text: '', family: 'Arial', weight: 'normal', style: 'normal' },
text: { text: '', family: FONT_FACE[contextTools.OSWALD], weight: 'normal', style: 'normal' },
fill: true
}
},

View File

@ -2,6 +2,7 @@ import { Group, Utils } from 'cal';
import createListener from 'touch-events';
import bowser from 'bowser';
import { convertEvent, isMouseEvent } from '../utils/pointerUtils.js';
import normalizeWheel from 'normalize-wheel';
const events = [
'wheel', 'tap',
@ -64,7 +65,9 @@ export default class EventGroup extends Group {
if (event.event) gestureEvent.position = _convertEvent(event.event);
if (event.events) gestureEvent.positions = event.events.map(_convertEvent);
if (event.preEvents) gestureEvent.preDrags = event.preEvents.map(_convertEvent);
if (event.event && event.event.deltaY !== undefined) gestureEvent.wheelDelta = event.event.deltaY;
if (event.event && event.event.deltaY !== undefined) {
gestureEvent.wheelDelta = normalizeWheel(event.event).pixelY;
}
this.onEvent(gestureEvent);
}

View File

@ -8,7 +8,7 @@ import { shapeToPoints } from '../../shape/shapeToPoints';
import { LINE_COLLISION_MARGIN } from '../../constants/d2Constants';
import { LINE_WIDTH } from '../../constants/d2Constants';
import { PIXEL_RATIO } from '../../constants/general';
import { Matrix } from 'cal';
import { Matrix, Vector } from 'cal';
const HIT_ORDER = {
RECT: 0,
@ -132,13 +132,27 @@ export default class BaseTool extends EventGroup {
const objects = shapeDatas
.filter(shapeData => {
const shapeMatrix = shapeData.transform.multiplyMatrix(matrix);
let shapePoints = shapeToPoints(shapeData);
let { fill } = shapeData;
const isHit = shapeToPoints(shapeData)
if (shapeData.type === 'TEXT') {
if (shapeData.text.text === '') return false;
const { min, max } = getPointsBounds(shapePoints);
shapePoints = [{ points: [
new Vector(min.x, min.y),
new Vector(min.x, max.y),
new Vector(max.x, max.y),
new Vector(max.x, min.y)
], holes: [] }];
fill = true;
}
const isHit = shapePoints
.some(({ points, holes }) => {
const shape = applyMatrixOnShape([points, ...holes], shapeMatrix);
const clipperShape = new ClipperShape(shape, shapeData.fill, true, true);
const clipperShape = new ClipperShape(shape, fill, true, true);
if (shapeData.fill) {
if (fill) {
return clipperShape
.fixOrientation()
.pointInShape(position, true, true);

View File

@ -58,24 +58,6 @@ export default class PolygonTool extends BaseTool {
context.restore();
context.beginPath();
// const anglePosition = this.anglePosition.applyMatrix(matrix);
// context.arc(anglePosition.x, anglePosition.y, 20, 0, this.angleDegrees, this.angleDegrees > Math.PI);
//
// context.strokeStyle = 'black';
// context.lineWidth = 1;
// context.stroke();
//
// const angleTextPos = new Vector(28, 0)
// .rotate((this.angleDegrees < Math.PI ? this.angleDegrees : this.angleDegrees - Math.PI * 2.0) / 2)
// .add(anglePosition);
// context.textAlign = 'start';
// context.textBaseline = 'middle';
// context.fillStyle = '#000';
// context.font = '10px Arial';
//
// const angleDegrees = this.angleDegrees < Math.PI ? this.angleDegrees : Math.PI * 2 - this.angleDegrees;
// dimensionsText.drawText(context, humanReadable.degrees(angleDegrees), angleTextPos.x, angleTextPos.y);
}
}
}

View File

@ -3,6 +3,7 @@ import { Utils } from 'cal';
import createListener from 'touch-events';
import bowser from 'bowser';
import { convertEvent, isMouseEvent } from '../utils/pointerUtils.js';
import normalizeWheel from 'normalize-wheel';
const events = ['wheel', 'tap', 'dragstart', 'drag', 'dragend', 'seconddragstart', 'seconddrag',
'seconddragend', 'multitouchstart', 'multitouch', 'multitouchend'];
@ -41,7 +42,9 @@ export class EventScene extends THREE.Scene {
if (event.event) gestureEvent.position = _convertEvent(event.event);
if (event.events) gestureEvent.positions = event.events.map(_convertEvent);
if (event.preEvents) gestureEvent.preDrags = event.preEvents.map(_convertEvent);
if (event.event && event.event.deltaY !== undefined) gestureEvent.wheelDelta = event.event.deltaY;
if (event.event && event.event.deltaY !== undefined) {
gestureEvent.wheelDelta = normalizeWheel(event.event).pixelY;
}
this.onEvent(gestureEvent);
};

71
src/d3/RenderChain.js vendored
View File

@ -1,43 +1,62 @@
import * as THREE from 'three';
import OutlinePass from './OutlinePass.js';
import RenderPass from './RenderPass.js';
import OutlinePass from './effects/OutlinePass.js';
import RenderPass from './effects/RenderPass.js';
import AnaglyphPass from './effects/AnaglyphPass.js';
import 'three/examples/js/shaders/CopyShader.js';
import 'three/examples/js/postprocessing/EffectComposer.js';
import 'three/examples/js/postprocessing/ShaderPass.js';
export const TOONSHADER_OUTLINE = 'toonshader-outline';
export const ANAGLYPH = 'anaglyph';
export const TOONSHADER = 'toonshader';
export default class RenderChain extends THREE.EffectComposer {
constructor(renderer, scene, camera, toonShader, groups) {
constructor(renderer, scene, camera, shader, groups) {
super(renderer);
this._groups = groups;
if (toonShader) {
const renderPass = new RenderPass(scene, camera, () => {
this._setVisible(this._initalValues, [groups.shapes, groups.plane, groups.boundingBox]);
});
this.addPass(renderPass);
switch (shader) {
case TOONSHADER_OUTLINE: {
const renderPass = new RenderPass(scene, camera, () => {
this._setVisible(this._initalValues, [groups.shapes, groups.plane, groups.boundingBox]);
});
this.addPass(renderPass);
const outlinePass = new OutlinePass(scene, camera, () => {
this._setVisible(this._initalValues, [groups.shapes]);
});
outlinePass.renderToScreen = true;
this.addPass(outlinePass);
const outlinePass = new OutlinePass(scene, camera, () => {
this._setVisible(this._initalValues, [groups.shapes]);
});
outlinePass.renderToScreen = true;
this.addPass(outlinePass);
const renderPassUI = new RenderPass(scene, camera, () => {
this._setVisible(this._initalValues, [groups.UI]);
});
renderPassUI.clear = false;
renderPassUI.renderToScreen = true;
this.addPass(renderPassUI);
} else {
const renderPass = new RenderPass(scene, camera);
renderPass.renderToScreen = true;
this.addPass(renderPass);
const renderPassUI = new RenderPass(scene, camera, () => {
this._setVisible(this._initalValues, [groups.UI]);
});
renderPassUI.clear = false;
renderPassUI.renderToScreen = true;
this.addPass(renderPassUI);
break;
}
case ANAGLYPH: {
const anaglyphPass = new AnaglyphPass(scene, camera);
anaglyphPass.renderToScreen = true;
this.addPass(anaglyphPass);
break;
}
case TOONSHADER:
default: {
const renderPass = new RenderPass(scene, camera);
renderPass.renderToScreen = true;
this.addPass(renderPass);
break;
}
}
this._renderer = renderer;
this._camera = camera;
this._scene = scene;
this._toonShader = toonShader;
this._shader = shader;
}
_getCurrentVisibleValues() {
@ -74,13 +93,13 @@ export default class RenderChain extends THREE.EffectComposer {
}
render() {
if (this._toonShader) {
if (this._shader === TOONSHADER_OUTLINE) {
this._initalValues = this._getCurrentVisibleValues();
}
super.render();
if (this._toonShader) {
if (this._shader === TOONSHADER_OUTLINE) {
const { shapes, UI, plane, boundingBox } = this._groups;
this._setVisible(this._initalValues, [shapes, UI, plane, boundingBox]);
}

2
src/d3/ShapeMesh.js vendored
View File

@ -21,7 +21,7 @@ class ShapeMesh extends THREE.Object3D {
super();
this.name = shapeData.UID;
const { sculpt, rotate, twist, height, type, transform, z, color, fill, solid } = shapeData;
const { sculpt, rotate, twist, height, type, transform, z, color, solid } = shapeData;
const material = new MatcapMaterial({ color: new THREE.Color(color) });

View File

@ -1,6 +1,6 @@
import * as THREE from 'three';
import ShapesManager from './ShapesManager.js';
import RenderChain from './RenderChain.js';
import RenderChain, { TOONSHADER, TOONSHADER_OUTLINE } from './RenderChain.js';
import { hasExtensionsFor } from '../utils/webGLSupport.js';
import { CANVAS_SIZE } from '../constants/d2Constants.js';
@ -17,7 +17,7 @@ export default function createScene(state, canvas) {
scene.add(camera);
const shapesManager = new ShapesManager({ toonShader: hasExtensionsFor.toonShaderThumbnail });
const shapesManager = new ShapesManager();
shapesManager.update(state);
scene.add(shapesManager);
@ -37,7 +37,8 @@ export default function createScene(state, canvas) {
const renderer = new THREE.WebGLRenderer({ canvas, alpha: true });
const renderChain = new RenderChain(renderer, scene, camera, hasExtensionsFor.toonShaderThumbnail, {
const shader = hasExtensionsFor.toonShaderThumbnail ? TOONSHADER_OUTLINE : TOONSHADER;
const renderChain = new RenderChain(renderer, scene, camera, shader, {
plane,
UI: new THREE.Object3D(),
shapes: shapesManager,

66
src/d3/effects/AnaglyphPass.js vendored Normal file
View File

@ -0,0 +1,66 @@
import * as THREE from 'three';
import anaglyphVert from '../../../shaders/anaglyph_vert.glsl';
import anaglyphFrag from '../../../shaders/anaglyph_frag.glsl';
const COLOR_MATRIX_LEFT = new THREE.Matrix3().fromArray([
1.0671679973602295, -0.0016435992438346148, 0.0001777536963345483,
-0.028107794001698494, -0.00019593400065787137, -0.0002875397040043026,
-0.04279090091586113, 0.000015809757314855233, -0.00024287120322696865
]);
const COLOR_MATRIX_RIGHT = new THREE.Matrix3().fromArray([
-0.0355340838432312, -0.06440307199954987, 0.018319187685847282,
-0.10269022732973099, 0.8079727292060852, -0.04835830628871918,
0.0001224992738571018, -0.009558862075209618, 0.567823588848114
]);
export default class AnaglyphPass {
constructor(scene, camera) {
this.scene = scene;
this.camera = camera;
this.clear = true;
this.renderToScreen = false;
const params = {
minFilter: THREE.LinearFilter,
magFilter: THREE.NearestFilter,
format: THREE.RGBAFormat
};
this._stereo = new THREE.StereoCamera();
this._renderTargetL = new THREE.WebGLRenderTarget(1, 1, params);
this._renderTargetR = new THREE.WebGLRenderTarget(1, 1, params);
this._material = new THREE.ShaderMaterial({
uniforms: {
mapLeft: { value: this._renderTargetL.texture },
mapRight: { value: this._renderTargetR.texture },
colorMatrixLeft: { value: COLOR_MATRIX_LEFT },
colorMatrixRight: { value: COLOR_MATRIX_RIGHT }
},
vertexShader: anaglyphVert,
fragmentShader: anaglyphFrag
});
this._camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
this._scene = new THREE.Scene();
this._quad = new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 2), this._material);
this._quad.frustumCulled = false;
this._scene.add(this._quad);
}
setSize(width, height, pixelRatio = 1) {
this._renderTargetL.setSize(width * pixelRatio, height * pixelRatio);
this._renderTargetR.setSize(width * pixelRatio, height * pixelRatio);
}
render(renderer, writeBuffer, readBuffer) {
this.scene.updateMatrixWorld();
this._stereo.update(this.camera);
renderer.render(this.scene, this._stereo.cameraL, this._renderTargetL, true);
renderer.render(this.scene, this._stereo.cameraR, this._renderTargetR, true);
renderer.render(this._scene, this._camera, this.renderToScreen ? null : readBuffer, this.clear);
}
}

View File

@ -1,10 +1,10 @@
import * as THREE from 'three';
import normalDepthVert from '../../shaders/normal_depth_vert.glsl';
import normalDepthFrag from '../../shaders/normal_depth_frag.glsl';
import edgeVert from '../../shaders/edge_vert.glsl';
import edgeFrag from '../../shaders/edge_frag.glsl';
import combineVert from '../../shaders/combine_vert.glsl';
import combineFrag from '../../shaders/combine_frag.glsl';
import normalDepthVert from '../../../shaders/normal_depth_vert.glsl';
import normalDepthFrag from '../../../shaders/normal_depth_frag.glsl';
import edgeVert from '../../../shaders/edge_vert.glsl';
import edgeFrag from '../../../shaders/edge_frag.glsl';
import combineVert from '../../../shaders/combine_vert.glsl';
import combineFrag from '../../../shaders/combine_frag.glsl';
export default class OutlinePass {
constructor(scene, camera, callbackBeforeRender) {

View File

@ -1,5 +1,3 @@
import * as THREE from 'three';
export default class RenderPass {
constructor(scene, camera, callbackBeforeRender) {
this.scene = scene;
@ -10,10 +8,10 @@ export default class RenderPass {
this.renderToScreen = false;
}
setSize(width, height) {
setSize() {
}
render(renderer, writeBuffer, readBuffer, delta, maskActive) {
render(renderer, writeBuffer, readBuffer) {
if (this._callbackBeforeRender) this._callbackBeforeRender();
let oldAutoClear;

View File

@ -2,8 +2,6 @@ import * as THREE from 'three';
import * as actions from '../../actions/index.js';
import { EventObject3D } from '../EventScene.js';
import transposeEvents from '../../utils/transposeEvents.js';
import ShapeMesh from '../ShapeMesh.js';
import { SpriteHandle } from '../../utils/threeUtils.js';
const RAY_CASTER = new THREE.Raycaster();
const MOUSE = new THREE.Vector2();

View File

@ -1,5 +1,4 @@
import { CANVAS_SIZE } from '../../constants/d2Constants';
import ShapeMesh from '../ShapeMesh.js';
import * as THREE from 'three';
import BaseTransformer from './BaseTransformer.js';
import * as actions from '../../actions/index.js';

View File

@ -1,6 +1,6 @@
import update from 'react-addons-update';
import * as contextTools from '../constants/contextTools.js';
import { COLOR_STRING_TO_HEX } from '../constants/general.js';
import { COLOR_STRING_TO_HEX, FONT_FACE } from '../constants/general.js';
import { ERASER_SIZES, BRUSH_SIZES } from '../constants/d2Constants.js';
import { SHAPE_TYPE_PROPERTIES } from '../constants/shapeTypeProperties.js';
import * as actions from '../actions/index.js';
@ -69,6 +69,33 @@ export default function (state, action) {
});
}
case contextTools.OSWALD:
case contextTools.RANGA:
case contextTools.JOTI_ONE:
case contextTools.BELLEFAIR:
case contextTools.LOBSTER:
case contextTools.ABRIL_FATFACE:
case contextTools.PLAY:
case contextTools.FASCINATE: {
const family = FONT_FACE[action.tool];
const { activeShape } = state.d2;
if (activeShape && state.objectsById[activeShape].type === 'TEXT') {
state = update(state, { objectsById: { [activeShape]: { text: { family: { $set: family } } } } });
}
return update(state, {
objectsById: state.selection.objects.reduce((updateObject, { id }) => {
if (state.objectsById[id].type === 'TEXT') {
updateObject[id] = { text: { family: { $set: FONT_FACE[action.tool] } } };
}
return updateObject;
}, {}),
context: {
font: { $set: FONT_FACE[action.tool] }
}
});
}
case contextTools.LIGHT_BLUE_A:
case contextTools.LIGHT_BLUE_B:
case contextTools.LIGHT_BLUE_C:
@ -94,6 +121,11 @@ export default function (state, action) {
case contextTools.BLACK_B:
case contextTools.BLACK_C: {
const color = COLOR_STRING_TO_HEX[action.tool];
const { activeShape } = state.d2;
if (activeShape) {
state = update(state, { objectsById: { [activeShape]: { color: { $set: color } } } });
}
return updateColor(state, color);
}

View File

@ -1,5 +1,4 @@
import update from 'react-addons-update';
import { Matrix } from 'cal';
import constrainMatrix from './constrainMatrix.js';
import { calculateGestureMatrix } from '../../utils/matrixUtils.js';
import * as actions from '../../actions/index.js';

View File

@ -8,7 +8,7 @@ import starReducer from './tools/shapes/starReducer.js';
import triangleReducer from './tools/shapes/triangleReducer.js';
import bucketReducer from './tools/bucketReducer.js';
import penReducer from './tools/penReducer.js';
import textReducer from './tools/textReducer.js';
import textReducer, { removeEmptyText } from './tools/textReducer.js';
import photoGuideReducer from './tools/photoGuideReducer.js';
import { transformReducer } from './tools/transformReducer.js';
import eraserReducer from './tools/eraserReducer.js';
@ -44,6 +44,7 @@ export default function toolReducer(state, action) {
// change 2D tool after explicit tool change action or on some selection
if (action.type === actions.D2_CHANGE_TOOL) {
state = updateTool(state, action.tool);
state = removeEmptyText(state);
}
if (action.category === actions.CAT_SELECTION) {
state = updateTool(state, tools.TRANSFORM);

View File

@ -31,5 +31,8 @@ function handleEraser(state, path, screenMatrixZoom) {
roundPrecision: 0.25
});
return subtractShapeFromState(state, eraserShape, d2Tools.ERASER, { matrix: screenMatrixZoom, scale: CLIPPER_PRECISION });
return subtractShapeFromState(state, eraserShape, d2Tools.ERASER, {
matrix: screenMatrixZoom,
scale: CLIPPER_PRECISION
});
}

View File

@ -1,7 +1,5 @@
import { updateColor } from '../../selectionReducer.js';
import { getColor } from '../../../utils/objectSelectors.js';
import { select } from '../../menusReducer.js';
import update from 'react-addons-update';
import { updateTool as updateTool2d } from '../toolReducer.js';
export default function pipetteReducer(state, action) {

View File

@ -8,10 +8,10 @@ const debug = createDebug('d3d:reducer:text');
export default function textReducer(state, action) {
if (action.log !== false) debug(action.type);
const activeShape = state.d2.activeShape;
switch (action.type) {
case actions.D2_TEXT_INIT: {
state = removeEmptyText(state);
const { position, textId, screenMatrixZoom } = action;
const screenPosition = (position && screenMatrixZoom) ?
position.applyMatrix(screenMatrixZoom.inverseMatrix()) :
@ -22,37 +22,40 @@ export default function textReducer(state, action) {
} else {
return addObjectActive2D(state, {
transform: new Matrix({ x: screenPosition.x, y: screenPosition.y }),
type: 'TEXT'
type: 'TEXT',
text: {
text: '',
family: state.context.font,
weight: 'normal',
style: 'normal'
}
});
}
return state;
}
case actions.D2_TEXT_INPUT_CHANGE: {
const { text, family, style, weight, fill } = action;
const { text } = action;
const { activeShape } = state.d2;
return update(state, {
objectsById: {
[activeShape]: {
text: {
text: { $set: text },
family: { $set: family },
style: { $set: style },
weight: { $set: weight }
},
fill: { $set: fill }
text: { $set: text }
}
}
}
});
}
case actions.D2_TEXT_ADD: {
if (activeShape && state.objectsById[activeShape].text.text.length === 0) {
return setActive2D(removeObject(state, activeShape), null);
} else {
return setActive2D(state, null);
}
break;
}
default:
return state;
}
}
export function removeEmptyText(state) {
const { activeShape } = state.d2;
if (activeShape && state.objectsById[activeShape].text.text === '') {
return setActive2D(removeObject(state, activeShape), null);
}
return state;
}

View File

@ -4,7 +4,7 @@ import undoFilter from '../utils/undoFilter.js';
import * as actions from '../actions/index.js';
import * as d2Tools from '../constants/d2Tools.js';
import * as d3Tools from '../constants/d3Tools.js';
import { COLOR_STRING_TO_HEX } from '../constants/general.js';
import { COLOR_STRING_TO_HEX, FONT_FACE } from '../constants/general.js';
import * as contextTools from '../constants/contextTools.js';
import { ERASER_SIZES, BRUSH_SIZES } from '../constants/d2Constants.js';
import update from 'react-addons-update';
@ -36,7 +36,8 @@ const initialState = {
objectIdCounter: 0,
context: {
solid: true,
color: COLOR_STRING_TO_HEX[contextTools.LIGHT_BLUE_B]
color: COLOR_STRING_TO_HEX[contextTools.LIGHT_BLUE_B],
font: FONT_FACE[contextTools.OSWALD]
},
selection: {
transform: new Matrix(),
@ -142,7 +143,6 @@ function sketcherReducer(state = initialState, action) {
case actions.MULTITOUCH_TRANSFORM_END:
case actions.D2_TEXT_INIT:
case actions.D2_TEXT_INPUT_CHANGE:
case actions.D2_TEXT_ADD:
case actions.MOVE_SELECTION:
return d2ToolReducer(state, action);
@ -188,11 +188,11 @@ function sketcherReducer(state = initialState, action) {
return d3ToolReducer(state, action);
case actions.D2_CHANGE_TOOL:
state = setActive2D(state, null);
state = selectionReducer(state, action);
state = d2ToolReducer(state, action); // switch and initialize tool
state = updateMenus(state, action);
state = contextReducer(state, action);
state = setActive2D(state, null);
return state;
case actions.D3_CHANGE_TOOL:

View File

@ -8,6 +8,7 @@ import { MAX_ANGLE } from '../constants/d3Constants.js';
import { SHAPE_CACHE_LIMIT } from '../constants/general.js';
import { createText } from '../utils/textUtils.js';
import { segmentBezierPath } from '../utils/curveUtils.js';
import { TEXT_TOOL_FONT_SIZE } from '../constants/d2Constants.js';
const setDirection = (clockwise) => (path) => {
return (THREE.ShapeUtils.isClockWise(path) === clockwise) ? path : path.reverse();
@ -64,7 +65,7 @@ function shapeToPointsRaw(shapeData) {
const points = [];
let even = false;
const numLines = rays * 2;
for (let i = 0, rad = 0; i <= numLines; i++, rad += Math.PI / rays) {
for (let i = 0, rad = 0; i <= numLines; i ++, rad += Math.PI / rays, even = !even) {
if (i === numLines) { // last line?
points.push(points[0].clone()); // go to first point
} else {
@ -72,7 +73,6 @@ function shapeToPointsRaw(shapeData) {
let x = Math.sin(rad) * radius;
let y = -Math.cos(rad) * radius;
points.push(new Vector(x, y));
even = !even;
}
}
shapes.push({ points, holes: [] });
@ -120,7 +120,7 @@ function shapeToPointsRaw(shapeData) {
}
case 'TEXT': {
const { text, family, style, weight } = shapeData.text;
const textShapes = createText(text, 400, family, style, weight)
const textShapes = createText(text, TEXT_TOOL_FONT_SIZE, 10, family, style, weight)
.map(([points, ...holes]) => ({ points, holes }));
shapes.push(...textShapes);

View File

@ -9,19 +9,17 @@ import memoize from 'memoizee';
const MARGIN = 200;
export const createText = memoize(createTextRaw, { max: SHAPE_CACHE_LIMIT });
export function createTextRaw(text, size, family, style, weight) {
export function createTextRaw(text, size, precision, family, style, weight) {
if (text === '') return [];
const { width, height, canvas } = createTextCanvas(text, size, family, style, weight);
const canvas = createTextCanvas(text, size * precision, family, style, weight);
// TODO merge with potrace in flood fill trace reducer
const paths = POTRACE.getPaths(POTRACE.traceCanvas(canvas, POTRACE_OPTIONS));
const halfWidth = width / 2;
const halfHeight = height / 2;
const pathsOffset = paths.map(path => path.map(({ x, y }) => ({
x: (x - halfWidth) / 10,
y: (y - halfHeight) / 10
x: (x - MARGIN) / precision,
y: (y - MARGIN) / precision
})));
const shapes = new ClipperShape(pathsOffset, true, true, false)
@ -33,7 +31,7 @@ export function createTextRaw(text, size, family, style, weight) {
return shapes;
}
const textContext = new Text();
const textContext = new Text({ baseline: 'top' });
export function createTextCanvas(text, size, family, style, weight) {
textContext.size = size;
textContext.family = family;
@ -52,7 +50,7 @@ export function createTextCanvas(text, size, family, style, weight) {
context.fillStyle = 'white';
context.fillRect(0, 0, width, height);
textContext.drawText(context, text, MARGIN, height / 2);
textContext.drawText(context, text, MARGIN, MARGIN);
return { width, height, canvas };
return canvas;
}

View File

@ -14,7 +14,6 @@ const INCLUDE = [
actions.DESELECT_ALL,
actions.STAMP,
actions.SELECT,
actions.D2_TEXT_ADD,
actions.CLEAR,
actions.TWIST_END,
actions.SCULPT_END,

View File

@ -249,6 +249,24 @@
flex-wrap: wrap;
}
#font-tool > .button {
background-image: url('../img/contextmenu/btnFont.png');
background-size: 40px auto;
width: 40px;
height: 40px;
}
#font-tool .menu {
flex-direction: column;
max-height: 170px;
width: 120px;
overflow-y: auto;
}
#font-tool .menu .menuitem {
text-align: center;
}
#color-light-blue-a { fill: #BCFFFF; }
#color-light-blue-b { fill: #68E1FD; }
#color-light-blue-c { fill: #01B8FF; }

View File

@ -3,6 +3,7 @@ const path = require('path');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const HTMLWebpackPlugin = require('html-webpack-plugin');
const CordovaPlugin = require('webpack-cordova-plugin');
const GoogleFontsPlugin = require('google-fonts-webpack-plugin');
const devMode = process.env.NODE_ENV !== 'production';
const appMode = process.env.TARGET === 'app';
@ -75,9 +76,7 @@ module.exports = {
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
'TARGET': JSON.stringify(process.env.TARGET)
}
'process.env.TARGET': JSON.stringify(process.env.TARGET)
}),
new HTMLWebpackPlugin({
title: 'Doodle3D Core - Simple example',
@ -86,6 +85,18 @@ module.exports = {
scripts: appMode ? ['cordova.js'] : null,
appMountId: 'app'
}),
new GoogleFontsPlugin({
fonts: [
{ family: 'Oswald' },
{ family: 'Ranga' },
{ family: 'Joti One' },
{ family: 'Bellefair' },
{ family: 'Lobster' },
{ family: 'Abril Fatface' },
{ family: 'Play' },
{ family: 'Fascinate' }
]
}),
...(appMode ? [
new CordovaPlugin({
config: 'config.xml',
@ -95,7 +106,7 @@ module.exports = {
})
] : [])
],
devtool: "source-map",
devtool: 'source-map',
devServer: {
contentBase: 'dist'
}