|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//
// Description:
// FixedPageStructure represents deduced information (such as boundary,
// geometry,layout, semantic, etc.) after a fixed page is analyzed.
//
namespace System.Windows.Documents
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Documents.DocumentStructures;
using MS.Internal.Documents;
using CultureInfo = System.Globalization.CultureInfo;
//=====================================================================
/// <summary>
/// FixedPageStructure represents deduced information (such as boundary,
/// geometry, layout, semantic, etc.) after a fixed page is analyzed.
/// </summary>
internal sealed class FixedPageStructure
{
//--------------------------------------------------------------------
//
// Constructors
//
//---------------------------------------------------------------------
#region Constructors
internal FixedPageStructure(int pageIndex)
{
Debug.Assert(pageIndex >= 0);
_pageIndex = pageIndex;
// Initialize to virtual
_flowStart = new FlowNode(FixedFlowMap.FlowOrderVirtualScopeId, FlowNodeType.Virtual, pageIndex);
_flowEnd = _flowStart;
//
_fixedStart = FixedNode.Create(pageIndex, 1, FixedFlowMap.FixedOrderStartVisual, -1, null);
_fixedEnd = FixedNode.Create(pageIndex, 1, FixedFlowMap.FixedOrderEndVisual, -1, null);
}
#endregion Constructors
//--------------------------------------------------------------------
//
// Public Methods
//
//---------------------------------------------------------------------
#region Public Methods
#if DEBUG
/// <summary>
/// Create a string representation of this object
/// </summary>
/// <returns>string - A string representation of this object</returns>
public override string ToString()
{
return $"Pg{_pageIndex}- ";
}
#endif
#endregion Public Methods
//--------------------------------------------------------------------
//
// Public Properties
//
//---------------------------------------------------------------------
//--------------------------------------------------------------------
//
// Public Events
//
//---------------------------------------------------------------------
//--------------------------------------------------------------------
//
// Internal Methods
//
//---------------------------------------------------------------------
#region Internal Methods
//-------------------------------------
// Line Detection
//-------------------------------------
internal void SetupLineResults(FixedLineResult[] lineResults)
{
_lineResults = lineResults;
#if DEBUG
DocumentsTrace.FixedTextOM.Builder.Trace("----LineResults Begin Dump-----\r\n");
foreach(FixedLineResult lineResult in _lineResults)
{
Debug.Assert(lineResult != null);
DocumentsTrace.FixedTextOM.Builder.Trace($"{lineResult.ToString()}\r\n");
}
DocumentsTrace.FixedTextOM.Builder.Trace("----LineResults End Dump-----\r\n");
#endif
}
// count: desired as input, remaining as output
// if input count == 0, return current line range.
// Return true if it can get some line range
internal FixedNode[] GetNextLine(int line, bool forward, ref int count)
{
Debug.Assert(_lineResults != null);
if (forward)
{
while (line < _lineResults.Length - 1 && count > 0)
{
line++;
count--;
}
}
else
{
while (line > 0 && count > 0)
{
line--;
count--;
}
}
if (count <= 0)
{
line = Math.Max(0, Math.Min(line, _lineResults.Length - 1));
return _lineResults[line].Nodes;
}
return null;
}
//
/// <summary>
/// If the point is in one of the lines, return that line.
/// Otherwise, return the line with smallest (modified) manhattan distance.
/// </summary>
/// <param name="pt"></param>
/// <returns></returns>
internal FixedNode[] FindSnapToLine(Point pt)
{
Debug.Assert(_lineResults != null);
FixedLineResult closestLine = null;
FixedLineResult closestManhattan = null;
double minVerDistance = double.MaxValue;
double minHorDistance = double.MaxValue;
double minManhattan = double.MaxValue;
foreach (FixedLineResult lineResult in _lineResults)
{
double absVerDistance = Math.Max(0, (pt.Y > lineResult.LayoutBox.Y) ? (pt.Y - lineResult.LayoutBox.Bottom) : (lineResult.LayoutBox.Y - pt.Y));
double absHorDistance = Math.Max(0, (pt.X > lineResult.LayoutBox.X) ? (pt.X - lineResult.LayoutBox.Right) : (lineResult.LayoutBox.X - pt.X));
if (absVerDistance == 0 && absHorDistance == 0)
{
return lineResult.Nodes;
}
//Update the closest line information. We need this if we can't find a close line below the point
if (absVerDistance < minVerDistance || (absVerDistance == minVerDistance && absHorDistance < minHorDistance))
{
minVerDistance = absVerDistance;
minHorDistance = absHorDistance;
closestLine = lineResult;
}
//Update closest manhattan. We decide which metric to choose later.
double manhattan = 5*absVerDistance + absHorDistance;
//Consider removing second condition, or perhaps come up with an exponential weighting for vertical.
if (manhattan < minManhattan && absVerDistance < lineResult.LayoutBox.Height)
{
minManhattan = manhattan;
closestManhattan = lineResult;
}
}
//We couldn't find the next line below. Return the closest line in this case
if (closestLine != null)
{
if (closestManhattan != null && (closestManhattan.LayoutBox.Left > closestLine.LayoutBox.Right || closestLine.LayoutBox.Left > closestManhattan.LayoutBox.Right))
{
// they don't overlap, so go with closer one
return closestManhattan.Nodes;
}
// no manhattan, or they overlap/are in same column
return closestLine.Nodes;
}
return null;
}
//-------------------------------------
// Flow Order
//-------------------------------------
internal void SetFlowBoundary(FlowNode flowStart, FlowNode flowEnd)
{
Debug.Assert(flowStart != null && flowStart.Type != FlowNodeType.Virtual);
Debug.Assert(flowEnd != null && flowEnd.Type != FlowNodeType.Virtual);
_flowStart = flowStart;
_flowEnd = flowEnd;
}
#if DEBUG
private void DrawRectOutline(DrawingContext dc, Pen pen, Rect rect)
{
Debug.Assert(!rect.IsEmpty);
dc.DrawLine(pen, rect.TopLeft, rect.TopRight);
dc.DrawLine(pen, rect.TopRight, rect.BottomRight);
dc.DrawLine(pen, rect.BottomRight, rect.BottomLeft);
dc.DrawLine(pen, rect.BottomLeft, rect.TopLeft);
}
internal void RenderLayoutBox(DrawingContext dc)
{
Pen pen = new Pen(Brushes.Blue, 1);
Rect rect;
for (int line = 0; line < _lineResults.Length; line++)
{
rect = _lineResults[line].LayoutBox;
if (!rect.IsEmpty)
{
DrawRectOutline(dc, pen, rect);
}
}
}
private Point CreateFromLastTextPoint(Point p)
{
// those is always in the left margin area
Point newp = new Point(1, p.Y + 10);
return newp;
}
internal void RenderFixedNode(DrawingContext dc)
{
//
//Iterate through fix node to draw red dotted line
//
CultureInfo EnglishCulture = System.Windows.Markup.TypeConverterHelper.InvariantEnglishUS;
int lineCount = _lineResults.Length;
if (lineCount == 0)
return;
FixedNode fixedStartPage = _lineResults[0].Start;
FixedNode fixedEndPage = _lineResults[lineCount-1].End;
FixedNode[] fixedNodes = _fixedTextBuilder.FixedFlowMap.FixedOrderGetRangeNodes(fixedStartPage, fixedEndPage);
FixedPage fp = _fixedTextBuilder.FixedTextContainer.FixedDocument.GetFixedPage(PageIndex);
FormattedText ft;
Point prevTextPoint = new Point(0, 0);
DpiScale dpi = fp.GetDpi();
foreach (FixedNode currentFixedNode in fixedNodes)
{
if (currentFixedNode.Page == FixedFlowMap.FixedOrderStartPage)
{
prevTextPoint.X = prevTextPoint.Y = 0;
ft = new FormattedText("FixedOrderStartPage",
EnglishCulture,
FlowDirection.LeftToRight,
new Typeface("Courier New"),
8,
Brushes.DarkViolet,
dpi.PixelsPerDip);
dc.DrawText(ft, prevTextPoint);
continue;
}
if (currentFixedNode.Page == FixedFlowMap.FixedOrderEndPage)
{
prevTextPoint.X = fp.Width - 100;
prevTextPoint.Y = fp.Height - 10;
ft = new FormattedText("FixedOrderEndPage",
EnglishCulture,
FlowDirection.LeftToRight,
new Typeface("Courier New"),
8,
Brushes.DarkViolet,
dpi.PixelsPerDip);
dc.DrawText(ft, prevTextPoint);
continue;
}
if (currentFixedNode[1] == FixedFlowMap.FixedOrderStartVisual ||
currentFixedNode[1] == FixedFlowMap.FixedOrderEndVisual)
{
prevTextPoint.X = 2;
prevTextPoint.Y = prevTextPoint.Y + 10;
String outputString = currentFixedNode[1] == FixedFlowMap.FixedOrderStartVisual ?
"FixedOrderStartVisual" : "FixedOrderEndVisual";
ft = new FormattedText(outputString,
EnglishCulture,
FlowDirection.LeftToRight,
new Typeface("Courier New"),
8,
Brushes.DarkViolet,
dpi.PixelsPerDip);
dc.DrawText(ft, prevTextPoint);
continue;
}
DependencyObject dependencyObject = fp.GetElement(currentFixedNode);
Image image = dependencyObject as Image;
if (image != null)
{
GeneralTransform transform = image.TransformToAncestor(fp);
// You can't use GetContentBounds inside OnRender
Rect boundingRect = new Rect(0, 0, image.Width, image.Height);
Rect imageRect = transform.TransformBounds(boundingRect);
if (!imageRect.IsEmpty)
{
// Image might overlap, inflate the box.
imageRect.Inflate(1, 1);
Pen pen = new Pen(Brushes.DarkMagenta, 1.5);
DrawRectOutline(dc, pen, imageRect);
prevTextPoint.X = imageRect.Right;
prevTextPoint.Y = imageRect.Bottom - 10;
}
else
{
prevTextPoint.X = 2;
prevTextPoint.Y = prevTextPoint.Y + 10;
}
ft = new FormattedText(currentFixedNode.ToString(),
EnglishCulture,
FlowDirection.LeftToRight,
new Typeface("Courier New"),
8,
Brushes.DarkViolet,
dpi.PixelsPerDip);
dc.DrawText(ft, prevTextPoint);
continue;
}
Path path = dependencyObject as Path;
if (path != null)
{
GeneralTransform transform = path.TransformToAncestor(fp);
// You can't use GetContentBounds inside OnRender
Rect boundingRect = path.Data.Bounds;
Rect imageRect = transform.TransformBounds(boundingRect);
if (!imageRect.IsEmpty)
{
// Image might overlap, inflate the box.
imageRect.Inflate(1, 1);
Pen pen = new Pen(Brushes.DarkMagenta, 1.5);
DrawRectOutline(dc, pen, imageRect);
prevTextPoint.X = imageRect.Right;
prevTextPoint.Y = imageRect.Bottom - 10;
}
else
{
prevTextPoint.X = 2;
prevTextPoint.Y = prevTextPoint.Y + 10;
}
ft = new FormattedText(currentFixedNode.ToString(),
EnglishCulture,
FlowDirection.LeftToRight,
new Typeface("Courier New"),
8,
Brushes.DarkViolet,
dpi.PixelsPerDip);
dc.DrawText(ft, prevTextPoint);
continue;
}
Glyphs glyphs = dependencyObject as Glyphs;
if (glyphs != null)
{
GlyphRun run = glyphs.ToGlyphRun();
if (run != null)
{
Rect glyphBox = run.ComputeAlignmentBox();
glyphBox.Offset(glyphs.OriginX, glyphs.OriginY);
GeneralTransform transform = glyphs.TransformToAncestor(fp);
//
// Draw it using the dotted red line
//
Pen pen = new Pen(Brushes.Red, 0.5);
Transform t = transform.AffineTransform;
if (t != null)
{
dc.PushTransform(t);
}
else
{
dc.PushTransform(Transform.Identity);
}
DrawRectOutline(dc, pen, glyphBox);
prevTextPoint.X = glyphBox.Right;
prevTextPoint.Y = glyphBox.Bottom;
transform.TryTransform(prevTextPoint, out prevTextPoint);
dc.Pop(); // transform
}
else
{
prevTextPoint.X = 2;
prevTextPoint.Y = prevTextPoint.Y + 10;
}
ft = new FormattedText(currentFixedNode.ToString(),
EnglishCulture,
FlowDirection.LeftToRight,
new Typeface("Courier New"),
8,
Brushes.DarkViolet,
dpi.PixelsPerDip);
dc.DrawText(ft, prevTextPoint);
continue;
}
//
// For anything else, there is this code to draw ...
//
prevTextPoint.X = 2;
prevTextPoint.Y = prevTextPoint.Y + 10;
ft = new FormattedText(currentFixedNode.ToString(),
EnglishCulture,
FlowDirection.LeftToRight,
new Typeface("Courier New"),
8,
Brushes.DarkViolet,
dpi.PixelsPerDip);
dc.DrawText(ft, prevTextPoint);
}
}
internal void RenderFlowNode(DrawingContext dc)
{
FormattedText ft;
FixedNode fixedNode;
FixedSOMElement[] somElements;
String ouptputString;
FixedElement fixedElement;
Random random = new Random();
CultureInfo EnglishCulture = System.Windows.Markup.TypeConverterHelper.InvariantEnglishUS;
FixedPage fp = _fixedTextBuilder.FixedTextContainer.FixedDocument.GetFixedPage(PageIndex);
//
//Iterate through flow node to draw Transparent Rect and draw its index
//
Point prevTextPoint=new Point(0, 0);
DpiScale dpi = fp.GetDpi();
for (int i = FlowStart.Fp; i <= FlowEnd.Fp; i++)
{
FlowNode fn = _fixedTextBuilder.FixedFlowMap[i];
switch (fn.Type)
{
case FlowNodeType.Boundary :
case FlowNodeType.Virtual :
// this two cases won't happen.
Debug.Assert(false);
break;
case FlowNodeType.Start :
case FlowNodeType.End :
{
fixedElement = fn.Cookie as FixedElement;
String typeString = fixedElement.Type.ToString();
int indexofDot = typeString.LastIndexOf('.');
ouptputString = $"{fn}-{typeString.AsSpan(indexofDot + 1)}";
ft = new FormattedText(ouptputString,
EnglishCulture,
FlowDirection.LeftToRight,
new Typeface("Courier New"),
8,
Brushes.DarkGreen,
dpi.PixelsPerDip);
// Ideally, for FlowNodeType.Start, this should find next FlowNode with physical location,
// and draw it around the physical location.
prevTextPoint = CreateFromLastTextPoint(prevTextPoint);
dc.DrawText(ft, prevTextPoint);
break;
}
case FlowNodeType.Noop:
ft = new FormattedText(fn.ToString(),
EnglishCulture,
FlowDirection.LeftToRight,
new Typeface("Courier New"),
8,
Brushes.DarkGreen,
dpi.PixelsPerDip);
prevTextPoint = CreateFromLastTextPoint(prevTextPoint);
dc.DrawText(ft, prevTextPoint);
break;
case FlowNodeType.Run :
//
// Paint the region. The rect is the union of child glyphs.
//
Glyphs glyphs;
Rect flowRunBox = Rect.Empty;
Rect glyphBox;
somElements = _fixedTextBuilder.FixedFlowMap.FlowNodes[fn.Fp].FixedSOMElements;
foreach (FixedSOMElement currentSomeElement in somElements)
{
FixedNode currentFixedNode = currentSomeElement.FixedNode;
int startIndex = currentSomeElement.StartIndex;
int endIndex = currentSomeElement.EndIndex;
// same as (_IsBoundaryFixedNode(currentFixedNode))
if (currentFixedNode.Page == FixedFlowMap.FixedOrderStartPage ||
currentFixedNode.Page == FixedFlowMap.FixedOrderEndPage ||
currentFixedNode[1] == FixedFlowMap.FixedOrderStartVisual ||
currentFixedNode[1] == FixedFlowMap.FixedOrderEndVisual)
{
continue;
}
glyphs = fp.GetGlyphsElement(currentFixedNode);
Debug.Assert(glyphs!= null);
glyphBox = FixedTextView._GetGlyphRunDesignRect(glyphs, startIndex, endIndex);
if (!glyphBox.IsEmpty)
{
GeneralTransform g = glyphs.TransformToAncestor(fp);
glyphBox = g.TransformBounds(glyphBox);
}
flowRunBox.Union(glyphBox);
}
if (flowRunBox.IsEmpty)
{
Debug.Assert(false);
}
prevTextPoint.X = flowRunBox.Right;
prevTextPoint.Y = flowRunBox.Bottom - random.Next(15);
// Draw something the upper left corner of region.
ft = new FormattedText($"{fn}-{(int)(fn.Cookie)}-{somElements.Length}",
EnglishCulture,
FlowDirection.LeftToRight,
new Typeface("Courier New"),
8,
Brushes.DarkBlue,
dpi.PixelsPerDip);
dc.DrawText(ft, prevTextPoint);
Pen pen = new Pen(Brushes.Blue, 2);
flowRunBox.Inflate(random.Next(3), random.Next(3));
DrawRectOutline(dc, pen, flowRunBox);
break;
case FlowNodeType.Object:
//
// Find the mapping fixed node
//
somElements = _fixedTextBuilder.FixedFlowMap.FlowNodes[fn.Fp].FixedSOMElements;
foreach (FixedSOMElement currentSomeElement in somElements)
{
fixedNode = currentSomeElement.FixedNode;
DependencyObject dependencyObject = fp.GetElement(fixedNode);
Image image = dependencyObject as Image;
Path path = dependencyObject as Path;
if (image != null || path != null)
{
Rect imageRect, boundingRect = Rect.Empty;
//
// Get Image bounding box.
//
GeneralTransform transform = ((Visual)dependencyObject).TransformToAncestor(fp);
// You can't use GetContentBounds inside OnRender
if (image != null)
{
boundingRect = new Rect(0, 0, image.Width, image.Height);
}
else
{
boundingRect = path.Data.Bounds;
}
if (!boundingRect.IsEmpty)
{
imageRect = transform.TransformBounds(boundingRect);
// Image might overlap, inflate the box.
imageRect.Inflate(3, 3);
dc.DrawRectangle(Brushes.CadetBlue, null, imageRect);
prevTextPoint.X = imageRect.Right;
prevTextPoint.Y = imageRect.Top;
}
}
else
{
//
// If the object is the Image type(that is not likey).
// Use the last Point to infer a comment area!
//
Debug.Assert(false);
}
fixedElement = fn.Cookie as FixedElement;
ft = new FormattedText(fn.ToString(),
EnglishCulture,
FlowDirection.LeftToRight,
new Typeface("Courier New"),
8,
Brushes.DarkGreen,
dpi.PixelsPerDip);
dc.DrawText(ft, prevTextPoint);
}
break;
default:
Debug.Assert(false);
break;
}
}
}
internal void RenderLines(DrawingContext dc)
{
for (int i=0; i<_lineResults.Length; i++)
{
FixedLineResult lineResult = _lineResults[i];
Pen pen = new Pen(Brushes.Red, 1);
Rect layoutBox = lineResult.LayoutBox;
dc.DrawRectangle(null, pen , layoutBox);
CultureInfo EnglishCulture = System.Windows.Markup.TypeConverterHelper.InvariantEnglishUS;
FixedPage fp = _fixedTextBuilder.FixedTextContainer.FixedDocument.GetFixedPage(PageIndex);
FormattedText ft = new FormattedText(i.ToString(),
EnglishCulture,
FlowDirection.LeftToRight,
new Typeface("Arial"),
10,
Brushes.White,
fp.GetDpi().PixelsPerDip);
Point labelLocation = new Point(layoutBox.Left-25, (layoutBox.Bottom + layoutBox.Top)/2 - 10);
Geometry geom = ft.BuildHighlightGeometry(labelLocation);
Pen backgroundPen = new Pen(Brushes.Black,1);
dc.DrawGeometry(Brushes.Black, backgroundPen, geom);
dc.DrawText(ft, labelLocation);
}
}
#endif
public void ConstructFixedSOMPage(List<FixedNode> fixedNodes)
{
Debug.Assert(_fixedSOMPageConstructor != null);
_fixedSOMPageConstructor.ConstructPageStructure(fixedNodes);
}
#endregion Internal Methods
//--------------------------------------------------------------------
//
// Internal Properties
//
//---------------------------------------------------------------------
#region Internal Properties
internal FixedNode[] LastLine
{
get
{
if (_lineResults.Length > 0)
{
return _lineResults[_lineResults.Length - 1].Nodes;
}
return null;
}
}
internal FixedNode[] FirstLine
{
get
{
if (_lineResults.Length > 0)
{
return _lineResults[0].Nodes;
}
return null;
}
}
//-------------------------------------
// Page
//-------------------------------------
internal int PageIndex
{
get
{
return _pageIndex;
}
}
//-------------------------------------
// Virtualization
//-------------------------------------
internal bool Loaded
{
get
{
return (_flowStart != null && _flowStart.Type != FlowNodeType.Virtual);
}
}
//-------------------------------------
// Flow Order
//-------------------------------------
internal FlowNode FlowStart
{
get
{
return _flowStart;
}
}
internal FlowNode FlowEnd
{
get
{
return _flowEnd;
}
}
//-------------------------------------
// Fixed Order
//-------------------------------------
internal FixedSOMPage FixedSOMPage
{
get
{
return _fixedSOMPage;
}
set
{
_fixedSOMPage = value;
}
}
internal FixedDSBuilder FixedDSBuilder
{
get
{
return _fixedDSBuilder;
}
set
{
_fixedDSBuilder = value;
}
}
internal FixedSOMPageConstructor PageConstructor
{
get
{
return _fixedSOMPageConstructor;
}
set
{
_fixedSOMPageConstructor = value;
}
}
#if DEBUG
internal FixedTextBuilder FixedTextBuilder
{
get
{
return _fixedTextBuilder;
}
set
{
_fixedTextBuilder = value;
}
}
internal FlowNode[] FlowNodes
{
get
{
if (_flowNodes == null)
{
List<FlowNode> nodes = new List<FlowNode>();
//Find the start of flow nodes
int flowCount = this.FixedTextBuilder.FixedFlowMap.FlowCount;
FlowNode flowNode = null;
int startIdx = 0;
if (flowCount > 0)
{
do
{
flowNode = this.FixedTextBuilder.FixedFlowMap.FlowNodes[startIdx++];
if (this.FlowStart == flowNode)
{
break;
}
}
while (startIdx < flowCount);
}
if (startIdx < flowCount)
{
do
{
flowNode = this.FixedTextBuilder.FixedFlowMap.FlowNodes[startIdx++];
nodes.Add(flowNode);
}while (startIdx < flowCount && this.FlowEnd != flowNode);
}
_flowNodes = nodes.ToArray();
}
return _flowNodes;
}
}
internal List<FixedNode> FixedNodes
{
get
{
return _fixedNodes;
}
set
{
_fixedNodes = value;
}
}
#endif
#endregion Internal Properties
//--------------------------------------------------------------------
//
// Private Fields
//
//---------------------------------------------------------------------
#region Private Fields
private readonly int _pageIndex;
// Flow Order Boundary
private FlowNode _flowStart;
private FlowNode _flowEnd;
// Fixed Order Boundary
private FixedNode _fixedStart;
private FixedNode _fixedEnd;
private FixedSOMPageConstructor _fixedSOMPageConstructor;
private FixedSOMPage _fixedSOMPage;
private FixedDSBuilder _fixedDSBuilder;
// Baseline sorted line results
private FixedLineResult[] _lineResults;
//Determines whether a point is close enough to a line when determining snap to line
#if DEBUG
private FixedTextBuilder _fixedTextBuilder;
private FlowNode[] _flowNodes; //Flow nodes for this page
private List<FixedNode> _fixedNodes;
#endif
#endregion Private Fields
}
}
|