classpath
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: extended peers


From: graydon hoare
Subject: Re: extended peers
Date: 17 Sep 2003 11:34:26 -0400
User-agent: Gnus/5.09 (Gnus v5.9.0) Emacs/21.2

Sascha Brawer <address@hidden> writes:

> Would you say that Font.createFont and Font.deriveFont should be fully
> implemented by java.awt.Font, so that they would construct the new Font
> without calling the Toolkit? I'd agree with this, although it makes
> things even more complicated.

the situation is somewhat less complicated than it looks; the general
problem is just that there are 3 distinct routes to arriving at a new
font:

(1) Font constructors

        must be implemented in Font, since can't change identity of
        the object being constructed.

(2) Font.createFont, Font.decode, Font.getFont

        static, can be implemented in Font or Toolkit (or a mixture,
        since decode and some createFont variants are trivially
        translated to getFont(Map) calls).

(3) Font.deriveFont

        method of Font, can be implemented in Font, or Toolkit, or
        ClasspathFontPeer (since deriveFont is a method of an object
        which already *has* a peer)

my inclination is to call peer-returning factory methods on the
Toolkit in case (1), call Font-returning factory methods on the
Toolkit in case (2), and to call a method *on the peer* in case (3).

> If the toolkit produces ClasspathFontPeers instead of java.awt.Fonts, the
> implementations of java.awt.Font.createFont and deriveFont somehow need
> to know whether the constructed Font should implement the
> java.awt.font.[OpenType, MultipleMaster] interfaces. 

I think it is again instructive to look at these 3 cases
separately. in case (1), there is no possibility for the returned Font
to implement OpenType or MultipleMaster, because the user is asking
for a specific class (Font) and that class has a known signature in
java.awt which does not include 'implements OpenType' or 'implements
MultipleMaster'. So we can ignore this case.

in case (2), the toolkit can return a Font subclass implementing
OpenType or MultipleMaster as it likes, possibly never, but it's up to
the Toolkit. I think this is right since some toolkits will probably
never support MultipleMaster.

in case (3), the font already *knows* which subclass it is (and
whether or not it implements MultipleMaster or OpenType) so can have a
hard-coded means to derive a new peer (of the appropriate peer
subclass) from its existing peer.

> What do you think about the following proposal, which puts a special
> key for "pre-created peers" into the attribute Map?

mm, seems a little odd. I don't understand exactly why you've done
this, nor whether it fully solves the problem. 

> This is terribly complicated. But do you see an easier way that
> would allow createFont and deriveFont to return a Font that
> implements a certain interface, such as java.awt.font.OpenType?

Yes, I think it can be done by arranging for override opportunities at
key points in the call chain. here is the collection of classes that I
propose (and will happily implement in their entirety):


public abstract class ClasspathToolkit
{
  public Font getFont (Map attrs) 
  {

    // note: you can override this method if you want to 
    // return an object which implements OpenType or 
    // MultipleMaster

    return new Font (attrs);
  }

  // no default implementation for these two
  public abstract Font getFont (int fontFormat, InputStream is);
  public abstract ClasspathFontPeer getPeer (Map attrs);  
}

public abstract class ClasspathFontPeer
{
  // derivation methods
  public Font deriveFont (float size)

    // note: you can override this method in a peer which
    // wishes to derive fonts in some other fashion.

    Map newAttrs = new HashMap (attrs);
    newAttrs.put (TextAttribute.SIZE, new Float(size));
    return Font.getFont (newAttrs);
  }
  // ... other default derivation policies are similar

  // accessors for peer font state
  public abstract String getFamilyName ();
  public abstract float getItalicAngle ();
  public abstract LineMetrics getLineMetrics (...);
  // ...
}

public class Font
{
  protected ClasspathFontPeer peer;

  public Font (Map attributes)
  {
    this.peer = ((ClasspathToolkit) Toolkit.getDefaultToolkit())
                .getPeer(attributes);
  }

  public static Font getFont (Map attrs)
  {
    return ((ClasspathToolkit) Toolkit.getDefaultToolkit())
           .getFont(attrs);    
  }

  public static Font getFont (String property)
  {
    // implement by translation to Toolkit.getFont() via decode
    String propval = System.getProperty (propname);    
    if (propval != null)
      return decode (propval);
    else
      return null;
  }

