2323
2424package org .openscience .cdk .renderer .generators .standard ;
2525
26+ import com .google .common .collect .HashMultimap ;
27+ import com .google .common .collect .Multimap ;
2628import org .openscience .cdk .CDKConstants ;
2729import org .openscience .cdk .geometry .GeometryUtil ;
2830import org .openscience .cdk .interfaces .IAtom ;
2931import org .openscience .cdk .interfaces .IAtomContainer ;
3032import org .openscience .cdk .interfaces .IBond ;
3133import org .openscience .cdk .renderer .RendererModel ;
34+ import org .openscience .cdk .renderer .elements .Bounds ;
3235import org .openscience .cdk .renderer .elements .ElementGroup ;
3336import org .openscience .cdk .renderer .elements .GeneralPath ;
3437import org .openscience .cdk .renderer .elements .IRenderingElement ;
4851import java .awt .geom .Path2D ;
4952import java .awt .geom .Point2D ;
5053import java .awt .geom .Rectangle2D ;
54+ import java .util .ArrayDeque ;
55+ import java .util .ArrayList ;
5156import java .util .Collection ;
5257import java .util .Collections ;
58+ import java .util .Comparator ;
59+ import java .util .Deque ;
5360import java .util .HashMap ;
5461import java .util .List ;
5562import java .util .Locale ;
@@ -71,6 +78,8 @@ final class StandardSgroupGenerator {
7178 private final double labelScale ;
7279 private final StandardAtomGenerator atomGenerator ;
7380 private final RendererModel parameters ;
81+ private final Multimap <Sgroup ,Sgroup > children = HashMultimap .create ();
82+ private final Map <Sgroup ,Bounds > boundsMap = new HashMap <>();
7483
7584 private StandardSgroupGenerator (RendererModel parameters , StandardAtomGenerator atomGenerator , double stroke ,
7685 Font font , Color foreground ) {
@@ -274,6 +283,17 @@ private static void contractAbbreviation(IAtomContainer container, Map<IAtom, St
274283 }
275284 }
276285
286+ int getTotalChildCount (Multimap <Sgroup ,Sgroup > map , Sgroup key ) {
287+ int count = 0 ;
288+ Deque <Sgroup > deque = new ArrayDeque <>(map .get (key ));
289+ while (!deque .isEmpty ()) {
290+ Sgroup sgroup = deque .poll ();
291+ deque .addAll (map .get (sgroup ));
292+ ++count ;
293+ }
294+ return count ;
295+ }
296+
277297 /**
278298 * Generate the Sgroup elements for the provided atom contains.
279299 *
@@ -293,6 +313,21 @@ IRenderingElement generateSgroups(IAtomContainer container, AtomSymbol[] symbols
293313 if (symbols [i ] != null )
294314 symbolMap .put (container .getAtom (i ), symbols [i ]);
295315 }
316+ for (Sgroup sgroup : sgroups ) {
317+ for (Sgroup parent : sgroup .getParents ()) {
318+ children .put (parent , sgroup );
319+ }
320+ }
321+
322+ // generate child brackets first
323+ sgroups = new ArrayList <>(sgroups );
324+ Collections .sort (sgroups , new Comparator <Sgroup >() {
325+ @ Override
326+ public int compare (Sgroup o1 , Sgroup o2 ) {
327+ return Integer .compare (getTotalChildCount (children , o1 ),
328+ getTotalChildCount (children , o2 ));
329+ }
330+ });
296331
297332 for (Sgroup sgroup : sgroups ) {
298333
@@ -316,7 +351,7 @@ IRenderingElement generateSgroups(IAtomContainer container, AtomSymbol[] symbols
316351 case CtabComponent :
317352 case CtabMixture :
318353 case CtabFormulation :
319- result .add (generateMixtureSgroup (sgroup ));
354+ result .add (generateMixtureSgroup (sgroup , sgroups , symbolMap ));
320355 break ;
321356 case CtabGeneric :
322357 // not strictly a polymer but okay to draw as one
@@ -484,7 +519,9 @@ else if ("ALT".equals(subtype))
484519 }
485520 }
486521
487- private IRenderingElement generateMixtureSgroup (Sgroup sgroup ) {
522+ private IRenderingElement generateMixtureSgroup (Sgroup sgroup ,
523+ List <Sgroup > sgroups ,
524+ Map <IAtom ,AtomSymbol > symbolMap ) {
488525 // draw the brackets
489526 // TODO - mixtures normally have attached Sgroup data
490527 // TODO - e.g. COMPONENT_FRACTION, ACTIVITY_TYPE, WEIGHT_PERCENT
@@ -509,11 +546,27 @@ private IRenderingElement generateMixtureSgroup(Sgroup sgroup) {
509546 break ;
510547 }
511548
549+ String superscript = null ;
550+ for (Sgroup child : sgroups ) {
551+ if (child .getType () == SgroupType .CtabData &&
552+ child .getParents ().contains (sgroup )) {
553+ String value = child .getValue (SgroupKey .Data );
554+ String units = child .getValue (SgroupKey .DataFieldUnits );
555+ if (value != null ) {
556+ superscript = value ;
557+ if (units != null )
558+ superscript += units ;
559+ }
560+ break ;
561+ }
562+ }
563+
512564 return generateSgroupBrackets (sgroup ,
513565 brackets ,
514- null ,
566+ symbolMap ,
515567 subscript ,
516- null );
568+ null ,
569+ superscript );
517570 } else {
518571 return new ElementGroup ();
519572 }
@@ -539,6 +592,15 @@ private IRenderingElement generateSgroupBrackets(Sgroup sgroup,
539592 Map <IAtom , AtomSymbol > symbols ,
540593 String subscriptSuffix ,
541594 String superscriptSuffix ) {
595+ return generateSgroupBrackets (sgroup , brackets , symbols , subscriptSuffix , superscriptSuffix , null );
596+ }
597+
598+ private IRenderingElement generateSgroupBrackets (Sgroup sgroup ,
599+ List <SgroupBracket > brackets ,
600+ Map <IAtom , AtomSymbol > symbols ,
601+ String subscriptSuffix ,
602+ String superscriptSuffix ,
603+ String superscriptPrefix ) {
542604
543605 // brackets are square by default (style:0)
544606 Integer style = sgroup .getValue (SgroupKey .CtabBracketStyle );
@@ -561,7 +623,9 @@ private IRenderingElement generateSgroupBrackets(Sgroup sgroup,
561623 : Collections .<SgroupBracket , IBond >emptyMap ();
562624
563625 // override bracket layout around single atoms to bring them in closer
564- if (atoms .size () == 1 ) {
626+ if (atoms .size () == 1 &&
627+ (sgroup .getType () == SgroupType .CtabStructureRepeatUnit ||
628+ sgroup .getType () == SgroupType .CtabMultipleGroup )) {
565629
566630 IAtom atom = atoms .iterator ().next ();
567631
@@ -624,6 +688,7 @@ else if (crossingBonds.size()>0) {
624688 result .add (GeneralPath .shapeOf (leftBracket .getOutline (), foreground ));
625689 result .add (GeneralPath .shapeOf (rightBracket .getOutline (), foreground ));
626690
691+ Rectangle2D leftBracketBounds = leftBracket .getBounds ();
627692 Rectangle2D rightBracketBounds = rightBracket .getBounds ();
628693
629694 // subscript/superscript suffix annotation
@@ -643,6 +708,14 @@ else if (crossingBonds.size()>0) {
643708 scriptscale ));
644709 result .add (GeneralPath .shapeOf (superscriptOutline .getOutline (), foreground ));
645710 }
711+ if (superscriptPrefix != null && !superscriptPrefix .isEmpty ()) {
712+ TextOutline superscriptOutline = rightAlign (makeText (superscriptPrefix .toLowerCase (Locale .ROOT ),
713+ new Point2d (leftBracketBounds .getMaxX (),
714+ leftBracketBounds .getMaxY () + 0.1 ),
715+ new Vector2d (-leftBracketBounds .getWidth (), 0 ),
716+ scriptscale ));
717+ result .add (GeneralPath .shapeOf (superscriptOutline .getOutline (), foreground ));
718+ }
646719 }
647720 } else if (!pairs .isEmpty ()) {
648721
@@ -725,14 +798,40 @@ else if (crossingBonds.size()>0) {
725798 supSufPnt , suffixBracketPerp , labelScale ));
726799 result .add (GeneralPath .shapeOf (superscriptOutline .getOutline (), foreground ));
727800 }
728-
729801 }
730- } else if (brackets .size () == 2 ) {
802+ }
803+ // no crossing-bonds we can shrink things down
804+ else if (brackets .size () == 2 ) {
805+
806+ Bounds bounds = new Bounds ();
807+ for (IAtom atom : sgroup .getAtoms ()) {
808+ AtomSymbol atomSymbol = symbols .get (atom );
809+ if (atomSymbol != null ) {
810+ ConvexHull hull = atomSymbol .getConvexHull ();
811+ Rectangle2D bounds2D = hull .outline ().getBounds2D ();
812+ bounds .add (bounds2D .getMinX (), bounds2D .getMinY ());
813+ bounds .add (bounds2D .getMaxX (), bounds2D .getMaxY ());
814+ } else {
815+ bounds .add (atom .getPoint2d ().x , atom .getPoint2d ().y );
816+ }
817+ }
818+ for (Sgroup child : children .get (sgroup )) {
819+ Bounds childBounds = boundsMap .get (child );
820+ if (childBounds != null )
821+ bounds .add (childBounds );
822+ }
823+
824+ Point2d b1p1 = brackets .get (0 ).getFirstPoint ();
825+ Point2d b1p2 = brackets .get (0 ).getSecondPoint ();
826+ Point2d b2p1 = brackets .get (1 ).getFirstPoint ();
827+ Point2d b2p2 = brackets .get (1 ).getSecondPoint ();
828+
829+ double margin = 5 *(parameters .get (BasicSceneGenerator .Margin .class )/scale );
731830
732- final Point2d b1p1 = brackets . get ( 0 ). getFirstPoint ( );
733- final Point2d b1p2 = brackets . get ( 0 ). getSecondPoint ( );
734- final Point2d b2p1 = brackets . get ( 1 ). getFirstPoint ( );
735- final Point2d b2p2 = brackets . get ( 1 ). getSecondPoint ( );
831+ b1p1 = new Point2d ( bounds . minX + margin , bounds . minY + margin );
832+ b1p2 = new Point2d ( bounds . minX + margin , bounds . maxY - margin );
833+ b2p1 = new Point2d ( bounds . maxX - margin , bounds . minY + margin );
834+ b2p2 = new Point2d ( bounds . maxX - margin , bounds . maxY - margin );
736835
737836 final Vector2d b1vec = VecmathUtil .newUnitVector (b1p1 , b1p2 );
738837 final Vector2d b2vec = VecmathUtil .newUnitVector (b2p1 , b2p2 );
@@ -795,8 +894,9 @@ else if (crossingBonds.size()>0) {
795894 double b1MaxY = Math .max (b1p1 .y , b1p2 .y );
796895 double b2MaxY = Math .max (b2p1 .y , b2p2 .y );
797896
798- Point2d subSufPnt = b2p2 ;
799- Point2d supSufPnt = b2p1 ;
897+ Point2d subSufPnt = b2p2 ;
898+ Point2d supSufPnt = b2p1 ;
899+ Point2d supPrefPnt = b1p2 ;
800900 Vector2d subpvec = b2pvec ;
801901
802902 double bXDiff = b1MaxX - b2MaxX ;
@@ -828,8 +928,19 @@ else if (crossingBonds.size()>0) {
828928 supSufPnt , subpvec , labelScale ));
829929 result .add (GeneralPath .shapeOf (superscriptOutline .getOutline (), foreground ));
830930 }
931+ if (superscriptPrefix != null && !superscriptPrefix .isEmpty ()) {
932+ subpvec .negate ();
933+ TextOutline superscriptOutline = rightAlign (makeText (superscriptPrefix .toLowerCase (Locale .ROOT ),
934+ supPrefPnt , subpvec , labelScale ));
935+ result .add (GeneralPath .shapeOf (superscriptOutline .getOutline (), foreground ));
936+ }
831937
832938 }
939+
940+ Bounds bounds = new Bounds ();
941+ bounds .add (result );
942+ boundsMap .put (sgroup , bounds );
943+
833944 return result ;
834945 }
835946
@@ -894,4 +1005,11 @@ private TextOutline leftAlign(TextOutline outline) {
8941005 return outline .translate (center .getX () - first .getX (),
8951006 center .getY () - first .getY ());
8961007 }
1008+
1009+ private TextOutline rightAlign (TextOutline outline ) {
1010+ Point2D center = outline .getCenter ();
1011+ Point2D last = outline .getLastGlyphCenter ();
1012+ return outline .translate (center .getX () - last .getX (),
1013+ center .getY () - last .getY ());
1014+ }
8971015}
0 commit comments