private void PaintLineForeground(DeviceContext deviceContext, PaintOptions paintOptions, ScriptLine* scriptLine, ScriptParagraph* scriptParagraph, int scriptRunIndex, int scriptRunCount, int* visualToLogicalMap, int xStartPosition, int xEndPosition, Rectangle layoutRect, bool isSelected, bool skipObjects) { int yCurrentBaseline = scriptLine->Y + scriptLine->Ascent; int xCurrentPosition = scriptLine->X; for (int i = 0; i < scriptRunCount; i++) { int logicalScriptRunIndex = visualToLogicalMap[currentLayoutRightToLeft ? scriptRunCount - i - 1 : i]; ScriptRun* scriptRun = scriptParagraph->ScriptRuns + logicalScriptRunIndex + scriptRunIndex; int measuredWidth; switch (scriptRun->RunKind) { case RunKind.Text: { Style style = document.LookupStyle(scriptRun->StyleIndex); ScriptMetrics scriptMetrics = deviceContext.SelectFont(style.Font); int glyphIndexInParagraph, glyphCount; measuredWidth = MeasureTextScriptRun(scriptParagraph, scriptRun, logicalScriptRunIndex == 0 ? scriptLine->TruncatedLeadingCharsCount : 0, logicalScriptRunIndex == scriptRunCount - 1 ? scriptLine->TruncatedTrailingCharsCount : 0, out glyphIndexInParagraph, out glyphCount); if (glyphCount > 0 && xCurrentPosition + measuredWidth > xStartPosition) { int x = currentLayoutRightToLeft ? layoutRect.Right - xCurrentPosition - measuredWidth : layoutRect.Left + xCurrentPosition; int y = layoutRect.Top + yCurrentBaseline - scriptRun->Ascent; deviceContext.SetTextColor(isSelected ? paintOptions.SelectedTextColor : style.Color); ScriptTextOut(deviceContext.HDC, ref scriptMetrics.ScriptCache, x, y, ExtTextOutOptions.NONE, null, &scriptRun->ScriptAnalysis, scriptParagraph->Glyphs + glyphIndexInParagraph, glyphCount, scriptParagraph->GlyphAdvanceWidths + glyphIndexInParagraph, null, scriptParagraph->GlyphOffsets + glyphIndexInParagraph); } break; } case RunKind.Object: { measuredWidth = MeasureObjectScriptRun(scriptRun); if (!skipObjects && xCurrentPosition + measuredWidth > xStartPosition) { int embeddedObjectCharIndex = scriptRun->CharIndexInParagraph + scriptParagraph->CharIndex; EmbeddedObjectHost embeddedObjectHost; if (embeddedObjectHosts.TryGetValue(embeddedObjectCharIndex, out embeddedObjectHost)) { if (embeddedObjectHost.RequiresPaint) { using (Graphics g = Graphics.FromHdc(deviceContext.HDC)) { Rectangle bounds = GetDrawingBoundsOfEmbeddedObject(embeddedObjectHost, layoutRect.Location); embeddedObjectHost.Paint(g, paintOptions, bounds, currentLayoutRightToLeft); } } } } break; } case RunKind.Tab: { measuredWidth = MeasureTabScriptRun(scriptRun, GetParagraphStyleAssumingItHasAtLeastOneRun(scriptParagraph), xCurrentPosition, false); break; } default: throw new NotSupportedException(); } xCurrentPosition += measuredWidth; if (xCurrentPosition >= xEndPosition) break; // can't fit any more runs within the clip rectangle } }
/// <summary> /// Analyzes a <see cref="Paragraph" /> to populate a <see cref="ScriptParagraph"/>. /// </summary> private void AnalyzeParagraph(DeviceContext deviceContext, Paragraph* paragraph, ScriptParagraph* scriptParagraph) { int paragraphCharIndex = paragraph->CharIndex; int paragraphCharCount = paragraph->CharCount; scriptParagraph->CharIndex = paragraphCharIndex; scriptParagraph->CharCount = paragraphCharCount; scriptParagraph->ScriptRunCount = 0; scriptParagraph->GlyphCount = 0; Run* runZero = document.GetRunZero(); Run* runs = runZero + paragraph->RunIndex; int runCount = paragraph->RunCount; // Skip paragraphs with no runs (this is the sentinel at end of document) if (runCount == 0) { return; } // Handle paragraphs with exactly one empty run (simple newline) Debug.Assert(paragraphCharCount != 0, "A paragraph with at least one run should always have at least one character."); char* charZero = document.GetCharZero(); char* chars = charZero + paragraphCharIndex; // Step 1. Itemize the script items within the paragraph. // Each script item represents a chunk of text (such as a word) // for Unicode rendering purposes. scriptParagraph->EnsureCharCapacity(paragraphCharCount); SCRIPT_ITEM* tempScriptItems; int tempScriptItemCount; ScriptItemize(chars, paragraphCharCount, currentLayoutRightToLeft, tempScriptItemBuffer, out tempScriptItems, out tempScriptItemCount); // Step 2. Compute logical attributes for characters in the paragraph for word-break purposes. ScriptBreak(scriptParagraph, chars, tempScriptItems, tempScriptItemCount); // Step 3. Split the Runs on SCRIPT_ITEM boundaries to produce ScriptRuns. SplitScriptRuns(scriptParagraph, runs, runCount, tempScriptItems, tempScriptItemCount); // Step 4. Shape and Place glyphs and Measure embedded objects one run at a time. // We do this in linear order because it is simpler and we do not care about // visual order until later when we pack runs into lines and draw them. ScriptRun* scriptRun = scriptParagraph->ScriptRuns; ScriptRun* endScriptRun = scriptRun + scriptParagraph->ScriptRunCount; for (; scriptRun != endScriptRun; scriptRun++) { Style scriptRunStyle = document.LookupStyle(scriptRun->StyleIndex); switch (scriptRun->RunKind) { case RunKind.Text: { // Fill in basic metrics. ScriptMetrics scriptMetrics = deviceContext.SelectFont(scriptRunStyle.Font); scriptRun->Height = scriptMetrics.Height; scriptRun->Descent = scriptMetrics.Descent; scriptRun->TopMargin = 0; scriptRun->BottomMargin = 0; // Set glyphs. ScriptShapeAndPlace(deviceContext.HDC, ref scriptMetrics.ScriptCache, scriptParagraph, scriptRun, chars); break; } case RunKind.Object: { // No glyphs for an embedded object. scriptRun->GlyphCount = 0; scriptRun->GlyphIndexInParagraph = 0; // Fill in object measurements. int charIndex = scriptRun->CharIndexInParagraph + paragraphCharIndex; EmbeddedObjectHost embeddedObjectHost; EmbeddedObjectMeasurements embeddedObjectMeasurements; if (embeddedObjectHosts.TryGetValue(charIndex, out embeddedObjectHost)) embeddedObjectMeasurements = embeddedObjectHost.Measure(); else embeddedObjectMeasurements = EmbeddedObjectHost.CreateDefaultEmbeddedObjectMeasurements(); scriptRun->Height = embeddedObjectMeasurements.Size.Height; scriptRun->Descent = embeddedObjectMeasurements.Descent; scriptRun->TopMargin = embeddedObjectMeasurements.Margin.Top; scriptRun->BottomMargin = embeddedObjectMeasurements.Margin.Bottom; scriptRun->ABC.abcA = embeddedObjectMeasurements.Margin.Left; scriptRun->ABC.abcB = embeddedObjectMeasurements.Size.Width; scriptRun->ABC.abcC = embeddedObjectMeasurements.Margin.Right; // Change the logical attributes of the character so that an object behaves like // a word rather than whitespace when performing word wrap. scriptParagraph->CharLogicalAttributes[scriptRun->CharIndexInParagraph].SetfSoftBreakfCharStopAndfWordStop(); break; } case RunKind.Tab: { // No glyphs for a tab. scriptRun->GlyphCount = 0; scriptRun->GlyphIndexInParagraph = 0; // Use same metrics as inline text. ScriptMetrics scriptMetrics = deviceContext.SelectFont(scriptRunStyle.Font); scriptRun->Height = scriptMetrics.Height; scriptRun->Descent = scriptMetrics.Descent; scriptRun->TopMargin = 0; scriptRun->BottomMargin = 0; scriptRun->ABC.abcA = 0; scriptRun->ABC.abcB = -1; // will be filled in when the TAB is measured scriptRun->ABC.abcC = 0; // Change the logical attributes of the character to make it whitespace since Uniscribe // does not consider a TAB to be whitespace. scriptParagraph->CharLogicalAttributes[scriptRun->CharIndexInParagraph].SetfWhiteSpace(); break; } } } }