All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.fontengine.font.TTScan Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
/*
 * File: TTScan.java
 * 
 *	ADOBE CONFIDENTIAL
 *	___________________
 *
 *	Copyright 2006 Adobe Systems Incorporated
 *	All Rights Reserved.
 *
 *	NOTICE: All information contained herein is, and remains the property of
 *	Adobe Systems Incorporated and its suppliers, if any. The intellectual
 *	and technical concepts contained herein are proprietary to Adobe Systems
 *	Incorporated and its suppliers and may be covered by U.S. and Foreign
 *	Patents, patents in process, and are protected by trade secret or
 *	copyright law. Dissemination of this information or reproduction of this
 *	material is strictly forbidden unless prior written permission is
 *  obtained from Adobe Systems Incorporated.
 *
 */

/*
 * Adobe Patent and/or Adobe Patent Pending invention included within this file:
 * 
 * Adobe patent application tracking # P002,
 * entitled "Dropout-free center point fill method for displaying characters"
 * inventors: William Paxton and Stephen Schiller
 * Issued US Patent 5,200,740 on April 6, 1993.
 */

/*
 * Based on code with the following copyright notices:
 * 
 *  Copyright (c) 1987-1990, 1992 by Apple Computer, Inc., all rights reserved.
 *  Copyright (c) 1989-2002. Microsoft Corporation, all rights reserved.
 */

package com.adobe.fontengine.font;

import com.adobe.fontengine.math.F26Dot6;
import com.adobe.fontengine.math.Math2;

/** Scan conversion by center scan.
 * 
 * This implentation follows very closely the Microsoft TrueType 
 * scan conversion process. 
 */
public final class TTScan implements ScanConverter {
  
  private int /*26.6*/ scanlineAbove (int /*26.6*/ y) {
    return F26Dot6.roundHalfUp (y);
  }
  
  private int /*26.6*/ scanlineBelow (int /*26.6*/ y) {
    return F26Dot6.roundHalfDown (y) - F26Dot6.ONE;
  }
  
  private static class Direction {
    public final boolean isUp;
    public final boolean isDown;
    public final boolean isLeft;
    public final boolean isRight;
    public final String name;
    
    private Direction (String name, boolean isUp, boolean isDown, boolean isLeft, boolean isRight) {
      this.name = name;
      this.isUp = isUp;
      this.isDown = isDown;
      this.isLeft = isLeft;
      this.isRight = isRight;
    }
    
    public static Direction ofSegment (double x1, double y1, double x2, double y2) {
      if (x1 < x2) {
        if (y1 < y2) {
          return NE; }
        else if (y1 == y2) {
          return E; }
        else {
          return SE; }}
      else if (x1 == x2) {
        if (y1 < y2) {
          return N; }
        else if (y1 == y2) {
          return NO_MOVE; }
        else {
          return S; }}
      else {
        if (y1 < y2) {
          return NW; }
        else if (y1 == y2) {
          return W; }
        else {
          return SW; }}
    }

    static final Direction E  = new Direction ("E",  false, false, false, true);
    static final Direction NE = new Direction ("NE", true,  false, false, true);
    static final Direction N  = new Direction ("N",  true,  false, false, false);
    static final Direction NW = new Direction ("NW", true,  false, true,  false);
    static final Direction W  = new Direction ("W",  false, false, true,  false);
    static final Direction SW = new Direction ("SW", false, true,  true,  false);
    static final Direction S  = new Direction ("S",  false, true,  false, false);
    static final Direction SE = new Direction ("SE", false, true,  false, true);
    
    static final Direction NONE = new Direction ("NONE", false, false, false, false);
    static final Direction NO_MOVE = new Direction ("NO_MOVE", false, false, false, false);    
  };
  
  private static class F26Dot6List {
    int /*26.6*/ [] values;
    
    public void add (int /*26.6*/ v) {
      if (values == null) {
        values = new int /*26.6*/[] {v}; }
      else {
        int /*26.6*/[] newValues = new int /*26.6*/ [values.length + 1];
        boolean inserted = false;
        int k = 0;
        for (int i = 0; i < values.length; i++) {
          if (! inserted && v < values [i]) {
            newValues [k++] = v; 
            inserted = true; }
          newValues [k++] = values [i]; }
        if (! inserted) {
          newValues [k++] = v; }
        values = newValues; }
    }
  }
  
  private static class ScanLine {
    F26Dot6List onCrosses = new F26Dot6List ();
    F26Dot6List offCrosses = new F26Dot6List ();
    
    public void addCross (int /*26.6*/ coord, boolean on) {
      if (on) {
        onCrosses.add (coord); }
      else {
        offCrosses.add (coord); }
    }
  }

  private class ScanLines {
    public int firstScanLineCoordI = Integer.MAX_VALUE;
    public ScanLine[] scanLines = null; 
    public final boolean horizontal;
    
    public ScanLines (boolean horizontal) {
      this.horizontal = horizontal;
    }
    
    public void addCross (int /*26.6*/ coordOnScanLine, int scanLineCoordI, boolean on) {
      
      if (ScalerDebugger.debugOn && outlineDebugger != null) {
        if (horizontal) {
          outlineDebugger.ttScanHCross (coordOnScanLine, scanLineCoordI, on); }
        else {
          outlineDebugger.ttScanVCross (coordOnScanLine, scanLineCoordI, on); }}
            
      if (scanLines == null) {
        scanLines = new ScanLine [1];
        firstScanLineCoordI = scanLineCoordI; }
      
      else if (scanLineCoordI < firstScanLineCoordI) {
        ScanLine[] newScanLines = new ScanLine [firstScanLineCoordI - scanLineCoordI + scanLines.length];
        System.arraycopy (scanLines, 0, newScanLines, firstScanLineCoordI - scanLineCoordI, scanLines.length);
        scanLines = newScanLines;
        firstScanLineCoordI = scanLineCoordI; }
      
      else if (firstScanLineCoordI + scanLines.length - 1 < scanLineCoordI) {
        ScanLine[] newScanLines = new ScanLine [scanLineCoordI - firstScanLineCoordI + 1];
        System.arraycopy (scanLines, 0, newScanLines, 0, scanLines.length);
        scanLines = newScanLines; }
        
      ScanLine scanLine = scanLines [scanLineCoordI - firstScanLineCoordI];
      if (scanLine == null) {
        scanLine = new ScanLine ();
        scanLines [scanLineCoordI - firstScanLineCoordI] = scanLine; }
      
      scanLine.addCross (coordOnScanLine, on);
    }
    
