diff --git a/README.md b/README.md
index bdebc93..8985b49 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,39 @@ CustomerBarCode code = BarCode.Create("1000013", "東京都千代田区霞が関
ただしこの場合は住所全体を住所Bとして扱うため、町域名に「丁目・番・ノ」などの前の漢数字を含む住所(一番町、八丁堀、三ノ輪 など)では、町域名の漢数字も抜き出されて誤ったバーコードになります。住所A・住所Bに分けて渡すことをおすすめします。
+### バーコードの内容
+```csharp
+string zip = code.ZipCode; // "1000013"
+string number = code.AddressNumber; // "1-3-2-503"(マニュアルの「バーコード・データ」)
+bool rep = code.IsRepresentativeZipCode; // 代表の郵便番号か
+```
+
+`AddressNumber` は 13 けたで切り捨てる前の値です。バーコードに含まれるのは先頭 13 けた分です。
+
+代表の郵便番号(郵便番号簿の「以下に掲載がない場合」の番号)の郵便物には、カスタマバーコードを付けないことになっています([マニュアル p18](https://www.post.japanpost.jp/zipcode/zipmanual/p18.html))。`IsRepresentativeZipCode` は、マニュアルの「原則として下2けたが 00」に従って判定します。
+
+### 印刷用の実寸
+[マニュアル p12](https://www.post.japanpost.jp/zipcode/zipmanual/p12.html) の寸法(10 ポイントのときバー幅 0.6mm、バーピッチ 1.2mm、ロングバー 3.6mm)で出力します。大きさは 8〜11.5 ポイントで指定でき、省略時は 10 ポイントです。
+
+```csharp
+// mm 単位の SVG(余白は省略時 2mm)
+string svg = code.ToPrintSvg(point: 10, margin: 2);
+
+// System.Drawing: 宛名印刷(PrintDocument など)で、用紙の (x, y) mm の位置に描画
+code.Draw(e.Graphics, 20, 30);
+
+// SkiaSharp: PDF の (x, y) mm の位置に描画
+code.Draw(canvas, 20, 30, CustomerBarCodeSkiaExtensions.PdfUnitsPerMillimeter);
+
+// 独自に描画する場合は mm 単位のバー配置を使う
+foreach (var bar in code.GetPrintBars())
+{
+ // bar.X, bar.Y, bar.Width, bar.Height (mm)
+}
+```
+
+マニュアル([p13](https://www.post.japanpost.jp/zipcode/zipmanual/p13.html))では、バーコードの上下左右に 2mm 以上の空白が必要です。`Draw` はバーだけを描くので、周囲を空けて配置してください。
+
### SVG(依存ライブラリなし)
```csharp
string svg = code.ToSvg();
diff --git a/ZipConvertCustomerBarCode.Drawing/CustomerBarCodeDrawingExtensions.cs b/ZipConvertCustomerBarCode.Drawing/CustomerBarCodeDrawingExtensions.cs
index e7ef546..fad6b39 100644
--- a/ZipConvertCustomerBarCode.Drawing/CustomerBarCodeDrawingExtensions.cs
+++ b/ZipConvertCustomerBarCode.Drawing/CustomerBarCodeDrawingExtensions.cs
@@ -54,5 +54,42 @@ public static void Save(this CustomerBarCode code, string outputPath, ImageForma
img.Save(outputPath, format);
}
}
+
+ ///
+ /// 印刷用の実寸(mm)でカスタマバーコードを描画する
+ /// 宛名印刷(PrintDocument など)で、用紙の指定位置に描画するときに使う
+ ///
+ ///
+ /// バーのみを黒で描画する(背景は描画しない)。
+ /// マニュアルでは上下左右に2mm以上の空白が必要。
+ ///
+ /// カスタマバーコード
+ /// 描画先
+ /// 最初のバーの左端のX座標(mm)
+ /// バーの上端のY座標(mm)
+ /// 大きさ(8~11.5ポイント)
+ public static void Draw(this CustomerBarCode code, Graphics graphics, float x, float y, double point = 10)
+ {
+ if (code == null) throw new ArgumentNullException(nameof(code));
+ if (graphics == null) throw new ArgumentNullException(nameof(graphics));
+
+ var bars = code.GetPrintBars(point);
+ var pageUnit = graphics.PageUnit;
+ var pageScale = graphics.PageScale;
+ try
+ {
+ graphics.PageUnit = GraphicsUnit.Millimeter;
+ graphics.PageScale = 1;
+ foreach (var bar in bars)
+ {
+ graphics.FillRectangle(Brushes.Black, x + (float)bar.X, y + (float)bar.Y, (float)bar.Width, (float)bar.Height);
+ }
+ }
+ finally
+ {
+ graphics.PageUnit = pageUnit;
+ graphics.PageScale = pageScale;
+ }
+ }
}
}
diff --git a/ZipConvertCustomerBarCode.SkiaSharp/CustomerBarCodeSkiaExtensions.cs b/ZipConvertCustomerBarCode.SkiaSharp/CustomerBarCodeSkiaExtensions.cs
index 19a977b..be4e7ed 100644
--- a/ZipConvertCustomerBarCode.SkiaSharp/CustomerBarCodeSkiaExtensions.cs
+++ b/ZipConvertCustomerBarCode.SkiaSharp/CustomerBarCodeSkiaExtensions.cs
@@ -66,5 +66,44 @@ public static void SavePng(this CustomerBarCode code, string outputPath, int sca
{
File.WriteAllBytes(outputPath, code.ToPng(scale));
}
+
+ ///
+ /// PDF(SKDocument.CreatePdf)のキャンバスで1mmあたりの単位数(1単位 = 1/72インチ)
+ ///
+ public const float PdfUnitsPerMillimeter = 72f / 25.4f;
+
+ ///
+ /// 印刷用の実寸(mm)でカスタマバーコードを描画する
+ /// PDF などに、用紙の指定位置に描画するときに使う
+ ///
+ ///
+ /// バーのみを黒で描画する(背景は描画しない)。
+ /// マニュアルでは上下左右に2mm以上の空白が必要。
+ ///
+ /// カスタマバーコード
+ /// 描画先
+ /// 最初のバーの左端のX座標(mm)
+ /// バーの上端のY座標(mm)
+ /// キャンバスの1mmあたりの単位数(PDF は )
+ /// 大きさ(8~11.5ポイント)
+ public static void Draw(this CustomerBarCode code, SKCanvas canvas, float x, float y, float unitsPerMillimeter, double point = 10)
+ {
+ if (code == null) throw new ArgumentNullException(nameof(code));
+ if (canvas == null) throw new ArgumentNullException(nameof(canvas));
+ if (!(unitsPerMillimeter > 0)) throw new ArgumentOutOfRangeException(nameof(unitsPerMillimeter), "1mmあたりの単位数は0より大きい値で指定してください。");
+
+ var bars = code.GetPrintBars(point);
+ using (var paint = new SKPaint { Color = SKColors.Black, IsAntialias = false })
+ {
+ foreach (var bar in bars)
+ {
+ canvas.DrawRect(SKRect.Create(
+ (x + (float)bar.X) * unitsPerMillimeter,
+ (y + (float)bar.Y) * unitsPerMillimeter,
+ (float)bar.Width * unitsPerMillimeter,
+ (float)bar.Height * unitsPerMillimeter), paint);
+ }
+ }
+ }
}
}
diff --git a/ZipConvertCustomerBarCode.Tests/BarCodeTests.cs b/ZipConvertCustomerBarCode.Tests/BarCodeTests.cs
index e7bc4b9..58b301d 100644
--- a/ZipConvertCustomerBarCode.Tests/BarCodeTests.cs
+++ b/ZipConvertCustomerBarCode.Tests/BarCodeTests.cs
@@ -93,6 +93,41 @@ public class BarCodeTests
Assert.Throws(() => BarCode.Create("0140113", null!));
}
+ [Theory]
+ [InlineData("100-0013", "1000013")]
+ [InlineData("100-0013", "1000013")]
+ public void 郵便番号は半角数字7桁で取得できる(string zipCode, string expected)
+ {
+ var code = BarCode.Create(zipCode, "1-3-2");
+
+ Assert.Equal(expected, code.ZipCode);
+ }
+
+ [Theory]
+ [InlineData("東京都千代田区霞が関", "1丁目3番2号 郵便プラザ503室", "1-3-2-503")]
+ [InlineData("千葉県鎌ヶ谷市右京塚", "東3丁目-20-5 郵便・A&bコーポB604号", "3-20-5B604")]
+ // 13けたを超えても切り捨てない
+ [InlineData("札幌市中央区南四条西", "29丁目1524-23 第2郵便ハウス501", "29-1524-23-2-501")]
+ [InlineData("とある町", "", "")]
+ public void 住所表示番号を取得できる(string townAddress, string blockAddress, string expected)
+ {
+ var code = BarCode.Create("1234567", townAddress, blockAddress);
+
+ Assert.Equal(expected, code.AddressNumber);
+ }
+
+ [Theory]
+ [InlineData("5250000", true)]
+ [InlineData("3510100", true)]
+ [InlineData("1000013", false)]
+ [InlineData("1000010", false)]
+ public void 代表の郵便番号を判定できる(string zipCode, bool expected)
+ {
+ var code = BarCode.Create(zipCode, "1-1");
+
+ Assert.Equal(expected, code.IsRepresentativeZipCode);
+ }
+
[Fact]
public void 常に23シンボルで開始コードと終了コードで挟まれる()
{
diff --git a/ZipConvertCustomerBarCode.Tests/DrawingRenderTests.cs b/ZipConvertCustomerBarCode.Tests/DrawingRenderTests.cs
index 73d9444..46dfbd9 100644
--- a/ZipConvertCustomerBarCode.Tests/DrawingRenderTests.cs
+++ b/ZipConvertCustomerBarCode.Tests/DrawingRenderTests.cs
@@ -43,6 +43,28 @@ public DrawingRenderTests()
}
}
+ [Theory]
+ [InlineData(GraphicsUnit.Pixel)]
+ [InlineData(GraphicsUnit.Inch)]
+ public void 実寸で描画できる(GraphicsUnit pageUnit)
+ {
+ var code = BarCode.Create("1000013", "東京都千代田区霞が関", "1丁目3番2号 郵便プラザ503室");
+ using var img = new Bitmap(1000, 80);
+ // 254dpi = 10ピクセル/mm
+ img.SetResolution(254, 254);
+ using (var gr = Graphics.FromImage(img))
+ {
+ gr.Clear(Color.White);
+ gr.PageUnit = pageUnit;
+ code.Draw(gr, 1, 1);
+
+ // 描画後は元の単位に戻る
+ Assert.Equal(pageUnit, gr.PageUnit);
+ }
+
+ PrintAssert.Bars(code, (x, y) => img.GetPixel(x, y).ToArgb() == Color.Black.ToArgb(), 10, 1);
+ }
+
private static void AssertSamePixels(Bitmap expected, Bitmap actual)
{
Assert.Equal(expected.Width, actual.Width);
diff --git a/ZipConvertCustomerBarCode.Tests/PrintAssert.cs b/ZipConvertCustomerBarCode.Tests/PrintAssert.cs
new file mode 100644
index 0000000..8eff0ea
--- /dev/null
+++ b/ZipConvertCustomerBarCode.Tests/PrintAssert.cs
@@ -0,0 +1,41 @@
+namespace ZipConvertCustomerBarCode.Tests
+{
+ public static class PrintAssert
+ {
+ ///
+ /// 実寸で描画した画像に、バーが正しい位置・形で描かれていることを確認する
+ /// バーの中心が黒、バーの間とバーのない段が白であることを点で調べる
+ ///
+ /// カスタマバーコード
+ /// ピクセルが黒かどうか
+ /// 1mmあたりのピクセル数
+ /// 描画位置(X、Y共通、mm)
+ public static void Bars(CustomerBarCode code, Func isBlack, double pixelsPerMillimeter, double originMillimeters)
+ {
+ bool At(double x, double y) =>
+ isBlack((int)((originMillimeters + x) * pixelsPerMillimeter), (int)((originMillimeters + y) * pixelsPerMillimeter));
+
+ var bars = code.GetPrintBars().ToList();
+ for (int i = 0; i < bars.Count; i++)
+ {
+ var bar = bars[i];
+ var centerX = bar.X + bar.Width / 2;
+ Assert.True(At(centerX, bar.Y + bar.Height / 2), $"{i}本目のバーが描画されていない");
+
+ // 上段(0~1.2mm)、下段(2.4~3.6mm)
+ var hasTop = bar.Type == BarType.Long || bar.Type == BarType.Ascender;
+ var hasBottom = bar.Type == BarType.Long || bar.Type == BarType.Descender;
+ Assert.Equal(hasTop, At(centerX, 0.6));
+ Assert.Equal(hasBottom, At(centerX, 3.0));
+
+ // バーの間(スペース0.6mm)
+ if (i < bars.Count - 1)
+ {
+ Assert.False(At(bar.X + 0.9, 1.8), $"{i}本目のバーの右が空いていない");
+ }
+ }
+ // 描画位置より左上は空白
+ Assert.False(At(-0.3, 1.8));
+ }
+ }
+}
diff --git a/ZipConvertCustomerBarCode.Tests/PrintTests.cs b/ZipConvertCustomerBarCode.Tests/PrintTests.cs
new file mode 100644
index 0000000..e941153
--- /dev/null
+++ b/ZipConvertCustomerBarCode.Tests/PrintTests.cs
@@ -0,0 +1,93 @@
+using System.Text.RegularExpressions;
+
+namespace ZipConvertCustomerBarCode.Tests
+{
+ // 印刷用の実寸
+ // http://www.post.japanpost.jp/zipcode/zipmanual/p12.html
+ public class PrintTests
+ {
+ private readonly CustomerBarCode code = BarCode.Create("1000013", "東京都千代田区霞が関", "1丁目3番2号 郵便プラザ503室");
+
+ [Theory]
+ // バー67本: (67 - 1) × バーピッチ + バー幅
+ [InlineData(10, 79.8, 3.6)]
+ [InlineData(8, 63.84, 2.88)]
+ [InlineData(11.5, 91.77, 4.14)]
+ public void バーコードの大きさはポイントに比例する(double point, double width, double height)
+ {
+ Assert.Equal(width, code.GetPrintWidth(point), 6);
+ Assert.Equal(height, code.GetPrintHeight(point), 6);
+ }
+
+ [Theory]
+ [InlineData(7.9)]
+ [InlineData(11.6)]
+ [InlineData(double.NaN)]
+ public void ポイントが8から11点5の範囲外なら例外(double point)
+ {
+ Assert.Throws(() => code.GetPrintWidth(point));
+ Assert.Throws(() => code.GetPrintHeight(point));
+ Assert.Throws(() => code.GetPrintBars(point));
+ Assert.Throws(() => code.ToPrintSvg(point));
+ }
+
+ [Fact]
+ public void バーの寸法はマニュアルどおり()
+ {
+ var bars = code.GetPrintBars().ToList();
+
+ Assert.Equal(67, bars.Count);
+ Assert.Equal(0, bars[0].X, 6);
+ for (int i = 1; i < bars.Count; i++)
+ {
+ // バーピッチ 1.2mm
+ Assert.Equal(1.2, bars[i].X - bars[i - 1].X, 6);
+ }
+ Assert.All(bars, bar =>
+ {
+ // バー幅 0.6mm
+ Assert.Equal(0.6, bar.Width, 6);
+ // ロングバー 3.6mm、セミロングバー 2.4mm、タイミングバー 1.2mm
+ var (y, height) = bar.Type switch
+ {
+ BarType.Long => (0.0, 3.6),
+ BarType.Ascender => (0.0, 2.4),
+ BarType.Descender => (1.2, 2.4),
+ _ => (1.2, 1.2),
+ };
+ Assert.Equal(y, bar.Y, 6);
+ Assert.Equal(height, bar.Height, 6);
+ });
+ }
+
+ [Fact]
+ public void 印刷用のバーはピクセル単位のバーと同じ種類と並び()
+ {
+ Assert.Equal(code.GetBars().Select(x => x.Type), code.GetPrintBars().Select(x => x.Type));
+ }
+
+ [Fact]
+ public void 印刷用SVGはmm単位で余白を含む()
+ {
+ var svg = code.ToPrintSvg(10, 2);
+
+ Assert.StartsWith("", svg);
+
+ var rects = Regex.Matches(svg, "").ToList();
+ Assert.Equal(67, rects.Count);
+ // 最初のバーは余白(2mm)の位置から
+ Assert.Equal("2", rects[0].Groups[1].Value);
+ Assert.Equal("2", rects[0].Groups[2].Value);
+ Assert.Equal("0.6", rects[0].Groups[3].Value);
+ Assert.Equal("3.6", rects[0].Groups[4].Value);
+ }
+
+ [Fact]
+ public void 余白が負なら例外()
+ {
+ Assert.Throws(() => code.ToPrintSvg(10, -1));
+ Assert.Throws(() => code.ToPrintSvg(10, double.NaN));
+ }
+ }
+}
diff --git a/ZipConvertCustomerBarCode.Tests/SkiaRenderTests.cs b/ZipConvertCustomerBarCode.Tests/SkiaRenderTests.cs
index 4c53770..3f2445c 100644
--- a/ZipConvertCustomerBarCode.Tests/SkiaRenderTests.cs
+++ b/ZipConvertCustomerBarCode.Tests/SkiaRenderTests.cs
@@ -44,6 +44,49 @@ public class SkiaRenderTests
}
}
+ [Fact]
+ public void 実寸で描画できる()
+ {
+ var code = BarCode.Create("1000013", "東京都千代田区霞が関", "1丁目3番2号 郵便プラザ503室");
+ using var img = new SKBitmap(1000, 80);
+ using (var canvas = new SKCanvas(img))
+ {
+ canvas.Clear(SKColors.White);
+ // 10ピクセル/mm
+ code.Draw(canvas, 1, 1, 10);
+ }
+
+ PrintAssert.Bars(code, (x, y) => img.GetPixel(x, y) == SKColors.Black, 10, 1);
+ }
+
+ [Fact]
+ public void PDFに描画できる()
+ {
+ var code = BarCode.Create("1000013", "東京都千代田区霞が関", "1丁目3番2号 郵便プラザ503室");
+ using var stream = new MemoryStream();
+ using (var document = SKDocument.CreatePdf(stream))
+ {
+ // A4
+ var canvas = document.BeginPage(210 * CustomerBarCodeSkiaExtensions.PdfUnitsPerMillimeter, 297 * CustomerBarCodeSkiaExtensions.PdfUnitsPerMillimeter);
+ code.Draw(canvas, 20, 20, CustomerBarCodeSkiaExtensions.PdfUnitsPerMillimeter);
+ document.EndPage();
+ }
+
+ Assert.True(stream.Length > 0);
+ Assert.Equal("%PDF", System.Text.Encoding.ASCII.GetString(stream.ToArray(), 0, 4));
+ }
+
+ [Fact]
+ public void 単位数が0以下なら例外()
+ {
+ var code = BarCode.Create("1000013", "1-3-2");
+ using var img = new SKBitmap(10, 10);
+ using var canvas = new SKCanvas(img);
+
+ Assert.Throws(() => code.Draw(canvas, 0, 0, 0));
+ Assert.Throws(() => code.Draw(canvas, 0, 0, float.NaN));
+ }
+
private static void AssertSamePixels(SKBitmap expected, SKBitmap actual)
{
Assert.Equal(expected.Width, actual.Width);
diff --git a/ZipConvertCustomerBarCode/Bar.cs b/ZipConvertCustomerBarCode/Bar.cs
index f5eb9e3..038f884 100644
--- a/ZipConvertCustomerBarCode/Bar.cs
+++ b/ZipConvertCustomerBarCode/Bar.cs
@@ -18,6 +18,44 @@ public enum BarType
Timing
}
+ ///
+ /// バー1本の配置(ミリメートル単位、印刷用)
+ ///
+ public readonly struct PrintBar
+ {
+ ///
+ /// バーを生成する
+ ///
+ /// バーの種類
+ /// X座標(mm)
+ /// Y座標(mm)
+ /// 幅(mm)
+ /// 高さ(mm)
+ public PrintBar(BarType type, double x, double y, double width, double height)
+ {
+ Type = type;
+ X = x;
+ Y = y;
+ Width = width;
+ Height = height;
+ }
+
+ /// バーの種類
+ public BarType Type { get; }
+
+ /// X座標(mm)
+ public double X { get; }
+
+ /// Y座標(mm)
+ public double Y { get; }
+
+ /// 幅(mm)
+ public double Width { get; }
+
+ /// 高さ(mm)
+ public double Height { get; }
+ }
+
///
/// バー1本の配置(ピクセル単位)
///
diff --git a/ZipConvertCustomerBarCode/BarCode.cs b/ZipConvertCustomerBarCode/BarCode.cs
index 7aede81..6a6b33f 100644
--- a/ZipConvertCustomerBarCode/BarCode.cs
+++ b/ZipConvertCustomerBarCode/BarCode.cs
@@ -87,8 +87,16 @@ public static CustomerBarCode Create(string zipCode, string address)
if (zipCode == null) throw new ArgumentNullException(nameof(zipCode));
if (address == null) throw new ArgumentNullException(nameof(address));
- var code = ConvertCustomerBarCode(address, CheckPostCode(zipCode));
- return new CustomerBarCode(code);
+ var zip = CheckPostCode(zipCode);
+ var addressNumber = ExtractAddressNumber(address);
+ var customerCode = ConvertCCode(addressNumber);
+
+ // スタートコード + 郵便番号 + 住所表示番号 + チェックデジット + ストップコード
+ var symbols = new[] { startCode }
+ .Concat(zip.Select(x => x.ToString()))
+ .Concat(customerCode)
+ .Concat(new[] { CalcCheckDigit(zip.Select(x => x.ToString()), customerCode), endCode });
+ return new CustomerBarCode(zip, addressNumber, symbols);
}
///
@@ -122,8 +130,8 @@ public static CustomerBarCode Create(string zipCode, string townAddress, string
/// ハイフンを除いて数値7桁であること
///
/// 郵便番号
- /// 郵便番号配列
- private static IEnumerable CheckPostCode(string zipCode)
+ /// 郵便番号(半角数字7桁)
+ private static string CheckPostCode(string zipCode)
{
// 全角→半角
zipCode = string.Join("", zipCode.Select(n => (HankakuDic.ContainsKey(n) ? HankakuDic[n] : n)));
@@ -137,25 +145,19 @@ private static IEnumerable CheckPostCode(string zipCode)
throw new ArgumentException("郵便番号は7桁の数値で入力してください。");
}
- return zipCode.Select(x => x.ToString());
-
+ return zipCode;
}
///
- /// カスタマバーコード変換
+ /// 住所表示番号の抜き出し
///
/// 住所
- /// 郵便番号
- ///
- private static IEnumerable ConvertCustomerBarCode(string address, IEnumerable zipCode)
+ /// 住所表示番号(13けたで切り捨てる前)
+ private static string ExtractAddressNumber(string address)
{
- yield return startCode;
-
// 全角→半角
address = string.Join("", address.Select(n => (HankakuDic.ContainsKey(n) ? HankakuDic[n] : n)));
- foreach (var pc in zipCode) yield return pc;
-
// http://www.post.japanpost.jp/zipcode/zipmanual/p19.html
// 1. まず、データ内にあるアルファベットの小文字は大文字に置き換えます。
// 実行環境のカルチャによらず変換する(tr-TR では i が İ になるため)
@@ -181,14 +183,7 @@ private static IEnumerable ConvertCustomerBarCode(string address, IEnume
address = Regex.Replace(address, "[^0-9A-Z\\-]|[A-Z]{2,}", "-");
// ハイフン処理
- address = TrimHyphen(address);
-
- var customerCode = ConvertCCode(address);
- foreach (var cd in customerCode) yield return cd;
-
- yield return CalcCheckDigit(zipCode, customerCode);
-
- yield return endCode;
+ return TrimHyphen(address);
}
///
diff --git a/ZipConvertCustomerBarCode/CustomerBarCode.cs b/ZipConvertCustomerBarCode/CustomerBarCode.cs
index 7d098e8..9e97bd7 100644
--- a/ZipConvertCustomerBarCode/CustomerBarCode.cs
+++ b/ZipConvertCustomerBarCode/CustomerBarCode.cs
@@ -21,6 +21,11 @@ public sealed class CustomerBarCode
private const int BarWidth = 2;
private const int BarPitch = 4;
+ // 印刷時の大きさ(ポイント)。マニュアルでは8~11.5ポイント相当を許す
+ private const double DefaultPoint = 10;
+ private const double MinPoint = 8;
+ private const double MaxPoint = 11.5;
+
// シンボル→バー配置辞書
// L:ロングバー、A:セミロングバー(上)、D:セミロングバー(下)、T:タイミングバー、空白:バーなし
private static readonly Dictionary BarPatternDic = new Dictionary()
@@ -32,12 +37,35 @@ public sealed class CustomerBarCode
{"STC"," LD"},{"SPC","DL "}
};
- internal CustomerBarCode(IEnumerable symbols)
+ internal CustomerBarCode(string zipCode, string addressNumber, IEnumerable symbols)
{
+ ZipCode = zipCode;
+ AddressNumber = addressNumber;
// 呼び出し側から書き換えられないよう、コピーを読み取り専用で保持する
Symbols = new ReadOnlyCollection(symbols.ToArray());
}
+ ///
+ /// 郵便番号(半角数字7桁、ハイフンなし)
+ ///
+ public string ZipCode { get; }
+
+ ///
+ /// 住所から抜き出した住所表示番号(マニュアルの「バーコード・データ」、例: 3-20-5B604)
+ /// 13けたで切り捨てる前の値で、バーコードに含まれるのは先頭13けた分
+ ///
+ public string AddressNumber { get; }
+
+ ///
+ /// 代表の郵便番号かどうか(下2けたが"00")
+ ///
+ ///
+ /// 代表の郵便番号(郵便番号簿の「以下に掲載がない場合」の番号)の郵便物には、カスタマバーコードを付けない。
+ /// マニュアルでは代表の郵便番号は「原則として」下2けたが"00"とされているため、下2けたで判定している。
+ /// http://www.post.japanpost.jp/zipcode/zipmanual/p18.html
+ ///
+ public bool IsRepresentativeZipCode => ZipCode.EndsWith("00", StringComparison.Ordinal);
+
///
/// シンボル列
/// 開始コード("STC")、郵便番号、住所表示番号、チェックデジット、終了コード("SPC")の順
@@ -102,6 +130,89 @@ public string ToSvg(int scale = 1)
return sb.ToString();
}
+ ///
+ /// 印刷時のバーコードの幅(mm、余白を含まない)
+ ///
+ /// 大きさ(8~11.5ポイント)
+ public double GetPrintWidth(double point = DefaultPoint)
+ {
+ var bars = GetBars().ToList();
+ var width = bars[bars.Count - 1].X + BarWidth - bars[0].X;
+ return width * MillimetersPerPixel(point);
+ }
+
+ ///
+ /// 印刷時のバーコードの高さ(mm、余白を含まない)
+ ///
+ /// 大きさ(8~11.5ポイント)
+ public double GetPrintHeight(double point = DefaultPoint)
+ {
+ return CellHeight * MillimetersPerPixel(point);
+ }
+
+ ///
+ /// 印刷時のバーの配置を返す(mm単位、左上の基準は最初のバーの左上)
+ ///
+ ///
+ /// 寸法は郵便番号・バーコードマニュアルに従う(10ポイントのときバー幅0.6mm、バーピッチ1.2mm、ロングバー3.6mm)。
+ /// http://www.post.japanpost.jp/zipcode/zipmanual/p12.html
+ ///
+ /// 大きさ(8~11.5ポイント)
+ /// バー配置
+ public IEnumerable GetPrintBars(double point = DefaultPoint)
+ {
+ var mm = MillimetersPerPixel(point);
+ var bars = GetBars().ToList();
+ var offset = bars[0].X;
+ return bars.Select(bar => new PrintBar(bar.Type, (bar.X - offset) * mm, bar.Y * mm, bar.Width * mm, bar.Height * mm));
+ }
+
+ ///
+ /// 印刷用のSVG形式の文字列を返す(サイズはmm単位)
+ ///
+ /// 大きさ(8~11.5ポイント)
+ /// 上下左右の余白(mm)。マニュアルでは2mm以上の空白が必要
+ /// SVG
+ public string ToPrintSvg(double point = DefaultPoint, double margin = 2)
+ {
+ if (double.IsNaN(margin) || margin < 0) throw new ArgumentOutOfRangeException(nameof(margin), "余白は0以上で指定してください。");
+
+ var width = GetPrintWidth(point) + margin * 2;
+ var height = GetPrintHeight(point) + margin * 2;
+ var sb = new StringBuilder();
+ sb.AppendFormat(CultureInfo.InvariantCulture,
+ "");
+ return sb.ToString();
+ }
+
+ ///
+ /// 1ピクセル(scale=1)あたりのmm
+ ///
+ /// 大きさ(8~11.5ポイント)
+ private static double MillimetersPerPixel(double point)
+ {
+ if (double.IsNaN(point) || point < MinPoint || point > MaxPoint)
+ {
+ throw new ArgumentOutOfRangeException(nameof(point), "大きさは8~11.5ポイントで指定してください。");
+ }
+ // バー幅(2ピクセル)が 0.6mm × ポイント / 10
+ return 0.6 * point / 10 / BarWidth;
+ }
+
+ ///
+ /// SVG出力用の丸め(浮動小数点の誤差を出力しないため)
+ ///
+ private static double Round(double value) => Math.Round(value, 4);
+
///
/// バー生成
///