Technical Notes =============== This section contains technical implementation notes about PSD to SVG conversion strategies. Clipping Conversion ------------------- Clipping involves non-trivial conversion. The basic approach is to use ```` or ```` SVG elements, but Photoshop can have arbitrarily complex rendering procedures due to the presence of vector drawings or filter effects. Basic Idea ~~~~~~~~~~ Consider the following case:: [1] ShapeLayer('Star 1' size=30x29) [2] +TypeLayer('B' size=22x23 clip) This can be translated to the following SVG structure: .. code-block:: xml B If the clipping base is not a shape layer, we can instead use a mask:: [1] PixelLayer('Star 1' size=30x29) [2] +TypeLayer('B' size=22x23 clip) .. code-block:: xml B This structure is the most basic form of translation. Styling SVG Use Element ~~~~~~~~~~~~~~~~~~~~~~~~ In SVG, the ```` element does not allow overriding the ``fill`` or ``stroke`` attributes of the referenced element. Therefore, in the following example, ``fill="transparent"`` is ignored. .. code-block:: xml To correctly apply drawing attributes, we instead need to do the following: prepare a plain ```` element (like a ````), then reference this element in the ```` element with the desired attributes. .. code-block:: xml Stroke After Clipping Layers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In Photoshop, stroke (both as a shape attribute and as a filter effect) is applied after all clipping layers are rendered. Consider the following example, where the first shape layer has both fill and stroke attributes enabled:: [1] ShapeLayer('Star 1' size=30x29) [2] +TypeLayer('B' size=22x23 clip) [3] +TypeLayer('C' size=22x23 clip) This translates to the following: .. code-block:: xml B C Or, we can group clipping layers: .. code-block:: xml B C There are filter effects that happen before (e.g., filling) or after (e.g., stroking) the rendering of the clipped layers. Stroke Effect on Raster Layers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Photoshop can apply stroke effects to any layer, whereas SVG allows stroke only on shape or text elements. We have to emulate the stroke effect using filter effects. The following example emulates a stroke effect on a pixel layer: .. code-block:: xml B The SVG filter configuration depends on the stroke properties (alignment and stroke width). Regular Masks and Clipping Masks ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When a clipped layer has a mask, we have to group the clipping layers and apply the mask to the group. .. code-block:: xml B Clipping Structure Summary ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Depending on the target layer kind, we have the following structure for clipping layers. **Drawing target:** .. code-block:: xml **Raster target:** .. code-block:: xml Conversion Logic ~~~~~~~~~~~~~~~~ The requirement to implement the above conversion is the following two logic: 1. Beginning of the clip section (mask or clipPath creation, fill, group) 2. End of the clip section (stroke) This would be translated to the following conversion loop structure: .. code-block:: python for layer in psdimage: if layer.has_clip_layers(): self.add_clip_target_begin(layer) for clip_layer in layer.clip_layers: self.add_layer(clip_layer) self.add_clip_target_end(layer) elif layer.clipping_layer: pass # Skip clipping layers else: self.add_layer(layer) Overlay Filter Effects ----------------------- Overlay effects can be thought of as adding another element on top of the original. .. code-block:: xml For simple overlays, this is equivalent to using a mask with fill. .. code-block:: xml Stroke Overlays ~~~~~~~~~~~~~~~ Things get complicated when there is a stroke, because the overlay must be applied before the stroke. .. code-block:: xml The above is equivalent to applying fill operations on geometric elements (shapes and text). .. code-block:: xml Interpreting overlay effects as clipped fill layers would likely be easier to implement. .. code-block:: xml Generic Rendering Procedure ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In any case, the generic rendering procedure for a single layer could be the following: 1. Shape definition; e.g., ````, ````, ```` 2. Fill 3. Clipping or filter effects for color 4. Stroke Effects on Strokes ~~~~~~~~~~~~~~~~~~ When a shape layer does not have fill, effects do not render correctly if we naively split the shape definition, because stroke becomes the only shape. The following result will be incorrect for the filter, because the ```` by default will have black fill. .. code-block:: xml This should be avoided by explicitly stating the fill is transparent in the definition. .. code-block:: xml Shape Operations ---------------- Photoshop supports boolean operations for path objects. SVG does not natively support path operations, but it is possible to reproduce path operations using ````. The basics are: - **AND** operation is a chain of ```` - **OR** operation simply places multiple shape elements inside the same mask container - **NOT** operation is a black fill Unfortunately, strokes do not render correctly. Using ```` might render strokes, but ```` does not support NOT operator unless ```` element is used with ``evenodd`` rule. Union (OR) ~~~~~~~~~~ A naive approach is to render multiple shapes in order. .. code-block:: xml If we separate the shape operations from painting, we can use ````. .. code-block:: xml Clip-path equivalent is the following: .. code-block:: xml The general conversion process would be: 1. Create a ```` container 2. Append shapes to the current ```` container 3. Apply the mask to the final target (````) Subtraction (NOT OR) ~~~~~~~~~~~~~~~~~~~~ Subtraction operation is equivalent to specifying ``fill`` to black (``#000000``). .. code-block:: xml It is not possible to directly use ```` for subtraction. Intersection (AND) ~~~~~~~~~~~~~~~~~~ Intersection is a chain of masks. .. code-block:: xml The general conversion process would be: 1. Create a ```` container 2. For each shape, create a new ```` container with the content referencing the previous ``mask`` container XOR ~~~ XOR is a combination: ``(A OR B) AND NOT (A AND B)``. Alternative formulation of XOR is available: ``(A AND NOT B) OR (NOT A AND B)``. Font Resolution Architecture ============================= This section explains psd2svg's font resolution system and its deferred resolution strategy. Background ---------- PSD files store font references as **PostScript names** (e.g., ``ArialMT``, ``HelveticaNeue-Bold``). For SVG output, we need to convert these to **CSS font families** (e.g., ``Arial``, ``Helvetica Neue``). When ``embed_fonts=False``: Only family names are needed for the SVG ``font-family`` attribute. When ``embed_fonts=True``: Must locate actual system font files for embedding. Deferred Resolution Strategy ----------------------------- psd2svg uses a **two-phase approach** to optimize performance: **Phase 1 - PSD Conversion** (``from_psd()``): - PostScript names stored directly in SVG ``font-family`` attributes - No font resolution performed (fast conversion) - Original PSD intent preserved **Phase 2 - Output** (``save()``, ``tostring()``, ``rasterize()``): - Extract PostScript names from SVG tree - Resolve to CSS family names based on context: - ``embed_fonts=False``: Use static mapping (fast, no system queries) - ``embed_fonts=True``: Use platform-specific resolution (locates font files) - Update ``font-family`` attributes with resolved names - Embed fonts if requested Resolution Methods ------------------ psd2svg provides two distinct resolution methods optimized for different use cases. FontInfo.lookup_static() ~~~~~~~~~~~~~~~~~~~~~~~~~ For font metadata lookup only: - Resolution chain: Custom mapping → Static mapping (~4,950 fonts) → None - Static mapping includes: - 539 default fonts (Arial, Times, Adobe fonts, etc.) - 370 Hiragino variants (W0-W9 pattern, generated dynamically) - 4,042 Morisawa fonts (Japanese typography) - Returns family name, style, and weight (no file path) - NO platform-specific queries (no fontconfig/Windows registry) - Preserves original PostScript names in SVG when fonts not found - Used when ``embed_fonts=False`` (prevents unwanted font substitution) - Fast, cross-platform, no system dependencies - JSON-based lazy loading (loaded on first access) FontInfo.resolve() ~~~~~~~~~~~~~~~~~~ For font file access: - Resolution chain: Custom mapping → Platform-specific resolution - Returns complete font metadata including file path - **Linux/macOS**: fontconfig with CharSet API (fontconfig-py >= 0.4.0) - **Windows**: Windows registry + fontTools cmap parsing - Used when ``embed_fonts=True`` or for rasterization - May substitute fonts based on system availability FontInfo.find() ~~~~~~~~~~~~~~~ Backward-compatible wrapper: - Delegates to ``lookup_static()`` by default (safe behavior) - Use ``disable_static_mapping=True`` to delegate to ``resolve()`` - Maintained for backward compatibility; prefer explicit methods in new code Custom font mapping: Users can provide custom mappings via ``font_mapping`` parameter (always checked first, regardless of method used). See CLI tool: ``python -m psd2svg.tools.generate_font_mapping`` Charset-Based Font Matching ---------------------------- When resolving fonts, psd2svg analyzes actual text characters for better matching: 1. Extract Unicode characters from text layers 2. Convert to codepoints (e.g., 'あ' → 0x3042) 3. Query system for fonts with best glyph coverage 4. Fallback to name-only matching on errors **Benefits**: Better selection for multilingual text (CJK, Arabic, etc.), minimal overhead (~10-50ms) Font Embedding Implementation ------------------------------ Font Subsetting ~~~~~~~~~~~~~~~ When ``embed_fonts=True``: - Character extraction reused for both charset matching and subsetting - Typically 90%+ size reduction with WOFF2 format Embedding Modes ~~~~~~~~~~~~~~~ - ``tostring()``/``save()``: Data URIs (portable files) - ``rasterize()`` with PlaywrightRasterizer: file:// URLs (60-80% faster) Key API Methods ~~~~~~~~~~~~~~~ - ``TypeSetting.get_postscript_name()``: Extract PostScript name from PSD - ``FontInfo.lookup_static()``: Lookup PostScript name to get font metadata (no platform queries) - ``FontInfo.resolve()``: Resolve PostScript name to font file with platform resolution - ``FontInfo.find()``: Backward-compatible wrapper (delegates to ``lookup_static()`` or ``resolve()``) - SVG tree is single source of truth for fonts (no separate font list maintained)