Agamnentzar / ag-psd

Javascript library for reading and writing PSD files
Other
489 stars 66 forks source link

Cariage return character is wrongly written #175

Open sasensi opened 3 months ago

sasensi commented 3 months ago

Hello and once again, thank you for the great work with this library which helps me so much ! šŸ˜Š

Working with the following file: input.zip

That looks like this in Photoshop: image Notice the text has a paragraph spacing set so the line 3 is separated from the line 2 by the expected gap but this gap is not between the line 1 and 2 because they are separated by a carriage return character and not a line break character.

So if we take this file, read it and write it using the library, like this:

import * as fs from 'fs';
import { readPsd, writePsdBuffer } from 'ag-psd';
import 'ag-psd/initialize-canvas.js';

const INPUT_FILE_PATH = 'input.psd';
const OUTPUT_FILE_PATH = 'output.psd';

const inBuffer = fs.readFileSync(INPUT_FILE_PATH);
const psd = readPsd(inBuffer);

const outBuffer = writePsdBuffer(psd, { invalidateTextLayers: true });
fs.writeFileSync(OUTPUT_FILE_PATH, outBuffer);

It then looks like this: image For some reason, the carriage return character is not preserved.


I tried to play with it a bit using this code, to try all possible line breaks characters that I knew but none seems to produce the expected result:

import * as fs from 'fs';
import { readPsd, writePsdBuffer } from 'ag-psd';
import 'ag-psd/initialize-canvas.js';

const INPUT_FILE_PATH = 'input.psd';
const OUTPUT_FILE_PATH = 'output.psd';
const LINE_BREAK_CHARACTER_INDEX = 1;

const inBuffer = fs.readFileSync(INPUT_FILE_PATH);
const psd = readPsd(inBuffer);

const shapeText = psd.children['0'].children['0'].text;
const carriageReturnCharacter = shapeText.text[LINE_BREAK_CHARACTER_INDEX];
const carriageReturnCharacterCode = shapeText.text.charCodeAt(LINE_BREAK_CHARACTER_INDEX);
const charactersToTest = [
  '\n',
  '\r',
  '\u0003',
  '\u8232',
  String.fromCharCode(3),
  String.fromCharCode(carriageReturnCharacterCode),
];
shapeText.text = `line${charactersToTest.join('line')}line`;

const outBuffer = writePsdBuffer(psd, { invalidateTextLayers: true });
fs.writeFileSync(OUTPUT_FILE_PATH, outBuffer);

And here's the visual result: image


Hoping you can help with this šŸ¤ž

Agamnentzar commented 3 months ago

It seems that Photoshop puts \u0003 symbol for new line, but also indicates that the line is broke in that spot in psd.engineData field, unfortunately engineData field is not yet implemented in ag-psd, there's no documentation for that object and it's compressed in a weird way that makes it really hard to decipher. invalidateTextLayers: true clears engineData field so that's why the information about that line break is lost.

sasensi commented 3 months ago

@Agamnentzar, thank you for your quick response, ok, I see. So is implementing engineData support in ag-psd something that you plan to do or not really ? Also, is there something I can help with in order to support this use case ?

Agamnentzar commented 3 months ago

The problem is that the data in that section looks like this:

image

And I have no way of figuring out which numbers map to what property names, I managed to figure out some of the fields by just changing fields in Photoshop and seeing which one of these change values, but that doesn't work for all of the fields, so I'm stuck at this point until I have some way to find what these fields are. (My guess for why it is this way is that they wanted to compress the data so they replaced property names with numbers, and Photoshop has some internal dictionary that can translate numbers to names)

Agamnentzar commented 3 months ago

Did you try if it works without invalidateTextLayers: true ?

sasensi commented 3 months ago

@Agamnentzar, I see quite clearly the problem because I once built a rough .ai writer (less advanced than yours though) and had to deal with this kind of data also šŸ˜„ And the way I went was exactly what you describe: guessing each piece of data meaning by trial and error. And since it's not documented, I guess that it's the only way unfortunately. I also tried without invalidateTextLayers: true but it doesn't work neither.