    public int countCrosses (int scanLineCoord, int v) {
      if (scanLineCoord < firstScanLineCoordI || firstScanLineCoordI + scanLines.length <= scanLineCoord) {
        return 0; }
      ScanLine r = scanLines [scanLineCoord - firstScanLineCoordI];
      if (r == null) {
        return 0; }
      
      int crossings = 0;
      int /*26.6*/ vv = F26Dot6.fromInt (v);
      for (int i = 0; i < r.onCrosses.values.length; i++) {
        if (vv - F26Dot6.ONE_HALF <= r.onCrosses.values [i] && r.onCrosses.values [i] < vv + F26Dot6.ONE_HALF) {
          crossings++; }
        if (vv - F26Dot6.ONE_HALF <= r.offCrosses.values [i] && r.offCrosses.values [i] < vv + F26Dot6.ONE_HALF) {
          crossings++; }}
      return crossings;
    }  
  }
  
  private ScanLines hScanLines;
  private ScanLines vScanLines;
  private int scanType = 2;
   
  //============================================================================
  
  private class CrossBuilder extends OutlineConsumer2BaseImpl {
      
    int /*26.6*/ xmin;
    int /*26.6*/ ymin;
    int /*26.6*/ xmax;
    int /*26.6*/ ymax;
    boolean zeroDimension;
    
    Direction currentDir;
    Direction firstSegmentDir;
    int /*26.6*/ firstSegmentX;
    int /*26.6*/ firstSegmentY;
     
    public CrossBuilder () {
    }

    //--------------------------------------------------------------------------      
    private double THRESHOLD = 127.0;
    private double epsilon;
    
    public void setFlatness (double flatness) {
       epsilon = Math.max (1.0d/8192, 1.5d * flatness / 4);
    }

    public void startOutline () {
      xmin = F26Dot6.MAX_VALUE;
      ymin = F26Dot6.MAX_VALUE;
      xmax = F26Dot6.MIN_VALUE;
      ymax = F26Dot6.MIN_VALUE;
      
      hScanLines = new ScanLines (true);
      vScanLines = new ScanLines (false);  
    }
    
    public void startContour () {
      currentDir = Direction.NONE;
    }
    
    public void line (int /*26.6*/ x1, int /*26.6*/ y1, int /*26.6*/ x2, int /*26.6*/ y2) { 

      xmin = F26Dot6.min (x1, F26Dot6.min (x2, xmin));
      ymin = F26Dot6.min (y1, F26Dot6.min (y2, ymin));
      xmax = F26Dot6.max (x1, F26Dot6.max (x2, xmax));
      ymax = F26Dot6.max (y1, F26Dot6.max (y2, ymax));

      Direction thisSegmentDir = Direction.ofSegment (x1, y1, x2, y2);

      if (thisSegmentDir == Direction.NO_MOVE) {
        return; }

      if (ScalerDebugger.debugOn && outlineDebugger != null) {
        outlineDebugger.ttScanLine (x1, y1, x2, y2); }

      doLine (thisSegmentDir, x1, y1, x2, y2);

      if (currentDir == Direction.NONE) {
        firstSegmentX = x1;
        firstSegmentY = y1;
        firstSegmentDir = thisSegmentDir; }
      else {
        endPointH (currentDir, x1, y1, thisSegmentDir);
        if (isFixing (scanType)) {
          endPointV (currentDir, x1, y1, thisSegmentDir); }}

      currentDir = thisSegmentDir;     }
    
