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;
}
}
}