1 /* Latin.java -- Latin specific glyph handling 2 Copyright (C) 2006 Free Software Foundation, Inc. 3 4 This file is part of GNU Classpath. 5 6 GNU Classpath is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 GNU Classpath is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with GNU Classpath; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19 02110-1301 USA. 20 21 Linking this library statically or dynamically with other modules is 22 making a combined work based on this library. Thus, the terms and 23 conditions of the GNU General Public License cover the whole 24 combination. 25 26 As a special exception, the copyright holders of this library give you 27 permission to link this library with independent modules to produce an 28 executable, regardless of the license terms of these independent 29 modules, and to copy and distribute the resulting executable under 30 terms of your choice, provided that you also meet, for each linked 31 independent module, the terms and conditions of the license of that 32 module. An independent module is a module which is not derived from 33 or based on this library. If you modify this library, you may extend 34 this exception to your version of the library, but you are not 35 obligated to do so. If you do not wish to do so, delete this 36 exception statement from your version. */ 37 38 39 package gnu.java.awt.font.autofit; 40 41 import java.awt.geom.AffineTransform; 42 import java.util.HashSet; 43 44 import gnu.java.awt.font.opentype.OpenTypeFont; 45 import gnu.java.awt.font.opentype.truetype.Fixed; 46 import gnu.java.awt.font.opentype.truetype.Point; 47 import gnu.java.awt.font.opentype.truetype.Zone; 48 49 /** 50 * Implements Latin specific glyph handling. 51 */ 52 class Latin 53 implements Script, Constants 54 { 55 56 static final int MAX_WIDTHS = 16; 57 58 private final static int MAX_TEST_CHARS = 12; 59 60 /** 61 * The types of the 6 blue zones. 62 */ 63 private static final int CAPITAL_TOP = 0; 64 private static final int CAPITAL_BOTTOM = 1; 65 private static final int SMALL_F_TOP = 2; 66 private static final int SMALL_TOP = 3; 67 private static final int SMALL_BOTTOM = 4; 68 private static final int SMALL_MINOR = 5; 69 static final int BLUE_MAX = 6; 70 71 /** 72 * The test chars for the blue zones. 73 * 74 * @see #initBlues(LatinMetrics, OpenTypeFont) 75 */ 76 private static final String[] TEST_CHARS = 77 new String[]{"THEZOCQS", "HEZLOCUS", "fijkdbh", 78 "xzroesc", "xzroesc", "pqgjy"}; 79 applyHints(GlyphHints hints, Zone outline, ScriptMetrics metrics)80 public void applyHints(GlyphHints hints, Zone outline, ScriptMetrics metrics) 81 { 82 hints.reload(outline); 83 hints.rescale(metrics); 84 if (hints.doHorizontal()) 85 { 86 detectFeatures(hints, DIMENSION_HORZ); 87 } 88 if (hints.doVertical()) 89 { 90 detectFeatures(hints, DIMENSION_VERT); 91 computeBlueEdges(hints, (LatinMetrics) metrics); 92 } 93 // Grid-fit the outline. 94 for (int dim = 0; dim < DIMENSION_MAX; dim++) 95 { 96 if (dim == DIMENSION_HORZ && hints.doHorizontal() 97 || dim == DIMENSION_VERT && hints.doVertical()) 98 { 99 hintEdges(hints, dim); 100 if (hints.doAlignEdgePoints()) 101 hints.alignEdgePoints(dim); 102 if (hints.doAlignStrongPoints()) 103 hints.alignStrongPoints(dim); 104 if (hints.doAlignWeakPoints()) 105 hints.alignWeakPoints(dim); 106 107 } 108 } 109 // FreeType does a save call here. I guess that's not needed as we operate 110 // on the live glyph data anyway. 111 } 112 hintEdges(GlyphHints hints, int dim)113 private void hintEdges(GlyphHints hints, int dim) 114 { 115 AxisHints axis = hints.axis[dim]; 116 Edge[] edges = axis.edges; 117 int numEdges = axis.numEdges; 118 Edge anchor = null; 119 int hasSerifs = 0; 120 121 // We begin by aligning all stems relative to the blue zone if 122 // needed -- that's only for horizontal edges. 123 if (dim == DIMENSION_VERT) 124 { 125 for (int e = 0; e < numEdges; e++) 126 { 127 Edge edge = edges[e]; 128 if ((edge.flags & Segment.FLAG_EDGE_DONE) != 0) 129 continue; 130 131 Width blue = edge.blueEdge; 132 Edge edge1 = null; 133 Edge edge2 = edge.link; 134 if (blue != null) 135 { 136 edge1 = edge; 137 } 138 else if (edge2 != null && edge2.blueEdge != null) 139 { 140 blue = edge2.blueEdge; 141 edge1 = edge2; 142 edge2 = edge; 143 } 144 if (edge1 == null) 145 continue; 146 147 edge1.pos = blue.fit; 148 edge1.flags |= Segment.FLAG_EDGE_DONE; 149 150 if (edge2 != null && edge2.blueEdge == null) 151 { 152 alignLinkedEdge(hints, dim, edge1, edge2); 153 edge2.flags |= Segment.FLAG_EDGE_DONE; 154 } 155 if (anchor == null) 156 anchor = edge; 157 } 158 } 159 160 // Now we will align all stem edges, trying to maintain the 161 // relative order of stems in the glyph. 162 for (int e = 0; e < numEdges; e++) 163 { 164 Edge edge = edges[e]; 165 if ((edge.flags & Segment.FLAG_EDGE_DONE) != 0) 166 continue; 167 Edge edge2 = edge.link; 168 if (edge2 == null) 169 { 170 hasSerifs++; 171 continue; 172 } 173 // Now align the stem. 174 // This should not happen, but it's better to be safe. 175 if (edge2.blueEdge != null || axis.getEdgeIndex(edge2) < e) 176 { 177 alignLinkedEdge(hints, dim, edge2, edge); 178 edge.flags |= Segment.FLAG_EDGE_DONE; 179 continue; 180 } 181 182 if (anchor == null) 183 { 184 int orgLen = edge2.opos - edge.opos; 185 int curLen = computeStemWidth(hints, dim, orgLen, edge.flags, 186 edge2.flags); 187 int uOff, dOff, orgCenter, curPos1, error1, error2; 188 if (curLen <= 64) // < 1 Pixel. 189 { 190 uOff = 32; 191 dOff = 32; 192 } 193 else 194 { 195 uOff = 38; 196 dOff = 26; 197 } 198 if (curLen < 96) 199 { 200 orgCenter = edge.opos + (orgLen >> 1); 201 curPos1 = Utils.pixRound(orgCenter); 202 error1 = orgCenter - (curPos1 - uOff); 203 if (error1 < 0) 204 error1 = -error1; 205 error2 = orgCenter - (curPos1 + dOff); 206 if (error2 < 0) 207 error2 = -error2; 208 if (error1 < error2) 209 { 210 curPos1 -= uOff; 211 } 212 else 213 { 214 curPos1 += dOff; 215 } 216 edge.pos = curPos1 - curLen / 2; 217 edge2.pos = curPos1 + curLen / 2; 218 } 219 else 220 { 221 edge.pos = Utils.pixRound(edge.opos); 222 } 223 anchor = edge; 224 edge.flags |= Segment.FLAG_EDGE_DONE; 225 alignLinkedEdge(hints, dim, edge, edge2); 226 } 227 else 228 { 229 int aDiff = edge.opos - anchor.opos; 230 int orgPos = anchor.pos + aDiff; 231 int orgLen = edge2.opos - edge.opos; 232 int orgCenter = orgPos + (orgLen >> 1); 233 int curLen = computeStemWidth(hints, dim, orgLen, edge.flags, 234 edge2.flags); 235 //System.err.println("stem width: " + curLen); 236 if (curLen < 96) 237 { 238 int uOff, dOff; 239 int curPos1 = Utils.pixRound(orgCenter); 240 if (curLen <= 64) 241 { 242 uOff = 32; 243 dOff = 32; 244 } 245 else 246 { 247 uOff = 38; 248 dOff = 26; 249 } 250 int delta1 = orgCenter - (curPos1 - uOff); 251 if (delta1 < 0) 252 delta1 = -delta1; 253 int delta2 = orgCenter - (curPos1 + dOff); 254 if (delta2 < 0) 255 delta2 = -delta2; 256 if (delta1 < delta2) 257 { 258 curPos1 -= uOff; 259 } 260 else 261 { 262 curPos1 += dOff; 263 } 264 edge.pos = curPos1 - curLen / 2; 265 edge2.pos = curPos1 + curLen / 2; 266 } 267 else 268 { 269 orgPos = anchor.pos + (edge.opos - anchor.opos); 270 orgLen = edge2.opos - edge.opos; 271 orgCenter = orgPos + (orgLen >> 1); 272 curLen = computeStemWidth(hints, dim, orgLen, edge.flags, 273 edge2.flags); 274 int curPos1 = Utils.pixRound(orgPos); 275 int delta1 = curPos1 + (curLen >> 1) - orgCenter; 276 if (delta1 < 0) 277 delta1 = -delta1; 278 int curPos2 = Utils.pixRound(orgPos + orgLen) - curLen; 279 int delta2 = curPos2 + (curLen >> 1) - orgCenter; 280 if (delta2 < 0) 281 delta2 = -delta2; 282 edge.pos = (delta1 < delta2) ? curPos1 : curPos2; 283 edge2.pos = edge.pos + curLen; 284 } 285 edge.flags |= Segment.FLAG_EDGE_DONE; 286 edge2.flags |= Segment.FLAG_EDGE_DONE; 287 288 if (e > 0 && edge.pos < edges[e - 1].pos) 289 { 290 edge.pos = edges[e - 1].pos; 291 } 292 } 293 } 294 // TODO: Implement the lowercase m symmetry thing. 295 296 // Now we hint the remaining edges (serifs and singles) in order 297 // to complete our processing. 298 if (hasSerifs > 0 || anchor == null) 299 { 300 for (int e = 0; e < numEdges; e++) 301 { 302 Edge edge = edges[e]; 303 if ((edge.flags & Segment.FLAG_EDGE_DONE) != 0) 304 continue; 305 if (edge.serif != null) 306 { 307 alignSerifEdge(hints, edge.serif, edge); 308 } 309 else if (anchor == null) 310 { 311 edge.pos = Utils.pixRound(edge.opos); 312 anchor = edge; 313 } 314 else 315 { 316 edge.pos = anchor.pos 317 + Utils.pixRound(edge.opos - anchor.opos); 318 } 319 edge.flags |= Segment.FLAG_EDGE_DONE; 320 321 if (e > 0 && edge.pos < edges[e - 1].pos) 322 { 323 edge.pos = edges[e - 1].pos; 324 } 325 if (e + 1 < numEdges 326 && (edges[e + 1].flags & Segment.FLAG_EDGE_DONE) != 0 327 && edge.pos > edges[e + 1].pos) 328 { 329 edge.pos = edges[e + 1].pos; 330 } 331 } 332 } 333 334 // Debug: print all hinted edges. 335 // System.err.println("hinted edges: " ); 336 // for (int i = 0; i < numEdges; i++) 337 // { 338 // System.err.println("edge#" + i + ": " + edges[i]); 339 // } 340 } 341 alignSerifEdge(GlyphHints hints, Edge base, Edge serif)342 private void alignSerifEdge(GlyphHints hints, Edge base, Edge serif) 343 { 344 serif.pos = base.pos + (serif.opos - base.opos); 345 } 346 computeStemWidth(GlyphHints hints, int dim, int width, int baseFlags, int stemFlags)347 private int computeStemWidth(GlyphHints hints, int dim, int width, 348 int baseFlags, int stemFlags) 349 { 350 LatinMetrics metrics = (LatinMetrics) hints.metrics; 351 LatinAxis axis = metrics.axis[dim]; 352 int dist = width; 353 int sign = 0; 354 boolean vertical = dim == DIMENSION_VERT; 355 if (! doStemAdjust(hints)) 356 return width; 357 if (dist < 0) 358 { 359 dist = -width; 360 sign = 1; 361 } 362 if ((vertical && ! doVertSnap(hints)) || ! vertical && ! doHorzSnap(hints)) 363 { 364 // Smooth hinting process. Very lightly quantize the stem width. 365 // Leave the widths of serifs alone. 366 if ((stemFlags & Segment.FLAG_EDGE_SERIF) != 0 && vertical 367 && dist < 3 * 64) 368 { 369 return doneWidth(dist, sign); 370 } 371 else if ((baseFlags & Segment.FLAG_EDGE_ROUND) != 0) 372 { 373 if (dist < 80) 374 dist = 64; 375 } 376 else if (dist < 56) 377 { 378 dist = 56; 379 } 380 if (axis.widthCount > 0) 381 { 382 int delta; 383 if (axis.widthCount > 0) 384 { 385 delta = dist - axis.widths[0].cur; 386 if (delta < 0) 387 { 388 delta = -delta; 389 } 390 if (delta < 40) 391 { 392 dist = axis.widths[0].cur; 393 if (dist < 48) 394 dist = 48; 395 return doneWidth(dist, sign); 396 } 397 } 398 if (dist < 3 * 64) // < 3 pixels. 399 { 400 delta = dist & 63; 401 dist &= -64; 402 if (delta < 10) 403 dist += delta; 404 else if (delta < 32) 405 dist += 10; 406 else if (delta < 54) 407 dist += 54; 408 else 409 dist += delta; 410 411 } 412 else 413 { 414 dist = (dist + 32) & ~63; 415 } 416 } 417 } 418 else 419 { 420 // Strong hinting process: Snap the stem width to integer pixels. 421 dist = snapWidth(axis.widths, axis.widthCount, dist); 422 if (vertical) 423 { 424 // In the case of vertical hinting, always round 425 // the stem heights to integer pixels. 426 if (dist >= 64) 427 dist = (dist + 16) & ~63; 428 else 429 dist = 64; 430 } 431 else 432 { 433 if (doMono(hints)) 434 { 435 // Monochrome horizontal hinting: Snap widths to integer pixels 436 // with a different threshold. 437 if (dist < 64) 438 dist = 64; 439 else 440 dist = (dist + 32) & ~63; 441 } 442 else 443 { 444 // For anti-aliased hinting, we adopt a more subtle 445 // approach: We strengthen small stems, round those stems 446 // whose size is between 1 and 2 pixels to an integer, 447 // otherwise nothing. 448 if (dist < 48) 449 dist = (dist + 64) >> 1; 450 else if (dist < 128) 451 dist = (dist + 22) & ~63; 452 else 453 // Round otherwise to prevent color fringes in LCD mode. 454 dist = (dist + 32) & ~63; 455 } 456 } 457 } 458 return doneWidth(dist, sign); 459 } 460 doMono(GlyphHints hints)461 private boolean doMono(GlyphHints hints) 462 { 463 return true; 464 } 465 snapWidth(Width[] widths, int count, int width)466 private int snapWidth(Width[] widths, int count, int width) 467 { 468 int best = 64 + 32 + 2; 469 int reference = width; 470 for (int n = 0; n < count; n++) 471 { 472 int w = widths[n].cur; 473 int dist = width - w; 474 if (dist < 0) 475 dist = -dist; 476 if (dist < best) 477 { 478 best = dist; 479 reference = w; 480 } 481 } 482 int scaled = Utils.pixRound(reference); 483 if (width >= reference) 484 { 485 if (width < scaled + 48) 486 width = reference; 487 } 488 else 489 { 490 if (width > scaled + 48) 491 width = reference; 492 } 493 return width; 494 } 495 doneWidth(int w, int s)496 private int doneWidth(int w, int s) 497 { 498 if (s == 1) 499 w = -w; 500 return w; 501 } 502 doVertSnap(GlyphHints hints)503 private boolean doVertSnap(GlyphHints hints) 504 { 505 // TODO Auto-generated method stub 506 return true; 507 } 508 doHorzSnap(GlyphHints hints)509 private boolean doHorzSnap(GlyphHints hints) 510 { 511 // TODO Auto-generated method stub 512 return true; 513 } 514 doStemAdjust(GlyphHints hints)515 private boolean doStemAdjust(GlyphHints hints) 516 { 517 // TODO Auto-generated method stub 518 return true; 519 } 520 alignLinkedEdge(GlyphHints hints, int dim, Edge base, Edge stem)521 private void alignLinkedEdge(GlyphHints hints, int dim, Edge base, Edge stem) 522 { 523 int dist = stem.opos - base.opos; 524 int fitted = computeStemWidth(hints, dim, dist, base.flags, stem.flags); 525 stem.pos = base.pos + fitted; 526 } 527 doneMetrics(ScriptMetrics metrics)528 public void doneMetrics(ScriptMetrics metrics) 529 { 530 // TODO Auto-generated method stub 531 532 } 533 534 /** 535 * Initializes the <code>hints</code> object. 536 * 537 * @param hints the hints to initialize 538 * @param metrics the metrics to use 539 */ initHints(GlyphHints hints, ScriptMetrics metrics)540 public void initHints(GlyphHints hints, ScriptMetrics metrics) 541 { 542 hints.rescale(metrics); 543 LatinMetrics lm = (LatinMetrics) metrics; 544 hints.xScale = lm.axis[DIMENSION_HORZ].scale; 545 hints.xDelta = lm.axis[DIMENSION_HORZ].delta; 546 hints.yScale = lm.axis[DIMENSION_VERT].scale; 547 hints.yDelta = lm.axis[DIMENSION_VERT].delta; 548 // TODO: Set the scaler and other flags. 549 } 550 551 /** 552 * Initializes the script metrics. 553 * 554 * @param metrics the script metrics to initialize 555 * @param face the font 556 */ initMetrics(ScriptMetrics metrics, OpenTypeFont face)557 public void initMetrics(ScriptMetrics metrics, OpenTypeFont face) 558 { 559 assert metrics instanceof LatinMetrics; 560 LatinMetrics lm = (LatinMetrics) metrics; 561 lm.unitsPerEm = face.unitsPerEm; 562 563 // TODO: Check for latin charmap. 564 565 initWidths(lm, face, 'o'); 566 initBlues(lm, face); 567 } 568 scaleMetrics(ScriptMetrics metrics, HintScaler scaler)569 public void scaleMetrics(ScriptMetrics metrics, HintScaler scaler) 570 { 571 LatinMetrics lm = (LatinMetrics) metrics; 572 lm.scaler.renderMode = scaler.renderMode; 573 lm.scaler.face = scaler.face; 574 scaleMetricsDim(lm, scaler, DIMENSION_HORZ); 575 scaleMetricsDim(lm, scaler, DIMENSION_VERT); 576 } 577 scaleMetricsDim(LatinMetrics lm, HintScaler scaler, int dim)578 private void scaleMetricsDim(LatinMetrics lm, HintScaler scaler, int dim) 579 { 580 int scale; 581 int delta; 582 if (dim == DIMENSION_HORZ) 583 { 584 scale = scaler.xScale; 585 delta = scaler.xDelta; 586 } 587 else 588 { 589 scale = scaler.yScale; 590 delta = scaler.yDelta; 591 } 592 LatinAxis axis = lm.axis[dim]; 593 if (axis.orgScale == scale && axis.orgDelta == delta) 594 // No change, no need to adjust. 595 return; 596 axis.orgScale = scale; 597 axis.orgDelta = delta; 598 599 // Correct X and Y scale to optimize the alignment of the top small 600 // letters to the pixel grid. 601 LatinAxis axis2 = lm.axis[DIMENSION_VERT]; 602 LatinBlue blue = null; 603 // for (int nn = 0; nn < axis2.blueCount; nn++) 604 // { 605 // if ((axis2.blues[nn].flags & LatinBlue.FLAG_ADJUSTMENT) != 0) 606 // { 607 // blue = axis2.blues[nn]; 608 // break; 609 // } 610 // } 611 // if (blue != null) 612 // { 613 // int scaled = Fixed.mul16(blue.shoot.org, scaler.yScale); 614 // int fitted = Utils.pixRound(scaled); 615 // if (scaled != fitted) 616 // { 617 // if (dim == DIMENSION_HORZ) 618 // { 619 // if (fitted < scaled) 620 // { 621 // scale -= scale / 50; 622 // } 623 // } 624 // else 625 // { 626 // scale = Utils.mulDiv(scale, fitted, scaled); 627 // } 628 // } 629 // } 630 axis.scale = scale; 631 axis.delta = delta; 632 if (dim == DIMENSION_HORZ) 633 { 634 lm.scaler.xScale = scale; 635 lm.scaler.xDelta = delta; 636 } 637 else 638 { 639 lm.scaler.yScale = scale; 640 lm.scaler.yDelta = delta; 641 } 642 // Scale the standard widths. 643 for (int nn = 0; nn < axis.widthCount; nn++) 644 { 645 Width w = axis.widths[nn]; 646 w.cur = Fixed.mul16(w.org, scale); 647 w.fit = w.cur; 648 } 649 // Scale blue zones. 650 if (dim == DIMENSION_VERT) 651 { 652 for (int nn = 0; nn < axis.blueCount; nn++) 653 { 654 blue = axis.blues[nn]; 655 blue.ref.cur = Fixed.mul16(blue.ref.org, scale) + delta; 656 blue.ref.fit = blue.ref.cur; 657 blue.shoot.cur = Fixed.mul16(blue.ref.org, scale) + delta; 658 blue.flags &= ~LatinBlue.FLAG_BLUE_ACTIVE; 659 // A blue zone is only active if it is less than 3/4 pixels tall. 660 int dist = Fixed.mul16(blue.ref.org - blue.shoot.org, scale); 661 if (dist <= 48 && dist >= -48) 662 { 663 int delta1 = blue.shoot.org - blue.ref.org; 664 int delta2 = delta1; 665 if (delta1 < 0) 666 delta2 = -delta2; 667 delta2 = Fixed.mul16(delta2, scale); 668 if (delta2 < 32) 669 delta2 = 0; 670 else if (delta2 < 64) 671 delta2 = 32 + (((delta2 - 32) + 16) & ~31); 672 else 673 delta2 = Utils.pixRound(delta2); 674 if (delta1 < 0) 675 delta2 = -delta2; 676 blue.ref.fit = Utils.pixRound(blue.ref.cur); 677 blue.shoot.fit = blue.ref.fit + delta2; 678 blue.flags |= LatinBlue.FLAG_BLUE_ACTIVE; 679 } 680 } 681 } 682 } 683 684 /** 685 * Determines the standard stem widths. 686 * 687 * @param metrics the metrics to use 688 * @param face the font face 689 * @param ch the character that is used for getting the widths 690 */ initWidths(LatinMetrics metrics, OpenTypeFont face, char ch)691 private void initWidths(LatinMetrics metrics, OpenTypeFont face, char ch) 692 { 693 GlyphHints hints = new GlyphHints(); 694 metrics.axis[DIMENSION_HORZ].widthCount = 0; 695 metrics.axis[DIMENSION_VERT].widthCount = 0; 696 int glyphIndex = face.getGlyph(ch); 697 Zone outline = face.getRawGlyphOutline(glyphIndex, IDENTITY); 698 LatinMetrics dummy = new LatinMetrics(); 699 HintScaler scaler = dummy.scaler; 700 dummy.unitsPerEm = metrics.unitsPerEm; 701 scaler.xScale = scaler.yScale = 10000; 702 scaler.xDelta = scaler.yDelta = 0; 703 scaler.face = face; 704 hints.rescale(dummy); 705 hints.reload(outline); 706 for (int dim = 0; dim < DIMENSION_MAX; dim++) 707 { 708 LatinAxis axis = metrics.axis[dim]; 709 AxisHints axHints = hints.axis[dim]; 710 int numWidths = 0; 711 computeSegments(hints, dim); 712 linkSegments(hints, dim); 713 Segment[] segs = axHints.segments; 714 HashSet<Segment> touched = new HashSet<Segment>(); 715 for (int i = 0; i < segs.length; i++) 716 { 717 Segment seg = segs[i]; 718 Segment link = seg.link; 719 if (link != null && link.link == seg && ! touched.contains(link)) 720 { 721 int dist = Math.abs(seg.pos - link.pos); 722 if (numWidths < MAX_WIDTHS) 723 axis.widths[numWidths++] = new Width(dist); 724 } 725 touched.add(seg); 726 } 727 Utils.sort(numWidths, axis.widths); 728 axis.widthCount = numWidths; 729 } 730 for (int dim = 0; dim < DIMENSION_MAX; dim++) 731 { 732 LatinAxis axis = metrics.axis[dim]; 733 int stdw = axis.widthCount > 0 ? axis.widths[0].org 734 : constant(metrics, 50); 735 axis.edgeDistanceTreshold= stdw / 5; 736 } 737 } 738 linkSegments(GlyphHints hints, int dim)739 void linkSegments(GlyphHints hints, int dim) 740 { 741 AxisHints axis = hints.axis[dim]; 742 Segment[] segments = axis.segments; 743 int numSegs = axis.numSegments; 744 int majorDir = axis.majorDir; 745 int lenThreshold = constant((LatinMetrics) hints.metrics, 8); 746 lenThreshold = Math.min(1, lenThreshold); 747 int lenScore = constant((LatinMetrics) hints.metrics, 3000); 748 for (int i1 = 0; i1 < numSegs; i1++) 749 { 750 Segment seg1 = segments[i1]; 751 // The fake segments are introduced to hint the metrics. 752 // Never link them to anything. 753 if (seg1.first == seg1.last || seg1.dir != majorDir) 754 continue; 755 for (int i2 = 0; i2 < numSegs; i2++) 756 { 757 Segment seg2 = segments[i2]; 758 if (seg2 != seg1 && seg1.dir + seg2.dir == 0) 759 { 760 int pos1 = seg1.pos; 761 int pos2 = seg2.pos; 762 // The vertical coords are swapped compared to how FT handles 763 // this. 764 int dist = dim == DIMENSION_VERT ? pos1 - pos2 : pos2 - pos1; 765 if (dist >= 0) 766 { 767 int min = seg1.minPos; 768 int max = seg1.maxPos; 769 int len, score; 770 if (min < seg2.minPos) 771 min = seg2.minPos; 772 if (max > seg2.maxPos) 773 max = seg2.maxPos; 774 len = max - min; 775 if (len > lenThreshold) 776 { 777 score = dist + lenScore / len; 778 if (score < seg1.score) 779 { 780 seg1.score = score; 781 seg1.link = seg2; 782 } 783 if (score < seg2.score) 784 { 785 seg2.score = score; 786 seg2.link = seg1; 787 } 788 } 789 } 790 } 791 } 792 } 793 for (int i1 = 0; i1 < numSegs; i1++) 794 { 795 Segment seg1 = segments[i1]; 796 Segment seg2 = seg1.link; 797 if (seg2 != null) 798 { 799 seg2.numLinked++; 800 if (seg2.link != seg1) 801 { 802 seg1.link = null; 803 seg1.serif = seg2.link; 804 } 805 } 806 // Uncomment to show all segments. 807 // System.err.println("segment#" + i1 + ": " + seg1); 808 } 809 } 810 811 /** 812 * Initializes the blue zones of the font. 813 * 814 * @param metrics the metrics to use 815 * @param face the font face to analyze 816 */ initBlues(LatinMetrics metrics, OpenTypeFont face)817 private void initBlues(LatinMetrics metrics, OpenTypeFont face) 818 { 819 int[] flats = new int[MAX_TEST_CHARS]; 820 int[] rounds = new int[MAX_TEST_CHARS]; 821 int numFlats; 822 int numRounds; 823 LatinBlue blue; 824 LatinAxis axis = metrics.axis[DIMENSION_VERT]; 825 // We compute the blues simply by loading each character in the test 826 // strings, then compute its topmost or bottommost points. 827 for (int bb = 0; bb < BLUE_MAX; bb++) 828 { 829 String p = TEST_CHARS[bb]; 830 int blueRef; 831 int blueShoot; 832 numFlats = 0; 833 numRounds = 0; 834 for (int i = 0; i < p.length(); i++) 835 { 836 // Load the character. 837 int glyphIndex = face.getGlyph(p.charAt(i)); 838 Zone glyph = 839 face.getRawGlyphOutline(glyphIndex, IDENTITY); 840 841 // Now compute the min and max points. 842 int numPoints = glyph.getSize() - 4; // 4 phantom points. 843 Point[] points = glyph.getPoints(); 844 Point point = points[0]; 845 int extremum = 0; 846 int index = 1; 847 if (isTopBlue(bb)) 848 { 849 for (; index < numPoints; index++) 850 { 851 point = points[index]; 852 // We have the vertical direction swapped. The higher 853 // points have smaller (negative) Y. 854 if (point.getOrigY() < points[extremum].getOrigY()) 855 extremum = index; 856 } 857 } 858 else 859 { 860 for (; index < numPoints; index++) 861 { 862 point = points[index]; 863 // We have the vertical direction swapped. The higher 864 // points have smaller (negative) Y. 865 if (point.getOrigY() > points[extremum].getOrigY()) 866 extremum = index; 867 } 868 } 869 // Debug, prints out the maxima. 870 // System.err.println("extremum for " + bb + " / "+ p.charAt(i) 871 // + ": " + points[extremum]); 872 873 // Now determine if the point is part of a straight or round 874 // segment. 875 boolean round; 876 int idx = extremum; 877 int first, last, prev, next, end; 878 int dist; 879 last = -1; 880 first = 0; 881 for (int n = 0; n < glyph.getNumContours(); n++) 882 { 883 end = glyph.getContourEnd(n); 884 // System.err.println("contour end for " + n + ": " + end); 885 if (end >= idx) 886 { 887 last = end; 888 break; 889 } 890 first = end + 1; 891 } 892 // Should never happen. 893 assert last >= 0; 894 895 // Now look for the previous and next points that are not on the 896 // same Y coordinate. Threshold the 'closeness'. 897 prev = idx; 898 next = prev; 899 do 900 { 901 if (prev > first) 902 prev--; 903 else 904 prev = last; 905 dist = points[prev].getOrigY() - points[extremum].getOrigY(); 906 if (dist < -5 || dist > 5) 907 break; 908 } while (prev != idx); 909 do 910 { 911 if (next < last) 912 next++; 913 else 914 next = first; 915 dist = points[next].getOrigY() - points[extremum].getOrigY(); 916 if (dist < -5 || dist > 5) 917 break; 918 } while (next != idx); 919 round = points[prev].isControlPoint() 920 || points[next].isControlPoint(); 921 922 if (round) 923 { 924 rounds[numRounds++] = points[extremum].getOrigY(); 925 // System.err.println("new round extremum: " + bb + ": " 926 // + points[extremum].getOrigY()); 927 } 928 else 929 { 930 flats[numFlats++] = points[extremum].getOrigY(); 931 // System.err.println("new flat extremum: " + bb + ": " 932 // + points[extremum].getOrigY()); 933 } 934 } 935 // We have computed the contents of the rounds and flats tables. 936 // Now determine the reference and overshoot position of the blues -- 937 // we simply take the median after a simple sort. 938 Utils.sort(numRounds, rounds); 939 Utils.sort(numFlats, flats); 940 blue = axis.blues[axis.blueCount] = new LatinBlue(); 941 axis.blueCount++; 942 if (numFlats == 0) 943 { 944 blue.ref = blue.shoot = new Width(rounds[numRounds / 2]); 945 } 946 else if (numRounds == 0) 947 { 948 blue.ref = blue.shoot = new Width(flats[numFlats / 2]); 949 } 950 else 951 { 952 blue.ref = new Width(flats[numFlats / 2]); 953 blue.shoot = new Width(rounds[numRounds / 2]); 954 } 955 // There are sometimes problems: if the overshoot position of top 956 // zones is under its reference position, or the opposite for bottom 957 // zones. We must check everything there and correct problems. 958 if (blue.shoot != blue.ref) 959 { 960 int ref = blue.ref.org; 961 int shoot = blue.shoot.org; 962 // Inversed vertical coordinates! 963 boolean overRef = shoot < ref; 964 if (isTopBlue(bb) ^ overRef) 965 { 966 blue.shoot = blue.ref = new Width((shoot + ref) / 2); 967 } 968 } 969 blue.flags = 0; 970 if (isTopBlue(bb)) 971 blue.flags |= LatinBlue.FLAG_TOP; 972 // The following flag is used later to adjust y and x scales in 973 // order to optimize the pixel grid alignment of the top small 974 // letters. 975 if (bb == SMALL_TOP) 976 { 977 blue.flags |= LatinBlue.FLAG_ADJUSTMENT; 978 } 979 // Debug: print out the blue zones. 980 // System.err.println("blue zone #" + bb + ": " + blue); 981 } 982 } 983 984 private static final AffineTransform IDENTITY = new AffineTransform(); 985 986 private int constant(LatinMetrics metrics, int c) 987 { 988 return c * (metrics.unitsPerEm / 2048); 989 } 990 991 private void computeSegments(GlyphHints hints, int dim) 992 { 993 Point[] points = hints.points; 994 if (dim == DIMENSION_HORZ) 995 { 996 for (int i = 0; i < hints.numPoints; i++) 997 { 998 points[i].setU(points[i].getOrigX()); 999 points[i].setV(points[i].getOrigY()); 1000 } 1001 } 1002 else 1003 { 1004 for (int i = 0; i < hints.numPoints; i++) 1005 { 1006 points[i].setU(points[i].getOrigY()); 1007 points[i].setV(points[i].getOrigX()); 1008 } 1009 } 1010 // Now look at each contour. 1011 AxisHints axis = hints.axis[dim]; 1012 int majorDir = Math.abs(axis.majorDir); 1013 int segmentDir = majorDir; 1014 Point[] contours = hints.contours; 1015 int numContours = hints.numContours; 1016 Segment segment = null; 1017 for (int i = 0; i < numContours; i++) 1018 { 1019 int minPos = 32000; 1020 int maxPos = -32000; 1021 1022 Point point = contours[i]; 1023 Point last = point.getPrev(); 1024 if (point == last) // Skip singletons. 1025 continue; 1026 if (Math.abs(last.getOutDir()) == majorDir 1027 && Math.abs(point.getOutDir()) == majorDir) 1028 { 1029 // We are already on an edge. Locate its start. 1030 last = point; 1031 while (true) 1032 { 1033 point = point.getPrev(); 1034 if (Math.abs(point.getOutDir()) != majorDir) 1035 { 1036 point = point.getNext(); 1037 break; 1038 } 1039 if (point == last) 1040 break; 1041 } 1042 } 1043 last = point; 1044 boolean passed = false; 1045 boolean onEdge = false; 1046 while (true) 1047 { 1048 int u, v; 1049 if (onEdge) 1050 { 1051 u = point.getU(); 1052 if (u < minPos) 1053 minPos = u; 1054 if (u > maxPos) 1055 maxPos = u; 1056 if (point.getOutDir() != segmentDir || point == last) 1057 { 1058 // Leaving an edge. Record new segment. 1059 segment.last = point; 1060 // (minPos + maxPos) / 2. 1061 segment.pos = (minPos + maxPos) >> 1; 1062 if (segment.first.isControlPoint() 1063 || point.isControlPoint()) 1064 segment.flags |= Segment.FLAG_EDGE_ROUND; 1065 minPos = maxPos = point.getV(); 1066 v = segment.first.getV(); 1067 if (v < minPos) 1068 minPos = v; 1069 if (v > maxPos) 1070 maxPos = v; 1071 segment.minPos = minPos; 1072 segment.maxPos = maxPos; 1073 onEdge = false; 1074 segment = null; 1075 } 1076 } 1077 if (point == last) 1078 { 1079 if (passed) 1080 break; 1081 passed = true; 1082 } 1083 if (! onEdge && Math.abs(point.getOutDir()) == majorDir) 1084 { 1085 // This is the start of a new segment. 1086 segmentDir = point.getOutDir(); 1087 segment = axis.newSegment(); 1088 segment.dir = segmentDir; 1089 segment.flags = Segment.FLAG_EDGE_NORMAL; 1090 minPos = maxPos = point.getU(); 1091 segment.first = point; 1092 segment.last = point; 1093 segment.contour = contours[i]; 1094 segment.score = 32000; 1095 segment.len = 0; 1096 segment.link = null; 1097 onEdge = true; 1098 } 1099 point = point.getNext(); 1100 } 1101 } 1102 1103 } 1104 1105 private boolean isTopBlue(int b) 1106 { 1107 return b == CAPITAL_TOP || b == SMALL_F_TOP || b == SMALL_TOP; 1108 } 1109 1110 private void detectFeatures(GlyphHints hints, int dim) 1111 { 1112 computeSegments(hints, dim); 1113 linkSegments(hints, dim); 1114 computeEdges(hints, dim); 1115 } 1116 1117 private void computeEdges(GlyphHints hints, int dim) 1118 { 1119 AxisHints axis = hints.axis[dim]; 1120 LatinAxis laxis = ((LatinMetrics) hints.metrics).axis[dim]; 1121 Segment[] segments = axis.segments; 1122 int numSegments = axis.numSegments; 1123 Segment seg; 1124 int upDir; 1125 int scale; 1126 int edgeDistanceThreshold; 1127 axis.numEdges = 0; 1128 scale = dim == DIMENSION_HORZ ? hints.xScale : hints.yScale; 1129 upDir = dim == DIMENSION_HORZ ? DIR_UP : DIR_RIGHT; 1130 1131 // We will begin by generating a sorted table of edges for the 1132 // current direction. To do so, we simply scan each segment and try 1133 // to find an edge in our table that corresponds to its position. 1134 // 1135 // If no edge is found, we create one and insert a new edge in the 1136 // sorted table. Otherwise, we simply add the segment to the egde's 1137 // list which will be processed in the second step to compute the 1138 // edge's properties. 1139 // 1140 // Note that the edge table is sorted along the segment/edge 1141 // position. 1142 1143 edgeDistanceThreshold = Fixed.mul16(laxis.edgeDistanceTreshold, scale); 1144 if (edgeDistanceThreshold > 64 / 4) 1145 edgeDistanceThreshold = 64 / 4; 1146 edgeDistanceThreshold = Fixed.div16(edgeDistanceThreshold, scale); 1147 for (int i = 0; i < numSegments; i++) 1148 { 1149 seg = segments[i]; 1150 Edge found = null; 1151 for (int ee = 0; ee < axis.numEdges; ee++) 1152 { 1153 Edge edge = axis.edges[ee]; 1154 int dist = seg.pos - edge.fpos; 1155 if (dist < 0) 1156 dist = -dist; 1157 if (dist < edgeDistanceThreshold) 1158 { 1159 found = edge; 1160 break; 1161 } 1162 } 1163 if (found == null) 1164 { 1165 // Insert new edge in the list and sort according to 1166 // the position. 1167 Edge edge = axis.newEdge(seg.pos); 1168 edge.first = seg; 1169 edge.last = seg; 1170 edge.fpos = seg.pos; 1171 edge.opos = edge.pos = Fixed.mul16(seg.pos, scale); 1172 seg.edgeNext = seg; 1173 seg.edge = edge; 1174 } 1175 else 1176 { 1177 seg.edgeNext = found.first; 1178 found.last.edgeNext = seg; 1179 found.last = seg; 1180 seg.edge = found; 1181 } 1182 } 1183 // Good. We will now compute each edge's properties according to 1184 // segments found on its position. Basically these are: 1185 // - Edge's main direction. 1186 // - Stem edge, serif edge, or both (which defaults to stem edge). 1187 // - Rounded edge, straight or both (which defaults to straight). 1188 // - Link for edge. 1189 1190 // Now, compute each edge properties. 1191 for (int e = 0; e < axis.numEdges; e++) 1192 { 1193 Edge edge = axis.edges[e]; 1194 // Does it contain round segments? 1195 int isRound = 0; 1196 // Does it contain straight segments? 1197 int isStraight = 0; 1198 // Number of upward segments. 1199 int ups = 0; 1200 // Number of downward segments. 1201 int downs = 0; 1202 1203 seg = edge.first; 1204 do 1205 { 1206 // Check for roundness of segment. 1207 if ((seg.flags & Segment.FLAG_EDGE_ROUND) != 0) 1208 isRound++; 1209 else 1210 isStraight++; 1211 1212 // Check for segment direction. 1213 if (seg.dir == upDir) 1214 ups += seg.maxPos - seg.minPos; 1215 else 1216 downs += seg.maxPos - seg.minPos; 1217 1218 // Check for links. If seg.serif is set, then seg.link must 1219 // be ignored. 1220 boolean isSerif = seg.serif != null && seg.serif.edge != edge; 1221 if (seg.link != null || isSerif) 1222 { 1223 Edge edge2 = edge.link; 1224 Segment seg2 = seg.link; 1225 if (isSerif) 1226 { 1227 seg2 = seg.serif; 1228 edge2 = edge.serif; 1229 } 1230 if (edge2 != null) 1231 { 1232 int edgeDelta = edge.fpos - edge2.fpos; 1233 if (edgeDelta < 0) 1234 edgeDelta = -edgeDelta; 1235 int segDelta = seg.pos - seg2.pos; 1236 if (segDelta < 0) 1237 segDelta = -segDelta; 1238 if (segDelta < edgeDelta) 1239 edge2 = seg2.edge; 1240 } 1241 else 1242 { 1243 edge2 = seg2.edge; 1244 } 1245 if (isSerif) 1246 { 1247 edge.serif = edge2; 1248 edge2.flags |= Segment.FLAG_EDGE_SERIF; 1249 } 1250 else 1251 { 1252 edge.link = edge2; 1253 } 1254 } 1255 seg = seg.edgeNext; 1256 } while (seg != edge.first); 1257 edge.flags = Segment.FLAG_EDGE_NORMAL; 1258 if (isRound > 0 && isRound > isStraight) 1259 edge.flags |= Segment.FLAG_EDGE_ROUND; 1260 1261 // Set the edge's main direction. 1262 edge.dir = DIR_NONE; 1263 if (ups > downs) 1264 edge.dir = upDir; 1265 else if (ups < downs) 1266 edge.dir = -upDir; 1267 else if (ups == downs) 1268 edge.dir = 0; 1269 1270 // Gets rid of serif if link is set. This gets rid of many 1271 // unpleasant artifacts. 1272 if (edge.serif != null && edge.link != null) 1273 { 1274 edge.serif = null; 1275 } 1276 1277 // Debug: Print out all edges. 1278 // System.err.println("edge# " + e + ": " + edge); 1279 } 1280 } 1281 1282 private void computeBlueEdges(GlyphHints hints, LatinMetrics metrics) 1283 { 1284 AxisHints axis = hints.axis[DIMENSION_VERT]; 1285 Edge[] edges = axis.edges; 1286 int numEdges = axis.numEdges; 1287 LatinAxis latin = metrics.axis[DIMENSION_VERT]; 1288 int scale = latin.scale; 1289 1290 // Compute which blue zones are active. I.e. have their scaled 1291 // size < 3/4 pixels. 1292 1293 // For each horizontal edge search the blue zone that is closest. 1294 for (int e = 0; e < numEdges; e++) 1295 { 1296 Edge edge = edges[e]; 1297 // System.err.println("checking edge: " + edge); 1298 Width bestBlue = null; 1299 int bestDist = Fixed.mul16(metrics.unitsPerEm / 40, scale); 1300 1301 if (bestDist > 64 / 2) 1302 bestDist = 64 / 2; 1303 for (int bb = 0; bb < BLUE_MAX; bb++) 1304 { 1305 LatinBlue blue = latin.blues[bb]; 1306 // System.err.println("checking blue: " + blue); 1307 // Skip inactive blue zones, i.e. those that are too small. 1308 if ((blue.flags & LatinBlue.FLAG_BLUE_ACTIVE) == 0) 1309 continue; 1310 // If it is a top zone, check for right edges. If it is a bottom 1311 // zone, check for left edges. 1312 boolean isTopBlue = (blue.flags & LatinBlue.FLAG_TOP) != 0; 1313 boolean isMajorDir = edge.dir == axis.majorDir; 1314 1315 // If it is a top zone, the edge must be against the major 1316 // direction. If it is a bottom zone it must be in the major 1317 // direction. 1318 if (isTopBlue ^ isMajorDir) 1319 { 1320 int dist = edge.fpos - blue.ref.org; 1321 if (dist < 0) 1322 dist = -dist; 1323 dist = Fixed.mul16(dist, scale); 1324 if (dist < bestDist) 1325 { 1326 bestDist = dist; 1327 bestBlue = blue.ref; 1328 } 1329 1330 // Now, compare it to the overshoot position if the edge is 1331 // rounded, and if the edge is over the reference position of 1332 // a top zone, or under the reference position of a bottom 1333 // zone. 1334 if ((edge.flags & Segment.FLAG_EDGE_ROUND) != 0 && dist != 0) 1335 { 1336 // Inversed vertical coordinates! 1337 boolean isUnderRef = edge.fpos > blue.ref.org; 1338 if (isTopBlue ^ isUnderRef) 1339 { 1340 blue = latin.blues[bb]; // Needed? 1341 dist = edge.fpos - blue.shoot.org; 1342 if (dist < 0) 1343 dist = -dist; 1344 dist = Fixed.mul16(dist, scale); 1345 if (dist < bestDist) 1346 { 1347 bestDist = dist; 1348 bestBlue = blue.shoot; 1349 } 1350 } 1351 } 1352 1353 } 1354 } 1355 if (bestBlue != null) 1356 { 1357 edge.blueEdge = bestBlue; 1358 // Debug: Print out the blue edges. 1359 // System.err.println("blue edge for: " + edge + ": " + bestBlue); 1360 } 1361 } 1362 } 1363 } 1364