Пример #1
0
        public static void GMFrac(MathNode node, XElement output)
        {
            DrawBox(node, output);
            if (node.GetProperty("bevelled") == "true")
            {
                DrawTranslatedNode(node.Enumerator, output, 0, node.Enumerator.Height - node.Height);
                DrawTranslatedNode(node.Denominator, output, node.Width - node.Denominator.Width, node.Depth - node.Denominator.Depth);
            }
            else
            {
                double enumalign  = GetAlign(node, "enumalign");
                double denomalign = GetAlign(node, "denomalign");
                DrawTranslatedNode(node.Enumerator, output, node.RuleWidth + (node.Width - 2 * node.RuleWidth - node.Enumerator.Width) * enumalign, node.Enumerator.Height - node.Height);
                DrawTranslatedNode(node.Denominator, output, node.RuleWidth + (node.Width - 2 * node.RuleWidth - node.Denominator.Width) * denomalign, node.Depth - node.Denominator.Depth);
            }

            //Draw fraction line
            double eh; double dh; double ruleX; double ruleY;
            double x1; double x2; double y1; double y2;

            if (node.GetProperty("bevelled") == "true")
            {
                eh = node.Enumerator.Height + node.Enumerator.Depth;
                dh = node.Denominator.Height + node.Denominator.Depth;
                // Determine a point lying on the rule
                ruleX = (node.Width + node.Enumerator.Width - node.Denominator.Width) / 2.0;
                if (eh < dh)
                {
                    ruleY = 0.75 * eh - node.Height;
                }
                else
                {
                    ruleY = node.Depth - 0.75 * dh;
                }

                x1 = Math.Max(0, ruleX - (node.Depth - ruleY) / node.Slope);
                x2 = Math.Min(node.Width, ruleX + (ruleY + node.Height) / node.Slope);
                y1 = Math.Min(node.Depth, ruleY + ruleX * node.Slope);
                y2 = Math.Max(-node.Height, ruleY - (node.Width - ruleX) * node.Slope);
            }
            else
            {
                x1 = 0;
                y1 = 0;
                x2 = node.Width;
                y2 = 0;
            }
            DrawLine(output, node.Color, node.RuleWidth, x1, y1, x2, y2, new Dictionary <string, string>()
            {
                { "stroke-linecap", "butt" }
            });
        }
Пример #2
0
        public static double GetAlign(MathNode node, string attrName)
        {
            string attrValue = node.GetProperty(attrName, "center");

            if (alignKeywords.ContainsKey(attrValue))
            {
                return(alignKeywords[attrValue]);
            }
            else
            {
                return(0.5);
                //ToDO: node.error("Bad value %s for %s", attrValue, attrName)
            }
        }
Пример #3
0
        public static void MakeLimitContext(MathNode node, MathNode child, string accentProperty)
        {
            child.DisplayStyle = false;
            child.TightSpaces  = true;
            string accentValue = node.GetProperty(accentProperty);

            if (accentValue == null)
            {
                accentValue = GetAccentValue(child);
            }

            child.Accent = (accentValue == "true");
            if (!child.Accent)
            {
                child.ScriptLevel += 1;
            }
        }
