使用icomoon从svg figma图标生成字体时自动填充颜色

7

我想使用icomoon应用程序,将svg转换为HTML/CSS中的图标字体元素。

<svg width="325" height="350" viewBox="0 0 325 350" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M205.43 59.38C205.43 26.5853 232.015 0 264.81 0C297.605 0 324.19 26.5853 324.19 59.38C324.19 92.1747 297.605 118.76 264.81 118.76C246.525 118.76 230.17 110.495 219.277 97.4975L116.536 157.274C117.985 162.41 118.76 167.83 118.76 173.43C118.76 178.348 118.162 183.127 117.035 187.697L222.002 248.758C232.806 237.522 247.991 230.53 264.81 230.53C297.605 230.53 324.19 257.115 324.19 289.91C324.19 322.705 297.605 349.29 264.81 349.29C232.015 349.29 205.43 322.705 205.43 289.91C205.43 280.791 207.486 272.152 211.159 264.431L109.497 205.292C98.9566 221.836 80.4498 232.81 59.38 232.81C26.5853 232.81 0 206.225 0 173.43C0 140.635 26.5853 114.05 59.38 114.05C79.7735 114.05 97.7659 124.331 108.457 139.992L209.556 81.1716C206.893 74.4248 205.43 67.0733 205.43 59.38ZM264.81 19C242.509 19 224.43 37.0787 224.43 59.38C224.43 81.6813 242.509 99.76 264.81 99.76C287.111 99.76 305.19 81.6813 305.19 59.38C305.19 37.0787 287.111 19 264.81 19ZM59.38 133.05C37.0787 133.05 19 151.129 19 173.43C19 195.731 37.0787 213.81 59.38 213.81C81.6813 213.81 99.76 195.731 99.76 173.43C99.76 151.129 81.6813 133.05 59.38 133.05ZM224.43 289.91C224.43 267.609 242.509 249.53 264.81 249.53C287.111 249.53 305.19 267.609 305.19 289.91C305.19 312.211 287.111 330.29 264.81 330.29C242.509 330.29 224.43 312.211 224.43 289.91Z" fill="#0D0F13"/>
</svg>

但是每次我生成图标字体文件时,在icomoon预览中三个圆圈中的一个会被填充成黑色,并在HTML中使用时也是如此:
<span class="icon icon-share">

enter image description here

1个回答

10

这个渲染问题是由不理想的路径方向引起的。

path directions

你的图标是一个复合路径 - 它包含多个子路径(即切割出的“孔”)。

通常,您可以通过切换路径方向来定义哪些形状应该被解释为实心或切割:
例如,如果外部形状的路径命令具有顺时针路径方向,则内部形状应使用逆时针方向。

无论您使用顺时针还是逆时针,都没有关系。内部(切割出的)路径只需使用相反的方向。
本文也解释了填充规则的绕组顺序

Svg: fill-rule="evenodd

原始的svg文件渲染得很好,因为它在路径元素上应用了fill-rule="evenodd"

显然,icomoon字体生成器希望您的图标具有前述的交替方向。
换句话说,它不能在将svg转换为字体文件时使用填充规则。

修复路径方向

这里有一个辅助脚本来修复内部路径。

const svg = document.querySelector('svg');
const path = svg.querySelector('path');

function fixPath(path) {
  // get pathData array
  let pathData = path.getPathData({
    normalize: true
  });
  // split sub paths
  let pathDataSubArr = splitSubpaths(pathData);
  let fixedPathData = fixInnerPathDirections(path, pathDataSubArr);
  path.setPathData(fixedPathData);
}


/**
 * helpers
 */
function fixInnerPathDirections(path, pathDataSubArr) {
  let svg = path.closest('svg');
  let fixedPathData = [];
  let subPathEls = [];
  let bbO = path.getBBox();
  let [xO, yO, wO, hO, rO, bO] = [bbO.x, bbO.y, bbO.width, bbO.height, (bbO.x + bbO.width), (bbO.y + bbO
    .height)];
  let outerPathData = pathDataSubArr[0];
  let outerPathTmp = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  outerPathTmp.setPathData(outerPathData);
  outerPathTmp.classList.add('outer');
  let outerClockwise = isClockwise(outerPathTmp)

  pathDataSubArr.forEach(function(pathDataSub, i) {
    // create temporary subpath elements for checking positions
    let subPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    subPath.setPathData(pathDataSub);
    subPath.setAttribute('stroke', 'red');
    subPath.setAttribute('stroke-width', '1');
    subPath.setAttribute('fill', 'none');
    svg.appendChild(subPath);
    subPathEls.push(subPath);
    let bb = subPath.getBBox();
    let [x, y, w, h, r, b] = [bb.x, bb.y, bb.width, bb.height, (bb.x + bb.width), (bb.y + bb
      .height)];
    // remove temporary subpaths
    subPath.remove();

    if (i > 0) {
      // is subpath within outer path
      if (x > xO && y > yO && r < rO && b < bO) {
        let isClockwiseInner = isClockwise(subPath);
        // if subpath has same direction as outer path: reverse direction
        if (isClockwiseInner == outerClockwise) {
          pathDataSub = reversePathData(pathDataSub);
        }
      }
    }
    fixedPathData = fixedPathData.concat(pathDataSub);
  })
  return fixedPathData;
}


function reversePathData(pathData) {
  let M = pathData[0];
  let newPathData = [M];
  // split subpaths
  let subPathDataArr = splitSubpaths(pathData);
  subPathDataArr.forEach(function(subPathData, s) {
    let subPathDataL = subPathData.length;
    let closed = subPathData[subPathDataL - 1]['type'] == 'Z' ? true : false;
    let subM = subPathData[0]['values'];

    // insert Lineto if last path segment has created by z
    let lastCom = closed ? subPathData[subPathDataL - 2] : subPathData[subPathDataL - 1];
    let lastComL = lastCom['values'].length;
    let lastXY = [lastCom['values'][lastComL - 2], lastCom['values'][lastComL - 1]];
    let diff = Math.abs(subM[0] - lastXY[0]);
    if (diff > 1 && closed) {
      subPathData.pop();
      subPathData.push({
        'type': 'L',
        'values': [subM[0], subM[1]]
      });
      subPathData.push({
        'type': 'Z',
        'values': []
      });
    }
    subPathData.forEach(function(com, i) {
      // reverse index
      let subpathDataL = subPathData.length;
      let indexR = subpathDataL - 1 - i;
      let comR = subPathData[indexR];
      let comF = subPathData[i];
      let [typeR, valuesR] = [comR['type'], comR['values']];
      let [typeF, valuesF] = [comF['type'], comF['values']];
      if (typeF == 'M' && s > 0) {
        newPathData.push(comF);
      } else if (typeR != 'M' && typeR != 'Z') {
        indexR--;
        let prevCom = i > 0 ? subPathData[indexR] : subPathData[subpathDataL - 1 - i];
        let prevVals = prevCom ? (prevCom['values'] ? prevCom['values'] : [0, 0]) : [];
        prevVals = prevCom['values'];
        let prevValsL = prevVals.length;
        let newCoords = [];

        if (typeR == 'C') {
          newCoords = [
            valuesR[2], valuesR[3],
            valuesR[0], valuesR[1],
            prevVals[prevValsL - 2], prevVals[prevValsL - 1]
          ];
          if (!closed) {
            let nextVals = i < subpathDataL - 1 ? subPathData[i + 1]['values'] : lastXY;
            let lastCX = (i < subpathDataL - 2) ? nextVals[prevValsL - 2] : subM[0];
            let lastCY = (i < subpathDataL - 2) ? nextVals[prevValsL - 1] : subM[1];
            newCoords[4] = lastCX;
            newCoords[5] = lastCY;
          }
        } else {
          newCoords = [prevVals[prevValsL - 2], prevVals[prevValsL - 1]];
        }
        newPathData.push({
          'type': typeR,
          'values': newCoords
        });
      }
    })
    //use last coordinates as M values if path isn't closed
    if (!closed) {
      newPathData[0]['values'] = lastXY;
    }
    if (closed) {
      newPathData.push({
        'type': 'Z',
        'values': []
      });
    }
  });
  return newPathData;
}