  public static Font createFont (int fontFormat, InputStream is) 
  {
    return ((ClasspathToolkit) Toolkit.getDefaultToolkit())
           .getFont(fontFormat, is);
  }

  public static Font decode (string fontspec)
  {
    // implement by translation to Toolkit.getFont() via decode
    Map attrs = new HashMap ();
    // decode fontspec into attrs; I've already got code to do this
    return getFont (attrs);
  }

  public Font deriveFont (float size)
  {
    return peer.deriveFont (size);
  }            
  // ...

  // normal methods delegate to peer
  public String getFamilyName ()
  {
    return peer.getFamilyName ();
  }
}

public class GdkToolkit 
{
  public Font getFont (int fontFormat, InputStream is)
  {
    // GdkToolkit doesn't know how to do this, sorry
    throw new UnsupportedOperationException ();
  }

  public ClasspathFontPeer getPeer (Map attrs)
  {    
    return new GdkFontPeer (attrs);
  }
}

public class GdkFontPeer extends ClasspathFontPeer
{
  // peer state
  private Map attrs;
  private String familyName;
  private String faceName;
  private float size;
  // ...

  public GdkFontPeer (Map attrs)
  {
    // decode attrs
    init(familyName, faceName, size);        
  }

  public void finalize()
  {
    dispose ();
  }

  // derivation methods
  // ...

  // state accessors, used as delegates from Font
  public String getFamilyName ()
  {
    return familyName;
  }
  // ...  

  // remainder is native interface to pango
  static 
  {
    if (Configuration.INIT_LOAD_LIBRARY)
      {
        System.loadLibrary("gtkpeer");
      }
    initStaticState ();
  }
  private native static void initStaticState ();
  private final int native_state = GtkGenericPeer.getUniqueInteger ();
  private native init (String familyName, 
                       String faceName, 
                       float size);
  private native dispose ();
  // ...
}


> I thought it would not hurt to allow that a single font peer is shared
> between scaled/transformed fonts.  On the other hand, if you think that
> passing the Font to the peer's methods is bad, I wouldn't have a problem
> to omit that argument.

the benefit of this approach is that it places all the decisions about
a font's behavior purely in the realm of the specific Toolkit and Peer
subclasses, along with any choices you make about state representation. 
the Font has no policy of its own; you store all state in the peer,
where all policy is. imo it improves flexibility quite a bit.

> public class Font
> {
>   protected ClasspathFontPeer peer;
> 
>   public float getItalicAngle()
>   {
>     // float ClasspathFontPeer.getItalicAngle(java.awt.Font)
>     return peer.getItalicAngle(this);
>   }
> }
> 
> Would you mind explaining where exactly the cast is performed?

well, how are you going to implement peer.getItalicAngle(), if the
italic angle state is stored in the Font rather than the peer? 


  public class GdkFontPeer extends ClasspathFontPeer
  {
    public float getItalicAngle (Font f)
    {
      // XXX can't do this, it causes infintie recursion
      return f.getItalicAngle (); 

      // how about this:
      if (f instanceof GdkFont)
      {
         GdkFont gf = (GdkFont)f;
         return gf.getGdkItalicAngle ();
      } 
      else
        throw new UnsupportedOperationException ();
    }
  }


I don't mean this facetiously; it's the exact problem I ran into when
I copied the classes out of your email and started implementing them.
unless the Font is just used as a key into some sort of Hashtable
stored in the FontPeer, these methods all require some kind of
downcast, or some other way of "digging back into the Font", to get at
its state in a way which doesn't wind up calling the peer back again.

> If we let ClasspathToolkit return ClasspathFontPeers instead of
> Fonts, as you suggested, a singleton-peer-for-all-fonts variant
> wouldn't work.

well, you can still do a singleton, of sorts: just split the peer in
two pieces, one which holds whatever amount of shared state you have
(kept in a shared hashtable) and one which holds whatever amount of
non-shared state you have (say, transformation and scale). I feel that
this is an implementation issue for the toolkit and peer to address; I
don't want to force a particular arrangement of splitting in the
interface between Font and peer.

-graydon





reply via email to

[Prev in Thread] Current Thread [Next in Thread]