private Bounds CreateArrow(double minWidth, double minHeight) { var arrow = new Bounds(); var headThickness = minHeight / 3; var inset = 0.8; var headLength = minHeight; switch (direction) { case ReactionDirection.Forward: { var fp = new PathFigure(); arrow.Add(new LineElement(new Point(0, 0), new Point(minWidth - 0.5 * headLength, 0), minHeight / 14, fgcol)); fp.StartPoint = new Point(minWidth, 0); fp.Segments.Add(new LineSegment(new Point(minWidth - headLength, +headThickness), true)); fp.Segments.Add(new LineSegment(new Point(minWidth - inset * headLength, 0), true)); fp.Segments.Add(new LineSegment(new Point(minWidth - headLength, -headThickness), true)); fp.IsClosed = true; var path = new PathGeometry(new[] { fp }); arrow.Add(GeneralPath.ShapeOf(path, fgcol)); } break; case ReactionDirection.Backward: { var fp = new PathFigure(); arrow.Add(new LineElement(new Point(0.5 * headLength, 0), new Point(minWidth, 0), minHeight / 14, fgcol)); fp.StartPoint = new Point(0, 0); fp.Segments.Add(new LineSegment(new Point(minHeight, +headThickness), true)); fp.Segments.Add(new LineSegment(new Point(minHeight - (1 - inset) * minHeight, 0), true)); fp.Segments.Add(new LineSegment(new Point(minHeight, -headThickness), true)); fp.IsClosed = true; var path = new PathGeometry(new[] { fp }); arrow.Add(GeneralPath.ShapeOf(path, fgcol)); } break; case ReactionDirection.Bidirectional: // equilibrium? { var fp1 = new PathFigure { StartPoint = new Point(0, 0.5 * +headThickness) }; fp1.Segments.Add(new LineSegment(new Point(minWidth + minHeight + minHeight, 0.5 * +headThickness), true)); fp1.Segments.Add(new LineSegment(new Point(minWidth + minHeight, 1.5 * +headThickness), true)); var fp2 = new PathFigure { StartPoint = new Point(minWidth + minHeight + minHeight, 0.5 * -headThickness) }; fp2.Segments.Add(new LineSegment(new Point(0, 0.5 * -headThickness), true)); fp2.Segments.Add(new LineSegment(new Point(minHeight, 1.5 * -headThickness), true)); var path = new PathGeometry(new[] { fp1, fp2 }); arrow.Add(GeneralPath.OutlineOf(path, minHeight / 14, fgcol)); } break; } return(arrow); }
private IRenderingElement GenerateAbbreviationSgroup(IAtomContainer mol, Sgroup sgroup) { string label = sgroup.Subscript; // already handled by symbol remapping if (sgroup.Bonds.Count > 0 || string.IsNullOrEmpty(label)) { return(new ElementGroup()); } if (!CheckAbbreviationHighlight(mol, sgroup)) { 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 var sgroupAtoms = sgroup.Atoms; Debug.Assert(sgroupAtoms.Any()); var highlight = sgroupAtoms.First().GetProperty <Color>(StandardGenerator.HighlightColorKey); var style = parameters.GetHighlighting(); var glowWidth = parameters.GetOuterGlowWidth(); Vector2 labelCoords = GeometryUtil.Get2DCenter(sgroupAtoms); ElementGroup labelgroup = new ElementGroup(); foreach (var outline in atomGenerator.GenerateAbbreviatedSymbol(label, HydrogenPosition.Right) .Resize(1 / scale, 1 / -scale) .GetOutlines()) { if (highlight != null && style == HighlightStyle.Colored) { labelgroup.Add(GeneralPath.ShapeOf(outline, highlight)); } else { labelgroup.Add(GeneralPath.ShapeOf(outline, foreground)); } } if (highlight != null && style == HighlightStyle.OuterGlow) { ElementGroup group = new ElementGroup { // outer glow needs to be being the label StandardGenerator.OuterGlow(labelgroup, highlight, glowWidth, stroke), labelgroup }; return(group); } else { return(MarkedElement.MarkupAtom(labelgroup, null)); } }
public void FilledPath() { SvgDrawVisitor visitor = new SvgDrawVisitor(50, 50, Depiction.UnitsMM); visitor.Visit(GeneralPath.ShapeOf(CreateRoundRectangle(new WPF::Rect(0, 0, 10, 10), 2, 2), Colors.Blue)); var str = visitor.ToString(); Assert.AreEqual( "<?xml version='1.0' encoding='UTF-8'?>\n" + "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n" + "<svg version='1.2' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='50mm' height='50mm' viewBox='0 0 50 50'>\n" + " <desc>Generated by the Chemistry Development Kit (http://github.com/cdk)</desc>\n" + " <g stroke-linecap='round' stroke-linejoin='round'>\n" + " <path d='M1 0h8c0.55 0 1 0.45 1 1v8c0 0.55 -0.45 1 -1 1h-8c-0.55 0 -1 -0.45 -1 -1v-8c0 -0.55 0.45 -1 1 -1z' stroke='none' fill='#0000FF'/>\n" + " </g>\n" + "</svg>\n", str); }
/// <summary> /// Make an embedded text label for display in a CDK renderer. If a piece of text contains newlines /// they are centred aligned below each other with a line height of 1.4. /// </summary> /// <param name="font">the font to embedded</param> /// <param name="text">the text label</param> /// <param name="color">the color</param> /// <param name="scale">the resize, should include the model scale</param> /// <returns>pre-rendered element</returns> public static IRenderingElement EmbedText(Typeface font, double emSize, string text, Color color, double scale) { var lines = text.Split('\n'); var group = new ElementGroup(); double yOffset = 0; double lineHeight = 1.4d; foreach (var line in lines) { var outline = new TextOutline(line, font, emSize).Resize(scale, -scale); var center = outline.GetCenter(); outline = outline.Translate(-center.X, -(center.Y + yOffset)); yOffset += lineHeight * outline.GetBounds().Height; group.Add(GeneralPath.ShapeOf(outline.GetOutline(), color)); var logicalBounds = outline.LogicalBounds; group.Add(new Bounds(logicalBounds.Left, logicalBounds.Top, logicalBounds.Right, logicalBounds.Bottom)); } return(group); }
private IRenderingElement GenerateSgroupBrackets(Sgroup sgroup, IList <SgroupBracket> brackets, IReadOnlyDictionary <IAtom, AtomSymbol> symbols, string subscriptSuffix, string superscriptSuffix) { // brackets are square by default (style:0) var style = (int?)sgroup.GetValue(SgroupKey.CtabBracketStyle); bool round = style != null && style == 1; var result = new ElementGroup(); var atoms = sgroup.Atoms; var crossingBonds = sgroup.Bonds; // 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 var pairs = crossingBonds.Count == brackets.Count ? BracketBondPairs(brackets, crossingBonds) : Dictionaries.Empty <SgroupBracket, IBond>(); // override bracket layout around single atoms to bring them in closer if (atoms.Count == 1) { var atom = atoms.First(); // e.g. 2 HCL, 8 H2O etc. if (IsUnsignedInt(subscriptSuffix) && !crossingBonds.Any() && symbols.ContainsKey(atom)) { var prefix = new TextOutline('·' + subscriptSuffix, font, emSize).Resize(1 / scale, 1 / -scale); var prefixBounds = prefix.LogicalBounds; var symbol = symbols[atom]; var bounds = symbol.GetConvexHull().Outline.Bounds; // make slightly large bounds = new Rect(bounds.Bottom - 2 * stroke, bounds.Left - 2 * stroke, bounds.Width + 4 * stroke, bounds.Height + 4 * stroke); prefix = prefix.Translate(bounds.Bottom - prefixBounds.Top, symbol.GetAlignmentCenter().Y - prefixBounds.CenterY()); result.Add(GeneralPath.ShapeOf(prefix.GetOutline(), foreground)); } // e.g. CC(O)nCC else if (crossingBonds.Count > 0) { var scriptscale = labelScale; var leftBracket = new TextOutline("(", font, emSize).Resize(1 / scale, 1 / -scale); var rightBracket = new TextOutline(")", font, emSize).Resize(1 / scale, 1 / -scale); var leftCenter = leftBracket.GetCenter(); var rightCenter = rightBracket.GetCenter(); if (symbols.ContainsKey(atom)) { var symbol = symbols[atom]; var bounds = symbol.GetConvexHull().Outline.Bounds; // make slightly large bounds = new Rect(bounds.Left - 2 * stroke, bounds.Top - 2 * stroke, bounds.Width + 4 * stroke, bounds.Height + 4 * stroke); leftBracket = leftBracket.Translate(bounds.Left - 0.1 - leftCenter.X, symbol.GetAlignmentCenter().Y - leftCenter.Y); rightBracket = rightBracket.Translate(bounds.Right + 0.1 - rightCenter.X, symbol.GetAlignmentCenter().Y - rightCenter.Y); } else { var p = atoms.First().Point2D.Value; leftBracket = leftBracket.Translate(p.X - 0.2 - leftCenter.X, p.Y - leftCenter.Y); rightBracket = rightBracket.Translate(p.X + 0.2 - rightCenter.X, p.Y - rightCenter.Y); } result.Add(GeneralPath.ShapeOf(leftBracket.GetOutline(), foreground)); result.Add(GeneralPath.ShapeOf(rightBracket.GetOutline(), foreground)); var rightBracketBounds = rightBracket.GetBounds(); // subscript/superscript suffix annotation if (subscriptSuffix != null && subscriptSuffix.Any()) { TextOutline subscriptOutline = LeftAlign(MakeText(subscriptSuffix.ToLowerInvariant(), new Vector2(rightBracketBounds.Right, rightBracketBounds.Top - 0.1), new Vector2(-0.5 * rightBracketBounds.Width, 0), scriptscale)); result.Add(GeneralPath.ShapeOf(subscriptOutline.GetOutline(), foreground)); } if (superscriptSuffix != null && superscriptSuffix.Any()) { var superscriptOutline = LeftAlign(MakeText(superscriptSuffix.ToLowerInvariant(), new Vector2(rightBracketBounds.Right, rightBracketBounds.Bottom + 0.1), new Vector2(-rightBracketBounds.Width, 0), scriptscale)); result.Add(GeneralPath.ShapeOf(superscriptOutline.GetOutline(), foreground)); } } } else if (pairs.Any()) { SgroupBracket suffixBracket = null; Vector2? suffixBracketPerp = null; foreach (var e in pairs) { var bracket = e.Key; var bond = e.Value; var inGroupAtom = atoms.Contains(bond.Begin) ? bond.Begin : bond.End; var p1 = bracket.FirstPoint; var p2 = bracket.SecondPoint; var perp = VecmathUtil.NewPerpendicularVector(VecmathUtil.NewUnitVector(p1, p2)); // point the vector at the atom group var midpoint = VecmathUtil.Midpoint(p1, p2); if (Vector2.Dot(perp, VecmathUtil.NewUnitVector(midpoint, inGroupAtom.Point2D.Value)) < 0) { perp = Vector2.Negate(perp); } perp *= bracketDepth; if (round) { result.Add(CreateRoundBracket(p1, p2, perp, midpoint)); } else { result.Add(CreateSquareBracket(p1, p2, perp)); } if (suffixBracket == null) { suffixBracket = bracket; suffixBracketPerp = perp; } else { // is this bracket better as a suffix? var sp1 = suffixBracket.FirstPoint; var sp2 = suffixBracket.SecondPoint; var bestMaxX = Math.Max(sp1.X, sp2.X); var thisMaxX = Math.Max(p1.X, p2.X); var bestMaxY = Math.Max(sp1.Y, sp2.Y); var thisMaxY = Math.Max(p1.Y, p2.Y); // choose the most eastern or.. the most southern var xDiff = thisMaxX - bestMaxX; var yDiff = thisMaxY - bestMaxY; if (xDiff > EQUIV_THRESHOLD || (xDiff > -EQUIV_THRESHOLD && yDiff < -EQUIV_THRESHOLD)) { suffixBracket = bracket; suffixBracketPerp = perp; } } } // write the labels if (suffixBracket != null) { var subSufPnt = suffixBracket.FirstPoint; var supSufPnt = suffixBracket.SecondPoint; // try to put the subscript on the bottom var xDiff = subSufPnt.X - supSufPnt.X; var yDiff = subSufPnt.Y - supSufPnt.Y; if (yDiff > EQUIV_THRESHOLD || (yDiff > -EQUIV_THRESHOLD && xDiff > EQUIV_THRESHOLD)) { var tmpP = subSufPnt; subSufPnt = supSufPnt; supSufPnt = tmpP; } // subscript/superscript suffix annotation if (subscriptSuffix != null && subscriptSuffix.Any()) { var subscriptOutline = LeftAlign(MakeText(subscriptSuffix.ToLowerInvariant(), subSufPnt, suffixBracketPerp.Value, labelScale)); result.Add(GeneralPath.ShapeOf(subscriptOutline.GetOutline(), foreground)); } if (superscriptSuffix != null && superscriptSuffix.Any()) { var superscriptOutline = LeftAlign(MakeText(superscriptSuffix.ToLowerInvariant(), supSufPnt, suffixBracketPerp.Value, labelScale)); result.Add(GeneralPath.ShapeOf(superscriptOutline.GetOutline(), foreground)); } } } else if (brackets.Count == 2) { var b1p1 = brackets[0].FirstPoint; var b1p2 = brackets[0].SecondPoint; var b2p1 = brackets[1].FirstPoint; var b2p2 = brackets[1].SecondPoint; var b1vec = VecmathUtil.NewUnitVector(b1p1, b1p2); var b2vec = VecmathUtil.NewUnitVector(b2p1, b2p2); var b1pvec = VecmathUtil.NewPerpendicularVector(b1vec); var b2pvec = VecmathUtil.NewPerpendicularVector(b2vec); // Point the vectors at each other if (Vector2.Dot(b1pvec, VecmathUtil.NewUnitVector(b1p1, b2p1)) < 0) { b1pvec = Vector2.Negate(b1pvec); } if (Vector2.Dot(b2pvec, VecmathUtil.NewUnitVector(b2p1, b1p1)) < 0) { b2pvec = Vector2.Negate(b2pvec); } // scale perpendicular vectors by how deep the brackets need to be b1pvec *= bracketDepth; b2pvec *= bracketDepth; // bad brackets if (double.IsNaN(b1pvec.X) || double.IsNaN(b1pvec.Y) || double.IsNaN(b2pvec.X) || double.IsNaN(b2pvec.Y)) { return(result); } { var path = new PathGeometry(); if (round) { { // bracket 1 (cp: control point) var pf = new PathFigure { StartPoint = new Point(b1p1.X + b1pvec.X, b1p1.Y + b1pvec.Y) }; Vector2 cpb1 = VecmathUtil.Midpoint(b1p1, b1p2); cpb1 += VecmathUtil.Negate(b1pvec); var seg = new QuadraticBezierSegment { Point1 = new Point(cpb1.X, cpb1.Y), Point2 = new Point(b1p2.X + b1pvec.X, b1p2.Y + b1pvec.Y) }; pf.Segments.Add(seg); path.Figures.Add(pf); } { // bracket 2 (cp: control point) var pf = new PathFigure { StartPoint = new Point(b2p1.X + b2pvec.X, b2p1.Y + b2pvec.Y) }; var cpb2 = VecmathUtil.Midpoint(b2p1, b2p2); cpb2 += VecmathUtil.Negate(b2pvec); var seg = new QuadraticBezierSegment { Point1 = new Point(cpb2.X, cpb2.Y), Point2 = new Point(b2p2.X + b2pvec.X, b2p2.Y + b2pvec.Y) }; pf.Segments.Add(seg); path.Figures.Add(pf); } } else { { // bracket 1 var pf = new PathFigure { StartPoint = new Point(b1p1.X + b1pvec.X, b1p1.Y + b1pvec.Y) }; var seg = new PolyLineSegment(); seg.Points.Add(new Point(b1p1.X, b1p1.Y)); seg.Points.Add(new Point(b1p2.X, b1p2.Y)); seg.Points.Add(new Point(b1p2.X + b1pvec.X, b1p2.Y + b1pvec.Y)); pf.Segments.Add(seg); path.Figures.Add(pf); } { // bracket 2 var pf = new PathFigure { StartPoint = new Point(b2p1.X + b2pvec.X, b2p1.Y + b2pvec.Y) }; var seg = new PolyLineSegment(); seg.Points.Add(new Point(b2p1.X, b2p1.Y)); seg.Points.Add(new Point(b2p2.X, b2p2.Y)); seg.Points.Add(new Point(b2p2.X + b2pvec.X, b2p2.Y + b2pvec.Y)); pf.Segments.Add(seg); path.Figures.Add(pf); } } 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 var b1MaxX = Math.Max(b1p1.X, b1p2.X); var b2MaxX = Math.Max(b2p1.X, b2p2.X); var b1MaxY = Math.Max(b1p1.Y, b1p2.Y); var b2MaxY = Math.Max(b2p1.Y, b2p2.Y); var subSufPnt = b2p2; var supSufPnt = b2p1; var subpvec = b2pvec; var bXDiff = b1MaxX - b2MaxX; var bYDiff = b1MaxY - b2MaxY; if (bXDiff > EQUIV_THRESHOLD || (bXDiff > -EQUIV_THRESHOLD && bYDiff < -EQUIV_THRESHOLD)) { subSufPnt = b1p2; supSufPnt = b1p1; subpvec = b1pvec; } var xDiff = subSufPnt.X - supSufPnt.X; var yDiff = subSufPnt.Y - supSufPnt.Y; if (yDiff > EQUIV_THRESHOLD || (yDiff > -EQUIV_THRESHOLD && xDiff > EQUIV_THRESHOLD)) { var tmpP = subSufPnt; subSufPnt = supSufPnt; supSufPnt = tmpP; } // subscript/superscript suffix annotation if (subscriptSuffix != null && subscriptSuffix.Any()) { var subscriptOutline = LeftAlign(MakeText(subscriptSuffix.ToLowerInvariant(), subSufPnt, subpvec, labelScale)); result.Add(GeneralPath.ShapeOf(subscriptOutline.GetOutline(), foreground)); } if (superscriptSuffix != null && superscriptSuffix.Any()) { var superscriptOutline = LeftAlign(MakeText(superscriptSuffix.ToLowerInvariant(), supSufPnt, subpvec, labelScale)); result.Add(GeneralPath.ShapeOf(superscriptOutline.GetOutline(), foreground)); } } return(result); }
private IRenderingElement GenerateAbbreviationSgroup(IAtomContainer mol, Sgroup sgroup) { string label = sgroup.Subscript; // already handled by symbol remapping if (sgroup.Bonds.Count > 0 || string.IsNullOrEmpty(label)) { return(new ElementGroup()); } if (!CheckAbbreviationHighlight(mol, sgroup)) { 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 var sgroupAtoms = sgroup.Atoms; Debug.Assert(sgroupAtoms.Any()); var highlight = sgroupAtoms.First().GetProperty <Color>(StandardGenerator.HighlightColorKey); var style = parameters.GetHighlighting(); var glowWidth = parameters.GetOuterGlowWidth(); Vector2 labelLocation; if (mol.Atoms.Count == sgroup.Atoms.Count) { labelLocation = GeometryUtil.Get2DCenter(sgroupAtoms); } else { // contraction of part of a fragment, e.g. SALT // here we work out the point we want to place the contract relative // to the SGroup Atoms labelLocation = new Vector2(); var sgrpCenter = GeometryUtil.Get2DCenter(sgroupAtoms); var molCenter = GeometryUtil.Get2DCenter(mol); var minMax = GeometryUtil.GetMinMax(sgroupAtoms); var xDiff = sgrpCenter.X - molCenter.X; var yDiff = sgrpCenter.Y - molCenter.Y; if (xDiff > 0.1) { labelLocation.X = minMax[0]; // min x label = INTERPUNCT + label; } else if (xDiff < -0.1) { labelLocation.X = minMax[2]; // max x label = label + INTERPUNCT; } else { labelLocation.X = sgrpCenter.X; label = INTERPUNCT + label; } if (yDiff > 0.1) { labelLocation.Y = minMax[1]; // min y } else if (yDiff < -0.1) { labelLocation.Y = minMax[3]; // max y } else { labelLocation.Y = sgrpCenter.Y; } } var labelgroup = new ElementGroup(); foreach (var outline in atomGenerator.GenerateAbbreviatedSymbol(label, HydrogenPosition.Right) .Center(labelLocation.X, labelLocation.Y) .Resize(1 / scale, 1 / -scale) .GetOutlines()) { if (highlight != null && style == HighlightStyle.Colored) { labelgroup.Add(GeneralPath.ShapeOf(outline, highlight)); } else { labelgroup.Add(GeneralPath.ShapeOf(outline, foreground)); } } if (highlight != null && style == HighlightStyle.OuterGlow) { var group = new ElementGroup { // outer glow needs to be being the label StandardGenerator.OuterGlow(labelgroup, highlight, glowWidth, stroke), labelgroup }; return(group); } else { return(MarkedElement.MarkupAtom(labelgroup, null)); } }
public IRenderingElement Generate() { if (!delocalisedDonuts) { return(null); } var group = new ElementGroup(); smallest = Cycles.EdgeShort.Find(mol).ToRingSet(); foreach (var ring in smallest) { if (!CanDelocalise(ring)) { continue; } foreach (var bond in ring.Bonds) { bonds.Add(bond); } int charge = 0; int unpaired = 0; foreach (var atom in ring.Atoms) { var q = atom.FormalCharge ?? 0; if (q == 0) { continue; } int nCyclic = 0; foreach (var bond in mol.GetConnectedBonds(atom)) { if (bond.IsInRing) { nCyclic++; } } if (nCyclic > 2) { continue; } atoms.Add(atom); charge += q; } var p2 = GeometryUtil.Get2DCenter(ring); if (charge != 0) { var qText = charge < 0 ? "–" : "+"; if (charge < -1) { qText = Math.Abs(charge) + qText; } else if (charge > +1) { qText = Math.Abs(charge) + qText; } TextOutline qSym = new TextOutline(qText, font, emSize); qSym = qSym.Resize(1 / scale, -1 / scale); qSym = qSym.Translate(p2.X - qSym.GetCenter().X, p2.Y - qSym.GetCenter().Y); group.Add(GeneralPath.ShapeOf(qSym.GetOutline(), fgColor)); } double s = GeometryUtil.GetBondLengthMedian(ring); double n = ring.Bonds.Count; double r = s / (2 * Math.Tan(Math.PI / n)); group.Add(new OvalElement(new WPF::Point(p2.X, p2.Y), r - 1.5 * dbSpacing, stroke, false, fgColor)); } return(group); }
/// <summary> /// Generate the intermediate <see cref="AtomSymbol"/> instances. /// </summary> /// <param name="container">structure representation</param> /// <param name="symbolRemap">use alternate symbols (used for Sgroup shortcuts)</param> /// <param name="visibility">defines whether an atom symbol is displayed</param> /// <param name="parameters">render model parameters</param> /// <returns>generated atom symbols (can contain <see langword="null"/>)</returns> private AtomSymbol[] GenerateAtomSymbols(IAtomContainer container, Dictionary <IAtom, string> symbolRemap, SymbolVisibility visibility, RendererModel parameters, ElementGroup annotations, Color foreground, double stroke, StandardDonutGenerator donutGen) { var scale = parameters.GetScale(); var annDist = parameters.GetAnnotationDistance() * (parameters.GetBondLength() / scale); var annScale = (1 / scale) * parameters.GetAnnotationFontScale(); var annColor = parameters.GetAnnotationColor(); var halfStroke = stroke / 2; var symbols = new AtomSymbol[container.Atoms.Count]; var builder = container.Builder; // check if we should annotate attachment point numbers (maxAttach>1) // and queue them all up for processing var attachPoints = new List <IPseudoAtom>(); int maxAttach = 0; for (int i = 0; i < container.Atoms.Count; i++) { var atom = container.Atoms[i]; if (IsHiddenFully(atom)) { continue; } if (atom is IPseudoAtom) { var attachNum = ((IPseudoAtom)atom).AttachPointNum; if (attachNum > 0) { attachPoints.Add((IPseudoAtom)atom); } if (attachNum > maxAttach) { maxAttach = attachNum; } } var remapped = symbolRemap.ContainsKey(atom); var bonds = container.GetConnectedBonds(atom); var neighbors = container.GetConnectedAtoms(atom); var visNeighbors = new List <IAtom>(); // if a symbol is remapped we only want to consider // visible neighbors in the alignment calculation, otherwise // we include all neighbors foreach (var neighbor in neighbors) { if (!remapped || !IsHidden(neighbor)) { visNeighbors.Add(neighbor); } } var auxVectors = new List <Vector2>(1); // only generate if the symbol is visible if (visibility.Visible(atom, bonds, parameters) || remapped) { var hPosition = HydrogenPositionTools.Position(atom, visNeighbors); if (atom.ImplicitHydrogenCount != null && atom.ImplicitHydrogenCount > 0) { auxVectors.Add(hPosition.Vector()); } if (remapped) { symbols[i] = atomGenerator.GenerateAbbreviatedSymbol(symbolRemap[atom], hPosition); } else if (donutGen.IsChargeDelocalised(atom)) { var charge = atom.FormalCharge; atom.FormalCharge = 0; // can't think of a better way to handle this without API // change to symbol visibility if (atom.AtomicNumber != 6) { symbols[i] = atomGenerator.GenerateSymbol(container, atom, hPosition, parameters); } atom.FormalCharge = charge; } else { symbols[i] = atomGenerator.GenerateSymbol(container, atom, hPosition, parameters); } if (symbols[i] != null) { // defines how the element is aligned on the atom point, when // aligned to the left, the first character 'e.g. Cl' is used. if (visNeighbors.Count < 4) { if (hPosition == HydrogenPosition.Left) { symbols[i] = symbols[i].AlignTo(AtomSymbol.SymbolAlignment.Right); } else { symbols[i] = symbols[i].AlignTo(AtomSymbol.SymbolAlignment.Left); } } var p = atom.Point2D; if (p == null) { throw new ArgumentException("Atom did not have 2D coordinates"); } symbols[i] = symbols[i].Resize(1 / scale, 1 / -scale).Center(p.Value.X, p.Value.Y); } } var label = GetAnnotationLabel(atom); if (label != null) { // to ensure consistent draw distance we need to adjust the annotation distance // depending on whether we are drawing next to an atom symbol or not. double strokeAdjust = symbols[i] != null ? -halfStroke : 0; var vector = NewAtomAnnotationVector(atom, bonds, auxVectors); var annOutline = GenerateAnnotation(atom.Point2D.Value, label, vector, annDist + strokeAdjust, annScale, font, emSize, symbols[i]); // the AtomSymbol may migrate during bond generation and therefore the annotation // needs to be tied to the symbol. If no symbol is available the annotation is // fixed and we can add it to the annotation ElementGroup right away. if (symbols[i] != null) { symbols[i] = symbols[i].AddAnnotation(annOutline); } else { annotations.Add(GeneralPath.ShapeOf(annOutline.GetOutline(), annColor)); } } } // label attachment points if (maxAttach > 1) { var attachNumOutlines = new List <TextOutline>(); double maxRadius = 0; foreach (IPseudoAtom atom in attachPoints) { int attachNum = atom.AttachPointNum; // to ensure consistent draw distance we need to adjust the annotation distance // depending on whether we are drawing next to an atom symbol or not. var strokeAdjust = -halfStroke; var vector = NewAttachPointAnnotationVector( atom, container.GetConnectedBonds(atom), new List <Vector2>()); var outline = GenerateAnnotation(atom.Point2D.Value, attachNum.ToString(), vector, 1.75 * annDist + strokeAdjust, annScale, new Typeface(font.FontFamily, font.Style, WPF.FontWeights.Bold, font.Stretch), emSize, null); attachNumOutlines.Add(outline); var w = outline.GetBounds().Width; var h = outline.GetBounds().Height; var r = Math.Sqrt(w * w + h * h) / 2; if (r > maxRadius) { maxRadius = r; } } foreach (var outline in attachNumOutlines) { var group = new ElementGroup(); var radius = 2 * stroke + maxRadius; var shape = new EllipseGeometry(outline.GetCenter(), radius, radius); var area1 = Geometry.Combine(shape, outline.GetOutline(), GeometryCombineMode.Exclude, Transform.Identity); group.Add(GeneralPath.ShapeOf(area1, foreground)); annotations.Add(group); } } return(symbols); }
/// <inheritdoc/> public IRenderingElement Generate(IAtomContainer container, RendererModel parameters) { if (container.Atoms.Count == 0) { return(new ElementGroup()); } var symbolRemap = new Dictionary <IAtom, string>(); StandardSgroupGenerator.PrepareDisplayShortcuts(container, symbolRemap); var scale = parameters.GetScale(); var visibility = parameters.GetVisibility(); var coloring = parameters.GetAtomColorer(); var annotationColor = parameters.GetAnnotationColor(); var foreground = coloring.GetAtomColor(container.Builder.NewAtom("C")); // the stroke width is based on the font. a better method is needed to get // the exact font stroke but for now we use the width of the pipe character. var fontStroke = new TextOutline("|", font, emSize).Resize(1 / scale, 1 / scale).GetBounds().Width; var stroke = parameters.GetStrokeRatio() * fontStroke; var annotations = new ElementGroup(); var donutGenerator = new StandardDonutGenerator(container, font, emSize, parameters, stroke); var donuts = donutGenerator.Generate(); var symbols = GenerateAtomSymbols(container, symbolRemap, visibility, parameters, annotations, foreground, stroke, donutGenerator); var bondElements = StandardBondGenerator.GenerateBonds(container, symbols, parameters, stroke, font, emSize, annotations, donutGenerator); var style = parameters.GetHighlighting(); var glowWidth = parameters.GetOuterGlowWidth(); var backLayer = new ElementGroup(); var middleLayer = new ElementGroup(); var frontLayer = new ElementGroup(); // bond elements can simply be added to the element group for (int i = 0; i < container.Bonds.Count; i++) { var bond = container.Bonds[i]; if (IsHidden(bond)) { continue; } var highlight = GetHighlightColor(bond, parameters); if (highlight != null && style == HighlightStyle.OuterGlow) { backLayer.Add(MarkedElement.Markup(OuterGlow(bondElements[i], highlight.Value, glowWidth, stroke), "outerglow")); } if (highlight != null && style == HighlightStyle.Colored) { frontLayer.Add(MarkedElement.MarkupBond(Recolor(bondElements[i], highlight.Value), bond)); } else { middleLayer.Add(MarkedElement.MarkupBond(bondElements[i], bond)); } } // bonds for delocalised aromatic frontLayer.Add(donuts); // convert the atom symbols to IRenderingElements for (int i = 0; i < container.Atoms.Count; i++) { var atom = container.Atoms[i]; if (IsHidden(atom)) { continue; } var highlight = GetHighlightColor(atom, parameters); var color = GetColorOfAtom(symbolRemap, coloring, foreground, style, atom, highlight); if (symbols[i] == null) { // we add a 'ball' around atoms with no symbols (e.g. carbons) if (highlight != null && style == HighlightStyle.OuterGlow) { backLayer.Add(MarkedElement.Markup(new OvalElement(ToPoint(atom.Point2D.Value), 1.75 * glowWidth * stroke, true, highlight.Value), "outerglow")); } continue; } var symbolElements = new ElementGroup(); foreach (var shape in symbols[i].GetOutlines()) { GeneralPath path = GeneralPath.ShapeOf(shape, color); symbolElements.Add(path); } // add the annotations of the symbol to the annotations ElementGroup foreach (var shape in symbols[i].GetAnnotationOutlines()) { annotations.Add(MarkedElement.Markup(GeneralPath.ShapeOf(shape, annotationColor), "annotation")); } if (highlight != null && style == HighlightStyle.OuterGlow) { backLayer.Add(MarkedElement.Markup(OuterGlow(symbolElements, highlight.Value, glowWidth, stroke), "outerglow")); } if (highlight != null && style == HighlightStyle.Colored) { frontLayer.Add(MarkedElement.MarkupAtom(symbolElements, atom)); } else { middleLayer.Add(MarkedElement.MarkupAtom(symbolElements, atom)); } } // Add the Sgroups display elements to the front layer var sgroups = StandardSgroupGenerator.Generate(parameters, stroke, font, emSize, foreground, atomGenerator, symbols, container); frontLayer.Add(sgroups); // Annotations are added to the front layer. frontLayer.Add(annotations); var group = new ElementGroup { backLayer, middleLayer, frontLayer }; return(MarkedElement.MarkupMol(group, container)); }
/// <inheritdoc/> public IRenderingElement Generate(IAtomContainer container, RendererModel model) { var highlight = container.GetProperty <IDictionary <IChemObject, int> >(IdMapKey); if (highlight == null) { return(null); } var palette = model.GetHighlightPalette(); double radius = model.GetHighlightRadius() / model.GetScale(); var shapes = new Dictionary <int, Geometry>(); foreach (var atom in container.Atoms) { if (!highlight.TryGetValue(atom, out int id)) { continue; } var area = shapes[id]; var shape = CreateAtomHighlight(atom, radius); if (area == null) { shapes[id] = shape; } else { area = new CombinedGeometry(area, shape); } } foreach (var bond in container.Bonds) { if (!highlight.TryGetValue(bond, out int id)) { continue; } var area = shapes[id]; var shape = CreateBondHighlight(bond, radius); if (area == null) { shapes[id] = (area = shape); } else { area = new CombinedGeometry(area, shape); } // punch out the area occupied by atoms highlighted with a // different color var a1 = bond.Begin; var a2 = bond.End; if (highlight.TryGetValue(a1, out int a1Id)) { if (!a1Id.Equals(id)) { area = new CombinedGeometry(GeometryCombineMode.Exclude, area, shapes[a1Id]); } } if (highlight.TryGetValue(a2, out int a2Id)) { if (!a2Id.Equals(id)) { area = new CombinedGeometry(GeometryCombineMode.Exclude, area, shapes[a2Id]); } } } // create rendering elements for each highlight shape var group = new ElementGroup(); foreach (var e in shapes) { group.Add(GeneralPath.ShapeOf(e.Value, palette.Color(e.Key))); } return(group); }