OLD | NEW |
1 // Copyright 2010 The Freetype-Go Authors. All rights reserved. | 1 // Copyright 2010 The Freetype-Go Authors. All rights reserved. |
2 // Use of this source code is governed by your choice of either the | 2 // Use of this source code is governed by your choice of either the |
3 // FreeType License or the GNU General Public License version 2, | 3 // FreeType License or the GNU General Public License version 2, |
4 // both of which can be found in the LICENSE file. | 4 // both of which can be found in the LICENSE file. |
5 | 5 |
6 package raster | 6 package raster |
7 | 7 |
8 import ( | 8 import ( |
9 "fmt" | 9 "fmt" |
10 "math" | 10 "math" |
(...skipping 251 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
262 (*p)[n+7] = 3 | 262 (*p)[n+7] = 3 |
263 } | 263 } |
264 | 264 |
265 // AddPath adds the Path q to p. | 265 // AddPath adds the Path q to p. |
266 func (p *Path) AddPath(q Path) { | 266 func (p *Path) AddPath(q Path) { |
267 n, m := len(*p), len(q) | 267 n, m := len(*p), len(q) |
268 p.grow(m) | 268 p.grow(m) |
269 copy((*p)[n:n+m], q) | 269 copy((*p)[n:n+m], q) |
270 } | 270 } |
271 | 271 |
272 // TODO(nigeltao): should a Cap be a func rather than an int, so that callers | 272 // A Capper signifies how to begin or end a stroked path. |
273 // can specify custom cap styles? Similarly for Join. | 273 type Capper interface { |
| 274 » // Cap adds a cap to p given a pivot point and the normal vector of a |
| 275 » // terminal segment. The normal's length is half of the stroke width. |
| 276 » Cap(p Adder, halfWidth Fix32, pivot, n1 Point) |
| 277 } |
274 | 278 |
275 // A Cap signifies how to begin or end a stroked curve. | 279 // The CapperFunc type adapts an ordinary function to be a Capper. |
276 type Cap int | 280 type CapperFunc func(Adder, Fix32, Point, Point) |
277 | 281 |
278 const ( | 282 func (f CapperFunc) Cap(p Adder, halfWidth Fix32, pivot, n1 Point) { |
279 » RoundCap Cap = iota | 283 » f(p, halfWidth, pivot, n1) |
280 » ButtCap | 284 } |
281 » SquareCap | |
282 ) | |
283 | 285 |
284 // A Join signifies how to join interior nodes of a stroked curve. | 286 // A Joiner signifies how to join interior nodes of a stroked path. |
285 type Join int | 287 type Joiner interface { |
| 288 » // Join adds a join to the two sides of a stroked path given a pivot |
| 289 » // point and the normal vectors of the trailing and leading segments. |
| 290 » // Both normals have length equal to half of the stroke width. |
| 291 » Join(lhs, rhs Adder, halfWidth Fix32, pivot, n0, n1 Point) |
| 292 } |
286 | 293 |
287 const ( | 294 // The JoinerFunc type adapts an ordinary function to be a Joiner. |
288 » RoundJoin Join = iota | 295 type JoinerFunc func(lhs, rhs Adder, halfWidth Fix32, pivot, n0, n1 Point) |
289 » BevelJoin | 296 |
290 » MiterJoin | 297 func (f JoinerFunc) Join(lhs, rhs Adder, halfWidth Fix32, pivot, n0, n1 Point) { |
291 ) | 298 » f(lhs, rhs, halfWidth, pivot, n0, n1) |
| 299 } |
292 | 300 |
293 // AddStroke adds a stroked Path. | 301 // AddStroke adds a stroked Path. |
294 func (p *Path) AddStroke(q Path, width Fix32, cap Cap, join Join) { | 302 func (p *Path) AddStroke(q Path, width Fix32, cr Capper, jr Joiner) { |
295 » Stroke(p, q, width, cap, join) | 303 » Stroke(p, q, width, cr, jr) |
296 } | 304 } |
297 | 305 |
298 // Stroke adds the stroked Path q to p. The resultant stroked path is typically | 306 // Stroke adds the stroked Path q to p. The resultant stroked path is typically |
299 // self-intersecting and should be rasterized with UseNonZeroWinding. | 307 // self-intersecting and should be rasterized with UseNonZeroWinding. |
300 func Stroke(p Adder, q Path, width Fix32, cap Cap, join Join) { | 308 // cr and jr may be nil, which defaults to a RoundCapper or RoundJoiner. |
| 309 func Stroke(p Adder, q Path, width Fix32, cr Capper, jr Joiner) { |
301 if len(q) == 0 { | 310 if len(q) == 0 { |
302 return | 311 return |
303 } | 312 } |
| 313 if cr == nil { |
| 314 cr = RoundCapper |
| 315 } |
| 316 if jr == nil { |
| 317 jr = RoundJoiner |
| 318 } |
304 if q[0] != 0 { | 319 if q[0] != 0 { |
305 panic("freetype/raster: bad path") | 320 panic("freetype/raster: bad path") |
306 } | 321 } |
307 i := 0 | 322 i := 0 |
308 for j := 4; j < len(q); { | 323 for j := 4; j < len(q); { |
309 switch q[j] { | 324 switch q[j] { |
310 case 0: | 325 case 0: |
311 » » » stroke(p, q[i:j], width, cap, join) | 326 » » » stroke(p, q[i:j], width, cr, jr) |
312 i, j = j, j+4 | 327 i, j = j, j+4 |
313 case 1: | 328 case 1: |
314 j += 4 | 329 j += 4 |
315 case 2: | 330 case 2: |
316 j += 6 | 331 j += 6 |
317 case 3: | 332 case 3: |
318 j += 8 | 333 j += 8 |
319 } | 334 } |
320 } | 335 } |
321 » stroke(p, q[i:len(q)], width, cap, join) | 336 » stroke(p, q[i:len(q)], width, cr, jr) |
322 } | 337 } |
323 | 338 |
324 func addCap(p Adder, cap Cap, center, end Point) { | 339 // A RoundCapper adds round caps to a stroked path. |
325 » switch cap { | 340 var RoundCapper Capper = CapperFunc(func(p Adder, halfWidth Fix32, pivot, n1 Poi
nt) { |
326 » case RoundCap: | 341 » // The cubic Bézier approximation to a circle involves the magic number |
327 » » // The cubic Bézier approximation to a circle involves the magic
number | 342 » // (√2 - 1) * 4/3, which is approximately 141/256. |
328 » » // (√2 - 1) * 4/3, which is approximately 141/256. | 343 » const k = 141 |
329 » » const k = 141 | 344 » e := n1.Rot90CCW() |
330 » » d := end.Sub(center) | 345 » side := pivot.Add(e) |
331 » » e := d.Rot90CCW() | 346 » start, end := pivot.Sub(n1), pivot.Add(n1) |
332 » » side := center.Add(e) | 347 » d, e := n1.Mul(k), e.Mul(k) |
333 » » start := center.Sub(d) | 348 » p.Add3(start.Add(e), side.Sub(d), side) |
334 » » d, e = d.Mul(k), e.Mul(k) | 349 » p.Add3(side.Add(d), end.Add(e), end) |
335 » » p.Add3(start.Add(e), side.Sub(d), side) | 350 }) |
336 » » p.Add3(side.Add(d), end.Add(e), end) | 351 |
337 » case ButtCap: | 352 // A ButtCapper adds butt caps to a stroked path. |
338 » » p.Add1(end) | 353 var ButtCapper Capper = CapperFunc(func(p Adder, halfWidth Fix32, pivot, n1 Poin
t) { |
339 » case SquareCap: | 354 » p.Add1(pivot.Add(n1)) |
340 » » d := end.Sub(center) | 355 }) |
341 » » e := d.Rot90CCW() | 356 |
342 » » side := center.Add(e) | 357 // A SquareCapper adds square caps to a stroked path. |
343 » » p.Add1(side.Sub(d)) | 358 var SquareCapper Capper = CapperFunc(func(p Adder, halfWidth Fix32, pivot, n1 Po
int) { |
344 » » p.Add1(side.Add(d)) | 359 » e := n1.Rot90CCW() |
345 » » p.Add1(end) | 360 » side := pivot.Add(e) |
| 361 » p.Add1(side.Sub(n1)) |
| 362 » p.Add1(side.Add(n1)) |
| 363 » p.Add1(pivot.Add(n1)) |
| 364 }) |
| 365 |
| 366 // A RoundJoiner adds round joins to a stroked path. |
| 367 var RoundJoiner Joiner = JoinerFunc(func(lhs, rhs Adder, haflWidth Fix32, pivot,
n0, n1 Point) { |
| 368 » dot := n0.Rot90CW().Dot(n1) |
| 369 » if dot >= 0 { |
| 370 » » addArc(lhs, pivot, n0, n1) |
| 371 » » rhs.Add1(pivot.Sub(n1)) |
| 372 » } else { |
| 373 » » lhs.Add1(pivot.Add(n1)) |
| 374 » » addArc(rhs, pivot, n0.Neg(), n1.Neg()) |
346 } | 375 } |
347 } | 376 }) |
348 | 377 |
349 func addJoin(lhs, rhs Adder, join Join, a, anorm, bnorm Point) { | 378 // A BevelJoiner adds bevel joins to a stroked path. |
350 » switch join { | 379 var BevelJoiner Joiner = JoinerFunc(func(lhs, rhs Adder, haflWidth Fix32, pivot,
n0, n1 Point) { |
351 » case RoundJoin: | 380 » lhs.Add1(pivot.Add(n1)) |
352 » » dot := anorm.Rot90CW().Dot(bnorm) | 381 » rhs.Add1(pivot.Sub(n1)) |
353 » » if dot >= 0 { | 382 }) |
354 » » » addArc(lhs, a, anorm, bnorm) | |
355 » » » rhs.Add1(a.Sub(bnorm)) | |
356 » » } else { | |
357 » » » lhs.Add1(a.Add(bnorm)) | |
358 » » » addArc(rhs, a, anorm.Neg(), bnorm.Neg()) | |
359 » » } | |
360 » case BevelJoin: | |
361 » » lhs.Add1(a.Add(bnorm)) | |
362 » » rhs.Add1(a.Sub(bnorm)) | |
363 » case MiterJoin: | |
364 » » panic("freetype/raster: miter join unimplemented") | |
365 » } | |
366 } | |
367 | 383 |
368 // addArc adds a circular arc from pivot+n0 to pivot+n1 to p. The shorter of | 384 // addArc adds a circular arc from pivot+n0 to pivot+n1 to p. The shorter of |
369 // the two possible arcs is taken, i.e. the one spanning <= 180 degrees. | 385 // the two possible arcs is taken, i.e. the one spanning <= 180 degrees. |
370 // The two vectors n0 and n1 must be of equal length. | 386 // The two vectors n0 and n1 must be of equal length. |
371 func addArc(p Adder, pivot, n0, n1 Point) { | 387 func addArc(p Adder, pivot, n0, n1 Point) { |
372 // r2 is the square of the length of n0. | 388 // r2 is the square of the length of n0. |
373 r2 := n0.Dot(n0) | 389 r2 := n0.Dot(n0) |
374 if r2 < 4096 { | 390 if r2 < 4096 { |
375 // The arc radius is so small that we collapse to a straight lin
e. | 391 // The arc radius is so small that we collapse to a straight lin
e. |
376 p.Add1(pivot.Add(n1)) | 392 p.Add1(pivot.Add(n1)) |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
444 // When the angle is 45 degrees then 150/256 is the ratio of the lengths
of | 460 // When the angle is 45 degrees then 150/256 is the ratio of the lengths
of |
445 // the two vectors {1, tan(π/8)} and {1 + 1/√2, 1/√2}. | 461 // the two vectors {1, tan(π/8)} and {1 + 1/√2, 1/√2}. |
446 // d is the normalized dot product between s and n1. Since the angle ran
ges | 462 // d is the normalized dot product between s and n1. Since the angle ran
ges |
447 // between 0 and 45 degrees then d ranges between 256/256 and 181/256. | 463 // between 0 and 45 degrees then d ranges between 256/256 and 181/256. |
448 d := 256 * s.Dot(n1) / r2 | 464 d := 256 * s.Dot(n1) / r2 |
449 multiple := Fix32(150 - 22*(d-181)/(256-181)) | 465 multiple := Fix32(150 - 22*(d-181)/(256-181)) |
450 p.Add2(pivot.Add(s.Add(n1).Mul(multiple)), pivot.Add(n1)) | 466 p.Add2(pivot.Add(s.Add(n1).Mul(multiple)), pivot.Add(n1)) |
451 } | 467 } |
452 | 468 |
453 // stroke adds the stroked Path q to p, where q consists of exactly one curve. | 469 // stroke adds the stroked Path q to p, where q consists of exactly one curve. |
454 func stroke(p Adder, q Path, width Fix32, cap Cap, join Join) { | 470 func stroke(p Adder, q Path, width Fix32, cr Capper, jr Joiner) { |
455 // Stroking is implemented by deriving two paths each width/2 apart from
q. | 471 // Stroking is implemented by deriving two paths each width/2 apart from
q. |
456 // The left-hand-side path is added immediately to p; the right-hand-sid
e | 472 // The left-hand-side path is added immediately to p; the right-hand-sid
e |
457 // path is accumulated in r, and once we've finished adding the LHS to p | 473 // path is accumulated in r, and once we've finished adding the LHS to p |
458 // we add the RHS in reverse order. | 474 // we add the RHS in reverse order. |
459 r := Path(make([]Fix32, 0, len(q))) | 475 r := Path(make([]Fix32, 0, len(q))) |
| 476 u := width / 2 |
460 var start, anorm Point | 477 var start, anorm Point |
461 a := Point{q[1], q[2]} | 478 a := Point{q[1], q[2]} |
462 i := 4 | 479 i := 4 |
463 for i < len(q) { | 480 for i < len(q) { |
464 switch q[i] { | 481 switch q[i] { |
465 case 1: | 482 case 1: |
466 b := Point{q[i+1], q[i+2]} | 483 b := Point{q[i+1], q[i+2]} |
467 » » » bnorm := b.Sub(a).Norm(width / 2).Rot90CCW() | 484 » » » bnorm := b.Sub(a).Norm(u).Rot90CCW() |
468 if i == 4 { | 485 if i == 4 { |
469 start = a.Add(bnorm) | 486 start = a.Add(bnorm) |
470 p.Start(start) | 487 p.Start(start) |
471 r.Start(a.Sub(bnorm)) | 488 r.Start(a.Sub(bnorm)) |
472 } else { | 489 } else { |
473 » » » » addJoin(p, &r, join, a, anorm, bnorm) | 490 » » » » jr.Join(p, &r, u, a, anorm, bnorm) |
474 } | 491 } |
475 p.Add1(b.Add(bnorm)) | 492 p.Add1(b.Add(bnorm)) |
476 r.Add1(b.Sub(bnorm)) | 493 r.Add1(b.Sub(bnorm)) |
477 a, anorm = b, bnorm | 494 a, anorm = b, bnorm |
478 i += 4 | 495 i += 4 |
479 case 2: | 496 case 2: |
480 panic("freetype/raster: stroke unimplemented for quadrat
ic segments") | 497 panic("freetype/raster: stroke unimplemented for quadrat
ic segments") |
481 case 3: | 498 case 3: |
482 panic("freetype/raster: stroke unimplemented for cubic s
egments") | 499 panic("freetype/raster: stroke unimplemented for cubic s
egments") |
483 default: | 500 default: |
484 panic("freetype/raster: bad path") | 501 panic("freetype/raster: bad path") |
485 } | 502 } |
486 } | 503 } |
487 i = len(r) - 1 | 504 i = len(r) - 1 |
488 » addCap(p, cap, Point{q[len(q)-3], q[len(q)-2]}, Point{r[i-2], r[i-1]}) | 505 » cr.Cap(p, u, Point{q[len(q)-3], q[len(q)-2]}, anorm.Neg()) |
489 // Add r reversed to p. | 506 // Add r reversed to p. |
490 // For example, if r consists of a linear segment from A to B followed b
y a | 507 // For example, if r consists of a linear segment from A to B followed b
y a |
491 // quadratic segment from B to C to D, then the values of r looks like: | 508 // quadratic segment from B to C to D, then the values of r looks like: |
492 // index: 01234567890123 | 509 // index: 01234567890123 |
493 // value: 0AA01BB12CCDD2 | 510 // value: 0AA01BB12CCDD2 |
494 // So, when adding r backwards to p, we want to Add2(C, B) followed by A
dd1(A). | 511 // So, when adding r backwards to p, we want to Add2(C, B) followed by A
dd1(A). |
495 loop: | 512 loop: |
496 for { | 513 for { |
497 switch r[i] { | 514 switch r[i] { |
498 case 0: | 515 case 0: |
499 break loop | 516 break loop |
500 case 1: | 517 case 1: |
501 i -= 4 | 518 i -= 4 |
502 p.Add1(Point{r[i-2], r[i-1]}) | 519 p.Add1(Point{r[i-2], r[i-1]}) |
503 case 2: | 520 case 2: |
504 i -= 6 | 521 i -= 6 |
505 p.Add2(Point{r[i+2], r[i+3]}, Point{r[i-2], r[i-1]}) | 522 p.Add2(Point{r[i+2], r[i+3]}, Point{r[i-2], r[i-1]}) |
506 case 3: | 523 case 3: |
507 i -= 8 | 524 i -= 8 |
508 p.Add3(Point{r[i+4], r[i+5]}, Point{r[i+2], r[i+3]}, Poi
nt{r[i-2], r[i-1]}) | 525 p.Add3(Point{r[i+4], r[i+5]}, Point{r[i+2], r[i+3]}, Poi
nt{r[i-2], r[i-1]}) |
509 default: | 526 default: |
510 panic("freetype/raster: bad path") | 527 panic("freetype/raster: bad path") |
511 } | 528 } |
512 } | 529 } |
513 // TODO(nigeltao): if q is a closed path then we should join the first a
nd | 530 // TODO(nigeltao): if q is a closed path then we should join the first a
nd |
514 // last segments instead of capping them. | 531 // last segments instead of capping them. |
515 » addCap(p, cap, Point{q[1], q[2]}, start) | 532 » pivot := Point{q[1], q[2]} |
| 533 » cr.Cap(p, u, pivot, start.Sub(pivot)) |
516 } | 534 } |
OLD | NEW |