// Copyright 2010 The Freetype-Go Authors. All rights reserved.// Use of this source code is governed by your choice of either the// FreeType License or the GNU General Public License version 2 (or// any later version), both of which can be found in the LICENSE file.package rasterimport ()// Two points are considered practically equal if the square of the distance// between them is less than one quarter (i.e. 1024 / 4096).const epsilon = fixed.Int52_12(1024)// A Capper signifies how to begin or end a stroked path.typeCapperinterface {// Cap adds a cap to p given a pivot point and the normal vector of a // terminal segment. The normal's length is half of the stroke width.Cap(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6)}// The CapperFunc type adapts an ordinary function to be a Capper.typeCapperFuncfunc(Adder, fixed.Int26_6, fixed.Point26_6, fixed.Point26_6)func ( CapperFunc) ( Adder, fixed.Int26_6, , fixed.Point26_6) { (, , , )}// A Joiner signifies how to join interior nodes of a stroked path.typeJoinerinterface {// Join adds a join to the two sides of a stroked path given a pivot // point and the normal vectors of the trailing and leading segments. // Both normals have length equal to half of the stroke width.Join(lhs, rhs Adder, halfWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6)}// The JoinerFunc type adapts an ordinary function to be a Joiner.typeJoinerFuncfunc(lhs, rhs Adder, halfWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6)func ( JoinerFunc) (, Adder, fixed.Int26_6, , , fixed.Point26_6) { (, , , , , )}// RoundCapper adds round caps to a stroked path.varRoundCapperCapper = CapperFunc(roundCapper)func roundCapper( Adder, fixed.Int26_6, , fixed.Point26_6) {// The cubic Bézier approximation to a circle involves the magic number // (√2 - 1) * 4/3, which is approximately 35/64.const = 35 := pRot90CCW() := .Add() , := .Sub(), .Add() , := .Mul(), .Mul() .Add3(.Add(), .Sub(), ) .Add3(.Add(), .Add(), )}// ButtCapper adds butt caps to a stroked path.varButtCapperCapper = CapperFunc(buttCapper)func buttCapper( Adder, fixed.Int26_6, , fixed.Point26_6) { .Add1(.Add())}// SquareCapper adds square caps to a stroked path.varSquareCapperCapper = CapperFunc(squareCapper)func squareCapper( Adder, fixed.Int26_6, , fixed.Point26_6) { := pRot90CCW() := .Add() .Add1(.Sub()) .Add1(.Add()) .Add1(.Add())}// RoundJoiner adds round joins to a stroked path.varRoundJoinerJoiner = JoinerFunc(roundJoiner)func roundJoiner(, Adder, fixed.Int26_6, , , fixed.Point26_6) { := pDot(pRot90CW(), )if >= 0 {addArc(, , , ) .Add1(.Sub()) } else { .Add1(.Add())addArc(, , pNeg(), pNeg()) }}// BevelJoiner adds bevel joins to a stroked path.varBevelJoinerJoiner = JoinerFunc(bevelJoiner)func bevelJoiner(, Adder, fixed.Int26_6, , , fixed.Point26_6) { .Add1(.Add()) .Add1(.Sub())}// addArc adds a circular arc from pivot+n0 to pivot+n1 to p. The shorter of// the two possible arcs is taken, i.e. the one spanning <= 180 degrees. The// two vectors n0 and n1 must be of equal length.func addArc( Adder, , , fixed.Point26_6) {// r2 is the square of the length of n0. := pDot(, )if < epsilon {// The arc radius is so small that we collapse to a straight line. .Add1(.Add())return }// We approximate the arc by 0, 1, 2 or 3 45-degree quadratic segments plus // a final quadratic segment from s to n1. Each 45-degree segment has // control points {1, 0}, {1, tan(π/8)} and {1/√2, 1/√2} suitably scaled, // rotated and translated. tan(π/8) is approximately 27/64.const = 27varfixed.Point26_6// We determine which octant the angle between n0 and n1 is in via three // dot products. m0, m1 and m2 are n0 rotated clockwise by 45, 90 and 135 // degrees. := pRot45CW() := pRot90CW() := pRot90CW()ifpDot(, ) >= 0 {ifpDot(, ) >= 0 {ifpDot(, ) <= 0 {// n1 is between 0 and 45 degrees clockwise of n0. = } else {// n1 is between 45 and 90 degrees clockwise of n0. .Add2(.Add().Add(.Mul()), .Add()) = } } else { , := .Add(), .Mul() .Add2(.Add().Add(.Mul()), .Add()) .Add2(.Add(), )ifpDot(, ) >= 0 {// n1 is between 90 and 135 degrees clockwise of n0. = } else {// n1 is between 135 and 180 degrees clockwise of n0. .Add2(.Sub(), .Add()) = } } } else {ifpDot(, ) >= 0 {ifpDot(, ) >= 0 {// n1 is between 0 and 45 degrees counter-clockwise of n0. = } else {// n1 is between 45 and 90 degrees counter-clockwise of n0. .Add2(.Add().Sub(.Mul()), .Sub()) = pNeg() } } else { , := .Sub(), .Mul() .Add2(.Add().Sub(.Mul()), .Sub()) .Add2(.Add(), )ifpDot(, ) <= 0 {// n1 is between 90 and 135 degrees counter-clockwise of n0. = pNeg() } else {// n1 is between 135 and 180 degrees counter-clockwise of n0. .Add2(.Sub(), .Sub()) = pNeg() } } }// The final quadratic segment has two endpoints s and n1 and the middle // control point is a multiple of s.Add(n1), i.e. it is on the angle // bisector of those two points. The multiple ranges between 128/256 and // 150/256 as the angle between s and n1 ranges between 0 and 45 degrees. // // When the angle is 0 degrees (i.e. s and n1 are coincident) then // s.Add(n1) is twice s and so the middle control point of the degenerate // quadratic segment should be half s.Add(n1), and half = 128/256. // // When the angle is 45 degrees then 150/256 is the ratio of the lengths of // the two vectors {1, tan(π/8)} and {1 + 1/√2, 1/√2}. // // d is the normalized dot product between s and n1. Since the angle ranges // between 0 and 45 degrees then d ranges between 256/256 and 181/256. := 256 * pDot(, ) / := fixed.Int26_6(150-(150-128)*(-181)/(256-181)) >> 2 .Add2(.Add(.Add().Mul()), .Add())}// midpoint returns the midpoint of two Points.func midpoint(, fixed.Point26_6) fixed.Point26_6 {returnfixed.Point26_6{(.X + .X) / 2, (.Y + .Y) / 2}}// angleGreaterThan45 returns whether the angle between two vectors is more// than 45 degrees.func angleGreaterThan45(, fixed.Point26_6) bool { := pRot45CCW()returnpDot(, ) < 0 || pDot(pRot90CW(), ) < 0}// interpolate returns the point (1-t)*a + t*b.func interpolate(, fixed.Point26_6, fixed.Int52_12) fixed.Point26_6 { := 1<<12 - := *fixed.Int52_12(.X) + *fixed.Int52_12(.X) := *fixed.Int52_12(.Y) + *fixed.Int52_12(.Y)returnfixed.Point26_6{fixed.Int26_6( >> 12), fixed.Int26_6( >> 12)}}// curviest2 returns the value of t for which the quadratic parametric curve// (1-t)²*a + 2*t*(1-t).b + t²*c has maximum curvature.//// The curvature of the parametric curve f(t) = (x(t), y(t)) is// |x′y″-y′x″| / (x′²+y′²)^(3/2).//// Let d = b-a and e = c-2*b+a, so that f′(t) = 2*d+2*e*t and f″(t) = 2*e.// The curvature's numerator is (2*dx+2*ex*t)*(2*ey)-(2*dy+2*ey*t)*(2*ex),// which simplifies to 4*dx*ey-4*dy*ex, which is constant with respect to t.//// Thus, curvature is extreme where the denominator is extreme, i.e. where// (x′²+y′²) is extreme. The first order condition is that// 2*x′*x″+2*y′*y″ = 0, or (dx+ex*t)*ex + (dy+ey*t)*ey = 0.// Solving for t gives t = -(dx*ex+dy*ey) / (ex*ex+ey*ey).func curviest2(, , fixed.Point26_6) fixed.Int52_12 { := int64(.X - .X) := int64(.Y - .Y) := int64(.X - 2*.X + .X) := int64(.Y - 2*.Y + .Y)if == 0 && == 0 {return2048 }returnfixed.Int52_12(-4096 * (* + *) / (* + *))}// A stroker holds state for stroking a path.type stroker struct {// p is the destination that records the stroked path. p Adder// u is the half-width of the stroke. u fixed.Int26_6// cr and jr specify how to end and connect path segments. cr Capper jr Joiner// r is the reverse path. Stroking a path involves constructing two // parallel paths 2*u apart. The first path is added immediately to p, // the second path is accumulated in r and eventually added in reverse. r Path// a is the most recent segment point. anorm is the segment normal of // length u at that point. a, anorm fixed.Point26_6}// addNonCurvy2 adds a quadratic segment to the stroker, where the segment// defined by (k.a, b, c) achieves maximum curvature at either k.a or c.func ( *stroker) (, fixed.Point26_6) {// We repeatedly divide the segment at its middle until it is straight // enough to approximate the stroke by just translating the control points. // ds and ps are stacks of depths and points. t is the top of the stack.const = 5var ( [ + 1]int [2* + 3]fixed.Point26_6int )// Initially the ps stack has one quadratic segment of depth zero. [0] = 0 [2] = .a [1] = [0] = := .anormvarfixed.Point26_6for { := [] := [2*+2] := [2*+1] := [2*+0] := .Sub() := .Sub() := pDot(, ) < fixed.Int52_12(1<<12) := pDot(, ) < fixed.Int52_12(1<<12)if && {// Approximate the segment by a circular arc. = pRot90CCW(pNorm(, .u)) := midpoint(, )addArc(.p, , , )addArc(&.r, , pNeg(), pNeg()) } elseif < && angleGreaterThan45(, ) {// Divide the segment in two and push both halves on the stack. := midpoint(, ) := midpoint(, ) ++ [+0] = + 1 [-1] = + 1 [2*+2] = [2*+1] = [2*+0] = midpoint(, ) [2*-1] = continue } else {// Translate the control points. := pRot90CCW(pNorm(.Sub(), .u)) = pRot90CCW(pNorm(, .u)) .p.Add2(.Add(), .Add()) .r.Add2(.Sub(), .Sub()) }if == 0 { .a, .anorm = , return } -- = }panic("unreachable")}// Add1 adds a linear segment to the stroker.func ( *stroker) ( fixed.Point26_6) { := pRot90CCW(pNorm(.Sub(.a), .u))iflen(.r) == 0 { .p.Start(.a.Add()) .r.Start(.a.Sub()) } else { .jr.Join(.p, &.r, .u, .a, .anorm, ) } .p.Add1(.Add()) .r.Add1(.Sub()) .a, .anorm = , }// Add2 adds a quadratic segment to the stroker.func ( *stroker) (, fixed.Point26_6) { := .Sub(.a) := .Sub() := pRot90CCW(pNorm(, .u))iflen(.r) == 0 { .p.Start(.a.Add()) .r.Start(.a.Sub()) } else { .jr.Join(.p, &.r, .u, .a, .anorm, ) }// Approximate nearly-degenerate quadratics by linear segments. := pDot(, ) < epsilon := pDot(, ) < epsilonif || { := pRot90CCW(pNorm(.Sub(.a), .u)) .p.Add1(.Add()) .r.Add1(.Sub()) .a, .anorm = , return }// The quadratic segment (k.a, b, c) has a point of maximum curvature. // If this occurs at an end point, we process the segment as a whole. := curviest2(.a, , )if <= 0 || 4096 <= { .addNonCurvy2(, )return }// Otherwise, we perform a de Casteljau decomposition at the point of // maximum curvature and process the two straighter parts. := interpolate(.a, , ) := interpolate(, , ) := interpolate(, , )// If the vectors ab and bc are close to being in opposite directions, // then the decomposition can become unstable, so we approximate the // quadratic segment by two linear segments joined by an arc. := pRot90CCW(pNorm(, .u))ifpDot(, ) < -fixed.Int52_12(.u)*fixed.Int52_12(.u)*2047/2048 { := pDot(, ) < 0 .p.Add1(.Add())if { := pRot90CW()addArc(.p, , , )addArc(.p, , , ) } .p.Add1(.Add()) .p.Add1(.Add()) .r.Add1(.Sub())if ! { := pRot90CW()addArc(&.r, , pNeg(), )addArc(&.r, , , pNeg()) } .r.Add1(.Sub()) .r.Add1(.Sub()) .a, .anorm = , return }// Process the decomposed parts. .addNonCurvy2(, ) .addNonCurvy2(, )}// Add3 adds a cubic segment to the stroker.func ( *stroker) (, , fixed.Point26_6) {panic("freetype/raster: stroke unimplemented for cubic segments")}// stroke adds the stroked Path q to p, where q consists of exactly one curve.func ( *stroker) ( Path) {// Stroking is implemented by deriving two paths each k.u apart from q. // The left-hand-side path is added immediately to k.p; the right-hand-side // path is accumulated in k.r. Once we've finished adding the LHS to k.p, // we add the RHS in reverse order. .r = make(Path, 0, len()) .a = fixed.Point26_6{[1], [2]}for := 4; < len(); {switch [] {case1: .Add1(fixed.Point26_6{[+1], [+2]}, ) += 4case2: .Add2(fixed.Point26_6{[+1], [+2]},fixed.Point26_6{[+3], [+4]}, ) += 6case3: .Add3(fixed.Point26_6{[+1], [+2]},fixed.Point26_6{[+3], [+4]},fixed.Point26_6{[+5], [+6]}, ) += 8default:panic("freetype/raster: bad path") } }iflen(.r) == 0 {return }// TODO(nigeltao): if q is a closed curve then we should join the first and // last segments instead of capping them. .cr.Cap(.p, .u, .lastPoint(), pNeg(.anorm))addPathReversed(.p, .r) := .firstPoint() .cr.Cap(.p, .u, , .Sub(fixed.Point26_6{.r[1], .r[2]}))}// Stroke adds q stroked with the given width to p. The result is typically// self-intersecting and should be rasterized with UseNonZeroWinding.// cr and jr may be nil, which defaults to a RoundCapper or RoundJoiner.func ( Adder, Path, fixed.Int26_6, Capper, Joiner) {iflen() == 0 {return }if == nil { = RoundCapper }if == nil { = RoundJoiner }if [0] != 0 {panic("freetype/raster: bad path") } := stroker{p: , u: / 2, cr: , jr: } := 0for := 4; < len(); {switch [] {case0: .stroke([:]) , = , +4case1: += 4case2: += 6case3: += 8default:panic("freetype/raster: bad path") } } .stroke([:])}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.