Пример #4
0
        public static void CMo(MathNode node)
        {
            // Apply special formatting to operators
            OperatorStyle extraStyle = node.Config.OperatorStyles.FirstOrDefault(x => x.OpName == node.Text);//.Styling;

            if (extraStyle != null)
            {
                node.Attributes.AddRange(extraStyle.Styling); //ToDo: verify
            }

            // Consult the operator dictionary, and set the appropriate defaults
            string form = "infix";

            if (node.Parent == null)
            {
                // pass
            }
            else if (new List <string>()
            {
                "mrow", "mstyle", "msqrt", "merror", "mpadded", "mphantom", "menclose", "mtd", "math"
            }.Any(x => x == node.Parent.ElementName))
            {
                //ToDo: verify
                List <MathNode> prevSiblings = node.Parent.Children.TakeWhile((value, i) => i != node.NodeIndex).Where(IsNonSpaceNode).ToList();
                List <MathNode> nextSiblings = node.Parent.Children.Skip(node.NodeIndex + 1).Where(IsNonSpaceNode).ToList();

                if (prevSiblings.Count == 0 && nextSiblings.Count > 0)
                {
                    form = "prefix";
                }
                if (nextSiblings.Count == 0 && prevSiblings.Count > 0)
                {
                    form = "postfix";
                }
            }

            if (node.Attributes.ContainsKey("form"))
            {
                form = node.Attributes["form"];
            }

            node.OpDefaults = node.Config.MathOperators.LookUp(node.Text, form);
            DefaultContext(node);
            string stretchyattr = node.GetProperty("stretchy", node.OpDefaults.Dict()["stretchy"]);

            node.Stretchy = (stretchyattr == "true");
            string symmetricattr = node.GetProperty("symmetric", node.OpDefaults.Dict()["symmetric"]);

            node.Symmetric = (symmetricattr == "true");
            node.Scaling   = node.OpDefaults.Dict()["scaling"];
            if (!node.TightSpaces)
            {
                string lspaceattr = node.GetProperty("lspace", node.OpDefaults.Dict()["lspace"]);
                node.LeftSpace = node.ParseSpace(lspaceattr);
                string rspaceattr = node.GetProperty("rspace", node.OpDefaults.Dict()["rspace"]);
                node.RightSpace = node.ParseSpace(rspaceattr);
            }

            if (node.DisplayStyle)
            {
                string value = node.OpDefaults.Dict()["largeop"];
                if (node.GetProperty("largeop", value) == "true")
                {
                    node.FontSize *= 1.41;
                }
            }
            else
            {
                string value = node.OpDefaults.Dict()["movablelimits"];
                node.MoveLimits = (node.GetProperty("movablelimits", value) == "true");
            }
        }
Пример #5
0
 public static double GetAlign(MathNode node, string attrName)
 {
     string attrValue = node.GetProperty(attrName, "center");
     if (alignKeywords.ContainsKey(attrValue))
     {
         return alignKeywords[attrValue];
     }
     else
     {
         return 0.5;
         //ToDO: node.error("Bad value %s for %s", attrValue, attrName)
     }
 }
Пример #6
0
 public static void CMTable(MathNode node)
 {
     DefaultContext(node);
     // Display style: no inheritance, default is 'false' unless redefined in 'mstyle'
     node.DisplayStyle = (node.GetProperty("displaystyle") == "true");
 }
Пример #7
0
		public static void MakeLimitContext(MathNode node, MathNode child, string accentProperty)
		{
			child.DisplayStyle = false;
			child.TightSpaces = true;
			string accentValue = node.GetProperty(accentProperty);
			if (accentValue == null)
				accentValue = GetAccentValue(child);

			child.Accent = (accentValue == "true");
			if (!child.Accent)
				child.ScriptLevel += 1;
		}
