UNPKG

psd2bmf

Version:
247 lines (192 loc) 7.14 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.exec = exec; exports.run = run; var _path = _interopRequireDefault(require("path")); var _psd = _interopRequireDefault(require("@porky-prince/psd")); var _Group = _interopRequireDefault(require("./model/Group")); var _layout = _interopRequireDefault(require("layout")); var _Option = _interopRequireDefault(require("./option/Option")); var _utils = require("./utils"); var _Font = require("./model/Font"); var _const = require("./const"); var _BmfFntParser = _interopRequireDefault(require("./BmfFntParser")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } async function runTask(option) { const psd = _psd.default.fromFile(option.input); psd.parse(); let srcPng = await (0, _utils.readPng)(option.inputPng); if (srcPng === null) { console.warn('warn: Png image exported with PhotoShop are of higher quality.'); srcPng = (0, _utils.createPng)(psd.image.width(), psd.image.height()); srcPng.data = Buffer.from(psd.image.pixelData); } const tree = psd.tree(); const children = tree.children(); const groups = []; let exportCount = 0; for (let i = 0, length = children.length; i < length; i++) { let child = children[i]; if (_Group.default.canExport(child)) { const group = new _Group.default(option); group.init(exportCount++, child); if (group.layers.length === 0) { console.warn(`warn: Group ${group.index} is empty.`); } else { groups.push(group); } } } if (groups.length === 1) { groups[0].onlyOne = true; } await exportGroups(srcPng, groups); } function recognize(srcPng, group) { const srcPngData = srcPng.data; if (srcPngData[3] > 0) throw new Error('Are you sure the background is transparent?'); const layers = group.layers; const splitSpace = group.recognizeOpt.splitSpace; const padding = group.recognizeOpt.padding; const maxLayerHeight = group.maxLayerHeight; const fonts = []; /* eslint max-depth: ["error", 5] */ for (let i = 0, length = layers.length; i < length; i++) { const layer = layers[i]; layer.showOutOfBoundsError(srcPng.width, srcPng.height); group.exportsOpt.setDefault(layer.size, layer.size); let start = NaN; let end = 0; let spaceCount = 0; let fontCount = 0; const xLen = Math.min(layer.x + layer.width + (splitSpace << 1), srcPng.width); const yLen = layer.y + layer.height; for (let x = layer.x; x < xLen; x++) { for (let y = layer.y; y < yLen; y++) { const idx = srcPng.width * y + x << 2; const alpha = srcPngData[idx + 3]; if (alpha > 0) { // 开始识别 if (isNaN(start)) start = end = x; end++; spaceCount = 0; break; // Start不为NaN说明已经开始了一个字的识别 // 且当一列都是透明时 } else if (!isNaN(start) && y === yLen - 1) { spaceCount++; // 当连续超过splitSpace列透明时则这个字识别结束 if (spaceCount > splitSpace) { const font = new _Font.Font(layer.getFontText(fontCount, true)); font.setBound(start, layer.y - Math.ceil((maxLayerHeight - layer.height) / 2), end - splitSpace - start, maxLayerHeight, padding); fonts.push(font); fontCount++; // 从新开始识别下一个字 start = NaN; } else { end++; } } } } layer.showNoCorrespondingError(fontCount); layer.hasSpace() && fonts.push(new _Font.Font(_const.SPACE)); layer.hasTab() && fonts.push(new _Font.Font(_const.TAB)); } return fonts; } async function build(fonts) { const length = fonts.length; if (length > 0) { const layout = (0, _layout.default)('binary-tree'); const unique = []; const specialItems = []; const customPng = {}; for (let i = 0; i < length; i++) { const font = fonts[i]; if (unique.indexOf(font.id) === -1) { if (font.isCustom()) { /* eslint no-await-in-loop: "off" */ const png = await font.readCustomPng(); if (png === null) break; customPng[font.id] = png; } unique.push(font.id); if (!font.isSpace() && !font.isTab()) { layout.addItem(font); } else { specialItems.push(font); } } } const layoutInfo = layout.export(); layoutInfo.specialItems = specialItems; layoutInfo.customPng = customPng; return layoutInfo; } return null; } async function exportGroups(srcPng, groups) { const taskAll = []; for (let i = 0, length = groups.length; i < length; i++) { taskAll.push(exportGroup(srcPng, groups[i])); } await Promise.all(taskAll); } async function exportGroup(srcPng, group) { const option = group.option; const groupOpt = group.groupOpt; const layoutInfo = await build(recognize(srcPng, group).concat(groupOpt.ext.chars)); if (layoutInfo !== null) { // TODO: more exports option const exportsOpt = groupOpt.exports; let output = exportsOpt.output; let filename = exportsOpt.filename; if (!output) { output = option.output; } if (!filename) { filename = option.filename; filename += group.getFilenameExt(output, filename); } const outputPath = _path.default.join(output, filename); await Promise.all([exportPng(srcPng, layoutInfo, outputPath), exportFnt(exportsOpt, layoutInfo, filename, outputPath)]); } } async function exportPng(srcPng, layoutInfo, outputPath) { const distPng = (0, _utils.createPng)(layoutInfo.width, layoutInfo.height); for (let i = 0, length = layoutInfo.items.length; i < length; i++) { const font = layoutInfo.items[i]; let _srcPng = srcPng; if (font.isCustom()) { _srcPng = layoutInfo.customPng[font.id]; } _srcPng.bitblt(distPng, font.posX, font.posY, font.actualWidth, font.actualHeight, font.x + font.padding, font.y + font.padding); } await (0, _utils.writePng)(outputPath + _const.PNG_EXT, distPng); } async function exportFnt(exportOpt, layoutInfo, filename, outputPath) { const fonts = layoutInfo.items.concat(layoutInfo.specialItems); const parser = new _BmfFntParser.default(); await parser.parse(exportOpt.bmfFntTemp); parser.replace('size', exportOpt.size); parser.replace('lineHeight', exportOpt.lineHeight); parser.replace('file', filename + _const.PNG_EXT); parser.replace('scaleW', layoutInfo.width); parser.replace('scaleH', layoutInfo.height); parser.replace('count', fonts.length); for (let i = 0, length = fonts.length; i < length; i++) { parser.addChar(fonts[i]); } await parser.save2BmfFnt(outputPath + _const.FNT_EXT); } async function exec(psdPath, output, filename, inputPng) { const opt = new _Option.default(); opt.input = psdPath; opt.output = output; opt.filename = filename; opt.inputPng = inputPng; await runTask(opt); } async function run(option) { await runTask(new _Option.default(option)); }