Skip to content

Commit

Permalink
Merge pull request #21 from georapbox/multiline-text
Browse files Browse the repository at this point in the history
Multiline text
georapbox authored Dec 19, 2023
2 parents 4bb6b08 + 1449f0a commit 93fc87d
Showing 17 changed files with 97 additions and 75 deletions.
4 changes: 4 additions & 0 deletions docs/index.09fc3c3b.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/index.09fc3c3b.js.map

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion docs/index.1dc82246.css.map

This file was deleted.

4 changes: 0 additions & 4 deletions docs/index.3416feef.js

This file was deleted.

1 change: 0 additions & 1 deletion docs/index.3416feef.js.map

This file was deleted.

6 changes: 3 additions & 3 deletions docs/index.eaf681fe.js → docs/index.3df76dfe.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/index.3df76dfe.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/index.1dc82246.css → docs/index.7d77b59f.css

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/index.7d77b59f.css.map

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion docs/index.eaf681fe.js.map

This file was deleted.

2 changes: 1 addition & 1 deletion docs/index.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/service-worker.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/service-worker.js.map

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions src/css/main.css
Original file line number Diff line number Diff line change
@@ -73,6 +73,13 @@ input[type="file"]:disabled::-webkit-file-upload-button {
cursor: pointer;
}

.inputs-container .meme-text {
min-width: 0;
min-height: calc(1.5em + 0.75rem + 2px);
height: calc(1.5em + 0.75rem + 2px);
margin: 0.5rem;
}

