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

com.adobe.fontengine.inlineformatting.infontformatting.IndicFormatter Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
/*
 * File: IndicFormatter.java
 * 
 *	ADOBE CONFIDENTIAL
 *	___________________
 *
 *	Copyright 2004-2005 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.
 *
 */
package com.adobe.fontengine.inlineformatting.infontformatting;

import java.util.ArrayList;

import com.adobe.agl.util.ULocale;
import com.adobe.fontengine.CharUtil;
import com.adobe.fontengine.font.InvalidFontException;
import com.adobe.fontengine.font.UnsupportedFontException;
import com.adobe.fontengine.font.opentype.OTSelector;
import com.adobe.fontengine.font.opentype.OTSelectorOnElementAttribute;
import com.adobe.fontengine.font.opentype.OpenTypeFont;
import com.adobe.fontengine.font.opentype.Tag;
import com.adobe.fontengine.inlineformatting.AttributedRun;
import com.adobe.fontengine.inlineformatting.ElementAttribute;

/** In-font formatter for OpenType layout on Indic orthographies. */
abstract class IndicFormatter extends GenericFormatter {
  
  /** Enumeration of the possible  shapes.
   */
  final public static class Shape {
    private String name;
    
    private Shape (String name) {
      this.name = name;
    }
    
    public String toString () {
      return name; 
    }
    
    public static final Shape any            = new Shape ("any");
    public static final Shape live           = new Shape ("live");
    public static final Shape dead           = new Shape ("dead");
    public static final Shape half           = new Shape ("half");
    public static final Shape explicitVirama = new Shape ("explicitVirama");
    public static final Shape rephCons       = new Shape ("rephCons");
    public static final Shape rephVowel      = new Shape ("rephVowel");
    public static final Shape vattu          = new Shape ("vattu");
    public static final Shape subjoins       = new Shape ("subjoins");
    public static final Shape postjoins      = new Shape ("postjoins");
  }

  /** Attribute to carry the Indic shape.
   * Its value must be a Shape.
   */
  private static final ElementAttribute indicShape
    = new ElementAttribute ("indicShape");
  

  /** Enumeration of the possible positions.
   */
  final public static class Position {
    private String name;
    
    private Position (String name) {
      this.name = name;
    }
    
    public String toString () {
      return name; 
    }
    
    public static final Position any            = new Position ("any");
    public static final Position left           = new Position ("left");
    public static final Position topMatra       = new Position ("topMatra");
    public static final Position topOther       = new Position ("topOther");
    public static final Position bottom         = new Position ("bottom");
    public static final Position rightMatra     = new Position ("rightMatra");
    public static final Position rightOther     = new Position ("rightOther");
  }

 
  
  abstract protected int nukta ();
  abstract protected int virama ();

  abstract protected int splitVowelsAndNormalize (AttributedRun run, int start, int limit);
  
  abstract protected Position getPosition (int usv);
  
  abstract protected boolean isConsonant (int usv);
  abstract protected boolean hasNukta (int usv);
  abstract protected int removeNukta (int usv);
  abstract protected boolean isMark (int usv);
  abstract protected boolean isIndependentVowel (int usv);

  abstract protected Shape rephLike (int usv);
  abstract protected boolean subjoins (int usv);
  abstract protected boolean postjoins (int usv);
  abstract protected boolean postjoinsIndependentVowels (int usv);
  
  
  final class AksaraPart {
    public int usv;
    public int nuktaCount;
    public Shape shape; 

    public int nbGlyphs () {
      return 1 + nuktaCount + (shape != Shape.live ? 1 : 0);
    }
    
    public int addGlyph (int[] glyphIds, Shape[] glyphForms, int glyphCount,
                         int ch, Shape shape, OpenTypeFont font) 
        throws InvalidFontException, UnsupportedFontException {

      glyphIds [glyphCount] = font.getGlyphForChar (ch);
      glyphForms [glyphCount] = shape;
      return glyphCount + 1;
    }

    public int toGlyphs (int[] glyphIds, Shape[] glyphForms, int glyphCount,
                         OpenTypeFont font)
        throws InvalidFontException, UnsupportedFontException {
      
      glyphCount = addGlyph (glyphIds, glyphForms, glyphCount,
                             usv, shape, font);
      
      for (int i = 0; i < nuktaCount; i++) {
        glyphCount = addGlyph (glyphIds, glyphForms, glyphCount,
                               nukta (), Shape.any, font); }
      
      if (shape != Shape.live) {
        glyphCount = addGlyph (glyphIds, glyphForms, glyphCount,
                               virama (), shape, font); }
      
      return glyphCount;
    }
  }
 
  final class Aksara {   
    public int start;
    public int firstMark;
    public int limit;
    
    public ArrayList /**/ parts;
 
    public Aksara () {
      reset ();
    }
    
    public void reset () {
      parts = new ArrayList ();
    }
    
    public int nbGlyphs () {
      int nbGlyphs = limit - firstMark;

      for (int i = 0; i < parts.size (); i++) {
        nbGlyphs += ((AksaraPart) parts.get (i)).nbGlyphs (); }
      
      return nbGlyphs;
    }
      
    public int addGlyph (int[] glyphIds, Shape[] glyphForms, int glyphCount,
                         int ch, Shape shape, OpenTypeFont font) 
        throws InvalidFontException, UnsupportedFontException {
      glyphIds [glyphCount] = font.getGlyphForChar (ch);
      glyphForms [glyphCount] = shape;
      return glyphCount + 1;
    }
    
    public int toGlyphs (AttributedRun run, OpenTypeFont font)
        throws InvalidFontException, UnsupportedFontException {
  
      int [] glyphIds = new int [nbGlyphs ()];
      Shape [] glyphForms = new Shape [glyphIds.length];
      int glyphCount = 0;   
      
      for (int i = firstMark; i < limit; i++) {
        int usv = run.elementAt (i);
        if (getPosition (usv) == Position.left) {
          glyphCount = addGlyph (glyphIds, glyphForms, glyphCount,
                                 usv, Shape.any, font); }}
      
      AksaraPart firstPart = null, lastPart = null;
      for (int i = 0; i < parts.size (); i++) {
        AksaraPart part = ((AksaraPart) parts.get (i));
        if (firstPart == null) {
          firstPart = part; }
        lastPart = part;
        if (part.shape != Shape.rephCons && part.shape != Shape.rephVowel && part.shape != Shape.postjoins) {
          glyphCount = part.toGlyphs (glyphIds, glyphForms, glyphCount, font); }}
      
      for (int i = firstMark; i < limit; i++) {
        int usv = run.elementAt (i);
        if (getPosition (usv) == Position.bottom) {
          glyphCount = addGlyph (glyphIds, glyphForms, glyphCount,
                                 usv, Shape.any, font); }}
     
      for (int i = firstMark; i < limit; i++) {
        int usv = run.elementAt (i);
        if (getPosition (usv) == Position.topMatra) {
          glyphCount = addGlyph (glyphIds, glyphForms, glyphCount,
                                 usv, Shape.any, font); }}
      
      if (firstPart.shape == Shape.rephCons) {
        glyphCount = firstPart.toGlyphs (glyphIds, glyphForms, glyphCount, font); }
      
      if (lastPart.shape == Shape.postjoins) {
        glyphCount = lastPart.toGlyphs (glyphIds, glyphForms, glyphCount, font); }

      for (int i = firstMark; i < limit; i++) {
        int usv = run.elementAt (i);
        if (getPosition (usv) == Position.rightMatra) {
          glyphCount = addGlyph (glyphIds, glyphForms, glyphCount,
                                 usv, Shape.any, font); }}
            
      if (firstPart.shape == Shape.rephVowel) {
        glyphCount = firstPart.toGlyphs (glyphIds, glyphForms, glyphCount, font); }
      
      for (int i = firstMark; i < limit; i++) {
        int usv = run.elementAt (i);
        if (getPosition (usv) == Position.topOther) {
          glyphCount = addGlyph (glyphIds, glyphForms, glyphCount,
                                 usv, Shape.any, font); }}
      
      for (int i = firstMark; i < limit; i++) {
        int usv = run.elementAt (i);
        if (getPosition (usv) == Position.rightOther) {
          glyphCount = addGlyph (glyphIds, glyphForms, glyphCount,
                                 usv, Shape.any, font); }}
      
      
      int [] positions = new int [limit - start];
      for (int i = 0; i < positions.length; i++) {
        positions [i] = start + i; }
      
      run.replace (positions, glyphIds);
      
      run.setElementStyle (start, start + glyphIds.length,
                           ElementAttribute.isGlyph, java.lang.Boolean.TRUE);
      
      for (int i = 0; i < glyphIds.length; i++) {
        run.setElementStyle (start + i, indicShape, glyphForms [i]); }
      
      return start + glyphIds.length;
    }
  }

