Agamnentzar / ag-psd

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

text layer is incorrect. #164

Open aticzz opened 7 months ago

aticzz commented 7 months ago

i want to apply simple linear gradient color on text layer. i read a issue #149 also i read Updating text layers information. 301173543-70358427-a408-4489-870f-b7c732e794b1 but how i can remove this error.

i try everything but i failed

my code `

const psdlib = window.agPsd; const readPsd = psdlib.readPsd; const writePsd = psdlib.writePsd; const writePsdUint8Array = psdlib.writePsdUint8Array;

$("#canvas-proccess-area").html ('');
$("#image-proccess-area").html ('');

const is_canvas_element = document.getElementById (window.currentCanvasId);
await $("#canvas-proccess-area").append (`
    <canvas id="mainmaincanvasofpsd" width="${canvas.options.width}" height="${canvas.options.height}"></canvas>
`);
const mainimg = await is_canvas_element.toDataURL ();
const _canvas_element = document.getElementById ("mainmaincanvasofpsd");
const mainctx = _canvas_element.getContext ("2d");
const nimf = new Image ();
nimf.src = await mainimg;
console.log (mainimg);
await mainctx.drawImage (nimf, 0, 0, canvas.options.width, canvas.options.height);
// const _canvas_element = document.getElementById (window.currentCanvasId);

const originZoomParam = canvas.getZoom();
const originHeightParam = canvas.getHeight();
const originWidthParam = canvas.getWidth();
canvas.setZoom(canvas.options.height * canvas.getZoom() / canvas.getHeight());
canvas.setHeight(canvas.options.height);
canvas.setWidth(canvas.options.width);
canvas.requestRenderAll();
canvas.renderAll();
var options = {};
options.height = canvas.options.height; 
options.width = canvas.options.width;
let _childrens = [];
const objects = canvas._objects;
for (const key in objects) {
    const obj =await objects [key];
    console.log (key, obj);
    // await canvas.add (obj);
    // await canvas.renderAll ();
    const objectSource = await (obj.clip_target && obj.type == "image") ? (obj.src) ? obj.src : obj._element.src : obj.toDataURL ();
    const sub_canvas_id = await `sub-canvas-init-${key}`;
    const sub_image_id = await `sub-image-init-${key}`;
    await $("#image-proccess-area").append (`
        <img id="${sub_image_id}" src="${objectSource}" width="${obj.width}" height="${obj.height}">
    `);
    await $("#canvas-proccess-area").append (`
        <canvas id="${sub_canvas_id}" width="${obj.width}" height="${obj.height}"></canvas>
    `);
    const sub_canvas_element =await document.getElementById (sub_canvas_id);
    const sub_canvas_element_ctx =await sub_canvas_element.getContext("2d");
    const dreaw_img =await document.getElementById (sub_image_id);

    sub_canvas_element.width = obj.getScaledWidth ();
        sub_canvas_element.height = obj.getScaledHeight ();
        await sub_canvas_element_ctx.drawImage (dreaw_img, 0, 0, obj.getScaledWidth (), obj.getScaledHeight ());

    let _childrenObject = await {};
    if (obj.text) {
        console.log (obj.text);
        let text = "";
        for (const txtline of obj.textLines) {
            text += txtline + "\n";
        }
        let fill, gradientOverlay;
        if (typeof obj.fill == "object") {
            let ncg =[], ocg=[], dckey = 0;
            for (const ckey in obj.fill.colorStops) {
                const cobj = obj.fill.colorStops [ckey];
                const [r,g,b] = hexToRgb(cobj);
                ncg.push({ location: dckey / 10, midpoint: 0.5, color: {r: r, g: g, b: b} });
                ocg.push({ location: dckey / 10, midpoint: 0.5, opacity: 1 });
                dckey++;
            }
            gradientOverlay = [
                {
                    "align": true,
                    "angle": 90,
                    "blendMode": "normal",
                    "gradient": {
                        colorStops: ncg,
                        "name": "Foreground to Background",
                        opacityStops: ocg,
                        smoothness: 1,
                        type:"solid"
                    },
                    "enabled": true,
                    "offset": {x: 0, y: 0},
                    "opacity": 1,
                    "reverse": false,
                    "scale": 1, 
                    "type": "linear",
                }
            ];
            fill = null;
        } else {
            const [r,g,b] = hexToRgb (obj.fill);
            fill = {r: r, g: g, b: b};
            gradientOverlay = [];
        }
        _childrenObject = await {
            "blendClippendElements": true,
            "blendInteriorElements": false,
            "top": obj.top,
            "left": obj.left,
            "bottom": obj.bottom,
            "right": obj.right,
            "blendMode": (obj.globalCompositeOperation == "source-over") ? "normal": obj.globalCompositeOperation,
            "opacity": obj.opacity,
            "transparencyProtected": false,
            "hidden": false,
            "clipping": false,
            "protected": {"composite":false, "position":false, "transparency":false},
            "referencePoint": {"x":0,"y":0},
            name:  `Layer - ${key}`,
            text: {text: text},
            text: {
                antiAlias: "sharp", bottom: 0, gridding: "none", left: 0,orientation: "horizontal",
                text: text, // text you want to draw
                transform: [obj.scaleX, 0, 0, obj.scaleY, obj.left, obj.top], // move text 50px horizontally and 50px vertically
                style: {
                    // font: { name: obj.fontFamily }, // need to provide full name here
                    fontSize: obj.fontSize,
                    fillColor: fill, // opaque red,
                },
                paragraphStyle: {autoHyphenate: true}
            },
            "layerColor": "none",
            knockout: false, 
            imageData: undefined,
            canvas: sub_canvas_element
        };
        if (!$.isEmptyObject(obj.shadow) || typeof obj.fill == "object" || obj.stroke) {
            _childrenObject.effectsOpen = true;
            _childrenObject.effects = {
                "gradientOverlay": gradientOverlay,
                "scale": 1
            };
        }
    } else {
        var clip = (obj.clip_target) ? true : false;
        console.log (clip);
        _childrenObject = await {
            "blendClippendElements": true,
            "blendInteriorElements": false,
            "top": obj.top,
            "left": obj.left,
            "bottom": obj.bottom,
            "right": obj.right,
            "height": obj.getScaledHeight (),
            "width": obj.getScaledWidth (),
            "effectsOpen": true,
            "blendMode": (obj.globalCompositeOperation == "source-over") ? "normal": obj.globalCompositeOperation,
            "opacity": obj.opacity,
            "transparencyProtected": false,
            "referencePoint": {"x":0,"y":0},
            "protected": {"composite":false, "position":false, "transparency":false},
            "hidden": false,
            "clipping": clip,
            "name": `Layer - ${obj.id || key}`,
            "layerColor": "none",
            knockout: false,
            imageData: undefined,
            canvas: sub_canvas_element
        };
    }
    await _childrens.push (_childrenObject);
}
// console.log (_childrens); throw ''
await canvas.setHeight (options.height);
await canvas.setWidth (options.width);
await canvas.renderAll ();

let imagedata = await _canvas_element.toDataURL ('image/jpeg', 0.5);
imagedata = imagedata.replaceAll ("data:image/jpeg;base64,", "");
const psd_layers = await {
    "width":options.width,
    "height":options.height,
    "channels":3,
    "bitsPerChannel":8,
    "colorMode":3,
    "hidden": false,
    "transparencyProtected": false,
    "clipping": false,
    "children": _childrens,
    engineData: imagedata,
    canvas: _canvas_element
};
const buffer = await writePsd (psd_layers, {width: options.width, height: options.height, generateThumbnail: true, trimImageData: true, invalidateTextLayers: true, logMissingFeatures: true});
const tblob = await new Blob ([buffer], { type: "application/octet-stream" });
const url = await URL.createObjectURL (tblob);
await console.log (url);
await $("body").append (`<a href="#" id="downloadpsd" download="some-file.psd"></a>`);
await $("#downloadpsd").attr ('href', url);
setTimeout (async function () {
    await $("#downloadpsd") [0].click ();
}, 1000);

`

Agamnentzar commented 7 months ago

As explained here: https://github.com/Agamnentzar/ag-psd?tab=readme-ov-file#updating-text-layers passing invalidateTextLayers: true will result in the error message when opening PSD file in Photoshop. You can remove that option to prevent the warning, but in that case you have to redraw the text layer canvas yourself. Otherwise Photoshop will show the old layer content when you open the file.

aticzz commented 7 months ago

Thank you so much for replying ☺️ How to redraw the text layer canvas. If I redraw that layer, will that layer remain an image layer or a text layer? I want text layer only because it should be edited in Photoshop also.

Agamnentzar commented 7 months ago

For compatibility reasons PSD files keep bitmap versions of all the layers, even text and vector layers, and when you open the file in Photoshop it uses those bitmaps instead of redrawing them, that's why even for vector and text layers when you change anything you also need to redraw the bitmap version. It will remain text/vector layer.

To replace it you just need to replace layer.canvas with your own canvas with updated content. Unfortunately to accurately redraw text layer you have to replicate behavior of Photoshop text rendering code. Depending on complexity of text properties you want to use it might be easier or harder to do. HTML canvas has basic text drawing functions, so maybe that would be enough for your use case.