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]packThe Stream method converts image data to a stream object
  • [2] Stream-baseddataEvent to get stream data
  • [3]BufferConvert 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,leftorright
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.

  • xandyattribute
  • transformattribute

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.