Skip to content

Editable Text Layers#205

Open
ogkalu2 wants to merge 46 commits intoEmilDohne:masterfrom
ogkalu2:master
Open

Editable Text Layers#205
ogkalu2 wants to merge 46 commits intoEmilDohne:masterfrom
ogkalu2:master

Conversation

@ogkalu2
Copy link
Copy Markdown

@ogkalu2 ogkalu2 commented Feb 21, 2026

Hi. So this is it. It's a really big pull. The text surface is huge, but it's almost entirely new files. Not much editing of old files and nothing breaks. The commits are staged nicely, so you could go through those. Would you prefer the PR split ?

Summary

This PR adds first-class editable text layer support to PhotoshopAPI in both C++ and Python bindings, including creation, high-level formatting, low-level run control, and layout-direction features.

What’s included

  • Added TextLayer_*bit creation/editing APIs.
    Supports editable text layer construction and updates in both bindings.

  • Added high-level formatting helpers.
    Character styling: style_all, style_text, style_range
    Paragraph styling: paragraph_all, paragraph_text, paragraph_range

  • Added run-level APIs for precise control.
    Read structure: style_run_lengths, paragraph_run_lengths
    Edit runs: style_run(index), paragraph_run(index)
    Direct setters: set_style_run_*, set_paragraph_run_*

  • Added character direction controls (LTR/RTL).
    Includes run-level character-direction reads.

  • Added orientation switching.
    Horizontal and vertical text are both supported.

  • Added text frame conversion APIs.
    Box text <-> point text conversion; creation defaults to box text.

  • Expanded read APIs for layer and run attributes.
    Includes font, size, fill/stroke, underline, justification, orientation, direction and more.

  • Updated README documentation and examples.
    Added complete C++/Python usage and UTF-16 code-unit notes for range APIs.

Important behavior when editing existing PSDs

If text is modified on a PSD loaded from disk, call invalidate_text_cache() on LayeredFile before writing. This ensures Photoshop refreshes text internals on open.

Compatibility

Additive API update; existing workflows are not intended to break.

Validation

  • Verified text layer creation and editing in C++ and Python.
  • Verified run-level style and paragraph modifications.
  • Verified persistence of orientation, frame type, and character direction after export/import.
  • Verified text cache invalidation behavior for edited PSD text visibility in Photoshop.

@EmilDohne
Copy link
Copy Markdown
Owner

Hey @ogkalu2 this is some truly fantastic work. I'll take some time to look over it and test it out but thanks a lot for contributing. One MR is perfectly fine, it is one (big) feature after all

@ogkalu2
Copy link
Copy Markdown
Author

ogkalu2 commented Feb 21, 2026

@EmilDohne Okay Thanks. Forgot to update some test assumptions for the text layer in python. The builds should hopefully pass now.

@ogkalu2
Copy link
Copy Markdown
Author

ogkalu2 commented Feb 22, 2026

Ran the workflows on my fork and addressed all the issues

Copy link
Copy Markdown
Owner

@EmilDohne EmilDohne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taking a first stab at this, I'll keep coming back at this a couple times as I won't be able to look through it all in a single go. Overall it looks very good and clean and I can't imagine how long it took you to do this.

As a semi-related question. How hard would you gauge it to be to add text rendering capabilities? Allowing users to call e.g. TextLayer<uint8_t>::get_image_data which would render the text using OpenType/TrueType (warps and other things could come later). It doesn't have to happen as part of this MR, or even by you, I'd just be curious about your estimate

@ogkalu2
Copy link
Copy Markdown
Author

ogkalu2 commented Feb 23, 2026

Taking a first stab at this, I'll keep coming back at this a couple times as I won't be able to look through it all in a single go. Overall it looks very good and clean and I can't imagine how long it took you to do this.

No worries. take your time. It's a lot. Haha yeah. I've been looking to support psd exports (and imports) for an app of mine and i really wanted a way to do it via python.

As a semi-related question. How hard would you gauge it to be to add text rendering capabilities? Allowing users to call e.g. TextLayer<uint8_t>::get_image_data which would render the text using OpenType/TrueType (warps and other things could come later). It doesn't have to happen as part of this MR, or even by you, I'd just be curious about your estimate

It depends on how far you want to take it. Structurally, it's not a big deal and you wouldn't be making many changes to existing code, and this PR would cover the data layer for the most part, i.e you can get styling information present in the text.

But to be frank, full coverage would essentially be impossible for you to handle on your own and you'll probably never get around to the upper bound stuff. The myriad of warp and text effects that are possible is a project on its own. And you'll never be able to guarantee it looks exactly like photoshop. Even for basic stuff, it'll probably look a little different because font size, line spacing probably won't map/render exactly the same. I know it doesn't with QT.

