public IEnumerable <NoForms.Common.Rectangle> HitTextRange(UTextGlyphingInfo ti, int start, int length, NoForms.Common.Point offset) { int cc = 0; foreach (var glyph in ti.glyphRuns) { // stride through glyphruns that are not involved. if (cc + glyph.run.runLength <= start) { cc += glyph.run.runLength; continue; } float cx = 0; foreach (var gchar in glyph.run.charSizes) { // end iteration when we fall past this bound if (cc >= start + length) { yield break; } // calculate and yield the current char rect yield return(new NoForms.Common.Rectangle(glyph.location.X + cx, glyph.location.Y + (glyph.run.runSize.height - gchar.height), gchar.width, gchar.height)); // inc counters for following glyphs cx += gchar.width; } } }
public NoForms.Common.Point HitText(UTextGlyphingInfo ti, int pos, bool trailing) { // Find the hit glyphrun int g, cc = 0; for (g = 0; g < ti.glyphRuns.Count; g++) { if ((cc += ti.glyphRuns[g].run.runLength) > pos) { break; } } if (g == ti.glyphRuns.Count) { g--; } Point retloc = new Point(0, 0); if (g > 0) { retloc = ti.glyphRuns[g].location; float cx = 0; int i = 0; cc -= ti.glyphRuns[g].run.runLength; // Get x-location of hit char in glyph for (i = 0; i < pos - cc; i++) { cx += ti.glyphRuns[g].run.charSizes[i].width; } if (i == ti.glyphRuns[g].run.charSizes.Length) { i--; } if (trailing) { cx -= ti.glyphRuns[g].run.charSizes[i].width; } // Get any y-offset of hit char in the glyph and return retloc += new Point(cx, ti.glyphRuns[g].run.runSize.height - ti.glyphRuns[g].run.charSizes[i].height); } // return retloc return(retloc); }
// WARNING this all assumes that char rects and lines are "tightly packed", except differing font sizes // on same line, which get "baselined". Is this what really happens? What about line,word and char spacing adjustments? // dirty way would be to adjust char rects, but does SDG do that? (does it even support that?) // Text Measuring - isText tells you if you actually hit a part of the string... public UTextHitInfo HitPoint(UTextGlyphingInfo ti, Point hitPoint) { int charPos = 0; // points float hx = hitPoint.X, cx; float hy = hitPoint.Y, cy; // Find the line we're hitting on int hitLine = 0; cy = 0; for (hitLine = 0; hitLine < ti.lineSizes.Count; hitLine++) { if (hy >= cy && hy < (cy += ti.lineSizes[hitLine].height)) { break; } charPos += ti.lineLengths[hitLine]; } // Get glyphruns on this line int sgr = -1, egr = -1; for (int gr = 0; gr < ti.glyphRuns.Count; gr++) { if (ti.glyphRuns[gr].lineNumber == hitLine) { if (sgr == -1) { sgr = gr; } else if (sgr != -1) { egr = gr; // WARNING this is first on next line (will be loopy on i<egr, so is ok) } } } // Find the glyphrun we're hitting on (in x direction) int hitGlyph = sgr; cx = 0; for (hitGlyph = sgr; hitGlyph < egr; hitGlyph++) { if (hx >= cx && hx < (cx += ti.glyphRuns[hitGlyph].run.runSize.width)) { break; } charPos += ti.glyphRuns[hitGlyph].run.content.Length; } cx -= ti.glyphRuns[hitGlyph].run.runSize.width; // reset to start of glyphrun hit // find the intersecting char rect (x direction) int hitGlyphChar; var hgr = ti.glyphRuns[hitGlyph].run; for (hitGlyphChar = 0; hitGlyphChar < hgr.charSizes.Length; hitGlyphChar++) { if (hx >= cx && hx < (cx += hgr.charSizes[hitGlyphChar].width)) { break; } } // we went all way to end...so come back if (hitGlyphChar == hgr.charSizes.Length) { hitGlyphChar--; } // add on the x inside this glyphrun charPos += hitGlyphChar; cx -= hgr.charSizes[hitGlyphChar].width; // reset to start of char. // determine if we've hit text, done simply by checking if we are inside the hit glyph Point charlocation = ti.glyphRuns[hitGlyph].location + new Point(cx, hgr.runSize.height - hgr.charSizes[hitGlyphChar].height); Rectangle charRect = new Rectangle(charlocation.X, charlocation.Y, hgr.charSizes[hitGlyphChar].width, hgr.charSizes[hitGlyphChar].height); bool isText = charRect.Contains(hitPoint); // Determine trailing or leading hit bool leading = hitPoint.X > cx + charRect.width / 2; return(new UTextHitInfo(charPos, leading, isText)); }
// FIXME Cache!! (most importantly) public UTextGlyphingInfo GetTextInfo(UText text) { // gotta do a line or end before we can decide the char tops (baseline aligned, presumably...) float currX = 0, currY = 0, maxGlyphHeight = 0; int lglst = 0; // last glyphrun line start int lastWordBreak = -1; // last wordbreaking glyph on the current line int currLine = 0; UTextGlyphingInfo ret = new UTextGlyphingInfo(); // begin on assumption we're top left align..then correct after UGlyphRun lastGr = null; foreach (var gr in GetGlyphRuns(text)) { // tracking max height for the line baselineing maxGlyphHeight = Math.Max(gr.runSize.height, maxGlyphHeight); // whichever way, this line is this many chars longer // Potential runs include words, spaces and linebreaks, and font breaks if (gr.breakingType == BreakType.line) {//LineBreaking // add the glyphrun, resolve info for the line, and begin on new line! ret.glyphRuns.Add(new UGlyphRunLayoutInfo() { lineNumber = currLine, location = new Point(currX, 0), // dunno about y position yet run = gr }); currY += maxGlyphHeight; maxGlyphHeight = 0; lastWordBreak = -1; float xAlighnShifty = GetShift(text.width, (currX + gr.runSize.width), text.halign); currX = 0; // resolving the glyphruns of this line do { var igr = ret.glyphRuns[lglst]; igr.location = new Point(igr.location.X + xAlighnShifty, currY - igr.run.runSize.height); } while (++lglst < ret.glyphRuns.Count); // begin a new line currLine++; } else if (lastWordBreak > -1 && currX + gr.runSize.width > text.width && text.wrapped) {// WordWrapping // Must define the concept of glyph groups here. Those between line and/or word breaks. // The whole of such a group must be broken. We need to define the breaking glyph. currY += maxGlyphHeight; maxGlyphHeight = 0; currX = 0; // #1 Is there a word break previous to this glyphrun on this line? // then put all glyphs following that on the next line. for (int i = lastWordBreak + 1; i < ret.glyphRuns.Count; i++) { ret.glyphRuns[i].lineNumber++; ret.glyphRuns[i].location = new Point(currX, 0); } lastWordBreak = -1; currLine++; // Is this glyphrun a wordbreak? who cares, next iteration will take care via #1. // Not wordbreak? no prev worbreak? who cares, carry on. ret.glyphRuns.Add(new UGlyphRunLayoutInfo() { lineNumber = currLine, location = new Point(currX, 0), // dunno about y position yet run = gr }); // resolving the glyphruns of the last line do { var igr = ret.glyphRuns[lglst]; igr.location = new Point(igr.location.X, currY - igr.run.runSize.height); } while (++lglst <= lastWordBreak); lastGr = gr; } else {// Buisness as Normal // add glyphrun, increment currX ret.glyphRuns.Add(new UGlyphRunLayoutInfo() { lineNumber = currLine, location = new Point(currX, 0), run = gr }); if (gr.breakingType == BreakType.word) { lastWordBreak = ret.glyphRuns.Count - 1; } currX += gr.runSize.width; } } currY += maxGlyphHeight; maxGlyphHeight = 0; lastWordBreak = -1; float lastXShift = GetShift(text.width, (currX + (lastGr == null ? 0 :lastGr.runSize.width)), text.halign); currX = 0; // resolving the glyphruns of the final line for (; lglst < ret.glyphRuns.Count; lglst++) { var igr = ret.glyphRuns[lglst]; igr.location.Y = currY - igr.run.runSize.height; igr.location.X += lastXShift; } float yAlignShifty = GetShift(text.height, currY, text.valign); // assign the linelengths to textinfo and do y alignment int cl = 0; int cc = 0; int cnl = 0; float cx = 0, cy = 0, maxy = 0, maxx = 0; for (int i = 0; i < ret.glyphRuns.Count; i++) { var gri = ret.glyphRuns[i]; gri.location.Y += yAlignShifty; if (gri.lineNumber > cl) { // add lineinfo (and reset counters) cl = gri.lineNumber; ret.lineLengths.Add(cc); ret.newLineLengths.Add(cl); ret.lineSizes.Add(new Size(cx, maxy)); cc = cnl = 0; cy += maxy; maxx = Math.Max(maxx, cx); maxy = cx = 0; } // Continue adding lineinfo data cc += gri.run.runLength; cx += gri.run.runSize.width; if (gri.run.breakingType == BreakType.line) { cnl += gri.run.runLength; } maxy = Math.Max(gri.run.runSize.height, maxy); } ret.minSize = new Size(maxx, cy); return(ret); }