/** * private ctor used for fit'ing */ private HorizontalArea(Area[] content, Area source) : base(content, source) { // calulate bounding box BoundingBox box = BoundingBox.New(); foreach(Area a in content) box.Append(a.BoundingBox); this.box = box; }
/** * private ctor used for fit operation */ private TableArea(MathMLTableElement e, Area[] cells, BoundingBox box, PointF[] solidLines, PointF[] dashedLines, Area source) : base(cells, source) { this.element = e; this.dashedLines = dashedLines; this.solidLines = solidLines; this.box = box; }
public GlyphArea(IFontHandle fontHandle, ref GlyphAttributes attributes) { this.font = fontHandle; this.leftEdge = attributes.Left; this.rightEdge = attributes.Right; this.box = attributes.Box; this.index = (ushort)attributes.Index; }
/** * create a box area. */ public TableCellArea(MathMLElement element, Area area, BoundingBox box, PointF cellShift, PointF areaShift) : base(area) { this.cellShift = cellShift; this.areaShift = areaShift; this.box = box; this.element = element; }
/// <summary> /// Draw the current mathml equation to an image object. /// </summary> /// <param name="type">The type of image to return, currently this can be /// either Bitmap or Metafile</param> /// <param name="gr">The graphics context in which this bitmap should be created.</param> /// <returns>A new image, null if an invalid type is given or there is no current element</returns> public Image GetImage(Type type, Graphics gr) { if (mathElement == null) return null; gr.PageUnit = GraphicsUnit.Pixel; // build the formatting tree MathMLFormatter formatter = new MathMLFormatter(); WinFormattingContext ctx = new WinFormattingContext(gr, fontSize); format = formatter.Format(mathElement, ctx); // build the are tree box = format.BoundingBox; area = format.Fit(box); Image image = null; int height = (int)Math.Ceiling(2+box.VerticalExtent); int width = (int)Math.Ceiling(2+box.HorizontalExtent); if(type.Equals(typeof(Bitmap))) { image = new Bitmap(width, height); } else if(type.Equals(typeof(Metafile))) { IntPtr dc = gr.GetHdc(); image = new Metafile(new MemoryStream(), dc, EmfType.EmfOnly); gr.ReleaseHdc(dc); } if(image != null && area != null) { using (Graphics gi = Graphics.FromImage(image)) { gi.PageUnit = GraphicsUnit.Pixel; gi.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; DrawWithoutFormatting(gi, width, height, 1, 1); } } return image; }
/** * private consturctor */ private VerticalArea(Area[] areas, int baseline, Area source) : base(areas, source) { this.baseline = baseline; BoundingBox bbox = content[baseline].BoundingBox; for(int i = 0; i < content.Length; i++) { if(i < baseline) { bbox.Over(content[i].BoundingBox); } else if(i > baseline) { bbox.Under(content[i].BoundingBox); } } this.box = bbox; }
/** * create a string area */ public StringArea(IFormattingContext context, string content) { this.font = context.GetFont(); this.content = content; box = BoundingBox.New(); float left = 0, right = 0; for(int i = 0; i < content.Length; i++) { BoundingBox tmp = BoundingBox.New(); context.MeasureGlyph(font, content[i], out tmp, out left, out right); box.Append(tmp); // left edge of first char if(i == 0) { leftEdge = left; } } // right edge of last char rightEdge = right; }
/** * find or calculate an area that will fill the requested cell * height. The returned area may be either a single glyph, or a * compound set of glyphs. * * @param pointSize the evaluated font size * @param desiredSize the desired stretch size (for either vertical or * horizontal stretchy glyphs. * @param c the character to find a glyph for. * @param lineThickness a value that get populated with the thickness of the * repated or stretched sections. */ public static Area GetStretchyGlyph(IFormattingContext context, float pointSize, char c, BoundingBox desiredSize, out float lineThickness) { GlyphFactory gf = Instance; Area result = null; lineThickness = 0; for(int i = 0; i < gf.maps.Length; i++) { if((result = gf.maps[i].GetStretchyGlyph(context, pointSize, c, desiredSize, out lineThickness)) != null) return result; } if(result == null) { Debug.WriteLine("no stretchy glyph found, returning standard glyph area"); result = GetGlyph(context, pointSize, c); } return result; }
/** * initialize the dashed and solid lines * pre-req verticalExtent is set * * The general idea behind this line layout algorighm is that from just * a grid of cells, with some null and other not, it is impossible to know * where to place lines. If we have 2 adjacent cells, both null, we do * not know to place a line between them as the left cell could be from * a column spanning cell, and the right could be from a row spanning cell. * * So, we need to know spanning numbers. */ private void CreateLines(BoundingBox[][] extents, MathMLTableCellElement[][] cells, Row[] rows, Column[] columns, LineStyle[] columnLines, LineStyle[] rowLines, LineStyle frame) { // TODO change these to arrays ArrayList dashedLineList = new ArrayList(); ArrayList solidLineList = new ArrayList(); try { // max number of TABLE columns int maxColCount = (columns.Length - 1) / 2; int[] rowSpan = new int[maxColCount]; float top = 0; float vLen = 0; float hLen = 0; float left = 0; // outer loop rows for(int i = 0, row = 1; i < cells.Length; i++, row += 2) { if(i == 0) { top = rows[0].VerticalExtent; vLen = rows[1].VerticalExtent + rows[2].VerticalExtent / 2.0f; } else { top = top + vLen; vLen = rows[row - 1].VerticalExtent / 2.0f + rows[row].VerticalExtent + rows[row + 1].VerticalExtent / 2.0f; } // spaning column length of next row, if a cell spans rows, then it needs a line //int currColSpan = (cells.Length && 0 < cells[i].Length && cells[i][0] != null) ? // cells[i][0].ColumnSpan : 0; //i//nt nextColSpan = (i + 1 < cells.Length && 0 < cells[i + 1].Length && cells[i + 1][0] != null) ? // cells[i + 1][0].ColumnSpan : 0; // inner loop columns for(int j = 0, col = 1; j < maxColCount; j++, col += 2) { if(j == 0) { left = columns[0].Width; hLen = columns[1].Width + columns[2].Width / 2.0f; } else { left = left + hLen; hLen = columns[col - 1].Width / 2.0f + columns[col].Width + columns[col + 1].Width / 2.0f; } // do the vertical line at end of cell if((j + 1 < cells[i].Length && cells[i][j + 1] != null) || (j == cells[i].Length - 1 && cells[i].Length < maxColCount)) { if(columnLines[j] == LineStyle.Solid) { solidLineList.Add(new PointF(left + hLen, top)); solidLineList.Add(new PointF(left + hLen, top + vLen)); } else if(columnLines[j] == LineStyle.Dashed) { dashedLineList.Add(new PointF(left + hLen, top)); dashedLineList.Add(new PointF(left + hLen, top + vLen)); } } // horz line at bottom of cell, only do if we are not the last row if(i + 1 < cells.Length) { // if a span count is 0, this means that the end of a cell was encountered, so // we need to read the span count from the current cell. Otherwise, we are in the // middle of a row spanning colum and we need to decrement the count indicating that // we used up that cell if(rowSpan[j] == 0) { rowSpan[j] = j < cells[i].Length && cells[i][j] != null ? cells[i][j].RowSpan - 1 : 0; } else { rowSpan[j]--; } if(rowSpan[j] == 0) { if(rowLines[i] == LineStyle.Solid) { solidLineList.Add(new PointF(left, top + vLen)); solidLineList.Add(new PointF(left + hLen, top + vLen)); } else if(rowLines[i] == LineStyle.Dashed) { dashedLineList.Add(new PointF(left, top + vLen)); dashedLineList.Add(new PointF(left + hLen, top + vLen)); } } } } } // draw the frame if(frame == LineStyle.Solid) { solidLineList.Add(new PointF(0, 0)); solidLineList.Add(new PointF(0, box.VerticalExtent)); solidLineList.Add(new PointF(0, 0)); solidLineList.Add(new PointF(box.Width, 0)); solidLineList.Add(new PointF(box.Width, box.VerticalExtent)); solidLineList.Add(new PointF(box.Width, 0)); solidLineList.Add(new PointF(box.Width, box.VerticalExtent)); solidLineList.Add(new PointF(0, box.VerticalExtent)); } else if(frame == LineStyle.Dashed) { dashedLineList.Add(new PointF(0, 0)); dashedLineList.Add(new PointF(0, box.VerticalExtent)); dashedLineList.Add(new PointF(0, 0)); dashedLineList.Add(new PointF(box.Width, 0)); dashedLineList.Add(new PointF(box.Width, box.VerticalExtent)); dashedLineList.Add(new PointF(box.Width, 0)); dashedLineList.Add(new PointF(box.Width, box.VerticalExtent)); dashedLineList.Add(new PointF(0, box.VerticalExtent)); } } catch(Exception ex) { throw new Exception("error creating table lines, table should still display, error: " + ex.Message); } dashedLines = (PointF[])dashedLineList.ToArray(typeof(PointF)); solidLines = (PointF[])solidLineList.ToArray(typeof(PointF)); }
/** * fit the child areas into a box. * Essentially, each child area is given an availible space which is proportional * to its' strength with respect to the overall strength of the overall array. Areas * that can not be re-sized in the width direction (have zero width strenght) simply * get re-sized to thier natural width. * * NOTE: Derived classes must override this method (compound glyphs) because this returns * a new HorzArea, not a derived type. */ public override Area Fit(BoundingBox box) { // the strength and un-adjusted size of the total array. Strength s = Strength; BoundingBox b = BoundingBox; Area[] areas = new Area[content.Length]; // new width of each child area float width; // availible space for exanding filler areas in the width direction // use max here, because width may be negative if we are asked to size into // a smaller area. float availibleSpace = Math.Max(0, box.Width - b.Width); for(int i = 0; i < content.Length; i++) { Strength sa = content[i].Strength; BoundingBox ba = content[i].BoundingBox; if(s.Width == 0 || sa.Width == 0) { // no re-sizing strength, so re-size to natural width width = ba.Width; } else { // re-size to natural width + % of availible space width = ba.Width + availibleSpace * sa.Width / s.Width; } areas[i] = content[i].Fit(BoundingBox.New(width, box.Height, box.Depth)); } return new HorizontalArea(areas, this); }
/** * create a new box area */ public static Area Box(BoundingBox box, Area area) { return new BoxArea(area, box); }
/** * fit this area to a bounding box. This just * creates a new vertical space area that is the height and depth * of the bounding box */ public override Area Fit(BoundingBox box) { return new VerticalSpaceArea(box.Height, box.Depth); }
/** * fit all child area to thier own bounding box. */ public override Area Fit(BoundingBox box) { Area[] areas = new Area[content.Length]; for(int i = 0; i < content.Length; i++) { areas[i] = content[i].Fit(content[i].BoundingBox); } // TODO optimize if all areas are the same as this return new TableArea(element, areas, box, solidLines, dashedLines, this); }
public TableArea(MathMLTableElement e, Area[] cells, BoundingBox box, PointF[] solidLines, PointF[] dashedLines) : this(e, cells, box, solidLines, dashedLines, null) { }
/** * initialize the shift and vertical extent vars * The final shift is calcuated by the formatter because it requires information * about the formatted size of the cell area. */ private static void CalculateShiftAndSize(IFormattingContext ctx, MathMLTableElement table, Row[] rows, Column[] columns, ref BoundingBox box, ref float shift) { shift = 0; TableAlign align = table.Align; float verticalExtent = 0; float width = 0; for(int i = 0; i < rows.Length; i++) { verticalExtent += rows[i].VerticalExtent; } for(int i = 0; i < columns.Length; i++) { width += columns[i].Width; } shift = -verticalExtent; box = BoundingBox.New(width, verticalExtent, 0); }
/// <summary> /// set this box to the area that is the overlap of this box, /// and the given box. /// </summary> /// <param name="box"></param> public void Overlap(BoundingBox box) { Width = Math.Max(Width, box.Width); if (!box.Defined) return; else if (Defined) { Height = Math.Max(Height, box.Height); Depth = Math.Max(Depth, box.Depth); } else { Height = box.Height; Depth = box.Depth; } }
/// <summary> /// add the contents of the given bounding box to this bounding box /// </summary> /// <param name="box"></param> public void Append(BoundingBox box) { Width += box.Width; Depth = Math.Max(Depth, box.Depth); Height = Math.Max(Height, box.Height); }
/// <summary> /// set this size to a region made by placing this box /// over the given box, e.g add the vertical extent of the /// given box to the depth this box. /// </summary> /// <param name="box"></param> public void Over(BoundingBox box) { Width = Math.Max(Width, box.Width); if (!box.Defined) return; else if (Defined) Depth += box.Height + box.Depth; else { Height = 0; Depth = box.Height + box.Depth; } }
public static Area GetStretchyGlyph(IFormattingContext context, float pointSize, char c, BoundingBox desiredSize) { float lineThickness; return GetStretchyGlyph(context, pointSize, c, desiredSize, out lineThickness); }
/** * Default behavior is for a container area not to change any * properties of the bounding box, and simply fit the child * to this box. */ public override Area Fit(BoundingBox box) { BinContainerArea area = (BinContainerArea)Clone(); Debug.Assert(area != null, "result of Clone() is null in BinContainerArea.Fit"); area.child = child.Fit(box); area.source = this; return area; }
/** * re-size all child nodes to the given width, and a height * and depth proportional */ public override Area Fit(BoundingBox box) { // new content area where fitted area are written to Area[] newContent = new Area[content.Length]; // bounding box (natural size), and strength of the array // as a whole BoundingBox b = BoundingBox; Strength s = Strength; // availible height and depth distance to stretch float aHeight = Math.Max(0, box.Height - b.Height); float aDepth = Math.Max(0, box.Depth - b.Depth); for(int i = 0; i < content.Length; i++) { // strength and size of the current child area Strength ps = content[i].Strength; BoundingBox pb = content[i].BoundingBox; // above baseline if(i > baseline && s.Height > 0) { pb.Height += (aHeight * ps.Height) / s.Height; pb.Depth += (aHeight * ps.Depth) / s.Height; } // below baseline else if(i < baseline && s.Depth > 0) { pb.Height += (aDepth * ps.Height) / s.Depth; pb.Depth += (aDepth * ps.Depth) / s.Depth; } // is baseline else if(i == baseline && s.Height + s.Depth > 0) { if (s.Height > 0) pb.Height += (aHeight * ps.Height) / s.Height; if (s.Depth > 0) pb.Depth += (aDepth * ps.Depth) / s.Depth; } newContent[i] = content[i].Fit(BoundingBox.New(box.Width, pb.Height, pb.Depth)); } return new VerticalArea(newContent, baseline, this); }
public BoundingBox[] MeasureElements(IFormattingContext ctx, MathMLElement[] elements) { BoundingBox[] boxes = new BoundingBox[elements.Length]; for(int i = 0; i < elements.Length; i++) { if(elements[i] != null) { boxes[i] = MeasureElement(ctx, elements[i]); } else { boxes[i] = BoundingBox.New(); } } return boxes; }
private static void CreateCellSizesAndShifts(MathMLTableCellElement[][] cells, Row[] rows, Column[] columns, ref BoundingBox[][] sizes, ref PointF[][] shifts) { sizes = new BoundingBox[cells.Length][]; shifts = new PointF[cells.Length][]; // init to 1 to skip over first spacing row for frame int rowIndex = 1; // y shift, start with upper frame space float y = rows[0].VerticalExtent; // outer loop - rows for(int i = 0; i < cells.Length; i++) { // init to 1 to skip over first spacing column for frame int colIndex = 1; // start x shift with frame space float x = columns[0].Width; sizes[i] = new BoundingBox[cells[i].Length]; shifts[i] = new PointF[cells[i].Length]; // inner loop - columns for(int j = 0; j < cells[i].Length; j++) { if(cells[i][j] != null) { BoundingBox box = BoundingBox.New(); for(int k = 0, k2 = 0; k < cells[i][j].ColumnSpan; k++, k2 += 2) { if(colIndex + k2 < columns.Length) { if(k2 == 0) { box.Width = columns[colIndex].Width; } else { // add content column box.Append(BoundingBox.New(columns[colIndex + k].Width, 0, 0)); // add space column box.Append(BoundingBox.New(columns[colIndex + k + 1].Width, 0, 0)); } } else { Debug.WriteLine("warning, insuffcient columns for spanning"); } } for(int k = 0, k2 = 0; k < cells[i][j].RowSpan; k++, k2 += 2) { if(rowIndex + k2 < rows.Length) { if(k2 == 0) { box.Height = rows[rowIndex].Height; box.Depth = rows[rowIndex].Depth; } else { // add content row box.Over(BoundingBox.New(0, rows[rowIndex + k2].Height, rows[rowIndex + k].Depth)); // add space row box.Over(BoundingBox.New(0, rows[rowIndex + k2 + 1].Height, rows[rowIndex + k + 1].Depth)); } } } Debug.Assert(box.Height == rows[rowIndex].Height); sizes[i][j] = box; shifts[i][j] = new PointF(x, y + box.Height); } // add width of current column and next column which is a spaceing column // and set index to next non space column x += columns[colIndex++].Width; x += columns[colIndex++].Width; } // add vertical extent of current row and next row which is a spaceing row // and set index to next non space row y += rows[rowIndex++].VerticalExtent; y += rows[rowIndex++].VerticalExtent; } }
private static void AdjustSpanningCells(MathMLTableCellElement[][] cells, BoundingBox[][] minCellSizes, Row[] rows, Column[] columns) { // init to 1 to skip over first spacing row for frame // outer loop - rows for(int i = 0, rowIndex = 1; i < cells.Length; i++, rowIndex += 2) { // init to 1 to skip over first spacing column for frame // inner loop - columns for(int j = 0, colIndex = 1; j < cells[i].Length; j++, colIndex += 2) { if(cells[i][j] != null) { int rowSpan = cells[i][j].RowSpan; if(rowSpan > 1) { float rowSpanVertExt = 0; for(int k = 0, k2 = 0; k < rowSpan; k++, k2 += 2) { if(rowIndex + k2 < rows.Length) { // content row rowSpanVertExt += rows[rowIndex + k2].VerticalExtent; // only add inner space rows if(k < rowSpan - 1) { rowSpanVertExt += rows[rowIndex + k2 + 1].VerticalExtent; } } else { Debug.WriteLine("warning, row span exceeds row count"); } } float reqVertExt = minCellSizes[i][j].VerticalExtent - rowSpanVertExt; if(reqVertExt > 0) { Debug.WriteLine("need " + reqVertExt + " to accomdate spanning row"); float newSpace = reqVertExt / (float)rowSpan; for(int k = 0, k2 = 0; k < rowSpan; k++, k2 += 2) { if(rowIndex + k2 < rows.Length) { rows[rowIndex + k2].Depth += newSpace / 2.0f; rows[rowIndex + k2].Height += newSpace / 2.0f; } else { Debug.WriteLine("warning, row span exceeds row count"); } } } } } } } }
/** * calculate the default amout the script the baseline of the * script rows. this ignores (for now anyway) the min shift values * specified in the DOM */ private static void CalculateScriptShift(IFormattingContext context, BoundingBox baseBox, BoundingBox subBox, Length subMinSize, BoundingBox superBox, Length superMinSize, out float subShift, out float superShift) { CalculateScriptShift(context, baseBox, subBox, superBox, out subShift, out superShift); }
/** * calculate a table shift * the initially consists of entierly height, this is a value given to the * TableArea object to determine the vertical location of a table. * * This value is also used to modify the bounding box that is returned from this * class that is used for the TableArea */ private static float GetTableShift(IFormattingContext ctx, MathMLTableElement table, BoundingBox tableExtent) { float shift = 0; // orient the row in the vertical direction, can be one of the following // (top | bottom | center | baseline | axis) [ rownumber ] // TODO rownumver is currently ignored switch(table.Align.Align) { case Align.Top: { shift = tableExtent.VerticalExtent; } break; case Align.Bottom: { shift = 0; } break; case Align.Center: { shift = tableExtent.VerticalExtent / 2.0f; } break; case Align.Axis: { // shift to the axis (shift up in the negative direction) shift = (tableExtent.VerticalExtent / 2.0f) -ctx.Axis; } break; default: { shift = tableExtent.VerticalExtent / 2.0f; } break; } return shift; }
public BoundingBox[][] MeasureElements(IFormattingContext ctx, MathMLElement[][] elements) { BoundingBox[][] boxes = new BoundingBox[elements.Length][]; for(int i = 0; i < elements.Length; i++) { boxes[i] = MeasureElements(ctx, elements[i]); } return boxes; }
/** * calculate the default amout the script the baseline of the * script rows. this ignores (for now anyway) the min shift values * specified in the DOM. */ private static void CalculateScriptShift(IFormattingContext context, BoundingBox baseBox, BoundingBox subBox, BoundingBox superBox, out float subShift, out float superShift) { float ex = context.Ex; float axis = context.Axis; float rule = context.DefaultLineThickness; superShift = Math.Max(ex, baseBox.Height - axis); subShift = Math.Max(axis, baseBox.Depth + axis); if(!superBox.Defined) { superShift = 0; subShift = Math.Max(subShift, subBox.Height - (ex * 4.0f) / 5); } else { superShift = Math.Max(superShift, superBox.Depth + ex / 4.0f); if(!subBox.Defined) { subShift = 0; } else { if((superShift - superBox.Depth) - (subBox.Height - superShift) < 4.0f * rule) { subShift = 4.0f * rule - superShift + superBox.Depth + subBox.Height; float psi = (4.0f * ex) / 5.0f - (superShift - superBox.Depth); if(psi > 0.0f) { subShift -= psi; superShift += psi; } } } } }
/// <summary> /// fit this area to a bounding box /// create a new horizontal space area with the width of /// the given bounding box /// </summary> public override Area Fit(BoundingBox box) { return new HorizontalSpaceArea(box.Width); }
/// <summary> /// create a space area that occupies the given size. /// </summary> /// <param name="size">the size to make a space area</param> /// <returns></returns> public static Area Space(BoundingBox size) { return new BoxArea(new IgnoreArea(), size); }