private void DrawCurve(Graphics g, EditGlyph curves) { foreach (var contour in curves.Curves) { var points = contour.Render().Select(p => p.ToGdiPoint()).ToArray(); g.DrawPolygon(Pens.Black, points); } }
private void FillCurve(Graphics g, EditGlyph curves, FillMode fillMode) { foreach (var contour in curves.Curves) { var points = contour.Render().Select(p => p.ToGdiPoint()).ToArray(); g.FillPolygon(Brushes.Black, points, fillMode); } }
private void characterBox_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e) { if (e?.KeyCode == Keys.Enter || e?.KeyCode == Keys.Return) // update character selection { var normal = NormaliseText(characterBox?.Text); var chr = normal?.FirstOrDefault() ?? '\0'; _lastContourIndex = -1; _lastPointIndex = -1; _glyph = new EditGlyph(_font?.ReadGlyph(chr)); // TODO: chars other than the 1st shown as onion layers; } Invalidate(); }
public void RenderToImage(Bitmap bmp) { var targetWidth = bmp.Width; var targetHeight = bmp.Height; var sourceWidth = _page.Width.Point; var sourceHeight = _page.Height.Point; if (Min(targetWidth, targetHeight, sourceWidth, sourceHeight) < 1) { return; // Nothing to draw } using var g = Graphics.FromImage(bmp); g.Clear(Color.White); var sx = targetWidth / sourceWidth; var sy = targetHeight / sourceHeight; // These will need wrapped in a state object... PointF textPoint = new PointF(); Font defaultFont = new("Arial", 8); var pf = new PortableFont(defaultFont, g.DpiX); var skipText = false; //g.RenderingOrigin = new Point(0, targetHeight); //g.Transform = new Matrix(1,0,0,-1, 0, targetHeight); //g.ScaleTransform(2,2); //g.RotateTransform(45f); // Just for testing -- demo that we affect fonts // NOTE TO SELF: try flipping order of params -- it may be stack re-ordered. var currentCurve = new EditGlyph(); float mx = 0, my = 0; foreach (var c in _content) { if (c is not COperator op) { Console.WriteLine($"Unknown root element: {c.GetType().Name} - {c}"); continue; } switch (op.OpCode.OpCodeName) { case OpCodeName.Dictionary: break; case OpCodeName.b: //Close, fill, and stroke path using nonzero winding number rule. currentCurve.ClosePath(); FillCurve(g, currentCurve, FillMode.Winding); DrawCurve(g, currentCurve); currentCurve.Clear(); break; case OpCodeName.B: // Fill and stroke path using nonzero winding number rule. FillCurve(g, currentCurve, FillMode.Winding); DrawCurve(g, currentCurve); currentCurve.Clear(); break; case OpCodeName.bx: // Close, fill, and stroke path using even-odd rule. currentCurve.ClosePath(); FillCurve(g, currentCurve, FillMode.Alternate); DrawCurve(g, currentCurve); currentCurve.Clear(); break; case OpCodeName.Bx: // Fill and stroke path using even-odd rule. FillCurve(g, currentCurve, FillMode.Alternate); DrawCurve(g, currentCurve); currentCurve.Clear(); break; case OpCodeName.BDC: // (PDF 1.2) Begin marked-content sequence with property list. break; case OpCodeName.BI: // Begin inline image object. break; case OpCodeName.BMC: // (PDF 1.2) Begin marked-content sequence. break; case OpCodeName.BT: // Begin text object. textPoint = new PointF(mx, my); // ? break; case OpCodeName.BX: // (PDF 1.1) Begin compatibility section. break; case OpCodeName.c: // append curved segment to path (3 control points) { //currentCurve.GetOffset(out var x, out var y); var p = GetFloatArray(op, 6); currentCurve.AddPoint(mx + p[0], my + p[1], onCurve: false); currentCurve.AddPoint(mx + p[2], my + p[3], false);// this isn't exactly right as Contour uses TrueType logic. currentCurve.AddPoint(mx + p[4], my + p[5], true); } break; case OpCodeName.v: // Append curved segment to path (initial point replicated) case OpCodeName.y: // Append curved segment to path (final point replicated) { // These cases should be different, but we're treating them as the same while we are using the TrueType curve logic var p = GetFloatArray(op, 4); currentCurve.AddPoint(mx + p[0], my + p[1], onCurve: false); currentCurve.AddPoint(mx + p[2], my + p[3], true); } break; case OpCodeName.cm: // concatenate matrix to current transform matrix // TODO: this { var m = GetFloatArray(op, 6); mx = m[4]; my = m[5]; } break; case OpCodeName.CS: break; case OpCodeName.cs: break; case OpCodeName.d: break; case OpCodeName.d0: break; case OpCodeName.d1: break; case OpCodeName.Do: break; case OpCodeName.DP: break; case OpCodeName.EI: break; case OpCodeName.EMC: break; case OpCodeName.ET: break; case OpCodeName.EX: break; case OpCodeName.f: // Fill path using non-zero winding rule case OpCodeName.F: FillCurve(g, currentCurve, FillMode.Winding); currentCurve.Clear(); break; case OpCodeName.fx: break; case OpCodeName.G: break; case OpCodeName.g: break; case OpCodeName.gs: break; case OpCodeName.h: // Close sub-path currentCurve.ClosePath(); break; case OpCodeName.i: break; case OpCodeName.ID: break; case OpCodeName.j: break; case OpCodeName.J: break; case OpCodeName.K: // Set CMYK color for stroking operations // TODO: this break; case OpCodeName.k: // set CMYK color for non-stroking operations // TODO: this break; case OpCodeName.l: // line-to - append straight line segment to path { var x = GetNumber(op, 0) + mx; var y = GetNumber(op, 1) + my; currentCurve.AddPoint(x, y, true); } break; case OpCodeName.m: // move-to - begin new sub path { var x = GetNumber(op, 0) + mx; var y = GetNumber(op, 1) + my; currentCurve.StartCurve(); currentCurve.AddPoint(x, y, true); // ignore sub-paths } break; case OpCodeName.M: break; case OpCodeName.MP: break; case OpCodeName.n: // End path without filling or stroking (used for masks) currentCurve.ClosePath(); break; case OpCodeName.q: // Push graphics state Console.Write("q; "); break; case OpCodeName.Q: // Pop graphics state Console.Write("Q; "); currentCurve.Clear(); break; case OpCodeName.re: // Add rectangle to path { var r = GetFloatArray(op, 4); // x y width height g.DrawRectangle(Pens.Black, r[0], r[1], r[2], r[3]); } break; case OpCodeName.RG: // set RGB color for stroking operations // TODO: this break; case OpCodeName.rg: // set RGB color for non-stroking operations // TODO: this break; case OpCodeName.ri: break; case OpCodeName.s: // Close path and stroke currentCurve.ClosePath(); DrawCurve(g, currentCurve); currentCurve.Clear(); break; case OpCodeName.S: // Stroke path DrawCurve(g, currentCurve); currentCurve.Clear(); break; case OpCodeName.SC: break; case OpCodeName.sc: break; case OpCodeName.SCN: break; case OpCodeName.scn: break; case OpCodeName.sh: break; case OpCodeName.Tx: break; case OpCodeName.Tc: break; case OpCodeName.Td: // Move text position: Move to the start of the next line, offset by [0],[1] { Console.Write("Td; "); if (op.Operands.Count != 2) { throw new Exception("Unexpected op length in Td"); } var x = GetNumber(op, 0); var y = GetNumber(op, 1); textPoint = new PointF((float)x, textPoint.Y + (float)y + pf.GetLineHeight()); } break; case OpCodeName.TD: // Move text position and set leading Console.Write("TD; "); break; case OpCodeName.Tf: break; case OpCodeName.Tj: // Show text //if (skipText) break; if (op.Operands.Count != 1) { throw new Exception("Unexpected op length in Tj"); } var str = GetString(op, 0); var adv = g.MeasureString(str, defaultFont); g.DrawString(str, defaultFont, Brushes.Black, textPoint); textPoint = new PointF(textPoint.X + adv.Width, textPoint.Y); break; case OpCodeName.TJ: // Show text with glyph positioning break; case OpCodeName.TL: break; case OpCodeName.Tm: // Set text matrix and text line matrix { var m = GetFloatArray(op, 6); /* * [ [0] [1] 0 * [2] [3] 0 * [4] [5] 1 ] */ //This replaces (rather that concatenating) the existing matrix //g.MultiplyTransform(new Matrix(m[0],m[1], m[2],m[3], m[4],m[5])); textPoint = new PointF(m[4] + mx, m[5] + my); // this is just for testing //g.Transform = new Matrix(1,0,0,1, m[4], m[5]); //g.Transform = new Matrix(m[0],m[1], m[2],m[3], m[4],m[5]); Console.WriteLine($"Tm - {string.Join(",", m)} would be x={m[4]}, y={m[5]} ? ; "); Console.Write("Tm; "); } break; case OpCodeName.Tr: var mode = GetNumber(op, 0); //skipText = mode == 3; break; case OpCodeName.Ts: break; case OpCodeName.Tw: break; case OpCodeName.Tz: break; case OpCodeName.w: break; case OpCodeName.W: break; case OpCodeName.Wx: break; case OpCodeName.QuoteSingle: break; case OpCodeName.QuoteDbl: break; default: throw new ArgumentOutOfRangeException(); } } g.DrawLine(Pens.Black, 0, 0, 100, 150); g.DrawLine(Pens.Red, 0, 0, 150, 100); g.DrawLine(Pens.DarkCyan, 0, 0, 150, 150); }