diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..66a369a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,66 @@ +name: Bug report +description: A diagram, script, or import path misbehaves — report it so we can fix it. +title: "[Bug]: " +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for reporting. Before submitting, check that this isn't an already-open issue. + - type: dropdown + id: area + attributes: + label: Area + description: What part of the project is affected? + options: + - A diagram example + - Style guide / skin / linter + - draw.io import + - Mermaid import + - Export (SVG / PNG) + - Documentation + - CI / tooling + - Other + validations: + required: true + - type: textarea + id: description + attributes: + label: What happened? + description: Describe the unexpected behavior. Include what you expected instead. + placeholder: "The linter accepts a diagram that breaks the accessible SVG contract…" + validations: + required: true + - type: textarea + id: repro + attributes: + label: Steps to reproduce + description: Concrete steps — commands, file names, and inputs matter here. + placeholder: | + 1. Run `python3 scripts/lint-skin.py ` + 2. … + validations: + required: true + - type: input + id: environment + attributes: + label: Environment + description: OS, Python version, agent (Claude Code / Codex / Pi) if relevant + placeholder: "macOS 14, Python 3.11, Claude Code" + - type: checkboxes + id: gates + attributes: + label: Validation gates + description: If relevant, which gates have you already run? + options: + - label: "`python3 scripts/test-lint-a11y.py`" + - label: "`python3 scripts/lint-skin.py --all --baseline`" + - label: "`python3 scripts/verify-drawio-import.py`" + - label: "`python3 scripts/verify-mermaid-import.py`" + - label: "I haven't run any gates" + - type: textarea + id: logs + attributes: + label: Relevant output + description: Paste error messages or linter findings, trimmed to what matters. + render: text \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..85c4c6c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: Security vulnerability + url: https://github.com/cathrynlavery/diagram-design/security/advisories/new + about: Report a suspected vulnerability privately — do not open a public issue for it. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..926bd55 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,45 @@ +name: Feature request +description: Suggest a new diagram type, primitive, or improvement. +title: "[Feature]: " +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: | + Ideas are welcome. Before submitting, check the README's "When *not* to use this skill" section — sometimes the right answer is a table, not a diagram. + - type: textarea + id: problem + attributes: + label: What are you trying to show? + description: Describe the situation and what the reader needs to understand. A sketch or ASCII mock helps a lot. + placeholder: "I want to show how... and the reader should see..." + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed solution + description: How could the skill serve this? A new type, a primitive, a change to an existing one, docs? + placeholder: "A new `type-*.md` for …, or an extension of …" + - type: dropdown + id: scope + attributes: + label: Scope + description: Roughly how much surface does this touch? + options: + - New diagram type (reference + 3 examples + gallery) + - New or changed primitive + - Import grammar support + - Tooling / scripts / CI + - Documentation only + - Not sure yet — help me scope it + validations: + required: true + - type: checkboxes + id: philosophy + attributes: + label: Design-system fit + description: Features in this project must fit the editorial philosophy in SKILL.md §1–§2. + options: + - label: "I've checked the anti-patterns and 'When not to use' — this still seems worth drawing" + - label: "I'm willing to contribute the implementation or pair with a maintainer" \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..58e3005 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,26 @@ +## What does this PR do? + + + +## Validation gates + + + +- [ ] `python3 scripts/test-lint-a11y.py` +- [ ] `python3 scripts/lint-skin.py --all --baseline` +- [ ] `python3 scripts/verify-sequence-oauth.py` +- [ ] `python3 scripts/verify-drawio-import.py` +- [ ] `python3 scripts/verify-mermaid-import.py` +- [ ] `git diff --exit-code` green after `python3 scripts/build-icons.py` (if icons changed) +- [ ] Docs updated in the same PR where behavior changed + +## Checklist + +- [ ] No new entries added to `scripts/lint-skin-baseline.txt` +- [ ] Generated and source files are consistent (extractor ↔ verifier ↔ reference ↔ command) +- [ ] Accessible SVG contract satisfied for new/changed examples +- [ ] Code of Conduct respected + +## Related issues + + \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 652b45b..8c2d6d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,6 +43,15 @@ jobs: id: a11y run: python scripts/test-lint-a11y.py + - name: Verify URL SVG normalization + run: python3 scripts/test-build-icons-quoted-attributes.py + + - name: Verify Devicon normalization + run: python3 scripts/test-build-icons-devicon.py + + - name: Verify mojibake repair utility + run: python3 scripts/test-fix-mojibake.py + - name: Run skin linter id: skin run: python scripts/lint-skin.py --all --baseline diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..29cc633 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,41 @@ +name: Deploy gallery to GitHub Pages + +on: + push: + branches: [ main ] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Configure Pages + uses: actions/configure-pages@v6 + + - name: Upload gallery artifact + uses: actions/upload-pages-artifact@v5 + with: + path: skills/diagram-design/assets + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v5 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f7f99e2 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,134 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or advances of + any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, + without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[github.com/cathrynlavery/diagram-design/issues](https://github.com/cathrynlavery/diagram-design/issues) +or via private vulnerability reporting ([SECURITY.md](SECURITY.md) links the +private path). All complaints will be reviewed and investigated promptly and +fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e6e6e2d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,136 @@ +# Contributing to Diagram Design + +Thanks for wanting to contribute — this project only gets better with more eyes on it. + +Please read [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) first. All contributions are expected to keep the community welcoming. + +--- + +## What this project is + +Diagram Design is an agent skill (Claude Code, Codex, Pi) that produces editorial-quality diagrams as self-contained HTML files. The repo is documentation-first: `skills/diagram-design/SKILL.md` is the index, each of the 27 diagram types has its own reference file, and the extractor scripts in `skills/diagram-design/scripts/` turn draw.io and Mermaid sources into a structured IR. + +See [README.md](README.md) for the full picture, including the design system and the import/export flows. + +--- + +## Before you start + +- **Create an issue first** for anything non-trivial (new type, behavior change, import grammar work). Small fixes and docs can go straight to a PR. +- **Work on a branch** — never commit directly to `main`. +- **Keep the scope tight.** One PR = one concern. Mixing a new diagram type with a docs rewrite makes review slow. +- **Python 3.10+ is required** for the development scripts (CI runs 3.11). + +--- + +## Validation gates + +Every validation gate below must pass before a PR is ready. They also run automatically as GitHub Actions CI (`.github/workflows/ci.yml`). + +| What it checks | Command | +|---|---| +| Accessible SVG contract (unit tests for the a11y linter) | `python3 scripts/test-lint-a11y.py` | +| Skin conformance of every example (colors, fonts, a11y) | `python3 scripts/lint-skin.py --all --baseline` | +| A single file, e.g. a new example | `python3 scripts/lint-skin.py skills/diagram-design/assets/example-my-type.html` | +| Sequence-doc consistency (ATL fragments, budgets) | `python3 scripts/verify-sequence-oauth.py` | +| draw.io import path (real extractor vs fixtures + docs sync) | `python3 scripts/verify-drawio-import.py` | +| Mermaid import path (grammars, adversarial input, caps, docs sync) | `python3 scripts/verify-mermaid-import.py` | +| Generated icon assets are up to date (`icons.html`, `primitive-icons.md`) | `python3 scripts/build-icons.py` then `git diff --exit-code` on the two generated files | + +Run them all at once before pushing: + +```bash +python3 scripts/test-lint-a11y.py \ + && python3 scripts/lint-skin.py --all --baseline \ + && python3 scripts/verify-sequence-oauth.py \ + && python3 scripts/verify-drawio-import.py \ + && python3 scripts/verify-mermaid-import.py +``` + +### If a gate fails + +- **`lint-skin.py`:** the failure message names the file, line, and category (`color`, `font-family`, `a11y`, `external-asset`, `pure-black`, `script`). Colors must come from the palette in `skills/diagram-design/references/style-guide.md`; fonts from the allowed list; diagrams must satisfy the accessible SVG contract (see below). +- **`verify-*.py`:** the extractor's real behavior no longer matches its fixture or the documentation, or the reference/command/prompt wiring drifted. Fix the source of truth — do not widen a test to avoid a failure. +- **Icon assets:** you changed `scripts/vendor/icons/` or `scripts/build-icons.py` and the generated files went stale. Rerun `python3 scripts/build-icons.py` and commit the regenerated files. + +Do **not** add a file to `scripts/lint-skin-baseline.txt` to get your example through. The baseline exists only for legacy pre-2.0 examples that legitimately predate the current skin, and it still receives a11y checks. + +--- + +## The accessible SVG contract (a11y) + +Every diagram `` must satisfy the contract enforced by the linter: + +1. `role="img"` and `aria-labelledby` naming the `` **and** ``. +2. `` is the **first child** of `<svg>` (before `<defs>`). +3. IDs are prefixed per diagram and variant: `<slug>-title` / `<slug>-desc` — for `example-loop-dark.html` the slug is `loop-dark`, so the IDs are `loop-dark-title` / `loop-dark-desc`. Bare `title`/`desc` IDs and duplicate IDs are rejected. +4. `<title>` is the short subject name (≈ the `<h1>`, ≤ 60 chars); `<desc>` is one sentence describing the *content*, not the geometry. +5. Purely decorative SVGs (`aria-hidden="true"`) are exempt. + +The contract lives in `scripts/lint-skin.py` (`lint_accessible_svgs`) and is unit-tested by `scripts/test-lint-a11y.py`. When in doubt, pattern-match an existing example. + +--- + +## Working on examples (diagrams) + +Every diagram type ships three variants: minimal light (`example-<type>.html`), minimal dark (`example-<type>-dark.html`), and full editorial (`example-<type>-full.html`). + +1. Copy the closest template (`skills/diagram-design/assets/template.html`, `template-dark.html`, or `template-full.html`). +2. Load the matching `references/type-<name>.md` and follow its layout conventions. +3. Replace the eyebrow, h1, and SVG body; replace the `[diagram-slug]` placeholders with your file's slug and keep the `<title>`/`<desc>` slots filled. +4. Run the taste gate in `SKILL.md` §9, then the linter: + +```bash +python3 scripts/lint-skin.py skills/diagram-design/assets/example-my-type.html +``` + +New examples should be added to the gallery (`assets/index.html`) so they stay browsable. + +## Adding a new diagram type + +1. Write `skills/diagram-design/references/type-<name>.md` — layout conventions, anti-patterns, and a worked pattern for that type. Mirror an existing reference's structure. +2. Add the row to the selection table in `skills/diagram-design/SKILL.md` §3. +3. Add the three example variants (see above) and register them in the gallery. +4. Run the full gate suite — new examples are linted automatically by `--all`. + +## Changing the icon set + +Icons are generated, never hand-edited: + +1. Add or replace the source SVG in `scripts/vendor/icons/<source>/` (`tabler/`, `simple/`, …). Keep license provenance in `THIRD_PARTY_LICENSES.md` accurate. +2. Regenerate and verify: + +```bash +python3 scripts/build-icons.py +git diff --exit-code -- skills/diagram-design/assets/icons.html skills/diagram-design/references/primitive-icons.md +``` + +## Touching the import paths + +- draw.io: `skills/diagram-design/scripts/drawio_extract.py` — must pass `scripts/verify-drawio-import.py`, which drives the extractor against `scripts/fixtures/sample-architecture.drawio` in all four container formats (raw XML, deflate+base64, PNG-embedded, SVG-embedded). +- Mermaid: `skills/diagram-design/scripts/mermaid_extract.py` — must pass `scripts/verify-mermaid-import.py`, which covers every supported grammar, multi-block Markdown, adversarial labels, trust-boundary behavior, resource caps, and named failures. + +Both scripts treat their input as **untrusted data** — they never render, fetch, or execute source content. Keep it that way. If you add a grammar or a new security boundary, extend the corresponding verifier with a fixture before merging. + +Documentation and wiring must stay in sync: the import references, `SKILL.md` §11, the slash commands in `commands/`, and the Pi prompt templates in `prompts/` each describe the same flows. The verifiers check this — keep both sides updated in one PR. + +## Documentation + +Most of this repo *is* documentation. When behavior changes, update the affected reference files and the README in the same PR. Loose ends here are what the verifiers and reviewers will catch. + +--- + +## Commit and PR conventions + +Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/): `type(scope): summary`, e.g. `fix(import): support current Mermaid syntax`, `ci: verify Mermaid imports`, `docs(onboarding): clarify the URL flow`. Keep summaries short and imperative. + +A good PR: + +- has a clear title and a description that says *what* changed and *why*; +- mentions how you tested it (which gates you ran); +- keeps generated and source files consistent (extractor + verifier + reference + command in one change); +- is rebased on `main` and green on CI. + +## Questions? + +Open a discussion or comment on the relevant issue. If it's about security, use the private reporting path in [SECURITY.md](SECURITY.md). \ No newline at end of file diff --git a/README.md b/README.md index 7879883..e41ae32 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ All 27 diagrams ship in three variants: minimal light, minimal dark, and full-ed </tr> </table> -**Browse the live gallery:** open [`skills/diagram-design/assets/index.html`](skills/diagram-design/assets/index.html) in your browser to flip through all 27 diagrams with light / dark / full-editorial tabs. +**Browse the live gallery:** [cathrynlavery.github.io/diagram-design](https://cathrynlavery.github.io/diagram-design/) — or open [`skills/diagram-design/assets/index.html`](skills/diagram-design/assets/index.html) locally to flip through all 27 diagrams with light / dark / full-editorial tabs. --- @@ -408,6 +408,12 @@ Before drawing, ask: *would a reader learn more from this than from a well-writt --- +## Contributing + +Contributions are welcome — new diagram types, import grammar support, examples, docs, and tooling. See [CONTRIBUTING.md](CONTRIBUTING.md) for the validation gates and workflows, and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for community standards. + +--- + ## About Made by **Cathryn Lavery** — founder of [BestSelf.co](https://bestself.co?utm_source=diagram-design&utm_medium=readme&utm_campaign=github&utm_content=bio). I write about AI, entrepreneurship, and designing nice-looking things at [littlemight.com](https://littlemight.com?utm_source=diagram-design&utm_medium=readme&utm_campaign=github&utm_content=bio) — blog + newsletter. diff --git a/scripts/build-icons.py b/scripts/build-icons.py index 4ee8d63..b146842 100644 --- a/scripts/build-icons.py +++ b/scripts/build-icons.py @@ -246,11 +246,13 @@ def normalize_url(raw: str) -> str: - White / near-white fills (#fff, #ffffff, white) are kept as-is — they act as contrast cutouts in 2-colour logos (e.g. white letter on coloured circle). Every other colour becomes currentColor. - - inline style="fill:#..." is rewritten to a fill= attribute. + - inline fill/stroke colours are normalized without discarding other styles. - Inkscape layer translate() transforms are stripped (canvas offset artefact). """ - vb_match = re.search(r'viewBox="([^"]+)"', raw, re.IGNORECASE) - viewbox = vb_match.group(1) if vb_match else "0 0 128 128" + vb_match = re.search( + r'\bviewBox\s*=\s*(["\'])(.*?)\1', raw, flags=re.IGNORECASE + ) + viewbox = vb_match.group(2).strip() if vb_match else "0 0 128 128" inner = re.search(r"<svg\b[^>]*>(.*?)</svg>", raw, flags=re.DOTALL | re.IGNORECASE) if not inner: raise ValueError("no <svg> in payload") @@ -261,27 +263,55 @@ def normalize_url(raw: str) -> str: # Strip Inkscape canvas-offset translate transforms from group wrappers body = re.sub(r'\btransform="translate\([^"]+\)"', '', body) - def _rewrite_fill(m: re.Match) -> str: + def _normalized_fill(value: str) -> str: """Keep white; turn everything else into currentColor.""" - hex_val = m.group(1).lower().strip() + hex_val = value.lower().strip() if hex_val in ("#fff", "#ffffff", "white", "#fefefe", "#fdfdfd"): - return 'fill="#fff"' - return 'fill="currentColor"' + return "#fff" + return "currentColor" - # Rewrite inline style="fill:#rrggbb" → standalone fill= attribute + def _normalize_inline_style(match: re.Match) -> str: + quote, declarations = match.group(1), match.group(2) + declarations = re.sub( + r'(\bfill\s*:\s*)(#[0-9a-fA-F]{3,8}|white)\b', + lambda fill: f"{fill.group(1)}{_normalized_fill(fill.group(2))}", + declarations, + flags=re.IGNORECASE, + ) + declarations = re.sub( + r'(\bstroke\s*:\s*)#[0-9a-fA-F]{3,8}\b', + lambda stroke: f"{stroke.group(1)}currentColor", + declarations, + flags=re.IGNORECASE, + ) + return f"style={quote}{declarations}{quote}" + + # Normalize supported inline colours while preserving unrelated declarations. body = re.sub( - r'style="[^"]*fill:\s*(#[0-9a-fA-F]{3,8}|white)[^"]*"', - lambda m: _rewrite_fill(re.search(r'(#[0-9a-fA-F]{3,8}|white)', m.group(0))), + r'style\s*=\s*(["\'])(.*?)\1', + _normalize_inline_style, body, + flags=re.IGNORECASE, ) - # Rewrite standalone fill="#rrggbb" attributes + # Rewrite standalone fill="#rrggbb" attributes. body = re.sub( - r'\bfill="(#[0-9a-fA-F]{3,8}|white)"', - _rewrite_fill, + r'\bfill\s*=\s*(["\'])(#[0-9a-fA-F]{3,8}|white)\1', + lambda m: f'fill="{_normalized_fill(m.group(2))}"', body, + flags=re.IGNORECASE, + ) + body = re.sub( + r'\bstroke\s*=\s*(["\'])#[0-9a-fA-F]{3,8}\1', + 'stroke="currentColor"', + body, + flags=re.IGNORECASE, + ) + body = re.sub( + r'\bfill\s*=\s*(["\'])none\1', + '', + body, + flags=re.IGNORECASE, ) - body = re.sub(r'\bstroke="#[0-9a-fA-F]{3,8}"', 'stroke="currentColor"', body) - body = re.sub(r'\bfill="none"', '', body) body = re.sub(r"\s+", " ", body).strip() return ( f'<svg aria-hidden="true" width="24" height="24" viewBox="{viewbox}" ' @@ -292,6 +322,10 @@ def normalize_url(raw: str) -> str: def normalize_devicon(raw: str) -> str: """Devicon SVGs have colored fills and a 128x128 or 32x32 viewBox. Strip fills/classes and rewrite to 24x24 currentColor.""" + viewbox_match = re.search( + r'\bviewBox\s*=\s*(["\'])(.*?)\1', raw, flags=re.IGNORECASE + ) + viewbox = viewbox_match.group(2).strip() if viewbox_match else "0 0 128 128" inner = re.search(r"<svg\b[^>]*>(.*?)</svg>", raw, flags=re.DOTALL | re.IGNORECASE) if not inner: raise ValueError("no <svg> in Devicon payload") @@ -303,7 +337,7 @@ def normalize_devicon(raw: str) -> str: body = re.sub(r'\bclass="[^"]*"', 'fill="currentColor"', body) body = re.sub(r"\s+", " ", body).strip() return ( - '<svg aria-hidden="true" width="24" height="24" viewBox="0 0 128 128" ' + f'<svg aria-hidden="true" width="24" height="24" viewBox="{viewbox}" ' f'fill="currentColor">{body}</svg>' ) diff --git a/scripts/fix-mojibake.py b/scripts/fix-mojibake.py index 856cf17..4334fa7 100644 --- a/scripts/fix-mojibake.py +++ b/scripts/fix-mojibake.py @@ -17,8 +17,10 @@ PAIRS = [ ('…', '…'), # horizontal ellipsis ('•', '•'), # bullet ('→', '→'), # right arrow - ('↑', '←'), # left arrow - ('↑', '↑'), # up arrow (same prefix collision - rare) + # CP1252 leaves byte 0x90 undefined, so spell the left-arrow corruption + # with a Unicode escape instead of embedding an invisible control character. + ('\u00e2\u2020\u0090', '←'), # left arrow + ('↑', '↑'), # up arrow ('↓', '↓'), # down arrow ('≤', '≤'), # less-than-or-equal ('≥', '≥'), # greater-than-or-equal diff --git a/scripts/test-build-icons-devicon.py b/scripts/test-build-icons-devicon.py new file mode 100755 index 0000000..ecb57e5 --- /dev/null +++ b/scripts/test-build-icons-devicon.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +"""Regression tests for Devicon viewBox preservation.""" + +from __future__ import annotations + +import importlib.util +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent.parent +BUILD = ROOT / "scripts" / "build-icons.py" + + +def load_build_module(): + sys.dont_write_bytecode = True + spec = importlib.util.spec_from_file_location("diagram_design_build_icons", BUILD) + if spec is None or spec.loader is None: + raise AssertionError("could not load build-icons.py") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def main() -> int: + build_icons = load_build_module() + svg = '<svg viewBox="0 0 32 32"><path fill="#123456" d="M0 0h32v32H0z"/></svg>' + normalized = build_icons.normalize_devicon(svg) + if 'viewBox="0 0 32 32"' not in normalized: + raise AssertionError(f"source viewBox was not preserved: {normalized}") + if 'fill="#123456"' in normalized: + raise AssertionError("source fill escaped currentColor normalization") + if 'fill="currentColor"' not in normalized: + raise AssertionError("normalized icon does not inherit currentColor") + print("PASS: Devicon normalization preserves non-128 source viewBoxes") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/test-build-icons-quoted-attributes.py b/scripts/test-build-icons-quoted-attributes.py new file mode 100755 index 0000000..df5001f --- /dev/null +++ b/scripts/test-build-icons-quoted-attributes.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +"""Regression tests for quote-independent URL SVG normalization.""" + +from __future__ import annotations + +import importlib.util +import re +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent.parent +BUILD = ROOT / "scripts" / "build-icons.py" + + +def load_build_module(): + sys.dont_write_bytecode = True + spec = importlib.util.spec_from_file_location("diagram_design_build_icons", BUILD) + if spec is None or spec.loader is None: + raise AssertionError("could not load build-icons.py") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def main() -> int: + build_icons = load_build_module() + svg = ( + "<svg viewBox='0 0 32 16'>" + "<path style='fill:#123456;stroke:#654321;opacity:.5' d='M0 0h32v16H0z'/>" + "<circle fill='#abcdef' stroke='#111111' cx='8' cy='8' r='4'/>" + "</svg>" + ) + normalized = build_icons.normalize_url(svg) + if 'viewBox="0 0 32 16"' not in normalized: + raise AssertionError(f"single-quoted viewBox was not preserved: {normalized}") + if any(color in normalized for color in ("#123456", "#654321", "#abcdef", "#111111")): + raise AssertionError(f"source colors escaped normalization: {normalized}") + path = re.search(r"<path\b([^>]*)>", normalized) + if path is None: + raise AssertionError(f"normalized path is missing: {normalized}") + path_attributes = path.group(1) + for declaration in ("fill:currentColor", "stroke:currentColor", "opacity:.5"): + if declaration not in path_attributes: + raise AssertionError( + f"path lost or failed to normalize {declaration}: {normalized}" + ) + if ( + 'fill="currentColor"' not in normalized + or 'stroke="currentColor"' not in normalized + ): + raise AssertionError(f"standalone quoted colours were not normalized: {normalized}") + print("PASS: URL SVG normalization handles single-quoted attributes") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/test-fix-mojibake.py b/scripts/test-fix-mojibake.py new file mode 100644 index 0000000..da4940e --- /dev/null +++ b/scripts/test-fix-mojibake.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +"""Regression tests for the mojibake repair utility.""" + +from __future__ import annotations + +import subprocess +import sys +import tempfile +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent.parent +SCRIPT = ROOT / "scripts" / "fix-mojibake.py" + + +def main() -> int: + # These are the strings produced when UTF-8 arrow bytes are decoded as + # Windows-1252. Byte 0x90 is undefined in CP1252, hence the explicit escape. + source = "right →, left \u00e2\u2020\u0090, up ↑, down ↓\n" + expected = "right →, left ←, up ↑, down ↓\n" + with tempfile.TemporaryDirectory(prefix="diagram-design-mojibake-") as directory: + path = Path(directory) / "fixture.md" + path.write_text(source, encoding="utf-8") + result = subprocess.run( + [sys.executable, str(SCRIPT), str(path)], + capture_output=True, + text=True, + check=False, + ) + if result.returncode != 0: + raise AssertionError(f"repair failed: {result.stdout}{result.stderr}") + actual = path.read_text(encoding="utf-8") + if actual != expected: + raise AssertionError(f"expected {expected!r}, got {actual!r}") + print("PASS: mojibake arrow sequences repair to the intended directions") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/skills/diagram-design/assets/icons.html b/skills/diagram-design/assets/icons.html index d6567e8..da96e06 100644 --- a/skills/diagram-design/assets/icons.html +++ b/skills/diagram-design/assets/icons.html @@ -212,7 +212,7 @@ </div></section> <section class="cat"><h2>Statistical tools</h2><div class="grid"> <div class="cell"><div class="icon"><svg aria-hidden="true" width="24" height="24" viewBox="0 0 128 128" fill="currentColor"><path fill="currentColor" d="M125 80.5c-1.7 0-3.1 1.3-3.1 3s1.3 3 3.1 3c1.7 0 3-1.3 3-3s-1.3-3-3-3zm-.1.5c1.4 0 2.4 1.1 2.4 2.5s-1 2.5-2.3 2.5c-1.4 0-2.4-1.1-2.4-2.5s.9-2.4 2.3-2.5zm-1 .8v3.4h.6v-1.5h.6l.9 1.5h.6l-.9-1.6c.6 0 .8-.4.8-.9 0-.6-.4-.9-1.2-.9h-1.4zm.5 1.5v-1.1h.7c.5 0 .7.2.7.6s-.3.6-.6.6l-.8-.1zM31.3 93.7c-1.6-1.2-3.5-2.2-6.1-3-3.3-1.1-8.1-2.2-10-2.9-1.2-.5-1.7-1.2-1.7-2.2 0-1.4 1.7-2.2 3.7-2.3 3.3.1 4.3 2 4.5 3.3h9.6v7.1zm12 8.9h7.1c6.2-.1 11.2-3.2 13.7-7.7.2-.4.4-.7.6-1.1.4.5.7.7 1.1 1.1 1.7 1.5 3.9 2.6 6.2 3.3 3.7 1.3 6.6 2.2 7.6 2.6 1.9.8 3.1 1.4 3.1 3.1 0 2-2.2 2.8-4.5 2.9-3.1-.1-6.2-1.6-6.1-4.7H60c.6 8.1 8.6 14.4 18 14.5 5.7-.1 11-1.7 14.4-5.8 3.4 3.5 7.4 5.7 13 5.8 8.9-.1 18.1-4.5 18.2-13.9 0-3-.8-5.6-2.7-7.8-1.7-2-4.1-3.3-7.4-4.2-3.3-1.1-8.1-2.2-10-2.9-1.2-.5-1.7-1.2-1.7-2.2 0-1.4 1.7-2.2 3.7-2.3 3.3.1 4.3 2 4.5 3.3h11.6c.1-8-6.6-13.3-16.1-13.3-7.1 0-11.9 2.7-14.3 6.6-2.6-4.1-7.8-6.6-14.3-6.6-6.8 0-11.5 2.6-14 6.3-2.8-3.6-7.4-5.9-13-5.9H31.3v6.1c-2.6-4.1-7.8-6.5-14.2-6.5-9.9 0-15.3 5.5-15.8 11.8 0 3.9 1.9 7.4 4.8 9.9 1.7 1.5 3.7 2.6 6 3.3 3.7 1.3 6.6 2.2 7.6 2.6 1.9.8 3.1 1.4 3.1 3.1 0 2-2.3 2.8-4.5 2.9-3.1-.1-6.2-1.6-6.1-4.7H-.1c.6 8.1 8.6 14.4 18 14.5 5.2-.1 10-1.5 13.2-4.5v3.8h12l.2-13.4zm0-18.9h6.5c2.4 0 4.5 2 4.5 4.5s-2 4.5-4.5 4.5h-6.5v-9zM92.5 95c-1.7-2-4.1-3.3-7.4-4.2-3.3-1.1-8.1-2.2-9.9-2.9-1.3-.5-1.9-1.2-1.9-2.2 0-1.4 1.7-2.2 3.7-2.3 3.4.1 4.3 2 4.5 3.3h8.2c.5 3.2 2.1 6.1 4.7 8.3 1.7 1.5 3.8 2.6 6.1 3.3 3.7 1.3 6.6 2.2 7.5 2.6 2 .8 3.1 1.4 3.1 3.1 0 2-2.3 2.8-4.5 2.9-3.1-.1-6.2-1.6-6.1-4.7H95c0-2.9-.9-5.3-2.5-7.2zM.1 11.2v3.5H25v-3.5H.1zm28.3 0v3.5H64s-3.6-3.5-8.4-3.5H28.4zm42.6 0v3.5h21.5l-1.3-3.5H71zm36.9 0l-1.3 3.5h21.3v-3.5h-20zM.1 18v3.5H25V18H.1zm28.3 0v3.5H68s-.5-2.7-1.3-3.5H28.4zM71 18v3.5h23.8L93.6 18H71zm34.3 0l-1.2 3.5h23.7V18h-22.5zm-98 6.8v3.5H18v-3.5H7.3zm28.3 0v3.5h10.8v-3.5H35.6zm21.4 0v3.5h10.8s.7-1.9.7-3.5H57zm21.2 0v3.5h19.1L96 24.8H78.2zm24.8 0l-1.3 3.5h19.2v-3.5H103zM7.3 31.7v3.5H18v-3.5H7.3zm28.3 0v3.5h27.6s2.3-1.8 3-3.5H35.6zm42.6 0v3.5H89v-1.9l.7 1.9h19.8l.7-1.9v1.9H121v-3.5h-20.3l-1.1 3-1.1-3H78.2zM7.3 38.5V42H18v-3.5H7.3zm28.3 0V42h30.6c-.7-1.7-3-3.5-3-3.5H35.6zm42.6 0V42H89v-3.5H78.2zm12.7 0l1.3 3.5H107l1.2-3.5H90.9zm19.3 0V42H121v-3.5h-10.8zM7.3 45.3v3.5H18v-3.5H7.3zm28.3 0v3.5h10.8v-3.5H35.6zm21.4 0v3.5h11.5c0-1.7-.7-3.5-.7-3.5H57zm21.2 0v3.5H89v-3.5H78.2zm15.2 0l1.3 3.5h9.9l1.3-3.5H93.4zm16.8 0v3.5H121v-3.5h-10.8zM.3 52.1v3.5h24.9v-3.5H.3zm28.1 0v3.5h38.4c.8-.8 1.3-3.5 1.3-3.5H28.4zm42.8 0v3.5H89v-3.5H71.2zm24.6 0l1.3 3.5h5.1l1.2-3.5h-7.6zm14.4 0v3.5h17.9v-3.5h-17.9zM.3 58.9v3.5h24.9v-3.5H.3zm28.1 0v3.5h27.1c4.8 0 8.4-3.5 8.4-3.5H28.4zm42.8 0v3.5H89v-3.5H71.2zm27.1 0l1.3 3.5h.2l1.3-3.5h-2.8zm11.9 0v3.5h17.9v-3.5h-17.9z"/></svg></div><div class="name">spss</div></div> -<div class="cell"><div class="icon"><svg aria-hidden="true" width="24" height="24" viewBox="0 0 129.24898 53.067852" fill="currentColor"><defs id="defs2322" /> <g id="layer1"> <path d="M 18.46875,0 C 16.638866,-0.041377236 14.748438,0.1725 12.8125,0.65625 C 3.86,2.8925 -6.16125,14.40875 4.78125,27.6875 L 11.3125,35.59375 L 13.15625,37.84375 C 14.39375,39.315 16.41875,39.28875 17.90625,38.0625 C 19.40125,36.829998 19.82625,34.80875 18.59375,33.3125 C 18.592173,33.310397 18.071121,32.679752 17.84375,32.40625 L 17.875,32.40625 C 17.402936,31.838361 17.300473,31.743127 16.8125,31.15625 C 14.448752,28.313409 11.75,25.03125 11.75,25.03125 C 6.9987503,19.265 9.11875,12.14125 15.5625,8.09375 C 21.24,4.5275001 31.65875,5.80125 35.25,11.65625 C 32.988202,4.9805465 26.398246,0.17930136 18.46875,0 z M 19.78125,13.9375 C 18.937031,13.90875 18.055625,14.230625 17.3125,14.84375 C 15.815001,16.0775 15.39375,18.10125 16.625,19.59375 C 16.627499,19.597501 16.77625,19.7825 17.03125,20.09375 C 19.863614,23.496657 23.625,28.0625 23.625,28.0625 C 28.3775,33.82875 26.25625,40.92125 19.8125,44.96875 C 14.136251,48.53375 3.71625,47.2575 0.125,41.40625 C 2.90875,49.618751 12.23875,54.9875 22.5625,52.40625 C 31.5175,50.166248 41.53625,38.6825 30.59375,25.40625 L 22.9375,16.15625 L 22.0625,15.0625 C 21.44375,14.326875 20.625469,13.96625 19.78125,13.9375 z " fill="currentColor" id="path2440" /> <path d="M 53.53125,6.3125 C 47.8625,6.3125 41.374998,9.2362506 41.375,16.28125 C 41.375,22.9875 46.708752,24.869999 52,26.15625 C 57.355,27.4425 62.656249,28.187498 62.65625,32.65625 C 62.65625,37.05875 58.121251,37.875002 54.78125,37.875 C 50.3725,37.875 46.218751,36.27125 46.03125,31.125 L 40.6875,31.125 C 41,39.79375 47.161249,42.968749 54.46875,42.96875 C 61.085,42.96875 68.343749,40.27125 68.34375,31.9375 C 68.34375,25.16375 63.041251,23.25625 57.6875,21.96875 C 52.7125,20.6825 47.031249,20.00625 47.03125,15.875 C 47.03125,12.3525 50.75625,11.40625 53.96875,11.40625 C 57.49875,11.40625 61.151251,12.810001 61.53125,17.28125 L 66.875,17.28125 C 66.435,8.74625 60.712498,6.3125001 53.53125,6.3125 z M 84.40625,6.3125 C 77.159998,6.3125001 70.90625,9.3625 70.59375,18.03125 L 75.9375,18.03125 C 76.190003,12.883749 79.5275,11.40625 84.0625,11.40625 C 87.466253,11.40625 91.3125,12.20625 91.3125,17.21875 C 91.312501,21.553749 86.2975,21.155 80.375,22.375 C 74.833751,23.526251 69.34375,25.23 69.34375,33.15625 C 69.343748,40.133748 74.20125,42.96875 80.125,42.96875 C 84.656249,42.968749 88.6025,41.255 91.5625,37.53125 C 91.562499,41.322498 93.3525,42.96875 96.125,42.96875 C 97.823751,42.968749 98.9925,42.60875 99.9375,42 L 99.9375,37.53125 C 99.244997,37.802498 98.7525,37.875 98.3125,37.875 C 96.612501,37.875002 96.625,36.68 96.625,33.96875 L 96.625,15.9375 C 96.624998,7.7424996 90.265,6.3125 84.40625,6.3125 z M 112.40625,6.3125 C 106.7375,6.3125 100.25,9.2362506 100.25,16.28125 C 100.25,22.9875 105.61625,24.869999 110.90625,26.15625 C 116.2625,27.4425 121.5625,28.187498 121.5625,32.65625 C 121.5625,37.05875 117.02625,37.875002 113.6875,37.875 C 109.2775,37.875 105.125,36.27125 104.9375,31.125 L 99.5625,31.125 C 99.87625,39.79375 106.06875,42.968749 113.375,42.96875 C 119.9875,42.96875 127.21875,40.27125 127.21875,31.9375 C 127.21875,25.16375 121.91625,23.25625 116.5625,21.96875 C 111.58875,20.6825 105.9375,20.00625 105.9375,15.875 C 105.9375,12.3525 109.63125,11.40625 112.84375,11.40625 C 116.37,11.40625 120.025,12.810001 120.40625,17.28125 L 125.78125,17.28125 C 125.3425,8.74625 119.59,6.3125001 112.40625,6.3125 z M 91.25,24.0625 L 91.25,29.96875 C 91.25,33.1525 88.36875,37.875 81.3125,37.875 C 78.040002,37.875002 75,36.51375 75,32.71875 C 75.000003,28.452501 78.0375,27.115 81.5625,26.4375 C 85.15375,25.761251 89.1725,25.6875 91.25,24.0625 z M 38.21875,39.40625 C 37.088748,39.406249 36.125,40.28375 36.125,41.46875 C 36.125001,42.658749 37.08875,43.53125 38.21875,43.53125 C 39.338748,43.53125 40.28125,42.65875 40.28125,41.46875 C 40.281252,40.283749 39.33875,39.40625 38.21875,39.40625 z M 127.15625,39.40625 C 126.0225,39.406249 125.0625,40.285 125.0625,41.46875 C 125.0625,42.66 126.0225,43.53125 127.15625,43.53125 C 128.275,43.53125 129.25,42.66 129.25,41.46875 C 129.25,40.285 128.275,39.40625 127.15625,39.40625 z M 38.21875,39.75 C 39.146248,39.750002 39.875,40.49 39.875,41.46875 C 39.875,42.456249 39.14625,43.1875 38.21875,43.1875 C 37.273748,43.187501 36.53125,42.45625 36.53125,41.46875 C 36.53125,40.489999 37.27375,39.75 38.21875,39.75 z M 127.15625,39.75 C 128.08375,39.750002 128.84375,40.49125 128.84375,41.46875 C 128.84375,42.4575 128.08375,43.1875 127.15625,43.1875 C 126.21,43.187499 125.5,42.4575 125.5,41.46875 C 125.5,40.49125 126.21,39.75 127.15625,39.75 z M 37.40625,40.28125 L 37.40625,42.65625 L 37.78125,42.65625 L 37.78125,41.625 L 38.1875,41.625 L 38.8125,42.65625 L 39.21875,42.65625 L 38.53125,41.59375 C 38.88375,41.553751 39.15625,41.395 39.15625,40.96875 C 39.156251,40.49875 38.8775,40.28125 38.3125,40.28125 L 37.40625,40.28125 z M 126.375,40.28125 L 126.375,42.65625 L 126.71875,42.65625 L 126.71875,41.625 L 127.15625,41.625 L 127.78125,42.65625 L 128.1875,42.65625 L 127.5,41.59375 C 127.84625,41.554998 128.125,41.395 128.125,40.96875 C 128.125,40.49875 127.8425,40.28125 127.28125,40.28125 L 126.375,40.28125 z M 37.78125,40.59375 L 38.28125,40.59375 C 38.528749,40.593749 38.78125,40.6425 38.78125,40.9375 C 38.78125,41.300001 38.5275,41.3125 38.21875,41.3125 L 37.78125,41.3125 L 37.78125,40.59375 z M 126.71875,40.59375 L 127.21875,40.59375 C 127.47125,40.593749 127.75,40.64125 127.75,40.9375 C 127.75,41.300001 127.4625,41.3125 127.15625,41.3125 L 126.71875,41.3125 L 126.71875,40.59375 z " fill="currentColor" id="path2448" /> </g></svg></div><div class="name">sas</div></div> +<div class="cell"><div class="icon"><svg aria-hidden="true" width="24" height="24" viewBox="0 0 129.24898 53.067852" fill="currentColor"><defs id="defs2322" /> <g id="layer1"> <path d="M 18.46875,0 C 16.638866,-0.041377236 14.748438,0.1725 12.8125,0.65625 C 3.86,2.8925 -6.16125,14.40875 4.78125,27.6875 L 11.3125,35.59375 L 13.15625,37.84375 C 14.39375,39.315 16.41875,39.28875 17.90625,38.0625 C 19.40125,36.829998 19.82625,34.80875 18.59375,33.3125 C 18.592173,33.310397 18.071121,32.679752 17.84375,32.40625 L 17.875,32.40625 C 17.402936,31.838361 17.300473,31.743127 16.8125,31.15625 C 14.448752,28.313409 11.75,25.03125 11.75,25.03125 C 6.9987503,19.265 9.11875,12.14125 15.5625,8.09375 C 21.24,4.5275001 31.65875,5.80125 35.25,11.65625 C 32.988202,4.9805465 26.398246,0.17930136 18.46875,0 z M 19.78125,13.9375 C 18.937031,13.90875 18.055625,14.230625 17.3125,14.84375 C 15.815001,16.0775 15.39375,18.10125 16.625,19.59375 C 16.627499,19.597501 16.77625,19.7825 17.03125,20.09375 C 19.863614,23.496657 23.625,28.0625 23.625,28.0625 C 28.3775,33.82875 26.25625,40.92125 19.8125,44.96875 C 14.136251,48.53375 3.71625,47.2575 0.125,41.40625 C 2.90875,49.618751 12.23875,54.9875 22.5625,52.40625 C 31.5175,50.166248 41.53625,38.6825 30.59375,25.40625 L 22.9375,16.15625 L 22.0625,15.0625 C 21.44375,14.326875 20.625469,13.96625 19.78125,13.9375 z " style="fill:currentColor;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path2440" /> <path d="M 53.53125,6.3125 C 47.8625,6.3125 41.374998,9.2362506 41.375,16.28125 C 41.375,22.9875 46.708752,24.869999 52,26.15625 C 57.355,27.4425 62.656249,28.187498 62.65625,32.65625 C 62.65625,37.05875 58.121251,37.875002 54.78125,37.875 C 50.3725,37.875 46.218751,36.27125 46.03125,31.125 L 40.6875,31.125 C 41,39.79375 47.161249,42.968749 54.46875,42.96875 C 61.085,42.96875 68.343749,40.27125 68.34375,31.9375 C 68.34375,25.16375 63.041251,23.25625 57.6875,21.96875 C 52.7125,20.6825 47.031249,20.00625 47.03125,15.875 C 47.03125,12.3525 50.75625,11.40625 53.96875,11.40625 C 57.49875,11.40625 61.151251,12.810001 61.53125,17.28125 L 66.875,17.28125 C 66.435,8.74625 60.712498,6.3125001 53.53125,6.3125 z M 84.40625,6.3125 C 77.159998,6.3125001 70.90625,9.3625 70.59375,18.03125 L 75.9375,18.03125 C 76.190003,12.883749 79.5275,11.40625 84.0625,11.40625 C 87.466253,11.40625 91.3125,12.20625 91.3125,17.21875 C 91.312501,21.553749 86.2975,21.155 80.375,22.375 C 74.833751,23.526251 69.34375,25.23 69.34375,33.15625 C 69.343748,40.133748 74.20125,42.96875 80.125,42.96875 C 84.656249,42.968749 88.6025,41.255 91.5625,37.53125 C 91.562499,41.322498 93.3525,42.96875 96.125,42.96875 C 97.823751,42.968749 98.9925,42.60875 99.9375,42 L 99.9375,37.53125 C 99.244997,37.802498 98.7525,37.875 98.3125,37.875 C 96.612501,37.875002 96.625,36.68 96.625,33.96875 L 96.625,15.9375 C 96.624998,7.7424996 90.265,6.3125 84.40625,6.3125 z M 112.40625,6.3125 C 106.7375,6.3125 100.25,9.2362506 100.25,16.28125 C 100.25,22.9875 105.61625,24.869999 110.90625,26.15625 C 116.2625,27.4425 121.5625,28.187498 121.5625,32.65625 C 121.5625,37.05875 117.02625,37.875002 113.6875,37.875 C 109.2775,37.875 105.125,36.27125 104.9375,31.125 L 99.5625,31.125 C 99.87625,39.79375 106.06875,42.968749 113.375,42.96875 C 119.9875,42.96875 127.21875,40.27125 127.21875,31.9375 C 127.21875,25.16375 121.91625,23.25625 116.5625,21.96875 C 111.58875,20.6825 105.9375,20.00625 105.9375,15.875 C 105.9375,12.3525 109.63125,11.40625 112.84375,11.40625 C 116.37,11.40625 120.025,12.810001 120.40625,17.28125 L 125.78125,17.28125 C 125.3425,8.74625 119.59,6.3125001 112.40625,6.3125 z M 91.25,24.0625 L 91.25,29.96875 C 91.25,33.1525 88.36875,37.875 81.3125,37.875 C 78.040002,37.875002 75,36.51375 75,32.71875 C 75.000003,28.452501 78.0375,27.115 81.5625,26.4375 C 85.15375,25.761251 89.1725,25.6875 91.25,24.0625 z M 38.21875,39.40625 C 37.088748,39.406249 36.125,40.28375 36.125,41.46875 C 36.125001,42.658749 37.08875,43.53125 38.21875,43.53125 C 39.338748,43.53125 40.28125,42.65875 40.28125,41.46875 C 40.281252,40.283749 39.33875,39.40625 38.21875,39.40625 z M 127.15625,39.40625 C 126.0225,39.406249 125.0625,40.285 125.0625,41.46875 C 125.0625,42.66 126.0225,43.53125 127.15625,43.53125 C 128.275,43.53125 129.25,42.66 129.25,41.46875 C 129.25,40.285 128.275,39.40625 127.15625,39.40625 z M 38.21875,39.75 C 39.146248,39.750002 39.875,40.49 39.875,41.46875 C 39.875,42.456249 39.14625,43.1875 38.21875,43.1875 C 37.273748,43.187501 36.53125,42.45625 36.53125,41.46875 C 36.53125,40.489999 37.27375,39.75 38.21875,39.75 z M 127.15625,39.75 C 128.08375,39.750002 128.84375,40.49125 128.84375,41.46875 C 128.84375,42.4575 128.08375,43.1875 127.15625,43.1875 C 126.21,43.187499 125.5,42.4575 125.5,41.46875 C 125.5,40.49125 126.21,39.75 127.15625,39.75 z M 37.40625,40.28125 L 37.40625,42.65625 L 37.78125,42.65625 L 37.78125,41.625 L 38.1875,41.625 L 38.8125,42.65625 L 39.21875,42.65625 L 38.53125,41.59375 C 38.88375,41.553751 39.15625,41.395 39.15625,40.96875 C 39.156251,40.49875 38.8775,40.28125 38.3125,40.28125 L 37.40625,40.28125 z M 126.375,40.28125 L 126.375,42.65625 L 126.71875,42.65625 L 126.71875,41.625 L 127.15625,41.625 L 127.78125,42.65625 L 128.1875,42.65625 L 127.5,41.59375 C 127.84625,41.554998 128.125,41.395 128.125,40.96875 C 128.125,40.49875 127.8425,40.28125 127.28125,40.28125 L 126.375,40.28125 z M 37.78125,40.59375 L 38.28125,40.59375 C 38.528749,40.593749 38.78125,40.6425 38.78125,40.9375 C 38.78125,41.300001 38.5275,41.3125 38.21875,41.3125 L 37.78125,41.3125 L 37.78125,40.59375 z M 126.71875,40.59375 L 127.21875,40.59375 C 127.47125,40.593749 127.75,40.64125 127.75,40.9375 C 127.75,41.300001 127.4625,41.3125 127.15625,41.3125 L 126.71875,41.3125 L 126.71875,40.59375 z " style="fill:currentColor;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path2448" /> </g></svg></div><div class="name">sas</div></div> <div class="cell"><div class="icon"><svg aria-hidden="true" width="24" height="24" viewBox="0 0 128 128" fill="currentColor"><path d="m124.9 47.192c-1.7102 0-3.0994 1.3892-3.0994 3.0994s1.3892 3.0997 3.0994 3.0997c1.7103 0 3.0997-1.3894 3.0997-3.0997s-1.3894-3.0994-3.0997-3.0994zm0 0.2663c1.5663 0 2.8334 1.2668 2.8334 2.8332 0 1.5663-1.267 2.8334-2.8334 2.8334-1.5663 0-2.8332-1.2671-2.8332-2.8334 0-1.5663 1.2668-2.8332 2.8332-2.8332zm-1.6879 1.0714v0.18857h0.27426c0.05 0 0.10361 0.0224 0.1252 0.0557 0.11325 0.15835 0.11391 0.33506 0.11391 0.33506v2.327s-6.6e-4 0.17671-0.11391 0.33506c-0.0216 0.0333-0.0752 0.0557-0.1252 0.0557h-0.27426v0.18855h1.5758v-0.18857h-0.27425c-0.05 0-0.10362-0.0224-0.1252-0.0557-0.11326-0.15835-0.11391-0.33506-0.11391-0.33506v-1.0847h0.35738c0.1188 0 0.3451 0.11827 0.39971 0.16343 0.21263 0.15698 0.32705 0.45654 0.35584 1.0752 6e-3 0.12808 0.0681 0.26617 0.18241 0.34918 0.25405 0.17339 0.70716 0.19688 0.99518-0.0377 0.14313-0.13033 0.19235-0.35261 0.1937-0.56031l-0.18471-0.0344s-0.0289 0.48335-0.28683 0.48335c-0.0803 0-0.19419-0.10295-0.20089-0.26451-0.0123-0.29702-0.17138-0.99918-0.97414-1.2143 0.25052 0 0.66509-0.107 0.79917-0.20473 0.20004-0.14762 0.39504-0.3796 0.41254-0.63395l2e-3 -1e-3v-0.14237l-1e-3 -7.7e-4c-0.0296-0.43143-0.57155-0.79866-0.77865-0.79866h-1.8208zm1.0624 0.26989h0.84304c0.17056 0 0.56011 0.19938 0.58546 0.53133 0 0.0115 2e-3 0.0229 2e-3 0.0341v0.0706c0 0.0112-2e-3 0.0227-2e-3 0.0341-0.0253 0.3314-0.41369 0.53067-0.58469 0.53132h-0.84381zm-95.427 3.5584-2.3513 6.3356h7.8816v22.114h6.3472v-22.114h6.3333l2.3513-6.3356zm48.619 0-2.3513 6.3356h7.8819v22.114h6.3469v-22.114h6.3333l2.3516-6.3356zm-49.565 0.0174-17.355 2.5e-4v1e-3c-0.7843 2e-3 -2.3705 6e-3 -2.5363 6e-3 -2.1745 0-4.0423 1.1602-5.5508 2.6802-1.5084 1.52-2.4609 3.2021-2.4609 5.3933v1.2096c4.99e-4 2.1904 0.95288 3.8719 2.4609 5.3915 1.5085 1.52 3.3763 2.6802 5.5508 2.6802 0.1564 0 1.5491 4e-3 2.3919 6e-3v2e-3h8.9407c1.3141 0 2.3771 1.0426 2.4237 2.3549-0.047 1.3123-1.1096 2.3547-2.4237 2.3547h-16.895l-2.3493 6.33h17.319v-1e-3c0.7401-2e-3 2.4434-6e-3 2.6143-6e-3 2.1745 0 4.0423-1.1602 5.5508-2.6802 1.508-1.5197 2.4606-3.2011 2.4611-5.3915v-1.2099c0-2.1912-0.95274-3.873-2.4611-5.393-1.5085-1.52-3.3763-2.6802-5.5508-2.6802-0.1538 0-1.4982-4e-3 -2.3521-6e-3v-2e-3h-8.9805c-1.3141 0-2.3771-1.0426-2.4237-2.3549 0.047-1.3123 1.1096-2.3549 2.4237-2.3549h16.853zm22.363 8e-3 -2.349 6.33h18.476c1.2888 0 2.3356 1.0028 2.4193 2.2795h0.018v2.6e-4h-0.0239v2.4298l-15.31 7e-3c-2.1745 0-4.0422 1.1602-5.5506 2.6802-3.1887 3.5728-3.3403 8.5643 0 11.994 1.5084 1.52 3.3761 2.6802 5.5506 2.6802l21.633 0.0126-0.0364-20.333c0-2.1912-0.95267-3.8733-2.4611-5.3933-1.5085-1.5201-3.3763-2.6802-5.5508-2.6802zm48.619 0-2.3493 6.33h18.476c1.2888 0 2.3353 1.0028 2.4191 2.2795h0.018v2.6e-4h-0.0239v2.4298l-15.31 7e-3c-2.1745 0-4.0424 1.1602-5.5508 2.6802-3.1886 3.5728-3.3402 8.5643 0 11.994 1.5085 1.52 3.3763 2.6802 5.5508 2.6802l21.633 0.0126-0.0364-20.333c0-2.1912-0.95268-3.8733-2.4611-5.3933-1.5085-1.5201-3.3763-2.6802-5.5508-2.6802zm-44.701 17.37h14.622v4.6857l-14.622 0.0239c-1.314 0-2.3771-1.0426-2.4237-2.3549 0.047-1.3123 1.1097-2.3547 2.4237-2.3547zm48.619 0h14.622v4.6857l-14.622 0.0239c-1.3141 0-2.3771-1.0426-2.4237-2.3549 0.0466-1.3123 1.1096-2.3547 2.4237-2.3547z" fill="currentColor"/></svg></div><div class="name">stata</div></div> <div class="cell"><div class="icon"><svg aria-hidden="true" width="24" height="24" viewBox="0 0 128 128" fill="currentColor"><path fill="currentColor" d="M71.4 38.8c-1.5-.6-3.9-1-6.9-1.1-4.2-.1-9 .4-9.2.5v20c13.3.6 15.5-1.7 15.5-1.7 11.6-5.9 4.3-16.2.6-17.7z"/><path fill="currentColor" d="M64 0C28.6 0 0 28.6 0 64s28.6 64 64 64 64-28.6 64-64S99.3 0 64 0zm28.6 89.8H82L64.4 63.5h-9V84h9v5.8H41.5v-5.7l7.6-.1-.1-45.9c-.8-.2-7.5-.8-7.5-.8V32c1 1 7.9 1.2 7.9 1.2 1.6.1 3.9.2 5.2-.1 9.3-1.7 16.4-.4 16.4-.4 14 3.2 14.2 15.8 10.3 22.6-3.5 5.8-10.3 7.2-10.3 7.2l14.4 21.8 7.2-.1v5.6z"/></svg></div><div class="name">rstudio</div></div> <div class="cell"><div class="icon"><svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><title>QGIS
qgis
diff --git a/skills/diagram-design/references/primitive-icons.md b/skills/diagram-design/references/primitive-icons.md index 0721e45..88e762b 100644 --- a/skills/diagram-design/references/primitive-icons.md +++ b/skills/diagram-design/references/primitive-icons.md @@ -754,7 +754,7 @@ Source: Devicon / `spss-plain` (MIT) SAS analytics platform. ```svg - + ``` Source: Direct fetch / `upload.wikimedia.org` — verify license before use