Пример #8
0
        public static void GMFrac(MathNode node, XElement output)
        {
            DrawBox(node, output);
            if (node.GetProperty("bevelled") == "true")
            {
                DrawTranslatedNode(node.Enumerator, output, 0, node.Enumerator.Height - node.Height);
                DrawTranslatedNode(node.Denominator, output, node.Width - node.Denominator.Width, node.Depth - node.Denominator.Depth);
            }
            else
            {
                double enumalign = GetAlign(node, "enumalign");
                double denomalign = GetAlign(node, "denomalign");
                DrawTranslatedNode(node.Enumerator, output, node.RuleWidth + (node.Width - 2 * node.RuleWidth - node.Enumerator.Width) * enumalign, node.Enumerator.Height - node.Height);
                DrawTranslatedNode(node.Denominator, output, node.RuleWidth + (node.Width - 2 * node.RuleWidth - node.Denominator.Width) * denomalign, node.Depth - node.Denominator.Depth);
            }

            //Draw fraction line
            double eh; double dh; double ruleX; double ruleY;
            double x1; double x2; double y1; double y2;
            if (node.GetProperty("bevelled") == "true")
            {
                eh = node.Enumerator.Height + node.Enumerator.Depth;
                dh = node.Denominator.Height + node.Denominator.Depth;
                // Determine a point lying on the rule
                ruleX = (node.Width + node.Enumerator.Width - node.Denominator.Width) / 2.0;
                if (eh < dh)
                    ruleY = 0.75 * eh - node.Height;
                else
                    ruleY = node.Depth - 0.75 * dh;

                x1 = Math.Max(0, ruleX - (node.Depth - ruleY) / node.Slope);
                x2 = Math.Min(node.Width, ruleX + (ruleY + node.Height) / node.Slope);
                y1 = Math.Min(node.Depth, ruleY + ruleX * node.Slope);
                y2 = Math.Max(-node.Height, ruleY - (node.Width - ruleX) * node.Slope);
            }
            else
            {
                x1 = 0;
                y1 = 0;
                x2 = node.Width;
                y2 = 0;
            }
            DrawLine(output, node.Color, node.RuleWidth, x1, y1, x2, y2, new Dictionary<string, string>() { { "stroke-linecap", "butt" } });
        }
Пример #9
0
		public static void CMTable(MathNode node)
		{
			DefaultContext(node);
			// Display style: no inheritance, default is 'false' unless redefined in 'mstyle'
			node.DisplayStyle = (node.GetProperty("displaystyle") == "true");
		}
Пример #10
0
		public static void CMo(MathNode node)
		{
            // Apply special formatting to operators
            OperatorStyle extraStyle = node.Config.OperatorStyles.Where(x => x.OpName == node.Text).FirstOrDefault();//.Styling;
			if (extraStyle != null)
			{
				node.Attributes.AddRange(extraStyle.Styling); //ToDo: verify
			}

			// Consult the operator dictionary, and set the appropriate defaults
			string form = "infix";
			if (node.Parent == null)
			{
				// pass
			}
			else if (new List<string>() { "mrow", "mstyle", "msqrt", "merror", "mpadded", "mphantom", "menclose", "mtd", "math" }.Any(x => x == node.Parent.ElementName))
			{
				//ToDo: verify
				List<MathNode> prevSiblings = node.Parent.Children.TakeWhile((value, i) => i != node.NodeIndex).Where(x=> IsNonSpaceNode(x)).ToList();
				List<MathNode> nextSiblings = node.Parent.Children.Skip(node.NodeIndex+1).Where(x => IsNonSpaceNode(x)).ToList();

				if (prevSiblings.Count == 0 && nextSiblings.Count > 0)
					form = "prefix";
				if (nextSiblings.Count == 0 && prevSiblings.Count > 0)
					form = "postfix";
			}

			if (node.Attributes.ContainsKey("form"))
				form = node.Attributes["form"];

			node.OpDefaults = node.Config.MathOperators.LookUp(node.Text, form);
			DefaultContext(node);
			string stretchyattr = node.GetProperty("stretchy", node.OpDefaults.Dict()["stretchy"]);
			node.Stretchy = (stretchyattr == "true");
			string symmetricattr = node.GetProperty("symmetric", node.OpDefaults.Dict()["symmetric"]);
			node.Symmetric = (symmetricattr == "true");
			node.Scaling = node.OpDefaults.Dict()["scaling"];
			if (!node.TightSpaces)
			{
				string lspaceattr = node.GetProperty("lspace", node.OpDefaults.Dict()["lspace"]);
				node.LeftSpace = node.ParseSpace(lspaceattr);
				string rspaceattr = node.GetProperty("rspace", node.OpDefaults.Dict()["rspace"]);
				node.RightSpace = node.ParseSpace(rspaceattr);
			}

			if (node.DisplayStyle)
			{
				string value = node.OpDefaults.Dict()["largeop"];
				if (node.GetProperty("largeop", value) == "true")
					node.FontSize *= 1.41;
			}
			else
			{
				string value = node.OpDefaults.Dict()["movablelimits"];
				node.MoveLimits = (node.GetProperty("movablelimits", value) == "true");
			}
		}
