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 textStyles = Object.assign({
|
||||
minHeight: '30px',
|
||||
minHeight: '20px',
|
||||
WebkitFontSmoothing: 'antialiased',
|
||||
MozOsxFontSmoothing: 'grayscale',
|
||||
}, fontFace.text);
|
||||
|
@ -67,5 +67,5 @@ export default class EditableFontface extends Component {
|
|||
export const PRESETS = {
|
||||
'Retro Rainbow': require('../FontFaces/RetroRainbow'),
|
||||
'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: {
|
||||
fontFamily: 'Arial, sans-serif',
|
||||
fontWeight: 'bold',
|
||||
fontSize: '1.2em',
|
||||
transform: 'scale(1, 1.5)',
|
||||
},
|
||||
text: {
|
||||
|
@ -10,8 +11,11 @@ module.exports = {
|
|||
fontWeight: 'bold',
|
||||
backgroundImage: 'linear-gradient(to right, #b306a9, #ef2667, #f42e2c, #ffa509, #fdfc00, #55ac2f, #0b13fd, #a804af)',
|
||||
backgroundClip: 'text',
|
||||
fontSize: '1.2em',
|
||||
transform: 'scale(1, 1.5)',
|
||||
color: 'transparent',
|
||||
paddingBottom: '.25em',
|
||||
paddingTop: '.1em',
|
||||
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 (
|
||||
<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' }}>
|
||||
{props.children}
|
||||
</div>
|
||||
|
|
|
@ -12,20 +12,89 @@ try {
|
|||
}
|
||||
} 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 {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const fontKeys = Object.keys(FontPresets);
|
||||
|
||||
this.canvas = React.createRef();
|
||||
this.contents = React.createRef();
|
||||
|
||||
const fontOptions = fontKeys.map(
|
||||
(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,
|
||||
}
|
||||
)
|
||||
|
@ -49,19 +118,15 @@ export default class Creatify extends Component {
|
|||
renderContents() {
|
||||
const me = this;
|
||||
|
||||
const canvas = me.canvas.current;
|
||||
let contents = me.contents.current.innerHTML;
|
||||
let contents = me.contents.current.outerHTML;
|
||||
|
||||
// Resolves a bug in Chrome where it renders correctly, but
|
||||
// replaces the inline styles with an invalid `background-clip`
|
||||
contents = contents.replace(/background\-clip:(.*)[;$]/g,
|
||||
(match, group) => (`-webkit-background-clip:${group};${match}`)
|
||||
);
|
||||
|
||||
rasterizeHTML.drawHTML(contents, canvas).then((renderResult) => {
|
||||
|
||||
}, (error) => {
|
||||
console.log(contents)
|
||||
// Cheap border/handles removal
|
||||
contents = `<style>.creatifyDecor{border-color:transparent!important;background-color:transparent!important}</style>` + contents;
|
||||
|
||||
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>
|
||||
<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)} />
|
||||
</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}>
|
||||
<EditableFontface fontFace={FontPresets[state.fontName]} value="Hello from LBRY" />
|
||||
</RichDraggable>
|
||||
|
|
Loading…
Reference in a new issue