Implemented font image renderer

This commit is contained in:
ErickSkrauch
2019-05-10 01:53:28 +03:00
parent cb84df8f96
commit 625b5a9b94
42 changed files with 628 additions and 531 deletions

View File

@@ -0,0 +1,120 @@
/* eslint-env node */
const fs = require('fs');
const path = require('path');
const glob = require('glob');
const { stringify } = require('qs');
const localFontPath = path.join(__dirname, 'RobotoCondensed-Regular.ttf');
const localFontName = 'RobotoCondensed';
const scale = 2;
module.exports = async function(content) {
this.cacheable && this.cacheable();
const callback = this.async();
const { publicPath } = this._compiler.options.output;
const ROOT_PATH = path.join(this.rootContext, 'src');
const localeName = path.basename(this.resourcePath, `.${this.resourcePath.split('.').pop()}`);
const renderText2Png = (key, { text, size, color }) => new Promise((resolve, reject) => {
if (!text || !size || !color) {
reject(new Error('text, size and color params are required'));
return;
}
const fileName = `${key.replace(/\./g, '_')}_${localeName}.png`;
const args = {
localFontPath,
localFontName,
text,
color,
font: `${size * scale}px RobotoCondensed`, // eslint-disable-line generator-star-spacing
};
const renderTextRequest = `text2png-loader?${stringify(args)}!`;
const emitFileRequest = `image-size-loader?name=assets${path.sep}${fileName}?[hash]!${renderTextRequest}`;
this.loadModule(emitFileRequest, (err, module) => {
if (err) {
reject(err);
return;
}
global.__webpack_public_path__ = publicPath; // eslint-disable-line camelcase
const { src, width, height } = this.exec(module, fileName);
Reflect.deleteProperty(global, '__webpack_public_path__');
const targetWidth = Math.ceil(width / scale);
const targetHeight = Math.ceil(height / scale);
resolve(`<img src="${src}" alt="${text}" width="${targetWidth}" height="${targetHeight}" style="vertical-align: middle" />`);
});
});
const examine = (key, value) => new Promise((resolve, reject) => {
const pathParts = key.split('.');
const id = pathParts.pop();
const pattern = path.join(ROOT_PATH, pathParts.join('/'), '*.intl.json');
// glob always uses linux separators
glob(pattern.replace(/\\/g, '/'), (err, matches) => {
if (err) {
reject(err);
return;
}
if (matches.length === 0) {
this.emitWarning(`Unable to find corresponding intl file for ${key} key`);
resolve(value);
return;
}
for (const path of matches) {
const json = JSON.parse(fs.readFileSync(path));
const descriptor = json[id];
if (!descriptor) {
continue;
}
this.addDependency(path);
if (typeof descriptor === 'string') {
resolve(value);
continue;
}
if (typeof descriptor !== 'object') {
this.emitWarning('Unknown value type');
continue;
}
const { type } = descriptor;
if (type !== 'text2png') {
this.emitWarning(`Unsupported object key type "${type}"`);
continue;
}
renderText2Png(key, {
...descriptor,
text: value,
}).then(resolve).catch(reject);
return;
}
resolve(value);
});
});
const json = JSON.parse(content);
const result = JSON.stringify(await Object.keys(json).reduce(async (translationsPromise, key) => {
const translations = await translationsPromise;
translations[key] = await examine(key, json[key]);
return translations;
}, Promise.resolve({})));
callback(null, `
import { defineMessages } from 'react-intl';
export default defineMessages(${result});
`);
};

View File

@@ -0,0 +1,13 @@
{
"name": "extended-translations-loader",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"@lesechos/image-size-loader": "file:./../image-size-loader",
"glob": "^7.1.4",
"loader-utils": "^1.2.3",
"text2png-loader": "file:./../text2png-loader",
"tmp": "^0.0.33",
"qs": "^6.0.0"
}
}

View File

