/// <summary> /// <para>Finds the right font size to fit characters (2x5 with default size, but it varies).</para> /// <para>Finds right vertical area based on font's actual display properties.</para> /// </summary> private void calibrateFont() { float width = Width; fontSize = 10.0F; // Measuring artefacts using (Bitmap bmp = new Bitmap(1, 1)) using (Graphics g = Graphics.FromImage(bmp)) { StringFormat sf = StringFormat.GenericTypographic; g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; // Keep growing font until we reach a comfortable width while (true) { //var xsi = HanziMeasure.Instance.GetMeasures(fontFace, fontSize); //charSize = xsi.RealRect.Size; charSize = HanziRenderer.GetCharSize(Magic.ZhoContentFontFamily, fontSize); if (charSize.Width * 5.0F >= width * 0.9F) { break; } fontSize += 0.5F; } } // Width of rectangle: using my space equally float rectWidth = (width - 2.0F) / 5.0F; // Height of rectange: depends on font's actual drawing behavior! float rectHeight = charSize.Height; // Horizontal padding is rectangle width minus measured char width, over two float hPad = (rectWidth - charSize.Width) / 2.0F; // Add twice horizontal padding to rectangle height; offset chars from top by padding rectHeight += 2.0F * hPad; lock (animLO) { for (int i = 0; i != 5; ++i) { float x = ((float)i) * rectWidth + 1.0F; RectangleF rtop = new RectangleF(x, 1.0F, rectWidth, rectHeight); RectangleF rbot = new RectangleF(x, rectHeight + 1.0F, rectWidth, rectHeight); charRects[i].Rect = rtop; charRects[i + 5].Rect = rbot; } } charOfsX = (rectWidth - charSize.Width) / 2.0F; charOfsY = (rectHeight - charSize.Height) / 2.0F; Height = (int)Math.Round((rectHeight) * 2.0F + 0.5F); MakeMePaint(false, RenderMode.Invalidate); }
/// <summary> /// Ctor: initializes main form. /// </summary> public MainForm(ICedictEngineFactory dictFact, ITextProvider tprov) : base(tprov) { this.tprov = tprov; // Initialize hanzi renderer // -- Scale (DPI) // -- Available systems fonts HanziRenderer.Scale = Scale; if (HanziRenderer.IsWinKaiAvailable()) { Magic.SetZhoContentFontFamily(IdeoFamily.WinKai); } else { Magic.SetZhoContentFontFamily(IdeoFamily.ArphicKai); } // Initialize system font provider with our own if (SystemFontProvider.Instance as ZydeoSystemFontProvider == null) { SystemFontProvider.Instance = new ZydeoSystemFontProvider(); } // Find out last window size and location from settings Size size = AppSettings.WindowLogicalSize; Point loc = AppSettings.WindowLoc; ignoredSavedSizeAndLocation = !verifySizeAndLoc(size, loc); // If location+size do not make sense, let system position window, and go with default size. if (ignoredSavedSizeAndLocation) { WinForm.StartPosition = FormStartPosition.WindowsDefaultLocation; LogicalSize = Magic.WinDefaultLogicalSize; } // Otherwise, position at last location else { WinForm.StartPosition = FormStartPosition.Manual; Location = loc; LogicalSize = size; } // Set (logical) minimum size LogicalMinimumSize = Magic.WinMinimumLogicalSize; Header = tprov.GetString("WinHeader"); lc = new LookupControl(this, dictFact, tprov); stgs = new SettingsControl(this, tprov, dictFact); MainTab = new ZenTab(stgs, tprov.GetString("TabMain")); Tabs.Add(new ZenTab(lc, tprov.GetString("TabLookup"))); }
/// <summary> /// Calculates right-aligned layout in headword area. /// </summary> private static bool doAnalyzeHanzi(Graphics g, string str, bool isSimp, StringFormat sf, List <HeadBlock> blocks, ref PointF loc, float right) { byte fntZhoHead = isSimp ? fntZhoHeadSimp : fntZhoHeadTrad; float left = loc.X; bool lineBreak = false; int firstCharOfLine = 0; int charsOnLine = 0; // Measure and position each character for (int i = 0; i != str.Length; ++i) { ++charsOnLine; // Measure each character. They may not all be hanzi: there are latin letters in some HWS HeadBlock hb = new HeadBlock { Char = str[i], Size = HanziRenderer.MeasureChar(g, Magic.ZhoContentFontFamily, str[i], Magic.ZhoResultFontSize), //Size = g.MeasureString(cstr, getFont(fntZhoHead), 65535, sf), Loc = loc, Faded = false, }; blocks.Add(hb); // Location moves right loc.X += hb.Size.Width; // If location is beyond headword's right edge, break line now. // This involves // - moving last added block to next line // - right-aligning blocks added so far if (loc.X > right) { lineBreak = true; loc.X = left; loc.Y += ideoLineHeight; doRightAlign(blocks, firstCharOfLine, charsOnLine - 1, right); charsOnLine = 1; firstCharOfLine = blocks.Count - 1; hb.Loc = loc; loc.X += hb.Size.Width; } } // Right-align the final bit doRightAlign(blocks, firstCharOfLine, charsOnLine, right); // Done - tell call if we had line break or not return(lineBreak); }
/// <summary> /// Loads dictionary in worker thread. /// </summary> /// <param name="ctxt"></param> private void loadDictionary(object ctxt) { Thread.Sleep(100); dict = dictFact.Create(Magic.DictFileName, HanziRenderer.GetFontCoverage(Magic.ZhoContentFontFamily)); }
/// <summary> /// Paints full control. Analyzes on demand, but meant to be called after analysis up front. /// </summary> public override void DoPaint(Graphics g) { // If size changed and we get a pain requested without having re-analized: // Analyze now. Not the best time here in paint, but must do. if (analyzedWidth != Width) { Analyze(g, Width); } // Background. Color bgcol = ZenParams.WindowColor; using (Brush b = new SolidBrush(bgcol)) { g.FillRectangle(b, 0, 0, Width, Height); } // Dotted line at bottom if (!last) { using (Pen p = new Pen(Magic.ResultsSeparator)) { float dp = (int)(1.3F * scale); int margin = (int)(8F * scale); p.DashPattern = new float[] { dp, dp }; p.Width = 1; g.SmoothingMode = SmoothingMode.None; g.DrawLine(p, margin, Height - 1, Width - margin, Height - 1); } } // Hanzi highlights. May draw on top, so must come before actual characters are drawn. doPaintHanziHilites(g, bgcol); // Target text highlights (characters will come on top). doPaintTargetHilites(g, bgcol); // This is how we draw text StringFormat sf = StringFormat.GenericTypographic; // Headword, pinyin, entry body using (Brush bnorm = new SolidBrush(Color.Black)) using (Brush bfade = new SolidBrush(Color.FromArgb(200, 200, 200))) using (Pen pnorm = new Pen(bnorm)) { // This works best for Hanzi g.TextRenderingHint = TextRenderingHint.AntiAlias; // Simplified and traditional - headword foreach (HeadBlock hb in headInfo.SimpBlocks) { PointF loc = new PointF(hb.Loc.X, hb.Loc.Y); HanziRenderer.DrawString(g, hb.Char.ToString(), loc, bnorm, Magic.ZhoContentFontFamily, IdeoScript.Simp, Magic.ZhoResultFontSize, FontStyle.Regular); } foreach (HeadBlock hb in headInfo.TradBlocks) { PointF loc = new PointF(hb.Loc.X, hb.Loc.Y); Brush b = hb.Faded ? bfade : bnorm; HanziRenderer.DrawString(g, hb.Char.ToString(), loc, b, Magic.ZhoContentFontFamily, IdeoScript.Trad, Magic.ZhoResultFontSize, FontStyle.Regular); } // Pinyin using (SolidBrush bhilite = new SolidBrush(Magic.HiliteColor)) using (SolidBrush bpinyin = new SolidBrush(Magic.PinyinColor)) { doPaintPinyin(g, bpinyin, bhilite, sf, bgcol); } // Target (body) doPaintTarget(g, pnorm, bnorm, sf); } }
/// <summary> /// Paints target (entry body). /// </summary> private void doPaintTarget(Graphics g, Pen pnorm, Brush bnorm, StringFormat sf) { // Take index of hovered-over sense once - this is atomic, and we're prolly in different thread from mouse short hsix = hoverSenseIx; Brush bhover = null; try { // All the measured and positioned blocks in entry body short currSenseIx = -1; foreach (PositionedBlock pb in positionedBlocks) { g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; Block block = measuredBlocks[pb.BlockIdx]; if (block.FirstInCedictSense) { ++currSenseIx; } bool hoverSense = currSenseIx == hsix; // Sense ID if (block.SenseId) { float pad = lemmaLineHeight * 0.1F; if (hoverSense) { using (Brush bh = new SolidBrush(Magic.SenseHoverColor)) { g.FillEllipse(bh, pb.LocX + 1F, pb.LocY + scale * pad + 1F, ((float)block.Width) - 2.0F * pad - 2F, lemmaCharHeight - 2.0F * pad - 2F); } } g.DrawEllipse(pnorm, pb.LocX, pb.LocY + scale * pad, ((float)block.Width) - 2.0F * pad, lemmaCharHeight - 2.0F * pad); float idOfsV = -2.896F + scale * 4.396F; g.DrawString(textPool.GetString(block.TextPos), getFont(fntSenseId), bnorm, pb.LocX + 2.0F * pad, pb.LocY + idOfsV, sf); // TO-DO: vertical paddig of character will need more work. } // Text else { // Extra vertical offset on Hanzi blocks float vOfs = 0; bool isHanzi = false; if (block.FontIdx == fntMetaHanziSimp || block.FontIdx == fntMetaHanziTrad || block.FontIdx == fntSenseHanziSimp || block.FontIdx == fntSenseHanziTrad) { vOfs += getTargetHanziOfs(); isHanzi = true; } // No hover: draw with normal brush Brush brush = bnorm; // Hover: create hover brush on demand; draw with that if (hoverLink != null) { if (hoverLink.BlockIds.Contains(pb.BlockIdx)) { if (bhover == null) { bhover = new SolidBrush(Magic.LinkHoverColor); } brush = bhover; } } if (isHanzi) { g.TextRenderingHint = TextRenderingHint.AntiAlias; } else { g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; } // Not a hanzi range (by font ID) if (block.FontIdx == fntSenseLatin || block.FontIdx == fntMetaLatin) { g.DrawString(textPool.GetString(block.TextPos), getFont(block.FontIdx), brush, pb.LocX, pb.LocY, sf); } // Hanzi range else { IdeoScript script = block.FontIdx == fntMetaHanziSimp || block.FontIdx == fntSenseHanziSimp ? IdeoScript.Simp : IdeoScript.Trad; bool meta = block.FontIdx == fntMetaHanziSimp || block.FontIdx == fntMetaHanziTrad; FontStyle fntStyle = meta ? FontStyle.Italic : FontStyle.Regular; HanziRenderer.DrawString(g, textPool.GetString(block.TextPos), new PointF(pb.LocX, pb.LocY + vOfs), brush, Magic.ZhoContentFontFamily, script, Magic.LemmaHanziFontSize, fntStyle); } } } } finally { if (bhover != null) { bhover.Dispose(); } } }
/// <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(); }
/// <summary> /// Analyzes layout of headword. /// </summary> /// <param name="g">A Graphics object used for measurements.</param> private void doAnalyzeHeadword(Graphics g) { // If already analyzed, nothing to do if (headInfo != null) { return; } // This is how we measure StringFormat sf = StringFormat.GenericTypographic; g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; // On-demand: measure a single ideograph's dimensions // We only measure simplified. Assume simplified and traditional fonts come in matching pairs -> same size. if (ideoSize.Width == 0) { ideoSize = HanziRenderer.GetCharSize(Magic.ZhoContentFontFamily, Magic.ZhoResultFontSize); float hanziLinePad = 6.0F; hanziLinePad *= scale; ideoLineHeight = ideoSize.Height + hanziLinePad; } headInfo = new HeadInfo(); if (analyzedScript == SearchScript.Simplified) { headInfo.HeadMode = HeadMode.OnlySimp; } else if (analyzedScript == SearchScript.Traditional) { headInfo.HeadMode = HeadMode.OnlyTrad; } else { headInfo.HeadMode = HeadMode.BothSingleLine; } //// For width of headword, use padLeft from border, plus 4 to 6 ideographs' worth of space //// Depending on longest headword in entire list //int hwChars = maxHeadLength; //if (hwChars < 4) hwChars = 4; //if (hwChars > 6) hwChars = 6; //float hwidth = ((float)hwChars) * ideoSize.Width; // Revised // For width of headword, always take 5 characters' width of space float hwidth = 5.0F * ideoSize.Width; headInfo.HeadwordRight = padLeft + hwidth; // Measure simplified chars from start; break when needed PointF loc = new PointF(((float)padLeft) + ideoSize.Width, padTop); bool lbrk = false; if (analyzedScript == SearchScript.Simplified || analyzedScript == SearchScript.Both) { lbrk |= doAnalyzeHanzi(g, entry.ChSimpl, true, sf, headInfo.SimpBlocks, ref loc, headInfo.HeadwordRight); } if (analyzedScript == SearchScript.Traditional || analyzedScript == SearchScript.Both) { //loc.X = padLeft; loc.X = ((float)padLeft) + ideoSize.Width; if (analyzedScript == SearchScript.Both) { loc.Y += ideoLineHeight; } lbrk |= doAnalyzeHanzi(g, entry.ChTrad, false, sf, headInfo.TradBlocks, ref loc, headInfo.HeadwordRight); } // If we're displaying both simplified and traditional, fade out // traditional chars that are same as simplified, right above them if (analyzedScript == SearchScript.Both) { for (int i = 0; i != headInfo.SimpBlocks.Count; ++i) { if (headInfo.SimpBlocks[i].Char == headInfo.TradBlocks[i].Char) { headInfo.TradBlocks[i].Faded = true; } } } // Bottom of headword area headInfo.HeadwordBottom = loc.Y + ideoLineHeight; // If we had a line break and we're showing both scripts, update info if (analyzedScript == SearchScript.Both && lbrk) { headInfo.HeadMode = HeadMode.BothMultiLine; } }
/// <summary> /// Paints the control. /// </summary> public override void DoPaint(System.Drawing.Graphics g) { // Get character rectangles CharRect[] rects = getCharRects(); // Background using (Brush b = new SolidBrush(ZenParams.WindowColor)) { g.FillRectangle(b, 0, 0, Width, Height); } // Characters if (items != null) { StringFormat sf = StringFormat.GenericTypographic; g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; using (Brush b = new SolidBrush(Color.Black)) { for (int i = 0; i != rects.Length; ++i) { RectangleF rect = rects[i].Rect; // Background using (Brush bgb = new SolidBrush(rects[i].BgColor)) { g.FillRectangle(bgb, rect); } // Draw character, if any if (i >= items.Length) { continue; } string str = ""; str += items[i]; HanziRenderer.DrawString(g, str, new PointF(rect.X + charOfsX, rect.Y + charOfsY), b, fontFam, fontScript, fontSize, FontStyle.Regular); } } } // Error message else { StringFormat sf = StringFormat.GenericDefault; g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; string line1 = tprov.GetString("CharPickerError1"); string line2 = tprov.GetString("CharPickerError2"); float fontSize = Magic.CharPickerErrorFontSize; using (Font f1 = SystemFontProvider.Instance.GetSystemFont(FontStyle.Bold, fontSize)) using (Font f2 = SystemFontProvider.Instance.GetSystemFont(FontStyle.Regular, fontSize)) using (Brush b = new SolidBrush(Color.Black)) { float padL = 10F * Scale; float padR = 10F * Scale; SizeF sz1 = g.MeasureString(line1, f1, (int)(Width - padL - padR), sf); SizeF sz2 = g.MeasureString(line2, f2, (int)(Width - padL - padR), sf); float hblock = sz1.Height + sz2.Height; PointF pt1 = new PointF(padL, (((float)Height - 2) - hblock) / 2F); PointF pt2 = new PointF(pt1.X, pt1.Y + sz1.Height); g.DrawString(line1, f1, b, pt1); g.DrawString(line2, f2, b, new RectangleF(pt2.X, pt2.Y, Width - pt2.X - 1, Height - pt2.Y - 1)); } } // Border using (Pen p = new Pen(ZenParams.BorderColor)) { g.DrawLine(p, 0, 0, Width, 0); g.DrawLine(p, Width - 1, 0, Width - 1, Height); g.DrawLine(p, Width - 1, Height - 1, 0, Height - 1); g.DrawLine(p, 0, Height - 1, 0, 0); } }