Пример #11
0
        private void Stretch(MathNode node, double? toWidth, double? toHeight, double? toDepth, bool symmetric=false)
        {
            if (node == null || !node.Core.Stretchy)
                return;

            if (node != node.Base)
            {
                if (toWidth != null)
                    toWidth -= node.Width - node.Base.Width;

                Stretch(node.Base, toWidth, toHeight, toDepth, symmetric);
                node.MeasureNode();
            }
            else if (node.ElementName == "mo")
            {
                if (node.FontSize == 0)
                    return;
                string maxsizedefault = node.OpDefaults.MaxSize;//["maxsize"];
                string maxsizeattr = node.GetProperty("maxsize", maxsizedefault);
                double? maxScale;
                if (maxsizeattr == "infinity")
                {
                    maxScale = null; //Todo: might be invalid. Check original code
                }
                else
                {
                    maxScale = node.ParseSpaceOrPercent(maxsizeattr, node.FontSize, node.FontSize) / node.FontSize;
                }
                string minsizedefault = node.OpDefaults.MinSize;
                string minsizeattr = node.GetProperty("minsize", minsizedefault);
                double minScale = node.ParseSpaceOrPercent(minsizeattr, node.FontSize, node.FontSize) / node.FontSize;
                if (toWidth == null)
                {
                    StretchVertically(node, (double)toHeight, (double)toDepth, minScale, maxScale, symmetric);
                }
                else
                {
                    StretchHorizontally(node, (double)toWidth, minScale, maxScale);
                }

            }
        }
Пример #12
0
        object[] GetAlign(MathNode node)
        {
            string alignattr = node.GetProperty("align").Trim();
            if (alignattr.Length == 0)
                alignattr = MathDefaults.globalDefaults["align"];

            List<string> splitalign = alignattr.Split(null).ToList();
            string aligntype = splitalign[0];

            int? alignRow = null;
            if (splitalign.Count != 1)
            {
                alignRow = int.Parse(splitalign[1]);
                // ToDo: the rest of the python code is not valid. Not implemented here.
            }
            return new object[] { aligntype, alignRow };
        }
