void MoleculeRenderInternal::_prepareLabelText()

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);
    }
}