    private void doLine (Direction thisSegmentDir, int /*26.6*/ x1, int /*26.6*/ y1, int /*26.6*/ x2, int /*26.6*/ y2) { 

      int quadrant;
      long q;

      int /*26.6*/ fxYScan;
      int /*26.6*/ fxYInit;
      int lYScan, lYSteps, lYIncr, lYOffset;
      int /*26.6*/ fxYY2;

      if (! thisSegmentDir.isDown) {                       /* if going up or flat */
        quadrant = 1;
        q = 0;
        fxYScan = scanlineAbove (y1) + F26Dot6.ONE_HALF;              /* first scanline to cross */
        fxYInit = fxYScan - y1;               /* first y step */
        lYScan = F26Dot6.toInt (fxYScan);
        lYSteps = F26Dot6.toInt (scanlineBelow (y2)) - lYScan + 1;
        lYIncr = 1;        
        lYOffset = 0;                           /* no reflection */
        fxYY2 = y2 - y1; }                  /* translate */
      else {                                    /* if going down */
        quadrant = 4;
        q = 1;                                /* to include pixel centers */

        fxYScan = scanlineBelow (y1) + F26Dot6.ONE_HALF;              /* first scanline to cross */
        fxYInit = y1 - fxYScan;               /* first y step */
        lYScan = F26Dot6.toInt (fxYScan);
        lYSteps = lYScan - F26Dot6.toInt (scanlineAbove (y2)) + 1;
        lYIncr = -1;        
        lYOffset = 1;                           /* reflection correction */
        fxYY2 = y1 - y2; }                  /* translate and reflect */

      if (y2 == y1) {                       /* if horizontal line */
        if (! isFixing (scanType)) {        /* if no dropout control */
          return; }                      /* if only horiz scan, done */
        // correction on y1 to include pix centers
        lYScan = F26Dot6.toInt (scanlineAbove (x2 < x1 ? (y1 - 1) : y1));
        lYSteps = 0; }

      /*  check x coordinates  */

      int /*26.6*/ fxXScan;
      int /*26.6*/ fxXInit;
      int lXScan, lXSteps, lXIncr, lXOffset;
      int /*26.6*/ fxXX2;

      if (! thisSegmentDir.isLeft) {                       /* if going right or vertical */
        fxXScan = scanlineAbove (x1) + F26Dot6.ONE_HALF;              /* first scanline to cross */
        fxXInit = fxXScan - x1;               /* first x step */
        lXScan = F26Dot6.toInt (fxXScan);
        lXSteps = F26Dot6.toInt (scanlineBelow (x2)) - lXScan + 1;
        lXIncr = 1;        
        lXOffset = 0;                           /* no reflection */
        fxXX2 = x2 - x1; }                  /* translate */
      else {                                    /* if going left */
        q = 1 - q;                           /* reverse it */
        quadrant = (quadrant == 1) ? 2 : 3;   /* negative x choices */

        fxXScan = scanlineBelow (x1) + F26Dot6.ONE_HALF;              /* first scanline to cross */
        fxXInit = x1 - fxXScan;               /* first x step */
        lXScan = F26Dot6.toInt (fxXScan);
        lXSteps = lXScan - F26Dot6.toInt (scanlineAbove (x2)) + 1;
        lXIncr = -1;        
        lXOffset = 1;                           /* reflection correction */
        fxXX2 = x1 - x2; }                  /* translate and reflect */

      if (x2 == x1) {                       /* if vertical line       */
        // correction on x1 to include pix centers
        lXScan = F26Dot6.toInt (scanlineAbove (y2 > y1 ? (x1 - 1) : x1));
        lXSteps = 0; }


      if (y1 == y2) { /* if horizontal line */
        if (isFixing (scanType)) {
          for (int i = 0; i < lXSteps; i++) {
            vScanLines.addCross (F26Dot6.fromInt (lYScan), lXScan, thisSegmentDir.isLeft);
            lXScan += lXIncr; }}}                   /* advance x scan + or - */

      else if (x1 == x2) { /* if vertical line */
        for (int i = 0; i < lYSteps; i++) {         /*   then blast a column   */
          hScanLines.addCross (F26Dot6.fromInt (lXScan), lYScan, thisSegmentDir.isUp);
          lYScan += lYIncr; }}                   /* advance y scan + or - */

      else { /*  handle general case:  line is neither horizontal nor vertical  */
        q += (((long) fxXX2) * fxYInit) - (((long) fxYY2) * fxXInit);  /* cross product init */
        long lDQy = fxXX2 << 6;
        long lDQx = -fxYY2 << 6;

        for (int i = 0; i < (lXSteps + lYSteps); i++) {
          if (q > 0) {                          /* if left of line */
            if (isFixing (scanType)) {
              vScanLines.addCross (F26Dot6.fromInt (lYScan + lYOffset), lXScan, thisSegmentDir.isLeft); }
            lXScan += lXIncr;                     /* advance x scan + or - */
            q += lDQx; }
          else {                                  /* if right of line */
            hScanLines.addCross (F26Dot6.fromInt (lXScan + lXOffset), lYScan, thisSegmentDir.isUp);
            lYScan += lYIncr;                     /* advance y scan + or - */
            q += lDQy; }}}
    }

    int depth = 0;
    
