Skip to content

Sync flag assets with flag-icons v7.5.0 - #98

Open
tisohjung wants to merge 8 commits into
LunaGao:stablefrom
tisohjung:feat/sync-flag-icons-v7.5.0
Open

tisohjung wants to merge 8 commits into
LunaGao:stablefrom
tisohjung:feat/sync-flag-icons-v7.5.0

Conversation

@tisohjung

@tisohjung tisohjung commented Sep 2, 2026

Copy link
Copy Markdown

Summary

The flags bundled in this package were still from flag-icons v4.1.4. This PR re-syncs every flag from flag-icons v7.5.0 — both res/1x1/ and res/4x3/ — and extends FlagsCode / baseFlagsCode to match.

Upstream also shrank most of the SVGs since v4.1.4, so a large part of the diff is just smaller artwork, not visual change.

Before After
flag-icons version v4.1.4 v7.5.0
flags per aspect ratio 266 274
FlagsCode entries 267 (incl. NULL) 275 (incl. NULL)
package version 7.0.2 7.1.0

res/1x1/ and res/4x3/ contain exactly the same 274 filenames.

Then something unexpected happened. While checking the new artwork, I found that 6 flags were rendering wrong in Flutter — silently, with no error and with the test suite green. Those are fixed here too. The details are further down, written to be readable without SVG background.


New flag codes (8)

Code Flag
ARAB Arab League
ASEAN ASEAN
EAC East African Community
ES_PV Basque Country
PC Pacific Community
SH_AC Ascension Island
SH_HL Saint Helena
SH_TA Tristan da Cunha

Why the filenames use _ instead of -

Upstream ships these as es-pv.svg, sh-ac.svg, gb-eng.svg, … but Flag builds its asset path straight from the enum name:

String countryName = EnumToString.convertToString(countryCode).toLowerCase();
return 'packages/flag/res/$folderName/$countryName.svg';

A Dart enum identifier can't contain -, so a hyphenated filename would be unreachable through Flag.fromCode. Every - in an upstream filename is therefore rewritten to _ on copy — the same convention this repo already used for es_ct, gb_eng and friends. No name collisions.

Why AC, TA and EA are kept

Upstream renamed or removed three codes that this package already exposes publicly. Dropping them would break existing users, so they stay and this remains a minor version bump:

Code Upstream in v7.5.0 Handling here
AC renamed to sh-ac kept, artwork re-synced from sh-ac (so AC and SH_AC render the same flag)
TA renamed to sh-ta kept, artwork re-synced from sh-ta
EA removed (Ceuta & Melilla) kept untouched at its v4.1.4 artwork

The rendering bugs I found (and fixed)

Everything in this section is about Flutter specifically. These files look correct in a browser. They were wrong only when flutter_svg drew them — which is exactly why nobody had noticed.

How this started

The US flag lost all 50 stars.

The stars in us.svg are drawn with an SVG tag called <marker> — think of it as "stamp this shape at these points". flutter_svg doesn't support that tag, and instead of complaining, it just skips it. No error, no warning, and the existing golden tests still passed because the golden files had been generated from the same broken output.

That is the scary part: a flag can be visibly wrong and every automated check stays green.

So I stopped trusting "it looks fine" and checked all of them properly.

How I checked (in plain terms)

I drew all 548 SVGs (274 codes × 2 aspect ratios) twice:

  • once with flutter_svg — what this package actually shows users
  • once with rsvg-convert (librsvg) — a mature, independent renderer used as the "second opinion"

…then compared the two images pixel by pixel, at two sizes each (2,208 images in total).

Two renderers never agree perfectly — they smooth edges differently — so "any difference at all" is a useless alarm. The measurement that actually decides is: mark every pixel that differs by more than 15%, then shrink that mask by a pixel. Thin edge-smoothing seams disappear; anything with real area survives.

I validated the method before trusting it. I threw the known-broken pre-fix us/um into the same batch. Every metric separated them cleanly from the 548 current files:

RMSE blurred RMSE eroded diff @256 eroded diff @512
pre-fix 1x1/us (stars missing) 0.1721 0.1242 0.0356 0.0282
pre-fix 4x3/us (stars missing) 0.1466 0.0972 0.0286 0.0142
worst of the 548 current files 0.1154 0.0739 0.0212 0.0000

In other words: the check demonstrably catches the bug we already knew about. Then I opened the 48 worst-scoring files and looked at them by eye.

All 548 rendered with zero parser warnings. The only "unhandled element" message in the whole run came from the broken control file.

Bug class 1 — an unsupported tag is silently dropped

us, um — all 50 stars missing. Fixed in 8899603 by drawing the stars as ordinary shapes instead of using <marker>.

Bug class 2 — squashed drawings get the wrong line thickness

This one needs one idea explained first.

Imagine drawing a line with a round pen, then squashing the whole drawing to 60% width but leaving the height alone. The line should get squashed too — the pen effectively becomes an oval, thinner sideways than up-down. That's what the SVG spec says should happen.

flutter_svg keeps the pen round, using one averaged width. So any line drawn inside a non-uniform squash comes out the wrong thickness — and, again, with no error.

es_ct (Catalonia) — the red stripes were 18% too thin → fixed in 203afc1

The Senyera is nine equal bands. Measured stripe widths at 256px:

top → bottom
librsvg (correct) 28 27 28 27 28 27 28 27 28
flutter_svg (before) 30 23 32 23 32 23 32 23 30
flutter_svg (after) 28 28 28 27 28 27 28 28 28

The fix bakes the squash into the coordinates so the pen is round again. The arithmetic is exact — 512/9 = 56.889 and 810 × 0.6321 = 512.0:

-<path stroke="#da121a" stroke-width="60" d="M0 90h810m0 120H0m0 120h810m0 120H0" transform="scale(.6321 .94815)"/>
+<path stroke="#da121a" stroke-width="56.889" d="M0 85.333h512M0 199.111h512M0 312.889h512M0 426.667h512"/>

Difference against the reference renderer drops 0.115 → 0.006, i.e. down into ordinary edge-smoothing noise.

vu (Vanuatu) — every band of the pall was wrong, plus a visible seam → fixed in 74b805d

Same root cause, worse. The yellow arm was 62% too wide while its black border lost 27%, and a hairline seam split the yellow arm in two — visible at normal size.

Baking in the squash wasn't enough here: the same path mixes diagonal and horizontal segments, so no single line width is right for both. So the pall is now filled shapes instead of lines — three nested polygons, outer black, yellow inset, inner black inset. There's no line left for a renderer to get wrong.

The vertices are the exact corner geometry of the lines they replace, not an eyeballed approximation.

flutter_svg vs reference before after
1x1 0.1271 0.0049
4x3 0.1074 0.0048

vu was the single worst file in the audit. It now sits at the median.

One flag left alone: 4x3/gb_sct

Same class, but the diagonal is only ~4% thin (55px vs 57px) — below what anyone can see. I scanned all 548 files for this pattern: 37 files have it, but only the ones above have an error big enough to matter. The rest are sub-pixel.

Worth noting it's line width × how uneven the squash is that hurts, not unevenness alone — om has the most uneven squash of all (4.24×) and an error of 0.18px.

Bug class 3 — the file points at paint that isn't there

ac / sh_ac (Ascension Island) → fixed in c68dbea