  private int processAksaraMarks (AttributedRun run, int start, int limit, 
                                  Aksara aksara) {
    aksara.firstMark = start;
    
    while (start < limit && isMark (run.elementAt (start))) {
      start++; }
    
    aksara.limit = start;
    return start;
  }
 
  private int processAksaras (AttributedRun run, int start, int limit, 
                              OpenTypeFont font)
      throws InvalidFontException, UnsupportedFontException {
    
    Aksara aksara = new Aksara ();
    
    while (start < limit) {
      aksara.reset ();
      aksara.start = start;
   
      if (isConsonant (run.elementAt (start))) {
        while (start < limit && isConsonant (run.elementAt (start))) {
          AksaraPart part = new AksaraPart ();
          
          part.shape = Shape.live;

          if (hasNukta (run.elementAt (start))) {
            part.nuktaCount = 1;
            part.usv = removeNukta (run.elementAt (start)); }
          else {
            part.nuktaCount = 0;
            part.usv = run.elementAt (start); }
          start++;

          while (start < limit && nukta () == run.elementAt (start)) {
            part.nuktaCount++; 
            start++;}

          if (start < limit && virama () == run.elementAt (start)) {
            part.shape = Shape.dead; 
            start++;

            if (start < limit && 0x200D /*ZWJ*/ == run.elementAt (start)) {
              part.shape = Shape.half; 
              start++; }

            else if (start < limit && 0x200C /*ZWNJ*/ == run.elementAt (start)) {
              part.shape = Shape.explicitVirama; 
              start++; }}

          aksara.parts.add (part);

          if (part.shape == Shape.live) {
            break; }}
        
        if (aksara.parts.size () > 1) {
          AksaraPart firstPart = (AksaraPart) aksara.parts.get (0);
          Shape rephShape = rephLike (firstPart.usv);
          if (rephShape != Shape.any
              && firstPart.shape == Shape.dead) {
            firstPart.shape = rephShape; }}
        
        for (int i = 1; i < aksara.parts.size (); i++) {
          AksaraPart part = (AksaraPart) aksara.parts.get (i);
          AksaraPart prevPart = (AksaraPart) aksara.parts.get (i-1);
          if (subjoins (part.usv) && (part.shape == Shape.dead || part.shape == Shape.live) && (prevPart.shape == Shape.dead)) {
            if (part.shape == Shape.live) {
              prevPart.shape = Shape.live; }
            part.shape = Shape.subjoins; }}
         
        if (aksara.parts.size () > 1) {
          AksaraPart part = (AksaraPart) aksara.parts.get (aksara.parts.size () - 1);
          AksaraPart prevPart = (AksaraPart) aksara.parts.get (aksara.parts.size () - 2);
          if (postjoins (part.usv) && (part.shape == Shape.dead || part.shape == Shape.live) && (prevPart.shape == Shape.dead)) {
            if (part.shape == Shape.live) {
              prevPart.shape = Shape.live; }
            part.shape = Shape.postjoins; }}

        start = processAksaraMarks (run, start, limit, aksara); }
      
      else if (isIndependentVowel (run.elementAt (start))) {
        AksaraPart part = new AksaraPart ();
        part.shape = Shape.live;
        part.usv = run.elementAt (start);
        part.nuktaCount = 0;
        aksara.parts.add (part);
        start++;

        while (   start + 1 < limit 
               && virama () == run.elementAt (start) 
               && postjoinsIndependentVowels (run.elementAt (start + 1))) {
          part = new AksaraPart ();
          part.shape = Shape.postjoins;
          part.usv = run.elementAt (start + 1);
          part.nuktaCount = 0;
          aksara.parts.add (part);
          start += 2; }
        
        start = processAksaraMarks (run, start, limit, aksara); }
      
      else {
        AksaraPart part = new AksaraPart ();
        part.shape = Shape.live;
        part.usv = run.elementAt (start);
        part.nuktaCount = 0;
        aksara.parts.add (part);
        start++;
        
        aksara.firstMark = start;
        aksara.limit = start; }
      
      int newStart = aksara.toGlyphs (run, font);
      limit += newStart - start;
      start = newStart; }
    
    return limit;
  }
      
