Reprinted to wechat public number: Fang Stool Ya Ji
background
With the development, the operation of the title pictures on the event venue page needs online templating, and the self-developed guide material production platform is connected to Haitong-Creative Center. Through the ability of the platform, the material is templated, and a variety of scene-oriented and personalized materials are generated through configuration. However, the material template of the Creative Center is based on SVG, while the topic image of the venue page is basically output based on Photoshop(PS), and the source file is PSD. SVG is a vector graphics-oriented markup language, and PS is an image processing software centered on bitmap processing. In most cases, PS cannot directly export SVG files.
In order to connect the title drawing template of the venue page to the purchasing material production platform and reduce the threshold for designers to use, we need to realize the function of directly converting PSD into SVG in the purchasing material production platform, online converting PSD into SVG, and then importing the title drawing into the creative center.
Parse the PSD using psd.js
The PSD document is a binary file, and there are format instructions on the website. At present, the most mature solution for parsing PSD documents in the front end is to use PSD.js. The usage method and API can be found here.
JSON structure of PSD
You can export the JSON structure of a PSD document in the following way.
const PSD = require('psd');
const psd = PSD.fromFile(file); // file is the PSD file storage path
psd.parse();
console.log(psd.tree().export());
Copy the code
The JSON structure is as follows:
{ children:
[ { type: 'group',
visible: false,
opacity: 1,
blendingMode: 'normal',
name: 'Version D',
left: 0,
right: 900,
top: 0,
bottom: 600,
height: 600,
width: 900,
children:
[ { type: 'layer',
visible: true,
opacity: 1,
blendingMode: 'normal',
name: 'Make a change and save.',
left: 275,
right: 636,
top: 435,
bottom: 466,
height: 31,
width: 361,
mask: {},
text:
{ value: 'Make a change and save.',
font:
{ name: 'HelveticaNeue-Light',
sizes: [ 33 ],
colors: [ [ 85, 96, 110, 255 ] ],
alignment: [ 'center' ] },
left: 0,
top: 0,
right: 0,
bottom: 0,
transform: { xx: 1, xy: 0, yx: 0, yy: 1, tx: 456, ty: 459 } },
image: {} } ] } ],
document:
{ width: 900,
height: 600,
resources:
{ layerComps:
[ { id: 692243163, name: 'Version A', capturedInfo: 1 },
{ id: 725235304, name: 'Version B', capturedInfo: 1 },
{ id: 730932877, name: 'Version C', capturedInfo: 1 } ],
guides: [],
slices: [] } } }
Copy the code
The outermost layer children describes the layer information, which is an array type object. Document describes the global information of the PSD document. We need not pay too much attention to other information except width and height, but we need to focus on the layer information under children.
Psd.js also supports exporting JSON for individual layers.
// Access the layer node
const node = psd.tree().childrenAtPath('Version A/Matte') [0];
// or
const node =psd.tree().childrenAtPath(['Version A'.'Matte'[])0];
// Get layer information
node.export();
Copy the code
JSON structure field description
Layer information is recorded in the children array. The item of the array contains some common fields, such as Type, name, Visible, Top, bottom, left, right, etc. At the same time, there are some special fields according to different layer types, such as text layer. The text field records information about the text, such as font, size, color, alignment, and so on. Here are some important fields.
field | instructions |
---|---|
type | Layer type: Group indicates a group layer and layer indicates a common layer |
name | The layer name |
visible | Whether or not visible |
opacity | Transparent layer, 0~1 |
blendingMode | Layer mode |
width/height | Width and height of layer content |
top/bottom/left/top | Layer contents relative to the scope of the document |
mask | Mask path information |
image | Layer image information |
Get layer details
The JSON data exported using the export method is not all the layer information. To obtain specific information, such as path nodes, gradients, and strokes, you can use the GET method.
const node = psd.tree().childrenAtPath(['Version A'.'Matte'[])0];
// Get the layer path
const vectorMask = node.get('vectorMask');
vectorMask.parse();
const data = vectorMask.export();
const { paths = [] } = data;
paths.forEach(path= > {
// Variable path node
});
Copy the code
Parameters of the GET method correspond to the types of image information to be obtained. The supported types can be referred to here
Due to the limited nature of SVG, we don’t need to use all the information of all PSD layers, just focus on the following major ones.
Information types | instructions |
---|---|
solidColor | Fill color |
gradientFill | gradient |
typeTool | The text |
vectorMask | The path |
vectorStroke | stroke |
Generate an SVG document root label
SVG document, the outermost tag is SVG, document encoding, namespace, viewBox size, etc. The content of the SVG tag can be generated from the Document information exported from psD.js
const generateSvg = function(doc, content) {
const { width, height } = doc;
return (
` <? The XML version = "1.0" encoding = "utf-8"? > <! - generated by LST - > < SVG version = "1.1" XMLNS = "http://www.w3.org/2000/svg" XMLNS: xlink = "http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0The ${this.width} The ${this.height}"
enable-background="new 0 0 The ${this.width} The ${this.height}"
xml:space="preserve"
>
${content}
</svg>`);
}
Copy the code
Working with image layers
At the most basic level, convert the PSD layer to SVG as an image. SVG displays images using the image tag and supports both inline and inline.
<svg width="200" height="200"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<! -- link -->
<image xlink:href="https://mdn.mozillademos.org/files/6457/mdn_logo_only_color.png" height="200" width="200"/>
<! -- base64 -->
<image xlink:href="data:image/png; base64,......" height="200" width="200"/>
</svg>
Copy the code
Convert the layer to PNG using psd.js
const node = psd.tree().childrenAtPath(['layer 1'[])0];
const pngData = node.layer.image.toPng();
Copy the code
Now that you know how to display images in SVG and how to convert PSD layers into images using PSD.js, the next step is to embed the image data converted from PSD.js into an SVG document.
Base64 – based encoding inline
The essence of Base64 encoding inlining is to embed the Base64 encoding content in the form of a string in the href attribute of the image tag. Therefore, the key step is how to convert the image data into Base64 encoding string.
const toBase64 = function(image) {
return new Promise((resolve, reject) = > {
const chunks = [];
image.pack(); / / [1]
image.on('data', (chunk) => {
chunks.push(chunk); / / [2]
});
image.on('end', () => {
resolve(`data:image/png; base64,${Buffer.concat(chunks).toString('base64')}`); / / [3]
});
image.on('error', (err) => {
reject(err);
});
});
}
const embedToImage = function(href, width, height) {
return `<image xlink:href="${href}" width="${width}" height="${height}"/ > `
}
const node = psd.tree().childrenAtPath(['layer 1'[])0];
const pngData = node.layer.image.toPng();
toBase64(pngData).then(content= > {
const image = embedToImage(content, node.get('width'), node.get('height'));
// ...
});
Copy the code
Key steps:
- [1]
pack
The Stream method converts image data to a stream object - [2] Stream-based
data
Event to get stream data - [3]
Buffer
Convert the stream data to a Base64 string
Based on CDN address external chain
The inline approach has the advantage that the image data can be packaged into an SVG document, independent of the external environment, but it also has the disadvantage of making SVG larger. With the help of CDN, we can upload the picture to CDN first, and embed the picture into the SVG document by way of external chain after getting the CDN address. For the group, we can use the TPS platform to upload pictures to the CDN of the group.
const Tps = require('@ali/tps-node');
const path = require('path');
const fs = require('fs');
const toLink = function(image) {
return new Promise((resolve, reject) = > {
const name = `tmp_png_The ${new Date().getTime()}.png`;
const fileName = path.resolve('temp', name);
image.pack()
.pipe(fs.createWriteStream(fileName)) / / [1]
.on('finish', () => {
resolve(fileName);
}).on('error', (err) => {
reject(err);
});
}).then(fileName= > {
/ / [2]
const tps = new Tps({
accesstoken: 'xxxxxxxxxx'
});
return tps.upload(fileName, {
empId: 123456.nick: 'flower'.folder: 'ps-to-svg'
}).then(({ url }) = > {
fs.unlinkSync(fileName);
return url;
});
}).then((url) = > {
return url;
});
}
const embedToImage = function(href, width, height) {
return `<image xlink:href="${href}" width="${width}" height="${height}"/ > `
}
const node = psd.tree().childrenAtPath(['layer 1'[])0];
const pngData = node.layer.image.toPng();
toLink(pngData).then(link= > {
const image = embedToImage(link, node.get('width'), node.get('height'));
// ...
});
Copy the code
The key steps are:
- [1] Cache the file locally
- [2] Upload to CDN and get the address
Word processing
Another scenario that you’ll often encounter is working with text layers in a PSD. SVG supports using text and tSPAN to display text. See here (text) and here (tspan) for details.
Basic processing
Basically, the text content, font family name, size, color, alignment, and positioning are retrieved from the layer JSON object’s text field.
field | instructions |
---|---|
value | content |
font | Font properties |
font.name | Font family name |
font.sizes | The font size |
font.colors | The font color |
font.alignment | Alignment,center ,left orright |
top/bottom/left/right | Text area |
transform | Deformation matrix of text |
Note that the font size and color are arrays. This is because PHOTOSHOP supports editing font styles for a part of the text in a paragraph. As a result, there may be multiple font sizes or font colors in a paragraph of text, so the font size and color are recorded in arrays.
Let’s take a look at a simpler way to display the PSD font layer using SVG’s Text tag.
Once you have the text data of the layer, map the field values to the attributes of the SVG tag as follows
The PSD field | SVG Tag Attributes |
---|---|
value | Label content |
font.name | font-family |
font.sizes[0] | font-size |
font.colors[0] | fill |
font.alignment | text-anchor |
left | x |
top + font.sizes[0] | y |
transform | transform |
Thus, the logic of the transformation looks something like this:
const toHex = (n) = > {
return parseInt(n, 10).toString(16).padStart(2.'0');
};
const toHexColor = (c = []) = > {
if (typeof c === 'string') {
return c;
}
const [ r = 0, g = 0, b = 0 ] = c;
return ` #${toHex(r)}${toHex(g)}${toHex(b)}`;
};
const embedToText = function(text) {
const { value, font, left, top } = text;
const { name, sizes, leadings, colors } = font;
return `<text x="${left}" y="${top + leadings[0]}" style="font-family: ${name}; font-size: ${sizes[0]}px" fill="${toHexColor(colors[0])}">${value}</text>`;
}
const node = psd.tree().childrenAtPath([Words' 1 '[])0];
const text = embedToText(node.export().text);
console.log(text);
//
Copy the code
There are a few areas that I think need attention: font color, size, font family and text positioning.
The font color
As you can see from the code, the colors taken by the colors field are not hexadecimal RGB values, but an array of values, for example
const color = text.font.colors[0];
console.log(color); // [85, 96, 110, 255]
Copy the code
The array consists of four integers corresponding to the color channel Red, Green, Blue, and Alpha, so a conversion is required. In PSD, color values are basically stored separately by color channel. So what about Alpha? This can be done using the fill-opacity property of the SVG tag.
Calculate the size
It took me some time and effort to deal with the font size.
First of all, the PS font support for multiple units, the most basic of pt, and px, also in and so on, but by PSD, js to get the PSD information, can’t get the font size of units, therefore, we need to do a simplified, such as requiring designers use px, so when we get to the font size value shall be calculated as px.
Then, in some PSD documents, the font size of the layer is not an integer, but a small decimal, but in THE PSD, it is the normal size. Why is that?
I don’t know, maybe it’s different versions of PHOTOSHOP.
As shown in the image above, the text on the right is significantly larger than the text on the left, but it has an oddly small font size value of 2.44. Later, I found the understanding method in psD.js issues and joined here.
Therefore, the calculation of the font size of the text layer is implemented as follows:
const computeFontSize = function(node, defValue = 24) {
const { text } = node.export();
const typeTool = node.get('typeTool');
typeTool.parse();
const sizes = typeTool.sizes();
let size;
if (sizes && sizes[0]) {
if(text.transform.yy ! = =1) {
size = Math.round((sizes[0] * text.transform.yy) * 100) * 0.01;
} else { If yy is 1, the value of sizes[0] is the size of the font and is not computed
size = sizes[0]; }}else {
size = defValue; / / the default}}Copy the code
Font family
Getting font families is also a headache for me. Even though the text layer may be using only one font, some versions of the exported PSD somehow recorded a different font from the font. After repeated CAI test (KENG), finally found a more reliable solution.
const resolveFontFamily = function(node) {
const { text } = node.export();
const typeTool = node.get('typeTool');
const fontFamily = typeTool.engineData.ResourceDict.FontSet
.filter(f= >! f.Synthetic) .map(f= > f.Name);
return fontFamily[0]? fontFamily[0] : text.font.name;
}
Copy the code
In another place, the Font Family recorded by this layer in the PSD document is not the Font Family we usually see, but PostScript Name. As for how to obtain the Font information, we recommend a website, opentype.js, after uploading the Font, you can see the detailed information of the Font and the symbol set.
Opentype. js is also a reliable and powerful font package parsing tool library, good to test, recommended wall crack.
Reprinted to wechat official account: Fang Stool Ya Ji
positioning
SVG’s Text tag provides two ways to position text layers.
x
andy
attributetransform
attribute
The PSD data exported by PSD. js can be calculated by the corresponding fields.
- x = text.left;
- y = text.top + leading;
- transform = text.transform;
After (you), (Shi), (CAI), (Keng), I recommend using Transform for text positioning. The implementation is as follows:
const resolveTransformAttrValue = function(text) {
const { transform } = text;
return `matrix(1 0 0 1 ${transform.tx} ${transform.ty}) `;
}
Copy the code
Why? Because Ali mom’s Begons-Creative Center SVG template supports the transform attribute to position text.