これは、文字のパスに沿って円を視覚的に配置することによって手動で行うのは難しい作業です。
人間の介入なしに自動的に (自動的に!) するのはさらに困難です。
円を自動的に配置して文字を形成する方法は次のとおりです。
答えは2部...
「字形」を見つけて、
円を作成してレターフォームを塗りつぶし、輪郭を描きます。
1.難しい部分
Frederik De Bleser はopentype.js
、.ttf フォント ファイルを受け取り、キャンバス上の二次曲線を使用して、指定された文字のグリフ アウトラインを解析するという名前の優れたライブラリをコーディングしました: https://github.com/nodebox/opentype.js
2. 硬さが少しだけ低い部分
各文字について:
各二次曲線で「多くの」点を見つけます。間隔 T で曲線上の [x,y] を計算するアルゴリズムは次のとおりです。T は、曲線の開始時の 0.00 から曲線の終了時の 1.00 までの範囲になります。T は曲線に沿って等間隔の [x,y] を生成しないため、オーバーサンプリングする必要があります (「多数」とは、0.00 から 1.00 の間の T の 1000 値を意味する場合があります)。
function getQuadraticBezierXYatT(startPt,controlPt,endPt,T) {
var x = Math.pow(1-T,2) * startPt.x + 2 * (1-T) * T * controlPt.x + Math.pow(T,2) * endPt.x;
var y = Math.pow(1-T,2) * startPt.y + 2 * (1-T) * T * controlPt.y + Math.pow(T,2) * endPt.y;
return( {x:x,y:y} );
}
それらの点で曲線の角度に接する角度を見つけます。(基本的に、曲線に対して直角になる角度を計算します)。二次方程式の次の導関数を使用してそれを行うことができます。
function quadraticBezierTangentAngle(t, p0, p2, p1) {
var tt = 1 - t;
var dx = (tt * p1.x + t * p2.x) - (tt * p0.x + t * p1.x);
var dy = (tt * p1.y + t * p2.y) - (tt * p0.y + t * p1.y);
return Math.tan(Math.atan2(dy,dx));
}
曲線の始点から始めて、現在の [x,y] から次の [x,y] までの各距離を計算します。ピタゴラスの定理でこれを行うことができます。
var dx=nextX-currentX;
var dy=nextY-currentY;
var distance=Math.sqrt(dx*dx+dy*dy);
残りのすべての [x,y] 要素が前の [x,y] 要素から 1px 離れているように、配列の重複を排除します。これを行うには、最初の配列の値を 2 番目の配列に入力します。parseInt( nextInOriginalArray - lastDistanceInNewArray)==1;
各文字を構成する円の半径を決定します。これは、実際には思ったよりも難しいことです。「ブロック」フォントの場合は、キャンバスに文字「I」を描くことができます。次に、を使用してすべてのピクセルをフェッチしgetImageData
ます。文字の垂直中央で水平に走る不透明なピクセルの数を検索して、「I」の垂直ストロークの幅を計算します。ブロック状のフォントの場合は、var radius = horizontalOpaquePixelCount/2;
. 幅が可変のフォントの場合は、工夫が必要です。多分var radius = horizontalOpaquePixelCount/3;
またはvar radius = horizontalOpaquePixelCount/4;
。
radius*2
ポイント配列を反復処理し、ピクセルごとに新しい円を定義します。次のように接線角度と三角法を使用して、各円の中心点を計算します。
var centerX = curvePointX + radius*Math.cos(tangentAngle);
var centerY = curvePointY + radius*Math.sin(tangentAngle);
円を作成しているときに、ある時点で文字の曲線が元に戻るので、作成する新しい円をそれぞれ確認して、既存の円と重ならないようにする必要があります。次のように、新しい円が既存の各円と交差するかどうかを計算できます。
var dx = newCircleCenterX - existingCircleCenterX;
var dy = newCircleCenterY - existingCircleCenterY;
var distance=Math.sqrt(dx*dx+dy*dy);
var circlesAreIntersecting=(distance<=newCircleRadius+existingCircleRadius);
微調整: レターのパスのいくつかの端点の近くで、次の完全な半径の円がレターフォームからこぼれることがわかります。その場合は、いくつかの円の半径を小さくして、文字の形に合わせることができます。円の固定半径だけが必要な場合は、すべての円の平均半径に基づいて、すべての円の固定半径を再計算できます。
例えば。これが15個の円からなる「L」の文字。

しかし、2 つの赤い円は文字の形から外れています。(1) 赤い円を縮小してレターフォームの内側に収めるか、(2) レターフォームに収まる平均半径に基づいて新しい固定円半径を再計算することができます。
var total=0;
total += greenRadii * 13;
total += verticalRedRadiusResizedToFitInsideLetterform;
total += horizontalRedRadiusResizedToFitInsideLetterform;
var newRadius = total / 15;
2 つの線の交点を計算することにより、レターフォームに適合する赤い半径の長さを計算できます: (1) 最後の緑の円の中心と赤い円の中心を結ぶことによって形成される線分、(2) から垂直に形成される線カーブの最後のポイント。2 つの線の交点を計算するアルゴリズムは次のとおりです。
// Get interseting point of 2 line segments (if any)
// Attribution: http://paulbourke.net/geometry/pointlineplane/
function line2lineIntersection(p0,p1,p2,p3) {
var unknownA = (p3.x-p2.x) * (p0.y-p2.y) - (p3.y-p2.y) * (p0.x-p2.x);
var unknownB = (p1.x-p0.x) * (p0.y-p2.y) - (p1.y-p0.y) * (p0.x-p2.x);
var denominator = (p3.y-p2.y) * (p1.x-p0.x) - (p3.x-p2.x) * (p1.y-p0.y);
// Test if Coincident
// If the denominator and numerator for the ua and ub are 0
// then the two lines are coincident.
if(unknownA==0 && unknownB==0 && denominator==0){return(null);}
// Test if Parallel
// If the denominator for the equations for ua and ub is 0
// then the two lines are parallel.
if (denominator == 0) return null;
// If the intersection of line segments is required
// then it is only necessary to test if ua and ub lie between 0 and 1.
// Whichever one lies within that range then the corresponding
// line segment contains the intersection point.
// If both lie within the range of 0 to 1 then
// the intersection point is within both line segments.
unknownA /= denominator;
unknownB /= denominator;
var isIntersecting=(unknownA>=0 && unknownA<=1 && unknownB>=0 && unknownB<=1)
if(!isIntersecting){return(null);}
return({
x: p0.x + unknownA * (p1.x-p0.x),
y: p0.y + unknownA * (p1.y-p0.y)
});
}