2525package org .openscience .cdk .renderer .generators .standard ;
2626
2727import org .openscience .cdk .CDKConstants ;
28- import org .openscience .cdk .geometry .GeometryUtil ;
2928import org .openscience .cdk .interfaces .IAtom ;
3029import org .openscience .cdk .interfaces .IAtomContainer ;
3130import org .openscience .cdk .interfaces .IBond ;
3231import org .openscience .cdk .interfaces .IChemObject ;
3332import org .openscience .cdk .interfaces .IChemObjectBuilder ;
3433import org .openscience .cdk .interfaces .IPseudoAtom ;
34+ import org .openscience .cdk .interfaces .IStereoElement ;
3535import org .openscience .cdk .renderer .RendererModel ;
3636import org .openscience .cdk .renderer .SymbolVisibility ;
3737import org .openscience .cdk .renderer .color .IAtomColorer ;
5252
5353import javax .vecmath .Point2d ;
5454import javax .vecmath .Vector2d ;
55- import java .awt .Color ;
56- import java .awt .Font ;
57- import java .awt .Shape ;
55+ import java .awt .*;
5856import java .awt .geom .Area ;
5957import java .awt .geom .Ellipse2D ;
6058import java .awt .geom .Point2D ;
7270 * diagram. These are generated together allowing the bonds to drawn cleanly without overlap. The
7371 * generate is heavily based on ideas documented in {@cdk.cite Brecher08} and {@cdk.cite Clark13}.
7472 *
75- *
73+ *
7674 *
7775 * Atom symbols are provided as {@link GeneralPath} outlines. This allows the depiction to be
7876 * independent of the system used to view the diagram (primarily important for vector graphic
79- * depictions). The font used to generate the diagram must be provided to the constructor.
77+ * depictions). The font used to generate the diagram must be provided to the constructor.
8078 *
8179 * Atoms and bonds can be highlighted by setting the {@link #HIGHLIGHT_COLOR}. The style of
8280 * highlight is set with the {@link Highlighting} parameter.
8381 *
84- *
82+ *
8583 *
8684 * The <a href="https://github.com/cdk/cdk/wiki/Standard-Generator">Standard Generator - CDK Wiki
8785 * page</a> provides extended details of using and configuring this generator.
@@ -221,6 +219,12 @@ public IRenderingElement generate(IAtomContainer container, RendererModel parame
221219 stroke );
222220 IRenderingElement donuts = donutGenerator .generate ();
223221
222+ String enantiomerText = determineEnantiomerText (container );
223+ if (enantiomerText == null ) {
224+ // no global 'enantiomer text' add them per atom if exists
225+ addStereoGroupAnnotations (container );
226+ }
227+
224228 AtomSymbol [] symbols = generateAtomSymbols (container , symbolRemap ,
225229 visibility , parameters ,
226230 annotations , foreground ,
@@ -308,31 +312,31 @@ public IRenderingElement generate(IAtomContainer container, RendererModel parame
308312 middleLayer .add (MarkedElement .markupAtom (symbolElements , atom ));
309313 }
310314 }
311-
315+
312316 if (style == HighlightStyle .OuterGlowWhiteEdge ) {
313317 for (int i = 0 ; i < container .getAtomCount (); i ++) {
314318 IAtom atom = container .getAtom (i );
315-
319+
316320 if (isHidden (atom ))
317321 continue ;
318-
322+
319323 Color highlight = getHighlightColor (atom , parameters );
320324 Color color = highlight != null ? highlight : coloring .getAtomColor (atom );
321325
322326 if (symbols [i ] == null ) {
323327 continue ;
324328 }
325-
329+
326330 ElementGroup symbolElements = new ElementGroup ();
327331 for (Shape shape : symbols [i ].getOutlines ()) {
328332 GeneralPath path = GeneralPath .shapeOf (shape , color );
329333 symbolElements .add (path );
330334 }
331-
335+
332336 if (highlight != null && !highlight .equals (Color .WHITE )) {
333337 backLayer .add (MarkedElement .markup (outerGlow (symbolElements , Color .WHITE , 10 *stroke , stroke ), "outerglow" ));
334338 }
335- }
339+ }
336340 }
337341
338342 // Add the Sgroups display elements to the front layer
@@ -348,9 +352,97 @@ public IRenderingElement generate(IAtomContainer container, RendererModel parame
348352 group .add (middleLayer );
349353 group .add (frontLayer );
350354
355+ // maybe move somewhere else (e.g. annotations)
356+ Bounds bounds = new Bounds ();
357+ bounds .add (group );
358+
359+ if (enantiomerText != null ) {
360+ TextOutline chiralInfo = new TextOutline (enantiomerText , font ).resize (1 / scale , -1 / scale );
361+ if (chiralInfo .getBounds ().getWidth () > bounds .width ()) {
362+ // position center right
363+ double centerY = (bounds .minY + bounds .maxY ) / 2 ;
364+ chiralInfo = chiralInfo .translate (bounds .maxX - (chiralInfo .getBounds ().getMinX () - (10 *stroke )),
365+ centerY - (chiralInfo .getBounds ().getCenterY ()));
366+ } else {
367+ // position bottom right corner
368+ chiralInfo = chiralInfo .translate (bounds .maxX - ((chiralInfo .getBounds ().getMaxX () + chiralInfo .getCenter ().getX ()) / 2 ),
369+ bounds .minY - (chiralInfo .getBounds ().getMaxY () + (5 *stroke )));
370+ }
371+ group .add (GeneralPath .shapeOf (chiralInfo .getOutline (), foreground ));
372+ }
373+
374+
351375 return MarkedElement .markupMol (group , container );
352376 }
353377
378+ /**
379+ * Adds &1 and or1 to each atom in the those stereo groups.
380+ * @param container the container
381+ */
382+ private void addStereoGroupAnnotations (IAtomContainer container ) {
383+ for (IStereoElement <?,?> se : container .stereoElements ()) {
384+ // tetrahedral only
385+ if (se .getConfigClass () == IStereoElement .TH ) {
386+ int groupInfo = se .getGroupInfo ();
387+ if (groupInfo != 0 ) {
388+
389+ // generate the label &1, or1
390+ String label = null ;
391+ switch ((groupInfo & IStereoElement .GRP_TYPE_MASK )) {
392+ case IStereoElement .GRP_AND :
393+ label = "&" ;
394+ break ;
395+ case IStereoElement .GRP_OR :
396+ label = "or" ;
397+ break ;
398+ case IStereoElement .GRP_ABS :
399+ continue ;
400+ }
401+ label += Integer .toString (groupInfo >> IStereoElement .GRP_NUM_SHIFT );
402+
403+ // attach it to the central atom
404+ IAtom focus = (IAtom ) se .getFocus ();
405+ String annotation = focus .getProperty (StandardGenerator .ANNOTATION_LABEL );
406+ if (annotation == null )
407+ annotation = label ;
408+ else
409+ annotation += ";" + label ;
410+ focus .setProperty (StandardGenerator .ANNOTATION_LABEL ,
411+ annotation );
412+ }
413+ }
414+ }
415+ }
416+
417+ /**
418+ * Determines the text to enantiomer text to display next to the structure, "and enantiomer" for racemic mixtures
419+ * or "or enantiomer" for relative stereochemistry. These are generated if all grouped stereo is in the same group,
420+ * i.e. all &1, all or1, all &2 etc.
421+ *
422+ * @param container the molecule
423+ * @return the text
424+ */
425+ private String determineEnantiomerText (IAtomContainer container ) {
426+ int ref_grp = 0 ;
427+ for (IStereoElement <?, ?> elem : container .stereoElements ()) {
428+ if (elem .getConfigClass () == IStereoElement .TH ) {
429+ if (elem .getGroupInfo () == 0 )
430+ return null ;
431+ if (ref_grp == 0 )
432+ ref_grp = elem .getGroupInfo ();
433+ else if (ref_grp != elem .getGroupInfo ())
434+ return null ;
435+ }
436+ }
437+ switch ((ref_grp & IStereoElement .GRP_TYPE_MASK )) {
438+ case IStereoElement .GRP_AND :
439+ return "and enantiomer" ;
440+ case IStereoElement .GRP_OR :
441+ return "or enantiomer" ;
442+ }
443+ return null ;
444+ }
445+
354446 private Color getColorOfAtom (Map <IAtom , String > symbolRemap , IAtomColorer coloring , Color foreground ,
355447 HighlightStyle style , IAtom atom , Color highlight ) {
356448 // atom is highlighted...?
0 commit comments