<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Omar Khaled</title>
    <description>The latest articles on DEV Community by Omar Khaled (@omarkhanafy).</description>
    <link>https://dev.to/omarkhanafy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4048670%2Fb72e993e-179d-4b92-9672-14c23c4bbffb.jpg</url>
      <title>DEV Community: Omar Khaled</title>
      <link>https://dev.to/omarkhanafy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/omarkhanafy"/>
    <language>en</language>
    <item>
      <title>The SVG Was Valid. My Flutter Icon Wasn't.</title>
      <dc:creator>Omar Khaled</dc:creator>
      <pubDate>Mon, 27 Jul 2026 05:29:42 +0000</pubDate>
      <link>https://dev.to/omarkhanafy/the-svg-was-valid-my-flutter-icon-wasnt-2pdh</link>
      <guid>https://dev.to/omarkhanafy/the-svg-was-valid-my-flutter-icon-wasnt-2pdh</guid>
      <description>&lt;p&gt;The SVG rendered correctly. The generated Flutter icon font did not.&lt;/p&gt;

&lt;p&gt;After conversion with FlutterIcon.com, a location pin lost its center hole, a chat bubble lost its third dot, and a mail icon lost its inner cutout. The same source files still looked correct in browsers and design tools.&lt;/p&gt;

&lt;p&gt;All three SVGs used &lt;code&gt;fill-rule="evenodd"&lt;/code&gt; to form cutouts. To isolate the conversion, I rendered the source SVGs, the FlutterIcon TTF, and a GlyphPact OTF/CFF build inside the same Flutter app:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm74n65xjt2qzr7pr7037.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm74n65xjt2qzr7pr7037.png" alt="A Flutter app comparing three source SVGs with FlutterIcon.com and GlyphPact font output. FlutterIcon loses the location hole, chat dot, and mail cutout. GlyphPact preserves all three." width="800" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This comparison used GlyphPact v1.1.0. All three columns were rendered by the same Flutter app.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is a three-file result, not a claim that every even-odd SVG fails in FlutterIcon.com. These files lost visible cutouts in my export. That was enough to make me look at the rest of my icon workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SVGs rendered correctly, but the conversion still changed them
&lt;/h2&gt;

&lt;p&gt;An SVG renderer and an icon-font generator have different jobs.&lt;/p&gt;

&lt;p&gt;The SVG renderer understands fill rules, strokes, transforms, clipping, paint, and the rest of the document. A monochrome icon font represents the artwork as glyph outlines and alpha coverage. Somewhere between those two representations, strokes may need outlining, transforms need flattening, and cutouts must survive as contour relationships.&lt;/p&gt;

&lt;p&gt;FlutterIcon.com has a familiar warning for this class of problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If image looks not as expected please convert to compound path manually.&lt;/p&gt;

&lt;p&gt;Skipped tags and attributes: ...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That advice can fix a file, but it sends the work back to the developer or designer. Open a vector editor, change the paths, export again, upload again, and check the result.&lt;/p&gt;

&lt;p&gt;For one icon, fine. For an icon set that keeps changing, the repair loop becomes part of every release.&lt;/p&gt;

&lt;h2&gt;
  
  
  The icon set keeps changing
&lt;/h2&gt;

&lt;p&gt;My icon sets rarely arrive once. More icons show up in later Figma handoffs, and every batch has to join the existing font without changing codepoints already used by the app.&lt;/p&gt;

&lt;p&gt;The Dart API matters as much as the pictures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppIcons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppIcons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppIcons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each field points to a codepoint in the font. If regeneration assigns an old field to a new glyph, the Dart code still compiles. The app simply shows the wrong picture.&lt;/p&gt;

&lt;p&gt;FlutterIcon.com can preserve assignments if you save its &lt;code&gt;config.json&lt;/code&gt; and import it on the next visit. That works, but it depends on keeping the file and following the same process every time.&lt;/p&gt;

&lt;p&gt;That was the weak point for my projects. Someone had to remember the previous config, upload the correct files, export the bundle, copy the right artifacts, and check that nothing moved. The repository could not tell us whether any step was missed.&lt;/p&gt;

&lt;p&gt;I wanted the icon font to behave like generated source code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving the build into the repository
&lt;/h2&gt;

&lt;p&gt;The SVG directory should be the source of truth. The command should run locally and in CI. Existing codepoints should stay fixed without depending on a remembered browser session.&lt;/p&gt;

&lt;p&gt;That became &lt;a href="https://omar-hanafy.github.io/glyphpact/" rel="noopener noreferrer"&gt;GlyphPact&lt;/a&gt;, a local SVG-to-Flutter icon compiler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv tool &lt;span class="nb"&gt;install &lt;/span&gt;glyphpact