Пример #13
0
        void CalculateRowHeights(MathNode node)
        {
            //Set  initial row heights for cells with rowspan == 1
            double commonAxis = node.Axis();
            foreach(RowDescriptor r in node.Rows)
            {
                r.Height = 0;
                r.Depth = 0;
                foreach (CellDescriptor c in r.Cells)
                {
                    if (c == null || c.Content == null || c.RowSpan != 1)
                        continue;
                    double cellAxis = c.Content.Axis();
                    c.VShift = 0;

                    if (c.VAlign == "baseline")
                    {
                        if (r.AlignToAxis == true)
                            c.VShift -= commonAxis;
                        if (c.Content.AlignToAxis == true)
                            c.VShift += cellAxis;
                    }
                    else if (c.VAlign == "axis")
                    {
                        if (!r.AlignToAxis)
                            c.VShift += commonAxis;
                        if (!c.Content.AlignToAxis)
                            c.VShift -= cellAxis;
                    }
                    else
                    {
                        c.VShift = (r.Height - r.Depth - c.Content.Height + c.Content.Depth) / 2;
                    }

                    r.Height = Math.Max(r.Height, c.Content.Height + c.VShift);
                    r.Depth = Math.Max(r.Depth, c.Content.Depth - c.VShift);
                }
            }

            // Calculate heights for cells with rowspan > 1
            while (true)
            {
                List<RowDescriptor> adjustedRows = new List<RowDescriptor>();
                double adjustedSize = 0;

                foreach (var item in node.Rows.Select((value,i) => new {i, value}))
                {
                    RowDescriptor r = item.value;
                    foreach (CellDescriptor c in r.Cells)
                    {
                        if (c == null || c.Content == null || c.RowSpan == 1)
                            continue;

                        List<RowDescriptor> rows = node.Rows.Skip(item.i).Take(item.i + c.RowSpan).ToList();
                        double requiredSize = c.Content.Height + c.Content.Depth;
                        requiredSize -= rows.Where((value, i) => i < rows.Count).Select(x=> x.SpaceAfter).Sum();
                        double fullSize = rows.Select(x=> x.Height+x.Depth).Sum();
                        if (fullSize >= requiredSize)
                            continue;

                        double unitSize = requiredSize / rows.Count;
                        while (true)
                        {
                            List<RowDescriptor> oversizedRows = rows.Where(x => x.Height + x.Depth > unitSize).ToList(); //ToDo: Verify
                            if (oversizedRows.Count == 0)
                                break;

                            rows = rows.Where(x => x.Height + x.Depth < unitSize).ToList();
                            if (rows.Count == 0)
                                break; // weird rounding effects

                            requiredSize -= oversizedRows.Select(x => x.Height + x.Depth).Sum();
                            unitSize = requiredSize / rows.Count;
                        }
                        
                        if (rows.Count == 0)
                            continue; // protection against arithmetic overflow

                        if (unitSize > adjustedSize)
                        {
                            adjustedSize = unitSize;
                            adjustedRows = rows;
                        }
                    }
                }

                if (adjustedRows.Count == 0)
                    break;

                foreach (RowDescriptor r in adjustedRows)
                {
                    double delta = (adjustedSize - r.Height - r.Depth) / 2;
                    r.Height += delta;
                    r.Depth += delta;
                }
            }

            if (node.GetProperty("equalrows") == "true")
            {
                double maxvsize = node.Rows.Select(x => x.Height + x.Depth).Max();
                foreach (RowDescriptor r in node.Rows)
                {
                    double delta = (maxvsize - r.Height - r.Depth) / 2;
                    r.Height += delta;
                    r.Depth += delta;
                }
            }
        }
Пример #14
0
        void CalculateColumnWidths(MathNode node)
        {
            // Get total width
            string fullwidthattr = "auto";
            if (node.Attributes.ContainsKey("width"))
                fullwidthattr = node.Attributes["width"];

            double? fullwidth;
            if (fullwidthattr == "auto")
            {
                fullwidth = null;
            }
            else
            {
                fullwidth = node.ParseLength(fullwidthattr);
                if (fullwidth <= 0)
                    fullwidth = null;
            }

            // Fill fixed column widths
            List<string> columnwidths = node.GetListProperty("columnwidth");
            foreach (var item in node.Columns.Select((value,i) => new {i, value}))
            {
                ColumnDescriptor column = item.value;
                string attr = GetByIndexOrLast(columnwidths, item.i);
                if (attr == "auto" || attr == "fit")
                {
                    column.Fit = (attr == "fit");
                }
                else if (attr.EndsWith("%"))
                {
                    if (fullwidth == null)
                    {
                        //ToDo: node.error("Percents in column widths supported only in tables with explicit width; width of column %d treated as 'auto'" % (i+1))
                    }
                    else
                    {
                        double value = 0.0;
                        bool result = double.TryParse(attr.Substring(0, attr.Length - 1), out value);
                        if (result && value > 0)
                        {
                            column.Width = (double)fullwidth * value / 100;
                            column.Auto = false;
                        }
                    }
                }
                else
                {
                    column.Width = node.ParseSpace(attr);
                    column.Auto = true;
                }
            }

            // Set  initial auto widths for cells with colspan == 1
            foreach (RowDescriptor r in node.Rows)
            {
                foreach (var item in r.Cells.Select((value, i)=> new {i, value}))
                {
                    CellDescriptor c = item.value;
                    if (c == null || c.Content == null || c.ColSpan > 1)
                        continue;
                    ColumnDescriptor column = node.Columns[item.i];
                    if (column.Auto)
                        column.Width = Math.Max(column.Width, c.Content.Width);
                }
            }

            // Calculate auto widths for cells with colspan > 1
            while (true)
            {
                List<ColumnDescriptor> adjustedColumns = new List<ColumnDescriptor>();
                double adjustedWidth = 0;

                foreach (RowDescriptor r in node.Rows)
                {
                    foreach (var item in r.Cells.Select((value,i) => new {i, value}))
                    {
                        CellDescriptor c = item.value;
                        if (c == null || c.Content == null || c.ColSpan == 1)
                            continue;

                        List<ColumnDescriptor> columns = node.Columns.Skip(item.i).Take(item.i + c.ColSpan).ToList(); //ToDo: Verify
                        List<ColumnDescriptor> autoColumns = columns.Where(x => x.Auto == true).ToList();
                        if (autoColumns.Count == 0)
                            continue;
                        List<ColumnDescriptor> fixedColumns = columns.Where(x=> x.Auto == false).ToList();

                        double fixedWidth = columns.TakeWhile((value, i) => i != columns.Count-1).Select(x=> x.SpaceAfter).Sum();
                        if (fixedColumns.Count > 0)
                            fixedWidth += fixedColumns.Select(x => x.Width).Sum(); //ToDo: Verify
                        double autoWidth = autoColumns.Select(x => x.Width).Sum(); //ToDo: Verify
                        if (c.Content.Width <= fixedWidth + autoWidth)
                            continue; // already fits

                        double requiredWidth = c.Content.Width - fixedWidth;
                        double unitWidth = requiredWidth / autoColumns.Count;

                        while (true)
                        {
                            List<ColumnDescriptor> oversizedColumns = autoColumns.Where(x=> x.Width >= unitWidth).ToList();
                            if (oversizedColumns.Count == 0)
                                break;

                            autoColumns = autoColumns.Where(x => x.Width < unitWidth).ToList();
                            if (autoColumns.Count == 0)
                                break; //weird rounding effects

                            requiredWidth -= oversizedColumns.Select(x => x.Width).Sum();
                            unitWidth = requiredWidth / autoColumns.Count;

                            if (autoColumns.Count == 0)
                                continue; //protection against arithmetic overflow

                            //Store the maximum unit width
                            if (unitWidth > adjustedWidth)
                            {
                                adjustedWidth = unitWidth;
                                adjustedColumns = autoColumns;
                            }
                        }
                    }
                }

                if (adjustedColumns.Count == 0)
                    break;
                foreach (ColumnDescriptor col in adjustedColumns)
                    col.Width = adjustedWidth;
            }

            if (node.GetProperty("equalcolumns") == "true")
            {
                double globalWidth = node.Columns.Where(x => x.Auto = true).Select(x => x.Width).Max();
                foreach (ColumnDescriptor col in node.Columns)
                {
                    if (col.Auto == true)
                        col.Width = globalWidth;
                }
            }
            if (fullwidth != null)
            {
                double delta = (double)fullwidth;
                delta -= node.Columns.Select(x => x.Width).Sum();
                delta -= node.Columns.TakeWhile((x, i) => i < node.Columns.Count - 1).Select(x => x.SpaceAfter).Sum(); //ToDo: verify
                delta -= 2 * node.FrameSpacings[0];
                if (delta != 0)
                {
                    List<ColumnDescriptor> sizableColumns = node.Columns.Where(x => x.Fit == true).ToList();
                    if (sizableColumns.Count == 0)
                        sizableColumns = node.Columns.Where(x => x.Auto == true).ToList();

                    if (sizableColumns.Count == 0)
                    {
                        //ToDo: Implement
                        //node.error("Overconstrained table layout: explicit table width specified, but no column has automatic width; table width attribute ignored")
                    }
                    else
                    {
                        delta /= sizableColumns.Count;
                        foreach (ColumnDescriptor col in sizableColumns)
                            col.Width += delta;
                    }
                }
            }
        }
