in core/render2d/src/render_internal.cpp [2981:3478]
void MoleculeRenderInternal::_prepareLabelText(int aid)
{
AtomDesc& ad = _ad(aid);
BaseMolecule& bm = *_mol;
ad.boundBoxMin.set(0, 0);
ad.boundBoxMax.set(0, 0);
int color = ad.color;
bool highlighted = _vertexIsHighlighted(aid);
int tilabel = -1, tihydro = -1, tiHydroIndex = -1, tiValence = -1, tiIsotope = -1, tiindex = -1;
int giChargeSign = -1, giRadical = -1, giRadical1 = -1, giRadical2 = -1;
ad.rightMargin = ad.leftMargin = ad.ypos = ad.height = 0;
int isotope = bm.getAtomIsotope(aid);
if (ad.type == AtomDesc::TYPE_PSEUDO)
{
_preparePseudoAtom(aid, CWC_BASE, highlighted);
bool chargeSignAdded = false;
for (auto i = 0; i < _data.graphitems.size(); i++)
{
if (_data.graphitems[i].ritype == RenderItem::RIT_CHARGESIGN)
{
chargeSignAdded = true;
break;
}
}
if (!chargeSignAdded)
{
_prepareChargeLabel(aid, color, highlighted);
}
}
else if (ad.showLabel)
{
tilabel = _pushTextItem(ad, RenderItem::RIT_LABEL, color, highlighted);
{
TextItem& label = _data.textitems[tilabel];
label.fontsize = FONT_SIZE_LABEL;
ArrayOutput output(label.text);
if (ad.type == AtomDesc::TYPE_REGULAR)
if (ad.label == ELEM_H && isotope == DEUTERIUM)
output.printf("D");
else if (ad.label == ELEM_H && isotope == TRITIUM)
output.printf("T");
else
output.printf(Element::toString(ad.label));
else if (ad.type == AtomDesc::TYPE_QUERY)
_writeQueryAtomToString(output, aid);
else
throw Error("Neither label nor query atom type available");
_writeQueryModifier(output, aid);
output.writeChar(0);
_cw.setTextItemSize(label, ad.pos);
_expandBoundRect(ad, label);
ad.rightMargin = label.bbp.x + label.bbsz.x;
ad.leftMargin = label.bbp.x;
ad.ypos = label.bbp.y;
ad.height = label.bbsz.y;
}
if (bm.isRSite(aid) && bm.getVertex(aid).degree() > 1)
{
// show indices for attachment bonds
const Vertex& v = bm.getVertex(aid);
ad.rSiteAttachmentIndexBegin = _data.rSiteAttachmentIndices.size();
// Check if there are indices for attachment bonds
bool hasAttachmentIndex = false, hasNoAttachmentIndex = false;
for (int k = v.neiBegin(), j = 0; k < v.neiEnd(); k = v.neiNext(k), ++j)
{
int apIdx = bm.getRSiteAttachmentPointByOrder(aid, j);
hasAttachmentIndex |= (apIdx != -1);
hasNoAttachmentIndex |= (apIdx == -1);
}
if (hasAttachmentIndex && hasNoAttachmentIndex)
throw Error("RSite %d is invalid: some attachments indices are specified and some are not");
if (hasNoAttachmentIndex)
ad.rSiteAttachmentIndexCount = 0;
else
{
ad.rSiteAttachmentIndexCount = v.degree();
for (int k = v.neiBegin(), j = 0; k < v.neiEnd(); k = v.neiNext(k), ++j)
{
int apIdx = bm.getRSiteAttachmentPointByOrder(aid, j);
int i = v.findNeiVertex(apIdx);
BondEnd& be = _getBondEnd(aid, i);
int tii = _pushTextItem(ad, RenderItem::RIT_ATTACHMENTPOINT, CWC_BASE, false);
TextItem& ti = _data.textitems[tii];
RenderItemRSiteAttachmentIndex& item = _data.rSiteAttachmentIndices.push();
item.number = j + 1;
item.radius = 0.7f * _settings.fzz[FONT_SIZE_RSITE_ATTACHMENT_INDEX];
item.bbsz.set(2 * item.radius, 2 * item.radius);
item.bbp = ad.pos;
item.color = CWC_BASE;
item.highlighted = false;
item.noBondOffset = true;
bprintf(ti.text, "%d", item.number);
ti.fontsize = FONT_SIZE_RSITE_ATTACHMENT_INDEX;
ti.noBondOffset = true;
_cw.setTextItemSize(ti, ad.pos);
TextItem& label = _data.textitems[tilabel];
// this is just an upper bound, it won't be used
float shift = item.bbsz.length() + label.bbsz.length();
// one of the next conditions should be satisfied
if (fabs(be.dir.x) > 1e-3)
shift = std::min(shift, (item.bbsz.x + label.bbsz.x) / 2.f / fabsf(be.dir.x));
if (fabs(be.dir.y) > 1e-3)
shift = std::min(shift, (item.bbsz.y + label.bbsz.y) / 2.f / fabsf(be.dir.y));
shift += _settings.unit;
item.bbp.addScaled(be.dir, shift);
ti.bbp.addScaled(be.dir, shift);
be.offset = shift + item.radius;
}
}
}
// isotope
if (isotope > 0 && (ad.label != ELEM_H || isotope > 3 || isotope < 2))
{
tiIsotope = _pushTextItem(ad, RenderItem::RIT_ISOTOPE, color, highlighted);
TextItem& itemIsotope = _data.textitems[tiIsotope];
itemIsotope.fontsize = FONT_SIZE_ATTR;
bprintf(itemIsotope.text, "%i", isotope);
_cw.setTextItemSize(itemIsotope);
ad.leftMargin -= _settings.labelInternalOffset + itemIsotope.bbsz.x;
itemIsotope.bbp.set(ad.leftMargin, ad.ypos + _settings.upperIndexShift * ad.height);
_expandBoundRect(ad, itemIsotope);
}
// hydrogen drawing
ad.showHydro = false;
if (!bm.isQueryMolecule())
{
int implicit_h = 0;
if (!bm.isRSite(aid) && !bm.isPseudoAtom(aid) && !bm.isTemplateAtom(aid))
implicit_h = bm.asMolecule().getImplicitH_NoThrow(aid, 0);
if (implicit_h > 0 && _opt.implHVisible)
{
ad.showHydro = true;
tihydro = _pushTextItem(ad, RenderItem::RIT_HYDROGEN, color, highlighted);
Vec2f hydrogenGroupSz;
{
TextItem& itemHydrogen = _data.textitems[tihydro];
itemHydrogen.fontsize = FONT_SIZE_LABEL;
bprintf(itemHydrogen.text, "H");
_cw.setTextItemSize(itemHydrogen, ad.pos);
hydrogenGroupSz.x = itemHydrogen.bbsz.x + _settings.labelInternalOffset;
hydrogenGroupSz.y = itemHydrogen.bbsz.y;
}
if (implicit_h > 1)
{
tiHydroIndex = _pushTextItem(ad, RenderItem::RIT_HYDROINDEX, color, highlighted);
TextItem& itemHydroIndex = _data.textitems[tiHydroIndex];
TextItem& itemHydrogen = _data.textitems[tihydro];
itemHydroIndex.fontsize = FONT_SIZE_ATTR;
bprintf(itemHydroIndex.text, "%i", implicit_h);
_cw.setTextItemSize(itemHydroIndex, ad.pos);
hydrogenGroupSz.x += itemHydroIndex.bbsz.x + _settings.labelInternalOffset;
hydrogenGroupSz.y = std::max(hydrogenGroupSz.y, _settings.lowerIndexShift * itemHydrogen.bbsz.y + itemHydroIndex.bbsz.y);
}
// take new reference, old one may be corrupted after adding 'tiHydroIndex'
TextItem& itemHydrogen = _data.textitems[tihydro];
if (ad.hydroPos == HYDRO_POS_LEFT)
{
ad.leftMargin -= hydrogenGroupSz.x;
itemHydrogen.bbp.set(ad.leftMargin, ad.ypos);
}
else if (ad.hydroPos == HYDRO_POS_RIGHT)
{
ad.rightMargin += _settings.labelInternalOffset;
itemHydrogen.bbp.set(ad.rightMargin, ad.ypos);
ad.rightMargin += hydrogenGroupSz.x;
}
else if (ad.hydroPos == HYDRO_POS_UP)
{
itemHydrogen.bbp.y = ad.pos.y + ad.boundBoxMin.y - hydrogenGroupSz.y - _settings.unit;
}
else if (ad.hydroPos == HYDRO_POS_DOWN)
{
itemHydrogen.bbp.y = ad.pos.y + ad.boundBoxMax.y + _settings.unit;
}
else
{
throw Error("hydrogen position value invalid");
}
_expandBoundRect(ad, itemHydrogen);
if (tiHydroIndex > 0)
{
_data.textitems[tiHydroIndex].bbp.set(itemHydrogen.bbp.x + itemHydrogen.bbsz.x + _settings.labelInternalOffset,
itemHydrogen.bbp.y + _settings.lowerIndexShift * itemHydrogen.bbsz.y);
_expandBoundRect(ad, _data.textitems[tiHydroIndex]);
}
}
}
// charge
_prepareChargeLabel(aid, color, highlighted);
// valence
int valence = bm.getExplicitValence(aid);
if (_opt.showValences && valence >= 0)
{
tiValence = _pushTextItem(ad, RenderItem::RIT_VALENCE, color, highlighted);
TextItem& itemValence = _data.textitems[tiValence];
itemValence.fontsize = FONT_SIZE_ATTR;
bprintf(itemValence.text, _valenceText(valence));
_cw.setTextItemSize(itemValence);
ad.rightMargin += _settings.labelInternalOffset;
itemValence.bbp.set(ad.rightMargin, ad.ypos + _settings.upperIndexShift * ad.height);
_expandBoundRect(ad, itemValence);
ad.rightMargin += itemValence.bbsz.x;
}
// radical
int radical = -1;
if (!bm.isRSite(aid) && !bm.isPseudoAtom(aid) && !bm.isTemplateAtom(aid))
radical = bm.getAtomRadical_NoThrow(aid, -1);
if (radical > 0)
{
const TextItem& label = _data.textitems[tilabel];
Vec2f ltc(label.bbp);
if (radical == RADICAL_DOUBLET)
{
giRadical = _pushGraphItem(ad, RenderItem::RIT_RADICAL, color, highlighted);
GraphItem& itemRadical = _data.graphitems[giRadical];
_cw.setGraphItemSizeDot(itemRadical);
if (!(ad.showHydro && ad.hydroPos == HYDRO_POS_RIGHT) && giChargeSign < 0 && tiValence < 0)
{
ltc.x += label.bbsz.x + _settings.radicalRightOffset;
ltc.y += _settings.radicalRightVertShift * ad.height;
itemRadical.bbp.copy(ltc);
}
else
{
ltc.x += label.bbsz.x / 2 - itemRadical.bbsz.x / 2;
ltc.y -= itemRadical.bbsz.y + _settings.radicalTopOffset;
itemRadical.bbp.copy(ltc);
}
_expandBoundRect(ad, itemRadical);
}
else
{
giRadical1 = _pushGraphItem(ad, RenderItem::RIT_RADICAL, color, highlighted);
giRadical2 = _pushGraphItem(ad, RenderItem::RIT_RADICAL, color, highlighted);
GraphItem& itemRadical1 = _data.graphitems[giRadical1];
GraphItem& itemRadical2 = _data.graphitems[giRadical2];
float dist;
if (radical == RADICAL_SINGLET)
{
_cw.setGraphItemSizeDot(itemRadical1);
_cw.setGraphItemSizeDot(itemRadical2);
dist = _settings.radicalTopDistDot;
}
else // if (radical == RADICAL_TRIPLET)
{
_cw.setGraphItemSizeCap(itemRadical1);
_cw.setGraphItemSizeCap(itemRadical2);
dist = _settings.radicalTopDistCap;
}
ltc.y -= itemRadical1.bbsz.y + _settings.radicalTopOffset;
ltc.x += label.bbsz.x / 2 - dist / 2 - itemRadical1.bbsz.x;
itemRadical1.bbp.copy(ltc);
ltc.x += dist + itemRadical1.bbsz.x;
itemRadical2.bbp.copy(ltc);
_expandBoundRect(ad, itemRadical1);
_expandBoundRect(ad, itemRadical2);
}
}
}
int bondEndRightToStereoGroupLabel = -1;
// prepare stereogroup labels
if ((ad.stereoGroupType > 0 && ad.stereoGroupType != MoleculeStereocenters::ATOM_ANY) || ad.inversion == STEREO_INVERTS || ad.inversion == STEREO_RETAINS)
{
int tiStereoGroup = _pushTextItem(ad, RenderItem::RIT_STEREOGROUP, CWC_BASE, false);
TextItem& itemStereoGroup = _data.textitems[tiStereoGroup];
itemStereoGroup.fontsize = FONT_SIZE_ATTR;
ArrayOutput itemOutput(itemStereoGroup.text);
if (ad.stereoGroupType > 0 && ad.stereoGroupType != MoleculeStereocenters::ATOM_ANY)
{
const char* stereoGroupText = _getStereoGroupText(ad.stereoGroupType);
itemOutput.printf("%s", stereoGroupText);
if (ad.stereoGroupType != MoleculeStereocenters::ATOM_ABS)
itemOutput.printf("%i", ad.stereoGroupNumber);
}
if (ad.inversion == STEREO_INVERTS || ad.inversion == STEREO_RETAINS)
{
if (itemOutput.tell() > 0)
itemOutput.printf(",");
itemOutput.printf("%s", ad.inversion == STEREO_INVERTS ? "Inv" : "Ret");
}
itemOutput.writeChar(0);
_cw.setTextItemSize(itemStereoGroup);
if (ad.showLabel)
{
// label visible - put stereo group label on the over or under the label
const Vertex& v = bm.getVertex(aid);
float vMin = 0, vMax = 0;
for (int i = v.neiBegin(); i < v.neiEnd(); i = v.neiNext(i))
{
float y = _getBondEnd(aid, i).dir.y;
if (y > vMax)
vMax = y;
if (y < vMin)
vMin = y;
}
if (vMax > -vMin)
itemStereoGroup.bbp.set(ad.pos.x - itemStereoGroup.bbsz.x / 2,
ad.pos.y + ad.boundBoxMin.y - itemStereoGroup.bbsz.y - _settings.stereoGroupLabelOffset);
else
itemStereoGroup.bbp.set(ad.pos.x - itemStereoGroup.bbsz.x / 2, ad.pos.y + ad.boundBoxMax.y + _settings.stereoGroupLabelOffset);
}
else
{
// label hidden - position stereo group label independently
Vec2f p;
bondEndRightToStereoGroupLabel = _findClosestBox(p, aid, itemStereoGroup.bbsz, _settings.unit);
p.addScaled(itemStereoGroup.bbsz, -0.5);
itemStereoGroup.bbp.copy(p);
}
_expandBoundRect(ad, itemStereoGroup);
}
// prepare AAM labels
if (ad.aam > 0)
{
int tiAAM = _pushTextItem(ad, RenderItem::RIT_AAM, CWC_BASE, false);
TextItem& itemAAM = _data.textitems[tiAAM];
itemAAM.fontsize = FONT_SIZE_ATTR;
bprintf(itemAAM.text, "%i", abs(ad.aam));
_cw.setTextItemSize(itemAAM);
if (ad.showLabel)
{
ad.leftMargin -= itemAAM.bbsz.x + _settings.labelInternalOffset;
itemAAM.bbp.set(ad.leftMargin, ad.ypos + _settings.lowerIndexShift * ad.height);
}
else
{
Vec2f p;
_findClosestBox(p, aid, itemAAM.bbsz, _settings.unit, bondEndRightToStereoGroupLabel);
p.addScaled(itemAAM.bbsz, -0.5);
itemAAM.bbp.copy(p);
}
_expandBoundRect(ad, itemAAM);
}
// prepare R-group attachment point labels
QS_DEF(Array<float>, angles);
QS_DEF(Array<int>, split);
QS_DEF(Array<int>, rGroupAttachmentIndices);
if (ad.isRGroupAttachmentPoint)
{
// collect the angles between adjacent bonds
const Vertex& v = bm.getVertex(aid);
angles.clear();
split.clear();
if (v.degree() != 0)
{
for (int i = v.neiBegin(); i < v.neiEnd(); i = v.neiNext(i))
{
float a = _getBondEnd(aid, i).lang;
angles.push(a);
split.push(1);
}
}
// collect attachment point indices
rGroupAttachmentIndices.clear();
bool multipleAttachmentPoints = _mol->attachmentPointCount() > 1;
for (int i = 1; i <= _mol->attachmentPointCount(); ++i)
for (int j = 0, k; (k = _mol->getAttachmentPoint(i, j)) >= 0; ++j)
if (k == aid)
rGroupAttachmentIndices.push(i);
if (v.degree() != 0)
{
for (int j = 0; j < rGroupAttachmentIndices.size(); ++j)
{
int i0 = -1;
for (int i = 0; i < angles.size(); ++i)
if (i0 < 0 || angles[i] / (split[i] + 1) > angles[i0] / (split[i0] + 1))
i0 = i;
split[i0]++;
}
}
// arrange the directions of the attachment points
QS_DEF(Array<Vec2f>, attachmentDirection);
attachmentDirection.clear();
if (v.degree() == 0)
{
// if no adjacent bonds present
if (rGroupAttachmentIndices.size() == 1)
attachmentDirection.push().set(0, -1);
else if (rGroupAttachmentIndices.size() == 2)
{
attachmentDirection.push().set(cos((float)M_PI / 6), -sin((float)M_PI / 6));
attachmentDirection.push().set(cos(5 * (float)M_PI / 6), -sin(5 * (float)M_PI / 6));
}
else
{
for (int j = 0; j < rGroupAttachmentIndices.size(); ++j)
{
float a = j * 2 * (float)M_PI / rGroupAttachmentIndices.size();
attachmentDirection.push().set(cos(a), sin(a));
}
}
}
else
{
// split the angles
for (int i = 0; i < split.size(); ++i)
{
angles[i] /= split[i];
}
for (int j = 0; j < rGroupAttachmentIndices.size(); ++j)
{
int i0 = -1, n = v.neiBegin();
for (int i = 0; i < split.size(); ++i, n = v.neiNext(n))
{
if (split[i] > 1)
{
i0 = i;
break;
}
}
if (i0 < 0)
throw Error("Error while arranging attachment points");
Vec2f d;
d.copy(_getBondEnd(aid, n).dir);
d.rotateL(angles[i0] * (--split[i0]));
attachmentDirection.push(d);
}
}
// create the attachment point items
ad.attachmentPointBegin = _data.attachmentPoints.size();
ad.attachmentPointCount = rGroupAttachmentIndices.size();
for (int j = 0; j < rGroupAttachmentIndices.size(); ++j)
{
RenderItemAttachmentPoint& attachmentPoint = _data.attachmentPoints.push();
float offset = std::min(std::max(_getBondOffset(aid, ad.pos, attachmentDirection[j], _settings.unit), 0.f), 0.4f);
attachmentPoint.dir.copy(attachmentDirection[j]);
attachmentPoint.p0.lineCombin(ad.pos, attachmentDirection[j], offset);
attachmentPoint.p1.lineCombin(ad.pos, attachmentDirection[j], 0.8f);
attachmentPoint.color = CWC_BASE;
attachmentPoint.highlighted = false;
if (multipleAttachmentPoints)
{
attachmentPoint.number = rGroupAttachmentIndices[j];
}
}
}
// prepare atom id's
if (_opt.showAtomIds)
{
tiindex = _pushTextItem(ad, RenderItem::RIT_ATOMID, CWC_BLUE, false);
TextItem& index = _data.textitems[tiindex];
index.fontsize = FONT_SIZE_INDICES;
int base = _opt.atomBondIdsFromOne ? 1 : 0;
bprintf(index.text, "%i", aid + base);
_cw.setTextItemSize(index, ad.pos);
if (ad.showLabel)
index.bbp.set(ad.rightMargin + _settings.labelInternalOffset, ad.ypos + 0.5f * ad.height);
}
}