Empty channels made PS report corrupted layers. Restore raster channels
(keeps layers valid). Template paragraph is justify (spreads short text
across the box, causing huge letter spacing after conversion) - set
Justification to center.
Photoshop warns the text layer needs conversion / editing changes layout
when the run length array (character-style/paragraph length per run) does
not match the text. We changed Editor.Text but left RunLengthArray at the
template's 16, so PS flagged every TypeLayer. Now set both run length
arrays to len(text).
PIL draws full-width punctuation offset inside the advance box (e.g.
left bracket '(' ink sits at x+42..x+61 while its advance is only 0.45em),
so the next glyph overlapped it and a trailing bracket could clip. Shift
the draw x by the ink offset for narrow punctuation and add right padding
so line ends never clip. Verified: bracket gap restored, line end intact.
The app only moves group children when the group bbox is disjoint from
its content (group moved away, e.g. groups 169/181). When the bbox
contains the content (e.g. group_64 with a taller frame), children stay
at their ppl coordinates. Fixes group_64 being shifted up 1225px.
- Group children render offset by the group bbox movement (the app moves
groups without updating child layer bboxes). Apply the group offset
using only the group's OWN layers (exclude nested groups' layers from
the inner bbox, which wrongly shifted parent groups like group_167).
- TypeLayer boundingBox is relative to the transform origin; set it to
(0,0)-(w,h) so text renders at the ppl boundingRect (verified:
display pos = transform x boundingBox).
Fixes: overlapping groups 178/181, '最佳记录' shifted up, shape_168
dropped down, overall diff vs app render 11.84 -> 8.84.
README documents both converters, features (raster/TypeLayer/group/bold),
installation, usage, limitations and calibration notes. .gitignore excludes
Python caches, sample assets and large media files while keeping the TySh
template.
Groups ([group] sections + layer groupId) become PSD groups: a bounding
divider (lsct kind 3) opens the group at its bottom, an open-folder
divider (kind 1) closes it at the top (giving the group its name), per
psd_tools' parsing order. Group divider layers get empty channels.
Verified: psd_tools reads the group with its 11 child layers.
- TypeLayer mode: StyleSheetData.FauxBold=true for bold text layers
- Rasterized mode: render with stroke width for faux bold
Verified: 50/54 text layers get FauxBold, stroke rendering thickens glyphs.
Reuses a reference TySh block from a PS-generated PSD as a template and
mutates its engine data (Text/FontSize/FillColor/FontSet/transform) per
ppl text layer. Rasterized pixels are kept as channel data so the layer
always displays, and Photoshop treats it as an editable text layer.
Font PostScript names are read from the font files via fontTools.
Verified: 54 TypeLayers, psd_tools reads correct text/engine data.
PS reads layer blending ranges to decide if a layer participates in
compositing. psd_tools wrote all-zero ranges (composite 0..0), which tells
Photoshop the layer has an empty blend range -> the canvas renders empty
even though layer pixels are correct (Ctrl+C/V shows content, export PNG is
transparent, save-as by PS keeps the bad ranges). Now writes full-range
(0..65535) composite + channel ranges like PS-generated files, verified
against the reference PSD.
The unicode layer name block must be [char_count(4B)][utf-16-be chars].
We wrote raw utf-16 without the count, so Photoshop read the first
characters as the length, shifted the entire extra-data parse, and every
layer's pixels came out empty (all layers transparent). Fixed: name now
reads back correctly and 167/167 layers match the rendered originals
(mean diff 0.000).
Photoshop reads a layer's tagged blocks until it sees a non-8BIM
signature. With only 'luni' the next bytes are the channel data, which PS
mis-parses and the layer pixels come out empty. PS writes the standard
block set, so we now mirror it (verified: blocks list now matches the
reference PSD).
PS-generated PSDs always set flags bit 3 (0x08) to signal Photoshop 5.0+
format where the layer extra data contains tagged blocks. Our layers had
flags=0x00, so Photoshop treated them as pre-5.0 records and failed to
read the extra data / layer pixels, showing every layer as transparent
(only ape_2 happened to render). Verified against the reference PSD.
Per Adobe PSD spec, the Image Data Section has ONE 2-byte compression
field followed by all planes in planar order (RRR GGG BBB). The previous
code wrote a compression marker per channel (4 extra bytes) and used RLE,
which third-party viewers parse incorrectly (stretching) and Photoshop
rejected. Now writes RAW data with a single compression marker, verified
by pytoshop (decompresses to 3x4882x9764).
- Layer info section was missing its 4-byte length field, causing all
subsequent offsets to shift and Photoshop to fail with 'unexpected end
of file'
- Layer channel data was ordered [R,G,B,A] with ids [-1,0,1,2], shifting
every channel by one (alpha held red, red held green, ...) - fixed to
[A,R,G,B] so id -1 is the alpha mask
- Verified: psd_tools opens the PSD, per-layer pixels match the rendered
originals with 0.000 mean difference