    public void quadraticCurve (int /*26.6*/ x1, int /*26.6*/ y1, 
        int /*26.6*/ x2, int /*26.6*/ y2, 
        int /*26.6*/ x3, int /*26.6*/ y3) {
      depth = 0;
      doQuadraticCurve (x1, y1, x2, y2, x3, y3);
    }    
    public void doQuadraticCurve (int /*26.6*/ x1, int /*26.6*/ y1, 
                                int /*26.6*/ x2, int /*26.6*/ y2, 
                                int /*26.6*/ x3, int /*26.6*/ y3) {
      depth++;
      
      if(depth >100) {
        System.err.println ("dpeth!"); }
      
      if (! ((y1 <= y2 && y2 <= y3) || (y3 <= y2 && y2 <= y1))) {
        int /*26.6*/ num = (y2 - y1);
        int /*26.6*/ denom = num - (y3 - y2);
        int /*26.6*/ x4 = x1 + F26Dot6.multiplyDivide (x2 - x1, num, denom);
        int /*26.6*/ x6 = x2 + F26Dot6.multiplyDivide ((x3 - x2), num , denom);
        int /*26.6*/ x5 = x4 + F26Dot6.multiplyDivide ((x6 - x4), num , denom);
        int /*26.6*/ y456 = y1 + F26Dot6.multiplyDivide ((y2 - y1), num , denom);
        doQuadraticCurve (x1, y1, x4, y456, x5, y456);
        doQuadraticCurve (x5, y456, x6, y456, x3, y3); 
        return; }

      if (! ((x1 <= x2 && x2 <= x3) || (x3 <= x2 && x2 <= x1))) {
        int /*26.6*/ num = (x2 - x1);
        int /*26.6*/ denom = num - (x3 - x2);
        int /*26.6*/ y4 = y1 + F26Dot6.multiplyDivide ((y2 - y1), num, denom);
        int /*26.6*/ y6 = y2 + F26Dot6.multiplyDivide ((y3 - y2), num, denom);
        int /*26.6*/ y5 = y4 + F26Dot6.multiplyDivide ((y6 - y4), num, denom);
        int /*26.6*/ x456 = x1 + F26Dot6.multiplyDivide ((x2 - x1), num, denom);
        doQuadraticCurve (x1, y1, x456, y4, x456, y5);
        doQuadraticCurve (x456, y5, x456, y6, x3, y3);
        return; }
      
      
      /* if the curve is too large, break it in parts */
      if ((Math.abs(x3 - x1) > 3200) || (Math.abs(y3 - y1) > 3200))
      {
          int /*26.6 */ fxX4 = (x1 + x2) >> 1;              /* first segment mid point */
          int /*26.6 */ fxY4 = (y1 + y2) >> 1;
          int /*26.6 */ fxX6 = (x2 + x3) >> 1;              /* second segment mid point */
          int /*26.6 */ fxY6 = (y2 + y3) >> 1;
          int /*26.6 */ fxX5 = (fxX4 + fxX6) >> 1;              /* mid segment mid point */
          int /*26.6 */ fxY5 = (fxY4 + fxY6) >> 1;

          doQuadraticCurve(x1, y1, fxX4, fxY4, fxX5, fxY5);
          doQuadraticCurve(fxX5, fxY5, fxX6, fxY6, x3, y3);
          return;
      }

      Direction thisSegmentDir = Direction.ofSegment (x1, y1, x3, y3);

      if (thisSegmentDir == Direction.NO_MOVE) {
        return; }

      xmin = F26Dot6.min (x1, F26Dot6.min (x3, xmin));
      ymin = F26Dot6.min (y1, F26Dot6.min (y3, ymin));
      xmax = F26Dot6.max (x1, F26Dot6.max (x3, xmax));
      ymax = F26Dot6.max (y1, F26Dot6.max (y3, ymax));

      if (ScalerDebugger.debugOn && outlineDebugger != null) {
        outlineDebugger.ttScanQuadCurve (x1, y1, x2, y2, x3, y3); }

      int /*26.6*/  yinit, yy2, yy3;
      int /*26.6*/  xinit, xx2, xx3;

      int q;
      int i_xscan, i_xstop, xincr, xoffset;
      int i_yscan, i_ystop, yincr, yoffset;

      if (thisSegmentDir.isUp) {
        q = 0;
        int /*26.6*/ yscan = scanlineAbove (y1) + F26Dot6.ONE_HALF;
        yinit = yscan - y1;
        i_yscan = F26Dot6.toInt (yscan);
        i_ystop = F26Dot6.toInt (scanlineBelow (y3) + F26Dot6.ONE_HALF) + 1;
        yincr = 1;
        yoffset = 0;
        yy2 = y2 - y1;
        yy3 = y3 - y1; }

      else {
        q = 1;
        int /*26.6*/ yscan = scanlineBelow (y1) + F26Dot6.ONE_HALF;
        yinit = y1 - yscan;
        i_yscan = F26Dot6.toInt (yscan);
        i_ystop = F26Dot6.toInt (scanlineAbove (y3) + F26Dot6.ONE_HALF) - 1;
        yincr = -1;
        yoffset = 1; 
        yy2 = y1 - y2;
        yy3 = y1 - y3; }

      if (thisSegmentDir.isRight) {
        int /*26.6*/ xscan = scanlineAbove (x1)+ F26Dot6.ONE_HALF;
        xinit = xscan - x1;
        i_xscan = F26Dot6.toInt (xscan);
        i_xstop = F26Dot6.toInt (scanlineBelow (x3) + F26Dot6.ONE_HALF) + 1;
        xincr = 1;
        xoffset = 0;
        xx2 = x2 - x1;
        xx3 = x3 - x1; }

      else {
        q = 1-q;
        int /*26.6*/ xscan = scanlineBelow (x1)+ F26Dot6.ONE_HALF;
        xinit = x1 - xscan;
        i_xscan = F26Dot6.toInt (xscan);
        i_xstop = F26Dot6.toInt (scanlineAbove (x3) + F26Dot6.ONE_HALF) - 1;
        xincr = -1;
        xoffset = 1; 
        xx2 = x1 - x2;
        xx3 = x1 - x3; }

      int /*20.12*/ alpha = 2 * (xx2 * yy3 - yy2 * xx3);

      if (alpha == 0) {
        line (x1, y1, x3, y3); 
        return; }

      int aBits = Math2.powerOf2 (alpha);
      int xyBits = Math2.powerOf2 (xx3 > yy3 ? xx3 : yy3);
      int zShift = zShiftTable [aBits + xyBits];
      int zBits = 6 /*of 26.6.*/ - zShift;

      if (zShift > 0) {
        int zRound = 1 << (zShift -1);
        xx2 = (xx2 + zRound) >> zShift;
        yy2 = (yy2 + zRound) >> zShift;
        xx3 = (xx3 + zRound) >> zShift;
        yy3 = (yy3 + zRound) >> zShift;

        xinit = (xinit + zRound) >> zShift;
        yinit = (yinit + zRound) >> zShift;

        alpha = 2 * (xx2 * yy3 - yy2 * xx3); }


      int /*26.6*/  ax = xx3 - xx2 * 2;
      int /*26.6*/  ay = yy3 - yy2 * 2;
      int /*20.12*/  r = ay * ay;
      int /*20.12*/  s2 = - ax * ay;
      int /*20.12*/  t = ax * ax;
      int /*14.18*/  u2 = yy2 * alpha;
      int /*14.18*/  v2 = - xx2 * alpha;

      int zSubpix = 1 << zBits;

      int dqx, dqy, ddqx, ddqy, rZ, sZ, tZ;

      if (xyBits <= 7) {
        q += (r * xinit + (s2 << 1) * yinit + (u2 << 1)) * xinit 
        + (t * yinit + (v2 << 1)) * yinit; /*8.24*/
        dqx = (r * ((xinit << 1) + zSubpix) + (s2 << 1) * yinit + (u2 << 1)) << zBits;
        dqy = (t * ((yinit << 1) + zSubpix) + (s2 << 1) * xinit + (v2 << 1)) << zBits;
        rZ = r << (zBits << 1);
        sZ = (s2 << 1) << (zBits << 1);
        tZ = t << (zBits << 1); }
      else {
        q += (((r >> 1) * xinit + s2 * yinit + u2) >> zBits) * xinit 
        + (((t >> 1) * yinit + v2) >> zBits) * yinit;
        dqx = r * (xinit + (zSubpix >> 1)) + s2 * yinit + u2;
        dqy = t * (yinit + (zSubpix >> 1)) + s2 * xinit + v2;
        rZ = r << (zBits - 1);
        sZ = s2 << (zBits     );
        tZ = t << (zBits - 1); }

      ddqx = 2 * rZ;
      ddqy = 2 * tZ;

      if (ScalerDebugger.debugOn && outlineDebugger != null) {
        outlineDebugger.ttScanLog ("  alpha=0x" + Integer.toHexString (alpha));
        outlineDebugger.ttScanLog ("  xscan=" + i_xscan + ", yscan=" + i_yscan 
            + ", xoffset=" + xoffset + ", yoffset=" + yoffset 
            + ", xinit=" + F26Dot6.toString (xinit) 
            + ", yinit=" + F26Dot6.toString (yinit));
        outlineDebugger.ttScanLog ("  ax=0x" + Integer.toHexString(ax)
            + ", ay=0x" + Integer.toHexString(ay) 
            + ", r=0x" + Integer.toHexString(r)
            + ", s2=0x" + Integer.toHexString(s2)
            + ", t=0x" + Integer.toHexString(t)
            + ", u2=0x" + Integer.toHexString(u2)
            + ", v2=0x" + Integer.toHexString(v2));
        outlineDebugger.ttScanLog ("  dqx=0x" + Integer.toHexString(dqx)
            + ", dqy=0x" + Integer.toHexString(dqy) 
            + ", ddqx=0x" + Integer.toHexString(ddqx)
            + ", ddqy=0x" + Integer.toHexString(ddqy)
            + ", rZ=0x" + Integer.toHexString(rZ)
            + ", sZ=0x" + Integer.toHexString(sZ)
            + ", tZ=0x" + Integer.toHexString(tZ)); }

      if (alpha > 0) {
        while (i_xscan != i_xstop && i_yscan != i_ystop) {
          if (ScalerDebugger.debugOn && outlineDebugger != null) {
            outlineDebugger.ttScanLog ("  q=0x" + Integer.toHexString (q) 
                + ", dqx=0x" + Integer.toHexString (dqx) 
                + ", dqy=0x" + Integer.toHexString (dqy)
                + ", ddqx=0x" + Integer.toHexString (ddqx) 
                + ", ddqy=0x" + Integer.toHexString (ddqy)); }
          if (q < 0 || dqy > tZ) {
            if (isFixing (scanType)) {
              if (ScalerDebugger.debugOn && outlineDebugger != null) {
                outlineDebugger.ttScanLog ("  vCross " + F26Dot6.toString (getY (F26Dot6.fromInt (i_xscan) + F26Dot6.ONE_HALF, x1, y1, x2, y2, x3, y3))); }
              vScanLines.addCross (F26Dot6.fromInt (i_yscan + yoffset), i_xscan, thisSegmentDir.isLeft); }
            i_xscan += xincr;
            q += dqx;
            dqx += ddqx;
            dqy += sZ; }
          else {
            if (ScalerDebugger.debugOn && outlineDebugger != null) {
              outlineDebugger.ttScanLog ("  hCross " + F26Dot6.toString (getX (F26Dot6.fromInt (i_yscan) + F26Dot6.ONE_HALF, x1, y1, x2, y2, x3, y3))); }
            hScanLines.addCross (F26Dot6.fromInt (i_xscan + xoffset), i_yscan, thisSegmentDir.isUp);
            i_yscan += yincr;
            q += dqy;
            dqy += ddqy;
            dqx += sZ; }}}

      else {
        while (i_xscan != i_xstop && i_yscan != i_ystop) {
          if (ScalerDebugger.debugOn && outlineDebugger != null) {
            outlineDebugger.ttScanLog ("  q=0x" + Integer.toHexString (q) 
                + ", dqx=0x" + Integer.toHexString (dqx) 
                + ", dqy=0x" + Integer.toHexString (dqy)
                + ", ddqx=0x" + Integer.toHexString (ddqx) 
                + ", ddqy=0x" + Integer.toHexString (ddqy)); }
          if (q < 0 || dqx > rZ) {
            if (ScalerDebugger.debugOn && outlineDebugger != null) {
              outlineDebugger.ttScanLog ("  hCross " + F26Dot6.toString (getX (F26Dot6.fromInt (i_yscan) + F26Dot6.ONE_HALF, x1, y1, x2, y2, x3, y3))); }
            hScanLines.addCross (F26Dot6.fromInt (i_xscan + xoffset), i_yscan, thisSegmentDir.isUp);
            i_yscan += yincr;
            q += dqy;
            dqy += ddqy;
            dqx += sZ; }
          else {
            if (isFixing (scanType)) {
              if (ScalerDebugger.debugOn && outlineDebugger != null) {
                outlineDebugger.ttScanLog ("  vCross " + F26Dot6.toString (getY (F26Dot6.fromInt (i_xscan) + F26Dot6.ONE_HALF, x1, y1, x2, y2, x3, y3))); }
              vScanLines.addCross (F26Dot6.fromInt (i_yscan + yoffset), i_xscan, thisSegmentDir.isLeft); }
            i_xscan += xincr;
            q += dqx;
            dqx += ddqx;
            dqy += sZ; }}}

      while (i_xscan != i_xstop) {
        if (isFixing (scanType)) {
          if (ScalerDebugger.debugOn && outlineDebugger != null) {
            outlineDebugger.ttScanLog ("  vCross " + F26Dot6.toString (getY (F26Dot6.fromInt (i_xscan) + F26Dot6.ONE_HALF, x1, y1, x2, y2, x3, y3))); }
          vScanLines.addCross (F26Dot6.fromInt (i_yscan + yoffset), i_xscan, thisSegmentDir.isLeft); }
        i_xscan += xincr; }

      while (i_yscan != i_ystop) {
        if (ScalerDebugger.debugOn && outlineDebugger != null) {
          outlineDebugger.ttScanLog ("  hCross " + F26Dot6.toString (getX (F26Dot6.fromInt (i_yscan) + F26Dot6.ONE_HALF, x1, y1, x2, y2, x3, y3))); }
        hScanLines.addCross (F26Dot6.fromInt (i_xscan + xoffset), i_yscan, thisSegmentDir.isUp);
        i_yscan += yincr; }

      if (currentDir == Direction.NONE) {
        firstSegmentX = x1;
        firstSegmentY = y1;
        firstSegmentDir = thisSegmentDir; }
      else {
        endPointH (currentDir, x1, y1, thisSegmentDir);
        if (isFixing (scanType)) {
          endPointV (currentDir, x1, y1, thisSegmentDir); }}

      currentDir = thisSegmentDir; 
    }

    public void cubicCurve (double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) {
      if (   (   (x1 <= x2 && x2 <= x3 && x3 <= x4)
              || (x4 <= x3 && x3 <= x2 && x2 <= x1))
          && (Math.abs (x4 - x1) < THRESHOLD)
          && (Math.abs ((x4 - x1) - 3 * (x2 - x1)) <= epsilon)
          && (Math.abs ((x4 - x1) - 3 * (x4 - x3)) <= epsilon)
          && (   (y1 <= y2 && y2 <= y3 && y3 <= y4)
              || (y4 <= y3 && y3 <= y2 && y2 <= y1))
          && (Math.abs (y4 - y1) < THRESHOLD)
          && (Math.abs ((y4 - y1) - 3 * (y2 - y1)) <= epsilon)
          && (Math.abs ((y4 - y1) - 3 * (y4 - y3)) <= epsilon)) {
        
        line (x1, y1, x4, y4); }
      
      else {
        double x12 = (x2 + x1) / 2.0; double y12 = (y2 + y1) / 2.0;
        double x23 = (x3 + x2) / 2.0; double y23 = (y3 + y2) / 2.0;
        double x34 = (x4 + x3) / 2.0; double y34 = (y4 + y3) / 2.0;
        
        double x1223 = (x23 + x12) / 2.0; double y1223 = (y23 + y12) / 2.0;
        double x2334 = (x34 + x23) / 2.0; double y2334 = (y34 + y23) / 2.0;
        
        double xx = (x1223 + x2334) / 2.0; double yy = (y1223 + y2334) / 2.0;
        
        cubicCurve (x1, y1, x12, y12, x1223, y1223, xx, yy);       
        cubicCurve (xx, yy, x2334, y2334, x34, y34, x4, y4); }
    }
    
    public void endContour () {
      if (currentDir != Direction.NONE) {
        endPointH (currentDir, firstSegmentX, firstSegmentY, firstSegmentDir);

        if (isFixing (scanType)) {
          endPointV (currentDir, firstSegmentX, firstSegmentY, firstSegmentDir); } 

        currentDir = Direction.NONE; }
    }

    public void endOutline () {
      xmin = F26Dot6.roundHalfDown (xmin);
      ymin = F26Dot6.roundHalfDown (ymin);
      xmax = F26Dot6.roundHalfUp (xmax);
      ymax = F26Dot6.roundHalfUp (ymax);

      zeroDimension = (xmin == xmax) || (ymin == ymax);
    }
    
    void endPointH (Direction beforeDir, int /*26.6*/ x, int /*26.6*/ y, Direction afterDir) {
      int yI = F26Dot6.toInt (F26Dot6.floor (y));
      if (F26Dot6.fromInt (yI) + F26Dot6.ONE_HALF != y) {
        return; }
      
      if (afterDir.isUp) {
        if (beforeDir.isUp) {
          hScanLines.addCross (x, yI, true); }
        else if (beforeDir.isDown) {
          hScanLines.addCross (x, yI, true);
          hScanLines.addCross (x, yI, false); }
        else {
          if (beforeDir.isLeft) {
            hScanLines.addCross (x, yI, true); }}}
            
      else if (afterDir.isDown) {
        if (beforeDir.isUp) {
          hScanLines.addCross (x, yI, true);
          hScanLines.addCross (x, yI, false); }
        else if (beforeDir.isDown) {
          hScanLines.addCross (x, yI, false); }
        else {
          if (beforeDir.isRight) {
            hScanLines.addCross (x, yI, false); }}}
      
      else {
        if (beforeDir.isUp) {
          if (afterDir.isRight) {
            hScanLines.addCross (x, yI, true); }}
        else if (beforeDir.isDown) {
          if (afterDir.isLeft) {
            hScanLines.addCross (x, yI, false); }}
        else {
          if (beforeDir.isRight && afterDir.isLeft) {
            hScanLines.addCross (x, yI, false); }
          else if (beforeDir.isLeft && afterDir.isRight) {   
            hScanLines.addCross (x, yI, true); }}}
    }
     
    void endPointV (Direction beforeDir, int /*26.6*/ x, int /*26.6*/ y, Direction afterDir) {
      int xI = F26Dot6.toInt (F26Dot6.floor (x));
      if (F26Dot6.fromInt (xI) + F26Dot6.ONE_HALF != x) {
        return; }
      
      if (afterDir.isLeft) {
        if (beforeDir.isLeft) {
          vScanLines.addCross (y, xI, true); }
        else if (beforeDir.isRight) {
          vScanLines.addCross (y, xI, true);
          vScanLines.addCross (y, xI, false); }
        else {
          if  (beforeDir.isDown) {
            vScanLines.addCross (y, xI, true); }}}
      
      else if (afterDir.isRight) {
        if (beforeDir.isLeft) {
          vScanLines.addCross (y, xI, true);
          vScanLines.addCross (y, xI, false); }
        else if (beforeDir.isRight) {
          vScanLines.addCross (y, xI, false); }
        else {
          if (beforeDir.isUp) {
            vScanLines.addCross (y, xI, false); }}}
      
      else {
        if (beforeDir.isLeft) {
          if (afterDir.isUp) {
            vScanLines.addCross (y, xI, true); }}
        else if (beforeDir.isRight) {
          if (afterDir.isDown) {
            vScanLines.addCross (y, xI, false); }}
        else {
          if (beforeDir.isUp && afterDir.isDown) {
            vScanLines.addCross (y, xI, false); }
          else if (beforeDir.isDown && afterDir.isUp) {   
            vScanLines.addCross (y, xI, true); }}}
    }
    
 
    final int zShiftTable [] = { /* for precision adjustment */
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   /*  0 -  9  */
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   /* 10 - 19 */
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   /* 20 - 29 */
      1, 1, 1, 2, 2, 2, 3, 3          /* 30 - 34 */
    };

    private int /*26.6*/ getY (int x, int /*26.6*/ x1, int /*26.6*/ y1, int /*26.6*/ x2, int /*26.6*/ y2, int /*26.6*/ x3, int /*26.6*/ y3) {

      return 0;
      
//      int xmid = (x1 + 2*x2 + x3 + 1) / 4;
//      int ymid = (y1 + 2*y2 + y3 + 1) / 4;
//      if (x == xmid) {
//        return ymid; }
//      else if ((x < xmid && x1 <= x2 && x2 <= x3) || (xmid < x && x3 <= x2 && x2 <= x1)) {
//        return getY (x, x1, y1, (x1 + x2) / 2, (y1 + y2) / 2, xmid, ymid); }
//      else {
//        return getY (x, xmid, ymid, (x2 + x3) / 2, (y2 + y3) / 2, x3, y3); }
    }
    
    private int /*26.6*/ getX (int y, int /*26.6*/ x1, int /*26.6*/ y1, int /*26.6*/ x2, int /*26.6*/ y2, int /*26.6*/ x3, int /*26.6*/ y3) {     
      
      return 0;
      
//      int xmid = (x1 + 2*x2 + x3 + 1) / 4;
//      int ymid = (y1 + 2*y2 + y3 + 1) / 4;
//      if (y == ymid) {
//        return xmid; }
//      else if ((y < ymid && y1 <= y2 && y2 <= y3) || (ymid < y && y3 <= y2 && y2 <= y1)) {
//        return getX (y, x1, y1, (x1 + x2) / 2, (y1 + y2) / 2, xmid, ymid); }
//      else {
//        return getX (y, xmid, ymid, (x2 + x3) / 2, (y2 + y3) / 2, x3, y3); }
    }
  }

  //--------------------------------------------------------------------------

  private CrossBuilder crossBuilder;
  
  public TTScan () {
    crossBuilder = new CrossBuilder ();
    crossBuilder.setFlatness (1.0d);
  }
  
  public void setScanType (int scanType) {
    this.scanType = scanType;
    if (ScalerDebugger.debugOn && outlineDebugger != null) {
      outlineDebugger.ttScanScanType (scanType); }
  }
  
  public OutlineConsumer2 getOutlineConsumer2 () {
    return crossBuilder;
  }
  
  public void getBitmap (BitmapConsumer dest) {
    if (crossBuilder.zeroDimension) {
      scanType = scanType & ~ 1; }
    
    if (! checkCrosses ()) {
      return; }
    
    Bitmap bitmap = new Bitmap (F26Dot6.toInt (crossBuilder.xmin), 
                                F26Dot6.toInt (crossBuilder.xmax), 
                                F26Dot6.toInt (crossBuilder.ymin), 
                                F26Dot6.toInt (crossBuilder.ymax));
    
    buildInitialRuns (bitmap);
    
    if (isFixing (scanType)) {
      fixHDropouts (bitmap);
      fixVDropouts (bitmap); }
    
    bitmap.emitBitmap (dest);
  }
  
  private boolean checkCrosses () {
    if (hScanLines.scanLines != null) {
      for (int s = 0; s < hScanLines.scanLines.length; s++) {
        ScanLine scanLine = hScanLines.scanLines [s];
        if (scanLine != null
            && (scanLine.onCrosses == null || scanLine.offCrosses == null 
                || scanLine.onCrosses.values == null || scanLine.offCrosses.values == null 
                || scanLine.onCrosses.values.length != scanLine.offCrosses.values.length)) {
          System.err.println ("****************** h crosses not paired!");
          return false; }}}
    
    if (vScanLines.scanLines != null) {
      for (int s = 0; s < vScanLines.scanLines.length; s++) {
        ScanLine scanLine = vScanLines.scanLines [s];
        if (scanLine != null
            && (scanLine.onCrosses == null || scanLine.offCrosses == null 
                || scanLine.onCrosses.values == null || scanLine.offCrosses.values == null 
                || scanLine.onCrosses.values.length != scanLine.offCrosses.values.length)) {
          System.err.println ("****************** v crosses not paired!");
          return false; }}}
    
    return true;
  }
  
  private void buildInitialRuns (Bitmap bitmap) {
    if (hScanLines.scanLines == null) {
      return; }
    
    for (int s = 0; s < hScanLines.scanLines.length; s++) {
      ScanLine scanLine = hScanLines.scanLines [s];
      if (scanLine == null) {
        continue; }
 
      int y = hScanLines.firstScanLineCoordI + s;
      
      for (int i = 0; i < scanLine.onCrosses.values.length; i++) {
        int /*26.6*/ x1 = scanLine.onCrosses.values [i];
        int /*26.6*/ x2 = scanLine.offCrosses.values [i];
        if (x1 > x2) {
          int /*26.6*/ tmp = x1; x1 = x2; x2 = tmp; }
        
        bitmap.paintBlack (F26Dot6.toInt (F26Dot6.roundHalfDown (x1)), 
                           F26Dot6.toInt (F26Dot6.roundHalfUp (x2)),
                           y); }}
  }
  
  private void fixHDropouts (Bitmap bitmap) {
    if (hScanLines.scanLines == null) {
      return; }
    
    for (int s = 0; s < hScanLines.scanLines.length; s++) {
      ScanLine scanLine = hScanLines.scanLines [s];
      if (scanLine == null) {
        continue; }
      
      int y = hScanLines.firstScanLineCoordI + s;
      for (int i = 0; i < scanLine.onCrosses.values.length; i++) {
        int /*26.6*/ x1 = scanLine.onCrosses.values [i];
        int /*26.6*/ x2 = scanLine.offCrosses.values [i];
        if (x1 > x2) {
          int /*26.6*/ tmp = x1; x1 = x2; x2 = tmp; }
        
        int x = F26Dot6.toInt (scanlineAbove (x1));
       
        if (x <= F26Dot6.toInt (scanlineBelow (x2))) {
          continue; }
        
        if (bitmap.isBlack (x - 1, y)
            || bitmap.isBlack (x, y)) {
          continue; }
        
        if (isExcludingStubs (scanType)) {
          int crossings = 0;  // above
          crossings += vScanLines.countCrosses (x - 1, y + 1);
          crossings += hScanLines.countCrosses (y + 1, x);
          crossings += vScanLines.countCrosses (x, y + 1);
          if (crossings < 2) {
            continue; }
          
          crossings = 0;   // below
          crossings += vScanLines.countCrosses (x - 1, y);
          crossings += hScanLines.countCrosses (y - 1, x);
          crossings += vScanLines.countCrosses (x, y);
          if (crossings < 2) {
            continue; }}
        
        int pixel;
        if (isSmart (scanType)) {
          pixel = F26Dot6.toInt (F26Dot6.floor ((scanLine.onCrosses.values [i] + scanLine.offCrosses.values [i]) / 2 )); }
        else {
          pixel = x - 1; }
        
        if (pixel < F26Dot6.toInt (crossBuilder.xmin)) {
          pixel++; }
        if (F26Dot6.toInt (crossBuilder.xmax) < pixel) {
          pixel--; }

        bitmap.paintBlack (pixel, y); }}
  }
  
  private void fixVDropouts (Bitmap bitmap) {
    if (vScanLines.scanLines == null) {
      return; }
    
    for (int s = 0; s < vScanLines.scanLines.length; s++) {
      ScanLine scanLine = vScanLines.scanLines [s];
      if (scanLine == null) {
        continue; }
      
      int x = vScanLines.firstScanLineCoordI + s;
      for (int i = scanLine.onCrosses.values.length - 1; i >= 0; i--) {
        int /*26.6*/ y1 = scanLine.onCrosses.values [i];
        int /*26.6*/ y2 = scanLine.offCrosses.values [i];
        if (y1 > y2) {
          int /*26.6*/ tmp = y1; y1 = y2; y2 = tmp; }
        
        int y = F26Dot6.toInt (scanlineAbove (y1));
        
        if (y <= F26Dot6.toInt (scanlineBelow (y2))) {
          continue; }
        
        if (bitmap.isBlack (x, y - 1)
            || bitmap.isBlack (x, y)) {
          continue; }
        
        if (isExcludingStubs (scanType)) {
          int crossings = 0;  // left
          crossings += hScanLines.countCrosses (y - 1, x);
          crossings += vScanLines.countCrosses (x - 1, y);
          crossings += hScanLines.countCrosses (y, x);
          if (crossings < 2) {
            continue; }
          
          crossings = 0; // right
          crossings += hScanLines.countCrosses (y - 1, x + 1);
          crossings += vScanLines.countCrosses (x + 1, y);
          crossings += hScanLines.countCrosses (y, x + 1);
          if (crossings < 2) {
            continue; }}
        
        int pixel;
        if (isSmart (scanType)) {
          pixel = F26Dot6.toInt (F26Dot6.floor ((scanLine.onCrosses.values [i] + scanLine.offCrosses.values [i]) / 2 )); }
        else {
          pixel = y - 1; }
        
        if (pixel < F26Dot6.toInt (crossBuilder.ymin)) {
          pixel++; }
        if (F26Dot6.toInt (crossBuilder.ymax) < pixel) {
          pixel--; }

        bitmap.paintBlack (x, pixel); }}
  }

  //----------------------------------------------------------------------------
  private boolean isFixing (int scanType) {
    return scanType == 0 || scanType == 1 || scanType == 4 || scanType == 5;
  }
  
  private boolean isSmart (int scanType) {
    return scanType == 4 || scanType == 5;
  }
  
  private boolean isExcludingStubs (int scanType) {
    return scanType == 1 || scanType == 5; 
  }
  
  //----------------------------------------------------------------------------

  private static class Bitmap {
    boolean[][] pixels;
    int blX;
    int blY;
    int width;
    
    public Bitmap (int xmin, int xmax, int ymin, int ymax) {
      if (xmin == Integer.MAX_VALUE) {
        pixels = null; 
        return; }
      
      blX = xmin;
      blY = ymin;
      pixels = new boolean [xmax - xmin + 1][];
      width = ymax - ymin + 1;
      for (int x = 0; x < pixels.length; x++) {
        pixels [x] = new boolean [width]; }
    }
    
    public boolean isBlack (int x, int y) {
      int iX = x - blX;
      int iY = y - blY;
      if (iX < 0 || pixels.length <= iX || iY < 0 || width <= iY) {
        return false; }
      return pixels [iX] [iY]; 
    }
    
    public void paintBlack (int x, int y) {
      pixels [x-blX] [y-blY] = true;
    }
    
    protected void paintBlack (int xOn, int xOff, int y) {     
      for (int x = xOn; x < xOff; x++) {
        paintBlack (x, y); }
    }
    
    protected void emitBitmap (BitmapConsumer bitmapConsumer) {     
      if (pixels == null) {
        return; }
      
      for (int x = 0; x < pixels.length; x++) {
        for (int y = 0; y < width; y++) {
          if (pixels [x] [y]) {
            bitmapConsumer.addRun (blX + x, blX + x + 1, blY + y); }}}
    }
  }
  
  //----------------------------------------------------------------------------
  private ScalerDebugger outlineDebugger;
  public void setDebugger (ScalerDebugger outlineDebugger) {
    this.outlineDebugger = outlineDebugger;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy