/// <summary> /// <para>Produces unmeasured display blocks from a single hybrid text. Marks highlights, if any.</para> /// <para>Does not fill in blocks' size, but fills in everything else.</para> /// </summary> /// <param name="htxt">Hybrid text to break down into blocks and measure.</param> /// <param name="isMeta">True if this is a domain or note (displayed in italics).</param> /// <param name="hl">Highlight to show in hybrid text, or null.</param> /// <param name="blocks">List of blocks to append to.</param> /// <param name="links">List to gather links (appending to list).</param> private void makeBlocks(HybridText htxt, bool isMeta, CedictTargetHighlight hl, List<Block> blocks, List<LinkArea> links) { byte fntIdxLatin = isMeta ? fntMetaLatin : fntSenseLatin; byte fntIdxZhoSimp = isMeta ? fntMetaHanziSimp : fntSenseHanziSimp; byte fntIdxZhoTrad = isMeta ? fntMetaHanziTrad : fntSenseHanziTrad; // Go run by run for (int runIX = 0; runIX != htxt.RunCount; ++runIX) { TextRun run = htxt.GetRunAt(runIX); // Latin run: split by spaces first if (run is TextRunLatin) { string[] bySpaces = run.GetPlainText().Split(new char[] { ' ' }); // Each word: also by dash int latnPos = 0; foreach (string str in bySpaces) { string[] byDashes = splitByDash(str); // Add block for each int ofsPos = 0; foreach (string blockStr in byDashes) { Block tb = new Block { TextPos = textPool.PoolString(blockStr), FontIdx = fntIdxLatin, SpaceAfter = false, // will set this true for last block in "byDashes" }; // Does block's text intersect with highlight? if (hl != null && hl.RunIx == runIX) { int blockStart = latnPos + ofsPos; int blockEnd = blockStart + blockStr.Length; if (blockStart >= hl.HiliteStart && blockStart < hl.HiliteStart + hl.HiliteLength) tb.Hilite = true; else if (blockEnd > hl.HiliteStart && blockEnd <= hl.HiliteStart + hl.HiliteLength) tb.Hilite = true; else if (blockStart < hl.HiliteStart && blockEnd >= hl.HiliteStart + hl.HiliteLength) tb.Hilite = true; } blocks.Add(tb); // Keep track of position for highlight ofsPos += blockStr.Length; } // Make sure last one is followed by space Block xb = blocks[blocks.Count - 1]; xb.SpaceAfter = true; blocks[blocks.Count - 1] = xb; // Keep track of position in text - for highlights latnPos += str.Length + 1; } } // Chinese: depends on T/S/Both display mode, and on available info else { TextRunZho zhoRun = run as TextRunZho; // Chinese range is made up of: // Simplified (empty string if only traditional requested) // Separator (if both simplified and traditional are requested) // Traditional (empty string if only simplified requested) // Pinyin with accents as tone marks, in brackets (if present) string strSimp = string.Empty; if (analyzedScript != SearchScript.Traditional && zhoRun.Simp != null) strSimp = zhoRun.Simp; string strTrad = string.Empty; if (analyzedScript != SearchScript.Simplified && zhoRun.Trad != null) strTrad = zhoRun.Trad; string strPy = string.Empty; // Convert pinyin to display format (tone marks as diacritics; r5 glued) if (zhoRun.Pinyin != null) strPy = "[" + zhoRun.GetPinyinInOne(true) + "]"; // Create link area, with query string string strPyNumbers = string.Empty; // Pinyin with numbers as tone marks if (zhoRun.Pinyin != null) strPyNumbers = zhoRun.GetPinyinRaw(); LinkArea linkArea = new LinkArea(strSimp, strTrad, strPyNumbers, analyzedScript); // Block for simplified, if present if (strSimp != string.Empty) { Block tb = new Block { TextPos = textPool.PoolString(strSimp), FontIdx = fntIdxZhoSimp, SpaceAfter = true, }; blocks.Add(tb); linkArea.BlockIds.Add(blocks.Count - 1); } // Separator if both simplified and traditional are there // AND they are different... if (strSimp != string.Empty && strTrad != string.Empty && strSimp != strTrad) { Block xb = blocks[blocks.Count - 1]; xb.StickRight = true; blocks[blocks.Count - 1] = xb; Block tb = new Block { TextPos = textPool.PoolString("•"), FontIdx = fntIdxLatin, SpaceAfter = true, }; blocks.Add(tb); linkArea.BlockIds.Add(blocks.Count - 1); } // Traditional, if present if (strTrad != string.Empty && strTrad != strSimp) { Block tb = new Block { TextPos = textPool.PoolString(strTrad), FontIdx = fntIdxZhoTrad, SpaceAfter = true, }; blocks.Add(tb); linkArea.BlockIds.Add(blocks.Count - 1); } // Pinyin, if present if (strPy != string.Empty) { // Split by spaces string[] pyParts = strPy.Split(new char[] { ' ' }); foreach (string pyPart in pyParts) { Block tb = new Block { TextPos = textPool.PoolString(pyPart), FontIdx = fntIdxLatin, SpaceAfter = true, }; blocks.Add(tb); linkArea.BlockIds.Add(blocks.Count - 1); } } // Last part will have requested a space after. // Look ahead and if next text run is Latin and starts with punctuation, make it stick TextRunLatin nextLatinRun = null; if (runIX + 1 < htxt.RunCount) nextLatinRun = htxt.GetRunAt(runIX + 1) as TextRunLatin; if (nextLatinRun != null && char.IsPunctuation(nextLatinRun.GetPlainText()[0])) { Block xb = blocks[blocks.Count - 1]; xb.SpaceAfter = false; blocks[blocks.Count - 1] = xb; } // Collect link area links.Add(linkArea); } } }
/// <summary> /// Breaks down body content into typographic blocks and caches the size of these. /// </summary> /// <param name="g">A Graphics object used for measurements.</param> private void doMeasureBlocks(Graphics g) { // Once measured, blocks don't change. Nothing to do then. if (measuredBlocks != null) return; // This is how we measure StringFormat sf = StringFormat.GenericTypographic; g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; // Decide about size of sense ID up front: that's always a square, letter-height SizeF xSize = g.MeasureString("x", getFont(fntSenseLatin), 65535, sf); ushort senseIdxWidth = (ushort)Math.Ceiling(xSize.Height); // Create array with as many items as senses // Each item is null, or highlight in sense's equiv CedictTargetHighlight[] hlArr = new CedictTargetHighlight[entry.SenseCount]; foreach (CedictTargetHighlight hl in res.TargetHilites) hlArr[hl.SenseIx] = hl; // Recreate list of blocks List<Block> newBlocks = new List<Block>(); // Collect links here. Will only keep at end if not empty. List<LinkArea> newLinks = new List<LinkArea>(); int senseIdx = -1; int displaySenseIdx = -1; bool lastWasClassifier = false; foreach (CedictSense cm in entry.Senses) { ++senseIdx; // Is this sense a classifier? bool classifier = cm.Domain.EqualsPlainText("CL:"); if (!classifier) ++displaySenseIdx; // Add one block for sense ID, unless this is a classifier "sense" if (!classifier) { Block sidBlock = new Block { Width = senseIdxWidth, StickRight = true, TextPos = textPool.PoolString(getSenseIdString(displaySenseIdx)), NewLine = lastWasClassifier, SenseId = true, FirstInCedictSense = true, }; newBlocks.Add(sidBlock); } // Split domain, equiv and note into typographic parts // Splits along spaces and dashes // Unpacks Chinese ranges // Domain is localized text for "Classifier:" if, well, this is a classifier sense int startIX = newBlocks.Count; if (!classifier) makeBlocks(cm.Domain, true, null, newBlocks, newLinks); else { string strClassifier = tprov.GetString("ResultCtrlClassifier"); HybridText htClassifier = new HybridText(strClassifier); int ix = newBlocks.Count; makeBlocks(htClassifier, true, null, newBlocks, newLinks); Block xb = newBlocks[ix]; xb.NewLine = true; newBlocks[ix] = xb; } makeBlocks(cm.Equiv, false, hlArr[senseIdx], newBlocks, newLinks); makeBlocks(cm.Note, true, null, newBlocks, newLinks); // If sense is a classifier, mark first block as sense starter if (classifier) { Block sstart = newBlocks[startIX]; sstart.FirstInCedictSense = true; newBlocks[startIX] = sstart; } // Measure each block for (int i = startIX; i != newBlocks.Count; ++i) { Block tb = newBlocks[i]; bool isHanzi = !(tb.FontIdx == fntMetaLatin || tb.FontIdx == fntSenseLatin); SizeF sz; if (!isHanzi) sz = g.MeasureString(textPool.GetString(tb.TextPos), getFont(tb.FontIdx), 65535, sf); else sz = HanziRenderer.MeasureString(g, Magic.ZhoContentFontFamily, textPool.GetString(tb.TextPos), Magic.LemmaHanziFontSize); tb.Width = (ushort)Math.Round(sz.Width); newBlocks[i] = tb; } lastWasClassifier = classifier; } if (newLinks.Count != 0) targetLinks = newLinks; measuredBlocks = newBlocks.ToArray(); }