/* Buttons */
.btn:disabled {
cursor: not-allowed;
23 changes: 16 additions & 7 deletions src/js/create-text-box.js
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ export const createTextBox = (index, data = {}) => {
<div class="d-flex align-items-center">
<button class="btn" data-button="delete-text-box" aria-label="Remove"></button>
<input class="form-control m-2" type="text" value="${data.text}" data-input="text" autocomplete="off" placeholder="${`Text #${index + 1}`}" style="min-width: 0;">
<textarea class="form-control meme-text" type="text" data-input="text" autocomplete="off" rows="1" placeholder="${`Text #${index + 1}`}">${data.text}</textarea>
<div class="d-flex align-items-center pr-2">
<input class="form-control" type="color" value="${data.fillColor}" data-input="fillColor" title="Fill color">
@@ -16,7 +16,7 @@ export const createTextBox = (index, data = {}) => {
<div class="p-2" data-section="settings" ${data._isSettingsOpen ? '' : 'hidden'}>
<div class="form-row">
<div class="col-6 mb-3">
<div class="col-4 mb-3">
<label for="fontInput_${index}" class="mb-1 d-block text-truncate">Font: </label>
<select class="custom-select" data-input="font" id="fontInput_${index}">
@@ -42,12 +42,12 @@ export const createTextBox = (index, data = {}) => {
</select>
</div>
<div class="col-3 mb-3">
<div class="col-4 mb-3">
<label for="fontSizeInput_${index}" class="mb-1 d-block text-truncate">Size:</label>
<input class="form-control" type="number" min="1" value="${data.fontSize}" data-input="fontSize" id="fontSizeInput_${index}">
</div>
<div class="col-3 mb-3">
<div class="col-4 mb-3">
<label for="fontWeightInput_${index}" class="mb-1 d-block text-truncate">Weight:</label>
<select class="custom-select" data-input="fontWeight" id="fontWeightInput_${index}">
<option value="normal" selected>Normal</option>
@@ -57,12 +57,21 @@ export const createTextBox = (index, data = {}) => {
</div>
<div class="form-row">
<div class="col-6 mb-3">
<div class="col-4 mb-3">
<label for="shadowWidthInput_${index}" class="mb-1 d-block text-truncate">Shadow width:</label>
<input class="form-control" type="number" min="0" max="10" value="${data.shadowBlur}" data-input="shadowBlur" id="shadowWidthInput_${index}">
</div>
<div class="col-6 mb-3">
<div class="col-4 mb-3">
<label for="textAlign_${index}" class="mb-1 d-block text-truncate">Text align:</label>
<select class="custom-select" data-input="textAlign" id="textAlignInput_${index}">
<option value="left">Left</option>
<option value="center" selected>Center</option>
<option value="right">Right</option>
</select>
</div>
<div class="col-4 mb-3">
<label class="mb-1 d-block text-truncate" for="textRotateInput_${index}">Rotate:</label>
<input class="form-control" type="number" value="${data.rotate}" data-input="rotate" id="textRotateInput_${index}" min="-360" max="360">
</div>
@@ -108,7 +117,7 @@ export const createTextBox = (index, data = {}) => {
div.setAttribute('data-index', index);
div.innerHTML = inputTemplate;
div.querySelector('[data-input="font"]').value = data.font;
// div.querySelector('[data-input="textAlign"]').value = data.textAlign;
div.querySelector('[data-input="textAlign"]').value = data.textAlign;
div.querySelector('[data-input="allCaps"]').checked = data.allCaps;

return fragment.appendChild(div);
50 changes: 50 additions & 0 deletions src/js/draw-canvas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
export const drawCanvas = (image, canvas, ctx, textOptions = []) => {
if (image == null) {
return;
}

ctx.clearRect(0, 0, canvas.width, canvas.height);

if (typeof image === 'string') { // Assume it's a color
ctx.fillStyle = image;
ctx.fillRect(0, 0, canvas.width, canvas.height);
} else {
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
}

textOptions.forEach(function (item, index) {
ctx.font = `${item.fontWeight} ${item.fontSize}px ${item.font}`;

const multiplier = index + 1;
const lineHeight = ctx.measureText('M').width + item.fontSize / 2;
const xPos = canvas.width / 2;
const shadowBlur = item.shadowBlur;
const text = item.allCaps === true ? item.text.toUpperCase() : item.text;
const textLines = text.split('\n');

ctx.fillStyle = item.fillColor;
ctx.textAlign = item.textAlign;
ctx.save();

if (shadowBlur !== 0) {
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = shadowBlur;
ctx.shadowColor = item.shadowColor;
}

if (item.rotate) {
ctx.translate(xPos + item.offsetX, lineHeight * multiplier + item.offsetY);
ctx.rotate(item.rotate * Math.PI / 180);
textLines.forEach((text, index) => ctx.fillText(text, 0, index * lineHeight));
ctx.rotate(-(item.rotate * Math.PI / 180));
ctx.translate(-(xPos + item.offsetX), -(lineHeight * multiplier + item.offsetY));
} else {
textLines.forEach((text, index) => {
ctx.fillText(text, xPos + item.offsetX, index * lineHeight + lineHeight * multiplier + item.offsetY);
});
}

ctx.restore();
});
};
62 changes: 9 additions & 53 deletions src/js/index.js
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ import { fileFromUrl } from './file-from-url.js';
import { toastAlert } from './toast-alert.js';
import { toggleModal } from './toggle-modal.js';
import { createTextBox } from './create-text-box.js';
import { drawCanvas } from './draw-canvas.js';

const videoModal = document.getElementById('videoModal');
const downloadModal = document.getElementById('downloadModal');
@@ -46,6 +47,7 @@ const defaultTextOptions = {
font: 'Anton',
fontSize: 40,
fontWeight: 'normal',
textAlign: 'center',
shadowBlur: 3,
offsetY: 0,
offsetX: 0,
@@ -89,54 +91,6 @@ const generateMeme = async () => {
toggleModal(downloadModal, true);
};

const draw = image => {
if (image == null) {
return;
}

ctx.clearRect(0, 0, canvas.width, canvas.height);

if (typeof image === 'string') { // Assume it's a color
ctx.fillStyle = image;
ctx.fillRect(0, 0, canvas.width, canvas.height);
} else {
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
}

textOptions.forEach(function (item, index) {
ctx.font = `${item.fontWeight} ${item.fontSize}px ${item.font}`;

const multiplier = index + 1;
const lineHeight = ctx.measureText('M').width + 20;
const xPos = canvas.width / 2;
const shadowBlur = item.shadowBlur;
const text = item.allCaps === true ? item.text.toUpperCase() : item.text;

ctx.fillStyle = item.fillColor;
ctx.textAlign = 'center';
ctx.save();

if (shadowBlur !== 0) {
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = shadowBlur;
ctx.shadowColor = item.shadowColor;
}

if (item.rotate) {
ctx.translate(xPos + item.offsetX, lineHeight * multiplier + item.offsetY);
ctx.rotate(item.rotate * Math.PI / 180);
ctx.fillText(text, 0, 0);
ctx.rotate(-(item.rotate * Math.PI / 180));
ctx.translate(-(xPos + item.offsetX), -(lineHeight * multiplier + item.offsetY));
} else {
ctx.fillText(text, xPos + item.offsetX, lineHeight * multiplier + item.offsetY);
}

ctx.restore();
});
};

const onImageLoaded = evt => {
const MAX_WIDTH = 800;
const MAX_HEIGHT = 600;
@@ -159,7 +113,7 @@ const onImageLoaded = evt => {

selectedImage = evt.target;

draw(selectedImage);
drawCanvas(selectedImage, canvas, ctx, textOptions);

generateMemeBtn.disabled = false;
canvas.hidden = false;
@@ -178,7 +132,7 @@ const handleSolidColorFormInput = evt => {
canvas.width = Number(solidColorForm['canvasWidth'].value) || DEFAULT_WIDTH;
canvas.height = Number(solidColorForm['canvasHeight'].value) || DEFAULT_HEIGHT;

draw(selectedImage);
drawCanvas(selectedImage, canvas, ctx, textOptions);

generateMemeBtn.disabled = false;
canvas.hidden = false;
@@ -221,7 +175,7 @@ const handleTextPropChange = (element, index, prop) => {
textOptions[index][prop] = element.value;
}

draw(selectedImage);
drawCanvas(selectedImage, canvas, ctx, textOptions);
};

const handleAddTextboxBtnClick = () => {
@@ -295,7 +249,7 @@ const moveText = (offsetDir, sign, index) => () => {
offsetXInput.value = textOptions[index].offsetX;
}

draw(selectedImage);
drawCanvas(selectedImage, canvas, ctx, textOptions);

reqAnimFrame = requestAnimationFrame(moveText(offsetDir, sign, index));
};
@@ -349,6 +303,8 @@ const handleInputsContainerInput = evt => {
prop = 'fontSize';
} else if (element.matches('[data-input="fontWeight"]')) {
prop = 'fontWeight';
} else if (element.matches('[data-input="textAlign"]')) {
prop = 'textAlign';
} else if (element.matches('[data-input="shadowBlur"]')) {
prop = 'shadowBlur';
} else if (element.matches('[data-input="offsetY"]')) {
@@ -410,7 +366,7 @@ const handleInputsContainerClick = evt => {
textOptions = arrayRemove(textOptions, index);
inputsContainer.querySelectorAll('[data-section="textBox"]').forEach(el => el.remove());
textOptions.forEach((item, index) => inputsContainer.appendChild(createTextBox(index, item)));
draw(selectedImage);
drawCanvas(selectedImage, canvas, ctx, textOptions);
}
}
};

0 comments on commit 93fc87d

Please sign in to comment.