@@ -0,0 +1,108 @@
/* eslint-disable multiline-ternary */
const path = require('path');
const fs = require('fs');
const sizeOf = require('image-size');
const loaderUtils = require('loader-utils');
const validateOptions = require('schema-utils');
const schema = require('./options.json');
function imageToString(image) {
return `
module.exports = {
src: ${image.src},
width: ${JSON.stringify(image.width)},
height: ${JSON.stringify(image.height)},
bytes: ${JSON.stringify(image.bytes)},
type: ${JSON.stringify(image.type)},
};
// For requires from CSS when used with webpack css-loader,
// outputting an Object doesn't make sense,
// So overriding the toString method to output just the URL
module.exports.toString = function() {
return ${image.src};
};
`;
}
module.exports = function(content) {
if (!this.emitFile) {
throw new Error('File Loader\n\nemitFile is required from module system');
}
const options = loaderUtils.getOptions(this) || {};
validateOptions(schema, options, 'File Loader');
const context = options.context || this.rootContext || (this.options && this.options.context);
const url = loaderUtils.interpolateName(this, options.name, {
context,
content,
regExp: options.regExp,
});
let image;
if (this.resourcePath) {
image = sizeOf(this.resourcePath);
image.bytes = fs.statSync(this.resourcePath).size;
} else {
image = sizeOf(content);
image.bytes = content.byteLength;
}
let outputPath = url;
if (options.outputPath) {
if (typeof options.outputPath === 'function') {
outputPath = options.outputPath(url);
} else {
outputPath = path.posix.join(options.outputPath, url);
}
}
if (options.useRelativePath) {
const filePath = this.resourcePath;
const issuer = options.context ? context : this._module && this._module.issuer && this._module.issuer.context;
const relativeUrl = issuer && path
.relative(issuer, filePath)
.split(path.sep)
.join('/');
const relativePath = relativeUrl && `${path.dirname(relativeUrl)}/`;
// eslint-disable-next-line no-bitwise
if (~relativePath.indexOf('../')) {
outputPath = path.posix.join(outputPath, relativePath, url);
} else {
outputPath = path.posix.join(relativePath, url);
}
}
let publicPath = `__webpack_public_path__ + ${JSON.stringify(outputPath)}`;
if (options.publicPath) {
if (typeof options.publicPath === 'function') {
publicPath = options.publicPath(url);
} else if (options.publicPath.endsWith('/')) {
publicPath = options.publicPath + url;
} else {
publicPath = `${options.publicPath}/${url}`;
}
publicPath = JSON.stringify(publicPath);
}
image.src = publicPath;
// eslint-disable-next-line no-undefined
if (options.emitFile === undefined || options.emitFile) {
this.emitFile(outputPath, content);
}
return imageToString(image);
};
module.exports.raw = true;

View File

@@ -0,0 +1,19 @@
{
"type": "object",
"properties": {
"name": {},
"regExp": {},
"context": {
"type": "string"
},
"publicPath": {},
"outputPath": {},
"useRelativePath": {
"type": "boolean"
},
"emitFile": {
"type": "boolean"
}
},
"additionalProperties": true
}

View File

@@ -0,0 +1,13 @@
{
"name": "@lesechos/image-size-loader",
"version": "1.0.0",
"main": "index.js",
"peerDependencies": {
"webpack": "^2.0.0 || ^3.0.0 || ^4.0.0"
},
"dependencies": {
"image-size": "^0.6.3",
"loader-utils": "^1.1.0",
"schema-utils": "^1.0.0"
}
}

View File

@@ -10,16 +10,25 @@ module.exports = function(input) {
const json = JSON.parse(input);
const result = JSON.stringify(Object.keys(json).reduce((translations, key) => {
translations[key] = {
id: `${moduleId}.${key}`,
defaultMessage: json[key],
};
const value = json[key];
const id = `${moduleId}.${key}`;
if (typeof value === 'object') {
translations[key] = {
...value,
id,
};
} else {
translations[key] = {
id,
defaultMessage: value,
};
}
return translations;
}, {}));
return `
import { defineMessages } from 'react-intl';
export default defineMessages(${result})
export default defineMessages(${result});
`;
};

View File

@@ -0,0 +1,16 @@
/* eslint-env node */
const { getOptions } = require('loader-utils');
const text2png = require('text2png');
module.exports = function() {
this.cacheable && this.cacheable();
const { text, ...options } = getOptions(this);
if (!text) {
this.emitError('The text param is required');
return '';
}
return text2png(text, options);
};

View File

@@ -0,0 +1,9 @@
{
"name": "text2png-loader",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"text2png": "^2.1.0",
"loader-utils": "^1.2.3"
}
}