/**
 * check path direction of polygon
 * based on answer:
 * @mpen: https://dev59.com/r3M_5IYBdhLWcg3w6X5e#1180256
 */
function isClockwise(path, divisions = 50) {
  var length = path.getTotalLength();
  let p1 = path.getPointAtLength(0);
  let A = [p1.x, p1.y];
  let p2 = path.getPointAtLength(length * 0.25);
  let B = [p2.x, p2.y];
  let p3 = path.getPointAtLength(length * 0.75);
  let C = [p3.x, p3.y];
  let poly = [A, B, C];
  let end = poly.length - 1;
  let sum = poly[end][0] * poly[0][1] - poly[0][0] * poly[end][1];
  for (let i = 0; i < end; ++i) {
    const n = i + 1;
    sum += poly[i][0] * poly[n][1] - poly[n][0] * poly[i][1];
  }
  let cw = sum > 0 ? true : false;
  return cw;
}


function splitSubpaths(pathData) {
  let pathDataL = pathData.length;
  let subPathArr = [];
  let subPathMindex = [];
  pathData.forEach(function(com, i) {
    let [type, values] = [com['type'], com['values']];
    if (type == 'M') {
      subPathMindex.push(i);
    }
  });
  //split segments after M command
  subPathMindex.forEach(function(index, i) {
    let n = subPathMindex[i + 1];
    let thisSeg = pathData.slice(index, n);
    subPathArr.push(thisSeg)
  })
  return subPathArr;
}
<script src="https://cdn.jsdelivr.net/npm/path-data-polyfill@1.0.3/path-data-polyfill.min.js"></script>


<p><button type="button" onclick="fixPath(path)">Fix path directions</button></p>
<svg width="325" height="350" viewBox="0 0 325 350" fill="none" xmlns="http://www.w3.org/2000/svg">
        <path d="
        M205.43 59.38C205.43 26.5853 232.015 0 264.81 0C297.605 0 324.19 26.5853 324.19 59.38C324.19 92.1747 297.605 118.76 264.81 118.76C246.525 118.76 230.17 110.495 219.277 97.4975L116.536 157.274C117.985 162.41 118.76 167.83 118.76 173.43C118.76 178.348 118.162 183.127 117.035 187.697L222.002 248.758C232.806 237.522 247.991 230.53 264.81 230.53C297.605 230.53 324.19 257.115 324.19 289.91C324.19 322.705 297.605 349.29 264.81 349.29C232.015 349.29 205.43 322.705 205.43 289.91C205.43 280.791 207.486 272.152 211.159 264.431L109.497 205.292C98.9566 221.836 80.4498 232.81 59.38 232.81C26.5853 232.81 0 206.225 0 173.43C0 140.635 26.5853 114.05 59.38 114.05C79.7735 114.05 97.7659 124.331 108.457 139.992L209.556 81.1716C206.893 74.4248 205.43 67.0733 205.43 59.38Z
        
        M264.81 19C242.509 19 224.43 37.0787 224.43 59.38C224.43 81.6813 242.509 99.76 264.81 99.76C287.111 99.76 305.19 81.6813 305.19 59.38C305.19 37.0787 287.111 19 264.81 19Z
        
        M59.38 133.05C37.0787 133.05 19 151.129 19 173.43C19 195.731 37.0787 213.81 59.38 213.81C81.6813 213.81 99.76 195.731 99.76 173.43C99.76 151.129 81.6813 133.05 59.38 133.05Z
        
        
        M224.43 289.91C224.43 267.609 242.509 249.53 264.81 249.53C287.111 249.53 305.19 267.609 305.19 289.91C305.19 312.211 287.111 330.29 264.81 330.29C242.509 330.29 224.43 312.211 224.43 289.91Z
        
        " fill="#0D0F13" />
    </svg>

