From 5649a3057f35cd186655e4bae702b10331db52d0 Mon Sep 17 00:00:00 2001 From: Dom Date: Fri, 13 Mar 2026 12:33:04 +0800 Subject: [PATCH] Remove align and distribute CLI commands in preparation for auto layout. --- .codebuddy/skills/pagx/SKILL.md | 6 +- .codebuddy/skills/pagx/references/cli.md | 61 ---- .../skills/pagx/references/generate-guide.md | 19 +- cli/npm/README.md | 40 --- resources/cli/align_distribute.pagx | 30 -- src/cli/CommandAlign.cpp | 288 ------------------ src/cli/CommandAlign.h | 30 -- src/cli/CommandDistribute.cpp | 218 ------------- src/cli/CommandDistribute.h | 29 -- src/cli/LayoutUtils.cpp | 106 ------- src/cli/LayoutUtils.h | 48 --- src/cli/main.cpp | 23 +- test/baseline/version.json | 5 - test/src/PAGXCliTest.cpp | 253 --------------- 14 files changed, 16 insertions(+), 1140 deletions(-) delete mode 100644 resources/cli/align_distribute.pagx delete mode 100644 src/cli/CommandAlign.cpp delete mode 100644 src/cli/CommandAlign.h delete mode 100644 src/cli/CommandDistribute.cpp delete mode 100644 src/cli/CommandDistribute.h diff --git a/.codebuddy/skills/pagx/SKILL.md b/.codebuddy/skills/pagx/SKILL.md index 1b07390fa8..f35fb79671 100644 --- a/.codebuddy/skills/pagx/SKILL.md +++ b/.codebuddy/skills/pagx/SKILL.md @@ -4,8 +4,8 @@ description: >- Generates well-structured PAGX files from visual descriptions and optimizes existing ones for size and rendering performance. Use when user asks to create, write, or design PAGX content, optimize or simplify a .pagx file, review PAGX structure, run - pagx CLI commands (render, validate, format, optimize, bounds, font info/embed, - align, distribute), or look up PAGX element attributes and syntax. + pagx CLI commands (render, validate, format, optimize, bounds, font info/embed), or + look up PAGX element attributes and syntax. --- # PAGX Skill @@ -44,4 +44,4 @@ generation or optimization task. | `spec-essentials.md` | Node types, Layer rendering pipeline, painter scope, text system, masking, resources | | `design-patterns.md` | Structure decisions (Layer vs Group), text layout patterns, practical pitfall patterns | | `attributes.md` | Attribute defaults, enumerations, required attributes | -| `cli.md` | CLI commands — `render`, `validate`, `optimize`, `format`, `bounds`, `font info`, `font embed`, `align`, `distribute` | +| `cli.md` | CLI commands — `render`, `validate`, `optimize`, `format`, `bounds`, `font info`, `font embed` | diff --git a/.codebuddy/skills/pagx/references/cli.md b/.codebuddy/skills/pagx/references/cli.md index 2cc39b5177..ca62782f77 100644 --- a/.codebuddy/skills/pagx/references/cli.md +++ b/.codebuddy/skills/pagx/references/cli.md @@ -217,64 +217,3 @@ pagx font embed --file a.ttf --fallback "PingFang SC" --fallback b.otf input.pag `"PingFang SC"` or `"Arial,Bold"`). All fonts added via `--fallback` are also registered automatically. Fallback fonts are tried in order when a character is not found in the primary font. System fallback fonts are always appended after user-specified fallbacks. - ---- - -## pagx align - -Align selected Layer nodes along a specified edge or center line. Supports cross-hierarchy -alignment — selected Layers can be at different nesting levels. Bounds are computed in global -canvas coordinates, and offsets are converted back to each Layer's local coordinate space. - -```bash -pagx align --id btn1 --id btn2 --anchor left input.pagx -pagx align --xpath "//Layer[@name='icon']" --anchor centerY input.pagx -pagx align --id header --id footer --anchor centerX -o aligned.pagx input.pagx -``` - -| Option | Description | -|--------|-------------| -| `--id ` | Select a Layer by its `id` attribute (can be repeated) | -| `--xpath ` | Select Layers by XPath expression | -| `--anchor ` | Alignment anchor (required) | -| `-o, --output ` | Output file path (default: overwrite input) | - -`--id` can be used multiple times and combined with `--xpath`. At least 2 Layers must be -selected. The file is modified in place unless `-o` is specified. - -**Anchor values**: - -| Anchor | Behavior | -|--------|----------| -| `left` | Align left edges to the leftmost Layer's left edge | -| `right` | Align right edges to the rightmost Layer's right edge | -| `top` | Align top edges to the topmost Layer's top edge | -| `bottom` | Align bottom edges to the bottommost Layer's bottom edge | -| `centerX` | Align horizontal centers to the selection's horizontal center | -| `centerY` | Align vertical centers to the selection's vertical center | - ---- - -## pagx distribute - -Distribute selected Layer nodes with equal spacing along an axis. The first and last Layers -(by current position) remain in place while middle Layers are repositioned to create uniform -gaps. Supports cross-hierarchy distribution. - -```bash -pagx distribute --id a --id b --id c --axis x input.pagx -pagx distribute --xpath "//Layer[@name='item']" --axis y input.pagx -pagx distribute --id col1 --id col2 --id col3 --id col4 --axis x -o out.pagx input.pagx -``` - -| Option | Description | -|--------|-------------| -| `--id ` | Select a Layer by its `id` attribute (can be repeated) | -| `--xpath ` | Select Layers by XPath expression | -| `--axis ` | Distribution axis: `x` or `y` (required) | -| `-o, --output ` | Output file path (default: overwrite input) | - -`--id` can be used multiple times and combined with `--xpath`. At least 3 Layers must be -selected (2 Layers have no gap to equalize). Layers are sorted by their current position -along the chosen axis before distribution. The file is modified in place unless `-o` is -specified. diff --git a/.codebuddy/skills/pagx/references/generate-guide.md b/.codebuddy/skills/pagx/references/generate-guide.md index 23955581aa..c9b6f782a9 100644 --- a/.codebuddy/skills/pagx/references/generate-guide.md +++ b/.codebuddy/skills/pagx/references/generate-guide.md @@ -19,7 +19,7 @@ Read these as needed: |-----------|---------| | `examples.md` | Structural patterns for Icons, UI, Logos, Charts, Decorative backgrounds | | `attributes.md` | Attribute defaults, enumerations, required attributes | -| `cli.md` | CLI tool usage — `render`, `bounds`, `font info`, `align`, `distribute` commands | +| `cli.md` | CLI tool usage — `render`, `bounds`, `font info` commands | --- @@ -141,10 +141,7 @@ be unified to the exact same value. Common scenarios include but are not limited - Container padding should be symmetric (left = right, top = bottom) - Similar containers (cards, buttons, badges) should use the same internal padding -Tool selection: -- **2+ Layers sharing an edge or center** → `pagx align` (see `cli.md`) -- **3+ Layers in a row or column** → `pagx distribute` (see `cli.md`) -- **Padding and gaps** → measure with `pagx bounds`, then adjust coordinates to equalize +Use `pagx bounds` to measure, then adjust coordinates to equalize. ### Step 4: Localize Coordinates @@ -154,8 +151,8 @@ Layer `x`/`y` carries the block offset; internal coordinates start from `0,0`. S ### Step 5: Verify and Refine After each render, follow the **Verification and Correction Loop** at the end of this -document — run `pagx bounds` first to detect misalignment by numbers, fix with CLI tools -or coordinate adjustment, then visually confirm. Do not skip bounds measurement even if +document — run `pagx bounds` first to detect misalignment by numbers, fix with +coordinate adjustment, then visually confirm. Do not skip bounds measurement even if the screenshot looks correct. After verification passes, continue to `optimize-guide.md` for optimization review. @@ -179,9 +176,8 @@ for targeted measurement when needed (see `cli.md`). ### 2. Check Alignment, Spacing, and Padding by Numbers -Scan the bounds data for any misalignment, uneven spacing, or asymmetric padding. Even if -`pagx align` / `pagx distribute` were applied during generation, fixes in one area can -introduce regressions elsewhere — always re-check everything. +Scan the bounds data for any misalignment, uneven spacing, or asymmetric padding. Fixes +in one area can introduce regressions elsewhere — always re-check everything. **Alignment** — elements that should share an edge or center: ``` @@ -216,8 +212,7 @@ Compute from bounds, not from Layer `x`/`y` — asymmetric content shifts the vi ### 3. Fix Issues -- **Alignment or distribution off** → re-run `pagx align` / `pagx distribute` -- **Uneven padding or gaps** → adjust Layer `x`/`y` to equalize values +- **Alignment or spacing off** → adjust Layer `x`/`y` or element `position`/`center` to equalize values ### 4. Re-render and Visually Confirm diff --git a/cli/npm/README.md b/cli/npm/README.md index 748e691f7c..00c4dbb764 100644 --- a/cli/npm/README.md +++ b/cli/npm/README.md @@ -24,8 +24,6 @@ npm install -g @libpag/pagx | `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 align` | Align selected layers along an edge or center line | -| `pagx distribute` | Distribute selected layers with equal spacing | ## Usage Examples @@ -74,15 +72,6 @@ pagx font info --file CustomFont.ttf --json # Embed fonts with a custom fallback pagx font embed --file BrandFont.ttf --fallback "Arial" input.pagx - -# Align three layers to the left edge -pagx align --id a --id b --id c --anchor left input.pagx - -# Align layers matched by XPath to their vertical center -pagx align --xpath "//Layer[@name='icon']" --anchor centerY input.pagx - -# Distribute layers with equal horizontal spacing -pagx distribute --id a --id b --id c --axis x input.pagx ``` ## Command Reference @@ -179,35 +168,6 @@ Embed fonts into a PAGX file by performing text layout and glyph extraction. | `--file ` | Register a font file (repeatable) | | `--fallback ` | Add a fallback font file or system font name (repeatable) | -### `pagx align [options] ` - -Align selected Layer nodes along a specified edge or center line. Computes bounds in global -coordinates and converts offsets back to each Layer's local coordinate space, so cross-hierarchy -alignment works correctly. - -| Option | Description | -|--------|-------------| -| `--id ` | Select a Layer by its id attribute (repeatable) | -| `--xpath ` | Select Layers by XPath expression | -| `--anchor ` | Alignment anchor: `left`, `right`, `top`, `bottom`, `centerX`, `centerY` | -| `-o, --output ` | Output file path (default: overwrite input) | - -At least 2 Layers must be selected. `--id` can be used multiple times and combined with `--xpath`. - -### `pagx distribute [options] ` - -Distribute selected Layer nodes with equal spacing along an axis. The first and last Layers (by -position) remain in place while middle Layers are repositioned to create uniform gaps. - -| Option | Description | -|--------|-------------| -| `--id ` | Select a Layer by its id attribute (repeatable) | -| `--xpath ` | Select Layers by XPath expression | -| `--axis ` | Distribution axis: `x` or `y` | -| `-o, --output ` | Output file path (default: overwrite input) | - -At least 3 Layers must be selected. `--id` can be used multiple times and combined with `--xpath`. - ## Supported Platforms - macOS (Apple Silicon and Intel) diff --git a/resources/cli/align_distribute.pagx b/resources/cli/align_distribute.pagx deleted file mode 100644 index a94e6da4b4..0000000000 --- a/resources/cli/align_distribute.pagx +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/cli/CommandAlign.cpp b/src/cli/CommandAlign.cpp deleted file mode 100644 index 350cb816c6..0000000000 --- a/src/cli/CommandAlign.cpp +++ /dev/null @@ -1,288 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////// -// -// Tencent is pleased to support the open source community by making libpag available. -// -// Copyright (C) 2026 Tencent. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file -// except in compliance with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// unless required by applicable law or agreed to in writing, software distributed under the -// license is distributed on an "as is" basis, without warranties or conditions of any kind, -// either express or implied. see the license for the specific language governing permissions -// and limitations under the license. -// -///////////////////////////////////////////////////////////////////////////////////////////////// - -#include "cli/CommandAlign.h" -#include -#include -#include -#include -#include -#include -#include "cli/CliUtils.h" -#include "cli/LayoutUtils.h" -#include "pagx/PAGXExporter.h" -#include "pagx/PAGXImporter.h" -#include "renderer/LayerBuilder.h" -#include "renderer/TextLayout.h" -#include "tgfx/layers/Layer.h" - -namespace pagx::cli { - -enum class AlignAnchor { - Left, - Right, - Top, - Bottom, - CenterX, - CenterY, -}; - -struct AlignOptions { - std::string inputFile = {}; - std::string outputFile = {}; - std::vector ids = {}; - std::string xpath = {}; - AlignAnchor anchor = AlignAnchor::Left; - bool hasAnchor = false; -}; - -static void PrintAlignUsage() { - std::cout << "Usage: pagx align [options] \n" - << "\n" - << "Align selected Layer nodes along a specified edge or center line.\n" - << "\n" - << "Options:\n" - << " --id Select a Layer by its id attribute (can be repeated)\n" - << " --xpath Select Layers by XPath expression\n" - << " --anchor Alignment anchor: left, right, top, bottom, centerX,\n" - << " centerY\n" - << " -o, --output Output file path (default: overwrite input)\n" - << " -h, --help Show this help message\n" - << "\n" - << "At least 2 Layers must be selected. --id can be used multiple times and\n" - << "combined with --xpath.\n" - << "\n" - << "Examples:\n" - << " pagx align --id btn1 --id btn2 --anchor left input.pagx\n" - << " pagx align --xpath \"//Layer[@name='icon']\" --anchor centerY input.pagx\n"; -} - -static bool ParseAlignAnchor(const std::string& value, AlignAnchor* anchor) { - if (value == "left") { - *anchor = AlignAnchor::Left; - } else if (value == "right") { - *anchor = AlignAnchor::Right; - } else if (value == "top") { - *anchor = AlignAnchor::Top; - } else if (value == "bottom") { - *anchor = AlignAnchor::Bottom; - } else if (value == "centerX") { - *anchor = AlignAnchor::CenterX; - } else if (value == "centerY") { - *anchor = AlignAnchor::CenterY; - } else { - return false; - } - return true; -} - -// Returns 0 on success, -1 if help was printed, 1 on error. -static int ParseAlignOptions(int argc, char* argv[], AlignOptions* options) { - int i = 1; - while (i < argc) { - std::string arg = argv[i]; - if (arg == "--id" && i + 1 < argc) { - options->ids.push_back(argv[++i]); - } else if (arg == "--xpath" && i + 1 < argc) { - options->xpath = argv[++i]; - } else if (arg == "--anchor" && i + 1 < argc) { - if (!ParseAlignAnchor(argv[++i], &options->anchor)) { - std::cerr << "pagx align: invalid anchor '" << argv[i] - << "' (expected: left, right, top, bottom, centerX, centerY)\n"; - return 1; - } - options->hasAnchor = true; - } else if ((arg == "-o" || arg == "--output") && i + 1 < argc) { - options->outputFile = argv[++i]; - } else if (arg == "--help" || arg == "-h") { - PrintAlignUsage(); - return -1; - } else if (arg[0] == '-') { - std::cerr << "pagx align: unknown option '" << arg << "'\n"; - return 1; - } else { - options->inputFile = arg; - } - i++; - } - if (options->inputFile.empty()) { - std::cerr << "pagx align: missing input file\n"; - return 1; - } - if (!options->hasAnchor) { - std::cerr << "pagx align: missing --anchor option\n"; - return 1; - } - if (options->ids.empty() && options->xpath.empty()) { - std::cerr << "pagx align: at least one --id or --xpath must be specified\n"; - return 1; - } - if (options->outputFile.empty()) { - options->outputFile = options->inputFile; - } - return 0; -} - -int RunAlign(int argc, char* argv[]) { - AlignOptions options = {}; - auto parseResult = ParseAlignOptions(argc, argv, &options); - if (parseResult != 0) { - return parseResult == -1 ? 0 : parseResult; - } - - auto document = PAGXImporter::FromFile(options.inputFile); - if (document == nullptr) { - std::cerr << "pagx align: failed to load '" << options.inputFile << "'\n"; - return 1; - } - for (auto& error : document->errors) { - std::cerr << "pagx align: warning: " << error << "\n"; - } - - // Build layer tree with mapping. - TextLayout textLayout = {}; - SetupSystemFallbackFonts(textLayout); - auto buildResult = LayerBuilder::BuildWithMap(document.get(), &textLayout); - if (buildResult.root == nullptr) { - std::cerr << "pagx align: failed to build layer tree\n"; - return 1; - } - - // Select target Layers. - auto targets = - SelectLayers(document.get(), options.inputFile, options.ids, options.xpath, "align"); - if (targets.empty()) { - return 1; - } - if (targets.size() < 2) { - std::cerr << "pagx align: at least 2 Layers must be selected (got " << targets.size() << ")\n"; - return 1; - } - - // Resolve selected Layers to tgfx::Layer pointers and compute global bounds. - auto infos = ResolveLayerInfos(targets, buildResult, "align"); - if (infos.size() < 2) { - std::cerr << "pagx align: not enough rendered Layers to align\n"; - return 1; - } - - // Compute the alignment target value. - float targetValue = 0.0f; - switch (options.anchor) { - case AlignAnchor::Left: { - targetValue = std::numeric_limits::max(); - for (auto& info : infos) { - targetValue = std::min(targetValue, info.globalBounds.left); - } - break; - } - case AlignAnchor::Right: { - targetValue = std::numeric_limits::lowest(); - for (auto& info : infos) { - targetValue = std::max(targetValue, info.globalBounds.right); - } - break; - } - case AlignAnchor::Top: { - targetValue = std::numeric_limits::max(); - for (auto& info : infos) { - targetValue = std::min(targetValue, info.globalBounds.top); - } - break; - } - case AlignAnchor::Bottom: { - targetValue = std::numeric_limits::lowest(); - for (auto& info : infos) { - targetValue = std::max(targetValue, info.globalBounds.bottom); - } - break; - } - case AlignAnchor::CenterX: { - float minLeft = std::numeric_limits::max(); - float maxRight = std::numeric_limits::lowest(); - for (auto& info : infos) { - minLeft = std::min(minLeft, info.globalBounds.left); - maxRight = std::max(maxRight, info.globalBounds.right); - } - targetValue = (minLeft + maxRight) / 2.0f; - break; - } - case AlignAnchor::CenterY: { - float minTop = std::numeric_limits::max(); - float maxBottom = std::numeric_limits::lowest(); - for (auto& info : infos) { - minTop = std::min(minTop, info.globalBounds.top); - maxBottom = std::max(maxBottom, info.globalBounds.bottom); - } - targetValue = (minTop + maxBottom) / 2.0f; - break; - } - } - - // Apply alignment offsets. - for (auto& info : infos) { - float deltaGlobalX = 0.0f; - float deltaGlobalY = 0.0f; - switch (options.anchor) { - case AlignAnchor::Left: - deltaGlobalX = targetValue - info.globalBounds.left; - break; - case AlignAnchor::Right: - deltaGlobalX = targetValue - info.globalBounds.right; - break; - case AlignAnchor::Top: - deltaGlobalY = targetValue - info.globalBounds.top; - break; - case AlignAnchor::Bottom: - deltaGlobalY = targetValue - info.globalBounds.bottom; - break; - case AlignAnchor::CenterX: { - float currentCenter = (info.globalBounds.left + info.globalBounds.right) / 2.0f; - deltaGlobalX = targetValue - currentCenter; - break; - } - case AlignAnchor::CenterY: { - float currentCenter = (info.globalBounds.top + info.globalBounds.bottom) / 2.0f; - deltaGlobalY = targetValue - currentCenter; - break; - } - } - - ApplyGlobalOffset(info.pagxLayer, info.tgfxLayer, deltaGlobalX, deltaGlobalY); - } - - // Write back. - auto xml = PAGXExporter::ToXML(*document); - std::ofstream out(options.outputFile); - if (!out.is_open()) { - std::cerr << "pagx align: failed to write '" << options.outputFile << "'\n"; - return 1; - } - out << xml; - out.close(); - if (out.fail()) { - std::cerr << "pagx align: error writing to '" << options.outputFile << "'\n"; - return 1; - } - - std::cout << "pagx align: aligned " << infos.size() << " layers, wrote " << options.outputFile - << "\n"; - return 0; -} - -} // namespace pagx::cli diff --git a/src/cli/CommandAlign.h b/src/cli/CommandAlign.h deleted file mode 100644 index 9adcb1af4f..0000000000 --- a/src/cli/CommandAlign.h +++ /dev/null @@ -1,30 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////// -// -// Tencent is pleased to support the open source community by making libpag available. -// -// Copyright (C) 2026 Tencent. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file -// except in compliance with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// unless required by applicable law or agreed to in writing, software distributed under the -// license is distributed on an "as is" basis, without warranties or conditions of any kind, -// either express or implied. see the license for the specific language governing permissions -// and limitations under the license. -// -///////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -namespace pagx::cli { - -/** - * Aligns selected Layer nodes along a specified edge or center line. Supports cross-hierarchy - * alignment by computing bounds in global coordinates and converting offsets back to each Layer's - * local coordinate space. - */ -int RunAlign(int argc, char* argv[]); - -} // namespace pagx::cli diff --git a/src/cli/CommandDistribute.cpp b/src/cli/CommandDistribute.cpp deleted file mode 100644 index d917f133c7..0000000000 --- a/src/cli/CommandDistribute.cpp +++ /dev/null @@ -1,218 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////// -// -// Tencent is pleased to support the open source community by making libpag available. -// -// Copyright (C) 2026 Tencent. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file -// except in compliance with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// unless required by applicable law or agreed to in writing, software distributed under the -// license is distributed on an "as is" basis, without warranties or conditions of any kind, -// either express or implied. see the license for the specific language governing permissions -// and limitations under the license. -// -///////////////////////////////////////////////////////////////////////////////////////////////// - -#include "cli/CommandDistribute.h" -#include -#include -#include -#include -#include -#include "cli/CliUtils.h" -#include "cli/LayoutUtils.h" -#include "pagx/PAGXExporter.h" -#include "pagx/PAGXImporter.h" -#include "renderer/LayerBuilder.h" -#include "renderer/TextLayout.h" -#include "tgfx/layers/Layer.h" - -namespace pagx::cli { - -enum class DistributeAxis { - X, - Y, -}; - -struct DistributeOptions { - std::string inputFile = {}; - std::string outputFile = {}; - std::vector ids = {}; - std::string xpath = {}; - DistributeAxis axis = DistributeAxis::X; - bool hasAxis = false; -}; - -static void PrintDistributeUsage() { - std::cout << "Usage: pagx distribute [options] \n" - << "\n" - << "Distribute selected Layer nodes with equal spacing along an axis.\n" - << "The first and last Layers (by position) remain in place.\n" - << "\n" - << "Options:\n" - << " --id Select a Layer by its id attribute (can be repeated)\n" - << " --xpath Select Layers by XPath expression\n" - << " --axis Distribution axis: x or y\n" - << " -o, --output Output file path (default: overwrite input)\n" - << " -h, --help Show this help message\n" - << "\n" - << "At least 3 Layers must be selected. --id can be used multiple times and\n" - << "combined with --xpath.\n" - << "\n" - << "Examples:\n" - << " pagx distribute --id a --id b --id c --axis x input.pagx\n" - << " pagx distribute --xpath \"//Layer[@name='item']\" --axis y input.pagx\n"; -} - -// Returns 0 on success, -1 if help was printed, 1 on error. -static int ParseDistributeOptions(int argc, char* argv[], DistributeOptions* options) { - int i = 1; - while (i < argc) { - std::string arg = argv[i]; - if (arg == "--id" && i + 1 < argc) { - options->ids.push_back(argv[++i]); - } else if (arg == "--xpath" && i + 1 < argc) { - options->xpath = argv[++i]; - } else if (arg == "--axis" && i + 1 < argc) { - std::string value = argv[++i]; - if (value == "x") { - options->axis = DistributeAxis::X; - } else if (value == "y") { - options->axis = DistributeAxis::Y; - } else { - std::cerr << "pagx distribute: invalid axis '" << value << "' (expected: x, y)\n"; - return 1; - } - options->hasAxis = true; - } else if ((arg == "-o" || arg == "--output") && i + 1 < argc) { - options->outputFile = argv[++i]; - } else if (arg == "--help" || arg == "-h") { - PrintDistributeUsage(); - return -1; - } else if (arg[0] == '-') { - std::cerr << "pagx distribute: unknown option '" << arg << "'\n"; - return 1; - } else { - options->inputFile = arg; - } - i++; - } - if (options->inputFile.empty()) { - std::cerr << "pagx distribute: missing input file\n"; - return 1; - } - if (!options->hasAxis) { - std::cerr << "pagx distribute: missing --axis option\n"; - return 1; - } - if (options->ids.empty() && options->xpath.empty()) { - std::cerr << "pagx distribute: at least one --id or --xpath must be specified\n"; - return 1; - } - if (options->outputFile.empty()) { - options->outputFile = options->inputFile; - } - return 0; -} - -int RunDistribute(int argc, char* argv[]) { - DistributeOptions options = {}; - auto parseResult = ParseDistributeOptions(argc, argv, &options); - if (parseResult != 0) { - return parseResult == -1 ? 0 : parseResult; - } - - auto document = PAGXImporter::FromFile(options.inputFile); - if (document == nullptr) { - std::cerr << "pagx distribute: failed to load '" << options.inputFile << "'\n"; - return 1; - } - for (auto& error : document->errors) { - std::cerr << "pagx distribute: warning: " << error << "\n"; - } - - // Build layer tree with mapping. - TextLayout textLayout = {}; - SetupSystemFallbackFonts(textLayout); - auto buildResult = LayerBuilder::BuildWithMap(document.get(), &textLayout); - if (buildResult.root == nullptr) { - std::cerr << "pagx distribute: failed to build layer tree\n"; - return 1; - } - - // Select target Layers. - auto targets = - SelectLayers(document.get(), options.inputFile, options.ids, options.xpath, "distribute"); - if (targets.empty()) { - return 1; - } - if (targets.size() < 3) { - std::cerr << "pagx distribute: at least 3 Layers must be selected (got " << targets.size() - << ")\n"; - return 1; - } - - // Resolve selected Layers and compute global bounds. - auto infos = ResolveLayerInfos(targets, buildResult, "distribute"); - if (infos.size() < 3) { - std::cerr << "pagx distribute: not enough rendered Layers to distribute\n"; - return 1; - } - - // Sort by position along the distribution axis. - bool isX = options.axis == DistributeAxis::X; - std::sort(infos.begin(), infos.end(), isX ? CompareByPositionX : CompareByPositionY); - - // Compute equal spacing. - // Total available space = (last element's leading edge) - (first element's trailing edge) - // Then subtract the sizes of all middle elements. - auto count = infos.size(); - float firstEnd = isX ? infos[0].globalBounds.right : infos[0].globalBounds.bottom; - float lastStart = isX ? infos[count - 1].globalBounds.left : infos[count - 1].globalBounds.top; - float middleSizes = 0.0f; - for (size_t i = 1; i < count - 1; i++) { - if (isX) { - middleSizes += infos[i].globalBounds.right - infos[i].globalBounds.left; - } else { - middleSizes += infos[i].globalBounds.bottom - infos[i].globalBounds.top; - } - } - float totalGap = lastStart - firstEnd - middleSizes; - float gap = totalGap / static_cast(count - 1); - - // Reposition middle elements. - float currentPos = firstEnd + gap; - for (size_t i = 1; i < count - 1; i++) { - float currentStart = isX ? infos[i].globalBounds.left : infos[i].globalBounds.top; - float delta = currentPos - currentStart; - float deltaGlobalX = isX ? delta : 0.0f; - float deltaGlobalY = isX ? 0.0f : delta; - ApplyGlobalOffset(infos[i].pagxLayer, infos[i].tgfxLayer, deltaGlobalX, deltaGlobalY); - float size = isX ? (infos[i].globalBounds.right - infos[i].globalBounds.left) - : (infos[i].globalBounds.bottom - infos[i].globalBounds.top); - currentPos += size + gap; - } - - // Write back. - auto xml = PAGXExporter::ToXML(*document); - std::ofstream out(options.outputFile); - if (!out.is_open()) { - std::cerr << "pagx distribute: failed to write '" << options.outputFile << "'\n"; - return 1; - } - out << xml; - out.close(); - if (out.fail()) { - std::cerr << "pagx distribute: error writing to '" << options.outputFile << "'\n"; - return 1; - } - - std::cout << "pagx distribute: distributed " << infos.size() << " layers along " - << (isX ? "x" : "y") << " axis, wrote " << options.outputFile << "\n"; - return 0; -} - -} // namespace pagx::cli diff --git a/src/cli/CommandDistribute.h b/src/cli/CommandDistribute.h deleted file mode 100644 index cf8d85edb4..0000000000 --- a/src/cli/CommandDistribute.h +++ /dev/null @@ -1,29 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////// -// -// Tencent is pleased to support the open source community by making libpag available. -// -// Copyright (C) 2026 Tencent. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file -// except in compliance with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// unless required by applicable law or agreed to in writing, software distributed under the -// license is distributed on an "as is" basis, without warranties or conditions of any kind, -// either express or implied. see the license for the specific language governing permissions -// and limitations under the license. -// -///////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -namespace pagx::cli { - -/** - * Distributes selected Layer nodes with equal spacing along an axis. The first and last Layers - * (by position) remain in place while middle Layers are repositioned to create uniform gaps. - */ -int RunDistribute(int argc, char* argv[]); - -} // namespace pagx::cli diff --git a/src/cli/LayoutUtils.cpp b/src/cli/LayoutUtils.cpp index 07448588c0..38fdb019fd 100644 --- a/src/cli/LayoutUtils.cpp +++ b/src/cli/LayoutUtils.cpp @@ -17,107 +17,9 @@ ///////////////////////////////////////////////////////////////////////////////////////////////// #include "cli/LayoutUtils.h" -#include -#include -#include -#include "cli/XPathQuery.h" namespace pagx::cli { -std::vector SelectLayers(PAGXDocument* document, const std::string& inputFile, - const std::vector& ids, const std::string& xpath, - const std::string& commandName) { - std::vector result = {}; - std::unordered_set seen = {}; - - // Collect Layers by --id. - for (auto& id : ids) { - auto* node = document->findNode(id); - if (node == nullptr) { - std::cerr << "pagx " << commandName << ": no node found with id '" << id << "'\n"; - return {}; - } - if (node->nodeType() != NodeType::Layer) { - std::cerr << "pagx " << commandName << ": node '" << id << "' is not a Layer\n"; - return {}; - } - auto* layer = static_cast(node); - if (seen.insert(layer).second) { - result.push_back(layer); - } - } - - // Collect Layers by --xpath. - if (!xpath.empty()) { - auto xmlDoc = xmlReadFile(inputFile.c_str(), nullptr, XML_PARSE_NONET); - if (xmlDoc == nullptr) { - std::cerr << "pagx " << commandName << ": failed to parse XML from '" << inputFile << "'\n"; - return {}; - } - auto xpathLayers = EvaluateXPath(xmlDoc, xpath, document); - xmlFreeDoc(xmlDoc); - if (xpathLayers.empty()) { - std::cerr << "pagx " << commandName << ": --xpath matched no Layer nodes\n"; - return {}; - } - for (auto* matchedLayer : xpathLayers) { - if (seen.insert(matchedLayer).second) { - result.push_back(matchedLayer); - } - } - } - - return result; -} - -std::vector ResolveLayerInfos(const std::vector& targets, - const LayerBuildResult& buildResult, - const std::string& commandName) { - std::vector infos = {}; - infos.reserve(targets.size()); - for (auto* layer : targets) { - auto it = buildResult.layerMap.find(layer); - if (it == buildResult.layerMap.end()) { - std::cerr << "pagx " << commandName << ": Layer '" << GetLayerLabel(layer) - << "' has no rendered layer\n"; - continue; - } - LayerInfo info = {}; - info.pagxLayer = layer; - info.tgfxLayer = it->second.get(); - info.globalBounds = info.tgfxLayer->getBounds(buildResult.root.get(), true); - infos.push_back(info); - } - return infos; -} - -void ApplyGlobalOffset(Layer* pagxLayer, tgfx::Layer* tgfxLayer, float deltaGlobalX, - float deltaGlobalY) { - if (deltaGlobalX == 0.0f && deltaGlobalY == 0.0f) { - return; - } - - auto* parentTgfx = tgfxLayer->parent(); - if (parentTgfx == nullptr) { - // Top-level Layer: global and local coordinates are the same. - pagxLayer->x += deltaGlobalX; - pagxLayer->y += deltaGlobalY; - return; - } - - // Convert global offset to parent's local coordinate space. - // globalToLocal converts a global point to the Layer's local space. To get the offset in the - // parent's coordinate system, we convert two global points through the parent and take the - // difference. This correctly handles any rotation, scaling, or skewing in the parent chain. - auto origin = parentTgfx->globalToLocal({0.0f, 0.0f}); - auto offset = parentTgfx->globalToLocal({deltaGlobalX, deltaGlobalY}); - float deltaLocalX = offset.x - origin.x; - float deltaLocalY = offset.y - origin.y; - - pagxLayer->x += deltaLocalX; - pagxLayer->y += deltaLocalY; -} - std::string GetLayerLabel(const Layer* layer) { if (!layer->id.empty()) { return layer->id; @@ -128,12 +30,4 @@ std::string GetLayerLabel(const Layer* layer) { return "(unnamed)"; } -bool CompareByPositionX(const LayerInfo& a, const LayerInfo& b) { - return a.globalBounds.left < b.globalBounds.left; -} - -bool CompareByPositionY(const LayerInfo& a, const LayerInfo& b) { - return a.globalBounds.top < b.globalBounds.top; -} - } // namespace pagx::cli diff --git a/src/cli/LayoutUtils.h b/src/cli/LayoutUtils.h index d1748b10aa..5da448bfb7 100644 --- a/src/cli/LayoutUtils.h +++ b/src/cli/LayoutUtils.h @@ -19,61 +19,13 @@ #pragma once #include -#include #include "pagx/PAGXDocument.h" -#include "renderer/LayerBuilder.h" -#include "tgfx/layers/Layer.h" namespace pagx::cli { -/** - * Holds a pagx::Layer, its corresponding tgfx::Layer, and the global bounds computed from the - * layer tree root. Used by align and distribute commands. - */ -struct LayerInfo { - Layer* pagxLayer = nullptr; - tgfx::Layer* tgfxLayer = nullptr; - tgfx::Rect globalBounds = {}; -}; - -/** - * Selects Layers from a PAGXDocument by a combination of --id values and an --xpath expression. - * Returns a deduplicated list of matching Layers. Prints errors to stderr and returns an empty - * vector on failure. - */ -std::vector SelectLayers(PAGXDocument* document, const std::string& inputFile, - const std::vector& ids, const std::string& xpath, - const std::string& commandName); - -/** - * Resolves selected Layers to LayerInfo entries by looking up each Layer in the build result's - * layer map and computing global bounds. Layers not found in the map are skipped with a warning. - */ -std::vector ResolveLayerInfos(const std::vector& targets, - const LayerBuildResult& buildResult, - const std::string& commandName); - -/** - * Applies a global-coordinate offset to a pagx::Layer by converting the offset into the Layer's - * parent coordinate space. This handles cross-hierarchy alignment correctly by using the tgfx - * Layer's parent globalToLocal conversion. - */ -void ApplyGlobalOffset(Layer* pagxLayer, tgfx::Layer* tgfxLayer, float deltaGlobalX, - float deltaGlobalY); - /** * Returns a display label for a Layer (id, name, or "(unnamed)"). */ std::string GetLayerLabel(const Layer* layer); -/** - * Compares two LayerInfo entries by their global bounds left edge for sorting along the X axis. - */ -bool CompareByPositionX(const LayerInfo& a, const LayerInfo& b); - -/** - * Compares two LayerInfo entries by their global bounds top edge for sorting along the Y axis. - */ -bool CompareByPositionY(const LayerInfo& a, const LayerInfo& b); - } // namespace pagx::cli diff --git a/src/cli/main.cpp b/src/cli/main.cpp index 69324cd280..952423f2b3 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -19,9 +19,7 @@ #include #include #include -#include "cli/CommandAlign.h" #include "cli/CommandBounds.h" -#include "cli/CommandDistribute.h" #include "cli/CommandFont.h" #include "cli/CommandFormat.h" #include "cli/CommandOptimize.h" @@ -38,14 +36,12 @@ static void PrintUsage() { << "Usage: pagx [options] \n" << "\n" << "Commands:\n" - << " validate Validate PAGX structure against the specification\n" - << " render Render PAGX to an image file (supports crop and scale)\n" - << " bounds Query the precise bounds of a node or layer\n" - << " font Query font metrics or embed fonts into a PAGX file\n" - << " format Format a PAGX file (indentation and attribute ordering)\n" - << " optimize Validate, optimize, and format a PAGX file in one step\n" - << " align Align selected Layers along an edge or center line\n" - << " distribute Distribute selected Layers with equal spacing\n" + << " validate Validate PAGX structure against the specification\n" + << " render Render PAGX to an image file (supports crop and scale)\n" + << " bounds Query the precise bounds of a node or layer\n" + << " font Query font metrics or embed fonts into a PAGX file\n" + << " format Format a PAGX file (indentation and attribute ordering)\n" + << " optimize Validate, optimize, and format a PAGX file in one step\n" << "\n" << "Options:\n" << " --help, -h Show help\n" @@ -88,13 +84,6 @@ int main(int argc, char* argv[]) { if (command == "optimize") { return pagx::cli::RunOptimize(argc - 1, argv + 1); } - if (command == "align") { - return pagx::cli::RunAlign(argc - 1, argv + 1); - } - if (command == "distribute") { - return pagx::cli::RunDistribute(argc - 1, argv + 1); - } - std::cerr << "pagx: unknown command '" << command << "'\n"; std::cerr << "Run 'pagx --help' for usage.\n"; return 1; diff --git a/test/baseline/version.json b/test/baseline/version.json index 908dd6f5e8..a94605fb35 100644 --- a/test/baseline/version.json +++ b/test/baseline/version.json @@ -8527,11 +8527,6 @@ } }, "PAGXCliTest": { - "AlignCenterX": "4d590923e", - "AlignCrossHierarchy": "4d590923e", - "AlignLeft": "4d590923e", - "DistributeX": "4d590923e", - "DistributeY": "4d590923e", "OptimizeComprehensive_optimized": "4c378e29a", "OptimizeComprehensive_original": "4c378e29a", "OptimizeDedupGradient_optimized": "4c378e29a", diff --git a/test/src/PAGXCliTest.cpp b/test/src/PAGXCliTest.cpp index 389d582a3d..1e3e30dec2 100644 --- a/test/src/PAGXCliTest.cpp +++ b/test/src/PAGXCliTest.cpp @@ -23,9 +23,7 @@ #include #include #include "base/PAGTest.h" -#include "cli/CommandAlign.h" #include "cli/CommandBounds.h" -#include "cli/CommandDistribute.h" #include "cli/CommandFont.h" #include "cli/CommandFormat.h" #include "cli/CommandOptimize.h" @@ -1116,255 +1114,4 @@ CLI_TEST(PAGXCliTest, Lint_C13_SimpleRectangleMask) { EXPECT_TRUE(output.find("scrollRect") != std::string::npos); } -//============================================================================== -// Align tests -//============================================================================== - -// Helper: extract the attribute value for a given layer id from PAGX XML. -// Returns the attribute value string, or empty if not found. -static std::string GetLayerAttribute(const std::string& xml, const std::string& id, - const std::string& attr) { - // Find the Layer element with the given id. - auto idPattern = "id=\"" + id + "\""; - auto pos = xml.find(idPattern); - if (pos == std::string::npos) { - return ""; - } - // Walk backward to find the opening '' or '/>'). - auto tagEnd = xml.find('>', layerStart); - if (tagEnd == std::string::npos) { - return ""; - } - auto tag = xml.substr(layerStart, tagEnd - layerStart + 1); - - // Search for the attribute within the tag. - auto attrPattern = attr + "=\""; - auto attrPos = tag.find(attrPattern); - if (attrPos == std::string::npos) { - return ""; - } - auto valueStart = attrPos + attrPattern.size(); - auto valueEnd = tag.find('"', valueStart); - if (valueEnd == std::string::npos) { - return ""; - } - return tag.substr(valueStart, valueEnd - valueStart); -} - -CLI_TEST(PAGXCliTest, Align_Left) { - auto inputPath = CopyToTemp("align_distribute.pagx", "align_left.pagx"); - auto outputPath = TempDir() + "/align_left_out.pagx"; - auto ret = CallRun(pagx::cli::RunAlign, {"align", "--id", "a", "--id", "b", "--id", "c", - "--anchor", "left", "-o", outputPath, inputPath}); - EXPECT_EQ(ret, 0); - auto output = ReadFile(outputPath); - EXPECT_EQ(GetLayerAttribute(output, "a", "x"), "10"); - EXPECT_EQ(GetLayerAttribute(output, "b", "x"), "10"); - EXPECT_EQ(GetLayerAttribute(output, "c", "x"), "10"); - EXPECT_TRUE(RenderAndCompare({"render", outputPath}, "PAGXCliTest/AlignLeft")); -} - -CLI_TEST(PAGXCliTest, Align_Right) { - auto inputPath = CopyToTemp("align_distribute.pagx", "align_right.pagx"); - auto outputPath = TempDir() + "/align_right_out.pagx"; - auto ret = CallRun(pagx::cli::RunAlign, {"align", "--id", "a", "--id", "b", "--id", "c", - "--anchor", "right", "-o", outputPath, inputPath}); - EXPECT_EQ(ret, 0); - auto output = ReadFile(outputPath); - EXPECT_EQ(GetLayerAttribute(output, "a", "x"), "250"); - EXPECT_EQ(GetLayerAttribute(output, "b", "x"), "250"); - EXPECT_EQ(GetLayerAttribute(output, "c", "x"), "250"); -} - -CLI_TEST(PAGXCliTest, Align_Top) { - auto inputPath = CopyToTemp("align_distribute.pagx", "align_top.pagx"); - auto outputPath = TempDir() + "/align_top_out.pagx"; - auto ret = CallRun(pagx::cli::RunAlign, {"align", "--id", "a", "--id", "b", "--id", "c", - "--anchor", "top", "-o", outputPath, inputPath}); - EXPECT_EQ(ret, 0); - auto output = ReadFile(outputPath); - // a: y=0 (default, attribute omitted), b: y=0 (omitted), c: y=0 (omitted) - EXPECT_EQ(GetLayerAttribute(output, "a", "y"), ""); - EXPECT_EQ(GetLayerAttribute(output, "b", "y"), ""); - EXPECT_EQ(GetLayerAttribute(output, "c", "y"), ""); -} - -CLI_TEST(PAGXCliTest, Align_Bottom) { - auto inputPath = CopyToTemp("align_distribute.pagx", "align_bottom.pagx"); - auto outputPath = TempDir() + "/align_bottom_out.pagx"; - auto ret = CallRun(pagx::cli::RunAlign, {"align", "--id", "a", "--id", "b", "--id", "c", - "--anchor", "bottom", "-o", outputPath, inputPath}); - EXPECT_EQ(ret, 0); - auto output = ReadFile(outputPath); - EXPECT_EQ(GetLayerAttribute(output, "a", "y"), "300"); - EXPECT_EQ(GetLayerAttribute(output, "b", "y"), "300"); - EXPECT_EQ(GetLayerAttribute(output, "c", "y"), "300"); -} - -CLI_TEST(PAGXCliTest, Align_CenterX) { - auto inputPath = CopyToTemp("align_distribute.pagx", "align_center_x.pagx"); - auto outputPath = TempDir() + "/align_center_x_out.pagx"; - auto ret = CallRun(pagx::cli::RunAlign, {"align", "--id", "a", "--id", "b", "--id", "c", - "--anchor", "centerX", "-o", outputPath, inputPath}); - EXPECT_EQ(ret, 0); - auto output = ReadFile(outputPath); - EXPECT_EQ(GetLayerAttribute(output, "a", "x"), "130"); - EXPECT_EQ(GetLayerAttribute(output, "b", "x"), "130"); - EXPECT_EQ(GetLayerAttribute(output, "c", "x"), "130"); - EXPECT_TRUE(RenderAndCompare({"render", outputPath}, "PAGXCliTest/AlignCenterX")); -} - -CLI_TEST(PAGXCliTest, Align_CenterY) { - auto inputPath = CopyToTemp("align_distribute.pagx", "align_center_y.pagx"); - auto outputPath = TempDir() + "/align_center_y_out.pagx"; - auto ret = CallRun(pagx::cli::RunAlign, {"align", "--id", "a", "--id", "b", "--id", "c", - "--anchor", "centerY", "-o", outputPath, inputPath}); - EXPECT_EQ(ret, 0); - auto output = ReadFile(outputPath); - EXPECT_EQ(GetLayerAttribute(output, "a", "y"), "150"); - EXPECT_EQ(GetLayerAttribute(output, "b", "y"), "150"); - EXPECT_EQ(GetLayerAttribute(output, "c", "y"), "150"); -} - -CLI_TEST(PAGXCliTest, Align_CrossHierarchy) { - auto inputPath = CopyToTemp("align_distribute.pagx", "align_cross.pagx"); - auto outputPath = TempDir() + "/align_cross_out.pagx"; - auto ret = CallRun(pagx::cli::RunAlign, {"align", "--id", "a", "--id", "d", "--anchor", "left", - "-o", outputPath, inputPath}); - EXPECT_EQ(ret, 0); - auto output = ReadFile(outputPath); - // target=min(10,30)=10. a: stays x=10. d: global delta=-20, parent has no x transform, x=30-20=10 - EXPECT_EQ(GetLayerAttribute(output, "a", "x"), "10"); - EXPECT_EQ(GetLayerAttribute(output, "d", "x"), "10"); - EXPECT_TRUE(RenderAndCompare({"render", outputPath}, "PAGXCliTest/AlignCrossHierarchy")); -} - -CLI_TEST(PAGXCliTest, Align_XPath) { - auto inputPath = CopyToTemp("align_distribute.pagx", "align_xpath.pagx"); - auto outputPath = TempDir() + "/align_xpath_out.pagx"; - auto ret = - CallRun(pagx::cli::RunAlign, {"align", "--xpath", "//Layer[@id='a' or @id='b' or @id='c']", - "--anchor", "left", "-o", outputPath, inputPath}); - EXPECT_EQ(ret, 0); - auto output = ReadFile(outputPath); - EXPECT_EQ(GetLayerAttribute(output, "a", "x"), "10"); - EXPECT_EQ(GetLayerAttribute(output, "b", "x"), "10"); - EXPECT_EQ(GetLayerAttribute(output, "c", "x"), "10"); -} - -CLI_TEST(PAGXCliTest, Align_MissingAnchor) { - auto inputPath = TestResourcePath("align_distribute.pagx"); - auto ret = CallRun(pagx::cli::RunAlign, {"align", "--id", "a", "--id", "b", inputPath}); - EXPECT_NE(ret, 0); -} - -CLI_TEST(PAGXCliTest, Align_TooFewLayers) { - auto inputPath = TestResourcePath("align_distribute.pagx"); - auto outputPath = TempDir() + "/align_toofew_out.pagx"; - auto ret = CallRun(pagx::cli::RunAlign, - {"align", "--id", "a", "--anchor", "left", "-o", outputPath, inputPath}); - EXPECT_NE(ret, 0); -} - -CLI_TEST(PAGXCliTest, Align_InvalidAnchor) { - auto inputPath = TestResourcePath("align_distribute.pagx"); - auto ret = CallRun(pagx::cli::RunAlign, - {"align", "--id", "a", "--id", "b", "--anchor", "middle", inputPath}); - EXPECT_NE(ret, 0); -} - -CLI_TEST(PAGXCliTest, Align_IdNotFound) { - auto inputPath = TestResourcePath("align_distribute.pagx"); - auto ret = CallRun(pagx::cli::RunAlign, - {"align", "--id", "nonexistent", "--id", "a", "--anchor", "left", inputPath}); - EXPECT_NE(ret, 0); -} - -CLI_TEST(PAGXCliTest, Align_NoSelection) { - auto inputPath = TestResourcePath("align_distribute.pagx"); - auto ret = CallRun(pagx::cli::RunAlign, {"align", "--anchor", "left", inputPath}); - EXPECT_NE(ret, 0); -} - -//============================================================================== -// Distribute tests -//============================================================================== - -CLI_TEST(PAGXCliTest, Distribute_X) { - auto inputPath = CopyToTemp("align_distribute.pagx", "distribute_x.pagx"); - auto outputPath = TempDir() + "/distribute_x_out.pagx"; - auto ret = CallRun(pagx::cli::RunDistribute, {"distribute", "--id", "a", "--id", "b", "--id", "c", - "--axis", "x", "-o", outputPath, inputPath}); - EXPECT_EQ(ret, 0); - auto output = ReadFile(outputPath); - // sorted by left: a(10), b(100), c(250). firstEnd=60, lastStart=250, middleSizes=50 - // totalGap=140, gap=70. b: newLeft=130, x=130 - EXPECT_EQ(GetLayerAttribute(output, "a", "x"), "10"); - EXPECT_EQ(GetLayerAttribute(output, "b", "x"), "130"); - EXPECT_EQ(GetLayerAttribute(output, "c", "x"), "250"); - EXPECT_TRUE(RenderAndCompare({"render", outputPath}, "PAGXCliTest/DistributeX")); -} - -CLI_TEST(PAGXCliTest, Distribute_Y) { - auto inputPath = CopyToTemp("align_distribute.pagx", "distribute_y.pagx"); - auto outputPath = TempDir() + "/distribute_y_out.pagx"; - auto ret = CallRun(pagx::cli::RunDistribute, {"distribute", "--id", "a", "--id", "b", "--id", "c", - "--axis", "y", "-o", outputPath, inputPath}); - EXPECT_EQ(ret, 0); - auto output = ReadFile(outputPath); - // sorted by top: a(0), b(100), c(300). firstEnd=40, lastStart=300, middleSizes=40 - // totalGap=220, gap=110. b: newTop=150, y=150 - EXPECT_EQ(GetLayerAttribute(output, "a", "y"), ""); - EXPECT_EQ(GetLayerAttribute(output, "b", "y"), "150"); - EXPECT_EQ(GetLayerAttribute(output, "c", "y"), "300"); - EXPECT_TRUE(RenderAndCompare({"render", outputPath}, "PAGXCliTest/DistributeY")); -} - -CLI_TEST(PAGXCliTest, Distribute_XPath) { - auto inputPath = CopyToTemp("align_distribute.pagx", "distribute_xpath.pagx"); - auto outputPath = TempDir() + "/distribute_xpath_out.pagx"; - auto ret = CallRun(pagx::cli::RunDistribute, - {"distribute", "--xpath", "//Layer[@id='a' or @id='b' or @id='c']", "--axis", - "x", "-o", outputPath, inputPath}); - EXPECT_EQ(ret, 0); - auto output = ReadFile(outputPath); - EXPECT_EQ(GetLayerAttribute(output, "a", "x"), "10"); - EXPECT_EQ(GetLayerAttribute(output, "b", "x"), "130"); - EXPECT_EQ(GetLayerAttribute(output, "c", "x"), "250"); -} - -CLI_TEST(PAGXCliTest, Distribute_MissingAxis) { - auto inputPath = TestResourcePath("align_distribute.pagx"); - auto ret = CallRun(pagx::cli::RunDistribute, - {"distribute", "--id", "a", "--id", "b", "--id", "c", inputPath}); - EXPECT_NE(ret, 0); -} - -CLI_TEST(PAGXCliTest, Distribute_TooFewLayers) { - auto inputPath = TestResourcePath("align_distribute.pagx"); - auto outputPath = TempDir() + "/distribute_toofew_out.pagx"; - auto ret = CallRun(pagx::cli::RunDistribute, {"distribute", "--id", "a", "--id", "b", "--axis", - "x", "-o", outputPath, inputPath}); - EXPECT_NE(ret, 0); -} - -CLI_TEST(PAGXCliTest, Distribute_InvalidAxis) { - auto inputPath = TestResourcePath("align_distribute.pagx"); - auto ret = CallRun(pagx::cli::RunDistribute, {"distribute", "--id", "a", "--id", "b", "--id", "c", - "--axis", "z", inputPath}); - EXPECT_NE(ret, 0); -} - -CLI_TEST(PAGXCliTest, Distribute_IdNotFound) { - auto inputPath = TestResourcePath("align_distribute.pagx"); - auto ret = CallRun(pagx::cli::RunDistribute, {"distribute", "--id", "nonexistent", "--id", "a", - "--id", "b", "--axis", "x", inputPath}); - EXPECT_NE(ret, 0); -} - } // namespace pag