WIP: Add initial meme generator! #748
6 changed files with 123 additions and 39 deletions
|
@ -26,7 +26,7 @@ export default class EditableFontface extends Component {
|
||||||
const textRender = fontFace.textRender || DEFAULT_TEXT_RENDER;
|
const textRender = fontFace.textRender || DEFAULT_TEXT_RENDER;
|
||||||
|
|
||||||
const textStyles = Object.assign({
|
const textStyles = Object.assign({
|
||||||
minHeight: '30px',
|
minHeight: '20px',
|
||||||
WebkitFontSmoothing: 'antialiased',
|
WebkitFontSmoothing: 'antialiased',
|
||||||
MozOsxFontSmoothing: 'grayscale',
|
MozOsxFontSmoothing: 'grayscale',
|
||||||
}, fontFace.text);
|
}, fontFace.text);
|
||||||
|
@ -67,5 +67,5 @@ export default class EditableFontface extends Component {
|
||||||
export const PRESETS = {
|
export const PRESETS = {
|
||||||
'Retro Rainbow': require('../FontFaces/RetroRainbow'),
|
'Retro Rainbow': require('../FontFaces/RetroRainbow'),
|
||||||
'Green Machine': require('../FontFaces/GreenMachine'),
|
'Green Machine': require('../FontFaces/GreenMachine'),
|
||||||
'Ocean Wave': require('../FontFaces/OceanWave'),
|
'vapor wave': require('../FontFaces/VaporWave'),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
container: {},
|
|
||||||
editorStyle: {},
|
|
||||||
text: {},
|
|
||||||
textRender: (text) => {
|
|
||||||
// TODO: Inline the path
|
|
||||||
const id = `curve-${text.replace(/[^A-Za-z0-9]/g, '')}-oceanwave`
|
|
||||||
return (
|
|
||||||
<svg viewBox="0 0 425 300" style={{ height: '10em' }}>
|
|
||||||
<path id={id} fill="transparent" d="M6,150C49.63,93,105.79,36.65,156.2,47.55,207.89,58.74,213,131.91,264,150c40.67,14.43,108.57-6.91,229-145" />
|
|
||||||
<text x="25">
|
|
||||||
<textPath href={`#${id}`}>
|
|
||||||
{text}
|
|
||||||
</textPath>
|
|
||||||
</text>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -3,6 +3,7 @@ module.exports = {
|
||||||
editorStyle: {
|
editorStyle: {
|
||||||
fontFamily: 'Arial, sans-serif',
|
fontFamily: 'Arial, sans-serif',
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
|
fontSize: '1.2em',
|
||||||
transform: 'scale(1, 1.5)',
|
transform: 'scale(1, 1.5)',
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
|
@ -10,8 +11,11 @@ module.exports = {
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
backgroundImage: 'linear-gradient(to right, #b306a9, #ef2667, #f42e2c, #ffa509, #fdfc00, #55ac2f, #0b13fd, #a804af)',
|
backgroundImage: 'linear-gradient(to right, #b306a9, #ef2667, #f42e2c, #ffa509, #fdfc00, #55ac2f, #0b13fd, #a804af)',
|
||||||
backgroundClip: 'text',
|
backgroundClip: 'text',
|
||||||
|
fontSize: '1.2em',
|
||||||
transform: 'scale(1, 1.5)',
|
transform: 'scale(1, 1.5)',
|
||||||
color: 'transparent',
|
color: 'transparent',
|
||||||
|
paddingBottom: '.25em',
|
||||||
|
paddingTop: '.1em',
|
||||||
WebkitBackgroundClip: 'text',
|
WebkitBackgroundClip: 'text',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
37
client/src/components/Creatify/FontFaces/VaporWave.js
Normal file
37
client/src/components/Creatify/FontFaces/VaporWave.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const charToFullWidth = char => {
|
||||||
|
const c = char.charCodeAt( 0 )
|
||||||
|
return c >= 33 && c <= 126
|
||||||
|
? String.fromCharCode( ( c - 33 ) + 65281 )
|
||||||
|
: char
|
||||||
|
}
|
||||||
|
|
||||||
|
const stringToFullWidth =
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
container: {},
|
||||||
|
editorStyle: {},
|
||||||
|
text: {
|
||||||
|
fontFamily: 'Segoe UI,Helvetica,Arial',
|
||||||
|
},
|
||||||
|
textRender: (text) => {
|
||||||
|
const formattedText = text.toLowerCase().split('').map((char) => {
|
||||||
|
const c = char.charCodeAt( 0 )
|
||||||
|
return (c >= 33 && c <= 126) ? String.fromCharCode(c + 65248) : char
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
// TODO: Inline the path
|
||||||
|
const id = `curve-${text.replace(/[^A-Za-z0-9]/g, '')}-oceanwave`
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 500 160" style={{ height: '10em' }}>
|
||||||
|
<path id={id} fill="transparent" d="M6,150C49.63,93,105.79,36.65,156.2,47.55,207.89,58.74,213,131.91,264,150c40.67,14.43,108.57-6.91,229-145" />
|
||||||
|
<text x="10">
|
||||||
|
<textPath href={`#${id}`}>
|
||||||
|
{formattedText}
|
||||||
|
</textPath>
|
||||||
|
</text>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
|
@ -49,7 +49,7 @@ export default class RichDraggable extends Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Draggable bounds={bounds} offsetParent={body} cancel=".no-drag">
|
<Draggable bounds={bounds} offsetParent={body} cancel=".no-drag">
|
||||||
<div ref={me.contents} style={{ padding: '10px', position: 'absolute', border: '4px dashed #ddd', cursor: 'move' }}>
|
<div ref={me.contents} style={{ padding: '10px', position: 'absolute', border: '4px dashed #ddd', cursor: 'move' }} className="creatifyDecor">
|
||||||
<div className="no-drag" style={{ overflow: 'hidden', position: 'relative', cursor: 'auto' }}>
|
<div className="no-drag" style={{ overflow: 'hidden', position: 'relative', cursor: 'auto' }}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,20 +12,89 @@ try {
|
||||||
}
|
}
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
|
|
||||||
|
const getRasterizedCanvas = (contents, width, height) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// Resolves a bug in Chrome where it renders correctly, but
|
||||||
|
// replaces the inline styles with an invalid `background-clip`.
|
||||||
|
if(/Chrome/.test(navigator.userAgent)) {
|
||||||
|
contents = contents.replace(/background\-clip:(.*)[;$]/g,
|
||||||
|
(match, group) => (`-webkit-background-clip:${group};${match}`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to match font kerning with the DOM.
|
||||||
|
contents = '<style>svg{font-kerning:normal}</style>' + contents;
|
||||||
|
|
||||||
|
rasterizeHTML.drawHTML(contents, document.createElement('canvas'), {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}).then((renderResult) => {
|
||||||
|
const pixelRatio = 2;
|
||||||
|
|
||||||
|
// Why do this? Because Firefox doesn't always give us what we expect
|
||||||
|
// `background-clip: text` is very broken and does not always render in time.
|
||||||
|
let img = document.createElement('img');
|
||||||
|
let canvas = document.createElement('canvas');
|
||||||
|
|
||||||
|
img.height = canvas.height = height * pixelRatio;
|
||||||
|
img.width = canvas.width = width * pixelRatio;
|
||||||
|
canvas.style.height = `${height}px`;
|
||||||
|
canvas.style.width = `${width}px`;
|
||||||
|
|
||||||
|
let shadowNode = document.createElement('div');
|
||||||
|
Object.assign(shadowNode.style, {
|
||||||
|
height: 0,
|
||||||
|
overflow: 'hidden',
|
||||||
|
width: 0,
|
||||||
|
});
|
||||||
|
document.body.appendChild(shadowNode);
|
||||||
|
|
||||||
|
shadowNode.appendChild(img);
|
||||||
|
//document.body.appendChild(canvas);
|
||||||
|
|
||||||
|
var svg64 = btoa(unescape(encodeURIComponent(renderResult.svg)));
|
||||||
|
var b64Start = 'data:image/svg+xml;base64,';
|
||||||
|
var image64 = b64Start + svg64;
|
||||||
|
img.addEventListener('load', () => {
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
// We still can't trust Firefox's %$%&* engine, add another 5ms timeout
|
||||||
|
setTimeout(() => {
|
||||||
|
let context = canvas.getContext('2d', { alpha: false });
|
||||||
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
context.fillStyle = 'white';
|
||||||
|
context.imageSmoothingEnabled = false;
|
||||||
|
context.scale(pixelRatio, pixelRatio);
|
||||||
|
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
context.drawImage(img, 0, 0);
|
||||||
|
|
||||||
|
document.body.removeChild(shadowNode);
|
||||||
|
|
||||||
|
resolve(canvas);
|
||||||
|
}, 10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
img.src = image64;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export default class Creatify extends Component {
|
export default class Creatify extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
const fontKeys = Object.keys(FontPresets);
|
const fontKeys = Object.keys(FontPresets);
|
||||||
|
|
||||||
this.canvas = React.createRef();
|
|
||||||
this.contents = React.createRef();
|
this.contents = React.createRef();
|
||||||
|
|
||||||
const fontOptions = fontKeys.map(
|
const fontOptions = fontKeys.map(
|
||||||
(fontName) => (
|
(fontName) => (
|
||||||
{
|
{
|
||||||
value: fontName,
|
value: fontName,
|
||||||
label: <EditableFontface key={fontName} fontFace={FontPresets[fontName]} value={fontName} editable="false" />,
|
label: (
|
||||||
|
<div style={{ maxHeight: '150px', maxWidth: '100%', fontSize: '16px', overflow: 'hidden' }}>
|
||||||
|
<EditableFontface key={fontName} fontFace={FontPresets[fontName]} value={fontName} editable="false" />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
fontName,
|
fontName,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -49,19 +118,15 @@ export default class Creatify extends Component {
|
||||||
renderContents() {
|
renderContents() {
|
||||||
const me = this;
|
const me = this;
|
||||||
|
|
||||||
const canvas = me.canvas.current;
|
let contents = me.contents.current.outerHTML;
|
||||||
let contents = me.contents.current.innerHTML;
|
|
||||||
|
|
||||||
// Resolves a bug in Chrome where it renders correctly, but
|
console.log(contents)
|
||||||
// replaces the inline styles with an invalid `background-clip`
|
// Cheap border/handles removal
|
||||||
contents = contents.replace(/background\-clip:(.*)[;$]/g,
|
contents = `<style>.creatifyDecor{border-color:transparent!important;background-color:transparent!important}</style>` + contents;
|
||||||
(match, group) => (`-webkit-background-clip:${group};${match}`)
|
|
||||||
);
|
|
||||||
|
|
||||||
rasterizeHTML.drawHTML(contents, canvas).then((renderResult) => {
|
|
||||||
|
|
||||||
}, (error) => {
|
|
||||||
|
|
||||||
|
getRasterizedCanvas(contents, 600, 500).then((element) => {
|
||||||
|
console.log(element);
|
||||||
|
document.body.appendChild(element);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,10 +141,9 @@ export default class Creatify extends Component {
|
||||||
<div style={{ flex: 1, display: 'flex' }}>
|
<div style={{ flex: 1, display: 'flex' }}>
|
||||||
<div>
|
<div>
|
||||||
<button onClick={() => this.renderContents()}>Rasterize</button>
|
<button onClick={() => this.renderContents()}>Rasterize</button>
|
||||||
<canvas ref={me.canvas} width="200" height="200"></canvas>
|
|
||||||
<Select isSearchable={false} options={state.fontOptions} onChange={(option) => this.setFont(option.fontName)} />
|
<Select isSearchable={false} options={state.fontOptions} onChange={(option) => this.setFont(option.fontName)} />
|
||||||
</div>
|
</div>
|
||||||
<div ref={me.contents} style={{ flex: 1 }}>
|
<div ref={me.contents} style={{ height: '600px', width: '500px', fontSize: '22px', overflow: 'hidden', transform: 'translateZ(0)' }}>
|
||||||
<RichDraggable bounds={state.bounds}>
|
<RichDraggable bounds={state.bounds}>
|
||||||
<EditableFontface fontFace={FontPresets[state.fontName]} value="Hello from LBRY" />
|
<EditableFontface fontFace={FontPresets[state.fontName]} value="Hello from LBRY" />
|
||||||
</RichDraggable>
|
</RichDraggable>
|
||||||
|
|
Loading…
Reference in a new issue