private IRenderingElement generateMixtureSgroup(Sgroup sgroup) { // draw the brackets // TODO - mixtures normally have attached Sgroup data // TODO - e.g. COMPONENT_FRACTION, ACTIVITY_TYPE, WEIGHT_PERCENT List<SgroupBracket> brackets = sgroup.getValue(SgroupKey.CtabBracket); if (brackets != null) { SgroupType type = sgroup.getType(); String subscript = "?"; switch (type) { case CtabComponent: Integer compNum = sgroup.getValue(SgroupKey.CtabComponentNumber); if (compNum != null) subscript = "c" + Integer.toString(compNum); else subscript = "c"; break; case CtabMixture: subscript = "mix"; break; case CtabFormulation: subscript = "f"; break; } return generateSgroupBrackets(sgroup, brackets, subscript, null); } else { return new ElementGroup(); } }
/** * Hide the atoms and bonds of a contracted abbreviation. If the abbreviations is attached we * remap the attachment symbol to display the name. If there are no attachments the symbol we be * added later ({@see #generateSgroups}). * * @param container molecule * @param sgroup abbreviation group display shortcut */ private static void contractAbbreviation( IAtomContainer container, Map<IAtom, String> symbolRemap, Sgroup sgroup) { final Set<IBond> crossing = sgroup.getBonds(); final Set<IAtom> atoms = sgroup.getAtoms(); // only do 0,1 attachments for now if (crossing.size() > 1) return; for (IAtom atom : atoms) { StandardGenerator.hide(atom); } for (IBond bond : container.bonds()) { if (atoms.contains(bond.getAtom(0)) || atoms.contains(bond.getAtom(1))) StandardGenerator.hide(bond); } for (IBond bond : crossing) { StandardGenerator.unhide(bond); IAtom a1 = bond.getAtom(0); IAtom a2 = bond.getAtom(1); StandardGenerator.unhide(a1); if (atoms.contains(a1)) symbolRemap.put(a1, sgroup.getSubscript()); StandardGenerator.unhide(a2); if (atoms.contains(a2)) symbolRemap.put(a2, sgroup.getSubscript()); } }
private IRenderingElement generateMultipleSgroup(Sgroup sgroup) { // just draw the brackets - multiplied group parts have already been hidden in prep phase List<SgroupBracket> brackets = sgroup.getValue(SgroupKey.CtabBracket); if (brackets != null) { return generateSgroupBrackets( sgroup, brackets, (String) sgroup.getValue(SgroupKey.CtabSubScript), null); } else { return new ElementGroup(); } }
/** * Generates polymer Sgroup elements. * * @param sgroup the Sgroup * @return the rendered elements (empty if no brackets defined) */ private IRenderingElement generatePolymerSgroup(Sgroup sgroup) { // draw the brackets List<SgroupBracket> brackets = sgroup.getValue(SgroupKey.CtabBracket); if (brackets != null) { SgroupType type = sgroup.getType(); String subscript = sgroup.getValue(SgroupKey.CtabSubScript); String connectivity = sgroup.getValue(SgroupKey.CtabConnectivity); switch (type) { case CtabCopolymer: subscript = "co"; String subtype = sgroup.getValue(SgroupKey.CtabSubType); if ("RAN".equals(subtype)) subscript = "ran"; else if ("BLK".equals(subtype)) subscript = "blk"; else if ("ALT".equals(subtype)) subscript = "alt"; break; case CtabCrossLink: subscript = "xl"; break; case CtabAnyPolymer: subscript = "any"; break; case CtabGraft: subscript = "grf"; break; case CtabMer: subscript = "mer"; break; case CtabMonomer: subscript = "mon"; break; case CtabModified: subscript = "mod"; break; case CtabStructureRepeatUnit: if (subscript == null) subscript = "n"; if (connectivity == null) connectivity = "eu"; break; } return generateSgroupBrackets(sgroup, brackets, subscript, connectivity); } else { return new ElementGroup(); } }
private IRenderingElement generateAbbreviationSgroup(Sgroup sgroup) { String label = sgroup.getSubscript(); // already handled by symbol remapping if (sgroup.getBonds().size() > 0 || label == null || label.isEmpty()) { return new ElementGroup(); } // we're showing a label where there were no atoms before, we put it in the // middle of all of those which were hidden final Point2d labelCoords = GeometryUtil.get2DCenter(sgroup.getAtoms()); ElementGroup group = new ElementGroup(); for (Shape outline : atomGenerator .generatePseudoSymbol(label, HydrogenPosition.Right) .resize(1 / scale, 1 / -scale) .getOutlines()) group.add(GeneralPath.shapeOf(outline, foreground)); return MarkedElement.markupAtom(group, null); }
/** * Hide the repeated atoms and bonds of a multiple group. We hide al atoms that belong to the * group unless they are defined in the parent atom list. Any bond to those atoms that is not a * crossing bond or one connecting atoms in the parent list is hidden. * * @param container molecule * @param sgroup multiple group display shortcut */ private static void hideMultipleParts(IAtomContainer container, Sgroup sgroup) { final Set<IBond> crossing = sgroup.getBonds(); final Set<IAtom> atoms = sgroup.getAtoms(); final Set<IAtom> parentAtoms = sgroup.getValue(SgroupKey.CtabParentAtomList); for (IBond bond : container.bonds()) { if (parentAtoms.contains(bond.getAtom(0)) && parentAtoms.contains(bond.getAtom(1))) continue; if (atoms.contains(bond.getAtom(0)) || atoms.contains(bond.getAtom(1))) StandardGenerator.hide(bond); } for (IAtom atom : atoms) { if (!parentAtoms.contains(atom)) StandardGenerator.hide(atom); } for (IBond bond : crossing) { StandardGenerator.unhide(bond); } }
/** * Generate the Sgroup elements for the provided atom contains. * * @param container molecule * @return Sgroup rendering elements */ IRenderingElement generateSgroups(IAtomContainer container) { ElementGroup result = new ElementGroup(); List<Sgroup> sgroups = container.getProperty(CDKConstants.CTAB_SGROUPS); if (sgroups == null || sgroups.isEmpty()) return result; for (Sgroup sgroup : sgroups) { switch (sgroup.getType()) { case CtabAbbreviation: result.add(generateAbbreviationSgroup(sgroup)); break; case CtabMultipleGroup: result.add(generateMultipleSgroup(sgroup)); break; case CtabAnyPolymer: case CtabMonomer: case CtabCrossLink: case CtabCopolymer: case CtabStructureRepeatUnit: case CtabMer: case CtabGraft: case CtabModified: result.add(generatePolymerSgroup(sgroup)); break; case CtabComponent: case CtabMixture: case CtabFormulation: result.add(generateMixtureSgroup(sgroup)); break; case CtabGeneric: // not strictly a polymer but okay to draw as one result.add(generatePolymerSgroup(sgroup)); break; } } return result; }
/** * If the molecule has display shortcuts (abbreviations or multiple group sgroups) certain parts * of the structure are hidden from display. This method marks the parts to hide and in the case * of abbreviations, remaps atom symbols. Appart from additional property flags, the molecule is * unchanged by this method. * * @param container molecule input * @param symbolRemap a map that will hold symbol remapping */ static void prepareDisplayShortcuts(IAtomContainer container, Map<IAtom, String> symbolRemap) { List<Sgroup> sgroups = container.getProperty(CDKConstants.CTAB_SGROUPS); if (sgroups == null || sgroups.isEmpty()) return; // select abbreviations that should be contracted for (Sgroup sgroup : sgroups) { if (sgroup.getType() == SgroupType.CtabAbbreviation) { Boolean expansion = sgroup.getValue(SgroupKey.CtabExpansion); // abbreviation is displayed as expanded if (expansion != null && expansion == Boolean.TRUE) continue; // no or empty label, skip it if (sgroup.getSubscript() == null || sgroup.getSubscript().isEmpty()) continue; contractAbbreviation(container, symbolRemap, sgroup); } else if (sgroup.getType() == SgroupType.CtabMultipleGroup) { hideMultipleParts(container, sgroup); } } }
private IRenderingElement generateSgroupBrackets( Sgroup sgroup, List<SgroupBracket> brackets, String subscriptSuffix, String superscriptSuffix) { // brackets are square by default (style:0) Integer style = sgroup.getValue(SgroupKey.CtabBracketStyle); boolean round = style != null && style == 1; ElementGroup result = new ElementGroup(); Set<IAtom> atoms = sgroup.getAtoms(); Set<IBond> crossingBonds = sgroup.getBonds(); // easy to depict in correct orientation, we just // point each bracket at the atom of a crossing // bond that is 'in' the group - this scales // to more than two brackets // first we need to pair the brackets with the bonds Map<SgroupBracket, IBond> pairs = crossingBonds.size() == brackets.size() ? bracketBondPairs(brackets, crossingBonds) : Collections.<SgroupBracket, IBond>emptyMap(); if (!pairs.isEmpty()) { SgroupBracket suffixBracket = null; Vector2d suffixBracketPerp = null; for (Map.Entry<SgroupBracket, IBond> e : pairs.entrySet()) { final SgroupBracket bracket = e.getKey(); final IBond bond = e.getValue(); final IAtom inGroupAtom = atoms.contains(bond.getAtom(0)) ? bond.getAtom(0) : bond.getAtom(1); final Point2d p1 = bracket.getFirstPoint(); final Point2d p2 = bracket.getSecondPoint(); final Vector2d perp = VecmathUtil.newPerpendicularVector(VecmathUtil.newUnitVector(p1, p2)); // point the vector at the atom group Point2d midpoint = VecmathUtil.midpoint(p1, p2); if (perp.dot(VecmathUtil.newUnitVector(midpoint, inGroupAtom.getPoint2d())) < 0) { perp.negate(); } perp.scale(bracketDepth); Path2D path = new Path2D.Double(); if (round) { // bracket 1 (cp: control point) path.moveTo(p1.x + perp.x, p1.y + perp.y); Point2d cpb1 = new Point2d(midpoint); cpb1.add(VecmathUtil.negate(perp)); path.quadTo(cpb1.x, cpb1.y, p2.x + perp.x, p2.y + p2.y); } else { path.moveTo(p1.x + perp.x, p1.y + perp.y); path.lineTo(p1.x, p1.y); path.lineTo(p2.x, p2.y); path.lineTo(p2.x + perp.x, p2.y + perp.y); } result.add(GeneralPath.outlineOf(path, stroke, foreground)); if (suffixBracket == null) { suffixBracket = bracket; suffixBracketPerp = perp; } else { // is this bracket better as a suffix? Point2d sp1 = suffixBracket.getFirstPoint(); Point2d sp2 = suffixBracket.getSecondPoint(); double bestMaxX = Math.max(sp1.x, sp2.x); double thisMaxX = Math.max(p1.x, p2.x); double bestMaxY = Math.max(sp1.y, sp2.y); double thisMaxY = Math.max(p1.y, p2.y); // choose the most eastern or.. the most southern double xDiff = thisMaxX - bestMaxX; double yDiff = thisMaxY - bestMaxY; if (xDiff > EQUIV_THRESHOLD || (xDiff > -EQUIV_THRESHOLD && yDiff < -EQUIV_THRESHOLD)) { suffixBracket = bracket; suffixBracketPerp = perp; } } } // write the labels if (suffixBracket != null) { Point2d subSufPnt = suffixBracket.getFirstPoint(); Point2d supSufPnt = suffixBracket.getSecondPoint(); // try to put the subscript on the bottom double xDiff = subSufPnt.x - supSufPnt.x; double yDiff = subSufPnt.y - supSufPnt.y; if (yDiff > EQUIV_THRESHOLD || (yDiff > -EQUIV_THRESHOLD && xDiff > EQUIV_THRESHOLD)) { Point2d tmpP = subSufPnt; subSufPnt = supSufPnt; supSufPnt = tmpP; } // subscript/superscript suffix annotation if (subscriptSuffix != null && !subscriptSuffix.isEmpty()) { TextOutline subscriptOutline = leftAlign( makeText( subscriptSuffix.toLowerCase(Locale.ROOT), subSufPnt, suffixBracketPerp, labelScale)); result.add(GeneralPath.shapeOf(subscriptOutline.getOutline(), foreground)); } if (superscriptSuffix != null && !superscriptSuffix.isEmpty()) { TextOutline superscriptOutline = leftAlign( makeText( superscriptSuffix.toLowerCase(Locale.ROOT), supSufPnt, suffixBracketPerp, labelScale)); result.add(GeneralPath.shapeOf(superscriptOutline.getOutline(), foreground)); } } } else if (brackets.size() == 2) { final Point2d b1p1 = brackets.get(0).getFirstPoint(); final Point2d b1p2 = brackets.get(0).getSecondPoint(); final Point2d b2p1 = brackets.get(1).getFirstPoint(); final Point2d b2p2 = brackets.get(1).getSecondPoint(); final Vector2d b1vec = VecmathUtil.newUnitVector(b1p1, b1p2); final Vector2d b2vec = VecmathUtil.newUnitVector(b2p1, b2p2); final Vector2d b1pvec = VecmathUtil.newPerpendicularVector(b1vec); final Vector2d b2pvec = VecmathUtil.newPerpendicularVector(b2vec); // Point the vectors at each other if (b1pvec.dot(VecmathUtil.newUnitVector(b1p1, b2p1)) < 0) b1pvec.negate(); if (b2pvec.dot(VecmathUtil.newUnitVector(b2p1, b1p1)) < 0) b2pvec.negate(); // scale perpendicular vectors by how deep the brackets need to be b1pvec.scale(bracketDepth); b2pvec.scale(bracketDepth); // bad brackets if (Double.isNaN(b1pvec.x) || Double.isNaN(b1pvec.y) || Double.isNaN(b2pvec.x) || Double.isNaN(b2pvec.y)) return result; Path2D path = new Path2D.Double(); if (round) { // bracket 1 (cp: control point) path.moveTo(b1p1.x + b1pvec.x, b1p1.y + b1pvec.y); Point2d cpb1 = VecmathUtil.midpoint(b1p1, b1p2); cpb1.add(VecmathUtil.negate(b1pvec)); path.quadTo(cpb1.x, cpb1.y, b1p2.x + b1pvec.x, b1p2.y + b1pvec.y); // bracket 2 (cp: control point) path.moveTo(b2p1.x + b2pvec.x, b2p1.y + b2pvec.y); Point2d cpb2 = VecmathUtil.midpoint(b2p1, b2p2); cpb2.add(VecmathUtil.negate(b2pvec)); path.quadTo(cpb2.x, cpb2.y, b2p2.x + b2pvec.x, b2p2.y + b2pvec.y); } else { // bracket 1 path.moveTo(b1p1.x + b1pvec.x, b1p1.y + b1pvec.y); path.lineTo(b1p1.x, b1p1.y); path.lineTo(b1p2.x, b1p2.y); path.lineTo(b1p2.x + b1pvec.x, b1p2.y + b1pvec.y); // bracket 2 path.moveTo(b2p1.x + b2pvec.x, b2p1.y + b2pvec.y); path.lineTo(b2p1.x, b2p1.y); path.lineTo(b2p2.x, b2p2.y); path.lineTo(b2p2.x + b2pvec.x, b2p2.y + b2pvec.y); } result.add(GeneralPath.outlineOf(path, stroke, foreground)); // work out where to put the suffix labels (e.g. ht/hh/eu) superscript // and (e.g. n, xl, c, mix) subscript // TODO: could be improved double b1MaxX = Math.max(b1p1.x, b1p2.x); double b2MaxX = Math.max(b2p1.x, b2p2.x); double b1MaxY = Math.max(b1p1.y, b1p2.y); double b2MaxY = Math.max(b2p1.y, b2p2.y); Point2d subSufPnt = b2p2; Point2d supSufPnt = b2p1; Vector2d subpvec = b2pvec; double bXDiff = b1MaxX - b2MaxX; double bYDiff = b1MaxY - b2MaxY; if (bXDiff > EQUIV_THRESHOLD || (bXDiff > -EQUIV_THRESHOLD && bYDiff < -EQUIV_THRESHOLD)) { subSufPnt = b1p2; supSufPnt = b1p1; subpvec = b1pvec; } double xDiff = subSufPnt.x - supSufPnt.x; double yDiff = subSufPnt.y - supSufPnt.y; if (yDiff > EQUIV_THRESHOLD || (yDiff > -EQUIV_THRESHOLD && xDiff > EQUIV_THRESHOLD)) { Point2d tmpP = subSufPnt; subSufPnt = supSufPnt; supSufPnt = tmpP; } // subscript/superscript suffix annotation if (subscriptSuffix != null && !subscriptSuffix.isEmpty()) { TextOutline subscriptOutline = leftAlign( makeText(subscriptSuffix.toLowerCase(Locale.ROOT), subSufPnt, subpvec, labelScale)); result.add(GeneralPath.shapeOf(subscriptOutline.getOutline(), foreground)); } if (superscriptSuffix != null && !superscriptSuffix.isEmpty()) { TextOutline superscriptOutline = leftAlign( makeText( superscriptSuffix.toLowerCase(Locale.ROOT), supSufPnt, subpvec, labelScale)); result.add(GeneralPath.shapeOf(superscriptOutline.getOutline(), foreground)); } } return result; }