在开发工具中检查元素以获取新路径。

它是如何工作的

  1. 解析路径命令(使用path-data-polyfill by Jarek Foksa
    1.2 以规范化的方式检索路径数据(path.getPathData({normalize: true}):
    所有命令将被转换为仅使用绝对MLCZ命令的简化集。
  2. 将路径数据拆分为子路径(每个子路径都以新的M命令开头)
  3. 检查子路径是否与外部形状相交
  4. 检查外部和内部子路径之间的方向(使用isClockwise(path)助手)
  5. 根据需要反转子路径方向(reversePathData(pathdata)
  6. 连接子路径并覆盖原始d属性

Codepen示例

你也可以使用这个codepen辅助程序:修复svg复合路径方向
这个脚本将保留原始的命令类型——所以AQ 命令不会被转换成cubic béziers。

HTML/CSS示例

@font-face {
    font-family: 'icomoon';
    src: url('data:font/woff2;charset=utf-8;base64,d09GMgABAAAAAAKsAA0AAAAABpgAAAJWAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP0ZGVE0cGh4GYACCXhEICoIwgicLDgABNgIkAxgEIAWDGwdFG5gFyJ4FdpMzjFBuZhHDYhE2Lyuvefjx8LVWvt89OwuoLkDsUKWigS2BIwnAKmwRyqeiD1z8VXT0WrS2WSR7w9M/YpFmHip+uESqS2jQkv/fzbMtejpWgbRBCusLh5mNJZxQLwq8+J+4Gbcs0ayoyySyNm4KAw7HxADTzsMwkLHPpyWQZQUKD0Zv7Xy3GOQpg6RIkmGRRZTNaBEChl5xBP7+oo23DrQwAJIQKkKBhJyGYq1CepJu3KLi+GdF1DGd6Klcj0blY/2GbQYFAcSpCPnkiQjsIBSYSGQ1aKhJkjB1VP8HxD9n+u8YsSw0AIRMlEQGIiEBAIICrg7RJwElaKGKPeAyoAAEQihyzqva7VbQSyfP/JuzIacHG9Ph+enDBacfK4dHbovJUxU8A6u/WJQ7Xqm9+nF6qDkbiclwo2VkBgb1ANjxPsxY2H3AZ4bRsplS0Zw5z18lt1pWnMqpZVxycbNKnuXD1oYHbk6mZGvfmfmdMLq4AgJQAH3+SAIPQNz+esn3KSVZ/Z/wwqyM0of8OfegJoEw3uUoQP4sfyGUBSC2AvhN/opAaTcRSAAA6HgjQKjqIZBUzUUgq1qPQKFqBwIVTacQKFXdF6BhvJcsirrQIQIJGQtkZClQIBuxigFprNRxvmfDSncH5TTVNTU14OCqn9JsAJswRX01GR14mZYGrQlL5G2Yjq6yW8/JxgFnVkarTN86GzQ19OgVMQ1MR0YPk6eXNaYXISRh2uCeHNrBzTrX/NNhDoTEti+JJEWWoyj3Hxm3Sk58/FjZQZy3Ai5jAAAA') format('woff2');
    font-weight: normal;
    font-style: normal;
    font-display: swap;
}

.icon{
  font-family: 'icomoon';
  font-size:5vw
}

.icon-share:before {
  content: "\e900";
}
<span class="icon icon-share">

替代方案:paper.js:

如果您的应用程序已经使用了paper.js,您也可以使用它的{{link1:reorient()}}方法来修复路径方向。请参见相关帖子{{link2:“Paper.js reorient: SVG subpaths are lost”}}


非常好的回复,也感谢您对问题的编辑。 - Kyll
1
非常愉快!很高兴我的对子路径方向的痴迷有所帮助 =) 顺便说一下:在paper.js中也有一种修复方向的方法,但您可能不想加载这个额外的库——除非您需要其他路径操作。我已经更新了信息。 - herrstrietzel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接