To start, your choices for rendering are to either:

  • Abstract everything to something that does basic text rendering. For example, if you're familiar with PIL, you could grab the styling info from the text layer and just translate most of it to equivalent commands or parameters. If you go this route, the implementation is easy, but you'll also be crippled by the limits of your renderer. For example PIL can do multiline stuff, but you won't be able to style particular character ranges within the block unless you use it to draw one character at a time, at which point some of the ease of using PIL for this use-case is gone. It also doesn't support multiline vertical text. Of course there may be better libraries out there for this specific use-case.

  • Handle all the rendering yourself with stuff like HarfBuzz (Text Shaper), FreeType (Text to bitmap), FriBidi(rtl scripts) etc. Lot more work than above to even get to the level of say PIL's renderer, but probably necessary if you really want to nail as much coverage/be as granular as you can (though again I'm unsure of the state of libraries that specialize in higher level text rendering). Also, there's probably some open source projects that have implemented similar whose code you could take a look at. It's a common enough problem.

But yeah, text rendering is its own problem that could fairly be called out of scope for this repo.

@ogkalu2 ogkalu2 requested a review from EmilDohne February 23, 2026 02:26
@ogkalu2
Copy link
Copy Markdown
Author

ogkalu2 commented Feb 23, 2026

The 1 fail seems to be build environment issues rather than code

@EmilDohne
Copy link
Copy Markdown
Owner

Sorry that I havent looked over it again yet, wanted to test it locally this morning but visual studio had different ideas so now I'll test your changes in CLion 🙃

@EmilDohne
Copy link
Copy Markdown
Owner

But to be frank, full coverage would essentially be impossible for you to handle on your own and you'll probably never get around to the upper bound stuff. The myriad of warp and text effects that are possible is a project on its own. And you'll never be able to guarantee it looks exactly like photoshop. Even for basic stuff, it'll probably look a little different because font size, line spacing probably won't map/render exactly the same. I know it doesn't with QT.

I feared something like this but one can dream I suppose. Text rendering seems simple on the surface but I think this MR shows pretty well it's not trivial at all :(

As for my MR feedback:

I had another deeper look into it and I quite like the public API. There is however still one thing I have concerns with which is how the internal data is stored and handled.

What the PhotoshopAPI does most of the time so far is it goes from the raw photoshop data into a more usable representation and then back to the photoshop representation like a tagged block, descriptor etc.

The code here does more or less the same but it does it every time you call/set something. The main thing I have a concern with is that it's writing temporary files to dump/read descriptors on the fly and uses the layers' m_UnparsedBlocks to do so.
I don't mind storing the tagged block directly on the class but I think we'd be better off storing e.g. a TypeToolTaggedBlock on the TextLayer<T> somewhere and then modifying its descriptors in-place.

Feel free to modify the Descriptor API if you feel like it does not support this yet, but I'd be a lot more comfortable with keeping it all in memory

@ogkalu2
Copy link
Copy Markdown
Author

ogkalu2 commented Feb 28, 2026

But to be frank, full coverage would essentially be impossible for you to handle on your own and you'll probably never get around to the upper bound stuff. The myriad of warp and text effects that are possible is a project on its own. And you'll never be able to guarantee it looks exactly like photoshop. Even for basic stuff, it'll probably look a little different because font size, line spacing probably won't map/render exactly the same. I know it doesn't with QT.

I feared something like this but one can dream I suppose. Text rendering seems simple on the surface but I think this MR shows pretty well it's not trivial at all :(

As for my MR feedback:

I had another deeper look into it and I quite like the public API. There is however still one thing I have concerns with which is how the internal data is stored and handled.

What the PhotoshopAPI does most of the time so far is it goes from the raw photoshop data into a more usable representation and then back to the photoshop representation like a tagged block, descriptor etc.

The code here does more or less the same but it does it every time you call/set something. The main thing I have a concern with is that it's writing temporary files to dump/read descriptors on the fly and uses the layers' m_UnparsedBlocks to do so. I don't mind storing the tagged block directly on the class but I think we'd be better off storing e.g. a TypeToolTaggedBlock on the TextLayer<T> somewhere and then modifying its descriptors in-place.

Feel free to modify the Descriptor API if you feel like it does not support this yet, but I'd be a lot more comfortable with keeping it all in memory

I've made the changes

@ogkalu2 ogkalu2 requested a review from EmilDohne February 28, 2026 18:46
Copy link
Copy Markdown
Owner

@EmilDohne EmilDohne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall I think we're almost there. I have some last comments and then I will do one final full pass of the code and API and then we're good for merging :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants