using FastReport.Utils; using System; using System.ComponentModel; using System.Drawing; using System.Text; namespace FastReport.Barcode { internal enum BarLineType { White, Black, // line with 2/5 height (used for PostNet) BlackHalf, // start/stop/middle lines in EAN, UPC codes BlackLong, // for Intelligent Mail Barcode // see https://upload.wikimedia.org/wikipedia/commons/7/7a/Four_State_Barcode.svg BlackTracker, BlackAscender, BlackDescender, } /// /// The base class for linear (1D) barcodes. /// public class LinearBarcodeBase : BarcodeBase { #region Fields private float wideBarRatio; private float[] modules; private bool calcCheckSum; private bool trim; internal RectangleF drawArea; internal RectangleF barArea; internal bool textUp; internal float ratioMin; internal float ratioMax; internal float extra1; internal float extra2; internal string pattern; #endregion #region Properties /// /// Gets or sets a value that determines if the barcode object should calculate /// the check digit automatically. /// [DefaultValue(true)] public bool CalcCheckSum { get { return calcCheckSum; } set { calcCheckSum = value; } } /// /// Gets or sets a relative width of wide bars in the barcode. /// [DefaultValue(2f)] public float WideBarRatio { get { return wideBarRatio; } set { wideBarRatio = value; if (ratioMin != 0 && wideBarRatio < ratioMin) wideBarRatio = ratioMin; if (ratioMax != 0 && wideBarRatio > ratioMax) wideBarRatio = ratioMax; } } /// /// Gets the value indicating that the barcode is numeric. /// [Browsable(false)] public virtual bool IsNumeric { get { return true; } } /// /// Gets or sets a value indicating that leading/trailing whitespaces must be trimmed. /// /// /// true if trim; otherwise, false. /// [DefaultValue(true)] public bool Trim { get { return trim; } set { trim = value; } } internal string Code { get { MakeModules(); pattern = GetPattern(); if (pattern == null) { MyRes res = new MyRes("Messages"); throw new FormatException(res.Get("BarcodeManyError")); } return pattern; } } #endregion #region Private Methods private void CheckText(string text) { foreach (char i in text) { if (i < '0' || i > '9') throw new Exception(Res.Get("Messages,InvalidBarcode2")); } } private void MakeModules() { modules[0] = 1; modules[1] = modules[0] * WideBarRatio; modules[2] = modules[1] * 1.5f; modules[3] = modules[1] * 2; } internal virtual void DoLines(string data, IGraphics g, float zoom) { using (Pen pen = new Pen(Color)) { float currentWidth = 0; foreach (char c in data) { float width; BarLineType lt; OneBarProps(c, out width, out lt); float heightStart = 0; float heightEnd = barArea.Height; if (lt == BarLineType.BlackHalf) { heightEnd = barArea.Height * 2 / 5; } else if (lt == BarLineType.BlackLong && showText) { heightEnd += 7; } else if (lt == BarLineType.BlackTracker) { heightStart = barArea.Height * 1 / 3; heightEnd = barArea.Height * 2 / 3; } else if (lt == BarLineType.BlackAscender) { heightEnd = barArea.Height * 2 / 3; } else if (lt == BarLineType.BlackDescender) { heightStart = barArea.Height * 1 / 3; } width *= zoom; heightStart *= zoom; heightEnd *= zoom; pen.Width = width; if (lt == BarLineType.BlackHalf) { g.DrawLine(pen, currentWidth + width / 2, barArea.Bottom * zoom, currentWidth + width / 2, barArea.Bottom * zoom - heightEnd); } else if (lt != BarLineType.White) { g.DrawLine(pen, currentWidth + width / 2, barArea.Top * zoom + heightStart, currentWidth + width / 2, barArea.Top * zoom + heightEnd); } currentWidth += width; } } } internal string CheckSumModulo10(string data) { int sum = 0; int fak = data.Length; for (int i = 0; i < data.Length; i++) { if ((fak % 2) == 0) sum += int.Parse(data[i].ToString()); else sum += int.Parse(data[i].ToString()) * 3; fak--; } if ((sum % 10) == 0) return data + "0"; else return data + (10 - (sum % 10)).ToString(); } private void OneBarProps(char code, out float width, out BarLineType lt) { switch (code) { case '0': width = modules[0]; lt = BarLineType.White; break; case '1': width = modules[1]; lt = BarLineType.White; break; case '2': width = modules[2]; lt = BarLineType.White; break; case '3': width = modules[3]; lt = BarLineType.White; break; case '5': width = modules[0]; lt = BarLineType.Black; break; case '6': width = modules[1]; lt = BarLineType.Black; break; case '7': width = modules[2]; lt = BarLineType.Black; break; case '8': width = modules[3]; lt = BarLineType.Black; break; case '9': width = modules[0]; lt = BarLineType.BlackHalf; break; case 'A': width = modules[0]; lt = BarLineType.BlackLong; break; case 'B': width = modules[1]; lt = BarLineType.BlackLong; break; case 'C': width = modules[2]; lt = BarLineType.BlackLong; break; case 'D': width = modules[3]; lt = BarLineType.BlackLong; break; // E,F,G for Intelligent Mail Barcode case 'E': width = modules[1]; lt = BarLineType.BlackTracker; break; case 'F': width = modules[1]; lt = BarLineType.BlackAscender; break; case 'G': width = modules[1]; lt = BarLineType.BlackDescender; break; default: // something went wrong :-( // mistyped pattern table throw new Exception("Incorrect barcode pattern code: " + code); } } #endregion #region Protected Methods internal float FontHeight => Font.Height * DrawUtils.ScreenDpiFX * 14 / 13; // 14/13 to be more or less compatible with old behavior (Arial,8 with hardcoded 14px height) internal int CharToInt(char c) { return int.Parse(Convert.ToString(c)); } internal string SetLen(int len) { string result = ""; for (int i = 0; i < len - text.Length; i++) { result = "0" + result; } result += text; return result.Substring(0, len); } internal string DoCheckSumming(string data) { return CheckSumModulo10(data); } internal string DoCheckSumming(string data, int len) { if (CalcCheckSum) return DoCheckSumming(SetLen(len - 1)); return SetLen(len); } internal string DoConvert(string s) { StringBuilder builder = new StringBuilder(s); for (int i = 0; i < s.Length; i++) { int v = s[i] - 1; if ((i % 2) == 0) v += 5; builder[i] = (char)v; } return builder.ToString(); } internal string MakeLong(string data) { StringBuilder builder = new StringBuilder(data); for (int i = 0; i < data.Length; i++) { char c = builder[i]; if (c >= '5' && c <= '8') c = (char)((int)c - (int)'5' + (int)'A'); builder[i] = c; } return builder.ToString(); } internal virtual float GetWidth(string code) { float result = 0; float w; BarLineType lt; foreach (char c in code) { OneBarProps(c, out w, out lt); result += w; } return result; } internal virtual string GetPattern() { return ""; } #endregion #region Public Methods /// public override void Assign(BarcodeBase source) { base.Assign(source); LinearBarcodeBase src = source as LinearBarcodeBase; WideBarRatio = src.WideBarRatio; CalcCheckSum = src.CalcCheckSum; Trim = src.Trim; } internal override void Serialize(FRWriter writer, string prefix, BarcodeBase diff) { base.Serialize(writer, prefix, diff); LinearBarcodeBase c = diff as LinearBarcodeBase; if (c == null || WideBarRatio != c.WideBarRatio) writer.WriteValue(prefix + "WideBarRatio", WideBarRatio); if (c == null || CalcCheckSum != c.CalcCheckSum) writer.WriteBool(prefix + "CalcCheckSum", CalcCheckSum); if (c == null || Trim != c.Trim) writer.WriteBool(prefix + "Trim", Trim); } internal override void Initialize(string text, bool showText, int angle, float zoom) { if (trim) text = text.Trim(); base.Initialize(text, showText, angle, zoom); } internal override SizeF CalcBounds() { float barWidth = GetWidth(Code); float extra1 = 0; float extra2 = 0; if (showText) { float txtWidth = 0; using (Bitmap bmp = new Bitmap(1, 1)) { bmp.SetResolution(96, 96); using (Graphics g = Graphics.FromImage(bmp)) { txtWidth = g.MeasureString(text, Font, 100000).Width; } } if (barWidth < txtWidth) { extra1 = (txtWidth - barWidth) / 2 + 2; extra2 = extra1; } } if (this.extra1 != 0) extra1 = this.extra1; if (this.extra2 != 0) extra2 = this.extra2; drawArea = new RectangleF(0, 0, barWidth + extra1 + extra2, 0); barArea = new RectangleF(extra1, 0, barWidth, 0); return new SizeF(drawArea.Width * 1.25f, 0); } /// public override void DrawBarcode(IGraphics g, RectangleF displayRect) { float originalWidth = CalcBounds().Width / 1.25f; float width = angle == 90 || angle == 270 ? displayRect.Height : displayRect.Width; float height = angle == 90 || angle == 270 ? displayRect.Width : displayRect.Height; zoom = width / originalWidth; barArea.Height = height / zoom; if (showText) { barArea.Height -= FontHeight; if (textUp) barArea.Y = FontHeight; } drawArea.Height = height / zoom; IGraphicsState state = g.Save(); try { // rotate g.TranslateTransform(displayRect.Left, displayRect.Top); g.RotateTransform(angle); switch (angle) { case 90: g.TranslateTransform(0, -displayRect.Width); break; case 180: g.TranslateTransform(-displayRect.Width, -displayRect.Height); break; case 270: g.TranslateTransform(-displayRect.Height, 0); break; } g.TranslateTransform(barArea.Left * zoom, 0); DoLines(pattern, g, zoom); if (showText) DrawText(g, text); } finally { g.Restore(state); } } internal void DrawString(IGraphics g, float x1, float x2, string s) { DrawString(g, x1, x2, s, false); } internal void DrawString(IGraphics g, float x1, float x2, string s, bool small) { if (String.IsNullOrEmpty(s)) return; // when we print, .Net automatically scales the font. However, we need to handle this process. // Downscale the font to the screen resolution, then scale by required value (Zoom). float fontZoom = FontHeight / (int)g.MeasureString(s, Font).Height * zoom; using (var drawFont = new Font(Font.FontFamily, (Font.Size - (small ? 2 : 0)) * fontZoom, Font.Style)) { SizeF size = g.MeasureString(s, drawFont); size.Width /= zoom; size.Height /= zoom; using (var brush = new SolidBrush(Color)) { g.DrawString(s, drawFont, brush, (x1 + (x2 - x1 - size.Width) / 2) * zoom, (textUp ? 0 : drawArea.Height - size.Height) * zoom); } } } internal virtual void DrawText(IGraphics g, string data) { data = StripControlCodes(data); DrawString(g, 0, drawArea.Width, data); } #endregion /// /// Initializes a new instance of the class with default settings. /// public LinearBarcodeBase() { modules = new float[4]; WideBarRatio = 2; calcCheckSum = true; trim = true; } } }