Пример #15
0
        private void MeasureScripts(MathNode node, List<MathNode> subscripts, List<MathNode> superscripts, List<MathNode> presubscripts = null, List<MathNode> presuperscripts = null)
        {
            node.SubScripts = subscripts == null ? new List<MathNode>() : subscripts;
            node.SuperScripts = superscripts == null ? new List<MathNode>() : superscripts;
            node.PreSubScripts = presubscripts == null ? new List<MathNode>() : presubscripts;
            node.PreSuperScripts = presuperscripts == null ? new List<MathNode>() : presuperscripts;

            SetNodeBase(node, node.Children[0]);
            node.Width = node.Base.Width;
            node.Height = node.Base.Height;
            node.Depth = node.Base.Depth;
            node.Ascender = node.Base.Ascender;
            node.Descender = node.Base.Descender;

            List<MathNode> both_subs = new List<MathNode>(node.SubScripts);
            both_subs.AddRange(node.PreSubScripts);
            List<MathNode> both_sups = new List<MathNode>(node.SuperScripts);
            both_sups.AddRange(node.PreSuperScripts);
            node.SubScriptAxis = both_subs.Select(x => x.Axis()).Union(new List<double>() { 0 }).Max();
            node.SuperScriptAxis = both_sups.Select(x => x.Axis()).Union(new List<double>() { 0 }).Max();
            List<MathNode> all = new List<MathNode>(both_subs);
            all.AddRange(both_sups);
            double gap = all.Select(x => x.NominalLineGap()).Max(); //ToDo:Validate
            double protrusion = node.ParseLength("0.25ex");
            double scriptMedian = node.Axis();

            double[] v = GetRowVerticalExtent(both_subs, false, node.SubScriptAxis);
            double subHeight = v[0];
            double subDepth = v[1];
            double subAscender = v[2];
            double subDescender = v[3];

            v = GetRowVerticalExtent(both_sups, false, node.SuperScriptAxis);
            double superHeight = v[0];
            double superDepth = v[1];
            double superAscender = v[2];
            double superDescender = v[3];

            node.SubShift = 0;
            if (both_subs.Count > 0)
            {
                string shiftAttr = node.GetProperty("subscriptshift");
                if (shiftAttr == null)
                    shiftAttr = "0.5ex";

                node.SubShift = node.ParseLength(shiftAttr);  // positive shifts down
                node.SubShift = Math.Max(node.SubShift, subHeight - scriptMedian + gap);
                if (node.AlignToAxis)
                    node.SubShift += node.Axis();

                node.SubShift = Math.Max(node.SubShift, node.Base.Depth + protrusion - subDepth);
                node.Height = Math.Max(node.Height, subHeight - node.SubShift);
                node.Depth = Math.Max(node.Depth, subDepth + node.SubShift);
                node.Ascender = Math.Max(node.Ascender, subAscender - node.SubShift);
                node.Descender = Math.Max(node.Descender, subDescender + node.SubShift);
            }

            node.SuperShift = 0;
            if (both_sups.Count > 0)
            {
                string shiftAttr = node.GetProperty("superscriptshift");
                if (shiftAttr == null)
                    shiftAttr = "1ex";

                node.SuperShift = node.ParseLength(shiftAttr); // positive shifts up
                node.SuperShift = Math.Max(node.SuperShift, superDepth + scriptMedian + gap);
                if (node.AlignToAxis)
                    node.SuperShift -= node.Axis();

                node.SuperShift = Math.Max(node.SuperShift, node.Base.Height + protrusion - superHeight);
                node.Height = Math.Max(node.Height, superHeight + node.SuperShift);
                node.Depth = Math.Max(node.Depth, superDepth - node.SuperShift);
                node.Ascender = Math.Max(node.Ascender, superHeight + node.SuperShift);
                node.Descender = Math.Max(node.Descender, superDepth - node.SuperShift);
            }

            node.PostWidths = ParallelWidths(node.SubScripts, node.SuperScripts);
            node.PreWidths = ParallelWidths(node.PreSubScripts, node.PreSuperScripts);
            node.Width += node.PreWidths.Sum() + node.PostWidths.Sum();
        }