glyphpact assets/icons &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; lib/generated/app_icons &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; AppIcons
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output includes an OpenType/CFF font, a const Dart &lt;code&gt;IconData&lt;/code&gt; provider, a machine-readable report, and &lt;code&gt;iconfont.lock.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The lock file is the API contract. Adding or reordering SVGs does not renumber existing icons. Removing an icon leaves a tombstone, so its codepoint cannot quietly return as a different picture later. A unique content-preserving rename keeps the same assignment and Dart name.&lt;/p&gt;

&lt;p&gt;There is a longer explanation of &lt;a href="https://omar-hanafy.github.io/glyphpact/stable-codepoints/" rel="noopener noreferrer"&gt;why stable icon codepoints matter&lt;/a&gt;, including a lock-file diff.&lt;/p&gt;

&lt;p&gt;CI can rebuild the candidate output without replacing the committed artifacts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;glyphpact &lt;span class="nt"&gt;--config&lt;/span&gt; icon_font.json &lt;span class="nt"&gt;--check&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command exits with code &lt;code&gt;3&lt;/code&gt; when the generated output is stale. A missing icon build stops looking like a visual QA problem and starts behaving like any other failed code-generation check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strict builds are sometimes annoying
&lt;/h2&gt;

&lt;p&gt;In strict mode, GlyphPact accepts supported SVG features only when it can preserve them. Approximations and skipped inputs require explicit flags. Malformed, unsafe, unknown, or over-budget input fails the build.&lt;/p&gt;

&lt;p&gt;This is stricter than accepting whatever a generator can produce, and sometimes it gets in the way. I prefer that interruption in a pull request to finding a damaged icon after a release.&lt;/p&gt;

&lt;p&gt;It also does not claim to support every valid SVG. Icon fonts represent monochrome alpha coverage, not the full SVG format. If source color, animation, filters, or browser-dependent painting matter, a font is the wrong output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Known limits for large icon packs
&lt;/h2&gt;

&lt;p&gt;GlyphPact automates bulk conversion, but each build still produces an icon font. OpenType glyph indexing has a practical ceiling of 65,534 usable glyphs per font, and GlyphPact enforces the same per-build limit.&lt;/p&gt;

&lt;p&gt;By default, assignments use the BMP private-use range from U+E000 through U+F8FF. That range has 6,400 lifetime slots. Active icons and tombstones both consume slots because GlyphPact never recycles a removed icon's codepoint. The build report tracks the remaining capacity and warns when range use reaches 80%.&lt;/p&gt;

&lt;p&gt;A larger pack can start in a supplementary private-use range, which provides up to 65,534 slots. That choice must be made before the first build because an established lock file rejects later start-codepoint changes.&lt;/p&gt;

&lt;p&gt;Larger catalogs need to be split into multiple independently versioned fonts. GlyphPact makes those builds repeatable, but it cannot make an unlimited catalog fit into one mobile font.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CLI remains the source of truth
&lt;/h2&gt;

&lt;p&gt;Because the build runs locally from a checked-in config, the same workflow can be exposed to coding agents without changing the compiler.&lt;/p&gt;

&lt;p&gt;The optional &lt;a href="https://omar-hanafy.github.io/glyphpact/mcp/" rel="noopener noreferrer"&gt;GlyphPact MCP integration&lt;/a&gt; exposes four local operations: audit an SVG pack, build from a checked-in config, check committed output for staleness, and page through a build report.&lt;/p&gt;

&lt;p&gt;MCP is not required. The CLI remains the compiler. The MCP layer gives a coding agent a defined boundary for the same workflow instead of asking it to operate a browser or guess which generated files it owns.&lt;/p&gt;

&lt;h2&gt;
  
  
  I still ship plenty of SVG files
&lt;/h2&gt;

&lt;p&gt;I would keep a multicolor illustration or a one-off static graphic as SVG and render it with &lt;code&gt;flutter_svg&lt;/code&gt;. For animation, I would use a format and renderer suited to that asset.&lt;/p&gt;

&lt;p&gt;FlutterIcon.com also remains convenient when I need a quick one-time font or want to choose from its bundled icon libraries.&lt;/p&gt;

&lt;p&gt;GlyphPact is for the narrower case I kept running into: a maintained set of monochrome Flutter UI icons, repeated design deliveries, and generated artifacts that need to stay correct across releases.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/omar-hanafy/glyphpact" rel="noopener noreferrer"&gt;GlyphPact's source is on GitHub&lt;/a&gt;. If you have an SVG that behaves badly during font conversion, especially one involving even-odd fills, strokes, or cutouts, open an issue with the file. Minimal examples of currently unsupported features are useful too. Awkward inputs are more useful to this compiler than clean demo icons.&lt;/p&gt;




&lt;p&gt;_Disclosure: I built and maintain GlyphPact.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>svg</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