These files contain 96 references to named pieces of paint (url(#…)) and define exactly one of them. The whole <defs> block — every gradient, every mask — is simply missing:

count
fill="url(#…)" pointing at a missing gradient 62
mask="url(#…)" pointing at a missing mask 34
ids actually defined in the file 1

Per spec, a shape whose paint can't be found isn't drawn at all. What's missing isn't decoration: the Green Mountain in the crest, the green chevron on the shield, and parts of the turtle supporters were all absent.

This is not something this PR introduced. The files here were byte-identical to upstream, and the same 96 dangling references are in every flag-icons release I checked back to 6.15.0, and in the GitHub source tree — so it isn't an npm packaging artefact. It looks like an id-cleanup pass that deleted "unused" definitions without scanning url() inside attributes.

Why the pixel comparison couldn't catch this: a broken reference is dropped identically by every renderer. So the two renderers agree perfectly, and the diff is clean — 1x1/ac scored 0.0125, nowhere near the outliers. It only surfaced from a separate static scan for undefined ids. Plainly put: "the two renderers agree" is not the same as "the file is correct."

The fix: the gradients are gone, not wrong, so there's nothing to repair and inventing 62 fills would be guesswork. The arms are taken from Flag of Ascension Island PD.svg on Wikimedia Commons — public domain, own work, no attribution required — which is pure path geometry: 461 paths, no gradients, masks, filters or embedded images, so there's nothing left for flutter_svg to silently drop.

The obvious alternative, Flag_of_Ascension_Island.svg, was rejected on two grounds: it embeds 36 base64 images behind 36 <filter> elements — neither supported by flutter_svg, so vendoring it would have recreated the exact bug being fixed — and its own credit line derives it from a CC BY-SA 3.0 coat of arms, which doesn't belong in an MIT package.

The blue field and the Union Jack canton are kept from the existing MIT file untouched, so only the arms change, fitted to the exact box the old arms occupied. The files also got ~19% smaller.

A follow-up on vu: conforming to the 1980 law

After the fix above, one difference from the "official" drawing remained. I originally wrote that this was upstream's own design choice and left it alone. That was wrong, and I'd rather correct it here than leave it buried in a comment thread.

The reason it was wrong: Wikimedia carries two Vanuatu constructions that disagree with each other, and I'd compared against the one that happens to match flag-icons. Only the other one cites the enabling law — the 18 × 12 grid of the State Flag and Armorial Bearings Public Declaration (19 March 1980).

On that grid the black border and the yellow pall are equal width. This repo drew the black wider:

as a fraction of height Declaration before delta
black border, each side 0.05556 (1/18) 0.06316 +14%
yellow pall 0.05556 (1/18) 0.05263 −5%
whole pall band 0.16667 (1/6) 0.17895 +7%
black triangle tip (of the width) 0.5 0.5684 +14%

Redrawn on the Declaration grid in 8f1f403. Diagonals at slope 5/9, each band 2/3 of a grid unit measured perpendicular. Every vertex follows from those constants and agrees with the reference file's own numbers to 6.4e-5 grid units. The boar's tusk emblem is untouched — and clearance around it actually grows, so nothing needed reseating.

Verification, scanning across columns of solid colour:

4x3  x=560 (arm)       before blk 29  yel 24  blk 29
                        after blk 26  yel 26  blk 26
                    reference blk 26  yel 26  blk 26

4x3  x=200 (diagonal)  before blk 32  yel 27  blk  92  yel 27  blk 32
                        after blk 29  yel 29  blk 106  yel 29  blk 29
                    reference blk 29  yel 29  blk 106  yel 29  blk 29

The after rows read identically off librsvg and off flutter_svg, in both aspect ratios, on all four scan lines.

This one is also filed upstream, since that's the only durable home for it:

If that lands, this divergence disappears on the next sync.


Summary of findings

finding what kind of bug status
us / um — 50 stars missing unsupported tag silently skipped fixed, 8899603
es_ct — stripes 18% thin wrong line width under a squash fixed, 203afc1
vu — pall bands wrong + visible seam wrong line width under a squash fixed, 74b805d
ac / sh_ac — 96 references to missing paint broken reference, upstream defect fixed, c68dbea
vu — pall off the 1980 Declaration grid artwork vs. the enabling law fixed, 8f1f403 + upstream PR
4x3/gb_sct — diagonal ~4% thin wrong line width under a squash left alone, below the visible threshold

12 files deliberately differ from upstream v7.5.0us, um, es_ct, vu, ac, sh_ac, each in both aspect ratios. Everything else is still byte-for-byte upstream.

Test plan

Run with the repo's pinned Flutter 3.35.3 (via fvm).

  • flutter analyzeNo issues found!
  • flutter testall 282 tests pass (the existing widget test iterates FlagsCode.values, so the 8 new codes are covered automatically)
  • Additionally verified by script, outside the test suite:
    • FlagsCode minus NULL equals baseFlagsCode exactly, same order, no duplicates
    • every one of the 274 codes has a matching res/1x1/<code>.svg and res/4x3/<code>.svg
    • all 548 files compared byte for byte against the v7.5.0 source — 0 mismatches apart from the 12 deliberate fixes above
    • ac.svg / ta.svg are byte-identical to sh_ac.svg / sh_ta.svg; ea.svg untouched
    • no asset file exists without a corresponding enum entry
    • dangling url(#…) references across all 548 files: 384 → 0; external references: 0
  • dart format was applied only to the touched Dart file, to keep the diff reviewable.

Caveats, stated plainly

  • Only one independent renderer was available on this machine (no resvg, no headless Chromium), so there was no best-of-three vote. For each finding above, librsvg's output matches the value computed from the spec or from the flag's own construction — that's what settles which renderer is right, rather than a majority.
  • The pixel comparison cannot catch the "missing paint" class at all, by construction. That class was found by a separate static scan. There may be other classes neither method covers.
  • One process note: the first rebuild of the Ascension files went through Python's ElementTree, which re-serialised the canton with namespace prefixes. librsvg accepted it; flutter_svg dropped all six elements, so the field and the Union Jack vanished. It was caught only because every rebuilt file goes through a real flutter_svg render — the same lesson this whole exercise kept producing.

Happy to split any of the six artwork fixes into a separate PR if you'd rather keep this one to the version sync — just say which.

tisohjung and others added 8 commits September 2, 2026 10:48
Copy flags/1x1 and flags/4x3 from lipis/flag-icons v7.5.0, replacing
hyphens in filenames with underscores so the names stay valid Dart enum
identifiers. Keep ac.svg / ta.svg (renamed to sh-ac / sh-ta upstream) in
sync with their renamed sources, and keep ea.svg (removed upstream) as is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add ARAB, ASEAN, EAC, ES_PV, PC, SH_AC, SH_HL and SH_TA to FlagsCode and
baseFlagsCode, bringing the set to 274 codes. AC, TA and EA are kept even
though upstream renamed or dropped them, since they are part of the public
API. Bump to 7.1.0 and point the docs at flag-icons v7.5.0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ag, ax, bi and bj were still carrying their v4.1.4 artwork in res/1x1
after the bulk sync. Replace them with the v7.5.0 sources so all 274
files in both res/1x1 and res/4x3 now match flag-icons v7.5.0 byte for
byte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
flag-icons v7.5.0 lays the 50 stars of us/um with a single guide path and
`marker-mid`. Renderers that have no <marker> support drop the whole star
field and leave a bare navy canton — flutter_svg (vector_graphics) is one
of them, and it logs `unhandled element <marker/>` when it hits these two
files. That makes this package's most used flag wrong on every Flutter app
that upgrades to the v7.5.0 artwork.

Expand the marker into a <defs> star plus one <use> per vertex. Marker
placement here reduces to a plain translate — markerUnits defaults to
strokeWidth, the guide path has no stroke-width so the scale is 1, and
refX/refY default to 0 — so the geometry is unchanged. Verified with
librsvg: the rewritten files render pixel-identical (AE = 0) to the marker
version at 440x330 (4x3) and 400x400 (1x1), and flutter_svg now draws all
50 stars.
flutter_svg (vector_graphics) resolves transforms at compile time and
keeps stroke-width a scalar, so a stroke under a non-uniform scale gets
one averaged pen instead of an elliptical one. es_ct drew its four red
stripes as a stroked path under `scale(.6321 .94815)`, so the red bands
came out ~18% too thin (23px vs 28px at a 256px render) while the yellow
ones grew to fill the gap -- the Senyera's nine bands were visibly
unequal. Nothing failed: no parse error, no warning, goldens passed.

Baking the scale into the path coordinates makes the pen uniform again,
which every renderer agrees on. Verified against rsvg-convert 2.58.4 at
256px: RMSE 0.115 -> 0.006 (1x1) and 0.055 -> 0.007 (4x3), and the nine
stripes now measure 28/28/28/27/28/27/28/28/28 px against the
reference's 28/27/28/27/28/27/28/27/28.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Same root cause as the Catalonia fix: vector_graphics folds transforms into
path coordinates at compile time but keeps stroke-width a scalar, so a stroke
under a non-uniform scale gets one averaged pen instead of an elliptical one.
vu drew its whole Y inside `scale(.56889 1.0779)` / `scale(.71111 1.01053)`,
so every band was wrong at once -- on the horizontal arm the yellow ran 62%
too wide (21px vs 13px at a 256px render) while the black fimbriation lost
27%, and on the diagonals the yellow lost 6% and the hoist triangle grew 8%.
A hairline seam also showed up inside the yellow arm. Nothing failed: no
parse error, no warning, goldens passed.

Baking the scale in is not enough here, because the same path mixes diagonal
and horizontal segments and no single stroke-width is correct for both. So
the pall is now three nested filled polygons -- outer black, yellow inset by
30, inner black inset by 55 -- which is the construction the standard
Vanuatu drawing uses and which no renderer can get wrong. Offsets are the
exact miter geometry of the strokes they replace, so the artwork is
unchanged; the clipPath is no longer needed and goes away with them.

Verified with rsvg-convert 2.58.4 at 900px. Reference render before vs after
the rewrite is unchanged (RMSE 0.006/0.005, antialiasing only), confirming
the geometry is a faithful transcription rather than a redesign. flutter_svg
against the reference goes 0.127 -> 0.005 (1x1) and 0.107 -> 0.005 (4x3),
and the band runs now match the reference exactly: arm 56/46/56 px (1x1) and
41/35/41 px (4x3), diagonals 61/50/228/50/61 px at x=250.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…not exist

Upstream flag-icons ships sh-ac.svg with 96 `url(#…)` references and exactly
one defined id -- the whole `<defs>` block is missing, so 62 elements fill
from a gradient that does not exist and 34 more mask against a mask that does
not exist. Per spec an invalid paint reference makes the element unrenderable,
so the Green Mountain of the crest, the green chevron of the shield and parts
of the turtle supporters were simply absent. Every renderer drops them
identically, which is why comparing two renderers never caught it and only a
static id scan did. Not our regression: these four files are byte-identical to
upstream, and the same 96 dangling references are present as far back as
flag-icons 6.15.0 and in the GitHub source, not just the npm build.

The gradients are gone rather than wrong, so there is nothing to repair -- the
intended shading is unrecoverable and inventing 62 fills would be guesswork.
The arms are instead taken from the public-domain "Flag of Ascension Island
PD.svg" on Wikimedia Commons (own work by Shervinafshar; PD, no attribution
required), which is pure `<path>` geometry: no gradients, masks, filters or
embedded rasters, so there is nothing in it that flutter_svg can silently drop.
The blue field and the Union Jack canton are kept from the existing MIT file
untouched, so only the arms change. The commons file credited as the ordinary
reference could not be used: it embeds 36 base64 rasters behind `<filter>`
elements, and its own credit line derives it from a CC BY-SA 3.0 coat of arms.

The arms are fitted to the exact bounding box the old arms occupied -- measured
at 316.8,148.48 300.16x276.8 (4x3) and 245.25,198.66 250.37x230.4 (1x1) -- with
a uniform scale, so the flag's layout is unchanged. Coordinates are baked and
requantised to 0.1 viewBox units against the emitted point rather than the
exact one, so rounding cannot accumulate along a path; round-trip error is
0.05 units and the render is within 0.003 RMSE of a lossless build. That takes
the files from 143,373 to 116,334 bytes (4x3) and 140,519 to 114,620 (1x1),
-19%; they stay large because the arms are genuinely 461 distinct paths with
almost no two adjacent ones sharing a fill, so merging runs saves 19 bytes.

Dangling `url(#…)` references across all 548 files: 384 -> 0, external
references 0. flutter_svg renders the four files with no parser warning and
within 0.008 RMSE of rsvg-convert 2.58.4. ac and sh_ac stay byte-identical.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pall bands did not match the grid the Vanuatu State Flag and Armorial
Bearings Public Declaration (19 March 1980) lays down. The declaration builds
the flag on an 18x12 grid where the black fimbriation and the yellow pall are
each 1/18 of the height and therefore equal; flag-icons draws the black wider
than the yellow. Measured at 640x480 against the reference stretched to the
same box, the horizontal arm read black 29 / yellow 24 / black 29 where the
grid gives 26 / 26 / 26 -- black 14% over, yellow 5% under, the whole pall 7%
over, and the tip of the hoist triangle 14% too far right.

Worth recording that Commons carries two constructions that disagree:
Flag_of_Vanuatu.svg (950x570) uses black 36 / yellow 30 / black 36, which is
exactly what flag-icons draws, while Flag_of_Vanuatu_(official).svg is built on
the 18x12 grid and cites the Declaration itself. The one citing the legal
instrument is taken as authoritative here. Reported upstream as
lipis/flag-icons#1465.

Redrawn from the grid rather than nudged: diagonals at slope 5/9, each band
2/3 of a grid unit measured perpendicular, so the vertical offset along a
diagonal is 2*sqrt(106)/27. Every vertex is derived from those constants and
agrees with the reference file's own numbers to 6.4e-5 grid units, the
reference being the rounded one. Drawn as four filled polygons with the grid
baked into the coordinates -- no <marker>, no stroke under a non-uniform
scale, nothing this audit has already been bitten by.

The boar's tusk emblem is untouched. It needed no reseating: the redrawn inner
black triangle reaches further right, so clearance at the emblem's right edge
goes from 2.11 to 7.82 units (4x3) and 1.66 to 8.20 (1x1).

Verified in both renderers and both ratios by column scan. Every run now
matches the reference exactly -- 4x3 arm 26/26/26 and diagonal
29/29/106/29/29, 1x1 arm 27/28/27 and diagonal 30/31/112/31/30 -- read
identically off rsvg-convert 2.58.4 and off flutter_svg. flutter_svg against
rsvg is RMSE 0.0060 (4x3) and 0.0049 (1x1) with no parser warning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant