Skip to content
Open
Show file tree
Hide file tree
Changes from 77 commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
fc2706f
Ignore GSD planning directory from version control.
YyZz-wy Apr 22, 2026
f6723e6
Add ImageEmbedder header declaring embed and lastErrorPath API.
YyZz-wy Apr 23, 2026
5b6a818
Implement ImageEmbedder with ReadFileToData supporting empty-file and…
YyZz-wy Apr 23, 2026
4c99b91
Add CommandEmbed header declaring RunEmbed entry point.
YyZz-wy Apr 23, 2026
849b4a6
Add pagx embed command orchestrating font and image embedding with sk…
YyZz-wy Apr 23, 2026
1435101
Register pagx embed command and update top-level usage.
YyZz-wy Apr 23, 2026
c864a08
Add embed_sample pagx and png fixtures for pagx embed CLI tests.
YyZz-wy Apr 23, 2026
0b403b1
Fix FontEmbedder::ClearEmbeddedGlyphRuns to remove stale Font and Gly…
YyZz-wy Apr 23, 2026
b02b1a7
Add EMBED CLI tests covering default embed skip flags missing image a…
YyZz-wy Apr 23, 2026
78de811
Fix FontEmbedder::ClearEmbeddedGlyphRuns to remove stale Font and Gly…
YyZz-wy Apr 23, 2026
9da0451
Replace lambda with explicit loop in FontEmbedder::ClearEmbeddedGlyph…
YyZz-wy Apr 23, 2026
2961465
Retire pagx font embed subcommand with redirect error and drop dead h…
YyZz-wy Apr 23, 2026
7e7d61e
Revise CommandFont header doc comment to query-only scope.
YyZz-wy Apr 23, 2026
103b09b
Add FONT-01 CLI test asserting pagx font embed redirect error.
YyZz-wy Apr 23, 2026
097a127
Add std::nothrow to ImageEmbedder buffer allocation to honor no-excep…
YyZz-wy Apr 23, 2026
f7436e6
Document full reset contract of FontEmbedder::ClearEmbeddedGlyphRuns …
YyZz-wy Apr 23, 2026
56e0165
Trim CommandEmbed header doc to a summary and defer flag list to Prin…
YyZz-wy Apr 23, 2026
c5d04d1
Add FontFamilyEntry struct and AllFontFamilies declaration to SystemF…
YyZz-wy Apr 24, 2026
ebcebb7
Implement SystemFonts::AllFontFamilies for macOS, Windows, Linux, and…
YyZz-wy Apr 24, 2026
f0794c7
Flatten pagx font into a single query command and delete the info sub…
YyZz-wy Apr 24, 2026
dcfdf36
Rename FontInfo tests to Font, add info redirect and help surface tests.
YyZz-wy Apr 24, 2026
15b57ea
Add --list flag parsing, validation, and system font list formatting …
YyZz-wy Apr 24, 2026
3b31a25
Add FontList text, JSON, and mutual exclusion tests for pagx font --l…
YyZz-wy Apr 24, 2026
a6e759b
Update CommandFont.h comment to reflect removed subcommands.
YyZz-wy Apr 24, 2026
1bad5eb
Eliminate redundant map lookup in SystemFonts Linux AllFontFamilies.
YyZz-wy Apr 24, 2026
32f58fe
Move EmbedOptions and helpers inside namespace pagx::cli.
YyZz-wy Apr 24, 2026
b8c47d2
Rename misleading Font_HelpShowsNewSurface test to Font_HelpShowsCurr…
YyZz-wy Apr 24, 2026
a3b86aa
Update npm README to reflect renamed font and embed commands.
YyZz-wy Apr 24, 2026
43d0ebd
Add font-not-embedded assertion to Embed_SkipFonts_ImagesOnly test.
YyZz-wy Apr 24, 2026
15cb770
Add font-embedded assertion to Embed_SkipImages_FontsOnly test.
YyZz-wy Apr 24, 2026
5cdebbf
Deduplicate file reads for identical paths in ImageEmbedder.
YyZz-wy Apr 24, 2026
0091dd1
Warn when --size is used with --list in pagx font command.
YyZz-wy Apr 24, 2026
a627d27
Fix embed command description to distinguish font glyph embedding fro…
YyZz-wy Apr 24, 2026
5bb47f9
Reuse existing image_as_mask.png for embed tests instead of a duplica…
YyZz-wy Apr 24, 2026
08f5f14
Align npm README embed command description and flag text with CLI hel…
YyZz-wy Apr 27, 2026
7d678ec
Assert font node is embedded in Embed_FontFlags_AcceptedLikeOldSubcom…
YyZz-wy Apr 27, 2026
0dcaef4
Assert font node is embedded in Embed_BothDefault_EmbedsFontsAndImages.
YyZz-wy Apr 27, 2026
9b37b03
Assert FontList text output contains at least two non-empty family li…
YyZz-wy Apr 27, 2026
36b1000
Loosen FontList_JsonOutput to check for family and styles tokens inst…
YyZz-wy Apr 27, 2026
40ae84c
Use word-boundary check for retired info subcommand in Font_HelpShows…
YyZz-wy Apr 27, 2026
f07cedd
Document macOS AllFontFamilies API choice over CTFontManagerCopyAvail…
YyZz-wy Apr 27, 2026
3735a12
Remove pre-C++11 template space in SystemFonts.cpp nested vector type.
YyZz-wy Apr 27, 2026
d7d8384
Update SystemFonts class doc to cover AllFontFamilies in addition to …
YyZz-wy Apr 27, 2026
0352774
Track --size presence via bool flag so explicit default value still t…
YyZz-wy Apr 27, 2026
a601ce1
Revert template space fix that conflicts with project clang-format co…
YyZz-wy Apr 27, 2026
2ed17a1
codeformat
YyZz-wy Apr 27, 2026
da92e93
Revert LayerBuilder.cpp and .gitignore to main branch state.
YyZz-wy Apr 27, 2026
2c79ea4
Add codebase map to .planning/codebase/.
YyZz-wy Apr 28, 2026
ebb7ce4
Add SystemFonts::FindFont fallback for CLI font resolution on FreeTyp…
YyZz-wy Apr 28, 2026
14d2021
Fix use-after-free in FontEmbedder nodeMap cleanup and harden embed C…
YyZz-wy Apr 30, 2026
f437e0d
Reorder nodeMap cleanup before node compaction to avoid use-after-fre…
YyZz-wy May 6, 2026
e5c1618
Use known URL scheme prefixes in IsUrlPath to avoid false positives o…
YyZz-wy May 6, 2026
e61ee9b
Remove unused sizeSpecified field from FontOptions after warning remo…
YyZz-wy May 6, 2026
d38388f
Support Windows backslash path separators in CliUtils path functions.
YyZz-wy May 6, 2026
75a4b6f
Lowercase font extension before comparison to make case-insensitive m…
YyZz-wy May 6, 2026
6e32c4c
Check return value of loadFileData in ImageEmbedder to detect missing…
YyZz-wy May 6, 2026
6e7ad22
Show concrete input path in embed usage line instead of placeholder s…
YyZz-wy May 6, 2026
565fb30
Remove .planning/codebase/ from repository and add .planning/ to giti…
YyZz-wy May 6, 2026
8396456
Stop tracking .planning/codebase/ files from repository index.
YyZz-wy May 6, 2026
6a3251b
Implement SystemFonts::FindFont for Windows using DirectWrite FindFam…
YyZz-wy May 6, 2026
a21eac1
Stop ignoring AI planning artifacts.
YyZz-wy May 7, 2026
ee06e50
Fix code review issues from PR #3405 second round review.
YyZz-wy May 8, 2026
ebc82dd
Add file attribute to PAGX Font node with XSD schema and import/expor…
YyZz-wy May 25, 2026
1b95b5d
Auto-register fonts from Font file attribute in pagx embed and update…
YyZz-wy May 25, 2026
8e4ead0
Update skills docs and add CLI tests for Font file attribute auto-reg…
YyZz-wy May 25, 2026
68c863e
Fix dangling glyph pointers in ClearEmbeddedGlyphRuns and remove unus…
YyZz-wy May 27, 2026
bafc4ef
Fix stale comment in ImageEmbedder.h to reflect all-or-nothing semant…
YyZz-wy May 27, 2026
f3fd086
Make IsUrlPath case-insensitive per RFC 3986 Section 3.1.
YyZz-wy May 27, 2026
a954969
Fix CFMutableDictionaryRef leak on CFSetCreate failure in FindFont.
YyZz-wy May 27, 2026
edf4412
Remove documented --file/--font-file flags from pagx embed per Comman…
YyZz-wy May 27, 2026
2129274
Remove unused tempPng copy from Embed_FileFlag_RejectedAsUnknown that…
YyZz-wy May 28, 2026
7c71e51
Lift duplicated glyph-removal loop in ClearEmbeddedGlyphRuns above th…
YyZz-wy May 28, 2026
ef24936
Update Font::file doc-comment to match the embed flow that preserves …
YyZz-wy May 28, 2026
d1f6586
Rewrite pagx font cli reference to match the flat command and drop re…
YyZz-wy May 28, 2026
815d1fa
Extract ResolveRelativePath helper to deduplicate Image and Font path…
YyZz-wy May 28, 2026
6e4f513
Add missing StreamCapture RAII helper used by Font file CLI tests.
YyZz-wy May 29, 2026
38eb8f4
Run clang-format on PAGXDocument.cpp to satisfy project style.
YyZz-wy May 29, 2026
a7c64a0
Fix Windows font path resolution and path comments.
YyZz-wy Jun 1, 2026
3c04ca3
Merge remote-tracking branch 'origin/main' into feature/codywwang_cli…
YyZz-wy Jun 3, 2026
17e370f
Remove duplicated MakeStandaloneParams in Text and reuse the one from…
YyZz-wy Jun 3, 2026
6fa0fd7
Add missing Font file import round-trip test for Phase 3 plan 03.
YyZz-wy Jun 3, 2026
b90fe4b
Make removeNodes, setNodeId and resetLayoutState private with FontEmb…
YyZz-wy Jun 3, 2026
890fb04
Error when --skip-fonts and --fallback are both set in embed command.
YyZz-wy Jun 3, 2026
1cd6eac
Fix IsUrlPath to also match data: URI scheme.
YyZz-wy Jun 4, 2026
54f6708
Preserve Font file original path across import/export round-trip.
YyZz-wy Jun 4, 2026
9d07c41
Unify atomic write in WriteStringToFile, fix font ID collision, remov…
YyZz-wy Jun 4, 2026
edce376
Merge remote-tracking branch 'origin/main' into feature/codywwang_cli…
YyZz-wy Jun 4, 2026
d31df93
Merge remote-tracking branch 'origin/main' into feature/codywwang_cli…
YyZz-wy Jun 4, 2026
e738016
Update screenshot baseline for layout_flex_weights test.
YyZz-wy Jun 4, 2026
1d4e909
Update screenshot baseline for spec app_icons game_hud glyph_run and …
YyZz-wy Jun 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .codebuddy/skills/pagx/references/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ Embedded font resource containing subsetted glyph data (vector outlines or bitma

| Attribute | Type | Default | Description |
|-----------|------|---------|-------------|
| `file` | string | - | External font file path (TTF/OTF). Font nodes with `file` serve as font source declarations. `pagx embed` auto-discovers them, loads the referenced font files, and registers fonts for text shaping. After embed, `file` is preserved for source traceability. |
| `unitsPerEm` | int | 1000 | Font design space units; rendering scale = fontSize / unitsPerEm |

### Glyph
Expand Down
54 changes: 33 additions & 21 deletions .codebuddy/skills/pagx/references/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,50 +246,62 @@ element, an error is reported.

## pagx font

Font operations with two subcommands: `info` (query metrics) and `embed` (embed into PAGX).

### pagx font info

Query font identity and metrics from a font file or system font.
Query font identity and metrics from a font file or system font, or enumerate installed
system font families.

```bash
pagx font info --file ./CustomFont.ttf
pagx font info --file ./CustomFont.ttf --size 24
pagx font info --name "PingFang SC,Bold"
pagx font info --name "Arial" --size 24 --json
pagx font --file ./CustomFont.ttf
pagx font --file ./CustomFont.ttf --size 24
pagx font --name "PingFang SC,Bold"
pagx font --name "Arial" --size 24 --json
pagx font --list
pagx font --list --json
```

| Option | Description |
|--------|-------------|
| `--file <path>` | Font file path |
| `--name <family[,style]>` | System font by name (e.g., `"Arial"` or `"Arial,Bold"`) |
| `--file <path>` | Query a font file |
| `--name <family[,style]>` | Query a system font (e.g., `"Arial"` or `"Arial,Bold"`) |
| `--size <pt>` | Font size in points (default: 12, the PAGX spec default) |
| `--json` | JSON output |
| `--json` | Output in JSON format |
| `--list` | List every installed system font family |

Either `--file` or `--name` is required (mutually exclusive).
Exactly one of `--file`, `--name`, or `--list` is required. `--list` cannot be combined
with `--file` or `--name`; `--file` and `--name` are mutually exclusive.

Returns typeface info (fontFamily, fontStyle, glyphsCount, unitsPerEm, hasColor, hasOutlines)
and all FontMetrics fields at the specified size (top, ascent, descent, bottom, leading, xMin,
xMax, xHeight, capHeight, underlineThickness, underlinePosition).

### pagx font embed
The retired `pagx font info` and `pagx font embed` subcommands now error out with a redirect
message; use `pagx font ...` for font queries and `pagx embed` for font embedding.

Embed fonts into a PAGX file by performing text layout and glyph extraction.
---

## pagx embed

Embed font glyphs and images into a PAGX file for self-contained output. Font embedding extracts glyph data from laid-out text; image embedding inlines external image files as base64. Font nodes with a `file` attribute are automatically discovered and registered for text shaping — no `--font-file` flag needed.

```bash
pagx font embed input.pagx
pagx font embed -o out.pagx input.pagx
pagx font embed --file a.ttf --file b.ttf input.pagx
pagx font embed --file a.ttf --fallback "PingFang SC" --fallback b.otf input.pagx
pagx embed input.pagx # embed fonts + images (overwrite)
pagx embed -o out.pagx input.pagx # embed fonts + images to new file
pagx embed --skip-fonts input.pagx # embed images only
pagx embed --skip-images input.pagx # embed fonts only
```

| Option | Description |
|--------|-------------|
| `-o, --output <path>` | Output file path (default: overwrite input) |
| `--file <path>` | Register a font file (can be specified multiple times) |
| `--fallback <path\|name>` | Fallback font file or system font name (can be specified multiple times) |
| `--skip-fonts` | Skip font embedding |
| `--skip-images` | Skip image embedding |
| `-h, --help` | Show this help message |

Fonts are resolved in the following order:
1. Font nodes with `file` attribute are loaded and registered by their internal family name
2. `--fallback` fonts are tried when a character is not found in the primary font

`--file` and `--fallback` work the same as in `pagx render`.
Image embedding inlines external file references (Image nodes with `filePath`) as base64 data. See the Font `file` attribute in `attributes.md` for details on external font references.

---

Expand Down
2 changes: 1 addition & 1 deletion .codebuddy/skills/pagx/references/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ over `position`.
- **Text**: `text`, `fontFamily`, `fontStyle`, `fontSize`, `letterSpacing`. Wrap in TextBox
for paragraph features. `fauxBold`/`fauxItalic` for algorithmic styles. `&#10;` for line
breaks. Use `<![CDATA[...]]>` for XML special characters (e.g., `<![CDATA[A < B]]>`).
- **GlyphRun**: Pre-laid-out glyph data with embedded font. Generated by `pagx font embed`,
- **GlyphRun**: Pre-laid-out glyph data with embedded font. Generated by `pagx embed`,
not written by hand.

## Painters
Expand Down
38 changes: 25 additions & 13 deletions cli/npm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ npm install -g @libpag/pagx
| `pagx optimize` | Validate, optimize, and format in one step |
| `pagx format` | Format a PAGX file with consistent indentation and attribute ordering |
| `pagx bounds` | Query the precise rendered bounds of layers |
| `pagx font info` | Query font identity and metrics from a file or system font |
| `pagx font embed` | Embed fonts into a PAGX file with glyph extraction |
| `pagx font` | Query a font file, a system font by name, or list system font families |
| `pagx embed` | Embed font glyphs and images into a PAGX file for self-contained output |

## Usage Examples

Expand Down Expand Up @@ -65,13 +65,22 @@ pagx bounds --xpath "//Layer[@id='btn']" input.pagx
pagx bounds --xpath "//Layer[@id='icon']" --relative "//Layer[@id='card']" --json input.pagx

# Query system font metrics at 24pt
pagx font info --name "Arial" --size 24
pagx font --name "Arial" --size 24

# Query font metrics from a file
pagx font info --file CustomFont.ttf --json
pagx font --file CustomFont.ttf --json

# Embed fonts with a custom fallback
pagx font embed --file BrandFont.ttf --fallback "Arial" input.pagx
# List all installed system font families
pagx font --list

# Embed fonts and images into a PAGX file
pagx embed input.pagx

# Embed with fallback fonts
pagx embed --fallback "Arial" input.pagx

# Embed images only (skip font embedding)
pagx embed --skip-fonts input.pagx
```

## Command Reference
Expand Down Expand Up @@ -147,26 +156,29 @@ coordinates by default.

`--id` and `--xpath` are mutually exclusive. Without either, outputs bounds for all layers.

### `pagx font info [options]`
### `pagx font [options]`

Query font identity and metrics. Requires either `--file` or `--name` (mutually exclusive).
Query a font file, a system font by name, or list system font families. Exactly one of `--file`,
`--name`, or `--list` must be specified.

| Option | Description |
|--------|-------------|
| `--file <path>` | Font file path |
| `--name <family,style>` | System font name (e.g., `"Arial"` or `"Arial,Bold"`) |
| `--file <path>` | Query a font file |
| `--name <family,style>` | Query a system font (e.g., `"Arial"` or `"Arial,Bold"`) |
| `--size <pt>` | Font size in points (default: `12`) |
| `--json` | Output in JSON format |
| `--list` | List every installed system font family |

### `pagx font embed [options] <file.pagx>`
### `pagx embed [options] <file.pagx>`

Embed fonts into a PAGX file by performing text layout and glyph extraction.
Embed font glyphs and images into a PAGX file for self-contained output.

| Option | Description |
|--------|-------------|
| `-o, --output <path>` | Output file path (default: overwrite input) |
| `--file <path>` | Register a font file (repeatable) |
| `--fallback <path\|name>` | Add a fallback font file or system font name (repeatable) |
| `--skip-fonts` | Skip font embedding |
| `--skip-images` | Skip image embedding |

## Supported Platforms

Expand Down
39 changes: 36 additions & 3 deletions include/pagx/PAGXDocument.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "pagx/FontConfig.h"
#include "pagx/nodes/Layer.h"
Expand Down Expand Up @@ -127,11 +129,20 @@ class PAGXDocument : public Node {
*/
bool loadFileData(const std::string& filePath, std::shared_ptr<Data> data);

/**
* Batch version of loadFileData. Loads file data for all Image nodes whose filePath matches
* a key in the map in a single pass over the nodes. More efficient than calling loadFileData
* individually for each file when embedding multiple images.
* @param fileDataMap a map from file path to the file content to embed
*/
void loadFileDataMap(
const std::unordered_map<std::string, std::shared_ptr<Data>>& fileDataMap);

/**
* Executes auto layout on the document, positioning layers according to their layout
* constraints. Must be called before rendering or font embedding. This method should only
* be called once per document — repeated calls may produce incorrect results because
* measurement data is cached and some layout operations permanently modify source geometry.
* constraints. Must be called before rendering or font embedding. Repeated calls are safe
* only after calling ClearEmbeddedGlyphRuns + resetLayoutState(), which clears embedded
* glyph data and resets the layout flag so layout re-runs with fresh shaping data.
* @param fontConfig Optional font config for text measurement and rendering. When provided,
* updates the internal config before layout. Pass nullptr to use the
* previously set config (or no config).
Expand Down Expand Up @@ -160,6 +171,28 @@ class PAGXDocument : public Node {
*/
void clearEmbed();

/**
* Removes the specified nodes from the document and cleans up their nodeMap entries.
* Node pointers in the set and any pointers derived from them (e.g. child glyphs) are
* invalidated after this call. Callers must first collect all affected nodes to remove
* before calling.
*/
void removeNodes(const std::unordered_set<Node*>& nodesToRemove);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

removeNodes / setNodeId / resetLayoutState 三个新方法本质上是为 FontEmbedder::ClearEmbeddedGlyphRuns 服务的内部协作接口,却暴露在公开头文件 include/pagx/PAGXDocument.h 中。第三方调用者一旦使用 removeNodes,就要承担注释里写的"any external pointers… become dangling"风险,没有任何编译期或运行期保护。建议把这三个方法改成 private + friend class FontEmbedder,或者迁移到内部头文件,避免污染公开 API surface。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已修改


/**
* Assigns or changes the ID of an existing node. If the new ID already exists in the
* document, the old entry is replaced. If the ID is empty, the node is removed from the
* lookup index. The node must already be managed by this document.
*/
void setNodeId(Node* node, const std::string& id);

/**
* Resets the layout-applied flag to allow applyLayout() to be called again. Must be paired
* with ClearEmbeddedGlyphRuns before re-embedding to ensure layout re-runs with fresh
* shaping data.
*/
void resetLayoutState();

NodeType nodeType() const override {
return NodeType::Document;
}
Expand Down
10 changes: 10 additions & 0 deletions include/pagx/nodes/Font.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#pragma once

#include <string>
#include <vector>
#include "pagx/nodes/Node.h"
#include "pagx/types/Point.h"
Expand Down Expand Up @@ -76,6 +77,15 @@ class Font : public Node {
*/
int unitsPerEm = 1000;

/**
* Path to an external font file. When set, this Font node serves as a font source declaration:
* `pagx embed` loads and registers the referenced font for text shaping. Extracted glyph data
* is stored in separate Font nodes (the source node's `glyphs` is preserved as empty across
* embed). The path is resolved relative to the PAGX file's directory. An empty string means
* no external reference.
*/
std::string file = {};

/**
* The list of glyphs in this font. GlyphID is the index + 1 (GlyphID 0 is reserved for missing
* glyph).
Expand Down
12 changes: 12 additions & 0 deletions resources/cli/embed_font_file.pagx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<pagx width="400" height="200">
<Layer>
<TextBox textAlign="center">
<Text text="Hello PAGX" fontFamily="Noto Sans SC" fontSize="32"/>
<Fill color="#000000"/>
</TextBox>
</Layer>
<Resources>
<Font id="noto" file="NotoSansSC-Regular.otf" unitsPerEm="1000"/>
</Resources>
</pagx>
18 changes: 18 additions & 0 deletions resources/cli/embed_sample.pagx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<pagx width="200" height="200">
<Layer>
<Rectangle left="10" top="10" width="80" height="80"/>
<Fill>
<ImagePattern image="@sample" matrix="0.3,0,0,0.3,0,0" tileModeX="clamp" tileModeY="clamp"/>
</Fill>
</Layer>
<Layer>
<TextBox textAlign="center">
<Text text="Hello" fontFamily="Helvetica" fontSize="24"/>
<Fill color="#000000"/>
</TextBox>
</Layer>
<Resources>
<Image id="sample" source="image_as_mask.png"/>
</Resources>
</pagx>
1 change: 1 addition & 0 deletions spec/pagx.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,7 @@
</xs:sequence>
<xs:attributeGroup ref="CommonAttributes"/>
<xs:attribute name="unitsPerEm" type="xs:integer" default="1000"/>
<xs:attribute name="file" type="xs:string"/>
</xs:complexType>

<!-- ========================= GlyphRun ========================= -->
Expand Down
6 changes: 5 additions & 1 deletion spec/pagx_spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ Compositions are used for content reuse (similar to After Effects pre-comps).

#### 3.3.5 Font

Font defines embedded font resources containing subsetted glyph data (vector outlines or bitmaps). Embedding glyph data makes PAGX files fully self-contained, ensuring consistent rendering across platforms.
Font defines embedded font resources containing subsetted glyph data (vector outlines or bitmaps). Embedding glyph data makes PAGX files fully self-contained, ensuring consistent rendering across platforms. Font nodes can also reference external font files via the optional `file` attribute, serving as font source declarations for `pagx embed` to discover and register before text layout.

```xml
<!-- Embedded vector font -->
Expand All @@ -502,10 +502,14 @@ Font defines embedded font resources containing subsetted glyph data (vector out
<Glyph advance="136" image="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA..."/>
<Glyph advance="136" image="emoji/heart.png" offset="0,-5"/>
</Font>

<!-- External font file reference (before embed) -->
<Font id="Noto" file="./fonts/NotoSansSC-Regular.otf" unitsPerEm="1000"/>
```

| Attribute | Type | Default | Description |
|-----------|------|---------|-------------|
| `file` | string | - | External font file path. When set, the Font node references an external TTF/OTF file. Relative paths resolve against the PAGX file's directory. `pagx embed` discovers Font nodes with `file`, loads the fonts, and registers them for text shaping. After embed, `file` is preserved for source traceability while embedded glyph data lives in separate Font nodes. |
| `unitsPerEm` | int | 1000 | Font design space units. Rendering scale = `fontSize / unitsPerEm` |

**Consistency Constraint**: All Glyphs within the same Font must be of the same type—either all `path` or all `image`. Mixing is not allowed.
Expand Down
6 changes: 5 additions & 1 deletion spec/pagx_spec.zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ PathData 定义可复用的路径数据,供 Path 元素和 TextPath 修改器

#### 3.3.5 字体(Font)

Font 定义嵌入字体资源,包含子集化的字形数据(矢量轮廓或位图)。PAGX 文件通过嵌入字形数据实现完全自包含,确保跨平台渲染一致性。
Font 定义嵌入字体资源,包含子集化的字形数据(矢量轮廓或位图)。PAGX 文件通过嵌入字形数据实现完全自包含,确保跨平台渲染一致性。Font 节点还可以通过可选的 `file` 属性引用外部字体文件,作为字体来源声明,供 `pagx embed` 在执行文本排版前发现并注册。

```xml
<!-- 嵌入矢量字体 -->
Expand All @@ -502,10 +502,14 @@ Font 定义嵌入字体资源,包含子集化的字形数据(矢量轮廓或
<Glyph advance="136" image="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA..."/>
<Glyph advance="136" image="emoji/heart.png" offset="0,-5"/>
</Font>

<!-- 外部字体文件引用(嵌入前) -->
<Font id="Noto" file="./fonts/NotoSansSC-Regular.otf" unitsPerEm="1000"/>
```

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `file` | string | - | 外部字体文件路径。设置后 Font 节点引用外部 TTF/OTF 文件。相对路径基于 PAGX 文件所在目录解析。`pagx embed` 会自动发现带 `file` 的 Font 节点,加载并注册字体用于文本排版。嵌入后 `file` 属性保留在输出中以保持源文件可追溯性,嵌入的字形数据位于独立的 Font 节点中。 |
| `unitsPerEm` | int | 1000 | 字体设计空间单位。渲染时按 `fontSize / unitsPerEm` 缩放 |

**一致性约束**:同一 Font 内的所有 Glyph 必须使用相同类型(全部 `path` 或全部 `image`),不允许混用。
Expand Down
Loading
Loading