  protected int canRenderWithFont (OpenTypeFont font, AttributedRun run, 
      int start, int limit, boolean notdefOk)
  throws InvalidFontException, UnsupportedFontException {
    
    int usv = run.elementAt (start);
    
    if (CharUtil.isControl (usv)) {
      return 1; }
    
    if (   ! CharUtil.isBase (usv) 
        || start + 1 == limit
        || run.getElementStyle (start + 1, ElementAttribute.isGlyph) == Boolean.TRUE
        || ! CharUtil.isCombining (run.elementAt (start + 1))) {
      // common case: a single character
      int gid = font.getGlyphForChar (usv);
      return (gid == 0) ? 0 : start + 1; }
    
    { int graphemeLimit = start + 1;
    while (   graphemeLimit < limit 
        && run.getElementStyle (graphemeLimit, ElementAttribute.isGlyph) == Boolean.FALSE 
        && CharUtil.isCombining (run.elementAt (graphemeLimit))) {
      graphemeLimit++; }
    
    boolean directMappingHasNotdef = false;
    int[] usvs = new int [graphemeLimit - start];
    int[] gids = new int [graphemeLimit - start];
    
    for (int i = start; i < graphemeLimit; i++) {
      usvs [i - start] = run.elementAt (i);
      int gid = font.getGlyphForChar (usvs [i - start]);
      if (gid == 0) {
        directMappingHasNotdef = true; }
      gids [i - start] = gid; }
    
    if (directMappingHasNotdef) {
      int usvc = CharUtil.compose (usvs, 0, graphemeLimit - start);
      if (usvc != -1) {
        int gid = font.getGlyphForChar (usvc);
        if (gid != 0) {
          return graphemeLimit - start; }}}
    
    if (notdefOk) {
      return graphemeLimit - start; }
    else {
      return 0; }}
  }
  
  protected boolean canFormatOT() {
	  return true;
  }
  
  protected int formatOT (OpenTypeFont otFont, AttributedRun run, int start, int limit, boolean shouldKern)
      throws InvalidFontException, UnsupportedFontException {
    // PRE: r.font [first, last] = one font
    //      r.direction [first, last] = one direction
    //      charRun is not defective, i.e. does not start in the 
    //      middle of a default grapheme cluster
    
    int scriptTag = getOTScriptTag ((Integer) run.getElementStyle (start, InFontFormatter.scriptAttribute));
    int langTag = getOTLanguageTag ((ULocale) run.getElementStyle (start, ElementAttribute.locale));
    
    limit = splitVowelsAndNormalize (run, start, limit);
    
    // Map characters to glyphs; simply skip existing glyphs
    limit = processAksaras (run, start, limit, otFont);
    
    if (otFont.gsub != null) {
      // Shape the glyphs
      int[][] gsubLookups = LookupsCache.resolveFeatureTag (otFont.gsub, scriptTag, langTag, gsubFeatures);
      limit = otFont.gsub.applyLookups (gsubLookups [0],  run, start, limit, everywhere,   otFont.gdef);  // nukt
      limit = otFont.gsub.applyLookups (gsubLookups [1],  run, start, limit, akhnSelector, otFont.gdef);  // akhn  
      limit = otFont.gsub.applyLookups (gsubLookups [2],  run, start, limit, rphfSelector, otFont.gdef);  // rphf
      limit = otFont.gsub.applyLookups (gsubLookups [3],  run, start, limit, blwfSelector, otFont.gdef);  // blwf
      limit = otFont.gsub.applyLookups (gsubLookups [11], run, start, limit, pstfSelector, otFont.gdef);  // pstf
      limit = otFont.gsub.applyLookups (gsubLookups [4],  run, start, limit, halfSelector, otFont.gdef);  // half
      limit = otFont.gsub.applyLookups (gsubLookups [5],  run, start, limit, everywhere,   otFont.gdef);  // vatu
      limit = otFont.gsub.applyLookups (gsubLookups [6],  run, start, limit, everywhere,   otFont.gdef);  // pres
      limit = otFont.gsub.applyLookups (gsubLookups [7],  run, start, limit, everywhere,   otFont.gdef);  // abvs
      limit = otFont.gsub.applyLookups (gsubLookups [8],  run, start, limit, everywhere,   otFont.gdef);  // blws
      limit = otFont.gsub.applyLookups (gsubLookups [9],  run, start, limit, everywhere,   otFont.gdef);  // psts
      limit = otFont.gsub.applyLookups (gsubLookups [10], run, start, limit, everywhere,   otFont.gdef);  } // haln
    
    // Position the glyphs
    posFromAdvanceWidth (run, otFont, start, limit);
    
    if (otFont.gpos != null) {
      int[][] gposLookups = LookupsCache.resolveFeatureTag (otFont.gpos, scriptTag, langTag, gposFeatures);
      limit = otFont.gpos.applyLookups (gposLookups [0], run, start, limit, everywhere, otFont.gdef);  // abvm
      limit = otFont.gpos.applyLookups (gposLookups [1], run, start, limit, everywhere, otFont.gdef);  // blwm
      limit = otFont.gpos.applyLookups (gposLookups [2], run, start, limit, everywhere, otFont.gdef);  } // dist
    
    return limit;
  }
  
  private static final OTSelector everywhere = new OTSelector ();
  
  private static final OTSelector akhnSelector = new OTSelector () {
    public boolean isApplied (AttributedRun run, int position) {
      Object v = run.getElementStyle (position, indicShape);
      return (   v != Shape.explicitVirama 
              && v != Shape.half);
    }
  };
  
  private static final OTSelector rphfSelector = new OTSelector () {
    public boolean isApplied (AttributedRun run, int position) {
      Object v = run.getElementStyle (position, indicShape);
      return (   v == Shape.rephCons
              || v == Shape.rephVowel);
    }
  };

  private static final OTSelector blwfSelector = new OTSelectorOnElementAttribute (indicShape, Shape.subjoins, true);
  private static final OTSelector pstfSelector = new OTSelectorOnElementAttribute (indicShape, Shape.postjoins, true);

  private static final OTSelector halfSelector = new OTSelector () {
    public boolean isApplied (AttributedRun run, int position) {
      Object v = run.getElementStyle (position, indicShape);
      return v != Shape.explicitVirama;
    }
  };

  static final int[] gsubFeatures = {
      Tag.feature_nukt, 
      Tag.feature_akhn, 
      Tag.feature_rphf, 
      Tag.feature_blwf,
      Tag.feature_half, 
      Tag.feature_vatu,
      Tag.feature_pres,
      Tag.feature_abvs,
      Tag.feature_blws,
      Tag.feature_psts,
      Tag.feature_haln,
      Tag.feature_pstf};

  static final int[] gposFeatures = {
      Tag.feature_abvm,
      Tag.feature_blwm, 
      Tag.feature_dist};
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy