void CreateTextRuns(char[] textBuffer, List <CssRun> runlist, int startIndex, int appendLength) { //this may produce more than 1 css text run if (TextBreaker != null) { //use text break to parse more breakAtList.Clear(); TextBreaker.DoBreak(textBuffer, startIndex, appendLength, breakAtList); int j = breakAtList.Count; int pos = startIndex; for (int i = 0; i < j; ++i) { int sepAt = breakAtList[i]; runlist.Add( CssTextRun.CreateTextRun(pos, sepAt - pos)); pos = sepAt; } breakAtList.Clear(); //TextBreaker.DoBreak(textBuffer, startIndex, appendLength, bounds => //{ // //iterate new split // runlist.Add( // CssTextRun.CreateTextRun(startIndex + bounds.startIndex, bounds.length)); //}); } else { runlist.Add(CssTextRun.CreateTextRun(startIndex, appendLength)); } }
void AddToRunList(char[] textBuffer, List <CssRun> runlist, int startIndex, int appendLength, ref bool needICUSplitter) { if (needICUSplitter) { //use icu splitter //copy text buffer to icu *** var parts = Icu.BreakIterator.GetSplitBoundIter(Icu.BreakIterator.UBreakIteratorType.WORD, icuLocal, textBuffer, startIndex, appendLength); //iterate new split foreach (var bound in parts) { runlist.Add( CssTextRun.CreateTextRun(startIndex + bound.startIndex, bound.length)); } needICUSplitter = false;//reset } else { runlist.Add(CssTextRun.CreateTextRun(startIndex, appendLength)); } }
internal void ParseWordContent( char[] textBuffer, BoxSpec spec, bool isBlock, List <CssRun> runlistOutput, out bool hasSomeCharacter) { bool preserverLine = false; bool preserveWhiteSpace = false; var isblock = spec.CssDisplay == CssDisplay.Block; switch (spec.WhiteSpace) { case CssWhiteSpace.Pre: case CssWhiteSpace.PreWrap: //run and preserve whitespace preserveWhiteSpace = true; preserverLine = true; break; case CssWhiteSpace.PreLine: preserverLine = true; break; } //--------------------------------------- //1. check if has some text //-------------------------------------- hasSomeCharacter = false; //-------------------------------------- //just parse and preserve all whitespace //-------------------------------------- int startIndex = 0; int buffLength = textBuffer.Length; //whitespace and respect newline WordParsingState parsingState = WordParsingState.Init; int appendLength = 0; //-------------------------------------- //some text bool needFinerTextParser = false; for (int i = 0; i < buffLength; ++i) { char c0 = textBuffer[i]; if (c0 == '\r') { continue; } //---------------------------------------- //switch by state switch (parsingState) { case WordParsingState.Init: { if (char.IsWhiteSpace(c0)) { if (c0 == '\n') { if (preserverLine) { runlistOutput.Add(CssTextRun.CreateLineBreak()); appendLength = 0; //clear append length continue; } } parsingState = WordParsingState.Whitespace; //start collect whitespace startIndex = i; appendLength = 1; //start collect whitespace } else if (char.IsPunctuation(c0)) { parsingState = WordParsingState.Init; startIndex = i; appendLength = 1; //add single token runlistOutput.Add(CssTextRun.CreateTextRun(startIndex, appendLength)); } else { //character if (c0 > '~') { needFinerTextParser = true; } parsingState = WordParsingState.CharacterCollecting; startIndex = i; appendLength = 1; //start collect whitespace } } break; case WordParsingState.Whitespace: { if (char.IsWhiteSpace(c0)) { if (c0 == '\n') { if (preserverLine) { runlistOutput.Add(CssTextRun.CreateLineBreak()); appendLength = 0; //clear append length continue; } } if (appendLength == 0) { startIndex = i; } appendLength++; } else { runlistOutput.Add(CssTextRun.CreateWhitespace(preserveWhiteSpace ? appendLength : 1)); if (char.IsPunctuation(c0)) { parsingState = WordParsingState.Init; startIndex = i; appendLength = 1; //add single token runlistOutput.Add(CssTextRun.CreateTextRun(startIndex, appendLength)); } else { if (c0 > '~') { needFinerTextParser = true; } parsingState = WordParsingState.CharacterCollecting; startIndex = i; //start collect appendLength = 1; //start append length } } } break; case WordParsingState.CharacterCollecting: { bool isWhiteSpace; if ((isWhiteSpace = char.IsWhiteSpace(c0)) || char.IsPunctuation(c0)) { //flush collecting token if (needFinerTextParser && TextBreaker != null) { CreateTextRuns(textBuffer, runlistOutput, startIndex, appendLength); needFinerTextParser = false; //reset } else { runlistOutput.Add(CssTextRun.CreateTextRun(startIndex, appendLength)); } hasSomeCharacter = true; startIndex = i; //start collect appendLength = 1; //collect whitespace if (isWhiteSpace) { if (c0 == '\n') { if (preserverLine) { runlistOutput.Add(CssTextRun.CreateLineBreak()); appendLength = 0; //clear append length continue; } } parsingState = WordParsingState.Whitespace; } else { parsingState = WordParsingState.Init; runlistOutput.Add(CssTextRun.CreateTextRun(startIndex, appendLength)); } //---------------------------------------- } else { if (c0 > '~') { needFinerTextParser = true; } if (appendLength == 0) { startIndex = i; } appendLength++; } } break; } } //-------------------- if (appendLength > 0) { switch (parsingState) { case WordParsingState.Whitespace: { //last with whitespace if (preserveWhiteSpace) { runlistOutput.Add(CssTextRun.CreateWhitespace(appendLength)); } else { if (!isblock || (runlistOutput.Count > 0)) { runlistOutput.Add(CssTextRun.CreateWhitespace(1)); } } } break; case WordParsingState.CharacterCollecting: { if (needFinerTextParser && TextBreaker != null) { CreateTextRuns(textBuffer, runlistOutput, startIndex, appendLength); needFinerTextParser = false; //reset } else { runlistOutput.Add(CssTextRun.CreateTextRun(startIndex, appendLength)); } hasSomeCharacter = true; } break; } } }
/* * Docs: https://www.w3.org/TR/css-display-3/#intro * Docs: https://www.w3.org/TR/CSS22/visuren.html#box-gen */ /// <summary> /// Generates an appropriate CSS principal box object for the given element, populated with any appropriate child boxes /// </summary> /// <param name="E"></param> /// <returns></returns> public static void Generate_Tree(Node StartNode, Node EndNode = null) { if (StartNode is null) { throw new ArgumentNullException(nameof(StartNode)); } if (!StartNode.GetFlag(ENodeFlags.NeedsBoxUpdate | ENodeFlags.ChildNeedsBoxUpdate)) { throw new ArgumentException($"Neither {nameof(StartNode)} nor its children are flagged for box updates."); } Contract.EndContractBlock(); /* This is the method we use to populate the tree from a given point onwards: * 1) While queue is not empty, Pop next node. * 2) If next node == End then break; * 3) Delete all box-nodes in chain to the nodes real principal-box parent. * 4) Generate a new principal-box or text-run. * 5) Insert the new box-node into tree. * 6) Queue all children of element. */ var Queue = new Queue <Node>(); Queue.Enqueue(StartNode); while (Queue.Count > 0) { Node node = Queue.Dequeue(); if (ReferenceEquals(node, EndNode)) { break; } if (node.GetFlag(ENodeFlags.NeedsBoxUpdate)) { CssBoxTreeNode Box = node.Box; Element nearestAncestor = Get_Closest_Box_Generating_Ancestor(node); // 3) Delete the nodes current box // 3) Unlink all box-nodes in the chain leading to the nodes real principal-box parent // The current box might be wrapped in an anonymous box. in which case the index we WANT is actually THAT boxs' int index = -1; if (Box is object) { var ChainRoot = Box.Unlink(nearestAncestor.Box); index = ChainRoot.index; } CssBoxTreeNode nextBox = null; // 4) Generate a new principal-box or text-run switch (node.nodeType) { case DOM.Enums.ENodeType.TEXT_NODE: { // Texts runs encompass all of the contiguous sibling text-nodes, skip those contiguous nodes in the queue var TextNodes = new List <Text>(node.parentNode.childNodes.Count); while (Queue.Peek().nodeType == DOM.Enums.ENodeType.TEXT_NODE && ReferenceEquals(Queue.Peek().parentNode, node.parentNode)) { TextNodes.Add((Text)Queue.Dequeue()); } nextBox = new CssTextRun(TextNodes.ToArray()); } break; case DOM.Enums.ENodeType.ELEMENT_NODE: { nextBox = Generate_Box((Element)node); } break; } // Transfer child nodes from old box to the new box (they will remove themselves if needed) ITreeNode current = Box.firstChild; while (current is object) { current.parentNode = null; nextBox.childNodes.Add(current); current = current.nextSibling; } // 5) Insert the new box-node into tree if (index > -1) { nearestAncestor.Box.Insert(index, nextBox); } else { nearestAncestor.Box.Add(nextBox); } // Notify the tree that we need to be reflowed node.Propagate_Flag(ENodeFlags.ChildNeedsReflow, exclude_self: true); } node.ClearFlag(ENodeFlags.NeedsBoxUpdate | ENodeFlags.ChildNeedsBoxUpdate); // 6) Queue all children of node. foreach (var n in node.childNodes) { // Only add items which we KNOW will need an update if (n.GetFlag(ENodeFlags.NeedsBoxUpdate | ENodeFlags.ChildNeedsBoxUpdate)) { Queue.Enqueue(n); } } } }