diff --git a/.editorconfig b/.editorconfig index f579ff5d..9592a0c0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -19,6 +19,7 @@ indent_size = 4 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true +max_line_length = 160 ########################################## # File Extension Settings @@ -134,6 +135,10 @@ csharp_style_prefer_null_check_over_type_check = true:warning # C# Style Rules # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules#c-style-rules [*.{cs,csx,cake}] +# Report lines exceeding the shared editor limit during analysis and builds. +roslynator_max_line_length = 160 +dotnet_diagnostic.RCS0056.severity = warning + # 'var' preferences csharp_style_var_for_built_in_types = false:error csharp_style_var_when_type_is_apparent = false:error diff --git a/.gitattributes b/.gitattributes index 4b946243..e594c0e9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -140,6 +140,8 @@ *.avif filter=lfs diff=lfs merge=lfs -text *.hvc1 filter=lfs diff=lfs merge=lfs -text *.hvcc filter=lfs diff=lfs merge=lfs -text +*.ivf filter=lfs diff=lfs merge=lfs -text +*.y4m filter=lfs diff=lfs merge=lfs -text *.yuv filter=lfs diff=lfs merge=lfs -text *.bit filter=lfs diff=lfs merge=lfs -text ############################################################################### diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index b0457857..c2e5a814 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -81,7 +81,7 @@ jobs: sdk-preview: true runtime: -x64 codecov: false - - os: ubuntu-22.04-arm + - os: ubuntu-24.04-arm framework: net11.0 sdk: 11.0.x sdk-preview: true @@ -103,7 +103,7 @@ jobs: sdk: 10.0.x runtime: -x64 codecov: false - - os: ubuntu-22.04-arm + - os: ubuntu-24.04-arm framework: net10.0 sdk: 10.0.x runtime: -x64 diff --git a/.gitignore b/.gitignore index 4e61c3c8..81d70e71 100644 --- a/.gitignore +++ b/.gitignore @@ -234,3 +234,7 @@ SixLabors.Shapes.Coverage.xml /plans/ samples/Avalonia** + +# Specifications are licensed documents and stay on the local machine only. +/src/ImageSharp.Drawing/Barcodes/specs/ +/src/ImageSharp.Drawing.FontGenerator/specs/ diff --git a/AGENTS.md b/AGENTS.md index 814e3ce2..38cc8eae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,9 +4,12 @@ These instructions apply to the entire repository. More-specific `AGENTS.md` fil ## Working Practices -- Inspect the relevant implementation, tests, benchmarks, project files, and nearby code before proposing or making changes. Do not infer current behavior when the source is available. +- Inspect only the source and supporting files needed for the task before proposing or making changes. Do not infer current behavior when the source is available. - Make the smallest complete change that solves the requested problem. Avoid unrelated cleanup, speculative abstractions, and formatting churn. -- Match established architecture, naming, formatting, documentation, and test patterns. Treat `.editorconfig`, analyzers, and repository build settings as authoritative. +- Trace the existing execution path before changing it. Extend and optimize the existing implementation within the requested behavior and affected path. +- Before implementing an operation, find and inspect the existing APIs that provide it. Use or extend those APIs instead of writing equivalent logic elsewhere. This includes color conversion, pixel conversion, memory management, and vectorized operations. If an API lacks required behavior, improve its owning implementation rather than bypassing it with a separate implementation. +- Follow existing naming, formatting, documentation, and test patterns. Treat `.editorconfig`, analyzers, and repository build settings as authoritative. +- Use simple technical English in all communication, documentation, and comments. Use short sentences, active voice, and existing code terminology. - Preserve public API and observable behavior unless the task explicitly requires a change. Public API documentation must describe observable behavior, not implementation details. - Do not use reflection against built assemblies, ad hoc assembly loading, or temporary probe projects unless explicitly requested. - Build .NET projects in Release configuration unless explicitly instructed otherwise. @@ -15,27 +18,26 @@ These instructions apply to the entire repository. More-specific `AGENTS.md` fil - Treat throughput, latency, memory use, and binary size as design constraints, especially in pixel-processing, drawing, parsing, encoding, and other hot paths. - Avoid unnecessary allocations, copies, boxing, closures, interface dispatch, repeated enumeration, and extra passes over data. -- Reuse the repository's existing memory ownership, pooling, span, vectorization, and parallelization patterns. Do not introduce a new mechanism when an established one fits. -- Keep hot loops simple and bounds-check-friendly. Hoist invariant work, preserve locality, and use the narrowest suitable data types without sacrificing correctness. -- Do not trade correctness or maintainability for assumed speed. Support non-obvious optimizations with measurements or clear evidence, and add or update benchmarks when performance is the purpose of the change. +- Reuse existing memory ownership, pooling, span, vectorization, and parallelization mechanisms. +- Use a SIMD-first design for suitable data processing code. Build on existing vectorized operations, dispatch mechanisms, and scalar fallbacks. Keep hot loops simple, hoist invariant work, and preserve memory locality. +- Preserve correctness and maintainability during optimization. Distinguish source-based reasoning from measured results. Claim a speedup only with measurements. Use existing benchmarks first. Add or update cases only when they measure relevant behavior that existing benchmarks miss. - Consider all supported target frameworks and runtime capabilities. Do not regress fallback paths while optimizing newer runtimes. ## C# Conventions -- Follow the existing code around the change; local patterns take precedence over generic preferences. +- Follow local patterns when they comply with the explicit rules. - Do not use `record` or `record struct` types. - Prefer established invariants over redundant guards. Validate at real external boundaries and do not add defensive checks for internally controlled states. -- Do not extract single-use helpers merely to name a block. Extract only for genuine reuse, an established local pattern, or meaningful complexity reduction. +- Do not extract single-use helpers merely to name a block. Extract only for genuine reuse, an established local pattern, or independently complex logic. - Add vertical whitespace after multi-line statements and declarations and between distinct logical stages. Never add trailing whitespace. -- Document every method, constructor, and property, regardless of whether it is public, internal, protected, or private. Keep public API documentation limited to observable behavior; use private and internal documentation to capture the contract and intent needed to maintain the code. -- Add inline comments throughout complex code. Explain algorithms, formulas, invariants, ownership, compatibility behavior, and performance tradeoffs at the operations and decisions they govern. Explain why the code is shaped that way rather than narrating the syntax. -- Document SIMD code especially thoroughly. Explain the vector layout, lane meaning, widening or narrowing, masks, shuffles, constants, alignment or remainder handling, supported instruction paths, scalar equivalence, and the reason each non-obvious operation is correct. -- Write algorithm and SIMD comments for a maintainer who is unfamiliar with the implementation. The reader should not need to reconstruct intent from external documentation, issue history, or benchmark results. +- Document every added method, constructor, and property, regardless of visibility. Update documentation when a member's behavior or contract changes. Keep public API documentation limited to observable behavior. Use private and internal documentation to explain the contract and intent. +- Add inline comments that explain why complex code works. Explain algorithms, formulas, invariants, memory ownership, compatibility behavior, and performance decisions where they apply. +- For SIMD, explain lane layouts, operations, alignment, remainders, and scalar equivalence so an unfamiliar maintainer can follow the code. ## Verification -- Add or update focused tests when behavior changes, following the test framework and conventions already used by the project. -- Never hack, weaken, skip, conditionally bypass, or otherwise manipulate a test to make it pass. Fix the production defect or the genuine test defect while preserving the test's intended coverage and sensitivity. -- Do not update golden files, reference images, snapshots, baselines, or expected-output artifacts to resolve a test failure. Treat a mismatch as evidence to investigate and correct the implementation. -- Run the narrowest relevant formatting, test, and Release build commands, then expand verification in proportion to the risk and scope of the change. +- Add or update tests only when they prove a required behavior or expose a real defect. Derive expectations from established contracts, explicit requirements, or defect evidence. Use source to identify contracts and integration points, not to copy implementation results into expectations. Prefer extending existing tests. Avoid redundant cases and assertions that merely repeat the implementation. +- Never weaken, skip, or bypass a valid test to make it pass. Fix the production defect or the genuine test defect. Replace or remove a test only with evidence that its expectation is invalid or its coverage is redundant. +- Do not update golden files, reference images, snapshots, baselines, or expected-output artifacts merely to silence a failure. Investigate mismatches. Update expected results only for an established requirement change or an incorrect reference result proved by independent contract or defect evidence. +- Test the actual behavior through the existing execution path. Synthetic inputs must exercise an established requirement or reproduce a real defect. Do not invent requirements or substitute internal structure and helper calls for assertions about required behavior. Run the narrowest relevant formatting, test, and Release build commands. - Report what changed, the verification performed, and any remaining risks or unverified assumptions. diff --git a/ImageSharp.Drawing.All.slnx b/ImageSharp.Drawing.All.slnx index 210a9644..60f1ffed 100644 --- a/ImageSharp.Drawing.All.slnx +++ b/ImageSharp.Drawing.All.slnx @@ -18,6 +18,9 @@ + + + diff --git a/ImageSharp.Drawing.slnx b/ImageSharp.Drawing.slnx index 8ba71a6c..e2cada87 100644 --- a/ImageSharp.Drawing.slnx +++ b/ImageSharp.Drawing.slnx @@ -23,6 +23,9 @@ + + + diff --git a/SECURITY.md b/SECURITY.md index 8b2e1df8..2e060f62 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,16 +2,23 @@ ## Supported Versions -Six Labors provides security fixes only for the latest major version of each library. +Six Labors provides security updates for the latest major version of each library. -Older major versions are end-of-life and do not receive security fixes. +Each major version remains eligible for security updates for 12 months after the first stable release of the next major version of that library. +After this period, that major version is end-of-life and does not receive security updates. -Users must upgrade to the latest major version to receive security fixes. +Users must install the latest available patch or minor release within a supported major version to receive security fixes. +Older releases within that major version are not maintained separately. -| Version | Supported | -| -------------------- | --------- | -| Latest major version | Yes | -| Older major versions | No | +| Version | Supported | +| ------- | --------- | +| Latest major version | Yes | +| Superseded major version within 12 months of the next major's first stable release | Yes | +| Superseded major version after that 12-month period | No | + +Security updates may require a license key under the existing license terms. This does not change the license terms. + +This policy covers security fixes only. It does not include feature backports or extend support for underlying .NET versions. Security fixes, if any, are provided at Six Labors' discretion. diff --git a/THIRD-PARTY-NOTICES b/THIRD-PARTY-NOTICES index 29b7b5c7..261aa50a 100644 --- a/THIRD-PARTY-NOTICES +++ b/THIRD-PARTY-NOTICES @@ -38,3 +38,77 @@ MIT License. The full license texts are available in the Vello repository LICENSE-APACHE and LICENSE-MIT files. ================================================================================ + +.NET Runtime (https://github.com/dotnet/runtime) +License: MIT + +Portions of this software were developed with reference to the .NET runtime +project, including the pooled string and list building buffers. + +Copyright (c) .NET Foundation and Contributors + +The .NET runtime project is distributed under the MIT License. The full license +text is available in the .NET runtime repository LICENSE.TXT file. + +================================================================================ + +Barcode Writer in Pure PostScript (https://github.com/bwipp/postscriptbarcode) +License: MIT + +Portions of this software were developed with reference to the Barcode Writer +in Pure PostScript project, including barcode symbology encodation. + +Copyright (c) 2004-2024 Terry Burton + +Barcode Writer in Pure PostScript is distributed under the MIT License. The +full license text is available in the project repository LICENSE file. + +================================================================================ + +bwip-js (https://github.com/metafloor/bwip-js) +License: MIT + +Portions of this software were developed with reference to the bwip-js +project, including barcode symbology encodation. + +Copyright (c) 2011-2026 Mark Warren + +The bwip-js project is distributed under the MIT License. The full license text +is available in the bwip-js repository LICENSE file. + +================================================================================ + +Zint (https://github.com/zint/zint) +License: BSD-3-Clause + +Portions of this software were developed with reference to the Zint barcode +library backend, including barcode symbology encodation. + +Copyright (C) 2008-2026 Robin Stuart and the Zint contributors + +The Zint backend is distributed under the BSD 3-Clause License. The full +license text is available in the Zint repository LICENSE file. + +The GS1 DataBar element width routine derives from the combins and getRSSwidths +functions of ISO/IEC 24724 Annex B, which the Zint backend carries with this +notice: "Copyright (C) 2006 BSI ... This does not preclude the free use, in the +course of implementing the standard, of necessary details such as symbols, and +size, type or grade designations." + +================================================================================ + +GS1 Barcode Syntax Dictionary (https://github.com/gs1/gs1-syntax-dictionary) +License: Apache-2.0 + +Portions of this software incorporate the GS1 Barcode Syntax Dictionary and +were developed with reference to its Syntax Tests. + +Copyright (c) 2020-2021 BWIPP project +Copyright (c) 2020-2021 Zint Project +Copyright (c) 2021-2026 GS1 AISBL + +The GS1 Barcode Syntax Dictionary is distributed under the Apache License, +Version 2.0. The full license text is available in the gs1-syntax-dictionary +repository LICENSE file. + +================================================================================ diff --git a/shared-infrastructure b/shared-infrastructure index 03471c6b..32a45b2d 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit 03471c6b458a2c11a0b1df1f5bf2777c8c773b99 +Subproject commit 32a45b2d369ff8628606a19c8e72b7e2dc5f8d76 diff --git a/src/ImageSharp.Drawing.FontGenerator/FontDesign.cs b/src/ImageSharp.Drawing.FontGenerator/FontDesign.cs new file mode 100644 index 00000000..57f3a412 --- /dev/null +++ b/src/ImageSharp.Drawing.FontGenerator/FontDesign.cs @@ -0,0 +1,164 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.FontGenerator; + +/// +/// One font design fed through the generator: the glyph skeletons, their stroke styling, the naming +/// and the acceptance expectations. +/// +internal sealed class FontDesign +{ + /// + /// Gets the short name that prefixes every output file, for example OcrA for OcrA.ttf, + /// OcrAFontData.cs and OcrA-proof.png. + /// + public required string Name { get; init; } + + /// + /// Gets the font family name written to the name table. + /// + public required string FamilyName { get; init; } + + /// + /// Gets the sentence naming this design's source standard, written into the generated data + /// file's XML documentation. + /// + public required string DataSummary { get; init; } + + /// + /// Gets the design units per em. + /// + public required ushort UnitsPerEm { get; init; } + + /// + /// Gets the nominal centerline height H in design units. + /// + public required float H { get; init; } + + /// + /// Gets the nominal centerline width W in design units. + /// + public required float W { get; init; } + + /// + /// Gets the descender centerline depth in design units, negative below the baseline. + /// + public required float Descender { get; init; } + + /// + /// Gets the default stroke width T in design units. + /// + public required float DefaultStrokeWidth { get; init; } + + /// + /// Gets the ink height of a small letter above the baseline, in design units, as the standard + /// dimensions the nominal printed image. + /// + public required float SmallLetterHeight { get; init; } + + /// + /// Gets the ink height of a capital letter and of a digit above the baseline, in design units. + /// + public required float CapitalHeight { get; init; } + + /// + /// Gets the ink height of an ascender above the baseline, in design units. + /// + public required float AscenderHeight { get; init; } + + /// + /// Gets the ink depth of a descender below the baseline, in design units, as a positive distance. + /// + public required float DescenderDepth { get; init; } + + /// + /// Gets the characters the standard dimensions on their own rather than on a nominal line. Those + /// keep the size they are drawn at, because normalizing them onto a line they were never meant to + /// touch would change a shape the standard states. + /// + public required string NormalizationExceptions { get; init; } + + /// + /// Gets the factor that maps the design grid onto the em. The grid carries the 0.1 inch character + /// pitch, which makes the ink far taller per em than any font convention, so the same point size + /// would render far larger glyphs than an established digitization of the same standard. The factor + /// lands the capital ink at the height those digitizations use, so a point size means the same + /// thing across them. + /// + public required float EmScale { get; init; } + + /// + /// Gets the ink height a small letter is drawn at, in design units, before normalization. + /// + public required float DrawnSmallLetterHeight { get; init; } + + /// + /// Gets the ink height an ascender is drawn at, in design units, before normalization. + /// + public required float DrawnAscenderHeight { get; init; } + + /// + /// Gets the ink depth a descender is drawn at, in design units, as a positive distance, before + /// normalization. + /// + public required float DrawnDescenderDepth { get; init; } + + /// + /// Gets the ink height a capital is drawn at, in design units, before normalization. A glyph whose + /// drawn ink reaches this line is a full height glyph and is normalized onto + /// . One that does not reach it, such as a hyphen or a comma, keeps its + /// place against the capitals instead. + /// + public required float DrawnCapitalHeight { get; init; } + + /// + /// Gets a value indicating whether open stroke ends project half a stroke width along their + /// tangent and cut flat, as the printed OCR-B terminals do, instead of ending in the round pen + /// sweep. + /// + public required bool CutTerminals { get; init; } + + /// + /// Gets the glyph skeletons keyed by character, in the stroke token format that + /// BuildStroke in Program.cs documents. + /// + public required IReadOnlyDictionary Skeletons { get; init; } + + /// + /// Gets, per character, the indices of strokes drawn with projecting square caps. + /// + public required IReadOnlyDictionary SquareStrokes { get; init; } + + /// + /// Gets, per character, the indices of strokes cut off exactly at their endpoints. + /// + public required IReadOnlyDictionary ButtStrokes { get; init; } + + /// + /// Gets, per character, the indices of strokes drawn with butt ends and sharp miter joins. + /// + public required IReadOnlyDictionary MiterStrokes { get; init; } + + /// + /// Gets, per character, the indices of strokes drawn with round caps and sharp miter joins. + /// + public required IReadOnlyDictionary MiterRoundStrokes { get; init; } + + /// + /// Gets per-character stroke width overrides for characters whose drawings use a thickness other + /// than throughout, such as the OCR-B small letters. + /// + public required IReadOnlyDictionary StrokeWidths { get; init; } + + /// + /// Gets per-stroke width overrides keyed by character and stroke index. The value zero marks a + /// stroke that is already a filled ink polygon rather than a centerline to stroke. + /// + public required IReadOnlyDictionary<(char Character, int Stroke), float> StrokeWidthOverrides { get; init; } + + /// + /// Gets the per-glyph acceptance expectations that enforces. + /// + public required IReadOnlyDictionary Expectations { get; init; } +} diff --git a/src/ImageSharp.Drawing.FontGenerator/GlyphClass.cs b/src/ImageSharp.Drawing.FontGenerator/GlyphClass.cs new file mode 100644 index 00000000..3bb25959 --- /dev/null +++ b/src/ImageSharp.Drawing.FontGenerator/GlyphClass.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.FontGenerator; + +/// +/// The class of character a glyph belongs to. The standards dimension the nominal printed image per +/// class, so a glyph is normalized onto the height its class stands at. +/// +internal enum GlyphClass +{ + /// + /// A capital letter, and the default for a character the standards draw against the capitals, such + /// as a bracket, a brace or an arithmetic sign. + /// + Capital, + + /// + /// A digit. + /// + Digit, + + /// + /// A small letter that rises to the ascender line. + /// + Ascender, + + /// + /// A small letter that stands on the small letter line. + /// + SmallLetter, + + /// + /// A small letter that hangs to the descender line. + /// + Descender, + + /// + /// A character the standards dimension in millimetres rather than draw, such as the vertical line + /// and the character erase. Those carry their own absolute size and are not normalized. + /// + Absolute, +} diff --git a/src/ImageSharp.Drawing.FontGenerator/ImageSharp.Drawing.FontGenerator.csproj b/src/ImageSharp.Drawing.FontGenerator/ImageSharp.Drawing.FontGenerator.csproj new file mode 100644 index 00000000..527d8bd3 --- /dev/null +++ b/src/ImageSharp.Drawing.FontGenerator/ImageSharp.Drawing.FontGenerator.csproj @@ -0,0 +1,27 @@ + + + + Exe + enable + false + ImageSharp.Drawing.FontGenerator + SixLabors.ImageSharp.Drawing.FontGenerator + + + + + + net10.0;net11.0 + + + + + net10.0 + + + + + + + + diff --git a/src/ImageSharp.Drawing.FontGenerator/OcrAGlyphs.cs b/src/ImageSharp.Drawing.FontGenerator/OcrAGlyphs.cs new file mode 100644 index 00000000..4e0a6b4d --- /dev/null +++ b/src/ImageSharp.Drawing.FontGenerator/OcrAGlyphs.cs @@ -0,0 +1,1239 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.FontGenerator; + +/// +/// The OCR-A glyph skeletons transcribed from the character drawings of FIPS PUB 32 (1974), Figures II-11 +/// through II-96, using the Size I dimensions of Table II-3. The drawings dimension stroke centerlines +/// directly on the 0.0, W and H reference lines, so ink projects a half stroke beyond them at every +/// terminal: round terminals get round caps and square terminals get projecting square caps, both ending +/// T/2 past the centerline endpoint. Labelled corner radii are outer ink radii, so the centerline radii +/// used here are those values less T/2. +/// +internal static class OcrAGlyphs +{ + /// + /// Design units per em. One em equals the 10 characters-per-inch pitch of 0.1 inch, so one unit is + /// 0.0001 inch and Table II-3 dimensions map directly: W = 550, H = 940, T = 140. + /// + public const ushort UnitsPerEm = 1000; + + /// + /// The nominal centerline width W of Table II-3 in design units. + /// + public const float W = 550; + + /// + /// The nominal centerline height H of Table II-3 in design units. + /// + public const float H = 940; + + /// + /// The nominal stroke width T of Table II-3 in design units. + /// + public const float T = 140; + + /// + /// The centerline radius of the r1 vertex arcs of Table II-3 at the C, D and O sides; the callouts + /// point at the dashed centerlines, so the table values are centerline radii. + /// + public const float R1 = 248; + + /// + /// The centerline radius of the r2 vertex arcs of Table II-3 at the O top and bottom. + /// + public const float R2 = 111; + + /// + /// The centerline radius of the r3 corner arcs of Table II-3 at the Q bowl corners. + /// + public const float R3 = 100; + + /// + /// The centerline radius of the r4 corner arcs of Table II-3 joining the S bars to its diagonal. + /// + public const float R4 = 87; + + /// + /// The centerline radius of corners the drawings label 3/2 T. + /// + public const float Corner3T2 = 210; + + /// + /// The centerline radius of corners the drawings label T. + /// + public const float CornerT = T; + + /// + /// The centerline radius of corners the drawings label 1/2 T. + /// + public const float CornerHalfT = T / 2; + + /// + /// The lower case x height of Figures II-68 through II-93, whose dimensions are given directly in + /// thousandths of an inch. + /// + public const float X = 646; + + /// + /// The lower case descender tail centerline of Figures II-68 through II-93; the ink reaches -294. + /// + public const float D = -224; + + /// + /// The ink height of a capital and of a digit above the baseline. Section 2.7 of FIPS PUB 32 gives + /// upper case dimensions in terms of W and H, so both reach the nominal centerline height of + /// Table II-1 plus half a stroke. + /// + public const float CapitalHeight = H + (T / 2); + + /// + /// The ink height of a small letter above the baseline, from the lower case drawings of Figures + /// II-68 through II-93. + /// + public const float SmallLetterHeight = X + (T / 2); + + /// + /// The ink height of an ascender above the baseline. The lower case drawings carry the ascenders to + /// the same line as the capitals. + /// + public const float AscenderHeight = CapitalHeight; + + /// + /// The ink depth of a descender below the baseline, as a positive distance. The descending letters + /// carry their tail centerline to 294 below the baseline. + /// + public const float DescenderDepth = 294 + (T / 2); + + /// + /// The centerline radius of the 13.8 corner arcs of the lower case drawings, measured on the outer + /// ink. + /// + public const float LowerR = 138 - (T / 2); + + /// + /// Gets the glyph skeletons keyed by character. Each glyph is a list of strokes; a stroke is a point + /// list in design units with the y axis up, closed when the first and last points are equal. A + /// in an x position starts an arc segment: the next three values are the arc + /// radius (negative to flip the sweep direction; positive sweeps counter-clockwise) and the end point. + /// Two consecutive values mark a rounded corner: the next three values are the + /// centerline corner radius and the corner vertex, and the generator replaces the vertex with a + /// tangent arc computed from the neighbouring segments. + /// + public static IReadOnlyDictionary Skeletons { get; } = new Dictionary + { + // Figure II-20. A rounded rectangle with 1/2 T corners. + ['0'] = + [ + [ + W / 2, H, + float.NaN, float.NaN, CornerHalfT, W, H, + float.NaN, float.NaN, CornerHalfT, W, 0, + float.NaN, float.NaN, CornerHalfT, 0, 0, + float.NaN, float.NaN, CornerHalfT, 0, H, + W / 2, H, + ], + ], + + // Figure II-11. A flag to 1/2 W with a square stem corner, a full-width base bar with a round + // join into the riser, and the riser cap at 3/8 H. + ['1'] = + [ + [0, H, W / 2, H, W / 2, 0], + [0, 0, W, 0, W, 3 * H / 8], + ], + + // Figure II-12. Three 1/2 T centerline bends, a square corner at the base, and round caps at the + // top-left and bottom-right. + ['2'] = + [ + [ + 0, H, + float.NaN, float.NaN, CornerHalfT, W, H, + float.NaN, float.NaN, CornerHalfT, W, H / 2, + float.NaN, float.NaN, CornerHalfT, 0, H / 2, + 0, 0, + W, 0, + ], + ], + + // Figure II-13. Two runs sharing the middle arm at 1/2 H: 1/2 T bends at the outer right corners + // and T bends where the arm turns, whose outer arcs pinch at the right notch. + ['3'] = + [ + [ + 0, H, + float.NaN, float.NaN, CornerHalfT, W, H, + float.NaN, float.NaN, CornerT, W, H / 2, + W / 4, H / 2, + ], + [ + W / 4, H / 2, + float.NaN, float.NaN, CornerT, W, H / 2, + float.NaN, float.NaN, CornerHalfT, W, 0, + 0, 0, + ], + ], + + // Figure II-14. A vertical left arm at 1/8 W to the bar at 3/8 H, and a right stem at 7/8 W ink, + // topping out at 7/8 H; the spacing reference is corrected by delta X of +1/16 W. + ['4'] = + [ + [W / 8, H, W / 8, 3 * H / 8, W, 3 * H / 8], + [7 * W / 8, 0, 7 * W / 8, 7 * H / 8], + ], + + // Figure II-15. Bars on H and 1/2 H off a stem at 1/4 W with sharp corners, 1/2 T corners onto + // the right side, and the base run bending at 1/4 W through a T corner up to the 1/16 H tail cap. + ['5'] = + [ + [ + W, H, W / 4, H, W / 4, H / 2, + float.NaN, float.NaN, CornerHalfT, W, H / 2, + float.NaN, float.NaN, CornerHalfT, W, 0, + float.NaN, float.NaN, CornerT, W / 4, 0, + 0, H / 16, + ], + ], + + // Figure II-16. A spine on the 1/8 W line with the top bar capped at 1/2 W and a closed bowl + // between the ink reference lines up to 3/8 H. + ['6'] = + [ + [0, 290, 0, H, W / 8, H], + [W / 2, 0, W, 0, W, 3 * H / 8, 0, 3 * H / 8, 0, 0, W / 2, 0], + ], + + // Figure II-17. A hook cap at 15/16 H arcing onto the top bar, a square corner at the top right, + // and the diagonal stepping through 5/8 H and 3/8 H onto the lower stem. + ['7'] = + [ + [0, 15 * H / 16, 0, H, W / 2, H], + [W / 4, H, W, H, W, 5 * H / 8], + [W, 5 * H / 8, W / 2, 3 * H / 8, W / 2, 0], + ], + + // Figure II-18. Two closed boxes: the narrow upper bowl between 1/4 W and 3/4 W and the full-width + // lower bowl. + ['8'] = + [ + [ + W / 2, H / 2, W / 4, H / 2, W / 4, H, 3 * W / 4, H, 3 * W / 4, H / 2, W / 2, H / 2, + ], + [ + W / 2, H / 2, + float.NaN, float.NaN, CornerHalfT, W, H / 2, + float.NaN, float.NaN, CornerHalfT, W, 0, + float.NaN, float.NaN, CornerHalfT, 0, 0, + float.NaN, float.NaN, CornerHalfT, 0, H / 2, + W / 2, H / 2, + ], + ], + + // Figure II-19. The digit six rotated a half turn: a spine on the 7/8 W line with the base bar on + // the baseline capped at 1/2 W, and a closed bowl down to 5/8 H. + ['9'] = + [ + [W, 650, W, 0, 7 * W / 8, 0], + [W / 2, H, 0, H, 0, 5 * H / 8, W, 5 * H / 8, W, H, W / 2, H], + ], + + // Figure II-25. Diagonals meeting at the apex with a crossbar at 1/4 H ending on the legs. + ['A'] = + [ + [0, 0, W / 2, H, W, 0], + [W / 8, H / 4, 7 * W / 8, H / 4], + ], + + // Figure II-26. A square-cut stem and two bowls whose right corners round at 3/2 T. + ['B'] = + [ + [0, 0, 0, H], + [ + 0, H, + float.NaN, float.NaN, Corner3T2, W, H, + float.NaN, float.NaN, Corner3T2, W, H / 2, + 0, H / 2, + ], + [ + 0, H / 2, + float.NaN, float.NaN, Corner3T2, W, H / 2, + float.NaN, float.NaN, Corner3T2, W, 0, + 0, 0, + ], + ], + + // Figure II-27. Bars turning at 3/8 W through T corners into 1:2 diagonals tangent to the r1 + // vertex arc, whose leftmost centerline point rides the left reference line at 1/2 H. + ['C'] = + [ + [ + W, H, + float.NaN, float.NaN, CornerT, 3 * W / 8, H, + 26.6f, 580.7f, + float.NaN, R1, 26.6f, 359.3f, + float.NaN, float.NaN, CornerT, 3 * W / 8, 0, + W, 0, + ], + ], + + // Figure II-28. Overhanging bars turning at 5/8 W into the r1 vertex arc on the right reference + // line, with an inset stem at 1/4 W. + ['D'] = + [ + [ + 0, H, + float.NaN, float.NaN, CornerT, 5 * W / 8, H, + 523.4f, 580.7f, + float.NaN, -R1, 523.4f, 359.3f, + float.NaN, float.NaN, CornerT, 5 * W / 8, 0, + 0, 0, + ], + [W / 4, 0, W / 4, H], + ], + + // Figure II-29. One mitered run from the top bar cap around the square stem corners to the bottom + // bar cap, with the middle bar to 1/2 W. + ['E'] = + [ + [W, H, 0, H, 0, 0, W, 0], + [0, H / 2, W / 2, H / 2], + ], + + // Figure II-30. A mitered run from the top bar cap through the square top corner down to the stem + // base cap; the middle bar sits at 5/8 H and ends at 3/4 W. + ['F'] = + [ + [W, H, 0, H, 0, 0], + [0, 5 * H / 8, 3 * W / 4, 5 * H / 8], + ], + + // Figure II-31. One stroke from the top-right cap through 3/2 T bends into the straight left side + // between 3/8 H and 5/8 H, around the base through T corners, up the right side to 3/8 H, and + // inward to the spur cap at 5/8 W. + ['G'] = + [ + [ + W, H, + float.NaN, float.NaN, CornerT, W / 2, H, + float.NaN, float.NaN, Corner3T2, 0, 5 * H / 8, + float.NaN, float.NaN, CornerT, 0, 0, + float.NaN, float.NaN, CornerT, W, 0, + W, 3 * H / 8, + 5 * W / 8, 3 * H / 8, + ], + ], + + // Figure II-32. + ['H'] = + [ + [0, 0, 0, H], + [W, 0, W, H], + [0, H / 2, W, H / 2], + ], + + // Figure II-33. + ['I'] = + [ + [0, H, W, H], + [W / 2, H, W / 2, 0], + [0, 0, W, 0], + ], + + // Figure II-34. The stem on the right turns through a semicircular bowl into a riser ending at + // 3/8 H on the left. + ['J'] = + [ + [ + 7 * W / 8, H, + float.NaN, float.NaN, CornerT, 7 * W / 8, 0, + float.NaN, float.NaN, CornerT, W / 8, 0, + W / 8, 3 * H / 8, + ], + ], + + // Figure II-35. Both diagonals spring from the stem at 1/2 H toward the corners. + ['K'] = + [ + [0, 0, 0, H], + [0, H / 2, W, H], + [0, H / 2, W, 0], + ], + + // Figure II-36. Round caps on the stem top and bar end; the base corner miters square. + ['L'] = + [ + [0, H, 0, 0, W, 0], + ], + + // Figure II-37. Edge stems, square at the top and round at the base; each vee arm leaves the stem + // with a butt face whose upper corner sits on the stem's inner top corner and ends in the round + // bulb cap at 11/16 H. + ['M'] = + [ + [0, 0, 0, H / 2], + [0, H / 4, 0, H], + [W, 0, W, H / 2], + [W, H / 4, W, H], + [0, H, W / 8, H, W / 2, 11 * H / 16, 7 * W / 8, H, W, H], + [W / 2, 11 * H / 16, W / 2, 5 * H / 8], + ], + + // Figure II-38. One run: round cap up the left stem, a short top bar to 1/8 W, the diagonal to + // 7/8 W on the baseline, a short bottom bar, and the right stem to a round cap; the two bar + // corners miter square. + ['N'] = + [ + [0, 0, 0, H, W / 8, H, 7 * W / 8, 0, W, 0, W, H], + ], + + // Figure II-39. Four diagonals joined by the r2 vertex arcs at the top and bottom, tangent at + // 3/8 W and 5/8 W, and the r1 vertex arcs on the sides, tangent at 7/16 H and 9/16 H. + ['O'] = + [ + [ + 206.25f, 916.2f, + float.NaN, -R2, 343.75f, 916.2f, + W - 7.1f, 9 * H / 16, + float.NaN, -R1, W - 7.1f, 7 * H / 16, + 343.75f, 23.8f, + float.NaN, -R2, 206.25f, 23.8f, + 7.1f, 7 * H / 16, + float.NaN, -R1, 7.1f, 9 * H / 16, + 206.25f, 916.2f, + ], + ], + + // Figure II-40. A stem with a square bowl closing at 7/16 H, its right corners rounded at T. + ['P'] = + [ + [0, 0, 0, H / 2], + [0, H / 4, 0, H], + [0, H, W / 2, H], + [ + W / 4, H, + float.NaN, float.NaN, CornerT, W, H, + float.NaN, float.NaN, CornerT, W, 7 * H / 16, + 0, 7 * H / 16, + ], + ], + + // Figure II-41. A slanted bowl: straight sides on the reference lines between 3/8 H and 5/8 H, + // slanted top and bottom runs, r3 corners top-right and bottom-left, 3/2 T corners at the other + // two bends, and a tail from 3/8 H through 3/4 W on the baseline to a foot cap at the right edge. + ['Q'] = + [ + [ + 0, 5 * H / 8, + float.NaN, float.NaN, R3, W, 1053.3f, + W, 3 * H / 8, + 157.2f, 25.2f, + float.NaN, -97, 0, 97, + 0, 5 * H / 8, + ], + [W / 2, 3 * H / 8, 415, 0, W, 0], + ], + + // Figure II-42. The P bowl closing at 5/8 H with a leg from 3/8 W on the bowl to the corner. + ['R'] = + [ + [0, 0, 0, H / 2], + [0, H / 4, 0, H], + [0, H, W / 2, H], + [ + W / 4, H, + float.NaN, float.NaN, CornerT, W, H, + float.NaN, float.NaN, CornerT, W, 5 * H / 8, + 0, 5 * H / 8, + ], + [3 * W / 8, 5 * H / 8, W, 0], + ], + + // Figure II-43. Point-symmetric: terminal curls to 7/8 H and 1/8 H, runs along the top and bottom + // reference lines, short sides on the vertical reference lines, and r4 corners onto the middle + // diagonal. + ['S'] = + [ + [ + W, 7 * H / 8, + float.NaN, H / 8, W - (H / 8), H, + float.NaN, float.NaN, R4, -91.7f, H, + float.NaN, float.NaN, R4, 641.7f, 0, + H / 8, 0, + float.NaN, -(H / 8), 0, H / 8, + ], + ], + + // Figure II-44. The bar carries short drops to caps at 7/8 H at both ends. + ['T'] = + [ + [0, 7 * H / 8, 0, H], + [0, H, W, H], + [W, H, W, 7 * H / 8], + [W / 2, H, W / 2, 0], + ], + + // Figure II-45. A flat bottom run with T corners. + ['U'] = + [ + [ + 0, H, + float.NaN, float.NaN, CornerT, 0, 0, + float.NaN, float.NaN, CornerT, W, 0, + W, H, + ], + ], + + // Figure II-46. Vertical drops to 3/4 H before the diagonals meet at the rounded base vertex. + ['V'] = + [ + [0, H, 0, 3 * H / 4, W / 2, 0, W, 3 * H / 4, W, H], + ], + + // Figure II-47. Three verticals: outer stems to H and a middle stroke capped at 5/8 H, joined by + // U valleys whose 1/2 T corners put their tangents on the 1/8 W and 3/8 W ticks. + ['W'] = + [ + [0, H, 0, H / 8, W / 8, 0, (W / 2) - (T / 2), 0], + [W, H, W, H / 8, 7 * W / 8, 0, (W / 2) + (T / 2), 0], + [W / 2, 0, W / 2, H / 2], + [W / 2, 3 * H / 8, W / 2, 5 * H / 8], + ], + + // Figure II-48. Diagonals corner to corner. + ['X'] = + [ + [0, 0, W, H], + [0, H, W, 0], + ], + + // Figure II-49. Arm stubs dropping to 7/8 H, the junction at 1/2 H, and the stem to the baseline. + ['Y'] = + [ + [0, H, 0, 7 * H / 8, W / 2, H / 2, W / 2, 0], + [W, H, W, 7 * H / 8, W / 2, H / 2], + ], + + // Figure II-50. The top bar has a round cap on the left and a square end projecting 1/2 T past + // the diagonal vertex on the right; the bottom bar mirrors it. The diagonal runs vertex to vertex + // with its caps buried under the square ends. + ['Z'] = + [ + [ + 0, 1010, + 620, 1010, + 620, 921, + 122, 70, + W, 70, + float.NaN, -70, 620, 0, + float.NaN, -70, W, -70, + -70, -70, + -70, 19, + 427.9f, 870, + 0, 870, + float.NaN, -70, -70, H, + float.NaN, -70, 0, 1010, + ], + ], + + // Figure II-51. A square centerline between 3/16 H and 5/16 H; the drawing's Y value keeps the + // ink clear of the baseline. + ['.'] = [.. SquareMark(3 * W / 8, 3 * H / 16, 5 * W / 8, 5 * H / 16)], + + // Figure II-52. A bar at 3/8 H from 1/4 W turning down at 3/4 W to a foot on the baseline. + [','] = + [ + [W / 4, 3 * H / 8, 3 * W / 4, 3 * H / 8, 3 * W / 4, 0], + ], + + // Figure II-53. Squares between 3/16 H and 5/16 H and between 11/16 H and 13/16 H. + [':'] = + [ + .. SquareMark(3 * W / 8, 3 * H / 16, 5 * W / 8, 5 * H / 16), + .. SquareMark(3 * W / 8, 11 * H / 16, 5 * W / 8, 13 * H / 16), + ], + + // Figure II-54. The comma elbow with a square between 3/4 H and 7/8 H. + [';'] = + [ + [W / 4, 3 * H / 8, 3 * W / 4, 3 * H / 8, 3 * W / 4, 0], + .. SquareMark(W / 4, 3 * H / 4, W / 2, 7 * H / 8), + ], + + // Figure II-55. Bars at Y = 3/8 H and 11/16 H. + ['='] = + [ + [0, 3 * H / 8, W, 3 * H / 8], + [0, 11 * H / 16, W, 11 * H / 16], + ], + + // Figure II-56. A cross centered at 1/2 H whose vertical bar spans W in total. + ['+'] = + [ + [0, H / 2, W, H / 2], + [W / 2, (H / 2) - (W / 2), W / 2, (H / 2) + (W / 2)], + ], + + // Figure II-66. A closed centerline loop between Y = 7/16 H and 1/2 H across the full width with + // 1/2 T corners. + ['-'] = + [ + [0, 7 * H / 16, 0, H / 2, W, H / 2, W, 7 * H / 16, 0, 7 * H / 16], + [80, 15 * H / 32, W - 80, 15 * H / 32], + ], + + // Figure II-57. A diagonal corner to corner. + ['/'] = + [ + [0, 0, W, H], + ], + + // Figure II-58. A vertical arm between 1/8 H and 7/8 H crossed by two diagonals ending at 1/4 H + // and 3/4 H on the reference lines. + ['*'] = + [ + [W / 2, H / 8, W / 2, 7 * H / 8], + [0, H / 4, W, 3 * H / 4], + [0, 3 * H / 4, W, H / 4], + ], + + // Figure II-59. Single ink outline: flat top spanning the full width plus the 1/2 T side + // overhangs, tapered sides parallel to the vee centerlines, round prong tips at 5/8 H, and the + // centre notch floor T below the top ink. + ['"'] = + [ + [ + -70, 1010, 620, 1010, 620, 933.2f, 549.96f, 574.1f, + float.NaN, -70, 412.54f, 574.1f, + 354.83f, 870, 195.17f, 870, 137.46f, 574.1f, + float.NaN, -70, 0.04f, 574.1f, + -70, 933.2f, -70, 1010, + ], + ], + + // Figure II-65. A vertical left arm to a round vertex at 1/2 H with the right arm rising back to + // the top line, capped flat by a short bar. + ['\''] = + [ + [3 * W / 8, H, 5 * W / 8, H], + [3 * W / 8, 900, 3 * W / 8, H / 2], + [3 * W / 8, H / 2, 332.05f, 900], + ], + + // Figure II-62. Centerline squares top-left and bottom-right with the slash from 1/8 H to 7/8 H. + ['%'] = + [ + [0, H / 8, W, 7 * H / 8], + .. SquareMark(0, 7 * H / 8, W / 4, H), + .. SquareMark(3 * W / 4, 0, W, H / 8), + ], + + // Figure II-67. A squared S body with bars at 1/4 H, 1/2 H and 3/4 H crossed by a centre stem + // spanning the full character height. + ['$'] = + [ + [W, 3 * H / 4, 0, 3 * H / 4, 0, H / 2, W, H / 2, W, H / 4, 0, H / 4], + [W / 2, 3 * H / 4, W / 2, H], + [W / 2, H / 4, W / 2, 0], + ], + + // Figure II-60. A spine at 1/2 W with bars reaching the right edge and a spur to the left edge at + // 1/2 H. + ['('] = + [ + [ + W, H, + float.NaN, float.NaN, CornerHalfT, W / 2, H, + W / 2, 610, + float.NaN, -CornerT, 135, H / 2, + 0, H / 2, + ], + [ + W, 0, + float.NaN, float.NaN, CornerHalfT, W / 2, 0, + W / 2, 330, + float.NaN, CornerT, 135, H / 2, + 0, H / 2, + ], + ], + + // Figure II-61. The mirrored closing form. + [')'] = + [ + [ + 0, H, + float.NaN, float.NaN, CornerHalfT, W / 2, H, + W / 2, 610, + float.NaN, CornerT, 415, H / 2, + W, H / 2, + ], + [ + 0, 0, + float.NaN, float.NaN, CornerHalfT, W / 2, 0, + W / 2, 330, + float.NaN, -CornerT, 415, H / 2, + W, H / 2, + ], + ], + + // Figure II-63. A slanted hairpin from a cap at 5/8 H around the top-right, descending to a drop + // cap at 1/4 H, with the dot at 1/4 W on the baseline. + ['?'] = + [ + [0, 5 * H / 8, 3 * W / 4, H, W, 7 * H / 8, W, 3 * H / 4, W / 4, 3 * H / 8, W / 4, H / 4], + [W / 4, 0, (W / 4) + 0.1f, 0], + ], + + // Figure II-64. One run: from the cap at (W, 1/4 H) down to the baseline vertex at 1/2 W, around + // the T fillet at the origin, up and diagonally through the self crossing to 3/4 W at 5/8 H, over + // the top bar at 7/8 H between two T fillets, and down the left side into the closing diagonal to + // the cap at (W, 0). + ['&'] = + [ + [ + W, H / 4, + W / 2, 0, + float.NaN, float.NaN, CornerT, 0, 0, + 0, H / 4, + 3 * W / 4, 5 * H / 8, + float.NaN, float.NaN, CornerT, 3 * W / 4, 7 * H / 8, + float.NaN, float.NaN, CornerT, 0, 7 * H / 8, + 0, 5 * H / 8, + W, 0, + ], + ], + + // Figure II-24. The Long Vertical Mark: length L = 1460 with square-cut ends, tied to no + // baseline, rising from the descender depth through the line box. The break is real ink, centred + // on the bar, splitting it into equal segments. + ['|'] = + [ + [W / 2, D - (T / 2), W / 2, 410.75f], + [W / 2, 461.25f, W / 2, D - (T / 2) + 1460], + ], + + // Figure II-21. Symbol Hook: one run with square corners from the 3/8 H cap on the left, along + // the baseline, up the 1/2 W stem, across the top, and down to the 5/8 H cap on the right. + ['⑀'] = + [ + [0, 3 * H / 8, 0, 0, W / 2, 0, W / 2, H, W, H, W, 5 * H / 8], + ], + + // Figure II-23. Symbol Chair: a full-height right stroke, a seat at 1/2 H turning square down the + // left front leg to the baseline. + ['⑁'] = + [ + [W, 0, W, H], + [W, H / 2, 0, H / 2, 0, 0], + ], + + // Figure II-22. Symbol Fork: prongs to 1/2 H joined by a square-ended bar, with the stem + // continuing to the baseline. + ['⑂'] = + [ + [0, H, 0, H / 2], + [W, H, W, H / 2], + [0, H / 2, W, H / 2], + [W / 2, H / 2, W / 2, 0], + ], + + // Figure II-95. Character Erase: solid ink covering the character cell, inside the figure's MIN + // and MAX rectangles. + ['█'] = + [ + [-70, -70, 620, -70, 620, 1010, -70, 1010, -70, -70], + ], + + // Figure II-96. Group Erase: a thin continuous line spanning the cell so adjacent marks join. + ['―'] = + [ + [-225, H / 2, W + 225, H / 2], + ], + + // Figure II-68. An arm capped at 13.8 turning through a 13.8 corner into the 2 degree right side + // ending in the 7.0 foot, with a low octagonal bowl to 35.3 whose bottom flat ends at 34.4 in a + // cut up to the side. + ['a'] = + [ + [138, X, float.NaN, float.NaN, 138, 527.4f, X, W, 0], + [ + 537.7f, 353, + float.NaN, float.NaN, 138, 0, 353, + float.NaN, float.NaN, 138, 0, 0, + float.NaN, float.NaN, 138, 344, 0, + 546, 118, + ], + ], + + // Figure II-69. An ascender stem with the bowl attached on its right. + ['b'] = + [ + [0, 0, 0, H], + [ + 0, 380, + float.NaN, float.NaN, 138, 0, 470, + float.NaN, float.NaN, 138, 206, X, + float.NaN, float.NaN, 138, 413, X, + float.NaN, float.NaN, 138, W, 529, + float.NaN, float.NaN, 138, W, 118, + float.NaN, float.NaN, 138, 413, 0, + float.NaN, float.NaN, 138, 206, 0, + float.NaN, float.NaN, 138, 0, 176, + 0, 266, + ], + ], + + // Figure II-70. The open bowl with caps at 3/4 of the width. + ['c'] = + [ + [ + W, X, + float.NaN, float.NaN, 138, 206, X, + float.NaN, float.NaN, 138, 0, 470, + float.NaN, float.NaN, 138, 0, 176, + float.NaN, float.NaN, 138, 206, 0, + W, 0, + ], + ], + + // Figure II-71. The mirrored b. + ['d'] = + [ + [W, 0, W, H], + [ + W, 380, + float.NaN, float.NaN, 138, W, 470, + float.NaN, float.NaN, 138, 344, X, + float.NaN, float.NaN, 138, 138, X, + float.NaN, float.NaN, 138, 0, 529, + float.NaN, float.NaN, 138, 0, 118, + float.NaN, float.NaN, 138, 138, 0, + float.NaN, float.NaN, 138, 344, 0, + float.NaN, float.NaN, 138, W, 176, + W, 266, + ], + ], + + // Figure II-72. A full-width bar at 29.4 with the shell opening bottom-right. + ['e'] = + [ + [0, 294, W, 294], + [ + W, 294, + float.NaN, float.NaN, 138, W, 529, + float.NaN, float.NaN, 138, 413, X, + float.NaN, float.NaN, 138, 138, X, + float.NaN, float.NaN, 138, 0, 529, + float.NaN, float.NaN, 138, 0, 118, + float.NaN, float.NaN, 138, 138, 0, + 300, 0, + ], + [200, 0, W, 0], + ], + + // Figure II-73. A stem leaning 2 degrees from 30.9 at the base, hooking right through the 17.6 + // corner to a cap at 48.1, crossed at the x line; the spacing reference is corrected by delta X + // of +3.4. + ['f'] = + [ + [206, 0, float.NaN, float.NaN, 176, 206, H, W, H], + [69, X, 413, X], + ], + + // Figure II-74. The bowl with a right stem descending to a hooked tail ending at 13.8. + ['g'] = + [ + [ + W, X, + float.NaN, float.NaN, 138, W, -176, + float.NaN, float.NaN, 138, 413, -294, + 69, -294, + ], + [ + W, 380, + float.NaN, float.NaN, 138, W, 470, + float.NaN, float.NaN, 138, 344, X, + float.NaN, float.NaN, 138, 138, X, + float.NaN, float.NaN, 138, 0, 529, + float.NaN, float.NaN, 138, 0, 176, + float.NaN, float.NaN, 138, 138, 59, + float.NaN, float.NaN, 138, 344, 59, + float.NaN, float.NaN, 138, W, 176, + W, 266, + ], + ], + + // Figure II-75. An ascender stem with the arch springing at 47.0; the arch geometry repeats + // Figure II-81, so the shoulder is the same tangent arc into the 2 degree leg. + ['h'] = + [ + [0, 0, 0, H], + [ + 0, 470, + float.NaN, float.NaN, 138, W / 2, X, + 394.1f, X, + float.NaN, -138, 532.1f, 512.8f, + W, 0, + ], + ], + + // Figure II-76. A flag capped at 6.9 into the stem at 27.5, a base bar from 6.9 to 48.1, and the + // dot: a closed centerline square between 20.6 and 27.5 and between 94.0 and 99.9. + ['i'] = + [ + [69, X, W / 2, X, W / 2, 0], + [69, 0, 481, 0], + .. SquareMark(206, 940, W / 2, 999), + ], + + // Figure II-77. A flag capped at 13.8 into the stem at 41.3, a hook with 13.8 corners whose run + // sits at -22.4 rising to a terminal on the left reference line, and the dot square between 34.4 + // and 41.3; the spacing reference is corrected by delta X of -6.9. + ['j'] = + [ + [138, X, 413, X], + [ + 413, X, + float.NaN, float.NaN, 138, 413, -294, + 138, -294, + float.NaN, -138, 0, -176, + ], + .. SquareMark(344, 940, 413, 999), + ], + + // Figure II-78. Diagonals from the stem at 35.3 toward the x line and the baseline corner. + ['k'] = + [ + [0, 0, 0, H], + [0, 235, 481, X], + [138, 353, W, 0], + ], + + // Figure II-79. The ascender stem with a base bar. + ['l'] = + [ + [69, H, W / 2, H, W / 2, 0], + [69, 0, 481, 0], + ], + + // Figure II-80. Stems lean two degrees from baseline centres 1.5 and 56.5; the r100 and r97 + // arches crest at 13.8 and 41.3 on the x line and run down straight tangents into the next leg + // at 52.9. + ['m'] = + [ + [-15, 0, -15, X], + [-15, 529, 112.4f, 631.1f, float.NaN, -100, W / 2, 553.1f], + [W / 2, 553.1f, W / 2, 0], + [W / 2, 529, 383.4f, 627.4f, float.NaN, -97, 545.5f, 559.1f, 565, 0], + ], + + // Figure II-81. The left stem with the arch springing at 47.0. + ['n'] = + [ + [0, 0, 0, X], + [ + 0, 470, + float.NaN, float.NaN, 138, W / 2, X, + 394.1f, X, + float.NaN, -138, 532.1f, 512.8f, + W, 0, + ], + ], + + // Figure II-82. An octagonal bowl: the centerline carries the drawing's corner cuts between 13.8 + // and 41.3 horizontally and 11.8 and 52.9 vertically, leaving the counter angled while the joins + // round the outer corners. + ['o'] = + [ + [ + W / 2, X, + float.NaN, float.NaN, 138, 413, X, + float.NaN, float.NaN, 138, W, 529, + float.NaN, float.NaN, 138, W, 118, + float.NaN, float.NaN, 138, 413, 0, + float.NaN, float.NaN, 138, 138, 0, + float.NaN, float.NaN, 138, 0, 118, + float.NaN, float.NaN, 138, 0, 529, + float.NaN, float.NaN, 138, 138, X, + W / 2, X, + ], + ], + + // Figure II-83. The bowl with a left stem rising to 67.55 and descending to the tail depth. + ['p'] = + [ + [0, -294, 0, 675.5f], + [ + 0, 470, + float.NaN, float.NaN, 138, 206, 675.5f, + float.NaN, float.NaN, 138, 344, 675.5f, + float.NaN, float.NaN, 138, W, 498, + float.NaN, float.NaN, 138, W, 148, + float.NaN, float.NaN, 138, 344, -29.5f, + float.NaN, float.NaN, 138, 206, -29.5f, + 0, 176, + ], + ], + + // Figure II-84. The mirrored p. + ['q'] = + [ + [W, -294, W, X], + [ + W, 470, + float.NaN, float.NaN, 138, 344, X, + float.NaN, float.NaN, 138, 138, X, + float.NaN, float.NaN, 138, 0, 529, + float.NaN, float.NaN, 138, 0, 118, + float.NaN, float.NaN, 138, 138, 0, + float.NaN, float.NaN, 138, 344, 0, + W, 176, + ], + ], + + // Figure II-85. The stem with an arm curling down to a cap at 41.1. + ['r'] = + [ + [0, 0, 0, X], + [ + 0, 411, + float.NaN, float.NaN, 138, W / 2, X, + 412, X, + float.NaN, -138, W, 508, + W, 470, + ], + ], + + // Figure II-86. The scaled S form: terminal curls of 13.8 radius, sides on the vertical reference + // lines, and the middle diagonal. + ['s'] = + [ + [ + 535, 588, + float.NaN, 138, 422.6f, X, + 147.7f, X, + float.NaN, 138, 15, 470, + 465.7f, 271, + float.NaN, -141.6f, 408.4f, 0, + 127.4f, 0, + float.NaN, -138, 15, 59, + ], + ], + + // Figure II-87. A stem rising to 88.1, crossed at the x line, with the base turning through a + // U-arc dipping to -13.8 ink and rising to a tip at 11.8. + ['t'] = + [ + [0, X, 481, X], + [ + 138, 881, 138, 138, + float.NaN, 138, 276, 0, + 413.5f, 0, + float.NaN, 138, W, 118, + ], + ], + + // Figure II-88. The n form flipped, with the right stem running to its own base cap. + ['u'] = + [ + [W, 0, W, X], + [ + W, 176, + float.NaN, float.NaN, 138, W / 2, 0, + 155.9f, 0, + float.NaN, -138, 17.9f, 133.2f, + 0, X, + ], + ], + + // Figure II-89. Legs to a short base flat between 23.2 and 31.8. + ['v'] = + [ + [0, X, 0, 529, 232, 0, 318, 0, W, 529, W, X], + ], + + // Figure II-90. Outer stems leaning 2 degrees bending at 23.5 into flat valley bottoms between + // 6.9 and 13.8 and between 41.3 and 48.1, with the centre peak at 41.1. + ['w'] = + [ + [-14, X, 0, 235, 69, 0, 138, 0, W / 2, 235, 413, 0, 481, 0, W, 235, W + 14, X], + [W / 2, 235, W / 2, 411], + ], + + // Figure II-91. Diagonals between 1.5 and 53.5. + ['x'] = + [ + [15, 0, 535, X], + [15, X, 535, 0], + ], + + // Figure II-92. The left arm meets the descending right diagonal, which hooks left at the tail. + ['y'] = + [ + [0, X, 0, 529, 232, 0], + [W, X, W, 529, 189, -294, 0, -294], + ], + + // Figure II-93. The Z form at x height with round caps on all bar ends. + ['z'] = + [ + [34, X, W, X, W, 588, 0, 59, 0, 0, W, 0], + ], + }; + + /// + /// Gets, per character, the indices of strokes whose terminals are square: they receive projecting + /// square caps that extend T/2 past the centerline endpoint, mirroring how round caps project. Mixed + /// terminals on one path are drawn as two overlapping strokes, one per cap style. + /// + public static IReadOnlyDictionary SquareStrokes { get; } = new Dictionary + { + ['B'] = [0], + ['M'] = [1, 3], + ['T'] = [1], + ['P'] = [1, 2], + ['R'] = [1, 2], + ['⑂'] = [2], + }; + + /// + /// Gets, per character, the indices of strokes cut off exactly at their endpoints with no cap + /// projection: the attachment strokes whose butt faces sit on another stroke's ink corners. + /// + public static IReadOnlyDictionary ButtStrokes { get; } = new Dictionary + { + ['W'] = [2], + ['|'] = [0, 1], + ['―'] = [0], + ['%'] = [1, 2, 7, 8], + ['.'] = [0, 1], + [':'] = [0, 1, 6, 7], + [';'] = [1, 2], + ['a'] = [1], + ['b'] = [1], + ['d'] = [1], + ['e'] = [1], + ['g'] = [1], + ['i'] = [2, 3], + ['j'] = [2, 3], + }; + + /// + /// Gets, per character, the indices of strokes drawn with butt ends and sharp miter joins: the + /// vees whose drawings dimension the outer vertex at the exact miter point. + /// + public static IReadOnlyDictionary MiterStrokes { get; } = new Dictionary + { + ['M'] = [4], + }; + + /// + /// Gets, per character, the indices of strokes drawn with round terminal caps and sharp miter + /// joins: paths whose drawings show square outer corners between straight runs. + /// + public static IReadOnlyDictionary MiterRoundStrokes { get; } = new Dictionary + { + ['1'] = [0], + ['2'] = [0], + ['4'] = [0], + ['5'] = [0], + ['7'] = [1], + ['E'] = [0], + ['F'] = [0], + ['G'] = [0], + ['L'] = [0], + ['N'] = [0], + ['⑀'] = [0], + ['⑁'] = [1], + }; + + /// + /// Gets per-stroke width overrides keyed by character and stroke index, used for the dots and marks + /// whose ink is shallower than a full stroke. The value zero marks a stroke that is already a filled + /// ink polygon rather than a centerline to stroke. + /// + public static IReadOnlyDictionary<(char Character, int Stroke), float> StrokeWidthOverrides { get; } = + new Dictionary<(char Character, int Stroke), float> + { + [('.', 0)] = 257.5f, + [('.', 1)] = 277.5f, + [(':', 0)] = 257.5f, + [(':', 1)] = 277.5f, + [(':', 6)] = 257.5f, + [(':', 7)] = 277.5f, + [(';', 1)] = 257.5f, + [(';', 2)] = 277.5f, + [('%', 1)] = 257.5f, + [('%', 2)] = 277.5f, + [('%', 7)] = 257.5f, + [('%', 8)] = 277.5f, + [('█', 0)] = 0, + [('Z', 0)] = 0, + [('"', 0)] = 0, + [('i', 2)] = 199, + [('i', 3)] = 209, + [('j', 2)] = 199, + [('j', 3)] = 209, + }; + + /// + /// Gets the design definition the generator consumes. + /// + public static FontDesign Design { get; } = new() + { + Name = "OcrA", + FamilyName = "SixLabors OCRA", + DataSummary = "Built clean-room from the dimensioned character drawings of FIPS PUB 32 (1974).", + UnitsPerEm = UnitsPerEm, + H = H, + W = W, + Descender = D, + DefaultStrokeWidth = T, + SmallLetterHeight = SmallLetterHeight, + CapitalHeight = CapitalHeight, + AscenderHeight = AscenderHeight, + DescenderDepth = DescenderDepth, + + // Section 2.1 of FIPS PUB 32 states that i, j, m, p and w were designed to exceed the nominal + // values of Table II-1, and Figure II-88 dimensions t at its own height rather than on a line. + NormalizationExceptions = "ijmpwt", + DrawnCapitalHeight = CapitalHeight, + + // The reference OCR-A digitization prints its capital ink 780 units above the baseline per em, so + // the scale lands ours there and a point size means the same glyph size in both. + EmScale = 780F / CapitalHeight, + DrawnSmallLetterHeight = SmallLetterHeight, + DrawnAscenderHeight = AscenderHeight, + DrawnDescenderDepth = DescenderDepth, + CutTerminals = false, + Skeletons = Skeletons, + SquareStrokes = SquareStrokes, + ButtStrokes = ButtStrokes, + MiterStrokes = MiterStrokes, + MiterRoundStrokes = MiterRoundStrokes, + StrokeWidths = new Dictionary(), + StrokeWidthOverrides = StrokeWidthOverrides, + Expectations = SpecChecks.OcrA, + }; + + /// + /// Builds the six strokes of a rounded square mark of full stroke ink: two butt-cut bars crossing to + /// form the body and four round dots supplying the corner radii. Loop-free so the outline union never + /// sees a degenerate inner offset. + /// + /// The centerline left edge. + /// The centerline bottom edge. + /// The centerline right edge. + /// The centerline top edge. + /// The strokes. + private static float[][] SquareMark(float minX, float minY, float maxX, float maxY) => + [ + [minX, (minY + maxY) / 2, maxX, (minY + maxY) / 2], + [(minX + maxX) / 2, minY, (minX + maxX) / 2, maxY], + [minX, minY, minX + 0.1f, minY], + [maxX, minY, maxX + 0.1f, minY], + [minX, maxY, minX + 0.1f, maxY], + [maxX, maxY, maxX + 0.1f, maxY], + ]; +} diff --git a/src/ImageSharp.Drawing.FontGenerator/OcrBGlyphs.cs b/src/ImageSharp.Drawing.FontGenerator/OcrBGlyphs.cs new file mode 100644 index 00000000..382200f1 --- /dev/null +++ b/src/ImageSharp.Drawing.FontGenerator/OcrBGlyphs.cs @@ -0,0 +1,1942 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.FontGenerator; + +/// +/// The OCR-B glyph outlines, set against ECMA-11, the origin standard for OCR-B: each outline is +/// placed on the printed 4:1 character sheet of the standard and squared to the dimensions its +/// tables give. Coordinates are on the 0.0001 inch design grid shared with OCR-A: x = 275 at the +/// cell centre, baseline at zero. The vertical line, the pre-printed long vertical mark, the +/// continuous underline and the character erase are exact primitives, because the standard +/// dimensions those four in millimetres instead of drawing them. Entries whose stroke width is zero +/// are filled ink outlines; primitives are centerline strokes. +/// +internal static class OcrBGlyphs +{ + /// + /// Design units per em before the em scale, equal to the 0.1 inch pitch. + /// + public const ushort UnitsPerEm = 1000; + + /// + /// The nominal centerline height of digit eight, the tallest character: 0.094 inch. + /// + public const float H = 940; + + /// + /// The nominal centerline width of digit zero, the widest character: 0.055 inch. + /// + public const float W = 550; + + /// + /// The nominal stroke width for digits, capitals and most symbols: 0.014 inch. + /// + public const float T = 140; + + /// + /// The nominal stroke width for all small letters plus #, % and @: 0.012 inch. + /// + public const float TSmall = 120; + + /// + /// The descender centerline depth. ECMA-11 section 4.1 gives 0,60 mm below the baseline as + /// the typical descender depth. + /// + public const float D = -236; + + /// + /// One grid square of the reference drawings in design units: 0.02 mm. + /// + public const float Square = 7.874f; + + /// + /// Design units per millimetre. The design grid is the 0,0001 inch grid. + /// + public const float PerMillimetre = 10000F / 25.4F; + + /// + /// Dimension C of ECMA-11 section 4.1: a small letter stands 1,83 mm above the baseline. + /// + public const float SmallLetterHeight = 1.83F * PerMillimetre; + + /// + /// Dimension B of ECMA-11 section 4.1: a capital letter and a digit both stand 2,46 mm above the + /// baseline. The standard gives the two one dimension. + /// + public const float CapitalHeight = 2.46F * PerMillimetre; + + /// + /// Dimension A of ECMA-11 section 4.1: an ascender stands 2,60 mm above the baseline. + /// + public const float AscenderHeight = 2.60F * PerMillimetre; + + /// + /// Dimension D of ECMA-11 section 4.1: a descender hangs 0,60 mm below the baseline. + /// + public const float DescenderDepth = 0.60F * PerMillimetre; + + /// + /// Gets the glyph outlines and the dimensional primitives keyed by character, in the + /// stroke token format that BuildStroke in Program.cs documents. + /// + public static IReadOnlyDictionary Skeletons { get; } = new Dictionary + { + ['!'] = + [ + [ + 188.8f, 1237.5f, 213.2f, 462.4f, 360.6f, 462.4f, 389.2f, 1237.5f, 188.8f, 1237.5f, + ], + [ + 174.8f, 197.5f, 174.8f, 7, 375.2f, 7, 375.2f, 197.5f, 174.8f, 197.5f, + ], + ], + + ['"'] = + [ + [ + 100.3f, 745.6f, 156.2f, 1212.3f, -38.6f, 1212.3f, -38.6f, 745.6f, 100.3f, 745.6f, + ], + [ + 534.4f, 745.6f, 588.6f, 1212.3f, 393.8f, 1212.3f, 393.8f, 745.6f, 534.4f, 745.6f, + ], + ], + + ['#'] = + [ + [ + 218.7f, 831.5f, 288.3f, 1168.5f, 171.3f, 1168.5f, 103.1f, 831.5f, -12.5f, 831.5f, + -42.1f, 719.1f, 82.4f, 719.1f, 23.1f, 436.6f, -42.1f, 436.6f, -70.2f, 324.2f, + -0.6f, 324.2f, -70.2f, 0, 51.3f, 0, 120.9f, 324.2f, 329.8f, 324.2f, + 264.6f, 0, 387.6f, 0, 451.3f, 324.2f, 562.5f, 324.2f, 590.6f, 436.6f, + 470.6f, 436.6f, 529.9f, 719.1f, 590.6f, 719.1f, 620.2f, 831.5f, 552.1f, 831.5f, + 620.2f, 1168.5f, 498.7f, 1168.5f, 430.6f, 831.5f, 218.7f, 831.5f, + ], + [ + 140.2f, 437.5f, 197.9f, 711.5f, 408.4f, 711.5f, 349.1f, 437.5f, 140.2f, 437.5f, + ], + ], + + ['$'] = + [ + [ + 213, 691, float.NaN, float.NaN, float.NaN, float.NaN, 143.2f, 717.1f, 56.3f, 730.1f, + 56.3f, 829.4f, float.NaN, float.NaN, float.NaN, float.NaN, 56.3f, 923.9f, 141.6f, 938.5f, + 213, 938.5f, 213, 691, + ], + [ + 338.6f, 490.7f, float.NaN, float.NaN, float.NaN, float.NaN, 406.8f, 466.3f, 493.7f, 440.3f, + 493.7f, 347.5f, float.NaN, float.NaN, float.NaN, float.NaN, 493.7f, 254.7f, 413, 228.6f, + 338.6f, 222.1f, 338.6f, 490.7f, + ], + [ + 336.7f, 1150.8f, 214.8f, 1150.8f, 214.8f, 1073.4f, float.NaN, float.NaN, float.NaN, float.NaN, + 64.2f, 1060.7f, -92.4f, 1003.8f, -92.4f, 817.3f, float.NaN, float.NaN, float.NaN, float.NaN, + -92.4f, 641.8f, 82.3f, 578.6f, 214.8f, 535.9f, 214.8f, 229.2f, 193.7f, 229.2f, + float.NaN, float.NaN, float.NaN, float.NaN, 124.4f, 229.2f, 59.7f, 251.3f, 62.7f, 336.7f, + -92.4f, 336.7f, -92.4f, 306.7f, float.NaN, float.NaN, float.NaN, float.NaN, -92.4f, 161.2f, + 65.7f, 80.6f, 186.2f, 80.6f, 214.8f, 80.6f, 214.8f, 0, 336.7f, 0, + 336.7f, 80.6f, float.NaN, float.NaN, float.NaN, float.NaN, 481.3f, 93.3f, 642.4f, 188.1f, + 642.4f, 358.8f, float.NaN, float.NaN, float.NaN, float.NaN, 642.4f, 540.6f, 476.7f, 602.3f, + 336.7f, 651.3f, 336.7f, 924.8f, float.NaN, float.NaN, float.NaN, float.NaN, 413.5f, 924.8f, + 463.2f, 913.7f, 458.7f, 817.3f, 613.7f, 817.3f, 613.7f, 844.1f, float.NaN, float.NaN, + float.NaN, float.NaN, 613.7f, 988, 449.6f, 1062.3f, 336.7f, 1073.4f, 336.7f, 1150.8f, + ], + ], + + ['%'] = + [ + [ + 97.4f, 1124.8f, float.NaN, float.NaN, float.NaN, float.NaN, -0.9f, 1124.8f, -70.5f, 1046.9f, + -70.5f, 938.5f, float.NaN, float.NaN, float.NaN, float.NaN, -70.5f, 839.2f, -3.9f, 756.7f, + 97.4f, 756.7f, float.NaN, float.NaN, float.NaN, float.NaN, 198.7f, 756.7f, 265.2f, 839.2f, + 265.2f, 938.5f, float.NaN, float.NaN, float.NaN, float.NaN, 265.2f, 1046.9f, 195.6f, 1124.8f, + 97.4f, 1124.8f, + ], + [ + 95.6f, 1036.2f, float.NaN, float.NaN, float.NaN, float.NaN, 157.2f, 1036.2f, 157.2f, 988.9f, + 157.2f, 938.5f, float.NaN, float.NaN, float.NaN, float.NaN, 157.2f, 897.2f, 157.2f, 845.3f, + 95.6f, 845.3f, float.NaN, float.NaN, float.NaN, float.NaN, 34, 845.3f, 34, 897.2f, + 34, 938.5f, float.NaN, float.NaN, float.NaN, float.NaN, 34, 988.9f, 34, 1036.2f, + 95.6f, 1036.2f, + ], + [ + 460.7f, 365, float.NaN, float.NaN, float.NaN, float.NaN, 360.9f, 365, 292.8f, 284.8f, + 292.8f, 173.1f, float.NaN, float.NaN, float.NaN, float.NaN, 292.8f, 70.8f, 359.3f, -14.2f, + 460.7f, -14.2f, float.NaN, float.NaN, float.NaN, float.NaN, 562, -14.2f, 628.5f, 70.8f, + 628.5f, 173.1f, float.NaN, float.NaN, float.NaN, float.NaN, 628.5f, 284.8f, 558.9f, 365, + 460.7f, 365, + ], + [ + 455.4f, 271.8f, float.NaN, float.NaN, float.NaN, float.NaN, 517, 271.8f, 517, 225.9f, + 517, 177, float.NaN, float.NaN, float.NaN, float.NaN, 517, 137, 517, 86.6f, + 455.4f, 86.6f, float.NaN, float.NaN, float.NaN, float.NaN, 393.8f, 86.6f, 393.8f, 137, + 393.8f, 177, float.NaN, float.NaN, float.NaN, float.NaN, 393.8f, 225.9f, 393.8f, 271.8f, + 455.4f, 271.8f, + ], + [ + 550, 1145.3f, -60.5f, 85, -60.5f, 0, 23.6f, 0, 626.3f, 1054.1f, + 626.3f, 1145.3f, 550, 1145.3f, + ], + ], + + ['&'] = + [ + [ + 159.7f, 698, float.NaN, float.NaN, float.NaN, float.NaN, 125.4f, 747.2f, 78.3f, 820.3f, + 78.3f, 883.8f, float.NaN, float.NaN, float.NaN, float.NaN, 78.3f, 972.8f, 134, 1034.7f, + 212.5f, 1034.7f, float.NaN, float.NaN, float.NaN, float.NaN, 279.6f, 1034.7f, 345.2f, 1001.4f, + 345.2f, 917.2f, float.NaN, float.NaN, float.NaN, float.NaN, 345.2f, 829.8f, 209.7f, 747.2f, + 159.7f, 698, + ], + [ + 355.6f, 156.2f, float.NaN, float.NaN, float.NaN, float.NaN, 323.7f, 134.9f, 285.9f, 125.1f, + 249.5f, 125.1f, float.NaN, float.NaN, float.NaN, float.NaN, 143.4f, 125.1f, 51, 195.4f, + 51, 318.1f, float.NaN, float.NaN, float.NaN, float.NaN, 51, 381.9f, 94.9f, 445.7f, + 138.9f, 485, 355.6f, 156.2f, + ], + [ + 454.9f, 0, 638.9f, 0, 532.9f, 155.7f, float.NaN, float.NaN, float.NaN, float.NaN, + 590.3f, 238.3f, 625.6f, 401.9f, 625.6f, 500.4f, 625.6f, 527.4f, 474.1f, 527.4f, + 474.1f, 490.8f, float.NaN, float.NaN, float.NaN, float.NaN, 474.1f, 420.9f, 465.2f, 349.5f, + 446.1f, 282.7f, 244.5f, 581.4f, 331.3f, 651.3f, float.NaN, float.NaN, float.NaN, float.NaN, + 415.2f, 718, 513.8f, 813.3f, 513.8f, 934, float.NaN, float.NaN, float.NaN, float.NaN, + 513.8f, 1092.8f, 365.2f, 1177, 235.7f, 1177, float.NaN, float.NaN, float.NaN, float.NaN, + 94.4f, 1177, -64.5f, 1086.5f, -64.5f, 913.3f, float.NaN, float.NaN, float.NaN, float.NaN, + -64.5f, 806.9f, 4.7f, 691, 59.1f, 608.4f, float.NaN, float.NaN, float.NaN, float.NaN, + -32.1f, 543.2f, -101.2f, 428.9f, -101.2f, 306.6f, float.NaN, float.NaN, float.NaN, float.NaN, + -101.2f, 101.7f, 42.9f, -14.3f, 222.5f, -14.3f, float.NaN, float.NaN, float.NaN, float.NaN, + 294.6f, -14.3f, 360.8f, -1.6f, 424, 42.9f, 454.9f, 0, + ], + ], + + ['\''] = + [ + [ + 198.2f, 628.7f, 351.8f, 628.7f, 383.5f, 1177.6f, 166.6f, 1177.6f, 198.2f, 628.7f, + ], + ], + + ['('] = + [ + [ + 362.9f, 1161.8f, float.NaN, float.NaN, float.NaN, float.NaN, 189, 1047.4f, 54.5f, 801.2f, + 54.5f, 580.1f, float.NaN, float.NaN, float.NaN, float.NaN, 54.5f, 360.6f, 189, 112.9f, + 362.9f, 0, 475.5f, 0, 475.5f, 112.9f, float.NaN, float.NaN, float.NaN, float.NaN, + 319.1f, 153.7f, 205.1f, 421.8f, 205.1f, 580.1f, float.NaN, float.NaN, float.NaN, float.NaN, + 205.1f, 738.5f, 319.1f, 1006.6f, 475.5f, 1048.9f, 475.5f, 1161.8f, 362.9f, 1161.8f, + ], + ], + + [')'] = + [ + [ + 70.5f, 1161.8f, 70.5f, 1048.9f, float.NaN, float.NaN, float.NaN, float.NaN, 224, 1006.6f, + 338.4f, 738.5f, 338.4f, 580.1f, float.NaN, float.NaN, float.NaN, float.NaN, 338.4f, 421.8f, + 224, 153.7f, 70.5f, 112.9f, 70.5f, 0, 182, 0, float.NaN, float.NaN, + float.NaN, float.NaN, 354.3f, 112.9f, 487.5f, 360.6f, 487.5f, 580.1f, float.NaN, float.NaN, + float.NaN, float.NaN, 487.5f, 801.2f, 354.3f, 1047.4f, 182, 1161.8f, 70.5f, 1161.8f, + ], + ], + + ['*'] = + [ + [ + 329.8f, 590.9f, 351.2f, 886.3f, 195.8f, 886.3f, 209.5f, 590.9f, -35.7f, 700.9f, + -98.2f, 543.7f, 165.3f, 457.3f, -6.8f, 232.6f, 116.6f, 139.9f, 276.5f, 381.9f, + 425.8f, 139.9f, 564.4f, 232.6f, 380.1f, 457.3f, 648.2f, 534.3f, 581.2f, 700.9f, + 329.8f, 590.9f, + ], + ], + + ['+'] = + [ + [ + 204.9f, 634.7f, -63.3f, 634.7f, -63.3f, 467.5f, 204.9f, 467.5f, 204.9f, 123.4f, + 343.7f, 123.4f, 343.7f, 467.5f, 613.3f, 467.5f, 613.3f, 634.7f, 343.7f, 634.7f, + 343.7f, 936.6f, 204.9f, 936.6f, 204.9f, 634.7f, + ], + ], + + [','] = + [ + [ + 297.8f, 276.4f, 3.1f, -270.2f, 169.9f, -270.2f, 560.9f, 157.7f, 560.9f, 276.4f, + 297.8f, 276.4f, + ], + ], + + ['-'] = + [ + [ + -67.7f, 544.6f, 617.7f, 544.6f, 617.7f, 739.3f, -67.7f, 739.3f, -67.7f, 544.6f, + ], + ], + + ['.'] = + [ + [ + 119.4f, 269.3f, 119.4f, 0, 430.7f, 0, 430.7f, 269.3f, 119.4f, 269.3f, + ], + ], + + ['/'] = + [ + [ + 423.3f, 1195.7f, -24.5f, 0, 128.3f, 0, 580.5f, 1195.7f, 423.3f, 1195.7f, + ], + ], + + ['0'] = + [ + [ + 279, 1137, float.NaN, float.NaN, float.NaN, float.NaN, 130.9f, 1137, 11.5f, 1120.1f, + -39.9f, 968.2f, float.NaN, float.NaN, float.NaN, float.NaN, -80.7f, 847, -88.3f, 704.3f, + -88.3f, 576.9f, float.NaN, float.NaN, float.NaN, float.NaN, -88.3f, 440.4f, -80.7f, 151.9f, + 19, 53.7f, float.NaN, float.NaN, float.NaN, float.NaN, 75, -1.5f, 191.3f, -13.8f, + 279, -13.8f, float.NaN, float.NaN, float.NaN, float.NaN, 365.2f, -13.8f, 483, -1.5f, + 537.5f, 53.7f, float.NaN, float.NaN, float.NaN, float.NaN, 638.7f, 151.9f, 646.3f, 440.4f, + 646.3f, 576.9f, float.NaN, float.NaN, float.NaN, float.NaN, 646.3f, 704.3f, 638.7f, 847, + 597.9f, 968.2f, float.NaN, float.NaN, float.NaN, float.NaN, 546.5f, 1120.1f, 425.6f, 1137, + 279, 1137, + ], + [ + 279, 1008.6f, float.NaN, float.NaN, float.NaN, float.NaN, 399.9f, 1008.6f, 454.3f, 1003.8f, + 474, 882.1f, float.NaN, float.NaN, float.NaN, float.NaN, 487.6f, 795.2f, 490.6f, 653, + 490.6f, 562.9f, float.NaN, float.NaN, float.NaN, float.NaN, 490.6f, 479.1f, 486.1f, 223.1f, + 443.7f, 163, float.NaN, float.NaN, float.NaN, float.NaN, 415, 121.9f, 330.4f, 120.3f, + 279, 120.3f, float.NaN, float.NaN, float.NaN, float.NaN, 227.6f, 120.3f, 143, 121.9f, + 114.3f, 163, float.NaN, float.NaN, float.NaN, float.NaN, 70.4f, 223.1f, 67.4f, 479.1f, + 67.4f, 562.9f, float.NaN, float.NaN, float.NaN, float.NaN, 67.4f, 653, 68.9f, 795.2f, + 84, 882.1f, float.NaN, float.NaN, float.NaN, float.NaN, 103.7f, 1003.8f, 158.1f, 1008.6f, + 279, 1008.6f, + ], + ], + + ['1'] = + [ + [ + 323.7f, 956.3f, 323.7f, 0, 470, 0, 470, 1137.6f, 312.3f, 1137.6f, + 60.9f, 906.3f, 60.9f, 706.3f, 323.7f, 956.3f, + ], + ], + + ['2'] = + [ + [ + 95.9f, 146.6f, 95.9f, 170, float.NaN, float.NaN, float.NaN, float.NaN, 95.9f, 301, + 212.4f, 393.1f, 308.1f, 460.1f, float.NaN, float.NaN, float.NaN, float.NaN, 452.5f, 558.4f, + 594, 637.9f, 594, 843.8f, float.NaN, float.NaN, float.NaN, float.NaN, 594, 1052.8f, + 437.8f, 1155.8f, 253.6f, 1155.8f, float.NaN, float.NaN, float.NaN, float.NaN, 156.4f, 1155.8f, + 42.9f, 1124.6f, -36.7f, 1060.6f, -36.7f, 887.5f, float.NaN, float.NaN, float.NaN, float.NaN, + 45.8f, 951.4f, 172.6f, 1009.2f, 277.2f, 1009.2f, float.NaN, float.NaN, float.NaN, float.NaN, + 362.7f, 1009.2f, 442.2f, 929.6f, 442.2f, 837.6f, float.NaN, float.NaN, float.NaN, float.NaN, + 442.2f, 725.3f, 322.9f, 653.5f, 243.3f, 600.5f, float.NaN, float.NaN, float.NaN, float.NaN, + 37, 461.7f, -55.8f, 354.1f, -55.8f, 85.8f, -55.8f, 0, 580.7f, 0, + 580.7f, 146.6f, 95.9f, 146.6f, + ], + ], + + ['3'] = + [ + [ + 580.7f, 1141.7f, -65.2f, 1141.7f, -65.2f, 994.3f, 388.4f, 994.3f, 113.9f, 694.7f, + 113.9f, 570.8f, 231.3f, 570.8f, float.NaN, float.NaN, float.NaN, float.NaN, 360.5f, 570.8f, + 472.1f, 512.8f, 472.1f, 360.7f, float.NaN, float.NaN, float.NaN, float.NaN, 472.1f, 196, + 342.9f, 133.3f, 207.8f, 133.3f, float.NaN, float.NaN, float.NaN, float.NaN, 110.9f, 133.3f, + 18.5f, 156.8f, -65.2f, 214.8f, -65.2f, 53.3f, float.NaN, float.NaN, float.NaN, float.NaN, + 24.3f, 12.5f, 112.4f, -14.1f, 210.8f, -14.1f, float.NaN, float.NaN, float.NaN, float.NaN, + 425.1f, -14.1f, 623.3f, 101.9f, 623.3f, 356, float.NaN, float.NaN, float.NaN, float.NaN, + 623.3f, 558.3f, 495.5f, 685.3f, 312.1f, 694.7f, 580.7f, 981.7f, 580.7f, 1141.7f, + ], + ], + + ['4'] = + [ + [ + 481, 387.5f, 481, 616.3f, 327.6f, 616.3f, 327.6f, 387.5f, 65.4f, 387.5f, + 393.1f, 1110.6f, 235.2f, 1110.6f, -83.5f, 408.8f, -83.5f, 244.1f, 327.6f, 244.1f, + 327.6f, 0, 481, 0, 481, 244.1f, 613.5f, 244.1f, 613.5f, 387.5f, + 481, 387.5f, + ], + ], + + ['5'] = + [ + [ + 137.2f, 994.3f, 552.9f, 994.3f, 552.9f, 1141.7f, 0.2f, 1141.7f, -33, 611.6f, + 153.8f, 625.7f, float.NaN, float.NaN, float.NaN, float.NaN, 242.6f, 632, 427.9f, 611.6f, + 427.9f, 409.3f, float.NaN, float.NaN, float.NaN, float.NaN, 427.9f, 192.9f, 232.1f, 133.3f, + 55.9f, 133.3f, -33, 133.3f, -33, -14.1f, 18.2f, -14.1f, float.NaN, float.NaN, + float.NaN, float.NaN, 153.8f, -14.1f, 583, 22, 583, 407.7f, float.NaN, float.NaN, + float.NaN, float.NaN, 583, 671.2f, 381.2f, 773.1f, 155.3f, 773.1f, 119.1f, 773.1f, + 137.2f, 994.3f, + ], + ], + + ['6'] = + [ + [ + 337, 1150.6f, 60.4f, 817.1f, float.NaN, float.NaN, float.NaN, float.NaN, -47, 687.5f, + -93.8f, 551.6f, -93.8f, 377.7f, float.NaN, float.NaN, float.NaN, float.NaN, -93.8f, 132.8f, + 40.7f, -14.2f, 276.5f, -14.2f, float.NaN, float.NaN, float.NaN, float.NaN, 488.1f, -14.2f, + 643.8f, 134.3f, 643.8f, 361.9f, float.NaN, float.NaN, float.NaN, float.NaN, 643.8f, 562.6f, + 506.3f, 739.7f, 306.7f, 739.7f, float.NaN, float.NaN, float.NaN, float.NaN, 256.9f, 739.7f, + 211.5f, 730.2f, 167.7f, 709.6f, 166.2f, 712.8f, 238.7f, 812.4f, float.NaN, float.NaN, + float.NaN, float.NaN, 323.4f, 926.2f, 427.7f, 1049.4f, 532, 1150.6f, 337, 1150.6f, + ], + [ + 272, 594.8f, float.NaN, float.NaN, float.NaN, float.NaN, 397.4f, 594.8f, 488.1f, 500.4f, + 488.1f, 362, float.NaN, float.NaN, float.NaN, float.NaN, 488.1f, 218.8f, 402, 124.4f, + 275, 124.4f, float.NaN, float.NaN, float.NaN, float.NaN, 145, 124.4f, 61.9f, 223.7f, + 61.9f, 363.7f, float.NaN, float.NaN, float.NaN, float.NaN, 61.9f, 495.5f, 149.5f, 594.8f, + 272, 594.8f, + ], + ], + + ['7'] = + [ + [ + 452.6f, 998.5f, float.NaN, float.NaN, float.NaN, float.NaN, 448.1f, 957.5f, 419.4f, 922.9f, + 392.3f, 891.4f, 337.9f, 825.3f, float.NaN, float.NaN, float.NaN, float.NaN, 184, 636.3f, + 72.3f, 437.8f, 72.3f, 184.3f, 72.3f, 0, 227.8f, 0, 227.8f, 184.3f, + float.NaN, float.NaN, float.NaN, float.NaN, 227.8f, 453.6f, 336.4f, 590.6f, 496.4f, 787.5f, + float.NaN, float.NaN, float.NaN, float.NaN, 529.6f, 830, 586.9f, 902.4f, 609.6f, 951.2f, + float.NaN, float.NaN, float.NaN, float.NaN, 636.7f, 1011.1f, 638.3f, 1061.5f, 638.3f, 1127.6f, + 638.3f, 1146.5f, -84.6f, 1146.5f, -84.6f, 998.5f, 452.6f, 998.5f, + ], + ], + + ['8'] = + [ + [ + 271, 121.9f, float.NaN, float.NaN, float.NaN, float.NaN, 164.4f, 121.9f, 54.8f, 176.3f, + 54.8f, 309, float.NaN, float.NaN, float.NaN, float.NaN, 54.8f, 435.4f, 159.8f, 492.9f, + 271, 555.3f, float.NaN, float.NaN, float.NaN, float.NaN, 380.7f, 492.9f, 487.2f, 435.4f, + 487.2f, 309, float.NaN, float.NaN, float.NaN, float.NaN, 487.2f, 176.3f, 377.6f, 121.9f, + 271, 121.9f, + ], + [ + 271, 1150.5f, float.NaN, float.NaN, float.NaN, float.NaN, 118.1f, 1150.5f, -51.4f, 1080.6f, + -51.4f, 900.5f, float.NaN, float.NaN, float.NaN, float.NaN, -51.4f, 770.1f, 40.1f, 695.6f, + 134.5f, 631.9f, float.NaN, float.NaN, float.NaN, float.NaN, -0.4f, 563.6f, -93.4f, 467.3f, + -93.4f, 299.7f, float.NaN, float.NaN, float.NaN, float.NaN, -93.4f, 69.9f, 71.6f, -14, + 271, -14, float.NaN, float.NaN, float.NaN, float.NaN, 468.9f, -14, 635.4f, 69.9f, + 635.4f, 299.7f, float.NaN, float.NaN, float.NaN, float.NaN, 635.4f, 467.3f, 542.4f, 563.6f, + 406, 631.9f, float.NaN, float.NaN, float.NaN, float.NaN, 501.9f, 695.6f, 593.4f, 770.1f, + 593.4f, 900.5f, float.NaN, float.NaN, float.NaN, float.NaN, 593.4f, 1080.6f, 423.9f, 1150.5f, + 271, 1150.5f, + ], + [ + 264, 1004.5f, float.NaN, float.NaN, float.NaN, float.NaN, 339.7f, 1004.5f, 437, 981.2f, + 437, 886.5f, float.NaN, float.NaN, float.NaN, float.NaN, 437, 798, 333.5f, 737.5f, + 264, 709.5f, float.NaN, float.NaN, float.NaN, float.NaN, 194.5f, 737.5f, 91, 798, + 91, 886.5f, float.NaN, float.NaN, float.NaN, float.NaN, 91, 981.2f, 188.3f, 1004.5f, + 264, 1004.5f, + ], + ], + + ['9'] = + [ + [ + 214.2f, 0, float.NaN, float.NaN, float.NaN, float.NaN, 401.9f, 205.8f, 643.7f, 477.2f, + 643.7f, 754.7f, float.NaN, float.NaN, float.NaN, float.NaN, 643.7f, 993.3f, 510.1f, 1155.5f, + 278.8f, 1155.5f, float.NaN, float.NaN, float.NaN, float.NaN, 56.5f, 1155.5f, -93.7f, 1005.8f, + -93.7f, 775, float.NaN, float.NaN, float.NaN, float.NaN, -93.7f, 569.2f, 50.5f, 410.1f, + 248.7f, 410.1f, float.NaN, float.NaN, float.NaN, float.NaN, 293.8f, 410.1f, 340.3f, 421, + 376.4f, 439.7f, 379.4f, 436.6f, 380.9f, 438.2f, float.NaN, float.NaN, float.NaN, float.NaN, + 278.8f, 276, 143.6f, 135.7f, 8.4f, 0, 214.2f, 0, + ], + [ + 278.8f, 1008.9f, float.NaN, float.NaN, float.NaN, float.NaN, 406.4f, 1008.9f, 489, 916.9f, + 489, 782.8f, float.NaN, float.NaN, float.NaN, float.NaN, 489, 651.8f, 403.4f, 556.7f, + 278.8f, 556.7f, float.NaN, float.NaN, float.NaN, float.NaN, 151.1f, 556.7f, 61, 647.1f, + 61, 779.7f, float.NaN, float.NaN, float.NaN, float.NaN, 61, 924.7f, 140.6f, 1008.9f, + 278.8f, 1008.9f, + ], + ], + + [':'] = + [ + [ + 135, 818, 135, 569.9f, 421.1f, 569.9f, 421.1f, 818, 135, 818, + ], + [ + 123.5f, 248.2f, 123.5f, 0, 418.5f, 0, 418.5f, 248.2f, 123.5f, 248.2f, + ], + ], + + [';'] = + [ + [ + 231.3f, 238, -5.2f, -272.7f, 180.6f, -272.7f, 546.2f, 238, 231.3f, 238, + ], + [ + 243.9f, 789.6f, 243.9f, 550.1f, 508.6f, 550.1f, 508.6f, 789.6f, 243.9f, 789.6f, + ], + ], + + ['<'] = + [ + [ + 626.9f, 313, 120.2f, 645.1f, 626.9f, 977.1f, 626.9f, 1174.3f, -104.9f, 664.1f, + -104.9f, 620.9f, 626.9f, 112.4f, 626.9f, 313, + ], + ], + + ['='] = + [ + [ + -59, 297.1f, 609, 297.1f, 609, 428.6f, -59, 428.6f, -59, 297.1f, + ], + [ + -59, 642, 609, 642, 609, 777.6f, -59, 777.6f, -59, 642, + ], + ], + + ['>'] = + [ + [ + 653, 661.5f, -67, 1169.7f, -67, 973.3f, 431.6f, 642.6f, -67, 311.8f, + -67, 112, 653, 618.5f, 653, 661.5f, + ], + ], + + ['?'] = + [ + [ + 385.1f, 396.1f, 385.1f, 425.2f, float.NaN, float.NaN, float.NaN, float.NaN, 385.1f, 478.6f, + 397.3f, 509.3f, 432.5f, 541.6f, float.NaN, float.NaN, float.NaN, float.NaN, 521.2f, 628.9f, + 576.2f, 701.7f, 576.2f, 835.9f, float.NaN, float.NaN, float.NaN, float.NaN, 576.2f, 1010.5f, + 446.2f, 1110.7f, 290.3f, 1110.7f, float.NaN, float.NaN, float.NaN, float.NaN, 84, 1110.7f, + -18.5f, 1004, -18.5f, 795.4f, -18.5f, 756.6f, 139, 756.6f, 139, 777.7f, + float.NaN, float.NaN, float.NaN, float.NaN, 139, 881.1f, 178.7f, 958.7f, 287.3f, 958.7f, + float.NaN, float.NaN, float.NaN, float.NaN, 371.3f, 958.7f, 418.7f, 897.3f, 418.7f, 813.2f, + float.NaN, float.NaN, float.NaN, float.NaN, 418.7f, 738.9f, 389.7f, 701.7f, 342.3f, 654.8f, + float.NaN, float.NaN, float.NaN, float.NaN, 314.8f, 630.5f, 259.8f, 573.9f, 244.5f, 541.6f, + float.NaN, float.NaN, float.NaN, float.NaN, 227.6f, 507.7f, 229.2f, 472.1f, 229.2f, 433.3f, + 229.2f, 396.1f, 385.1f, 396.1f, + ], + [ + 193.7f, 203.9f, 193.7f, 0, 404.2f, 0, 404.2f, 203.9f, 193.7f, 203.9f, + ], + ], + + ['@'] = + [ + [ + 366.5f, 613.4f, 247.5f, 613.4f, 247.5f, 570.6f, 226.7f, 589.6f, float.NaN, float.NaN, + float.NaN, float.NaN, 198.4f, 613.4f, 165.7f, 621.3f, 139, 621.3f, float.NaN, float.NaN, + float.NaN, float.NaN, -42.5f, 621.3f, -70.7f, 467.6f, -70.7f, 378.8f, -70.7f, 220.3f, + float.NaN, float.NaN, float.NaN, float.NaN, -70.7f, 80.8f, 2.1f, -14.3f, 139, -14.3f, + float.NaN, float.NaN, float.NaN, float.NaN, 195.5f, -14.3f, 250.5f, 7.9f, 286.2f, 58.6f, + 292.1f, 69.7f, 305.5f, 46, float.NaN, float.NaN, float.NaN, float.NaN, 332.3f, -3.2f, + 373.9f, -14.3f, 426, -14.3f, float.NaN, float.NaN, float.NaN, float.NaN, 595.5f, -14.3f, + 620.8f, 144.2f, 620.8f, 291.6f, 620.8f, 599.1f, float.NaN, float.NaN, float.NaN, float.NaN, + 620.8f, 874.9f, 525.6f, 1074.6f, 266.8f, 1074.6f, float.NaN, float.NaN, float.NaN, float.NaN, + 72, 1074.6f, -70.7f, 987.4f, -70.7f, 779.8f, -70.7f, 765.5f, 48.2f, 765.5f, + 48.2f, 794.1f, float.NaN, float.NaN, float.NaN, float.NaN, 48.2f, 908.2f, 158.3f, 952.5f, + 266.8f, 952.5f, float.NaN, float.NaN, float.NaN, float.NaN, 436.4f, 952.5f, 501.8f, 828.9f, + 501.8f, 648.2f, 501.8f, 309.1f, float.NaN, float.NaN, float.NaN, float.NaN, 501.8f, 191.8f, + 489.9f, 107.8f, 426, 107.8f, float.NaN, float.NaN, float.NaN, float.NaN, 370.9f, 107.8f, + 366.5f, 164.8f, 366.5f, 204.5f, 366.5f, 613.4f, + ], + [ + 55.1f, 376.2f, float.NaN, float.NaN, float.NaN, float.NaN, 55.1f, 444.7f, 83.9f, 500.2f, + 143.1f, 500.2f, float.NaN, float.NaN, float.NaN, float.NaN, 219.5f, 500.2f, 248.4f, 444.7f, + 248.4f, 376.2f, 248.4f, 212.9f, float.NaN, float.NaN, float.NaN, float.NaN, 248.4f, 146, + 219.5f, 97, 143.1f, 97, float.NaN, float.NaN, float.NaN, float.NaN, 82.5f, 97, + 55.1f, 146, 55.1f, 212.9f, 55.1f, 376.2f, + ], + ], + + ['A'] = + [ + [ + 386.4f, 451.4f, 163.5f, 451.4f, 274.2f, 903.5f, 277.2f, 903.5f, 386.4f, 451.4f, + ], + [ + 420.8f, 309.1f, 494.1f, 0, 649.7f, 0, 373, 1074.9f, 177, 1074.9f, + -99.7f, 0, 55.8f, 0, 127.6f, 309.1f, 420.8f, 309.1f, + ], + ], + + ['B'] = + [ + [ + 56.4f, 910.4f, 243.7f, 910.4f, float.NaN, float.NaN, float.NaN, float.NaN, 343.5f, 910.4f, + 463.2f, 893.3f, 463.2f, 762.3f, float.NaN, float.NaN, float.NaN, float.NaN, 463.2f, 634.5f, + 363.4f, 623.6f, 265.2f, 623.6f, 56.4f, 623.6f, 56.4f, 910.4f, + ], + [ + 56.4f, 476.3f, 262.1f, 476.3f, float.NaN, float.NaN, float.NaN, float.NaN, 383.4f, 476.3f, + 487.8f, 449.1f, 487.8f, 305.5f, float.NaN, float.NaN, float.NaN, float.NaN, 487.8f, 169.4f, + 383.4f, 142.1f, 269.8f, 142.1f, 56.4f, 142.1f, 56.4f, 476.3f, + ], + [ + 304.7f, 0, float.NaN, float.NaN, float.NaN, float.NaN, 538.7f, 0, 641.6f, 154.3f, + 641.6f, 314.9f, float.NaN, float.NaN, float.NaN, float.NaN, 641.6f, 417.8f, 589.4f, 528.5f, + 488, 551.9f, 488, 555, float.NaN, float.NaN, float.NaN, float.NaN, 570, 584.6f, + 617.7f, 665.7f, 617.7f, 754.5f, float.NaN, float.NaN, float.NaN, float.NaN, 617.7f, 988.4f, + 431.4f, 1057, 242.1f, 1057, -84.3f, 1057, -84.3f, 0, 304.7f, 0, + ], + ], + + ['C'] = + [ + [ + 572, 837.6f, float.NaN, float.NaN, float.NaN, float.NaN, 572, 992.4f, 446.1f, 1096.1f, + 307, 1096.1f, float.NaN, float.NaN, float.NaN, float.NaN, 42, 1096.1f, -30.4f, 786.5f, + -30.4f, 537.7f, float.NaN, float.NaN, float.NaN, float.NaN, -30.4f, 301.5f, 42, -14.4f, + 307, -14.4f, float.NaN, float.NaN, float.NaN, float.NaN, 446.1f, -14.4f, 572, 92.5f, + 572, 253.7f, 422.9f, 253.7f, float.NaN, float.NaN, float.NaN, float.NaN, 418.5f, 183.5f, + 363.5f, 135.6f, 301.2f, 135.6f, float.NaN, float.NaN, float.NaN, float.NaN, 140.5f, 135.6f, + 118.8f, 416.4f, 118.8f, 545.6f, float.NaN, float.NaN, float.NaN, float.NaN, 118.8f, 673.3f, + 143.4f, 946.1f, 301.2f, 946.1f, float.NaN, float.NaN, float.NaN, float.NaN, 363.5f, 946.1f, + 418.5f, 903, 422.9f, 837.6f, 572, 837.6f, + ], + ], + + ['D'] = + [ + [ + -60f, 0, 51.8f, 0, float.NaN, float.NaN, float.NaN, float.NaN, 361.3f, 0, + 586.4f, 186.4f, 586.4f, 509, float.NaN, float.NaN, float.NaN, float.NaN, 586.4f, 830.1f, + 347.2f, 1061.9f, 31.4f, 1061.9f, -60f, 1061.9f, -60f, 0, + ], + [ + 103.6f, 914.6f, float.NaN, float.NaN, float.NaN, float.NaN, 300.9f, 905.3f, 426.9f, 703.2f, + 426.9f, 512.1f, float.NaN, float.NaN, float.NaN, float.NaN, 426.9f, 311.7f, 310, 142.5f, + 103.6f, 147.2f, 103.6f, 914.6f, + ], + ], + + ['E'] = + [ + [ + 98.9f, 925.5f, 563.6f, 925.5f, 563.6f, 1074.5f, -59.1f, 1074.5f, -59.1f, 0, + 582, 0, 582, 149, 98.9f, 149, 98.9f, 486.5f, 503.8f, 486.5f, + 503.8f, 635.5f, 98.9f, 635.5f, 98.9f, 925.5f, + ], + ], + + ['F'] = + [ + [ + 151.9f, 933.3f, 536.9f, 933.3f, 536.9f, 1083.5f, -4.9f, 1083.5f, -4.9f, 0, + 151.9f, 0, 151.9f, 490.6f, 462.3f, 490.6f, 462.3f, 640.9f, 151.9f, 640.9f, + 151.9f, 933.3f, + ], + ], + + ['G'] = + [ + [ + 469.2f, 429.3f, 469.2f, 161.2f, float.NaN, float.NaN, float.NaN, float.NaN, 429.7f, 145.2f, + 388.6f, 135.6f, 343, 135.6f, float.NaN, float.NaN, float.NaN, float.NaN, 114.8f, 135.6f, + 88.9f, 367, 88.9f, 556.9f, float.NaN, float.NaN, float.NaN, float.NaN, 88.9f, 761.2f, + 125.4f, 946.3f, 344.5f, 946.3f, float.NaN, float.NaN, float.NaN, float.NaN, 412.9f, 946.3f, + 454, 928.7f, 469.2f, 864.9f, 625.9f, 864.9f, 625.9f, 877.7f, float.NaN, float.NaN, + float.NaN, float.NaN, 625.9f, 987.8f, 495.1f, 1096.3f, 344.5f, 1096.3f, float.NaN, float.NaN, + float.NaN, float.NaN, 23.5f, 1096.3f, -67.8f, 833, -67.8f, 542.5f, float.NaN, float.NaN, + float.NaN, float.NaN, -67.8f, 264.9f, 17.4f, -14.4f, 329.3f, -14.4f, float.NaN, float.NaN, + float.NaN, float.NaN, 428.2f, -14.4f, 534.7f, 11.2f, 625.9f, 51.1f, 625.9f, 579.3f, + 256.2f, 579.3f, 256.2f, 429.3f, 469.2f, 429.3f, + ], + ], + + ['H'] = + [ + [ + 453.1f, 472.3f, 453.1f, 0, 615.4f, 0, 615.4f, 1049.9f, 453.1f, 1049.9f, + 453.1f, 617.9f, 96.9f, 617.9f, 96.9f, 1049.9f, -65.4f, 1049.9f, -65.4f, 0, + 96.9f, 0, 96.9f, 472.3f, 453.1f, 472.3f, + ], + ], + + ['I'] = + [ + [ + 338.7f, 925.5f, 483.2f, 925.5f, 483.2f, 1074.5f, 45.4f, 1074.5f, 45.4f, 925.5f, + 191.4f, 925.5f, 191.4f, 149, 16.8f, 149, 16.8f, 0, 513.3f, 0, + 513.3f, 149, 338.7f, 149, 338.7f, 925.5f, + ], + ], + + ['J'] = + [ + [ + 396.9f, 1065.9f, 396.9f, 323.8f, float.NaN, float.NaN, float.NaN, float.NaN, 396.9f, 231.1f, + 404.5f, 133.6f, 287.1f, 133.6f, float.NaN, float.NaN, float.NaN, float.NaN, 216.4f, 133.6f, + 180.3f, 176.1f, 180.3f, 246.8f, 180.3f, 345.9f, 25.4f, 345.9f, 25.4f, 254.7f, + float.NaN, float.NaN, float.NaN, float.NaN, 25.4f, 91.2f, 127.7f, -14.1f, 288.6f, -14.1f, + float.NaN, float.NaN, float.NaN, float.NaN, 455.6f, -14.1f, 551.9f, 84.9f, 551.9f, 294, + 551.9f, 1065.9f, 396.9f, 1065.9f, + ], + ], + + ['K'] = + [ + [ + 37.2f, 631.1f, 37.2f, 1067.1f, -117.1f, 1067.1f, -117.1f, 0, 37.2f, 0, + 37.2f, 478.5f, 467.1f, 0, 679.9f, 0, 160, 558.7f, 649.9f, 1067.1f, + 449.2f, 1067.1f, 37.2f, 631.1f, + ], + ], + + ['L'] = + [ + [ + 85.4f, 147.3f, 85.4f, 1062.3f, -65.6f, 1062.3f, -65.6f, 0, 593.8f, 0, + 593.8f, 147.3f, 85.4f, 147.3f, + ], + ], + + ['M'] = + [ + [ + 283.6f, 648.1f, 280.4f, 648.1f, 157.9f, 1053.8f, -88.8f, 1053.8f, -88.8f, 0, + 71, 0, 71, 878.2f, 74.1f, 878.2f, 218.4f, 341.9f, 345.6f, 341.9f, + 489.9f, 878.2f, 493, 878.2f, 493, 0, 652.8f, 0, 652.8f, 1053.8f, + 406.1f, 1053.8f, 283.6f, 648.1f, + ], + ], + + ['N'] = + [ + [ + 475.8f, 290.4f, 472.7f, 290.4f, 114.1f, 1058.4f, -69.9f, 1058.4f, -69.9f, 0, + 74.2f, 0, 74.2f, 800.8f, 77.3f, 800.8f, 448.2f, 0, 619.9f, 0, + 619.9f, 1058.4f, 475.8f, 1058.4f, 475.8f, 290.4f, + ], + ], + + ['O'] = + [ + [ + 279, 1101, float.NaN, float.NaN, float.NaN, float.NaN, 15.7f, 1101, -88.4f, 764.4f, + -88.4f, 544.9f, float.NaN, float.NaN, float.NaN, float.NaN, -88.4f, 306.1f, 14.2f, -14.4f, + 279, -14.4f, float.NaN, float.NaN, float.NaN, float.NaN, 543.8f, -14.4f, 646.4f, 306.1f, + 646.4f, 544.9f, float.NaN, float.NaN, float.NaN, float.NaN, 646.4f, 764.4f, 542.3f, 1101, + 279, 1101, + ], + [ + 279, 964.9f, float.NaN, float.NaN, float.NaN, float.NaN, 461.2f, 964.9f, 488.7f, 682.6f, + 488.7f, 539, float.NaN, float.NaN, float.NaN, float.NaN, 488.7f, 398.7f, 458.1f, 126.3f, + 279, 126.3f, float.NaN, float.NaN, float.NaN, float.NaN, 98.4f, 126.3f, 69.3f, 398.7f, + 69.3f, 539, float.NaN, float.NaN, float.NaN, float.NaN, 69.3f, 682.6f, 95.3f, 964.9f, + 279, 964.9f, + ], + ], + + ['P'] = + [ + [ + -91.4f, 0, 67.4f, 0, 67.4f, 438.8f, 250.9f, 438.8f, float.NaN, float.NaN, + float.NaN, float.NaN, 445.1f, 438.8f, 616.3f, 525, 616.3f, 746, float.NaN, float.NaN, + float.NaN, float.NaN, 616.3f, 967, 469.8f, 1062.6f, 261.7f, 1062.6f, -91.4f, 1062.6f, + -91.4f, 0, + ], + [ + 74.4f, 579.1f, 74.4f, 908.3f, 262.5f, 908.3f, float.NaN, float.NaN, float.NaN, float.NaN, + 381.2f, 908.3f, 464.5f, 881.6f, 464.5f, 742.1f, float.NaN, float.NaN, float.NaN, float.NaN, + 464.5f, 613.6f, 370.4f, 579.1f, 261, 579.1f, 74.4f, 579.1f, + ], + ], + + ['Q'] = + [ + [ + 501.5f, 0, 690.6f, 0, 510.6f, 238.3f, float.NaN, float.NaN, float.NaN, float.NaN, + 570.1f, 331.4f, 579.3f, 452.9f, 579.3f, 561.7f, float.NaN, float.NaN, float.NaN, float.NaN, + 579.3f, 789, 504.5f, 1084, 229.9f, 1084, float.NaN, float.NaN, float.NaN, float.NaN, + -43.1f, 1084, -119.4f, 789, -119.4f, 561.7f, float.NaN, float.NaN, float.NaN, float.NaN, + -119.4f, 329.8f, -34, 55.2f, 229.9f, 55.2f, float.NaN, float.NaN, float.NaN, float.NaN, + 297, 55.2f, 364.2f, 78.9f, 411.5f, 119.9f, 501.5f, 0, + ], + [ + 324.8f, 224.3f, float.NaN, float.NaN, float.NaN, float.NaN, 299.6f, 197.4f, 261.9f, 189.6f, + 228.9f, 189.6f, float.NaN, float.NaN, float.NaN, float.NaN, 56.1f, 189.6f, 30.9f, 437.3f, + 30.9f, 565.1f, float.NaN, float.NaN, float.NaN, float.NaN, 30.9f, 696.1f, 54.5f, 921.7f, + 228.9f, 921.7f, float.NaN, float.NaN, float.NaN, float.NaN, 403.3f, 921.7f, 426.9f, 696.1f, + 426.9f, 565.1f, float.NaN, float.NaN, float.NaN, float.NaN, 426.9f, 487.8f, 426.9f, 426.2f, + 409.6f, 369.4f, 299.6f, 508.3f, 107.9f, 508.3f, 324.8f, 224.3f, + ], + ], + + ['R'] = + [ + [ + 71.7f, 445.5f, 200.9f, 445.5f, 445.2f, 0, 631.2f, 0, 352.2f, 478.8f, + float.NaN, float.NaN, float.NaN, float.NaN, 490.9f, 504.1f, 565, 626.2f, 565, 761, + float.NaN, float.NaN, float.NaN, float.NaN, 565, 973.4f, 415.3f, 1074.9f, 219.8f, 1074.9f, + -90.6f, 1074.9f, -90.6f, 0, 71.7f, 0, 71.7f, 445.5f, + ], + [ + 83.3f, 594.5f, 83.3f, 925.8f, 198, 925.8f, float.NaN, float.NaN, float.NaN, float.NaN, + 309.8f, 925.8f, 404.6f, 908.4f, 404.6f, 767.3f, float.NaN, float.NaN, float.NaN, float.NaN, + 404.6f, 626.2f, 326.6f, 594.5f, 199.6f, 594.5f, 83.3f, 594.5f, + ], + ], + + ['S'] = + [ + [ + 269.5f, -14.5f, float.NaN, float.NaN, float.NaN, float.NaN, 438.6f, -14.5f, 594.1f, 98.1f, + 594.1f, 289.5f, float.NaN, float.NaN, float.NaN, float.NaN, 594.1f, 474.5f, 474.8f, 551.7f, + 335.9f, 622.5f, 245.3f, 667.5f, float.NaN, float.NaN, float.NaN, float.NaN, 186.4f, 696.5f, + 127.6f, 738.3f, 127.6f, 817.1f, float.NaN, float.NaN, float.NaN, float.NaN, 127.6f, 920.1f, + 203.1f, 953.8f, 284.6f, 953.8f, float.NaN, float.NaN, float.NaN, float.NaN, 366.1f, 953.8f, + 434.1f, 907.2f, 428, 815.5f, 583.5f, 815.5f, 583.5f, 836.4f, float.NaN, float.NaN, + float.NaN, float.NaN, 583.5f, 1000.5f, 428, 1105, 287.6f, 1105, float.NaN, float.NaN, + float.NaN, float.NaN, 118.5f, 1105, -28, 990.8f, -28, 802.6f, float.NaN, float.NaN, + float.NaN, float.NaN, -28, 640.2f, 89.8f, 571, 206.1f, 511.5f, float.NaN, float.NaN, + float.NaN, float.NaN, 322.3f, 453.6f, 438.6f, 406.9f, 438.6f, 287.9f, float.NaN, float.NaN, + float.NaN, float.NaN, 438.6f, 181.8f, 357.1f, 136.7f, 269.5f, 136.7f, float.NaN, float.NaN, + float.NaN, float.NaN, 172.9f, 136.7f, 104.9f, 199.5f, 103.4f, 302.4f, -52.1f, 302.4f, + -52.1f, 268.6f, float.NaN, float.NaN, float.NaN, float.NaN, -52.1f, 88.5f, 115.5f, -14.5f, + 269.5f, -14.5f, + ], + ], + + ['T'] = + [ + [ + 351.2f, 941.1f, 622, 941.1f, 622, 1092.6f, -72, 1092.6f, -72, 941.1f, + 198.8f, 941.1f, 198.8f, 0, 351.2f, 0, 351.2f, 941.1f, + ], + ], + + ['U'] = + [ + [ + -92.2f, 1087.3f, -92.2f, 364, float.NaN, float.NaN, float.NaN, float.NaN, -92.2f, 128.3f, + 17.9f, -14.4f, 268, -14.4f, float.NaN, float.NaN, float.NaN, float.NaN, 516.5f, -14.4f, + 628.2f, 128.3f, 628.2f, 364, 628.2f, 1087.3f, 466.2f, 1087.3f, 466.2f, 375.3f, + float.NaN, float.NaN, float.NaN, float.NaN, 466.2f, 222.9f, 430, 136.3f, 268, 136.3f, + float.NaN, float.NaN, float.NaN, float.NaN, 106, 136.3f, 69.8f, 222.9f, 69.8f, 375.3f, + 69.8f, 1087.3f, -92.2f, 1087.3f, + ], + ], + + ['V'] = + [ + [ + 277.2f, 159.2f, 274.3f, 159.2f, 52.4f, 1058.4f, -98, 1058.4f, 169.2f, 0, + 377.9f, 0, 648.1f, 1058.4f, 497.7f, 1058.4f, 277.2f, 159.2f, + ], + ], + + ['W'] = + [ + [ + 219.4f, 660.5f, 112.6f, 279.7f, 109.5f, 279.7f, float.NaN, float.NaN, float.NaN, float.NaN, + 80.1f, 518.3f, 60, 774.3f, 60, 982.9f, 60, 1071.4f, -99.4f, 1071.4f, + -99.4f, 982.9f, float.NaN, float.NaN, float.NaN, float.NaN, -99.4f, 875.4f, -87, 595.7f, + -65.4f, 469.3f, 13.6f, 0, 186.9f, 0, 278.2f, 372.9f, 281.3f, 372.9f, + 371.1f, 0, 544.4f, 0, 623.3f, 469.3f, float.NaN, float.NaN, float.NaN, float.NaN, + 645, 595.7f, 657.4f, 875.4f, 657.4f, 982.9f, 657.4f, 1071.4f, 498, 1071.4f, + 498, 982.9f, float.NaN, float.NaN, float.NaN, float.NaN, 498, 774.3f, 477.8f, 518.3f, + 448.4f, 279.7f, 445.3f, 279.7f, 338.6f, 660.5f, 219.4f, 660.5f, + ], + ], + + ['X'] = + [ + [ + 265, 691.6f, 75.3f, 1058.4f, -106.8f, 1058.4f, 181.5f, 544.8f, -106.8f, 0, + 75.3f, 0, 265, 401.2f, 453.2f, 0, 636.8f, 0, 360.6f, 544.8f, + 636.8f, 1058.4f, 453.2f, 1058.4f, 265, 691.6f, + ], + ], + + ['Y'] = + [ + [ + 275, 668.4f, 86.4f, 1071.4f, -76.4f, 1071.4f, 201.6f, 500.9f, 201.6f, 0, + 349.9f, 0, 349.9f, 500.9f, 626.4f, 1071.4f, 463.6f, 1071.4f, 275, 668.4f, + ], + ], + + ['Z'] = + [ + [ + 146.3f, 147.3f, 533.2f, 938.5f, 533.2f, 1062.3f, -19.3f, 1062.3f, -19.3f, 915, + 364.6f, 915, -32.9f, 111.2f, -32.9f, 0, 582.8f, 0, 582.8f, 147.3f, + 146.3f, 147.3f, + ], + ], + + ['['] = + [ + [ + 147.2f, 1064.3f, 543.4f, 1064.3f, 543.4f, 1219, -7.4f, 1219, -7.4f, 0, + 543.4f, 0, 543.4f, 154.6f, 147.2f, 154.6f, 147.2f, 1064.3f, + ], + ], + + ['\\'] = + [ + [ + -49.6f, 1197.4f, 426.7f, 0, 587.6f, 0, 114.5f, 1197.4f, -49.6f, 1197.4f, + ], + ], + + [']'] = + [ + [ + 402.1f, 154.6f, 18.2f, 154.6f, 18.2f, 0, 551.8f, 0, 551.8f, 1219, + 18.2f, 1219, 18.2f, 1064.3f, 402.1f, 1064.3f, 402.1f, 154.6f, + ], + ], + + ['^'] = + [ + [ + 308.6f, 1180.1f, 239.8f, 1180.1f, -86.2f, 651.4f, 6.5f, 573.3f, 274.2f, 877.5f, + 541.9f, 573.3f, 636.2f, 651.4f, 308.6f, 1180.1f, + ], + ], + + ['_'] = + [ + [ + -77.7f, -311, 607.7f, -311, 607.7f, -125.8f, -77.7f, -125.8f, -77.7f, -311, + ], + ], + + ['`'] = + [ + [ + 296, 1160.8f, 113.4f, 1160.8f, 288.9f, 904.1f, 425.9f, 904.1f, 296, 1160.8f, + ], + ], + + ['a'] = + [ + [ + 438.8f, 363.6f, 438.8f, 337.6f, float.NaN, float.NaN, float.NaN, float.NaN, 438.8f, 211.7f, + 372.3f, 105.9f, 234.7f, 105.9f, float.NaN, float.NaN, float.NaN, float.NaN, 159.1f, 105.9f, + 103.1f, 150.4f, 103.1f, 230.1f, float.NaN, float.NaN, float.NaN, float.NaN, 103.1f, 331.4f, + 192.3f, 363.6f, 277, 363.6f, 438.8f, 363.6f, + ], + [ + 434.8f, 0, 586, 0, 586, 493.5f, float.NaN, float.NaN, float.NaN, float.NaN, + 586, 726, 503.8f, 806.7f, 291, 806.7f, float.NaN, float.NaN, float.NaN, float.NaN, + 155.9f, 806.7f, 0.3f, 768.7f, -9.9f, 593.2f, 141.2f, 593.2f, float.NaN, float.NaN, + float.NaN, float.NaN, 141.2f, 670.7f, 220.5f, 683.3f, 298.3f, 683.3f, float.NaN, float.NaN, + float.NaN, float.NaN, 423.1f, 683.3f, 434.8f, 616.9f, 434.8f, 526.7f, 434.8f, 498.3f, + 298.3f, 498.3f, float.NaN, float.NaN, float.NaN, float.NaN, 136.8f, 498.3f, -42.2f, 436.6f, + -42.2f, 230.9f, float.NaN, float.NaN, float.NaN, float.NaN, -42.2f, 69.6f, 79.6f, -14.2f, + 222, -14.2f, float.NaN, float.NaN, float.NaN, float.NaN, 299.8f, -14.2f, 377.6f, 17.4f, + 434.8f, 75.9f, 434.8f, 0, + ], + ], + + ['b'] = + [ + [ + 282, 678.7f, float.NaN, float.NaN, float.NaN, float.NaN, 440.5f, 678.7f, 489.6f, 518.6f, + 489.6f, 386.3f, float.NaN, float.NaN, float.NaN, float.NaN, 489.6f, 253.9f, 425.8f, 111.8f, + 273.8f, 111.8f, float.NaN, float.NaN, float.NaN, float.NaN, 113.6f, 111.8f, 54.7f, 257.2f, + 54.7f, 394.4f, float.NaN, float.NaN, float.NaN, float.NaN, 54.7f, 539.8f, 116.9f, 678.7f, + 282, 678.7f, + ], + [ + -116.9f, 0, 46.6f, 0, 46.6f, 80.9f, float.NaN, float.NaN, float.NaN, float.NaN, + 105.3f, 17.4f, 191, -14.3f, 276.7f, -14.3f, float.NaN, float.NaN, float.NaN, float.NaN, + 503.7f, -14.3f, 632.2f, 171.3f, 632.2f, 383.8f, float.NaN, float.NaN, float.NaN, float.NaN, + 632.2f, 598, 532.2f, 808.9f, 287.8f, 808.9f, float.NaN, float.NaN, float.NaN, float.NaN, + 202.1f, 808.9f, 105.3f, 778.8f, 51.4f, 707.4f, 46.6f, 707.4f, 46.6f, 1154.7f, + -116.9f, 1154.7f, -116.9f, 0, + ], + ], + + ['c'] = + [ + [ + 571.6f, 590.5f, float.NaN, float.NaN, float.NaN, float.NaN, 549.8f, 748.6f, 434.9f, 822.8f, + 299.7f, 822.8f, float.NaN, float.NaN, float.NaN, float.NaN, 87.5f, 822.8f, -21.6f, 624.4f, + -21.6f, 408.2f, float.NaN, float.NaN, float.NaN, float.NaN, -21.6f, 190.4f, 71.5f, -14.5f, + 286.6f, -14.5f, float.NaN, float.NaN, float.NaN, float.NaN, 421.8f, -14.5f, 571.6f, 75.8f, + 571.6f, 246.9f, 421.8f, 246.9f, float.NaN, float.NaN, float.NaN, float.NaN, 421.8f, 177.5f, + 360.8f, 124.2f, 296.8f, 124.2f, float.NaN, float.NaN, float.NaN, float.NaN, 155.8f, 124.2f, + 128.2f, 282.3f, 128.2f, 409.8f, float.NaN, float.NaN, float.NaN, float.NaN, 128.2f, 542.1f, + 158.7f, 684.1f, 304.1f, 684.1f, float.NaN, float.NaN, float.NaN, float.NaN, 359.3f, 684.1f, + 418.9f, 648.6f, 421.8f, 590.5f, 571.6f, 590.5f, + ], + ], + + ['d'] = + [ + [ + 270.6f, 110.7f, float.NaN, float.NaN, float.NaN, float.NaN, 109.8f, 110.7f, 56.1f, 266.2f, + 56.1f, 402.3f, float.NaN, float.NaN, float.NaN, float.NaN, 56.1f, 528.7f, 109.8f, 672.8f, + 259.2f, 672.8f, float.NaN, float.NaN, float.NaN, float.NaN, 421.7f, 672.8f, 483.5f, 546.5f, + 483.5f, 402.3f, float.NaN, float.NaN, float.NaN, float.NaN, 483.5f, 266.2f, 428.2f, 110.7f, + 270.6f, 110.7f, + ], + [ + 498.7f, 0, 661.2f, 0, 661.2f, 1144.9f, 498.7f, 1144.9f, 498.7f, 704.6f, + 495.6f, 704.6f, float.NaN, float.NaN, float.NaN, float.NaN, 443.5f, 773.8f, 348.9f, 802.1f, + 262.1f, 802.1f, float.NaN, float.NaN, float.NaN, float.NaN, 25.5f, 802.1f, -78.6f, 603.9f, + -78.6f, 391.6f, float.NaN, float.NaN, float.NaN, float.NaN, -78.6f, 176.1f, 36.5f, -14.2f, + 273.2f, -14.2f, float.NaN, float.NaN, float.NaN, float.NaN, 353.6f, -14.2f, 443.5f, 26.7f, + 498.7f, 81.8f, 498.7f, 0, + ], + ], + + ['e'] = + [ + [ + 602.4f, 368.1f, 602.4f, 417.8f, float.NaN, float.NaN, float.NaN, float.NaN, 602.4f, 651.4f, + 512.3f, 816.3f, 280.6f, 816.3f, float.NaN, float.NaN, float.NaN, float.NaN, 47.4f, 816.3f, + -60.3f, 633.8f, -60.3f, 395.3f, float.NaN, float.NaN, float.NaN, float.NaN, -60.3f, 145.7f, + 65.1f, -14.4f, 299.8f, -14.4f, float.NaN, float.NaN, float.NaN, float.NaN, 414.9f, -14.4f, + 566.9f, 27.2f, 574.3f, 179.3f, 423.8f, 179.3f, float.NaN, float.NaN, float.NaN, float.NaN, + 407.5f, 128, 351.5f, 123.2f, 307.2f, 123.2f, float.NaN, float.NaN, float.NaN, float.NaN, + 171.4f, 123.2f, 84.3f, 222.5f, 91.7f, 368.1f, 602.4f, 368.1f, + ], + [ + 100.3f, 507, float.NaN, float.NaN, float.NaN, float.NaN, 106.4f, 617.4f, 196.1f, 685, + 291.8f, 685, float.NaN, float.NaN, float.NaN, float.NaN, 392.2f, 685, 468.2f, 617.4f, + 469.7f, 507, 100.3f, 507, + ], + ], + + ['f'] = + [ + [ + 347.1f, 789.6f, 347.1f, 827.4f, float.NaN, float.NaN, float.NaN, float.NaN, 347.1f, 981.9f, + 375.6f, 1011.8f, 515.3f, 1011.8f, 548, 1011.8f, 548, 1147.4f, 472.5f, 1147.4f, + float.NaN, float.NaN, float.NaN, float.NaN, 270.2f, 1147.4f, 200.4f, 1027.6f, 200.4f, 827.4f, + 200.4f, 789.6f, -3.3f, 789.6f, -3.3f, 654.1f, 200.4f, 654.1f, 200.4f, 0, + 347.1f, 0, 347.1f, 654.1f, 548, 654.1f, 548, 789.6f, 347.1f, 789.6f, + ], + ], + + ['g'] = + [ + [ + 273.5f, 160.8f, float.NaN, float.NaN, float.NaN, float.NaN, 116.8f, 160.8f, 76.5f, 286.3f, + 76.5f, 418, float.NaN, float.NaN, float.NaN, float.NaN, 76.5f, 541.9f, 115.2f, 672.1f, + 265.4f, 672.1f, float.NaN, float.NaN, float.NaN, float.NaN, 418.8f, 672.1f, 465.7f, 552.9f, + 465.7f, 422.7f, float.NaN, float.NaN, float.NaN, float.NaN, 465.7f, 298.8f, 422, 160.8f, + 273.5f, 160.8f, + ], + [ + 636, 785.7f, 474.5f, 785.7f, 474.5f, 715.1f, 471.3f, 715.1f, 472.9f, 715.1f, + float.NaN, float.NaN, float.NaN, float.NaN, 425.9f, 777.9f, 333.4f, 799.8f, 255, 799.8f, + float.NaN, float.NaN, float.NaN, float.NaN, 40.2f, 799.8f, -64.9f, 635.2f, -64.9f, 434.4f, + float.NaN, float.NaN, float.NaN, float.NaN, -64.9f, 225.8f, 30.8f, 47, 258.1f, 47, + float.NaN, float.NaN, float.NaN, float.NaN, 344.3f, 47, 400.8f, 72.1f, 468.2f, 125.5f, + 474.5f, 130.2f, 474.5f, 14.1f, float.NaN, float.NaN, float.NaN, float.NaN, 474.5f, -108.2f, + 444.7f, -150.6f, 262.8f, -150.6f, float.NaN, float.NaN, float.NaN, float.NaN, 209.5f, -150.6f, + 124.8f, -142.7f, 129.5f, -72.1f, -31.9f, -72.1f, -31.9f, -89.4f, float.NaN, float.NaN, + float.NaN, float.NaN, -31.9f, -254.1f, 140.5f, -271.3f, 264.4f, -271.3f, float.NaN, float.NaN, + float.NaN, float.NaN, 472.9f, -271.3f, 636, -214.9f, 636, 26.7f, 636, 785.7f, + ], + ], + + ['h'] = + [ + [ + 110.8f, 1154.3f, -50.3f, 1154.3f, -50.3f, 0, 110.8f, 0, 110.8f, 424.9f, + float.NaN, float.NaN, float.NaN, float.NaN, 110.8f, 558.1f, 131.1f, 659.6f, 290.6f, 659.6f, + float.NaN, float.NaN, float.NaN, float.NaN, 404.8f, 659.6f, 425.2f, 591.4f, 425.2f, 491.5f, + 425.2f, 0, 586.3f, 0, 586.3f, 478.9f, float.NaN, float.NaN, float.NaN, float.NaN, + 586.3f, 661.2f, 515.9f, 808.7f, 314.1f, 808.7f, float.NaN, float.NaN, float.NaN, float.NaN, + 240.6f, 808.7f, 160.8f, 781.7f, 110.8f, 729.4f, 110.8f, 1154.3f, + ], + ], + + ['i'] = + [ + [ + 89.1f, 789.6f, 89.1f, 641.5f, 332.9f, 641.5f, 332.9f, 0, 489, 0, + 489, 789.6f, 89.1f, 789.6f, + ], + [ + 264.4f, 1175.9f, 264.4f, 969.5f, 466.9f, 969.5f, 466.9f, 1175.9f, 264.4f, 1175.9f, + ], + ], + + ['j'] = + [ + [ + 172.1f, 781.3f, 172.1f, 634.7f, 344.3f, 634.7f, 344.3f, 51.5f, float.NaN, float.NaN, + float.NaN, float.NaN, 344.3f, -4.7f, 344.3f, -57.7f, 313.9f, -90.4f, float.NaN, float.NaN, + float.NaN, float.NaN, 277.7f, -129.4f, 234.3f, -123.2f, 179.3f, -123.2f, 76.6f, -123.2f, + 76.6f, -269.8f, 185.1f, -269.8f, float.NaN, float.NaN, float.NaN, float.NaN, 412.2f, -269.8f, + 493.3f, -185.6f, 493.3f, 43.7f, 493.3f, 781.3f, 172.1f, 781.3f, + ], + [ + 289.9f, 1149.3f, 289.9f, 951.2f, 483.4f, 951.2f, 483.4f, 1149.3f, 289.9f, 1149.3f, + ], + ], + + ['k'] = + [ + [ + 71.5f, 1136.5f, -87.4f, 1136.5f, -87.4f, 0, 71.5f, 0, 71.5f, 365.3f, + 403.3f, 0, 610.1f, 0, 207.3f, 443.4f, 576.2f, 782.1f, 366.3f, 782.1f, + 71.5f, 502.7f, 71.5f, 1136.5f, + ], + ], + + ['l'] = + [ + [ + 202.4f, 1182.9f, 52.8f, 1182.9f, 52.8f, 355.8f, float.NaN, float.NaN, float.NaN, float.NaN, + 52.8f, 126.7f, 110.9f, 0, 328.8f, 0, 463.9f, 0, 463.9f, 139.7f, + 366.6f, 139.7f, float.NaN, float.NaN, float.NaN, float.NaN, 247.5f, 139.7f, 202.4f, 178.7f, + 202.4f, 313.6f, 202.4f, 1182.9f, + ], + ], + + ['m'] = + [ + [ + 56.7f, 781.7f, -90.7f, 781.7f, -90.7f, 0, 56.7f, 0, 56.7f, 510.2f, + float.NaN, float.NaN, float.NaN, float.NaN, 56.7f, 575.8f, 58.1f, 675.6f, 141.2f, 675.6f, + float.NaN, float.NaN, float.NaN, float.NaN, 194.1f, 675.6f, 201.3f, 619.5f, 201.3f, 575.8f, + 201.3f, 0, 348.7f, 0, 348.7f, 510.2f, float.NaN, float.NaN, float.NaN, float.NaN, + 348.7f, 575.8f, 345.8f, 675.6f, 431.7f, 675.6f, float.NaN, float.NaN, float.NaN, float.NaN, + 490.4f, 675.6f, 493.3f, 624.1f, 493.3f, 575.8f, 493.3f, 0, 640.7f, 0, + 640.7f, 575.8f, float.NaN, float.NaN, float.NaN, float.NaN, 640.7f, 691.2f, 583.4f, 795.8f, + 467.5f, 795.8f, float.NaN, float.NaN, float.NaN, float.NaN, 411.7f, 795.8f, 363, 777.1f, + 322.9f, 734.9f, float.NaN, float.NaN, float.NaN, float.NaN, 292.9f, 778.6f, 248.5f, 795.8f, + 198.4f, 795.8f, float.NaN, float.NaN, float.NaN, float.NaN, 169.8f, 795.8f, 144, 789.5f, + 119.7f, 777.1f, float.NaN, float.NaN, float.NaN, float.NaN, 96.8f, 764.6f, 75.3f, 747.4f, + 56.7f, 724, 56.7f, 781.7f, + ], + ], + + ['n'] = + [ + [ + 78.6f, 778.7f, -82.6f, 778.7f, -82.6f, 0, 78.6f, 0, 78.6f, 405.7f, + float.NaN, float.NaN, float.NaN, float.NaN, 78.6f, 551.8f, 144.4f, 659.1f, 302.4f, 659.1f, + float.NaN, float.NaN, float.NaN, float.NaN, 410.4f, 659.1f, 457.4f, 603.1f, 457.4f, 500.5f, + 457.4f, 0, 618.6f, 0, 618.6f, 489.6f, float.NaN, float.NaN, float.NaN, float.NaN, + 618.6f, 669.9f, 505.9f, 792.7f, 319.6f, 792.7f, float.NaN, float.NaN, float.NaN, float.NaN, + 233.6f, 792.7f, 131.8f, 757, 81.8f, 691.7f, 78.6f, 691.7f, 78.6f, 778.7f, + ], + ], + + ['o'] = + [ + [ + 275, 800.3f, float.NaN, float.NaN, float.NaN, float.NaN, 45.2f, 800.3f, -92.4f, 635.5f, + -92.4f, 397, float.NaN, float.NaN, float.NaN, float.NaN, -92.4f, 164.8f, 42.2f, -14.1f, + 275, -14.1f, float.NaN, float.NaN, float.NaN, float.NaN, 507.8f, -14.1f, 642.4f, 164.8f, + 642.4f, 397, float.NaN, float.NaN, float.NaN, float.NaN, 642.4f, 635.5f, 503.3f, 800.3f, + 275, 800.3f, + ], + [ + 268, 679.5f, float.NaN, float.NaN, float.NaN, float.NaN, 428.4f, 679.5f, 486, 541.4f, + 486, 395.4f, float.NaN, float.NaN, float.NaN, float.NaN, 486, 249.5f, 436.2f, 106.7f, + 268, 106.7f, float.NaN, float.NaN, float.NaN, float.NaN, 99.8f, 106.7f, 50, 249.5f, + 50, 395.4f, float.NaN, float.NaN, float.NaN, float.NaN, 50, 541.4f, 107.6f, 679.5f, + 268, 679.5f, + ], + ], + + ['p'] = + [ + [ + 275.2f, 664.6f, float.NaN, float.NaN, float.NaN, float.NaN, 444, 664.6f, 483.8f, 519.2f, + 483.8f, 373.8f, float.NaN, float.NaN, float.NaN, float.NaN, 483.8f, 233.3f, 432.9f, 97.8f, + 275.2f, 97.8f, float.NaN, float.NaN, float.NaN, float.NaN, 114.4f, 97.8f, 61.9f, 238.2f, + 61.9f, 382, float.NaN, float.NaN, float.NaN, float.NaN, 61.9f, 524.1f, 114.4f, 664.6f, + 275.2f, 664.6f, + ], + [ + 47.9f, 770.7f, -116.1f, 770.7f, -116.1f, -266.1f, 47.9f, -266.1f, 47.9f, 72.3f, + float.NaN, float.NaN, float.NaN, float.NaN, 102, 15.4f, 203.9f, -13.8f, 278.7f, -13.8f, + float.NaN, float.NaN, float.NaN, float.NaN, 511.2f, -13.8f, 633.8f, 175.4f, 633.8f, 384.6f, + float.NaN, float.NaN, float.NaN, float.NaN, 633.8f, 595.3f, 509.6f, 784.5f, 272.4f, 784.5f, + float.NaN, float.NaN, float.NaN, float.NaN, 180, 784.5f, 127.5f, 756.8f, 59, 704.5f, + 47.9f, 698.4f, 47.9f, 770.7f, + ], + ], + + ['q'] = + [ + [ + 277.7f, 667.6f, float.NaN, float.NaN, float.NaN, float.NaN, 430.1f, 667.6f, 486.9f, 529.3f, + 486.9f, 397.5f, float.NaN, float.NaN, float.NaN, float.NaN, 486.9f, 248, 438.2f, 109.8f, + 266.4f, 109.8f, float.NaN, float.NaN, float.NaN, float.NaN, 94.5f, 109.8f, 58.8f, 252.8f, + 58.8f, 394.3f, float.NaN, float.NaN, float.NaN, float.NaN, 58.8f, 535.8f, 114, 667.6f, + 277.7f, 667.6f, + ], + [ + 667.9f, 781.9f, 500.9f, 781.9f, 500.9f, 703.8f, float.NaN, float.NaN, float.NaN, float.NaN, + 437.6f, 763.2f, 355, 795.9f, 265.8f, 795.9f, float.NaN, float.NaN, float.NaN, float.NaN, + 21, 795.9f, -94.2f, 593, -94.2f, 377.7f, float.NaN, float.NaN, float.NaN, float.NaN, + -94.2f, 163.9f, 35.5f, -14, 267.4f, -14, float.NaN, float.NaN, float.NaN, float.NaN, + 355, -14, 442.5f, 15.6f, 500.9f, 78, 500.9f, -270, 667.9f, -270, + 667.9f, 781.9f, + ], + ], + + ['r'] = + [ + [ + 119.9f, 791.4f, -39.8f, 791.4f, -39.8f, 0, 119.9f, 0, 119.9f, 409.1f, + float.NaN, float.NaN, float.NaN, float.NaN, 119.9f, 529.2f, 150.9f, 683.9f, 302.8f, 683.9f, + float.NaN, float.NaN, float.NaN, float.NaN, 371.1f, 683.9f, 400.5f, 619.2f, 400.5f, 557.6f, + 400.5f, 533.9f, 560.2f, 533.9f, 560.2f, 565.5f, float.NaN, float.NaN, float.NaN, float.NaN, + 560.2f, 706.1f, 461, 805.6f, 324.5f, 805.6f, float.NaN, float.NaN, float.NaN, float.NaN, + 242.4f, 805.6f, 171.1f, 769.2f, 123, 704.5f, 119.9f, 704.5f, 119.9f, 791.4f, + ], + ], + + ['s'] = + [ + [ + 544.8f, 597, 544.8f, 620.8f, float.NaN, float.NaN, float.NaN, float.NaN, 544.8f, 747.9f, + 405.2f, 809.8f, 271.4f, 809.8f, float.NaN, float.NaN, float.NaN, float.NaN, 156.5f, 809.8f, + -16.6f, 768.5f, -16.6f, 582.7f, float.NaN, float.NaN, float.NaN, float.NaN, -16.6f, 500.2f, + 18.3f, 425.5f, 91, 392.2f, float.NaN, float.NaN, float.NaN, float.NaN, 150.6f, 362, + 243.7f, 338.2f, 307.7f, 325.5f, float.NaN, float.NaN, float.NaN, float.NaN, 367.4f, 314.4f, + 427, 301.7f, 427, 220.7f, float.NaN, float.NaN, float.NaN, float.NaN, 427, 128.6f, + 310.6f, 109.6f, 248.1f, 109.6f, float.NaN, float.NaN, float.NaN, float.NaN, 200.1f, 109.6f, + 109.9f, 117.5f, 107, 211.2f, -42.8f, 211.2f, float.NaN, float.NaN, float.NaN, float.NaN, + -42.8f, 34.9f, 109.9f, -14.3f, 245.2f, -14.3f, float.NaN, float.NaN, float.NaN, float.NaN, + 386.3f, -14.3f, 576.8f, 28.6f, 576.8f, 220.7f, float.NaN, float.NaN, float.NaN, float.NaN, + 576.8f, 435.1f, 376.1f, 457.3f, 229.2f, 489, float.NaN, float.NaN, float.NaN, float.NaN, + 173.9f, 500.2f, 133.2f, 522.4f, 133.2f, 590.7f, float.NaN, float.NaN, float.NaN, float.NaN, + 133.2f, 671.6f, 217.5f, 685.9f, 271.4f, 685.9f, float.NaN, float.NaN, float.NaN, float.NaN, + 316.5f, 685.9f, 392.1f, 668.5f, 395, 597, 544.8f, 597, + ], + ], + + ['t'] = + [ + [ + 301.1f, 1020.3f, 151.6f, 978, 151.6f, 799.3f, 2, 799.3f, 2, 664.5f, + 151.6f, 664.5f, 151.6f, 286.8f, float.NaN, float.NaN, float.NaN, float.NaN, 151.6f, 111.3f, + 219.8f, -14.1f, 395.5f, -14.1f, float.NaN, float.NaN, float.NaN, float.NaN, 450.6f, -14.1f, + 514.5f, 3.1f, 561, 32.9f, 561, 181.8f, float.NaN, float.NaN, float.NaN, float.NaN, + 520.3f, 148.9f, 449.2f, 120.7f, 398.4f, 120.7f, float.NaN, float.NaN, float.NaN, float.NaN, + 312.7f, 120.7f, 301.1f, 197.5f, 301.1f, 271.1f, 301.1f, 664.5f, 561, 664.5f, + 561, 799.3f, 301.1f, 799.3f, 301.1f, 1020.3f, + ], + ], + + ['u'] = + [ + [ + 475.4f, 0, 640.1f, 0, 640.1f, 781.7f, 475.4f, 781.7f, 475.4f, 385.4f, + float.NaN, float.NaN, float.NaN, float.NaN, 475.4f, 227.8f, 414.7f, 120.1f, 237.2f, 120.1f, + float.NaN, float.NaN, float.NaN, float.NaN, 123.7f, 120.1f, 88.6f, 163.8f, 88.6f, 269.9f, + 88.6f, 781.7f, -76.1f, 781.7f, -76.1f, 269.9f, float.NaN, float.NaN, float.NaN, float.NaN, + -76.1f, 99.9f, 27.8f, -14, 208.5f, -14, float.NaN, float.NaN, float.NaN, float.NaN, + 301.2f, -14, 413.1f, 15.6f, 475.4f, 87.4f, 475.4f, 0, + ], + ], + + ['v'] = + [ + [ + 66.6f, 772.1f, -101.2f, 772.1f, 166.8f, 0, 383.2f, 0, 651.2f, 772.1f, + 483.4f, 772.1f, 275.7f, 131, 272.8f, 131, 66.6f, 772.1f, + ], + ], + + ['w'] = + [ + [ + 67.6f, 780.8f, -88, 780.8f, 6.9f, 0, 186.1f, 0, 273.5f, 319.5f, + 276.5f, 319.5f, 363.9f, 0, 547.6f, 0, 638, 780.8f, 482.4f, 780.8f, + 429.1f, 202.6f, 426.1f, 202.6f, 349.1f, 478.4f, 199.4f, 478.4f, 123.9f, 219.7f, + 120.9f, 219.7f, 67.6f, 780.8f, + ], + ], + + ['x'] = + [ + [ + 122.2f, 780.8f, -77.2f, 780.8f, 186.8f, 392.7f, -77.2f, 0, 92.1f, 0, + 267.1f, 285.2f, 442.1f, 0, 627.1f, 0, 353.2f, 397.4f, 599.9f, 780.8f, + 429.2f, 780.8f, 277.1f, 519, 122.2f, 780.8f, + ], + ], + + ['y'] = + [ + [ + 291, 347.1f, 74.6f, 790.4f, -108.4f, 790.4f, 214, 189.3f, 83.3f, -60, + float.NaN, float.NaN, float.NaN, float.NaN, 58.6f, -105.7f, 23.7f, -137.3f, -30, -137.3f, + -53.2f, -137.3f, -53.2f, -272.9f, -19.8f, -272.9f, float.NaN, float.NaN, float.NaN, float.NaN, + 109.5f, -272.9f, 158.8f, -224, 219.8f, -101, 664.3f, 790.4f, 507.5f, 790.4f, + 291, 347.1f, + ], + ], + + ['z'] = + [ + [ + 553.7f, 815.6f, 2.2f, 815.6f, 2.2f, 675.6f, 382.4f, 675.6f, -24.8f, 169.3f, + -24.8f, 0, 586.8f, 0, 586.8f, 140, 142, 140, 553.7f, 654.4f, + 553.7f, 815.6f, + ], + ], + + ['{'] = + [ + [ + -58.8f, 505.3f, 15.6f, 505.3f, float.NaN, float.NaN, float.NaN, float.NaN, 111.4f, 505.3f, + 152.7f, 488.1f, 152.7f, 389.5f, 152.7f, 286.3f, float.NaN, float.NaN, float.NaN, float.NaN, + 152.7f, 92.3f, 280, 0, 458.4f, 0, 608.8f, 0, 608.8f, 147, + 509.6f, 147, float.NaN, float.NaN, float.NaN, float.NaN, 400.6f, 147, 322.9f, 153.3f, + 322.9f, 286.3f, 322.9f, 389.5f, float.NaN, float.NaN, float.NaN, float.NaN, 322.9f, 450.5f, + 304.7f, 535, 248.6f, 561.6f, 208.9f, 578.8f, 248.6f, 597.6f, float.NaN, float.NaN, + float.NaN, float.NaN, 304.7f, 624.2f, 322.9f, 708.6f, 322.9f, 768.1f, 322.9f, 872.9f, + float.NaN, float.NaN, float.NaN, float.NaN, 322.9f, 1005.9f, 400.6f, 1012.1f, 509.6f, 1012.1f, + 608.8f, 1012.1f, 608.8f, 1159.2f, 458.4f, 1159.2f, float.NaN, float.NaN, float.NaN, float.NaN, + 280, 1159.2f, 152.7f, 1065.3f, 152.7f, 872.9f, 152.7f, 768.1f, float.NaN, float.NaN, + float.NaN, float.NaN, 152.7f, 671.1f, 111.4f, 652.3f, 15.6f, 652.3f, -58.8f, 652.3f, + -58.8f, 505.3f, + ], + ], + + ['}'] = + [ + [ + 614.3f, 657.3f, 539.6f, 657.3f, float.NaN, float.NaN, float.NaN, float.NaN, 441.7f, 657.3f, + 401.8f, 676.2f, 401.8f, 773.9f, 401.8f, 879.5f, float.NaN, float.NaN, float.NaN, float.NaN, + 401.8f, 1073.4f, 274, 1168, 93.1f, 1168, -56.3f, 1168, -56.3f, 1019.8f, + 43.3f, 1019.8f, float.NaN, float.NaN, float.NaN, float.NaN, 152.8f, 1019.8f, 230.9f, 1013.5f, + 230.9f, 879.5f, 230.9f, 773.9f, float.NaN, float.NaN, float.NaN, float.NaN, 230.9f, 714, + 249.1f, 628.9f, 305.6f, 602.1f, 345.4f, 583.2f, 305.6f, 565.9f, float.NaN, float.NaN, + float.NaN, float.NaN, 249.1f, 539.1f, 230.9f, 453.9f, 230.9f, 392.5f, 230.9f, 288.4f, + float.NaN, float.NaN, float.NaN, float.NaN, 230.9f, 154.5f, 152.8f, 148.2f, 43.3f, 148.2f, + -56.3f, 148.2f, -56.3f, 0, 93.1f, 0, float.NaN, float.NaN, float.NaN, float.NaN, + 274, 0, 401.8f, 93, 401.8f, 288.4f, 401.8f, 392.5f, float.NaN, float.NaN, + float.NaN, float.NaN, 401.8f, 491.8f, 441.7f, 509.1f, 539.6f, 509.1f, 614.3f, 509.1f, + 614.3f, 657.3f, + ], + ], + + ['~'] = + [ + [ + 109.2f, 910.2f, float.NaN, float.NaN, float.NaN, float.NaN, 109.2f, 943.8f, 119.9f, 994.3f, + 161.1f, 994.3f, float.NaN, float.NaN, float.NaN, float.NaN, 190.1f, 994.3f, 220.6f, 969.8f, + 242, 954.5f, float.NaN, float.NaN, float.NaN, float.NaN, 287.8f, 919.4f, 338.2f, 896.4f, + 397.7f, 896.4f, float.NaN, float.NaN, float.NaN, float.NaN, 516.7f, 896.4f, 574.7f, 1008.1f, + 574.7f, 1113.6f, 432.8f, 1113.6f, float.NaN, float.NaN, float.NaN, float.NaN, 432.8f, 1083, + 423.6f, 1032.6f, 379.4f, 1032.6f, float.NaN, float.NaN, float.NaN, float.NaN, 347.3f, 1032.6f, + 321.4f, 1058.6f, 286.3f, 1083, float.NaN, float.NaN, float.NaN, float.NaN, 254.2f, 1107.5f, + 213, 1133.5f, 155, 1133.5f, float.NaN, float.NaN, float.NaN, float.NaN, 32.9f, 1133.5f, + -32.7f, 1041.7f, -32.7f, 925.5f, -32.7f, 910.2f, 109.2f, 910.2f, + ], + ], + + ['\u00a3'] = + [ + [ + 289.9f, 532.2f, 490.6f, 532.2f, 490.6f, 681.1f, 322.6f, 681.1f, 370.2f, 907.6f, + float.NaN, float.NaN, float.NaN, float.NaN, 383.6f, 966.3f, 402.9f, 1024.9f, 468.3f, 1024.9f, + float.NaN, float.NaN, float.NaN, float.NaN, 511.4f, 1024.9f, 547.1f, 993.2f, 567.9f, 955.2f, + 613.9f, 955.2f, 613.9f, 1108.8f, float.NaN, float.NaN, float.NaN, float.NaN, 573.8f, 1151.6f, + 527.7f, 1173.8f, 469.8f, 1173.8f, float.NaN, float.NaN, float.NaN, float.NaN, 307.8f, 1173.8f, + 243.8f, 1024.9f, 212.6f, 879.1f, 169.5f, 681.1f, -22.2f, 681.1f, -22.2f, 532.2f, + 136.8f, 532.2f, 55.1f, 148.9f, -51.9f, 148.9f, -51.9f, 0, 613.9f, 0, + 613.9f, 148.9f, 206.7f, 148.9f, 289.9f, 532.2f, + ], + ], + + ['\u00a4'] = + [ + [ + -102.2f, 770.1f, 15.5f, 654.2f, float.NaN, float.NaN, float.NaN, float.NaN, -26.2f, 584.1f, + -29.2f, 532.2f, -29.2f, 491, float.NaN, float.NaN, float.NaN, float.NaN, -29.2f, 448.3f, + -26.2f, 396.5f, 15.5f, 324.8f, -102.2f, 208.9f, -0.9f, 108.3f, 110.8f, 228.7f, + float.NaN, float.NaN, float.NaN, float.NaN, 139.1f, 199.8f, 224.1f, 178.4f, 271.7f, 178.4f, + float.NaN, float.NaN, float.NaN, float.NaN, 317.9f, 178.4f, 402.8f, 199.8f, 431.1f, 228.7f, + 542.9f, 108.3f, 644.2f, 208.9f, 526.5f, 324.8f, float.NaN, float.NaN, float.NaN, float.NaN, + 566.7f, 396.5f, 571.2f, 448.3f, 571.2f, 491, float.NaN, float.NaN, float.NaN, float.NaN, + 571.2f, 532.2f, 566.7f, 584.1f, 526.5f, 654.2f, 644.2f, 770.1f, 542.9f, 872.3f, + 431.1f, 750.3f, float.NaN, float.NaN, float.NaN, float.NaN, 402.8f, 780.8f, 317.9f, 800.6f, + 271.7f, 800.6f, float.NaN, float.NaN, float.NaN, float.NaN, 224.1f, 800.6f, 139.1f, 780.8f, + 110.8f, 750.3f, -0.9f, 872.3f, -102.2f, 770.1f, + ], + [ + 271.7f, 304.9f, float.NaN, float.NaN, float.NaN, float.NaN, 174.9f, 304.9f, 98.9f, 389.7f, + 98.9f, 491.8f, float.NaN, float.NaN, float.NaN, float.NaN, 98.9f, 590.7f, 174.9f, 675.5f, + 271.7f, 675.5f, float.NaN, float.NaN, float.NaN, float.NaN, 365.6f, 675.5f, 443, 590.7f, + 443, 491.8f, float.NaN, float.NaN, float.NaN, float.NaN, 443, 389.7f, 365.6f, 304.9f, + 271.7f, 304.9f, + ], + ], + + ['\u00a5'] = + [ + [ + 692, 1153.8f, 493.2f, 1153.8f, 271.8f, 843.2f, 48.8f, 1153.8f, -150, 1153.8f, + 187.8f, 686.3f, 187.8f, 497.7f, -98.3f, 497.7f, -98.3f, 348.7f, 187.8f, 348.7f, + 187.8f, 0, 354.2f, 0, 354.2f, 348.7f, 641.9f, 348.7f, 641.9f, 497.7f, + 354.2f, 497.7f, 354.2f, 686.3f, 692, 1153.8f, + ], + ], + + ['\u00a7'] = + [ + [ + 252.5f, 637.5f, float.NaN, float.NaN, float.NaN, float.NaN, 296.3f, 632.9f, 341.7f, 628.3f, + 384.1f, 622.2f, float.NaN, float.NaN, float.NaN, float.NaN, 456.7f, 610, 489.9f, 573.3f, + 479.4f, 533.5f, float.NaN, float.NaN, float.NaN, float.NaN, 473.3f, 515.2f, 467.3f, 490.7f, + 453.6f, 470.8f, float.NaN, float.NaN, float.NaN, float.NaN, 411.3f, 477, 361.4f, 489.2f, + 306.9f, 490.7f, float.NaN, float.NaN, float.NaN, float.NaN, 264.6f, 492.2f, 204.1f, 498.4f, + 143.6f, 506, float.NaN, float.NaN, float.NaN, float.NaN, 111.8f, 510.6f, 90.6f, 538.1f, + 90.6f, 570.2f, float.NaN, float.NaN, float.NaN, float.NaN, 90.6f, 611.5f, 110.3f, 635.9f, + 134.5f, 663.5f, float.NaN, float.NaN, float.NaN, float.NaN, 167.8f, 649.7f, 208.6f, 643.6f, + 252.5f, 637.5f, + ], + [ + -54.9f, 250.7f, float.NaN, float.NaN, float.NaN, float.NaN, -25.5f, 44.3f, 114, -13.8f, + 307.8f, -13.8f, float.NaN, float.NaN, float.NaN, float.NaN, 576.5f, -13.8f, 625, 172.7f, + 625, 230.8f, float.NaN, float.NaN, float.NaN, float.NaN, 625, 275.2f, 629.4f, 333.3f, + 558.9f, 405.1f, float.NaN, float.NaN, float.NaN, float.NaN, 592.7f, 440.3f, 625, 477, + 625, 561, float.NaN, float.NaN, float.NaN, float.NaN, 625, 704.7f, 475.2f, 770.5f, + 310.7f, 776.6f, float.NaN, float.NaN, float.NaN, float.NaN, 159.5f, 782.7f, 131.6f, 836.2f, + 131.6f, 889.7f, float.NaN, float.NaN, float.NaN, float.NaN, 131.6f, 943.2f, 197.7f, 989.1f, + 293.1f, 989.1f, float.NaN, float.NaN, float.NaN, float.NaN, 391.5f, 989.1f, 447.3f, 952.4f, + 447.3f, 880.5f, 598.5f, 880.5f, float.NaN, float.NaN, float.NaN, float.NaN, 598.5f, 1019.6f, + 486.9f, 1132.8f, 299, 1132.8f, float.NaN, float.NaN, float.NaN, float.NaN, 59.6f, 1132.8f, + -19.7f, 963.1f, -19.7f, 895.8f, float.NaN, float.NaN, float.NaN, float.NaN, -19.7f, 807.2f, + 3.8f, 761.3f, 34.7f, 730.7f, float.NaN, float.NaN, float.NaN, float.NaN, 9.7f, 704.7f, + -54.9f, 668, -54.9f, 574.8f, float.NaN, float.NaN, float.NaN, float.NaN, -54.9f, 388.3f, + 109.5f, 353.1f, 272.5f, 351.6f, float.NaN, float.NaN, float.NaN, float.NaN, 406.2f, 350.1f, + 473.7f, 322.6f, 473.7f, 240, float.NaN, float.NaN, float.NaN, float.NaN, 473.7f, 157.5f, + 368, 129.9f, 281.3f, 129.9f, float.NaN, float.NaN, float.NaN, float.NaN, 197.7f, 129.9f, + 131.6f, 157.5f, 96.3f, 250.7f, -54.9f, 250.7f, + ], + ], + + ['\u00a8'] = + [ + [ + 491, 974.2f, 491, 1227, 351.6f, 1227, 351.6f, 974.2f, 491, 974.2f, + ], + [ + 207.9f, 974.2f, 207.9f, 1227, 67, 1227, 67, 974.2f, 207.9f, 974.2f, + ], + ], + + ['\u00b4'] = + [ + [ + 253.9f, 1195.7f, 125, 931.2f, 262.4f, 931.2f, 435.1f, 1195.7f, 253.9f, 1195.7f, + ], + ], + + ['\u00b8'] = + [ + [ + 381.2f, -13.5f, 259.4f, -13.5f, 168.8f, -293.8f, 317.6f, -293.8f, 381.2f, -13.5f, + ], + ], + + ['\u00c4'] = + [ + [ + 417, 309.1f, 490.3f, 0, 646.1f, 0, 369.1f, 1074.9f, 172.9f, 1074.9f, + -104.1f, 0, 51.6f, 0, 123.5f, 309.1f, 417, 309.1f, + ], + [ + 374, 417.6f, 168.1f, 417.6f, 270.4f, 883.7f, 273.1f, 883.7f, 374, 417.6f, + ], + [ + 633.3f, 951.3f, 633.3f, 1162.2f, 500.6f, 1162.2f, 500.6f, 951.3f, 633.3f, 951.3f, + ], + [ + 48.6f, 951.3f, 48.6f, 1162.2f, -89.6f, 1162.2f, -89.6f, 951.3f, 48.6f, 951.3f, + ], + ], + + ['\u00c5'] = + [ + [ + 634.9f, 0, 448.9f, 696.2f, float.NaN, float.NaN, float.NaN, float.NaN, 523.9f, 741.5f, + 564.4f, 803.4f, 568.9f, 894, float.NaN, float.NaN, float.NaN, float.NaN, 564.4f, 1069.2f, + 415.9f, 1119.1f, 269, 1119.1f, float.NaN, float.NaN, float.NaN, float.NaN, 117.6f, 1119.1f, + -30.9f, 1069.2f, -30.9f, 894, float.NaN, float.NaN, float.NaN, float.NaN, -30.9f, 803.4f, + 9.6f, 741.5f, 83.1f, 696.2f, -96.9f, 0, 51.6f, 0, 98.1f, 197.8f, + 439.9f, 197.8f, 486.4f, 0, 634.9f, 0, + ], + [ + 406.9f, 333.8f, 131.1f, 333.8f, 206, 653.9f, 269, 649.4f, 332, 653.9f, + 406.9f, 333.8f, + ], + [ + 262, 1002.8f, float.NaN, float.NaN, float.NaN, float.NaN, 340.8f, 1002.8f, 431.9f, 980.1f, + 431.9f, 898.6f, float.NaN, float.NaN, float.NaN, float.NaN, 431.9f, 798.9f, 348.5f, 770.2f, + 262, 770.2f, float.NaN, float.NaN, float.NaN, float.NaN, 175.5f, 770.2f, 92.1f, 798.9f, + 92.1f, 898.6f, float.NaN, float.NaN, float.NaN, float.NaN, 92.1f, 980.1f, 183.2f, 1002.8f, + 262, 1002.8f, + ], + ], + + ['\u00c6'] = + [ + [ + 289.7f, 394.4f, 130, 394.4f, 224, 896.1f, 259.8f, 896.1f, 289.7f, 394.4f, + ], + [ + 298.6f, 268.7f, 315, 0, 622.4f, 0, 622.4f, 146, 444.8f, 146, + 421, 479.9f, 611.9f, 479.9f, 611.9f, 625.9f, 410.5f, 625.9f, 389.6f, 907, + 611.9f, 907, 611.9f, 1052.9f, 118.1f, 1052.9f, -83.3f, 0, 52.5f, 0, + 104.7f, 268.7f, 298.6f, 268.7f, + ], + ], + + ['\u00d1'] = + [ + [ + 469.5f, 218.6f, 466.5f, 218.6f, 119.1f, 793.6f, -59.1f, 793.6f, -59.1f, 0, + 80.5f, 0, 80.5f, 600.4f, 83.5f, 600.4f, 442.8f, 0, 609.1f, 0, + 609.1f, 793.6f, 469.5f, 793.6f, 469.5f, 218.6f, + ], + [ + 110.6f, 949.6f, float.NaN, float.NaN, float.NaN, float.NaN, 110.6f, 984.4f, 121, 1036.7f, + 161.1f, 1036.7f, float.NaN, float.NaN, float.NaN, float.NaN, 189.3f, 1036.7f, 219, 1011.3f, + 239.8f, 995.5f, float.NaN, float.NaN, float.NaN, float.NaN, 284.3f, 959.1f, 333.3f, 935.3f, + 391.2f, 935.3f, float.NaN, float.NaN, float.NaN, float.NaN, 507.1f, 935.3f, 565, 1050.9f, + 565, 1160.2f, 425.4f, 1160.2f, float.NaN, float.NaN, float.NaN, float.NaN, 425.4f, 1128.6f, + 416.5f, 1076.3f, 373.4f, 1076.3f, float.NaN, float.NaN, float.NaN, float.NaN, 342.2f, 1076.3f, + 317, 1103.2f, 282.8f, 1128.6f, float.NaN, float.NaN, float.NaN, float.NaN, 251.7f, 1153.9f, + 211.6f, 1180.8f, 155.2f, 1180.8f, float.NaN, float.NaN, float.NaN, float.NaN, 36.4f, 1180.8f, + -29, 1085.8f, -29, 965.4f, -29, 949.6f, 110.6f, 949.6f, + ], + ], + + ['\u00d6'] = + [ + [ + 281.7f, 913.8f, float.NaN, float.NaN, float.NaN, float.NaN, 48.2f, 913.8f, -74.4f, 678.8f, + -74.4f, 456.1f, float.NaN, float.NaN, float.NaN, float.NaN, -74.4f, 235, 48.2f, -13.9f, + 281.7f, -13.9f, float.NaN, float.NaN, float.NaN, float.NaN, 515.3f, -13.9f, 636.4f, 235, + 636.4f, 456.1f, float.NaN, float.NaN, float.NaN, float.NaN, 636.4f, 678.8f, 515.3f, 913.8f, + 281.7f, 913.8f, + ], + [ + 281.7f, 784.5f, float.NaN, float.NaN, float.NaN, float.NaN, 435.4f, 784.5f, 484.2f, 612.5f, + 484.2f, 462.8f, float.NaN, float.NaN, float.NaN, float.NaN, 484.2f, 324.3f, 435.4f, 128.4f, + 281.7f, 128.4f, float.NaN, float.NaN, float.NaN, float.NaN, 128, 128.4f, 77.8f, 324.3f, + 77.8f, 462.8f, float.NaN, float.NaN, float.NaN, float.NaN, 77.8f, 612.5f, 128, 784.5f, + 281.7f, 784.5f, + ], + [ + 633.1f, 943.6f, 633.1f, 1149.3f, 495.5f, 1149.3f, 495.5f, 943.6f, 633.1f, 943.6f, + ], + [ + 45.5f, 943.6f, 45.5f, 1149.3f, -92.1f, 1149.3f, -92.1f, 943.6f, 45.5f, 943.6f, + ], + ], + + ['\u00d8'] = + [ + [ + 275, 1026.9f, float.NaN, float.NaN, float.NaN, float.NaN, 18.1f, 1026.9f, -83.5f, 746.5f, + -83.5f, 563.5f, float.NaN, float.NaN, float.NaN, float.NaN, -83.5f, 364.5f, 16.6f, 97.4f, + 275, 97.4f, float.NaN, float.NaN, float.NaN, float.NaN, 533.4f, 97.4f, 633.5f, 364.5f, + 633.5f, 563.5f, float.NaN, float.NaN, float.NaN, float.NaN, 633.5f, 746.5f, 531.9f, 1026.9f, + 275, 1026.9f, + ], + [ + 275, 913.5f, float.NaN, float.NaN, float.NaN, float.NaN, 452.8f, 913.5f, 479.7f, 678.3f, + 479.7f, 558.6f, float.NaN, float.NaN, float.NaN, float.NaN, 479.7f, 441.7f, 449.8f, 214.7f, + 275, 214.7f, float.NaN, float.NaN, float.NaN, float.NaN, 98.7f, 214.7f, 70.3f, 441.7f, + 70.3f, 558.6f, float.NaN, float.NaN, float.NaN, float.NaN, 70.3f, 678.3f, 95.7f, 913.5f, + 275, 913.5f, + ], + [ + 373.5f, 1138.8f, 69.4f, 14.5f, 176.5f, -14.4f, 480.6f, 1109.8f, 373.5f, 1138.8f, + ], + ], + + ['\u00dc'] = + [ + [ + -88.4f, 868.4f, -88.4f, 349.5f, float.NaN, float.NaN, float.NaN, float.NaN, -88.4f, 123.2f, + 29.1f, -13.9f, 296, -13.9f, float.NaN, float.NaN, float.NaN, float.NaN, 561.2f, -13.9f, + 680.4f, 123.2f, 680.4f, 349.5f, 680.4f, 868.4f, 507.5f, 868.4f, 507.5f, 360.3f, + float.NaN, float.NaN, float.NaN, float.NaN, 507.5f, 214, 468.9f, 130.9f, 296, 130.9f, + float.NaN, float.NaN, float.NaN, float.NaN, 123.1f, 130.9f, 84.5f, 214, 84.5f, 360.3f, + 84.5f, 868.4f, -88.4f, 868.4f, + ], + [ + 122.8f, 1028.1f, 122.8f, 1137.4f, -84.2f, 1137.4f, -84.2f, 1028.1f, 122.8f, 1028.1f, + ], + [ + 637, 1028.1f, 637, 1137.4f, 436.3f, 1137.4f, 436.3f, 1028.1f, 637, 1028.1f, + ], + ], + + ['\u00df'] = + [ + [ + 274.4f, 529.7f, float.NaN, float.NaN, float.NaN, float.NaN, 423.1f, 529.7f, 465.6f, 450.5f, + 465.6f, 304.5f, float.NaN, float.NaN, float.NaN, float.NaN, 465.6f, 175.5f, 417.1f, 133.6f, + 297.2f, 133.6f, 280.5f, 133.6f, 280.5f, 0, 313.9f, 0, float.NaN, float.NaN, + float.NaN, float.NaN, 491.4f, 0, 622, 122.7f, 622, 313.8f, float.NaN, float.NaN, + float.NaN, float.NaN, 622, 448.9f, 567.3f, 562.3f, 441.4f, 607.4f, float.NaN, float.NaN, + float.NaN, float.NaN, 530.9f, 658.7f, 587.1f, 741, 587.1f, 851.3f, float.NaN, float.NaN, + float.NaN, float.NaN, 587.1f, 1045.5f, 417.1f, 1151.1f, 247.1f, 1151.1f, float.NaN, float.NaN, + float.NaN, float.NaN, 51.3f, 1151.1f, -91.4f, 1014.4f, -91.4f, 806.2f, -91.4f, 0, + 65, 0, 65, 753.4f, float.NaN, float.NaN, float.NaN, float.NaN, 65, 901, + 89.2f, 1017.5f, 256.2f, 1017.5f, float.NaN, float.NaN, float.NaN, float.NaN, 368.5f, 1017.5f, + 430.7f, 956.9f, 430.7f, 837.3f, float.NaN, float.NaN, float.NaN, float.NaN, 430.7f, 727, + 376.1f, 675.7f, 274.4f, 675.7f, 250.1f, 675.7f, 250.1f, 529.7f, 274.4f, 529.7f, + ], + ], + + ['\u00e5'] = + [ + [ + 595.9f, 471.3f, float.NaN, float.NaN, float.NaN, float.NaN, 595.9f, 542.2f, 604.9f, 675.2f, + 474.3f, 712.9f, float.NaN, float.NaN, float.NaN, float.NaN, 538.9f, 758.2f, 565.9f, 806.6f, + 565.9f, 886.6f, float.NaN, float.NaN, float.NaN, float.NaN, 565.9f, 1054.3f, 427.8f, 1119.2f, + 283.7f, 1119.2f, float.NaN, float.NaN, float.NaN, float.NaN, 139.6f, 1119.2f, 1.5f, 1054.3f, + 1.5f, 886.6f, float.NaN, float.NaN, float.NaN, float.NaN, 1.5f, 806.6f, 28.6f, 758.2f, + 93.1f, 712.9f, float.NaN, float.NaN, float.NaN, float.NaN, 31.6f, 684.2f, -3, 635.9f, + -13.5f, 566.4f, 141.1f, 566.4f, float.NaN, float.NaN, float.NaN, float.NaN, 141.1f, 640.4f, + 222.2f, 654, 301.7f, 654, float.NaN, float.NaN, float.NaN, float.NaN, 429.3f, 654, + 441.3f, 589.1f, 441.3f, 503, 441.3f, 475.8f, 301.7f, 475.8f, float.NaN, float.NaN, + float.NaN, float.NaN, 136.6f, 475.8f, -46.5f, 416.9f, -46.5f, 220.5f, float.NaN, float.NaN, + float.NaN, float.NaN, -46.5f, 66.5f, 78.1f, -13.6f, 223.7f, -13.6f, float.NaN, float.NaN, + float.NaN, float.NaN, 303.2f, -13.6f, 382.8f, 16.6f, 441.3f, 72.5f, 441.3f, 0, + 595.9f, 0, 595.9f, 471.3f, + ], + [ + 438.4f, 359.5f, 438.4f, 332.3f, float.NaN, float.NaN, float.NaN, float.NaN, 438.4f, 208.4f, + 370.4f, 102.7f, 229.7f, 102.7f, float.NaN, float.NaN, float.NaN, float.NaN, 152.4f, 102.7f, + 95.2f, 148, 95.2f, 226.6f, float.NaN, float.NaN, float.NaN, float.NaN, 95.2f, 326.3f, + 186.4f, 359.5f, 273, 359.5f, 438.4f, 359.5f, + ], + [ + 290, 1007.5f, float.NaN, float.NaN, float.NaN, float.NaN, 375.1f, 1007.5f, 453.9f, 974.2f, + 453.9f, 886.6f, float.NaN, float.NaN, float.NaN, float.NaN, 453.9f, 786.9f, 373.5f, 764.3f, + 290, 764.3f, float.NaN, float.NaN, float.NaN, float.NaN, 206.6f, 764.3f, 126.2f, 786.9f, + 126.2f, 886.6f, float.NaN, float.NaN, float.NaN, float.NaN, 126.2f, 974.2f, 205, 1007.5f, + 290, 1007.5f, + ], + ], + + ['\u00e6'] = + [ + [ + 338.2f, 477.9f, 338.2f, 523.1f, float.NaN, float.NaN, float.NaN, float.NaN, 338.2f, 597.2f, + 338.2f, 682.7f, 417.7f, 682.7f, float.NaN, float.NaN, float.NaN, float.NaN, 500.3f, 682.7f, + 500.3f, 597.2f, 500.3f, 523.1f, 500.3f, 477.9f, 338.2f, 477.9f, + ], + [ + 209.9f, 359.1f, 209.9f, 260.7f, float.NaN, float.NaN, float.NaN, float.NaN, 209.9f, 202.7f, + 203.9f, 110.7f, 129.7f, 110.7f, float.NaN, float.NaN, float.NaN, float.NaN, 55.5f, 110.7f, + 49.5f, 186.5f, 49.5f, 236.5f, float.NaN, float.NaN, float.NaN, float.NaN, 49.5f, 343, + 108.5f, 359.1f, 199.3f, 359.1f, 209.9f, 359.1f, + ], + [ + 630.7f, 355.4f, 630.7f, 504.2f, float.NaN, float.NaN, float.NaN, float.NaN, 630.7f, 660.8f, + 612.5f, 798.6f, 427.9f, 798.6f, float.NaN, float.NaN, float.NaN, float.NaN, 371.9f, 798.6f, + 305.3f, 775.1f, 276.5f, 718.7f, float.NaN, float.NaN, float.NaN, float.NaN, 250.8f, 782.9f, + 181.2f, 798.6f, 120.6f, 798.6f, float.NaN, float.NaN, float.NaN, float.NaN, -5, 798.6f, + -80.7f, 709.3f, -80.7f, 580.9f, 49.5f, 580.9f, float.NaN, float.NaN, float.NaN, float.NaN, + 49.5f, 629.5f, 76.7f, 676.4f, 132.7f, 676.4f, float.NaN, float.NaN, float.NaN, float.NaN, + 209.9f, 676.4f, 209.9f, 593.4f, 209.9f, 512, 209.9f, 477.6f, 197.8f, 477.6f, + float.NaN, float.NaN, float.NaN, float.NaN, 5.6f, 477.6f, -80.7f, 440, -80.7f, 222.3f, + float.NaN, float.NaN, float.NaN, float.NaN, -80.7f, 93.9f, -32.3f, -14.1f, 107, -14.1f, + float.NaN, float.NaN, float.NaN, float.NaN, 176.6f, -14.1f, 246.2f, 11, 276.5f, 79.9f, + float.NaN, float.NaN, float.NaN, float.NaN, 302.2f, 9.4f, 364.3f, -14.1f, 433.9f, -14.1f, + float.NaN, float.NaN, float.NaN, float.NaN, 564.1f, -14.1f, 630.7f, 68.9f, 630.7f, 198.9f, + 630.7f, 234.9f, 497.5f, 234.9f, float.NaN, float.NaN, float.NaN, float.NaN, 497.5f, 178.5f, + 489.9f, 114.3f, 423.3f, 114.3f, float.NaN, float.NaN, float.NaN, float.NaN, 343.1f, 114.3f, + 343.1f, 205.1f, 343.1f, 267.8f, 343.1f, 355.4f, 630.7f, 355.4f, + ], + ], + + ['\u00f8'] = + [ + [ + 599.3f, 905.5f, 479.5f, 905.5f, 409.4f, 769, float.NaN, float.NaN, float.NaN, float.NaN, + 368.5f, 783, 321.8f, 789.2f, 278, 790.7f, float.NaN, float.NaN, float.NaN, float.NaN, + 56, 790.7f, -76.9f, 627.9f, -76.9f, 392.3f, float.NaN, float.NaN, float.NaN, float.NaN, + -76.9f, 274.4f, -38.9f, 150.4f, 50.2f, 66.7f, -55, -139.5f, 63.3f, -139.5f, + 139.3f, 9.3f, float.NaN, float.NaN, float.NaN, float.NaN, 181.6f, -4.7f, 226.9f, -14, + 278, -14, float.NaN, float.NaN, float.NaN, float.NaN, 502.9f, -14, 632.9f, 162.8f, + 632.9f, 392.3f, float.NaN, float.NaN, float.NaN, float.NaN, 632.9f, 502.4f, 584.7f, 637.2f, + 498.5f, 707, 599.3f, 905.5f, + ], + [ + 449.4f, 578.3f, float.NaN, float.NaN, float.NaN, float.NaN, 489.7f, 525.6f, 502.1f, 448.1f, + 502.1f, 390.7f, float.NaN, float.NaN, float.NaN, float.NaN, 502.1f, 246.5f, 452.5f, 105.4f, + 285, 105.4f, float.NaN, float.NaN, float.NaN, float.NaN, 250.9f, 105.4f, 224.5f, 116.3f, + 202.8f, 127.1f, 449.4f, 578.3f, + ], + [ + 111.3f, 184, float.NaN, float.NaN, float.NaN, float.NaN, 67.9f, 239.9f, 67.9f, 319.8f, + 67.9f, 388.4f, float.NaN, float.NaN, float.NaN, float.NaN, 67.9f, 537, 125.3f, 677.5f, + 285, 677.5f, float.NaN, float.NaN, float.NaN, float.NaN, 309.8f, 677.5f, 336.2f, 672.7f, + 361, 658.3f, 111.3f, 184, + ], + ], + + ['\u0132'] = + [ + [ + 54.4f, 1065.6f, -106.2f, 1065.6f, -106.2f, 0, 54.4f, 0, 54.4f, 1065.6f, + ], + [ + 503.5f, 303.3f, float.NaN, float.NaN, float.NaN, float.NaN, 503.5f, 238.9f, 497.3f, 136.7f, + 395.9f, 136.7f, float.NaN, float.NaN, float.NaN, float.NaN, 347.6f, 136.7f, 299.2f, 146.2f, + 254, 161.9f, 254, 0, 410, -12.6f, float.NaN, float.NaN, float.NaN, float.NaN, + 503.5f, -18.9f, 590.9f, 11, 634.6f, 100.6f, float.NaN, float.NaN, float.NaN, float.NaN, + 664.2f, 160.3f, 664.2f, 240.5f, 664.2f, 303.3f, 664.2f, 1065.6f, 503.5f, 1065.6f, + 503.5f, 303.3f, + ], + ], + + ['\u0133'] = + [ + [ + 93, 771.6f, -56.4f, 771.6f, -56.4f, 7, 93, 7, 93, 771.6f, + ], + [ + 447.2f, 764.6f, 447.2f, 80.9f, float.NaN, float.NaN, float.NaN, float.NaN, 447.2f, -67.2f, + 434.2f, -132.8f, 281.9f, -132.8f, 194.9f, -132.8f, 194.9f, -264, 303.7f, -264, + float.NaN, float.NaN, float.NaN, float.NaN, 548.7f, -264, 596.6f, -148, 596.6f, 90, + 596.6f, 764.6f, 447.2f, 764.6f, + ], + [ + 428.3f, 1152.4f, 428.3f, 952.6f, 646.9f, 952.6f, 646.9f, 1152.4f, 428.3f, 1152.4f, + ], + [ + -104.7f, 1152.4f, -104.7f, 952.6f, 113.9f, 952.6f, 113.9f, 1152.4f, -104.7f, 1152.4f, + ], + ], + + ['|'] = + [ + [275, -90, 275, 1170], + ], + + ['\u2223'] = + [ + [275, -190, 275, 514.75f], + [275, 565.25f, 275, 1270], + ], + + ['\u2015'] = + [ + [-309, -454, 859, -454], + ], + + ['\u2588'] = + [ + [-99, -51, 649, -51, 649, 1090, -99, 1090, -99, -51], + ], + }; + + /// + /// Gets, per character, the indices of strokes drawn with projecting square caps. + /// + public static IReadOnlyDictionary SquareStrokes { get; } = new Dictionary + { + }; + + /// + /// Gets, per character, the indices of strokes cut off exactly at their endpoints. + /// + public static IReadOnlyDictionary ButtStrokes { get; } = new Dictionary + { + ['|'] = [0], + ['\u2223'] = [0, 1], + ['\u2015'] = [0], + }; + + /// + /// Gets, per character, the indices of strokes drawn with butt ends and sharp miter joins. + /// + public static IReadOnlyDictionary MiterStrokes { get; } = new Dictionary + { + }; + + /// + /// Gets, per character, the indices of strokes drawn with round caps and sharp miter joins. + /// + public static IReadOnlyDictionary MiterRoundStrokes { get; } = new Dictionary + { + }; + + /// + /// Gets the stroke width per character. Zero marks a character whose entry is filled ink + /// outlines fitted to the standard. + /// + public static IReadOnlyDictionary StrokeWidths { get; } = new Dictionary + { + ['!'] = 0, + ['"'] = 0, + ['#'] = 0, + ['$'] = 0, + ['%'] = 0, + ['&'] = 0, + ['\''] = 0, + ['('] = 0, + [')'] = 0, + ['*'] = 0, + ['+'] = 0, + [','] = 0, + ['-'] = 0, + ['.'] = 0, + ['/'] = 0, + ['0'] = 0, + ['1'] = 0, + ['2'] = 0, + ['3'] = 0, + ['4'] = 0, + ['5'] = 0, + ['6'] = 0, + ['7'] = 0, + ['8'] = 0, + ['9'] = 0, + [':'] = 0, + [';'] = 0, + ['<'] = 0, + ['='] = 0, + ['>'] = 0, + ['?'] = 0, + ['@'] = 0, + ['A'] = 0, + ['B'] = 0, + ['C'] = 0, + ['D'] = 0, + ['E'] = 0, + ['F'] = 0, + ['G'] = 0, + ['H'] = 0, + ['I'] = 0, + ['J'] = 0, + ['K'] = 0, + ['L'] = 0, + ['M'] = 0, + ['N'] = 0, + ['O'] = 0, + ['P'] = 0, + ['Q'] = 0, + ['R'] = 0, + ['S'] = 0, + ['T'] = 0, + ['U'] = 0, + ['V'] = 0, + ['W'] = 0, + ['X'] = 0, + ['Y'] = 0, + ['Z'] = 0, + ['['] = 0, + ['\\'] = 0, + [']'] = 0, + ['^'] = 0, + ['_'] = 0, + ['`'] = 0, + ['a'] = 0, + ['b'] = 0, + ['c'] = 0, + ['d'] = 0, + ['e'] = 0, + ['f'] = 0, + ['g'] = 0, + ['h'] = 0, + ['i'] = 0, + ['j'] = 0, + ['k'] = 0, + ['l'] = 0, + ['m'] = 0, + ['n'] = 0, + ['o'] = 0, + ['p'] = 0, + ['q'] = 0, + ['r'] = 0, + ['s'] = 0, + ['t'] = 0, + ['u'] = 0, + ['v'] = 0, + ['w'] = 0, + ['x'] = 0, + ['y'] = 0, + ['z'] = 0, + ['{'] = 0, + ['}'] = 0, + ['~'] = 0, + ['\u00a3'] = 0, + ['\u00a4'] = 0, + ['\u00a5'] = 0, + ['\u00a7'] = 0, + ['\u00a8'] = 0, + ['\u00b4'] = 0, + ['\u00b8'] = 0, + ['\u00c4'] = 0, + ['\u00c5'] = 0, + ['\u00c6'] = 0, + ['\u00d1'] = 0, + ['\u00d6'] = 0, + ['\u00d8'] = 0, + ['\u00dc'] = 0, + ['\u00df'] = 0, + ['\u00e5'] = 0, + ['\u00e6'] = 0, + ['\u00f8'] = 0, + ['\u0132'] = 0, + ['\u0133'] = 0, + }; + + /// + /// Gets per-stroke width overrides keyed by character and stroke index. The value zero + /// marks a stroke that is already a filled ink polygon rather than a centerline to stroke. + /// + public static IReadOnlyDictionary<(char Character, int Stroke), float> StrokeWidthOverrides { get; } = + new Dictionary<(char Character, int Stroke), float> + { + [('\u2588', 0)] = 0, + }; + + /// + /// Gets the design definition the generator consumes. + /// + public static FontDesign Design { get; } = new() + { + Name = "OcrB", + FamilyName = "SixLabors OCRB", + DataSummary = "Built clean-room from the character sheet and dimension tables of ECMA-11.", + UnitsPerEm = UnitsPerEm, + H = H, + W = W, + Descender = D, + DefaultStrokeWidth = T, + SmallLetterHeight = SmallLetterHeight, + CapitalHeight = CapitalHeight, + AscenderHeight = AscenderHeight, + DescenderDepth = DescenderDepth, + NormalizationExceptions = string.Empty, + DrawnCapitalHeight = 1050, + + // The reference OCR-B digitization prints its capital ink 678 units above the baseline per em, so + // the scale lands ours there and a point size means the same glyph size in both. + EmScale = 678F / CapitalHeight, + DrawnSmallLetterHeight = 780.6F, + DrawnAscenderHeight = 1154.2F, + DrawnDescenderDepth = 266.7F, + CutTerminals = false, + Skeletons = Skeletons, + SquareStrokes = SquareStrokes, + ButtStrokes = ButtStrokes, + MiterStrokes = MiterStrokes, + MiterRoundStrokes = MiterRoundStrokes, + StrokeWidths = StrokeWidths, + StrokeWidthOverrides = StrokeWidthOverrides, + Expectations = SpecChecks.OcrB, + }; +} diff --git a/src/ImageSharp.Drawing.FontGenerator/Program.cs b/src/ImageSharp.Drawing.FontGenerator/Program.cs new file mode 100644 index 00000000..6ccf7dfc --- /dev/null +++ b/src/ImageSharp.Drawing.FontGenerator/Program.cs @@ -0,0 +1,1255 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// Generates the SixLabors OCR fonts from the OCR standards themselves: OCR-A from the dimensioned +// character drawings of FIPS PUB 32, OCR-B from the character sheet and dimension tables of ECMA-11. +// Each design's glyph skeletons are stroked with the library's own path stroker, the outlines are +// verified against the independently transcribed SpecChecks expectations, and FontData.cs is +// written straight into the library. The .ttf and the proof, grid and comparison sheets are written +// into artifacts/fonts. See specs/README.md for the source documents. +using System.Globalization; +using System.Numerics; +using System.Text; +using SixLabors.Fonts; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Drawing; +using SixLabors.ImageSharp.Drawing.FontGenerator; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +string? referencePath = null; +string? referenceBPath = null; +List inspectTexts = []; +for (int argIndex = 0; argIndex < args.Length; argIndex++) +{ + if (args[argIndex] == "--ref" && argIndex + 1 < args.Length) + { + referencePath = args[++argIndex]; + } + else if (args[argIndex] == "--refb" && argIndex + 1 < args.Length) + { + referenceBPath = args[++argIndex]; + } + else if (args[argIndex] is "-h" or "--help") + { + Console.WriteLine("Usage: ImageSharp.Drawing.FontGenerator [--ref ] [--refb ] [...]"); + Console.WriteLine(" --ref renders a side-by-side comparison sheet of the OCR-A design against the given font"); + Console.WriteLine(" --refb renders the same sheet for the OCR-B design"); + Console.WriteLine(" Remaining arguments render full-size single glyph inspection images per design."); + return 0; + } + else + { + inspectTexts.Add(args[argIndex]); + } +} + +string libraryFontDirectory = RepositoryPaths.LibraryFonts; +string outputDirectory = RepositoryPaths.Artifacts; +Directory.CreateDirectory(outputDirectory); + +StrokeOptions roundStroke = new() +{ + LineJoin = LineJoin.Round, + LineCap = LineCap.Round, +}; + +StrokeOptions squareStroke = new() +{ + LineJoin = LineJoin.Round, + LineCap = LineCap.Square, +}; + +StrokeOptions buttStroke = new() +{ + LineJoin = LineJoin.Miter, + LineCap = LineCap.Butt, + MiterLimit = 1.5D, +}; + +StrokeOptions miterSquareStroke = new() +{ + LineJoin = LineJoin.Miter, + LineCap = LineCap.Square, + MiterLimit = 1.5D, +}; + +StrokeOptions miterStroke = new() +{ + LineJoin = LineJoin.Miter, + LineCap = LineCap.Butt, +}; + +StrokeOptions miterRoundStroke = new() +{ + LineJoin = LineJoin.Miter, + LineCap = LineCap.Round, +}; + +FontDesign[] designs = [OcrAGlyphs.Design, OcrBGlyphs.Design]; +FontCollection collection = new(); +Dictionary families = []; +foreach (FontDesign design in designs) +{ + if (design.Skeletons.Count == 0) + { + Console.WriteLine($"{design.Name}: no glyphs yet, skipped"); + continue; + } + + float emScale = design.EmScale; + ushort advanceWidth = (ushort)MathF.Round(design.UnitsPerEm * emScale); + + // The sheet the skeletons are traced from fixes shape but not size, so every glyph is normalized + // onto the height the standard gives its class. The scale is the glyph's own drawn bounds against + // the bounds the standard calls for, and it applies to both axes, so the glyph lands on the line + // and keeps every ratio it was drawn with. Each glyph carries its own scale, so a line of capitals, + // digits and full height marks is one height throughout. + float NormalizedScale(char character, float top, float bottom) + { + if (design.NormalizationExceptions.Contains(character, StringComparison.Ordinal)) + { + return 1F; + } + + // A letter carries its category's scale, so a letter that reaches past its line, an accented one + // for example, keeps the mark in proportion with the body. A digit and a mark carry their own, + // so a line of digits and full height marks is one height throughout. A mark that never reaches + // the capital line, a hyphen or a comma, is not a full height glyph: normalizing it onto that + // line would enlarge it out of all proportion, so it carries the scale of the capitals. + switch (Classify(character)) + { + case GlyphClass.Absolute: + return 1F; + case GlyphClass.Digit: + return design.CapitalHeight / top; + case GlyphClass.Ascender: + return design.AscenderHeight / design.DrawnAscenderHeight; + case GlyphClass.SmallLetter: + return design.SmallLetterHeight / design.DrawnSmallLetterHeight; + case GlyphClass.Descender: + return design.DescenderDepth / design.DrawnDescenderDepth; + default: + float capitalScale = design.CapitalHeight / design.DrawnCapitalHeight; + return char.IsLetter(character) || top < design.DrawnCapitalHeight + ? capitalScale + : design.CapitalHeight / top; + } + } + + // Ink projects a half stroke beyond the centerline box at every terminal, dots rise above it and + // descenders drop below it, so the vertical metrics cover those extremes. Text layout grows the + // line box for any glyph whose ink passes the ascender, which shifts that glyph off the shared + // baseline, so the metrics are measured from the geometry rather than assumed from the box. + float inkTop = 0; + float inkBottom = 0; + + // The cap height is the ink height of a capital, which text layout uses to set a line of capitals + // or digits against a reference line. + float capHeight = design.CapitalHeight; + + float sideBearing = (design.UnitsPerEm - design.W) / 2; + Dictionary>> auditOutlines = []; + Dictionary>> quantizedGlyphs = []; + Dictionary glyphScales = []; + List specFailures = []; + foreach ((char character, float[][] strokes) in design.Skeletons.OrderBy(entry => entry.Key)) + { + List> designContours = []; + design.SquareStrokes.TryGetValue(character, out int[]? squareIndices); + design.ButtStrokes.TryGetValue(character, out int[]? buttIndices); + design.MiterStrokes.TryGetValue(character, out int[]? miterIndices); + design.MiterRoundStrokes.TryGetValue(character, out int[]? miterRoundIndices); + float characterWidth = design.StrokeWidths.TryGetValue(character, out float perCharacter) + ? perCharacter + : design.DefaultStrokeWidth; + + // A skeleton may record one outline as a run of single-segment strokes that share endpoints. + // Under cut terminals every seam would project two square caps, so such runs merge into one + // figure. Glyphs with per-stroke styling or width overrides keep their stroke list untouched + // so the recorded stroke indices stay valid. + float[][] strokeSet = strokes; + if (design.CutTerminals + && squareIndices is null + && buttIndices is null + && miterIndices is null + && miterRoundIndices is null + && !design.StrokeWidthOverrides.Keys.Any(key => key.Character == character)) + { + List chained = []; + foreach (float[] stroke in strokes) + { + float[]? previous = chained.Count > 0 ? chained[^1] : null; + if (previous is not null && previous[^2] == stroke[0] && previous[^1] == stroke[1]) + { + float[] run = new float[previous.Length + stroke.Length - 2]; + + previous.CopyTo(run, 0); + stroke.AsSpan(2).CopyTo(run.AsSpan(previous.Length)); + chained[^1] = run; + } + else + { + chained.Add(stroke); + } + } + + strokeSet = [.. chained]; + } + + // Union all strokes into one outline so the glyph carries no overlapping contours: coincident + // edges from overlaid strokes otherwise double-cover antialiased pixels at render time. + List centerlines = []; + List strokeWidths = []; + List strokeStyles = []; + List cutFlags = []; + for (int strokeIndex = 0; strokeIndex < strokeSet.Length; strokeIndex++) + { + float[] tokens = strokeSet[strokeIndex]; + PathBuilder builder = new(); + builder.StartFigure(); + BuildStroke(builder, tokens); + + // A stroke that returns to its start point is a loop: close it so the seam takes a join + // rather than two caps. + if (tokens[0] == tokens[^2] && tokens[1] == tokens[^1]) + { + builder.CloseFigure(); + } + + StrokeOptions options = design.CutTerminals ? buttStroke : roundStroke; + bool cutEnds = design.CutTerminals; + if (squareIndices is not null && Array.IndexOf(squareIndices, strokeIndex) >= 0) + { + options = squareStroke; + cutEnds = false; + } + else if (buttIndices is not null && Array.IndexOf(buttIndices, strokeIndex) >= 0) + { + options = buttStroke; + cutEnds = false; + } + else if (miterIndices is not null && Array.IndexOf(miterIndices, strokeIndex) >= 0) + { + options = miterStroke; + cutEnds = false; + } + else if (miterRoundIndices is not null && Array.IndexOf(miterRoundIndices, strokeIndex) >= 0) + { + options = miterRoundStroke; + cutEnds = false; + } + + float effectiveWidth = design.StrokeWidthOverrides.TryGetValue((character, strokeIndex), out float overrideWidth) + ? overrideWidth + : characterWidth; + + // A sub-quantum width offset keeps stroke outlines from sharing exactly coincident + // collinear edges, which the polygon clipper cannot union reliably; the deviation is far + // below the design grid and vanishes in quantization. Width zero marks a pre-computed + // filled ink polygon, carried through as drawn. + if (effectiveWidth != 0) + { + effectiveWidth += strokeIndex * 0.02f; + } + + centerlines.Add(builder.Build()); + strokeWidths.Add(effectiveWidth); + strokeStyles.Add(options); + cutFlags.Add(cutEnds); + } + + // Butt-capped ink for every stroke, the probe geometry that tells a buried junction end from + // a true terminal. + IPath[] probeInk = new IPath[centerlines.Count]; + if (design.CutTerminals) + { + for (int strokeIndex = 0; strokeIndex < centerlines.Count; strokeIndex++) + { + probeInk[strokeIndex] = strokeWidths[strokeIndex] == 0 + ? centerlines[strokeIndex] + : centerlines[strokeIndex].GenerateOutline(strokeWidths[strokeIndex], buttStroke); + } + } + + // Open-stroke endpoints, indexed by stroke, for junction detection at shared points. + List<(int Stroke, Vector2 Point)> strokeEndpoints = []; + for (int strokeIndex = 0; strokeIndex < centerlines.Count; strokeIndex++) + { + ISimplePath[] flat = [.. centerlines[strokeIndex].Flatten()]; + if (flat.Length == 1 && !flat[0].IsClosed && strokeWidths[strokeIndex] != 0) + { + ReadOnlySpan pp = flat[0].Points.Span; + + strokeEndpoints.Add((strokeIndex, pp[0])); + strokeEndpoints.Add((strokeIndex, pp[^1])); + } + } + + List outlines = []; + for (int strokeIndex = 0; strokeIndex < centerlines.Count; strokeIndex++) + { + float effectiveWidth = strokeWidths[strokeIndex]; + if (effectiveWidth == 0) + { + outlines.Add(centerlines[strokeIndex]); + continue; + } + + ISimplePath[] flattened = [.. centerlines[strokeIndex].Flatten()]; + if (!cutFlags[strokeIndex] || flattened.Length != 1 || flattened[0].IsClosed) + { + StrokeOptions closedOptions = cutFlags[strokeIndex] ? miterSquareStroke : strokeStyles[strokeIndex]; + + outlines.Add(centerlines[strokeIndex].GenerateOutline(effectiveWidth, closedOptions)); + continue; + } + + CutTerminalStroke( + flattened[0].Points.ToArray(), + centerlines[strokeIndex], + effectiveWidth, + strokeIndex, + probeInk, + strokeEndpoints, + buttStroke, + outlines); + } + + // TrueType fills with the nonzero winding rule, so overlapping same-winding contours render + // identically to their union; the polygon clipper drops pieces from many-input unions, so the + // cut-terminal design writes its overlapping contours directly. + bool allInk = strokeWidths.TrueForAll(width => width == 0); + IPath merged = outlines.Count == 1 + ? outlines[0] + : design.CutTerminals || allInk + ? new ComplexPolygon([.. outlines]) + : outlines[0].Clip(BooleanOperation.Union, outlines.Skip(1)); + + List> contours = []; + foreach (ISimplePath simple in merged.Flatten()) + { + ReadOnlySpan points = simple.Points.Span; + Vector2[] dense = new Vector2[points.Length]; + for (int i = 0; i < points.Length; i++) + { + dense[i] = new Vector2(points[i].X, points[i].Y); + } + + // The stroker flattens curves densely; most of those points are collinear within a + // fraction of a design unit and would bloat the font. 0.6 units is 1.5 microns of print. + designContours.Add(SimplifyContour(dense, 0.6f)); + } + + // Both axes take one scale, about the baseline and the centre of the character cell, so the + // glyph lands on the height its category stands at and keeps the shape it was drawn with. + float drawnTop = 0; + float drawnBottom = 0; + foreach (IReadOnlyList drawnContour in designContours) + { + foreach (Vector2 point in drawnContour) + { + drawnTop = MathF.Max(drawnTop, point.Y); + drawnBottom = MathF.Min(drawnBottom, point.Y); + } + } + + float glyphScale = NormalizedScale(character, drawnTop, drawnBottom); + glyphScales[character] = glyphScale; + float cellCentre = design.W / 2; + foreach (Vector2[] designPoints in designContours.Cast()) + { + for (int i = 0; i < designPoints.Length; i++) + { + Vector2 point = designPoints[i]; + designPoints[i] = new Vector2(cellCentre + ((point.X - cellCentre) * glyphScale), point.Y * glyphScale); + inkTop = MathF.Max(inkTop, designPoints[i].Y); + inkBottom = MathF.Min(inkBottom, designPoints[i].Y); + } + + List<(short X, short Y)> contour = new(designPoints.Length); + foreach (Vector2 point in designPoints) + { + (short X, short Y) quantized = ((short)MathF.Round((point.X + sideBearing) * emScale), (short)MathF.Round(point.Y * emScale)); + if (contour.Count == 0 || contour[^1] != quantized) + { + contour.Add(quantized); + } + } + + if (contour.Count > 2 && contour[0] == contour[^1]) + { + contour.RemoveAt(contour.Count - 1); + } + + if (contour.Count > 2) + { + contours.Add(contour); + } + } + + quantizedGlyphs[character] = contours; + auditOutlines[character] = designContours; + SpecChecks.Verify(character, designContours, design.Expectations, specFailures); + } + + // Text layout centers the em box inside the declared line height, so a glyph cell reaches + // ascender - (lineHeight - unitsPerEm) / 2 above the baseline. Ink above that line makes the + // layout lower that one glyph to keep it inside the cell, which breaks the shared baseline. + // Solving for a zero shift with a zero line gap gives the ascender below. This design needs it + // because the em carries the 0.1 inch pitch while the ink stands taller than the pitch. + float inkTopUnits = inkTop * emScale; + short descender = (short)MathF.Round(inkBottom * emScale); + short ascender = (short)MathF.Round(MathF.Max(inkTopUnits, (2 * inkTopUnits) - descender - design.UnitsPerEm)); + TrueTypeWriter writer = new( + design.UnitsPerEm, + ascender, + descender, + (short)MathF.Round(capHeight * emScale), + design.FamilyName, + "Copyright (c) Six Labors. Licensed under the Six Labors Split License."); + + writer.AddGlyph(' ', [], advanceWidth); + foreach ((char glyphCharacter, List> glyphContours) in quantizedGlyphs.OrderBy(entry => entry.Key)) + { + writer.AddGlyph(glyphCharacter, glyphContours, advanceWidth); + } + + if (specFailures.Count > 0) + { + Console.WriteLine($"{design.Name} SPEC CHECK FAILED: {specFailures.Count} deviations"); + foreach (string failure in specFailures) + { + Console.WriteLine($" {failure}"); + } + } + else + { + Console.WriteLine($"{design.Name} SPEC CHECK PASSED"); + } + + // Normalizing the glyphs moves every extreme, so the expectations that record accepted ink are + // rewritten from the normalized outlines. The probe points are not rewritten: each was chosen by + // eye to sit inside or outside a particular sweep, so it takes the same scale as the outline it + // probes and keeps testing the same feature. + StringBuilder expectationRows = new(); + foreach (char expectedCharacter in auditOutlines.Keys.OrderBy(character => character)) + { + float expectedMinX = float.MaxValue; + float expectedMaxX = float.MinValue; + float expectedMinY = float.MaxValue; + float expectedMaxY = float.MinValue; + foreach (IReadOnlyList contour in auditOutlines[expectedCharacter]) + { + foreach (Vector2 point in contour) + { + expectedMinX = MathF.Min(expectedMinX, point.X); + expectedMaxX = MathF.Max(expectedMaxX, point.X); + expectedMinY = MathF.Min(expectedMinY, point.Y); + expectedMaxY = MathF.Max(expectedMaxY, point.Y); + } + } + + float expectedScale = glyphScales[expectedCharacter]; + float expectedCentre = design.W / 2; + (float[] Bounds, float[][] Inked, float[][] Blank) old = design.Expectations[expectedCharacter]; + string Probes(float[][] points) => $"[{string.Join(", ", points.Select(Probe))}]"; + string Probe(float[] point) => $"[{Number(expectedCentre + ((point[0] - expectedCentre) * expectedScale))}, {Number(point[1] * expectedScale)}]"; + string bounds = $"[{Number(expectedMinX)}, {Number(expectedMaxX)}, {Number(expectedMinY)}, {Number(expectedMaxY)}]"; + string key = $" [{Literal(expectedCharacter)}] = ("; + string row = $"{key}{bounds}, {Probes(old.Inked)}, {Probes(old.Blank)}),"; + + // The .editorconfig limits lines to 160 characters. A longer row puts each tuple element on its own line. + if (row.Length <= 160) + { + expectationRows.AppendLine(row); + } + else + { + expectationRows.AppendLine(key); + expectationRows.AppendLine(CultureInfo.InvariantCulture, $" {bounds},"); + expectationRows.AppendLine(CultureInfo.InvariantCulture, $" {Probes(old.Inked)},"); + expectationRows.AppendLine(CultureInfo.InvariantCulture, $" {Probes(old.Blank)}),"); + } + } + + string expectationPath = System.IO.Path.Combine(outputDirectory, $"{design.Name}-expectations.txt"); + File.WriteAllText(expectationPath, expectationRows.ToString()); + Console.WriteLine(expectationPath); + + static string Number(float value) + { + float rounded = MathF.Round(value, 1); + return rounded == MathF.Round(rounded) + ? ((int)rounded).ToString(CultureInfo.InvariantCulture) + : rounded.ToString("0.0#", CultureInfo.InvariantCulture) + "f"; + } + + static string Literal(char value) => value switch + { + '\'' => @"'\''", + '\\' => @"'\\'", + _ => $"'{value}'", + }; + + static GlyphClass Classify(char value) => value switch + { + // ECMA-11 section 10 and FIPS PUB 32 section 2.6 dimension these in millimetres rather than + // drawing them, so they carry their own absolute size. + '―' or '∣' or '█' or '|' => GlyphClass.Absolute, + >= '0' and <= '9' => GlyphClass.Digit, + 'b' or 'd' or 'f' or 'h' or 'i' or 'k' or 'l' or 't' => GlyphClass.Ascender, + 'g' or 'j' or 'p' or 'q' or 'y' => GlyphClass.Descender, + _ when char.IsLower(value) => GlyphClass.SmallLetter, + _ => GlyphClass.Capital, + }; + + byte[] font = writer.Write(); + string fontPath = System.IO.Path.Combine(outputDirectory, $"{design.Name}.ttf"); + File.WriteAllBytes(fontPath, font); + Console.WriteLine($"{fontPath}: {font.Length} bytes"); + + // Emit the font bytes as C# data for embedding in the library. + StringBuilder data = new(); + data.AppendLine("// Copyright (c) Six Labors."); + data.AppendLine("// Licensed under the Six Labors Split License."); + data.AppendLine(); + data.AppendLine("// "); + data.AppendLine("// Generated by ImageSharp.Drawing.FontGenerator. Do not edit."); + data.AppendLine("// "); + data.AppendLine(); + data.AppendLine("namespace SixLabors.ImageSharp.Drawing.Barcodes;"); + data.AppendLine(); + data.AppendLine("/// "); + data.AppendLine(CultureInfo.InvariantCulture, $"/// The {design.FamilyName} TrueType font file."); + data.AppendLine(CultureInfo.InvariantCulture, $"/// {design.DataSummary}"); + data.AppendLine("/// "); + data.AppendLine(CultureInfo.InvariantCulture, $"internal static class {design.Name}FontData"); + data.AppendLine("{"); + data.AppendLine(" /// "); + data.AppendLine(" /// Gets the font file bytes."); + data.AppendLine(" /// "); + data.AppendLine(" public static ReadOnlySpan Bytes =>"); + data.AppendLine(" ["); + for (int byteIndex = 0; byteIndex < font.Length; byteIndex += 16) + { + int lineEnd = Math.Min(byteIndex + 16, font.Length); + data.Append(" "); + for (int lineByte = byteIndex; lineByte < lineEnd; lineByte++) + { + data.Append("0x"); + data.Append(font[lineByte].ToString("X2", CultureInfo.InvariantCulture)); + data.Append(','); + if (lineByte + 1 < lineEnd) + { + data.Append(' '); + } + } + + data.AppendLine(); + } + + data.AppendLine(" ];"); + data.AppendLine("}"); + string dataPath = System.IO.Path.Combine(libraryFontDirectory, $"{design.Name}FontData.cs"); + File.WriteAllText(dataPath, data.ToString()); + Console.WriteLine(dataPath); + + // Proof sheet: load the generated font back and render the full set with the library itself. + using MemoryStream stream = new(font); + FontFamily family = collection.Add(stream); + families[design.Name] = family; + Font proofFont = family.CreateFont(100); + + string[] lines = + [ + .. design.Skeletons.Keys + .OrderBy(character => character) + .Select((character, index) => (character, index)) + .GroupBy(entry => entry.index / 17) + .Select(group => new string([.. group.Select(entry => entry.character)])), + ]; + + using Image proof = new(1800, 20 + (lines.Length * 160), Color.White.ToPixel()); + proof.Mutate(context => context.Paint(canvas => + { + for (int i = 0; i < lines.Length; i++) + { + canvas.DrawText( + new RichTextOptions(proofFont) + { + Origin = new PointF(20, 20 + (i * 160)), + }, + lines[i], + Brushes.Solid(Color.Black), + null); + } + })); + + string proofPath = System.IO.Path.Combine(outputDirectory, $"{design.Name}-proof.png"); + proof.SaveAsPng(proofPath); + Console.WriteLine(proofPath); + + // Large per-glyph grid for auditing each outline against its source drawing, every cell labelled + // with the character and code point it renders. + Font gridFont = family.CreateFont(200); + Font labelFont = SystemFonts.CreateFont("Arial", 18); + char[] auditCharacters = [.. design.Skeletons.Keys.OrderBy(character => character)]; + int columns = 10; + int rows = (auditCharacters.Length + columns - 1) / columns; + + using Image grid = new(columns * 240, rows * 360, Color.White.ToPixel()); + grid.Mutate(context => context.Paint(canvas => + { + for (int index = 0; index < auditCharacters.Length; index++) + { + int column = index % columns; + int row = index / columns; + canvas.DrawText( + new RichTextOptions(gridFont) + { + Origin = new PointF((column * 240) + 20, (row * 360) + 56), + }, + auditCharacters[index].ToString(), + Brushes.Solid(Color.Black), + null); + canvas.DrawText( + new RichTextOptions(labelFont) + { + Origin = new PointF((column * 240) + 20, (row * 360) + 8), + }, + $"U+{(int)auditCharacters[index]:X4} {auditCharacters[index]}", + Brushes.Solid(Color.Gray), + null); + } + })); + + string gridPath = System.IO.Path.Combine(outputDirectory, $"{design.Name}-grid.png"); + grid.SaveAsPng(gridPath); + Console.WriteLine(gridPath); +} + +// Reference comparison sheets: each glyph from a reference font on the left, ours on the right, +// baseline aligned so metric differences between the faces do not read as glyph offsets. +(string Name, string? Path)[] referenceJobs = [("OcrA", referencePath), ("OcrB", referenceBPath)]; +foreach ((string designName, string? jobPath) in referenceJobs) +{ + if (jobPath is null || !families.TryGetValue(designName, out FontFamily compareFamily)) + { + continue; + } + + FontDesign design = designs.First(entry => entry.Name == designName); + FontCollection referenceCollection = new(); + Font referenceFont = referenceCollection.Add(jobPath).CreateFont(200); + Font compareFont = compareFamily.CreateFont(200); + char[] compareCharacters = [.. design.Skeletons.Keys.OrderBy(c => c)]; + int pairColumns = 5; + int pairRows = (compareCharacters.Length + pairColumns - 1) / pairColumns; + using Image pairs = new(pairColumns * 520, pairRows * 320, Color.White.ToPixel()); + pairs.Mutate(context => context.Paint(canvas => + { + float referenceAscent = canvas.MeasureText(new RichTextOptions(referenceFont), "0").LineMetrics[0].Ascender; + float compareAscent = canvas.MeasureText(new RichTextOptions(compareFont), "0").LineMetrics[0].Ascender; + float baselineY = MathF.Max(referenceAscent, compareAscent) + 30; + for (int index = 0; index < compareCharacters.Length; index++) + { + int column = index % pairColumns; + int row = index / pairColumns; + string text = compareCharacters[index].ToString(); + canvas.DrawText( + new RichTextOptions(referenceFont) + { + Origin = new PointF((column * 520) + 20, (row * 320) + baselineY - referenceAscent), + }, + text, + Brushes.Solid(Color.ParseHex("007700")), + null); + canvas.DrawText( + new RichTextOptions(compareFont) + { + Origin = new PointF((column * 520) + 250, (row * 320) + baselineY - compareAscent), + }, + text, + Brushes.Solid(Color.Black), + null); + } + })); + + string referenceStem = System.IO.Path.GetFileNameWithoutExtension(jobPath).ToLowerInvariant(); + string pairsPath = System.IO.Path.Combine(outputDirectory, $"{designName}-vs-{referenceStem}.png"); + pairs.SaveAsPng(pairsPath); + Console.WriteLine(pairsPath); +} + +// Full-size single glyph renders for inspection, requested as extra arguments. +foreach ((string designName, FontFamily inspectFamily) in families) +{ + Font inspectFont = inspectFamily.CreateFont(700); + foreach (string text in inspectTexts) + { + using Image single = new(900, 1100, Color.White.ToPixel()); + single.Mutate(context => context.Paint(canvas => canvas.DrawText( + new RichTextOptions(inspectFont) + { + Origin = new PointF(100, 100), + }, + text, + Brushes.Solid(Color.Black), + null))); + + string singlePath = System.IO.Path.Combine(outputDirectory, $"inspect-{designName}-{(int)text[0]}.png"); + single.SaveAsPng(singlePath); + Console.WriteLine(singlePath); + } +} + +return 0; + +// Adds one stroke's ink to the piece list with its terminals cut on axis-aligned planes, the way the +// drawings print them: feet sit exactly on the baseline and diagonal arms end on vertical cuts. The +// stroke takes butt caps, then every open end whose projection is not buried inside another stroke +// gains a filled patch running from its butt face out to the cut plane, half a stroke width from the +// endpoint. A buried end stays butt so nothing pokes through the partner stroke. probeInk carries +// butt-capped ink for every stroke of the glyph, the geometry a terminal is tested against, and +// strokeEndpoints every open endpoint paired with its stroke index. +static void CutTerminalStroke( + PointF[] points, + IPath centerline, + float width, + int strokeIndex, + IPath[] probeInk, + List<(int Stroke, Vector2 Point)> strokeEndpoints, + StrokeOptions buttStroke, + List pieces) +{ + float half = width / 2; + IPath body = centerline.GenerateOutline(width, buttStroke); + + pieces.Add(body); + + // Nonzero fill cancels where windings oppose, so every patch must turn the same way as the + // stroke outline it overlaps. + float bodyWinding = 0; + foreach (ISimplePath simple in body.Flatten()) + { + float area = SignedArea(simple.Points.Span); + if (MathF.Abs(area) > MathF.Abs(bodyWinding)) + { + bodyWinding = area; + } + } + + Vector2[] pts = [.. points.Select(p => (Vector2)p)]; + List<(bool Horizontal, float Plane, Vector2 P, Vector2 Tangent)> cutsWanted = []; + for (int end = 0; end < 2; end++) + { + Vector2 p = end == 0 ? pts[0] : pts[^1]; + Vector2 inner = p; + for (int i = 1; i < pts.Length; i++) + { + inner = end == 0 ? pts[i] : pts[^(i + 1)]; + if ((inner - p).LengthSquared() > 0.01F) + { + break; + } + } + + Vector2 tangent = Vector2.Normalize(p - inner); + Vector2 probe = p + (tangent * (half / 2)); + bool buried = false; + for (int other = 0; other < probeInk.Length; other++) + { + if (other != strokeIndex && probeInk[other].Contains(probe, IntersectionRule.NonZero, Vector2.One)) + { + buried = true; + break; + } + } + + if (buried) + { + // Two butt faces meeting at a shared junction point leave an unfilled wedge, so such + // ends run a short way past the point. An end buried in a partner's body stays exactly + // butt: any overrun there pokes into the partner's counter. + bool meetsAnotherEnd = false; + foreach ((int otherStroke, Vector2 otherPoint) in strokeEndpoints) + { + if (otherStroke != strokeIndex && (otherPoint - p).Length() < half) + { + meetsAnotherEnd = true; + break; + } + } + + if (meetsAnotherEnd) + { + Vector2 tucked = p + (tangent * (half * 0.6F)); + if (end == 0) + { + pts[0] = tucked; + } + else + { + pts[^1] = tucked; + } + } + + continue; + } + + // Two open ends meeting tip-to-tip form a point, as in the curly bracket middles: both run + // out along their tangents and the union forms the point, with no cut plane. + bool meetsEnd = false; + foreach ((int otherStroke, Vector2 otherPoint) in strokeEndpoints) + { + if (otherStroke != strokeIndex && (otherPoint - p).Length() < width) + { + meetsEnd = true; + break; + } + } + + if (meetsEnd) + { + Vector2 pointward = p + (tangent * (half * 0.9F)); + if (end == 0) + { + pts[0] = pointward; + } + else + { + pts[^1] = pointward; + } + + continue; + } + + // A curved terminal ends on a face perpendicular to the stroke, extended half a stroke + // width; only straight-segment terminals take the axis-aligned cuts of the drawings. + Vector2 back = p; + float walked = 0; + int step = 1; + while (step < pts.Length && walked < width * 1.2F) + { + Vector2 next = end == 0 ? pts[step] : pts[^(step + 1)]; + walked += (next - back).Length(); + back = next; + step++; + } + + Vector2 chord = Vector2.Normalize(p - back); + bool curvedEnd = Vector2.Dot(chord, tangent) < 0.985F; + if (curvedEnd) + { + Vector2 extended = p + (tangent * (half * 0.9F)); + if (end == 0) + { + pts[0] = extended; + } + else + { + pts[^1] = extended; + } + + continue; + } + + // The drawings cut a straight terminal on the reference line its ink abuts: the baseline, + // the cap line or the x-height line for steep strokes, a vertical side plane otherwise. + bool meetsLine = MathF.Abs(tangent.Y) >= 0.55F + && (p.Y - half <= 35 || p.Y + half >= 905 || MathF.Abs(p.Y + half - 741) <= 40); + bool horizontalCut = meetsLine || MathF.Abs(tangent.X) < 0.5F * MathF.Abs(tangent.Y); + float plane = horizontalCut + ? p.Y + (half * MathF.Sign(tangent.Y)) + : p.X + (half * MathF.Sign(tangent.X)); + cutsWanted.Add((horizontalCut, plane, p, tangent)); + } + + // Both arms of a chevron cut on the same axis toward the same side end flush on a single line + // in the drawings, so their planes unify to the outermost one. + if (cutsWanted.Count == 2 && cutsWanted[0].Horizontal == cutsWanted[1].Horizontal + && MathF.Abs(cutsWanted[0].Plane - cutsWanted[1].Plane) <= half) + { + float d0 = cutsWanted[0].Horizontal ? cutsWanted[0].Tangent.Y : cutsWanted[0].Tangent.X; + float d1 = cutsWanted[1].Horizontal ? cutsWanted[1].Tangent.Y : cutsWanted[1].Tangent.X; + if (MathF.Sign(d0) == MathF.Sign(d1)) + { + float unified = d0 > 0 + ? MathF.Max(cutsWanted[0].Plane, cutsWanted[1].Plane) + : MathF.Min(cutsWanted[0].Plane, cutsWanted[1].Plane); + + cutsWanted[0] = (cutsWanted[0].Horizontal, unified, cutsWanted[0].P, cutsWanted[0].Tangent); + cutsWanted[1] = (cutsWanted[1].Horizontal, unified, cutsWanted[1].P, cutsWanted[1].Tangent); + } + } + + foreach ((bool horizontalCut, float plane, Vector2 p, Vector2 tangent) in cutsWanted) + { + // The patch roots half a stroke inside the body and runs a hair narrower than the stroke: + // exactly coincident edges are the one geometry the polygon clipper cannot union reliably. + Vector2 normal = new(-tangent.Y, tangent.X); + Vector2 inside = p - (tangent * (half * 0.5F)); + Vector2 side = normal * (half - 0.02F); + Vector2 c1 = inside + side; + Vector2 c2 = inside - side; + float t1 = horizontalCut ? (plane - c1.Y) / tangent.Y : (plane - c1.X) / tangent.X; + float t2 = horizontalCut ? (plane - c2.Y) / tangent.Y : (plane - c2.X) / tangent.X; + Vector2 c1Out = c1 + (tangent * Math.Clamp(t1, 0, half * 2.5F)); + Vector2 c2Out = c2 + (tangent * Math.Clamp(t2, 0, half * 2.5F)); + + PointF[] quad = [(PointF)c1, (PointF)c1Out, (PointF)c2Out, (PointF)c2]; + if (SignedArea(quad) * bodyWinding < 0) + { + Array.Reverse(quad); + } + + pieces.Add(new Polygon(new LinearLineSegment(quad))); + } +} + +// Returns the signed area of a closed point loop. The sign carries the winding direction, which +// nonzero fill depends on: a patch must turn the same way as the outline it overlaps. +static float SignedArea(ReadOnlySpan points) +{ + float area = 0; + for (int i = 0; i < points.Length; i++) + { + PointF a = points[i]; + PointF b = points[(i + 1) % points.Length]; + area += (a.X * b.Y) - (b.X * a.Y); + } + + return area / 2; +} + +// Converts one skeleton stroke into path segments. The token stream is a leading point, then line +// points, arcs (NaN, radius, end), corners (NaN, NaN, radius, vertex), splines +// (NaN, NaN, NaN, count, then count points) and cubic Bezier segments +// (NaN, NaN, NaN, NaN, control1, control2, end). Corners resolve against the neighbouring anchor +// points: a positive radius rounds the corner tangentially, a negative radius produces an arc through +// the vertex itself so a dimensioned pointed extreme keeps its position. A spline runs a +// tangent-continuous curve from the current point through every listed anchor, and cubic segments +// carry exact control points, the form the OCR-B outlines use. +static void BuildStroke(PathBuilder builder, float[] stroke) +{ + List<(int Kind, float Radius, Vector2 Point, Vector2[]? Anchors)> elements = []; + int i = 0; + while (i < stroke.Length) + { + if (float.IsNaN(stroke[i])) + { + if (float.IsNaN(stroke[i + 1]) && float.IsNaN(stroke[i + 2]) && float.IsNaN(stroke[i + 3])) + { + Vector2[] controls = + [ + new Vector2(stroke[i + 4], stroke[i + 5]), + new Vector2(stroke[i + 6], stroke[i + 7]), + ]; + elements.Add((4, 0, new Vector2(stroke[i + 8], stroke[i + 9]), controls)); + i += 10; + } + else if (float.IsNaN(stroke[i + 1]) && float.IsNaN(stroke[i + 2])) + { + int count = (int)stroke[i + 3]; + Vector2[] anchors = new Vector2[count]; + for (int a = 0; a < count; a++) + { + anchors[a] = new Vector2(stroke[i + 4 + (a * 2)], stroke[i + 5 + (a * 2)]); + } + + elements.Add((3, 0, anchors[^1], anchors)); + i += 4 + (count * 2); + } + else if (float.IsNaN(stroke[i + 1])) + { + elements.Add((2, stroke[i + 2], new Vector2(stroke[i + 3], stroke[i + 4]), null)); + i += 5; + } + else + { + elements.Add((1, stroke[i + 1], new Vector2(stroke[i + 2], stroke[i + 3]), null)); + i += 4; + } + } + else + { + elements.Add((0, 0, new Vector2(stroke[i], stroke[i + 1]), null)); + i += 2; + } + } + + bool closed = elements.Count > 2 && elements[0].Kind == 0 && elements[^1].Kind != 2 && + elements[0].Point == elements[^1].Point; + + Vector2 current = elements[0].Point; + builder.MoveTo(current); + for (int k = 1; k < elements.Count; k++) + { + (int kind, float radius, Vector2 point, Vector2[]? anchors) = elements[k]; + if (kind == 0) + { + builder.LineTo(point); + current = point; + } + else if (kind == 1) + { + AddArc(builder, current, point, MathF.Abs(radius), radius > 0); + current = point; + } + else if (kind == 3) + { + AddSpline(builder, current, anchors!); + current = point; + } + else if (kind == 4) + { + AddCubic(builder, current, anchors![0], anchors[1], point); + current = point; + } + else + { + Vector2 next = elements[k + 1].Point; + Vector2 u1 = Vector2.Normalize(point - current); + Vector2 u2 = Vector2.Normalize(next - point); + float cross = (u1.X * u2.Y) - (u1.Y * u2.X); + float dot = Vector2.Dot(u1, u2); + float r = MathF.Abs(radius); + + float tangent = r * MathF.Abs(cross) / (1 + dot); + Vector2 start = point - (u1 * tangent); + Vector2 end = point + (u2 * tangent); + + builder.LineTo(start); + AddArc(builder, start, end, r, cross > 0); + current = end; + } + } + + if (closed) + { + builder.CloseFigure(); + } +} + +// Samples a C2 cubic spline from the current point through the anchors, parameterized by chord +// length: a natural spline for open runs, which gives zero end curvature, and a periodic spline when +// the curve closes on its start. The anchor sets were authored for smooth interpolation, so a merely +// tangent-continuous curve through them reads as lumpy, and C2 continuity restores the drawn shape. +static void AddSpline(PathBuilder builder, Vector2 current, Vector2[] anchors) +{ + Vector2[] points = new Vector2[anchors.Length + 1]; + points[0] = current; + anchors.CopyTo(points, 1); + bool cyclic = points[0] == points[^1] && points.Length > 3; + int knotCount = cyclic ? points.Length - 1 : points.Length; + + float[] chords = new float[points.Length - 1]; + for (int i = 0; i < chords.Length; i++) + { + chords[i] = MathF.Max(Vector2.Distance(points[i], points[i + 1]), 1e-3f); + } + + // Solve for the second derivative M at every knot, per coordinate, with a dense Gaussian solve: + // the systems are tiny. Row i couples M[i-1], M[i], M[i+1] with the standard cubic spline + // continuity equation; open ends pin M to zero, cyclic rows wrap. + Vector2[] m = new Vector2[knotCount]; + int unknowns = cyclic ? knotCount : knotCount - 2; + if (unknowns > 0) + { + float[,] matrix = new float[unknowns, unknowns]; + Vector2[] rhs = new Vector2[unknowns]; + for (int row = 0; row < unknowns; row++) + { + int i = cyclic ? row : row + 1; + float hPrev = chords[cyclic ? ((i - 1) + chords.Length) % chords.Length : i - 1]; + float hNext = chords[i % chords.Length]; + Vector2 prev = points[cyclic ? ((i - 1) + knotCount) % knotCount : i - 1]; + Vector2 here = points[i]; + Vector2 next = points[cyclic ? (i + 1) % knotCount : i + 1]; + + void Add(int knot, float value) + { + int column = cyclic ? ((knot % knotCount) + knotCount) % knotCount : knot - 1; + if (column >= 0 && column < unknowns) + { + matrix[row, column] += value; + } + } + + Add(i - 1, hPrev); + Add(i, 2 * (hPrev + hNext)); + Add(i + 1, hNext); + rhs[row] = 6 * (((next - here) / hNext) - ((here - prev) / hPrev)); + } + + for (int pivot = 0; pivot < unknowns; pivot++) + { + int best = pivot; + for (int row = pivot + 1; row < unknowns; row++) + { + if (MathF.Abs(matrix[row, pivot]) > MathF.Abs(matrix[best, pivot])) + { + best = row; + } + } + + if (best != pivot) + { + for (int column = 0; column < unknowns; column++) + { + (matrix[pivot, column], matrix[best, column]) = (matrix[best, column], matrix[pivot, column]); + } + + (rhs[pivot], rhs[best]) = (rhs[best], rhs[pivot]); + } + + float diagonal = matrix[pivot, pivot]; + for (int row = pivot + 1; row < unknowns; row++) + { + float factor = matrix[row, pivot] / diagonal; + if (factor == 0) + { + continue; + } + + for (int column = pivot; column < unknowns; column++) + { + matrix[row, column] -= factor * matrix[pivot, column]; + } + + rhs[row] -= factor * rhs[pivot]; + } + } + + Vector2[] solution = new Vector2[unknowns]; + for (int row = unknowns - 1; row >= 0; row--) + { + Vector2 sum = rhs[row]; + for (int column = row + 1; column < unknowns; column++) + { + sum -= matrix[row, column] * solution[column]; + } + + solution[row] = sum / matrix[row, row]; + } + + for (int row = 0; row < unknowns; row++) + { + m[cyclic ? row : row + 1] = solution[row]; + } + } + + for (int segment = 0; segment < points.Length - 1; segment++) + { + float h = chords[segment]; + Vector2 p1 = points[segment]; + Vector2 p2 = points[segment + 1]; + Vector2 m1 = m[segment % knotCount]; + Vector2 m2 = m[(segment + 1) % knotCount]; + int steps = Math.Max(4, (int)MathF.Ceiling(h / 8)); + for (int step = 1; step <= steps; step++) + { + float t = h * step / steps; + float a = (h - t) / h; + float b = t / h; + Vector2 value = (a * p1) + (b * p2) + + ((h * h / 6) * ((((a * a * a) - a) * m1) + (((b * b * b) - b) * m2))); + builder.LineTo(value); + } + } +} + +// Removes flattening points that deviate from a straight run by less than the tolerance, keeping the +// outline within a fraction of a design unit of the dense curve. +static Vector2[] SimplifyContour(Vector2[] points, float tolerance) +{ + if (points.Length < 4) + { + return points; + } + + bool[] keep = new bool[points.Length]; + keep[0] = keep[^1] = true; + Simplify(0, points.Length - 1); + return [.. points.Where((_, index) => keep[index])]; + + void Simplify(int first, int last) + { + if (last <= first + 1) + { + return; + } + + Vector2 span = points[last] - points[first]; + float norm = span.Length(); + float worst = -1; + int peak = first; + for (int i = first + 1; i < last; i++) + { + Vector2 offset = points[i] - points[first]; + float distance = norm < 1e-6f + ? offset.Length() + : MathF.Abs((span.X * offset.Y) - (span.Y * offset.X)) / norm; + if (distance > worst) + { + worst = distance; + peak = i; + } + } + + if (worst > tolerance) + { + keep[peak] = true; + Simplify(first, peak); + Simplify(peak, last); + } + } +} + +// Samples one cubic Bezier segment with exact control points. +static void AddCubic(PathBuilder builder, Vector2 from, Vector2 control1, Vector2 control2, Vector2 to) +{ + float chord = Vector2.Distance(from, to) + + Vector2.Distance(from, control1) + Vector2.Distance(control2, to); + int steps = Math.Max(4, (int)MathF.Ceiling(chord / 8)); + for (int step = 1; step <= steps; step++) + { + float t = (float)step / steps; + float u = 1 - t; + builder.LineTo( + (u * u * u * from) + (3 * u * u * t * control1) + (3 * u * t * t * control2) + (t * t * t * to)); + } +} + +// Samples a circular arc of the given radius between two points. The centre sits on whichever side of +// the chord the requested direction demands, so the arc keeps both endpoints exactly. +static void AddArc(PathBuilder builder, Vector2 from, Vector2 to, float radius, bool counterClockwise) +{ + Vector2 chord = to - from; + float chordLength = chord.Length(); + Vector2 mid = (from + to) / 2; + float height = MathF.Sqrt(MathF.Max(0, (radius * radius) - (chordLength * chordLength / 4))); + Vector2 normal = Vector2.Normalize(new Vector2(-chord.Y, chord.X)); + Vector2 center = counterClockwise ? mid + (normal * height) : mid - (normal * height); + + float startAngle = MathF.Atan2(from.Y - center.Y, from.X - center.X); + float endAngle = MathF.Atan2(to.Y - center.Y, to.X - center.X); + float sweep = endAngle - startAngle; + if (counterClockwise && sweep < 0) + { + sweep += 2 * MathF.PI; + } + else if (!counterClockwise && sweep > 0) + { + sweep -= 2 * MathF.PI; + } + + int steps = Math.Max(4, (int)MathF.Ceiling(MathF.Abs(sweep) / (2 * MathF.Sqrt(0.2f / radius)))); + for (int step = 1; step <= steps; step++) + { + float angle = startAngle + (sweep * step / steps); + builder.LineTo(center + (radius * new Vector2(MathF.Cos(angle), MathF.Sin(angle)))); + } +} diff --git a/src/ImageSharp.Drawing.FontGenerator/RepositoryPaths.cs b/src/ImageSharp.Drawing.FontGenerator/RepositoryPaths.cs new file mode 100644 index 00000000..5fd8f57d --- /dev/null +++ b/src/ImageSharp.Drawing.FontGenerator/RepositoryPaths.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.FontGenerator; + +/// +/// Resolves the directories the generator writes to. The generator takes no output path: the font data +/// is source, so it is written where the library compiles it from, and the sheets are written to the +/// repository artifacts directory. Both are found by walking up from the running assembly to the +/// solution file. +/// +internal static class RepositoryPaths +{ + private const string SolutionFileName = "ImageSharp.Drawing.slnx"; + private const string LibraryFontsRelativePath = @"src\ImageSharp.Drawing\Barcodes\Fonts"; + private const string ArtifactsRelativePath = @"artifacts\fonts"; + + private static readonly Lazy SolutionDirectoryFullPathLazy = new(GetSolutionDirectoryFullPathImpl); + + /// + /// Gets the directory the library compiles the generated font data from. + /// + public static string LibraryFonts => GetFullPath(LibraryFontsRelativePath); + + /// + /// Gets the directory the fonts and the proof, grid and comparison sheets are written to. + /// + public static string Artifacts => GetFullPath(ArtifactsRelativePath); + + private static string SolutionDirectoryFullPath => SolutionDirectoryFullPathLazy.Value; + + private static string GetSolutionDirectoryFullPathImpl() + { + string assemblyLocation = AppContext.BaseDirectory; + DirectoryInfo? directory = new FileInfo(assemblyLocation).Directory + ?? throw new IOException($"Unable to find the SixLabors solution directory from {assemblyLocation}!"); + + while (!directory.EnumerateFiles(SolutionFileName).Any()) + { + directory = directory.Parent; + if (directory is null) + { + throw new IOException($"Unable to find the SixLabors solution directory from {assemblyLocation}!"); + } + } + + return directory.FullName; + } + + private static string GetFullPath(string relativePath) + => System.IO.Path.Combine(SolutionDirectoryFullPath, relativePath) + .Replace('\\', System.IO.Path.DirectorySeparatorChar); +} diff --git a/src/ImageSharp.Drawing.FontGenerator/SpecChecks.cs b/src/ImageSharp.Drawing.FontGenerator/SpecChecks.cs new file mode 100644 index 00000000..6510de03 --- /dev/null +++ b/src/ImageSharp.Drawing.FontGenerator/SpecChecks.cs @@ -0,0 +1,482 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; + +namespace SixLabors.ImageSharp.Drawing.FontGenerator; + +/// +/// The regression gate for the generated outlines, run on every build. Each glyph asserts its exact +/// ink bounding box (the drawings dimension every extreme: the reference lines plus the half-stroke +/// cap projection, and per-glyph values such as the -13.8 tail of the OCR-A t) plus probe points that +/// must be inside or outside the ink, catching arc sweep and construction errors the bounding box +/// cannot see. A failure means an edit moved accepted ink: an extreme drifted, a counter filled in, or +/// a stroke vanished. The expectations were transcribed from the same drawings as the skeletons, so +/// they cannot catch a misreading of the drawings themselves; full-size visual review against the +/// figures remains the acceptance gate for shape. +/// +internal static class SpecChecks +{ + /// + /// The permitted difference in design units between an expected and a measured ink extreme. Arc + /// flattening and the sub-quantum stroke width perturbation that keeps union edges apart move the + /// measured extremes by fractions of a unit; the drawings dimension nothing finer than whole units. + /// + private const float BoundsTolerance = 4; + + /// + /// Gets the OCR-B per-glyph expectations, in the format of . ECMA-11 prints + /// OCR-B as a character sheet rather than as dimensioned drawings, so unlike the OCR-A rows these + /// values are not transcribed numbers: they record the ink of the outlines that passed review + /// against that sheet. They therefore catch an edit that moves accepted ink, which is what a + /// regression gate is for, but they cannot judge whether a shape matches the standard. That + /// judgement stays with the printed sheet and the generated proof and grid images. + /// + public static IReadOnlyDictionary OcrB { get; } = + new Dictionary + { + ['!'] = ([196.6f, 364.4f, 5.5f, 968.5f], [[286, 891.8f], [223.3f, 30.9f], [320.4f, 456.7f], [242.1f, 669.5f]], []), + ['"'] = ([24.5f, 525.5f, 595.7f, 968.5f], [[96.1f, 896.8f], [460.4f, 621.9f], [415.7f, 941.5f], [51.4f, 621.9f]], []), + ['#'] = ( + [-11.1f, 561.1f, 0, 968.5f], + [[96.1f, 306.8f], [527.1f, 940], [165.8f, 721.2f], [503.9f, 336.6f]], + [[278.5f, 499], [195.6f, 389.6f], [311.6f, 389.6f]]), + ['$'] = ( + [-34.2f, 584.2f, 0, 968.5f], + [[269.9f, 129.1f], [300.2f, 940.4f], [81.3f, 543.2f], [468.5f, 455.6f]], + [[158.8f, 708.1f], [377.6f, 223.4f], [354, 371.5f]]), + ['%'] = ( + [-17.2f, 573.9f, -12, 968.5f], + [[332.7f, 568.1f], [18.1f, 26.9f], [535.6f, 87.8f], [55.3f, 899.6f]], + [[123, 815], [440.9f, 104.7f], [427.4f, 202.8f]]), + ['&'] = ( + [-34.4f, 574.4f, -11.8f, 968.5f], + [[174.3f, 489.1f], [523.2f, 25], [296.1f, 936.7f], [85.4f, 57.9f]], + [[220.4f, 745.8f], [276.3f, 130.3f], [161.1f, 357.4f]]), + ['\''] = ([185.8f, 364.2f, 517.1f, 968.5f], [[276.4f, 884.8f], [236.9f, 542.6f], [322.5f, 707.1f], [223.8f, 776.2f]], []), + ['('] = ([91.2f, 442.1f, 0, 968.5f], [[159.3f, 542.8f], [416, 25.9f], [416, 939.6f], [186, 232.7f]], []), + [')'] = ([104.5f, 452.1f, 0, 968.5f], [[386.5f, 536.1f], [133.1f, 25.9f], [133.1f, 939.6f], [359.9f, 232.7f]], []), + ['*'] = ([-69.2f, 619.2f, 129, 817.5f], [[271.7f, 457.6f], [312.3f, 786], [493.1f, 217.8f], [120.4f, 173.5f]], []), + ['+'] = ([-37, 587, 113.8f, 863.9f], [[274.2f, 507.4f], [241, 142.1f], [241, 832.1f], [558.3f, 555.4f]], []), + [','] = ([24.2f, 538.7f, -249.2f, 254.9f], [[365.2f, 135], [77.4f, -219.2f], [280.3f, -90], [509.1f, 223.6f]], []), + ['-'] = ([-41.1f, 591.1f, 502.3f, 681.9f], [[48.9f, 591.2f], [561.8f, 650.2f], [314.6f, 532.1f], [200.2f, 650.2f]], []), + ['.'] = ([131.5f, 418.6f, 0, 248.4f], [[255.1f, 124.9f], [387.9f, 29], [387.9f, 217.1f], [162.8f, 29]], []), + ['/'] = ([32.4f, 522.5f, 0, 968.5f], [[435.4f, 902.2f], [72.5f, 27.4f], [292.8f, 448.6f], [322, 688.3f]], []), + ['0'] = ( + [-34.5f, 591.2f, -11.8f, 968.5f], + [[31.5f, 568.6f], [467.6f, 57.5f], [515.3f, 841.2f], [34.9f, 132.5f]], + [[276.8f, 623.1f], [300.6f, 129], [126.9f, 367.6f]]), + ['1'] = ([92.7f, 441, 0, 968.5f], [[356.8f, 881.6f], [414.7f, 26.8f], [343.2f, 452.5f], [121.8f, 673.8f]], []), + ['2'] = ([-2.2f, 542, 0, 968.5f], [[70, 78.4f], [378.4f, 919.7f], [348.2f, 453.8f], [505.8f, 28.2f]], []), + ['3'] = ([-13.6f, 570.4f, -11.7f, 968.5f], [[446.1f, 878.6f], [15.2f, 64.2f], [544.5f, 305.1f], [164.5f, 535.9f]], []), + ['4'] = ([-37.6f, 570.2f, 0, 968.5f], [[386.4f, 276.5f], [257.3f, 939.3f], [86.4f, 559.1f], [-7.8f, 241.6f]], []), + ['5'] = ([13.7f, 536.2f, -12, 968.5f], [[93.2f, 600.3f], [42.3f, 16.7f], [503.8f, 274.6f], [483.4f, 939.7f]], []), + ['6'] = ( + [-35.4f, 585.3f, -12, 968.5f], + [[110.5f, 542.1f], [450.6f, 70.7f], [420.3f, 939.4f], [39.8f, 131.3f]], + [[275.5f, 309.8f], [289, 131.3f], [282.2f, 474.7f]]), + ['7'] = ([-28.8f, 581.9f, 0, 968.5f], [[496.6f, 885.1f], [131.6f, 26.9f], [-0.1f, 939.2f], [226.3f, 493.2f]], []), + ['8'] = ( + [-35.1f, 578.4f, -11.8f, 968.5f], + [[310.3f, 532], [135.2f, 37], [539.2f, 171.7f], [152, 922.6f]], + [[269.9f, 272.7f], [290.1f, 818.2f], [259.8f, 629.7f]]), + ['9'] = ( + [-34, 583.8f, 0, 968.5f], + [[434.2f, 423.8f], [122.4f, 903.2f], [119, 28.2f], [31.8f, 500.9f]], + [[276.6f, 655.1f], [206.2f, 806], [370.5f, 789.2f]]), + [':'] = ([135.3f, 409.8f, 0, 754.5f], [[259, 638.4f], [377, 29.6f], [166.7f, 199.3f], [340.1f, 199.3f]], []), + [';'] = ([16.5f, 525.2f, -251.5f, 728.3f], [[357.5f, 616.4f], [66, -221.1f], [420.2f, 125.7f], [176.7f, 22.4f]], []), + ['<'] = ([-38.3f, 565.2f, 92.7f, 968.5f], [[71.6f, 530.8f], [536.8f, 144.8f], [536.8f, 913.5f], [332.2f, 372.5f]], []), + ['='] = ([-33.1f, 583.1f, 274, 717.2f], [[32.1f, 652.9f], [552.4f, 302.4f], [434.3f, 686.1f], [172.3f, 302.4f]], []), + ['>'] = ([-8.2f, 588, 92.7f, 968.5f], [[477.3f, 529.4f], [20.2f, 913.6f], [20.2f, 145.2f], [218.9f, 373.8f]], []), + ['?'] = ([19.1f, 537.6f, 0, 968.5f], [[289.7f, 91.6f], [265.3f, 939.2f], [415.3f, 520.7f], [49.1f, 688.1f]], []), + ['@'] = ( + [-36.6f, 586.7f, -12.9f, 968.5f], + [[289.6f, 462.5f], [127.3f, 916.8f], [73.2f, 33.4f], [524, 76.6f]], + [[163.4f, 350.7f], [145.4f, 116.3f], [221.1f, 228.1f]]), + ['A'] = ( + [-70.6f, 620.6f, 0, 991.5f], + [[440.1f, 358.8f], [207.7f, 960.2f], [-28.5f, 30.4f], [108, 572.8f]], + [[274.1f, 499], [274.1f, 713], [248.3f, 606]]), + ['B'] = ( + [-56.4f, 613, 0, 975], + [[26.2f, 508.1f], [542.8f, 142.9f], [528, 829.1f], [-25.4f, 28.5f]], + [[225.5f, 286.8f], [103.7f, 810.7f], [413.6f, 663.1f]]), + ['C'] = ([-6.7f, 548.9f, -13.3f, 1011], [[304.9f, 57], [286.4f, 979.4f], [24.5f, 514.5f], [65.1f, 223]], []), + ['D'] = ( + [-34, 562.2f, 0, 979.5f], + [[45.2f, 85.3f], [-10.1f, 948.7f], [524.9f, 550.2f], [85.8f, 520.7f]], + [[266.6f, 491.2f], [148.5f, 170.2f], [148.5f, 808.5f]]), + ['E'] = ([-33.2f, 558.2f, 0, 991.1f], [[56.7f, 517.5f], [528.9f, 30.4f], [510.5f, 960.2f], [-2.4f, 30.4f]], []), + ['F'] = ([16.8f, 516.6f, 0, 999.4f], [[106.6f, 521.4f], [486.6f, 967.9f], [47.5f, 30.7f], [47.5f, 956.8f]], []), + ['G'] = ([-41.1f, 598.7f, -13.3f, 1011.2f], [[513.8f, 451.8f], [144.9f, 927.7f], [96.9f, 108.7f], [-10.1f, 525.6f]], []), + ['H'] = ([-39, 589, 0, 968.4f], [[497.9f, 505.5f], [-7.5f, 29.5f], [-7.5f, 937.1f], [560.7f, 29.5f]], []), + ['I'] = ([36.8f, 494.8f, 0, 991.1f], [[263.7f, 904.9f], [466.6f, 30.4f], [226.8f, 436.3f], [68.1f, 67.3f]], []), + ['J'] = ([44.8f, 530.4f, -13, 983.2f], [[459.5f, 910.7f], [238.1f, 21.5f], [500.1f, 427.3f], [75.8f, 290.8f]], []), + ['K'] = ([-86.7f, 648.5f, 0, 984.3f], [[4.1f, 513.5f], [579.7f, 30.2f], [550.1f, 952.5f], [-54.9f, 30.2f]], []), + ['L'] = ([-39.2f, 569.1f, 0, 979.8f], [[44.1f, 81.6f], [-7.5f, 948.7f], [538.5f, 30], [70, 517]], []), + ['M'] = ([-60.6f, 623.5f, 0, 972], [[280.6f, 498.3f], [594.2f, 29.7f], [-29.3f, 29.7f], [594.2f, 941]], []), + ['N'] = ([-43.1f, 593.1f, 0, 976.3f], [[483.1f, 162.6f], [-11.3f, 944.8f], [564.3f, 764], [-11.3f, 395.1f]], []), + ['O'] = ( + [-60.2f, 617.5f, -13.2f, 1015.5f], + [[542.6f, 582.4f], [125.7f, 73.2f], [-18.2f, 637.7f], [299.1f, 984.6f]], + [[280.6f, 519.7f], [265.9f, 147], [262.2f, 859.1f]]), + ['P'] = ( + [-63, 589.8f, 0, 980.1f], + [[27.2f, 469], [514.2f, 852.7f], [-31.9f, 948.7f], [-31.9f, 30]], + [[241.1f, 686.7f], [410.9f, 749.4f], [119.4f, 808.5f]]), + ['Q'] = ( + [-88.8f, 658.3f, 0, 999.7f], + [[407.5f, 248.4f], [127, 949.4f], [-57.4f, 469.8f], [507.1f, 694.8f]], + [[230.4f, 639.5f], [237.7f, 204.1f], [381.6f, 425.5f]]), + ['R'] = ( + [-62.2f, 603.6f, 0, 991.5f], + [[32.1f, 480.6f], [552.4f, 30.4f], [449.1f, 882.7f], [-30.6f, 960.2f]], + [[246.1f, 701.9f], [128.1f, 576.5f], [128.1f, 823.7f]]), + ['S'] = ([-26.7f, 569.3f, -13.4f, 1019.2f], [[59.3f, 184.1f], [384, 970], [516.8f, 383.3f], [44.6f, 649]], []), + ['T'] = ([-45.1f, 595.1f, 0, 1007.8f], [[273.9f, 917.9f], [233.3f, 28.7f], [314.5f, 471.4f], [565.4f, 976.9f]], []), + ['U'] = ([-63.7f, 600.8f, -13.3f, 1002.9f], [[12.4f, 927.5f], [407.2f, 38.3f], [569.5f, 643.4f], [-31.9f, 374]], []), + ['V'] = ([-69, 619.1f, 0, 976.3f], [[242.2f, 77.8f], [577.9f, 944.8f], [42.9f, 653.3f], [463.5f, 483.6f]], []), + ['W'] = ([-70.3f, 627.7f, 0, 988.2f], [[440.1f, 152], [-39.5f, 956.3f], [595.1f, 790.3f], [-6.3f, 428.7f]], []), + ['X'] = ([-77.2f, 608.7f, 0, 976.3f], [[271.4f, 546.3f], [-27.4f, 29.8f], [559.2f, 29.8f], [-23.8f, 944.8f]], []), + ['Y'] = ([-49.1f, 599.1f, 0, 988.2f], [[277.4f, 532], [0.7f, 956.3f], [550.4f, 956.3f], [236.8f, 30.3f]], []), + ['Z'] = ([-9, 558.9f, 0, 979.8f], [[84.7f, 89], [483.2f, 948.7f], [239.7f, 539.1f], [531.1f, 30]], []), + ['['] = ([50.6f, 488.2f, 0, 968.5f], [[122.5f, 897.3f], [462.6f, 26.5f], [78, 379.3f], [106.6f, 26.5f]], []), + ['\\'] = ([12.5f, 527.8f, 0, 968.5f], [[104.3f, 902], [489.3f, 25.2f], [249.9f, 442.6f], [224, 688.5f]], []), + [']'] = ([71, 494.9f, 0, 968.5f], [[425.2f, 897.3f], [97.9f, 26.5f], [469.7f, 382.5f], [457, 26.5f]], []), + ['^'] = ([-21.4f, 571.4f, 470.5f, 968.5f], [[275.7f, 819.5f], [26.2f, 530.7f], [531.8f, 537.2f], [118.2f, 704.6f]], []), + ['_'] = ([-50.3f, 581.9f, -286.9f, -116], [[36.3f, -203.3f], [552.8f, -148], [301.9f, -258.6f], [187.6f, -148]], []), + ['`'] = ([140.2f, 400.9f, 754.3f, 968.5f], [[255.7f, 907], [359.2f, 780.2f], [275.8f, 816.9f], [192.3f, 940.4f]], []), + ['a'] = ( + [-17.8f, 562, -13.1f, 744.5f], + [[471.9f, 137.4f], [110.1f, 672.8f], [14.1f, 215], [523.6f, 562]], + [[246.7f, 215], [398, 307.3f], [361.1f, 189.1f]]), + ['b'] = ( + [-72.6f, 591.8f, -12.6f, 1024.1f], + [[25.2f, 149.2f], [-42.2f, 993.5f], [493.4f, 589.1f], [493.4f, 120.8f]], + [[273.5f, 344.3f], [294.8f, 571.3f], [287.7f, 127.9f]]), + ['c'] = ([1.3f, 548.8f, -13.4f, 759.4f], [[74, 439.5f], [480.1f, 107.2f], [513.3f, 576.1f], [125.7f, 85.1f]], []), + ['d'] = ( + [-38.6f, 617.5f, -12.5f, 1015.4f], + [[519.6f, 154.7f], [590.5f, 984.8f], [58.4f, 587.5f], [58.4f, 119.2f]], + [[271.3f, 371.1f], [257.1f, 126.3f], [211, 559.1f]]), + ['e'] = ( + [-34.5f, 577.2f, -13.2f, 753.4f], + [[52.8f, 401.2f], [503.3f, 98.5f], [503.3f, 608], [145.1f, 54.2f]], + [[281.7f, 548.9f], [422, 497.2f], [148.8f, 497.2f]]), + ['f'] = ([28.2f, 517.1f, 0, 1017.6f], [[271.6f, 639.6f], [310.7f, 29.4f], [488, 987.3f], [236.2f, 331]], []), + ['g'] = ( + [-26.1f, 594.7f, -240.2f, 708.4f], + [[503.2f, 554.5f], [85.2f, -178.9f], [3.7f, 352.5f], [567, 30.1f]], + [[269.4f, 387.9f], [290.7f, 171.8f], [276.5f, 568.6f]]), + ['h'] = ([-13.5f, 551.1f, 0, 1023.7f], [[80.8f, 574.9f], [524.2f, 28.6f], [16.9f, 67.6f], [517.1f, 475.5f]], []), + ['i'] = ([110.1f, 464.8f, 0, 1042.9f], [[353, 952.9f], [438.1f, 27], [438.1f, 495.2f], [140.1f, 672.6f]], []), + ['j'] = ([99.3f, 468.4f, -239, 1018], [[373.7f, 930.5f], [129.2f, -210.3f], [441, 321.2f], [210.7f, 604.6f]], []), + ['k'] = ([-46.4f, 572.2f, 0, 1007.9f], [[40.4f, 385], [-16.3f, 977.4f], [505.1f, 26.7f], [466.1f, 665.2f]], []), + ['l'] = ([77.9f, 442.5f, 0, 1049.1f], [[186.1f, 118.2f], [108.1f, 1019.3f], [108.1f, 565.2f], [413.2f, 29.5f]], []), + ['m'] = ([-62.5f, 612.5f, 0, 734.5f], [[279.3f, 607.1f], [-30.8f, 31.2f], [582.1f, 71.8f], [-30.8f, 404.1f]], []), + ['n'] = ([-55.1f, 592.1f, 0, 731.6f], [[43.4f, 564], [564, 28.7f], [-23, 28.7f], [549.2f, 538.2f]], []), + ['o'] = ( + [-64.1f, 614.1f, -13, 738.7f], + [[542.6f, 396.9f], [-14.9f, 227.1f], [151.2f, 684.8f], [350.6f, 27.7f]], + [[269.4f, 371], [258.3f, 127.4f], [250.9f, 596.2f]]), + ['p'] = ( + [-71.4f, 592.8f, -235.7f, 694.7f], + [[25.5f, 533.8f], [-41.8f, -206.6f], [500.2f, 126.4f], [479, 579.9f]], + [[273.5f, 335.4f], [255.8f, 558.6f], [287.7f, 115.8f]]), + ['q'] = ( + [-52, 623, -239.1f, 704.8f], + [[523.5f, 543.7f], [594.4f, -211], [45.2f, 118.5f], [59.4f, 586.2f]], + [[272, 352.3f], [257.8f, 125.6f], [307.4f, 561.4f]]), + ['r'] = ([-15.6f, 538.2f, 0, 743.4f], [[78.1f, 575.6f], [15.4f, 29.2f], [510.1f, 520.2f], [100.3f, 295]], []), + ['s'] = ([-18.3f, 553.5f, -13.2f, 747.3f], [[75.5f, 542.4f], [463.2f, 73.6f], [31.2f, 110.5f], [492.7f, 597.8f]], []), + ['t'] = ([32.9f, 528.6f, -12.5f, 904.9f], [[232.8f, 648.1f], [484.7f, 38], [193.8f, 275.6f], [498.8f, 680]], []), + ['u'] = ([-49.1f, 612, -12.9f, 721.5f], [[513.7f, 155], [-18, 690.3f], [583.8f, 690.3f], [-6.9f, 169.7f]], []), + ['v'] = ([-72.2f, 622.2f, 0, 712.6f], [[520.1f, 637], [198.9f, 27.9f], [-30, 681.3f], [442.6f, 290]], []), + ['w'] = ([-60, 610, 0, 720.7f], [[119.5f, 116.8f], [577.3f, 689.1f], [-24.5f, 689.1f], [525.6f, 260.8f]], []), + ['x'] = ([-50.1f, 600, 0, 720.7f], [[268.7f, 397.4f], [541.9f, 28.2f], [6.6f, 28.2f], [6.6f, 689.1f]], []), + ['y'] = ([-64.6f, 619.8f, -241.7f, 700.1f], [[288, 227], [-16.7f, 669.9f], [575, 669.9f], [11.7f, -212.3f]], []), + ['z'] = ([-1.7f, 562.8f, 0, 752.8f], [[443, 662], [29.5f, 30.7f], [531.6f, 100.9f], [51.6f, 721.1f]], []), + ['{'] = ([-3.9f, 553.9f, 0, 968.5f], [[245.5f, 766.7f], [526.2f, 28.2f], [198.7f, 325.6f], [526.2f, 940.5f]], []), + ['|'] = ([205, 345, -90, 1170], [[275.6f, 1099.4f], [239.6f, -56.6f], [311.6f, 519.4f], [239.6f, 807.4f]], []), + ['}'] = ([0.3f, 556.3f, 0, 968.5f], [[306.6f, 767.9f], [28, 25], [353, 326.8f], [28, 940.4f]], []), + ['~'] = ([12.1f, 531.1f, 766, 968.5f], [[105.6f, 881.2f], [502, 925.6f], [314, 809.4f], [235.4f, 925.6f]], []), + ['£'] = ([5.3f, 554.6f, 0, 968.5f], [[251.6f, 511.3f], [528.8f, 26.1f], [33.7f, 62.4f], [509, 920.5f]], []), + ['¤'] = ( + [-72.9f, 615.5f, 99.9f, 804.6f], + [[101.8f, 625.6f], [522.4f, 145.9f], [20.6f, 145.9f], [570.3f, 710.4f]], + [[271.5f, 455.8f], [282.6f, 312], [260.4f, 592.4f]]), + ['¥'] = ([-81.7f, 625, 0, 968.5f], [[272.5f, 356.1f], [-26.3f, 940.3f], [571.3f, 940.3f], [238.9f, 708.6f]], []), + ['§'] = ( + [-7.1f, 574.2f, -11.7f, 968.5f], + [[137.7f, 638.9f], [459.2f, 54.1f], [24.8f, 187.5f], [544.7f, 481.6f]], + [[178.7f, 495.3f], [425, 467.9f], [305.3f, 512.4f]]), + ['¨'] = ([110.8f, 445.5f, 769, 968.5f], [[165.8f, 912.8f], [421.6f, 792.8f], [361.6f, 941.2f], [197.4f, 792.8f]], []), + ['´'] = ([153.5f, 404.7f, 754.3f, 968.5f], [[294.4f, 905.4f], [197.2f, 779], [275, 817.9f], [356, 941]], []), + ['¸'] = ([177, 373, -271, -12.5f], [[268, -199.1f], [334.4f, -44.2f], [264.3f, -106.9f], [312.3f, -151.2f]], []), + ['Ä'] = ( + [-74.7f, 617.3f, 0, 1072], + [[251.8f, 904.4f], [576.4f, 30], [41.5f, 328.8f], [458.4f, 502.2f]], + [[270.2f, 458], [273.9f, 675.6f], [248.1f, 568.7f]]), + ['Å'] = ( + [-68, 607, 0, 1032.2f], + [[81.1f, 252.2f], [420.6f, 975.3f], [564.4f, 30.8f], [435.3f, 525.2f]], + [[273, 816.7f], [177, 337], [306.2f, 562.1f]]), + ['Æ'] = ( + [-55.5f, 595.4f, 0, 971.2f], + [[319, 888.1f], [-16.8f, 28.4f], [566.2f, 105.9f], [71.8f, 489.6f]], + [[219.4f, 430.6f], [237.8f, 725.7f], [212, 578.2f]]), + ['Ñ'] = ([-33.2f, 583.2f, 0, 1089.2f], [[484.7f, 105.7f], [100.9f, 1046.5f], [-2.4f, 456.2f], [554.8f, 685]], []), + ['Ö'] = ( + [-63.6f, 608.3f, -12.8f, 1060.1f], + [[23.5f, 367.7f], [576.9f, 1028.1f], [-31.9f, 976.4f], [576.9f, 463.6f]], + [[281.7f, 426.7f], [270.7f, 150], [263.3f, 692.3f]]), + ['Ø'] = ( + [-55.7f, 605.6f, -13.3f, 1050.4f], + [[536.7f, 510], [123.5f, 34], [-9.4f, 650.2f], [389.1f, 1008.1f]], + [[186.2f, 683.4f], [322.7f, 237], [433.4f, 513.7f]]), + ['Ü'] = ([-60.2f, 648.9f, -12.8f, 1049.1f], [[568.4f, 719], [40.8f, 102.8f], [-25.6f, 1017.9f], [590.5f, 165.6f]], []), + ['ß'] = ([-63.2f, 595, 0, 1062.4f], [[355.6f, 558.8f], [-32.1f, 30.8f], [104.5f, 998.1f], [462.6f, 75.1f]], []), + ['å'] = ( + [-21.7f, 571.2f, -12.6f, 1033], + [[479.2f, 137.9f], [161.7f, 979.6f], [17.7f, 274.5f], [479.2f, 617.8f]], + [[265, 215.4f], [316.7f, 898.4f], [176.4f, 776.6f]]), + ['æ'] = ( + [-53.3f, 603.3f, -12.8f, 737.1f], + [[277.2f, 380.8f], [37.2f, 41.2f], [513.5f, 41.2f], [528.3f, 668.8f]], + [[140.6f, 222.1f], [432.3f, 591.3f], [362.1f, 469.4f]]), + ['ø'] = ( + [-49.8f, 605.3f, -128.8f, 835.8f], + [[137.1f, 84.5f], [524.8f, 804.5f], [52.2f, 594], [572.8f, 313.4f]], + [[369.7f, 232.2f], [229.4f, 586.6f], [115, 335.6f]]), + ['IJ'] = ([-76.6f, 634, -12.3f, 982.9f], [[516.1f, 102.7f], [-48.4f, 951.3f], [604.7f, 774.2f], [-48.4f, 338.8f]], []), + ['ij'] = ([-75.5f, 618.3f, -243.7f, 1063.6f], [[19.2f, 969.9f], [388.3f, -207.9f], [543.4f, 486.2f], [-3, 246.2f]], []), + ['―'] = ([-309, 859, -524, -384], [[-238.3f, -453.3f], [825.7f, -493.3f], [297.7f, -417.3f], [33.7f, -493.3f]], []), + ['∣'] = ([205, 345, -190, 1270], [[275.6f, 1199.4f], [239.6f, -156.6f], [311.6f, 483.4f], [239.6f, 839.4f]], []), + ['█'] = ([-99, 649, -51, 1090], [[271.4f, 716.3f], [615.4f, -19.7f], [-68.6f, 108.3f], [615.4f, 1056.3f]], []), + }; + + /// + /// Gets the OCR-A per-glyph expectations. Bounds are [minX, maxX, minY, maxY] of the ink in design + /// units, or an empty array when the figure leaves an extreme underdetermined; probes are [x, y] + /// points. Inked probes assert ink where a stroke must pass and blank probes assert holes in + /// counters and gaps, so errors that preserve the bounding box still trip a check. + /// + public static IReadOnlyDictionary OcrA { get; } = + new Dictionary + { + ['0'] = ([-70, 620, -70, 1010], [[275, 940], [275, 0], [0, 470], [550, 470]], [[275, 470]]), + ['1'] = ([-70, 620, -70, 1010], [[137, 940], [275, 470], [275, 0], [550, 300]], [[450, 600]]), + ['2'] = ([-70, 620, -70, 1010], [[275, 940], [275, 470], [275, 0], [0, 235], [550, 705]], [[100, 700], [450, 235]]), + ['3'] = ([-70, 620, -70, 1010], [[275, 940], [275, 470], [275, 0], [550, 700]], [[100, 700], [100, 235]]), + ['4'] = ([-1.25f, 620, -70, 1010], [[68.75f, 600], [550, 600], [275, 352.5f]], [[275, 600]]), + ['5'] = ([-70, 620, -70, 1010], [[300, 975], [137.5f, 700], [300, 500], [550, 235], [300, -30], [0, 59]], [[300, 700], [300, 180], [-40, 940]]), + ['6'] = ([-70, 620, -70, 1010], [[0, 500], [68.75f, 940], [275, 0], [550, 176]], [[275, 176], [275, 600]]), + ['7'] = ([-70, 620, -70, 1010], [[0, 900], [275, 940], [550, 750], [275, 100]], [[100, 300], [450, 200]]), + ['8'] = ([-70, 620, -70, 1010], [[275, 470], [275, 940], [275, 0], [0, 235], [550, 235]], [[275, 705], [275, 235]]), + ['9'] = ([-70, 620, -70, 1010], [[550, 500], [481.25f, 0], [275, 940], [0, 750]], [[275, 764], [275, 300]]), + ['A'] = ([-70, 620, -70, 1010], [[275, 940], [275, 235], [30, 100], [520, 100]], [[275, 100], [275, 500]]), + ['B'] = ([-70, 620, -70, 1010], [[0, 470], [275, 940], [275, 470], [275, 0], [550, 700], [550, 235]], [[275, 705], [275, 235]]), + ['C'] = ([-70, 620, -70, 1010], [[0, 470], [275, 940], [275, 0], [550, 940], [550, 0]], [[275, 470], [550, 470]]), + ['D'] = ([-70, 620, -70, 1010], [[137.5f, 470], [275, 940], [275, 0], [550, 470]], [[340, 470], [30, 470]]), + ['E'] = ([-70, 620, -70, 1010], [[0, 470], [275, 940], [275, 470], [275, 0], [550, 940]], [[450, 470], [275, 705], [275, 235]]), + ['F'] = ([-70, 620, -70, 1010], [[0, 470], [275, 940], [275, 587.5f], [412, 587.5f]], [[450, 300], [275, 300], [275, 760]]), + ['G'] = ([-70, 620, -70, 1010], [[275, 940], [0, 470], [275, 0], [550, 200], [400, 352.5f]], [[275, 600], [450, 500]]), + ['H'] = ([-70, 620, -70, 1010], [[0, 470], [550, 470], [275, 470]], [[275, 700], [275, 235]]), + ['I'] = ([-70, 620, -70, 1010], [[275, 470], [275, 940], [275, 0], [0, 940], [550, 0]], [[100, 470], [450, 470]]), + ['J'] = ([-1.25f, 551.25f, -70, 1010], [[481.25f, 700], [275, 0], [68.75f, 300]], [[275, 400], [275, 700]]), + ['K'] = ([-70, 620, -70, 1010], [[0, 470], [275, 705], [275, 235]], [[450, 470], [300, 900]]), + ['L'] = ([-70, 620, -70, 1010], [[0, 470], [275, 0], [550, 0]], [[275, 470]]), + ['M'] = ([-70, 620, -70, 1010], [[0, 470], [550, 470], [275, 660], [275, 530], [137, 800], [0, 990]], [[275, 300], [275, 880]]), + ['N'] = ([-70, 620, -70, 1010], [[0, 470], [550, 470], [275, 470]], [[200, 235], [350, 705]]), + ['O'] = ([-70, 620, -70, 1010], [[0, 470], [550, 470], [275, 940], [275, 0]], [[275, 470]]), + ['P'] = ([-70, 620, -70, 1010], [[0, 470], [275, 940], [275, 411.25f], [550, 700]], [[275, 700], [350, 150]]), + ['Q'] = ([-70, 620, -70, 1010], [[400, 940], [550, 470], [0, 300], [430, 25]], [[250, 550]]), + ['R'] = ([-70, 620, -70, 1010], [[0, 470], [275, 940], [275, 587.5f], [378, 294]], [[275, 760], [150, 235]]), + ['S'] = ( + [-70, 620, -70, 1010], + [[275, 470], [300, 1000], [550, 822.5f], [0, 117.5f], [550, 765], [300, -60], [550, 87]], + [[350, 700], [200, 200], [275, 700], [275, 200], [432, 822], [117, 117]]), + ['T'] = ([-70, 620, -70, 1010], [[275, 470], [0, 870], [550, 870], [275, 940]], [[100, 400], [450, 400]]), + ['U'] = ([-70, 620, -70, 1010], [[0, 470], [550, 470], [275, 0]], [[275, 470]]), + ['V'] = ([-70, 620, -70, 1010], [[0, 800], [137.5f, 352.5f], [412.5f, 352.5f]], [[275, 600], [60, 100]]), + ['W'] = ( + [-70, 620, -70, 1010], + [[0, 470], [550, 470], [275, 400], [137.5f, -30], [412.5f, -30]], + [[137.5f, 500], [412.5f, 500], [275, -60], [275, 800]]), + ['X'] = ([-70, 620, -70, 1010], [[275, 470], [50, 85], [500, 855]], [[275, 100], [275, 840]]), + ['Y'] = ([-70, 620, -70, 1010], [[275, 470], [275, 100], [0, 900], [550, 900]], [[100, 300], [450, 300]]), + ['Z'] = ([-70, 620, -70, 1010], [[275, 940], [275, 470], [275, 0], [590, 940], [-40, 0], [590, 0]], [[60, 500], [500, 200]]), + ['.'] = ([136.25f, 413.75f, 106.25f, 363.75f], [[275, 235]], [[275, 600]]), + [','] = ([67.5f, 482.5f, -70, 422.5f], [[275, 352.5f], [412.5f, 100]], [[200, 100]]), + [':'] = ([136.25f, 413.75f, 106.25f, 833.75f], [[275, 235], [275, 705]], [[275, 470]]), + [';'] = ([67.5f, 482.5f, -70, 892.5f], [[275, 352.5f], [206, 764]], [[275, 550]]), + ['='] = ([-70, 620, 282.5f, 716.25f], [[275, 352.5f], [275, 646.25f]], [[275, 500]]), + ['+'] = ([-70, 620, 125, 815], [[275, 470], [275, 200], [275, 740], [0, 470], [550, 470]], [[100, 200]]), + ['-'] = ([-70, 620, 341.25f, 540], [[275, 440], [0, 440], [550, 440]], [[275, 600]]), + ['/'] = ([-70, 620, -70, 1010], [[275, 470]], [[100, 700], [450, 235]]), + ['*'] = ([-70, 620, 47.5f, 892.5f], [[275, 470], [275, 150], [275, 790]], [[100, 470]]), + ['"'] = ([-70, 620, 517.5f, 1010], [[275, 940], [37, 750], [513, 750], [68.75f, 620], [481.25f, 620]], [[275, 700]]), + ['\''] = ([136, 414, 400, 1010], [[206.25f, 700], [275, 940], [280, 600]], [[450, 800]]), + ['%'] = ([-70, 620, -70, 1010], [[68.75f, 880], [481.25f, 60], [275, 470]], [[450, 880], [100, 60]]), + ['$'] = ([-70, 620, -70, 1010], [[275, 470], [275, 970], [0, 600]], [[100, 100], [450, 850]]), + ['('] = ([-70, 620, -70, 1010], [[275, 470], [550, 940], [550, 0], [0, 470]], [[450, 470]]), + [')'] = ([-70, 620, -70, 1010], [[275, 470], [0, 940], [0, 0], [550, 470]], [[100, 470]]), + ['?'] = ([-70, 620, -70, 1010], [[137.5f, 0], [0, 587.5f], [275, 822.5f], [412, 900], [550, 705], [137.5f, 300]], [[275, 300], [400, 150]]), + ['&'] = ([-70, 620, -70, 892.5f], [[275, 0], [0, 300], [206, 822.5f], [412.5f, 650], [183, 392], [550, 30]], [[450, 400], [100, 700]]), + ['|'] = ([205, 345, -294, 1166], [[275, 0], [275, 1000], [275, -250]], [[100, 470], [275, 436]]), + ['⑀'] = ([-70, 620, -70, 1010], [[275, 470], [550, 700], [0, 176], [275, 940], [275, 0]], [[450, 176]]), + ['⑁'] = ([-70, 620, -70, 1010], [[550, 470], [0, 235], [-30, 470]], [[300, 150]]), + ['⑂'] = ([-70, 620, -70, 1010], [[0, 700], [550, 700], [275, 470], [275, 100]], [[275, 700]]), + ['█'] = ([-70, 620, -70, 1010], [[275, 470], [0, 470], [550, 470], [275, 940], [275, 0]], []), + ['―'] = ([-225, 775, 400, 540], [[275, 470], [-200, 470], [750, 470]], [[275, 600]]), + ['a'] = ([-70, 620, -70, 716], [[138, 646], [275, 353], [275, 0], [0, 176], [470, 400]], [[200, 176], [250, 500]]), + ['b'] = ([-70, 620, -70, 1010], [[0, 800], [275, 646], [275, 0], [550, 300]], [[275, 300]]), + ['c'] = ([-70, 620, -70, 716], [[275, 646], [275, 0], [0, 300]], [[275, 300], [480, 300]]), + ['d'] = ([-70, 620, -70, 1010], [[550, 800], [275, 646], [275, 0], [0, 300]], [[275, 300]]), + ['e'] = ([-70, 620, -70, 716], [[275, 294], [275, 646], [275, 0], [0, 470]], [[275, 450], [480, 100]]), + ['f'] = ([-1, 620, -70, 1010], [[206, 300], [275, 646], [450, 940]], [[60, 300], [520, 646]]), + ['g'] = ([-70, 620, -364, 716], [[275, 646], [275, 0], [550, 300], [550, -100], [300, -294]], [[275, 300], [100, -150]]), + ['h'] = ([-70, 620, -70, 1010], [[0, 800], [0, 300], [275, 646], [545, 300]], [[275, 300]]), + ['i'] = ([-1, 551, -70, 1069], [[275, 300], [240, 970], [275, 0], [100, 646]], [[275, 800], [100, 300]]), + ['j'] = ([-70, 483, -364, 1069], [[413, 300], [378, 970], [200, -294], [200, 646]], [[200, 300], [200, -50]]), + ['k'] = ([-70, 620, -70, 1010], [[0, 800], [0, 300], [300, 536], [300, 161]], [[450, 353]]), + ['l'] = ([-1, 551, -70, 1010], [[275, 600], [275, 0], [100, 0]], [[100, 400]]), + ['m'] = ([-85, 635, -70, 725], [[-15, 50], [-15, 600], [275, 300], [565, 50], [178, 700], [454, 700]], [[140, 300], [420, 300], [314, 690]]), + ['n'] = ([-70, 620, -70, 716], [[0, 300], [550, 300], [300, 646]], [[275, 300]]), + ['o'] = ([-70, 620, -70, 716], [[0, 300], [550, 300], [275, 646], [275, 0]], [[275, 300]]), + ['p'] = ([-70, 620, -364, 745.5f], [[0, 300], [0, -200], [550, 300], [275, 675.5f], [275, -29.5f]], [[275, 300]]), + ['q'] = ([-70, 620, -364, 716], [[550, -200], [0, 300], [275, 646], [275, 0], [550, 300]], [[275, 300]]), + ['r'] = ([-70, 620, -70, 716], [[0, 300], [300, 646], [550, 500]], [[300, 300]]), + ['s'] = ([-61, 616, -70, 716], [[275, 323], [300, 700], [250, -55], [535, 588], [15, 59], [560, 138]], [[275, 500], [275, 150], [422, 508]]), + ['t'] = ([-70, 620, -70, 951], [[275, 646], [138, 300], [345, -30], [550, 118], [138, 880]], [[300, 300], [0, 300]]), + ['u'] = ([-70, 620, -70, 716], [[0, 300], [550, 300], [275, 0]], [[275, 300]]), + ['v'] = ([-70, 620, -70, 716], [[0, 500], [550, 500], [275, 0]], [[275, 500]]), + ['w'] = ([-84, 634, -70, 716], [[-10, 600], [560, 600], [275, 350], [275, 411], [103, 0], [447, 0]], [[150, 500], [275, 550]]), + ['x'] = ([-55, 605, -70, 716], [[275, 323], [480, 578]], [[275, 100], [275, 550]]), + ['y'] = ([-70, 620, -364, 716], [[0, 600], [550, 600], [100, -294], [350, 100]], [[100, 100]]), + ['z'] = ([-70, 620, -70, 716], [[275, 646], [275, 323], [275, 0]], [[60, 300], [490, 350]]), + }; + + /// + /// Verifies one glyph's flattened contours against its expectations, appending any deviation to + /// as a line of glyph, feature, expected and actual values. A character + /// with no recorded expectation is itself a failure, so a new glyph cannot land unchecked. + /// + /// The character being verified. + /// The flattened outline contours in design units. + /// The expectation table of the design being built. + /// The failure sink. + public static void Verify( + char character, + IReadOnlyList> contours, + IReadOnlyDictionary expectations, + List failures) + { + if (!expectations.TryGetValue(character, out (float[] Bounds, float[][] Inked, float[][] Blank) spec)) + { + failures.Add($"U+{(int)character:X4} '{character}': no spec expectation recorded"); + return; + } + + float minX = float.MaxValue; + float maxX = float.MinValue; + float minY = float.MaxValue; + float maxY = float.MinValue; + foreach (IReadOnlyList contour in contours) + { + foreach (Vector2 point in contour) + { + minX = MathF.Min(minX, point.X); + maxX = MathF.Max(maxX, point.X); + minY = MathF.Min(minY, point.Y); + maxY = MathF.Max(maxY, point.Y); + } + } + + if (spec.Bounds.Length == 4) + { + Check(failures, character, "minX", spec.Bounds[0], minX); + Check(failures, character, "maxX", spec.Bounds[1], maxX); + Check(failures, character, "minY", spec.Bounds[2], minY); + Check(failures, character, "maxY", spec.Bounds[3], maxY); + } + + foreach (float[] point in spec.Inked) + { + if (!Contains(contours, new Vector2(point[0], point[1]))) + { + failures.Add($"'{character}': expected ink at ({point[0]}, {point[1]}) but found none"); + } + } + + foreach (float[] point in spec.Blank) + { + if (Contains(contours, new Vector2(point[0], point[1]))) + { + failures.Add($"'{character}': expected no ink at ({point[0]}, {point[1]}) but found ink"); + } + } + } + + /// + /// Compares one measured ink extreme against its expected value within . + /// + /// The failure sink. + /// The character being verified. + /// The name of the extreme being compared. + /// The expected value in design units. + /// The measured value in design units. + private static void Check(List failures, char character, string feature, float expected, float actual) + { + if (MathF.Abs(expected - actual) > BoundsTolerance) + { + failures.Add($"'{character}': {feature} expected {expected} but measured {actual:F1}"); + } + } + + /// + /// Determines whether a point lies inside the outline ink by the non-zero winding rule, the fill + /// rule the TrueType rasteriser applies to the glyph. + /// + /// The flattened outline contours in design units. + /// The point to test. + /// when the point is inside the ink; otherwise . + private static bool Contains(IReadOnlyList> contours, Vector2 point) + { + int winding = 0; + foreach (IReadOnlyList contour in contours) + { + for (int i = 0; i < contour.Count; i++) + { + Vector2 a = contour[i]; + Vector2 b = contour[(i + 1) % contour.Count]; + if (a.Y <= point.Y) + { + if (b.Y > point.Y && Cross(a, b, point) > 0) + { + winding++; + } + } + else if (b.Y <= point.Y && Cross(a, b, point) < 0) + { + winding--; + } + } + } + + return winding != 0; + } + + /// + /// Computes the z component of the cross product of the edge from to + /// with the vector from to , + /// positive when the point lies left of the edge. + /// + /// The edge start. + /// The edge end. + /// The point to classify. + /// The signed area term. + private static float Cross(Vector2 a, Vector2 b, Vector2 point) => + ((b.X - a.X) * (point.Y - a.Y)) - ((point.X - a.X) * (b.Y - a.Y)); +} diff --git a/src/ImageSharp.Drawing.FontGenerator/TrueTypeWriter.cs b/src/ImageSharp.Drawing.FontGenerator/TrueTypeWriter.cs new file mode 100644 index 00000000..4c3d913a --- /dev/null +++ b/src/ImageSharp.Drawing.FontGenerator/TrueTypeWriter.cs @@ -0,0 +1,616 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers.Binary; + +namespace SixLabors.ImageSharp.Drawing.FontGenerator; + +/// +/// Writes a minimal TrueType font from flattened glyph contours. Outlines contain on-curve points only, +/// which the TrueType glyf format permits, so no curve conversion is required. The emitted tables are +/// head, hhea, maxp, OS/2, hmtx, cmap, loca, glyf, name and post. +/// +internal sealed class TrueTypeWriter +{ + private readonly List glyphs = []; + private readonly SortedDictionary characterMap = []; + + /// + /// Initializes a new instance of the class. Glyph index 0 is the + /// required empty .notdef glyph. + /// + /// The design units per em. + /// The typographic ascender in design units. + /// The typographic descender in design units, negative below the baseline. + /// The ink height of a flat topped capital in design units. + /// The font family name. + /// The copyright notice written to name ID 0. + public TrueTypeWriter(ushort unitsPerEm, short ascender, short descender, short capHeight, string familyName, string copyright) + { + this.UnitsPerEm = unitsPerEm; + this.Ascender = ascender; + this.Descender = descender; + this.CapHeight = capHeight; + this.FamilyName = familyName; + this.Copyright = copyright; + this.glyphs.Add(new GlyphRecord([], unitsPerEm)); + } + + /// + /// Gets the design units per em. + /// + public ushort UnitsPerEm { get; } + + /// + /// Gets the typographic ascender in design units. + /// + public short Ascender { get; } + + /// + /// Gets the typographic descender in design units. + /// + public short Descender { get; } + + /// + /// Gets the ink height of a flat topped capital in design units. Text layout uses this value to + /// place a line of capitals or digits against a reference line. + /// + public short CapHeight { get; } + + /// + /// Gets the font family name. + /// + public string FamilyName { get; } + + /// + /// Gets the copyright notice. + /// + public string Copyright { get; } + + /// + /// Adds a glyph mapped to a character. Contours are closed point lists in font units with the y axis + /// pointing up; fill follows the non-zero winding rule. + /// + /// The character the glyph represents. + /// The closed contours, each a list of on-curve points. + /// The advance width in font units. + public void AddGlyph(char character, IReadOnlyList> contours, ushort advanceWidth) + { + this.characterMap.Add(character, (ushort)this.glyphs.Count); + this.glyphs.Add(new GlyphRecord(contours, advanceWidth)); + } + + /// + /// Serializes the font. + /// + /// The font file bytes. + public byte[] Write() + { + byte[] glyf = this.BuildGlyf(out uint[] locaOffsets, out short xMin, out short yMin, out short xMax, out short yMax); + byte[] loca = BuildLoca(locaOffsets); + byte[] head = this.BuildHead(xMin, yMin, xMax, yMax); + byte[] hhea = this.BuildHhea(xMin, xMax); + byte[] maxp = this.BuildMaxp(); + byte[] hmtx = this.BuildHmtx(); + byte[] cmap = this.BuildCmap(); + byte[] os2 = this.BuildOs2(xMin, yMin, xMax, yMax); + byte[] name = this.BuildName(); + byte[] post = BuildPost(); + + (string Tag, byte[] Data)[] tables = + [ + ("OS/2", os2), + ("cmap", cmap), + ("glyf", glyf), + ("head", head), + ("hhea", hhea), + ("hmtx", hmtx), + ("loca", loca), + ("maxp", maxp), + ("name", name), + ("post", post), + ]; + + ushort numTables = (ushort)tables.Length; + ushort searchRange = 16; + ushort entrySelector = 0; + while (searchRange * 2 <= numTables * 16) + { + searchRange *= 2; + entrySelector++; + } + + int headerLength = 12 + (numTables * 16); + int totalLength = headerLength; + foreach ((_, byte[] data) in tables) + { + totalLength += (data.Length + 3) & ~3; + } + + byte[] font = new byte[totalLength]; + BinaryPrimitives.WriteUInt32BigEndian(font, 0x00010000); + BinaryPrimitives.WriteUInt16BigEndian(font.AsSpan(4), numTables); + BinaryPrimitives.WriteUInt16BigEndian(font.AsSpan(6), searchRange); + BinaryPrimitives.WriteUInt16BigEndian(font.AsSpan(8), entrySelector); + BinaryPrimitives.WriteUInt16BigEndian(font.AsSpan(10), (ushort)((numTables * 16) - searchRange)); + + int record = 12; + int offset = headerLength; + int headOffset = 0; + foreach ((string tag, byte[] data) in tables) + { + if (tag == "head") + { + headOffset = offset; + } + + for (int i = 0; i < 4; i++) + { + font[record + i] = (byte)tag[i]; + } + + data.CopyTo(font, offset); + BinaryPrimitives.WriteUInt32BigEndian(font.AsSpan(record + 4), TableChecksum(font.AsSpan(offset, (data.Length + 3) & ~3))); + BinaryPrimitives.WriteUInt32BigEndian(font.AsSpan(record + 8), (uint)offset); + BinaryPrimitives.WriteUInt32BigEndian(font.AsSpan(record + 12), (uint)data.Length); + record += 16; + offset += (data.Length + 3) & ~3; + } + + uint total = TableChecksum(font); + BinaryPrimitives.WriteUInt32BigEndian(font.AsSpan(headOffset + 8), 0xB1B0AFBA - total); + return font; + } + + /// + /// Sums the table as big-endian 32 bit words with zero padding, the per-table checksum the sfnt + /// directory records. + /// + /// The table bytes. + /// The checksum. + private static uint TableChecksum(ReadOnlySpan data) + { + uint sum = 0; + for (int i = 0; i < data.Length; i += 4) + { + uint value = 0; + for (int b = 0; b < 4; b++) + { + value <<= 8; + if (i + b < data.Length) + { + value |= data[i + b]; + } + } + + sum += value; + } + + return sum; + } + + /// + /// Builds the long-format loca table: one 32 bit glyf offset per glyph plus the trailing end offset. + /// + /// The glyf byte offsets. + /// The table bytes. + private static byte[] BuildLoca(uint[] offsets) + { + byte[] loca = new byte[offsets.Length * 4]; + for (int i = 0; i < offsets.Length; i++) + { + BinaryPrimitives.WriteUInt32BigEndian(loca.AsSpan(i * 4), offsets[i]); + } + + return loca; + } + + /// + /// Builds a version 3.0 post table, the variant that stores no glyph names. + /// + /// The table bytes. + private static byte[] BuildPost() + { + byte[] post = new byte[32]; + BinaryPrimitives.WriteUInt32BigEndian(post, 0x00030000); + return post; + } + + /// + /// Builds the glyf table from the glyph contours: per glyph a simple-glyph header, contour end + /// indices, one on-curve flag per point and relative 16 bit coordinates, padded to 4 bytes. + /// + /// The glyf byte offset of every glyph plus the trailing end offset. + /// The font-wide minimum x of the ink. + /// The font-wide minimum y of the ink. + /// The font-wide maximum x of the ink. + /// The font-wide maximum y of the ink. + /// The table bytes. + private byte[] BuildGlyf(out uint[] locaOffsets, out short xMin, out short yMin, out short xMax, out short yMax) + { + using MemoryStream stream = new(); + Span coordinate = stackalloc byte[2]; + locaOffsets = new uint[this.glyphs.Count + 1]; + xMin = short.MaxValue; + yMin = short.MaxValue; + xMax = short.MinValue; + yMax = short.MinValue; + + for (int g = 0; g < this.glyphs.Count; g++) + { + locaOffsets[g] = (uint)stream.Length; + GlyphRecord glyph = this.glyphs[g]; + if (glyph.Contours.Count == 0) + { + continue; + } + + glyph.GetBounds(out short gxMin, out short gyMin, out short gxMax, out short gyMax); + xMin = Math.Min(xMin, gxMin); + yMin = Math.Min(yMin, gyMin); + xMax = Math.Max(xMax, gxMax); + yMax = Math.Max(yMax, gyMax); + + int pointCount = glyph.Contours.Sum(contour => contour.Count); + byte[] header = new byte[10 + (glyph.Contours.Count * 2) + 2]; + BinaryPrimitives.WriteInt16BigEndian(header, (short)glyph.Contours.Count); + BinaryPrimitives.WriteInt16BigEndian(header.AsSpan(2), gxMin); + BinaryPrimitives.WriteInt16BigEndian(header.AsSpan(4), gyMin); + BinaryPrimitives.WriteInt16BigEndian(header.AsSpan(6), gxMax); + BinaryPrimitives.WriteInt16BigEndian(header.AsSpan(8), gyMax); + + int end = -1; + for (int c = 0; c < glyph.Contours.Count; c++) + { + end += glyph.Contours[c].Count; + BinaryPrimitives.WriteUInt16BigEndian(header.AsSpan(10 + (c * 2)), (ushort)end); + } + + BinaryPrimitives.WriteUInt16BigEndian(header.AsSpan(10 + (glyph.Contours.Count * 2)), 0); + stream.Write(header); + + // All points are on-curve; write one flag byte per point followed by absolute-to-relative + // deltas as int16 coordinates. + for (int i = 0; i < pointCount; i++) + { + stream.WriteByte(0x01); + } + + short previous = 0; + foreach (IReadOnlyList<(short X, short Y)> contour in glyph.Contours) + { + foreach ((short x, _) in contour) + { + BinaryPrimitives.WriteInt16BigEndian(coordinate, (short)(x - previous)); + stream.Write(coordinate); + previous = x; + } + } + + previous = 0; + foreach (IReadOnlyList<(short X, short Y)> contour in glyph.Contours) + { + foreach ((_, short y) in contour) + { + BinaryPrimitives.WriteInt16BigEndian(coordinate, (short)(y - previous)); + stream.Write(coordinate); + previous = y; + } + } + + while ((stream.Length & 3) != 0) + { + stream.WriteByte(0); + } + } + + locaOffsets[this.glyphs.Count] = (uint)stream.Length; + if (xMin == short.MaxValue) + { + xMin = 0; + yMin = 0; + xMax = 0; + yMax = 0; + } + + return stream.ToArray(); + } + + /// + /// Builds the head table: units per em, the font-wide ink bounds, a lowest recommended size of 8 + /// pixels per em and the long loca format flag. + /// + /// The font-wide minimum x of the ink. + /// The font-wide minimum y of the ink. + /// The font-wide maximum x of the ink. + /// The font-wide maximum y of the ink. + /// The table bytes. + private byte[] BuildHead(short xMin, short yMin, short xMax, short yMax) + { + byte[] head = new byte[54]; + BinaryPrimitives.WriteUInt32BigEndian(head, 0x00010000); + BinaryPrimitives.WriteUInt32BigEndian(head.AsSpan(4), 0x00010000); + BinaryPrimitives.WriteUInt32BigEndian(head.AsSpan(12), 0x5F0F3CF5); + BinaryPrimitives.WriteUInt16BigEndian(head.AsSpan(16), 0x0003); + BinaryPrimitives.WriteUInt16BigEndian(head.AsSpan(18), this.UnitsPerEm); + BinaryPrimitives.WriteInt16BigEndian(head.AsSpan(36), xMin); + BinaryPrimitives.WriteInt16BigEndian(head.AsSpan(38), yMin); + BinaryPrimitives.WriteInt16BigEndian(head.AsSpan(40), xMax); + BinaryPrimitives.WriteInt16BigEndian(head.AsSpan(42), yMax); + BinaryPrimitives.WriteUInt16BigEndian(head.AsSpan(46), 8); + BinaryPrimitives.WriteInt16BigEndian(head.AsSpan(48), 2); + BinaryPrimitives.WriteInt16BigEndian(head.AsSpan(50), 1); + return head; + } + + /// + /// Builds the hhea table: the vertical metrics, a zero line gap, the maximum advance width and the + /// horizontal metric count. + /// + /// The font-wide minimum x of the ink. + /// The font-wide maximum x of the ink. + /// The table bytes. + private byte[] BuildHhea(short xMin, short xMax) + { + byte[] hhea = new byte[36]; + BinaryPrimitives.WriteUInt32BigEndian(hhea, 0x00010000); + BinaryPrimitives.WriteInt16BigEndian(hhea.AsSpan(4), this.Ascender); + BinaryPrimitives.WriteInt16BigEndian(hhea.AsSpan(6), this.Descender); + BinaryPrimitives.WriteInt16BigEndian(hhea.AsSpan(8), 0); + BinaryPrimitives.WriteUInt16BigEndian(hhea.AsSpan(10), this.glyphs.Max(glyph => glyph.AdvanceWidth)); + BinaryPrimitives.WriteInt16BigEndian(hhea.AsSpan(12), xMin); + BinaryPrimitives.WriteInt16BigEndian(hhea.AsSpan(14), 0); + BinaryPrimitives.WriteInt16BigEndian(hhea.AsSpan(16), xMax); + BinaryPrimitives.WriteInt16BigEndian(hhea.AsSpan(18), 1); + BinaryPrimitives.WriteUInt16BigEndian(hhea.AsSpan(34), (ushort)this.glyphs.Count); + return hhea; + } + + /// + /// Builds the version 1.0 maxp table with the glyph count and the point and contour maxima. + /// + /// The table bytes. + private byte[] BuildMaxp() + { + int maxPoints = 0; + int maxContours = 0; + foreach (GlyphRecord glyph in this.glyphs) + { + maxPoints = Math.Max(maxPoints, glyph.Contours.Sum(contour => contour.Count)); + maxContours = Math.Max(maxContours, glyph.Contours.Count); + } + + byte[] maxp = new byte[32]; + BinaryPrimitives.WriteUInt32BigEndian(maxp, 0x00010000); + BinaryPrimitives.WriteUInt16BigEndian(maxp.AsSpan(4), (ushort)this.glyphs.Count); + BinaryPrimitives.WriteUInt16BigEndian(maxp.AsSpan(6), (ushort)maxPoints); + BinaryPrimitives.WriteUInt16BigEndian(maxp.AsSpan(8), (ushort)maxContours); + BinaryPrimitives.WriteUInt16BigEndian(maxp.AsSpan(14), 2); + return maxp; + } + + /// + /// Builds the hmtx table: the advance width and left side bearing of every glyph. + /// + /// The table bytes. + private byte[] BuildHmtx() + { + byte[] hmtx = new byte[this.glyphs.Count * 4]; + for (int i = 0; i < this.glyphs.Count; i++) + { + GlyphRecord glyph = this.glyphs[i]; + glyph.GetBounds(out short gxMin, out _, out _, out _); + BinaryPrimitives.WriteUInt16BigEndian(hmtx.AsSpan(i * 4), glyph.AdvanceWidth); + BinaryPrimitives.WriteInt16BigEndian(hmtx.AsSpan((i * 4) + 2), glyph.Contours.Count == 0 ? (short)0 : gxMin); + } + + return hmtx; + } + + /// + /// Builds the cmap table with a single format 4 subtable for the Windows Unicode BMP platform. + /// + /// The table bytes. + private byte[] BuildCmap() + { + // Build format 4 segments from runs of consecutive characters mapping to consecutive glyph ids. + List<(ushort Start, ushort End, ushort GlyphStart)> segments = []; + foreach (KeyValuePair entry in this.characterMap) + { + if (segments.Count > 0) + { + (ushort start, ushort end, ushort glyphStart) = segments[^1]; + if (entry.Key == end + 1 && entry.Value == glyphStart + (end - start) + 1) + { + segments[^1] = (start, entry.Key, glyphStart); + continue; + } + } + + segments.Add((entry.Key, entry.Key, entry.Value)); + } + + segments.Add((0xFFFF, 0xFFFF, 0)); + ushort segCount = (ushort)segments.Count; + ushort segCountX2 = (ushort)(segCount * 2); + ushort searchRange = 2; + ushort entrySelector = 0; + while (searchRange * 2 <= segCountX2) + { + searchRange *= 2; + entrySelector++; + } + + int subtableLength = 16 + (segCount * 8); + byte[] cmap = new byte[12 + subtableLength]; + BinaryPrimitives.WriteUInt16BigEndian(cmap.AsSpan(2), 1); + BinaryPrimitives.WriteUInt16BigEndian(cmap.AsSpan(4), 3); + BinaryPrimitives.WriteUInt16BigEndian(cmap.AsSpan(6), 1); + BinaryPrimitives.WriteUInt32BigEndian(cmap.AsSpan(8), 12); + + Span subtable = cmap.AsSpan(12); + BinaryPrimitives.WriteUInt16BigEndian(subtable, 4); + BinaryPrimitives.WriteUInt16BigEndian(subtable[2..], (ushort)subtableLength); + BinaryPrimitives.WriteUInt16BigEndian(subtable[6..], segCountX2); + BinaryPrimitives.WriteUInt16BigEndian(subtable[8..], searchRange); + BinaryPrimitives.WriteUInt16BigEndian(subtable[10..], entrySelector); + BinaryPrimitives.WriteUInt16BigEndian(subtable[12..], (ushort)(segCountX2 - searchRange)); + + int endCodes = 14; + int startCodes = endCodes + segCountX2 + 2; + int idDeltas = startCodes + segCountX2; + int idRanges = idDeltas + segCountX2; + for (int i = 0; i < segCount; i++) + { + (ushort start, ushort end, ushort glyphStart) = segments[i]; + BinaryPrimitives.WriteUInt16BigEndian(subtable[(endCodes + (i * 2))..], end); + BinaryPrimitives.WriteUInt16BigEndian(subtable[(startCodes + (i * 2))..], start); + ushort delta = i == segCount - 1 ? (ushort)1 : (ushort)(glyphStart - start); + BinaryPrimitives.WriteUInt16BigEndian(subtable[(idDeltas + (i * 2))..], delta); + BinaryPrimitives.WriteUInt16BigEndian(subtable[(idRanges + (i * 2))..], 0); + } + + return cmap; + } + + /// + /// Builds the version 4 OS/2 table: regular weight and width, the SixL vendor tag, the character + /// range, the typographic and Windows vertical metrics and the space break character. + /// + /// The font-wide minimum x of the ink. + /// The font-wide minimum y of the ink. + /// The font-wide maximum x of the ink. + /// The font-wide maximum y of the ink. + /// The table bytes. + private byte[] BuildOs2(short xMin, short yMin, short xMax, short yMax) + { + byte[] os2 = new byte[96]; + BinaryPrimitives.WriteUInt16BigEndian(os2, 4); + BinaryPrimitives.WriteInt16BigEndian(os2.AsSpan(2), (short)this.UnitsPerEm); + BinaryPrimitives.WriteUInt16BigEndian(os2.AsSpan(4), 400); + BinaryPrimitives.WriteUInt16BigEndian(os2.AsSpan(6), 5); + BinaryPrimitives.WriteInt16BigEndian(os2.AsSpan(10), (short)(this.UnitsPerEm / 5)); + BinaryPrimitives.WriteInt16BigEndian(os2.AsSpan(12), (short)(this.UnitsPerEm / 10)); + os2[58] = (byte)'S'; + os2[59] = (byte)'i'; + os2[60] = (byte)'x'; + os2[61] = (byte)'L'; + BinaryPrimitives.WriteUInt16BigEndian(os2.AsSpan(62), 0x0040); + BinaryPrimitives.WriteUInt16BigEndian(os2.AsSpan(64), this.characterMap.Keys.Min()); + BinaryPrimitives.WriteUInt16BigEndian(os2.AsSpan(66), this.characterMap.Keys.Max()); + BinaryPrimitives.WriteInt16BigEndian(os2.AsSpan(68), this.Ascender); + BinaryPrimitives.WriteInt16BigEndian(os2.AsSpan(70), this.Descender); + BinaryPrimitives.WriteInt16BigEndian(os2.AsSpan(72), 0); + BinaryPrimitives.WriteUInt16BigEndian(os2.AsSpan(74), (ushort)yMax); + BinaryPrimitives.WriteUInt16BigEndian(os2.AsSpan(76), (ushort)Math.Max(0, -yMin)); + BinaryPrimitives.WriteInt16BigEndian(os2.AsSpan(88), this.CapHeight); + BinaryPrimitives.WriteUInt16BigEndian(os2.AsSpan(92), ' '); + return os2; + } + + /// + /// Builds the name table for the Windows Unicode platform: copyright, family, subfamily, unique and + /// full names, the version string and the PostScript name. + /// + /// The table bytes. + private byte[] BuildName() + { + (ushort Id, string Value)[] entries = + [ + (0, this.Copyright), + (1, this.FamilyName), + (2, "Regular"), + (3, this.FamilyName), + (4, this.FamilyName), + (5, "Version 1.000"), + (6, this.FamilyName.Replace(" ", string.Empty)), + ]; + + int stringLength = entries.Sum(entry => entry.Value.Length * 2); + byte[] name = new byte[6 + (entries.Length * 12) + stringLength]; + BinaryPrimitives.WriteUInt16BigEndian(name.AsSpan(2), (ushort)entries.Length); + BinaryPrimitives.WriteUInt16BigEndian(name.AsSpan(4), (ushort)(6 + (entries.Length * 12))); + + int record = 6; + int offset = 0; + int storage = 6 + (entries.Length * 12); + foreach ((ushort id, string value) in entries) + { + BinaryPrimitives.WriteUInt16BigEndian(name.AsSpan(record), 3); + BinaryPrimitives.WriteUInt16BigEndian(name.AsSpan(record + 2), 1); + BinaryPrimitives.WriteUInt16BigEndian(name.AsSpan(record + 4), 0x0409); + BinaryPrimitives.WriteUInt16BigEndian(name.AsSpan(record + 6), id); + BinaryPrimitives.WriteUInt16BigEndian(name.AsSpan(record + 8), (ushort)(value.Length * 2)); + BinaryPrimitives.WriteUInt16BigEndian(name.AsSpan(record + 10), (ushort)offset); + foreach (char c in value) + { + BinaryPrimitives.WriteUInt16BigEndian(name.AsSpan(storage + offset), c); + offset += 2; + } + + record += 12; + } + + return name; + } + + /// + /// One glyph's closed contours and advance width. + /// + private sealed class GlyphRecord + { + /// + /// Initializes a new instance of the class. + /// + /// The closed contours, each a list of on-curve points. + /// The advance width in font units. + public GlyphRecord(IReadOnlyList> contours, ushort advanceWidth) + { + this.Contours = contours; + this.AdvanceWidth = advanceWidth; + } + + /// + /// Gets the closed contours, each a list of on-curve points. + /// + public IReadOnlyList> Contours { get; } + + /// + /// Gets the advance width in font units. + /// + public ushort AdvanceWidth { get; } + + /// + /// Reports the bounding box of the contours, or a zero box for the empty glyph. + /// + /// The minimum x of the contours. + /// The minimum y of the contours. + /// The maximum x of the contours. + /// The maximum y of the contours. + public void GetBounds(out short xMin, out short yMin, out short xMax, out short yMax) + { + xMin = short.MaxValue; + yMin = short.MaxValue; + xMax = short.MinValue; + yMax = short.MinValue; + foreach (IReadOnlyList<(short X, short Y)> contour in this.Contours) + { + foreach ((short x, short y) in contour) + { + xMin = Math.Min(xMin, x); + yMin = Math.Min(yMin, y); + xMax = Math.Max(xMax, x); + yMax = Math.Max(yMax, y); + } + } + + if (xMin == short.MaxValue) + { + xMin = 0; + yMin = 0; + xMax = 0; + yMax = 0; + } + } + } +} diff --git a/src/ImageSharp.Drawing.WebGPU.ShaderGen/ImageSharp.Drawing.WebGPU.ShaderGen.csproj b/src/ImageSharp.Drawing.WebGPU.ShaderGen/ImageSharp.Drawing.WebGPU.ShaderGen.csproj index 2fbb26a1..1aa2b63d 100644 --- a/src/ImageSharp.Drawing.WebGPU.ShaderGen/ImageSharp.Drawing.WebGPU.ShaderGen.csproj +++ b/src/ImageSharp.Drawing.WebGPU.ShaderGen/ImageSharp.Drawing.WebGPU.ShaderGen.csproj @@ -14,7 +14,6 @@ false false false - false $(NoWarn);RS1036;SA1513;SA1600;SA1633 false diff --git a/src/ImageSharp.Drawing.WebGPU.ShaderGen/WgslSourceGenerator.cs b/src/ImageSharp.Drawing.WebGPU.ShaderGen/WgslSourceGenerator.cs index 73ba63ab..a80c5d94 100644 --- a/src/ImageSharp.Drawing.WebGPU.ShaderGen/WgslSourceGenerator.cs +++ b/src/ImageSharp.Drawing.WebGPU.ShaderGen/WgslSourceGenerator.cs @@ -146,7 +146,12 @@ private static string GenerateSource(SourceProductionContext context, Dictionary _ = builder.AppendLine(" 0,"); _ = builder.AppendLine(" ];"); _ = builder.AppendLine(); - _ = builder.Append(" public static ReadOnlySpan ").Append(propertyName).Append("Code => ").Append(propertyName).AppendLine("CodeBytes;"); + _ = builder.Append(" public static ReadOnlySpan ") + .Append(propertyName) + .Append("Code => ") + .Append(propertyName) + .AppendLine("CodeBytes;"); + _ = builder.AppendLine(); } } diff --git a/src/ImageSharp.Drawing.WebGPU/Shaders/FineAreaComputeShader.cs b/src/ImageSharp.Drawing.WebGPU/Shaders/FineAreaComputeShader.cs index 1070eeca..ac790c15 100644 --- a/src/ImageSharp.Drawing.WebGPU/Shaders/FineAreaComputeShader.cs +++ b/src/ImageSharp.Drawing.WebGPU/Shaders/FineAreaComputeShader.cs @@ -33,7 +33,9 @@ internal static class FineAreaComputeShader /// /// Specialized shader bytes per target texture format and alpha representation, guarded by its own monitor. /// - private static readonly Dictionary<(WGPUTextureFormat TextureFormat, PixelAlphaRepresentation AlphaRepresentation, WebGPUTargetNumericEncoding NumericEncoding), byte[]> ShaderCache = []; + private static readonly Dictionary< + (WGPUTextureFormat TextureFormat, PixelAlphaRepresentation AlphaRepresentation, WebGPUTargetNumericEncoding NumericEncoding), + byte[]> ShaderCache = []; /// /// Gets the WGSL entry point used by this shader. @@ -167,7 +169,8 @@ private static ShaderTraits GetTraits( { WGPUTextureFormat.RGBA8Unorm or WGPUTextureFormat.BGRA8Unorm => "return unpack4x8unorm(pack4x8unorm(color));", WGPUTextureFormat.RGBA8Snorm => "return unpack4x8snorm(pack4x8snorm(color));", - WGPUTextureFormat.RGBA16Float => "return vec4(quantize_f16_rtne(color.x), quantize_f16_rtne(color.y), quantize_f16_rtne(color.z), quantize_f16_rtne(color.w));" + WGPUTextureFormat.RGBA16Float => + "return vec4(quantize_f16_rtne(color.x), quantize_f16_rtne(color.y), quantize_f16_rtne(color.z), quantize_f16_rtne(color.w));" }; // Fine shading uses associated colors internally. Unassociated targets cross the storage diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUColorMatrixLayerEffect.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUColorMatrixLayerEffect.cs index 51c68dbe..5a1d1b75 100644 --- a/src/ImageSharp.Drawing.WebGPU/WebGPUColorMatrixLayerEffect.cs +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUColorMatrixLayerEffect.cs @@ -49,7 +49,23 @@ public WebGPUColorMatrixLayerEffect(ColorMatrix matrix) // ImageSharp stores its affine colour transform as a 5x4 matrix. The upper 4x4 block maps // directly to WGSL's row-vector multiplication; M51-M54 form the separate translation row. - Matrix4x4 transform = new(matrix.M11, matrix.M12, matrix.M13, matrix.M14, matrix.M21, matrix.M22, matrix.M23, matrix.M24, matrix.M31, matrix.M32, matrix.M33, matrix.M34, matrix.M41, matrix.M42, matrix.M43, matrix.M44); + Matrix4x4 transform = new( + matrix.M11, + matrix.M12, + matrix.M13, + matrix.M14, + matrix.M21, + matrix.M22, + matrix.M23, + matrix.M24, + matrix.M31, + matrix.M32, + matrix.M33, + matrix.M34, + matrix.M41, + matrix.M42, + matrix.M43, + matrix.M44); // A pass retains an immutable uniform snapshot, so later instances cannot alter this matrix. this.AddShaderPass(uniforms => diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.CompositePixels.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.CompositePixels.cs index 4ad9bf93..6d7c9d33 100644 --- a/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.CompositePixels.cs +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.CompositePixels.cs @@ -31,8 +31,14 @@ public sealed partial class WebGPUDrawingBackend private static readonly CompositePixelRegistration[] CompositePixelRegistrations = [ - CompositePixelRegistration.Create(WebGPUTextureFormat.Rgba8Snorm, PixelAlphaRepresentation.Unassociated, WebGPUTargetNumericEncoding.SignedUnit), - CompositePixelRegistration.Create(WebGPUTextureFormat.Rgba8Snorm, PixelAlphaRepresentation.Associated, WebGPUTargetNumericEncoding.SignedUnit), + CompositePixelRegistration.Create( + WebGPUTextureFormat.Rgba8Snorm, + PixelAlphaRepresentation.Unassociated, + WebGPUTargetNumericEncoding.SignedUnit), + CompositePixelRegistration.Create( + WebGPUTextureFormat.Rgba8Snorm, + PixelAlphaRepresentation.Associated, + WebGPUTargetNumericEncoding.SignedUnit), CompositePixelRegistration.Create(WebGPUTextureFormat.Rgba16Float, PixelAlphaRepresentation.Unassociated, WebGPUTargetNumericEncoding.Unit), CompositePixelRegistration.Create(WebGPUTextureFormat.Rgba16Float, PixelAlphaRepresentation.Associated, WebGPUTargetNumericEncoding.Unit), CompositePixelRegistration.Create(WebGPUTextureFormat.Rgba8Unorm, PixelAlphaRepresentation.Unassociated, WebGPUTargetNumericEncoding.Unit), diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.Readback.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.Readback.cs index f9311cfc..c5854175 100644 --- a/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.Readback.cs +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.Readback.cs @@ -90,7 +90,8 @@ public void ReadRegion( if (requiredFeature != default && !WebGPURuntime.GetOrCreateDeviceState(api, nativeTarget.DeviceHandle).HasFeature(requiredFeature)) { - throw new NotSupportedException($"The target device does not support WebGPU feature '{requiredFeature}' required to read back pixel type '{typeof(TPixel).Name}'."); + throw new NotSupportedException( + $"The target device does not support WebGPU feature '{requiredFeature}' required to read back pixel type '{typeof(TPixel).Name}'."); } WGPUQueueImpl* queue = (WGPUQueueImpl*)queueReference.Handle; diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUNativeSurface.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUNativeSurface.cs index b9c41398..22c0417d 100644 --- a/src/ImageSharp.Drawing.WebGPU/WebGPUNativeSurface.cs +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUNativeSurface.cs @@ -167,12 +167,14 @@ public static unsafe WebGPUNativeSurface Create( if (requiredFeature != default && !WebGPURuntime.GetOrCreateDeviceState(api, deviceHandle).HasFeature(requiredFeature)) { - throw new NotSupportedException($"The WebGPU device does not support required feature '{requiredFeature}' for texture format '{targetDescriptor.Format}'."); + throw new NotSupportedException( + $"The WebGPU device does not support required feature '{requiredFeature}' for texture format '{targetDescriptor.Format}'."); } WGPUTextureDescriptor textureDescriptor = new() { - usage = (ulong)(TextureUsage.RenderAttachment | TextureUsage.CopySrc | TextureUsage.CopyDst | TextureUsage.TextureBinding | TextureUsage.StorageBinding), + usage = (ulong)(TextureUsage.RenderAttachment | TextureUsage.CopySrc | TextureUsage.CopyDst | + TextureUsage.TextureBinding | TextureUsage.StorageBinding), dimension = WGPUTextureDimension._2D, size = new WGPUExtent3D((uint)width, (uint)height, 1), format = textureFormat, diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPURenderTarget.cs b/src/ImageSharp.Drawing.WebGPU/WebGPURenderTarget.cs index 4a6f3daa..fe962350 100644 --- a/src/ImageSharp.Drawing.WebGPU/WebGPURenderTarget.cs +++ b/src/ImageSharp.Drawing.WebGPU/WebGPURenderTarget.cs @@ -93,7 +93,13 @@ public WebGPURenderTarget( /// The target width in pixels. /// The target height in pixels. public WebGPURenderTarget(Configuration configuration, WebGPUTextureFormat format, PixelAlphaRepresentation alphaRepresentation, int width, int height) - : this(new WebGPUDeviceContext(configuration), true, WebGPUDrawingBackend.CreateOffscreenTargetDescriptor(format, alphaRepresentation), width, height, isPresentationSurface: false) + : this( + new WebGPUDeviceContext(configuration), + true, + WebGPUDrawingBackend.CreateOffscreenTargetDescriptor(format, alphaRepresentation), + width, + height, + isPresentationSurface: false) { } diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPURuntime.DeviceSharedState.cs b/src/ImageSharp.Drawing.WebGPU/WebGPURuntime.DeviceSharedState.cs index 33bc0542..67748799 100644 --- a/src/ImageSharp.Drawing.WebGPU/WebGPURuntime.DeviceSharedState.cs +++ b/src/ImageSharp.Drawing.WebGPU/WebGPURuntime.DeviceSharedState.cs @@ -379,7 +379,8 @@ public void CompletePipelineWarmup() /// The pipeline cache key. /// when the pipeline exists; otherwise, . public bool HasCompositeComputePipeline(string pipelineKey) - => this.compositeComputePipelines.TryGetValue(pipelineKey, out CompositeComputePipelineInfrastructure? infrastructure) && infrastructure.Pipeline is not null; + => this.compositeComputePipelines.TryGetValue(pipelineKey, out CompositeComputePipelineInfrastructure? infrastructure) && + infrastructure.Pipeline is not null; /// /// Rents a pooled map-readable status buffer, or creates one when the pool has no buffer diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPURuntime.cs b/src/ImageSharp.Drawing.WebGPU/WebGPURuntime.cs index 03ecbefc..199c2147 100644 --- a/src/ImageSharp.Drawing.WebGPU/WebGPURuntime.cs +++ b/src/ImageSharp.Drawing.WebGPU/WebGPURuntime.cs @@ -683,13 +683,15 @@ public static string CreateEnvironmentExceptionMessage(WebGPUEnvironmentError er WebGPUEnvironmentError.InstanceCreationFailed => "The WebGPU runtime could not create an instance.", WebGPUEnvironmentError.AdapterRequestTimedOut => "Timed out while waiting for the WebGPU adapter request callback.", WebGPUEnvironmentError.AdapterRequestFailed => "The WebGPU runtime failed to acquire a WebGPU adapter.", - WebGPUEnvironmentError.SoftwareAdapterUnsupported => "The only available WebGPU adapter is a software rasterizer, which this backend does not support; use the CPU drawing backend instead.", + WebGPUEnvironmentError.SoftwareAdapterUnsupported => + "The only available WebGPU adapter is a software rasterizer, which this backend does not support; use the CPU drawing backend instead.", WebGPUEnvironmentError.DeviceRequestTimedOut => "Timed out while waiting for the WebGPU device request callback.", WebGPUEnvironmentError.DeviceRequestFailed => "The WebGPU runtime failed to acquire a WebGPU device.", WebGPUEnvironmentError.QueueAcquisitionFailed => "The WebGPU runtime acquired a device but could not retrieve its default queue.", WebGPUEnvironmentError.DeviceAcquisitionFailed => "The WebGPU runtime failed to provision a WebGPU device and queue.", WebGPUEnvironmentError.ComputePipelineCreationFailed => "The isolated WebGPU compute-pipeline probe reported failure.", - WebGPUEnvironmentError.ComputePipelineProbeProcessFailed => "The isolated WebGPU compute-pipeline probe process terminated before it could report a result.", + WebGPUEnvironmentError.ComputePipelineProbeProcessFailed => + "The isolated WebGPU compute-pipeline probe process terminated before it could report a result.", WebGPUEnvironmentError.WgpuExtensionUnavailable => "The required WGPU extension is unavailable.", _ => "The WebGPU runtime failed for an unknown reason." }; diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUSceneConfig.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUSceneConfig.cs index 027e39ca..ec86e669 100644 --- a/src/ImageSharp.Drawing.WebGPU/WebGPUSceneConfig.cs +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUSceneConfig.cs @@ -18,7 +18,11 @@ internal readonly struct WebGPUSceneConfig /// The planned GPU buffer sizes for this flush. /// The current dynamic scratch capacities to expose to the shaders. /// The tile-row window rendered by this staged-scene attempt. - public WebGPUSceneConfig(WebGPUSceneWorkgroupCounts workgroupCounts, WebGPUSceneBufferSizes bufferSizes, WebGPUSceneBumpSizes bumpSizes, WebGPUSceneChunkWindow chunkWindow) + public WebGPUSceneConfig( + WebGPUSceneWorkgroupCounts workgroupCounts, + WebGPUSceneBufferSizes bufferSizes, + WebGPUSceneBumpSizes bumpSizes, + WebGPUSceneChunkWindow chunkWindow) { this.WorkgroupCounts = workgroupCounts; this.BufferSizes = bufferSizes; @@ -913,7 +917,11 @@ public static WebGPUSceneBufferSizes Create(WebGPUEncodedScene scene, WebGPUScen /// The current dynamic scratch capacities for the staged pipeline. /// The tile-row window rendered by this staged-scene attempt. /// The planned GPU buffer sizes for this encoded range and chunk window. - public static WebGPUSceneBufferSizes Create(WebGPUEncodedScene scene, WebGPUSceneRange range, WebGPUSceneBumpSizes bumpSizes, WebGPUSceneChunkWindow chunkWindow) + public static WebGPUSceneBufferSizes Create( + WebGPUEncodedScene scene, + WebGPUSceneRange range, + WebGPUSceneBumpSizes bumpSizes, + WebGPUSceneChunkWindow chunkWindow) { WebGPUSceneWorkgroupCounts workgroupCounts = WebGPUSceneWorkgroupCounts.Create(range, chunkWindow); uint pathTagWgs = workgroupCounts.PathReduceX; diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUSceneDispatch.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUSceneDispatch.cs index 40ab9f87..a46b3d78 100644 --- a/src/ImageSharp.Drawing.WebGPU/WebGPUSceneDispatch.cs +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUSceneDispatch.cs @@ -208,7 +208,12 @@ public static WebGPUStagedScene CreateStagedScene( throw new InvalidOperationException(error); } - if (!TryValidateBindingSizes(encodedScene, config, flushContext.ScratchBufferBindingSizeLimit, out BindingLimitFailure bindingLimitFailure, out error)) + if (!TryValidateBindingSizes( + encodedScene, + config, + flushContext.ScratchBufferBindingSizeLimit, + out BindingLimitFailure bindingLimitFailure, + out error)) { if (!IsChunkableBindingFailure(bindingLimitFailure.Buffer)) { @@ -232,7 +237,14 @@ public static WebGPUStagedScene CreateStagedScene( true); } - if (!WebGPUSceneResources.TryCreate(flushContext, encodedScene, config, baseColor, ref resourceArena, out WebGPUSceneResourceSet resources, out error)) + if (!WebGPUSceneResources.TryCreate( + flushContext, + encodedScene, + config, + baseColor, + ref resourceArena, + out WebGPUSceneResourceSet resources, + out error)) { throw new InvalidOperationException(error ?? "Failed to create WebGPU scene resources."); } @@ -401,7 +413,8 @@ private static bool TryValidateEncodedClipStream( { if (drawTagStart < 0 || drawTagCount < 0 || drawTagStart + drawTagCount > encodedScene.DrawTagCount) { - error = $"The WebGPU {label} draw-tag range [{drawTagStart}, {drawTagStart + drawTagCount}) is outside the encoded draw-tag stream length {encodedScene.DrawTagCount}."; + error = $"The WebGPU {label} draw-tag range [{drawTagStart}, {drawTagStart + drawTagCount}) " + + $"is outside the encoded draw-tag stream length {encodedScene.DrawTagCount}."; return false; } @@ -435,13 +448,15 @@ private static bool TryValidateEncodedClipStream( if (sceneOffset + drawMonoid.SceneOffset > expectedDrawDataWordCount) { - error = $"The WebGPU {label} draw tag {drawTagStart + i} reads draw-data words [{sceneOffset}, {sceneOffset + drawMonoid.SceneOffset}), but the stream declares {expectedDrawDataWordCount} words."; + error = $"The WebGPU {label} draw tag {drawTagStart + i} reads draw-data words [{sceneOffset}, {sceneOffset + drawMonoid.SceneOffset}), " + + $"but the stream declares {expectedDrawDataWordCount} words."; return false; } if (infoOffset + drawMonoid.InfoOffset > expectedInfoWordCount) { - error = $"The WebGPU {label} draw tag {drawTagStart + i} writes info words [{infoOffset}, {infoOffset + drawMonoid.InfoOffset}), but the stream declares {expectedInfoWordCount} words."; + error = $"The WebGPU {label} draw tag {drawTagStart + i} writes info words [{infoOffset}, {infoOffset + drawMonoid.InfoOffset}), " + + $"but the stream declares {expectedInfoWordCount} words."; return false; } @@ -475,13 +490,15 @@ private static bool TryValidateEncodedClipStream( long parentDrawIndex = clipDrawStack[depth]; if (parentPathIndex < 0 || parentPathIndex >= expectedPathCount) { - error = $"The WebGPU {label} EndClip at draw tag {drawTagStart + i} resolves to path {parentPathIndex}, but the stream declares {expectedPathCount} paths."; + error = $"The WebGPU {label} EndClip at draw tag {drawTagStart + i} resolves to path {parentPathIndex}, " + + $"but the stream declares {expectedPathCount} paths."; return false; } if (parentDrawIndex < 0 || parentDrawIndex >= drawTagCount) { - error = $"The WebGPU {label} EndClip at draw tag {drawTagStart + i} resolves to draw tag {parentDrawIndex}, but the stream contains {drawTagCount} draw tags."; + error = $"The WebGPU {label} EndClip at draw tag {drawTagStart + i} resolves to draw tag {parentDrawIndex}, " + + $"but the stream contains {drawTagCount} draw tags."; return false; } @@ -557,32 +574,186 @@ public static bool TryValidateBindingSizes( { bindingLimitFailure = BindingLimitFailure.None; WebGPUSceneBufferSizes bufferSizes = config.BufferSizes; - nuint infoBinDataByteLength = checked(config.BufferSizes.Info.ByteLength + config.BufferSizes.BinData.ByteLength + config.BufferSizes.BinHeaders.ByteLength); - if (!TryValidateBufferSize(GetBindingByteLength(1), "scene config", BindingLimitBuffer.None, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(GetBindingByteLength(encodedScene.SceneWordCount), "scene data", BindingLimitBuffer.None, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.PathReduced.ByteLength, "path reduced", BindingLimitBuffer.None, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.PathReduced2.ByteLength, "path reduced2", BindingLimitBuffer.None, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.PathReducedScan.ByteLength, "path reduced scan", BindingLimitBuffer.None, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.PathMonoids.ByteLength, "path monoids", BindingLimitBuffer.None, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.PathBboxes.ByteLength, "path bboxes", BindingLimitBuffer.None, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.DrawReduced.ByteLength, "draw reduced", BindingLimitBuffer.None, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.DrawMonoids.ByteLength, "draw monoids", BindingLimitBuffer.None, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(infoBinDataByteLength, "scene info/bin data", BindingLimitBuffer.None, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.ClipInputs.ByteLength, "clip inputs", BindingLimitBuffer.None, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.ClipElements.ByteLength, "clip elements", BindingLimitBuffer.None, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.ClipBics.ByteLength, "clip bics", BindingLimitBuffer.None, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.ClipBboxes.ByteLength, "clip bboxes", BindingLimitBuffer.None, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.DrawBboxes.ByteLength, "draw bboxes", BindingLimitBuffer.None, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.Paths.ByteLength, "scene paths", BindingLimitBuffer.None, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.Lines.ByteLength, "scene lines", BindingLimitBuffer.None, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.BinHeaders.ByteLength, "bin headers", BindingLimitBuffer.None, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.IndirectCount.ByteLength, "indirect count", BindingLimitBuffer.None, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.PathRows.ByteLength, "path rows", BindingLimitBuffer.PathRows, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.PathTiles.ByteLength, "path tiles", BindingLimitBuffer.PathTiles, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.SegCounts.ByteLength, "segment counts", BindingLimitBuffer.SegCounts, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.Segments.ByteLength, "segments", BindingLimitBuffer.Segments, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.BlendSpill.ByteLength, "blend spill", BindingLimitBuffer.BlendSpill, maxStorageBufferBindingSize, out bindingLimitFailure, out error) || - !TryValidateBufferSize(bufferSizes.Ptcl.ByteLength, "ptcl", BindingLimitBuffer.Ptcl, maxStorageBufferBindingSize, out bindingLimitFailure, out error)) + nuint infoBinDataByteLength = checked( + config.BufferSizes.Info.ByteLength + + config.BufferSizes.BinData.ByteLength + + config.BufferSizes.BinHeaders.ByteLength); + + if (!TryValidateBufferSize( + GetBindingByteLength(1), + "scene config", + BindingLimitBuffer.None, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + GetBindingByteLength(encodedScene.SceneWordCount), + "scene data", + BindingLimitBuffer.None, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.PathReduced.ByteLength, + "path reduced", + BindingLimitBuffer.None, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.PathReduced2.ByteLength, + "path reduced2", + BindingLimitBuffer.None, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.PathReducedScan.ByteLength, + "path reduced scan", + BindingLimitBuffer.None, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.PathMonoids.ByteLength, + "path monoids", + BindingLimitBuffer.None, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.PathBboxes.ByteLength, + "path bboxes", + BindingLimitBuffer.None, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.DrawReduced.ByteLength, + "draw reduced", + BindingLimitBuffer.None, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.DrawMonoids.ByteLength, + "draw monoids", + BindingLimitBuffer.None, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + infoBinDataByteLength, + "scene info/bin data", + BindingLimitBuffer.None, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.ClipInputs.ByteLength, + "clip inputs", + BindingLimitBuffer.None, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.ClipElements.ByteLength, + "clip elements", + BindingLimitBuffer.None, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.ClipBics.ByteLength, + "clip bics", + BindingLimitBuffer.None, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.ClipBboxes.ByteLength, + "clip bboxes", + BindingLimitBuffer.None, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.DrawBboxes.ByteLength, + "draw bboxes", + BindingLimitBuffer.None, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.Paths.ByteLength, + "scene paths", + BindingLimitBuffer.None, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.Lines.ByteLength, + "scene lines", + BindingLimitBuffer.None, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.BinHeaders.ByteLength, + "bin headers", + BindingLimitBuffer.None, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.IndirectCount.ByteLength, + "indirect count", + BindingLimitBuffer.None, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.PathRows.ByteLength, + "path rows", + BindingLimitBuffer.PathRows, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.PathTiles.ByteLength, + "path tiles", + BindingLimitBuffer.PathTiles, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.SegCounts.ByteLength, + "segment counts", + BindingLimitBuffer.SegCounts, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.Segments.ByteLength, + "segments", + BindingLimitBuffer.Segments, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.BlendSpill.ByteLength, + "blend spill", + BindingLimitBuffer.BlendSpill, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error) || + !TryValidateBufferSize( + bufferSizes.Ptcl.ByteLength, + "ptcl", + BindingLimitBuffer.Ptcl, + maxStorageBufferBindingSize, + out bindingLimitFailure, + out error)) { return false; } @@ -1645,7 +1816,13 @@ public static unsafe bool TryRenderStagedScene( int targetWidth = target.Bounds.Width; int targetHeight = target.Bounds.Height; - if (!WebGPUDrawingBackend.TryCreateCompositionTexture(flushContext, targetWidth, targetHeight, out WGPUTextureImpl* outputTexture, out WGPUTextureViewImpl* outputTextureView, out error)) + if (!WebGPUDrawingBackend.TryCreateCompositionTexture( + flushContext, + targetWidth, + targetHeight, + out WGPUTextureImpl* outputTexture, + out WGPUTextureViewImpl* outputTextureView, + out error)) { return false; } @@ -1688,7 +1865,12 @@ public static unsafe bool TryRenderStagedScene( // The readback map blocks until the GPU finishes everything. if (!TryEnqueueSchedulingStatusReadback(flushContext, scheduling.BumpBuffer, currentArena.ReadbackBuffer, 0, out error) || !WebGPUDrawingBackend.TrySubmitWithIndex(flushContext, out ulong schedulingSubmissionIndex) || - !TryReadSchedulingStatus(flushContext, currentArena.ReadbackBuffer, schedulingSubmissionIndex, out GpuSceneBumpAllocators bumpAllocators, out error)) + !TryReadSchedulingStatus( + flushContext, + currentArena.ReadbackBuffer, + schedulingSubmissionIndex, + out GpuSceneBumpAllocators bumpAllocators, + out error)) { return false; } @@ -1834,7 +2016,13 @@ private static unsafe bool TryRenderSegmentChunkedStagedScene( int targetHeight = target.Bounds.Height; nuint maxStorageBufferBindingSize = flushContext.ScratchBufferBindingSizeLimit; - if (!WebGPUDrawingBackend.TryCreateCompositionTexture(flushContext, targetWidth, targetHeight, out WGPUTextureImpl* outputTexture, out WGPUTextureViewImpl* outputTextureView, out error)) + if (!WebGPUDrawingBackend.TryCreateCompositionTexture( + flushContext, + targetWidth, + targetHeight, + out WGPUTextureImpl* outputTexture, + out WGPUTextureViewImpl* outputTextureView, + out error)) { return false; } @@ -1879,7 +2067,9 @@ private static unsafe bool TryRenderSegmentChunkedStagedScene( try { // Track each chunk's config sizes and tile heights for the batch readback at the end. - using IMemoryOwner chunkBumpSizeOwner = flushContext.MemoryAllocator.Allocate(checked((int)Math.Max(totalTileHeight, 1U))); + using IMemoryOwner chunkBumpSizeOwner = + flushContext.MemoryAllocator.Allocate(checked((int)Math.Max(totalTileHeight, 1U))); + using IMemoryOwner chunkTileHeightOwner = flushContext.MemoryAllocator.Allocate(checked((int)Math.Max(totalTileHeight, 1U))); Span chunkAttemptBumpSizes = chunkBumpSizeOwner.Memory.Span; Span chunkAttemptTileHeights = chunkTileHeightOwner.Memory.Span; @@ -1910,7 +2100,12 @@ private static unsafe bool TryRenderSegmentChunkedStagedScene( ? WebGPUSceneConfig.Create(encodedScene, stagedScene.Range.Value, chunkBumpSize, chunkWindow) : WebGPUSceneConfig.Create(encodedScene, chunkBumpSize, chunkWindow); - if (!TryValidateBindingSizes(encodedScene, chunkConfig, maxStorageBufferBindingSize, out BindingLimitFailure bindingLimitFailure, out error)) + if (!TryValidateBindingSizes( + encodedScene, + chunkConfig, + maxStorageBufferBindingSize, + out BindingLimitFailure bindingLimitFailure, + out error)) { if (IsChunkableBindingFailure(bindingLimitFailure.Buffer)) { @@ -1930,9 +2125,20 @@ private static unsafe bool TryRenderSegmentChunkedStagedScene( // Chunk fits. Re-ensure the arena for this chunk's buffer sizes (may be // different from the previous chunk if the scene has non-uniform density). WebGPUStagedScene chunkScene = stagedScene.Range.HasValue - ? new WebGPUStagedScene(flushContext, encodedScene, stagedScene.Range.Value, chunkConfig, stagedScene.Resources, BindingLimitFailure.None) + ? new WebGPUStagedScene( + flushContext, + encodedScene, + stagedScene.Range.Value, + chunkConfig, + stagedScene.Resources, + BindingLimitFailure.None) : new WebGPUStagedScene(flushContext, encodedScene, chunkConfig, stagedScene.Resources, BindingLimitFailure.None, false, false); - WebGPUSceneSchedulingArena currentArena = EnsureSchedulingArena(flushContext, chunkConfig.BufferSizes, readbackByteLength, ref schedulingArena); + WebGPUSceneSchedulingArena currentArena = EnsureSchedulingArena( + flushContext, + chunkConfig.BufferSizes, + readbackByteLength, + ref schedulingArena); + WGPUBufferImpl* statusReadbackBuffer = deferOverflowCheck ? deferredReadbackBuffer : currentArena.ReadbackBuffer; // A first chunk that already covers the full tile height renders through the @@ -2108,7 +2314,12 @@ private static unsafe bool TryRenderChunkAttempt( if (reuseSharedSchedulingState) { - if (!TryDispatchChunkLocalSchedulingStages(ref stagedScene, schedulingArena, resetChunkLocalBumpAllocators, out WebGPUSceneSchedulingResources sharedScheduling, out error)) + if (!TryDispatchChunkLocalSchedulingStages( + ref stagedScene, + schedulingArena, + resetChunkLocalBumpAllocators, + out WebGPUSceneSchedulingResources sharedScheduling, + out error)) { return false; } @@ -2178,7 +2389,12 @@ private static unsafe bool TryRecordChunkAttempt( WGPUBufferImpl* headerSourceBuffer = CreateAndUploadCopySourceBuffer(stagedScene.FlushContext, in header); if (!TryEnqueueSceneHeaderCopy(stagedScene.FlushContext, headerSourceBuffer, stagedScene.Resources.HeaderBuffer, out error) || - !TryDispatchChunkLocalSchedulingStages(ref stagedScene, schedulingArena, resetChunkLocalBumpAllocators, out WebGPUSceneSchedulingResources scheduling, out error)) + !TryDispatchChunkLocalSchedulingStages( + ref stagedScene, + schedulingArena, + resetChunkLocalBumpAllocators, + out WebGPUSceneSchedulingResources scheduling, + out error)) { return false; } @@ -2559,7 +2775,8 @@ private static uint ClampEstimate(long estimate, ulong maxStorageBufferBindingSi /// when the failed binding shrinks with the chunk window; otherwise, . [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsChunkableBindingFailure(BindingLimitBuffer buffer) - => buffer is BindingLimitBuffer.PathRows or BindingLimitBuffer.PathTiles or BindingLimitBuffer.SegCounts or BindingLimitBuffer.Segments or BindingLimitBuffer.BlendSpill or BindingLimitBuffer.Ptcl; + => buffer is BindingLimitBuffer.PathRows or BindingLimitBuffer.PathTiles or BindingLimitBuffer.SegCounts or + BindingLimitBuffer.Segments or BindingLimitBuffer.BlendSpill or BindingLimitBuffer.Ptcl; /// /// Scales one count by the chunk's tile-height ratio while keeping the result non-zero. @@ -2829,7 +3046,14 @@ private static unsafe bool TryDispatchPathtagScan( entries[2] = CreateBufferBinding(2, parentBuffer, parentBufferSize); entries[3] = CreateBufferBinding(3, resources.PathMonoidBuffer, pathMonoidBufferSize); - return recording.TryRecord(useSmallVariant ? WebGPUSceneShaderId.PathtagScanSmall : WebGPUSceneShaderId.PathtagScan, entries, 4, dispatchX, 1, 1, out error); + return recording.TryRecord( + useSmallVariant ? WebGPUSceneShaderId.PathtagScanSmall : WebGPUSceneShaderId.PathtagScan, + entries, + 4, + dispatchX, + 1, + 1, + out error); } /// @@ -3311,7 +3535,8 @@ void Callback(WGPUMapAsyncStatus status, void* userData) flushContext.Api.BufferMapAsync(readbackBuffer, MapMode.Read, 0, (nuint)sizeof(GpuSceneBumpAllocators), callback, null); using (WebGPUHandle.HandleReference deviceReference = flushContext.DeviceHandle.AcquireReference()) { - if (!WaitForMapSignal(flushContext.Api, (WGPUDeviceImpl*)deviceReference.Handle, mapReady, submissionIndex) || mapStatus != WGPUMapAsyncStatus.Success) + if (!WaitForMapSignal(flushContext.Api, (WGPUDeviceImpl*)deviceReference.Handle, mapReady, submissionIndex) || + mapStatus != WGPUMapAsyncStatus.Success) { error = $"Failed to map staged-scene scheduling status with status '{mapStatus}'."; return false; @@ -3450,7 +3675,8 @@ void Callback(WGPUMapAsyncStatus status, void* userData) flushContext.Api.BufferMapAsync(readbackBuffer, MapMode.Read, 0, mappedByteLength, callback, null); using (WebGPUHandle.HandleReference deviceReference = flushContext.DeviceHandle.AcquireReference()) { - if (!WaitForMapSignal(flushContext.Api, (WGPUDeviceImpl*)deviceReference.Handle, mapReady, submissionIndex) || mapStatus != WGPUMapAsyncStatus.Success) + if (!WaitForMapSignal(flushContext.Api, (WGPUDeviceImpl*)deviceReference.Handle, mapReady, submissionIndex) || + mapStatus != WGPUMapAsyncStatus.Success) { error = $"Failed to map staged-scene chunk scheduling status with status '{mapStatus}'."; return false; @@ -4054,7 +4280,11 @@ private static unsafe bool TryDispatchFineArea( entries[0] = CreateBufferBinding(0, resources.HeaderBuffer, (nuint)sizeof(GpuSceneConfig)); entries[1] = CreateBufferBinding(1, scheduling.SegmentBuffer, bufferSizes.Segments.ByteLength); entries[2] = CreateBufferBinding(2, scheduling.PtclBuffer, bufferSizes.Ptcl.ByteLength); - entries[3] = CreateBufferBinding(3, resources.InfoBinDataBuffer, checked(bufferSizes.Info.ByteLength + bufferSizes.BinData.ByteLength + bufferSizes.BinHeaders.ByteLength)); + entries[3] = CreateBufferBinding( + 3, + resources.InfoBinDataBuffer, + checked(bufferSizes.Info.ByteLength + bufferSizes.BinData.ByteLength + bufferSizes.BinHeaders.ByteLength)); + entries[4] = CreateBufferBinding(4, scheduling.BlendBuffer, bufferSizes.BlendSpill.ByteLength); entries[5] = new WGPUBindGroupEntry { binding = 5, textureView = outputTextureView }; entries[6] = new WGPUBindGroupEntry { binding = 6, textureView = resources.GradientTextureView }; @@ -4170,7 +4400,12 @@ private static unsafe bool TryExecuteComputeRecording( foreach (WebGPUSceneComputeCommand command in recording.Commands) { string shaderName = GetShaderDebugName(command.ShaderId); - if (!TryResolveComputeShader(flushContext.DeviceState, command.ShaderId, out WGPUBindGroupLayoutImpl* bindGroupLayout, out WGPUComputePipelineImpl* pipeline, out error)) + if (!TryResolveComputeShader( + flushContext.DeviceState, + command.ShaderId, + out WGPUBindGroupLayoutImpl* bindGroupLayout, + out WGPUComputePipelineImpl* pipeline, + out error)) { error = error is null ? null : $"{error} Stage: {shaderName}."; return false; @@ -4306,7 +4541,8 @@ bool LayoutFactory(WebGPU api, WGPUDeviceImpl* device, out WGPUBindGroupLayoutIm WebGPUSceneShaderId.PathtagReduce => PathtagReduceComputeShader.TryCreateBindGroupLayout(api, device, out layout, out layoutError), WebGPUSceneShaderId.PathtagReduce2 => PathtagReduce2ComputeShader.TryCreateBindGroupLayout(api, device, out layout, out layoutError), WebGPUSceneShaderId.PathtagScan1 => PathtagScan1ComputeShader.TryCreateBindGroupLayout(api, device, out layout, out layoutError), - WebGPUSceneShaderId.PathtagScan or WebGPUSceneShaderId.PathtagScanSmall => PathtagScanComputeShader.TryCreateBindGroupLayout(api, device, out layout, out layoutError), + WebGPUSceneShaderId.PathtagScan or WebGPUSceneShaderId.PathtagScanSmall => + PathtagScanComputeShader.TryCreateBindGroupLayout(api, device, out layout, out layoutError), WebGPUSceneShaderId.BboxClear => BboxClearComputeShader.TryCreateBindGroupLayout(api, device, out layout, out layoutError), WebGPUSceneShaderId.PathLowering => PathLoweringComputeShader.TryCreateBindGroupLayout(api, device, out layout, out layoutError), WebGPUSceneShaderId.DrawReduce => DrawReduceComputeShader.TryCreateBindGroupLayout(api, device, out layout, out layoutError), @@ -4468,13 +4704,22 @@ public static unsafe void BeginFinePipelineWarmup(WebGPURuntime.DeviceSharedStat } deviceState.RegisterPipelineWarmup(); - (WebGPURuntime.DeviceSharedState DeviceState, WGPUTextureFormat TextureFormat, WebGPUTargetDescriptor Descriptor) warmupState = (deviceState, textureFormat, targetDescriptor); + (WebGPURuntime.DeviceSharedState DeviceState, WGPUTextureFormat TextureFormat, WebGPUTargetDescriptor Descriptor) warmupState = + (deviceState, textureFormat, targetDescriptor); + bool queued = ThreadPool.UnsafeQueueUserWorkItem( static (state) => { try { - _ = TryResolveFinePipeline(state.DeviceState, state.TextureFormat, state.Descriptor.AlphaRepresentation, state.Descriptor.NumericEncoding, out _, out _, out _); + _ = TryResolveFinePipeline( + state.DeviceState, + state.TextureFormat, + state.Descriptor.AlphaRepresentation, + state.Descriptor.NumericEncoding, + out _, + out _, + out _); } catch { @@ -4502,7 +4747,10 @@ public static unsafe void BeginFinePipelineWarmup(WebGPURuntime.DeviceSharedStat /// The target numeric encoding. /// The cache key. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string GetFinePipelineKey(WGPUTextureFormat textureFormat, PixelAlphaRepresentation alphaRepresentation, WebGPUTargetNumericEncoding numericEncoding) + private static string GetFinePipelineKey( + WGPUTextureFormat textureFormat, + PixelAlphaRepresentation alphaRepresentation, + WebGPUTargetNumericEncoding numericEncoding) => $"{FineAreaPipelineKey}/{textureFormat}/{alphaRepresentation}/{numericEncoding}"; /// @@ -4771,7 +5019,8 @@ private static bool TryValidateBufferSize( if (byteLength > maxStorageBufferBindingSize) { bindingLimitFailure = new BindingLimitFailure(bindingLimitBuffer, byteLength, maxStorageBufferBindingSize); - error = $"The staged-scene {bufferName} buffer requires {byteLength} bytes, exceeding the current WebGPU binding limit of {maxStorageBufferBindingSize} bytes."; + error = $"The staged-scene {bufferName} buffer requires {byteLength} bytes, " + + $"exceeding the current WebGPU binding limit of {maxStorageBufferBindingSize} bytes."; return false; } diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUSceneEncoder.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUSceneEncoder.cs index 73de865d..91fdd852 100644 --- a/src/ImageSharp.Drawing.WebGPU/WebGPUSceneEncoder.cs +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUSceneEncoder.cs @@ -236,7 +236,10 @@ public static bool TryEncode( // Boundary-snapped ranges can be empty; empty ranges skip clip seeding so they // do not emit balanced-but-useless clip records. int commandStart = partitionBoundaries is null ? (partitionIndex * commandCount) / partitionCount : partitionBoundaries[partitionIndex]; - int commandEnd = partitionBoundaries is null ? ((partitionIndex + 1) * commandCount) / partitionCount : partitionBoundaries[partitionIndex + 1]; + int commandEnd = partitionBoundaries is null + ? ((partitionIndex + 1) * commandCount) / partitionCount + : partitionBoundaries[partitionIndex + 1]; + SceneEncodingPlan partitionPlan = plan.GetPartitionPlan(partitionIndex); SupportedSubsetSceneEncoding partitionEncoding = new(allocator, targetRectangle, partitionPlan); @@ -358,7 +361,17 @@ public static bool TryEncodeOrdered( try { SceneEncodingCheckpoint rangeStart = encoding.BeginPackedIndependentRange(targetBounds, ClipBlendMode, replayActiveClips: true); - if (!TryEncodeOrderedOperations(scene, 0, scene.CommandCount, targetBounds, 0, ref encoding, operations, ref rangeStart, ref nextLayerTargetId, out error)) + if (!TryEncodeOrderedOperations( + scene, + 0, + scene.CommandCount, + targetBounds, + 0, + ref encoding, + operations, + ref rangeStart, + ref nextLayerTargetId, + out error)) { encodedScene = WebGPUEncodedScene.Empty; return false; @@ -445,7 +458,17 @@ private static bool TryEncodeOrderedOperations( operations.Add(new WebGPUSceneOperation(layerBounds, layerTargetId, targetId)); SceneEncodingCheckpoint childStart = encoding.BeginPackedIndependentRange(layerBounds, ClipBlendMode, replayActiveClips: true); - if (!TryEncodeOrderedOperations(scene, i + 1, layerEnd, layerBounds, layerTargetId, ref encoding, operations, ref childStart, ref nextLayerTargetId, out error)) + if (!TryEncodeOrderedOperations( + scene, + i + 1, + layerEnd, + layerBounds, + layerTargetId, + ref encoding, + operations, + ref childStart, + ref nextLayerTargetId, + out error)) { return false; } @@ -598,7 +621,12 @@ private static bool TryAppendApplyCommand( IWebGPUShaderEffectSource? shaderEffect = source.ApplyEffect as IWebGPUShaderEffectSource; bool hasNativeEffect = shaderEffect is not null; GpuImageSource imageSource = hasNativeEffect - ? new GpuImageSource(new Size(outputRect.Width, outputRect.Height), brushOffset, WrapMode.Repeat, WrapMode.Repeat, WebGPUShaderEffectWorkingTexture.Descriptor) + ? new GpuImageSource( + new Size(outputRect.Width, outputRect.Height), + brushOffset, + WrapMode.Repeat, + WrapMode.Repeat, + WebGPUShaderEffectWorkingTexture.Descriptor) : new GpuImageSource(new Size(outputRect.Width, outputRect.Height), brushOffset, WrapMode.Repeat, WrapMode.Repeat); RasterizerOptions rasterizerOptions = source.RasterizerOptions; @@ -620,7 +648,8 @@ private static bool TryAppendApplyCommand( WebGPUSceneRange drawRange = encoding.EndPackedIndependentRange(targetBounds, drawStart, closeActiveClips: true); operation = hasNativeEffect ? new WebGPUSceneOperation(new WebGPUShaderEffectSceneItem(shaderEffect!, outputRect, outputRect, source.ApplyWriteBackOffset, drawRange)) - : new WebGPUSceneOperation(new WebGPUApplySceneItem(source.ApplyOperation, source.ApplyEffect, outputRect, outputRect, source.ApplyWriteBackOffset, drawRange)); + : new WebGPUSceneOperation( + new WebGPUApplySceneItem(source.ApplyOperation, source.ApplyEffect, outputRect, outputRect, source.ApplyWriteBackOffset, drawRange)); error = null; return true; } @@ -1101,7 +1130,10 @@ public static SceneEncodingPlan Create( partitionIndex => { int commandStart = partitionBoundaries is null ? (partitionIndex * commandCount) / partitionCount : partitionBoundaries[partitionIndex]; - int commandEnd = partitionBoundaries is null ? ((partitionIndex + 1) * commandCount) / partitionCount : partitionBoundaries[partitionIndex + 1]; + int commandEnd = partitionBoundaries is null + ? ((partitionIndex + 1) * commandCount) / partitionCount + : partitionBoundaries[partitionIndex + 1]; + SceneCapacityEstimate estimate = default; for (int i = commandStart; i < commandEnd; i++) @@ -2541,7 +2573,8 @@ private void PadPathTagsToSegmentBoundary() /// The scene transform to classify. /// when the transform is a pure translation; otherwise . private static bool IsProfileCompatibleTransform(in GpuSceneTransform transform) - => transform.M11 == 1F && transform.M22 == 1F && transform.M12 == 0F && transform.M21 == 0F && transform.M14 == 0F && transform.M24 == 0F && transform.M44 == 1F; + => transform.M11 == 1F && transform.M22 == 1F && transform.M12 == 0F && transform.M21 == 0F && + transform.M14 == 0F && transform.M24 == 0F && transform.M44 == 1F; /// /// Encodes one visible fill command into the path, draw, style, and auxiliary payload streams. @@ -4628,13 +4661,16 @@ public static WebGPUEncodedScene Resolve( if (partition.GradientRowCount > 0) { - partition.GradientPixels.CopyTo(gradientPixelsOwner!.Memory.Span.Slice(gradientPixelOffset, partition.GradientRowCount * GradientRowWordCount)); + partition.GradientPixels.CopyTo( + gradientPixelsOwner!.Memory.Span.Slice(gradientPixelOffset, partition.GradientRowCount * GradientRowWordCount)); + gradientPixelOffset += partition.GradientRowCount * GradientRowWordCount; } if (partition.PathGradientDataWordCount > 0) { - partition.PathGradientData.CopyTo(pathGradientDataOwner!.Memory.Span.Slice(pathGradientDataOffset, partition.PathGradientDataWordCount)); + partition.PathGradientData.CopyTo( + pathGradientDataOwner!.Memory.Span.Slice(pathGradientDataOffset, partition.PathGradientDataWordCount)); } gradientRowOffset += partition.GradientRowCount; @@ -5991,7 +6027,13 @@ private static int EncodeRectangleClipPath( continue; } - AppendRectangleContour(new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height), translateX, translateY, ref pathTags, ref pathData); + AppendRectangleContour( + new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height), + translateX, + translateY, + ref pathTags, + ref pathData); + lineCount += 2; } @@ -7770,7 +7812,9 @@ public ReadOnlyMemory SceneData /// pixels wide, matching the encoder's fixed gradient ramp width. /// public ReadOnlyMemory GradientPixels - => this.gradientPixelsOwner is null ? ReadOnlyMemory.Empty : this.gradientPixelsOwner.Memory[..(this.GradientRowCount * WebGPUSceneEncoder.GradientRowWordCount)]; + => this.gradientPixelsOwner is null + ? ReadOnlyMemory.Empty + : this.gradientPixelsOwner.Memory[..(this.GradientRowCount * WebGPUSceneEncoder.GradientRowWordCount)]; /// /// Gets the encoded path-gradient payload stream. diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUSceneOperations.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUSceneOperations.cs index 04503e2b..4874a501 100644 --- a/src/ImageSharp.Drawing.WebGPU/WebGPUSceneOperations.cs +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUSceneOperations.cs @@ -450,7 +450,12 @@ internal sealed class WebGPUShaderEffectSceneItem /// The bounds within which the working texture is written back. /// The offset subtracted from when reading source pixels. /// The encoded working-texture write-back range. - public WebGPUShaderEffectSceneItem(IWebGPUShaderEffectSource effect, Rectangle inputRect, Rectangle outputRect, Point readOffset, WebGPUSceneRange drawRange) + public WebGPUShaderEffectSceneItem( + IWebGPUShaderEffectSource effect, + Rectangle inputRect, + Rectangle outputRect, + Point readOffset, + WebGPUSceneRange drawRange) { this.Effect = effect; this.InputRect = inputRect; diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUSceneResources.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUSceneResources.cs index d0180ef2..bf4b879e 100644 --- a/src/ImageSharp.Drawing.WebGPU/WebGPUSceneResources.cs +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUSceneResources.cs @@ -158,7 +158,14 @@ private static bool TryCreateCore( return false; } - if (!TryCreateImageAtlasTexture(flushContext, scene, range, flushContext.TextureFormat, externalTextureView, out WGPUTextureViewImpl* imageAtlasTextureView, out error)) + if (!TryCreateImageAtlasTexture( + flushContext, + scene, + range, + flushContext.TextureFormat, + externalTextureView, + out WGPUTextureViewImpl* imageAtlasTextureView, + out error)) { return false; } @@ -166,7 +173,11 @@ private static bool TryCreateCore( // Compute byte lengths for the two variable-size data buffers. The combined // info/bin-data buffer is laid out as [info | brush data | bin data | bin headers], // matching config.bin_data_start and config.brush_data_base in Shared/config.wgsl. - nuint infoBinDataByteLength = checked(GetBindingByteLength(infoWordCount + scene.PathGradientDataWordCount) + config.BufferSizes.BinData.ByteLength + config.BufferSizes.BinHeaders.ByteLength); + nuint infoBinDataByteLength = checked( + GetBindingByteLength(infoWordCount + scene.PathGradientDataWordCount) + + config.BufferSizes.BinData.ByteLength + + config.BufferSizes.BinHeaders.ByteLength); + nuint sceneByteLength = GetBindingByteLength(scene.SceneWordCount); // Reuse arena buffers if all capacities fit this scene. diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUShaderModuleSource.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUShaderModuleSource.cs index 1bd4fc2a..ae9dacac 100644 --- a/src/ImageSharp.Drawing.WebGPU/WebGPUShaderModuleSource.cs +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUShaderModuleSource.cs @@ -123,7 +123,8 @@ public static WebGPUShaderModuleSource Create( _ = builder.AppendLine("@group(0) @binding(3) var imagesharp_filtering_sampler: sampler;"); _ = builder.AppendLine(); _ = builder.AppendLine("fn imagesharp_layer_load_scaled(position: vec2) -> vec4 {"); - _ = builder.AppendLine(" if (any(position < imagesharp_framework.imagesharp_valid_min) || any(position >= imagesharp_framework.imagesharp_valid_max)) {"); + _ = builder.AppendLine( + " if (any(position < imagesharp_framework.imagesharp_valid_min) || any(position >= imagesharp_framework.imagesharp_valid_max)) {"); _ = builder.AppendLine(" return vec4(0.0);"); _ = builder.AppendLine(" }"); _ = builder.AppendLine(); @@ -233,10 +234,13 @@ public static WebGPUShaderModuleSource Create( _ = builder.AppendLine(" let imagesharp_minimum = vec2(floor(imagesharp_texel_position));"); _ = builder.AppendLine(" let imagesharp_maximum = imagesharp_minimum + vec2(1);"); _ = builder.AppendLine(); - _ = builder.AppendLine(" if (all(imagesharp_minimum >= imagesharp_framework.imagesharp_valid_min) && all(imagesharp_maximum < imagesharp_framework.imagesharp_valid_max)) {"); + _ = builder.AppendLine( + " if (all(imagesharp_minimum >= imagesharp_framework.imagesharp_valid_min) && " + + "all(imagesharp_maximum < imagesharp_framework.imagesharp_valid_max)) {"); _ = builder.AppendLine(" let imagesharp_source_position = vec2(imagesharp_framework.imagesharp_source_origin) + position;"); _ = builder.AppendLine(" let imagesharp_coordinate = imagesharp_source_position / vec2(textureDimensions(imagesharp_source));"); - _ = builder.AppendLine(" let imagesharp_native = textureSampleLevel(imagesharp_source, imagesharp_filtering_sampler, imagesharp_coordinate, 0.0);"); + _ = builder.AppendLine( + " let imagesharp_native = textureSampleLevel(imagesharp_source, imagesharp_filtering_sampler, imagesharp_coordinate, 0.0);"); if (sourceDescriptor.NumericEncoding == WebGPUTargetNumericEncoding.SignedUnit) { @@ -342,7 +346,9 @@ private static void AppendBorderCoordinateFunction( _ = builder.AppendLine(" let imagesharp_period = imagesharp_extent * 2;"); _ = builder.AppendLine(" let imagesharp_remainder = (value - imagesharp_minimum) % imagesharp_period;"); _ = builder.AppendLine(" let imagesharp_offset = (imagesharp_remainder + imagesharp_period) % imagesharp_period;"); - _ = builder.AppendLine(" let imagesharp_reflected = select(imagesharp_period - 1 - imagesharp_offset, imagesharp_offset, imagesharp_offset < imagesharp_extent);"); + _ = builder.AppendLine( + " let imagesharp_reflected = select(imagesharp_period - 1 - imagesharp_offset, " + + "imagesharp_offset, imagesharp_offset < imagesharp_extent);"); _ = builder.AppendLine(" return imagesharp_minimum + imagesharp_reflected;"); break; @@ -355,7 +361,8 @@ private static void AppendBorderCoordinateFunction( _ = builder.AppendLine(" let imagesharp_period = (imagesharp_extent * 2) - 2;"); _ = builder.AppendLine(" let imagesharp_remainder = (value - imagesharp_minimum) % imagesharp_period;"); _ = builder.AppendLine(" let imagesharp_offset = (imagesharp_remainder + imagesharp_period) % imagesharp_period;"); - _ = builder.AppendLine(" let imagesharp_reflected = select(imagesharp_period - imagesharp_offset, imagesharp_offset, imagesharp_offset < imagesharp_extent);"); + _ = builder.AppendLine( + " let imagesharp_reflected = select(imagesharp_period - imagesharp_offset, imagesharp_offset, imagesharp_offset < imagesharp_extent);"); _ = builder.AppendLine(" return imagesharp_minimum + imagesharp_reflected;"); break; } diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUShaderProgram.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUShaderProgram.cs index b73f925e..41ce5fd1 100644 --- a/src/ImageSharp.Drawing.WebGPU/WebGPUShaderProgram.cs +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUShaderProgram.cs @@ -23,7 +23,10 @@ internal sealed class WebGPUShaderProgram private readonly object moduleSourceSync = new(); private readonly Dictionary< - (PixelAlphaRepresentation AlphaRepresentation, WebGPUTargetNumericEncoding NumericEncoding, BorderWrappingMode? XBorderMode, BorderWrappingMode? YBorderMode), + (PixelAlphaRepresentation AlphaRepresentation, + WebGPUTargetNumericEncoding NumericEncoding, + BorderWrappingMode? XBorderMode, + BorderWrappingMode? YBorderMode), WebGPUShaderModuleSource> moduleSources = []; /// diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUShaderSourceValidator.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUShaderSourceValidator.cs index c20f7dd8..20ebf86a 100644 --- a/src/ImageSharp.Drawing.WebGPU/WebGPUShaderSourceValidator.cs +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUShaderSourceValidator.cs @@ -411,7 +411,9 @@ public static void Validate(string source, string parameterName) bool isPublicUniformBinding = name.SequenceEqual("imagesharp_uniforms"); if (hasReservedPrefix && (!isPublicUniformBinding || isDeclarationName)) { - throw new ArgumentException("WGSL identifiers beginning with 'imagesharp_' are reserved by ImageSharp; only imagesharp_uniforms may be read by user source.", parameterName); + throw new ArgumentException( + "WGSL identifiers beginning with 'imagesharp_' are reserved by ImageSharp; only imagesharp_uniforms may be read by user source.", + parameterName); } // Function parameters and structure members have no declaration keyword. Retain diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUShaderUniformLayout.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUShaderUniformLayout.cs index c8ed499d..498b5c41 100644 --- a/src/ImageSharp.Drawing.WebGPU/WebGPUShaderUniformLayout.cs +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUShaderUniformLayout.cs @@ -94,7 +94,9 @@ public WebGPUShaderUniformLayout(ReadOnlySpan uniforms) // limit here keeps every successfully constructed program portable across conforming devices. if (this.ByteLength > MaximumByteLength) { - throw new ArgumentException($"The uniform layout requires {this.ByteLength} bytes; WebGPU layer-effect uniforms are limited to {MaximumByteLength} bytes.", nameof(uniforms)); + throw new ArgumentException( + $"The uniform layout requires {this.ByteLength} bytes; WebGPU layer-effect uniforms are limited to {MaximumByteLength} bytes.", + nameof(uniforms)); } this.WgslStructureDeclaration = CreateWgslStructureDeclaration(this.members); diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUSurfaceResources.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUSurfaceResources.cs index f3dc8af7..72eaa28e 100644 --- a/src/ImageSharp.Drawing.WebGPU/WebGPUSurfaceResources.cs +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUSurfaceResources.cs @@ -666,7 +666,8 @@ private static void NegotiateSurfaceConfiguration( TextureUsage supportedUsage = (TextureUsage)capabilities.usages; if ((supportedUsage & RequiredSurfaceUsage) != RequiredSurfaceUsage) { - throw new NotSupportedException($"The WebGPU surface does not support the texture usages required by the drawing backend. Supported usages: '{supportedUsage}'."); + throw new NotSupportedException( + $"The WebGPU surface does not support the texture usages required by the drawing backend. Supported usages: '{supportedUsage}'."); } // A same-format native copy avoids the presentation shader entirely. Surfaces that @@ -716,7 +717,10 @@ private static void NegotiateSurfaceConfiguration( foreach (WebGPUTextureFormat fallbackFormat in fallbackFormats) { - WebGPUDrawingBackend.GetCompositeTextureFormatInfo(fallbackFormat, out WGPUTextureFormat fallbackNativeFormat, out WGPUFeatureName fallbackFeature); + WebGPUDrawingBackend.GetCompositeTextureFormatInfo( + fallbackFormat, + out WGPUTextureFormat fallbackNativeFormat, + out WGPUFeatureName fallbackFeature); if (fallbackFeature != default && !api.AdapterHasFeature(adapter, fallbackFeature)) { diff --git a/src/ImageSharp.Drawing/Barcodes/AlphanumericCharacterSet.cs b/src/ImageSharp.Drawing/Barcodes/AlphanumericCharacterSet.cs new file mode 100644 index 00000000..7e50de50 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/AlphanumericCharacterSet.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The 43 character alphanumeric set that several bar code standards define identically: the digits, the capital letters, the space and the symbols +/// -.$/+%. ISO/IEC 16388 Table A.1 gives it for Code 39, ANSI/AIM BC5-1995 gives the same 43 as the first part of the Code 93 set, and the Health +/// Industry Bar Code standard gives the same again. Each standard values a character by its position in the set, and each takes its check character modulo the +/// set size. +/// +internal static class AlphanumericCharacterSet +{ + /// + /// The characters in value order, so the value of a character is its index. + /// + private const string Characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%"; + + /// + /// The characters of the set, for one vectorized search over a whole text. + /// + private static readonly SearchValues Set = SearchValues.Create(Characters); + + /// + /// Returns the index of the first character of a text that is outside the set. + /// + /// The text. + /// The index, or -1 when the set holds every character. + public static int IndexOfAnyExcept(ReadOnlySpan text) => text.IndexOfAnyExcept(Set); + + /// + /// Gets the value the set assigns the given code point, or a negative number when the code point is outside the set. Taking a code point rather than a + /// UTF-16 unit keeps a surrogate pair from truncating onto a character the set carries. + /// + /// The code point to value. + /// The value, or a negative number. + public static int Value(int codePoint) => codePoint switch + { + >= '0' and <= '9' => codePoint - '0', + >= 'A' and <= 'Z' => codePoint - 'A' + 10, + '-' => 36, + '.' => 37, + ' ' => 38, + '$' => 39, + '/' => 40, + '+' => 41, + '%' => 42, + _ => -1, + }; + + /// + /// Returns the check character of a sum of character values: the character whose value is the sum modulo the set size. + /// + /// The sum of the values of the characters the check character covers. + /// The check character. + public static char CheckCharacter(int sum) => Characters[sum % Characters.Length]; +} diff --git a/src/ImageSharp.Drawing/Barcodes/Aztec/AztecCodeCompactSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Aztec/AztecCodeCompactSymbology.cs new file mode 100644 index 00000000..d48e8165 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Aztec/AztecCodeCompactSymbology.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Compact Aztec Code, ISO/IEC 24778: an Aztec Code symbol in the compact format, 15 x 15 to 27 x 27 modules with a 2-ring bullseye and 1 to 4 data layers. By +/// default the symbol is the smallest compact symbol that holds the data with 23 percent of its capacity plus 3 codewords for error correction. +/// +public sealed class AztecCodeCompactSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class at the recommended error correction level of 23 percent. + /// + public AztecCodeCompactSymbology() + : this(AztecEncoder.DefaultErrorCorrectionPercentage) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The minimum percentage of the symbol capacity that error correction takes, 5 to 95. Three more check codewords + /// are always added. + public AztecCodeCompactSymbology(int errorCorrectionPercentage) + { + Guard.MustBeBetweenOrEqualTo(errorCorrectionPercentage, 5, 95, nameof(errorCorrectionPercentage)); + this.ErrorCorrectionPercentage = errorCorrectionPercentage; + } + + /// + /// Initializes a new instance of the class with a fixed number of data layers. + /// + /// The minimum percentage of the symbol capacity that error correction takes, 5 to 95. Three more check codewords + /// are always added. + /// The number of data layers, 1 to 4. + public AztecCodeCompactSymbology(int errorCorrectionPercentage, int layers) + : this(errorCorrectionPercentage) + { + Guard.MustBeBetweenOrEqualTo(layers, 1, 4, nameof(layers)); + this.Layers = layers; + } + + /// + /// Gets the minimum error correction percentage. + /// + public int ErrorCorrectionPercentage { get; } + + /// + /// Gets the number of data layers, or when the symbol is the smallest compact symbol that holds the data. + /// + public int? Layers { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + // Clause 12 of ISO/IEC 24778: "This symbology specification places no limits on the size of X." + AztecEncoder.BuildSymbol( + text, + AztecFormat.Compact, + this.ErrorCorrectionPercentage, + this.Layers ?? 0, + options.ResolveXDimension(0, DefaultMatrixXDimension, double.PositiveInfinity), + ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Aztec/AztecCodeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Aztec/AztecCodeSymbology.cs new file mode 100644 index 00000000..c9b8b56c --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Aztec/AztecCodeSymbology.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Aztec Code symbology, ISO/IEC 24778: a square matrix of 15 x 15 to 151 x 151 modules around a bullseye finder, with no quiet zone. It carries the +/// characters U+0000 to U+00FF with Reed-Solomon error correction. By default the symbol is the smallest that holds the data with 23 percent of its capacity +/// plus 3 codewords for error correction: a compact symbol of 1 to 4 layers, or a full-range symbol of 4 to 32 layers. +/// +public sealed class AztecCodeSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class at the recommended error correction level of 23 percent. + /// + public AztecCodeSymbology() + : this(AztecEncoder.DefaultErrorCorrectionPercentage) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The minimum percentage of the symbol capacity that error correction takes, 5 to 95. Three more check codewords + /// are always added. + public AztecCodeSymbology(int errorCorrectionPercentage) + { + Guard.MustBeBetweenOrEqualTo(errorCorrectionPercentage, 5, 95, nameof(errorCorrectionPercentage)); + this.ErrorCorrectionPercentage = errorCorrectionPercentage; + } + + /// + /// Initializes a new instance of the class with a full-range symbol of a fixed number of data layers. + /// + /// The minimum percentage of the symbol capacity that error correction takes, 5 to 95. Three more check codewords + /// are always added. + /// The number of data layers, 1 to 32. + public AztecCodeSymbology(int errorCorrectionPercentage, int layers) + : this(errorCorrectionPercentage) + { + Guard.MustBeBetweenOrEqualTo(layers, 1, 32, nameof(layers)); + this.Layers = layers; + } + + /// + /// Gets the minimum error correction percentage. + /// + public int ErrorCorrectionPercentage { get; } + + /// + /// Gets the number of data layers of the full-range symbol, or when the symbol is the smallest that holds the data. + /// + public int? Layers { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + // Clause 12 of ISO/IEC 24778: "This symbology specification places no limits on the size of X." + AztecEncoder.BuildSymbol( + text, + this.Layers is null ? AztecFormat.Auto : AztecFormat.Full, + this.ErrorCorrectionPercentage, + this.Layers ?? 0, + options.ResolveXDimension(0, DefaultMatrixXDimension, double.PositiveInfinity), + ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Aztec/AztecEncoder.cs b/src/ImageSharp.Drawing/Barcodes/Aztec/AztecEncoder.cs new file mode 100644 index 00000000..0c159e66 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Aztec/AztecEncoder.cs @@ -0,0 +1,1253 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodes Aztec Code symbols, ISO/IEC 24778. The text becomes a bit stream in the five code sets and the byte shift of Table 2. The bit stream becomes B-bit +/// codewords with no codeword of all zeros or all ones. Reed-Solomon check codewords fill the symbol. The codewords spiral outward from the Core Symbol in +/// reverse order. The mode search finds the shortest sequence of latches, shifts and characters for the whole message. +/// +internal static class AztecEncoder +{ + /// + /// The recommended error correction level of section 11.3: "23% of symbol capacity plus 3 codewords more". + /// + public const int DefaultErrorCorrectionPercentage = 23; + + /// + /// The longest input. The largest symbol holds 1664 codewords of 12 bits, 19968 bits, and no character takes fewer than 2.5 bits: the punctuation pairs of + /// Table 2 put two characters in 5 bits. + /// + public const int MaximumLength = 19968 * 2 / 5; + + /// + /// The check codewords added to the percentage, section 11.3: "plus always, for data security, three additional checkwords". + /// + private const int ExtraCheckCodewords = 3; + + /// + /// The most bytes one B/S carries, section 7.3.2.2: 31 with a 5-bit length, or 31 more than the 11-bit length, at most 2047. + /// + private const int MaximumByteRun = 31 + 2047; + + /// + /// The number of states of the search: the five code sets of Table 2 and the byte state. + /// + private const int StateCount = 6; + + /// + /// The number of code sets that hold characters: every state but . + /// + private const int CharacterSetCount = 5; + + /// + /// The number of compact sizes, 1 to 4 layers, which lead . + /// + private const int CompactSizeCount = 4; + + /// + /// The cost of a state that the search has not reached. + /// + private const int Unreached = 1_000_000; + + /// + /// The sequence element of U/L, the latch to the upper code set. + /// + private const int LatchUpper = -2; + + /// + /// The sequence element of L/L, the latch to the lower code set. + /// + private const int LatchLower = -3; + + /// + /// The sequence element of M/L, the latch to the mixed code set. + /// + private const int LatchMixed = -4; + + /// + /// The sequence element of P/L, the latch to the punctuation code set. + /// + private const int LatchPunct = -5; + + /// + /// The sequence element of D/L, the latch to the digit code set. + /// + private const int LatchDigit = -6; + + /// + /// The sequence element of U/S, the shift to the upper code set for one character. + /// + private const int ShiftUpper = -7; + + /// + /// The sequence element of P/S, the shift to the punctuation code set for one character. + /// + private const int ShiftPunct = -8; + + /// + /// The sequence element of B/S, the shift to the byte state. + /// + private const int ShiftBinary = -9; + + /// + /// The sequence element of the punctuation pair CR LF. + /// + private const int PairCrLf = -11; + + /// + /// The sequence element of the punctuation pair period and space. + /// + private const int PairPeriodSpace = -12; + + /// + /// The sequence element of the punctuation pair comma and space. + /// + private const int PairCommaSpace = -13; + + /// + /// The sequence element of the punctuation pair colon and space. + /// + private const int PairColonSpace = -14; + + /// + /// The field of the mode message, section 7.2.4: GF(16) with the prime modulus polynomial x^4 + x + 1. + /// + private static readonly GaloisField ModeField = new(16, 19); + + /// + /// The field of the 6-bit data codewords, Table 3: GF(64) with the polynomial 67. Each data field builds its tables on first use, so a symbol builds only + /// the field of its codeword size. The GF(4096) tables alone hold 12286 ints. + /// + private static readonly Lazy Field6 = new(() => new GaloisField(64, 67)); + + /// + /// The field of the 8-bit data codewords, Table 3: GF(256) with the polynomial 301. + /// + private static readonly Lazy Field8 = new(() => new GaloisField(256, 301)); + + /// + /// The field of the 10-bit data codewords, Table 3: GF(1024) with the polynomial 1033. + /// + private static readonly Lazy Field10 = new(() => new GaloisField(1024, 1033)); + + /// + /// The field of the 12-bit data codewords, Table 3. + /// + private static readonly Lazy Field12 = new(() => new GaloisField(4096, 4201)); + + /// + /// The states of the search, in the row and column order of the latch and shift tables: the five code sets of Table 2, then the byte state of a B/S. + /// + private enum CodeSet + { + /// + /// The upper code set of Table 2. + /// + Upper, + + /// + /// The lower code set of Table 2. + /// + Lower, + + /// + /// The mixed code set of Table 2. + /// + Mixed, + + /// + /// The punctuation code set of Table 2. + /// + Punct, + + /// + /// The digit code set of Table 2. + /// + Digit, + + /// + /// The byte state of a B/S. + /// + Binary, + } + + /// + /// Gets the bits each code set writes per character, latch or shift: 5, and 4 in the digit code set, and 8 for each byte of a byte shift. + /// + private static ReadOnlySpan CharacterBits => [5, 5, 5, 5, 4, 8]; + + /// + /// Gets the bits of the cheapest latch from each state, row, to each state, column. A latch into the byte state is a B/S and its 5-bit length, and the + /// return from it costs nothing because the length ends it. + /// + private static ReadOnlySpan LatchBits => + [ + 0, 5, 5, 10, 5, 10, + 9, 0, 5, 10, 5, 10, + 5, 5, 0, 5, 10, 10, + 5, 10, 10, 0, 10, 15, + 4, 9, 9, 14, 0, 14, + 0, 0, 0, 0, 0, 0, + ]; + + /// + /// Gets the latch codes of each latch of , up to three each, 0 where none. + /// + private static ReadOnlySpan LatchCodes => + [ + 0, 0, 0, LatchLower, 0, 0, LatchMixed, 0, 0, LatchMixed, LatchPunct, 0, LatchDigit, 0, 0, ShiftBinary, 0, 0, + LatchDigit, LatchUpper, 0, 0, 0, 0, LatchMixed, 0, 0, LatchMixed, LatchPunct, 0, LatchDigit, 0, 0, ShiftBinary, 0, 0, + LatchUpper, 0, 0, LatchLower, 0, 0, 0, 0, 0, LatchPunct, 0, 0, LatchUpper, LatchDigit, 0, ShiftBinary, 0, 0, + LatchUpper, 0, 0, LatchUpper, LatchLower, 0, LatchUpper, LatchMixed, 0, 0, 0, 0, LatchUpper, LatchDigit, 0, LatchUpper, ShiftBinary, 0, + LatchUpper, 0, 0, LatchUpper, LatchLower, 0, LatchUpper, LatchMixed, 0, LatchUpper, LatchMixed, LatchPunct, 0, 0, 0, LatchUpper, ShiftBinary, 0, + LatchUpper, 0, 0, LatchLower, 0, 0, LatchMixed, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + + /// + /// Gets the bits of a shift from each state, row, to each state, column: U/S from the lower and digit code sets and P/S from every code set but + /// punctuation. Unreached marks no shift. + /// + private static ReadOnlySpan ShiftBits => + [ + Unreached, Unreached, Unreached, 5, Unreached, + 5, Unreached, Unreached, 5, Unreached, + Unreached, Unreached, Unreached, 5, Unreached, + Unreached, Unreached, Unreached, Unreached, Unreached, + 4, Unreached, Unreached, 4, Unreached, + ]; + + /// + /// Gets the codeword count of Table 1 for each symbol in the order of section 11.5: the compact symbols of 1 to 4 layers, then the full-range symbols of 1 + /// to 32 layers. + /// + private static ReadOnlySpan SymbolCodewords => + [ + 17, 40, 51, 76, + 21, 48, 60, 88, 120, 156, 196, 240, 230, 272, 316, 364, 416, 470, 528, 588, 652, 720, 790, 864, 940, 1020, + 920, 992, 1066, 1144, 1224, 1306, 1392, 1480, 1570, 1664, + ]; + + /// + /// Gets the orientation patterns of section 7.1.3 for a finder of half width F, as x and y offsets from F and the module: the six dark modules, then the + /// six light ones. An offset of 1 is F + 1, 0 is F, and the signs follow. + /// + private static ReadOnlySpan Orientation => + [ + -2, 1, 1, -2, 2, 1, -1, 2, 1, 2, 2, 1, 2, 1, 1, 2, -1, 1, + 1, 2, 0, 2, -2, 0, 1, -2, 0, -1, -2, 0, -2, -2, 0, -2, -1, 0, + ]; + + /// + /// Gets the value of each ASCII character in each code set of Table 2, at index code set x 128 + character, or 0xFF where the code set has no such + /// character. The upper, lower, mixed, punctuation and digit code sets follow in turn, 128 characters each. No code set holds a byte above 127. + /// + private static ReadOnlySpan CharacterValues => + [ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 1, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 1, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 255, 255, 255, 255, 255, + 255, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 15, 16, 17, 18, 19, + 1, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 20, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 21, 255, 22, 23, + 24, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 25, 255, 26, 27, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 1, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 21, 22, 23, 24, 25, 26, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 27, 255, 28, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 29, 255, 30, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 1, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 12, 255, 13, 255, + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + ]; + + /// + /// Builds an Aztec Code symbol. + /// + /// The text, characters U+0000 to U+00FF. + /// The symbol format. + /// The minimum error correction percentage, 5 to 95. + /// The number of data layers, or 0 for the smallest symbol that holds the data. + /// The X dimension in millimetres. + /// The symbol to write. + /// The text is empty, has a character above U+00FF, or does not fit. + public static void BuildSymbol( + ReadOnlySpan text, + AztecFormat format, + int errorCorrectionPercentage, + int layers, + double xDimension, + ref BarcodeSymbol symbol) + { + if (text.IsEmpty) + { + throw new ArgumentException("Aztec Code needs at least one character.", nameof(text)); + } + + Guard.MustBeLessThanOrEqualTo(text.Length, MaximumLength, nameof(text)); + + // Section 4.1 a): "All 8-bit values can be encoded", with values 128 to 255 read as ISO/IEC 8859-1. + int invalid = text.IndexOfAnyExceptInRange('\0', '\u00FF'); + if (invalid >= 0) + { + throw new ArgumentException($"Aztec Code encodes U+0000 to U+00FF; got {text.CodePointAt(invalid).ToDisplayString()}.", nameof(text)); + } + + PooledBuffer bits = default; + PooledBuffer codewords = default; + try + { + int bitCount = EncodeBits(text, ref bits); + ReadOnlySpan stream = bits.Span[..bitCount]; + + // The codeword count after stuffing depends only on the codeword size, so the encoder lays out each size once. Every symbol of that size reads the + // count. + InlineArray14 dataCountsStorage = default; + Span dataCounts = ((Span)dataCountsStorage)[..13]; + dataCounts.Fill(-1); + + SymbolSize chosen = default; + bool found = false; + int start = format == AztecFormat.Full ? CompactSizeCount : 0; + int end = format == AztecFormat.Compact ? CompactSizeCount : SymbolCodewords.Length; + for (int index = start; index < end; index++) + { + SymbolSize size = SymbolSize.FromIndex(index); + + // Section 11.5: printing programs "print the smallest symbol for a given data set and error correction percentage in the data layer sequence + // 1*,2*,3*,4*,4,5,6,...,32". Full-range symbols of 1 to 3 layers are "useful only for reader initialization", so the search skips them unless + // the caller asks for that size. + if (layers > 0 ? size.Layers != layers : (format == AztecFormat.Auto && !size.IsCompact && size.Layers < 4)) + { + continue; + } + + int total = size.Codewords; + int codewordBits = size.CodewordBits; + if (dataCounts[codewordBits] < 0) + { + dataCounts[codewordBits] = Stuff(stream, codewordBits, []); + } + + // Section 11.3: the percentage of the symbol capacity plus three codewords is the minimum number of check codewords. + int check = (((total * errorCorrectionPercentage) + 99) / 100) + ExtraCheckCodewords; + int data = dataCounts[codewordBits]; + if (data + check <= total && data <= size.MaximumDataCodewords) + { + chosen = size; + found = true; + break; + } + } + + if (!found) + { + throw new ArgumentException("The text does not fit in an Aztec Code symbol of the requested size.", nameof(text)); + } + + codewords = new PooledBuffer(chosen.Codewords); + Span all = codewords.Span; + int dataCount = Stuff(stream, chosen.CodewordBits, all); + AddCheckCodewords(all, dataCount, chosen.CodewordBits); + + InlineArray64 modeStorage = default; + Span mode = ((Span)modeStorage)[..40]; + int modeLength = WriteModeMessage(chosen, dataCount, false, mode); + Layout(chosen, all, mode[..modeLength], xDimension, ref symbol); + } + finally + { + bits.Dispose(); + codewords.Dispose(); + } + } + + /// + /// Builds an Aztec Rune: the Core Symbol of a compact symbol whose mode message carries one value of 0 to 255. + /// + /// The value, 0 to 255. + /// The X dimension in millimetres. + /// The symbol to write. + public static void BuildRune(int value, double xDimension, ref BarcodeSymbol symbol) + { + InlineArray32 modeStorage = default; + Span mode = ((Span)modeStorage)[..28]; + SymbolSize core = new(true, 0); + WriteModeMessage(core, value, true, mode); + Layout(core, [], mode, xDimension, ref symbol); + } + + /// + /// Translates the text into the bit stream of section 7.3.2.2. The search keeps, for each code set, the cheapest sequence of characters, latches and shifts + /// that ends in it. The sequences share their common start: each is a node that points back to the element before it, so a copy is one index and an + /// extension is one new node. + /// + /// The text. + /// The buffer the bits are written to, one bit per byte, rented here. + /// The number of bits. + private static int EncodeBits(ReadOnlySpan text, ref PooledBuffer bits) + { + NodeArena arena = new(text.Length * 8); + try + { + // The costs and sequence heads of each state, indexed by CodeSet, before and after the current character. + InlineArray6 currentCostStorage = default; + Span currentCost = currentCostStorage; + InlineArray6 nextCostStorage = default; + Span nextCost = nextCostStorage; + InlineArray6 currentHeadStorage = default; + Span currentHead = currentHeadStorage; + InlineArray6 nextHeadStorage = default; + Span nextHead = nextHeadStorage; + InlineArray16 afterStorage = default; + Span after = afterStorage; + currentCost.Fill(Unreached); + currentCost[(int)CodeSet.Upper] = 0; + currentHead.Fill(-1); + + // Encoding starts in the upper code set. The byte state returns to the code set it shifted from, which the punctuation and digit code sets reach + // through upper. + CodeSet backTo = CodeSet.Upper; + int lastCharacter = -1; + for (int t = 0; t < text.Length; t++) + { + int character = text[t]; + + // A latch can make the sequence of another state cheaper. Each improvement can enable another, so the pass repeats until none applies. + bool improved; + do + { + improved = false; + for (CodeSet x = CodeSet.Upper; x <= CodeSet.Binary; x++) + { + for (CodeSet y = CodeSet.Upper; y <= CodeSet.Binary; y++) + { + if (x == CodeSet.Binary && y != backTo) + { + continue; + } + + int latch = LatchIndex(x, y); + int cost = currentCost[(int)x] + LatchBits[latch]; + if (cost < currentCost[(int)y]) + { + currentCost[(int)y] = cost; + int head = currentHead[(int)x]; + foreach (sbyte code in LatchCodes.Slice(latch * 3, 3)) + { + if (code == 0) + { + break; + } + + head = arena.Append(head, code); + } + + currentHead[(int)y] = head; + if (y == CodeSet.Binary) + { + backTo = x is CodeSet.Punct or CodeSet.Digit ? CodeSet.Upper : x; + } + + improved = true; + } + } + } + } + while (improved); + + // Each state that holds the character extends its own sequence, and the other code sets can reach it with a shift. + nextCost.Fill(Unreached); + nextHead.Fill(-1); + for (CodeSet x = CodeSet.Upper; x <= CodeSet.Binary; x++) + { + if (x != CodeSet.Binary && (character > 127 || CharacterValues[((int)x << 7) | character] == 0xFF)) + { + continue; + } + + int cost = currentCost[(int)x] + CharacterBits[(int)x]; + int head = currentHead[(int)x]; + if (x == CodeSet.Binary && head >= 0 && arena[head].Run == MaximumByteRun) + { + // A full byte run returns to the code set it shifted from and shifts again: the return marker, then a new B/S and its 5-bit length. + cost += LatchBits[LatchIndex(backTo, CodeSet.Binary)]; + head = arena.Append(arena.Append(head, LatchCodes[LatchIndex(CodeSet.Binary, backTo) * 3]), ShiftBinary); + } + + if (cost < nextCost[(int)x]) + { + nextCost[(int)x] = cost; + nextHead[(int)x] = arena.Append(head, character); + } + + if (x == CodeSet.Binary) + { + continue; + } + + for (CodeSet y = CodeSet.Upper; y < CodeSet.Binary; y++) + { + if (x == y) + { + continue; + } + + cost = currentCost[(int)y] + ShiftBits[((int)y * CharacterSetCount) + (int)x] + CharacterBits[(int)x]; + if (cost < nextCost[(int)y]) + { + nextCost[(int)y] = cost; + nextHead[(int)y] = arena.Append(arena.Append(currentHead[(int)y], x == CodeSet.Upper ? ShiftUpper : ShiftPunct), character); + } + } + } + + // A punctuation pair of Table 2 puts the previous character and this one in one 5-bit value. + int pair = Pair(lastCharacter, character); + if (pair != 0) + { + for (CodeSet state = CodeSet.Upper; state < CodeSet.Binary; state++) + { + int i = (int)state; + + // The mixed code set holds CR and the digit code set holds the comma and the period themselves, so a sequence in those states did not + // reach the first character through punctuation. + bool inPunct = !(state == CodeSet.Mixed && lastCharacter == '\r') && !(state == CodeSet.Digit && lastCharacter is ',' or '.'); + if (!inPunct || currentCost[i] >= nextCost[i]) + { + continue; + } + + // The scan walks back over the latches this step added to the previous character. The marker before it is the latch that set the code + // set it was written in, and a byte shift there means it was a byte. + bool latchDigit = false; + int found = currentHead[i]; + while (found >= 0 && arena[found].Value != lastCharacter) + { + found = arena[found].Previous; + } + + if (found >= 0) + { + int previous = arena[found].Previous; + bool shifted = previous >= 0 && arena[previous].Value == ShiftPunct; + int marker = previous >= 0 ? arena[previous].Marker : 0; + if (marker == ShiftBinary) + { + found = -1; + } + else if (marker != 0) + { + if (state == CodeSet.Punct) + { + latchDigit = marker == LatchDigit; + } + else if (marker != LatchPunct) + { + inPunct = shifted; + } + } + } + + if (!inPunct || found < 0) + { + continue; + } + + nextCost[i] = currentCost[i]; + + // The elements after the previous character are the latches this step added. The pair replaces the character where it stands, except in + // punctuation, where it follows the latches into that code set. + int count = 0; + for (int n = currentHead[i]; n != found; n = arena[n].Previous) + { + after[count++] = arena[n].Value; + } + + int rebuilt = arena[found].Previous; + if (state != CodeSet.Punct) + { + rebuilt = arena.Append(rebuilt, pair); + } + else if (latchDigit && count > 0) + { + // The character took 4 bits in the digit code set, and the pair takes 5. + nextCost[i]++; + } + + for (int c = count - 1; c >= 0; c--) + { + rebuilt = arena.Append(rebuilt, after[c]); + } + + if (state == CodeSet.Punct) + { + rebuilt = arena.Append(rebuilt, pair); + } + + nextHead[i] = rebuilt; + } + } + + // Section 7.3.2.2: a byte shift of more than 31 bytes takes an 11-bit length after a zero. + if (nextHead[(int)CodeSet.Binary] >= 0 && arena[nextHead[(int)CodeSet.Binary]].Run == 32) + { + nextCost[(int)CodeSet.Binary] += 11; + } + + nextCost.CopyTo(currentCost); + nextHead.CopyTo(currentHead); + lastCharacter = character; + } + + int best = 0; + for (int i = 1; i < StateCount; i++) + { + if (currentCost[i] < currentCost[best]) + { + best = i; + } + } + + int length = 0; + for (int n = currentHead[best]; n >= 0; n = arena[n].Previous) + { + length++; + } + + using PooledBuffer sequenceBuffer = new(length); + Span sequence = sequenceBuffer.Span; + int position = length; + for (int n = currentHead[best]; n >= 0; n = arena[n].Previous) + { + sequence[--position] = arena[n].Value; + } + + bits = new PooledBuffer(currentCost[best]); + return WriteBits(sequence, bits.Span); + } + finally + { + arena.Dispose(); + } + } + + /// + /// Writes the bits of a sequence of characters, latches and shifts, most significant bit first. + /// + /// The sequence. + /// The buffer, one bit per byte. + /// The number of bits written. + private static int WriteBits(ReadOnlySpan sequence, Span bits) + { + CodeSet state = CodeSet.Upper; + int written = 0; + int i = 0; + while (i < sequence.Length) + { + if (state != CodeSet.Binary) + { + int element = sequence[i++]; + written = Write(bits, written, Value(state, element), CharacterBits[(int)state]); + if (element is ShiftUpper or ShiftPunct) + { + CodeSet shiftState = element == ShiftUpper ? CodeSet.Upper : CodeSet.Punct; + written = Write(bits, written, Value(shiftState, sequence[i++]), 5); + } + + state = element switch + { + LatchUpper => CodeSet.Upper, + LatchLower => CodeSet.Lower, + LatchMixed => CodeSet.Mixed, + LatchPunct => CodeSet.Punct, + LatchDigit => CodeSet.Digit, + ShiftBinary => CodeSet.Binary, + _ => state, + }; + + continue; + } + + // Section 7.3.2.2: the B/S is followed by the byte count, 1 to 31 in 5 bits, or 0 and the count less 31 in 11 bits. The bytes run to the next + // element that is not a character, a latch or shift code below 0. + int byteCount = sequence[i..].IndexOfAnyExceptInRange(0, 255); + if (byteCount < 0) + { + byteCount = sequence.Length - i; + } + + if (byteCount <= 31) + { + written = Write(bits, written, byteCount, 5); + } + else + { + written = Write(bits, written, 0, 5); + written = Write(bits, written, byteCount - 31, 11); + } + + for (int b = 0; b < byteCount; b++) + { + written = Write(bits, written, sequence[i++], 8); + } + + // "At the end of the byte string, encoding returns to the mode from which B/S was invoked." The sequence marks that mode with the latch code the + // search added. + if (i < sequence.Length) + { + state = sequence[i++] switch + { + LatchLower => CodeSet.Lower, + LatchMixed => CodeSet.Mixed, + _ => CodeSet.Upper, + }; + } + } + + return written; + } + + /// + /// Lays the bit stream into codewords, section 7.3.2.3: "whenever the first B-1 bits placed in a codeword are all 0's, then a dummy 1 is inserted into that + /// codeword's LSB and the following message bit starts off the next codeword", and the same with a dummy 0 after B-1 ones. The final codeword is "padded + /// out with 1's (and possibly a final dummy 0 if necessary)". + /// + /// The bit stream. + /// The codeword size B. + /// The buffer the codewords are written to. Codewords past its end are counted and not written, so an empty buffer only + /// counts. + /// The number of codewords. + private static int Stuff(ReadOnlySpan bits, int size, Span codewords) + { + int count = 0; + int ones = (1 << size) - 1; + int position = 0; + while (position < bits.Length) + { + int codeword = 0; + if (bits.Length - position >= size) + { + for (int b = 0; b < size - 1; b++) + { + codeword = (codeword << 1) | bits[position + b]; + } + + int head = codeword; + codeword <<= 1; + if (head == 0) + { + codeword |= 1; + position += size - 1; + } + else if (head == ones >> 1) + { + position += size - 1; + } + else + { + codeword |= bits[position + size - 1]; + position += size; + } + } + else + { + int remaining = bits.Length - position; + for (int b = 0; b < size; b++) + { + codeword = (codeword << 1) | (b < remaining ? bits[position + b] : 1); + } + + if (codeword == ones) + { + codeword ^= 1; + } + + position = bits.Length; + } + + if (count < codewords.Length) + { + codewords[count] = codeword; + } + + count++; + } + + return count; + } + + /// + /// Adds the check codewords of section 7.3.3 after the data codewords: the remainder of the data polynomial times x^K divided by (x - 2^1) ... (x - 2^K) + /// over the field of Table 3. + /// + /// The data codewords followed by room for the check codewords. + /// The number of data codewords. + /// The codeword size B. + private static void AddCheckCodewords(Span codewords, int dataCount, int size) + { + GaloisField field = size switch + { + 6 => Field6.Value, + 8 => Field8.Value, + 10 => Field10.Value, + _ => Field12.Value, + }; + + int checkCount = codewords.Length - dataCount; + using PooledBuffer generatorBuffer = new(checkCount); + Span generator = generatorBuffer.Span; + field.Generator(1, generator); + field.Remainder(codewords, 1, dataCount, generator, codewords[dataCount..]); + } + + /// + /// Writes the mode message bits of section 7.2: the layer count less one and the data codeword count less one, as 4-bit datawords, followed by the GF(16) + /// check words of section 7.2.4. A rune carries its value in place of the two counts, with alternate bits of every 4-bit word inverted. + /// + /// The symbol size. + /// The number of data codewords, or the rune value. + /// Whether the symbol is a rune. + /// The buffer, one bit per byte. + /// The number of bits: 28 compact, 40 full-range. + private static int WriteModeMessage(SymbolSize size, int dataCount, bool rune, Span bits) + { + bool compact = size.IsCompact; + int dataWords = compact ? 2 : 4; + int checkWords = compact ? 5 : 6; + int layers = size.Layers; + int value = rune ? dataCount : compact ? ((layers - 1) << 6) | (dataCount - 1) : ((layers - 1) << 11) | (dataCount - 1); + + InlineArray10 wordsStorage = default; + Span words = wordsStorage; + for (int i = 0; i < dataWords; i++) + { + words[i] = (value >> ((dataWords - 1 - i) * 4)) & 0xF; + } + + InlineArray6 generatorStorage = default; + Span generator = ((Span)generatorStorage)[..checkWords]; + ModeField.Generator(1, generator); + ModeField.Remainder(words, 1, dataWords, generator, words.Slice(dataWords, checkWords)); + + int count = dataWords + checkWords; + for (int i = 0; i < count; i++) + { + int word = rune ? words[i] ^ 0b1010 : words[i]; + for (int b = 0; b < 4; b++) + { + bits[(i * 4) + b] = (byte)((word >> (3 - b)) & 1); + } + } + + return count * 4; + } + + /// + /// Places the modules. The data layers spiral outward clockwise from the upper left of the Core Symbol in dominos of two modules, section 7.3.4, taking the + /// codewords in reverse order. A full-range symbol is first laid out without its reference grid, then the grid lines of section 7.1.4 are inserted every 16 + /// modules from the centre, which moves every data module outward by the grid lines it crosses. + /// + /// The symbol size. + /// The data and check codewords. + /// The mode message bits. + /// The X dimension in millimetres. + /// The symbol to write. + private static void Layout(SymbolSize symbolSize, ReadOnlySpan codewords, ReadOnlySpan mode, double xDimension, ref BarcodeSymbol symbol) + { + bool compact = symbolSize.IsCompact; + int layers = symbolSize.Layers; + int size = symbolSize.CodewordBits; + + // The core is 11 x 11 modules in a compact symbol and 15 x 15 in a full-range symbol, finder and mode message ring included. Without the grid the + // full-range core is 14 modules wide, because the grid line through the centre is not there yet. The core width here is the finder alone: 9 modules + // in a compact symbol, and 13 less the grid line through the centre, 12, in a full-range symbol. The mode message ring is between it and layer 1. + int coreWidth = compact ? 9 : 12; + int packedSide = coreWidth + (layers * 4) + 2; + using PooledBuffer packedBuffer = new(packedSide * packedSide); + Span packed = packedBuffer.Span; + packed.Clear(); + int packedMiddle = ((packedSide - 1) / 2 * packedSide) + ((packedSide - 1) / 2); + + // The bit stream fills the layers from the inside out with the last bit first. The codewords stand at the end of the layer capacity, so the outermost + // dominos left over at the end of the spiral are light. Layer l, of width coreWidth + 4l, holds 8 (coreWidth + 4l + 4) bits, so L layers hold + // 16 L^2 + (8 coreWidth + 16) L bits. + int capacityBits = (16 * layers * layers) + (((8 * coreWidth) + 16) * layers); + int offset = capacityBits - (codewords.Length * size); + int index = 0; + for (int layer = 1; layer <= layers; layer++) + { + int layerWidth = coreWidth + (layer * 4); + for (int bit = 0; bit < layerWidth * 8; bit++) + { + int t = capacityBits - 1 - index++; + bool dark = false; + if (t >= offset) + { + int codewordBit = t - offset; + dark = ((codewords[codewordBit / size] >> (size - 1 - (codewordBit % size))) & 1) == 1; + } + + (int x, int y) = LayerPosition(coreWidth, layer, bit, layerWidth); + packed[packedMiddle + x - (y * packedSide)] = dark; + } + } + + int side = packedSide; + int finder = compact ? 4 : 6; + Span modules; + if (compact) + { + // A compact symbol has no reference grid, so the packed layout is the module grid. + modules = symbol.BeginMatrix(side, side, 0, xDimension); + packed.CopyTo(modules); + } + else + { + // Section 7.1.4: the grid occupies "every location where x is a multiple of 16 or y is a multiple of 16", each module (x + y + 1) mod 2. The grid + // lines a layer crosses add to the side: one line for each 16 modules out from the centre, on both sides. The line count is (layers + 10.5) / 7.5 - + // 1 rounded down, which integers give exactly as (2 x layers + 21) / 15 - 1. + side = coreWidth + 1 + (layers * 4) + 2 + (2 * ((((2 * layers) + 21) / 15) - 1)); + modules = symbol.BeginMatrix(side, side, 0, xDimension); + int half = side / 2; + int next = 0; + for (int row = 0; row < side; row++) + { + int y = half - row; + for (int column = 0; column < side; column++) + { + int x = column - half; + if (x % 16 == 0 || y % 16 == 0) + { + modules[(row * side) + column] = ((x + y + 1) & 1) == 1; + } + else + { + modules[(row * side) + column] = packed[next++]; + } + } + } + } + + int middle = side / 2; + + // Section 7.1.2: every module of the finder is ((max(abs(x), abs(y))) + 1) mod 2. + for (int y = -finder; y <= finder; y++) + { + for (int x = -finder; x <= finder; x++) + { + modules[((middle - y) * side) + middle + x] = ((Math.Max(Math.Abs(x), Math.Abs(y)) + 1) & 1) == 1; + } + } + + ReadOnlySpan orientation = Orientation; + for (int i = 0; i < orientation.Length; i += 3) + { + int x = Corner(orientation[i], finder); + int y = Corner(orientation[i + 1], finder); + modules[((middle - y) * side) + middle + x] = orientation[i + 2] == 1; + } + + // Section 7.2.5: the mode message starts near the upper left corner of the finder and wraps clockwise, in four runs of 7 bits in a compact symbol and + // eight runs of 5 in a full-range symbol, which skip the grid line through the centre of each side. + int ring = finder + 1; + int perSide = mode.Length / 4; + for (int i = 0; i < mode.Length; i++) + { + int side4 = i / perSide; + int along = i % perSide; + int step = compact ? along - 3 : along < 5 ? along - 5 : along - 4; + (int x, int y) = side4 switch + { + 0 => (step, ring), + 1 => (ring, -step), + 2 => (-step, -ring), + _ => (-ring, step), + }; + + modules[((middle - y) * side) + middle + x] = mode[i] == 1; + } + } + + /// + /// Returns the position of a bit of a data layer in the layout without the reference grid: two modules per domino, the more significant bit outward, + /// running along the top, down the right, back along the bottom and up the left. + /// + /// The width of the finder without the grid line through the centre. The mode message ring is outside it. + /// The layer, from 1 inside. + /// The bit within the layer. + /// The length of a side of the layer in dominos. + /// The x and y of the module, the y axis upward. + private static (int X, int Y) LayerPosition(int coreWidth, int layer, int bit, int layerWidth) + { + int along = (bit / 2) % layerWidth; + int depth = (layer * 2) + (bit % 2); + return ((bit / 2) / layerWidth) switch + { + 0 => (-((layerWidth - 1) / 2) + 1 + along, ((coreWidth - 1) / 2) + depth), + 1 => ((coreWidth / 2) + depth, ((layerWidth - 1) / 2) - 1 - along), + 2 => ((layerWidth / 2) - 1 - along, -((coreWidth / 2) + depth)), + _ => (-(((coreWidth - 1) / 2) + depth), -((layerWidth / 2) - 1 - along)), + }; + } + + /// + /// Returns a coordinate of the orientation table: F + 1, F, -F or -F - 1 for 2, 1, -1 and -2. + /// + /// The code. + /// The finder half width F. + /// The coordinate. + private static int Corner(int code, int finder) + => code switch + { + 2 => finder + 1, + 1 => finder, + -1 => -finder, + _ => -finder - 1, + }; + + /// + /// Returns the index of the latch from one state to another in , the rows by the state latched from and the columns by the state + /// latched to. Three times the index is the first latch code in . + /// + /// The state latched from. + /// The state latched to. + /// The table index. + private static int LatchIndex(CodeSet from, CodeSet to) => ((int)from * StateCount) + (int)to; + + /// + /// Returns the punctuation pair code of two characters, or 0 when they are no pair of Table 2. + /// + /// The first character, or -1. + /// The second character. + /// The pair code, or 0. + private static int Pair(int first, int second) + => (first, second) switch + { + ('\r', '\n') => PairCrLf, + ('.', ' ') => PairPeriodSpace, + (',', ' ') => PairCommaSpace, + (':', ' ') => PairColonSpace, + _ => 0, + }; + + /// + /// Returns the Table 2 value of a sequence element in a code set. + /// + /// The code set. + /// The character, latch, shift or pair. + /// The value. + private static int Value(CodeSet state, int element) + { + // The search puts a character in a code set only when the code set holds it, so the character is ASCII. + if (element >= 0) + { + return CharacterValues[((int)state << 7) | element]; + } + + return (state, element) switch + { + (_, ShiftPunct) => 0, + (CodeSet.Upper, LatchLower) or (CodeSet.Mixed, LatchLower) => 28, + (CodeSet.Lower, ShiftUpper) => 28, + (CodeSet.Upper, LatchMixed) or (CodeSet.Lower, LatchMixed) => 29, + (CodeSet.Mixed, LatchUpper) => 29, + (CodeSet.Upper, LatchDigit) or (CodeSet.Lower, LatchDigit) => 30, + (CodeSet.Mixed, LatchPunct) => 30, + (_, ShiftBinary) => 31, + (CodeSet.Punct, LatchUpper) => 31, + (CodeSet.Digit, LatchUpper) => 14, + (CodeSet.Digit, ShiftUpper) => 15, + (_, PairCrLf) => 2, + (_, PairPeriodSpace) => 3, + (_, PairCommaSpace) => 4, + (_, PairColonSpace) => 5, + _ => throw new UnreachableException(), + }; + } + + /// + /// Writes a value most significant bit first. + /// + /// The buffer, one bit per byte. + /// The position of the first bit. + /// The value. + /// The number of bits. + /// The position after the last bit. + private static int Write(Span bits, int position, int value, int count) + { + for (int b = count - 1; b >= 0; b--) + { + bits[position++] = (byte)((value >> b) & 1); + } + + return position; + } + + /// + /// The size of a symbol: compact or full-range, and the number of data layers around the core. + /// + private readonly struct SymbolSize + { + /// + /// Initializes a new instance of the struct. + /// + /// Whether the symbol is compact. + /// The number of data layers, 0 for the core of a rune. + public SymbolSize(bool isCompact, int layers) + { + this.IsCompact = isCompact; + this.Layers = layers; + } + + /// + /// Gets a value indicating whether the symbol is compact. + /// + public bool IsCompact { get; } + + /// + /// Gets the number of data layers. + /// + public int Layers { get; } + + /// + /// Gets the codeword size B of Table 3. + /// + public int CodewordBits => this.Layers <= 2 ? 6 : this.Layers <= 8 || this.IsCompact ? 8 : this.Layers <= 22 ? 10 : 12; + + /// + /// Gets the number of codewords of Table 1, data and check together. + /// + public int Codewords => SymbolCodewords[this.IsCompact ? this.Layers - 1 : this.Layers + CompactSizeCount - 1]; + + /// + /// Gets the most data codewords the mode message can count. Section 7.2.3 gives the message length 6 bits in a compact symbol and 11 bits in a + /// full-range symbol. + /// + public int MaximumDataCodewords => this.IsCompact ? 64 : 2048; + + /// + /// Returns the symbol size at an index of . + /// + /// The index, in the order of section 11.5. + /// The symbol size. + public static SymbolSize FromIndex(int index) + => index < CompactSizeCount ? new SymbolSize(true, index + 1) : new SymbolSize(false, index - CompactSizeCount + 1); + } + + /// + /// One element of a search sequence. + /// + private struct Node + { + /// + /// The element: a character as its byte value, 0 to 255, or the negative code of a pair, a latch or a shift. + /// + public int Value; + + /// + /// The node before it, or -1 at the start of the sequence. + /// + public int Previous; + + /// + /// The number of elements since the last B/S, which is the length of the byte run when the sequence is in the byte state. + /// + public int Run; + + /// + /// The latch code or B/S nearest before or at this node, or 0 when the sequence has none, so the code set an element was written in reads without a + /// walk back. + /// + public int Marker; + } + + /// + /// The nodes of the search sequences in one pooled buffer that doubles when it fills. + /// + private struct NodeArena : IDisposable + { + /// + /// The nodes. The arena owns the buffer and returns it in . + /// + private PooledBuffer buffer; + + /// + /// The number of nodes in use. + /// + private int count; + + /// + /// Initializes a new instance of the struct. + /// + /// The initial number of nodes. + public NodeArena(int capacity) + { + this.buffer = new PooledBuffer(Math.Max(capacity, 64)); + this.count = 0; + } + + /// + /// Gets a node. + /// + /// The node index. + /// The node. + public readonly ref Node this[int index] => ref this.buffer.Span[index]; + + /// + /// Adds a node after another. + /// + /// The node before it, or -1 for the start of a sequence. + /// The element. + /// The index of the new node. + public int Append(int previous, int value) + { + Span nodes = this.buffer.Span; + if (this.count == nodes.Length) + { + PooledBuffer larger = new(nodes.Length * 2); + nodes.CopyTo(larger.Span); + this.buffer.Dispose(); + this.buffer = larger; + nodes = larger.Span; + } + + int run = value == ShiftBinary ? 0 : previous < 0 ? 1 : nodes[previous].Run + 1; + int marker = value is (>= LatchDigit and <= LatchUpper) or ShiftBinary ? value : previous < 0 ? 0 : nodes[previous].Marker; + nodes[this.count] = new Node { Value = value, Previous = previous, Run = run, Marker = marker }; + return this.count++; + } + + /// + public void Dispose() => this.buffer.Dispose(); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Aztec/AztecFormat.cs b/src/ImageSharp.Drawing/Barcodes/Aztec/AztecFormat.cs new file mode 100644 index 00000000..a96124e5 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Aztec/AztecFormat.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The formats an Aztec Code symbol can take, section 5.1 of ISO/IEC 24778. +/// +internal enum AztecFormat +{ + /// + /// The smallest symbol in the sequence of section 11.5: compact symbols of 1 to 4 layers, then full-range symbols of 4 to 32 layers. + /// + Auto, + + /// + /// A compact symbol of 1 to 4 layers, with a 2-ring bullseye. + /// + Compact, + + /// + /// A full-range symbol of 1 to 32 layers, with a 3-ring bullseye and a reference grid. + /// + Full, +} diff --git a/src/ImageSharp.Drawing/Barcodes/Aztec/AztecRuneSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Aztec/AztecRuneSymbology.cs new file mode 100644 index 00000000..5007cf92 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Aztec/AztecRuneSymbology.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Aztec Runes of ISO/IEC 24778: 256 small marks of 11 x 11 modules, the Core Symbol of a compact Aztec Code symbol whose mode message carries one value. The +/// text is the value, 0 to 255, in decimal digits. +/// +public sealed class AztecRuneSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public AztecRuneSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + if (text.Length is < 1 or > 3 || text.ContainsAnyExceptInRange('0', '9')) + { + throw new ArgumentException("An Aztec Rune carries a value of 0 to 255 in decimal digits.", nameof(text)); + } + + int value = int.Parse(text, System.Globalization.CultureInfo.InvariantCulture); + if (value > 255) + { + throw new ArgumentException($"An Aztec Rune carries a value of 0 to 255; got {value}.", nameof(text)); + } + + // Clause 12 of ISO/IEC 24778: "This symbology specification places no limits on the size of X." + AztecEncoder.BuildRune(value, options.ResolveXDimension(0, DefaultMatrixXDimension, double.PositiveInfinity), ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Aztec/HibcAztecCodeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Aztec/HibcAztecCodeSymbology.cs new file mode 100644 index 00000000..3c56c1b8 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Aztec/HibcAztecCodeSymbology.cs @@ -0,0 +1,91 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// HIBC Aztec Code, a Health Industry Bar Code carried by an Aztec Code symbol. The symbol carries the flag character, the data and the modulo 43 check +/// character, in the smallest symbol that holds them. +/// The text follows a data structure of ANSI/HIBC 2.6: primary data, secondary data that ends with its link character, or primary and secondary data joined +/// with "/", each with any supplemental data fields. +/// +/// When a font is set, that same string prints below the symbol between delimiters, with a space printed as an underscore and a zero as "Ø". Primary and +/// secondary data that the symbol joins with "/" print on two lines, broken after the "/". When the options set no X dimension, the X dimension +/// is 0.015 inch, from section 5.2 of ANSI/HIBC 2.6. An X dimension under 0.010 inch draws at 0.010 inch, the minimum of the same section. +/// +/// +public sealed class HibcAztecCodeSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class at the recommended error correction level of 23 percent, calculating the + /// check character from the data. + /// + public HibcAztecCodeSymbology() + : this(AztecEncoder.DefaultErrorCorrectionPercentage, false) + { + } + + /// + /// Initializes a new instance of the class at the recommended error correction level of 23 percent. + /// + /// Whether the last character of the input is the check character, which the encoder validates against the data, + /// rather than data the encoder appends a check character to. + public HibcAztecCodeSymbology(bool validateCheckCharacter) + : this(AztecEncoder.DefaultErrorCorrectionPercentage, validateCheckCharacter) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The minimum percentage of the symbol capacity that error correction takes, 5 to 95. Three more check codewords + /// are always added. + /// Whether the last character of the input is the check character, which the encoder validates against the data, + /// rather than data the encoder appends a check character to. + public HibcAztecCodeSymbology(int errorCorrectionPercentage, bool validateCheckCharacter) + { + Guard.MustBeBetweenOrEqualTo(errorCorrectionPercentage, 5, 95, nameof(errorCorrectionPercentage)); + this.ErrorCorrectionPercentage = errorCorrectionPercentage; + this.ValidateCheckCharacter = validateCheckCharacter; + } + + /// + /// Gets the minimum error correction percentage. + /// + public int ErrorCorrectionPercentage { get; } + + /// + /// Gets a value indicating whether the last character of the input is the check character. + /// + public bool ValidateCheckCharacter { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray64 bufferStorage = default; + int capacity = HibcData.PreparedCapacity(text, AztecEncoder.MaximumLength); + ValueStringBuilder encoded = capacity <= HibcData.InlineBufferLength ? new(bufferStorage) : new(capacity); + try + { + HibcData.Prepare(text, this.ValidateCheckCharacter, ref encoded); + AztecEncoder.BuildSymbol( + encoded.AsSpan(), + AztecFormat.Auto, + this.ErrorCorrectionPercentage, + 0, + options.ResolveXDimension(HibcData.MinimumMatrixXDimension, HibcData.NominalMatrixXDimension, double.PositiveInfinity), + ref symbol); + + if (options.Font is not null) + { + HibcData.AddTwoDimensionalReadable(encoded.AsSpan(), false, ref symbol); + } + } + finally + { + encoded.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/BarEmitter.cs b/src/ImageSharp.Drawing/Barcodes/BarEmitter.cs new file mode 100644 index 00000000..5812eb97 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/BarEmitter.cs @@ -0,0 +1,1033 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.Fonts; +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Helpers; +using SixLabors.ImageSharp.Drawing.Processing; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Measures and draws a linear or stacked . All bars render through a single fill of one . Each text +/// placement draws through one text command. The measure and the draw share one layout, so the measured metrics are the metrics of what is drawn. +/// +internal static class BarEmitter +{ + /// + /// Measures the symbol without drawing anything. + /// + /// The encoded linear or stacked symbol. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The measured layout of the symbol. + public static BarcodeMetrics Measure(ref BarcodeSymbol symbol, BarcodeOptions options, PointF origin) + { + using PooledBuffer lineBuffer = BarcodeEmitter.CreateLineBuffer(ref symbol, options); + + // A TextBlock is a class, so the span holds object references and cannot come from the byte pool. A bar symbol has at most 14 text placements: an + // ISBN has one for each of its 13 digits and one for its caption. The 16 stack slots hold all of them. + InlineArray16 blockStorage = default; + Span blocks = ((Span)blockStorage)[..lineBuffer.Span.Length]; + Shape(ref symbol, options, lineBuffer.Span, blocks); + return Measure(ref symbol, options, origin, lineBuffer.Span, out _); + } + + /// + /// Draws the symbol onto the canvas. + /// + /// The canvas to render onto. + /// The encoded linear or stacked symbol. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The brush that fills the renderable bounds before the bars draw, or for none. + /// The measured layout of the symbol, the same as returns. + public static BarcodeMetrics Draw(DrawingCanvas canvas, ref BarcodeSymbol symbol, BarcodeOptions options, PointF origin, Brush? background) + { + using PooledBuffer lineBuffer = BarcodeEmitter.CreateLineBuffer(ref symbol, options); + + // A TextBlock is a class, so the span holds object references and cannot come from the byte pool. A bar symbol has at most 14 text placements: an + // ISBN has one for each of its 13 digits and one for its caption. The 16 stack slots hold all of them. + InlineArray16 blockStorage = default; + Span blocks = ((Span)blockStorage)[..lineBuffer.Span.Length]; + Shape(ref symbol, options, lineBuffer.Span, blocks); + return Draw(canvas, ref symbol, options, origin, lineBuffer.Span, blocks, background); + } + + /// + /// Shapes the text of the symbol: one text block and one measured line for each text placement. The layout reads the lines and the draw renders the + /// blocks, so neither shapes the text again. A symbology that limits the size of its text and a caption that spans the bars measure their lines one more + /// time before the blocks, to find the font size. + /// + /// The encoded linear or stacked symbol. + /// The sizing and painting options. + /// The measured lines to write, one for each text placement, or empty when the options carry no font. + /// The text blocks to write, one for each line. + public static void Shape(ref BarcodeSymbol symbol, BarcodeOptions options, Span lines, Span blocks) + { + Font? font = options.Font; + if (font is null || lines.IsEmpty) + { + return; + } + + // The text spans stretch with a bar block of whole pixel runs, so they still cover it. + float moduleWidth = options.ModuleWidth(symbol.XDimension); + float stretch = symbol.UniformBars ? BlockWidth(ref symbol, moduleWidth) / (symbol.WidthInModules * moduleWidth) : 1F; + + // A bar symbol has at most 14 text placements, so the scales fit inline. + InlineArray16 lineScaleStorage = default; + Span lineScales = ((Span)lineScaleStorage)[..lines.Length]; + lineScales.Fill(1F); + Font digitFont = HasSizeLimits(ref symbol) ? DigitFont(ref symbol, font, options, moduleWidth, moduleWidth * stretch, lineScales) : font; + LineFonts fonts = new(digitFont, options.CaptionFont ?? font, symbol.CaptionMinimumSize, symbol.TextWeight, options, moduleWidth * stretch); + + ReadOnlySpan text = symbol.Text; + for (int i = 0; i < lines.Length; i++) + { + BarcodeTextPlacement placement = text[i]; + ReadOnlySpan characters = symbol.TextOf(in placement); + RichTextOptions textOptions = fonts.OptionsFor(in placement, characters, lineScales[i]); + + // The rectangles of a line are anchored at its origin, so the line is measured from its top left corner and placed on the cell later. The block + // keeps the options it was prepared with, and the draw renders the same block at the place the layout gives it. + textOptions.Origin = PointF.Empty; + TextBlock block = new(characters, textOptions); + blocks[i] = block; + TextMetrics metrics = block.Measure(-1); + + // The lines below the bars share one baseline, which the tallest of them sets, so they keep the baseline and the line height of their first line. + float baseline = 0; + float lineHeight = 0; + if (placement.Side != BarcodeTextSide.AboveBars) + { + LineMetrics lineMetrics = metrics.LineMetrics[0]; + baseline = lineMetrics.Baseline; + lineHeight = lineMetrics.LineHeight; + } + + lines[i] = new MeasuredLine(metrics.Advance, metrics.Bounds, metrics.RenderableBounds, baseline, lineHeight); + } + } + + /// + /// Measures the symbol from its shaped text without drawing anything. + /// + /// The encoded linear or stacked symbol. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The lines that wrote. The layout writes the position of each line. + /// The top left corner of the first module of the symbol, in pixels, before it rounds onto the pixel grid. + /// The measured layout of the symbol. + public static BarcodeMetrics Measure(ref BarcodeSymbol symbol, BarcodeOptions options, PointF origin, Span lines, out PointF symbolOrigin) + { + BarLayout layout = new(ref symbol, options, origin, lines); + symbolOrigin = layout.SymbolOrigin; + return layout.Metrics; + } + + /// + /// Draws the symbol from its shaped text onto the canvas. + /// + /// The canvas to render onto. + /// The encoded linear or stacked symbol. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The lines that wrote. The layout writes the position of each line. + /// The blocks that wrote, one for each line. + /// The brush that fills the renderable bounds before the bars draw, or for none. + /// The measured layout of the symbol. + public static BarcodeMetrics Draw( + DrawingCanvas canvas, + ref BarcodeSymbol symbol, + BarcodeOptions options, + PointF origin, + Span lines, + ReadOnlySpan blocks, + Brush? background) + { + BarLayout layout = new(ref symbol, options, origin, lines); + BarcodeMetrics metrics = layout.Metrics; + + // The path retains the rectangles until the canvas flushes, so it owns this array. + RectangleF[] bars = new RectangleF[layout.BarCount(ref symbol)]; + layout.WriteBars(ref symbol, bars); + + if (background is not null) + { + RectangleF renderable = metrics.RenderableBounds; + canvas.Fill(background, new RectanglePolygon(renderable.X, renderable.Y, renderable.Width, renderable.Height)); + } + + canvas.Fill(options.BarBrush, new BarcodePath(bars, layout.BarInk)); + layout.DrawText(canvas, blocks, options.BarBrush); + return metrics; + } + + /// + /// Returns whether the symbology limits the size of its digit lines: it states a size in points or the printed height of its characters, or a line must + /// stay within its span. + /// + /// The encoded symbol. + /// when the size of the digit lines is limited. + private static bool HasSizeLimits(ref BarcodeSymbol symbol) + { + if (symbol.TextHeightMaximum > 0 || symbol.TextMinimumSize > 0 || symbol.TextMaximumSize > 0) + { + return true; + } + + foreach (BarcodeTextPlacement placement in symbol.Text) + { + if (placement.WithinSpan) + { + return true; + } + } + + return false; + } + + /// + /// Returns the font of the digit lines of a symbology that limits their size, and the scale of each line that must fit its span. A stated size in points + /// moves the size of the font of the options into that range, and a stated printed height of the characters moves it into that range. A line that is wider + /// than its span at that size then prints at the scale that fits it. + /// + /// The encoded symbol. + /// The font of the options. + /// The sizing and painting options. + /// The width of one module in pixels. + /// The width of one module of a text span in pixels, stretched to the drawn bar block. + /// The scale of each line, one for each text placement, to write. + /// The font of the digit lines. + /// A line does not fit its span at the smallest printed height of the symbology. + private static Font DigitFont(ref BarcodeSymbol symbol, Font font, BarcodeOptions options, float moduleWidth, float spanModuleWidth, Span lineScales) + { + // A size in points needs no measure. The other limits measure the text in the font of that size. + float pointSize = font.Size; + if (symbol.TextMinimumSize > 0) + { + pointSize = MathF.Max(pointSize, symbol.TextMinimumSize); + } + + if (symbol.TextMaximumSize > 0) + { + pointSize = MathF.Min(pointSize, symbol.TextMaximumSize); + } + + font = pointSize == font.Size ? font : new Font(font, pointSize); + + // The ink height comes from the design bounds of the glyphs, and hinting does not move a glyph on the x axis, so the measured height and widths scale + // with the font size. Each line gives the sizes that put it on its limits, and where the limits of two lines disagree, the minimum height wins over + // the maximum height. Only a line that a limit applies to is measured. A line of one character that only has to fit its span takes the width of its + // glyph, which needs no shaping. + ReadOnlySpan text = symbol.Text; + float minimumHeight = symbol.TextHeightMinimum * moduleWidth; + float maximumHeight = symbol.TextHeightMaximum * moduleWidth; + float sizeAtMinimum = 0; + float sizeAtMaximum = float.MaxValue; + RichTextOptions? measureOptions = null; + for (int i = 0; i < text.Length; i++) + { + BarcodeTextPlacement placement = text[i]; + if (placement.IsCaption || (maximumHeight <= 0 && !placement.WithinSpan)) + { + continue; + } + + ReadOnlySpan characters = symbol.TextOf(in placement); + float width; + if (maximumHeight <= 0 + && placement.Alignment != BarcodeTextAlignment.Centered + && characters.Length == 1 + && font.TryGetGlyphs(new CodePoint(characters[0]), out Glyph? glyph) + && glyph is Glyph characterGlyph) + { + width = characterGlyph.BoundingBox(GlyphLayoutMode.Horizontal, Vector2.Zero, options.Dpi).Width; + } + else + { + measureOptions ??= BarcodeTextOptionsFactory.Create(font, options.Dpi, symbol.TextWeight); + TextMetrics metrics = TextMeasurer.Measure(characters, measureOptions); + float inkHeight = metrics.Bounds.Height * placement.FontScale; + if (maximumHeight > 0 && inkHeight > 0) + { + sizeAtMinimum = MathF.Max(sizeAtMinimum, font.Size * minimumHeight / inkHeight); + sizeAtMaximum = MathF.Min(sizeAtMaximum, font.Size * maximumHeight / inkHeight); + } + + // A line aligned on an edge fits its ink to the span, and a centred line fits its renderable bounds, which hold its ink. + width = placement.Alignment == BarcodeTextAlignment.Centered ? metrics.RenderableBounds.Width : metrics.Bounds.Width; + } + + // The width at the size of the options, until the size of the font is known. + lineScales[i] = placement.WithinSpan ? width * placement.FontScale : 1F; + } + + float size = MathF.Max(MathF.Min(font.Size, sizeAtMaximum), sizeAtMinimum); + for (int i = 0; i < text.Length; i++) + { + BarcodeTextPlacement placement = text[i]; + if (!placement.WithinSpan || placement.IsCaption) + { + continue; + } + + float width = lineScales[i] * size / font.Size; + float span = (placement.Right - placement.Left) * spanModuleWidth; + float scale = width > span ? span / width : 1F; + + // The smallest printed height and the span are both requirements, so a line that needs a smaller font than the height allows cannot be printed. + if (maximumHeight > 0 && size * scale < sizeAtMinimum) + { + throw new ArgumentException("A text line does not fit its span at the smallest printed height of its symbology."); + } + + lineScales[i] = scale; + } + + return size == font.Size ? font : new Font(font, size); + } + + /// + /// Returns the width of the bar block in pixels. Uniform bars draw each run at a whole number of pixels, so their block is the sum of those runs on the + /// first row. Other bars draw at the exact width. + /// + /// The encoded linear or stacked symbol. + /// The width of one module in pixels. + /// The width in pixels. + private static float BlockWidth(ref BarcodeSymbol symbol, float moduleWidth) + { + if (!symbol.UniformBars) + { + return symbol.WidthInModules * moduleWidth; + } + + float runWidthInPixels = symbol.RunUnit * moduleWidth; + BarcodeRow first = symbol.Rows[0]; + ReadOnlySpan runs = symbol.Runs.Slice(first.RunStart, first.RunCount); + float blockWidth = 0; + for (int i = 0; i < runs.Length; i++) + { + blockWidth += RunPixels(runs[i], runWidthInPixels); + } + + return blockWidth; + } + + /// + /// Returns the pixel offset of an edge in a row of uniform bars: the sum of the whole pixel widths of the runs before the edge. + /// + /// The runs of the row. + /// The edge in run units from the start of the row. + /// The width of one run unit in pixels. + /// The offset in pixels. + private static float PixelOffset(ReadOnlySpan runs, int units, float runWidthInPixels) + { + float pixels = 0; + int position = 0; + for (int i = 0; position < units; i++) + { + pixels += RunPixels(runs[i], runWidthInPixels); + position += runs[i]; + } + + return pixels; + } + + /// + /// Returns the whole number of pixels a run of a symbol with uniform bars draws at. A run that is not empty draws at least one pixel. + /// + /// The run width in run units. + /// The width of one run unit in pixels. + /// The run width in pixels. + private static float RunPixels(int units, float runWidthInPixels) + => units == 0 ? 0F : MathF.Max(1F, MathF.Round(units * runWidthInPixels, MidpointRounding.AwayFromZero)); + + /// + /// Resolves the font an ISBN, ISMN or ISSN caption prints in. Under the size is the one that spans the + /// bars. Otherwise the font keeps its own size. The minimum size of the symbology wins over both, and a caption that cannot fit at that size widens the + /// drawing. + /// + /// The caption text. + /// The width the caption prints across, in modules. + /// The width of one module in pixels. + /// The caption font of the options, or their font when they carry no caption font. + /// The smallest size of the caption in points, from . + /// The font weight of the text, from . + /// The options that carry the resolution and the fitting choice. + /// The font to print the caption with. + private static Font ResolveCaptionFont( + ReadOnlySpan caption, + float spanModules, + float moduleWidth, + Font font, + float minimumSize, + FontWeight? weight, + BarcodeOptions options) + { + float size = font.Size; + if (options.FitCaptionToSymbolWidth) + { + float width = TextMeasurer.Measure(caption, BarcodeTextOptionsFactory.Create(font, options.Dpi, weight)).RenderableBounds.Width; + size = font.Size * spanModules * moduleWidth / width; + } + + size = MathF.Max(size, minimumSize); + return size == font.Size ? font : new Font(font, size); + } + + /// + /// The layout of a linear or stacked symbol: where the bar block, the frame, the identification bars and each text line stand, from one origin. The + /// constructor places everything and measures the ink, and writes the bars for the draw, so the measure and the draw read one + /// layout. + /// + private ref struct BarLayout + { + /// + /// The width of one module in pixels. + /// + private readonly float moduleWidth; + + /// + /// The top edge of the rows in pixels, on a whole pixel. + /// + private readonly float blockTop; + + /// + /// The x of the first bar, in pixels. + /// + private readonly float symbolLeft; + + /// + /// The width of the bar block in pixels. + /// + private readonly float blockWidth; + + /// + /// Whether every run draws at a whole number of pixels of its own. + /// + private readonly bool wholePixelRuns; + + /// + /// The width of one run unit in pixels. + /// + private readonly float runWidthInPixels; + + /// + /// The number of run units in one module of runs that are whole modules with corrections, or 0, from . + /// + private readonly int correctedModuleUnits; + + /// + /// The thickness of the frame in modules, or zero for none. + /// + private readonly float frame; + + /// + /// The frame thickness in whole pixels. + /// + private readonly float frameThickness; + + /// + /// The top edge of the upper frame section, in pixels. + /// + private readonly float frameTop; + + /// + /// The bottom edge of the upper frame section, in pixels. + /// + private readonly float upperBottom; + + /// + /// The top edge of the lower frame section, in pixels. + /// + private readonly float lowerTop; + + /// + /// The left edge of the frame, in pixels. + /// + private readonly float frameLeft; + + /// + /// The right edge of the frame, in pixels. + /// + private readonly float frameRight; + + /// + /// Whether the frame has vertical sections. + /// + private readonly bool sides; + + /// + /// The upper identification bar in pixels, or an empty rectangle for none. + /// + private readonly RectangleF upperIdentificationBar; + + /// + /// The lower identification bar in pixels, or an empty rectangle for none. + /// + private readonly RectangleF lowerIdentificationBar; + + /// + /// The measured lines, one for each text placement, or none when the options carry no font. + /// + private readonly Span lines; + + /// + /// Initializes a new instance of the struct. A linear symbol is one row. The rows share the width, the run unit and the bar + /// rounding of the symbol. + /// + /// The encoded linear or stacked symbol. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The shaped lines, one for each text placement, or empty when the options carry no font. The layout writes their origins. + public BarLayout(ref BarcodeSymbol symbol, BarcodeOptions options, PointF origin, Span lines) + { + this.lines = lines; + float moduleWidth = options.ModuleWidth(symbol.XDimension); + this.moduleWidth = moduleWidth; + + ReadOnlySpan allRuns = symbol.Runs; + ReadOnlySpan text = symbol.Text; + + // The bars that close the top and the bottom: a bearer bar on a linear symbol, which also has vertical sections when the quiet zones are drawn, or + // the edge bars of a stacked symbol. + float frame = symbol.FrameThickness; + this.frame = frame; + float widthInModules = symbol.WidthInModules; + + // A symbol whose bars are one width at one pitch, the postal symbologies, draws every run at a whole number of pixels of its own, so every bar + // keeps that width and that pitch: Table 11 of the Mailmark barcode definition document requires that "they must be equally spaced", and page 12 + // of the Japan Post manual gives one bar width, 0.6×a/10. Edges rounded from the accumulated exact position alternate between two widths. + // The text spans stretch with the bar block so they still cover it. + this.wholePixelRuns = symbol.UniformBars; + this.runWidthInPixels = symbol.RunUnit * moduleWidth; + this.correctedModuleUnits = symbol.CorrectedModuleUnits; + float blockWidth = BlockWidth(ref symbol, moduleWidth); + this.blockWidth = blockWidth; + float stretch = this.wholePixelRuns ? blockWidth / (symbol.WidthInModules * moduleWidth) : 1F; + float textModuleWidth = moduleWidth * stretch; + + // The first bar stands after the leading quiet zone and, when the frame has vertical sections, after the frame. + float leadingOffset = 0; + bool frameSpansQuietZones = false; + if (options.IncludeQuietZones) + { + widthInModules += symbol.LeadingQuietZone + symbol.TrailingQuietZone; + leadingOffset += symbol.LeadingQuietZone * moduleWidth; + + // Section 5.3.2.4 of the GS1 General Specifications runs the bearer bar around the quiet zones, so its vertical sections need the quiet zones + // on the page. Without them the same clause permits the horizontal sections alone. Section 2.1 of USS-16K and of USS-49 gives the top and + // bottom separator bars the same reach: they "extend to the ends of the quiet zones". + frameSpansQuietZones = frame > 0; + this.sides = frameSpansQuietZones && symbol.Kind == BarcodeSymbolKind.Linear; + if (this.sides) + { + widthInModules += frame + frame; + leadingOffset += frame * moduleWidth; + } + } + + float sideZones = (widthInModules - symbol.WidthInModules) * moduleWidth; + float topZone = options.IncludeQuietZones ? symbol.TopQuietZone : 0F; + float bottomZone = options.IncludeQuietZones ? symbol.BottomQuietZone : 0F; + + // Every line drawn in one font shares one rise, so the lines share a baseline and their glyphs sit level with each other. The rise is the + // tallest ink any of those lines carries, which a cap height metric misses for an ascender or the overshoot of a round glyph. A symbol prints one + // line below the bars and at most one above. The clear space is measured to the ink, so the line moves by the offset of its ink edge to put that + // edge on the gap whatever the font or its size. + float aboveInk = 0; + float aboveInkTop = float.MaxValue; + float belowInk = float.MaxValue; + float belowInkBottom = float.MinValue; + float belowBaseline = 0; + float belowHeight = 0; + float aboveEdge = float.NaN; + float belowEdge = float.NaN; + for (int i = 0; i < lines.Length; i++) + { + BarcodeTextPlacement placement = text[i]; + MeasuredLine line = lines[i]; + if (placement.Side == BarcodeTextSide.AboveBars) + { + aboveInk = MathF.Max(aboveInk, line.Bounds.Bottom); + aboveInkTop = MathF.Min(aboveInkTop, line.Bounds.Top); + aboveEdge = float.IsNaN(aboveEdge) ? placement.TextEdge : MathF.Min(aboveEdge, placement.TextEdge); + } + else + { + belowInkBottom = MathF.Max(belowInkBottom, line.Bounds.Bottom); + belowEdge = float.IsNaN(belowEdge) ? placement.TextEdge : MathF.Max(belowEdge, placement.TextEdge); + + // The quiet zone digits of a UPC symbol print smaller than the rest of the line. The tallest line sets where the line sits, and every line + // on it shares that baseline. + if (line.LineHeight > belowHeight) + { + belowHeight = line.LineHeight; + belowBaseline = line.Baseline; + } + + belowInk = MathF.Min(belowInk, line.Bounds.Top); + } + } + + // The layout box: the barcode and the line box of each line, in exact positions relative to the top left corner of the barcode. The identification + // bars belong to the layout of the symbol, as the frame does, and they reach across the quiet zones whether or not the quiet zones are drawn. + // Nothing in this box rounds, as nothing in the layout box of text rounds. + float layoutLeft = 0; + float layoutTop = 0; + float layoutRight = sideZones + blockWidth; + float layoutBottom = (topZone + symbol.HeightInModules + bottomZone) * moduleWidth; + for (int i = 0; i < lines.Length; i++) + { + BarcodeTextPlacement placement = text[i]; + MeasuredLine line = lines[i]; + float cellLeft = leadingOffset + (placement.Left * textModuleWidth); + float cellRight = leadingOffset + (placement.Right * textModuleWidth); + float x = placement.Origin(cellLeft, cellRight, line.Bounds, line.RenderableBounds.Width); + float edge = (topZone + placement.TextEdge) * moduleWidth; + float y = placement.Side == BarcodeTextSide.AboveBars ? edge - aboveInk : edge - belowInk + belowBaseline - line.Baseline; + layoutLeft = MathF.Min(layoutLeft, x + line.Advance.Left); + layoutTop = MathF.Min(layoutTop, y + line.Advance.Top); + layoutRight = MathF.Max(layoutRight, x + line.Advance.Right); + layoutBottom = MathF.Max(layoutBottom, y + line.Advance.Bottom); + } + + float identificationThickness = symbol.IdentificationBarThickness * moduleWidth; + float identificationClearance = symbol.IdentificationBarClearance * moduleWidth; + if (identificationThickness > 0) + { + layoutLeft = MathF.Min(layoutLeft, leadingOffset - (symbol.LeadingQuietZone * moduleWidth)); + layoutRight = MathF.Max(layoutRight, leadingOffset + blockWidth + (symbol.TrailingQuietZone * moduleWidth)); + if (!float.IsNaN(aboveEdge)) + { + float bottom = ((topZone + aboveEdge) * moduleWidth) - (aboveInk - aboveInkTop) - identificationClearance; + layoutTop = MathF.Min(layoutTop, bottom - identificationThickness); + } + + if (!float.IsNaN(belowEdge)) + { + float top = ((topZone + belowEdge) * moduleWidth) + (belowInkBottom - belowInk) + identificationClearance; + layoutBottom = MathF.Max(layoutBottom, top + identificationThickness); + } + } + + // The origin is the top left corner of the layout box, as it is for text. Text above the symbol or to the left of it moves the symbol down or + // right. + PointF symbolOrigin = new(origin.X - layoutLeft, origin.Y - layoutTop); + RectangleF advance = new(origin.X, origin.Y, layoutRight - layoutLeft, layoutBottom - layoutTop); + + // The bar block starts on a whole pixel, whatever the fraction of a pixel the origin and the quiet zone end on. Each edge then rounds from a whole + // pixel, so a module of a whole number of pixels draws at exactly that number wherever the symbol stands. From a start on half a pixel, edges round + // to the even pixel and alternate, and a bar of three pixels draws as four and then two. + this.SymbolOrigin = new PointF(symbolOrigin.X + leadingOffset, symbolOrigin.Y + (topZone * moduleWidth)); + float symbolLeft = MathF.Round(this.SymbolOrigin.X); + this.symbolLeft = symbolLeft; + this.blockTop = MathF.Round(this.SymbolOrigin.Y); + + // The edge the reader sees is what rounds onto the device pixel grid, as the bar edges do. The ink of a line below the bars starts on its text + // edge, and the ink of a line above the bars ends on it. + RectangleF textInk = RectangleF.Empty; + for (int i = 0; i < lines.Length; i++) + { + BarcodeTextPlacement placement = text[i]; + ref MeasuredLine line = ref lines[i]; + float textEdge = MathF.Round(symbolOrigin.Y + ((topZone + placement.TextEdge) * moduleWidth)); + float x = placement.GridOrigin(line.Bounds, line.RenderableBounds.Width, symbolLeft, textModuleWidth); + float y = placement.Side == BarcodeTextSide.AboveBars + ? textEdge - aboveInk + : textEdge - belowInk + belowBaseline - line.Baseline; + + line.Origin = new Vector2(x, y); + RectangleF ink = new(x + line.Bounds.X, y + line.Bounds.Y, line.Bounds.Width, line.Bounds.Height); + textInk = i == 0 ? ink : RectangleF.Union(textInk, ink); + } + + // Section 5.3.2.4 of the GS1 General Specifications gives the bearer bar "a constant thickness", so the sections of the frame share one pixel + // thickness. On every side where the frame is the outermost ink its outer edge snaps outward, so no background shows past it. Where text or a quiet + // zone follows below, the lower edge rounds as the bar edges do, which keeps the clear space. The bars that touch the frame butt against its inner + // edges, so their height absorbs the fraction of a pixel that the snapping moves, never the frame. + if (frame > 0) + { + this.frameThickness = MathF.Round(frame * moduleWidth); + float exactTop = this.blockTop; + float exactBottom = this.blockTop + (symbol.HeightInModules * moduleWidth); + this.frameTop = topZone > 0 ? MathF.Round(exactTop) : MathF.Floor(exactTop); + float frameBottom = bottomZone > 0 || !float.IsNaN(belowEdge) ? MathF.Round(exactBottom) : MathF.Ceiling(exactBottom); + this.upperBottom = this.frameTop + this.frameThickness; + this.lowerTop = frameBottom - this.frameThickness; + + // When the quiet zones are drawn the frame reaches across them, and its outer edges snap outward from the exact ones, so the frame fills + // the measured area. The quiet zones absorb the fraction of a pixel between the snapped outer edge and the exact one. The vertical sections + // also stand at least the quiet zone and their thickness away from the bar block, which starts and ends on whole pixels. The thickness + // rounds on its own, so without that limit a quiet zone can lose a pixel under its minimum. A quiet zone is a minimum, so a quiet zone of + // a fraction of a pixel rounds up. Without the quiet zones the frame ends on the bars. + if (this.sides) + { + float leadingZone = (float)Math.Ceiling(symbol.LeadingQuietZone * (double)moduleWidth); + float trailingZone = (float)Math.Ceiling(symbol.TrailingQuietZone * (double)moduleWidth); + this.frameLeft = MathF.Min(MathF.Floor(symbolOrigin.X), symbolLeft - leadingZone - this.frameThickness); + float blockRight = symbolLeft + blockWidth; + this.frameRight = MathF.Max(MathF.Ceiling(symbolOrigin.X + sideZones + blockWidth), blockRight + trailingZone + this.frameThickness); + } + else + { + this.frameLeft = frameSpansQuietZones ? MathF.Floor(symbolOrigin.X) : MathF.Round(symbolLeft); + this.frameRight = frameSpansQuietZones ? MathF.Ceiling(symbolOrigin.X + sideZones + blockWidth) : MathF.Round(symbolLeft + blockWidth); + } + } + + // An identification bar stands its clearance beyond the far ink edge of the line on its side, and spans the symbol and its quiet zones whether or + // not the quiet zones are drawn, because section 2.4.3 of USPS2000508 extends the bars "at least the total combined width of the barcode and the + // minimum clear zones". Section 4.9.2 of Publication 199: "Human-readable numbers cannot extend beyond the length of the identification bars", and + // section 4.9.4 lets the bars extend to "the entire width of the label", so the bars also span the ink of the text. The bars stand on the text + // edges the lines draw at, so the clearance holds to the drawn ink. The edge next to the text rounds away from it, and both bars share one pixel + // thickness. + if (identificationThickness > 0) + { + float thickness = MathF.Max(1F, MathF.Round(identificationThickness)); + float left = MathF.Floor(MathF.Min(symbolLeft - (symbol.LeadingQuietZone * moduleWidth), textInk.Left)); + float right = MathF.Ceiling(MathF.Max(symbolLeft + blockWidth + (symbol.TrailingQuietZone * moduleWidth), textInk.Right)); + if (!float.IsNaN(aboveEdge)) + { + float edge = MathF.Round(symbolOrigin.Y + ((topZone + aboveEdge) * moduleWidth)); + float bottom = MathF.Floor(edge - (aboveInk - aboveInkTop) - identificationClearance); + this.upperIdentificationBar = RectangleF.FromLTRB(left, bottom - thickness, right, bottom); + } + + if (!float.IsNaN(belowEdge)) + { + float edge = MathF.Round(symbolOrigin.Y + ((topZone + belowEdge) * moduleWidth)); + float top = MathF.Ceiling(edge + (belowInkBottom - belowInk) + identificationClearance); + this.lowerIdentificationBar = RectangleF.FromLTRB(left, top, right, top + thickness); + } + } + + // The ink of the bars comes from the ink edges of each row, which the symbol records once: each edge rounds onto the pixel grid as the bar edge it + // names does, so the ink is the union of the bars without a visit to each bar. Uniform bars place each run at a whole number of pixels, so their + // edges add those pixel widths up to the edge. + RectangleF barInk = RectangleF.Empty; + ReadOnlySpan rows = symbol.Rows; + for (int r = 0; r < rows.Length; r++) + { + BarcodeRow row = rows[r]; + if (row.InkStart < 0) + { + continue; + } + + float left; + float right; + if (this.wholePixelRuns) + { + ReadOnlySpan rowRuns = allRuns.Slice(row.RunStart, row.RunCount); + left = symbolLeft + PixelOffset(rowRuns, row.InkStart, this.runWidthInPixels); + right = symbolLeft + PixelOffset(rowRuns, row.InkEnd, this.runWidthInPixels); + } + else if (this.correctedModuleUnits > 0) + { + left = this.CorrectedEdge(row.InkStart); + right = this.CorrectedEdge(row.InkEnd); + } + else + { + left = (float)Math.Round(symbolLeft + (row.InkStart * (double)this.runWidthInPixels)); + right = (float)Math.Round(symbolLeft + (row.InkEnd * (double)this.runWidthInPixels)); + } + + RectangleF rowInk = RectangleF.FromLTRB(left, this.RowEdge(row.Top + row.InkTop), right, this.RowEdge(row.Top + row.Height)); + barInk = barInk.IsEmpty ? rowInk : RectangleF.Union(barInk, rowInk); + } + + // The frame is the outermost ink on the sides it closes, and an identification bar spans the symbol and its quiet zones. + if (frame > 0) + { + barInk = RectangleF.Union(barInk, RectangleF.FromLTRB(this.frameLeft, this.frameTop, this.frameRight, this.lowerTop + this.frameThickness)); + } + + if (!this.upperIdentificationBar.IsEmpty) + { + barInk = RectangleF.Union(barInk, this.upperIdentificationBar); + } + + if (!this.lowerIdentificationBar.IsEmpty) + { + barInk = RectangleF.Union(barInk, this.lowerIdentificationBar); + } + + this.BarInk = barInk; + + // As in text measuring, the advance is relative to the origin, and the bounds and the renderable bounds are absolute. + RectangleF bounds = textInk.IsEmpty ? barInk : RectangleF.Union(barInk, textInk); + this.Metrics = new BarcodeMetrics(new RectangleF(0, 0, advance.Width, advance.Height), bounds, RectangleF.Union(advance, bounds)); + } + + /// + /// Gets the top left corner of the first module of the symbol, in pixels, before it rounds onto the pixel grid. + /// + public PointF SymbolOrigin { get; } + + /// + /// Gets the ink of the bars, the frame and the identification bars, in pixels. + /// + public RectangleF BarInk { get; } + + /// + /// Gets the metrics of the layout. + /// + public BarcodeMetrics Metrics { get; } + + /// + /// Returns the number of bar rectangles writes: the bars, the frame sections and the identification bars. + /// + /// The encoded symbol. + /// The number of rectangles. + public readonly int BarCount(ref BarcodeSymbol symbol) + { + int count = symbol.BarHeights.Length; + if (this.frame > 0) + { + count += this.sides ? 4 : 2; + } + + count += this.upperIdentificationBar.IsEmpty ? 0 : 1; + count += this.lowerIdentificationBar.IsEmpty ? 0 : 1; + return count; + } + + /// + /// Returns the pixel position of an edge of runs that are whole modules with corrections. The module edge rounds onto the pixel grid as an edge without + /// a correction does, and the correction rounds to its own whole number of pixels, so the edges are those of the runs without corrections until a + /// correction is half a pixel or more. Rounded together, a correction of a fraction of a pixel can move one edge of a bar a whole pixel and its + /// neighbour not, and change a bar of two pixels by half its width. The corrections of a character sum to zero and are less than half a module, so the + /// nearest module to an edge is the module edge it corrects. + /// + /// The edge in run units from the start of the row. + /// The edge in pixels. + private readonly float CorrectedEdge(int units) + { + int unitsPerModule = this.correctedModuleUnits; + int modules = (units + (unitsPerModule >> 1)) / unitsPerModule; + int correction = units - (modules * unitsPerModule); + double edge = Math.Round(this.symbolLeft + (modules * (double)this.moduleWidth)); + return (float)(edge + Math.Round(correction * (double)this.runWidthInPixels)); + } + + /// + /// Writes every bar rectangle: the bars, then the frame sections, then the identification bars. + /// + /// The encoded symbol. + /// The rectangles to write, of them. + public readonly void WriteBars(ref BarcodeSymbol symbol, Span bars) + { + int index = 0; + ReadOnlySpan rows = symbol.Rows; + ReadOnlySpan allRuns = symbol.Runs; + ReadOnlySpan barHeights = symbol.BarHeights; + ReadOnlySpan barTops = symbol.BarTops; + float frame = this.frame; + bool corrected = this.correctedModuleUnits > 0; + for (int r = 0; r < rows.Length; r++) + { + BarcodeRow row = rows[r]; + ReadOnlySpan runs = allRuns.Slice(row.RunStart, row.RunCount); + ReadOnlySpan rowHeights = barHeights.Slice(row.BarStart, row.BarCount); + ReadOnlySpan rowBarTops = barTops.Slice(row.BarStart, row.BarCount); + float rowTop = row.Top; + + // The position adds every run before it. A double holds each product of a whole run and the run width exactly, so the sum carries no rounding + // error from the runs before it. + double x = this.symbolLeft; + int units = 0; + for (int i = 0; i < runs.Length; i++) + { + double runWidth = this.wholePixelRuns ? RunPixels(runs[i], this.runWidthInPixels) : runs[i] * (double)this.runWidthInPixels; + if ((i & 1) == 0) + { + // Each bar edge rounds from its exact accumulated position onto the device pixel grid, so bars stay crisp for scanning at any module + // width and rounding cannot drift across the symbol, and the edge a row shares with the row below it rounds to one pixel row for both. + // Integral module widths, heights and origins round to themselves. Corrected runs round their module edges and corrections apart. + int bar = i >> 1; + float top = rowTop + rowBarTops[bar]; + float bottom = top + rowHeights[bar]; + float barTop = frame > 0 && top == frame ? this.upperBottom : this.RowEdge(top); + float barBottom = frame > 0 && bottom == symbol.HeightInModules - frame ? this.lowerTop : this.RowEdge(bottom); + float left = corrected ? this.CorrectedEdge(units) : (float)Math.Round(x); + float right = corrected ? this.CorrectedEdge(units + runs[i]) : (float)Math.Round(x + runWidth); + bars[index++] = RectangleF.FromLTRB(left, barTop, right, barBottom); + } + + x += runWidth; + units += runs[i]; + } + } + + // The vertical sections of the frame run between the horizontal ones, so no two rectangles overlap. + if (frame > 0) + { + float width = this.frameRight - this.frameLeft; + float height = this.lowerTop - this.upperBottom; + bars[index++] = new RectangleF(this.frameLeft, this.frameTop, width, this.frameThickness); + bars[index++] = new RectangleF(this.frameLeft, this.lowerTop, width, this.frameThickness); + if (this.sides) + { + bars[index++] = new RectangleF(this.frameLeft, this.upperBottom, this.frameThickness, height); + bars[index++] = new RectangleF(this.frameRight - this.frameThickness, this.upperBottom, this.frameThickness, height); + } + } + + if (!this.upperIdentificationBar.IsEmpty) + { + bars[index++] = this.upperIdentificationBar; + } + + if (!this.lowerIdentificationBar.IsEmpty) + { + bars[index] = this.lowerIdentificationBar; + } + } + + /// + /// Draws each text line where the layout put it. Each line renders its shaped block, so it is not shaped again. + /// + /// The canvas to render onto. + /// The shaped blocks, one for each line. + /// The brush of the text. + public readonly void DrawText(DrawingCanvas canvas, ReadOnlySpan blocks, Brush brush) + { + for (int i = 0; i < this.lines.Length; i++) + { + canvas.DrawText(blocks[i], this.lines[i].Origin, -1, brush, null); + } + } + + /// + /// Returns a horizontal edge of the bar block in pixels: an exact position in modules from the symbol top, rounded onto the device pixel grid. + /// + /// The position in modules from the symbol top. + /// The edge in pixels. + private readonly float RowEdge(float modules) + => MathF.Round(this.blockTop + (modules * this.moduleWidth)); + } + + /// + /// The text options the lines of one symbol render with: the digit options, the caption options and the options of the scaled lines. The digit and the + /// caption options are built on first use and kept. The scaled options are kept while the next scaled line has the same font size. + /// reads the options of each line from here. + /// + private struct LineFonts + { + /// + /// The font the digit lines render in, from . + /// + private readonly Font digitFont; + + /// + /// The font a caption starts from: the caption font of the options, or their font when they carry no caption font. + /// + private readonly Font captionFont; + + /// + /// The smallest size of the caption in points, or 0 when the symbology states none. + /// + private readonly float captionMinimumSize; + + /// + /// The font weight of the text, or for the weight of the fonts. + /// + private readonly FontWeight? weight; + + /// + /// The sizing and painting options, which carry the resolution and the fitting choice. + /// + private readonly BarcodeOptions options; + + /// + /// The width of one module in pixels, stretched to the drawn bar block. + /// + private readonly float moduleWidth; + + /// + /// The options of the digit lines, or before first use. + /// + private RichTextOptions? digitOptions; + + /// + /// The options of the last scaled line, or before first use. + /// + private RichTextOptions? scaledOptions; + + /// + /// The options of the caption, or before first use. + /// + private RichTextOptions? captionOptions; + + /// + /// Initializes a new instance of the struct. + /// + /// The font the digit lines render in. + /// The font a caption starts from. + /// The smallest size of the caption in points, or 0 when the symbology states none. + /// The font weight of the text, from . + /// The sizing and painting options. + /// The width of one module in pixels, stretched to the drawn bar block. + public LineFonts(Font digitFont, Font captionFont, float captionMinimumSize, FontWeight? weight, BarcodeOptions options, float moduleWidth) + { + this.digitFont = digitFont; + this.captionFont = captionFont; + this.captionMinimumSize = captionMinimumSize; + this.weight = weight; + this.options = options; + this.moduleWidth = moduleWidth; + } + + /// + /// Returns the text options a placement renders with. A caption renders in the caption font, resolved once for the symbol so that the measure and the + /// draw use one size. + /// + /// The placement to resolve for. + /// The characters of the placement. + /// The scale of a line that must fit its span, or 1. + /// The text options for the placement. + public RichTextOptions OptionsFor(in BarcodeTextPlacement placement, ReadOnlySpan characters, float lineScale) + { + float dpi = this.options.Dpi; + if (placement.IsCaption) + { + // The caption font resolves once for the symbol, so that the measure and the draw use one size. + if (this.captionOptions is null) + { + Font caption = ResolveCaptionFont( + characters, + placement.Right - placement.Left, + this.moduleWidth, + this.captionFont, + this.captionMinimumSize, + this.weight, + this.options); + + this.captionOptions = BarcodeTextOptionsFactory.Create(caption, dpi, this.weight); + } + + return this.captionOptions; + } + + float scale = placement.FontScale * lineScale; + if (scale == 1F) + { + return this.digitOptions ??= BarcodeTextOptionsFactory.Create(this.digitFont, dpi, this.weight); + } + + // A symbol scales at most a handful of placements, so the scaled font and its options are built once and reused while the factor holds. + float scaledSize = this.digitFont.Size * scale; + if (this.scaledOptions is null || this.scaledOptions.Font.Size != scaledSize) + { + this.scaledOptions = BarcodeTextOptionsFactory.Create(new Font(this.digitFont, scaledSize), dpi, this.weight); + } + + return this.scaledOptions; + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/BarcodeEmitter.cs b/src/ImageSharp.Drawing/Barcodes/BarcodeEmitter.cs new file mode 100644 index 00000000..8098b6b8 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/BarcodeEmitter.cs @@ -0,0 +1,159 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts; +using SixLabors.ImageSharp.Drawing.Helpers; +using SixLabors.ImageSharp.Drawing.Processing; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Measures or draws an encoded symbol through the emitter of its kind. The drawing endpoints, the measuring endpoints and the composites share this one choice +/// of emitter. writes one text block and one measured line for each text placement, and the layout of each measure and draw reads them. +/// +internal static class BarcodeEmitter +{ + /// + /// Returns the number of text lines the symbol prints: one for each text placement when the options carry a font, and none otherwise. + /// + /// The encoded symbol. + /// The sizing and painting options. + /// The number of lines. + public static int LineCount(ref BarcodeSymbol symbol, BarcodeOptions options) + => options.Font is null ? 0 : symbol.Text.Length; + + /// + /// Rents the buffer that holds the measured lines of the symbol, of them. + /// + /// The encoded symbol. + /// The sizing and painting options. + /// The line buffer. + public static PooledBuffer CreateLineBuffer(ref BarcodeSymbol symbol, BarcodeOptions options) + { + int count = LineCount(ref symbol, options); + return count == 0 ? default : new(count); + } + + /// + /// Measures the symbol without drawing anything. + /// + /// The encoded symbol. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The measured layout of the symbol. + public static BarcodeMetrics Measure(ref BarcodeSymbol symbol, BarcodeOptions options, PointF origin) + { + if (symbol.Kind is BarcodeSymbolKind.Matrix or BarcodeSymbolKind.Dotted or BarcodeSymbolKind.Colored) + { + return MatrixEmitter.Measure(ref symbol, options, origin); + } + + if (symbol.Kind == BarcodeSymbolKind.Hexagonal) + { + return HexagonalEmitter.Measure(ref symbol, options, origin, out _); + } + + return BarEmitter.Measure(ref symbol, options, origin); + } + + /// + /// Draws the symbol onto the canvas. + /// + /// The canvas to render onto. + /// The encoded symbol. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The brush that fills the renderable bounds before the symbol draws, or for none. + /// The measured layout of the symbol, the same as returns. + public static BarcodeMetrics Draw(DrawingCanvas canvas, ref BarcodeSymbol symbol, BarcodeOptions options, PointF origin, Brush? background) + { + if (symbol.Kind is BarcodeSymbolKind.Matrix or BarcodeSymbolKind.Dotted or BarcodeSymbolKind.Colored) + { + return MatrixEmitter.Draw(canvas, ref symbol, options, origin, background); + } + + if (symbol.Kind == BarcodeSymbolKind.Hexagonal) + { + return HexagonalEmitter.Draw(canvas, ref symbol, options, origin, background); + } + + return BarEmitter.Draw(canvas, ref symbol, options, origin, background); + } + + /// + /// Shapes the text of the symbol: one text block and one measured line for each text placement. A hexagonal symbol prints no text. + /// + /// The encoded symbol. + /// The sizing and painting options. + /// The measured lines to write, of them. + /// The text blocks to write, one for each line. + public static void Shape(ref BarcodeSymbol symbol, BarcodeOptions options, Span lines, Span blocks) + { + if (symbol.Kind is BarcodeSymbolKind.Matrix or BarcodeSymbolKind.Dotted or BarcodeSymbolKind.Colored) + { + MatrixEmitter.Shape(ref symbol, options, lines, blocks); + } + else if (symbol.Kind != BarcodeSymbolKind.Hexagonal) + { + BarEmitter.Shape(ref symbol, options, lines, blocks); + } + } + + /// + /// Measures the symbol from its shaped text without drawing anything, and reports where the symbol stands. Nothing in the layout box rounds, so the + /// symbol stands at the same offset from the origin wherever the origin is. + /// + /// The encoded symbol. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The lines that wrote. The layout writes the origin of each line. + /// The top left corner of the first module of the symbol, in pixels, before it rounds onto the pixel grid. + /// The measured layout of the symbol. + public static BarcodeMetrics Measure(ref BarcodeSymbol symbol, BarcodeOptions options, PointF origin, Span lines, out PointF symbolOrigin) + { + if (symbol.Kind is BarcodeSymbolKind.Matrix or BarcodeSymbolKind.Dotted or BarcodeSymbolKind.Colored) + { + return MatrixEmitter.Measure(ref symbol, options, origin, lines, out symbolOrigin); + } + + if (symbol.Kind == BarcodeSymbolKind.Hexagonal) + { + return HexagonalEmitter.Measure(ref symbol, options, origin, out symbolOrigin); + } + + return BarEmitter.Measure(ref symbol, options, origin, lines, out symbolOrigin); + } + + /// + /// Draws the symbol from its shaped text onto the canvas. + /// + /// The canvas to render onto. + /// The encoded symbol. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The lines that wrote. The layout writes the origin of each line. + /// The blocks that wrote, one for each line. + /// The brush that fills the renderable bounds before the symbol draws, or for none. + /// The measured layout of the symbol. + public static BarcodeMetrics Draw( + DrawingCanvas canvas, + ref BarcodeSymbol symbol, + BarcodeOptions options, + PointF origin, + Span lines, + ReadOnlySpan blocks, + Brush? background) + { + if (symbol.Kind is BarcodeSymbolKind.Matrix or BarcodeSymbolKind.Dotted or BarcodeSymbolKind.Colored) + { + return MatrixEmitter.Draw(canvas, ref symbol, options, origin, lines, blocks, background); + } + + if (symbol.Kind == BarcodeSymbolKind.Hexagonal) + { + return HexagonalEmitter.Draw(canvas, ref symbol, options, origin, background); + } + + return BarEmitter.Draw(canvas, ref symbol, options, origin, lines, blocks, background); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/BarcodeMeasurer.cs b/src/ImageSharp.Drawing/Barcodes/BarcodeMeasurer.cs new file mode 100644 index 00000000..1f109858 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/BarcodeMeasurer.cs @@ -0,0 +1,270 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodes barcode symbols and measures their layout without drawing them. +/// +public static class BarcodeMeasurer +{ + /// + /// Measures the layout of the barcode in pixel units. + /// + /// The barcode symbology to encode with. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The text to encode. + /// The advance, the bounds and the renderable bounds of the barcode. + /// + /// The text is not valid for the symbology, or the options break a requirement of the standard of the symbology. Examples are an X + /// dimension for a symbol whose size the standard fixes, and a text line that does not fit its span at the smallest printed height. + /// + public static BarcodeMetrics Measure(BarcodeSymbology symbology, BarcodeOptions options, PointF origin, ReadOnlySpan text) + { + Guard.NotNull(symbology, nameof(symbology)); + Guard.NotNull(options, nameof(options)); + + BarcodeSymbol symbol = default; + try + { + symbology.Encode(text, options, ref symbol); + return BarcodeEmitter.Measure(ref symbol, options, origin); + } + finally + { + symbol.Dispose(); + } + } + + /// + /// Measures the layout of a composite barcode of two parts in pixel units. + /// + /// The composite barcode symbology to encode with. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The text of the first symbology of the composite. + /// The text of the second symbology of the composite. + /// The advance, the bounds and the renderable bounds of the barcode. + /// + /// The composite does not have two parts, a text is not valid, the parts do not join, or the options break a requirement of the standard of the + /// symbology. Examples are an X dimension for a symbol whose size the standard fixes, and a text line that does not fit its span. + /// + public static BarcodeMetrics Measure( + CompositeBarcodeSymbology symbology, + BarcodeOptions options, + PointF origin, + ReadOnlySpan first, + ReadOnlySpan second) + { + Guard.NotNull(symbology, nameof(symbology)); + Guard.NotNull(options, nameof(options)); + + return symbology.Measure(options, origin, first, second); + } + + /// + /// Measures the layout of a composite barcode of three parts in pixel units. + /// + /// The composite barcode symbology to encode with. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The text of the first symbology of the composite. + /// The text of the second symbology of the composite. + /// The text of the third symbology of the composite. + /// The advance, the bounds and the renderable bounds of the barcode. + /// + /// The composite does not have three parts, a text is not valid, the parts do not join, or the options break a requirement of the standard of the + /// symbology. Examples are an X dimension for a symbol whose size the standard fixes, and a text line that does not fit its span. + /// + public static BarcodeMetrics Measure( + CompositeBarcodeSymbology symbology, + BarcodeOptions options, + PointF origin, + ReadOnlySpan first, + ReadOnlySpan second, + ReadOnlySpan third) + { + Guard.NotNull(symbology, nameof(symbology)); + Guard.NotNull(options, nameof(options)); + + return symbology.Measure(options, origin, first, second, third); + } + + /// + /// Measures the logical advance of the barcode in pixel units, relative to the origin. + /// + /// The barcode symbology to encode with. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The text to encode. + /// The rectangle that holds the barcode and the line box of each text line, relative to the origin. + /// + /// The text is not valid for the symbology, or the options break a requirement of the standard of the symbology. Examples are an X + /// dimension for a symbol whose size the standard fixes, and a text line that does not fit its span at the smallest printed height. + /// + public static RectangleF MeasureAdvance(BarcodeSymbology symbology, BarcodeOptions options, PointF origin, ReadOnlySpan text) + => Measure(symbology, options, origin, text).Advance; + + /// + /// Measures the logical advance of a composite barcode of two parts in pixel units, relative to the origin. + /// + /// The composite barcode symbology to encode with. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The text of the first symbology of the composite. + /// The text of the second symbology of the composite. + /// The rectangle that holds the parts and the line box of each text line, relative to the origin. + /// + /// The composite does not have two parts, a text is not valid, the parts do not join, or the options break a requirement of the standard of the + /// symbology. Examples are an X dimension for a symbol whose size the standard fixes, and a text line that does not fit its span. + /// + public static RectangleF MeasureAdvance( + CompositeBarcodeSymbology symbology, + BarcodeOptions options, + PointF origin, + ReadOnlySpan first, + ReadOnlySpan second) + => Measure(symbology, options, origin, first, second).Advance; + + /// + /// Measures the logical advance of a composite barcode of three parts in pixel units, relative to the origin. + /// + /// The composite barcode symbology to encode with. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The text of the first symbology of the composite. + /// The text of the second symbology of the composite. + /// The text of the third symbology of the composite. + /// The rectangle that holds the parts and the line box of each text line, relative to the origin. + /// + /// The composite does not have three parts, a text is not valid, the parts do not join, or the options break a requirement of the standard of the + /// symbology. Examples are an X dimension for a symbol whose size the standard fixes, and a text line that does not fit its span. + /// + public static RectangleF MeasureAdvance( + CompositeBarcodeSymbology symbology, + BarcodeOptions options, + PointF origin, + ReadOnlySpan first, + ReadOnlySpan second, + ReadOnlySpan third) + => Measure(symbology, options, origin, first, second, third).Advance; + + /// + /// Measures the rendered ink bounds of the barcode in pixel units. + /// + /// The barcode symbology to encode with. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The text to encode. + /// The rectangle that the bars or modules and the glyphs of the text cover. + /// + /// The text is not valid for the symbology, or the options break a requirement of the standard of the symbology. Examples are an X + /// dimension for a symbol whose size the standard fixes, and a text line that does not fit its span at the smallest printed height. + /// + public static RectangleF MeasureBounds(BarcodeSymbology symbology, BarcodeOptions options, PointF origin, ReadOnlySpan text) + => Measure(symbology, options, origin, text).Bounds; + + /// + /// Measures the rendered ink bounds of a composite barcode of two parts in pixel units. + /// + /// The composite barcode symbology to encode with. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The text of the first symbology of the composite. + /// The text of the second symbology of the composite. + /// The rectangle that the bars or modules and the glyphs of the text of all parts cover. + /// + /// The composite does not have two parts, a text is not valid, the parts do not join, or the options break a requirement of the standard of the + /// symbology. Examples are an X dimension for a symbol whose size the standard fixes, and a text line that does not fit its span. + /// + public static RectangleF MeasureBounds( + CompositeBarcodeSymbology symbology, + BarcodeOptions options, + PointF origin, + ReadOnlySpan first, + ReadOnlySpan second) + => Measure(symbology, options, origin, first, second).Bounds; + + /// + /// Measures the rendered ink bounds of a composite barcode of three parts in pixel units. + /// + /// The composite barcode symbology to encode with. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The text of the first symbology of the composite. + /// The text of the second symbology of the composite. + /// The text of the third symbology of the composite. + /// The rectangle that the bars or modules and the glyphs of the text of all parts cover. + /// + /// The composite does not have three parts, a text is not valid, the parts do not join, or the options break a requirement of the standard of the + /// symbology. Examples are an X dimension for a symbol whose size the standard fixes, and a text line that does not fit its span. + /// + public static RectangleF MeasureBounds( + CompositeBarcodeSymbology symbology, + BarcodeOptions options, + PointF origin, + ReadOnlySpan first, + ReadOnlySpan second, + ReadOnlySpan third) + => Measure(symbology, options, origin, first, second, third).Bounds; + + /// + /// Measures the full renderable bounds of the barcode in pixel units. + /// + /// The barcode symbology to encode with. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The text to encode. + /// The union of the advance, positioned at the origin, and the ink bounds. + /// + /// The text is not valid for the symbology, or the options break a requirement of the standard of the symbology. Examples are an X + /// dimension for a symbol whose size the standard fixes, and a text line that does not fit its span at the smallest printed height. + /// + public static RectangleF MeasureRenderableBounds(BarcodeSymbology symbology, BarcodeOptions options, PointF origin, ReadOnlySpan text) + => Measure(symbology, options, origin, text).RenderableBounds; + + /// + /// Measures the full renderable bounds of a composite barcode of two parts in pixel units. + /// + /// The composite barcode symbology to encode with. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The text of the first symbology of the composite. + /// The text of the second symbology of the composite. + /// The union of the advance, positioned at the origin, and the ink bounds. + /// + /// The composite does not have two parts, a text is not valid, the parts do not join, or the options break a requirement of the standard of the + /// symbology. Examples are an X dimension for a symbol whose size the standard fixes, and a text line that does not fit its span. + /// + public static RectangleF MeasureRenderableBounds( + CompositeBarcodeSymbology symbology, + BarcodeOptions options, + PointF origin, + ReadOnlySpan first, + ReadOnlySpan second) + => Measure(symbology, options, origin, first, second).RenderableBounds; + + /// + /// Measures the full renderable bounds of a composite barcode of three parts in pixel units. + /// + /// The composite barcode symbology to encode with. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The text of the first symbology of the composite. + /// The text of the second symbology of the composite. + /// The text of the third symbology of the composite. + /// The union of the advance, positioned at the origin, and the ink bounds. + /// + /// The composite does not have three parts, a text is not valid, the parts do not join, or the options break a requirement of the standard of the + /// symbology. Examples are an X dimension for a symbol whose size the standard fixes, and a text line that does not fit its span. + /// + public static RectangleF MeasureRenderableBounds( + CompositeBarcodeSymbology symbology, + BarcodeOptions options, + PointF origin, + ReadOnlySpan first, + ReadOnlySpan second, + ReadOnlySpan third) + => Measure(symbology, options, origin, first, second, third).RenderableBounds; +} diff --git a/src/ImageSharp.Drawing/Barcodes/BarcodeMetrics.cs b/src/ImageSharp.Drawing/Barcodes/BarcodeMetrics.cs new file mode 100644 index 00000000..826a5a4f --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/BarcodeMetrics.cs @@ -0,0 +1,47 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The measured layout of a barcode in pixel units. +/// +public readonly struct BarcodeMetrics +{ + /// + /// Initializes a new instance of the struct. + /// + /// The layout box of the barcode and its text lines, relative to the origin. + /// The exact ink of the bars or modules and of the text. + /// The union of the advance, positioned at the origin, and the bounds. + internal BarcodeMetrics(RectangleF advance, RectangleF bounds, RectangleF renderableBounds) + { + this.Advance = advance; + this.Bounds = bounds; + this.RenderableBounds = renderableBounds; + } + + /// + /// Gets the logical advance rectangle of the barcode in pixel units, relative to the origin. + /// + /// + /// The rectangle holds the barcode and the line box of each text line. Its top left corner is the origin, so its position is always zero. + /// + public RectangleF Advance { get; } + + /// + /// Gets the rendered ink bounds of the barcode in pixel units: the bars or modules as they are drawn and the glyphs of the text. + /// + /// + /// The rectangle can be smaller or larger than the advance. It can have a non-zero origin. + /// + public RectangleF Bounds { get; } + + /// + /// Gets the union of the advance rectangle, positioned at the origin, and the bounds, in pixel units. The background fills this rectangle. + /// + /// + /// Use this rectangle when the advance and the ink must fit in the same box. + /// + public RectangleF RenderableBounds { get; } +} diff --git a/src/ImageSharp.Drawing/Barcodes/BarcodeModuleColor.cs b/src/ImageSharp.Drawing/Barcodes/BarcodeModuleColor.cs new file mode 100644 index 00000000..ecdeeaa1 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/BarcodeModuleColor.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The colour of a module of a colour symbol. Light and dark take the background and the bar brush, as the modules of every other symbol do; the four +/// data colours are fixed, because a reader tells them apart by hue. +/// +internal enum BarcodeModuleColor : byte +{ + /// + /// A light module, drawn as the background. + /// + Light, + + /// + /// A dark module, drawn with the bar brush. + /// + Dark, + + /// + /// A cyan module, sRGB 00FFFF. + /// + Cyan, + + /// + /// A magenta module, sRGB FF00FF. + /// + Magenta, + + /// + /// A yellow module, sRGB FFFF00. + /// + Yellow, + + /// + /// A green module, sRGB 00FF00. + /// + Green, +} diff --git a/src/ImageSharp.Drawing/Barcodes/BarcodeOptions.cs b/src/ImageSharp.Drawing/Barcodes/BarcodeOptions.cs new file mode 100644 index 00000000..8e1b1f41 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/BarcodeOptions.cs @@ -0,0 +1,211 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts; +using SixLabors.ImageSharp.Drawing.Processing; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Options that control how a barcode is sized and painted. The sizes are physical: the X dimension and the bar height in millimetres, and the resolution that +/// turns them into pixels. +/// +public sealed class BarcodeOptions +{ + /// + /// The number of millimetres in one inch, which converts the millimetre sizes to pixels at . + /// + internal const double MillimetresPerInch = 25.4; + + /// + /// The tolerance in pixels that keeps a millimetre limit on the whole pixel it falls on against the rounding error of the millimetre to pixel conversion. + /// + internal const double LimitTolerance = 1E-9; + + private float dpi = 300F; + private double? xDimension; + private double? barHeight; + + /// + /// Gets or sets the resolution in dots per inch. This value converts the millimetre sizes to pixels. gives the rule that rounds a + /// module to whole pixels. The text also uses this resolution. Set this value to the resolution of the device that prints or shows the image. The default + /// is 300. Many printers use this resolution. + /// + /// The value is not greater than zero. + public float Dpi + { + get => this.dpi; + set + { + Guard.MustBeGreaterThan(value, 0, nameof(this.Dpi)); + this.dpi = value; + } + } + + /// + /// Gets or sets the X dimension in millimetres: the width of the narrowest element of the symbology, which is one module. Set to + /// take the nominal X dimension the specification of the symbology gives. Where the specification fixes the width of the whole symbol, as for + /// , the nominal X dimension is that width divided by the module count of the symbol. Some specifications give no X + /// dimension. Aztec Code, Code One, Data Matrix, DotCode, Han Xin Code, QR Code and Ultracode then use + /// , 0.495 millimetres. The other symbologies then use + /// , 0.330 millimetres. + /// + /// A symbol draws at the X dimension of the nearest whole number of pixels at , at least one pixel, so every module of the symbol is + /// the same number of pixels wide. The symbol can then be slightly larger or smaller than this X dimension gives. Where the specification of the symbology + /// gives a smallest and a largest X dimension, the code clamps the number of pixels to that range. A value outside the range thus draws at the nearest + /// limit. The specification sets the range to make sure that a scanner can read the symbol. Thus each symbol stays inside its specification for all + /// values. At a resolution where no whole number of pixels is inside the range, the symbol draws at the smallest whole number of pixels over the smallest X + /// dimension. , whose hexagonal modules do not stand on whole pixels, and , whose width + /// the specification fixes, draw at the exact X dimension. throws when this value is set. + /// + /// + /// The value is not greater than zero. + public double? XDimension + { + get => this.xDimension; + set + { + if (value is double x) + { + Guard.MustBeGreaterThan(x, 0, nameof(this.XDimension)); + } + + this.xDimension = value; + } + } + + /// + /// Gets or sets the bar height in millimetres. Set to take the nominal height that the symbology specification gives. + /// + /// Some specifications give a minimum bar height, a maximum bar height, or both. A value outside that range draws at the nearest limit. The specification + /// sets the range to make sure that a scanner can read the symbol. Thus each symbol stays inside its specification for all values. The X dimension uses + /// the same rule. + /// + /// + /// The value is not greater than zero. + public double? BarHeight + { + get => this.barHeight; + set + { + if (value is double height) + { + Guard.MustBeGreaterThan(height, 0, nameof(this.BarHeight)); + } + + this.barHeight = value; + } + } + + /// + /// Gets or sets the brush used to fill the bars and dark modules. + /// + public Brush BarBrush { get; set; } = Brushes.Solid(Color.Black); + + /// + /// Gets or sets the brush that fills the symbol background and the quiet zones. Set to leave the background as it is. Symbology + /// specifications require a light background for reliable scanning. A background is safe over light content only. + /// + public Brush? Background { get; set; } + + /// + /// Gets or sets the font for the human readable interpretation, whose size is in points and renders at . Set to + /// print no text. The text prints at the size of this font, except where the standard of a symbology sets a size. The text of an Intelligent Mail Package + /// Barcode moves into its printed character height of 0.090 to 0.125 inch. This text prints bold, as its standard requires. When the font has no bold face, + /// this text prints with a synthetic weight. The text of an Intelligent Mail barcode prints at 10 points or larger, and the text of an Australia Post + /// barcode at 8 points or smaller. The text of a PZN prints at 6 points or larger. The text of a GS1-128, GS1 DataBar, ITF-14 or GS1 two-dimensional symbol + /// prints at an em height of 2 mm or larger, which is about 5.67 points. When a line that must stay within its span is wider than the span, the line prints + /// smaller. The quiet zone digits of a UPC symbol are an example of such a line. + /// + public Font? Font { get; set; } + + /// + /// Gets or sets the font for the caption that a data layer symbology prints above its symbol. Set to use . Its + /// size is the starting point, and then decides the final size. An ISBN or ISMN caption does not print below 9 point, + /// the minimum the ISBN and ISMN manuals set. + /// + public Font? CaptionFont { get; set; } + + /// + /// Gets or sets a value indicating whether the caption spans the bars of its symbol. When , which is the default, the ends of the + /// caption meet the ends of the bars. When , the caption prints at the size of its own font. The drawn area then becomes wider when + /// the caption is wider than the bars. + /// + public bool FitCaptionToSymbolWidth { get; set; } = true; + + /// + /// Gets or sets a value indicating whether the drawn area holds the mandatory quiet zones. When , the symbol starts at the draw + /// origin, and the caller must keep the surrounding area clear. CAUTION: scanners reject a symbol without its quiet zones. + /// + public bool IncludeQuietZones { get; set; } = true; + + /// + /// Resolves the X dimension that a symbol draws at, in millimetres: , or the nominal X dimension when no X dimension is set, at + /// the nearest whole number of pixels at , clamped to the whole numbers of pixels inside the range of the specification. A module of a + /// whole number of pixels draws every bar and space of one width at the same number of pixels. The encoder converts the millimetre sizes of the symbol, + /// such as the bar height, to modules with this X dimension, so they keep their sizes. + /// + /// The smallest X dimension the specification permits, in millimetres, or zero where it gives none. + /// The nominal X dimension of the symbology in millimetres. + /// The largest X dimension the specification permits, in millimetres, or where it gives + /// none. + /// The X dimension in millimetres. + internal double ResolveXDimension(double minimum, double nominal, double maximum) + { + double pixelsPerMillimetre = this.Dpi / MillimetresPerInch; + double pixels = PixelsOf(this.XDimension ?? nominal, pixelsPerMillimetre); + + // The clamp works in whole pixels: the largest whole number of pixels at or under the maximum, and the smallest at or over the minimum and at least + // one. The tolerance keeps a limit that falls on a whole pixel, such as 0.254 mm at 100 dpi, on that pixel against the rounding error of the + // conversion. At a low resolution a narrow range can hold no whole pixel, and the lowest count is then over the highest. The minimum applies last, so + // the module is never under the minimum and is then over the maximum. + double highest = Math.Floor((maximum * pixelsPerMillimetre) + LimitTolerance); + double lowest = Math.Max(1, Math.Ceiling((minimum * pixelsPerMillimetre) - LimitTolerance)); + pixels = Math.Max(Math.Min(pixels, highest), lowest); + + // An inch is 254 tenths of a millimetre. The product 254 x pixels is a whole number, and 10 x Dpi is exact in a double because Dpi is a float. So + // the division is the only rounding, and IEEE 754 rounds it to the nearest double of the true size. Division by pixelsPerMillimetre rounds twice, + // and the size can then miss the nearest double by one bit. + return pixels * 254 / (10 * (double)this.Dpi); + } + + /// + /// Returns the number of pixels a module of an X dimension from draws at, at . A + /// symbology whose elements are not whole modules counts its runs in these pixels, so every element draws at a whole number of pixels. + /// + /// The resolved X dimension in millimetres. + /// The number of pixels in one module. + internal int ModulePixels(double xDimension) + => (int)PixelsOf(xDimension, this.Dpi / MillimetresPerInch); + + /// + /// Returns the width in pixels of a module of a built symbol at . + /// + /// The X dimension of the built symbol in millimetres. + /// The width of one module in pixels. + internal float ModuleWidth(double xDimension) + => (float)(xDimension / MillimetresPerInch * this.Dpi); + + /// + /// Returns the nearest whole number of pixels to an X dimension, at least one pixel. Half a pixel rounds up, to the larger module. + /// + /// The X dimension in millimetres. + /// The number of pixels in one millimetre. + /// The number of pixels. + private static double PixelsOf(double xDimension, double pixelsPerMillimetre) + => Math.Max(1, Math.Round(xDimension * pixelsPerMillimetre, MidpointRounding.AwayFromZero)); + + /// + /// Resolves the bar height in modules: divided by the X dimension, both in millimetres, or the nominal height of the symbology when + /// no bar height is set, clamped to the range of heights that the specification of the symbology gives. A bar height outside the range draws at the + /// nearest limit, as an X dimension outside its range does. + /// + /// The X dimension of the symbol in millimetres, . + /// The smallest height that the specification permits in modules, or zero where it gives none. + /// The nominal height of the symbology in modules. + /// The largest height that the specification permits in modules, or where it gives + /// none. + /// The bar height in modules. + internal float ResolveBarHeight(double xDimension, float minimumHeight, float nominalHeight, float maximumHeight) + => Math.Clamp(this.BarHeight.HasValue ? (float)(this.BarHeight.Value / xDimension) : nominalHeight, minimumHeight, maximumHeight); +} diff --git a/src/ImageSharp.Drawing/Barcodes/BarcodePath.cs b/src/ImageSharp.Drawing/Barcodes/BarcodePath.cs new file mode 100644 index 00000000..4c7480bc --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/BarcodePath.cs @@ -0,0 +1,379 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The dark elements of one barcode symbol as a single closed path: the bars or dark module runs, each an axis aligned rectangle of four points, the dark +/// hexagons of a hexagonal symbol, each a pointed hexagon of six points around its centre, and the dark dots of a dotted symbol, each a circle of one radius +/// around its centre. The canvas retains the path until it flushes, so the path owns its arrays. +/// +internal sealed class BarcodePath : IPath +{ + /// + /// The number of points of the closed contour of a rectangle, one for each corner. + /// + private const int RectanglePointCount = 4; + + /// + /// The number of points of the closed contour of a hexagon, one for each vertex. + /// + private const int HexagonPointCount = 6; + + /// + /// The rectangles, in pixels. No two rectangles overlap. + /// + private readonly RectangleF[] rectangles; + + /// + /// The centres of the hexagons, in pixels. No two hexagons overlap. + /// + private readonly PointF[] hexagonCentres; + + /// + /// Half the width and half the height of every hexagon, in pixels. A hexagon has its points at the top and the bottom and its flat sides on the left and + /// the right. + /// + private readonly SizeF hexagonHalfSize; + + /// + /// The centres of the circles, in pixels. No two circles overlap. + /// + private readonly PointF[] circleCentres; + + /// + /// The radius of every circle, in pixels. + /// + private readonly float circleRadius; + + /// + /// The geometry baked at the last scale the pipeline asked for. + /// + private LinearGeometryCache geometryCache; + + /// + /// Initializes a new instance of the class with rectangles. + /// + /// The rectangles, in pixels. The path takes ownership of the array. + /// The union of the rectangles. + public BarcodePath(RectangleF[] rectangles, RectangleF bounds) + : this(rectangles, [], default, [], 0, bounds) + { + } + + /// + /// Initializes a new instance of the class with hexagons of one size. + /// + /// The centres of the hexagons, in pixels. The path takes ownership of the array. + /// Half the width and half the height of every hexagon, in pixels. + /// The union of the hexagons. + public BarcodePath(PointF[] hexagonCentres, SizeF hexagonHalfSize, RectangleF bounds) + : this([], hexagonCentres, hexagonHalfSize, [], 0, bounds) + { + } + + /// + /// Initializes a new instance of the class with circles of one radius. + /// + /// The centres of the circles, in pixels. The path takes ownership of the array. + /// The radius of every circle, in pixels. + /// The union of the circles. + public BarcodePath(PointF[] circleCentres, float circleRadius, RectangleF bounds) + : this([], [], default, circleCentres, circleRadius, bounds) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The rectangles, in pixels. + /// The centres of the hexagons, in pixels. + /// Half the width and half the height of every hexagon, in pixels. + /// The centres of the circles, in pixels. + /// The radius of every circle, in pixels. + /// The union of the rectangles, the hexagons and the circles. + private BarcodePath(RectangleF[] rectangles, PointF[] hexagonCentres, SizeF hexagonHalfSize, PointF[] circleCentres, float circleRadius, RectangleF bounds) + { + this.rectangles = rectangles; + this.hexagonCentres = hexagonCentres; + this.hexagonHalfSize = hexagonHalfSize; + this.circleCentres = circleCentres; + this.circleRadius = circleRadius; + this.Bounds = bounds; + } + + /// + public PathTypes PathType => PathTypes.Closed; + + /// + public RectangleF Bounds { get; } + + /// + public IEnumerable Flatten() + { + foreach (RectangleF rectangle in this.rectangles) + { + yield return new RectanglePolygon(rectangle); + } + + foreach (PointF centre in this.hexagonCentres) + { + PointF[] points = new PointF[HexagonPointCount]; + WriteHexagon(centre, this.hexagonHalfSize, Vector2.One, points); + yield return new Polygon(points); + } + + foreach (PointF centre in this.circleCentres) + { + yield return new EllipsePolygon(centre, this.circleRadius); + } + } + + /// + public LinearGeometry ToLinearGeometry(Vector2 scale) + => this.geometryCache.TryGet(scale, out LinearGeometry? hit) + ? hit + : this.geometryCache.Store(scale, this.BuildLinearGeometry(scale)); + + /// + public float ComputeLength(Vector2 scale) + => this.ToLinearGeometry(scale).ComputeLength(); + + /// + public float ComputeArea(Vector2 scale) + => this.ToLinearGeometry(scale).ComputeArea(); + + /// + public bool Contains(PointF point, IntersectionRule intersectionRule, Vector2 scale) + { + PointF scaledPoint = new(point.X * scale.X, point.Y * scale.Y); + + return this.ToLinearGeometry(scale).Contains(scaledPoint, intersectionRule); + } + + /// + public bool TryGetPathPointAtDistance(float distance, Vector2 scale, out PathPoint pathPoint) + => this.ToLinearGeometry(scale).TryGetPathPointAtDistance(distance, out pathPoint); + + /// + public bool TryGetPathPointAtDistanceUnbounded(float distance, Vector2 scale, out PathPoint pathPoint) + => this.ToLinearGeometry(scale).TryGetPathPointAtDistanceUnbounded(distance, out pathPoint); + + /// + public bool TryGetSegment(float startDistance, float stopDistance, bool startOnBeginFigure, Vector2 scale, out IPath path) + => this.ToLinearGeometry(scale).TryGetSegment(startDistance, stopDistance, startOnBeginFigure, out path); + + /// + public IPath Transform(Matrix4x4 matrix) + { + if (matrix.IsIdentity) + { + return this; + } + + RectangleF[] rectangles = this.rectangles; + PointF[] centres = this.hexagonCentres; + PointF[] circles = this.circleCentres; + + // A translation or a scale keeps every rectangle axis aligned and every hexagon pointed at the top and the bottom, so the path keeps its form. An axis + // swap keeps the rectangles but turns the hexagons on their side, so it keeps the form only when there are no hexagons. A circle stays a circle only + // when both axes scale by the same magnitude; otherwise it becomes an ellipse. + bool scaleOnly = matrix.M12 == 0 && matrix.M21 == 0; + bool keepsCircles = circles.Length == 0 || MathF.Abs(matrix.M11) == MathF.Abs(matrix.M22); + if (keepsCircles && (scaleOnly || (centres.Length == 0 && circles.Length == 0 && MatrixUtilities.PreservesAxisAlignedRectangles(matrix)))) + { + // Each rectangle transforms by two opposite corners; a negative scale or a swap can exchange the edges, so the edges are sorted again. A hexagon is + // symmetric about both of its axes, so a negative scale moves only its centre, and its half size takes the scale magnitudes. + RectangleF[] transformed = new RectangleF[rectangles.Length]; + Vector2 boundsMin = Vector2.Transform(new Vector2(this.Bounds.Left, this.Bounds.Top), matrix); + Vector2 boundsMax = Vector2.Transform(new Vector2(this.Bounds.Right, this.Bounds.Bottom), matrix); + for (int i = 0; i < rectangles.Length; i++) + { + RectangleF rectangle = rectangles[i]; + Vector2 a = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix); + Vector2 b = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix); + transformed[i] = RectangleF.FromLTRB(MathF.Min(a.X, b.X), MathF.Min(a.Y, b.Y), MathF.Max(a.X, b.X), MathF.Max(a.Y, b.Y)); + } + + PointF[] transformedCentres = new PointF[centres.Length]; + for (int i = 0; i < centres.Length; i++) + { + transformedCentres[i] = Vector2.Transform(centres[i], matrix); + } + + // A circle is symmetric about its centre, so only the centre moves, and the radius takes the scale magnitude, which is the same on both axes here. + PointF[] transformedCircles = new PointF[circles.Length]; + for (int i = 0; i < circles.Length; i++) + { + transformedCircles[i] = Vector2.Transform(circles[i], matrix); + } + + return new BarcodePath( + transformed, + transformedCentres, + new SizeF(this.hexagonHalfSize.Width * MathF.Abs(matrix.M11), this.hexagonHalfSize.Height * MathF.Abs(matrix.M22)), + transformedCircles, + this.circleRadius * MathF.Abs(matrix.M11), + RectangleF.FromLTRB( + MathF.Min(boundsMin.X, boundsMax.X), + MathF.Min(boundsMin.Y, boundsMax.Y), + MathF.Max(boundsMin.X, boundsMax.X), + MathF.Max(boundsMin.Y, boundsMax.Y))); + } + + // A rotation or a skew turns the rectangles into general quadrilaterals, the hexagons into general hexagons and the circles into ellipses. + IPath[] paths = new IPath[rectangles.Length + centres.Length + circles.Length]; + int index = 0; + foreach (ISimplePath polygon in this.Flatten()) + { + paths[index++] = ((IPath)polygon).Transform(matrix); + } + + return new ComplexPolygon(paths); + } + + /// + public IPath AsClosedPath() => this; + + /// + /// Writes the six points of a hexagon clockwise from its top point, scaled by . The flat sides stand at half the width from the + /// centre, and they run from half the half height above the centre to half the half height below it. + /// + /// The centre. + /// Half the width and half the height. + /// The X/Y scale applied to the points. + /// The buffer of six points. + private static void WriteHexagon(PointF centre, SizeF halfSize, Vector2 scale, Span points) + { + float x = centre.X; + float y = centre.Y; + float w = halfSize.Width; + float h = halfSize.Height; + points[0] = new PointF(x * scale.X, (y - h) * scale.Y); + points[1] = new PointF((x + w) * scale.X, (y - (h / 2)) * scale.Y); + points[2] = new PointF((x + w) * scale.X, (y + (h / 2)) * scale.Y); + points[3] = new PointF(x * scale.X, (y + h) * scale.Y); + points[4] = new PointF((x - w) * scale.X, (y + (h / 2)) * scale.Y); + points[5] = new PointF((x - w) * scale.X, (y - (h / 2)) * scale.Y); + } + + /// + /// Builds the geometry: one closed contour of four points for each rectangle, clockwise from the top left corner, then one closed contour of six points for + /// each hexagon, clockwise from its top point, then one closed contour for each circle, each point scaled by . + /// + /// The X/Y scale applied to the points. + /// The retained linear geometry. + private LinearGeometry BuildLinearGeometry(Vector2 scale) + { + RectangleF[] rectangles = this.rectangles; + PointF[] centres = this.hexagonCentres; + PointF[] circles = this.circleCentres; + + // Every circle has the same radius, so one circle about the origin is flattened at this scale by the ellipse's own curve flattening, and each circle + // copies its points offset by its scaled centre. The template is one closed contour that does not repeat its first point. + IReadOnlyList circlePoints = []; + RectangleF circleBounds = default; + if (circles.Length != 0) + { + LinearGeometry template = new EllipsePolygon(PointF.Empty, this.circleRadius).ToLinearGeometry(scale); + circlePoints = template.Points; + circleBounds = template.Info.Bounds; + } + + int circlePointCount = circlePoints.Count; + int rectanglePoints = rectangles.Length * RectanglePointCount; + int hexagonPoints = centres.Length * HexagonPointCount; + PointF[] points = new PointF[rectanglePoints + hexagonPoints + (circles.Length * circlePointCount)]; + LinearContour[] contours = new LinearContour[rectangles.Length + centres.Length + circles.Length]; + for (int i = 0; i < rectangles.Length; i++) + { + RectangleF rectangle = rectangles[i]; + float left = rectangle.Left * scale.X; + float top = rectangle.Top * scale.Y; + float right = rectangle.Right * scale.X; + float bottom = rectangle.Bottom * scale.Y; + + // A negative scale mirrors the rectangle, so the contour bounds sort the edges. + RectangleF bounds = RectangleF.FromLTRB(MathF.Min(left, right), MathF.Min(top, bottom), MathF.Max(left, right), MathF.Max(top, bottom)); + int start = i * RectanglePointCount; + points[start] = new PointF(left, top); + points[start + 1] = new PointF(right, top); + points[start + 2] = new PointF(right, bottom); + points[start + 3] = new PointF(left, bottom); + contours[i] = new LinearContour + { + PointStart = start, + PointCount = RectanglePointCount, + Bounds = bounds, + SegmentStart = start, + SegmentCount = RectanglePointCount, + IsClosed = true + }; + } + + SizeF halfSize = this.hexagonHalfSize; + float halfWidth = MathF.Abs(halfSize.Width * scale.X); + float halfHeight = MathF.Abs(halfSize.Height * scale.Y); + for (int i = 0; i < centres.Length; i++) + { + int start = rectanglePoints + (i * HexagonPointCount); + WriteHexagon(centres[i], halfSize, scale, points.AsSpan(start, HexagonPointCount)); + float x = centres[i].X * scale.X; + float y = centres[i].Y * scale.Y; + contours[rectangles.Length + i] = new LinearContour + { + PointStart = start, + PointCount = HexagonPointCount, + Bounds = RectangleF.FromLTRB(x - halfWidth, y - halfHeight, x + halfWidth, y + halfHeight), + SegmentStart = start, + SegmentCount = HexagonPointCount, + IsClosed = true + }; + } + + int circleContour = rectangles.Length + centres.Length; + for (int i = 0; i < circles.Length; i++) + { + int start = rectanglePoints + hexagonPoints + (i * circlePointCount); + float x = circles[i].X * scale.X; + float y = circles[i].Y * scale.Y; + for (int p = 0; p < circlePointCount; p++) + { + PointF point = circlePoints[p]; + points[start + p] = new PointF(point.X + x, point.Y + y); + } + + contours[circleContour + i] = new LinearContour + { + PointStart = start, + PointCount = circlePointCount, + Bounds = new RectangleF(circleBounds.X + x, circleBounds.Y + y, circleBounds.Width, circleBounds.Height), + SegmentStart = start, + SegmentCount = circlePointCount, + IsClosed = true + }; + } + + float boundsLeft = this.Bounds.Left * scale.X; + float boundsTop = this.Bounds.Top * scale.Y; + float boundsRight = this.Bounds.Right * scale.X; + float boundsBottom = this.Bounds.Bottom * scale.Y; + return new LinearGeometry( + new LinearGeometryInfo + { + Bounds = RectangleF.FromLTRB( + MathF.Min(boundsLeft, boundsRight), + MathF.Min(boundsTop, boundsBottom), + MathF.Max(boundsLeft, boundsRight), + MathF.Max(boundsTop, boundsBottom)), + ContourCount = contours.Length, + PointCount = points.Length, + SegmentCount = points.Length + }, + contours, + points); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/BarcodeRow.cs b/src/ImageSharp.Drawing/Barcodes/BarcodeRow.cs new file mode 100644 index 00000000..7ac201e1 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/BarcodeRow.cs @@ -0,0 +1,89 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// One row of bars and spaces in a linear or stacked symbol: where its runs and bars are in the symbol storage, and where it stands. +/// +internal readonly struct BarcodeRow +{ + /// + /// Initializes a new instance of the struct. + /// + /// The index of the first run of the row. + /// The number of runs of the row. + /// The index of the first bar of the row. + public BarcodeRow(int runStart, int runCount, int barStart) + { + this.RunStart = runStart; + this.RunCount = runCount; + this.BarStart = barStart; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The row whose storage the new row takes. + /// The top of the row in modules from the top of the symbol. + /// The height of the row in modules: the lowest bar bottom of the row. + /// The highest bar top of the row, in modules from the top of the row. + /// The left edge of the first bar of the row that is not empty, in run units from the start of the row. + /// The right edge of the last bar of the row that is not empty, in run units from the start of the row. + public BarcodeRow(in BarcodeRow row, float top, float height, float inkTop, int inkStart, int inkEnd) + { + this.RunStart = row.RunStart; + this.RunCount = row.RunCount; + this.BarStart = row.BarStart; + this.Top = top; + this.Height = height; + this.InkTop = inkTop; + this.InkStart = inkStart; + this.InkEnd = inkEnd; + } + + /// + /// Gets the index of the first run of the row. + /// + public int RunStart { get; } + + /// + /// Gets the number of runs of the row. The runs alternate bar and space, starting with a bar. + /// + public int RunCount { get; } + + /// + /// Gets the index of the first bar of the row. + /// + public int BarStart { get; } + + /// + /// Gets the number of bars of the row: one for each run at an even index. + /// + public int BarCount => (this.RunCount + 1) >> 1; + + /// + /// Gets the top of the row in modules from the top of the symbol. + /// + public float Top { get; } + + /// + /// Gets the height of the row in modules: the lowest bar bottom of the row. + /// + public float Height { get; } + + /// + /// Gets the highest bar top of the row, in modules from the top of the row. + /// + public float InkTop { get; } + + /// + /// Gets the left edge of the first bar of the row that is not empty, in run units from the start of the row, or -1 when the row has no ink. + /// + public int InkStart { get; } + + /// + /// Gets the right edge of the last bar of the row that is not empty, in run units from the start of the row. + /// + public int InkEnd { get; } +} diff --git a/src/ImageSharp.Drawing/Barcodes/BarcodeSymbol.cs b/src/ImageSharp.Drawing/Barcodes/BarcodeSymbol.cs new file mode 100644 index 00000000..13700041 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/BarcodeSymbol.cs @@ -0,0 +1,1127 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.Fonts; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The device independent description of an encoded barcode, measured in modules. A module is the narrowest nominal element of a symbology, whose width the GS1 +/// General Specifications and related specifications call the X-dimension. A symbol carries no pixel sizes, brushes or fonts. The emitter adds those when it +/// turns the symbol into canvas commands. +/// +/// +/// An encoder writes the symbol once: it begins a linear, stacked or matrix symbol with the sizes that its validated input gives, fills the spans that the +/// begin methods return, adds the text and ends the symbol. The symbol keeps its values in three buffers rented from the shared pool. The +/// layout buffer holds the rows, runs and bars or the modules. , or +/// rents it, and replaces it with a larger buffer. The text buffer holds the text +/// placements and their characters, and rents it. The overlay buffer holds the edges of the overlay graphic, and +/// rents it. Each rent happens before the encoder's loops, with the exact size that its validated input gives. +/// returns all three buffers. A rented range is cleared, so every value that the encoder does not write is zero. The caller declares the symbol as a local +/// and passes it by reference. +/// +internal struct BarcodeSymbol : IDisposable +{ + /// + /// The layout buffer: the rows, then the runs, then the bar heights and the bar tops of a linear or stacked symbol, or the modules of a matrix symbol. + /// + private PooledBuffer layoutBuffer; + + /// + /// The text buffer: the text placements, then their characters. + /// + private PooledBuffer textBuffer; + + /// + /// The overlay buffer: the edges of the dark rectangles of the graphic centred on a matrix symbol. + /// + private PooledBuffer overlayBuffer; + + /// + /// The number of edges in , four for each rectangle. + /// + private int overlayEdgeCount; + + /// + /// The byte offset of the runs in the layout buffer. + /// + private int runOffset; + + /// + /// The byte offset of the bar heights in the layout buffer. + /// + private int barOffset; + + /// + /// The number of bars reserved. The bar tops follow that many bar heights. + /// + private int barCapacity; + + /// + /// The byte offset of the characters in the text buffer. + /// + private int charOffset; + + /// + /// The number of modules of a matrix symbol. + /// + private int moduleCount; + + /// + /// The number of runs the rows added so far take. + /// + private int runCount; + + /// + /// The number of bars the rows added so far take. + /// + private int barCount; + + /// + /// The number of rows added so far. + /// + private int rowCount; + + /// + /// The number of text placements added so far. + /// + private int textCount; + + /// + /// The number of characters the text placements added so far take. + /// + private int charCount; + + /// + /// Gets the form of the symbol. + /// + public BarcodeSymbolKind Kind { get; private set; } + + /// + /// Gets the width of the symbol in modules, excluding quiet zones. + /// + public float WidthInModules { get; private set; } + + /// + /// Gets the height of the symbol in modules, excluding the human readable interpretation. + /// + public float HeightInModules { get; private set; } + + /// + /// Gets the width in modules of the quiet zone that comes before the symbol. Each symbology sets its own quiet zone width. For example, Table 5-11 of the + /// GS1 General Specifications gives 11 modules before an EAN-13 symbol, and a postal symbology states its clear zone in inches, which is not a whole number + /// of modules. + /// + public float LeadingQuietZone { get; private set; } + + /// + /// Gets the width of the quiet zone that must follow the symbol, in modules. + /// + public float TrailingQuietZone { get; private set; } + + /// + /// Gets the height of the quiet zone above the symbol, in modules. A symbology whose specification gives no vertical quiet zone has none. + /// + public float TopQuietZone { get; private set; } + + /// + /// Gets the height of the quiet zone below the symbol, in modules. A symbology whose specification gives no vertical quiet zone has none. + /// + public float BottomQuietZone { get; private set; } + + /// + /// Gets the thickness in modules of the bars that close the top and the bottom of a linear or stacked symbol, or zero when there are none: the bearer bar + /// of a linear symbol, which also closes the sides when the quiet zones are drawn, or the edge bars of a stacked symbol. + /// + public float FrameThickness { get; private set; } + + /// + /// Gets the width of one run unit in modules. A run width counts run units. + /// + public float RunUnit { get; private set; } + + /// + /// Gets the number of run units in one module of a symbol whose runs are each a whole number of modules with a correction of less than half a module, or 0 + /// for a symbol without such corrections. Table 5-10 of the GS1 General Specifications corrects the bars and spaces of some EAN/UPC characters by one + /// thirteenth of a module. The emitter rounds the module edges and the corrections onto the pixel grid apart, so a correction never moves an edge further + /// than its own nearest whole number of pixels. + /// + public int CorrectedModuleUnits { get; private set; } + + /// + /// Gets a value indicating whether every bar of the symbol is one width at one pitch, so each run draws at a whole number of pixels of its own. + /// + public bool UniformBars { get; private set; } + + /// + /// Gets the number of module columns of a matrix symbol. + /// + public int Columns { get; private set; } + + /// + /// Gets the number of module rows of a matrix symbol. + /// + public int MatrixRows { get; private set; } + + /// + /// Gets the X dimension in millimetres, the width of one module. The encoder resolves it once from the X dimension of the options, else the nominal X + /// dimension of the specification. gives the rules that round and clamp it. The encoder measures every height it + /// states in millimetres with it, and the emitter draws the modules at it. + /// + public double XDimension { get; private set; } + + /// + /// Gets the side in modules of the square graphic centred on a matrix symbol, or 0 when the symbol has none. The graphic covers its square completely, so + /// the modules under the square do not draw. + /// + public float OverlaySize { get; private set; } + + /// + /// Gets the side of the overlay graphic in the units of . + /// + public float OverlayUnit { get; private set; } + + /// + /// Gets the dark rectangles of the overlay graphic, each as its left, top, right and bottom edge in the units of from the top + /// left corner of its square, the y axis down. The rest of the square is light. + /// + public readonly ReadOnlySpan OverlayEdges => Slice(this.overlayBuffer, 0, this.overlayEdgeCount); + + /// + /// Gets the thickness in modules of the identification bars of a linear symbol, or 0 when the symbol has none. One bar stands above the topmost text line + /// and one below the lowest, each spanning the symbol and its quiet zones. + /// + public float IdentificationBarThickness { get; private set; } + + /// + /// Gets the clear space in modules between an identification bar and the ink of the text line next to it. + /// + public float IdentificationBarClearance { get; private set; } + + /// + /// Gets the smallest printed height of the characters of the text lines in modules, or 0 when the symbology states none. + /// + public float TextHeightMinimum { get; private set; } + + /// + /// Gets the largest printed height of the characters of the text lines in modules, or 0 when the symbology states none. + /// + public float TextHeightMaximum { get; private set; } + + /// + /// Gets the smallest size of the caption in points, or 0 when the symbology states none. + /// + public float CaptionMinimumSize { get; private set; } + + /// + /// Gets the font weight the text of the symbol renders at, or for the weight of the font of the options. A symbology whose standard + /// requires a weight sets it, and a font without a face of that weight renders it with a synthetic weight. + /// + public FontWeight? TextWeight { get; private set; } + + /// + /// Gets the smallest size of the text lines in points, or 0 when the symbology states none. + /// + public float TextMinimumSize { get; private set; } + + /// + /// Gets the largest size of the text lines in points, or 0 when the symbology states none. + /// + public float TextMaximumSize { get; private set; } + + /// + /// Gets the rows of a linear or stacked symbol from the top down. A linear symbol has one row. + /// + public readonly ReadOnlySpan Rows => Slice(this.layoutBuffer, 0, this.rowCount); + + /// + /// Gets the run widths of all rows in run units. The runs of a row alternate bar and space, starting with a bar. + /// + public readonly ReadOnlySpan Runs => Slice(this.layoutBuffer, this.runOffset, this.runCount); + + /// + /// Gets the bar heights of all rows, in modules. + /// + public readonly ReadOnlySpan BarHeights => Slice(this.layoutBuffer, this.barOffset, this.barCount); + + /// + /// Gets the bar tops of all rows, in modules from the top of their row. + /// + public readonly ReadOnlySpan BarTops => Slice(this.layoutBuffer, this.barOffset + (this.barCapacity * sizeof(float)), this.barCount); + + /// + /// Gets the modules of a matrix symbol, row-major, true for a dark module. + /// + public readonly ReadOnlySpan Modules => Slice(this.layoutBuffer, 0, this.moduleCount); + + /// + /// Gets the module colours of a colour symbol, row-major. + /// + public readonly ReadOnlySpan ModuleColors => Slice(this.layoutBuffer, 0, this.moduleCount); + + /// + /// Gets the human readable interpretation. The span is empty when the symbol carries no text. + /// + public readonly ReadOnlySpan Text => Slice(this.textBuffer, 0, this.textCount); + + /// + /// Returns the characters of a text placement of this symbol. + /// + /// The placement. + /// The characters. + public readonly ReadOnlySpan TextOf(in BarcodeTextPlacement placement) + => Slice(this.textBuffer, this.charOffset + (placement.TextStart * sizeof(char)), placement.TextLength); + + /// + /// Begins a linear symbol of one row. + /// + /// The number of runs. + /// The X dimension in millimetres, . + /// The bar heights to write, one for each run at an even index. + /// The bar tops to write, in modules from the top of the symbol. + /// The run widths to write, in run units. + public Span BeginLinear(int runCount, double xDimension, out Span barHeights, out Span barTops) + { + this.Kind = BarcodeSymbolKind.Linear; + this.XDimension = xDimension; + this.Reserve(1, runCount, (runCount + 1) >> 1); + return this.AddRow(runCount, out barHeights, out barTops); + } + + /// + /// Begins a stacked symbol. The encoder then adds each row with . + /// + /// The number of rows. + /// The number of runs of all rows. + /// The number of bars of all rows: for each row, half its run count rounded up. + /// The X dimension in millimetres, . + public void BeginStacked(int rowCount, int runCount, int barCount, double xDimension) + { + this.Kind = BarcodeSymbolKind.Stacked; + this.XDimension = xDimension; + this.Reserve(rowCount, runCount, barCount); + } + + /// + /// Adds the next row of a stacked symbol, below the rows added before it. + /// + /// The number of runs of the row. + /// The bar heights to write, one for each run at an even index. + /// The bar tops to write, in modules from the top of the row. + /// The run widths to write, in run units. + public Span AddRow(int runCount, out Span barHeights, out Span barTops) + { + int rowBars = (runCount + 1) >> 1; + Slice(this.layoutBuffer, this.rowCount * Unsafe.SizeOf(), 1)[0] = new BarcodeRow(this.runCount, runCount, this.barCount); + Span runs = Slice(this.layoutBuffer, this.runOffset + (this.runCount * sizeof(int)), runCount); + int heightOffset = this.barOffset + (this.barCount * sizeof(float)); + barHeights = Slice(this.layoutBuffer, heightOffset, rowBars); + barTops = Slice(this.layoutBuffer, heightOffset + (this.barCapacity * sizeof(float)), rowBars); + this.rowCount++; + this.runCount += runCount; + this.barCount += rowBars; + return runs; + } + + /// + /// Returns the width of a row of runs in run units, for an encoder that places its text or sizes its bars from the width before the symbol ends, and for + /// the ink edges of each row. + /// + /// The alternating bar and space run widths in run units. + /// The width in run units. + public static int RunWidth(ReadOnlySpan runs) + { + // Each lane of the vector adds every lane-count-th run, and the lanes add together at the end. Integer addition is exact, so the sum equals the + // scalar sum in any order. The scalar loop adds the runs that do not fill a vector. + int width = 0; + int i = 0; + if (Vector.IsHardwareAccelerated && runs.Length >= Vector.Count) + { + Vector lanes = Vector.Zero; + for (; i <= runs.Length - Vector.Count; i += Vector.Count) + { + lanes += new Vector(runs[i..]); + } + + width = Vector.Sum(lanes); + } + + for (; i < runs.Length; i++) + { + width += runs[i]; + } + + return width; + } + + /// + /// Shortens the last row added to the given number of runs, for an encoder that reserves the most runs its data can take and learns the exact count as it + /// writes. The storage stays reserved. + /// + /// The number of runs the row keeps, at most the number it was added with. + public void TrimLastRow(int runCount) + { + Span rows = Slice(this.layoutBuffer, 0, this.rowCount); + BarcodeRow row = rows[^1]; + rows[^1] = new BarcodeRow(row.RunStart, runCount, row.BarStart); + this.runCount = row.RunStart + runCount; + this.barCount = row.BarStart + ((runCount + 1) >> 1); + } + + /// + /// Inserts rows into an ended linear or stacked symbol that has no bearer bar and no edge bars, so the symbol becomes a stacked symbol. The rows from the + /// index down and the text lines at or below the top of the new rows move down by their height. A GS1 Composite symbol joins its parts with such rows: + /// section 11.4 of ISO/IEC 24723 puts a separator pattern between the linear component and the 2D component. + /// + /// The index the first new row takes: 0 for rows above the symbol, or the row count for rows below it. + /// The runs of the new rows, one row after the other. Each row is as wide as the symbol. + /// The number of runs of each new row. + /// The height of each new row in modules. + public void InsertRows(int index, ReadOnlySpan runs, ReadOnlySpan rowRunCounts, float height) + { + // The layout buffer holds the rows, the runs and the bars in one block sized when the symbol began, so the rows copy into a new block with room for the + // new rows. All new rows take one rent. The old block stays rented until the copy ends. + PooledBuffer old = this.layoutBuffer; + int oldRowCount = this.rowCount; + int oldRunCount = this.runCount; + int oldBarCount = this.barCount; + int oldRunOffset = this.runOffset; + int oldBarOffset = this.barOffset; + int oldBarCapacity = this.barCapacity; + float insertTop = index < oldRowCount ? this.Rows[index].Top : this.HeightInModules; + try + { + ReadOnlySpan oldRows = Slice(old, 0, oldRowCount); + ReadOnlySpan oldRuns = Slice(old, oldRunOffset, oldRunCount); + ReadOnlySpan oldHeights = Slice(old, oldBarOffset, oldBarCount); + ReadOnlySpan oldTops = Slice(old, oldBarOffset + (oldBarCapacity * sizeof(float)), oldBarCount); + + int newBarCount = 0; + foreach (int count in rowRunCounts) + { + newBarCount += (count + 1) >> 1; + } + + this.Reserve(oldRowCount + rowRunCounts.Length, oldRunCount + runs.Length, oldBarCount + newBarCount); + this.rowCount = 0; + this.runCount = 0; + this.barCount = 0; + for (int r = 0; r <= oldRowCount; r++) + { + if (r == index) + { + int start = 0; + foreach (int count in rowRunCounts) + { + runs.Slice(start, count).CopyTo(this.AddRow(count, out Span newHeights, out _)); + newHeights.Fill(height); + start += count; + } + } + + if (r < oldRowCount) + { + BarcodeRow row = oldRows[r]; + oldRuns.Slice(row.RunStart, row.RunCount).CopyTo(this.AddRow(row.RunCount, out Span heights, out Span tops)); + oldHeights.Slice(row.BarStart, row.BarCount).CopyTo(heights); + oldTops.Slice(row.BarStart, row.BarCount).CopyTo(tops); + } + } + } + finally + { + old.Dispose(); + } + + this.Kind = BarcodeSymbolKind.Stacked; + this.HeightInModules = this.EndRows(0F); + + // A text line at or below the new rows keeps its place relative to the bars it belongs to. + float insertedHeight = height * rowRunCounts.Length; + Span placements = Slice(this.textBuffer, 0, this.textCount); + for (int i = 0; i < placements.Length; i++) + { + BarcodeTextPlacement placement = placements[i]; + if (placement.TextEdge >= insertTop) + { + placements[i] = new BarcodeTextPlacement( + placement.TextStart, + placement.TextLength, + placement.Left, + placement.Right, + placement.Side, + placement.TextEdge + insertedHeight, + placement.FontScale, + placement.IsCaption, + placement.Alignment, + placement.WithinSpan); + } + } + } + + /// + /// Ends a linear symbol with no bearer bar, whose runs are whole modules. + /// + /// The quiet zone before the symbol, in modules. + /// The quiet zone after the symbol, in modules. + public void EndLinear(float leadingQuietZone, float trailingQuietZone) + => this.EndLinear(leadingQuietZone, trailingQuietZone, 0F); + + /// + /// Ends a linear symbol whose runs are whole modules. + /// + /// The quiet zone before the symbol, in modules. + /// The quiet zone after the symbol, in modules. + /// The thickness of the bearer bar in modules, or zero for none. + public void EndLinear(float leadingQuietZone, float trailingQuietZone, float bearerBarThickness) + => this.EndLinear(leadingQuietZone, trailingQuietZone, bearerBarThickness, 1F, false); + + /// + /// Ends a linear symbol. + /// + /// The quiet zone before the symbol, in modules. + /// The quiet zone after the symbol, in modules. + /// The thickness of the bearer bar in modules, or zero for none. + /// The width of one run unit in modules. + /// Whether every bar is one width at one pitch. + public void EndLinear(float leadingQuietZone, float trailingQuietZone, float bearerBarThickness, float runUnit, bool uniformBars) + => this.EndLinear(leadingQuietZone, trailingQuietZone, 0F, 0F, bearerBarThickness, runUnit, uniformBars); + + /// + /// Ends a linear symbol that has quiet zones above and below it. + /// + /// The quiet zone before the symbol, in modules. + /// The quiet zone after the symbol, in modules. + /// The quiet zone above the symbol, in modules. + /// The quiet zone below the symbol, in modules. + /// The thickness of the bearer bar in modules, or zero for none. + /// The width of one run unit in modules. + /// Whether every bar is one width at one pitch. + public void EndLinear( + float leadingQuietZone, + float trailingQuietZone, + float topQuietZone, + float bottomQuietZone, + float bearerBarThickness, + float runUnit, + bool uniformBars) + { + this.LeadingQuietZone = leadingQuietZone; + this.TrailingQuietZone = trailingQuietZone; + this.TopQuietZone = topQuietZone; + this.BottomQuietZone = bottomQuietZone; + this.FrameThickness = bearerBarThickness; + this.RunUnit = runUnit; + this.UniformBars = uniformBars; + + // The bearer bar closes the bottom of the symbol below the lowest bar. + this.HeightInModules = this.EndRows(0F) + bearerBarThickness; + } + + /// + /// Ends a stacked symbol with no vertical quiet zones. + /// + /// The height of the bars that close the top and the bottom of the symbol, or zero for none. + /// The quiet zone before the symbol, in modules. + /// The quiet zone after the symbol, in modules. + public void EndStacked(float edgeBarHeight, float leadingQuietZone, float trailingQuietZone) + => this.EndStacked(edgeBarHeight, leadingQuietZone, trailingQuietZone, 0F, 0F); + + /// + /// Ends a stacked symbol. Every row has the width of the first row; its runs are whole modules. + /// + /// The height of the bars that close the top and the bottom of the symbol, or zero for none. + /// The quiet zone before the symbol, in modules. + /// The quiet zone after the symbol, in modules. + /// The quiet zone above the symbol, in modules. + /// The quiet zone below the symbol, in modules. + public void EndStacked(float edgeBarHeight, float leadingQuietZone, float trailingQuietZone, float topQuietZone, float bottomQuietZone) + { + this.LeadingQuietZone = leadingQuietZone; + this.TrailingQuietZone = trailingQuietZone; + this.TopQuietZone = topQuietZone; + this.BottomQuietZone = bottomQuietZone; + this.FrameThickness = edgeBarHeight; + this.RunUnit = 1F; + + // The rows stand between the top and the bottom edge bars. + this.HeightInModules = this.EndRows(edgeBarHeight) + edgeBarHeight; + } + + /// + /// Begins a matrix symbol. The modules start light. + /// + /// The number of module columns. + /// The number of module rows. + /// The quiet zone on each of the four sides, in modules. + /// The X dimension in millimetres, . + /// The modules to write, row-major, true for a dark module. + public Span BeginMatrix(int columns, int rows, int quietZone, double xDimension) + => this.BeginMatrix(columns, rows, quietZone, quietZone, quietZone, quietZone, xDimension); + + /// + /// Begins a matrix symbol with a quiet zone of its own on each side. The modules start light. + /// + /// The number of module columns. + /// The number of module rows. + /// The quiet zone on the left, in modules. + /// The quiet zone on the right, in modules. + /// The quiet zone above, in modules. + /// The quiet zone below, in modules. + /// The X dimension in millimetres, . + /// The modules to write, row-major, true for a dark module. + public Span BeginMatrix(int columns, int rows, int leadingQuietZone, int trailingQuietZone, int topQuietZone, int bottomQuietZone, double xDimension) + { + this.Kind = BarcodeSymbolKind.Matrix; + this.XDimension = xDimension; + this.Columns = columns; + this.MatrixRows = rows; + this.WidthInModules = columns; + this.HeightInModules = rows; + this.LeadingQuietZone = leadingQuietZone; + this.TrailingQuietZone = trailingQuietZone; + this.TopQuietZone = topQuietZone; + this.BottomQuietZone = bottomQuietZone; + this.RunUnit = 1F; + + this.moduleCount = columns * rows; + this.layoutBuffer = Rent(this.moduleCount); + return Slice(this.layoutBuffer, 0, this.moduleCount); + } + + /// + /// Begins a hexagonal symbol: rows of hexagonal modules X wide, every odd row half a module to the right, the rows √3/2 X apart. The modules start light. + /// + /// The number of module columns. + /// The number of module rows. + /// The quiet zone on the left and the right, in X. + /// The quiet zone on the top and the bottom, in X. + /// The X dimension in millimetres, . + /// The modules to write, row-major, true for a dark module. + public Span BeginHexagonal(int columns, int rows, float sideQuietZone, float endQuietZone, double xDimension) + { + Span modules = this.BeginMatrix(columns, rows, 0, xDimension); + this.Kind = BarcodeSymbolKind.Hexagonal; + + // The pointed hexagons are 2/√3 X tall, and the rows stand √3/2 X apart, so the symbol is that tall over its last row. + this.HeightInModules = (float)(((rows - 1) * Math.Sqrt(3) / 2) + (2 / Math.Sqrt(3))); + this.LeadingQuietZone = sideQuietZone; + this.TrailingQuietZone = sideQuietZone; + this.TopQuietZone = endQuietZone; + this.BottomQuietZone = endQuietZone; + return modules; + } + + /// + /// Begins a dotted symbol: a grid of cells X wide and X high, each dark module a round dot centred in its cell. The modules start light. + /// + /// The number of module columns. + /// The number of module rows. + /// The quiet zone on each of the four sides, in modules. + /// The X dimension in millimetres, . + /// The modules to write, row-major, true for a dark module. + public Span BeginDotted(int columns, int rows, int quietZone, double xDimension) + { + Span modules = this.BeginMatrix(columns, rows, quietZone, xDimension); + this.Kind = BarcodeSymbolKind.Dotted; + return modules; + } + + /// + /// Begins a colour symbol: a grid of square modules, each a . The modules start light. + /// + /// The number of module columns. + /// The number of module rows. + /// The quiet zone on each of the four sides, in modules. + /// The X dimension in millimetres, . + /// The module colours to write, row-major. + public Span BeginColored(int columns, int rows, int quietZone, double xDimension) + { + // A colour takes one byte, as a bool does, so the module buffer holds the colours. + Span modules = this.BeginMatrix(columns, rows, quietZone, xDimension); + this.Kind = BarcodeSymbolKind.Colored; + return MemoryMarshal.Cast(modules); + } + + /// + /// Reserves the text placements and their characters. The encoder calls it once, before it adds any text. + /// + /// The number of placements. + /// The number of characters of all placements. + public void ReserveText(int placementCount, int charCount) + { + // A placement is a whole number of its widest field, four bytes, so the characters that follow the placements start on an even byte offset, which a + // char needs. + this.charOffset = placementCount * Unsafe.SizeOf(); + this.textBuffer = Rent(this.charOffset + (charCount * sizeof(char))); + } + + /// + /// Adds a centred text placement at the full font size. + /// + /// The characters to render. + /// The left edge of the horizontal span, in modules. + /// The right edge of the horizontal span, in modules. + /// The side of the bars the line prints on. + /// The edge of the ink of the line, in modules from the symbol top. + public void AddText(ReadOnlySpan text, float left, float right, BarcodeTextSide side, float textEdge) + => this.AddText(text, left, right, side, textEdge, 1F, false, BarcodeTextAlignment.Centered); + + /// + /// Adds a centred text placement. + /// + /// The characters to render. + /// The left edge of the horizontal span, in modules. + /// The right edge of the horizontal span, in modules. + /// The side of the bars the line prints on. + /// The edge of the ink of the line, in modules from the symbol top. + /// The factor applied to the caller's font size for this placement. + public void AddText(ReadOnlySpan text, float left, float right, BarcodeTextSide side, float textEdge, float fontScale) + => this.AddText(text, left, right, side, textEdge, fontScale, false, BarcodeTextAlignment.Centered); + + /// + /// Adds a text placement at the full font size. + /// + /// The characters to render. + /// The left edge of the horizontal span, in modules. + /// The right edge of the horizontal span, in modules. + /// The side of the bars the line prints on. + /// The edge of the ink of the line, in modules from the symbol top. + /// Where the line stands within its span. + public void AddText(ReadOnlySpan text, float left, float right, BarcodeTextSide side, float textEdge, BarcodeTextAlignment alignment) + => this.AddText(text, left, right, side, textEdge, 1F, false, alignment); + + /// + /// Adds a centred text placement. + /// + /// The characters to render. + /// The left edge of the horizontal span, in modules. + /// The right edge of the horizontal span, in modules. + /// The side of the bars the line prints on. + /// The edge of the ink of the line, in modules from the symbol top. + /// The factor applied to the caller's font size for this placement. + /// Whether this placement is a data layer caption rendered with the caption font. + public void AddText(ReadOnlySpan text, float left, float right, BarcodeTextSide side, float textEdge, float fontScale, bool isCaption) + => this.AddText(text, left, right, side, textEdge, fontScale, isCaption, BarcodeTextAlignment.Centered); + + /// + /// Adds a text placement. The characters copy into the character storage that reserved. + /// + /// The characters to render. + /// The left edge of the horizontal span, in modules. + /// The right edge of the horizontal span, in modules. + /// The side of the bars the line prints on. + /// The edge of the ink of the line, in modules from the symbol top. + /// The factor applied to the caller's font size for this placement. + /// Whether this placement is a data layer caption rendered with the caption font. + /// Where the line stands within its span. + public void AddText( + ReadOnlySpan text, + float left, + float right, + BarcodeTextSide side, + float textEdge, + float fontScale, + bool isCaption, + BarcodeTextAlignment alignment) + => this.AddText(text, left, right, side, textEdge, fontScale, isCaption, alignment, false); + + /// + /// Adds a centred text placement at the full font size that must stay within its span. The line prints in a smaller font when it is wider than its span. + /// The characters copy into the character storage that reserved. + /// + /// The characters to render. + /// The left edge of the horizontal span, in modules. + /// The right edge of the horizontal span, in modules. + /// The side of the bars the line prints on. + /// The edge of the ink of the line, in modules from the symbol top. + public void AddTextWithinSpan(ReadOnlySpan text, float left, float right, BarcodeTextSide side, float textEdge) + => this.AddText(text, left, right, side, textEdge, 1F, false, BarcodeTextAlignment.Centered, true); + + /// + /// Adds a text placement at the full font size whose ink must stay within its span. The line prints in a smaller font when it is wider than its span. The + /// characters copy into the character storage that reserved. + /// + /// The characters to render. + /// The left edge of the horizontal span, in modules. + /// The right edge of the horizontal span, in modules. + /// The side of the bars the line prints on. + /// The edge of the ink of the line, in modules from the symbol top. + /// Where the line stands within its span. + public void AddTextWithinSpan(ReadOnlySpan text, float left, float right, BarcodeTextSide side, float textEdge, BarcodeTextAlignment alignment) + => this.AddText(text, left, right, side, textEdge, 1F, false, alignment, true); + + /// + /// Adds a centred text placement at the full font size whose characters are three parts in turn, so a caller that frames data with delimiters writes it + /// with no buffer of its own. + /// + /// The characters before the body. + /// The body characters. + /// The characters after the body. + /// The left edge of the horizontal span, in modules. + /// The right edge of the horizontal span, in modules. + /// The side of the bars the line prints on. + /// The edge of the ink of the line, in modules from the symbol top. + public void AddText( + ReadOnlySpan prefix, + ReadOnlySpan body, + ReadOnlySpan suffix, + float left, + float right, + BarcodeTextSide side, + float textEdge) + => this.AddText(prefix, body, suffix, left, right, side, textEdge, 1F, false, BarcodeTextAlignment.Centered); + + /// + /// Adds a text placement whose characters are three parts in turn, so a caller that frames data with delimiters writes it with no buffer of its own. + /// + /// The characters before the body. + /// The body characters. + /// The characters after the body. + /// The left edge of the horizontal span, in modules. + /// The right edge of the horizontal span, in modules. + /// The side of the bars the line prints on. + /// The edge of the ink of the line, in modules from the symbol top. + /// The factor applied to the caller's font size for this placement. + /// Whether this placement is a data layer caption rendered with the caption font. + /// Where the line stands within its span. + public void AddText( + ReadOnlySpan prefix, + ReadOnlySpan body, + ReadOnlySpan suffix, + float left, + float right, + BarcodeTextSide side, + float textEdge, + float fontScale, + bool isCaption, + BarcodeTextAlignment alignment) + => this.AddText(prefix, body, suffix, left, right, side, textEdge, fontScale, isCaption, alignment, false); + + /// + /// Adds a centred text placement at the full font size whose characters are three parts in turn and which must stay within its span. The line prints in a + /// smaller font when it is wider than its span. + /// + /// The characters before the body. + /// The body characters. + /// The characters after the body. + /// The left edge of the horizontal span, in modules. + /// The right edge of the horizontal span, in modules. + /// The side of the bars the line prints on. + /// The edge of the ink of the line, in modules from the symbol top. + public void AddTextWithinSpan( + ReadOnlySpan prefix, + ReadOnlySpan body, + ReadOnlySpan suffix, + float left, + float right, + BarcodeTextSide side, + float textEdge) + => this.AddText(prefix, body, suffix, left, right, side, textEdge, 1F, false, BarcodeTextAlignment.Centered, true); + + /// + /// Reserves and adds the human readable interpretation of a matrix symbol, one placement below the symbol across its width. The ink starts below the bottom + /// quiet zone: rule 1a of section 4.14 of the GS1 General Specifications keeps the interpretation clear of the quiet zones, and section 4.1 of ANSI/HIBC + /// 2.6 keeps all marking out of the symbol region, its quiet zones included. A symbol with a quiet zone narrower than + /// , such as Aztec Code, which needs none, keeps that clear space instead. + /// + /// The interpretation, its lines separated by . + /// Where the lines stand on the width of the symbol. + /// The factor applied to the font size of the caller. + public void AddMatrixText(ReadOnlySpan text, BarcodeTextAlignment alignment, float fontScale) + { + this.ReserveText(1, text.Length); + this.AddText( + text, + 0F, + this.Columns, + BarcodeTextSide.BelowBars, + this.HeightInModules + MathF.Max(this.BottomQuietZone, BarcodeTextPlacement.Clearance), + fontScale, + false, + alignment); + } + + /// + /// Reserves and adds the human readable interpretation of a stacked symbol, one placement below the symbol across its width. The ink starts below the + /// bottom quiet zone, or below the symbol when the quiet zone is narrower. + /// + /// The interpretation, its lines separated by . + /// Where the lines stand on the width of the symbol. + /// The factor applied to the font size of the caller. + public void AddStackedText(ReadOnlySpan text, BarcodeTextAlignment alignment, float fontScale) + { + this.ReserveText(1, text.Length); + this.AddText( + text, + 0F, + this.WidthInModules, + BarcodeTextSide.BelowBars, + this.HeightInModules + MathF.Max(this.BottomQuietZone, BarcodeTextPlacement.Clearance), + fontScale, + false, + alignment); + } + + /// + /// Adds a square graphic to the centre of a built matrix symbol. + /// + /// The side of the graphic in modules. + /// The side of the graphic in the units of . + /// The dark rectangles of the graphic, as holds them. + public void AddOverlay(float size, float unit, ReadOnlySpan darkEdges) + { + this.OverlaySize = size; + this.OverlayUnit = unit; + this.overlayBuffer = Rent(darkEdges.Length * sizeof(float)); + this.overlayEdgeCount = darkEdges.Length; + darkEdges.CopyTo(Slice(this.overlayBuffer, 0, darkEdges.Length)); + } + + /// + /// Adds identification bars to a built linear symbol that carries a text line above and below its bars. + /// + /// The thickness of each bar in modules. + /// The clear space between each bar and the ink of its text line in modules. + public void AddIdentificationBars(float thickness, float clearance) + { + this.IdentificationBarThickness = thickness; + this.IdentificationBarClearance = clearance; + } + + /// + /// Sets the range of the printed height of the characters of the text lines, which the emitter measures as the height of the ink of each line. + /// + /// The smallest height in modules. + /// The largest height in modules. + public void SetTextHeight(float minimum, float maximum) + { + this.TextHeightMinimum = minimum; + this.TextHeightMaximum = maximum; + } + + /// + /// Sets the smallest size of the caption in points. + /// + /// The smallest size in points. + public void SetCaptionMinimumSize(float points) => this.CaptionMinimumSize = points; + + /// + /// Sets , the font weight the standard of the symbology requires for its text. + /// + /// The font weight. + public void SetTextWeight(FontWeight weight) => this.TextWeight = weight; + + /// + /// Sets , the number of run units in one module of runs that are whole modules with corrections. + /// + /// The number of run units in one module. + public void SetCorrectedModuleUnits(int units) => this.CorrectedModuleUnits = units; + + /// + /// Sets the range of the size of the text lines in points. + /// + /// The smallest size in points, or 0 for none. + /// The largest size in points, or 0 for none. + public void SetTextSize(float minimum, float maximum) + { + this.TextMinimumSize = minimum; + this.TextMaximumSize = maximum; + } + + /// + /// Returns the rented buffers to the shared pool. + /// + public void Dispose() + { + this.layoutBuffer.Dispose(); + this.textBuffer.Dispose(); + this.overlayBuffer.Dispose(); + } + + /// + /// Returns a typed view of a range of a buffer. A buffer that was never rented gives an empty view when the count is zero, so a symbol with no text reads + /// no text. + /// + /// The element type. + /// The buffer. + /// The byte offset of the first element. + /// The number of elements. + /// The elements. + private static Span Slice(PooledBuffer buffer, int byteOffset, int count) + where T : unmanaged + => MemoryMarshal.Cast(buffer.Span.Slice(byteOffset, count * Unsafe.SizeOf())); + + /// + /// Rents a buffer from the shared pool and clears it. The pool hands out used arrays and allocates new ones without clearing, so the + /// symbol clears them itself. + /// + /// The number of bytes the symbol uses. + /// The rented buffer. + private static PooledBuffer Rent(int byteCount) + { + PooledBuffer rented = new(byteCount); + rented.Span.Clear(); + return rented; + } + + /// + /// Adds a text placement. The characters copy into the character storage that reserved. + /// + /// The characters to render. + /// The left edge of the horizontal span, in modules. + /// The right edge of the horizontal span, in modules. + /// The side of the bars the line prints on. + /// The edge of the ink of the line, in modules from the symbol top. + /// The factor applied to the caller's font size for this placement. + /// Whether this placement is a data layer caption rendered with the caption font. + /// Where the line stands within its span. + /// Whether the ink of the line must stay within its span. + private void AddText( + ReadOnlySpan text, + float left, + float right, + BarcodeTextSide side, + float textEdge, + float fontScale, + bool isCaption, + BarcodeTextAlignment alignment, + bool withinSpan) + { + text.CopyTo(Slice(this.textBuffer, this.charOffset + (this.charCount * sizeof(char)), text.Length)); + Slice(this.textBuffer, this.textCount++ * Unsafe.SizeOf(), 1)[0] = + new BarcodeTextPlacement(this.charCount, text.Length, left, right, side, textEdge, fontScale, isCaption, alignment, withinSpan); + + this.charCount += text.Length; + } + + /// + /// Adds a text placement whose characters are three parts in turn. The characters copy into the character storage that reserved. + /// + /// The characters before the body. + /// The body characters. + /// The characters after the body. + /// The left edge of the horizontal span, in modules. + /// The right edge of the horizontal span, in modules. + /// The side of the bars the line prints on. + /// The edge of the ink of the line, in modules from the symbol top. + /// The factor applied to the caller's font size for this placement. + /// Whether this placement is a data layer caption rendered with the caption font. + /// Where the line stands within its span. + /// Whether the ink of the line must stay within its span. + private void AddText( + ReadOnlySpan prefix, + ReadOnlySpan body, + ReadOnlySpan suffix, + float left, + float right, + BarcodeTextSide side, + float textEdge, + float fontScale, + bool isCaption, + BarcodeTextAlignment alignment, + bool withinSpan) + { + int start = this.charCount; + int length = prefix.Length + body.Length + suffix.Length; + Span destination = Slice(this.textBuffer, this.charOffset + (start * sizeof(char)), length); + prefix.CopyTo(destination); + body.CopyTo(destination[prefix.Length..]); + suffix.CopyTo(destination[(prefix.Length + body.Length)..]); + Slice(this.textBuffer, this.textCount++ * Unsafe.SizeOf(), 1)[0] = + new BarcodeTextPlacement(start, length, left, right, side, textEdge, fontScale, isCaption, alignment, withinSpan); + + this.charCount = start + length; + } + + /// + /// Reserves the rows, the runs and the bars of a linear or stacked symbol. + /// + /// The number of rows. + /// The number of runs of all rows. + /// The number of bars of all rows. + private void Reserve(int rowCount, int runCount, int barCount) + { + // The rows, the runs, the bar heights and the bar tops follow each other in one buffer. Each holds four byte fields, so every part starts on a four + // byte offset. + this.runOffset = rowCount * Unsafe.SizeOf(); + this.barOffset = this.runOffset + (runCount * sizeof(int)); + this.barCapacity = barCount; + this.layoutBuffer = Rent(this.barOffset + (2 * barCount * sizeof(float))); + } + + /// + /// Places the rows from the top down and sets the width of the symbol from the first row. + /// + /// The top of the first row in modules. + /// The bottom of the last row in modules. + private float EndRows(float firstTop) + { + ReadOnlySpan runs = this.Runs; + ReadOnlySpan heights = this.BarHeights; + ReadOnlySpan tops = this.BarTops; + Span rows = Slice(this.layoutBuffer, 0, this.rowCount); + + // The product of a whole number of run units and the run unit is exact in a double, so the width carries no rounding error. + BarcodeRow first = rows[0]; + this.WidthInModules = (float)(RunWidth(runs.Slice(first.RunStart, first.RunCount)) * (double)this.RunUnit); + + // Each row is as tall as its lowest bar bottom, and its ink starts at its highest bar top. The encoders give an empty bar the height of its row, so + // the bars alone give both. Each row top adds every row above it, so the sum is in double and each top converts to float once. + double top = firstTop; + for (int r = 0; r < rows.Length; r++) + { + BarcodeRow row = rows[r]; + ReadOnlySpan rowTops = tops.Slice(row.BarStart, row.BarCount); + ReadOnlySpan rowHeights = heights.Slice(row.BarStart, row.BarCount); + float height = 0; + float inkTop = float.MaxValue; + for (int i = 0; i < rowTops.Length; i++) + { + height = MathF.Max(height, rowTops[i] + rowHeights[i]); + inkTop = MathF.Min(inkTop, rowTops[i]); + } + + // The ink edges are the left edge of the first bar that is not empty and the right edge of the last. A row can start with an empty bar, so + // that it starts with a space, and it can end with a space. A short walk from each end of the row passes those runs, so no run between is read. + // A row of spaces alone has no ink, and its ink start is -1. + ReadOnlySpan rowRuns = runs.Slice(row.RunStart, row.RunCount); + int start = 0; + int inkStart = 0; + while (start < rowRuns.Length && rowRuns[start] == 0) + { + inkStart += start + 1 < rowRuns.Length ? rowRuns[start + 1] : 0; + start += 2; + } + + int inkEnd = 0; + if (start >= rowRuns.Length) + { + inkStart = -1; + } + else + { + // Runs alternate bar and space from a bar, so the last bar is the last even index. The space after it, if the row ends with one, and each + // empty bar before it with the space before that bar, are the trailing runs. + int end = (rowRuns.Length - 1) & ~1; + int trailing = end + 1 < rowRuns.Length ? rowRuns[end + 1] : 0; + while (rowRuns[end] == 0) + { + trailing += rowRuns[end - 1]; + end -= 2; + } + + inkEnd = RunWidth(rowRuns) - trailing; + } + + rows[r] = new BarcodeRow(row, (float)top, height, inkTop, inkStart, inkEnd); + top += height; + } + + return (float)top; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/BarcodeSymbolKind.cs b/src/ImageSharp.Drawing/Barcodes/BarcodeSymbolKind.cs new file mode 100644 index 00000000..ff2bf753 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/BarcodeSymbolKind.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The form of an encoded symbol. +/// +internal enum BarcodeSymbolKind +{ + /// + /// No symbol has been encoded. + /// + None, + + /// + /// One row of bars and spaces. + /// + Linear, + + /// + /// Rows of bars and spaces stacked from the top down. + /// + Stacked, + + /// + /// A grid of square modules. + /// + Matrix, + + /// + /// Offset rows of hexagonal modules around a circular finder, the form of MaxiCode. The modules are a grid of rows and columns in which every odd row + /// stands half a module to the right. + /// + Hexagonal, + + /// + /// A grid of round dots, the form of DotCode. The modules are a grid of square cells, and each dark module is a dot centred in its cell. + /// + Dotted, + + /// + /// A grid of square modules in several colours, the form of Ultracode. Each module is a . + /// + Colored, +} diff --git a/src/ImageSharp.Drawing/Barcodes/BarcodeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/BarcodeSymbology.cs new file mode 100644 index 00000000..93190936 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/BarcodeSymbology.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// A barcode symbology: one machine readable encoding scheme. A symbology validates the input and applies the check character rules of its specification. It +/// then encodes the input into a symbol. The canvas draws that symbol through +/// . +/// +public abstract class BarcodeSymbology +{ + /// + /// The default X dimension in millimetres of a linear or stacked symbol: 0.330. When the specification of a symbology gives no X dimension, the symbology + /// uses this value. GS1 General Specifications release 26.0 gives this target X dimension to linear symbols in Table 5-44. + /// + public const double DefaultXDimension = 0.330; + + /// + /// The default X dimension in millimetres of a 2D matrix symbol: 0.495. The specifications of Aztec Code, Code One, Data Matrix, DotCode, Han Xin Code, + /// QR Code and Ultracode give no X dimension. Thus these symbologies use this value. GS1 General Specifications release 26.0 gives this target X + /// dimension to 2D symbols in Table 5-45 and Table 5-46. The notes to those tables give the reason. Because of optical effects in image capture, the X + /// dimension of a 2D symbol must be 1.5 times the X dimension of a linear symbol. + /// + public const double DefaultMatrixXDimension = 0.495; + + /// + /// Initializes a new instance of the class. + /// + private protected BarcodeSymbology() + { + } + + /// + /// Validates the given text and encodes it into a device-independent symbol measured in modules. An implementation checks the text length against the + /// maximum of the symbology before it sizes a buffer from the text. + /// + /// The text to encode. + /// The options that control layout choices made during encoding. + /// The symbol to write. It is empty on entry. + /// The text is not valid for the symbology. + internal abstract void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol); + + /// + /// Validates that the text is the ASCII digits 0 to 9 alone. The vectorized search finds the first other character. Only then does the method decode the + /// code point there for the message. + /// + /// The text. + /// The text has a character that is not a digit. + internal static void ValidateDigits(ReadOnlySpan text) + { + int index = text.IndexOfAnyExceptInRange('0', '9'); + if (index >= 0) + { + throw new ArgumentException($"Only the digits 0 to 9 are accepted; got {text.CodePointAt(index).ToDisplayString()}.", nameof(text)); + } + } + + /// + /// Calculates the modulo 11 check value over the digits. The weights start at 2 on the rightmost digit and go up by one for each digit to the left. After + /// the maximum weight, the weight starts at 2 again. The check value lifts the weighted sum to the next multiple of eleven. + /// + /// The digits the check value covers. + /// The highest weight: 7 for the IBM MSI calculation, 9 for the NCR MSI calculation, 10 for ISBN-10 and 8 for ISSN. + /// The check value, 0 to 10. + internal static int Modulo11(ReadOnlySpan digits, int maximumWeight) + { + int sum = 0; + int weight = 2; + for (int i = digits.Length - 1; i >= 0; i--) + { + sum += weight * (digits[i] - '0'); + weight = weight == maximumWeight ? 2 : weight + 1; + } + + return (11 - (sum % 11)) % 11; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/BarcodeTextAlignment.cs b/src/ImageSharp.Drawing/Barcodes/BarcodeTextAlignment.cs new file mode 100644 index 00000000..01193ab0 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/BarcodeTextAlignment.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Specifies where a line of the human readable interpretation stands within its horizontal span. +/// +internal enum BarcodeTextAlignment +{ + /// + /// The line is centred on the span. + /// + Centered, + + /// + /// The left edge of the line's ink stands on the left edge of the span. + /// + Left, + + /// + /// The right edge of the line's ink stands on the right edge of the span. + /// + Right, +} diff --git a/src/ImageSharp.Drawing/Barcodes/BarcodeTextOptionsFactory.cs b/src/ImageSharp.Drawing/Barcodes/BarcodeTextOptionsFactory.cs new file mode 100644 index 00000000..5b75d571 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/BarcodeTextOptionsFactory.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts; +using SixLabors.ImageSharp.Drawing.Processing; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Creates the that every barcode symbology measures and draws its human readable interpretation with. +/// +internal static class BarcodeTextOptionsFactory +{ + /// + /// Returns the options for one line of the human readable interpretation. The options keep the default alignment of the text box, so the origin is the + /// top left corner of the line. The emitter calculates the origin of each line from its measured rectangles. One instance serves both measuring and + /// drawing, so the rectangle measured for a line is the rectangle the line draws into. + /// + /// The font the line renders in, whose size is in points. + /// The resolution the line renders at, in dots per inch. + /// The font weight the symbology requires, , or for the weight of the + /// font. + /// The options. + public static RichTextOptions Create(Font font, float dpi, FontWeight? weight) + => new(font) + { + Dpi = dpi, + HintingMode = HintingMode.Standard, + FontWeight = weight + }; +} diff --git a/src/ImageSharp.Drawing/Barcodes/BarcodeTextPlacement.cs b/src/ImageSharp.Drawing/Barcodes/BarcodeTextPlacement.cs new file mode 100644 index 00000000..03f4f817 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/BarcodeTextPlacement.cs @@ -0,0 +1,149 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// One piece of the human readable interpretation of a barcode, positioned in module space. The placement names the edge its ink starts or ends on and the side +/// of the bars it prints on. The room that the line needs belongs to the renderer, because only the renderer knows the font. The characters are a range of the +/// character storage of the symbol that holds the placement. +/// +internal readonly struct BarcodeTextPlacement +{ + /// + /// The clear space in modules between a bar edge and the ink of a line where the standard of the symbology gives no figure. Section 5.2.5 of the GS1 + /// General Specifications sets the minimum at 0.5X both below the main symbol and above an add-on symbol, and states: "Normally the minimum is one module, + /// which is close enough to keep the human readable interpretation associated with the symbol." The same space stands above a caption. + /// + public const float Clearance = 1F; + + /// + /// The character that starts a new line within the characters of a placement. The lines of one placement stack downwards and stand within its span as its + /// alignment gives. + /// + public const char LineBreak = '\n'; + + /// + /// Initializes a new instance of the struct. + /// + /// The index of the first character in the character storage of the symbol. + /// The number of characters. + /// The left edge of the horizontal span, in modules. + /// The right edge of the horizontal span, in modules. + /// The side of the bars the line prints on. + /// The edge of the ink of the line, in modules from the symbol top. + /// The factor applied to the caller's font size for this placement. + /// Whether this placement is a data layer caption rendered with the caption font. + /// Where the line stands within its span. + /// Whether the ink of the line must stay within its span. + public BarcodeTextPlacement( + int textStart, + int textLength, + float left, + float right, + BarcodeTextSide side, + float textEdge, + float fontScale, + bool isCaption, + BarcodeTextAlignment alignment, + bool withinSpan) + { + this.TextStart = textStart; + this.TextLength = textLength; + this.Left = left; + this.Right = right; + this.Side = side; + this.TextEdge = textEdge; + this.FontScale = fontScale; + this.IsCaption = isCaption; + this.Alignment = alignment; + this.WithinSpan = withinSpan; + } + + /// + /// Gets the index of the first character in the character storage of the symbol. + /// + public int TextStart { get; } + + /// + /// Gets the number of characters. + /// + public int TextLength { get; } + + /// + /// Gets the left edge of the horizontal span, in modules. The value is negative for text that prints inside the leading quiet zone, such as the first digit + /// of an EAN-13 symbol. + /// + public float Left { get; } + + /// + /// Gets the right edge of the horizontal span, in modules. + /// + public float Right { get; } + + /// + /// Gets the side of the bars the line prints on. + /// + public BarcodeTextSide Side { get; } + + /// + /// Gets the edge of the ink of the line, in modules from the symbol top, before any room is made for text above the bars. The ink of a line below the bars + /// starts on it, and the ink of a line above the bars ends on it. The EAN-13 digits hang below the digit bars rather than the extended guard bars, which + /// they flow past. + /// + public float TextEdge { get; } + + /// + /// Gets the factor applied to the caller's font size for this placement. The UPC number system and check digits print in smaller type in the quiet zones + /// than the digits below the symbol. + /// + public float FontScale { get; } + + /// + /// Gets a value indicating whether this placement is a data layer caption, such as the ISBN line above its symbol. Captions render with the caption font + /// when one is set. + /// + public bool IsCaption { get; } + + /// + /// Gets where the line stands within its span. + /// + public BarcodeTextAlignment Alignment { get; } + + /// + /// Gets a value indicating whether the line must stay within its span. The line then prints in a smaller font when it is wider than its span: its ink + /// for a line aligned on an edge, and its renderable bounds for a centred line. + /// + public bool WithinSpan { get; } + + /// + /// Returns the x origin of the line in a span. A centred line straddles the middle of the span. A left aligned line puts the left edge of its ink on the + /// left edge of the span, and a right aligned line puts the right edge of its ink on the right edge of the span. + /// + /// The left edge of the span in pixels. + /// The right edge of the span in pixels. + /// The ink of the measured line, from . + /// The width of the measured line, from . + /// The x origin in pixels. + public float Origin(float spanLeft, float spanRight, FontRectangle bounds, float renderableWidth) => this.Alignment switch + { + BarcodeTextAlignment.Left => spanLeft - bounds.Left, + BarcodeTextAlignment.Right => spanRight - bounds.Right, + _ => ((spanLeft + spanRight) * 0.5F) - (renderableWidth * 0.5F), + }; + + /// + /// Returns the x origin of the line on the drawn symbol. The span stands on the cell edges where the modules draw, rounded onto the pixel grid, not on the + /// exact fractional position, so a digit stays over its own symbol character. With unrounded edges, the text and the modules can disagree by up to half a + /// module. + /// + /// The ink of the measured line, from . + /// The width of the measured line, from . + /// The x of the first module, in pixels. + /// The module width in pixels. + /// The x origin in pixels. + public float GridOrigin(FontRectangle bounds, float renderableWidth, float symbolLeft, float moduleWidth) + => this.Origin(MathF.Round(symbolLeft + (this.Left * moduleWidth)), MathF.Round(symbolLeft + (this.Right * moduleWidth)), bounds, renderableWidth); +} diff --git a/src/ImageSharp.Drawing/Barcodes/BarcodeTextSide.cs b/src/ImageSharp.Drawing/Barcodes/BarcodeTextSide.cs new file mode 100644 index 00000000..94409ab7 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/BarcodeTextSide.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The side of the bars a line of the human readable interpretation prints on. +/// +internal enum BarcodeTextSide +{ + /// + /// The line prints below the bars and grows downward from its text edge. + /// + BelowBars, + + /// + /// The line prints above the bars and grows upward from its text edge. + /// + AboveBars, +} diff --git a/src/ImageSharp.Drawing/Barcodes/Bc412/Bc412Encoder.cs b/src/ImageSharp.Drawing/Barcodes/Bc412/Bc412Encoder.cs new file mode 100644 index 00000000..2aaaa63c --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Bc412/Bc412Encoder.cs @@ -0,0 +1,205 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodation for the BC412 symbology of SEMI T1-95. Every character is four bars of one module in twelve module positions. A symbol is a quiet zone, the start +/// character, the first data character, the check character, the remaining data characters, the stop character and a quiet zone. +/// +internal static class Bc412Encoder +{ + /// + /// The smallest number of data characters a symbol carries. + /// + public const int MinimumLength = 7; + + /// + /// The largest number of data characters a symbol carries. + /// + public const int MaximumLength = 18; + + /// + /// The position of the check character in the symbol: after the first data character. + /// + public const int CheckPosition = 1; + + /// + /// The characters the symbology encodes, in pattern order and in the order the check calculation values them. The letter O is not in the set, and the digit + /// 0 stands in for it. + /// + private const string Characters = "0R9GLVHA8EZ4NTS1J2Q6C7DYKBUIX3FWP5M"; + + /// + /// The quiet zone in modules on each side. The symbol widths, 13.2 mm at 7 data characters and 29.04 mm at 18, exceed the bars by 0.96 mm at the 0.12 mm + /// module, 4 modules on each side. + /// + private const int QuietZone = 4; + + /// + /// The nominal X dimension in millimetres: a module spacing of 0.12 mm. + /// + private const double NominalXDimension = 0.12; + + /// + /// The smallest X dimension in millimetres. Table 1 of SEMI T1-95 gives the module spacing as "0.120 mm ± 0.025 mm". + /// + private const double MinimumXDimension = 0.120 - 0.025; + + /// + /// The largest X dimension in millimetres, from the same tolerance. + /// + private const double MaximumXDimension = 0.120 + 0.025; + + /// + /// The number of runs in one character: four bars and four spaces, the last of which separates it from the next character. + /// + private const int RunsPerCharacter = 8; + + /// + /// The number of runs of the start character: a bar and a space. + /// + private const int StartElements = 2; + + /// + /// The number of runs of the stop character: two bars and the space between them. + /// + private const int StopElements = 3; + + /// + /// The modulus of the check character, the size of the character set. + /// + private const int CheckModulus = 35; + + /// + /// The multiplier the check calculation applies to the reduced total. + /// + private const int CheckMultiplier = 17; + + /// + /// The bar height in millimetres when the caller sets none. Table 1 of SEMI T1-95: "Module Height (dot centerline to dot centerline)", 2.000 mm. The + /// height stays 2.000 mm at every module width, so a module that rounds to whole pixels does not change it. + /// + private const double NominalBarHeight = 2.000; + + /// + /// The smallest bar height in millimetres, from the tolerance of the same entry of Table 1, "2.000 mm ± 0.025 mm". + /// + private const double MinimumBarHeight = 2.000 - 0.025; + + /// + /// The largest bar height in millimetres, from the same tolerance. + /// + private const double MaximumBarHeight = 2.000 + 0.025; + + /// + /// Gets the space widths of every character, four per character, in the order of . Each space follows a bar of one module, and + /// the four bars and four spaces add to twelve modules. + /// + private static ReadOnlySpan Spaces => + [ + 1, 1, 1, 5, 3, 1, 2, 2, 1, 3, 1, 3, 2, 1, 2, 3, 2, 2, 3, 1, + 3, 3, 1, 1, 2, 1, 3, 2, 1, 3, 2, 2, 1, 2, 4, 1, 1, 5, 1, 1, + 5, 1, 1, 1, 1, 1, 5, 1, 2, 3, 2, 1, 3, 2, 1, 2, 3, 1, 3, 1, + 1, 1, 2, 4, 2, 2, 1, 3, 1, 1, 3, 3, 3, 1, 1, 3, 1, 2, 2, 3, + 1, 4, 1, 2, 1, 2, 3, 2, 1, 4, 2, 1, 4, 2, 1, 1, 2, 2, 2, 2, + 1, 3, 3, 1, 3, 2, 2, 1, 2, 1, 4, 1, 4, 1, 2, 1, 1, 1, 4, 2, + 2, 1, 1, 4, 4, 1, 1, 2, 2, 4, 1, 1, 1, 2, 1, 4, 2, 3, 1, 2, + ]; + + /// + /// Gets the value of every code point from 0 to Z, which is its index in , or -1 for a code point outside the set: the seven + /// symbols between the digits and the letters, and the letter O. + /// + private static ReadOnlySpan Values => + [ + 0, 15, 17, 29, 11, 33, 19, 21, 8, 2, + -1, -1, -1, -1, -1, -1, -1, + 7, 25, 20, 22, 9, 30, 3, 6, 27, 16, 24, 4, 34, 12, -1, 32, 18, 1, 14, 13, 26, 5, 31, 28, 23, 10, + ]; + + /// + /// Returns the value of a character, which is its index in . + /// + /// The code point to value. + /// The value, or -1 when the character is not in the set. + public static int Value(int codePoint) => codePoint is >= '0' and <= 'Z' ? Values[codePoint - '0'] : -1; + + /// + /// Calculates the check character over the data. The check character is counted as the second character of the symbol with the value 0. The values in the + /// odd positions and the values in the even positions are added, each sum is reduced modulo 35, the even sum is doubled, and the total is reduced modulo + /// 35, multiplied by 17 and reduced modulo 35 again. + /// + /// The data characters, already validated. + /// The check character. + public static char CheckCharacter(ReadOnlySpan data) + { + int oddSum = Value(data[0]); + int evenSum = 0; + for (int i = 1; i < data.Length; i++) + { + int value = Value(data[i]); + if (((i + CheckPosition) & 1) == 0) + { + oddSum += value; + } + else + { + evenSum += value; + } + } + + int check = ((oddSum % CheckModulus) + (2 * (evenSum % CheckModulus))) % CheckModulus; + return Characters[(check * CheckMultiplier) % CheckModulus]; + } + + /// + /// Encodes the characters the symbol carries and builds the symbol. The runs start with the bar of the start character and end on the last bar of the stop + /// character. BC412 carries no guard bars, so every bar runs the full height, and the human readable interpretation, the carried characters, sits below the + /// symbol. + /// + /// The first data character, the check character and the remaining data. + /// The options that control layout choices. + /// The symbol to write. + public static void BuildSymbol(ReadOnlySpan carried, BarcodeOptions options, ref BarcodeSymbol symbol) + { + double xDimension = options.ResolveXDimension(MinimumXDimension, NominalXDimension, MaximumXDimension); + Span runs = symbol.BeginLinear(StartElements + (carried.Length * RunsPerCharacter) + StopElements, xDimension, out Span heights, out _); + + // The start character is a bar and a two module space, and the stop character is a bar, a space and a bar of one module each. + int written = 0; + runs[written++] = 1; + runs[written++] = 2; + const int spacesPerCharacter = RunsPerCharacter / 2; + foreach (char c in carried) + { + foreach (byte space in Spaces.Slice(Value(c) * spacesPerCharacter, spacesPerCharacter)) + { + runs[written++] = 1; + runs[written++] = space; + } + } + + runs[written++] = 1; + runs[written++] = 1; + runs[written] = 1; + + int widthInModules = BarcodeSymbol.RunWidth(runs); + + float barHeight = options.ResolveBarHeight( + xDimension, + (float)(MinimumBarHeight / xDimension), + (float)(NominalBarHeight / xDimension), + (float)(MaximumBarHeight / xDimension)); + + heights.Fill(barHeight); + + if (options.Font is not null) + { + symbol.ReserveText(1, carried.Length); + symbol.AddText(carried, 0F, widthInModules, BarcodeTextSide.BelowBars, barHeight + BarcodeTextPlacement.Clearance); + } + + symbol.EndLinear(QuietZone, QuietZone); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Bc412/Bc412Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Bc412/Bc412Symbology.cs new file mode 100644 index 00000000..afb08570 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Bc412/Bc412Symbology.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The BC412 symbology of SEMI T1-95. It carries 7 to 18 characters from the digits and the capital letters without O, for which the digit 0 stands. Every +/// symbol carries the modulo 35 check character after its first data character, and the printed line shows the data with the check character in that place. The +/// start and stop characters are not shown. +/// +public sealed class Bc412Symbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Bc412Symbology() + : this(false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the second character of the input is the check character, which the encoder validates, rather than data, + /// for which the encoder calculates it. + public Bc412Symbology(bool validateCheckCharacter) + => this.ValidateCheckCharacter = validateCheckCharacter; + + /// + /// Gets a value indicating whether the second character of the input is the check character, which the encoder validates, rather than data, for which the + /// encoder calculates it. + /// + public bool ValidateCheckCharacter { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + int supplied = this.ValidateCheckCharacter ? 1 : 0; + int dataLength = text.Length - supplied; + Guard.MustBeBetweenOrEqualTo(dataLength, Bc412Encoder.MinimumLength, Bc412Encoder.MaximumLength, nameof(text)); + + SpanCodePointEnumerator codePoints = text.EnumerateCodePoints(); + while (codePoints.MoveNext()) + { + CodePoint current = codePoints.Current; + if (Bc412Encoder.Value(current.Value) < 0) + { + throw new ArgumentException($"BC412 carries only digits and capital letters other than O; got {current.ToDisplayString()}.", nameof(text)); + } + } + + // The data without the check character, then the characters the symbol carries: the first data character, the check character and the remaining data. A + // symbol carries at most 18 data characters. + InlineArray18 dataStorage = default; + Span data = dataStorage; + data[0] = text[0]; + text.Slice(1 + supplied).CopyTo(data[1..]); + data = data[..dataLength]; + + char check = Bc412Encoder.CheckCharacter(data); + if (this.ValidateCheckCharacter && text[Bc412Encoder.CheckPosition] != check) + { + throw new ArgumentException($"Incorrect check character: expected {check}, got {text[Bc412Encoder.CheckPosition]}.", nameof(text)); + } + + InlineArray19 carriedStorage = default; + Span carried = carriedStorage; + carried[0] = data[0]; + carried[Bc412Encoder.CheckPosition] = check; + data[1..].CopyTo(carried[(Bc412Encoder.CheckPosition + 1)..]); + carried = carried[..(dataLength + 1)]; + + Bc412Encoder.BuildSymbol(carried, options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/ChannelCode/ChannelCodeEncoder.cs b/src/ImageSharp.Drawing/Barcodes/ChannelCode/ChannelCodeEncoder.cs new file mode 100644 index 00000000..6885d945 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/ChannelCode/ChannelCodeEncoder.cs @@ -0,0 +1,282 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodation for the Channel Code symbology, which ANSI/AIM BC12-1998 defines. A symbol carries a finder pattern of five one-module bars with four one-module +/// spaces between them, then one space and one bar per channel. A symbol of C channels is 4C + 7 modules wide for all values. The value is the position of the +/// space and bar widths of the symbol in an ordered list. This list holds the sequences that keep that width and the width rules of the symbology. +/// +internal static class ChannelCodeEncoder +{ + /// + /// The smallest number of digits a symbol carries, which selects three channels. + /// + public const int MinimumLength = 2; + + /// + /// The largest number of digits a symbol carries, which selects eight channels. + /// + public const int MaximumLength = 7; + + /// + /// The quiet zone in modules before the symbol: "at least 1X wide". + /// + private const int LeadingQuietZone = 1; + + /// + /// The quiet zone in modules after the symbol: "at least 2X wide". + /// + private const int TrailingQuietZone = 2; + + /// + /// The number of channels beyond the number of digits. + /// + private const int ChannelsBeyondDigits = 1; + + /// + /// The number of elements in the full finder pattern: five bars and four spaces. + /// + private const int FinderElements = 9; + + /// + /// The number of elements in the short finder pattern: three bars and two spaces. + /// + private const int ShortFinderElements = 5; + + /// + /// The bar height in modules when the caller sets none: twenty modules, and at least . The minimum height is 5.0 + /// mm or 15 per cent of the symbol length, whichever is greater, and the longest symbol is 39 modules, so twenty modules always exceed 15 per cent. + /// + private const float NominalBarHeight = 20F; + + /// + /// The smallest bar height in millimetres when the caller sets none. + /// + private const float MinimumBarHeightMillimetres = 5F; + + /// + /// Gets the largest value each channel count carries, from three to eight channels. + /// + private static ReadOnlySpan MaximumValues => [26, 292, 3493, 44072, 576688, 7742862]; + + /// + /// Returns the largest value a number of digits carries. Two to seven digits select three to eight channels. + /// + /// The number of digits. + /// The largest value. + public static int MaximumValue(int digits) => MaximumValues[digits - MinimumLength]; + + /// + /// Encodes a value and builds the symbol. The runs start with the first bar of the finder pattern and end on the bar of the last channel. Channel Code + /// carries no guard bars, so every bar runs the full height, and the human readable interpretation sits below the symbol. + /// + /// The value, within the range of the channel count. + /// The number of digits, which sets the channel count. + /// Whether the finder pattern is the short form of three bars. + /// The human readable interpretation. + /// The options that control layout choices. + /// The symbol to write. + public static void BuildSymbol(int value, int digits, bool shortFinder, ReadOnlySpan readable, BarcodeOptions options, ref BarcodeSymbol symbol) + { + int channels = digits + ChannelsBeyondDigits; + int finder = shortFinder ? ShortFinderElements : FinderElements; + double xDimension = options.ResolveXDimension(0, BarcodeSymbology.DefaultXDimension, double.PositiveInfinity); + Span runs = symbol.BeginLinear(finder + (channels * 2), xDimension, out Span heights, out _); + runs[..finder].Fill(1); + + // Eight channels, the most, fit inline. + InlineArray8 spaceStorage = default; + InlineArray8 barStorage = default; + Span spaces = ((Span)spaceStorage)[..channels]; + Span bars = ((Span)barStorage)[..channels]; + + // The memo holds one count for each state the sequence counter reaches, and the most channels give at most 8 * 9 * 9 * 12 = 7776 of them. That is too + // large to hold inline, so it is rented once for the symbol. + using (PooledBuffer countBuffer = new(SequenceCounter.StateCount(channels))) + { + new SequenceCounter(countBuffer.Span, channels).Unrank(value, spaces, bars); + } + + for (int channel = 0; channel < channels; channel++) + { + runs[finder + (channel * 2)] = spaces[channel]; + runs[finder + (channel * 2) + 1] = bars[channel]; + } + + // A finder element is one module, and the spaces and bars of C channels add to 4C - 2 modules. + int widthInModules = finder + (channels * 4) - 2; + + float nominalBarHeight = MathF.Max(NominalBarHeight, (float)(MinimumBarHeightMillimetres / xDimension)); + float barHeight = options.ResolveBarHeight(xDimension, 0, nominalBarHeight, float.PositiveInfinity); + heights.Fill(barHeight); + + if (options.Font is not null) + { + symbol.ReserveText(1, readable.Length); + symbol.AddText(readable, 0F, widthInModules, BarcodeTextSide.BelowBars, barHeight + BarcodeTextPlacement.Clearance); + } + + symbol.EndLinear(LeadingQuietZone, TrailingQuietZone); + } + + /// + /// Counts and unranks the space and bar sequences of one channel count. The order of the symbology takes each space width from the narrowest, then each bar + /// width from the narrowest, channel by channel, so the sequences under one choice form one block of positions. The counter owns the memo of the block + /// sizes it has counted, which the caller rents. + /// + private readonly ref struct SequenceCounter + { + /// + /// The cap on the previous bar width and on the pair width. The bar rule tests a sum of three widths against 4, so a larger width cannot change it. + /// + private const int WidthCap = 4; + + /// + /// The number of states of the previous bar width, 1 to . + /// + private const int PreviousBarStates = WidthCap; + + /// + /// The number of states of the pair width, 2 to : a bar and a space are each at least one module. + /// + private const int PairStates = WidthCap - 1; + + /// + /// The counts found so far, -1 where none is, indexed by channel, bar width left, space width left, previous bar and pair. + /// + private readonly Span counts; + + /// + /// The number of channels. + /// + private readonly int channels; + + /// + /// The number of values the channel and each width left take: 0 to the channel count. + /// + private readonly int stride; + + /// + /// Initializes a new instance of the struct and marks every count as not yet found. + /// + /// The memo, at least long. + /// The number of channels. + public SequenceCounter(Span counts, int channels) + { + this.counts = counts; + this.channels = channels; + this.stride = channels + 1; + counts.Fill(-1); + } + + /// + /// Returns the length of the memo for a channel count: one entry for each channel, bar width left, space width left, previous bar and pair. + /// + /// The number of channels. + /// The number of states. + public static int StateCount(int channels) => channels * (channels + 1) * (channels + 1) * PreviousBarStates * PairStates; + + /// + /// Writes the space and bar widths of the sequence at a position in the order of the symbology. Each choice counts the sequences under it and skips + /// that block whole until the position falls inside one, so the work grows with the choices, not with the value. + /// + /// The position of the sequence, within the range of the channel count. + /// The buffer that receives the space widths, one a channel. + /// The buffer that receives the bar widths, one a channel. + public void Unrank(int value, Span spaces, Span bars) + { + // The end of the finder pattern stands before the first channel: three narrow bars and the narrow spaces between them, so the previous bar is 1 and + // the bar and space before it add to 2. + int barWidth = this.channels; + int spaceWidth = this.channels; + int previousBar = 1; + int pair = 2; + int remaining = value; + for (int channel = 0; channel < this.channels - 1; channel++) + { + bool chosen = false; + for (int space = 1; space <= spaceWidth && !chosen; space++) + { + for (int bar = FirstBar(space, previousBar, pair); bar <= barWidth; bar++) + { + int nextPrevious = Math.Min(bar, WidthCap); + int nextPair = Math.Min(previousBar + space, WidthCap); + int count = this.Count(channel + 1, barWidth + 1 - bar, spaceWidth + 1 - space, nextPrevious, nextPair); + if (remaining < count) + { + spaces[channel] = space; + bars[channel] = bar; + barWidth += 1 - bar; + spaceWidth += 1 - space; + previousBar = nextPrevious; + pair = nextPair; + chosen = true; + break; + } + + remaining -= count; + } + } + } + + // The last space and the last bar take all the width left, so the symbol width stays constant. + spaces[this.channels - 1] = spaceWidth; + bars[this.channels - 1] = barWidth; + } + + /// + /// Counts the sequences that complete the channels from a channel on. The counts depend only on the channel, the widths left and the two sums the bar + /// rule reads, so each is counted once. + /// + /// The first channel to complete. + /// The width left for the bars. + /// The width left for the spaces. + /// The width of the bar before the channel, at most . + /// The width of the bar and the space before that bar, at most . + /// The number of sequences. + private int Count(int channel, int barWidth, int spaceWidth, int previousBar, int pair) + { + // The last channel takes all the width left. When its bar can take that width, the last channel completes one sequence. + if (channel == this.channels - 1) + { + return FirstBar(spaceWidth, previousBar, pair) <= barWidth ? 1 : 0; + } + + int index = (((((channel * this.stride) + barWidth) * this.stride) + spaceWidth) * PreviousBarStates) + previousBar - 1; + index = (index * PairStates) + pair - 2; + if (this.counts[index] >= 0) + { + return this.counts[index]; + } + + int total = 0; + for (int space = 1; space <= spaceWidth; space++) + { + for (int bar = FirstBar(space, previousBar, pair); bar <= barWidth; bar++) + { + int nextPrevious = Math.Min(bar, WidthCap); + int nextPair = Math.Min(previousBar + space, WidthCap); + total += this.Count(channel + 1, barWidth + 1 - bar, spaceWidth + 1 - space, nextPrevious, nextPair); + } + } + + this.counts[index] = total; + return total; + } + + /// + /// Returns the narrowest width a bar can take. The elements before a bar are the space before it, the bar before that space, and the bar and space + /// before that bar. When the widths of these elements add to four or less, the bar starts at two modules. As a result, no four consecutive elements + /// other than the finder pattern are all one module wide. + /// + /// The width of the space before the bar. + /// The width of the bar before that space, at most . + /// The width of the bar and the space before that bar, at most . + /// The narrowest bar width, 1 or 2. + private static int FirstBar(int space, int previousBar, int pair) => space + previousBar + pair > WidthCap ? 1 : 2; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/ChannelCode/ChannelCodeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/ChannelCode/ChannelCodeSymbology.cs new file mode 100644 index 00000000..3a65a6d9 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/ChannelCode/ChannelCodeSymbology.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Globalization; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Channel Code symbology, which ANSI/AIM BC12-1998 defines. It carries a number of two to seven digits. The number of digits, leading zeros included, +/// selects the channel count, one more than the digits, and each channel count carries a range of values: 0 to 26 for three channels, then 292, 3493, 44072, +/// 576688 and 7742862 for four to eight. The printed line shows the digits as given. +/// +public sealed class ChannelCodeSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public ChannelCodeSymbology() + : this(false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the finder pattern is the short form of three bars and two spaces rather than the full form of five bars and four + /// spaces. + public ChannelCodeSymbology(bool shortFinder) + => this.ShortFinder = shortFinder; + + /// + /// Gets a value indicating whether the finder pattern is the short form of three bars and two spaces rather than the full form of five bars and four + /// spaces. + /// + public bool ShortFinder { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Guard.MustBeBetweenOrEqualTo(text.Length, ChannelCodeEncoder.MinimumLength, ChannelCodeEncoder.MaximumLength, nameof(text)); + + ValidateDigits(text); + int value = int.Parse(text, NumberStyles.None, CultureInfo.InvariantCulture); + + int maximum = ChannelCodeEncoder.MaximumValue(text.Length); + if (value > maximum) + { + throw new ArgumentException($"Channel Code carries values up to {maximum} in {text.Length} digits; got {value}.", nameof(text)); + } + + ChannelCodeEncoder.BuildSymbol(value, text.Length, this.ShortFinder, text, options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/CheckCharacterMode.cs b/src/ImageSharp.Drawing/Barcodes/CheckCharacterMode.cs new file mode 100644 index 00000000..82bc8fbc --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/CheckCharacterMode.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Specifies whether a symbol carries a check character that its symbology makes optional. The encoder either calculates the character or validates the one +/// that the input carries. +/// +public enum CheckCharacterMode +{ + /// + /// The symbol carries no check character. All of the input is data. + /// + None, + + /// + /// The encoder calculates the check character from the input and puts it after the data. + /// + Compute, + + /// + /// The last character of the input is the check character. The encoder validates it against the data and carries it once. + /// + Validate, +} diff --git a/src/ImageSharp.Drawing/Barcodes/Codabar/CodabarEncoder.cs b/src/ImageSharp.Drawing/Barcodes/Codabar/CodabarEncoder.cs new file mode 100644 index 00000000..2509b120 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Codabar/CodabarEncoder.cs @@ -0,0 +1,193 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodation for the Codabar symbology, which ANSI/AIM BC3-1995 and BS EN 798:1995 define. Every symbol character has seven elements, four bars and three +/// spaces, of which two or three are wide, and a narrow inter-character gap follows it. Section 4.2 of BS EN 798 gives the structure: a quiet zone, a start +/// character, one or more data characters, a stop character and a quiet zone. +/// +internal static class CodabarEncoder +{ + /// + /// The largest number of characters a symbol carries. The symbology sets no maximum. + /// + public const int MaximumLength = 500; + + /// + /// The value of the first start and stop character. Values below it are data characters. + /// + public const int FirstStartStop = 16; + + /// + /// The characters the symbology encodes, in pattern order: the sixteen data characters, then the four start and stop characters. + /// + private const string Characters = "0123456789-$:/.+ABCD"; + + /// + /// The sixteen data characters of , for one vectorized search over the data. + /// + public static readonly SearchValues DataCharacterSet = SearchValues.Create(Characters.AsSpan(0, FirstStartStop)); + + /// + /// The quiet zone in modules on each side when 10X is wider than . Section 4.4.1 (d) of BS EN 798: "minimum width + /// of quiet zone: 10X or 2,54 mm, whichever is greater". + /// + private const int QuietZone = 10; + + /// + /// The smallest quiet zone in millimetres, section 4.4.1 (d) of BS EN 798. + /// + private const double MinimumQuietZoneMillimetres = 2.54; + + /// + /// The smallest X dimension in millimetres. Section 4.4.1 (a) of BS EN 798: "minimum width of narrow element (X): 0,191 mm". The section gives no largest X + /// dimension. + /// + private const double MinimumXDimension = 0.191; + + /// + /// The number of elements in a symbol character, its inter-character gap excluded. + /// + private const int ElementsPerCharacter = 7; + + /// + /// The width of a wide element in modules, three times the narrow element. + /// + private const int WideElement = 3; + + /// + /// The smallest bar height as a fraction of the symbol width, quiet zones excluded. Section 4.4.1 (e) of BS EN 798: "minimum bar code height: 5,0 mm or + /// 15 % of symbol width, whichever is greater". + /// + private const float MinimumBarHeightFraction = 0.15F; + + /// + /// The smallest bar height in millimetres, section 4.4.1 (e) of BS EN 798. + /// + private const float MinimumBarHeightMillimetres = 5F; + + /// + /// Gets the element widths of every character, one bit per element, most significant element first, in bar and space order: a set bit is a wide element and + /// a clear bit a narrow one. The gap that follows the character is not in the pattern. The entries are in the order of . + /// + private static ReadOnlySpan Patterns => + [ + 0x03, 0x06, 0x09, 0x60, 0x12, 0x42, 0x21, 0x24, 0x30, 0x48, + 0x0C, 0x18, 0x45, 0x51, 0x54, 0x15, 0x1A, 0x29, 0x0B, 0x0E, + ]; + + /// + /// Returns the value of a character, which is its index in . The letters T, N, * and E are the alternative names of the start and + /// stop characters A, B, C and D, and take the same values. + /// + /// The code point to value. + /// The value, or -1 when the character is not in the set. + public static int Value(int codePoint) => codePoint switch + { + >= '0' and <= '9' => codePoint - '0', + '-' => 10, + '$' => 11, + ':' => 12, + '/' => 13, + '.' => 14, + '+' => 15, + 'A' or 'T' => 16, + 'B' or 'N' => 17, + 'C' or '*' => 18, + 'D' or 'E' => 19, + _ => -1, + }; + + /// + /// Calculates the modulo 16 check character over a symbol. Annex A.3 of BS EN 798 leaves the check character to the application. The values of the start + /// character, the data characters and the stop character are added, and the check character is the one whose value lifts the sum to the next multiple of + /// sixteen. + /// + /// The start character and the data. + /// The stop character. + /// The check character. + public static char CheckCharacter(ReadOnlySpan head, char stop) + { + int sum = Value(stop); + foreach (char c in head) + { + sum += Value(c); + } + + return Characters[(FirstStartStop - (sum % FirstStartStop)) % FirstStartStop]; + } + + /// + /// Encodes the characters and builds the symbol. The runs carry the start character, the data, an optional check character and the stop character, each + /// followed by its inter-character gap except the stop character, so the runs end on a bar. The check character sits before the stop character. Codabar + /// carries no guard bars, so every bar runs the full height, and the human readable interpretation sits below the symbol: the start character and the data, + /// the check character when it prints, and the stop character. + /// + /// The start character and the data, already validated. + /// The stop character, already validated. + /// The check character to carry before the stop character, or . + /// Whether the check character is part of the human readable interpretation. + /// The options that control layout choices. + /// The symbol to write. + public static void BuildSymbol(ReadOnlySpan head, char stop, char? check, bool printCheck, BarcodeOptions options, ref BarcodeSymbol symbol) + { + int characters = head.Length + 1 + (check is null ? 0 : 1); + double xDimension = options.ResolveXDimension(MinimumXDimension, BarcodeSymbology.DefaultXDimension, double.PositiveInfinity); + Span runs = symbol.BeginLinear((characters * (ElementsPerCharacter + 1)) - 1, xDimension, out Span heights, out _); + int written = 0; + + foreach (char c in head) + { + Append(runs, ref written, Value(c), true); + } + + if (check is not null) + { + Append(runs, ref written, Value(check.Value), true); + } + + Append(runs, ref written, Value(stop), false); + + int widthInModules = BarcodeSymbol.RunWidth(runs); + + // The minimum of section 4.4.1 (e) is also the nominal bar height. The section gives no maximum. + float minimumBarHeight = MathF.Max(widthInModules * MinimumBarHeightFraction, (float)(MinimumBarHeightMillimetres / xDimension)); + float barHeight = options.ResolveBarHeight(xDimension, minimumBarHeight, minimumBarHeight, float.PositiveInfinity); + heights.Fill(barHeight); + + if (options.Font is not null) + { + ReadOnlySpan tail = check is not null && printCheck ? [check.Value, stop] : [stop]; + symbol.ReserveText(1, head.Length + tail.Length); + symbol.AddText(head, tail, [], 0F, widthInModules, BarcodeTextSide.BelowBars, barHeight + BarcodeTextPlacement.Clearance); + } + + float quietZone = MathF.Max(QuietZone, (float)(MinimumQuietZoneMillimetres / xDimension)); + symbol.EndLinear(quietZone, quietZone); + } + + /// + /// Writes the seven element widths of one character and, when one follows, its inter-character gap. + /// + /// The buffer the widths are written to. + /// The write position, advanced by the element count. + /// The character value, which is the pattern index. + /// Whether a narrow inter-character gap follows the character. + private static void Append(Span runs, ref int written, int value, bool gap) + { + byte pattern = Patterns[value]; + for (int element = ElementsPerCharacter - 1; element >= 0; element--) + { + runs[written++] = ((pattern >> element) & 1) == 1 ? WideElement : 1; + } + + if (gap) + { + runs[written++] = 1; + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Codabar/CodabarSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Codabar/CodabarSymbology.cs new file mode 100644 index 00000000..02a8bfc3 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Codabar/CodabarSymbology.cs @@ -0,0 +1,125 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Codabar symbology, which ANSI/AIM BC3-1995 and BS EN 798:1995 define. It carries the digits 0 to 9 and the symbols - $ : / . + between a start character +/// and a stop character, each of which is one of A, B, C and D, or one of their alternative names T, N, * and E. Section 4.2 of BS EN 798 requires at least one +/// data character, and section 4.3.2 permits A, B, C and D as start and stop characters only. The input is the start character, the data and the stop +/// character, and the printed line shows it as given. +/// +/// The symbology is self-checking and Annex A.3 of BS EN 798 leaves the check character to the application. The optional check character is the modulo 16 +/// character, placed before the stop character, and by default it prints there too. +/// +/// +public sealed class CodabarSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public CodabarSymbology() + : this(CheckCharacterMode.None, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the symbol carries the modulo 16 check character, and whether the encoder calculates it or validates it. + public CodabarSymbology(CheckCharacterMode checkCharacter) + : this(checkCharacter, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the symbol carries the modulo 16 check character, and whether the encoder calculates it or validates it. + /// Whether a check character the symbol carries is part of the human readable interpretation. + public CodabarSymbology(CheckCharacterMode checkCharacter, bool printCheckCharacter) + { + this.CheckCharacter = checkCharacter; + this.PrintCheckCharacter = printCheckCharacter; + } + + /// + /// Gets a value that specifies whether the symbol carries the modulo 16 check character, and whether the encoder calculates it or validates it. + /// + public CheckCharacterMode CheckCharacter { get; } + + /// + /// Gets a value indicating whether a check character the symbol carries is part of the human readable interpretation. A symbol that carries none prints + /// none either way. + /// + public bool PrintCheckCharacter { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Guard.MustBeLessThanOrEqualTo(text.Length, CodabarEncoder.MaximumLength, nameof(text)); + + // A start character, at least one data character, the check character when the caller supplies one, and a stop character. + int minimum = this.CheckCharacter == CheckCharacterMode.Validate ? 4 : 3; + if (text.Length < minimum) + { + throw new ArgumentException( + $"Codabar carries a start character, at least one data character and a stop character; got {text.Length} characters.", nameof(text)); + } + + // The first character, the data and the last character are checked in turn, so the message reports the first character outside its set. A surrogate + // pair at the end fails the data check at its first half, so the message reports the whole pair. + if (CodabarEncoder.Value(text[0]) < CodabarEncoder.FirstStartStop) + { + ThrowStartStop(text, 0); + } + + int index = text[1..^1].IndexOfAnyExcept(CodabarEncoder.DataCharacterSet); + if (index >= 0) + { + throw new ArgumentException( + $"Codabar carries only digits and the symbols -$:/.+ between its start and stop characters; got {text.CodePointAt(index + 1).ToDisplayString()}.", + nameof(text)); + } + + if (CodabarEncoder.Value(text[^1]) < CodabarEncoder.FirstStartStop) + { + ThrowStartStop(text, text.Length - 1); + } + + // The symbol carries the start character and the data, then the check character, then the stop character. A supplied check character stands before the + // stop character in the text. + char stop = text[^1]; + ReadOnlySpan head = text.Slice(0, text.Length - 1); + char? check = null; + if (this.CheckCharacter == CheckCharacterMode.Validate) + { + head = text.Slice(0, text.Length - 2); + check = CodabarEncoder.CheckCharacter(head, stop); + if (text[^2] != check) + { + throw new ArgumentException($"Incorrect check character: expected {check}, got {text[^2]}.", nameof(text)); + } + } + else if (this.CheckCharacter == CheckCharacterMode.Compute) + { + check = CodabarEncoder.CheckCharacter(head, stop); + } + + CodabarEncoder.BuildSymbol(head, stop, check, this.PrintCheckCharacter, options, ref symbol); + } + + /// + /// Throws for a first or last character that is not a start or stop character. + /// + /// The text. + /// The position of the character. + /// Always. + private static void ThrowStartStop(ReadOnlySpan text, int index) + => throw new ArgumentException( + $"Codabar starts and ends with one of A, B, C and D, or T, N, * and E; got {text.CodePointAt(index).ToDisplayString()}.", + nameof(text)); +} diff --git a/src/ImageSharp.Drawing/Barcodes/CodablockF/CodablockFEncoder.cs b/src/ImageSharp.Drawing/Barcodes/CodablockF/CodablockFEncoder.cs new file mode 100644 index 00000000..aee8ff33 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/CodablockF/CodablockFEncoder.cs @@ -0,0 +1,680 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodation for Codablock F. A symbol is 2 to 44 rows of Code 128 characters. Every row carries a Start A character, a code set selector, a row indicator, +/// the data columns, a modulo 103 row check character and the stop character. The first row's indicator is the row count less two. The indicator of every other +/// row is its index plus 42. The last row gives two of its data columns to the modulo 86 check characters K1 and K2 of the whole message. In code sets A and B, +/// the row and check values 0 to 31 map to the characters 64 to 95. The values 32 to 47 map to the characters 0 to 15. The values 48 to 85 map to the +/// characters 26 to 63. In code set C, each row and check value maps to the character with the same value. +/// +internal static class CodablockFEncoder +{ + /// + /// The smallest number of data columns in a row. + /// + public const int MinimumColumns = 4; + + /// + /// The largest number of data columns in a row. + /// + public const int MaximumColumns = 62; + + /// + /// The number of data columns in a row when none is given. + /// + public const int DefaultColumns = 8; + + /// + /// The longest text a symbol carries: two digits per character over 44 rows of 62 columns. + /// + public const int MaximumText = MaximumRows * MaximumColumns * 2; + + /// + /// The quiet zone in modules on each side. + /// + private const int QuietZone = 10; + + /// + /// The largest number of rows. + /// + private const int MaximumRows = 44; + + /// + /// The characters a row carries beyond its data columns: the start character, the code set selector, the row indicator, the row check character and the + /// stop character. + /// + private const int Overhead = 5; + + /// + /// The height in modules of the separator bars between rows and at the top and bottom of the symbol. + /// + private const float SeparatorHeight = 1F; + + /// + /// The modulus of the check characters K1 and K2. + /// + private const int CheckModulus = 86; + + /// + /// The symbol character value of Shift, which changes code set A to B, or B to A, for one character. After the start character of a row, the same value + /// selects code set A for the row. + /// + private const int Shift = 98; + + /// + /// The symbol character value of Code C, which changes code set A or B to code set C. + /// + private const int LatchC = 99; + + /// + /// The symbol character value of Code B, which changes code set A or C to code set B. + /// + private const int LatchB = 100; + + /// + /// The symbol character value of Code A, which changes code set B or C to code set A. + /// + private const int LatchA = 101; + + /// + /// The symbol character value of FNC4 in code set A, Table 1 of ISO/IEC 15417. It adds 128 to the next data character. + /// + private const int FunctionFourInA = 101; + + /// + /// The symbol character value of FNC4 in code set B, Table 1 of ISO/IEC 15417. It adds 128 to the next data character. + /// + private const int FunctionFourInB = 100; + + /// + /// The symbol character value of Start A, which starts every row. + /// + private const int StartA = 103; + + /// + /// The symbol character value of the stop character, the one character of seven elements. + /// + private const int Stop = 106; + + /// + /// The modulus of the Code 128 check character that ends every row. + /// + private const int RowModulus = 103; + + /// + /// The row indicator of the second row; each row that follows adds one. + /// + private const int SecondRowIndicator = 43; + + /// + /// The code sets of the Code 128 symbol characters. + /// + private enum CodeSet + { + /// + /// Code set A: ASCII 32 to 95 as 0 to 63 and the control characters 0 to 31 as 64 to 95. + /// + A, + + /// + /// Code set B: ASCII 32 to 127 as 0 to 95. + /// + B, + + /// + /// Code set C: the digit pairs 00 to 99 as 0 to 99. + /// + C, + } + + /// + /// The code set that carries the first character, at a position or after it, that only one of code sets A and B carries. + /// + private enum FirstExclusive + { + /// + /// No character from the position on is carried by only one of code sets A and B. + /// + None, + + /// + /// A control character, which only code set A carries, comes first. + /// + SetA, + + /// + /// A lower case character, which only code set B carries, comes first. + /// + SetB, + } + + /// + /// Gets the element widths of the Code 128 symbol characters the rows carry, packed four bits per element, the first element in the highest bits: six + /// elements each, three bars and three spaces of one to four modules, except the stop character, , which takes seven. + /// + private static ReadOnlySpan Patterns => + [ + 0x212222, 0x222122, 0x222221, 0x121223, 0x121322, 0x131222, 0x122213, 0x122312, + 0x132212, 0x221213, 0x221312, 0x231212, 0x112232, 0x122132, 0x122231, 0x113222, + 0x123122, 0x123221, 0x223211, 0x221132, 0x221231, 0x213212, 0x223112, 0x312131, + 0x311222, 0x321122, 0x321221, 0x312212, 0x322112, 0x322211, 0x212123, 0x212321, + 0x232121, 0x111323, 0x131123, 0x131321, 0x112313, 0x132113, 0x132311, 0x211313, + 0x231113, 0x231311, 0x112133, 0x112331, 0x132131, 0x113123, 0x113321, 0x133121, + 0x313121, 0x211331, 0x231131, 0x213113, 0x213311, 0x213131, 0x311123, 0x311321, + 0x331121, 0x312113, 0x312311, 0x332111, 0x314111, 0x221411, 0x431111, 0x111224, + 0x111422, 0x121124, 0x121421, 0x141122, 0x141221, 0x112214, 0x112412, 0x122114, + 0x122411, 0x142112, 0x142211, 0x241211, 0x221114, 0x413111, 0x241112, 0x134111, + 0x111242, 0x121142, 0x121241, 0x114212, 0x124112, 0x124211, 0x411212, 0x421112, + 0x421211, 0x212141, 0x214121, 0x412121, 0x111143, 0x111341, 0x131141, 0x114113, + 0x114311, 0x411113, 0x411311, 0x113141, 0x114131, 0x311141, 0x411131, 0x211412, + 0x211214, 0x211232, 0x2331112, + ]; + + /// + /// Encodes text and builds the symbol. The Code 128 character values of the whole symbol run one row after another. Each row is + /// plus values long. Each row of values becomes one row of Code 128 patterns. A separator between rows + /// keeps the start and stop patterns and fills the columns between them. A solid bar runs at the top and at the bottom. When a font is set, the data prints + /// as a text line below the bottom bar. The text line does not show the check characters. + /// + /// The text to encode. Every character must be U+0000 to U+00FF. + /// The number of data columns in a row, 4 to 62. + /// The human readable interpretation. + /// The options that control layout choices. + /// The symbol to write. + /// The text carries a character the symbology cannot encode, or more data than 44 rows hold. + public static void BuildSymbol(ReadOnlySpan text, int columns, ReadOnlySpan readable, BarcodeOptions options, ref BarcodeSymbol symbol) + { + int n = text.Length; + if (n > MaximumText) + { + throw new ArgumentException($"Codablock F carries at most {MaximumText} characters in 44 rows of 62 columns; the text is longer.", nameof(text)); + } + + // The rows are Code 128 characters, which carry ASCII 0 to 127 and, with FNC4, the byte values 128 to 255. + int invalid = text.IndexOfAnyExceptInRange('\0', '\u00FF'); + if (invalid >= 0) + { + throw new ArgumentException( + $"Codablock F encodes U+0000 to U+00FF; {text.CodePointAt(invalid).ToDisplayString()} is outside that range.", + nameof(text)); + } + + // The encoder knows the value count only after it lays out the rows. Because the encoder checks the 44 row limit before it starts each row, the values + // take room for 44 rows. The lookahead takes one entry, two integers, for every position and one past the end. Because the text is at most MaximumText + // characters, one bounded array holds the values and the lookahead. The encoder rents this array once. + int rowSize = columns + Overhead; + int capacity = MaximumRows * rowSize; + PooledBuffer buffer = new(capacity + ((n + 1) * 2)); + try + { + Span values = buffer.Span[..capacity]; + Span lookahead = MemoryMarshal.Cast(buffer.Span[capacity..]); + values = values[..Encode(text, columns, lookahead, values)]; + + int rowCount = values.Length / rowSize; + int width = RowWidth(columns); + double xDimension = options.ResolveXDimension(0, BarcodeSymbology.DefaultXDimension, double.PositiveInfinity); + float barHeight = options.ResolveBarHeight(xDimension, 0, NominalRowHeight(columns), float.PositiveInfinity); + + // The rows from the top: a solid edge bar, each data row with a separator below it but the last, and a solid edge bar. A separator keeps the start + // and stop patterns, thirteen runs and seven bars, and an edge bar is one bar across the row width. + const int separatorRuns = 13; + int dataRuns = (6 * (rowSize - 1)) + 7; + int separators = rowCount - 1; + symbol.BeginStacked( + (rowCount * 2) + 1, + 2 + (rowCount * dataRuns) + (separators * separatorRuns), + 2 + (rowCount * ((dataRuns + 1) >> 1)) + (separators * ((separatorRuns + 1) >> 1)), + xDimension); + + // The separator is the Start A pattern and the stop pattern with one bar between them across the columns, which also takes the first bar of the + // stop pattern: the row width less the 11 modules of Start A and the 11 modules of the stop pattern after its first bar. + InlineArray16 separatorStorage = default; + Span separatorPattern = ((Span)separatorStorage)[..separatorRuns]; + int separatorIndex = 0; + AppendPattern(Patterns[StartA], 6, separatorPattern, ref separatorIndex); + int middle = separatorIndex; + AppendPattern(Patterns[Stop], 7, separatorPattern, ref separatorIndex); + separatorPattern[middle] = width - 22; + + AddEdge(width, ref symbol); + for (int row = 0; row < rowCount; row++) + { + Span runs = symbol.AddRow(dataRuns, out Span heights, out _); + heights.Fill(barHeight); + + // The runs start with a bar. Every character is six elements, and the stop character is seven. + int index = 0; + foreach (int value in values.Slice(row * rowSize, rowSize)) + { + AppendPattern(Patterns[value], value == Stop ? 7 : 6, runs, ref index); + } + + if (row < rowCount - 1) + { + Span separator = symbol.AddRow(separatorRuns, out Span separatorHeights, out _); + separatorPattern.CopyTo(separator); + separatorHeights.Fill(SeparatorHeight); + } + } + + AddEdge(width, ref symbol); + + float heightInModules = (rowCount * barHeight) + ((rowCount + 1) * SeparatorHeight); + if (options.Font is not null && readable.Length > 0) + { + symbol.ReserveText(1, readable.Length); + symbol.AddText(readable, 0F, width, BarcodeTextSide.BelowBars, heightInModules + BarcodeTextPlacement.Clearance); + } + + symbol.EndStacked(0F, QuietZone, QuietZone); + } + finally + { + buffer.Dispose(); + } + } + + /// + /// Returns the nominal row height in modules for a column count: ten, or 0.55 modules per data column plus three when that is more. + /// + /// The number of data columns. + /// The nominal row height in modules. + private static float NominalRowHeight(int columns) + => MathF.Max(10F, (0.55F * columns) + 3F); + + /// + /// Returns the width of a row in modules: eleven per character and thirteen for the stop character. + /// + /// The number of data columns. + /// The row width in modules. + private static int RowWidth(int columns) + => (11 * (columns + Overhead - 1)) + 13; + + /// + /// Encodes text into character values: the rows with their pad, the check characters and the row indicators. + /// + /// The text to encode, U+0000 to U+00FF. + /// The number of data columns in a row. + /// The scratch for the lookahead, one entry for every position and one past the end. + /// The buffer the character values are written to, one row after another, with room for 44 rows. + /// The number of character values written. + /// The text needs more than 44 rows. + private static int Encode(ReadOnlySpan text, int columns, Span lookahead, Span values) + { + // The row break rules read the digit run from a position and which of a control character or a lower case character comes first. The lookahead holds + // both for every position, read from the end so every entry builds on the one after it. The entry past the end has no digits and no such character. + int n = text.Length; + lookahead[n] = default; + for (int p = n - 1; p >= 0; p--) + { + char c = text[p]; + Lookahead next = lookahead[p + 1]; + lookahead[p] = new Lookahead + { + DigitRun = char.IsAsciiDigit(c) ? next.DigitRun + 1 : 0, + First = OnlyIn(CodeSet.A, c) ? FirstExclusive.SetA : OnlyIn(CodeSet.B, c) ? FirstExclusive.SetB : next.First, + }; + } + + int rowSize = columns + Overhead; + int count = 0; + int i = 0; + int rows = 1; + CodeSet set = CodeSet.B; + bool lastRow = false; + while (!lastRow) + { + if (rows > MaximumRows) + { + throw new ArgumentException($"Codablock F carries at most 44 rows of {columns} columns; the text needs more.", nameof(text)); + } + + // Every row starts in the code set of its first characters: C for a digit pair, A when a control character comes before a lower case one, B + // otherwise. A row past the end of the data starts in B. + values[count++] = StartA; + if (lookahead[i].DigitRun >= 2) + { + values[count++] = LatchC; + set = CodeSet.C; + } + else if (lookahead[i].First == FirstExclusive.SetA) + { + values[count++] = Shift; + set = CodeSet.A; + } + else + { + values[count++] = LatchB; + set = CodeSet.B; + } + + // The row indicator takes its value once the row count is known. + values[count++] = 0; + + // Each step writes the next characters while the row has room for them, and the row ends at the first step that does not fit. + int remaining; + while (true) + { + remaining = columns + 3 - (count % rowSize); + if (i == n) + { + break; + } + + char c = text[i]; + int run = Math.Min(lookahead[i].DigitRun, 2 * remaining); + + // A character above 127 takes a single FNC4 before it, which adds 128 to it, and the FNC4 stays in the row of its character. + int functionFour = c > '\u007F' ? 1 : 0; + if (set == CodeSet.C) + { + // Code set C carries the digit pairs, and gives way to code set A or B, whichever carries the first character only one of them carries. + if (run >= 2) + { + count = WritePairs(text, i, 1, values, count); + i += 2; + continue; + } + + if (remaining < 2 + functionFour) + { + break; + } + + set = lookahead[i].First == FirstExclusive.SetA ? CodeSet.A : CodeSet.B; + values[count++] = set == CodeSet.A ? LatchA : LatchB; + if (functionFour != 0) + { + values[count++] = FunctionFourIn(set); + } + + values[count++] = ValueIn(set, c); + i++; + continue; + } + + // A run of four or more digits moves to code set C: an even run at once, and an odd run after its first digit. + if (run >= 4) + { + if ((run & 1) == 0 && remaining >= 3) + { + values[count++] = LatchC; + set = CodeSet.C; + count = WritePairs(text, i, 2, values, count); + i += 4; + continue; + } + + if ((run & 1) == 1 && remaining >= 4) + { + values[count++] = ValueIn(set, c); + i++; + values[count++] = LatchC; + set = CodeSet.C; + count = WritePairs(text, i, 2, values, count); + i += 4; + continue; + } + } + + // Code sets A and B mirror each other: each shifts or latches to the other for a character only the other carries. A shift pays off when the + // characters after it return to a character only the current set carries first. + CodeSet other = set == CodeSet.A ? CodeSet.B : CodeSet.A; + if (OnlyIn(other, c)) + { + if (remaining < 2 + functionFour) + { + break; + } + + // A shifted character takes the order of clause 4.3.4.2 d) of ISO/IEC 15417, FNC4 then Shift, so the FNC4 is the one of the code set in + // force. After a latch the FNC4 is the one of the new code set. + FirstExclusive ownFirst = set == CodeSet.A ? FirstExclusive.SetA : FirstExclusive.SetB; + if (lookahead[i + 1].First == ownFirst) + { + if (functionFour != 0) + { + values[count++] = FunctionFourIn(set); + } + + values[count++] = Shift; + } + else + { + values[count++] = other == CodeSet.A ? LatchA : LatchB; + set = other; + if (functionFour != 0) + { + values[count++] = FunctionFourIn(set); + } + } + + values[count++] = ValueIn(other, c); + i++; + continue; + } + + if (remaining < 1 + functionFour) + { + break; + } + + if (functionFour != 0) + { + values[count++] = FunctionFourIn(set); + } + + values[count++] = ValueIn(set, c); + i++; + } + + // The last row needs two columns for K1 and K2 and a symbol needs two rows. + if (rows > 1 && i == n && remaining >= 2) + { + count = Pad(values, count, remaining - 2, ref set); + + // K1, K2 and the row check character take their values below. + values.Slice(count, 3).Clear(); + count += 3; + values[count++] = Stop; + lastRow = true; + } + else + { + count = Pad(values, count, remaining, ref set); + + // The row check character takes its value below. + values[count++] = 0; + values[count++] = Stop; + rows++; + } + } + + // The check characters K1 and K2 of the message take the code set the last row ends in. + int k1 = 0; + int k2 = 0; + for (int p = 0; p < n; p++) + { + k1 = (k1 + ((p + 1) * text[p])) % CheckModulus; + k2 = (k2 + (p * text[p])) % CheckModulus; + } + + values[count - 4] = Map(k1, set != CodeSet.C); + values[count - 3] = Map(k2, set != CodeSet.C); + + // The row indicators: the row count in the first row, the row index from 43 in the rest, each in the code set its row starts in. + values[2] = Map(rows - 2, values[1] != LatchC); + for (int row = 1; row < rows; row++) + { + int at = row * rowSize; + values[at + 2] = Map(row + SecondRowIndicator - 1, values[at + 1] != LatchC); + } + + // Every row ends in the Code 128 check character of its own characters. + for (int row = 0; row < rows; row++) + { + int at = row * rowSize; + values[at + columns + 3] = RowCheckCharacter(values.Slice(at, columns + 3)); + } + + return count; + } + + /// + /// Adds a solid edge bar across the row width, at the separator height. + /// + /// The row width in modules. + /// The symbol to write. + private static void AddEdge(int width, ref BarcodeSymbol symbol) + { + Span runs = symbol.AddRow(1, out Span heights, out _); + runs[0] = width; + heights[0] = SeparatorHeight; + } + + /// + /// Writes pad characters to the end of a row: Code C in code sets A and B, Code B in code set C, each changing the set for the next. + /// + /// The buffer the character values are written to. + /// The number of character values written so far. + /// The number of pad characters. + /// The code set in effect, updated as the pads change it. + /// The number of character values written after the pad. + private static int Pad(Span values, int count, int pads, ref CodeSet set) + { + for (int k = 0; k < pads; k++) + { + if (set == CodeSet.C) + { + values[count++] = LatchB; + set = CodeSet.B; + } + else + { + values[count++] = LatchC; + set = CodeSet.C; + } + } + + return count; + } + + /// + /// Maps a row indicator or check value of 0 to 85 to a character value: 64 to 95 for 0 to 31, 0 to 15 for 32 to 47 and 26 to 63 for 48 to 85 in code sets A + /// and B, and the value itself in code set C. + /// + /// The value. + /// Whether the row is in code set A or B rather than C. + /// The character value. + private static int Map(int value, bool alphabetic) + => !alphabetic ? value : value switch + { + < 32 => value + 64, + < 48 => value - 32, + _ => value - 22, + }; + + /// + /// Returns the symbol character value of a character in code set A or B: code set A runs from the space at value 0 through to the control characters, and + /// code set B from the space through to the delete character. A character above 127 takes the value of the character 128 below it, which the FNC4 before + /// it shifts back up. + /// + /// The code set, A or B. + /// The character. + /// The symbol character value. + private static int ValueIn(CodeSet set, char character) + { + int value = character & 0x7F; + return set == CodeSet.A && value < 32 ? value + 64 : value - 32; + } + + /// + /// Returns whether only one code set of A and B carries a character: the control characters 0 to 31 for code set A, and the lower case letters and the + /// characters above them, 96 to 127, for code set B. A character above 127 classifies as the character 128 below it, which FNC4 shifts. + /// + /// The code set, A or B. + /// The character. + /// when only that code set carries it. + private static bool OnlyIn(CodeSet set, char character) => set == CodeSet.A ? (character & 0x7F) < 32 : (character & 0x7F) >= 96; + + /// + /// Returns the symbol character value of FNC4 in code set A or B, Table 1 of ISO/IEC 15417. + /// + /// The code set, A or B. Code set C has no FNC4. + /// The symbol character value. + private static int FunctionFourIn(CodeSet set) => set == CodeSet.A ? FunctionFourInA : FunctionFourInB; + + /// + /// Writes digit pairs as code set C values, each pair the value 0 to 99 it reads as. + /// + /// The text being encoded. + /// The index of the first digit. + /// The number of pairs to write. + /// The buffer the symbol character values are written to. + /// The number of values written so far. + /// The number of values written after the pairs. + private static int WritePairs(ReadOnlySpan text, int start, int pairs, Span values, int count) + { + for (int p = 0; p < pairs; p++) + { + int at = start + (p * 2); + values[count++] = ((text[at] - '0') * 10) + (text[at + 1] - '0'); + } + + return count; + } + + /// + /// Writes the element widths of a pattern packed as packs them. + /// + /// The pattern, four bits per element, the first element in the highest bits. + /// The number of elements. + /// The run widths in modules to write. + /// The position in , advanced past the elements. + private static void AppendPattern(uint pattern, int elements, Span runs, ref int index) + { + for (int shift = (elements - 1) * 4; shift >= 0; shift -= 4) + { + runs[index++] = (int)((pattern >> shift) & 0xF); + } + } + + /// + /// Returns the Code 128 check character of a row: the start character weighted by one and each following character by its position, the sum modulo 103. + /// + /// The symbol character values of the row, from the start character. + /// The check character value. + private static int RowCheckCharacter(ReadOnlySpan values) + { + int sum = values[0]; + for (int i = 1; i < values.Length; i++) + { + sum += values[i] * i; + } + + return sum % RowModulus; + } + + /// + /// What the row break rules read ahead from one position of the text. The entry is two integers, so the lookahead shares the rented array of the values. + /// + private struct Lookahead + { + /// + /// The length of the run of digits that starts at the position. + /// + public int DigitRun; + + /// + /// The code set of the first character, at the position or after it, that only one of code sets A and B carries. + /// + public FirstExclusive First; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/CodablockF/CodablockFSymbology.cs b/src/ImageSharp.Drawing/Barcodes/CodablockF/CodablockFSymbology.cs new file mode 100644 index 00000000..fd884cd5 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/CodablockF/CodablockFSymbology.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Codablock F symbology: 2 to 44 rows of Code 128 characters, each row with its own start, code set selector, row indicator, check and stop characters, +/// and two modulo 86 check characters of the whole message in the last row. It carries the characters U+0000 to U+00FF, ISO/IEC 8859-1, and a character above +/// U+007F takes an extra FNC4 character. The data prints as a text line below the symbol when a font is set. +/// +public sealed class CodablockFSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class with eight data columns. + /// + public CodablockFSymbology() + : this(CodablockFEncoder.DefaultColumns) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The number of data columns in a row, 4 to 62. The row count follows from the data. + public CodablockFSymbology(int columns) + { + Guard.MustBeBetweenOrEqualTo(columns, CodablockFEncoder.MinimumColumns, CodablockFEncoder.MaximumColumns, nameof(columns)); + + this.Columns = columns; + } + + /// + /// Gets the number of data columns in a row. + /// + public int Columns { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + => CodablockFEncoder.BuildSymbol(text, this.Columns, text, options, ref symbol); +} diff --git a/src/ImageSharp.Drawing/Barcodes/CodablockF/HibcCodablockFSymbology.cs b/src/ImageSharp.Drawing/Barcodes/CodablockF/HibcCodablockFSymbology.cs new file mode 100644 index 00000000..4b3dfe82 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/CodablockF/HibcCodablockFSymbology.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// HIBC Codablock F, a Health Industry Bar Code carried by a Codablock F symbol. The symbol carries the flag character, the data and the modulo 43 check +/// character, and prints that same string below the symbol between delimiters when a font is set, with a space printed as an underscore and a zero as "Ø". +/// The text follows a data structure of ANSI/HIBC 2.6: primary data, secondary data that ends with its link character, or primary and secondary data joined +/// with "/", each with any supplemental data fields. +/// +public sealed class HibcCodablockFSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class with eight data columns, calculating the check character from the data. + /// + public HibcCodablockFSymbology() + : this(CodablockFEncoder.DefaultColumns, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The number of data columns in a row, 4 to 62. The row count follows from the data. + /// Whether the last character of the input is the check character, which the encoder validates against the data, + /// rather than data the encoder appends a check character to. + public HibcCodablockFSymbology(int columns, bool validateCheckCharacter) + { + Guard.MustBeBetweenOrEqualTo(columns, CodablockFEncoder.MinimumColumns, CodablockFEncoder.MaximumColumns, nameof(columns)); + + this.Columns = columns; + this.ValidateCheckCharacter = validateCheckCharacter; + } + + /// + /// Gets the number of data columns in a row. + /// + public int Columns { get; } + + /// + /// Gets a value indicating whether the last character of the input is the check character. + /// + public bool ValidateCheckCharacter { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray64 bufferStorage = default; + int capacity = HibcData.PreparedCapacity(text, CodablockFEncoder.MaximumText); + ValueStringBuilder encoded = capacity <= HibcData.InlineBufferLength ? new(bufferStorage) : new(capacity); + try + { + HibcData.Prepare(text, this.ValidateCheckCharacter, ref encoded); + + ReadOnlySpan data = encoded.AsSpan(); + + // The interpretation is the encoded data between two delimiters, so a buffer three characters longer holds it, and longer data rents it once. + InlineArray64 readableStorage = default; + int readableCapacity = HibcData.ReadableCapacity(data.Length); + using PooledBuffer rented = readableCapacity <= HibcData.InlineBufferLength ? default : new(readableCapacity); + Span readable = readableCapacity <= HibcData.InlineBufferLength ? ((Span)readableStorage)[..readableCapacity] : rented.Span; + int written = HibcData.WriteReadable(data, readable); + CodablockFEncoder.BuildSymbol(data, this.Columns, readable[..written], options, ref symbol); + } + finally + { + encoded.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code11/Code11Encoder.cs b/src/ImageSharp.Drawing/Barcodes/Code11/Code11Encoder.cs new file mode 100644 index 00000000..ae59040e --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code11/Code11Encoder.cs @@ -0,0 +1,189 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodation for the Code 11 symbology, also called USD-8. Every character has five elements, three bars and two spaces, of which one or two are wide, and a +/// narrow inter-character gap follows it. A symbol is a quiet zone, the start character, the data, one or two optional check characters, the stop character and +/// a quiet zone. +/// +internal static class Code11Encoder +{ + /// + /// The characters the symbology encodes, in pattern order. + /// + public const string Characters = "0123456789-"; + + /// + /// The largest number of characters a symbol carries. The symbology sets no maximum. + /// + public const int MaximumLength = 500; + + /// + /// The number of data characters from which a symbol carries two check characters. Below it a symbol carries one. + /// + public const int TwoCheckCharactersFrom = 10; + + /// + /// The quiet zone in modules on each side. There is no public specification of Code 11. + /// + private const int QuietZone = 10; + + /// + /// The number of elements in a symbol character, its inter-character gap excluded. + /// + private const int ElementsPerCharacter = 5; + + /// + /// The pattern index of the start and stop character, which carries no data. + /// + private const int StartStop = 11; + + /// + /// The number of start and stop characters a symbol carries around its data and check characters. + /// + private const int StartStopCharacters = 2; + + /// + /// The width of a wide element in modules. There is no public specification of Code 11. + /// + private const int WideElement = 3; + + /// + /// The bar height as a fraction of the symbol width, quiet zones excluded, when the caller sets no height. There is no public specification of Code 11. + /// + private const float NominalBarHeightFraction = 0.15F; + + /// + /// The characters of , for one vectorized search over a whole text. + /// + public static readonly SearchValues CharacterSet = SearchValues.Create(Characters); + + /// + /// Gets the element widths of every character, one bit per element, most significant element first, in bar and space order: a set bit is a wide element and + /// a clear bit a narrow one. The gap that follows the character is not in the pattern. The entries are in the order of , and the + /// last entry is the start and stop character. + /// + private static ReadOnlySpan Patterns => [0x01, 0x11, 0x09, 0x18, 0x05, 0x14, 0x0C, 0x03, 0x12, 0x10, 0x04, 0x06]; + + /// + /// Returns the value of a character, which is its index in . + /// + /// The code point to value. + /// The value, or -1 when the character is not in the set. + public static int Value(int codePoint) => codePoint switch + { + >= '0' and <= '9' => codePoint - '0', + '-' => 10, + _ => -1, + }; + + /// + /// Calculates the C check character over the data. Each character is weighted by its position from the right, 1 for the rightmost and up to 10, after which + /// the weights start again at 1. The check value is the weighted sum modulo 11. + /// + /// The data the check character covers. + /// The value of the check character. + public static int CheckC(ReadOnlySpan data) + { + int sum = 0; + int weight = 1; + for (int i = data.Length - 1; i >= 0; i--) + { + sum += weight * Value(data[i]); + weight = weight == 10 ? 1 : weight + 1; + } + + return sum % 11; + } + + /// + /// Calculates the K check character over the data and the C check character, which is the rightmost character it covers. Each character is weighted by its + /// position from the right, 1 for the C check character and up to 9, after which the weights start again at 1. The check value is the weighted sum modulo + /// 11. + /// + /// The data the check character covers. + /// The value of the C check character. + /// The value of the check character. + public static int CheckK(ReadOnlySpan data, int checkC) + { + int sum = checkC; + int weight = 2; + for (int i = data.Length - 1; i >= 0; i--) + { + sum += weight * Value(data[i]); + weight = weight == 9 ? 1 : weight + 1; + } + + return sum % 11; + } + + /// + /// Encodes data and builds the symbol. The runs carry the start character, the data, the check characters and the stop character, each followed by its + /// inter-character gap except the stop character, so the runs end on a bar. Code 11 carries no guard bars, so every bar runs the full height, and the human + /// readable interpretation sits below the symbol: the data, with the check characters when they print. + /// + /// The data to encode, already validated. + /// The check characters to carry after the data, none, one or two. + /// Whether the check characters are part of the human readable interpretation. + /// The options that control layout choices. + /// The symbol to write. + public static void BuildSymbol(ReadOnlySpan data, ReadOnlySpan checks, bool printChecks, BarcodeOptions options, ref BarcodeSymbol symbol) + { + int characters = data.Length + checks.Length + StartStopCharacters; + double xDimension = options.ResolveXDimension(0, BarcodeSymbology.DefaultXDimension, double.PositiveInfinity); + Span runs = symbol.BeginLinear((characters * (ElementsPerCharacter + 1)) - 1, xDimension, out Span heights, out _); + int written = 0; + + Append(runs, ref written, StartStop, true); + foreach (char c in data) + { + Append(runs, ref written, Value(c), true); + } + + foreach (char c in checks) + { + Append(runs, ref written, Value(c), true); + } + + Append(runs, ref written, StartStop, false); + + int widthInModules = BarcodeSymbol.RunWidth(runs); + + float barHeight = options.ResolveBarHeight(xDimension, 0, widthInModules * NominalBarHeightFraction, float.PositiveInfinity); + heights.Fill(barHeight); + + if (options.Font is not null) + { + ReadOnlySpan printed = printChecks ? checks : []; + symbol.ReserveText(1, data.Length + printed.Length); + symbol.AddText(data, printed, [], 0F, widthInModules, BarcodeTextSide.BelowBars, barHeight + BarcodeTextPlacement.Clearance); + } + + symbol.EndLinear(QuietZone, QuietZone); + } + + /// + /// Writes the five element widths of one character and, when one follows, its inter-character gap. + /// + /// The buffer the widths are written to. + /// The write position, advanced by the element count. + /// The character value, which is the pattern index. + /// Whether a narrow inter-character gap follows the character. + private static void Append(Span runs, ref int written, int value, bool gap) + { + byte pattern = Patterns[value]; + for (int element = ElementsPerCharacter - 1; element >= 0; element--) + { + runs[written++] = ((pattern >> element) & 1) == 1 ? WideElement : 1; + } + + if (gap) + { + runs[written++] = 1; + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code11/Code11Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Code11/Code11Symbology.cs new file mode 100644 index 00000000..b19ff719 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code11/Code11Symbology.cs @@ -0,0 +1,117 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Code 11 symbology, also called USD-8. It carries the digits 0 to 9 and the dash between a start character and a stop character, which the printed line +/// does not show. +/// +/// The check characters are optional. A symbol with fewer than ten data characters carries the C check character, and a symbol with ten or more carries the C +/// and K check characters. C is the weighted sum of the data modulo 11, and K is the weighted sum of the data and C modulo 11. The check characters print after +/// the data by default. +/// +/// +public sealed class Code11Symbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Code11Symbology() + : this(CheckCharacterMode.None, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the symbol carries the C and K check characters, and whether the encoder calculates them or validates them. + /// + public Code11Symbology(CheckCharacterMode checkCharacters) + : this(checkCharacters, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the symbol carries the C and K check characters, and whether the encoder calculates them or validates them. + /// + /// Whether the check characters the symbol carries are part of the human readable interpretation. + public Code11Symbology(CheckCharacterMode checkCharacters, bool printCheckCharacters) + { + this.CheckCharacters = checkCharacters; + this.PrintCheckCharacters = printCheckCharacters; + } + + /// + /// Gets a value that specifies whether the symbol carries the C and K check characters, and whether the encoder calculates them or validates them. + /// + public CheckCharacterMode CheckCharacters { get; } + + /// + /// Gets a value indicating whether the check characters the symbol carries are part of the human readable interpretation. A symbol that carries none prints + /// none either way. + /// + public bool PrintCheckCharacters { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Guard.MustBeGreaterThan(text.Length, 0, nameof(text)); + Guard.MustBeLessThanOrEqualTo(text.Length, Code11Encoder.MaximumLength, nameof(text)); + + int index = text.IndexOfAnyExcept(Code11Encoder.CharacterSet); + if (index >= 0) + { + throw new ArgumentException($"Code 11 carries only digits and the dash; got {text.CodePointAt(index).ToDisplayString()}.", nameof(text)); + } + + ReadOnlySpan data = text; + InlineArray2 checkStorage = default; + Span checks = checkStorage; + int checkCount = 0; + if (this.CheckCharacters == CheckCharacterMode.Validate) + { + // Nine data characters and one check character make ten, and ten data characters and two check characters make twelve, so eleven characters cannot + // carry a valid check. + if (text.Length == Code11Encoder.TwoCheckCharactersFrom + 1) + { + throw new ArgumentException( + $"Code 11 cannot carry {text.Length} characters with its check characters: nine data characters take one and ten take two.", nameof(text)); + } + + checkCount = text.Length <= Code11Encoder.TwoCheckCharactersFrom ? 1 : 2; + if (text.Length <= checkCount) + { + throw new ArgumentException("Code 11 carries at least one data character before its check characters.", nameof(text)); + } + + data = text.Slice(0, text.Length - checkCount); + } + else if (this.CheckCharacters == CheckCharacterMode.Compute) + { + checkCount = data.Length >= Code11Encoder.TwoCheckCharactersFrom ? 2 : 1; + } + + if (checkCount > 0) + { + int checkC = Code11Encoder.CheckC(data); + checks[0] = Code11Encoder.Characters[checkC]; + if (checkCount == 2) + { + checks[1] = Code11Encoder.Characters[Code11Encoder.CheckK(data, checkC)]; + } + + if (this.CheckCharacters == CheckCharacterMode.Validate && !text.Slice(data.Length).SequenceEqual(checks[..checkCount])) + { + throw new ArgumentException($"Incorrect check characters: expected {checks[..checkCount]}, got {text.Slice(data.Length)}.", nameof(text)); + } + } + + Code11Encoder.BuildSymbol(data, checks[..checkCount], this.PrintCheckCharacters, options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code128/Code128CodeSet.cs b/src/ImageSharp.Drawing/Barcodes/Code128/Code128CodeSet.cs new file mode 100644 index 00000000..98e6d42d --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code128/Code128CodeSet.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The three Code 128 code sets of section 5.4.3.3 of the GS1 General Specifications. The set in force decides which character each symbol character value +/// stands for. +/// +internal enum Code128CodeSet +{ + /// + /// Code set A: the space through the upper case letters, then the control characters. + /// + A, + + /// + /// Code set B: the space through the delete character. + /// + B, + + /// + /// Code set C: pairs of digits, two per symbol character. + /// + C, +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code128/Code128Encoder.cs b/src/ImageSharp.Drawing/Barcodes/Code128/Code128Encoder.cs new file mode 100644 index 00000000..a3f7d598 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code128/Code128Encoder.cs @@ -0,0 +1,856 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Shared encodation for the Code 128 symbology family. Section 5.4 of the GS1 General Specifications describes the symbology and names ISO/IEC 15417 as its +/// full definition. Every symbol character is six elements wide, three bars and three spaces of one to four modules, and carries eleven modules. The stop +/// character is the one exception: seven elements over thirteen modules. +/// +/// A GS1 symbol starts with Start C and a Function 1 character, step 1 of section 5.4.7.6 of the GS1 General Specifications. When other text starts with four +/// or more digits, or when the whole text is two digits, the text starts in code set C. Otherwise, the text starts in the one of code sets A and B that carries +/// the first character that only one of them carries. A run of four or more digits changes to code set C, as step 3 does. When the run is odd, the change comes +/// after its first digit. Some characters are only in the other of code sets A and B. When the next such character is in the current code set, a character of +/// this type takes a Shift. Otherwise, the character takes a code set change. +/// +/// +/// Clause 4.3.4.2 d) of ISO/IEC 15417 encodes the byte values 128 to 255, ISO/IEC 8859-1 by default, with the FNC4 character. A single FNC4 adds 128 to the +/// next data character, and two FNC4 characters start or end extended mode, which adds 128 to every data character between them. Note 3 of Annex E chooses +/// code set A or B for such a character by its value less 128, and takes extended mode for a run of five or more of them, or three or more at the end of the +/// data. A shorter run takes a single FNC4 before each character. +/// +/// +internal static class Code128Encoder +{ + /// + /// The quiet zone in modules on each side. Section 5.4.4.2 requires ten times the X-dimension. + /// + public const int QuietZone = 10; + + /// + /// The largest number of characters this library encodes in one Code 128 symbol. The value sizes the buffers. The symbology sets no maximum of its own. + /// + public const int MaximumLength = 500; + + /// + /// The width of a symbol character in modules, section 5.4.7.2. + /// + public const int ModulesPerCharacter = 11; + + /// + /// The width of the stop character in modules, section 5.4.7.2. + /// + public const int StopModules = 13; + + /// + /// The symbol character value of the Function 1 character. Section 5.4.2 places it directly after the start character to mark a GS1-128 symbol, and section + /// 5.4.7.4 transmits it "in the third or subsequent character position" as the control character GS, the separator of section 7.8.6.2. + /// + private const int FunctionOne = 102; + + /// + /// The nominal X dimension in millimetres of a GS1 symbol: the target of 0.495 mm of Table 5-51 of the GS1 General Specifications, and of most other GS1 + /// symbol specification tables that list GS1-128. A Code 128 symbol that is not a GS1 symbol takes , + /// because section 4.4.1 of ISO/IEC 15417 gives no value: "The width of a module should be defined by the application specification". + /// + private const double NominalXDimension = 0.495; + + /// + /// The symbol character value of Shift, which changes code set A to B, or B to A, for one character. + /// + private const int Shift = 98; + + /// + /// The symbol character value of Code C, which changes code set A or B to code set C. + /// + private const int LatchC = 99; + + /// + /// The symbol character value of Code B, which changes code set A or C to code set B. + /// + private const int LatchB = 100; + + /// + /// The symbol character value of Code A, which changes code set B or C to code set A. + /// + private const int LatchA = 101; + + /// + /// The symbol character value of FNC4 in code set A, Table 1 of ISO/IEC 15417. + /// + private const int FunctionFourInA = 101; + + /// + /// The symbol character value of FNC4 in code set B, Table 1 of ISO/IEC 15417. Code set C has no FNC4. + /// + private const int FunctionFourInB = 100; + + /// + /// The shortest run of characters above 127 that takes extended mode, note 3 of Annex E of ISO/IEC 15417. The two FNC4 characters before the run and the + /// two after it cost four symbol characters, and a single FNC4 before each character of the run costs one each. + /// + private const int ShortestExtendedRun = 5; + + /// + /// The shortest run of characters above 127 that takes extended mode when the run ends the data, note 3 of Annex E of ISO/IEC 15417. The data ends in + /// extended mode, so only the two FNC4 characters before the run are needed. + /// + private const int ShortestFinalExtendedRun = 3; + + /// + /// The symbol character value of Start A. + /// + private const int StartA = 103; + + /// + /// The symbol character value of Start B. + /// + private const int StartB = 104; + + /// + /// The symbol character value of Start C. + /// + private const int StartC = 105; + + /// + /// The symbol character value of the stop character, the one character of seven elements. + /// + private const int Stop = 106; + + /// + /// The modulus of the symbol check character, step 5 of section 5.4.7.5.1. + /// + private const int CheckModulus = 103; + + /// + /// The element count of a symbol character, section 5.4.1: three bars and three spaces. + /// + private const int ElementsPerCharacter = 6; + + /// + /// The element count of the stop character, section 5.4.1: four bars and three spaces. + /// + private const int StopElements = 7; + + /// + /// The character count that the inline buffers hold. The buffers hold 128 values and 256 look-ahead bytes. ASCII text takes two values for each character. + /// Text that has a character above 127 takes three values for each character. The look-ahead takes four bytes for each character. The values also hold five + /// characters that are not text, so ASCII text of at most 61 characters encodes inline. Section 5.4.4.3 caps a GS1-128 symbol at 48 data characters of + /// ASCII, so every conforming GS1-128 symbol encodes inline. + /// + private const int InlineLength = 64; + + /// + /// The nominal bar height in modules: the minimum symbol height of 31.75 mm at the X dimension of 0.495 mm, Table 5-51 of the GS1 General Specifications. + /// + private const float NominalBarHeight = (float)(31.75 / NominalXDimension); + + /// + /// The bar height of a GS1 symbol in millimetres. Footnote (****) of Table 5-47 of the GS1 General Specifications: "For GS1-128 and ITF-14 symbols the + /// minimum symbol height for General distribution scanning is always 31.75 millimetres (1.250 inch)." + /// + private const double Gs1BarHeight = 31.75; + + /// + /// Gets the element widths of every symbol character, packed four bits per element, the first element in the highest bits. Table 5-25 of the GS1 General + /// Specifications lists them: six elements each, three bars and three spaces of one to four modules, except the stop character, , which + /// takes seven. + /// + private static ReadOnlySpan Patterns => + [ + 0x212222, 0x222122, 0x222221, 0x121223, 0x121322, 0x131222, 0x122213, 0x122312, + 0x132212, 0x221213, 0x221312, 0x231212, 0x112232, 0x122132, 0x122231, 0x113222, + 0x123122, 0x123221, 0x223211, 0x221132, 0x221231, 0x213212, 0x223112, 0x312131, + 0x311222, 0x321122, 0x321221, 0x312212, 0x322112, 0x322211, 0x212123, 0x212321, + 0x232121, 0x111323, 0x131123, 0x131321, 0x112313, 0x132113, 0x132311, 0x211313, + 0x231113, 0x231311, 0x112133, 0x112331, 0x132131, 0x113123, 0x113321, 0x133121, + 0x313121, 0x211331, 0x231131, 0x213113, 0x213311, 0x213131, 0x311123, 0x311321, + 0x331121, 0x312113, 0x312311, 0x332111, 0x314111, 0x221411, 0x431111, 0x111224, + 0x111422, 0x121124, 0x121421, 0x141122, 0x141221, 0x112214, 0x112412, 0x122114, + 0x122411, 0x142112, 0x142211, 0x241211, 0x221114, 0x413111, 0x241112, 0x134111, + 0x111242, 0x121142, 0x121241, 0x114212, 0x124112, 0x124211, 0x411212, 0x421112, + 0x421211, 0x212141, 0x214121, 0x412121, 0x111143, 0x111341, 0x131141, 0x114113, + 0x114311, 0x411113, 0x411311, 0x113141, 0x114131, 0x311141, 0x411131, 0x211412, + 0x211214, 0x211232, 0x2331112, + ]; + + /// + /// Builds a GS1-128 symbol that carries one element string of a predefined length, such as the GTIN of EAN-14 or the SSCC of SSCC-18. The input is the + /// Application Identifier in parentheses and the data digits, with the check digit or without it. Spaces are ignored. The check digit is the one of + /// section 7.9. The interpretation prints the input as given. When the input leaves out the check digit, the calculated one follows it, after a space when + /// the input groups its digits with spaces. + /// + /// The input. + /// The two digit Application Identifier. + /// The number of data digits with the check digit, at most 18. + /// The smallest X dimension in millimetres. + /// The largest X dimension in millimetres. + /// The options that control layout choices. + /// The symbol to write. + /// The input is not the identifier and the digits, or the check digit is not correct. + public static void BuildFixedElementString( + ReadOnlySpan text, + ReadOnlySpan identifier, + int dataLength, + double minimumXDimension, + double maximumXDimension, + BarcodeOptions options, + ref BarcodeSymbol symbol) + { + // The element string without grouping spaces is the four characters of the identifier in parentheses and the digits. The caption repeats the input, + // so the input is bounded too: at most one grouping space after each character of the element string. + int elementStringLength = 4 + dataLength; + Guard.MustBeLessThanOrEqualTo(text.Length, elementStringLength * 2, nameof(text)); + + // The longest element string is the 22 characters of an SSCC. + InlineArray24 compactStorage = default; + Span compact = ((Span)compactStorage)[..elementStringLength]; + int length = 0; + foreach (char character in text) + { + if (character == ' ') + { + continue; + } + + if (length == compact.Length) + { + throw new ArgumentException( + $"The text takes the ({identifier}) application identifier and {dataLength - 1} or {dataLength} digits.", + nameof(text)); + } + + compact[length++] = character; + } + + if (length < elementStringLength - 1) + { + throw new ArgumentException( + $"The text takes the ({identifier}) application identifier and {dataLength - 1} or {dataLength} digits; got {length} characters.", + nameof(text)); + } + + if (compact[0] != '(' || !compact.Slice(1, 2).SequenceEqual(identifier) || compact[3] != ')') + { + throw new ArgumentException($"The text begins with the ({identifier}) application identifier.", nameof(text)); + } + + ReadOnlySpan supplied = compact[4..length]; + BarcodeSymbology.ValidateDigits(supplied); + + int check = Gs1Data.ComputeCheckDigit(supplied[..(dataLength - 1)]); + if (supplied.Length == dataLength && supplied[^1] - '0' != check) + { + throw new ArgumentException($"Incorrect check digit: expected {check}, got {supplied[^1]}.", nameof(text)); + } + + // The symbol carries the identifier and the digits with no separator, because Table 7-6 gives this element string a predefined length. + char checkCharacter = (char)('0' + check); + InlineArray24 encodedStorage = default; + Span encoded = ((Span)encodedStorage)[..(2 + dataLength)]; + identifier.CopyTo(encoded); + supplied[..(dataLength - 1)].CopyTo(encoded[2..]); + encoded[^1] = checkCharacter; + + // The caption is the input as given, with the check digit after it when the input left it out, set off by a space when the input groups its digits + // with spaces. + ReadOnlySpan suffix = supplied.Length == dataLength ? [] : text.Contains(' ') ? [' ', checkCharacter] : [checkCharacter]; + BuildSymbol(encoded, true, [], text, suffix, minimumXDimension, maximumXDimension, Code128Linkage.None, options, ref symbol); + } + + /// + /// Encodes text and builds the symbol. The runs carry the start character, the code set switches, the check character of section 5.4.3.6 and the stop + /// character, whose seven elements end the list on a bar. Every symbology in the family shares this step, so only the encodation differs between them. Code + /// 128 carries no guard bars, so every bar runs the full height. The human readable interpretation prints below the symbol, adjacent to it as rule 1 of + /// section 4.14 asks. + /// + /// The text to encode. Every character must be U+0000 to U+00FF. + /// Whether this is a GS1 symbol. Such a symbol carries a Function 1 character after its start character, and encodes every + /// in the text as another one. + /// The characters of the human readable interpretation before its body. + /// The body of the human readable interpretation. + /// The characters of the human readable interpretation after its body. + /// The options that control layout choices. + /// The symbol to write. + /// The text carries a character the symbology cannot encode. + public static void BuildSymbol( + ReadOnlySpan text, + bool gs1, + ReadOnlySpan readablePrefix, + ReadOnlySpan readableBody, + ReadOnlySpan readableSuffix, + BarcodeOptions options, + ref BarcodeSymbol symbol) + => BuildSymbol(text, gs1, readablePrefix, readableBody, readableSuffix, 0, double.PositiveInfinity, Code128Linkage.None, options, ref symbol); + + /// + /// Encodes text and builds the symbol at an X dimension clamped to the range of the application standard. The steps are those of + /// . + /// + /// The text to encode. Every character must be U+0000 to U+00FF. + /// Whether this is a GS1 symbol. Such a symbol carries a Function 1 character after its start character, and encodes every + /// in the text as another one. + /// The characters of the human readable interpretation before its body. + /// The body of the human readable interpretation. + /// The characters of the human readable interpretation after its body. + /// The smallest X dimension the application standard permits, in millimetres. + /// The largest X dimension the application standard permits, in millimetres. + /// The linkage flag of a GS1-128 symbol to which a 2D component of a GS1 Composite symbol is linked. + /// The options that control layout choices. + /// The symbol to write. + /// The text carries a character the symbology cannot encode. + public static void BuildSymbol( + ReadOnlySpan text, + bool gs1, + ReadOnlySpan readablePrefix, + ReadOnlySpan readableBody, + ReadOnlySpan readableSuffix, + double minimumXDimension, + double maximumXDimension, + Code128Linkage linkage, + BarcodeOptions options, + ref BarcodeSymbol symbol) + { + // The bars of a GS1 symbol are 31.75 millimetres at any X dimension. The module draws at a whole number of pixels. The notes of Tables 5-47 and 5-51 + // of the GS1 General Specifications: "There is no maximum for the height". The notes of Tables 5-50 and 5-51: "In no case SHALL the bar height be less + // than 5.08 millimetres". Section 4.4 of ISO/IEC 15417 gives Code 128 itself no bar height, and section 4.4.1 leaves the X dimension to "the + // application specification". + double xDimension = options.ResolveXDimension(minimumXDimension, gs1 ? NominalXDimension : BarcodeSymbology.DefaultXDimension, maximumXDimension); + Code128Layout layout = new() + { + XDimension = xDimension, + NominalBarHeight = gs1 ? (float)(Gs1BarHeight / xDimension) : NominalBarHeight, + MinimumBarHeight = gs1 ? (float)(Gs1Data.MinimumTruncatedBarHeight / xDimension) : 0F, + MaximumBarHeight = float.PositiveInfinity, + TextClearance = BarcodeTextPlacement.Clearance, + HorizontalQuietZone = QuietZone, + }; + + BuildSymbol(text, gs1, readablePrefix, readableBody, readableSuffix, [], linkage, in layout, options, ref symbol); + + // Section 4.14 rule 2.a.iii of the GS1 General Specifications: "The font size SHOULD be at least 2 mm (0.08 in) in height". + if (gs1) + { + symbol.SetTextSize(Gs1Data.MinimumTextSize, 0F); + } + } + + /// + /// Encodes text and builds the symbol at the dimensions of an application standard, which can print a second line above the bars. The ink of the line + /// above the bars stays within the symbol and the quiet zones of . + /// + /// The text to encode. Every character must be U+0000 to U+00FF. + /// Whether this is a GS1 symbol. Such a symbol carries a Function 1 character after its start character, and encodes every + /// in the text as another one. + /// The characters of the human readable interpretation before its body. + /// The body of the human readable interpretation. + /// The characters of the human readable interpretation after its body. + /// The line above the bars, or empty for none. + /// The linkage flag of a GS1-128 symbol to which a 2D component of a GS1 Composite symbol is linked. + /// The dimensions of the application standard. + /// The options that control layout choices. + /// The symbol to write. + /// The text carries a character the symbology cannot encode. + public static void BuildSymbol( + ReadOnlySpan text, + bool gs1, + ReadOnlySpan readablePrefix, + ReadOnlySpan readableBody, + ReadOnlySpan readableSuffix, + ReadOnlySpan above, + Code128Linkage linkage, + in Code128Layout layout, + BarcodeOptions options, + ref BarcodeSymbol symbol) + { + double xDimension = layout.XDimension; + Guard.MustBeLessThanOrEqualTo(text.Length, MaximumLength, nameof(text)); + + // The code sets carry ASCII 0 to 127 between them, and clause 4.3.4.2 d) of ISO/IEC 15417 adds the byte values 128 to 255 with FNC4, so anything above + // U+00FF is rejected. The message decodes the code point at the first such unit, so a surrogate pair reports as the one character it is. + int invalid = text.IndexOfAnyExceptInRange('\0', '\u00FF'); + if (invalid >= 0) + { + throw new ArgumentException( + $"Code 128 encodes U+0000 to U+00FF; {text.CodePointAt(invalid).ToDisplayString()} is outside that range.", + nameof(text)); + } + + // A code set change or a shift costs one extra symbol character, so two per ASCII character bounds the run. A character above 127 can also take a + // single FNC4, so three per character bounds text that has one. Extended mode costs at most four FNC4 characters for a run of at least three, which + // the third value per character covers. The start, the optional Function 1, the optional linkage flag, the check and the stop add five more. The text + // is at most MaximumLength characters, so the buffers are bounded: short text works inline, and longer text rents each buffer once. + int capacity = (text.Length * (text.ContainsAnyExceptInRange('\0', '\u007F') ? 3 : 2)) + 5; + int lookahead = text.Length + 1; + InlineArray128 valueStorage = default; + InlineArray256 lookaheadStorage = default; + PooledBuffer rentedValues = default; + PooledBuffer rentedLookahead = default; + Span values = capacity <= InlineLength * 2 + ? valueStorage + : (rentedValues = new PooledBuffer(capacity)).Span; + + Span lookaheadTables = lookahead <= InlineLength + ? lookaheadStorage + : (rentedLookahead = new PooledBuffer(lookahead * 4)).Span; + + try + { + int count = new Encodation(text, gs1, linkage, values, lookaheadTables[..(lookahead * 4)]).Run(); + + // The stop character is always the last value, so every other value takes the six elements and eleven modules of an ordinary character. + Span runs = symbol.BeginLinear(((count - 1) * ElementsPerCharacter) + StopElements, xDimension, out Span heights, out _); + WriteRuns(values[..count], runs); + int widthInModules = ((count - 1) * ModulesPerCharacter) + StopModules; + + float barHeight = options.ResolveBarHeight( + xDimension, + layout.MinimumBarHeight, + MathF.Max(layout.NominalBarHeight, widthInModules * layout.MinimumHeightFraction), + layout.MaximumBarHeight); + + heights.Fill(barHeight); + + int readableLength = readablePrefix.Length + readableBody.Length + readableSuffix.Length; + if (options.Font is not null && readableLength + above.Length > 0) + { + symbol.ReserveText(above.IsEmpty ? 1 : 2, readableLength + above.Length); + float textEdge = barHeight + layout.TextClearance; + symbol.AddText(readablePrefix, readableBody, readableSuffix, 0F, widthInModules, BarcodeTextSide.BelowBars, textEdge); + + // The line above the bars spans the symbol and the quiet zones of QuietZone, and its ink stays within them. + if (!above.IsEmpty) + { + symbol.AddTextWithinSpan(above, -QuietZone, widthInModules + QuietZone, BarcodeTextSide.AboveBars, -layout.AboveClearance); + } + } + + float horizontal = layout.HorizontalQuietZone; + symbol.EndLinear(horizontal, horizontal, layout.VerticalQuietZone, layout.VerticalQuietZone, 0F, 1F, false); + } + finally + { + rentedValues.Dispose(); + rentedLookahead.Dispose(); + } + } + + /// + /// Increments a saturating distance, so a run longer than a byte still compares as further away. + /// + /// The distance to increment. + /// The incremented distance, held at the maximum. + private static byte SaturatingIncrement(byte value) => value == byte.MaxValue ? value : (byte)(value + 1); + + /// + /// Adds to a saturating count, so a run longer than a byte still compares as long enough. + /// + /// The count to add to. + /// The amount to add. + /// The new count. + private static byte SaturatingAdd(byte value, int amount) => value + amount >= byte.MaxValue ? byte.MaxValue : (byte)(value + amount); + + /// + /// Writes the run lengths of symbol character values, starting with a bar. + /// + /// The symbol character values, including the start, check and stop characters. + /// The alternating bar and space run widths in modules to write. + private static void WriteRuns(ReadOnlySpan values, Span runs) + { + ReadOnlySpan patterns = Patterns; + int index = 0; + foreach (int value in values) + { + uint pattern = patterns[value]; + int elements = value == Stop ? StopElements : ElementsPerCharacter; + for (int shift = (elements - 1) * 4; shift >= 0; shift -= 4) + { + runs[index++] = (int)((pattern >> shift) & 0xF); + } + } + } + + /// + /// Computes the symbol check character. Section 5.4.7.5.1 weights the start character by one and each following character by its position, sums the + /// products and takes the remainder modulo 103. + /// + /// The symbol character values, starting with the start character. + /// The symbol check character value. + private static int ComputeCheckCharacter(ReadOnlySpan values) + { + long sum = values[0]; + for (int i = 1; i < values.Length; i++) + { + sum += (long)values[i] * i; + } + + return (int)(sum % CheckModulus); + } + + /// + /// Determines whether only code set A carries the character. Section 5.4.3.3.1 gives code set A the control characters, which code set B replaces with the + /// lower case letters. Note 3 of Annex E of ISO/IEC 15417 classifies a character above 127 by its value less 128, the character that FNC4 shifts. + /// + /// The character to classify. + /// when only code set A carries it. + private static bool OnlyInSetA(char character) => (character & 0x7F) < 32; + + /// + /// Determines whether only code set B carries the character. Section 5.4.3.3.2 gives code set B the lower case letters and the characters above them, which + /// code set A replaces with control codes. Note 3 of Annex E of ISO/IEC 15417 classifies a character above 127 by its value less 128, the character that + /// FNC4 shifts. + /// + /// The character to classify. + /// when only code set B carries it. + private static bool OnlyInSetB(char character) => (character & 0x7F) >= 96; + + /// + /// Returns the symbol character value of a character in the given code set. Code set A runs from the space at value zero through to the control characters, + /// and code set B from the space through to the delete character. A character above 127 takes the value of the character 128 below it, which FNC4 shifts + /// back up, as Annex F of ISO/IEC 15417 gives: C = (D - 128). + /// + /// The code set to encode in. + /// The character to encode. + /// The symbol character value. + private static int ValueIn(Code128CodeSet set, char character) + { + int value = character & 0x7F; + if (set == Code128CodeSet.A && value < 32) + { + return value + 64; + } + + return value - 32; + } + + /// + /// Returns the symbol character value of FNC4 in the given code set, Table 1 of ISO/IEC 15417. + /// + /// The code set, A or B. Code set C has no FNC4. + /// The symbol character value. + private static int FunctionFourIn(Code128CodeSet set) => set == Code128CodeSet.A ? FunctionFourInA : FunctionFourInB; + + /// + /// The state of one encodation: the text, the look-ahead the code set choices read, and the symbol character values written so far. + /// + private ref struct Encodation + { + /// + /// The text to encode. + /// + private readonly ReadOnlySpan text; + + /// + /// Whether this is a GS1 symbol, which encodes every as a Function 1 character. + /// + private readonly bool gs1; + + /// + /// The linkage flag the symbol carries before its check character. + /// + private readonly Code128Linkage linkage; + + /// + /// The buffer the symbol character values are written to. + /// + private readonly Span values; + + /// + /// The distance from each position to the next character only code set A carries, one entry per position and one past the end. + /// + private readonly Span nextOnlyInA; + + /// + /// The distance from each position to the next character only code set B carries, one entry per position and one past the end. + /// + private readonly Span nextOnlyInB; + + /// + /// The digits a code set C run carries from each position, one entry per position and one past the end. + /// + private readonly Span digitsFromEven; + + /// + /// The length of the run of characters above 127 from each position, one entry per position and one past the end. + /// + private readonly Span extendedRun; + + /// + /// The number of symbol character values written. + /// + private int written; + + /// + /// Whether two FNC4 characters have started extended mode, which adds 128 to every data character until two more end it. + /// + private bool extendedMode; + + /// + /// Initializes a new instance of the struct and measures the look-ahead. + /// + /// The text to encode. + /// Whether this is a GS1 symbol. + /// The linkage flag the symbol carries before its check character. + /// The buffer the symbol character values are written to, of at least twice the text length plus five, or three times the text + /// length plus five when the text has a character above 127. + /// Scratch for the four look-ahead tables, each one entry longer than the text. + public Encodation(ReadOnlySpan text, bool gs1, Code128Linkage linkage, Span values, Span lookahead) + { + int stride = text.Length + 1; + this.text = text; + this.gs1 = gs1; + this.linkage = linkage; + this.values = values; + this.nextOnlyInA = lookahead[..stride]; + this.nextOnlyInB = lookahead.Slice(stride, stride); + this.digitsFromEven = lookahead.Slice(stride * 2, stride); + this.extendedRun = lookahead.Slice(stride * 3, stride); + this.written = 0; + this.extendedMode = false; + this.MeasureLookahead(); + } + + /// + /// Encodes the text, then writes the check character and the stop character. + /// + /// The number of symbol character values written. + public int Run() + { + ReadOnlySpan text = this.text; + Code128CodeSet set = this.ChooseStartSet(); + if (this.gs1) + { + this.values[this.written++] = FunctionOne; + } + + int position = 0; + while (position < text.Length) + { + if (this.gs1 && text[position] == Gs1Data.Separator) + { + this.values[this.written++] = FunctionOne; + position++; + continue; + } + + // Extended mode covers exactly one run of characters above 127, so two FNC4 characters end it before the next character of 127 or below. That + // happens before any change to code set C, which has no FNC4. + if (this.extendedMode && text[position] <= '\u007F') + { + this.values[this.written++] = FunctionFourIn(set); + this.values[this.written++] = FunctionFourIn(set); + this.extendedMode = false; + continue; + } + + int digits = this.digitsFromEven[position]; + if (set != Code128CodeSet.C && digits >= 4) + { + if ((digits & 1) == 0) + { + this.values[this.written++] = LatchC; + set = Code128CodeSet.C; + continue; + } + + // An odd run starts one character late, so the current set takes the first digit and code set C takes the even remainder. + this.values[this.written++] = ValueIn(set, text[position++]); + if (this.digitsFromEven[position] >= 4) + { + this.values[this.written++] = LatchC; + set = Code128CodeSet.C; + continue; + } + } + + char character = text[position]; + bool extended = character > '\u007F'; + + // Note 3 of Annex E of ISO/IEC 15417: a long run of characters above 127 takes extended mode. Code set C has no FNC4, so the run waits for the + // change out of code set C below. A saturated run length is long enough whether or not it ends the data. + if (extended && !this.extendedMode && set != Code128CodeSet.C) + { + int run = this.extendedRun[position]; + if (run >= (position + run == text.Length ? ShortestFinalExtendedRun : ShortestExtendedRun)) + { + this.values[this.written++] = FunctionFourIn(set); + this.values[this.written++] = FunctionFourIn(set); + this.extendedMode = true; + continue; + } + } + + // Outside extended mode a single FNC4 adds 128 to the one data character after it. Clause 4.3.4.2 d) lets a Shift follow the FNC4, so the + // FNC4 takes its value from the code set in force before the Shift. + bool functionFour = extended && !this.extendedMode; + if (set == Code128CodeSet.B && OnlyInSetA(character)) + { + // A shift serves one character when the next character only one of the two sets carries belongs to the current set. + if (position < text.Length - 1 && this.nextOnlyInB[position + 1] < this.nextOnlyInA[position + 1]) + { + if (functionFour) + { + this.values[this.written++] = FunctionFourInB; + } + + this.values[this.written++] = Shift; + this.values[this.written++] = ValueIn(Code128CodeSet.A, character); + position++; + continue; + } + + this.values[this.written++] = LatchA; + set = Code128CodeSet.A; + continue; + } + + if (set == Code128CodeSet.A && OnlyInSetB(character)) + { + if (position < text.Length - 1 && this.nextOnlyInA[position + 1] < this.nextOnlyInB[position + 1]) + { + if (functionFour) + { + this.values[this.written++] = FunctionFourInA; + } + + this.values[this.written++] = Shift; + this.values[this.written++] = ValueIn(Code128CodeSet.B, character); + position++; + continue; + } + + this.values[this.written++] = LatchB; + set = Code128CodeSet.B; + continue; + } + + if (set == Code128CodeSet.C && digits < 2) + { + bool intoA = this.nextOnlyInA[position] < this.nextOnlyInB[position]; + this.values[this.written++] = intoA ? LatchA : LatchB; + set = intoA ? Code128CodeSet.A : Code128CodeSet.B; + continue; + } + + if (set == Code128CodeSet.C) + { + this.values[this.written++] = ((text[position] - '0') * 10) + (text[position + 1] - '0'); + position += 2; + continue; + } + + if (functionFour) + { + this.values[this.written++] = FunctionFourIn(set); + } + + this.values[this.written++] = ValueIn(set, character); + position++; + } + + // Section 7.4 of ISO/IEC 24723: a linked symbol ends its data with an extra code set character, which Table 8 chooses from the active code set. + if (this.linkage != Code128Linkage.None) + { + bool ccC = this.linkage == Code128Linkage.CcC; + this.values[this.written++] = set switch + { + Code128CodeSet.A => ccC ? LatchC : LatchB, + Code128CodeSet.B => ccC ? LatchA : LatchC, + _ => ccC ? LatchB : LatchA, + }; + } + + this.values[this.written] = ComputeCheckCharacter(this.values[..this.written]); + this.written++; + this.values[this.written++] = Stop; + return this.written; + } + + /// + /// Fills the look-ahead tables in one backward walk. The distance to the next character that only one of code sets A and B can carry decides which set + /// to start in and which to latch back to. Because the walk runs backwards, the tables must hold the distances. Everything else that the encoder needs + /// is one step ahead of the write. Both distances saturate at the byte maximum, which is further than any comparison can reach. + /// + private readonly void MeasureLookahead() + { + ReadOnlySpan text = this.text; + Span nextOnlyInA = this.nextOnlyInA; + Span nextOnlyInB = this.nextOnlyInB; + Span digitsFromEven = this.digitsFromEven; + Span extendedRun = this.extendedRun; + nextOnlyInA[text.Length] = byte.MaxValue; + nextOnlyInB[text.Length] = byte.MaxValue; + digitsFromEven[text.Length] = 0; + extendedRun[text.Length] = 0; + + // The same digit count one character out of step is read only at the position after, so the walk carries it. + byte digitsFromOdd = 0; + for (int i = text.Length - 1; i >= 0; i--) + { + // A separator encodes as a Function 1 character, which every code set carries, so it never forces a set of its own. + bool separator = this.gs1 && text[i] == Gs1Data.Separator; + nextOnlyInA[i] = !separator && OnlyInSetA(text[i]) ? (byte)0 : SaturatingIncrement(nextOnlyInA[i + 1]); + nextOnlyInB[i] = !separator && OnlyInSetB(text[i]) ? (byte)0 : SaturatingIncrement(nextOnlyInB[i + 1]); + extendedRun[i] = text[i] > '\u007F' ? SaturatingIncrement(extendedRun[i + 1]) : (byte)0; + + // The digits a code set C run carries from here, counted on the two parities code set C packs against. A separator carries two, because + // code set C encodes the Function 1 in the one symbol character that a digit pair takes, so a run of digits continues across it. + if (separator) + { + digitsFromEven[i] = SaturatingAdd(digitsFromEven[i + 1], 2); + digitsFromOdd = 0; + } + else if (char.IsAsciiDigit(text[i])) + { + digitsFromEven[i] = SaturatingAdd(digitsFromOdd, 1); + digitsFromOdd = SaturatingAdd(digitsFromEven[i + 1], 1); + } + else + { + digitsFromEven[i] = 0; + digitsFromOdd = 0; + } + } + } + + /// + /// Chooses the code set the symbol starts in and writes its start character. + /// + /// The code set the symbol starts in. + private Code128CodeSet ChooseStartSet() + { + // Step 1 of section 5.4.7.6 begins a GS1 symbol with start character C and a Function 1 character. An Application Identifier opens every such + // symbol with two or more digits, so the pair code set C packs pays for the switch out of it, and the symbol is never longer for it. + if (this.gs1) + { + this.values[this.written++] = StartC; + return Code128CodeSet.C; + } + + if (this.text.Length == 0) + { + this.values[this.written++] = StartB; + return Code128CodeSet.B; + } + + int digits = this.digitsFromEven[0]; + if ((this.text.Length == 2 && digits == 2) || digits >= 4) + { + this.values[this.written++] = StartC; + return Code128CodeSet.C; + } + + if (this.nextOnlyInA[0] < this.nextOnlyInB[0]) + { + this.values[this.written++] = StartA; + return Code128CodeSet.A; + } + + this.values[this.written++] = StartB; + return Code128CodeSet.B; + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code128/Code128Layout.cs b/src/ImageSharp.Drawing/Barcodes/Code128/Code128Layout.cs new file mode 100644 index 00000000..42323b24 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code128/Code128Layout.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The dimensions an application standard sets for a Code 128 symbol: the X dimension, the bar height, the quiet zones and the space between the bars and the +/// text. builds the symbol at them. +/// +internal readonly struct Code128Layout +{ + /// + /// Gets the X dimension in millimetres. + /// + public double XDimension { get; init; } + + /// + /// Gets the bar height in modules when the options set none. + /// + public float NominalBarHeight { get; init; } + + /// + /// Gets the smallest bar height as a fraction of the width of the bars when the options set none, or 0 for none. + /// + public float MinimumHeightFraction { get; init; } + + /// + /// Gets the smallest bar height in modules that the application standard permits, or zero where it gives none. The code clamps the bar height to it. + /// + public float MinimumBarHeight { get; init; } + + /// + /// Gets the largest bar height in modules that the application standard permits, or where it gives none. The code + /// clamps the bar height to it. + /// + public float MaximumBarHeight { get; init; } + + /// + /// Gets the space between the bars and the human readable interpretation in modules. + /// + public float TextClearance { get; init; } + + /// + /// Gets the space between the bars and the ink of the line above them in modules. + /// + public float AboveClearance { get; init; } + + /// + /// Gets the quiet zone before and after the bars in modules. It is at least . + /// + public float HorizontalQuietZone { get; init; } + + /// + /// Gets the clear zone above and below the bars in modules. + /// + public float VerticalQuietZone { get; init; } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code128/Code128Linkage.cs b/src/ImageSharp.Drawing/Barcodes/Code128/Code128Linkage.cs new file mode 100644 index 00000000..252a99b6 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code128/Code128Linkage.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The linkage flag of a GS1-128 symbol. Section 7.4 of ISO/IEC 24723 links a 2D component to a GS1-128 symbol with an extra code set character in the last +/// position before the check character, and Table 8 chooses the character from the code set active at the end of the data and the kind of 2D component. +/// +internal enum Code128Linkage : byte +{ + /// + /// No 2D component is linked to the symbol. + /// + None, + + /// + /// A CC-A or CC-B 2D component is linked to the symbol. Table 8: code set A takes Code B, code set B takes Code C, and code set C takes Code A. + /// + CcAOrCcB, + + /// + /// A CC-C 2D component is linked to the symbol. Table 8: code set A takes Code C, code set B takes Code A, and code set C takes Code B. + /// + CcC, +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code128/Code128Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Code128/Code128Symbology.cs new file mode 100644 index 00000000..03567e95 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code128/Code128Symbology.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Code 128 symbology. Section 5.4 of the GS1 General Specifications describes it, and ISO/IEC 15417 defines it in full. A symbol carries a start +/// character, the data, one check character and the stop character. Each character takes eleven modules, and the stop character takes thirteen. It carries +/// the characters U+0000 to U+00FF, ISO/IEC 8859-1. A character above U+007F takes an extra FNC4 character, or a run of them takes extended mode. +/// +public sealed class Code128Symbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Code128Symbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Code128Encoder.BuildSymbol(text, false, [], text, [], options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code128/Ean14Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Code128/Ean14Symbology.cs new file mode 100644 index 00000000..97e2e1a9 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code128/Ean14Symbology.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// EAN-14, a GS1-128 symbol that carries one element string: the Global Trade Item Number of GS1 Application Identifier (01). Section 3.3.2 of the GS1 General +/// Specifications gives that identifier a 14 digit GTIN. Table 7-6 gives the element string a total length of 16, and section 7.9 defines the check digit. +/// The symbol specification tables of section 5.12.3 give GS1-128 X dimensions from 0.170 to 1.016 millimetres, and an X dimension outside that range is +/// clamped to it. +/// +/// The input is the element string syntax, (01) and then the GTIN. Spaces are ignored. Give 13 digits, and this class calculates the check digit. Give +/// 14 digits, and this class compares the last digit with the calculated one. The interpretation prints the number as the caller grouped it. A +/// calculated check digit follows a space when the input groups its digits with spaces, which section 4.14 rule 2.a.v permits, and follows the digits +/// directly otherwise. +/// +/// +public sealed class Ean14Symbology : BarcodeSymbology +{ + /// + /// The length of the GTIN of Application Identifier (01), section 3.3.2, with its check digit. + /// + private const int DataLength = 14; + + /// + /// Initializes a new instance of the class. + /// + public Ean14Symbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + => Code128Encoder.BuildFixedElementString( + text, + "01", + DataLength, + Gs1Data.Gs1128MinimumXDimension, + Gs1Data.Gs1128MaximumXDimension, + options, + ref symbol); +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code128/Gs1128Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Code128/Gs1128Symbology.cs new file mode 100644 index 00000000..d6f3eb30 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code128/Gs1128Symbology.cs @@ -0,0 +1,81 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The GS1-128 symbology of section 5.4 of the GS1 General Specifications. A GS1-128 symbol is a Code 128 symbol with a Function 1 character after its start +/// character. It carries one or more element strings. Each element string is a GS1 Application Identifier and the data that belongs to it. +/// +/// The input is the element string syntax the standard prints: an Application Identifier in parentheses, then its data, repeated. For example, +/// (01)09521234543213(3103)000123. The symbol does not carry the parentheses. Section 4.14 rule 2c requires them in the human readable interpretation, +/// and rule 2b keeps the separators out of it. The human readable interpretation prints the element strings on one line, except an element string of +/// Application Identifier (8200), which does not print. +/// +/// +/// The symbol specification tables of section 5.12.3 give GS1-128 X dimensions from 0.170 to 1.016 millimetres, and an X dimension outside that range is +/// clamped to it. +/// +/// +public sealed class Gs1128Symbology : BarcodeSymbology +{ + /// + /// The largest number of data characters in one symbol. Section 5.4.4.3: "The number of encoded data characters SHALL NOT exceed 48." + /// + private const int MaximumDataCharacters = 48; + + /// + /// The largest length of a symbol in millimetres. Section 5.4.4.3: "The length, including Quiet Zones, cannot exceed 165.10 millimetres (6.500 inches)." + /// + internal const double MaximumLength = 165.10; + + /// + /// Initializes a new instance of the class. + /// + public Gs1128Symbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray64 bufferStorage = default; + int capacity = Gs1Data.PreparedCapacity(text, MaximumDataCharacters); + ValueStringBuilder encoded = capacity <= Gs1Data.InlineBufferLength ? new(bufferStorage) : new(capacity); + try + { + Gs1Data.Prepare(text, ref encoded); + Guard.MustBeLessThanOrEqualTo(encoded.Length, MaximumDataCharacters, nameof(text)); + + // The interpretation is never longer than the text, which PreparedCapacity bounds to 80 characters, so the inline buffer always holds it. + InlineArray128 readableStorage = default; + Span readable = readableStorage; + int readableLength = options.Font is null ? 0 : Gs1Data.WriteReadable(text, false, readable); + Code128Encoder.BuildSymbol( + encoded.AsSpan(), + true, + [], + readable[..readableLength], + [], + Gs1Data.Gs1128MinimumXDimension, + Gs1Data.Gs1128MaximumXDimension, + Code128Linkage.None, + options, + ref symbol); + + double length = (symbol.LeadingQuietZone + symbol.WidthInModules + symbol.TrailingQuietZone) * symbol.XDimension; + if (length > MaximumLength) + { + throw new ArgumentException( + $"A GS1-128 symbol with its quiet zones is at most {MaximumLength} mm long; this one is {length:0.##} mm at its X dimension.", + nameof(text)); + } + } + finally + { + encoded.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code128/HibcCode128Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Code128/HibcCode128Symbology.cs new file mode 100644 index 00000000..d90d3afb --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code128/HibcCode128Symbology.cs @@ -0,0 +1,76 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// HIBC Code 128, a Health Industry Bar Code. A Code 128 symbol carries it. The symbol carries the flag character, the data and the check character. The human +/// readable interpretation shows that same string between delimiters, with a space printed as an underscore and a zero as "Ø". When the options set no X +/// dimension, the X dimension is 0.010 inch, from section 5.1 of ANSI/HIBC 2.6. An X dimension under 0.0067 inch draws at 0.0067 inch, the minimum of the +/// same section. When the options set no bar height, the bars are 15% of the symbol width. +/// The text follows a data structure of ANSI/HIBC 2.6: primary data, secondary data that ends with its link character, or primary and secondary data joined +/// with "/", each with any supplemental data fields. +/// +public sealed class HibcCode128Symbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public HibcCode128Symbology() + : this(false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the check character at the end of the input is validated. When the whole input is + /// data. + public HibcCode128Symbology(bool validateCheckCharacter) => this.ValidateCheckCharacter = validateCheckCharacter; + + /// + /// Gets a value indicating whether the check character at the end of the input is validated. When the whole input is data. + /// + public bool ValidateCheckCharacter { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray64 bufferStorage = default; + int capacity = HibcData.PreparedCapacity(text, Code128Encoder.MaximumLength); + ValueStringBuilder encoded = capacity <= HibcData.InlineBufferLength ? new(bufferStorage) : new(capacity); + try + { + HibcData.Prepare(text, this.ValidateCheckCharacter, ref encoded); + + ReadOnlySpan data = encoded.AsSpan(); + + // The interpretation is the encoded data between two delimiters, so a buffer three characters longer holds it, and longer data rents it once. + InlineArray64 readableStorage = default; + int readableCapacity = HibcData.ReadableCapacity(data.Length); + using PooledBuffer rented = readableCapacity <= HibcData.InlineBufferLength ? default : new(readableCapacity); + Span readable = readableCapacity <= HibcData.InlineBufferLength ? ((Span)readableStorage)[..readableCapacity] : rented.Span; + int written = HibcData.WriteReadable(data, readable); + + // Section 5.1 of ANSI/HIBC 2.6: "The height of the bars should be at least 15% of the symbol length." That is a recommendation, so it sets the + // nominal bar height and does not clamp the bar height. + Code128Layout layout = new() + { + XDimension = options.ResolveXDimension(HibcData.MinimumLinearXDimension, HibcData.NominalLinearXDimension, double.PositiveInfinity), + MinimumHeightFraction = HibcData.MinimumBarHeightFraction, + MinimumBarHeight = 0F, + MaximumBarHeight = float.PositiveInfinity, + TextClearance = BarcodeTextPlacement.Clearance, + HorizontalQuietZone = Code128Encoder.QuietZone, + }; + + Code128Encoder.BuildSymbol(data, false, [], readable[..written], [], [], Code128Linkage.None, in layout, options, ref symbol); + } + finally + { + encoded.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code128/IntelligentMailPackageSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Code128/IntelligentMailPackageSymbology.cs new file mode 100644 index 00000000..0aeb01a8 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code128/IntelligentMailPackageSymbology.cs @@ -0,0 +1,334 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The United States Postal Service Intelligent Mail package barcode (IMpb), which USPS2000508 revision E defines and USPS Publication 199 applies. Section +/// 2.2.1 of USPS2000508: the barcode is a GS1-128 symbol, and "Only Subset C is permitted". The symbol carries an optional routing code, Application Identifier +/// (420) and a 5 or 9 digit destination ZIP Code, then the package identification code (PIC): a channel Application Identifier of 92, 93, 94 or 95, the fields +/// of that channel and a Mod 10 check digit. +/// +/// The input is the element string syntax, for example (420)12345(92)12391234567812345670, and must match one barcode construct of USPS2000508 Appendix +/// D or Publication 199 Appendix J, Table 1. The check digit is required. Section 2.4.1 suppresses the routing code from the human readable interpretation, and +/// Appendix D prints the PIC in groups of four digits with any remaining digits in a group at the end. +/// +/// +/// Section 2.4 makes three more elements part of the IMpb structure: the PIC below the bars, a service banner above them, and an identification bar above the +/// banner and below the PIC. The structure "shall include" all three, so the symbol needs a font. Section 2.4.1 and section 2.4.2 call for a "boldface*, sans +/// serif font". The caller supplies a sans serif font, and the text renders bold, with a synthetic weight when the font has no bold face. Section 2.2.2 limits +/// the X dimension to 0.013 to 0.021 inch, and section 2.2.4 requires a bar height of at least 0.75 inch. An X dimension outside that range draws at the +/// nearest limit. A bar height under that minimum draws at 0.75 inch. +/// +/// +public sealed class IntelligentMailPackageSymbology : BarcodeSymbology +{ + /// + /// The nominal X dimension in millimetres. Section 2.2.2 permits 0.013 to 0.021 inch, and "X-dimensions ranging from 0.015 to 0.017 inch inclusive are + /// preferred". The nominal value is the middle of the preferred range, 0.016 inch. + /// + private const double NominalXDimension = 0.016 * 25.4; + + /// + /// The smallest X dimension in millimetres. Section 2.2.2: "The mailer shall print the barcode with an X-dimension measuring between 0.013 inch (13.0 mils) + /// and 0.021 inch (21.0 mils)." + /// + private const double MinimumXDimension = 0.013 * 25.4; + + /// + /// The largest X dimension in millimetres, the upper limit of section 2.2.2. + /// + private const double MaximumXDimension = 0.021 * 25.4; + + /// + /// The clear zone above and below the bars in millimetres. Section 2.3.2: "A clear zone measuring at least 0.125 inch shall be maintained directly above + /// and below the barcode." + /// + private const double VerticalClearZone = 0.125 * 25.4; + + /// + /// The clear zone before and after the bars in millimetres. Section 4.10.1 of Publication 199: "A minimum left/right clear zone of 0.25 inch is + /// recommended." + /// + private const double HorizontalClearZone = 0.25 * 25.4; + + /// + /// The bar height in millimetres. Section 2.2.4: "The overall minimum height of the barcode shall measure at least 0.75 inch". + /// + private const double BarHeight = 0.75 * 25.4; + + /// + /// The space between the bars and the human readable interpretation in millimetres. Section 2.4.1 prints the interpretation "at least 0.125 inch, but not + /// more than 0.25 inch below the barcode". The space is the middle of that range, 0.1875 inch. + /// + private const double TextClearance = 0.1875 * 25.4; + + /// + /// The longest input that can match a construct: "(420)", a 9 digit ZIP Code and "(92)" with the 20 digits that follow it in a 22 digit PIC, 38 characters. + /// "(420)", a 5 digit ZIP Code and a 26 digit PIC, and a 30 digit PIC alone, are no longer. The bound keeps the prepared data in the inline buffer. + /// + private const int MaximumInputLength = 38; + + /// + /// The space between the bars and the ink of the banner in millimetres. Section 2.4.2 keeps "A clear zone of 0.125, but not more than 0.500 inch" between + /// the banner and the barcode, and section 4.9.1 of Publication 199 prints the banner "at a minimum 0.125 inch above the barcode, but not more than 0.25 + /// inch above the barcode". The space is the middle of the range both allow, 0.1875 inch. + /// + private const double BannerClearance = 0.1875 * 25.4; + + /// + /// The thickness of an identification bar in millimetres. Section 2.4.3 gives "a thickness within the range of 0.031 to 0.063 inch". The thickness is the + /// middle of that range, 0.047 inch. + /// + private const double IdentificationBarThickness = 0.047 * 25.4; + + /// + /// The space between an identification bar and the ink of its text line in millimetres: the "0.031" minimum clearance" of Figure 1. + /// + private const double IdentificationBarClearance = 0.031 * 25.4; + + /// + /// The smallest printed height of the characters in millimetres. Sections 2.4.1 and 2.4.2: "The printed height of the characters shall be between 0.090 to + /// 0.125 inch". + /// + private const double MinimumTextHeight = 0.090 * 25.4; + + /// + /// The largest printed height of the characters in millimetres, the upper limit of sections 2.4.1 and 2.4.2. + /// + private const double MaximumTextHeight = 0.125 * 25.4; + + /// + /// The Application Identifier of the destination ZIP Code routing code, section 2.2.1, which opens the prepared data when the symbol carries one. + /// + private const string RoutingIdentifier = "420"; + + /// + /// Initializes a new instance of the class. + /// + /// The service banner printed above the bars, for example USPS TRACKING #. The banner depends on the product. Publication 199 + /// section 4.5 and Appendix I give the banner for each service type code. + /// The banner is empty, white space, or has a lowercase letter. + public IntelligentMailPackageSymbology(string banner) + { + Guard.NotNullOrWhiteSpace(banner, nameof(banner)); + + // Section 2.4.2: "A service banner shall be printed in all uppercase letters". + foreach (char character in banner) + { + if (char.IsLower(character)) + { + throw new ArgumentException($"The service banner is printed in uppercase letters; got '{character}'.", nameof(banner)); + } + } + + this.Banner = banner; + } + + /// + /// Gets the service banner printed above the bars. + /// + public string Banner { get; } + + /// + /// Gets the barcode constructs as four bytes each: the channel Application Identifier, the length of the mailer ID (0 where the channel carries none), the + /// length of the destination ZIP Code (0 where the construct carries no routing code) and the length of the PIC with its check digit. The commercial + /// constructs C01 to C10 and the online constructs N01 to N10 are Publication 199 Appendix J, Table 1. The retail constructs A01, A02 and P02 are + /// USPS2000508 Appendix D, Tables 24 to 26. N01 and N04, N02 and N05, and N03 and N06 differ only in the split of the mailer ID and the serial number, so + /// the mailer ID length tells them apart. + /// + private static ReadOnlySpan Constructs => + [ + 92, 9, 9, 22, // C01 + 92, 9, 5, 26, // C02 + 92, 9, 5, 22, // C03 + 92, 9, 0, 22, // C04 + 93, 6, 9, 22, // C05 + 93, 6, 5, 26, // C06 + 93, 6, 5, 22, // C07 + 93, 6, 0, 22, // C08 + 93, 6, 0, 26, // C09 + 92, 9, 0, 26, // C10 + 94, 6, 5, 22, // N01 + 94, 6, 9, 22, // N02 + 94, 6, 0, 22, // N03 + 94, 9, 5, 22, // N04 + 94, 9, 9, 22, // N05 + 94, 9, 0, 22, // N06 + 94, 6, 5, 26, // N07 + 94, 9, 5, 26, // N08 + 94, 6, 0, 26, // N09 + 94, 9, 0, 30, // N10 + 95, 0, 9, 22, // A01 + 95, 0, 5, 22, // A02 + 95, 0, 0, 22, // P02 + ]; + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Guard.MustBeLessThanOrEqualTo(text.Length, MaximumInputLength, nameof(text)); + + // Section 2.4: "The IM Package Barcode structure shall include the elements defined in 2.4.1 through 2.4.3", and two of them are text. + if (options.Font is null) + { + throw new ArgumentException("The Intelligent Mail package barcode prints its PIC and its banner, so it needs a font.", nameof(options)); + } + + double xDimension = options.ResolveXDimension(MinimumXDimension, NominalXDimension, MaximumXDimension); + + InlineArray64 encodedStorage = default; + ValueStringBuilder encoded = new(encodedStorage); + try + { + Gs1Data.Prepare(text, ref encoded); + + // Gs1Data writes a separator after the variable length routing code, which section 2.2.1 requires: "Concatenated barcodes use a second Function 1 + // (FNC1) character immediately following the ZIP Code". Everything after it is the PIC. + ReadOnlySpan data = encoded.AsSpan(); + int zipLength = 0; + if (data.StartsWith(RoutingIdentifier)) + { + int separator = data.IndexOf(Gs1Data.Separator); + if (separator < 0) + { + throw new ArgumentException("The Intelligent Mail package barcode carries a PIC after the routing code.", nameof(text)); + } + + zipLength = separator - RoutingIdentifier.Length; + if (data[RoutingIdentifier.Length..separator].ContainsAnyExceptInRange('0', '9')) + { + throw new ArgumentException("The destination ZIP Code of the Intelligent Mail package barcode is digits.", nameof(text)); + } + + data = data[(separator + 1)..]; + } + + if (data.Length < 2 || data.ContainsAnyExceptInRange('0', '9')) + { + throw new ArgumentException("The Intelligent Mail package barcode carries an optional (420) routing code and one PIC of digits.", nameof(text)); + } + + int channel = ((data[0] - '0') * 10) + (data[1] - '0'); + int mailerIdLength = MailerIdLength(channel, data); + if (!IsConstruct(channel, mailerIdLength, zipLength, data.Length)) + { + throw new ArgumentException( + $"No Intelligent Mail package barcode construct has channel {channel}, a {zipLength}-digit ZIP Code and a {data.Length}-digit PIC.", + nameof(text)); + } + + // Publication 199 section 1.4: "A six-digit MID must begin with 0-8" and "A nine-digit MID must begin with 9". Channel 92 carries a nine digit MID + // and channel 93 a six digit MID, Table 5 of USPS2000508. Every construct of those channels has a PIC of at least 22 digits. + if ((channel == 92 && data[5] != '9') || (channel == 93 && data[5] == '9')) + { + throw new ArgumentException( + $"Channel {channel} carries a {mailerIdLength}-digit mailer ID, which must begin with {(channel == 92 ? "9" : "0 to 8")}.", + nameof(text)); + } + + // USPS2000508 Appendix E weights the PIC digits 3 and 1 from the rightmost digit before the check digit, the check digit calculation of the GS1 + // General Specifications. + int check = Gs1Data.ComputeCheckDigit(data[..^1]); + if (data[^1] - '0' != check) + { + throw new ArgumentException($"Incorrect Intelligent Mail package barcode check digit: expected {check}, got {data[^1]}.", nameof(text)); + } + + // The longest PIC is 30 digits, which prints as seven groups of four and one of two with seven spaces between them. + InlineArray64 readableStorage = default; + Span readable = readableStorage; + int readableLength = 0; + + // Each step copies one group of four digits and the space after it. The last group takes the remaining digits and no space: two, because every PIC + // is 22, 26 or 30 digits. + int start = 0; + for (; start + 4 < data.Length; start += 4) + { + data.Slice(start, 4).CopyTo(readable[readableLength..]); + readable[readableLength + 4] = ' '; + readableLength += 5; + } + + data[start..].CopyTo(readable[readableLength..]); + readableLength += data.Length - start; + + // Section 4.10.1 of Publication 199: "A minimum left/right clear zone of 0.25 inch is recommended", over the "10 times the X dimension" it + // requires. Section 2.2.4 gives the bar height a minimum and no maximum. + Code128Layout layout = new() + { + XDimension = xDimension, + NominalBarHeight = (float)(BarHeight / xDimension), + MinimumBarHeight = (float)(BarHeight / xDimension), + MaximumBarHeight = float.PositiveInfinity, + TextClearance = (float)(TextClearance / xDimension), + AboveClearance = (float)(BannerClearance / xDimension), + HorizontalQuietZone = MathF.Max(Code128Encoder.QuietZone, (float)(HorizontalClearZone / xDimension)), + VerticalQuietZone = (float)(VerticalClearZone / xDimension), + }; + + Code128Encoder.BuildSymbol( + encoded.AsSpan(), + true, + [], + readable[..readableLength], + [], + this.Banner, + Code128Linkage.None, + in layout, + options, + ref symbol); + + symbol.AddIdentificationBars((float)(IdentificationBarThickness / xDimension), (float)(IdentificationBarClearance / xDimension)); + symbol.SetTextHeight((float)(MinimumTextHeight / xDimension), (float)(MaximumTextHeight / xDimension)); + + // Sections 2.4.1 and 2.4.2: the PIC and the banner "shall be printed in a boldface* sans serif font". + symbol.SetTextWeight(FontWeight.Bold); + } + finally + { + encoded.Dispose(); + } + } + + /// + /// Gets the length of the mailer ID of a PIC. Table 5 of USPS2000508 gives channel 92 a nine digit and channel 93 a six digit mailer ID. Channel 94 carries + /// either after its two digit source identifier, and section 1.4 of Publication 199 tells them apart by the first digit. Channel 95 carries none. + /// + /// The channel Application Identifier. + /// The PIC digits. + /// The length of the mailer ID, or zero. + private static int MailerIdLength(int channel, ReadOnlySpan pic) + => channel switch + { + 92 => 9, + 93 => 6, + 94 => pic.Length > 7 && pic[7] == '9' ? 9 : 6, + _ => 0, + }; + + /// + /// Gets a value indicating whether the fields match one barcode construct. + /// + /// The channel Application Identifier. + /// The length of the mailer ID, or zero. + /// The length of the destination ZIP Code, or zero. + /// The length of the PIC with its check digit. + /// when a construct matches; otherwise, . + private static bool IsConstruct(int channel, int mailerIdLength, int zipLength, int picLength) + { + ReadOnlySpan constructs = Constructs; + for (int i = 0; i < constructs.Length; i += 4) + { + if (constructs[i] == channel && constructs[i + 1] == mailerIdLength && constructs[i + 2] == zipLength && constructs[i + 3] == picLength) + { + return true; + } + } + + return false; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code128/Sscc18Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Code128/Sscc18Symbology.cs new file mode 100644 index 00000000..763bfcf4 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code128/Sscc18Symbology.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// SSCC-18, a GS1-128 symbol that carries one element string: the Serial Shipping Container Code of GS1 Application Identifier (00). Section 3.3.1 of the GS1 +/// General Specifications gives that identifier an 18 digit SSCC. Table 7-6 gives the element string a total length of 20, and section 7.9 defines the check +/// digit. Table 5-51, symbol specification table 5 for logistic units, gives an X dimension of 0.495 to 0.940 millimetres, and 0.250 millimetre when the +/// logistic unit is too small for the minimum. An X dimension outside 0.250 to 0.940 millimetres draws at the nearest limit. +/// +/// The input is the element string syntax, (00) and then the SSCC. Spaces are ignored. Give 17 digits, and this class calculates the check digit. Give +/// 18 digits, and this class compares the last digit with the calculated one. The interpretation prints the number as the caller grouped it. A +/// calculated check digit follows a space when the input groups its digits with spaces, which section 4.14 rule 2.a.v permits, and follows the digits +/// directly otherwise. +/// +/// +public sealed class Sscc18Symbology : BarcodeSymbology +{ + /// + /// The length of the SSCC of Application Identifier (00), section 3.3.1, with its check digit. + /// + private const int DataLength = 18; + + /// + /// Initializes a new instance of the class. + /// + public Sscc18Symbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + => Code128Encoder.BuildFixedElementString(text, "00", DataLength, Gs1Data.SsccMinimumXDimension, Gs1Data.SsccMaximumXDimension, options, ref symbol); +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code16K/Code16KEncoder.cs b/src/ImageSharp.Drawing/Barcodes/Code16K/Code16KEncoder.cs new file mode 100644 index 00000000..48771ade --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code16K/Code16KEncoder.cs @@ -0,0 +1,619 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodation for Code 16K, AIM USS-16K. Section 2.1: a symbol is 2 to 16 rows, and each row is a start character, a one module guard bar, five symbol +/// characters and a stop character, 70 modules in all. The symbol characters run from the first row down. The first is the starting symbol character +/// and the last two are the check characters. Section 2.2: a symbol character is the eleven module Code 128 pattern of Table 2, three spaces alternating with +/// three bars, starting with a space. +/// +internal static class Code16KEncoder +{ + /// + /// The quiet zone in modules before the start character: the "ten modules adjacent to the start pattern" of section 3.4. + /// + private const int LeadingQuietZone = 10; + + /// + /// The quiet zone in modules after the stop character: the "one module adjacent to the stop pattern" of section 3.4. + /// + private const int TrailingQuietZone = 1; + + /// + /// The width of a row in modules: "the 70X bars and spaces" of section 3.6.1. + /// + private const int RowWidth = 70; + + /// + /// The smallest number of rows, section 2.1. + /// + private const int MinimumRows = 2; + + /// + /// The largest number of rows, section 2.1. + /// + private const int MaximumRows = 16; + + /// + /// The number of symbol characters in a row, section 2.1. + /// + private const int CharactersPerRow = 5; + + /// + /// The bar height of a row in modules, nominal and smallest. Section 4.5 d) of EN 12323: "row height (Y dimension): minimum 8X". The section gives no + /// largest row height. + /// + private const float NominalBarHeight = 8F; + + /// + /// The height in modules of the separator bars: the "1-module high separator bar" of section 2.1. + /// + private const float SeparatorHeight = 1F; + + /// + /// The modulus of the check characters, section 2.6. + /// + private const int Modulus = 107; + + /// + /// The pad character, section 2.3.5: "When it appears at the end of the symbol characters, just before the check characters, it represents no data." + /// + private const int Pad = 103; + + /// + /// The double shift of code sets A and B, Double Shift B in code set A and Double Shift A in code set B, Table 2. Code sets A and B take the values 98 to + /// 101 of Code 128: Shift, Code C, Code B and Code A. + /// + private const int DoubleShift = 104; + + /// + /// Double Shift C in code sets A and B, Table 2. + /// + private const int DoubleShiftC = 105; + + /// + /// Triple Shift C in code sets A and B, Table 2. + /// + private const int TripleShiftC = 106; + + /// + /// Shift B in code set C, Table 2: the value of in code sets A and B. + /// + private const int CodeSetCShiftB = 104; + + /// + /// Double Shift B in code set C, Table 2: the value of in code sets A and B. + /// + private const int CodeSetCDoubleShiftB = 105; + + /// + /// Triple Shift B in code set C, Table 2: the value of in code sets A and B. + /// + private const int CodeSetCTripleShiftB = 106; + + /// + /// The pattern of the value 106, the one symbol character Table 2 defines beyond the Code 128 set, packed as packs its patterns. + /// + private const uint TripleShiftPattern = 0x211133; + + /// + /// The symbol characters a symbol carries beyond its data: the starting symbol character and the two check characters. + /// + private const int Overhead = 3; + + /// + /// The longest text a symbol carries: two digits per symbol character in code set C over the data symbol characters of sixteen rows. + /// + private const int MaximumText = ((MaximumRows * CharactersPerRow) - Overhead) * 2; + + /// + /// The symbol character value of Shift, which changes code set A to B, or B to A, for one character. + /// + private const int Shift = 98; + + /// + /// The symbol character value of Code C, which changes code set A or B to code set C. + /// + private const int LatchC = 99; + + /// + /// The symbol character value of Code B, which changes code set A or C to code set B. + /// + private const int LatchB = 100; + + /// + /// The symbol character value of Code A, which changes code set B or C to code set A. + /// + private const int LatchA = 101; + + /// + /// The code sets of the Code 128 symbol characters. + /// + private enum CodeSet + { + /// + /// Code set A: ASCII 32 to 95 as 0 to 63 and the control characters 0 to 31 as 64 to 95. + /// + A, + + /// + /// Code set B: ASCII 32 to 127 as 0 to 95. + /// + B, + + /// + /// Code set C: the digit pairs 00 to 99 as 0 to 99. + /// + C, + } + + /// + /// The starting modes of Table 3 that the encoder uses, with their mode values. + /// + private enum StartingMode + { + /// + /// The symbol starts in code set A. + /// + CodeSetA = 0, + + /// + /// The symbol starts in code set B. + /// + CodeSetB = 1, + + /// + /// The symbol starts in code set C. + /// + CodeSetC = 2, + + /// + /// The symbol starts in code set C with an implied Shift B, so the first character is in code set B. + /// + CodeSetCShiftB = 5, + + /// + /// The symbol starts in code set C with an implied Double Shift B, so the first two characters are in code set B. + /// + CodeSetCDoubleShiftB = 6, + } + + /// + /// The code set that carries the first character, at a position or after it, that only one of code sets A and B carries. + /// + private enum FirstExclusive : byte + { + /// + /// No character from the position on is carried by only one of code sets A and B. + /// + None, + + /// + /// A control character, which only code set A carries, comes first. + /// + SetA, + + /// + /// A lower case character, which only code set B carries, comes first. + /// + SetB, + } + + /// + /// Gets the element widths of the symbol characters 0 to 105, the Code 128 patterns of Table 2, packed four bits per element, the first element in the + /// highest bits: six elements each, three spaces and three bars of one to four modules. + /// + private static ReadOnlySpan Patterns => + [ + 0x212222, 0x222122, 0x222221, 0x121223, 0x121322, 0x131222, 0x122213, 0x122312, + 0x132212, 0x221213, 0x221312, 0x231212, 0x112232, 0x122132, 0x122231, 0x113222, + 0x123122, 0x123221, 0x223211, 0x221132, 0x221231, 0x213212, 0x223112, 0x312131, + 0x311222, 0x321122, 0x321221, 0x312212, 0x322112, 0x322211, 0x212123, 0x212321, + 0x232121, 0x111323, 0x131123, 0x131321, 0x112313, 0x132113, 0x132311, 0x211313, + 0x231113, 0x231311, 0x112133, 0x112331, 0x132131, 0x113123, 0x113321, 0x133121, + 0x313121, 0x211331, 0x231131, 0x213113, 0x213311, 0x213131, 0x311123, 0x311321, + 0x331121, 0x312113, 0x312311, 0x332111, 0x314111, 0x221411, 0x431111, 0x111224, + 0x111422, 0x121124, 0x121421, 0x141122, 0x141221, 0x112214, 0x112412, 0x122114, + 0x122411, 0x142112, 0x142211, 0x241211, 0x221114, 0x413111, 0x241112, 0x134111, + 0x111242, 0x121142, 0x121241, 0x114212, 0x124112, 0x124211, 0x411212, 0x421112, + 0x421211, 0x212141, 0x214121, 0x412121, 0x111143, 0x111341, 0x131141, 0x114113, + 0x114311, 0x411113, 0x411311, 0x113141, 0x114131, 0x311141, 0x411131, 0x211412, + 0x211214, 0x211232, + ]; + + /// + /// Gets the eight start patterns of Table 4, bar, space, bar and space, one nibble each. Table 5 gives the stop patterns the same widths with the bars and + /// spaces exchanged. + /// + private static ReadOnlySpan StartPatterns => [0x3211, 0x2221, 0x2122, 0x1411, 0x1132, 0x1231, 0x1114, 0x3112]; + + /// + /// Gets the stop pattern value of every row, Table 6. + /// + private static ReadOnlySpan RowStopValues => [0, 1, 2, 3, 4, 5, 6, 7, 4, 5, 6, 7, 0, 1, 2, 3]; + + /// + /// Encodes text and builds the symbol. + /// + /// The text to encode. Every character must be ASCII 0 to 127. + /// The human readable interpretation. + /// The options that control layout choices. + /// The symbol to write. + /// The text carries a character the symbology cannot encode, or more data than sixteen rows hold. + public static void BuildSymbol(ReadOnlySpan text, ReadOnlySpan readable, BarcodeOptions options, ref BarcodeSymbol symbol) + { + if (text.Length > MaximumText) + { + throw new ArgumentException( + $"Code 16K carries at most {(MaximumRows * CharactersPerRow) - Overhead} symbol characters in sixteen rows; the text needs more.", + nameof(text)); + } + + int invalid = text.IndexOfAnyExceptInRange('\0', '\u007F'); + if (invalid >= 0) + { + throw new ArgumentException($"Code 16K encodes ASCII 0 to 127; {text.CodePointAt(invalid).ToDisplayString()} is outside that range.", nameof(text)); + } + + // The lookahead takes one entry for every position and one past the end, which the text bound keeps inline. The encoder stops when the values pass the + // capacity of the largest symbol, so the values stay inside its sixteen rows and the four values of one more step. + InlineArray256 lookaheadStorage = default; + Span lookahead = ((Span)lookaheadStorage)[..(text.Length + 1)]; + InlineArray128 valueStorage = default; + int total = Encode(text, lookahead, valueStorage); + WriteRows(((Span)valueStorage)[..total], readable, options, ref symbol); + } + + /// + /// Encodes text into the symbol character values of the whole symbol: the starting symbol character, the data with its code and shift characters, the pad + /// characters and the two check characters, five per row. Appendix G chooses the starting mode and the code and shift characters. + /// + /// The text to encode, ASCII 0 to 127 and at most characters. + /// The scratch for the lookahead, one entry for every position and one past the end. + /// The buffer that receives the symbol character values. + /// The number of symbol character values, five per row. + /// The text holds more data than sixteen rows hold. + private static int Encode(ReadOnlySpan text, Span lookahead, Span values) + { + int capacity = (MaximumRows * CharactersPerRow) - Overhead; + int n = text.Length; + + // The rules of Appendix G depend on the digit run from a position and on which of a control character or a lower case character comes first. The + // lookahead holds both for every position, read from the end so every entry builds on the one after it. A digit run fits a byte with the text bounded + // above. + lookahead[n] = default; + for (int p = n - 1; p >= 0; p--) + { + char c = text[p]; + Lookahead next = lookahead[p + 1]; + lookahead[p] = new Lookahead + { + DigitRun = (byte)(char.IsAsciiDigit(c) ? next.DigitRun + 1 : 0), + First = OnlyIn(CodeSet.A, c) ? FirstExclusive.SetA : OnlyIn(CodeSet.B, c) ? FirstExclusive.SetB : next.First, + }; + } + + // The data starts at value 1. Value 0 is the starting symbol character, which needs the row count, so it is written after the data. + int count = 1; + int i = 0; + CodeSet set; + StartingMode mode; + + // Appendix G rule 1: the starting mode, Table 3. The modes with an implied Shift B or Double Shift B start in code set C, which carries the first one + // or two characters in code set B without spending a symbol character on the shift. + int firstRun = lookahead[0].DigitRun; + if (IsEvenRun(firstRun)) + { + mode = StartingMode.CodeSetC; + set = CodeSet.C; + } + else if (firstRun >= 3 || (n >= 1 && In(CodeSet.B, text[0]) && IsEvenRun(lookahead[1].DigitRun))) + { + mode = StartingMode.CodeSetCShiftB; + set = CodeSet.C; + values[count++] = ValueIn(CodeSet.B, text[i++]); + } + else if ((n >= 1 && In(CodeSet.B, text[0]) && lookahead[1].DigitRun >= 3) + || (n >= 2 && In(CodeSet.B, text[0]) && In(CodeSet.B, text[1]) && IsEvenRun(lookahead[2].DigitRun))) + { + mode = StartingMode.CodeSetCDoubleShiftB; + set = CodeSet.C; + values[count++] = ValueIn(CodeSet.B, text[i++]); + values[count++] = ValueIn(CodeSet.B, text[i++]); + } + else if (lookahead[0].First == FirstExclusive.SetA) + { + mode = StartingMode.CodeSetA; + set = CodeSet.A; + } + else + { + mode = StartingMode.CodeSetB; + set = CodeSet.B; + } + + // Rules 3, 4 and 5: the next symbol character from the data that remains, in the current code set. One step writes at most four values, so a check at + // each step keeps the values inside the symbol and the four that the last step can add. + while (i < n) + { + if (count - 1 > capacity) + { + throw new ArgumentException($"Code 16K carries at most {capacity} symbol characters in sixteen rows; the text needs more.", nameof(text)); + } + + char c = text[i]; + int run = lookahead[i].DigitRun; + if (set == CodeSet.C) + { + if (run >= 2) + { + count = WritePairs(text, i, 1, values, count); + i += 2; + } + else if (i + 1 < n && In(CodeSet.B, c) && IsEvenRun(lookahead[i + 1].DigitRun)) + { + values[count++] = CodeSetCShiftB; + values[count++] = ValueIn(CodeSet.B, c); + i++; + } + else if ((i + 1 < n && In(CodeSet.B, c) && lookahead[i + 1].DigitRun >= 3) + || (i + 2 < n && In(CodeSet.B, c) && In(CodeSet.B, text[i + 1]) && IsEvenRun(lookahead[i + 2].DigitRun))) + { + values[count++] = CodeSetCDoubleShiftB; + values[count++] = ValueIn(CodeSet.B, c); + values[count++] = ValueIn(CodeSet.B, text[i + 1]); + i += 2; + } + else if (i + 3 < n + && In(CodeSet.B, c) + && In(CodeSet.B, text[i + 1]) + && (lookahead[i + 2].DigitRun >= 3 || (In(CodeSet.B, text[i + 2]) && IsEvenRun(lookahead[i + 3].DigitRun)))) + { + values[count++] = CodeSetCTripleShiftB; + values[count++] = ValueIn(CodeSet.B, c); + values[count++] = ValueIn(CodeSet.B, text[i + 1]); + values[count++] = ValueIn(CodeSet.B, text[i + 2]); + i += 3; + } + else if (lookahead[i].First == FirstExclusive.SetA) + { + values[count++] = LatchA; + set = CodeSet.A; + } + else + { + values[count++] = LatchB; + set = CodeSet.B; + } + + continue; + } + + // Code sets A and B mirror each other: each shifts or latches to the other for the characters only the other carries. A shift pays off when the + // characters after it return to a character only the current set carries first. + CodeSet other = set == CodeSet.A ? CodeSet.B : CodeSet.A; + FirstExclusive ownFirst = set == CodeSet.A ? FirstExclusive.SetA : FirstExclusive.SetB; + if (OnlyIn(other, c) && lookahead[i + 1].First == ownFirst) + { + values[count++] = Shift; + values[count++] = ValueIn(other, c); + i++; + } + else if (i + 1 < n && OnlyIn(other, c) && OnlyIn(other, text[i + 1]) && lookahead[i + 2].First == ownFirst) + { + values[count++] = DoubleShift; + values[count++] = ValueIn(other, c); + values[count++] = ValueIn(other, text[i + 1]); + i += 2; + } + else if (OnlyIn(other, c)) + { + values[count++] = other == CodeSet.A ? LatchA : LatchB; + set = other; + } + else if (run == 4 && i + 4 < n && In(set, text[i + 4])) + { + values[count++] = DoubleShiftC; + count = WritePairs(text, i, 2, values, count); + i += 4; + } + else if (run == 6 && i + 6 < n && In(set, text[i + 6])) + { + values[count++] = TripleShiftC; + count = WritePairs(text, i, 3, values, count); + i += 6; + } + else if (run >= 4 && (run & 1) == 0) + { + values[count++] = LatchC; + set = CodeSet.C; + } + else + { + values[count++] = ValueIn(set, c); + i++; + } + } + + int dataCount = count - 1; + if (dataCount > capacity) + { + throw new ArgumentException($"Code 16K carries at most {capacity} symbol characters in sixteen rows; the text needs {dataCount}.", nameof(text)); + } + + // Section 2.1 gives the smallest symbol that holds the data, and section 2.3.5 pads the last row. + int rows = MinimumRows; + while ((rows * CharactersPerRow) - Overhead < dataCount) + { + rows++; + } + + int total = rows * CharactersPerRow; + values[count..(total - 2)].Fill(Pad); + + // Section 2.3.1: s = 7(r - 2) + m. + values[0] = (7 * (rows - MinimumRows)) + (int)mode; + + // Section 2.6: C1 weights the symbol characters from two upward, C2 from one upward and takes C1 as its last term. + int c1 = 0; + int c2 = 0; + for (int j = 0; j < total - 2; j++) + { + c1 += (j + 2) * values[j]; + c2 += (j + 1) * values[j]; + } + + c1 %= Modulus; + c2 = (c2 + ((total - 1) * c1)) % Modulus; + values[total - 2] = c1; + values[total - 1] = c2; + return total; + } + + /// + /// Writes the symbol from its symbol character values: one row of bars per five values, one module separator bars between the rows, and the top and bottom + /// separator bars of section 2.1. + /// + /// The symbol character values, five per row. + /// The human readable interpretation. + /// The options that control layout choices. + /// The symbol to write. + private static void WriteRows(ReadOnlySpan values, ReadOnlySpan readable, BarcodeOptions options, ref BarcodeSymbol symbol) + { + int rowCount = values.Length / CharactersPerRow; + + // Section 4.5 a) of EN 12323: the X dimension "should be defined by the application specification", so the symbology gives no limit. + double xDimension = options.ResolveXDimension(0, BarcodeSymbology.DefaultXDimension, double.PositiveInfinity); + float barHeight = options.ResolveBarHeight(xDimension, NominalBarHeight, NominalBarHeight, float.PositiveInfinity); + + // Section 2.1: start, guard bar, five characters and stop, 4 + 1 + 30 + 4 elements, on a bar at both ends. A separator between two rows is one bar + // across the row width. + const int rowRuns = 4 + 1 + (CharactersPerRow * 6) + 4; + int separators = rowCount - 1; + symbol.BeginStacked(rowCount + separators, (rowCount * rowRuns) + separators, (rowCount * ((rowRuns + 1) >> 1)) + separators, xDimension); + for (int row = 0; row < rowCount; row++) + { + // Table 2 starts every character on a space, which the guard bar leads into. + Span runs = symbol.AddRow(rowRuns, out Span heights, out _); + heights.Fill(barHeight); + + // Table 6 starts the rows with the start patterns 0 to 7 in turn. + int index = 0; + AppendPattern(StartPatterns[row & 7], 4, runs, ref index); + runs[index++] = 1; + foreach (int value in values.Slice(row * CharactersPerRow, CharactersPerRow)) + { + uint pattern = value == TripleShiftC ? TripleShiftPattern : Patterns[value]; + AppendPattern(pattern, 6, runs, ref index); + } + + AppendPattern(StartPatterns[RowStopValues[row]], 4, runs, ref index); + + if (row < rowCount - 1) + { + Span separator = symbol.AddRow(1, out Span separatorHeight, out _); + separator[0] = RowWidth; + separatorHeight[0] = SeparatorHeight; + } + } + + // Appendix D: the human readable interpretation "may accompany the symbol. It should not interfere with the symbol itself nor the quiet zones." + float heightInModules = (rowCount * barHeight) + ((rowCount + 1) * SeparatorHeight); + if (options.Font is not null && readable.Length > 0) + { + symbol.ReserveText(1, readable.Length); + symbol.AddText(readable, 0F, RowWidth, BarcodeTextSide.BelowBars, heightInModules + BarcodeTextPlacement.Clearance); + } + + symbol.EndStacked(SeparatorHeight, LeadingQuietZone, TrailingQuietZone); + } + + /// + /// Returns the symbol character value of a character in code set A or B: code set A runs from the space at value 0 through to the control characters, and + /// code set B from the space through to the delete character. + /// + /// The code set, A or B. + /// The character. + /// The symbol character value. + private static int ValueIn(CodeSet set, char character) + => set == CodeSet.A && character < 32 ? character + 64 : character - 32; + + /// + /// Writes digit pairs as code set C values, each pair the value 0 to 99 it reads as. + /// + /// The text being encoded. + /// The index of the first digit. + /// The number of pairs to write. + /// The buffer the symbol character values are written to. + /// The number of values written so far. + /// The number of values written after the pairs. + private static int WritePairs(ReadOnlySpan text, int start, int pairs, Span values, int count) + { + for (int p = 0; p < pairs; p++) + { + int at = start + (p * 2); + values[count++] = ((text[at] - '0') * 10) + (text[at + 1] - '0'); + } + + return count; + } + + /// + /// Writes the element widths of a pattern packed as packs them. + /// + /// The pattern, four bits per element, the first element in the highest bits. + /// The number of elements. + /// The run widths in modules to write. + /// The position in , advanced past the elements. + private static void AppendPattern(uint pattern, int elements, Span runs, ref int index) + { + for (int shift = (elements - 1) * 4; shift >= 0; shift -= 4) + { + runs[index++] = (int)((pattern >> shift) & 0xF); + } + } + + /// + /// Returns whether only one code set of A and B carries a character: the control characters 0 to 31 for code set A, and the lower case letters and the + /// characters above them, 96 to 127, for code set B. + /// + /// The code set, A or B. + /// The character. + /// when only that code set carries it. + private static bool OnlyIn(CodeSet set, char character) => set == CodeSet.A ? character < 32 : character >= 96; + + /// + /// Returns whether a code set of A and B carries a character: ASCII 0 to 95 for code set A, and ASCII 32 to 127 for code set B. + /// + /// The code set, A or B. + /// The character. + /// when the code set carries it. + private static bool In(CodeSet set, char character) => set == CodeSet.A ? character < 96 : character >= 32; + + /// + /// Returns whether a digit run is an even run of at least two digits, which code set C carries as whole pairs. + /// + /// The length of the digit run. + /// when the run is even and at least two long. + private static bool IsEvenRun(int run) => run >= 2 && (run & 1) == 0; + + /// + /// What the encodation rules read ahead from one position of the text. + /// + private struct Lookahead + { + /// + /// The length of the run of digits that starts at the position. + /// + public byte DigitRun; + + /// + /// The code set of the first character, at the position or after it, that only one of code sets A and B carries. + /// + public FirstExclusive First; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code16K/Code16KSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Code16K/Code16KSymbology.cs new file mode 100644 index 00000000..2458d2ac --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code16K/Code16KSymbology.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Code 16K symbology, AIM USS-16K. A symbol is 2 to 16 rows of five Code 128 symbol characters, each row between its own start and stop characters, and +/// carries the full ASCII character set with two modulo 107 check characters. +/// +public sealed class Code16KSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Code16KSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + => Code16KEncoder.BuildSymbol(text, text, options, ref symbol); +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code2Of5/Code2Of5Encoder.cs b/src/ImageSharp.Drawing/Barcodes/Code2Of5/Code2Of5Encoder.cs new file mode 100644 index 00000000..c30cd5b2 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code2Of5/Code2Of5Encoder.cs @@ -0,0 +1,256 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Shared encodation for the plain 2 of 5 symbologies: Industrial, IATA, Matrix, COOP and Datalogic. No published standard defines them. Every digit has the +/// weights 1, 2, 4 and 7 and a parity element, and two of its five elements are wide. +/// +/// Industrial and IATA encode a digit in five bars with a narrow space after each bar, so a digit is ten elements. Matrix, COOP and Datalogic encode a digit in +/// three bars and two spaces with a narrow space after them, so a digit is six elements. The check digit is optional in every variant and is the modulo 10 +/// calculation of Appendix C of AIM USS-I 2/5. No document gives a wide element width, a quiet zone or a bar height for these symbologies. The values are those +/// of Interleaved 2 of 5. +/// +/// +internal static class Code2Of5Encoder +{ + /// + /// The largest number of digits a symbol carries. None of the symbologies fixes a maximum of its own. + /// + private const int MaximumLength = 500; + + /// + /// The quiet zone in modules on each side, or when that is wider, as section 3.2 of AIM USS-I 2/5 gives it for + /// Interleaved 2 of 5. + /// + private const int QuietZone = 10; + + /// + /// The smallest quiet zone in millimetres, the 0.10 inch of section 3.2 of AIM USS-I 2/5. + /// + private const double MinimumQuietZoneMillimetres = 2.54; + + /// + /// The number of digits a symbol holds inline. Data this long covers the labels the symbologies are used for, and longer data rents its buffer once. + /// + private const int InlineBufferLength = 64; + + /// + /// The width of a wide element in modules. + /// + private const int WideElement = 3; + + /// + /// The number of elements in a digit pattern: the weights 1, 2, 4 and 7 and the parity element. + /// + private const int PatternElements = 5; + + /// + /// The bar height as a fraction of the symbol length, quiet zones included, when the caller sets no height: 15 per cent, and at least + /// . + /// + private const float NominalBarHeightFraction = 0.15F; + + /// + /// The smallest bar height in millimetres when the caller sets none. + /// + private const float MinimumBarHeightMillimetres = 6.35F; + + /// + /// The number of elements of one digit of Industrial and IATA 2 of 5: five bars, each with a narrow space after it. + /// + private const int BarElementsPerDigit = 10; + + /// + /// The number of elements of one digit of Matrix, COOP and Datalogic 2 of 5: three bars and two spaces, with a narrow space after them. + /// + private const int MatrixElementsPerDigit = 6; + + /// + /// Gets the element widths of every digit, one bit per element, most significant element first: a set bit is a wide element and a clear bit a narrow one. + /// Every digit has the weights 1, 2, 4 and 7 and a parity element, and two of its five elements are wide. Industrial and IATA read the five bits as bars. + /// Matrix and Datalogic read them as a bar, a space, a bar, a space and a bar. The narrow spaces that separate the elements and the digits are not in the + /// pattern. + /// + private static ReadOnlySpan Patterns => + [ + 0b00110, 0b10001, 0b01001, 0b11000, 0b00101, + 0b10100, 0b01100, 0b00011, 0b10010, 0b01010, + ]; + + /// + /// Gets the element widths of every digit of COOP 2 of 5, in the layout of . COOP assigns the same ten patterns to the digits in a + /// different order. + /// + private static ReadOnlySpan CoopPatterns => + [ + 0b11000, 0b00011, 0b00101, 0b00110, 0b01001, + 0b01010, 0b01100, 0b10001, 0b10010, 0b10100, + ]; + + /// + /// Validates the input, applies the check digit and builds the symbol. + /// + /// The digits to encode. + /// The options that control layout choices. + /// The symbology whose patterns to use. + /// Whether the symbol carries the modulo 10 check digit, and whether the encoder calculates it or validates it. + /// Whether a check digit the symbol carries is part of the human readable interpretation. + /// The symbol to write. + public static void Encode( + ReadOnlySpan text, + BarcodeOptions options, + Code2Of5Variant variant, + CheckCharacterMode checkDigit, + bool printCheckDigit, + ref BarcodeSymbol symbol) + { + Guard.MustBeGreaterThan(text.Length, 0, nameof(text)); + Guard.MustBeLessThanOrEqualTo(text.Length, MaximumLength, nameof(text)); + + // Validate mode reads the last digit as the check digit, so at least one data digit comes before it. + if (checkDigit == CheckCharacterMode.Validate && text.Length < 2) + { + throw new ArgumentException("A supplied check digit follows at least one data digit.", nameof(text)); + } + + BarcodeSymbology.ValidateDigits(text); + + ReadOnlySpan data = checkDigit == CheckCharacterMode.Validate ? text.Slice(0, text.Length - 1) : text; + + // The modulo 10 check digit of Appendix C of AIM USS-I 2/5 weights the digits 3 and 1 from the right, the same as the GS1 calculation. + int? check = checkDigit == CheckCharacterMode.None ? null : Gs1Data.ComputeCheckDigit(data); + if (checkDigit == CheckCharacterMode.Validate && text[^1] - '0' != check) + { + throw new ArgumentException($"Incorrect check digit: expected {check}, got {text[^1]}.", nameof(text)); + } + + // The digits the symbol carries are the data and the check digit. The text is at most MaximumLength digits, so a short symbol works inline and a longer + // one rents its digits once. + int carried = data.Length + (check is null ? 0 : 1); + InlineArray64 digitStorage = default; + PooledBuffer rented = default; + Span encoded = carried <= InlineBufferLength + ? ((Span)digitStorage)[..carried] + : (rented = new PooledBuffer(carried)).Span; + + try + { + data.CopyTo(encoded); + if (check is not null) + { + encoded[^1] = (char)('0' + check.Value); + } + + ReadOnlySpan readable = check is null || printCheckDigit ? encoded : encoded[..^1]; + BuildSymbol(encoded, readable, variant, options, ref symbol); + } + finally + { + rented.Dispose(); + } + } + + /// + /// Encodes digits and builds the symbol. The runs alternate bar and space, start with the first bar of the start pattern and end on the last bar of the + /// stop pattern. The 2 of 5 symbologies carry no guard bars, so every bar runs the full height, and the human readable interpretation sits below the + /// symbol. + /// + /// The digits to encode, the check digit included, already validated. + /// The human readable interpretation. + /// The symbology whose patterns to use. + /// The options that control layout choices. + /// The symbol to write. + private static void BuildSymbol( + ReadOnlySpan digits, + ReadOnlySpan readable, + Code2Of5Variant variant, + BarcodeOptions options, + ref BarcodeSymbol symbol) + { + ReadOnlySpan start = StartPattern(variant); + ReadOnlySpan stop = StopPattern(variant); + ReadOnlySpan patterns = variant == Code2Of5Variant.Coop ? CoopPatterns : Patterns; + bool barsOnly = variant is Code2Of5Variant.Industrial or Code2Of5Variant.Iata; + int elementsPerDigit = barsOnly ? BarElementsPerDigit : MatrixElementsPerDigit; + + double xDimension = options.ResolveXDimension(0, BarcodeSymbology.DefaultXDimension, double.PositiveInfinity); + int runCount = (digits.Length * elementsPerDigit) + start.Length + stop.Length; + Span runs = symbol.BeginLinear(runCount, xDimension, out Span heights, out _); + int written = 0; + foreach (byte width in start) + { + runs[written++] = width; + } + + // Industrial and IATA put a narrow space after every bar. The other variants put one narrow space after the five elements of the digit. + foreach (char digit in digits) + { + int pattern = patterns[digit - '0']; + for (int bit = PatternElements - 1; bit >= 0; bit--) + { + runs[written++] = ((pattern >> bit) & 1) != 0 ? WideElement : 1; + if (barsOnly) + { + runs[written++] = 1; + } + } + + if (!barsOnly) + { + runs[written++] = 1; + } + } + + foreach (byte width in stop) + { + runs[written++] = width; + } + + int widthInModules = BarcodeSymbol.RunWidth(runs); + + // As in Interleaved 2 of 5, the quiet zone is ten modules or 2.54 mm when that is wider, and the bar height is 15 per cent of the symbol length with + // its quiet zones. + float quietZone = MathF.Max(QuietZone, (float)(MinimumQuietZoneMillimetres / xDimension)); + float nominalBarHeight = MathF.Max((widthInModules + (2 * quietZone)) * NominalBarHeightFraction, (float)(MinimumBarHeightMillimetres / xDimension)); + float barHeight = options.ResolveBarHeight(xDimension, 0, nominalBarHeight, float.PositiveInfinity); + heights.Fill(barHeight); + + if (options.Font is not null) + { + symbol.ReserveText(1, readable.Length); + symbol.AddText(readable, 0F, widthInModules, BarcodeTextSide.BelowBars, barHeight + BarcodeTextPlacement.Clearance); + } + + symbol.EndLinear(quietZone, quietZone); + } + + /// + /// Gets the start pattern of a variant in bar and space order, as describes it. + /// + /// The symbology. + /// The run widths of the start pattern. + private static ReadOnlySpan StartPattern(Code2Of5Variant variant) => variant switch + { + Code2Of5Variant.Industrial => [3, 1, 3, 1, 1, 1], + Code2Of5Variant.Matrix => [3, 1, 1, 1, 1, 1], + Code2Of5Variant.Coop => [3, 1, 3, 1], + _ => [1, 1, 1, 1], + }; + + /// + /// Gets the stop pattern of a variant in bar and space order, ending on a bar, as describes it. + /// + /// The symbology. + /// The run widths of the stop pattern. + private static ReadOnlySpan StopPattern(Code2Of5Variant variant) => variant switch + { + Code2Of5Variant.Industrial => [3, 1, 1, 1, 3], + Code2Of5Variant.Matrix => [3, 1, 1, 1, 1], + Code2Of5Variant.Coop => [1, 3, 3], + _ => [3, 1, 1], + }; +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code2Of5/Code2Of5Variant.cs b/src/ImageSharp.Drawing/Barcodes/Code2Of5/Code2Of5Variant.cs new file mode 100644 index 00000000..f1547995 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code2Of5/Code2Of5Variant.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The plain 2 of 5 symbologies that encodes. They share the digit patterns and differ in their start and stop patterns and in +/// whether the spaces carry data. +/// +internal enum Code2Of5Variant +{ + /// + /// Industrial 2 of 5: the digit in five bars, a start pattern of a wide bar, a narrow space, a wide bar, a narrow space, a narrow bar and a narrow space, + /// and a stop pattern of a wide bar, a narrow space, a narrow bar, a narrow space and a wide bar. + /// + Industrial, + + /// + /// IATA 2 of 5: the digit in five bars, with the start and stop patterns of Interleaved 2 of 5. + /// + Iata, + + /// + /// Matrix 2 of 5: the digit in three bars and two spaces, a start pattern of a wide bar and five narrow elements, and a stop pattern of a wide bar and four + /// narrow elements. + /// + Matrix, + + /// + /// COOP 2 of 5: the digit in three bars and two spaces with its own assignment of patterns to digits, a start pattern of a wide bar, a narrow space, a wide + /// bar and a narrow space, and a stop pattern of a narrow bar, a wide space and a wide bar. + /// + Coop, + + /// + /// Datalogic 2 of 5: the digit in three bars and two spaces, with the start and stop patterns of Interleaved 2 of 5. + /// + Datalogic, +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code2Of5/Coop2Of5Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Code2Of5/Coop2Of5Symbology.cs new file mode 100644 index 00000000..8ce1b8d1 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code2Of5/Coop2Of5Symbology.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The COOP 2 of 5 symbology. No published document describes it. It carries the digits 0 to 9, each in three bars and two spaces of which two elements are +/// wide, with its own assignment of patterns to digits. A symbol carries a start pattern, the digits, an optional check digit and a stop pattern. +/// +/// The check digit is the modulo 10 calculation of Appendix C of AIM USS-I 2/5. The printed line shows the digits the symbol carries and, by default, the check +/// digit. +/// +/// +public sealed class Coop2Of5Symbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Coop2Of5Symbology() + : this(CheckCharacterMode.None, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the symbol carries the modulo 10 check digit, and whether the encoder calculates it or validates it. + public Coop2Of5Symbology(CheckCharacterMode checkDigit) + : this(checkDigit, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the symbol carries the modulo 10 check digit, and whether the encoder calculates it or validates it. + /// Whether a check digit the symbol carries is part of the human readable interpretation. + public Coop2Of5Symbology(CheckCharacterMode checkDigit, bool printCheckDigit) + { + this.CheckDigit = checkDigit; + this.PrintCheckDigit = printCheckDigit; + } + + /// + /// Gets a value that specifies whether the symbol carries the modulo 10 check digit, and whether the encoder calculates it or validates it. + /// + public CheckCharacterMode CheckDigit { get; } + + /// + /// Gets a value indicating whether a check digit the symbol carries is part of the human readable interpretation. A symbol that carries none prints none + /// either way. + /// + public bool PrintCheckDigit { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + => Code2Of5Encoder.Encode(text, options, Code2Of5Variant.Coop, this.CheckDigit, this.PrintCheckDigit, ref symbol); +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code2Of5/Datalogic2Of5Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Code2Of5/Datalogic2Of5Symbology.cs new file mode 100644 index 00000000..0c031473 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code2Of5/Datalogic2Of5Symbology.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Datalogic 2 of 5 symbology, also called Code 2 of 5 Data Logic. No published standard defines it. It carries the digits 0 to 9 in the three bar and two +/// space patterns of Matrix 2 of 5. A symbol carries the start pattern of Interleaved 2 of 5, the digits, an optional check digit and the stop pattern of +/// Interleaved 2 of 5. +/// +/// The check digit is the modulo 10 calculation of Appendix C of AIM USS-I 2/5. The printed line shows the digits the symbol carries and, by default, the +/// check digit. +/// +/// +public sealed class Datalogic2Of5Symbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Datalogic2Of5Symbology() + : this(CheckCharacterMode.None, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the symbol carries the modulo 10 check digit, and whether the encoder calculates it or validates it. + public Datalogic2Of5Symbology(CheckCharacterMode checkDigit) + : this(checkDigit, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the symbol carries the modulo 10 check digit, and whether the encoder calculates it or validates it. + /// Whether a check digit the symbol carries is part of the human readable interpretation. + public Datalogic2Of5Symbology(CheckCharacterMode checkDigit, bool printCheckDigit) + { + this.CheckDigit = checkDigit; + this.PrintCheckDigit = printCheckDigit; + } + + /// + /// Gets a value that specifies whether the symbol carries the modulo 10 check digit, and whether the encoder calculates it or validates it. + /// + public CheckCharacterMode CheckDigit { get; } + + /// + /// Gets a value indicating whether a check digit the symbol carries is part of the human readable interpretation. A symbol that carries none prints none + /// either way. + /// + public bool PrintCheckDigit { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + => Code2Of5Encoder.Encode(text, options, Code2Of5Variant.Datalogic, this.CheckDigit, this.PrintCheckDigit, ref symbol); +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code2Of5/Iata2Of5Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Code2Of5/Iata2Of5Symbology.cs new file mode 100644 index 00000000..08cdf89c --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code2Of5/Iata2Of5Symbology.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The IATA 2 of 5 symbology, also called Computer Identics 2 of 5. No published standard defines it. It carries the digits 0 to 9 in the five bar patterns of +/// Industrial 2 of 5, and the spaces carry no data. A symbol carries the start pattern of Interleaved 2 of 5, the digits, an optional check digit and the stop +/// pattern of Interleaved 2 of 5. +/// +/// The check digit is the modulo 10 calculation of Appendix C of AIM USS-I 2/5. The printed line shows the digits the symbol carries and, by default, the +/// check digit. +/// +/// +public sealed class Iata2Of5Symbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Iata2Of5Symbology() + : this(CheckCharacterMode.None, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the symbol carries the modulo 10 check digit, and whether the encoder calculates it or validates it. + public Iata2Of5Symbology(CheckCharacterMode checkDigit) + : this(checkDigit, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the symbol carries the modulo 10 check digit, and whether the encoder calculates it or validates it. + /// Whether a check digit the symbol carries is part of the human readable interpretation. + public Iata2Of5Symbology(CheckCharacterMode checkDigit, bool printCheckDigit) + { + this.CheckDigit = checkDigit; + this.PrintCheckDigit = printCheckDigit; + } + + /// + /// Gets a value that specifies whether the symbol carries the modulo 10 check digit, and whether the encoder calculates it or validates it. + /// + public CheckCharacterMode CheckDigit { get; } + + /// + /// Gets a value indicating whether a check digit the symbol carries is part of the human readable interpretation. A symbol that carries none prints none + /// either way. + /// + public bool PrintCheckDigit { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + => Code2Of5Encoder.Encode(text, options, Code2Of5Variant.Iata, this.CheckDigit, this.PrintCheckDigit, ref symbol); +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code2Of5/Industrial2Of5Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Code2Of5/Industrial2Of5Symbology.cs new file mode 100644 index 00000000..4605d08e --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code2Of5/Industrial2Of5Symbology.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Industrial 2 of 5 symbology, also called Code 25. No published standard defines it. It carries the digits 0 to 9, each in five bars of which two are +/// wide, and the spaces carry no data. A symbol carries a start pattern, the digits, an optional check digit and a stop pattern. +/// +/// The check digit is the modulo 10 calculation of Appendix C of AIM USS-I 2/5. The printed line shows the digits the symbol carries and, by default, the +/// check digit. +/// +/// +public sealed class Industrial2Of5Symbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Industrial2Of5Symbology() + : this(CheckCharacterMode.None, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the symbol carries the modulo 10 check digit, and whether the encoder calculates it or validates it. + public Industrial2Of5Symbology(CheckCharacterMode checkDigit) + : this(checkDigit, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the symbol carries the modulo 10 check digit, and whether the encoder calculates it or validates it. + /// Whether a check digit the symbol carries is part of the human readable interpretation. + public Industrial2Of5Symbology(CheckCharacterMode checkDigit, bool printCheckDigit) + { + this.CheckDigit = checkDigit; + this.PrintCheckDigit = printCheckDigit; + } + + /// + /// Gets a value that specifies whether the symbol carries the modulo 10 check digit, and whether the encoder calculates it or validates it. + /// + public CheckCharacterMode CheckDigit { get; } + + /// + /// Gets a value indicating whether a check digit the symbol carries is part of the human readable interpretation. A symbol that carries none prints none + /// either way. + /// + public bool PrintCheckDigit { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + => Code2Of5Encoder.Encode(text, options, Code2Of5Variant.Industrial, this.CheckDigit, this.PrintCheckDigit, ref symbol); +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code2Of5/Matrix2Of5Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Code2Of5/Matrix2Of5Symbology.cs new file mode 100644 index 00000000..bf8bc365 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code2Of5/Matrix2Of5Symbology.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Matrix 2 of 5 symbology, also called Code 2 of 5 Standard. No published standard defines it. It carries the digits 0 to 9, each in three bars and two +/// spaces of which two elements are wide. A symbol carries a start pattern, the digits, an optional check digit and a stop pattern. +/// +/// The check digit is the modulo 10 calculation of Appendix C of AIM USS-I 2/5. The printed line shows the digits the symbol carries and, by default, the check +/// digit. +/// +/// +public sealed class Matrix2Of5Symbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Matrix2Of5Symbology() + : this(CheckCharacterMode.None, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the symbol carries the modulo 10 check digit, and whether the encoder calculates it or validates it. + public Matrix2Of5Symbology(CheckCharacterMode checkDigit) + : this(checkDigit, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the symbol carries the modulo 10 check digit, and whether the encoder calculates it or validates it. + /// Whether a check digit the symbol carries is part of the human readable interpretation. + public Matrix2Of5Symbology(CheckCharacterMode checkDigit, bool printCheckDigit) + { + this.CheckDigit = checkDigit; + this.PrintCheckDigit = printCheckDigit; + } + + /// + /// Gets a value that specifies whether the symbol carries the modulo 10 check digit, and whether the encoder calculates it or validates it. + /// + public CheckCharacterMode CheckDigit { get; } + + /// + /// Gets a value indicating whether a check digit the symbol carries is part of the human readable interpretation. A symbol that carries none prints none + /// either way. + /// + public bool PrintCheckDigit { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + => Code2Of5Encoder.Encode(text, options, Code2Of5Variant.Matrix, this.CheckDigit, this.PrintCheckDigit, ref symbol); +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code39/Code32Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Code39/Code32Symbology.cs new file mode 100644 index 00000000..e03b0e15 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code39/Code32Symbology.cs @@ -0,0 +1,152 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Code 32 is the Italian pharmaceutical code. Allegato A of the decree in Gazzetta Ufficiale della Repubblica Italiana, Serie generale n. 165 of 18 July 2014, +/// defines it. +/// +/// The AIC code is nine digits. The first digit is zero and the last digit is the check digit. Six base 32 characters carry those nine digits, and a Code 39 +/// symbol carries those six characters. +/// +/// +/// The input is the eight data digits, or those digits and the check digit. This class always calculates the check digit. When the input carries one, this +/// class compares the two. +/// +/// +/// The printed line is the letter A and then the nine digits. That letter is the field identifier for automatic reading equipment. The symbol does not carry +/// the letter or the digits. +/// +/// +/// The symbol draws its wide elements at the nominal ratio of 2.5 that Allegato A gives. +/// +/// +public sealed class Code32Symbology : BarcodeSymbology +{ + /// + /// The base 32 characters in value order, from Table 1 of Allegato A. Section 3 gives the rule: "l'uso delle cifre da 0 a 9 e delle lettere dell'alfabeto + /// inglese ad eccezione delle lettere A, E, I, O". + /// + private const string Base32Characters = "0123456789BCDFGHJKLMNPQRSTUVWXYZ"; + + /// + /// The letter that opens the printed line. Section 3 of Allegato A prints the code "preceduto dalla lettera A, avente funzione di identificatore di campo + /// per apparecchiature di lettura automatica". + /// + private const char FieldIdentifier = 'A'; + + /// + /// The number of data digits, excluding the check digit. + /// + private const int Digits = 8; + + /// + /// The number of base 32 characters the symbol carries. Section 3: the numbering system "consente di rappresentare le nove cifre del codice con sei + /// caratteri alfanumerici". + /// + private const int Base32Length = 6; + + /// + /// The X dimension in millimetres a symbol takes when the caller sets none. Allegato A: "lo spessore nominale delle barre e degli spazi stretti è di mm + /// 0,250". + /// + private const double NominalXDimension = 0.25; + + /// + /// The width of a narrow element in run units, so a run unit is half a module. + /// + private const int NarrowElement = 2; + + /// + /// The width of a wide element in run units. Allegato A: the wide elements are such "che il rapporto tra S ed M risulti nominalmente uguale a 2,5". + /// + private const int WideElement = 5; + + /// + /// Initializes a new instance of the class. + /// + public Code32Symbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Guard.MustBeBetweenOrEqualTo(text.Length, Digits, Digits + 1, nameof(text)); + + BarcodeSymbology.ValidateDigits(text); + + // Section 3: the code "è composto da nove cifre di cui la prima è la cifra zero". + if (text[0] != '0') + { + throw new ArgumentException($"An AIC code starts with the digit zero; got '{text[0]}'.", nameof(text)); + } + + // Section 3 doubles the second, fourth, sixth and eighth digits, and sums the quotient and the remainder of each product divided by ten. That sum joins + // the sum of the first, third, fifth and seventh digits, and the check digit is the remainder of the total divided by ten. + int sum = 0; + for (int i = 0; i < Digits; i++) + { + int value = text[i] - '0'; + if ((i & 1) == 1) + { + value *= 2; + value = (value / 10) + (value % 10); + } + + sum += value; + } + + int check = sum % 10; + if (text.Length > Digits && text[Digits] - '0' != check) + { + throw new ArgumentException($"Incorrect AIC check digit: expected {check}, got {text[Digits]}.", nameof(text)); + } + + InlineArray10 printedStorage = default; + Span printed = ((Span)printedStorage)[..(Digits + 1)]; + text.Slice(0, Digits).CopyTo(printed); + printed[Digits] = (char)('0' + check); + + int number = 0; + foreach (char digit in printed) + { + number = (number * 10) + (digit - '0'); + } + + InlineArray6 encodedStorage = default; + Span encoded = encodedStorage; + for (int i = Base32Length - 1; i >= 0; i--) + { + encoded[i] = Base32Characters[number % Base32Characters.Length]; + number /= Base32Characters.Length; + } + + double xDimension = options.ResolveXDimension(0, NominalXDimension, double.PositiveInfinity); + + // The runs count pixels, so every element draws at a whole number of pixels. A wide element is WideElement / NarrowElement modules, rounded half up + // to a whole pixel: at an odd number of pixels a module the ratio of 2.5 becomes 3.0 at one pixel and 2.67 at three, and every wide element takes the + // same width. + int pixels = options.ModulePixels(xDimension); + int widePixels = ((WideElement * pixels) + (NarrowElement / 2)) / NarrowElement; + + // The bar height is 15 % of the width the symbol draws at. The start character, the six base 32 characters and the stop character each have three + // wide and six narrow elements, and every character but the stop character has a narrow gap. + float widthInModules = (((Base32Length + 2) * ((3 * widePixels) + (7 * pixels))) - pixels) / (float)pixels; + Code39Layout layout = new() + { + XDimension = xDimension, + NominalBarHeight = Code39Encoder.ProportionalBarHeight(widthInModules, xDimension), + MinimumBarHeight = 0F, + MaximumBarHeight = float.PositiveInfinity, + NarrowElement = pixels, + WideElement = widePixels, + }; + + // The interpretation is the field identifier, then the nine digits of the code. + Code39Encoder.BuildSymbol(encoded, null, [FieldIdentifier], printed, [], in layout, options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code39/Code39Encoder.cs b/src/ImageSharp.Drawing/Barcodes/Code39/Code39Encoder.cs new file mode 100644 index 00000000..c87d0278 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code39/Code39Encoder.cs @@ -0,0 +1,265 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Shared encodation for the Code 39 symbology family, defined by ISO/IEC 16388. Section 4.1 c) gives every symbol character nine elements, "5 bars and 4 +/// spaces" of which "3 wide and 6 narrow", and section 4.2 separates the characters within the symbol with an inter-character gap. Section 4.2 also gives the +/// structure: a quiet zone, the start character, the data and its optional check character, the stop character and a quiet zone. +/// +internal static class Code39Encoder +{ + /// + /// The largest number of characters a symbol carries. Section 4.1 e) makes the data string length variable, so the symbology sets no maximum of its own. + /// + public const int MaximumLength = 500; + + /// + /// The number of characters a caller holds inline to build symbol data in. Data this long covers the labels the symbology is used for, and longer data + /// rents its buffer once. + /// + public const int InlineBufferLength = 64; + + /// + /// The quiet zone in modules on each side. Section 4.4 d): "Minimum width of quiet zone: 10 X". + /// + private const int QuietZone = 10; + + /// + /// The element count of a symbol character and the gap that follows it. + /// + private const int ElementsPerCharacter = 10; + + /// + /// The pattern index of the start and stop character, which carries no data. + /// + private const int StartStop = 43; + + /// + /// The width in modules of a symbol character and its gap: three wide elements, six narrow elements and the narrow gap, the sixteen modules section 4.1 h) + /// gives at the top of the ratio range. + /// + private const int ModulesPerCharacter = (3 * WideElement) + 6 + 1; + + /// + /// The width of a wide element in modules. Section 4.4 b) allows a wide to narrow ratio of "2,0 : 1 to 3,0 : 1", and this is the widest of that range, so a + /// symbol character and its gap take the sixteen modules at the top of the range section 4.1 h) gives. An application standard that names a nominal ratio + /// inside that range builds the symbol with a . + /// + private const int WideElement = 3; + + /// + /// The bar height as a fraction of the symbol width, quiet zones excluded, when the caller sets no height. Section 4.4 e) recommends a minimum of "5,0 mm + /// or 15 % of symbol width excluding quiet zones, whichever is greater". + /// + private const float NominalBarHeightFraction = 0.15F; + + /// + /// The smallest bar height in millimetres when the caller sets none, section 4.4 e). + /// + private const float MinimumBarHeightMillimetres = 5F; + + /// + /// Gets the element widths of every symbol character, one bit per element, most significant element first: a set bit is a wide element and a clear bit a + /// narrow one. The first nine bits are the pattern Table 1 of ISO/IEC 16388 gives in bar and space order, and the tenth is the inter-character gap, which + /// section 4.4 c) gives a minimum width of one narrow element. The last entry is the start and stop character. + /// + private static ReadOnlySpan Patterns => + [ + 0x068, 0x242, 0x0C2, 0x2C0, 0x062, 0x260, 0x0E0, 0x04A, + 0x248, 0x0C8, 0x212, 0x092, 0x290, 0x032, 0x230, 0x0B0, + 0x01A, 0x218, 0x098, 0x038, 0x206, 0x086, 0x284, 0x026, + 0x224, 0x0A4, 0x00E, 0x20C, 0x08C, 0x02C, 0x302, 0x182, + 0x380, 0x122, 0x320, 0x1A0, 0x10A, 0x308, 0x188, 0x150, + 0x144, 0x114, 0x054, 0x128, + ]; + + /// + /// Calculates the check character over the given data. Annex A.1.1 assigns each data character the value Table A.1 gives it, sums those values, divides the + /// sum by 43, and takes "the character whose value (from Table A.1) is the remainder from the division" as the check character. Table A.1 is the 43 + /// characters of . + /// + /// The data the check character covers. + /// The check character. + public static char CheckCharacter(ReadOnlySpan data) + { + int sum = 0; + foreach (char character in data) + { + sum += AlphanumericCharacterSet.Value(character); + } + + return AlphanumericCharacterSet.CheckCharacter(sum); + } + + /// + /// Validates the given text against the Code 39 character set. The message reports the first character outside the set, and a surrogate pair as one + /// character. + /// + /// The text to validate. + /// The text carries a character outside the set. + public static void Validate(ReadOnlySpan text) + { + Guard.MustBeGreaterThan(text.Length, 0, nameof(text)); + Guard.MustBeLessThanOrEqualTo(text.Length, MaximumLength, nameof(text)); + + int index = AlphanumericCharacterSet.IndexOfAnyExcept(text); + if (index >= 0) + { + throw new ArgumentException( + $"Code 39 carries only digits, capital letters, spaces and the symbols -.$/+%; {text.CodePointAt(index).ToDisplayString()} is outside that set.", + nameof(text)); + } + } + + /// + /// Builds the symbol. Code 39 carries no guard bars, so every bar runs the full height, at the proportional height of section 4.4 e), and the human + /// readable interpretation sits below the symbol. + /// + /// The data to encode, already validated against the character set. + /// The check character to carry after the data, or . + /// The characters of the human readable interpretation before its body. + /// The body of the human readable interpretation. + /// The characters of the human readable interpretation after its body. + /// The options that control layout choices. + /// The X dimension in millimetres, . + /// The symbol to write. + public static void BuildSymbol( + ReadOnlySpan data, + char? check, + ReadOnlySpan readablePrefix, + ReadOnlySpan readableBody, + ReadOnlySpan readableSuffix, + BarcodeOptions options, + double xDimension, + ref BarcodeSymbol symbol) + { + // Section 4.4 e) gives a "Recommended minimum bar code height" and no limit, so it sets the nominal bar height and does not clamp the bar height. + int widthInModules = SymbolWidth(CharacterCount(data.Length, check)); + Code39Layout layout = new() + { + XDimension = xDimension, + NominalBarHeight = ProportionalBarHeight(widthInModules, xDimension), + MinimumBarHeight = 0F, + MaximumBarHeight = float.PositiveInfinity, + NarrowElement = 1, + WideElement = WideElement, + }; + + BuildSymbol(data, check, readablePrefix, readableBody, readableSuffix, in layout, options, ref symbol); + } + + /// + /// Returns the bar height in modules that section 4.4 e) recommends when the caller sets none: 15 % of the symbol width, quiet zones excluded, and at + /// least 5 mm. + /// + /// The width of the symbol in modules, quiet zones excluded. + /// The X dimension in millimetres. + /// The bar height in modules. + public static float ProportionalBarHeight(float widthInModules, double xDimension) + => MathF.Max(widthInModules * NominalBarHeightFraction, (float)(MinimumBarHeightMillimetres / xDimension)); + + /// + /// Builds the symbol at the dimensions of an application standard, which can name a wide to narrow ratio that is not a whole number. The runs count run + /// units of a module divided by the narrow width. The runs carry the start character, the data, an optional check character and the stop character, each + /// followed by its inter-character gap. Section 4.2 separates the characters "within the symbol" with that gap, and section 4.3.3 puts the stop character + /// at the right end, so no gap follows it and the runs end on a bar. Annex A.1.1 places the check character "immediately following the final data + /// character and before the stop character". + /// + /// The data to encode, already validated against the character set. + /// The check character to carry after the data, or . + /// The characters of the human readable interpretation before its body. + /// The body of the human readable interpretation. + /// The characters of the human readable interpretation after its body. + /// The dimensions of the application standard. + /// The options that control layout choices. + /// The symbol to write. + public static void BuildSymbol( + ReadOnlySpan data, + char? check, + ReadOnlySpan readablePrefix, + ReadOnlySpan readableBody, + ReadOnlySpan readableSuffix, + in Code39Layout layout, + BarcodeOptions options, + ref BarcodeSymbol symbol) + { + int narrow = layout.NarrowElement; + int wide = layout.WideElement; + int characters = CharacterCount(data.Length, check); + Span runs = symbol.BeginLinear((characters * ElementsPerCharacter) - 1, layout.XDimension, out Span heights, out _); + int written = 0; + + AppendElements(runs, ref written, StartStop, ElementsPerCharacter, narrow, wide); + foreach (char character in data) + { + AppendElements(runs, ref written, AlphanumericCharacterSet.Value(character), ElementsPerCharacter, narrow, wide); + } + + if (check is not null) + { + AppendElements(runs, ref written, AlphanumericCharacterSet.Value(check.Value), ElementsPerCharacter, narrow, wide); + } + + AppendElements(runs, ref written, StartStop, ElementsPerCharacter - 1, narrow, wide); + + // Every character has three wide and six narrow elements and a narrow gap, and the stop character carries no gap. + float runUnit = 1F / narrow; + float widthInModules = ((characters * ((3 * wide) + (7 * narrow))) - narrow) * runUnit; + float barHeight = options.ResolveBarHeight(layout.XDimension, layout.MinimumBarHeight, layout.NominalBarHeight, layout.MaximumBarHeight); + heights.Fill(barHeight); + + if (options.Font is not null) + { + symbol.ReserveText(1, readablePrefix.Length + readableBody.Length + readableSuffix.Length); + symbol.AddText( + readablePrefix, + readableBody, + readableSuffix, + 0F, + widthInModules, + BarcodeTextSide.BelowBars, + barHeight + BarcodeTextPlacement.Clearance); + } + + symbol.EndLinear(QuietZone, QuietZone, 0F, runUnit, false); + } + + /// + /// Gets the number of symbol characters a symbol carries: the start character, the data, the optional check character and the stop character. + /// + /// The number of data characters. + /// The check character to carry after the data, or . + /// The number of symbol characters. + private static int CharacterCount(int dataLength, char? check) => dataLength + (check is null ? 2 : 3); + + /// + /// Gets the width of a symbol in modules, quiet zones excluded. Every pattern has three wide elements among its nine, so each character and its gap take + /// modules, and the stop character carries no gap. + /// + /// The number of symbol characters, the start and stop characters included. + /// The width in modules. + private static int SymbolWidth(int characters) => (characters * ModulesPerCharacter) - 1; + + /// + /// Writes the leading element widths of one symbol character: all ten for a character and its gap, or the first nine for the stop character. + /// + /// The buffer the widths are written to. + /// The write position, advanced by the element count. + /// The pattern index, which is the character value. + /// The number of elements to write. + /// The width of a narrow element in run units. + /// The width of a wide element in run units. + private static void AppendElements(Span runs, ref int written, int index, int count, int narrow, int wide) + { + ushort pattern = Patterns[index]; + for (int element = ElementsPerCharacter - 1; element >= ElementsPerCharacter - count; element--) + { + runs[written++] = ((pattern >> element) & 1) == 1 ? wide : narrow; + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code39/Code39ExtendedSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Code39/Code39ExtendedSymbology.cs new file mode 100644 index 00000000..d63b753c --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code39/Code39ExtendedSymbology.cs @@ -0,0 +1,152 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Code 39 Extended, the full ASCII transmission mode of Annex A.3.1 of ISO/IEC 16388. The 128 character set of ISO 646 IRV "may be encoded using either one or +/// combinations of two symbol characters, made up of one of the four characters ($ + % /) followed by one of the 26 alphabetic characters", and Table A.2 gives +/// the combinations. +/// +/// A.3 warns that the mode needs a decoder that is programmed for it. The symbol itself is an ordinary Code 39 symbol, so only a decoder in full ASCII mode +/// reads back what went in. Annex A.2 prints an interpretation "of the data characters". Here those are the ASCII characters the caller gave, not the symbol +/// characters that stand for them. The interpretation therefore shows the text as given, and prints a space where a character has no printed form. +/// +/// +public sealed class Code39ExtendedSymbology : BarcodeSymbology +{ + /// + /// The character the human readable interpretation prints for the start and stop character, the asterisk section 4.3.3 of ISO/IEC 16388 shows. + /// + private const char ReadableDelimiter = '*'; + + /// + /// Table A.2, two characters for every ASCII value in order. A value encoded by a single symbol character has a space in front of it, which is never a + /// prefix, so the first character of a pair says which of the two forms it is. + /// + private const string Substitutions = + "%U$A$B$C$D$E$F$G" + + "$H$I$J$K$L$M$N$O" + + "$P$Q$R$S$T$U$V$W" + + "$X$Y$Z%A%B%C%D%E" + + " /A/B/C/D/E/F/G" + + "/H/I/J/K/L - ./O" + + " 0 1 2 3 4 5 6 7" + + " 8 9/Z%F%G%H%I%J" + + "%V A B C D E F G" + + " H I J K L M N O" + + " P Q R S T U V W" + + " X Y Z%K%L%M%N%O" + + "%W+A+B+C+D+E+F+G" + + "+H+I+J+K+L+M+N+O" + + "+P+Q+R+S+T+U+V+W" + + "+X+Y+Z%P%Q%R%S%T"; + + /// + /// Initializes a new instance of the class. + /// + public Code39ExtendedSymbology() + : this(CheckCharacterMode.None, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the symbol carries the modulo 43 check character, and whether the encoder calculates it. + public Code39ExtendedSymbology(CheckCharacterMode checkCharacter) + : this(checkCharacter, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the symbol carries the modulo 43 check character, and whether the encoder calculates it. + /// The symbology rejects . The check character covers the substituted symbol characters, and a caller that works + /// in ASCII does not have these characters. + /// Whether a check character the symbol carries is part of the human readable interpretation. + public Code39ExtendedSymbology(CheckCharacterMode checkCharacter, bool printCheckCharacter) + { + Guard.IsFalse( + checkCharacter == CheckCharacterMode.Validate, + nameof(checkCharacter), + "Code 39 Extended calculates the check character over the substituted symbol characters, so a supplied one cannot be validated against the input."); + + this.CheckCharacter = checkCharacter; + this.PrintCheckCharacter = printCheckCharacter; + } + + /// + /// Gets a value that specifies whether the symbol carries the modulo 43 check character, and whether the encoder calculates it. + /// + public CheckCharacterMode CheckCharacter { get; } + + /// + /// Gets a value indicating whether a check character the symbol carries is part of the human readable interpretation. A symbol that carries none prints + /// none either way. + /// + public bool PrintCheckCharacter { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Guard.MustBeGreaterThan(text.Length, 0, nameof(text)); + + // Each character substitutes to one or two symbol characters, and the symbol carries at most Code39Encoder.MaximumLength of them, so longer text cannot + // fit. A pair per character bounds the substitution, and the readable interpretation matches the text character for character, so neither builder + // grows: short text stays inline and longer text rents once. + Guard.MustBeLessThanOrEqualTo(text.Length, Code39Encoder.MaximumLength, nameof(text)); + + // The message decodes the code point at the first unit outside ASCII, so a surrogate pair reports as the one character it is. + int invalid = text.IndexOfAnyExceptInRange('\0', '\u007F'); + if (invalid >= 0) + { + throw new ArgumentException( + $"Code 39 Extended encodes ASCII 0 to 127; {text.CodePointAt(invalid).ToDisplayString()} is outside that range.", + nameof(text)); + } + + InlineArray64 encodedStorage = default; + InlineArray64 readableStorage = default; + int encodedCapacity = text.Length * 2; + ValueStringBuilder encoded = encodedCapacity <= Code39Encoder.InlineBufferLength ? new(encodedStorage) : new(encodedCapacity); + ValueStringBuilder readable = text.Length <= Code39Encoder.InlineBufferLength ? new(readableStorage) : new(text.Length); + try + { + foreach (char character in text) + { + ReadOnlySpan pair = Substitutions.AsSpan(character * 2, 2); + encoded.Append(pair[0] == ' ' ? pair[1..] : pair); + + // A control character has no printed form, so the interpretation shows a space instead. + readable.Append(char.IsControl(character) ? ' ' : character); + } + + // The substitutions are Code 39 characters, so only the length of the result needs a check. + Guard.MustBeLessThanOrEqualTo(encoded.Length, Code39Encoder.MaximumLength, nameof(text)); + char? check = this.CheckCharacter == CheckCharacterMode.None ? null : Code39Encoder.CheckCharacter(encoded.AsSpan()); + + // The human readable interpretation is the text as given, then the check character the symbol carries when it prints, between the delimiters that + // stand for the start and stop character. + ReadOnlySpan opening = [ReadableDelimiter]; + ReadOnlySpan closing = check is not null && this.PrintCheckCharacter ? [check.Value, ReadableDelimiter] : [ReadableDelimiter]; + Code39Encoder.BuildSymbol( + encoded.AsSpan(), + check, + opening, + readable.AsSpan(), + closing, + options, + options.ResolveXDimension(0, DefaultXDimension, double.PositiveInfinity), + ref symbol); + } + finally + { + readable.Dispose(); + encoded.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code39/Code39Layout.cs b/src/ImageSharp.Drawing/Barcodes/Code39/Code39Layout.cs new file mode 100644 index 00000000..dafdba67 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code39/Code39Layout.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The dimensions an application standard sets for a Code 39 symbol: the X dimension, the bar height, and the widths of the narrow and wide elements. +/// builds the symbol at them. +/// +internal readonly struct Code39Layout +{ + /// + /// Gets the X dimension in millimetres. + /// + public double XDimension { get; init; } + + /// + /// Gets the bar height in modules when the options set none. + /// + public float NominalBarHeight { get; init; } + + /// + /// Gets the smallest bar height in modules that the application standard permits, or zero where it gives none. The code clamps the bar height to it. + /// + public float MinimumBarHeight { get; init; } + + /// + /// Gets the largest bar height in modules that the application standard permits, or where it gives none. The code + /// clamps the bar height to it. + /// + public float MaximumBarHeight { get; init; } + + /// + /// Gets the width of a narrow element in run units. A run unit is the module divided by this width, so a narrow element is one module wide. + /// + public int NarrowElement { get; init; } + + /// + /// Gets the width of a wide element in run units. The wide to narrow ratio is this width divided by . + /// + public int WideElement { get; init; } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code39/Code39Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Code39/Code39Symbology.cs new file mode 100644 index 00000000..c6379a42 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code39/Code39Symbology.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Code 39 symbology, which ISO/IEC 16388 defines. Section 4.1 a) gives it the digits, the capital letters and the special characters space $ % + - . +/// /. Section 4.2 puts a start and a stop character around the data, and both share one pattern. +/// +/// Annex A.2 prints the human readable interpretation "of the data characters (and data and symbol check character(s), if used)". It also says the start and +/// stop characters "may be printed". Section 4.3.3 shows that character as an asterisk. A symbol therefore prints its data between asterisks, with the check +/// character after the data when it carries one. +/// +/// +public sealed class Code39Symbology : BarcodeSymbology +{ + /// + /// The character the human readable interpretation prints for the start and stop character, the asterisk section 4.3.3 shows. + /// + private const char ReadableDelimiter = '*'; + + /// + /// Initializes a new instance of the class. + /// + public Code39Symbology() + : this(CheckCharacterMode.None, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the symbol carries the modulo 43 check character, and whether the encoder calculates it or validates it. + public Code39Symbology(CheckCharacterMode checkCharacter) + : this(checkCharacter, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the symbol carries the modulo 43 check character, and whether the encoder calculates it or validates it. + /// Whether a check character the symbol carries is part of the human readable interpretation. + public Code39Symbology(CheckCharacterMode checkCharacter, bool printCheckCharacter) + { + this.CheckCharacter = checkCharacter; + this.PrintCheckCharacter = printCheckCharacter; + } + + /// + /// Gets a value that specifies whether the symbol carries the modulo 43 check character, and whether the encoder calculates it or validates it. + /// + public CheckCharacterMode CheckCharacter { get; } + + /// + /// Gets a value indicating whether a check character the symbol carries is part of the human readable interpretation. A symbol that carries none prints + /// none either way. + /// + public bool PrintCheckCharacter { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Code39Encoder.Validate(text); + + // Validate mode reads the last character as the check character, so at least one data character comes before it. + if (this.CheckCharacter == CheckCharacterMode.Validate && text.Length < 2) + { + throw new ArgumentException("A supplied check character follows at least one data character.", nameof(text)); + } + + ReadOnlySpan data = this.CheckCharacter == CheckCharacterMode.Validate ? text.Slice(0, text.Length - 1) : text; + char? check = this.CheckCharacter == CheckCharacterMode.None ? null : Code39Encoder.CheckCharacter(data); + if (this.CheckCharacter == CheckCharacterMode.Validate && text[^1] != check) + { + throw new ArgumentException($"Incorrect check character: expected '{check}', got '{text[^1]}'.", nameof(text)); + } + + // The human readable interpretation is the data, then the check character the symbol carries when it prints, between the delimiters that stand for the + // start and stop character. + ReadOnlySpan opening = [ReadableDelimiter]; + ReadOnlySpan closing = check is not null && this.PrintCheckCharacter ? [check.Value, ReadableDelimiter] : [ReadableDelimiter]; + + // Section 4.4 a) of ISO/IEC 16388: the X dimension "should be defined by the application specification", so the symbology gives no limit. + double xDimension = options.ResolveXDimension(0, DefaultXDimension, double.PositiveInfinity); + Code39Encoder.BuildSymbol(data, check, opening, data, closing, options, xDimension, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code39/HibcCode39Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Code39/HibcCode39Symbology.cs new file mode 100644 index 00000000..054b40cf --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code39/HibcCode39Symbology.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// HIBC Code 39, a Health Industry Bar Code. A Code 39 symbol carries it. The symbol carries the flag character, the data and the modulo 43 check character. +/// Code 39 adds no check character of its own. The human readable interpretation shows that same string between delimiters, with a space printed as an +/// underscore and a zero as "Ø". When the options set no X dimension, the X dimension is 0.010 inch, from section 5.1 of ANSI/HIBC 2.6. +/// An X dimension under 0.0067 inch draws at 0.0067 inch, the minimum of the same section. +/// The text follows a data structure of ANSI/HIBC 2.6: primary data, secondary data that ends with its link character, or primary and secondary data joined +/// with "/", each with any supplemental data fields. +/// +public sealed class HibcCode39Symbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public HibcCode39Symbology() + : this(false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the check character at the end of the input is validated. When the whole input is + /// data. + public HibcCode39Symbology(bool validateCheckCharacter) => this.ValidateCheckCharacter = validateCheckCharacter; + + /// + /// Gets a value indicating whether the check character at the end of the input is validated. When the whole input is data. + /// + public bool ValidateCheckCharacter { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray64 bufferStorage = default; + int capacity = HibcData.PreparedCapacity(text, Code39Encoder.MaximumLength); + ValueStringBuilder encoded = capacity <= HibcData.InlineBufferLength ? new(bufferStorage) : new(capacity); + try + { + // The prepared data is in the Code 39 character set and at most Code39Encoder.MaximumLength long. + HibcData.Prepare(text, this.ValidateCheckCharacter, ref encoded); + + ReadOnlySpan data = encoded.AsSpan(); + + // The interpretation is the encoded data between two delimiters, so a buffer three characters longer holds it, and longer data rents it once. + InlineArray64 readableStorage = default; + int readableCapacity = HibcData.ReadableCapacity(data.Length); + using PooledBuffer rented = readableCapacity <= HibcData.InlineBufferLength ? default : new(readableCapacity); + Span readable = readableCapacity <= HibcData.InlineBufferLength ? ((Span)readableStorage)[..readableCapacity] : rented.Span; + int written = HibcData.WriteReadable(data, readable); + double xDimension = options.ResolveXDimension(HibcData.MinimumLinearXDimension, HibcData.NominalLinearXDimension, double.PositiveInfinity); + Code39Encoder.BuildSymbol(data, null, [], readable[..written], [], options, xDimension, ref symbol); + } + finally + { + encoded.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code39/Pzn7Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Code39/Pzn7Symbology.cs new file mode 100644 index 00000000..9c2181d9 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code39/Pzn7Symbology.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// PZN7, the seven digit Pharmazentralnummer. A Code 39 symbol carries it. The symbol contains the minus sign identifier of ISO/IEC 15418, six data digits and +/// the check digit. +/// +/// The PZN7 was valid until 31 December 2012, so a new symbol uses . The input is the six data digits, or those digits and the +/// check digit. This class always calculates the check digit. When the input carries one, this class compares the two. +/// +/// +public sealed class Pzn7Symbology : BarcodeSymbology +{ + /// + /// The number of data digits, excluding the check digit. + /// + private const int Digits = 6; + + /// + /// Initializes a new instance of the class. + /// + public Pzn7Symbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + => PznData.BuildSymbol(text, Digits, options, ref symbol); +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code39/Pzn8Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Code39/Pzn8Symbology.cs new file mode 100644 index 00000000..328c7858 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code39/Pzn8Symbology.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// PZN8, the eight digit Pharmazentralnummer. A Code 39 symbol carries it. The symbol contains the minus sign identifier of ISO/IEC 15418, seven data digits +/// and the check digit. +/// +/// The input is the seven data digits, or those digits and the check digit. This class always calculates the check digit. When the input carries one, this +/// class compares the two. +/// +/// +/// The printed line shows the term PZN, the identifier and the digits. The symbol does not contain the term or the spaces around the identifier. +/// +/// +public sealed class Pzn8Symbology : BarcodeSymbology +{ + /// + /// The number of data digits, excluding the check digit. + /// + private const int Digits = 7; + + /// + /// Initializes a new instance of the class. + /// + public Pzn8Symbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + => PznData.BuildSymbol(text, Digits, options, ref symbol); +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code39/PznData.cs b/src/ImageSharp.Drawing/Barcodes/Code39/PznData.cs new file mode 100644 index 00000000..3ba6cfaf --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code39/PznData.cs @@ -0,0 +1,146 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The data layer the Pharmazentralnummer symbologies share. IFA gives the data structure as a minus sign followed by the number: "The minus sign is +/// internationally standardized as an identifier for the PZN in ISO/IEC 15418 and serves to identify the PZN", and "the last digit of the PZN is the check +/// digit". +/// +internal static class PznData +{ + /// + /// The identifier ISO/IEC 15418 gives the PZN, which opens the encoded data. + /// + private const char Identifier = '-'; + + /// + /// The term the printed line opens with, with the identifier and the spaces IFA adds for readability. Neither the term nor the spaces are encoded. + /// + private const string ReadablePrefix = "PZN - "; + + /// + /// The bar height in modules a symbol takes when the caller sets none. IFA gives a nominal code height of 10 mm at the nominal module width of 0.25 mm, and + /// says "the code height of nominally 10 mm changes proportionately to the nominal module width". + /// + private const float NominalBarHeight = 40F; + + /// + /// The narrow term of the wide to narrow ratio, / . + /// + private const int NarrowElement = 2; + + /// + /// The wide term of the wide to narrow ratio. IFA: "The nominal ratio (proportion of narrow to wide bar) should be 1:2.5. Permissible is 1:2 to 1:3." + /// + private const int WideElement = 5; + + /// + /// The smallest bar height in modules. IFA: "For a normal size of X = 0.25 mm, the code can be between 8 and 20 mm high", and the height "changes + /// proportionately to the nominal module width", so 8 mm is 32 modules. + /// + private const float MinimumBarHeight = 32F; + + /// + /// The largest bar height in modules, the 20 mm of the same range, 80 modules. + /// + private const float MaximumBarHeight = 80F; + + /// + /// The smallest size of the printed line in points. IFA prints the PZN "in a well-readable font size (at least 6 pt. for a small code)". + /// + private const float MinimumTextSize = 6F; + + /// + /// The X dimension in millimetres a symbol takes when the caller sets none: the nominal module width of 0.25 mm that IFA gives. + /// + private const double NominalXDimension = 0.25; + + /// + /// The smallest X dimension in millimetres. IFA: "The code size may vary within the limits stipulated by the parameter listed below", and "Permissible is + /// a minimal width: Module width X = 187 µm". + /// + private const double MinimumXDimension = 0.187; + + /// + /// The largest X dimension in millimetres. IFA: "a maximum code size: Module width X = 450 µm". + /// + private const double MaximumXDimension = 0.45; + + /// + /// The modulus of the check digit. IFA: "The check digit of the PZN is calculated based on mod 11." + /// + private const int CheckModulus = 11; + + /// + /// The weight the final data digit takes, whatever the length. IFA weights the seven digits of a PZN8 from one, so the seventh takes seven, and a PZN7 + /// carries one digit fewer and starts at two. + /// + private const int FinalWeight = 7; + + /// + /// Encodes a Pharmazentralnummer of the given length into a Code 39 symbol. + /// + /// The data digits on their own, in which case the check digit is calculated, or the data digits and the check digit, in which case the + /// last is checked against the rest. + /// The number of data digits the length carries, excluding the check digit. + /// The options that control layout choices. + /// The symbol to write. + /// The text is not a valid Pharmazentralnummer. + public static void BuildSymbol(ReadOnlySpan text, int digits, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Guard.MustBeBetweenOrEqualTo(text.Length, digits, digits + 1, nameof(text)); + + BarcodeSymbology.ValidateDigits(text); + + // IFA: "The sum is formed across the products and divided by 11. The whole number remainder is the check digit." + int sum = 0; + int weight = FinalWeight - digits + 1; + for (int i = 0; i < digits; i++) + { + sum += (text[i] - '0') * (weight + i); + } + + int check = sum % CheckModulus; + if (check == 10) + { + // IFA: "If the remainder is the number 10, this digit sequence is not used as PZN." + throw new ArgumentException($"The digits '{text[..digits]}' leave a remainder of 10, which IFA does not issue as a PZN.", nameof(text)); + } + + if (text.Length > digits && text[digits] - '0' != check) + { + throw new ArgumentException($"Incorrect PZN check digit: expected {check}, got {text[digits]}.", nameof(text)); + } + + // The symbol carries the identifier, the digits and the check digit: at most nine characters. + InlineArray10 encodedStorage = default; + Span encoded = ((Span)encodedStorage)[..(digits + 2)]; + encoded[0] = Identifier; + text.Slice(0, digits).CopyTo(encoded[1..]); + encoded[^1] = (char)('0' + check); + + double xDimension = options.ResolveXDimension(MinimumXDimension, NominalXDimension, MaximumXDimension); + + // The runs count pixels, so every element draws at a whole number of pixels. A wide element is WideElement / NarrowElement modules, rounded half up + // to a whole pixel: at an odd number of pixels a module the ratio of 2.5 becomes 3.0 at one pixel and 2.67 at three, both inside the permitted 1:2 to + // 1:3, and every wide element takes the same width. + int pixels = options.ModulePixels(xDimension); + Code39Layout layout = new() + { + XDimension = xDimension, + NominalBarHeight = NominalBarHeight, + MinimumBarHeight = MinimumBarHeight, + MaximumBarHeight = MaximumBarHeight, + NarrowElement = pixels, + WideElement = ((WideElement * pixels) + (NarrowElement / 2)) / NarrowElement, + }; + + Code39Encoder.BuildSymbol(encoded, null, ReadablePrefix, encoded[1..], [], in layout, options, ref symbol); + + symbol.SetTextSize(MinimumTextSize, 0F); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code49/Code49Encoder.cs b/src/ImageSharp.Drawing/Barcodes/Code49/Code49Encoder.cs new file mode 100644 index 00000000..617ba9fd --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code49/Code49Encoder.cs @@ -0,0 +1,586 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodation for Code 49, AIM USS-49. Section 2.1: a symbol is 2 to 8 rows, and each row is a start pattern, four symbol characters of 16 modules and a stop +/// pattern, 70 modules in all. A symbol character carries two code characters of value 0 to 48, W = 49 C1 + C2, in the parity Table 2 gives its row and column. +/// Every row ends in a row check character, and the last row carries the row count, the mode and the symbol check characters of section 2.1.4. +/// +internal static class Code49Encoder +{ + /// + /// The quiet zone in modules before the start pattern: the "ten modules adjacent to the start pattern" of section 3.4. + /// + private const int LeadingQuietZone = 10; + + /// + /// The quiet zone in modules after the stop pattern: the "one module adjacent to the stop pattern" of section 3.4. + /// + private const int TrailingQuietZone = 1; + + /// + /// The width of a row in modules: "the 70X bars and spaces" of section 3.6.1. + /// + private const int RowWidth = 70; + + /// + /// The smallest number of rows, section 2.1. + /// + private const int MinimumRows = 2; + + /// + /// The largest number of rows, section 2.1. + /// + private const int MaximumRows = 8; + + /// + /// The number of code characters in a row, Table 3. + /// + private const int CodeCharactersPerRow = 8; + + /// + /// The nominal bar height in modules: the "minimum bar height of 8X" of section 3.3. + /// + private const float NominalBarHeight = 8F; + + /// + /// The smallest X dimension in millimetres. Section 3.2: "The minimum standard Xdimension used is 0.0075 inches (0.191 mm)". Appendix E permits a + /// smaller X dimension only in a closed system, as a deviation "from standard tolerances". The specification gives no largest X dimension. + /// + private const double MinimumXDimension = 0.0075 * 25.4; + + /// + /// The height in modules of the separator bars: the "1-module high separator bar" of section 2.1. + /// + private const float SeparatorHeight = 1F; + + /// + /// The modulus of the row check character, section 2.1.2. + /// + private const int RowModulus = 49; + + /// + /// The modulus of the symbol check characters, Formula 2. + /// + private const int SymbolModulus = 2401; + + /// + /// The code character value of Shift 1, Table 6. + /// + private const int ShiftOne = 43; + + /// + /// The code character value of Shift 2, Table 6. + /// + private const int ShiftTwo = 44; + + /// + /// The code character value of the numeric shift, Table 6, which section 2.2.1 also makes the pad character: "When one or more numeric shifts appear + /// immediately preceding the symbol check characters, they are pad characters and do not represent data." + /// + private const int NumericShift = 48; + + /// + /// The shortest digit run this encoder carries in the numeric encodation method: five digits, one block of section 2.2.2. Section 2.2.2 says only that + /// "Long sequences of numeric digits can be compressed", and sets no shortest run. + /// + private const int NumericRun = 5; + + /// + /// The number of symbol characters in a row, section 2.1.1. + /// + private const int SymbolCharactersPerRow = 4; + + /// + /// The number of digits one block of three numeric code characters carries, section 2.2.2. + /// + private const int NumericBlock = 5; + + /// + /// The number a four digit remainder is added to, section 2.2.2: "48²C1 + 48C2 + C3 = 100,000 plus the four digits to be represented". + /// + private const int FourDigitOffset = 100000; + + /// + /// The longest text a symbol carries: the 81 numeric characters of Table 1, which exceed its 49 alphanumeric ones. + /// + private const int MaximumText = 81; + + /// + /// The number of runs in a row: the start bar and space, four bars and four spaces per symbol character, and the stop bar. Every symbol character begins + /// with a bar and ends with a space, so no two runs merge. + /// + private const int RowRunCount = 2 + (SymbolCharactersPerRow * 8) + 1; + + /// + /// The base of the numeric encodation method of section 2.2.2, in which a block of digits converts to code characters of value 0 to 47. + /// + private const int NumericBase = 48; + + /// + /// The starting modes of Table 8 that the encoder uses, with their mode values. + /// + private enum StartingMode + { + /// + /// The data starts in the alphanumeric set with no shift. + /// + Alphanumeric = 0, + + /// + /// The data starts with the numeric shift, in the numeric encodation method. + /// + Numeric = 2, + + /// + /// The data starts with Shift 1. + /// + ShiftOne = 4, + + /// + /// The data starts with Shift 2. + /// + ShiftTwo = 5, + } + + /// + /// Gets the check character weights of Table 5 in the order of the symbol characters. The X weights start at the first entry, the Y weights at the second + /// and the Z weights at the third; the row count character weighs 20, 16 and 38. + /// + private static ReadOnlySpan Weights => + [ + 1, 9, 31, 26, 2, 12, 17, 23, 37, 18, 22, 6, 27, 44, 15, 43, 39, 11, 13, 5, 41, 33, 36, 8, 4, 32, 3, 19, 40, 25, 29, 10, 24, 30 + ]; + + /// + /// Gets the parity of the four symbol characters of rows 1 to 7, Table 2, one bit per symbol character from the left, a set bit for odd parity. The last + /// row is always even. + /// + private static ReadOnlySpan RowParity => [0b1001, 0b0101, 0b1100, 0b0011, 0b1010, 0b0110, 0b1111]; + + /// + /// Gets the code character value of each ASCII character, 0 to 127, by Table 7. The value is from Table 6, after the shift that + /// gives. + /// + private static ReadOnlySpan AsciiValues => + [ + 38, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 1, 2, 3, 4, 5, + 38, 6, 7, 8, 39, 42, 9, 0, 36, 37, 39, 41, 40, 36, 37, 40, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 41, 1, 2, 3, 4, 5, + 6, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 7, 8, 9, 0, 36, + 37, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 39, 40, 41, 42, 38 + ]; + + /// + /// Gets the shift before the code character of each ASCII character, 0 to 127, by Table 7: 0 for none, 1 for Shift 1 and 2 for Shift 2. + /// + private static ReadOnlySpan AsciiShifts => + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, + 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 + ]; + + /// + /// Encodes text and builds the symbol. + /// + /// The text to encode. Every character must be ASCII 0 to 127. + /// The human readable interpretation. + /// The options that control layout choices. + /// The symbol to write. + /// The text is empty, carries a character the symbology cannot encode, or holds more data than eight rows + /// carry. + public static void BuildSymbol(ReadOnlySpan text, ReadOnlySpan readable, BarcodeOptions options, ref BarcodeSymbol symbol) + { + // Eight code characters for each of at most eight rows. + InlineArray64 codeStorage = default; + int count = Encode(text, codeStorage); + WriteRows(((ReadOnlySpan)codeStorage)[..count], readable, options, ref symbol); + } + + /// + /// Encodes text into the code characters of the whole symbol, eight per row: the data with its shift characters, the pad, the row check characters, the row + /// count character and the symbol check characters, laid out as Table 3 does. + /// + /// The text to encode. Every character must be ASCII 0 to 127. + /// The buffer that receives the code character values, eight per row for eight rows. + /// The number of code character values, eight per row. + /// The text is empty, carries a character the symbology cannot encode, or holds more data than eight rows + /// carry. + public static int Encode(ReadOnlySpan text, Span codes) + { + if (text.Length == 0) + { + throw new ArgumentException("Code 49 needs at least one character.", nameof(text)); + } + + int invalid = text.IndexOfAnyExceptInRange('\0', '\u007F'); + if (invalid >= 0) + { + throw new ArgumentException($"Code 49 encodes ASCII 0 to 127; {text.CodePointAt(invalid).ToDisplayString()} is outside that range.", nameof(text)); + } + + int n = text.Length; + if (n > MaximumText) + { + throw new ArgumentException( + $"Code 49 carries at most {MaximumText} digits or {Capacity(MaximumRows)} code characters in eight rows; the text is longer.", + nameof(text)); + } + + // The text is at most MaximumText characters, so the digit runs and the values fit inline. + InlineArray128 digitStorage = default; + Span digits = ((Span)digitStorage)[..(MaximumText + 1)]; + digits[n] = 0; + for (int p = n - 1; p >= 0; p--) + { + digits[p] = (byte)(char.IsAsciiDigit(text[p]) ? digits[p + 1] + 1 : 0); + } + + // A character costs at most a shift and itself, and a numeric shift opens and closes each run. + InlineArray256 valueStorage = default; + Span values = ((Span)valueStorage)[..((MaximumText * 2) + 2)]; + int count = 0; + int i = 0; + bool numeric = false; + while (i < n) + { + int run = digits[i]; + if (!numeric) + { + if (run >= NumericRun) + { + values[count++] = NumericShift; + numeric = true; + continue; + } + + int value = CodeCharacter(text[i], out int shift); + if (shift != 0) + { + values[count++] = shift; + } + + values[count++] = value; + i++; + } + else + { + if (run < NumericRun) + { + values[count++] = NumericShift; + numeric = false; + continue; + } + + count = WriteNumeric(text.Slice(i, run), values, count); + i += run; + } + } + + // Table 8: a leading numeric shift or shift is the starting mode rather than a code character. + StartingMode mode = values[0] switch + { + NumericShift => StartingMode.Numeric, + ShiftOne => StartingMode.ShiftOne, + ShiftTwo => StartingMode.ShiftTwo, + _ => StartingMode.Alphanumeric, + }; + + ReadOnlySpan data = values[(mode == StartingMode.Alphanumeric ? 0 : 1)..count]; + int dataCount = data.Length; + + // Section 2.1.4 and Table 4: a symbol of up to six rows has two symbol check characters and one of seven or eight rows has three, so the last row holds + // two data code characters in the first case and none in the second. Every other row holds seven and its row check character. + int rows = MinimumRows; + while (rows <= MaximumRows && Capacity(rows) < dataCount) + { + rows++; + } + + if (rows > MaximumRows) + { + throw new ArgumentException( + $"Code 49 carries at most {Capacity(MaximumRows)} code characters in eight rows; the text needs {dataCount}.", + nameof(text)); + } + + // The data fills the data positions of the rows in order, and the numeric shift pads the positions it leaves. + codes = codes[..(rows * CodeCharactersPerRow)]; + for (int row = 0; row < rows; row++) + { + int perRow = row < rows - 1 ? 7 : (rows <= 6 ? 2 : 0); + Span slots = codes.Slice(row * CodeCharactersPerRow, perRow); + int taken = Math.Min(perRow, data.Length); + data[..taken].CopyTo(slots); + slots[taken..].Fill(NumericShift); + data = data[taken..]; + } + + // Section 2.1.2: the last code character of every row is the modulo 49 sum of the other seven. + for (int row = 0; row < rows - 1; row++) + { + Span rowCodes = codes.Slice(row * CodeCharactersPerRow, CodeCharactersPerRow); + rowCodes[7] = RowCheckCharacter(rowCodes); + } + + // Section 2.1.3: Cr7 = 7(r - 2) + M. + int last = (rows - 1) * CodeCharactersPerRow; + int rowCount = (7 * (rows - MinimumRows)) + (int)mode; + codes[last + 6] = rowCount; + + // Formula 2 with the weights of Table 5: the symbol characters of every row but the last, in order, then Wr1 for the Y and X sums and Wr2 for the X + // sum. + ReadOnlySpan weights = Weights; + long x = 20L * rowCount; + long y = 16L * rowCount; + long z = 38L * rowCount; + int position = 0; + for (int row = 0; row < rows - 1; row++) + { + for (int j = 0; j < SymbolCharactersPerRow; j++) + { + int w = SymbolCharacter(codes, (row * CodeCharactersPerRow) + (j * 2)); + x += weights[position] * w; + y += weights[position + 1] * w; + z += weights[position + 2] * w; + position++; + } + } + + if (rows > 6) + { + int wr1 = (int)(z % SymbolModulus); + codes[last] = wr1 / RowModulus; + codes[last + 1] = wr1 % RowModulus; + } + + int first1 = SymbolCharacter(codes, last); + x += weights[position] * first1; + y += weights[position + 1] * first1; + position++; + + int wr2 = (int)(y % SymbolModulus); + codes[last + 2] = wr2 / RowModulus; + codes[last + 3] = wr2 % RowModulus; + x += weights[position] * wr2; + + int wr3 = (int)(x % SymbolModulus); + codes[last + 4] = wr3 / RowModulus; + codes[last + 5] = wr3 % RowModulus; + + codes[last + 7] = RowCheckCharacter(codes[last..]); + return codes.Length; + } + + /// + /// Writes the symbol from its code characters: one row of bars per eight, one module separator bars between the rows, and the top and bottom separator bars + /// of section 2.1. + /// + /// The code character values, eight per row. + /// The human readable interpretation. + /// The options that control layout choices. + /// The symbol to write. + private static void WriteRows(ReadOnlySpan codes, ReadOnlySpan readable, BarcodeOptions options, ref BarcodeSymbol symbol) + { + int rowCount = codes.Length / CodeCharactersPerRow; + double xDimension = options.ResolveXDimension(MinimumXDimension, BarcodeSymbology.DefaultXDimension, double.PositiveInfinity); + + // Section 3.3 recommends "a minimum bar height of 8X" and sets no limit, so the bar height is not clamped. + float barHeight = options.ResolveBarHeight(xDimension, 0, NominalBarHeight, float.PositiveInfinity); + ReadOnlySpan parities = RowParity; + ReadOnlySpan even = Code49Patterns.Even; + ReadOnlySpan odd = Code49Patterns.Odd; + + // A separator between two rows is one bar across the row width. + int separators = rowCount - 1; + symbol.BeginStacked(rowCount + separators, (rowCount * RowRunCount) + separators, (rowCount * ((RowRunCount + 1) >> 1)) + separators, xDimension); + for (int row = 0; row < rowCount; row++) + { + // Section 2.1.5: the start pattern is a one module bar and a one module space, and the stop pattern a four module bar. Every symbol character + // begins with a bar and ends with a space, so the row is one run of bits from the start bar to the stop bar. + int parity = row < rowCount - 1 ? parities[row] : 0; + Span runs = symbol.AddRow(RowRunCount, out Span heights, out _); + heights.Fill(barHeight); + runs[0] = 1; + runs[1] = 1; + int length = 2; + bool bar = false; + for (int j = 0; j < SymbolCharactersPerRow; j++) + { + int w = SymbolCharacter(codes, (row * CodeCharactersPerRow) + (j * 2)); + bool isOdd = ((parity >> (SymbolCharactersPerRow - 1 - j)) & 1) == 1; + ushort bits = isOdd ? odd[w] : even[w]; + for (int shift = 15; shift >= 0; shift--) + { + bool set = ((bits >> shift) & 1) == 1; + if (set == bar) + { + runs[length - 1]++; + } + else + { + runs[length++] = 1; + bar = set; + } + } + } + + runs[length] = 4; + if (row < rowCount - 1) + { + Span separator = symbol.AddRow(1, out Span separatorHeight, out _); + separator[0] = RowWidth; + separatorHeight[0] = SeparatorHeight; + } + } + + // Appendix C: the human readable interpretation "may accompany the symbol. It should not interfere with the symbol itself nor the quiet zones." + float heightInModules = (rowCount * barHeight) + ((rowCount + 1) * SeparatorHeight); + if (options.Font is not null && readable.Length > 0) + { + symbol.ReserveText(1, readable.Length); + symbol.AddText(readable, 0F, RowWidth, BarcodeTextSide.BelowBars, heightInModules + BarcodeTextPlacement.Clearance); + } + + symbol.EndStacked(SeparatorHeight, LeadingQuietZone, TrailingQuietZone); + } + + /// + /// Returns the number of data code characters a symbol of the given row count carries: seven per row before the last, and two in the last row of a symbol + /// with up to six rows, Table 4. + /// + /// The number of rows. + /// The number of data code characters. + private static int Capacity(int rows) => (7 * (rows - 1)) + (rows <= 6 ? 2 : 0); + + /// + /// Returns the row check character of a row, section 2.1.2: the modulo 49 sum of its first seven code characters. + /// + /// The code characters of the row, from its first. + /// The row check character. + private static int RowCheckCharacter(ReadOnlySpan row) + { + int sum = 0; + foreach (int code in row[..7]) + { + sum += code; + } + + return sum % RowModulus; + } + + /// + /// Returns the value of the symbol character at a code character position, Formula 1: W = 49 C1 + C2. + /// + /// The code characters. + /// The index of the first code character of the pair. + /// The symbol character value. + private static int SymbolCharacter(ReadOnlySpan codes, int index) + => (codes[index] * RowModulus) + codes[index + 1]; + + /// + /// Writes a run of digits by the numeric encodation method of section 2.2.2: three code characters per five digits, then one for a remainder of one, two + /// for three, three for four with 100,000 added, and a run whose remainder is two ends in four digits then three. + /// + /// The digit run. + /// The buffer the code characters are written to. + /// The number of values written so far. + /// The number of values written after the run. + private static int WriteNumeric(ReadOnlySpan digits, Span values, int count) + { + int remainder = digits.Length % NumericBlock; + int blocks = remainder == 2 ? digits.Length - 7 : digits.Length - remainder; + int at = 0; + for (; at < blocks; at += NumericBlock) + { + count = WriteBase48(Number(digits.Slice(at, NumericBlock)), 3, values, count); + } + + switch (digits.Length - at) + { + case 1: + values[count++] = digits[at] - '0'; + break; + case 3: + count = WriteBase48(Number(digits.Slice(at, 3)), 2, values, count); + break; + case 4: + count = WriteBase48(FourDigitOffset + Number(digits.Slice(at, 4)), 3, values, count); + break; + case 7: + count = WriteBase48(FourDigitOffset + Number(digits.Slice(at, 4)), 3, values, count); + count = WriteBase48(Number(digits.Slice(at + 4, 3)), 2, values, count); + break; + } + + return count; + } + + /// + /// Writes a number as code characters in base 48, most significant first. + /// + /// The number, less than 48 to the power of . + /// The number of code characters. + /// The buffer the code characters are written to. + /// The number of values written so far. + /// The number of values written after the number. + private static int WriteBase48(int number, int characters, Span values, int count) + { + int divisor = 1; + for (int k = 1; k < characters; k++) + { + divisor *= NumericBase; + } + + for (; divisor > 0; divisor /= NumericBase) + { + values[count++] = number / divisor; + number %= divisor; + } + + return count; + } + + /// + /// Returns the value of a digit string. + /// + /// The digits. + /// The value. + private static int Number(ReadOnlySpan digits) + { + int value = 0; + foreach (char digit in digits) + { + value = (value * 10) + (digit - '0'); + } + + return value; + } + + /// + /// Returns the code character of an ASCII character by Table 7, with the Shift 1 or Shift 2 that precedes it when the character is outside the alphanumeric + /// set of Table 6. + /// + /// The character. + /// The shift code character that precedes the value, or zero for none. + /// The code character value from Table 6. + private static int CodeCharacter(char c, out int shift) + { + int set = AsciiShifts[c]; + shift = set == 0 ? 0 : set == 1 ? ShiftOne : ShiftTwo; + return AsciiValues[c]; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code49/Code49Patterns.cs b/src/ImageSharp.Drawing/Barcodes/Code49/Code49Patterns.cs new file mode 100644 index 00000000..c70c78a1 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code49/Code49Patterns.cs @@ -0,0 +1,428 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The symbol character patterns of Code 49, Appendix F of AIM USS-49: for every symbol character value 0 to 2400, one pattern of even parity and one of odd +/// parity. A pattern is 16 modules, four bars and four spaces of one to six modules starting with a bar, held as one bit per module from the most significant +/// bit, a set bit for a bar. The parity is that of the sum of the bar modules. +/// +internal static class Code49Patterns +{ + /// + /// Gets the even parity pattern of every symbol character value. + /// + public static ReadOnlySpan Even => + [ + 0xBE5C, 0xC16E, 0x86DC, 0xC126, 0x864C, 0x9EDC, 0xC726, 0x9E4C, 0xDF26, 0x82CC, 0x8244, 0x8ECC, + 0xC322, 0x8E44, 0xBECC, 0xCF22, 0xBE44, 0xC162, 0x86C4, 0xC762, 0x9EC4, 0xDF62, 0x812E, 0x872E, + 0x9F2E, 0x836E, 0x8326, 0x8F6E, 0x8F26, 0xBF6E, 0x8166, 0x8122, 0x8766, 0x8722, 0x9F66, 0x9F22, + 0x8362, 0x8F62, 0xBF62, 0xA2E0, 0xE8B8, 0xFA2E, 0xD370, 0xF4DC, 0xD130, 0xF44C, 0xAEE0, 0xEBB8, + 0xFAEE, 0xA660, 0xE998, 0xFA66, 0xA220, 0xE888, 0xFA22, 0xD730, 0xF5CC, 0xD310, 0xF4C4, 0xAE20, + 0xEB88, 0xFAE2, 0x9170, 0xE45C, 0xD8B8, 0xF62E, 0xC9B8, 0xF26E, 0xB370, 0xC898, 0xF226, 0xB130, + 0xEC4C, 0x9770, 0xE5DC, 0x9330, 0xE4CC, 0x9110, 0xE444, 0xD888, 0xF622, 0xCB98, 0xF2E6, 0xB730, + 0xC988, 0xF262, 0xB310, 0xECC4, 0x9710, 0xE5C4, 0xDB88, 0xF6E2, 0x88B8, 0xE22E, 0xCC5C, 0xB8B8, + 0xEE2E, 0xC4DC, 0x99B8, 0xC44C, 0x9898, 0xE626, 0xDC4C, 0x8BB8, 0xE2EE, 0x8998, 0xE266, 0xBBB8, + 0x8888, 0xE222, 0xB998, 0xCC44, 0xB888, 0xEE22, 0xC5CC, 0x9B98, 0xC4C4, 0x9988, 0xE662, 0xDCC4, + 0x8B88, 0xE2E2, 0xCDC4, 0xBB88, 0xEEE2, 0x845C, 0xC62E, 0x9C5C, 0xDE2E, 0xC26E, 0x8CDC, 0xC226, + 0x8C4C, 0xBCDC, 0xCE26, 0xBC4C, 0x85DC, 0x84CC, 0x9DDC, 0x8444, 0x9CCC, 0xC622, 0x9C44, 0xDE22, + 0xC2E6, 0x8DCC, 0xC262, 0x8CC4, 0xBDCC, 0xCE62, 0xBCC4, 0x85C4, 0xC6E2, 0x9DC4, 0xDEE2, 0x822E, + 0x8E2E, 0x866E, 0x8626, 0x9E6E, 0x9E26, 0x82EE, 0x8266, 0x8EEE, 0x8222, 0x8E66, 0xBEEE, 0x8E22, + 0xBE66, 0x86E6, 0x8662, 0x9EE6, 0x9E62, 0x82E2, 0x8EE2, 0xBEE2, 0xA170, 0xE85C, 0xD1B8, 0xF46E, + 0xD098, 0xF426, 0xA770, 0xE9DC, 0xA330, 0xE8CC, 0xA110, 0xE844, 0xD7B8, 0xF5EE, 0xD398, 0xF4E6, + 0xD188, 0xF462, 0xAF30, 0xEBCC, 0xA710, 0xE9C4, 0xD788, 0xF5E2, 0x90B8, 0xE42E, 0xD85C, 0xC8DC, + 0xB1B8, 0xC84C, 0xB098, 0xEC26, 0x93B8, 0xE4EE, 0x9198, 0xE466, 0x9088, 0xE422, 0xD844, 0xCBDC, + 0xB7B8, 0xC9CC, 0xB398, 0xC8C4, 0xB188, 0xEC62, 0x9798, 0xE5E6, 0x9388, 0xE4E2, 0xD9C4, 0xCBC4, + 0xB788, 0xEDE2, 0x885C, 0xCC2E, 0xB85C, 0xC46E, 0x98DC, 0xC426, 0x984C, 0xDC26, 0x89DC, 0x88CC, + 0xB9DC, 0x8844, 0xB8CC, 0xCC22, 0xB844, 0xC5EE, 0x9BDC, 0xC4E6, 0x99CC, 0xC462, 0x98C4, 0xDC62, + 0x8BCC, 0x89C4, 0xBBCC, 0xCCE2, 0xB9C4, 0xC5E2, 0x9BC4, 0xDDE2, 0x842E, 0x9C2E, 0x8C6E, 0x8C26, + 0xBC6E, 0x84EE, 0x8466, 0x9CEE, 0x8422, 0x9C66, 0x9C22, 0x8DEE, 0x8CE6, 0xBDEE, 0x8C62, 0xBCE6, + 0xBC62, 0x85E6, 0x84E2, 0x9DE6, 0x9CE2, 0x8DE2, 0xBDE2, 0xA0B8, 0xE82E, 0xD0DC, 0xD04C, 0xA3B8, + 0xE8EE, 0xA198, 0xE866, 0xA088, 0xE822, 0xD3DC, 0xD1CC, 0xD0C4, 0xAFB8, 0xEBEE, 0xA798, 0xE9E6, + 0xA388, 0xE8E2, 0xD7CC, 0xD3C4, 0x905C, 0xD82E, 0xC86E, 0xB0DC, 0xC826, 0xB04C, 0x91DC, 0x90CC, + 0x9044, 0xD822, 0xC9EE, 0xB3DC, 0xC8E6, 0xB1CC, 0xC862, 0xB0C4, 0x97DC, 0x93CC, 0x91C4, 0xD8E2, + 0xCBE6, 0xB7CC, 0xC9E2, 0xB3C4, 0x882E, 0x986E, 0x9826, 0x88EE, 0x8866, 0xB8EE, 0x8822, 0xB866, + 0x99EE, 0x98E6, 0x9862, 0x8BEE, 0x89E6, 0xBBEE, 0x88E2, 0xB9E6, 0xB8E2, 0x9BE6, 0x99E2, 0xA05C, + 0xD06E, 0xD026, 0xA1DC, 0xA0CC, 0xA044, 0xD1EE, 0xD0E6, 0xD062, 0xA7DC, 0xA3CC, 0xA1C4, 0xD7EE, + 0xD3E6, 0xD1E2, 0x902E, 0xB06E, 0x90EE, 0x9066, 0x9022, 0xB1EE, 0xB0E6, 0xB062, 0x93EE, 0x91E6, + 0x90E2, 0xB7EE, 0xB3E6, 0xB1E2, 0xA9C0, 0xEA70, 0xFA9C, 0xD460, 0xF518, 0xFD46, 0xA840, 0xEA10, + 0xFA84, 0xED78, 0xFB5E, 0x94E0, 0xE538, 0xF94E, 0xDA70, 0xF69C, 0xCA30, 0xF28C, 0xB460, 0xED18, + 0xFB46, 0x9420, 0xE508, 0xF942, 0xDA10, 0xF684, 0x9AF0, 0xE6BC, 0xDD78, 0xF75E, 0x8A70, 0xE29C, + 0xCD38, 0xF34E, 0xBA70, 0xEE9C, 0xC518, 0xF146, 0x9A30, 0xE68C, 0xDD18, 0xF746, 0x8A10, 0xE284, + 0xCD08, 0xF342, 0xBA10, 0xEE84, 0x8D78, 0xE35E, 0xCEBC, 0xBD78, 0xEF5E, 0x8538, 0xE14E, 0xC69C, + 0x9D38, 0xE74E, 0xDE9C, 0xC28C, 0x8D18, 0xE346, 0xCE8C, 0xBD18, 0xEF46, 0x8508, 0xE142, 0xC684, + 0x9D08, 0xE742, 0xDE84, 0x86BC, 0xC75E, 0x9EBC, 0xDF5E, 0x829C, 0xC34E, 0x8E9C, 0xCF4E, 0xBE9C, + 0xC146, 0x868C, 0xC746, 0x9E8C, 0xDF46, 0x8284, 0xC342, 0x8E84, 0xCF42, 0xBE84, 0x835E, 0x8F5E, + 0xBF5E, 0x814E, 0x874E, 0x9F4E, 0x8346, 0x8F46, 0xBF46, 0x8142, 0x8742, 0x9F42, 0xD2F0, 0xF4BC, + 0xADE0, 0xEB78, 0xFADE, 0xA4E0, 0xE938, 0xFA4E, 0xD670, 0xF59C, 0xD230, 0xF48C, 0xAC60, 0xEB18, + 0xFAC6, 0xA420, 0xE908, 0xFA42, 0xD610, 0xF584, 0xC978, 0xF25E, 0xB2F0, 0xECBC, 0x96F0, 0xE5BC, + 0x9270, 0xE49C, 0xD938, 0xF64E, 0xCB38, 0xF2CE, 0xB670, 0xC918, 0xF246, 0xB230, 0xEC8C, 0x9630, + 0xE58C, 0x9210, 0xE484, 0xD908, 0xF642, 0xCB08, 0xF2C2, 0xB610, 0xED84, 0xC4BC, 0x9978, 0xE65E, + 0xDCBC, 0x8B78, 0xE2DE, 0x8938, 0xE24E, 0xBB78, 0xCC9C, 0xB938, 0xEE4E, 0xC59C, 0x9B38, 0xC48C, + 0x9918, 0xE646, 0xDC8C, 0x8B18, 0xE2C6, 0x8908, 0xE242, 0xBB18, 0xCC84, 0xB908, 0xEE42, 0xC584, + 0x9B08, 0xE6C2, 0xDD84, 0xC25E, 0x8CBC, 0xCE5E, 0xBCBC, 0x85BC, 0x849C, 0x9DBC, 0xC64E, 0x9C9C, + 0xDE4E, 0xC2CE, 0x8D9C, 0xC246, 0x8C8C, 0xBD9C, 0xCE46, 0xBC8C, 0x858C, 0x8484, 0x9D8C, 0xC642, + 0x9C84, 0xDE42, 0xC2C2, 0x8D84, 0xCEC2, 0xBD84, 0x865E, 0x9E5E, 0x82DE, 0x824E, 0x8EDE, 0x8E4E, + 0xBEDE, 0xBE4E, 0x86CE, 0x8646, 0x9ECE, 0x9E46, 0x82C6, 0x8242, 0x8EC6, 0x8E42, 0xBEC6, 0xBE42, + 0x86C2, 0x9EC2, 0xD178, 0xF45E, 0xA6F0, 0xE9BC, 0xA270, 0xE89C, 0xD778, 0xF5DE, 0xD338, 0xF4CE, + 0xD118, 0xF446, 0xAE70, 0xEB9C, 0xA630, 0xE98C, 0xA210, 0xE884, 0xD718, 0xF5C6, 0xD308, 0xF4C2, + 0xAE10, 0xEB84, 0xC8BC, 0xB178, 0xEC5E, 0x9378, 0xE4DE, 0x9138, 0xE44E, 0xD89C, 0xCBBC, 0xB778, + 0xC99C, 0xB338, 0xC88C, 0xB118, 0xEC46, 0x9738, 0xE5CE, 0x9318, 0xE4C6, 0x9108, 0xE442, 0xD884, + 0xCB8C, 0xB718, 0xC984, 0xB308, 0xECC2, 0x9708, 0xE5C2, 0xDB84, 0xC45E, 0x98BC, 0xDC5E, 0x89BC, + 0x889C, 0xB9BC, 0xCC4E, 0xB89C, 0xC5DE, 0x9BBC, 0xC4CE, 0x999C, 0xC446, 0x988C, 0xDC46, 0x8B9C, + 0x898C, 0xBB9C, 0x8884, 0xB98C, 0xCC42, 0xB884, 0xC5C6, 0x9B8C, 0xC4C2, 0x9984, 0xDCC2, 0x8B84, + 0xCDC2, 0xBB84, 0x8C5E, 0xBC5E, 0x84DE, 0x844E, 0x9CDE, 0x9C4E, 0x8DDE, 0x8CCE, 0xBDDE, 0x8C46, + 0xBCCE, 0xBC46, 0x85CE, 0x84C6, 0x9DCE, 0x8442, 0x9CC6, 0x9C42, 0x8DC6, 0x8CC2, 0xBDC6, 0xBCC2, + 0x85C2, 0x9DC2, 0xD0BC, 0xA378, 0xE8DE, 0xA138, 0xE84E, 0xD3BC, 0xD19C, 0xD08C, 0xAF78, 0xEBDE, + 0xA738, 0xE9CE, 0xA318, 0xE8C6, 0xA108, 0xE842, 0xD79C, 0xD38C, 0xD184, 0xAF18, 0xEBC6, 0xA708, + 0xE9C2, 0xC85E, 0xB0BC, 0x91BC, 0x909C, 0xD84E, 0xC9DE, 0xB3BC, 0xC8CE, 0xB19C, 0xC846, 0xB08C, + 0x97BC, 0x939C, 0x918C, 0x9084, 0xD842, 0xCBCE, 0xB79C, 0xC9C6, 0xB38C, 0xC8C2, 0xB184, 0x978C, + 0x9384, 0xD9C2, 0x985E, 0x88DE, 0x884E, 0xB8DE, 0xB84E, 0x99DE, 0x98CE, 0x9846, 0x8BDE, 0x89CE, + 0xBBDE, 0x88C6, 0xB9CE, 0x8842, 0xB8C6, 0xB842, 0x9BCE, 0x99C6, 0x98C2, 0x8BC6, 0x89C2, 0xBBC6, + 0xB9C2, 0xD05E, 0xA1BC, 0xA09C, 0xD1DE, 0xD0CE, 0xD046, 0xA7BC, 0xA39C, 0xA18C, 0xA084, 0xD7DE, + 0xD3CE, 0xD1C6, 0xD0C2, 0xAF9C, 0xA78C, 0xA384, 0xB05E, 0x90DE, 0x904E, 0xB1DE, 0xB0CE, 0xB046, + 0x93DE, 0x91CE, 0x90C6, 0x9042, 0xB7DE, 0xB3CE, 0xB1C6, 0xB0C2, 0x97CE, 0x93C6, 0x91C2, 0xA0DE, + 0xA04E, 0xA3DE, 0xA1CE, 0xA0C6, 0xA042, 0xAFDE, 0xA7CE, 0xA3C6, 0xA1C2, 0xD4F0, 0xF53C, 0xA8E0, + 0xEA38, 0xFA8E, 0xD430, 0xF50C, 0xA820, 0xEA08, 0xFA82, 0xDAF8, 0xF6BE, 0xCA78, 0xF29E, 0xB4F0, + 0xED3C, 0x9470, 0xE51C, 0xDA38, 0xF68E, 0xCA18, 0xF286, 0xB430, 0xED0C, 0x9410, 0xE504, 0xDA08, + 0xF682, 0xCD7C, 0xBAF8, 0xEEBE, 0xC53C, 0x9A78, 0xE69E, 0xDD3C, 0x8A38, 0xE28E, 0xCD1C, 0xBA38, + 0xEE8E, 0xC50C, 0x9A18, 0xE686, 0xDD0C, 0x8A08, 0xE282, 0xCD04, 0xBA08, 0xEE82, 0xC6BE, 0x9D7C, + 0xDEBE, 0xC29E, 0x8D3C, 0xCE9E, 0xBD3C, 0x851C, 0xC68E, 0x9D1C, 0xDE8E, 0xC286, 0x8D0C, 0xCE86, + 0xBD0C, 0x8504, 0xC682, 0x9D04, 0xDE82, 0x8EBE, 0xBEBE, 0x869E, 0x9E9E, 0x828E, 0x8E8E, 0xBE8E, + 0x8686, 0x9E86, 0x8282, 0x8E82, 0xBE82, 0xE97C, 0xD6F8, 0xF5BE, 0xD278, 0xF49E, 0xACF0, 0xEB3C, + 0xA470, 0xE91C, 0xD638, 0xF58E, 0xD218, 0xF486, 0xAC30, 0xEB0C, 0xA410, 0xE904, 0xD608, 0xF582, + 0x92F8, 0xE4BE, 0xD97C, 0xCB7C, 0xB6F8, 0xC93C, 0xB278, 0xEC9E, 0x9678, 0xE59E, 0x9238, 0xE48E, + 0xD91C, 0xCB1C, 0xB638, 0xC90C, 0xB218, 0xEC86, 0x9618, 0xE586, 0x9208, 0xE482, 0xD904, 0xCB04, + 0xB608, 0xED82, 0x897C, 0xCCBE, 0xB97C, 0xC5BE, 0x9B7C, 0xC49E, 0x993C, 0xDC9E, 0x8B3C, 0x891C, + 0xBB3C, 0xCC8E, 0xB91C, 0xC58E, 0x9B1C, 0xC486, 0x990C, 0xDC86, 0x8B0C, 0x8904, 0xBB0C, 0xCC82, + 0xB904, 0xC582, 0x9B04, 0xDD82, 0x84BE, 0x9CBE, 0x8DBE, 0x8C9E, 0xBDBE, 0xBC9E, 0x859E, 0x848E, + 0x9D9E, 0x9C8E, 0x8D8E, 0x8C86, 0xBD8E, 0xBC86, 0x8586, 0x8482, 0x9D86, 0x9C82, 0x8D82, 0xBD82, + 0xA2F8, 0xE8BE, 0xD37C, 0xD13C, 0xAEF8, 0xEBBE, 0xA678, 0xE99E, 0xA238, 0xE88E, 0xD73C, 0xD31C, + 0xD10C, 0xAE38, 0xEB8E, 0xA618, 0xE986, 0xA208, 0xE882, 0xD70C, 0xD304, 0x917C, 0xD8BE, 0xC9BE, + 0xB37C, 0xC89E, 0xB13C, 0x977C, 0x933C, 0x911C, 0xD88E, 0xCB9E, 0xB73C, 0xC98E, 0xB31C, 0xC886, + 0xB10C, 0x971C, 0x930C, 0x9104, 0xD882, 0xCB86, 0xB70C, 0xC982, 0xB304, 0x88BE, 0xB8BE, 0x99BE, + 0x989E, 0x8BBE, 0x899E, 0xBBBE, 0x888E, 0xB99E, 0xB88E, 0x9B9E, 0x998E, 0x9886, 0x8B8E, 0x8986, + 0xBB8E, 0x8882, 0xB986, 0xB882, 0x9B86, 0x9982, 0xA17C, 0xD1BE, 0xD09E, 0xA77C, 0xA33C, 0xA11C, + 0xD7BE, 0xD39E, 0xD18E, 0xD086, 0xAF3C, 0xA71C, 0xA30C, 0xA104, 0xD78E, 0xD386, 0xD182, 0x90BE, + 0xB1BE, 0xB09E, 0x93BE, 0x919E, 0x908E, 0xB7BE, 0xB39E, 0xB18E, 0xB086, 0x979E, 0x938E, 0x9186, + 0x9082, 0xB78E, 0xB386, 0xB182, 0xA0BE, 0xA3BE, 0xA19E, 0xA08E, 0xAFBE, 0xA79E, 0xA38E, 0xA186, + 0xA082, 0xA9F0, 0xEA7C, 0xD478, 0xF51E, 0xA870, 0xEA1C, 0xD418, 0xF506, 0xA810, 0xEA04, 0xED7E, + 0x94F8, 0xE53E, 0xDA7C, 0xCA3C, 0xB478, 0xED1E, 0x9438, 0xE50E, 0xDA1C, 0xCA0C, 0xB418, 0xED06, + 0x9408, 0xE502, 0xDA04, 0x9AFC, 0xDD7E, 0x8A7C, 0xCD3E, 0xBA7C, 0xC51E, 0x9A3C, 0xDD1E, 0x8A1C, + 0xCD0E, 0xBA1C, 0xC506, 0x9A0C, 0xDD06, 0x8A04, 0xCD02, 0xBA04, 0x8D7E, 0xBD7E, 0x853E, 0x9D3E, + 0x8D1E, 0xBD1E, 0x850E, 0x9D0E, 0x8D06, 0xBD06, 0x8502, 0x9D02, 0xD2FC, 0xADF8, 0xEB7E, 0xA4F8, + 0xE93E, 0xD67C, 0xD23C, 0xAC78, 0xEB1E, 0xA438, 0xE90E, 0xD61C, 0xD20C, 0xAC18, 0xEB06, 0xA408, + 0xE902, 0xC97E, 0xB2FC, 0x96FC, 0x927C, 0xD93E, 0xCB3E, 0xB67C, 0xC91E, 0xB23C, 0x963C, 0x921C, + 0xD90E, 0xCB0E, 0xB61C, 0xC906, 0xB20C, 0x960C, 0x9204, 0xD902, 0x997E, 0x8B7E, 0x893E, 0xBB7E, + 0xB93E, 0xE4A0, 0xF928, 0xD940, 0xF650, 0xFD94, 0xCB40, 0xF2D0, 0xEDA0, 0xFB68, 0x8940, 0xE250, + 0xCCA0, 0xF328, 0xB940, 0xEE50, 0xFB94, 0xC5A0, 0xF168, 0x9B40, 0xE6D0, 0xF9B4, 0xDDA0, 0xF768, + 0xFDDA, 0x84A0, 0xE128, 0xC650, 0xF194, 0x9CA0, 0xE728, 0xF9CA, 0xDE50, 0xF794, 0xC2D0, 0x8DA0, + 0xE368, 0xCED0, 0xF3B4, 0xBDA0, 0xEF68, 0xFBDA, 0x8250, 0xC328, 0x8E50, 0xE394, 0xCF28, 0xF3CA, + 0xBE50, 0xEF94, 0xC168, 0x86D0, 0xE1B4, 0xC768, 0xF1DA, 0x9ED0, 0xE7B4, 0xDF68, 0xF7DA, 0x8128, + 0xC194, 0x8728, 0xE1CA, 0xC794, 0x9F28, 0xE7CA, 0x8368, 0xC3B4, 0x8F68, 0xE3DA, 0xCFB4, 0xBF68, + 0xEFDA, 0xE8A0, 0xFA28, 0xD340, 0xF4D0, 0xFD34, 0xEBA0, 0xFAE8, 0x9140, 0xE450, 0xF914, 0xD8A0, + 0xF628, 0xFD8A, 0xC9A0, 0xF268, 0xB340, 0xECD0, 0xFB34, 0x9740, 0xE5D0, 0xF974, 0xDBA0, 0xF6E8, + 0xFDBA, 0x88A0, 0xE228, 0xCC50, 0xF314, 0xB8A0, 0xEE28, 0xFB8A, 0xC4D0, 0xF134, 0x99A0, 0xE668, + 0xF99A, 0xDCD0, 0xF734, 0x8BA0, 0xE2E8, 0xCDD0, 0xF374, 0xBBA0, 0xEEE8, 0xFBBA, 0x8450, 0xE114, + 0xC628, 0xF18A, 0x9C50, 0xE714, 0xDE28, 0xF78A, 0xC268, 0x8CD0, 0xE334, 0xCE68, 0xF39A, 0xBCD0, + 0xEF34, 0x85D0, 0xE174, 0xC6E8, 0xF1BA, 0x9DD0, 0xE774, 0xDEE8, 0xF7BA, 0x8228, 0xC314, 0x8E28, + 0xE38A, 0xCF14, 0xC134, 0x8668, 0xE19A, 0xC734, 0x9E68, 0xE79A, 0xDF34, 0x82E8, 0xC374, 0x8EE8, + 0xE3BA, 0xCF74, 0xBEE8, 0xEFBA, 0x8114, 0xC18A, 0x8714, 0xC78A, 0x8334, 0xC39A, 0x8F34, 0xCF9A, + 0x8174, 0xC1BA, 0x8774, 0xC7BA, 0x9F74, 0xDFBA, 0xA140, 0xE850, 0xFA14, 0xD1A0, 0xF468, 0xFD1A, + 0xA740, 0xE9D0, 0xFA74, 0xD7A0, 0xF5E8, 0xFD7A, 0x90A0, 0xE428, 0xF90A, 0xD850, 0xF614, 0xC8D0, + 0xF234, 0xB1A0, 0xEC68, 0xFB1A, 0x93A0, 0xE4E8, 0xF93A, 0xD9D0, 0xF674, 0xCBD0, 0xF2F4, 0xB7A0, + 0xEDE8, 0xFB7A, 0x8850, 0xE214, 0xCC28, 0xF30A, 0xB850, 0xEE14, 0xC468, 0xF11A, 0x98D0, 0xE634, + 0xDC68, 0xF71A, 0x89D0, 0xE274, 0xCCE8, 0xF33A, 0xB9D0, 0xEE74, 0xC5E8, 0xF17A, 0x9BD0, 0xE6F4, + 0xDDE8, 0xF77A, 0x8428, 0xE10A, 0xC614, 0x9C28, 0xE70A, 0xC234, 0x8C68, 0xE31A, 0xCE34, 0xBC68, + 0xEF1A, 0x84E8, 0xE13A, 0xC674, 0x9CE8, 0xE73A, 0xDE74, 0xC2F4, 0x8DE8, 0xE37A, 0xCEF4, 0xBDE8, + 0xEF7A, 0x8214, 0xC30A, 0x8E14, 0xC11A, 0x8634, 0xC71A, 0x9E34, 0x8274, 0xC33A, 0x8E74, 0xCF3A, + 0xBE74, 0xC17A, 0x86F4, 0xC77A, 0x9EF4, 0xDF7A, 0x810A, 0x870A, 0x831A, 0x8F1A, 0x813A, 0x873A, + 0x9F3A, 0x837A, 0x8F7A, 0xBF7A, 0xA0A0, 0xE828, 0xFA0A, 0xD0D0, 0xF434, 0xA3A0, 0xE8E8, 0xFA3A, + 0xD3D0, 0xF4F4, 0xAFA0, 0xEBE8, 0xFAFA, 0x9050, 0xE414, 0xD828, 0xF60A, 0xC868, 0xF21A, 0xB0D0, + 0xEC34, 0x91D0, 0xE474, 0xD8E8, 0xF63A, 0xC9E8, 0xF27A, 0xB3D0, 0xECF4, 0x97D0, 0xE5F4, 0xDBE8, + 0xF6FA, 0x8828, 0xE20A, 0xCC14, 0xC434, 0x9868, 0xE61A, 0xDC34, 0x88E8, 0xE23A, 0xCC74, 0xB8E8, + 0xEE3A, 0xC4F4, 0x99E8, 0xE67A, 0xDCF4, 0x8BE8, 0xE2FA, 0xCDF4, 0xBBE8, 0xEEFA, 0x8414, 0xC60A, + 0xC21A, 0x8C34, 0xCE1A, 0x8474, 0xC63A, 0x9C74, 0xDE3A, 0xC27A, 0x8CF4, 0xCE7A, 0xBCF4, 0x85F4, + 0xC6FA, 0x9DF4, 0xDEFA, 0x820A, 0x861A, 0x823A, 0x8E3A, 0x867A, 0x9E7A, 0x82FA, 0x8EFA, 0xBEFA, + 0xA050, 0xE814, 0xD068, 0xF41A, 0xA1D0, 0xE874, 0xD1E8, 0xF47A, 0xA7D0, 0xE9F4, 0xD7E8, 0xF5FA, + 0x9028, 0xE40A, 0xC834, 0xB068, 0xEC1A, 0x90E8, 0xE43A, 0xD874, 0xC8F4, 0xB1E8, 0xEC7A, 0x93E8, + 0xE4FA, 0xD9F4, 0xCBF4, 0xB7E8, 0xEDFA, 0x8814, 0xC41A, 0x9834, 0x8874, 0xCC3A, 0xB874, 0xC47A, + 0x98F4, 0xDC7A, 0x89F4, 0xCCFA, 0xB9F4, 0xC5FA, 0x9BF4, 0xDDFA, 0x840A, 0x8C1A, 0x843A, 0x9C3A, + 0x8C7A, 0xBC7A, 0x84FA, 0x9CFA, 0x8DFA, 0xBDFA, 0xEA40, 0xFA90, 0xED60, 0xFB58, 0xE520, 0xF948, + 0xDA40, 0xF690, 0xFDA4, 0x9AC0, 0xE6B0, 0xF9AC, 0xDD60, 0xF758, 0xFDD6, 0x8A40, 0xE290, 0xCD20, + 0xF348, 0xBA40, 0xEE90, 0xFBA4, 0x8D60, 0xE358, 0xCEB0, 0xF3AC, 0xBD60, 0xEF58, 0xFBD6, 0x8520, + 0xE148, 0xC690, 0xF1A4, 0x9D20, 0xE748, 0xF9D2, 0xDE90, 0xF7A4, 0x86B0, 0xE1AC, 0xC758, 0xF1D6, + 0x9EB0, 0xE7AC, 0xDF58, 0xF7D6, 0x8290, 0xC348, 0x8E90, 0xE3A4, 0xCF48, 0xF3D2, 0xBE90, 0xEFA4, + 0x8358, 0xC3AC, 0x8F58, 0xE3D6, 0xCFAC, 0xBF58, 0xEFD6, 0x8148, 0xC1A4, 0x8748, 0xE1D2, 0xC7A4, + 0x9F48, 0xE7D2, 0xDFA4, 0xD2C0, 0xF4B0, 0xFD2C, 0xEB60, 0xFAD8, 0xE920, 0xFA48, 0xD640, 0xF590, + 0xFD64, 0xC960, 0xF258, 0xB2C0, 0xECB0, 0xFB2C, 0x96C0, 0xE5B0, 0xF96C, 0x9240, 0xE490, 0xF924, + 0xD920, 0xF648, 0xFD92, 0xCB20, 0xF2C8, 0xB640, 0xED90, 0xFB64, 0xC4B0, 0xF12C, 0x9960, 0xE658, + 0xF996, 0xDCB0, 0xF72C, 0x8B60, 0xE2D8, 0x8920, 0xE248, 0xBB60, 0xCC90, 0xF324, 0xB920, 0xEE48, + 0xFB92, 0xC590, 0xF164, 0x9B20, 0xE6C8, 0xF9B2, 0xDD90, 0xF764, 0xC258, 0x8CB0, 0xE32C, 0xCE58, + 0xF396, 0xBCB0, 0xEF2C, 0x85B0, 0xE16C, 0x8490, 0xE124, 0x9DB0, 0xC648, 0xF192, 0x9C90, 0xE724, + 0xDE48, 0xF792, 0xC2C8, 0x8D90, 0xE364, 0xCEC8, 0xF3B2, 0xBD90, 0xEF64, 0xC12C, 0x8658, 0xE196, + 0xC72C, 0x9E58, 0xE796, 0xDF2C, 0x82D8, 0x8248, 0x8ED8, 0xC324, 0x8E48, 0xE392, 0xBED8, 0xCF24, + 0xBE48, 0xEF92, 0xC164, 0x86C8, 0xE1B2, 0xC764, 0x9EC8, 0xE7B2, 0xDF64, 0x832C, 0xC396, 0x8F2C, + 0xCF96, 0x816C, 0x8124, 0x876C, 0xC192, 0x8724, 0x9F6C, 0xC792, 0x9F24, 0x8364, 0xC3B2, 0x8F64, + 0xCFB2, 0xBF64, 0xD160, 0xF458, 0xFD16, 0xA6C0, 0xE9B0, 0xFA6C, 0xA240, 0xE890, 0xFA24, 0xD760, + 0xF5D8, 0xFD76, 0xD320, 0xF4C8, 0xFD32, 0xAE40, 0xEB90, 0xFAE4, 0xC8B0, 0xF22C, 0xB160, 0xEC58, + 0xFB16, 0x9360, 0xE4D8, 0xF936, 0x9120, 0xE448, 0xF912, 0xD890, 0xF624, 0xCBB0, 0xF2EC, 0xB760, + 0xC990, 0xF264, 0xB320, 0xECC8, 0xFB32, 0x9720, 0xE5C8, 0xF972, 0xDB90, 0xF6E4, 0xC458, 0xF116, + 0x98B0, 0xE62C, 0xDC58, 0xF716, 0x89B0, 0xE26C, 0x8890, 0xE224, 0xB9B0, 0xCC48, 0xF312, 0xB890, + 0xEE24, 0xC5D8, 0xF176, 0x9BB0, 0xC4C8, 0xF132, 0x9990, 0xE664, 0xDCC8, 0xF732, 0x8B90, 0xE2E4, + 0xCDC8, 0xF372, 0xBB90, 0xEEE4, 0xC22C, 0x8C58, 0xE316, 0xCE2C, 0xBC58, 0xEF16, 0x84D8, 0xE136, + 0x8448, 0xE112, 0x9CD8, 0xC624, 0x9C48, 0xE712, 0xDE24, 0xC2EC, 0x8DD8, 0xC264, 0x8CC8, 0xE332, + 0xBDD8, 0xCE64, 0xBCC8, 0xEF32, 0x85C8, 0xE172, 0xC6E4, 0x9DC8, 0xE772, 0xDEE4, 0xC116, 0x862C, + 0xC716, 0x9E2C, 0x826C, 0x8224, 0x8E6C, 0xC312, 0x8E24, 0xBE6C, 0xCF12, 0xC176, 0x86EC, 0xC132, + 0x8664, 0x9EEC, 0xC732, 0x9E64, 0xDF32, 0x82E4, 0xC372, 0x8EE4, 0xCF72, 0xBEE4, 0x8316, 0x8F16, + 0x8136, 0x8112, 0x8736, 0x8712, 0x9F36, 0x8376, 0x8332, 0x8F76, 0x8F32, 0xBF76, 0x8172, 0x8772, + 0x9F72, 0xD0B0, 0xF42C, 0xA360, 0xE8D8, 0xFA36, 0xA120, 0xE848, 0xFA12, 0xD3B0, 0xF4EC, 0xD190, + 0xF464, 0xAF60, 0xEBD8, 0xFAF6, 0xA720, 0xE9C8, 0xFA72, 0xD790, 0xF5E4, 0xC858, 0xF216, 0xB0B0, + 0xEC2C, 0x91B0, 0xE46C, 0x9090, 0xE424, 0xD848, 0xF612, 0xC9D8, 0xF276, 0xB3B0, 0xC8C8, 0xF232, + 0xB190, 0xEC64, 0x97B0, 0xE5EC, 0x9390, 0xE4E4, 0xD9C8, 0xF672, 0xCBC8, 0xF2F2, 0xB790, 0xEDE4, + 0xC42C, 0x9858, 0xE616, 0xDC2C, 0x88D8, 0xE236, 0x8848, 0xE212, 0xB8D8, 0xCC24, 0xB848, 0xEE12, + 0xC4EC, 0x99D8, 0xC464, 0x98C8, 0xE632, 0xDC64, 0x8BD8, 0xE2F6, 0x89C8, 0xE272, 0xBBD8, 0xCCE4, + 0xB9C8, 0xEE72, 0xC5E4, 0x9BC8, 0xE6F2, 0xDDE4, 0xC216, 0x8C2C, 0xCE16, 0x846C, 0x8424, 0x9C6C, + 0xC612, 0x9C24, 0xC276, 0x8CEC, 0xC232, 0x8C64, 0xBCEC, 0xCE32, 0xBC64, 0x85EC, 0x84E4, 0x9DEC, + 0xC672, 0x9CE4, 0xDE72, 0xC2F2, 0x8DE4, 0xCEF2, 0xBDE4, 0x8616, 0x8236, 0x8212, 0x8E36, 0x8E12, + 0x8676, 0x8632, 0x9E76, 0x9E32, 0x82F6, 0x8272, 0x8EF6, 0x8E72, 0xBEF6, 0xBE72, 0x86F2, 0x9EF2, + 0xD058, 0xF416, 0xA1B0, 0xE86C, 0xA090, 0xE824, 0xD1D8, 0xF476, 0xD0C8, 0xF432, 0xA7B0, 0xE9EC, + 0xA390, 0xE8E4, 0xD7D8, 0xF5F6, 0xD3C8, 0xF4F2, 0xAF90, 0xEBE4, 0xC82C, 0xB058, 0xEC16, 0x90D8, + 0xE436, 0x9048, 0xE412, 0xD824, 0xC8EC, 0xB1D8, 0xC864, 0xB0C8, 0xEC32, 0x93D8, 0xE4F6, 0x91C8, + 0xE472, 0xD8E4, 0xCBEC, 0xB7D8, 0xC9E4, 0xB3C8, 0xECF2, 0x97C8, 0xE5F2, 0xDBE4, 0xC416, 0x982C, + 0x886C, 0x8824, 0xB86C, 0xCC12, 0xC476, 0x98EC, 0xC432, 0x9864, 0xDC32, 0x89EC, 0x88E4, 0xB9EC, + 0xCC72, 0xB8E4, 0xC5F6, 0x9BEC, 0xC4F2, 0x99E4, 0xDCF2, 0x8BE4, 0xCDF2, 0xBBE4, 0x8C16, 0x8436, + 0x8412, 0x9C36, 0x8C76, 0x8C32, 0xBC76, 0x84F6, 0x8472, 0x9CF6, 0x9C72, 0x8DF6, 0x8CF2, 0xBDF6, + 0xBCF2, 0x85F2, 0x9DF2, 0xD02C, 0xA0D8, 0xE836, 0xA048, 0xE812, 0xD0EC, 0xD064, 0xA3D8, 0xE8F6, + 0xA1C8, 0xE872, 0xD3EC, 0xD1E4, 0xAFD8, 0xEBF6, 0xA7C8, 0xE9F2, 0xC816, 0x906C, 0x9024, 0xC876, + 0xB0EC, 0xC832, 0xB064, 0x91EC, 0x90E4, 0xD872, 0xC9F6, 0xB3EC, 0xC8F2, 0xB1E4, 0x97EC, 0x93E4, + 0xD9F2, 0x8836, 0x8812, 0x9876, 0x9832, 0x88F6, 0x8872, 0xB8F6, 0xB872, 0x99F6, 0x98F2, 0x8BF6, + 0x89F2, 0xBBF6, 0xB9F2, 0xD4C0, 0xF530, 0xFD4C, 0xEA20, 0xFA88, 0xDAE0, 0xF6B8, 0xFDAE, 0xCA60, + 0xF298, 0xB4C0, 0xED30, 0xFB4C, 0x9440, 0xE510, 0xF944, 0xDA20, 0xF688, 0xFDA2, 0xCD70, 0xF35C, + 0xBAE0, 0xEEB8, 0xFBAE, 0xC530, 0xF14C, 0x9A60, 0xE698, 0xF9A6, 0xDD30, 0xF74C, 0x8A20, 0xE288, + 0xCD10, 0xF344, 0xBA20, 0xEE88, 0xFBA2, 0xC6B8, 0xF1AE, 0x9D70, 0xE75C, 0xDEB8, 0xF7AE, 0xC298, + 0x8D30, 0xE34C, 0xCE98, 0xF3A6, 0xBD30, 0xEF4C, 0x8510, 0xE144, 0xC688, 0xF1A2, 0x9D10, 0xE744, + 0xDE88, 0xF7A2, 0xC35C, 0x8EB8, 0xE3AE, 0xCF5C, 0xBEB8, 0xEFAE, 0xC14C, 0x8698, 0xE1A6, 0xC74C, + 0x9E98, 0xE7A6, 0xDF4C, 0x8288, 0xC344, 0x8E88, 0xE3A2, 0xCF44, 0xBE88, 0xEFA2, 0xC1AE, 0x875C, + 0xC7AE, 0x9F5C, 0xDFAE, 0x834C, 0xC3A6, 0x8F4C, 0xCFA6, 0xBF4C, 0x8144, 0xC1A2, 0x8744, 0xC7A2, + 0x9F44, 0xDFA2, 0xE970, 0xFA5C, 0xD6E0, 0xF5B8, 0xFD6E, 0xD260, 0xF498, 0xFD26, 0xACC0, 0xEB30, + 0xFACC, 0xA440, 0xE910, 0xFA44, 0xD620, 0xF588, 0xFD62, 0x92E0, 0xE4B8, 0xF92E, 0xD970, 0xF65C, + 0xCB70, 0xF2DC, 0xB6E0, 0xC930, 0xF24C, 0xB260, 0xEC98, 0xFB26, 0x9660, 0xE598, 0xF966, 0x9220, + 0xE488, 0xF922, 0xD910, 0xF644, 0xCB10, 0xF2C4, 0xB620, 0xED88, 0xFB62, 0x8970, 0xE25C, 0xCCB8, + 0xF32E, 0xB970, 0xEE5C, 0xC5B8, 0xF16E, 0x9B70, 0xC498, 0xF126, 0x9930, 0xE64C, 0xDC98, 0xF726, + 0x8B30, 0xE2CC, 0x8910, 0xE244, 0xBB30, 0xCC88, 0xF322, 0xB910, 0xEE44, 0xC588, 0xF162, 0x9B10, + 0xE6C4, 0xDD88, 0xF762, 0x84B8, 0xE12E, 0xC65C, 0x9CB8, 0xE72E, 0xDE5C, 0xC2DC, 0x8DB8, 0xC24C, + 0x8C98, 0xE326, 0xBDB8, 0xCE4C, 0xBC98, 0xEF26, 0x8598, 0xE166, 0x8488, 0xE122, 0x9D98, 0xC644, + 0x9C88, 0xE722, 0xDE44, 0xC2C4, 0x8D88, 0xE362, 0xCEC4, 0xBD88, 0xEF62, 0x825C, 0xC32E, 0x8E5C, + 0xCF2E, + ]; + + /// + /// Gets the odd parity pattern of every symbol character value. + /// + public static ReadOnlySpan Odd => + [ + 0xC940, 0xF250, 0xECA0, 0xFB28, 0xE5A0, 0xF968, 0xDB40, 0xF6D0, 0xFDB4, 0xC4A0, 0xF128, 0x9940, + 0xE650, 0xF994, 0xDCA0, 0xF728, 0xFDCA, 0x8B40, 0xE2D0, 0xCDA0, 0xF368, 0xBB40, 0xEED0, 0xFBB4, + 0xC250, 0x8CA0, 0xE328, 0xCE50, 0xF394, 0xBCA0, 0xEF28, 0xFBCA, 0x85A0, 0xE168, 0xC6D0, 0xF1B4, + 0x9DA0, 0xE768, 0xF9DA, 0xDED0, 0xF7B4, 0xC128, 0x8650, 0xE194, 0xC728, 0xF1CA, 0x9E50, 0xE794, + 0xDF28, 0xF7CA, 0x82D0, 0xC368, 0x8ED0, 0xE3B4, 0xCF68, 0xF3DA, 0xBED0, 0xEFB4, 0x8328, 0xC394, + 0x8F28, 0xE3CA, 0xCF94, 0x8168, 0xC1B4, 0x8768, 0xE1DA, 0xC7B4, 0x9F68, 0xE7DA, 0xDFB4, 0xD140, + 0xF450, 0xFD14, 0xE9A0, 0xFA68, 0xD740, 0xF5D0, 0xFD74, 0xC8A0, 0xF228, 0xB140, 0xEC50, 0xFB14, + 0x9340, 0xE4D0, 0xF934, 0xD9A0, 0xF668, 0xFD9A, 0xCBA0, 0xF2E8, 0xB740, 0xEDD0, 0xFB74, 0xC450, + 0xF114, 0x98A0, 0xE628, 0xF98A, 0xDC50, 0xF714, 0x89A0, 0xE268, 0xCCD0, 0xF334, 0xB9A0, 0xEE68, + 0xFB9A, 0xC5D0, 0xF174, 0x9BA0, 0xE6E8, 0xF9BA, 0xDDD0, 0xF774, 0xC228, 0x8C50, 0xE314, 0xCE28, + 0xF38A, 0xBC50, 0xEF14, 0x84D0, 0xE134, 0xC668, 0xF19A, 0x9CD0, 0xE734, 0xDE68, 0xF79A, 0xC2E8, + 0x8DD0, 0xE374, 0xCEE8, 0xF3BA, 0xBDD0, 0xEF74, 0xC114, 0x8628, 0xE18A, 0xC714, 0x9E28, 0xE78A, + 0x8268, 0xC334, 0x8E68, 0xE39A, 0xCF34, 0xBE68, 0xEF9A, 0xC174, 0x86E8, 0xE1BA, 0xC774, 0x9EE8, + 0xE7BA, 0xDF74, 0x8314, 0xC38A, 0x8F14, 0x8134, 0xC19A, 0x8734, 0xC79A, 0x9F34, 0x8374, 0xC3BA, + 0x8F74, 0xCFBA, 0xBF74, 0xD0A0, 0xF428, 0xFD0A, 0xA340, 0xE8D0, 0xFA34, 0xD3A0, 0xF4E8, 0xFD3A, + 0xAF40, 0xEBD0, 0xFAF4, 0xC850, 0xF214, 0xB0A0, 0xEC28, 0xFB0A, 0x91A0, 0xE468, 0xF91A, 0xD8D0, + 0xF634, 0xC9D0, 0xF274, 0xB3A0, 0xECE8, 0xFB3A, 0x97A0, 0xE5E8, 0xF97A, 0xDBD0, 0xF6F4, 0xC428, + 0xF10A, 0x9850, 0xE614, 0xDC28, 0xF70A, 0x88D0, 0xE234, 0xCC68, 0xF31A, 0xB8D0, 0xEE34, 0xC4E8, + 0xF13A, 0x99D0, 0xE674, 0xDCE8, 0xF73A, 0x8BD0, 0xE2F4, 0xCDE8, 0xF37A, 0xBBD0, 0xEEF4, 0xC214, + 0x8C28, 0xE30A, 0xCE14, 0x8468, 0xE11A, 0xC634, 0x9C68, 0xE71A, 0xDE34, 0xC274, 0x8CE8, 0xE33A, + 0xCE74, 0xBCE8, 0xEF3A, 0x85E8, 0xE17A, 0xC6F4, 0x9DE8, 0xE77A, 0xDEF4, 0xC10A, 0x8614, 0xC70A, + 0x8234, 0xC31A, 0x8E34, 0xCF1A, 0xC13A, 0x8674, 0xC73A, 0x9E74, 0xDF3A, 0x82F4, 0xC37A, 0x8EF4, + 0xCF7A, 0xBEF4, 0x830A, 0x811A, 0x871A, 0x833A, 0x8F3A, 0x817A, 0x877A, 0x9F7A, 0xD050, 0xF414, + 0xA1A0, 0xE868, 0xFA1A, 0xD1D0, 0xF474, 0xA7A0, 0xE9E8, 0xFA7A, 0xD7D0, 0xF5F4, 0xC828, 0xF20A, + 0xB050, 0xEC14, 0x90D0, 0xE434, 0xD868, 0xF61A, 0xC8E8, 0xF23A, 0xB1D0, 0xEC74, 0x93D0, 0xE4F4, + 0xD9E8, 0xF67A, 0xCBE8, 0xF2FA, 0xB7D0, 0xEDF4, 0xC414, 0x9828, 0xE60A, 0x8868, 0xE21A, 0xCC34, + 0xB868, 0xEE1A, 0xC474, 0x98E8, 0xE63A, 0xDC74, 0x89E8, 0xE27A, 0xCCF4, 0xB9E8, 0xEE7A, 0xC5F4, + 0x9BE8, 0xE6FA, 0xDDF4, 0xC20A, 0x8C14, 0x8434, 0xC61A, 0x9C34, 0xC23A, 0x8C74, 0xCE3A, 0xBC74, + 0x84F4, 0xC67A, 0x9CF4, 0xDE7A, 0xC2FA, 0x8DF4, 0xCEFA, 0xBDF4, 0x860A, 0x821A, 0x8E1A, 0x863A, + 0x9E3A, 0x827A, 0x8E7A, 0xBE7A, 0x86FA, 0x9EFA, 0xD028, 0xF40A, 0xA0D0, 0xE834, 0xD0E8, 0xF43A, + 0xA3D0, 0xE8F4, 0xD3E8, 0xF4FA, 0xAFD0, 0xEBF4, 0xC814, 0x9068, 0xE41A, 0xD834, 0xC874, 0xB0E8, + 0xEC3A, 0x91E8, 0xE47A, 0xD8F4, 0xC9F4, 0xB3E8, 0xECFA, 0x97E8, 0xE5FA, 0xDBF4, 0xC40A, 0x8834, + 0xCC1A, 0xC43A, 0x9874, 0xDC3A, 0x88F4, 0xCC7A, 0xB8F4, 0xC4FA, 0x99F4, 0xDCFA, 0x8BF4, 0xCDFA, + 0xBBF4, 0x841A, 0x8C3A, 0x847A, 0x9C7A, 0x8CFA, 0xBCFA, 0x85FA, 0x9DFA, 0xF520, 0xFD48, 0xDAC0, + 0xF6B0, 0xFDAC, 0xCA40, 0xF290, 0xED20, 0xFB48, 0xCD60, 0xF358, 0xBAC0, 0xEEB0, 0xFBAC, 0xC520, + 0xF148, 0x9A40, 0xE690, 0xF9A4, 0xDD20, 0xF748, 0xFDD2, 0xC6B0, 0xF1AC, 0x9D60, 0xE758, 0xF9D6, + 0xDEB0, 0xF7AC, 0xC290, 0x8D20, 0xE348, 0xCE90, 0xF3A4, 0xBD20, 0xEF48, 0xFBD2, 0xC358, 0x8EB0, + 0xE3AC, 0xCF58, 0xF3D6, 0xBEB0, 0xEFAC, 0xC148, 0x8690, 0xE1A4, 0xC748, 0xF1D2, 0x9E90, 0xE7A4, + 0xDF48, 0xF7D2, 0xC1AC, 0x8758, 0xE1D6, 0xC7AC, 0x9F58, 0xE7D6, 0xDFAC, 0x8348, 0xC3A4, 0x8F48, + 0xE3D2, 0xCFA4, 0xBF48, 0xEFD2, 0xE960, 0xFA58, 0xD6C0, 0xF5B0, 0xFD6C, 0xD240, 0xF490, 0xFD24, + 0xEB20, 0xFAC8, 0x92C0, 0xE4B0, 0xF92C, 0xD960, 0xF658, 0xFD96, 0xCB60, 0xF2D8, 0xB6C0, 0xC920, + 0xF248, 0xB240, 0xEC90, 0xFB24, 0x9640, 0xE590, 0xF964, 0xDB20, 0xF6C8, 0xFDB2, 0x8960, 0xE258, + 0xCCB0, 0xF32C, 0xB960, 0xEE58, 0xFB96, 0xC5B0, 0xF16C, 0x9B60, 0xC490, 0xF124, 0x9920, 0xE648, + 0xF992, 0xDC90, 0xF724, 0x8B20, 0xE2C8, 0xCD90, 0xF364, 0xBB20, 0xEEC8, 0xFBB2, 0x84B0, 0xE12C, + 0xC658, 0xF196, 0x9CB0, 0xE72C, 0xDE58, 0xF796, 0xC2D8, 0x8DB0, 0xC248, 0x8C90, 0xE324, 0xBDB0, + 0xCE48, 0xF392, 0xBC90, 0xEF24, 0x8590, 0xE164, 0xC6C8, 0xF1B2, 0x9D90, 0xE764, 0xDEC8, 0xF7B2, + 0x8258, 0xC32C, 0x8E58, 0xE396, 0xCF2C, 0xBE58, 0xEF96, 0xC16C, 0x86D8, 0xC124, 0x8648, 0xE192, + 0x9ED8, 0xC724, 0x9E48, 0xE792, 0xDF24, 0x82C8, 0xC364, 0x8EC8, 0xE3B2, 0xCF64, 0xBEC8, 0xEFB2, + 0x812C, 0xC196, 0x872C, 0xC796, 0x9F2C, 0x836C, 0x8324, 0x8F6C, 0xC392, 0x8F24, 0xBF6C, 0xCF92, + 0x8164, 0xC1B2, 0x8764, 0xC7B2, 0x9F64, 0xDFB2, 0xA2C0, 0xE8B0, 0xFA2C, 0xD360, 0xF4D8, 0xFD36, + 0xD120, 0xF448, 0xFD12, 0xAEC0, 0xEBB0, 0xFAEC, 0xA640, 0xE990, 0xFA64, 0xD720, 0xF5C8, 0xFD72, + 0x9160, 0xE458, 0xF916, 0xD8B0, 0xF62C, 0xC9B0, 0xF26C, 0xB360, 0xC890, 0xF224, 0xB120, 0xEC48, + 0xFB12, 0x9760, 0xE5D8, 0xF976, 0x9320, 0xE4C8, 0xF932, 0xD990, 0xF664, 0xCB90, 0xF2E4, 0xB720, + 0xEDC8, 0xFB72, 0x88B0, 0xE22C, 0xCC58, 0xF316, 0xB8B0, 0xEE2C, 0xC4D8, 0xF136, 0x99B0, 0xC448, + 0xF112, 0x9890, 0xE624, 0xDC48, 0xF712, 0x8BB0, 0xE2EC, 0x8990, 0xE264, 0xBBB0, 0xCCC8, 0xF332, + 0xB990, 0xEE64, 0xC5C8, 0xF172, 0x9B90, 0xE6E4, 0xDDC8, 0xF772, 0x8458, 0xE116, 0xC62C, 0x9C58, + 0xE716, 0xDE2C, 0xC26C, 0x8CD8, 0xC224, 0x8C48, 0xE312, 0xBCD8, 0xCE24, 0xBC48, 0xEF12, 0x85D8, + 0xE176, 0x84C8, 0xE132, 0x9DD8, 0xC664, 0x9CC8, 0xE732, 0xDE64, 0xC2E4, 0x8DC8, 0xE372, 0xCEE4, + 0xBDC8, 0xEF72, 0x822C, 0xC316, 0x8E2C, 0xCF16, 0xC136, 0x866C, 0xC112, 0x8624, 0x9E6C, 0xC712, + 0x9E24, 0x82EC, 0x8264, 0x8EEC, 0xC332, 0x8E64, 0xBEEC, 0xCF32, 0xBE64, 0xC172, 0x86E4, 0xC772, + 0x9EE4, 0xDF72, 0x8116, 0x8716, 0x8336, 0x8312, 0x8F36, 0x8F12, 0x8176, 0x8132, 0x8776, 0x8732, + 0x9F76, 0x9F32, 0x8372, 0x8F72, 0xBF72, 0xA160, 0xE858, 0xFA16, 0xD1B0, 0xF46C, 0xD090, 0xF424, + 0xA760, 0xE9D8, 0xFA76, 0xA320, 0xE8C8, 0xFA32, 0xD7B0, 0xF5EC, 0xD390, 0xF4E4, 0xAF20, 0xEBC8, + 0xFAF2, 0x90B0, 0xE42C, 0xD858, 0xF616, 0xC8D8, 0xF236, 0xB1B0, 0xC848, 0xF212, 0xB090, 0xEC24, + 0x93B0, 0xE4EC, 0x9190, 0xE464, 0xD8C8, 0xF632, 0xCBD8, 0xF2F6, 0xB7B0, 0xC9C8, 0xF272, 0xB390, + 0xECE4, 0x9790, 0xE5E4, 0xDBC8, 0xF6F2, 0x8858, 0xE216, 0xCC2C, 0xB858, 0xEE16, 0xC46C, 0x98D8, + 0xC424, 0x9848, 0xE612, 0xDC24, 0x89D8, 0xE276, 0x88C8, 0xE232, 0xB9D8, 0xCC64, 0xB8C8, 0xEE32, + 0xC5EC, 0x9BD8, 0xC4E4, 0x99C8, 0xE672, 0xDCE4, 0x8BC8, 0xE2F2, 0xCDE4, 0xBBC8, 0xEEF2, 0x842C, + 0xC616, 0x9C2C, 0xC236, 0x8C6C, 0xC212, 0x8C24, 0xBC6C, 0xCE12, 0x84EC, 0x8464, 0x9CEC, 0xC632, + 0x9C64, 0xDE32, 0xC2F6, 0x8DEC, 0xC272, 0x8CE4, 0xBDEC, 0xCE72, 0xBCE4, 0x85E4, 0xC6F2, 0x9DE4, + 0xDEF2, 0x8216, 0x8E16, 0x8636, 0x8612, 0x9E36, 0x8276, 0x8232, 0x8E76, 0x8E32, 0xBE76, 0x86F6, + 0x8672, 0x9EF6, 0x9E72, 0x82F2, 0x8EF2, 0xBEF2, 0xA0B0, 0xE82C, 0xD0D8, 0xF436, 0xD048, 0xF412, + 0xA3B0, 0xE8EC, 0xA190, 0xE864, 0xD3D8, 0xF4F6, 0xD1C8, 0xF472, 0xAFB0, 0xEBEC, 0xA790, 0xE9E4, + 0xD7C8, 0xF5F2, 0x9058, 0xE416, 0xD82C, 0xC86C, 0xB0D8, 0xC824, 0xB048, 0xEC12, 0x91D8, 0xE476, + 0x90C8, 0xE432, 0xD864, 0xC9EC, 0xB3D8, 0xC8E4, 0xB1C8, 0xEC72, 0x97D8, 0xE5F6, 0x93C8, 0xE4F2, + 0xD9E4, 0xCBE4, 0xB7C8, 0xEDF2, 0x882C, 0xCC16, 0xC436, 0x986C, 0xC412, 0x9824, 0x88EC, 0x8864, + 0xB8EC, 0xCC32, 0xB864, 0xC4F6, 0x99EC, 0xC472, 0x98E4, 0xDC72, 0x8BEC, 0x89E4, 0xBBEC, 0xCCF2, + 0xB9E4, 0xC5F2, 0x9BE4, 0xDDF2, 0x8416, 0x8C36, 0x8C12, 0x8476, 0x8432, 0x9C76, 0x9C32, 0x8CF6, + 0x8C72, 0xBCF6, 0xBC72, 0x85F6, 0x84F2, 0x9DF6, 0x9CF2, 0x8DF2, 0xBDF2, 0xA058, 0xE816, 0xD06C, + 0xD024, 0xA1D8, 0xE876, 0xA0C8, 0xE832, 0xD1EC, 0xD0E4, 0xA7D8, 0xE9F6, 0xA3C8, 0xE8F2, 0xD7EC, + 0xD3E4, 0x902C, 0xC836, 0xB06C, 0xC812, 0x90EC, 0x9064, 0xD832, 0xC8F6, 0xB1EC, 0xC872, 0xB0E4, + 0x93EC, 0x91E4, 0xD8F2, 0xCBF6, 0xB7EC, 0xC9F2, 0xB3E4, 0x8816, 0x9836, 0x8876, 0x8832, 0xB876, + 0x98F6, 0x9872, 0x89F6, 0x88F2, 0xB9F6, 0xB8F2, 0x9BF6, 0x99F2, 0xEA60, 0xFA98, 0xD440, 0xF510, + 0xFD44, 0xED70, 0xFB5C, 0x94C0, 0xE530, 0xF94C, 0xDA60, 0xF698, 0xFDA6, 0xCA20, 0xF288, 0xB440, + 0xED10, 0xFB44, 0x9AE0, 0xE6B8, 0xF9AE, 0xDD70, 0xF75C, 0x8A60, 0xE298, 0xCD30, 0xF34C, 0xBA60, + 0xEE98, 0xFBA6, 0xC510, 0xF144, 0x9A20, 0xE688, 0xF9A2, 0xDD10, 0xF744, 0x8D70, 0xE35C, 0xCEB8, + 0xF3AE, 0xBD70, 0xEF5C, 0x8530, 0xE14C, 0xC698, 0xF1A6, 0x9D30, 0xE74C, 0xDE98, 0xF7A6, 0xC288, + 0x8D10, 0xE344, 0xCE88, 0xF3A2, 0xBD10, 0xEF44, 0x86B8, 0xE1AE, 0xC75C, 0x9EB8, 0xE7AE, 0xDF5C, + 0x8298, 0xC34C, 0x8E98, 0xE3A6, 0xCF4C, 0xBE98, 0xEFA6, 0xC144, 0x8688, 0xE1A2, 0xC744, 0x9E88, + 0xE7A2, 0xDF44, 0x835C, 0xC3AE, 0x8F5C, 0xCFAE, 0xBF5C, 0x814C, 0xC1A6, 0x874C, 0xC7A6, 0x9F4C, + 0xDFA6, 0x8344, 0xC3A2, 0x8F44, 0xCFA2, 0xBF44, 0xD2E0, 0xF4B8, 0xFD2E, 0xADC0, 0xEB70, 0xFADC, + 0xA4C0, 0xE930, 0xFA4C, 0xD660, 0xF598, 0xFD66, 0xD220, 0xF488, 0xFD22, 0xAC40, 0xEB10, 0xFAC4, + 0xC970, 0xF25C, 0xB2E0, 0xECB8, 0xFB2E, 0x96E0, 0xE5B8, 0xF96E, 0x9260, 0xE498, 0xF926, 0xD930, + 0xF64C, 0xCB30, 0xF2CC, 0xB660, 0xC910, 0xF244, 0xB220, 0xEC88, 0xFB22, 0x9620, 0xE588, 0xF962, + 0xDB10, 0xF6C4, 0xC4B8, 0xF12E, 0x9970, 0xE65C, 0xDCB8, 0xF72E, 0x8B70, 0xE2DC, 0x8930, 0xE24C, + 0xBB70, 0xCC98, 0xF326, 0xB930, 0xEE4C, 0xC598, 0xF166, 0x9B30, 0xC488, 0xF122, 0x9910, 0xE644, + 0xDC88, 0xF722, 0x8B10, 0xE2C4, 0xCD88, 0xF362, 0xBB10, 0xEEC4, 0xC25C, 0x8CB8, 0xE32E, 0xCE5C, + 0xBCB8, 0xEF2E, 0x85B8, 0xE16E, 0x8498, 0xE126, 0x9DB8, 0xC64C, 0x9C98, 0xE726, 0xDE4C, 0xC2CC, + 0x8D98, 0xC244, 0x8C88, 0xE322, 0xBD98, 0xCE44, 0xBC88, 0xEF22, 0x8588, 0xE162, 0xC6C4, 0x9D88, + 0xE762, 0xDEC4, 0xC12E, 0x865C, 0xC72E, 0x9E5C, 0xDF2E, 0x82DC, 0x824C, 0x8EDC, 0xC326, 0x8E4C, + 0xBEDC, 0xCF26, 0xBE4C, 0xC166, 0x86CC, 0xC122, 0x8644, 0x9ECC, 0xC722, 0x9E44, 0xDF22, 0x82C4, + 0xC362, 0x8EC4, 0xCF62, 0xBEC4, 0x832E, 0x8F2E, 0x816E, 0x8126, 0x876E, 0x8726, 0x9F6E, 0x9F26, + 0x8366, 0x8322, 0x8F66, 0x8F22, 0xBF66, 0x8162, 0x8762, 0x9F62, 0xD170, 0xF45C, 0xA6E0, 0xE9B8, + 0xFA6E, 0xA260, 0xE898, 0xFA26, 0xD770, 0xF5DC, 0xD330, 0xF4CC, 0xD110, 0xF444, 0xAE60, 0xEB98, + 0xFAE6, 0xA620, 0xE988, 0xFA62, 0xD710, 0xF5C4, 0xC8B8, 0xF22E, 0xB170, 0xEC5C, 0x9370, 0xE4DC, + 0x9130, 0xE44C, 0xD898, 0xF626, 0xCBB8, 0xF2EE, 0xB770, 0xC998, 0xF266, 0xB330, 0xC888, 0xF222, + 0xB110, 0xEC44, 0x9730, 0xE5CC, 0x9310, 0xE4C4, 0xD988, 0xF662, 0xCB88, 0xF2E2, 0xB710, 0xEDC4, + 0xC45C, 0x98B8, 0xE62E, 0xDC5C, 0x89B8, 0xE26E, 0x8898, 0xE226, 0xB9B8, 0xCC4C, 0xB898, 0xEE26, + 0xC5DC, 0x9BB8, 0xC4CC, 0x9998, 0xC444, 0x9888, 0xE622, 0xDC44, 0x8B98, 0xE2E6, 0x8988, 0xE262, + 0xBB98, 0xCCC4, 0xB988, 0xEE62, 0xC5C4, 0x9B88, 0xE6E2, 0xDDC4, 0xC22E, 0x8C5C, 0xCE2E, 0xBC5C, + 0x84DC, 0x844C, 0x9CDC, 0xC626, 0x9C4C, 0xDE26, 0xC2EE, 0x8DDC, 0xC266, 0x8CCC, 0xC222, 0xBDDC, + 0x8C44, 0xBCCC, 0xCE22, 0xBC44, 0x85CC, 0x84C4, 0x9DCC, 0xC662, 0x9CC4, 0xDE62, 0xC2E2, 0x8DC4, + 0xCEE2, 0xBDC4, 0x862E, 0x9E2E, 0x826E, 0x8226, 0x8E6E, 0x8E26, 0xBE6E, 0x86EE, 0x8666, 0x9EEE, + 0x8622, 0x9E66, 0x9E22, 0x82E6, 0x8262, 0x8EE6, 0x8E62, 0xBEE6, 0xBE62, 0x86E2, 0x9EE2, 0xD0B8, + 0xF42E, 0xA370, 0xE8DC, 0xA130, 0xE84C, 0xD3B8, 0xF4EE, 0xD198, 0xF466, 0xD088, 0xF422, 0xAF70, + 0xEBDC, 0xA730, 0xE9CC, 0xA310, 0xE8C4, 0xD798, 0xF5E6, 0xD388, 0xF4E2, 0xAF10, 0xEBC4, 0xC85C, + 0xB0B8, 0xEC2E, 0x91B8, 0xE46E, 0x9098, 0xE426, 0xD84C, 0xC9DC, 0xB3B8, 0xC8CC, 0xB198, 0xC844, + 0xB088, 0xEC22, 0x97B8, 0xE5EE, 0x9398, 0xE4E6, 0x9188, 0xE462, 0xD8C4, 0xCBCC, 0xB798, 0xC9C4, + 0xB388, 0xECE2, 0x9788, 0xE5E2, 0xDBC4, 0xC42E, 0x985C, 0xDC2E, 0x88DC, 0x884C, 0xB8DC, 0xCC26, + 0xB84C, 0xC4EE, 0x99DC, 0xC466, 0x98CC, 0xC422, 0x9844, 0xDC22, 0x8BDC, 0x89CC, 0xBBDC, 0x88C4, + 0xB9CC, 0xCC62, 0xB8C4, 0xC5E6, 0x9BCC, 0xC4E2, 0x99C4, 0xDCE2, 0x8BC4, 0xCDE2, 0xBBC4, 0x8C2E, + 0x846E, 0x8426, 0x9C6E, 0x9C26, 0x8CEE, 0x8C66, 0xBCEE, 0x8C22, 0xBC66, 0x85EE, 0x84E6, 0x9DEE, + 0x8462, 0x9CE6, 0x9C62, 0x8DE6, 0x8CE2, 0xBDE6, 0xBCE2, 0x85E2, 0x9DE2, 0xD05C, 0xA1B8, 0xE86E, + 0xA098, 0xE826, 0xD1DC, 0xD0CC, 0xD044, 0xA7B8, 0xE9EE, 0xA398, 0xE8E6, 0xA188, 0xE862, 0xD7DC, + 0xD3CC, 0xD1C4, 0xAF98, 0xEBE6, 0xA788, 0xE9E2, 0xC82E, 0xB05C, 0x90DC, 0x904C, 0xD826, 0xC8EE, + 0xB1DC, 0xC866, 0xB0CC, 0xC822, 0xB044, 0x93DC, 0x91CC, 0x90C4, 0xD862, 0xCBEE, 0xB7DC, 0xC9E6, + 0xB3CC, 0xC8E2, 0xB1C4, 0x97CC, 0x93C4, 0xD9E2, 0x982E, 0x886E, 0x8826, 0xB86E, 0x98EE, 0x9866, + 0x9822, 0x89EE, 0x88E6, 0xB9EE, 0x8862, 0xB8E6, 0xB862, 0x9BEE, 0x99E6, 0x98E2, 0x8BE6, 0x89E2, + 0xBBE6, 0xB9E2, 0xD02E, 0xA0DC, 0xA04C, 0xD0EE, 0xD066, 0xD022, 0xA3DC, 0xA1CC, 0xA0C4, 0xD3EE, + 0xD1E6, 0xD0E2, 0xAFDC, 0xA7CC, 0xA3C4, 0x906E, 0x9026, 0xB0EE, 0xB066, 0x91EE, 0x90E6, 0x9062, + 0xB3EE, 0xB1E6, 0xB0E2, 0x97EE, 0x93E6, 0x91E2, 0xD4E0, 0xF538, 0xFD4E, 0xA8C0, 0xEA30, 0xFA8C, + 0xD420, 0xF508, 0xFD42, 0xDAF0, 0xF6BC, 0xCA70, 0xF29C, 0xB4E0, 0xED38, 0xFB4E, 0x9460, 0xE518, + 0xF946, 0xDA30, 0xF68C, 0xCA10, 0xF284, 0xB420, 0xED08, 0xFB42, 0xCD78, 0xF35E, 0xBAF0, 0xEEBC, + 0xC538, 0xF14E, 0x9A70, 0xE69C, 0xDD38, 0xF74E, 0x8A30, 0xE28C, 0xCD18, 0xF346, 0xBA30, 0xEE8C, + 0xC508, 0xF142, 0x9A10, 0xE684, 0xDD08, 0xF742, 0xC6BC, 0x9D78, 0xE75E, 0xDEBC, 0xC29C, 0x8D38, + 0xE34E, 0xCE9C, 0xBD38, 0xEF4E, 0x8518, 0xE146, 0xC68C, 0x9D18, 0xE746, 0xDE8C, 0xC284, 0x8D08, + 0xE342, 0xCE84, 0xBD08, 0xEF42, 0xC35E, 0x8EBC, 0xCF5E, 0xBEBC, 0xC14E, 0x869C, 0xC74E, 0x9E9C, + 0xDF4E, 0x828C, 0xC346, 0x8E8C, 0xCF46, 0xBE8C, 0xC142, 0x8684, 0xC742, 0x9E84, 0xDF42, 0x875E, + 0x9F5E, 0x834E, 0x8F4E, 0xBF4E, 0x8146, 0x8746, 0x9F46, 0x8342, 0x8F42, 0xBF42, 0xE978, 0xFA5E, + 0xD6F0, 0xF5BC, 0xD270, 0xF49C, 0xACE0, 0xEB38, 0xFACE, 0xA460, 0xE918, 0xFA46, 0xD630, 0xF58C, + 0xD210, 0xF484, 0xAC20, 0xEB08, 0xFAC2, 0x92F0, 0xE4BC, 0xD978, 0xF65E, 0xCB78, 0xF2DE, 0xB6F0, + 0xC938, 0xF24E, 0xB270, 0xEC9C, 0x9670, 0xE59C, 0x9230, 0xE48C, 0xD918, 0xF646, 0xCB18, 0xF2C6, + 0xB630, 0xC908, 0xF242, 0xB210, 0xEC84, 0x9610, 0xE584, 0xDB08, 0xF6C2, 0x8978, 0xE25E, 0xCCBC, + 0xB978, 0xEE5E, 0xC5BC, 0x9B78, 0xC49C, 0x9938, 0xE64E, 0xDC9C, 0x8B38, 0xE2CE, 0x8918, 0xE246, + 0xBB38, 0xCC8C, 0xB918, 0xEE46, 0xC58C, 0x9B18, 0xC484, 0x9908, 0xE642, 0xDC84, 0x8B08, 0xE2C2, + 0xCD84, 0xBB08, 0xEEC2, 0x84BC, 0xC65E, 0x9CBC, 0xDE5E, 0xC2DE, 0x8DBC, 0xC24E, 0x8C9C, 0xBDBC, + 0xCE4E, 0xBC9C, 0x859C, 0x848C, 0x9D9C, 0xC646, 0x9C8C, 0xDE46, 0xC2C6, 0x8D8C, 0xC242, 0x8C84, + 0xBD8C, 0xCE42, 0xBC84, 0x8584, 0xC6C2, 0x9D84, 0xDEC2, 0x825E, 0x8E5E, 0xBE5E, 0x86DE, 0x864E, + 0x9EDE, 0x9E4E, 0x82CE, 0x8246, 0x8ECE, 0x8E46, 0xBECE, 0xBE46, 0x86C6, 0x8642, 0x9EC6, 0x9E42, + 0x82C2, 0x8EC2, 0xBEC2, 0xA2F0, 0xE8BC, 0xD378, 0xF4DE, 0xD138, 0xF44E, 0xAEF0, 0xEBBC, 0xA670, + 0xE99C, 0xA230, 0xE88C, 0xD738, 0xF5CE, 0xD318, 0xF4C6, 0xD108, 0xF442, 0xAE30, 0xEB8C, 0xA610, + 0xE984, 0xD708, 0xF5C2, 0x9178, 0xE45E, 0xD8BC, 0xC9BC, 0xB378, 0xC89C, 0xB138, 0xEC4E, 0x9778, + 0xE5DE, 0x9338, 0xE4CE, 0x9118, 0xE446, 0xD88C, 0xCB9C, 0xB738, 0xC98C, 0xB318, 0xC884, 0xB108, + 0xEC42, 0x9718, 0xE5C6, 0x9308, 0xE4C2, 0xD984, 0xCB84, 0xB708, 0xEDC2, 0x88BC, 0xCC5E, 0xB8BC, + 0xC4DE, 0x99BC, 0xC44E, 0x989C, 0xDC4E, 0x8BBC, 0x899C, 0xBBBC, 0x888C, 0xB99C, 0xCC46, 0xB88C, + 0xC5CE, 0x9B9C, 0xC4C6, 0x998C, 0xC442, 0x9884, 0xDC42, 0x8B8C, 0x8984, 0xBB8C, 0xCCC2, 0xB984, + 0xC5C2, 0x9B84, 0xDDC2, 0x845E, 0x9C5E, 0x8CDE, 0x8C4E, 0xBCDE, 0xBC4E, 0x85DE, 0x84CE, 0x9DDE, + 0x8446, 0x9CCE, 0x9C46, 0x8DCE, 0x8CC6, 0xBDCE, 0x8C42, 0xBCC6, 0xBC42, 0x85C6, 0x84C2, 0x9DC6, + 0x9CC2, 0x8DC2, 0xBDC2, 0xA178, 0xE85E, 0xD1BC, 0xD09C, 0xA778, 0xE9DE, 0xA338, 0xE8CE, 0xA118, + 0xE846, 0xD7BC, 0xD39C, 0xD18C, 0xD084, 0xAF38, 0xEBCE, 0xA718, 0xE9C6, 0xA308, 0xE8C2, 0xD78C, + 0xD384, 0x90BC, 0xD85E, 0xC8DE, 0xB1BC, 0xC84E, 0xB09C, 0x93BC, 0x919C, 0x908C, 0xD846, 0xCBDE, + 0xB7BC, 0xC9CE, 0xB39C, 0xC8C6, 0xB18C, 0xC842, 0xB084, 0x979C, 0x938C, 0x9184, 0xD8C2, 0xCBC6, + 0xB78C, 0xC9C2, 0xB384, 0x885E, 0xB85E, 0x98DE, 0x984E, 0x89DE, 0x88CE, 0xB9DE, 0x8846, 0xB8CE, + 0xB846, 0x9BDE, 0x99CE, 0x98C6, 0x9842, 0x8BCE, 0x89C6, 0xBBCE, 0x88C2, 0xB9C6, 0xB8C2, 0x9BC6, + 0x99C2, 0xA0BC, 0xD0DE, 0xD04E, 0xA3BC, 0xA19C, 0xA08C, 0xD3DE, 0xD1CE, 0xD0C6, 0xD042, 0xAFBC, + 0xA79C, 0xA38C, 0xA184, 0xD7CE, 0xD3C6, 0xD1C2, 0x905E, 0xB0DE, 0xB04E, 0x91DE, 0x90CE, 0x9046, + 0xB3DE, 0xB1CE, 0xB0C6, 0xB042, 0x97DE, 0x93CE, 0x91C6, 0x90C2, 0xB7CE, 0xB3C6, 0xB1C2, 0xA05E, + 0xA1DE, 0xA0CE, 0xA046, 0xA7DE, 0xA3CE, 0xA1C6, 0xA0C2, 0xA9E0, 0xEA78, 0xFA9E, 0xD470, 0xF51C, + 0xA860, 0xEA18, 0xFA86, 0xD410, 0xF504, 0xED7C, 0x94F0, 0xE53C, 0xDA78, 0xF69E, 0xCA38, 0xF28E, + 0xB470, 0xED1C, 0x9430, 0xE50C, 0xDA18, 0xF686, 0xCA08, 0xF282, 0xB410, 0xED04, 0x9AF8, 0xE6BE, + 0xDD7C, 0x8A78, 0xE29E, 0xCD3C, 0xBA78, 0xEE9E, 0xC51C, 0x9A38, 0xE68E, 0xDD1C, 0x8A18, 0xE286, + 0xCD0C, 0xBA18, 0xEE86, 0xC504, 0x9A08, 0xE682, 0xDD04, 0x8D7C, 0xCEBE, 0xBD7C, 0x853C, 0xC69E, + 0x9D3C, 0xDE9E, 0xC28E, 0x8D1C, 0xCE8E, 0xBD1C, 0x850C, 0xC686, 0x9D0C, 0xDE86, 0xC282, 0x8D04, + 0xCE82, 0xBD04, 0x86BE, 0x9EBE, 0x829E, 0x8E9E, 0xBE9E, 0x868E, 0x9E8E, 0x8286, 0x8E86, 0xBE86, + 0x8682, 0x9E82, 0xD2F8, 0xF4BE, 0xADF0, 0xEB7C, 0xA4F0, 0xE93C, 0xD678, 0xF59E, 0xD238, 0xF48E, + 0xAC70, 0xEB1C, 0xA430, 0xE90C, 0xD618, 0xF586, 0xD208, 0xF482, 0xAC10, 0xEB04, 0xC97C, 0xB2F8, + 0xECBE, 0x96F8, 0xE5BE, 0x9278, 0xE49E, 0xD93C, 0xCB3C, 0xB678, 0xC91C, 0xB238, 0xEC8E, 0x9638, + 0xE58E, 0x9218, 0xE486, 0xD90C, 0xCB0C, 0xB618, 0xC904, 0xB208, 0xEC82, 0x9608, 0xE582, 0xDB04, + 0xC4BE, 0x997C, 0xDCBE, 0x8B7C, 0x893C, 0xBB7C, 0xCC9E, 0xB93C, 0xC59E, 0x9B3C, 0xC48E, 0x991C, + 0xDC8E, 0x8B1C, 0x890C, 0xBB1C, 0xCC86, 0xB90C, 0xC586, 0x9B0C, 0xC482, 0x9904, 0xDC82, 0x8B04, + 0xCD82, 0xBB04, 0x8CBE, 0xBCBE, 0x85BE, 0x849E, 0x9DBE, 0x9C9E, 0x8D9E, 0x8C8E, 0xBD9E, 0xBC8E, + 0x858E, 0x8486, 0x9D8E, 0x9C86, 0x8D86, 0x8C82, 0xBD86, 0xBC82, 0x8582, 0x9D82, 0xD17C, 0xA6F8, + 0xE9BE, 0xA278, 0xE89E, 0xD77C, 0xD33C, 0xD11C, 0xAE78, 0xEB9E, 0xA638, 0xE98E, 0xA218, 0xE886, + 0xD71C, 0xD30C, 0xD104, 0xAE18, 0xEB86, 0xA608, 0xE982, 0xC8BE, 0xB17C, 0x937C, 0x913C, 0xD89E, + 0xCBBE, 0xB77C, 0xC99E, 0xB33C, 0xC88E, 0xB11C, 0x973C, 0x931C, 0x910C, 0xD886, 0xCB8E, 0xB71C, + 0xC986, 0xB30C, 0xC882, 0xB104, 0x970C, 0x9304, 0xD982, 0x98BE, 0x89BE, 0x889E, 0xB9BE, 0xB89E, + 0x9BBE, 0x999E, 0x988E, 0x8B9E, 0x898E, 0xBB9E, 0x8886, 0xB98E, 0xB886, 0x9B8E, 0x9986, 0x9882, + 0x8B86, 0x8982, 0xBB86, 0xB982, 0xD0BE, 0xA37C, 0xA13C, 0xD3BE, 0xD19E, 0xD08E, 0xAF7C, 0xA73C, + 0xA31C, 0xA10C, 0xD79E, 0xD38E, 0xD186, 0xD082, 0xAF1C, 0xA70C, 0xA304, 0xB0BE, 0x91BE, 0x909E, + 0xB3BE, 0xB19E, 0xB08E, 0x97BE, 0x939E, 0x918E, 0x9086, 0xB79E, 0xB38E, 0xB186, 0xB082, 0x978E, + 0x9386, 0x9182, 0xA1BE, 0xA09E, 0xA7BE, 0xA39E, 0xA18E, 0xA086, 0xAF9E, 0xA78E, 0xA386, 0xA182, + 0xD4F8, 0xF53E, 0xA8F0, 0xEA3C, 0xD438, 0xF50E, 0xA830, 0xEA0C, 0xD408, 0xF502, 0xDAFC, 0xCA7C, + 0xB4F8, 0xED3E, 0x9478, 0xE51E, 0xDA3C, 0xCA1C, 0xB438, 0xED0E, 0x9418, 0xE506, 0xDA0C, 0xCA04, + 0xB408, 0xED02, 0xCD7E, 0xBAFC, 0xC53E, 0x9A7C, 0xDD3E, 0x8A3C, 0xCD1E, 0xBA3C, 0xC50E, 0x9A1C, + 0xDD0E, 0x8A0C, 0xCD06, 0xBA0C, 0xC502, 0x9A04, 0xDD02, 0x9D7E, 0x8D3E, 0xBD3E, 0x851E, 0x9D1E, + 0x8D0E, 0xBD0E, 0x8506, 0x9D06, 0x8D02, 0xBD02, 0xE97E, 0xD6FC, 0xD27C, 0xACF8, 0xEB3E, 0xA478, + 0xE91E, 0xD63C, 0xD21C, 0xAC38, 0xEB0E, 0xA418, 0xE906, 0xD60C, 0xD204, 0x92FC, 0xD97E, 0xCB7E, + 0xB6FC, 0xC93E, 0xB27C, 0x967C, 0x923C, 0xD91E, 0xCB1E, 0xB63C, 0xC90E, 0xB21C, 0x961C, 0x920C, + 0xD906, 0xCB06, 0xB60C, 0xC902, 0xB204, 0x897E, 0xB97E, 0x9B7E, 0x993E, 0x8B3E, 0x891E, 0xBB3E, + 0xB91E, 0x9B1E, 0x990E, 0x8B0E, 0x8906, 0xBB0E, 0xB906, 0x9B06, 0x9902, 0xA2FC, 0xD37E, 0xD13E, + 0xAEFC, + ]; +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code49/Code49Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Code49/Code49Symbology.cs new file mode 100644 index 00000000..7baf0280 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code49/Code49Symbology.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Code 49 symbology, AIM USS-49. A symbol is 2 to 8 rows of four symbol characters, each carrying two code characters, and carries the full ASCII +/// character set with a row check character in every row and two or three modulo 2401 symbol check characters in the last. +/// +public sealed class Code49Symbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Code49Symbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Code49Encoder.BuildSymbol(text, text, options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code93/Code93Encoder.cs b/src/ImageSharp.Drawing/Barcodes/Code93/Code93Encoder.cs new file mode 100644 index 00000000..616e2288 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code93/Code93Encoder.cs @@ -0,0 +1,235 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Shared encodation for the Code 93 symbology family, defined by ANSI/AIM BC5-1995. Every symbol character is six elements wide, three bars and three spaces, +/// and spans nine modules. A symbol carries a quiet zone, the start character, the data and two check characters. The stop character with its termination bar +/// and a second quiet zone close the symbol. No gap separates the characters. +/// +internal static class Code93Encoder +{ + /// + /// The largest number of characters a symbol carries. ANSI/AIM BC5-1995 makes the data string length variable, so the symbology sets no maximum of its own. + /// + public const int MaximumLength = 500; + + /// + /// The number of characters a caller holds inline to build symbol data in. Data this long covers the labels the symbology is used for, and longer data + /// rents its buffer once. + /// + public const int InlineBufferLength = 64; + + /// + /// The number of data characters ANSI/AIM BC5-1995 carries. They are the 43 of , and the four shift characters take + /// the values above them. + /// + private const int DataCharacters = 43; + + /// + /// The quiet zone in modules on each side. Section 2.6 of ANSI/AIM BC5-1995 measures a symbol as (9 * (C + 4) + 1) * X + 2 * Q, where C is the data + /// character count and Q is ten modules. + /// + private const int QuietZone = 10; + + /// + /// The element count of a symbol character. ANSI/AIM BC5-1995 gives every character three bars and three spaces. + /// + private const int ElementsPerCharacter = 6; + + /// + /// The element count a symbol carries beyond its data and check characters: the six of the start character and the seven of the stop character with its + /// termination bar. + /// + private const int StartStopElements = 13; + + /// + /// The number of check characters every symbol carries. ANSI/AIM BC5-1995 fixes both of them, so an application standard cannot make them optional. + /// + private const int CheckCharacters = 2; + + /// + /// The modulus ANSI/AIM BC5-1995 takes both check characters over, which is the size of the symbol character set. + /// + private const int CheckModulus = 47; + + /// + /// The highest weight the first check character of ANSI/AIM BC5-1995 reaches before it returns to one. + /// + private const int FirstCheckWeights = 20; + + /// + /// The highest weight the second check character of ANSI/AIM BC5-1995 reaches before it returns to one. + /// + private const int SecondCheckWeights = 15; + + /// + /// The bar height as a fraction of the symbol width, quiet zones included, when the caller sets no height. Section 2.6 of ANSI/AIM BC5-1995 recommends a + /// minimum of 0.2 inches or 15 per cent of the symbol length, whichever is greater. + /// + private const float NominalBarHeightFraction = 0.15F; + + /// + /// The smallest bar height in millimetres when the caller sets none: the 0.2 inches of section 2.6 of ANSI/AIM BC5-1995. + /// + private const float MinimumBarHeightMillimetres = 5.08F; + + /// + /// Gets the element widths of every symbol character in modules, six to a character, in bar and space order. These are the patterns of Table 2 of ANSI/AIM + /// BC5-1995, indexed by character value. + /// + private static ReadOnlySpan Patterns => + [ + 1, 3, 1, 1, 1, 2, 1, 1, 1, 2, 1, 3, 1, 1, 1, 3, 1, 2, 1, 1, 1, 4, 1, 1, + 1, 2, 1, 1, 1, 3, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 1, 1, 1, 1, 1, 1, 4, + 1, 3, 1, 2, 1, 1, 1, 4, 1, 1, 1, 1, 2, 1, 1, 1, 1, 3, 2, 1, 1, 2, 1, 2, + 2, 1, 1, 3, 1, 1, 2, 2, 1, 1, 1, 2, 2, 2, 1, 2, 1, 1, 2, 3, 1, 1, 1, 1, + 1, 1, 2, 1, 1, 3, 1, 1, 2, 2, 1, 2, 1, 1, 2, 3, 1, 1, 1, 2, 2, 1, 1, 2, + 1, 3, 2, 1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1, 2, 2, 2, 1, 1, 1, 3, 2, 1, + 1, 2, 1, 1, 2, 2, 1, 3, 1, 1, 2, 1, 2, 1, 2, 1, 1, 2, 2, 1, 2, 2, 1, 1, + 2, 1, 1, 1, 2, 2, 2, 1, 1, 2, 2, 1, 2, 2, 1, 1, 2, 1, 2, 2, 2, 1, 1, 1, + 1, 1, 2, 1, 2, 2, 1, 1, 2, 2, 2, 1, 1, 2, 2, 1, 2, 1, 1, 2, 3, 1, 1, 1, + 1, 2, 1, 1, 3, 1, 3, 1, 1, 1, 1, 2, 3, 1, 1, 2, 1, 1, 3, 2, 1, 1, 1, 1, + 1, 1, 2, 1, 3, 1, 1, 1, 3, 1, 2, 1, 2, 1, 1, 1, 3, 1, 1, 2, 1, 2, 2, 1, + 3, 1, 2, 1, 1, 1, 3, 1, 1, 1, 2, 1, 1, 2, 2, 2, 1, 1, + ]; + + /// + /// Gets the element widths of the start and stop characters of ANSI/AIM BC5-1995 in modules. The first six are the start character, and all seven are the + /// stop character, whose extra bar terminates the symbol. + /// + private static ReadOnlySpan StartStopPattern => [1, 1, 1, 1, 4, 1, 1]; + + /// + /// Validates the given text against the data character set of ANSI/AIM BC5-1995, the 43 characters of . The shift + /// characters carry no data, so they are not input. The message reports the first character outside the set, and a surrogate pair as one character. + /// + /// The text to validate. + /// The text carries a character outside the set. + public static void Validate(ReadOnlySpan text) + { + Guard.MustBeGreaterThan(text.Length, 0, nameof(text)); + Guard.MustBeLessThanOrEqualTo(text.Length, MaximumLength, nameof(text)); + + int index = AlphanumericCharacterSet.IndexOfAnyExcept(text); + if (index >= 0) + { + throw new ArgumentException( + $"Code 93 carries only digits, capital letters, spaces and the symbols -.$/+%; {text.CodePointAt(index).ToDisplayString()} is outside that set.", + nameof(text)); + } + } + + /// + /// Encodes symbol characters and builds the symbol. The runs carry the start character, the data, both check characters and the stop character, in the + /// order ANSI/AIM BC5-1995 gives. That standard fixes both check characters, so the caller cannot turn them off. Code 93 carries no guard bars, so every + /// bar runs the full height, and the human readable interpretation sits below the symbol. + /// + /// The symbol characters to encode, shift characters included. + /// The human readable interpretation. + /// The options that control layout choices. + /// The symbol to write. + public static void BuildSymbol(ReadOnlySpan text, ReadOnlySpan readable, BarcodeOptions options, ref BarcodeSymbol symbol) + { + int runCount = ((text.Length + CheckCharacters) * ElementsPerCharacter) + StartStopElements; + double xDimension = options.ResolveXDimension(0, BarcodeSymbology.DefaultXDimension, double.PositiveInfinity); + Span runs = symbol.BeginLinear(runCount, xDimension, out Span heights, out _); + int written = 0; + + AppendElements(runs, ref written, StartStopPattern[..ElementsPerCharacter]); + foreach (char character in text) + { + AppendPattern(runs, ref written, Value(character)); + } + + (int first, int second) = CheckValues(text); + AppendPattern(runs, ref written, first); + AppendPattern(runs, ref written, second); + AppendElements(runs, ref written, StartStopPattern); + + int widthInModules = BarcodeSymbol.RunWidth(runs); + float nominalBarHeight = MathF.Max((widthInModules + (QuietZone * 2)) * NominalBarHeightFraction, (float)(MinimumBarHeightMillimetres / xDimension)); + float barHeight = options.ResolveBarHeight(xDimension, 0, nominalBarHeight, float.PositiveInfinity); + heights.Fill(barHeight); + + if (options.Font is not null && readable.Length > 0) + { + symbol.ReserveText(1, readable.Length); + symbol.AddText(readable, 0F, widthInModules, BarcodeTextSide.BelowBars, barHeight + BarcodeTextPlacement.Clearance); + } + + symbol.EndLinear(QuietZone, QuietZone); + } + + /// + /// Gets the value ANSI/AIM BC5-1995 assigns the given character, or a negative number when the character is outside the symbol character set. + /// + /// The code point to value. + /// The value, or a negative number. + private static int Value(int codePoint) => codePoint switch + { + >= 'a' and <= 'd' => codePoint - 'a' + DataCharacters, + _ => AlphanumericCharacterSet.Value(codePoint), + }; + + /// + /// Calculates both check characters of ANSI/AIM BC5-1995 in one backward pass over the symbol characters. The first, called C, weights the characters one + /// upward from the last, returns to one after twenty, and takes the sum modulo 47. The second, called K, covers the first check character too: C takes + /// weight one, the characters take weights from two upward from the last, returning to one after fifteen, and the sum is taken modulo 47. C is not known + /// until the pass ends, so K adds its weighted term to the sum of the characters at the end. + /// + /// The symbol characters the checks cover. + /// The values of the first and second check characters. + private static (int First, int Second) CheckValues(ReadOnlySpan text) + { + int firstSum = 0; + int secondSum = 0; + int firstWeight = 1; + int secondWeight = 2; + for (int i = text.Length - 1; i >= 0; i--) + { + int value = Value(text[i]); + firstSum += value * firstWeight; + secondSum += value * secondWeight; + if (++firstWeight > FirstCheckWeights) + { + firstWeight = 1; + } + + if (++secondWeight > SecondCheckWeights) + { + secondWeight = 1; + } + } + + int first = firstSum % CheckModulus; + return (first, (first + secondSum) % CheckModulus); + } + + /// + /// Writes the six elements of one symbol character. + /// + /// The run widths being written. + /// The number of runs written so far. + /// The value of the character to write. + private static void AppendPattern(Span runs, ref int written, int value) + => AppendElements(runs, ref written, Patterns.Slice(value * ElementsPerCharacter, ElementsPerCharacter)); + + /// + /// Writes element widths to the runs. + /// + /// The run widths being written. + /// The number of runs written so far. + /// The element widths in modules. + private static void AppendElements(Span runs, ref int written, ReadOnlySpan widths) + { + foreach (byte width in widths) + { + runs[written++] = width; + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code93/Code93ExtendedSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Code93/Code93ExtendedSymbology.cs new file mode 100644 index 00000000..50bb1a09 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code93/Code93ExtendedSymbology.cs @@ -0,0 +1,92 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Code 93 Extended, the full ASCII mode of ANSI/AIM BC5-1995. Table 3 encodes the 128 characters of ASCII as either one symbol character or a shift character +/// followed by one more. +/// +/// The symbol is an ordinary Code 93 symbol, so only a decoder in full ASCII mode reads back what went in. The human readable interpretation shows the ASCII +/// characters the caller gave rather than the symbol characters that stand for them, and prints a space where a character has no printed form. +/// +/// +public sealed class Code93ExtendedSymbology : BarcodeSymbology +{ + /// + /// Table 3, two characters for every ASCII value in order. A value encoded by a single symbol character has a space in front of it, and a space is never a + /// shift character, so the first character of a pair says which of the two forms it is. + /// + private const string Substitutions = + "bUaAaBaCaDaEaFaG" + + "aHaIaJaKaLaMaNaO" + + "aPaQaRaSaTaUaVaW" + + "aXaYaZbAbBbCbDbE" + + " cAcBcC $ %cFcG" + + "cHcIcJ +cL - . /" + + " 0 1 2 3 4 5 6 7" + + " 8 9cZbFbGbHbIbJ" + + "bV A B C D E F G" + + " H I J K L M N O" + + " P Q R S T U V W" + + " X Y ZbKbLbMbNbO" + + "bWdAdBdCdDdEdFdG" + + "dHdIdJdKdLdMdNdO" + + "dPdQdRdSdTdUdVdW" + + "dXdYdZbPbQbRbSbT"; + + /// + /// Initializes a new instance of the class. + /// + public Code93ExtendedSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Guard.MustBeGreaterThan(text.Length, 0, nameof(text)); + + // Each character substitutes to one or two symbol characters, and the symbol carries at most Code93Encoder.MaximumLength of them, so longer text cannot + // fit. A pair per character bounds the substitution, and the readable interpretation matches the input character for character, so neither builder + // grows: short text stays inline and longer text rents once. + Guard.MustBeLessThanOrEqualTo(text.Length, Code93Encoder.MaximumLength, nameof(text)); + + // The message decodes the code point at the first unit outside ASCII, so a surrogate pair reports as the one character it is. + int invalid = text.IndexOfAnyExceptInRange('\0', '\u007F'); + if (invalid >= 0) + { + throw new ArgumentException( + $"Code 93 Extended encodes ASCII 0 to 127; {text.CodePointAt(invalid).ToDisplayString()} is outside that range.", + nameof(text)); + } + + InlineArray64 encodedStorage = default; + InlineArray64 readableStorage = default; + int encodedCapacity = text.Length * 2; + ValueStringBuilder encoded = encodedCapacity <= Code93Encoder.InlineBufferLength ? new(encodedStorage) : new(encodedCapacity); + ValueStringBuilder readable = text.Length <= Code93Encoder.InlineBufferLength ? new(readableStorage) : new(text.Length); + try + { + foreach (char character in text) + { + ReadOnlySpan pair = Substitutions.AsSpan(character * 2, 2); + encoded.Append(pair[0] == ' ' ? pair[1..] : pair); + + // A control character has no printed form, so the interpretation shows a space instead. + readable.Append(char.IsControl(character) ? ' ' : character); + } + + Guard.MustBeLessThanOrEqualTo(encoded.Length, Code93Encoder.MaximumLength, nameof(text)); + + Code93Encoder.BuildSymbol(encoded.AsSpan(), readable.AsSpan(), options, ref symbol); + } + finally + { + encoded.Dispose(); + readable.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Code93/Code93Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Code93/Code93Symbology.cs new file mode 100644 index 00000000..e66d76d2 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Code93/Code93Symbology.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Code 93 symbology, which ANSI/AIM BC5-1995 defines. It carries the digits, the capital letters, the space and the special characters $ % + - . /. +/// Table 2 gives every character six elements, three bars and three spaces. A symbol carries a start character, the data, two check characters and a stop +/// character. Each character takes nine modules, and the stop character takes ten. +/// +/// The two check characters are modulo 47 and Section 2.6 counts them in the symbol length, so every symbol carries them. The human readable interpretation +/// shows the data characters. +/// +/// +public sealed class Code93Symbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Code93Symbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Code93Encoder.Validate(text); + + Code93Encoder.BuildSymbol(text, text, options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/CodeOne/CodeOneEncoder.cs b/src/ImageSharp.Drawing/Barcodes/CodeOne/CodeOneEncoder.cs new file mode 100644 index 00000000..37330c09 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/CodeOne/CodeOneEncoder.cs @@ -0,0 +1,1577 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodes Code One symbols. Versions A to H and T carry 8-bit codewords in the ASCII, C40, Text, X12, Decimal and Byte modes, chosen by a look-ahead over the +/// data, with Reed-Solomon error correction over GF(256). Versions S carry a number as 5-bit codewords with error correction over GF(32). The codewords fill +/// the data columns in 2-row bricks around the centre pattern, the risers and the black dots. +/// +internal static class CodeOneEncoder +{ + /// + /// The longest input: version H holds 1480 data codewords, and Decimal mode, the densest, writes three digits in 10 bits, so at most 3550 characters fit. + /// + private const int MaximumLength = 3550; + + /// + /// The ASCII codeword that pads the data codewords to the capacity of the version. + /// + private const int Pad = 129; + + /// + /// The ASCII codeword of the first digit pair, 00. A pair of digits is this codeword plus its value. + /// + private const int DigitPairBase = 130; + + /// + /// The ASCII codeword that latches to C40 mode. + /// + private const int LatchC40 = 230; + + /// + /// The ASCII codeword that latches to Byte mode. + /// + private const int LatchByte = 231; + + /// + /// The ASCII codeword of the Upper Shift, which adds 128 to the next character. + /// + private const int UpperShift = 235; + + /// + /// The ASCII codeword that latches to X12 mode. + /// + private const int LatchX12 = 238; + + /// + /// The ASCII codeword that latches to Text mode. + /// + private const int LatchText = 239; + + /// + /// The C40 and Text value of Shift 1, the set of the control characters. + /// + private const int Shift1 = 0; + + /// + /// The C40 and Text value of Shift 2, the set of the punctuation and the Upper Shift. + /// + private const int Shift2 = 1; + + /// + /// The C40 and Text value of Shift 3, the set of the other case and the characters 96 to 127. + /// + private const int Shift3 = 2; + + /// + /// The Shift 2 set value of the Upper Shift in C40 and Text mode. + /// + private const int ShiftedUpperShift = 30; + + /// + /// The scale of the look-ahead counts. The counts step in halves and thirds, so twelve units to the codeword keep every count a whole number and every + /// comparison exact. + /// + private const int Unit = 12; + + /// + /// The data codewords of version H, the most any version holds. + /// + private const int MaximumCodewords = 1480; + + /// + /// The check codewords of version H, the most any version holds. + /// + private const int MaximumCheckCodewords = 560; + + /// + /// The cells of version H, 148 rows of 134 columns, the most any version holds. + /// + private const int MaximumCells = 148 * 134; + + /// + /// The number of values one run of C40, Text or X12 mode can gather: three values pack into two codewords, so a run of more than three halves of + /// cannot fit. A character takes at most four values, and the twelve places beyond hold the values of the last three + /// characters. + /// + private const int MaximumValues = (MaximumCodewords * 3 / 2) + 12; + + /// + /// The codeword that unlatches C40, Text and X12 mode back to ASCII mode. + /// + private const int Unlatch = 255; + + /// + /// The four bits that latch from ASCII mode into Decimal mode, the high bits of the first Decimal codeword. + /// + private const int DecimalLatch = 0b1111; + + /// + /// The six bits that unlatch Decimal mode back to ASCII mode. + /// + private const int DecimalUnlatch = 0b111111; + + /// + /// The number of characters the look-ahead reads before a mode can win early. + /// + private const int MinimumLookAhead = 4; + + /// + /// The number of entries a version takes in . + /// + private const int VersionStride = 13; + + /// + /// The field of the 8-bit codewords of versions A to H and T: GF(256) with the polynomial x^8 + x^5 + x^3 + x^2 + 1, 301 decimal. + /// + private static readonly GaloisField Field8 = new(256, 301); + + /// + /// The field of the 5-bit codewords of the S versions: GF(32) with the polynomial x^5 + x^2 + 1, 37 decimal. + /// + private static readonly GaloisField Field5 = new(32, 37); + + /// + /// The modes of versions A to H and T. + /// + private enum Mode + { + /// + /// ASCII mode: each codeword carries one character, one digit pair, a shift or a latch. + /// + Ascii, + + /// + /// C40 mode: three values in two codewords, with the capital letters in the basic set. + /// + C40, + + /// + /// Text mode: three values in two codewords, with the small letters in the basic set. + /// + Text, + + /// + /// X12 mode: three values in two codewords from one set of 40 values. + /// + X12, + + /// + /// Byte mode: a length, then one codeword for each byte. + /// + Byte, + + /// + /// Decimal mode: three digits in 10 bits. + /// + Decimal, + } + + /// + /// The kinds of the rows of the centre pattern. + /// + private enum CentreRow : byte + { + /// + /// Every module light. + /// + Light = 1, + + /// + /// Every module dark. + /// + Dark = 2, + + /// + /// Light ends around a dark run. + /// + DarkWithLightEnds = 3, + + /// + /// Light, with a dark module one in from each end. + /// + LightWithDarkInner = 4, + + /// + /// A single dark module in the middle. The other cells take data. + /// + DarkCentre = 5, + + /// + /// A single light module in the middle. The other cells take data. + /// + LightCentre = 6, + + /// + /// Dark ends around a light run. + /// + LightWithDarkEnds = 7, + + /// + /// Dark, with a light module one in from each end. + /// + DarkWithLightInner = 8, + } + + /// + /// Gets the dimensions and fixed patterns of each version in the order of , entries a version: the + /// rows, the columns, the columns the codeword bricks fill, the data codewords, the check codewords, the Reed-Solomon blocks, the column of the first + /// riser, the columns from one riser to the next, the rows of a riser, the start and the length of the centre pattern in , and the + /// start and the count of the black dots in . + /// + private static ReadOnlySpan VersionTable => + [ + 16, 18, 16, 10, 10, 1, 4, 18, 6, 0, 6, 0, 1, + 22, 22, 20, 19, 16, 1, 4, 22, 8, 6, 8, 1, 1, + 28, 32, 28, 44, 26, 1, 4, 22, 11, 14, 8, 2, 1, + 40, 42, 36, 91, 44, 1, 4, 16, 16, 22, 10, 3, 0, + 52, 54, 48, 182, 70, 1, 4, 22, 22, 32, 10, 3, 1, + 70, 76, 68, 370, 140, 2, 4, 22, 31, 42, 10, 4, 4, + 104, 98, 88, 732, 280, 4, 6, 21, 47, 52, 12, 8, 2, + 148, 134, 120, 1480, 560, 8, 6, 20, 69, 64, 12, 10, 6, + 8, 11, 10, 4, 4, 1, 0, 0, 0, 76, 8, 16, 0, + 8, 21, 20, 8, 8, 1, 0, 0, 0, 76, 8, 16, 1, + 8, 31, 30, 12, 12, 1, 0, 0, 0, 76, 8, 17, 2, + 16, 17, 16, 10, 10, 1, 0, 0, 0, 84, 16, 19, 1, + 16, 33, 32, 24, 16, 1, 0, 0, 0, 84, 16, 20, 2, + 16, 49, 48, 38, 22, 1, 0, 0, 0, 84, 16, 22, 3, + ]; + + /// + /// Gets the centre patterns of the versions as kinds, top row first: versions A to H in turn, then the pattern the S versions share + /// and the pattern the T versions share. + /// + private static ReadOnlySpan CentreRows => + [ + 1, 2, 1, 3, 4, 3, + 1, 2, 1, 3, 4, 3, 4, 3, + 1, 2, 1, 2, 1, 3, 4, 3, + 1, 2, 1, 3, 4, 3, 4, 3, 4, 3, + 1, 2, 1, 2, 1, 3, 4, 3, 4, 3, + 1, 2, 1, 2, 1, 2, 1, 3, 4, 3, + 1, 2, 1, 2, 1, 3, 4, 3, 4, 3, 4, 3, + 1, 2, 1, 2, 1, 2, 1, 3, 4, 3, 4, 3, + 5, 6, 6, 6, 1, 2, 7, 8, + 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 1, 2, 7, 8, 7, 8, + ]; + + /// + /// Gets the black dots of the versions as column and row pairs, the versions in the order of . + /// + private static ReadOnlySpan BlackDots => + [ + 12, 5, + 16, 7, + 26, 12, + 26, 23, + 26, 32, 70, 32, 26, 34, 70, 34, + 27, 48, 69, 48, + 26, 70, 66, 70, 106, 70, 26, 72, 66, 72, 106, 72, + 10, 4, + 15, 4, 15, 6, + 8, 10, + 16, 10, 16, 12, + 24, 10, 24, 12, 24, 14, + ]; + + /// + /// Builds a Code One symbol. + /// + /// The text, characters U+0000 to U+00FF, or digits for an S version. + /// The version, or for the smallest of versions A to H. + /// The X dimension in millimetres. + /// The symbol to write. + /// The text is empty, has a character the version cannot carry, or does not fit. + public static void BuildSymbol(ReadOnlySpan text, CodeOneVersion? version, double xDimension, ref BarcodeSymbol symbol) + { + if (text.IsEmpty) + { + throw new ArgumentException("Code One needs at least one character.", nameof(text)); + } + + bool number = version is CodeOneVersion.S10 or CodeOneVersion.S20 or CodeOneVersion.S30; + if (!number) + { + Guard.MustBeLessThanOrEqualTo(text.Length, MaximumLength, nameof(text)); + int invalid = text.IndexOfAnyExceptInRange('\0', '\u00FF'); + if (invalid >= 0) + { + throw new ArgumentException($"Code One encodes U+0000 to U+00FF; got {text.CodePointAt(invalid).ToDisplayString()}.", nameof(text)); + } + } + + // One rented block holds the codewords and the cells of the largest version, and, for the data of versions A to H and T, the scratch of the encodation. + // The text is at most MaximumLength characters, so the block is bounded. + const int codewordLength = MaximumCodewords + MaximumCheckCodewords; + const int cellLength = MaximumCells / sizeof(int); + int scratchLength = number ? 0 : ScratchLength(text.Length); + using PooledBuffer block = new(codewordLength + cellLength + scratchLength); + Span codewords = block.Span[..codewordLength]; + Span cells = MemoryMarshal.Cast(block.Span.Slice(codewordLength, cellLength)); + CodeOneVersion chosen; + if (number) + { + chosen = version!.Value; + EncodeNumber(text, new VersionInfo(chosen).DataCodewords, codewords); + } + else + { + int count = EncodeData(text, version, codewords, block.Span.Slice(codewordLength + cellLength, scratchLength)); + if (count < 0 || !TryChoose(version, count, out chosen)) + { + throw new ArgumentException("The text does not fit in a Code One symbol of the requested version.", nameof(text)); + } + + codewords[count..new VersionInfo(chosen).DataCodewords].Fill(Pad); + } + + VersionInfo info = new(chosen); + Span all = codewords[..(info.DataCodewords + info.CheckCodewords)]; + AddCheckCodewords(all, info.DataCodewords, info.CheckBlocks, info.IsNumeric); + + // Versions A to H need no quiet zone. Versions S and T have one module of quiet zone on the left, the right and the bottom. + Span modules = chosen >= CodeOneVersion.S10 + ? symbol.BeginMatrix(info.Columns, info.Rows, 1, 1, 0, 1, xDimension) + : symbol.BeginMatrix(info.Columns, info.Rows, 0, xDimension); + + Layout(info, all, cells[..(info.Rows * info.Columns)], modules); + } + + /// + /// Returns the length in ints of the scratch takes: one for each position of the text and one past + /// its end, then the values of a C40, Text or X12 run. + /// + /// The length of the text. + /// The scratch length. + private static int ScratchLength(int length) + => (LookAheadEntry.IntCount * (length + 1)) + MaximumValues; + + /// + /// Finds the smallest permitted version that holds the codewords. + /// + /// The version, or for versions A to H. + /// The number of data codewords. + /// The version found. + /// when a version holds the codewords; otherwise, . + private static bool TryChoose(CodeOneVersion? version, int count, out CodeOneVersion chosen) + { + for (CodeOneVersion v = CodeOneVersion.A; v <= CodeOneVersion.T48; v++) + { + if (IsPermitted(version, v) && count <= new VersionInfo(v).DataCodewords) + { + chosen = v; + return true; + } + } + + chosen = default; + return false; + } + + /// + /// Gets a value indicating whether a version is open to data of the requested version: versions A to H when none is requested, or the requested version + /// itself. The S versions carry numbers only. + /// + /// The requested version, or . + /// The candidate version. + /// when the candidate is open. + private static bool IsPermitted(CodeOneVersion? requested, CodeOneVersion candidate) + => !new VersionInfo(candidate).IsNumeric && (requested is null ? candidate <= CodeOneVersion.H : candidate == requested.Value); + + /// + /// Encodes the digits of an S version as one number plus one in 5-bit codewords, the most significant first, padded at the front with zero codewords to the + /// data codeword count. + /// + /// The digits. + /// The data codeword count of the version. + /// The buffer that receives the codewords. + /// The text is not digits, or has more digits than the version carries. + private static void EncodeNumber(ReadOnlySpan text, int dataCount, Span codewords) + { + // S-10, S-20 and S-30 carry 6, 12 and 18 digits in 4, 8 and 12 codewords, three digits for every two codewords. The largest number of that many + // digits, plus one, then fits the 5 bits of each codeword: 10^6 is under 2^20, 10^12 under 2^40 and 10^18 under 2^60. + int maximumDigits = dataCount * 3 / 2; + if (text.Length > maximumDigits || text.ContainsAnyExceptInRange('0', '9')) + { + throw new ArgumentException($"This Code One S version carries 1 to {maximumDigits} digits.", nameof(text)); + } + + ulong value = ulong.Parse(text, System.Globalization.CultureInfo.InvariantCulture) + 1; + + for (int i = dataCount - 1; i >= 0; i--) + { + codewords[i] = (int)(value & 31); + value >>= 5; + } + } + + /// + /// Encodes the data of versions A to H and T into codewords. + /// + /// The data. + /// The version, or for versions A to H. + /// The buffer the codewords are written to. + /// The scratch of the encodation, long. + /// The number of codewords, or -1 when no permitted version holds them. + private static int EncodeData(ReadOnlySpan text, CodeOneVersion? version, Span codewords, Span scratch) + { + // The entries are plain ints, so the int scratch holds them in place. + int tableLength = LookAheadEntry.IntCount * (text.Length + 1); + Span table = MemoryMarshal.Cast(scratch[..tableLength]); + PrepareLookAhead(text, table); + Encoder encoder = new(text, version, codewords, table, scratch.Slice(tableLength, MaximumValues)); + return encoder.Run(); + } + + /// + /// Fills the entries the look-ahead reads, one for each position of the message and one past its end. Each entry holds three runs from its position. The + /// first run is the digits that start there. The second run is the distance to the next X12 terminator, carriage return, asterisk or greater than. The + /// third run is the distance to the next character that X12 cannot carry. The entry past the end stops each run, and a distance with no end stands past the + /// end. Each entry also holds the running sums of the costs of the ASCII, C40, Text, X12 and Byte modes before its position. + /// + /// In ASCII mode, a digit costs half a codeword, and a character above 127 costs two whole codewords. Any other character rounds the count up first, + /// because it cannot share a codeword with a digit before it. In C40, Text and X12 mode, a basic set character costs two thirds of a codeword, one of three + /// values in two codewords. A shifted character costs two values and an Upper Shift two more. X12 costs a character it cannot carry 10/3 codewords, or + /// 13/3 above 127. Byte mode costs one codeword a byte. + /// + /// + /// The message, characters U+0000 to U+00FF. + /// The entries, the message length plus one. + private static void PrepareLookAhead(ReadOnlySpan message, Span table) + { + int length = message.Length; + ref LookAheadEntry end = ref table[length]; + end.Digits = 0; + end.Terminator = length + 1; + end.NonX12 = length + 1; + for (int i = length - 1; i >= 0; i--) + { + char value = message[i]; + ref LookAheadEntry entry = ref table[i]; + ref readonly LookAheadEntry next = ref table[i + 1]; + entry.Digits = IsDigit(value) ? next.Digits + 1 : 0; + entry.Terminator = value is '\r' or '*' or '>' ? 0 : next.Terminator + 1; + entry.NonX12 = IsX12(value) ? next.NonX12 + 1 : 0; + } + + table[0].Sums = default; + for (int i = 0; i < length; i++) + { + char value = message[i]; + bool extended = value > 127; + ref readonly ModeCounts sum = ref table[i].Sums; + ref ModeCounts next = ref table[i + 1].Sums; + next.Ascii = AddAsciiCost(sum.Ascii, value); + next.C40 = sum.C40 + (IsC40Basic(value) ? 2 * Unit / 3 : extended ? 8 * Unit / 3 : 4 * Unit / 3); + next.Text = sum.Text + (IsTextBasic(value) ? 2 * Unit / 3 : extended ? 8 * Unit / 3 : 4 * Unit / 3); + next.X12 = sum.X12 + (IsX12(value) ? 2 * Unit / 3 : extended ? 13 * Unit / 3 : 10 * Unit / 3); + next.Byte = sum.Byte + Unit; + } + } + + /// + /// Returns whether the message value is a digit. + /// + /// The message value. + /// for the digits 0 to 9. + private static bool IsDigit(int value) => value is >= '0' and <= '9'; + + /// + /// Returns whether the message value is in the basic set of C40 mode: space, the digits and the capital letters. + /// + /// The message value. + /// for a basic set character. + private static bool IsC40Basic(int value) => value is ' ' or (>= '0' and <= '9') or (>= 'A' and <= 'Z'); + + /// + /// Returns whether the message value is in the basic set of Text mode: space, the digits and the small letters. + /// + /// The message value. + /// for a basic set character. + private static bool IsTextBasic(int value) => value is ' ' or (>= '0' and <= '9') or (>= 'a' and <= 'z'); + + /// + /// Returns whether X12 mode carries the message value: carriage return, asterisk, greater than, space, the digits and the capital letters. + /// + /// The message value. + /// for an X12 character. + private static bool IsX12(int value) => value is '\r' or '*' or '>' || IsC40Basic(value); + + /// + /// Rounds a look-ahead count up to a whole codeword. + /// + /// The count in units. + /// The count rounded up to a multiple of . + private static int Ceiling(int count) => (count + Unit - 1) / Unit * Unit; + + /// + /// Adds the ASCII cost of one message value to a look-ahead count. + /// + /// The count in units. + /// The message value. + /// The count with the cost of the value added. + private static int AddAsciiCost(int count, int value) + => IsDigit(value) ? count + (Unit / 2) : Ceiling(count) + (value > 127 ? 2 * Unit : Unit); + + /// + /// Writes the C40, Text or X12 values of one message value. + /// + /// The mode, , or . + /// The message value, 0 to 255. + /// The buffer of at least four values that receives the values. + /// The number of values written, or 0 when X12 mode cannot carry the message value. + private static int TripletValues(Mode mode, int value, Span values) + { + // X12 has one set of 40 values and no shifts: carriage return 0, asterisk 1, greater than 2, space 3, the digits 4 to 13 and the capital letters 14 to + // 39. Any other character ends X12. + if (mode == Mode.X12) + { + int x12 = value switch + { + '\r' => 0, + '*' => 1, + '>' => 2, + ' ' => 3, + >= '0' and <= '9' => value - 44, + >= 'A' and <= 'Z' => value - 51, + _ => -1, + }; + + if (x12 < 0) + { + return 0; + } + + values[0] = x12; + return 1; + } + + // A character above 127 takes the Upper Shift in the Shift 2 set, then the values of the character 128 below it. + int count = 0; + if (value > 127) + { + values[0] = Shift2; + values[1] = ShiftedUpperShift; + count = 2; + value -= 128; + } + + // The basic set: space is 3 and the digits 0 to 9 are 4 to 13, so a digit is its character code less 44. + Span rest = values[count..]; + if (value == ' ') + { + rest[0] = 3; + return count + 1; + } + + if (IsDigit(value)) + { + rest[0] = value - 44; + return count + 1; + } + + // The basic set holds the capital letters in C40 mode and the small letters in Text mode, as the values 14 to 39, so A is 65 - 51 and a is 97 - 83. The + // other case moves to the Shift 3 set, where C40 puts the small letters at 1 to 26 after the grave accent at 0, and Text puts the capital letters + // there. + if (value is >= 'A' and <= 'Z') + { + if (mode == Mode.C40) + { + rest[0] = value - 51; + return count + 1; + } + + rest[0] = Shift3; + rest[1] = value - 64; + return count + 2; + } + + if (value is >= 'a' and <= 'z') + { + if (mode == Mode.Text) + { + rest[0] = value - 83; + return count + 1; + } + + rest[0] = Shift3; + rest[1] = value - 96; + return count + 2; + } + + // The rest of the characters to 127 take a shift and a value in that set. The control characters 0 to 31 are the Shift 1 set in order. The Shift 2 set + // holds the punctuation in three runs: 33 to 47 as 0 to 14, 58 to 64 as 15 to 21 and 91 to 95 as 22 to 26. The Shift 3 set holds 96 to 127 as 0 to 31; + // the letters of that range were handled above. + (int shift, int shifted) = value switch + { + < 32 => (Shift1, value), + <= 47 => (Shift2, value - 33), + <= 64 => (Shift2, value - 43), + <= 95 => (Shift2, value - 69), + _ => (Shift3, value - 96), + }; + + rest[0] = shift; + rest[1] = shifted; + return count + 2; + } + + /// + /// Adds the check codewords after the data codewords. The data codewords are spread over the blocks in turn, and the check codewords of the blocks are + /// interleaved the same way. Each block uses the generator with roots alpha^0 to alpha^(k-1). + /// + /// The data codewords followed by room for the check codewords. + /// The number of data codewords. + /// The number of blocks. + /// Whether the codewords are the 5-bit codewords of an S version. + private static void AddCheckCodewords(Span codewords, int dataCount, int blocks, bool numeric) + { + // A block takes at most 70 check codewords: version E in one block, and versions F, G and H in 2, 4 and 8. + GaloisField field = numeric ? Field5 : Field8; + int perBlock = (codewords.Length - dataCount) / blocks; + InlineArray128 generatorStorage = default; + InlineArray128 checkStorage = default; + Span generator = ((Span)generatorStorage)[..perBlock]; + field.Generator(0, generator); + field.InterleavedRemainders(codewords, dataCount, blocks, generator, ((Span)checkStorage)[..perBlock]); + } + + /// + /// Places the modules: the centre pattern rows, the risers and the black dots first, then the codeword bricks in the cells that remain, in reading order. + /// + /// The version. + /// The data and check codewords. + /// The scratch of the cells, one a module. + /// The modules to write. + private static void Layout(VersionInfo info, ReadOnlySpan codewords, Span cells, Span modules) + { + int rows = info.Rows; + int columns = info.Columns; + + // Each cell is unset, -1, until a pattern takes it; the cells left unset take the data. + cells.Fill(-1); + + ReadOnlySpan pattern = info.CentrePattern; + int patternTop = (rows - pattern.Length) / 2; + for (int p = 0; p < pattern.Length; p++) + { + WriteCentreRow((CentreRow)pattern[p], cells.Slice((patternTop + p) * columns, columns)); + } + + // A riser is a dark module and, every 12 rows, a second dark module beside it, on the top rows, with its mirror on the bottom rows. + for (int i = 0; i < info.RiserLength; i++) + { + sbyte second = (sbyte)(i % 12 == 0 ? 1 : 0); + for (int x = info.RiserOffset; x < columns; x += info.RiserInterval) + { + cells[(i * columns) + x] = 1; + cells[(i * columns) + x + 1] = second; + if (i != info.RiserLength - 1) + { + int mirror = ((rows - i - 1) * columns) + columns - x - 2; + cells[mirror] = 1; + cells[mirror + 1] = second; + } + } + } + + ReadOnlySpan dots = info.BlackDots; + for (int d = 0; d < dots.Length; d += 2) + { + cells[(dots[d + 1] * columns) + dots[d]] = 1; + } + + // The data cells, in reading order, are the data grid of the version row by row. A brick is two rows high. An 8-bit codeword is 4 modules wide, with + // its first four bits on top. Two 5-bit codewords make a brick 5 modules wide: the top row holds the first three bits of the first codeword and then + // the first two bits of the second. + int dataColumns = info.DataColumns; + bool numeric = info.IsNumeric; + int brickWidth = numeric ? 5 : 4; + int bricksPerRow = dataColumns / brickWidth; + int next = 0; + for (int i = 0; i < cells.Length; i++) + { + if (cells[i] >= 0) + { + modules[i] = cells[i] == 1; + continue; + } + + int row = next / dataColumns; + int column = next % dataColumns; + next++; + int brick = ((row / 2) * bricksPerRow) + (column / brickWidth); + int b = column % brickWidth; + bool top = (row & 1) == 0; + int bit; + if (numeric) + { + int first = codewords[2 * brick]; + int second = codewords[(2 * brick) + 1]; + bit = top + ? b < 3 ? first >> (4 - b) : second >> (7 - b) + : b < 2 ? first >> (1 - b) : second >> (4 - b); + } + else + { + bit = codewords[brick] >> (top ? 7 - b : 3 - b); + } + + modules[i] = (bit & 1) == 1; + } + } + + /// + /// Writes one row of the centre pattern. The and rows set the middle cell alone and + /// leave the other cells to the data. + /// + /// The row kind. + /// The row of cells. + private static void WriteCentreRow(CentreRow kind, Span row) + { + int last = row.Length - 1; + switch (kind) + { + case CentreRow.Light: + row.Clear(); + break; + case CentreRow.Dark: + row.Fill(1); + break; + case CentreRow.DarkWithLightEnds: + row.Fill(1); + row[0] = 0; + row[last] = 0; + break; + case CentreRow.LightWithDarkInner: + row.Clear(); + row[1] = 1; + row[last - 1] = 1; + break; + case CentreRow.DarkCentre: + row[last / 2] = 1; + break; + case CentreRow.LightCentre: + row[last / 2] = 0; + break; + case CentreRow.LightWithDarkEnds: + row.Clear(); + row[0] = 1; + row[last] = 1; + break; + default: + row.Fill(1); + row[1] = 0; + row[last - 1] = 0; + break; + } + } + + /// + /// The dimensions and fixed patterns of one version, read from . + /// + private readonly struct VersionInfo + { + /// + /// The index of the first entry of the version in . + /// + private readonly int offset; + + /// + /// Initializes a new instance of the struct. + /// + /// The version. + public VersionInfo(CodeOneVersion version) => this.offset = (int)version * VersionStride; + + /// + /// Gets the rows. + /// + public int Rows => VersionTable[this.offset]; + + /// + /// Gets the columns. + /// + public int Columns => VersionTable[this.offset + 1]; + + /// + /// Gets the columns the codeword bricks fill. + /// + public int DataColumns => VersionTable[this.offset + 2]; + + /// + /// Gets the data codewords. + /// + public int DataCodewords => VersionTable[this.offset + 3]; + + /// + /// Gets the check codewords. + /// + public int CheckCodewords => VersionTable[this.offset + 4]; + + /// + /// Gets the Reed-Solomon blocks. + /// + public int CheckBlocks => VersionTable[this.offset + 5]; + + /// + /// Gets the column of the first riser. + /// + public int RiserOffset => VersionTable[this.offset + 6]; + + /// + /// Gets the columns from one riser to the next; the column count where one riser stands alone. + /// + public int RiserInterval => VersionTable[this.offset + 7]; + + /// + /// Gets the rows of a riser, 0 for none. + /// + public int RiserLength => VersionTable[this.offset + 8]; + + /// + /// Gets the row kinds of the centre pattern, top row first, as values. + /// + public ReadOnlySpan CentrePattern => CentreRows.Slice(VersionTable[this.offset + 9], VersionTable[this.offset + 10]); + + /// + /// Gets the black dots as column and row pairs. + /// + public ReadOnlySpan BlackDots => CodeOneEncoder.BlackDots.Slice(VersionTable[this.offset + 11] * 2, VersionTable[this.offset + 12] * 2); + + /// + /// Gets a value indicating whether the version carries a number in 5-bit codewords, the S versions, which alone are 8 rows high. + /// + public bool IsNumeric => this.Rows == 8; + } + + /// + /// The counts of the modes the look-ahead compares, in the units of . + /// + private struct ModeCounts + { + /// + /// The ASCII count. + /// + public int Ascii; + + /// + /// The C40 count. + /// + public int C40; + + /// + /// The Text count. + /// + public int Text; + + /// + /// The X12 count. + /// + public int X12; + + /// + /// The Byte count. + /// + public int Byte; + + /// + /// Adds the counts of each mode. + /// + /// The first counts. + /// The second counts. + /// The sums. + public static ModeCounts operator +(ModeCounts left, ModeCounts right) + => new() + { + Ascii = left.Ascii + right.Ascii, + C40 = left.C40 + right.C40, + Text = left.Text + right.Text, + X12 = left.X12 + right.X12, + Byte = left.Byte + right.Byte, + }; + + /// + /// Subtracts the counts of each mode. + /// + /// The counts. + /// The counts to subtract. + /// The differences. + public static ModeCounts operator -(ModeCounts left, ModeCounts right) + => new() + { + Ascii = left.Ascii - right.Ascii, + C40 = left.C40 - right.C40, + Text = left.Text - right.Text, + X12 = left.X12 - right.X12, + Byte = left.Byte - right.Byte, + }; + } + + /// + /// The look-ahead data of one position of the message, which fills. The fields are ints only, so an int scratch buffer holds + /// the entries in place. + /// + private struct LookAheadEntry + { + /// + /// The digits that start at the position. + /// + public int Digits; + + /// + /// The distance from the position to the next X12 terminator. + /// + public int Terminator; + + /// + /// The distance from the position to the next character X12 cannot carry. + /// + public int NonX12; + + /// + /// The running sums of the mode costs of the characters before the position. + /// + public ModeCounts Sums; + + /// + /// Gets the size of an entry in ints. + /// + public static int IntCount => Unsafe.SizeOf() / sizeof(int); + } + + /// + /// The state of the encodation of versions A to H and T. + /// + private ref struct Encoder + { + /// + /// The message, characters U+0000 to U+00FF. + /// + private readonly ReadOnlySpan message; + + /// + /// The requested version, or for versions A to H. + /// + private readonly CodeOneVersion? version; + + /// + /// The codewords written so far. + /// + private readonly Span codewords; + + /// + /// The look-ahead entries, one for each position of the message and one past its end. + /// + private readonly ReadOnlySpan table; + + /// + /// The values a C40, Text or X12 run gathers. + /// + private readonly Span values; + + /// + /// The current mode. + /// + private Mode mode; + + /// + /// The position in the message. + /// + private int position; + + /// + /// The number of codewords written. + /// + private int written; + + /// + /// Whether the data overran the largest permitted version. + /// + private bool overflow; + + /// + /// The Decimal mode bits not yet written as a whole codeword. + /// + private int pendingBits; + + /// + /// The number of bits in . + /// + private int pendingCount; + + /// + /// Initializes a new instance of the struct. + /// + /// The message, characters U+0000 to U+00FF. + /// The version, or for versions A to H. + /// The buffer the codewords are written to. + /// The look-ahead entries of . + /// The buffer of the values of a C40, Text or X12 run. + public Encoder(ReadOnlySpan message, CodeOneVersion? version, Span codewords, ReadOnlySpan table, Span values) + { + this.message = message; + this.version = version; + this.codewords = codewords; + this.table = table; + this.values = values; + this.mode = Mode.Ascii; + } + + /// + /// Encodes the data in the mode each step chooses until every character is written. + /// + /// The number of codewords, or -1 when no permitted version holds them. + public int Run() + { + while (this.position < this.message.Length && !this.overflow) + { + switch (this.mode) + { + case Mode.Ascii: + this.EncodeAscii(); + break; + case Mode.Decimal: + this.EncodeDecimal(); + break; + case Mode.Byte: + this.EncodeByte(); + break; + default: + this.EncodeTriplets(); + break; + } + } + + return this.overflow ? -1 : this.written; + } + + /// + /// One step of ASCII mode: a latch into Decimal mode before a long digit run, a digit pair, a latch into the mode the look-ahead chooses, or one + /// character. + /// + private void EncodeAscii() + { + int run = this.table[this.position].Digits; + if (run >= 21 || (run >= 13 && this.position + run == this.message.Length)) + { + this.pendingBits = DecimalLatch; + this.pendingCount = 4; + this.mode = Mode.Decimal; + return; + } + + if (run >= 2) + { + this.AddDigitPair(); + return; + } + + Mode next = this.LookAhead(); + if (next != this.mode) + { + this.Add(next switch + { + Mode.C40 => LatchC40, + Mode.Text => LatchText, + Mode.X12 => LatchX12, + _ => LatchByte, + }); + + this.mode = next; + return; + } + + this.AddAscii(this.message[this.position++]); + } + + /// + /// Encodes in C40, Text or X12 mode, three values to two codewords, until the look-ahead or the end of the data returns to ASCII mode. + /// + private void EncodeTriplets() + { + Span values = this.values; + InlineArray4 scratchStorage = default; + Span scratch = scratchStorage; + int p = 0; + while (this.position != this.message.Length) + { + // Values past three halves of the largest version cannot fit, and the place for the last three characters stays free. + if (p > values.Length - 12) + { + this.overflow = true; + return; + } + + if (p % 3 == 0) + { + int run = this.table[this.position].Digits; + if (run >= 12 || (run >= 8 && this.position + run == this.message.Length)) + { + this.UnlatchAfter(values[..p]); + return; + } + + if (this.mode == Mode.X12) + { + if (!IsX12(this.message[this.position])) + { + this.AddTriplets(values[..p]); + + // Unlatch to ASCII unless one codeword is left and one ASCII character fits it. + if (this.Remaining(this.written) != 1 || this.message[this.position] > 127) + { + this.Add(Unlatch); + } + + this.mode = Mode.Ascii; + return; + } + + if ((this.position + 1 < this.message.Length && !IsX12(this.message[this.position + 1])) + || (this.position + 2 < this.message.Length && !IsX12(this.message[this.position + 2]))) + { + break; + } + } + else if (this.LookAhead() != this.mode) + { + this.UnlatchAfter(values[..p]); + return; + } + + if (this.message.Length - this.position <= 3 && this.TryEndTriplets(values, p)) + { + return; + } + } + + p += TripletValues(this.mode, this.message[this.position++], values[p..]); + } + + // Back up to a whole triplet and return to ASCII mode. + while (p % 3 != 0) + { + p -= TripletValues(this.mode, this.message[--this.position], scratch); + } + + this.UnlatchAfter(values[..p]); + if (this.position != this.message.Length) + { + // A character written in ASCII mode keeps the next step from latching straight back. + if (this.table[this.position].Digits >= 2) + { + this.AddDigitPair(); + } + else + { + this.AddAscii(this.message[this.position++]); + } + } + } + + /// + /// Ends C40, Text or X12 mode at the last three or fewer characters when they fill the last codewords: a whole triplet, or two values and a pad Shift + /// 1, with no unlatch, or one character in ASCII mode. + /// + /// The values, the first already gathered. + /// The number of values gathered, a multiple of three. + /// when the data ends here; otherwise, . + private bool TryEndTriplets(Span values, int p) + { + int remaining = this.Remaining(this.written + (p / 3 * 2)); + int tail = p; + for (int k = this.position; k < this.message.Length; k++) + { + int count = TripletValues(this.mode, this.message[k], values[tail..]); + if (count == 0) + { + // A character X12 cannot carry counts as four values, which no ending below accepts. + values.Slice(tail, 4).Fill(-1); + count = 4; + } + + tail += count; + } + + int tailCount = tail - p; + if (remaining == 2 && tailCount == 3) + { + this.AddTriplets(values[..tail]); + } + else if (remaining == 2 && tailCount == 2 && this.mode != Mode.X12) + { + values[tail] = 0; + this.AddTriplets(values[..(tail + 1)]); + } + else if (remaining == 2 && tailCount == 1) + { + this.AddTriplets(values[..p]); + this.Add(Unlatch); + this.AddAscii(this.message[this.position]); + } + else if (remaining == 1 && tailCount == 1) + { + this.AddTriplets(values[..p]); + this.AddAscii(this.message[this.position]); + } + else + { + return false; + } + + this.mode = Mode.Ascii; + this.position = this.message.Length; + return true; + } + + /// + /// Encodes in Decimal mode: three digits as the 10-bit value plus one, then the end of the run, which fills or unlatches the last codeword. Each whole + /// codeword is written as its eighth bit arrives. + /// + private void EncodeDecimal() + { + ReadOnlySpan message = this.message; + while (this.table[this.position].Digits >= 3) + { + int value = ((message[this.position] - '0') * 100) + ((message[this.position + 1] - '0') * 10) + (message[this.position + 2] - '0'); + this.AddBits(value + 1, 10); + this.position += 3; + } + + int free = (8 - this.pendingCount) % 8; + int remaining = this.Remaining(this.written); + bool lastDigit = this.position == message.Length - 1 && this.table[this.position].Digits == 1; + bool lastPair = this.position == message.Length - 2 && this.table[this.position].Digits == 2; + if (this.position == message.Length && ((this.Remaining(this.written - 1) == 1 && free == 0) || (remaining == 1 && free != 0))) + { + // The data ends in the last codeword: fill its free bits. + if (free is 4 or 6) + { + this.AddBits(0b1111, 4); + } + + if (free is 2 or 6) + { + this.AddBits(0b01, 2); + } + } + else if (!((lastDigit || lastPair) && remaining == 1 && free == 0)) + { + // Unlatch to ASCII unless the last digit fits the 4 or 6 bits left in the last codeword. A final digit or digit pair that fills the last + // codeword goes in it as ASCII instead. + if (!(lastDigit && remaining == 1 && free is 4 or 6)) + { + this.AddBits(DecimalUnlatch, 6); + free = (8 - this.pendingCount) % 8; + } + + if (free is 4 or 6) + { + this.AddBits(this.table[this.position].Digits >= 1 ? message[this.position++] - '0' + 1 : 0b1111, 4); + free -= 4; + } + + if (free == 2) + { + this.AddBits(0b01, 2); + } + } + + this.mode = Mode.Ascii; + } + + /// + /// Encodes in Byte mode while the look-ahead keeps it: a length of one codeword below 250 or two above, or 0 when the bytes fill the symbol, then the + /// bytes. + /// + private void EncodeByte() + { + int start = this.position; + while (this.position != this.message.Length && this.LookAhead() == Mode.Byte) + { + this.position++; + } + + int count = this.position - start; + if (this.Remaining(this.written + count) == 1 && this.position == this.message.Length) + { + this.Add(0); + } + else if (count < 250) + { + this.Add(count); + } + else + { + this.Add((count / 250) + 249); + this.Add(count % 250); + } + + foreach (char value in this.message.Slice(start, count)) + { + this.Add(value); + } + + this.mode = Mode.Ascii; + } + + /// + /// The mode look-ahead. Each mode keeps a count of the codewords it needs for the data read so far, in the units of , starting with + /// the cost of the latch to it. The count of each mode but ASCII is an offset plus its running sum, so each step reads the sums; the ASCII count + /// follows its sum once it stands at the same part of a codeword. From the fourth character one mode can win early, and at the end of the data the + /// rounded counts decide. + /// + /// The mode to continue in. + private readonly Mode LookAhead() + { + // Each count starts at the cost of the latch from the current mode: from ASCII mode one codeword into C40, Text or X12 mode and two into Byte mode, + // and from any other mode one codeword more for the unlatch. The current mode costs nothing. + const int unit = Unit; + ModeCounts initial = this.mode == Mode.Ascii + ? new ModeCounts { Ascii = 0, C40 = unit, Text = unit, X12 = unit, Byte = 2 * unit } + : new ModeCounts { Ascii = unit, C40 = 2 * unit, Text = 2 * unit, X12 = 2 * unit, Byte = 3 * unit }; + + switch (this.mode) + { + case Mode.C40: + initial.C40 = 0; + break; + case Mode.Text: + initial.Text = 0; + break; + case Mode.X12: + initial.X12 = 0; + break; + case Mode.Byte: + initial.Byte = 0; + break; + } + + // The count of a mode at a position is its offset plus the running sum there. Stage 1 counts ASCII by hand, so its offset is set when stage 1 ends. + ReadOnlySpan table = this.table; + int length = this.message.Length; + int start = this.position; + ModeCounts offset = initial - table[start].Sums; + + // Stage 1 reads one character at a time. It stops when the early test can run and the ASCII count stands at the same part of a codeword as its + // running sum. + int ascii = initial.Ascii; + int index = start; + while (index < length) + { + ascii = AddAsciiCost(ascii, this.message[index]); + index++; + if (index - start >= MinimumLookAhead) + { + ModeCounts counts = offset + table[index].Sums; + counts.Ascii = ascii; + if (this.EarlyChoice(counts, index) is Mode early) + { + return early; + } + + if ((ascii - table[index].Sums.Ascii) % unit == 0) + { + break; + } + } + } + + // Stage 2 reads the rest of the data from the running sums alone. + offset.Ascii = ascii - table[index].Sums.Ascii; + while (index < length) + { + index++; + if (this.EarlyChoice(offset + table[index].Sums, index) is Mode early) + { + return early; + } + } + + ModeCounts final = offset + table[length].Sums; + int a = Ceiling(final.Ascii); + int c = Ceiling(final.C40); + int t = Ceiling(final.Text); + int x = Ceiling(final.X12); + if (final.Byte <= a && final.Byte <= c && final.Byte <= t && final.Byte <= x) + { + return Mode.Byte; + } + + if (final.Ascii <= c && final.Ascii <= t && final.Ascii <= x && final.Ascii <= Ceiling(final.Byte)) + { + return Mode.Ascii; + } + + if (c <= t && c <= x) + { + return Mode.C40; + } + + return t <= x ? Mode.Text : Mode.X12; + } + + /// + /// The early test of the look-ahead: a mode wins when it is a whole codeword cheaper than every other mode, each other count rounded up. ASCII and Byte + /// compare their own count as it stands, and C40, Text and X12 their rounded count. Equal C40 and X12 counts go to X12 when an X12 terminator comes + /// before a character X12 cannot carry. + /// + /// The counts of the modes. + /// The position after the characters read. + /// The winning mode, or when no mode wins yet. + private readonly Mode? EarlyChoice(ModeCounts counts, int read) + { + const int unit = Unit; + int ascii = counts.Ascii; + int bytes = counts.Byte; + int a = Ceiling(ascii); + int c = Ceiling(counts.C40); + int t = Ceiling(counts.Text); + int x = Ceiling(counts.X12); + int b = Ceiling(bytes); + if (bytes + unit <= a && bytes + unit <= c && bytes + unit <= t && bytes + unit <= x) + { + return Mode.Byte; + } + + if (ascii + unit <= c && ascii + unit <= t && ascii + unit <= x && ascii + unit <= b) + { + return Mode.Ascii; + } + + if (t + unit <= a && t + unit <= c && t + unit <= x && t + unit <= b) + { + return Mode.Text; + } + + if (c + unit <= a && c + unit <= t) + { + if (c < x) + { + return Mode.C40; + } + + if (counts.C40 == counts.X12) + { + return this.table[read].Terminator < this.table[read].NonX12 ? Mode.X12 : Mode.C40; + } + } + + if (x + unit <= a && x + unit <= c && x + unit <= t && x + unit <= b) + { + return Mode.X12; + } + + return null; + } + + /// + /// Returns the codewords left before the end of the smallest permitted version that holds more than the given number of codewords, or + /// when none does. Every caller compares the result with 1 or 2 only, so a full largest version reads as no end in reach. + /// + /// The codewords used. + /// The codewords left. + private readonly int Remaining(int used) + { + for (CodeOneVersion v = CodeOneVersion.A; v <= CodeOneVersion.T48; v++) + { + int capacity = new VersionInfo(v).DataCodewords; + if (IsPermitted(this.version, v) && capacity > used) + { + return capacity - used; + } + } + + return int.MaxValue; + } + + /// + /// Writes the triplets gathered so far, then the unlatch to ASCII mode. + /// + /// The values, a multiple of three. + private void UnlatchAfter(ReadOnlySpan values) + { + this.AddTriplets(values); + this.Add(Unlatch); + this.mode = Mode.Ascii; + } + + /// + /// Writes the triplets of mode values as codewords: (1600 v1 + 40 v2 + v3 + 1) in two bytes. + /// + /// The values, a multiple of three. + private void AddTriplets(ReadOnlySpan values) + { + for (int k = 0; k + 2 < values.Length; k += 3) + { + int value = (values[k] * 1600) + (values[k + 1] * 40) + values[k + 2] + 1; + this.Add(value >> 8); + this.Add(value & 0xFF); + } + } + + /// + /// Writes the two digits at the current position as one ASCII codeword. + /// + private void AddDigitPair() + { + this.Add(DigitPairBase + ((this.message[this.position] - '0') * 10) + (this.message[this.position + 1] - '0')); + this.position += 2; + } + + /// + /// Writes one character in ASCII mode: its value plus one, after an Upper Shift above 127. + /// + /// The character. + private void AddAscii(int value) + { + if (value > 127) + { + this.Add(UpperShift); + value -= 128; + } + + this.Add(value + 1); + } + + /// + /// Adds Decimal mode bits, the most significant first, and writes each codeword as it completes. + /// + /// The bits. + /// The number of bits, at most 10. + private void AddBits(int value, int count) + { + this.pendingBits = (this.pendingBits << count) | value; + this.pendingCount += count; + + // Up to seven pending bits and ten new bits can complete two codewords. + while (this.pendingCount >= 8) + { + this.pendingCount -= 8; + this.Add((this.pendingBits >> this.pendingCount) & 0xFF); + this.pendingBits &= (1 << this.pendingCount) - 1; + } + } + + /// + /// Writes one codeword, or marks the encodation as overflowed past the largest version. + /// + /// The codeword. + private void Add(int codeword) + { + if (this.written >= MaximumCodewords) + { + this.overflow = true; + return; + } + + this.codewords[this.written++] = codeword; + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/CodeOne/CodeOneSymbology.cs b/src/ImageSharp.Drawing/Barcodes/CodeOne/CodeOneSymbology.cs new file mode 100644 index 00000000..747a1f93 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/CodeOne/CodeOneSymbology.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Code One symbology of the AIM USA Uniform Symbology Specification Code One: a matrix of square modules with a centre pattern of horizontal bars and +/// vertical risers. Versions A to H carry the characters U+0000 to U+00FF, versions S carry up to 18 digits, and versions T carry data in a symbol 16 rows +/// high. By default the symbol is the smallest of versions A to H that holds the data. +/// +public sealed class CodeOneSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class that takes the smallest of versions A to H that holds the data. + /// + public CodeOneSymbology() + { + } + + /// + /// Initializes a new instance of the class with a fixed version. + /// + /// The version. + public CodeOneSymbology(CodeOneVersion version) + { + Guard.MustBeBetweenOrEqualTo((int)version, (int)CodeOneVersion.A, (int)CodeOneVersion.T48, nameof(version)); + this.Version = version; + } + + /// + /// Gets the version, or when the symbol takes the smallest of versions A to H that holds the data. + /// + public CodeOneVersion? Version { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + CodeOneEncoder.BuildSymbol(text, this.Version, options.ResolveXDimension(0, DefaultMatrixXDimension, double.PositiveInfinity), ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/CodeOne/CodeOneVersion.cs b/src/ImageSharp.Drawing/Barcodes/CodeOne/CodeOneVersion.cs new file mode 100644 index 00000000..329cc382 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/CodeOne/CodeOneVersion.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The versions of a Code One symbol. +/// +public enum CodeOneVersion +{ + /// + /// Version A: 16 rows by 18 columns, 10 data codewords. + /// + A, + + /// + /// Version B: 22 rows by 22 columns, 19 data codewords. + /// + B, + + /// + /// Version C: 28 rows by 32 columns, 44 data codewords. + /// + C, + + /// + /// Version D: 40 rows by 42 columns, 91 data codewords. + /// + D, + + /// + /// Version E: 52 rows by 54 columns, 182 data codewords. + /// + E, + + /// + /// Version F: 70 rows by 76 columns, 370 data codewords. + /// + F, + + /// + /// Version G: 104 rows by 98 columns, 732 data codewords. + /// + G, + + /// + /// Version H: 148 rows by 134 columns, 1480 data codewords. + /// + H, + + /// + /// Version S-10: 8 rows by 11 columns, up to 6 digits only. + /// + S10, + + /// + /// Version S-20: 8 rows by 21 columns, up to 12 digits only. + /// + S20, + + /// + /// Version S-30: 8 rows by 31 columns, up to 18 digits only. + /// + S30, + + /// + /// Version T-16: 16 rows by 17 columns, 10 data codewords. + /// + T16, + + /// + /// Version T-32: 16 rows by 33 columns, 24 data codewords. + /// + T32, + + /// + /// Version T-48: 16 rows by 49 columns, 38 data codewords. + /// + T48, +} diff --git a/src/ImageSharp.Drawing/Barcodes/Composite/Ean13CompositeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Composite/Ean13CompositeSymbology.cs new file mode 100644 index 00000000..24b0e5cc --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Composite/Ean13CompositeSymbology.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The EAN-13 Composite symbology of ISO/IEC 24723: an EAN-13 linear component, a 6X separator pattern, and a 4-column CC-A or CC-B 2D component above it. +/// The first text is the digits of the EAN-13 symbol, and the second the element strings of the 2D component. +/// +public sealed class Ean13CompositeSymbology : CompositeBarcodeSymbology +{ + /// + /// The data columns of the 2D component, Table 1 of ISO/IEC 24723. + /// + private const int Columns = 4; + + /// + /// Initializes a new instance of the class. + /// + public Ean13CompositeSymbology() + : base(new Ean13Symbology(), new Gs1CompositeComponentSymbology(Gs1CompositeComponent.CcA, Columns)) + { + } + + /// + internal override void EncodeParts( + Span parts, + BarcodeOptions options, + ReadOnlySpan first, + ReadOnlySpan second, + ReadOnlySpan third) + { + InlineArray16 gtinStorage = default; + ReadOnlySpan gtin = EanUpcEncoder.ValidateAndApplyCheckDigit(first, 12, gtinStorage); + Gs1CompositeLayout.EncodeEanUpc(this.Symbologies[0], Columns, parts, options, first, second, gtin); + } + + /// + internal override PointF Place(int index, ReadOnlySpan parts, ReadOnlySpan symbolOrigins, BarcodeOptions options) + { + int offset = Gs1CompositeLayout.EanUpcOffset((int)parts[0].WidthInModules, (int)parts[1].WidthInModules); + return Gs1CompositeLayout.PlaceAbove(parts, symbolOrigins[0], offset, options); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Composite/Ean8CompositeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Composite/Ean8CompositeSymbology.cs new file mode 100644 index 00000000..c20c8152 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Composite/Ean8CompositeSymbology.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The EAN-8 Composite symbology of ISO/IEC 24723: an EAN-8 linear component, a 6X separator pattern, and a 3-column CC-A or CC-B 2D component above it. +/// The first text is the digits of the EAN-8 symbol, and the second the element strings of the 2D component. +/// +public sealed class Ean8CompositeSymbology : CompositeBarcodeSymbology +{ + /// + /// The data columns of the 2D component, Table 1 of ISO/IEC 24723. + /// + private const int Columns = 3; + + /// + /// Initializes a new instance of the class. + /// + public Ean8CompositeSymbology() + : base(new Ean8Symbology(), new Gs1CompositeComponentSymbology(Gs1CompositeComponent.CcA, Columns)) + { + } + + /// + internal override void EncodeParts( + Span parts, + BarcodeOptions options, + ReadOnlySpan first, + ReadOnlySpan second, + ReadOnlySpan third) + { + InlineArray16 gtinStorage = default; + ReadOnlySpan gtin = EanUpcEncoder.ValidateAndApplyCheckDigit(first, 7, gtinStorage); + Gs1CompositeLayout.EncodeEanUpc(this.Symbologies[0], Columns, parts, options, first, second, gtin); + } + + /// + internal override PointF Place(int index, ReadOnlySpan parts, ReadOnlySpan symbolOrigins, BarcodeOptions options) + { + int offset = Gs1CompositeLayout.EanUpcOffset((int)parts[0].WidthInModules, (int)parts[1].WidthInModules); + return Gs1CompositeLayout.PlaceAbove(parts, symbolOrigins[0], offset, options); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Composite/Gs1128CompositeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1128CompositeSymbology.cs new file mode 100644 index 00000000..453ef85a --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1128CompositeSymbology.cs @@ -0,0 +1,168 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The GS1-128 Composite symbology of ISO/IEC 24723: a GS1-128 linear component with its linkage character, a 1X separator pattern, and a 2D component above +/// it. The first text is the element strings the linear component carries, and the second those of the 2D component. +/// +/// Section 7.4 takes a 4-column CC-A or CC-B, or a CC-C of any columns. CC-A takes CC-B when the data exceeds it, and CC-B takes CC-C when the data exceeds +/// it. CC-C takes the most columns whose rows fit over the linear component and its quiet zones, and more when the data needs them. +/// +/// +public sealed class Gs1128CompositeSymbology : CompositeBarcodeSymbology +{ + /// + /// The data columns of CC-A and CC-B, section 7.4 of ISO/IEC 24723. + /// + private const int Columns = 4; + + /// + /// The largest number of data characters in the linear component. Section 5.4.4.3 of the GS1 General Specifications: "The number of encoded data + /// characters SHALL NOT exceed 48." + /// + private const int MaximumDataCharacters = 48; + + /// + /// The CC-C columns over the linear component: a CC-C row of c columns is 17c + 69 modules, from its start pattern to its stop pattern. Clause 12 f) puts + /// it 7 modules left of the linear component, so it fits over the linear component and its trailing quiet zone of 10 modules when 17c + 62 is at most the + /// width of the linear component plus 10. + /// + private const int CcCOverhang = 52; + + /// + /// The modules of a CC-C data column. + /// + private const int CcCColumnModules = 17; + + /// + /// The offset of a CC-C from the linear component. Clause 12 f): the first interior space of the CC-C, the ninth module of its start pattern, stands over + /// the second module of the start character of the linear component. + /// + private const int CcCOffset = -7; + + /// + /// The width in modules of a 4-column CC-A or CC-B. A CC-C of c columns is 17c + 69 modules wide, which is never this width. + /// + private const int CcAOrCcBWidth = 99; + + /// + /// Initializes a new instance of the class with a CC-A 2D component, which grows to CC-B and to CC-C as the data + /// needs. + /// + public Gs1128CompositeSymbology() + : this(Gs1CompositeComponent.CcA) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The smallest 2D component the symbol takes. + public Gs1128CompositeSymbology(Gs1CompositeComponent component) + : base(new Gs1128Symbology(), new Gs1CompositeComponentSymbology(component, Columns)) + => this.Component = component; + + /// + /// Gets the smallest 2D component the symbol takes. + /// + public Gs1CompositeComponent Component { get; } + + /// + internal override void EncodeParts( + Span parts, + BarcodeOptions options, + ReadOnlySpan first, + ReadOnlySpan second, + ReadOnlySpan third) + { + Gs1CompositeLayout.Validate(first, second); + InlineArray64 bufferStorage = default; + int capacity = Gs1Data.PreparedCapacity(first, MaximumDataCharacters); + ValueStringBuilder encoded = capacity <= Gs1Data.InlineBufferLength ? new(bufferStorage) : new(capacity); + try + { + Gs1Data.Encode(first, ref encoded); + Guard.MustBeLessThanOrEqualTo(encoded.Length, MaximumDataCharacters, nameof(first)); + + // The interpretation is never longer than the text, which PreparedCapacity bounds to 80 characters, so the inline buffer always holds it. + InlineArray128 readableStorage = default; + Span readable = readableStorage; + int readableLength = options.Font is null ? 0 : Gs1Data.WriteReadable(first, false, readable); + + // The linkage character of CC-A and CC-B and that of CC-C are each one symbol character, so the width of the linear component, which sets the + // columns of CC-C, is the same for both. A symbol that takes CC-C encodes the linear component again with its linkage character. + Code128Linkage linkage = this.Component == Gs1CompositeComponent.CcC ? Code128Linkage.CcC : Code128Linkage.CcAOrCcB; + BuildLinear(encoded.AsSpan(), readable[..readableLength], linkage, options, ref parts[0]); + int ccCColumns = Math.Max(Gs1CompositeComponentEncoder.MinimumCcCColumns, ((int)parts[0].WidthInModules - CcCOverhang) / CcCColumnModules); + int columns = this.Component == Gs1CompositeComponent.CcC ? ccCColumns : Columns; + double xDimension = parts[0].XDimension; + Gs1CompositeComponent used = Gs1CompositeLayout.BuildComponent(second, this.Component, columns, ccCColumns, xDimension, options, ref parts[1]); + if (used == Gs1CompositeComponent.CcC && linkage != Code128Linkage.CcC) + { + parts[0].Dispose(); + parts[0] = default; + BuildLinear(encoded.AsSpan(), readable[..readableLength], Code128Linkage.CcC, options, ref parts[0]); + } + + if (used != Gs1CompositeComponent.CcC) + { + Gs1CompositeLayout.ValidateGs1128Length((int)parts[0].WidthInModules); + } + + BarcodeSymbol linear = parts[0]; + double length = (linear.LeadingQuietZone + linear.WidthInModules + linear.TrailingQuietZone) * linear.XDimension; + if (length > Gs1128Symbology.MaximumLength) + { + throw new ArgumentException( + $"A GS1-128 symbol with its quiet zones is at most {Gs1128Symbology.MaximumLength} mm long; this one is {length:0.##} mm at its X dimension.", + nameof(first)); + } + } + finally + { + encoded.Dispose(); + } + + // Section 11.4: the separator is the complement of the linear component. + Gs1CompositeLayout.AddSeparator(ref parts[0], 0, 0, [], false); + } + + /// + internal override PointF Place(int index, ReadOnlySpan parts, ReadOnlySpan symbolOrigins, BarcodeOptions options) + { + // The width of the 2D component tells CC-A or CC-B from CC-C. + int componentWidth = (int)parts[1].WidthInModules; + int offset = componentWidth == CcAOrCcBWidth ? Gs1CompositeLayout.Gs1128Offset((int)parts[0].WidthInModules, componentWidth) : CcCOffset; + return Gs1CompositeLayout.PlaceAbove(parts, symbolOrigins[0], offset, options); + } + + /// + /// Builds the linear component, as builds it, with a linkage character. + /// + /// The prepared element strings. + /// The human readable interpretation. + /// The linkage character. + /// The options that control layout choices. + /// The symbol to write. + private static void BuildLinear( + ReadOnlySpan encoded, + ReadOnlySpan readable, + Code128Linkage linkage, + BarcodeOptions options, + ref BarcodeSymbol symbol) + => Code128Encoder.BuildSymbol( + encoded, + true, + [], + readable, + [], + Gs1Data.Gs1128MinimumXDimension, + Gs1Data.Gs1128MaximumXDimension, + linkage, + options, + ref symbol); +} diff --git a/src/ImageSharp.Drawing/Barcodes/Composite/Gs1CompositeComponent.cs b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1CompositeComponent.cs new file mode 100644 index 00000000..2c681f6b --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1CompositeComponent.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The 2D components of a GS1 Composite symbol. Each one carries the same bit stream, which ISO/IEC 24723 encodes into a different MicroPDF417 or PDF417 +/// symbol. +/// +public enum Gs1CompositeComponent +{ + /// + /// CC-A, a MicroPDF417 variant of 2, 3 or 4 data columns and 3 to 12 rows that carries the bit stream in base 928. A symbol whose data exceeds CC-A takes + /// CC-B of the same columns, as section 4.4 of ISO/IEC 24723 requires. + /// + CcA, + + /// + /// CC-B, a MicroPDF417 symbol of 2, 3 or 4 data columns that carries the bit stream in Byte Compaction mode after the codeword 920. + /// + CcB, + + /// + /// CC-C, a PDF417 symbol of 1 to 30 data columns that carries the bit stream in Byte Compaction mode after the codeword 920. Only a GS1-128 Composite + /// symbol takes it. + /// + CcC, +} diff --git a/src/ImageSharp.Drawing/Barcodes/Composite/Gs1CompositeComponentEncoder.cs b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1CompositeComponentEncoder.cs new file mode 100644 index 00000000..29a45b4f --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1CompositeComponentEncoder.cs @@ -0,0 +1,997 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Builds the 2D component of a GS1 Composite symbol, ISO/IEC 24723. The element strings become one bit stream, clause 5: an encodation method field, a +/// compressed field for some leading Application Identifiers, and a general purpose field, padded to the capacity of the smallest symbol that holds it. CC-A +/// carries the bit stream in base 928, clause 8. CC-B and CC-C carry it in PDF417 Byte Compaction mode after the codeword 920, clauses 9 and 10. +/// +internal static class Gs1CompositeComponentEncoder +{ + /// + /// The fewest data columns of CC-A and CC-B. + /// + public const int MinimumColumns = 2; + + /// + /// The most data columns of CC-A and CC-B. + /// + public const int MaximumColumns = 4; + + /// + /// The fewest data columns of CC-C, section 5.11.2.3 of the GS1 General Specifications: "1 to 30 data/EC codeword columns". + /// + public const int MinimumCcCColumns = 1; + + /// + /// The most data columns of CC-C. + /// + public const int MaximumCcCColumns = 30; + + /// + /// The row height of CC-A and CC-B in modules. Section 11.3 of ISO/IEC 24723 gives a minimum of 2X, and Table 9 sizes the symbols at that height. + /// + private const float RowHeight = 2F; + + /// + /// The fewest rows of CC-C, section 5.11.2.3 of the GS1 General Specifications: "from 3 to 30 rows". + /// + private const int MinimumCcCRows = 3; + + /// + /// The most rows of CC-C. + /// + private const int MaximumCcCRows = 30; + + /// + /// The columns of a CC-C row that carry no data: the start pattern, the two row indicators and the stop pattern. + /// + private const int CcCOverheadColumns = 4; + + /// + /// The most bits of a bit stream: the byte capacity of the largest CC-C symbol, 30 columns of 30 rows at error correction level 4, eight bits each. + /// + private const int MaximumBits = 8304; + + /// + /// The longest prepared data a 2D component can hold. No encodation of ISO/IEC 24723 takes fewer than 2 bits for a character of prepared data, so data + /// longer than half of never fits. + /// + public const int MaximumPrepared = MaximumBits / 2; + + /// + /// The number of bits section 8.5 converts into one group of base 928 codewords. + /// + private const int Base928GroupBits = 69; + + /// + /// The number of base 928 codewords of a full group of bits. + /// + private const int Base928GroupCodewords = 7; + + /// + /// The base of the CC-A codewords, section 8.5. + /// + private const int Base928 = 928; + + /// + /// The codeword that starts the data of CC-B, clause 9 a), and follows the Symbol Length Descriptor of CC-C, clause 10 a). + /// + private const int CompositeCodeword = 920; + + /// + /// The modes of the general purpose field. The class of a character is the mode with the fewest bits that carries it. + /// + private enum FieldMode : byte + { + /// + /// The class of a character the field cannot carry. + /// + None, + + /// + /// Digits and the separator, two per seven bits, section 5.4.1. + /// + Numeric, + + /// + /// Digits, capital letters and five punctuation characters, section 5.4.2. + /// + Alphanumeric, + + /// + /// The characters of Table 7, section 5.4.3. + /// + Iso646, + + /// + /// Capital letters and digits, section 5.3.3. Only the rest of Application Identifier (90) starts in it, and no character class is alpha. + /// + Alpha, + } + + /// + /// Gets the 17 CC-A sizes, six values each: the data columns, the rows, the error correction codewords of Table 9, and the left, centre and right row + /// address patterns of the first row from Table 10 and Table 11. The sizes of one column count stand in order of capacity. A 3-column CC-A has no left row + /// address pattern, clause 8.2 d), and its left value gives the row of Table 11 that sets the cluster of its first row. + /// + private static ReadOnlySpan CcASizes => + [ + 2, 5, 4, 39, 0, 19, + 2, 6, 4, 1, 0, 33, + 2, 7, 5, 32, 0, 12, + 2, 8, 5, 8, 0, 40, + 2, 9, 6, 14, 0, 46, + 2, 10, 6, 43, 0, 23, + 2, 12, 7, 20, 0, 52, + 3, 4, 4, 11, 43, 23, + 3, 5, 5, 1, 33, 13, + 3, 6, 6, 5, 37, 17, + 3, 7, 7, 15, 47, 27, + 3, 8, 7, 21, 1, 33, + 4, 3, 4, 40, 20, 52, + 4, 4, 5, 43, 23, 3, + 4, 5, 6, 46, 26, 6, + 4, 6, 7, 34, 14, 46, + 4, 7, 8, 29, 9, 41, + ]; + + /// + /// Gets the CC-B sizes of Table 5-41 of the GS1 General Specifications, which "lists all the possible column and row combinations for CC-B", two values + /// each: the data columns and the rows. The sizes of one column count stand in order of capacity. + /// + private static ReadOnlySpan CcBSizes => + [ + 2, 17, 2, 20, 2, 23, 2, 26, + 3, 15, 3, 20, 3, 26, 3, 32, 3, 38, 3, 44, + 4, 10, 4, 12, 4, 15, 4, 20, 4, 26, 4, 32, 4, 38, 4, 44, + ]; + + /// + /// Gets the letters of Table 3, whose four bit code is their index. + /// + private static ReadOnlySpan Table3Letters => "BDHIJKLNPQRSTVWZ"; + + /// + /// Gets the punctuation characters ISO/IEC 646 mode encodes, in the order of their values from 232 in Table 7. + /// + private static ReadOnlySpan Iso646Punctuation => "!\"%&'()*+,-./:;<=>?_ "; + + /// + /// Encodes the element strings and builds the 2D component. + /// + /// The element strings, in the element string syntax. + /// The smallest 2D component the symbol takes. + /// The data columns: 2 to 4 for CC-A and CC-B, 1 to 30 for CC-C. + /// The data columns of CC-C when a CC-B symbol cannot hold the data, or 0 when the symbol cannot take CC-C. + /// The X dimension in millimetres. + /// The symbol to write. + /// The 2D component the symbol took. + /// The text is not valid element string syntax, carries a character the bit stream cannot encode, or does not fit the + /// largest symbol. + public static Gs1CompositeComponent Build( + ReadOnlySpan text, + Gs1CompositeComponent component, + int columns, + int ccCColumns, + double xDimension, + ref BarcodeSymbol symbol) + { + // The length check runs before the buffers take their size from the text. + _ = Gs1Data.PreparedCapacity(text, MaximumPrepared); + + // An element string is at least five characters of text, so the text holds at most a fifth of its length in element strings. The general purpose field + // drops the parentheses and adds at most one separator after each element string, so it is never longer than the text. + InlineArray16 elementStorage = default; + int elementCapacity = (text.Length / 5) + 1; + ValueListBuilder elements = elementCapacity <= Gs1Data.InlineElementCount ? new(elementStorage) : new(elementCapacity); + PooledBuffer field = new(text.Length); + PooledBuffer stream = new(MaximumBits / 8); + try + { + Gs1Data.Parse(text, ref elements); + Gs1Data.Validate(text, elements.AsSpan()); + + stream.Span.Clear(); + BitStream bits = new(stream.Span); + int fieldLength = EncodeLeadingFields(text, elements.AsSpan(), ref bits, field.Span, out FieldMode mode); + mode = EncodeGeneralField(field.Span[..fieldLength], mode, ref bits, out char lastDigit); + + int c = columns; + int rows; + int level; + if (lastDigit != '\0') + { + // Section 5.4.1 c) covers a single digit left at the end. The remaining bit count comes from the smallest symbol that holds the bit stream so + // far. When four to six bits remain, the digit takes four bits as its value plus one. The fifth and sixth bits are zero. Otherwise, the digit + // takes seven bits as a pair with a separator. + int remaining = Capacity(bits.Position, ref component, ref c, ccCColumns, out _, out _) - bits.Position; + if (remaining is >= 4 and <= 6) + { + bits.Append(lastDigit - '0' + 1, 4); + bits.Append(0, remaining - 4); + } + else + { + bits.Append(((lastDigit - '0') * 11) + 10 + 8, 7); + } + } + + int capacity = Capacity(bits.Position, ref component, ref c, ccCColumns, out rows, out level); + if (capacity < 0) + { + throw new ArgumentException( + $"The element strings take {bits.Position} bits; the 2D component of a GS1 Composite symbol holds fewer.", + nameof(text)); + } + + // Section 5.4.4: the unused bits repeat 00100, the last copy cut on the right. A field that ends in numeric mode first latches out of it with 0000, + // and one that ends in alpha mode with the separator 11111 and 0000, each cut short when fewer bits remain. + int pad = capacity - bits.Position; + if (mode == FieldMode.Numeric) + { + bits.Append(0, Math.Min(4, pad)); + } + else if (mode == FieldMode.Alpha) + { + int latch = Math.Min(9, pad); + bits.Append(0b111110000 >> (9 - latch), latch); + } + + for (int k = 0; bits.Position < capacity; k = (k + 1) % 5) + { + bits.Append(k == 2 ? 1 : 0, 1); + } + + BuildSymbol(ref bits, component, c, rows, level, xDimension, ref symbol); + return component; + } + finally + { + elements.Dispose(); + field.Dispose(); + stream.Dispose(); + } + } + + /// + /// Writes the encodation method field of section 5.2 and the compressed field of section 5.3, and writes the characters the general purpose field carries: + /// what the compressed field leaves of the element strings, each Application Identifier and its data, with a separator after every variable length element + /// string but the last. + /// + /// The element strings. + /// The parsed element strings. + /// The bit stream. + /// The buffer the characters of the general purpose field are written to, as long as the text. + /// The mode the general purpose field starts in. + /// The number of characters of the general purpose field. + private static int EncodeLeadingFields( + scoped ReadOnlySpan text, + scoped ReadOnlySpan elements, + ref BitStream bits, + scoped Span field, + out FieldMode mode) + { + int written = 0; + int next = 0; + mode = FieldMode.Numeric; + ReadOnlySpan first = elements[0].Identifier(text); + ReadOnlySpan firstData = elements[0].Data(text); + if (first.SequenceEqual("10") || first.SequenceEqual("11") || first.SequenceEqual("17")) + { + // Section 5.3.1, method "10": a production date (11) or an expiration date (17) takes 16 bits and a bit that names its Application Identifier, + // and "11" stands for no date. + bits.Append(0b10, 2); + if (first[1] != '0') + { + int year = ((firstData[0] - '0') * 10) + (firstData[1] - '0'); + int month = ((firstData[2] - '0') * 10) + (firstData[3] - '0'); + int day = ((firstData[4] - '0') * 10) + (firstData[5] - '0'); + bits.Append((year * 384) + ((month - 1) * 32) + day, 16); + bits.Append(first[1] == '7' ? 1 : 0, 1); + next = 1; + } + else + { + bits.Append(0b11, 2); + } + + // A batch or lot number (10) next to the date carries its data alone, and a separator ends it when more data follows. Section 5.3.1: when no lot + // number follows the date directly, a separator follows the date, also when no other data follows. The separator is not transmitted. + if (next < elements.Length && elements[next].Identifier(text).SequenceEqual("10")) + { + written = Append(field, written, elements[next].Data(text)); + next++; + if (next < elements.Length) + { + field[written++] = Gs1Data.Separator; + } + } + else + { + field[written++] = Gs1Data.Separator; + } + } + else if (first.SequenceEqual("90") && Ai90Prefix(firstData, out int digits)) + { + // Section 5.3.2, method "11": the data of (90) starts with up to three digits and a capital letter. The rest of it takes the scheme that suits it + // best, and a serial number (21) or an (8004) right after it carries its data alone. + bits.Append(0b11, 2); + ReadOnlySpan rest = firstData[(digits + 1)..]; + int letters = 0; + int numbers = 0; + foreach (char c in rest) + { + letters += char.IsAsciiLetterUpper(c) ? 1 : 0; + numbers += char.IsAsciiDigit(c) ? 1 : 0; + } + + // Section 5.3.3: alpha mode suits a rest of capital letters and digits with more letters than digits. A rest of digits alone takes numeric mode, + // and any other rest alphanumeric mode. + if (letters + numbers == rest.Length && letters > numbers) + { + mode = FieldMode.Alpha; + bits.Append(0b11, 2); + } + else if (letters + numbers == rest.Length && letters == 0) + { + bits.Append(0b10, 2); + } + else + { + mode = FieldMode.Alphanumeric; + bits.Append(0, 1); + } + + ReadOnlySpan second = elements.Length > 1 ? elements[1].Identifier(text) : []; + bool implied = second.SequenceEqual("21") || second.SequenceEqual("8004"); + if (implied) + { + bits.Append(second.SequenceEqual("21") ? 0b10 : 0b11, 2); + } + else + { + bits.Append(0, 1); + } + + // The digits and the letter take nine bits when the number is under 31 and Table 3 holds the letter, and otherwise 11111, ten bits and five. + int number = 0; + for (int i = 0; i < digits; i++) + { + number = (number * 10) + (firstData[i] - '0'); + } + + char letter = firstData[digits]; + int code = Table3Letters.IndexOf(letter); + if (number < 31 && code >= 0) + { + bits.Append(number, 5); + bits.Append(code, 4); + } + else + { + bits.Append(0b11111, 5); + bits.Append(number, 10); + bits.Append(letter - 'A', 5); + } + + written = Append(field, written, rest); + next = 1; + if (next < elements.Length) + { + field[written++] = Gs1Data.Separator; + } + + if (implied) + { + written = Append(field, written, elements[1].Data(text)); + next = 2; + if (next < elements.Length) + { + field[written++] = Gs1Data.Separator; + } + } + } + else + { + // Section 5.2, method "0": the general purpose field carries all the data. + bits.Append(0, 1); + } + + for (int i = next; i < elements.Length; i++) + { + written = Append(field, written, elements[i].Identifier(text)); + written = Append(field, written, elements[i].Data(text)); + if (i < elements.Length - 1 && !elements[i].Entry.PredefinedLength) + { + field[written++] = Gs1Data.Separator; + } + } + + return written; + } + + /// + /// Returns whether the data of Application Identifier (90) starts as method "11" of section 5.3.2 requires: zero to three digits without a leading zero, + /// then a capital letter. + /// + /// The data of (90). + /// The number of leading digits. + /// when the data starts that way. + private static bool Ai90Prefix(ReadOnlySpan data, out int digits) + { + digits = 0; + while (digits < 3 && digits < data.Length && char.IsAsciiDigit(data[digits]) && !(digits == 0 && data[0] == '0')) + { + digits++; + } + + return digits < data.Length && char.IsAsciiLetterUpper(data[digits]); + } + + /// + /// Copies characters into the general purpose field. + /// + /// The general purpose field. + /// The number of characters written before. + /// The characters. + /// The number of characters written after. + private static int Append(Span field, int written, ReadOnlySpan characters) + { + characters.CopyTo(field[written..]); + return written + characters.Length; + } + + /// + /// Encodes the general purpose field of section 5.4 from a mode. Numeric mode carries two digits or a digit and a separator per seven bits, alphanumeric + /// mode a character per five or six bits, and ISO/IEC 646 mode a character per five, seven or eight bits. Each mode latches to another when the characters + /// ahead favour it. Alpha mode, section 5.3.3, carries the rest of (90) up to its separator, which latches to numeric mode. + /// + /// The characters of the general purpose field. + /// The mode at the start of the field. + /// The bit stream. + /// A single digit left at the end in numeric mode, or zero. + /// The mode at the end of the field. + /// The field holds a character the bit stream cannot carry. + private static FieldMode EncodeGeneralField(ReadOnlySpan text, FieldMode mode, ref BitStream bits, out char lastDigit) + { + // The latch decisions look up to ten characters ahead, so every character is classified once. The field is bounded by the text, so a short field + // works inline and a longer one rents once. + InlineArray128 classStorage = default; + using PooledBuffer rented = text.Length <= 128 ? default : new(text.Length); + Span classes = text.Length <= 128 ? ((Span)classStorage)[..text.Length] : rented.Span[..text.Length]; + for (int k = 0; k < text.Length; k++) + { + classes[k] = ClassOf(text[k]); + if (classes[k] == FieldMode.None) + { + throw new ArgumentException($"The 2D component of a GS1 Composite symbol cannot carry the character U+{(int)text[k]:X4}.", nameof(text)); + } + } + + lastDigit = '\0'; + int i = 0; + while (i < text.Length) + { + char c = text[i]; + FieldMode type = classes[i]; + switch (mode) + { + case FieldMode.Numeric: + if (i < text.Length - 1) + { + // Table 5: two characters take V = 11 x D1 + D2 + 8, the separator counting as 10. Anything else latches with 0000. + if (type != FieldMode.Numeric || classes[i + 1] != FieldMode.Numeric) + { + bits.Append(0b0000, 4); + mode = FieldMode.Alphanumeric; + } + else + { + bits.Append((11 * NumericValue(c)) + NumericValue(text[i + 1]) + 8, 7); + i += 2; + } + } + else if (!char.IsAsciiDigit(c)) + { + bits.Append(0b0000, 4); + mode = FieldMode.Alphanumeric; + } + else + { + lastDigit = c; + i++; + } + + break; + + case FieldMode.Alpha: + // Table 4: a capital letter is its code less 65 in five bits, a digit its code plus 4 in six bits, and the separator 31 in five bits, which + // latches to numeric mode. + if (c == Gs1Data.Separator) + { + bits.Append(0b11111, 5); + mode = FieldMode.Numeric; + } + else if (char.IsAsciiDigit(c)) + { + bits.Append(c + 4, 6); + } + else + { + bits.Append(c - 'A', 5); + } + + i++; + break; + + case FieldMode.Alphanumeric: + // Section 5.4.2: a separator encodes here, an ISO/IEC 646 character latches to that mode, and six numeric characters ahead, or four or more + // that end the field, latch to numeric mode. + if (c == Gs1Data.Separator) + { + bits.Append(0b01111, 5); + mode = FieldMode.Numeric; + i++; + } + else if (type == FieldMode.Iso646) + { + bits.Append(0b00100, 5); + mode = FieldMode.Iso646; + } + else if (Next(classes, i, 6, FieldMode.Numeric) || EndsNumeric(classes, i)) + { + bits.Append(0b000, 3); + mode = FieldMode.Numeric; + } + else if (char.IsAsciiDigit(c)) + { + bits.Append(c - 43, 5); + i++; + } + else if (char.IsAsciiLetterUpper(c)) + { + bits.Append(c - 33, 6); + i++; + } + else + { + bits.Append("*,-./".IndexOf(c) + 58, 6); + i++; + } + + break; + + default: + // Section 5.4.3: a separator encodes here. With no ISO/IEC 646 only character in the next ten, four numeric characters ahead latch to + // numeric mode and five alphanumeric ones to alphanumeric mode. + if (c == Gs1Data.Separator) + { + bits.Append(0b01111, 5); + mode = FieldMode.Numeric; + i++; + break; + } + + bool noIso646Ahead = NoneAhead(classes, i, 10, FieldMode.Iso646); + if (noIso646Ahead && Next(classes, i, 4, FieldMode.Numeric)) + { + bits.Append(0b000, 3); + mode = FieldMode.Numeric; + } + else if (noIso646Ahead && Next(classes, i, 5, FieldMode.Alphanumeric, FieldMode.Numeric)) + { + bits.Append(0b00100, 5); + mode = FieldMode.Alphanumeric; + } + else if (char.IsAsciiDigit(c)) + { + bits.Append(c - 43, 5); + i++; + } + else if (char.IsAsciiLetterUpper(c)) + { + bits.Append(c - 1, 7); + i++; + } + else if (char.IsAsciiLetterLower(c)) + { + bits.Append(c - 7, 7); + i++; + } + else + { + bits.Append(Iso646Punctuation.IndexOf(c) + 232, 8); + i++; + } + + break; + } + } + + return mode; + } + + /// + /// Returns the bit capacity of the smallest symbol that holds a number of bits, and takes the next larger 2D component when none of the current one does: + /// CC-B after CC-A, as section 4.4 requires, and CC-C after CC-B when the symbol can take it. + /// + /// The number of bits. + /// The 2D component, updated when it grows. + /// The data columns, updated when the symbol takes CC-C. + /// The data columns of CC-C, or 0 when the symbol cannot take CC-C. + /// The rows of the symbol. + /// The error correction level of CC-C. + /// The bit capacity, or -1 when no symbol holds the bits. + private static int Capacity(int used, ref Gs1CompositeComponent component, ref int columns, int ccCColumns, out int rows, out int level) + { + level = 0; + while (true) + { + int capacity = component switch + { + Gs1CompositeComponent.CcA => CcACapacity(used, columns, out rows), + Gs1CompositeComponent.CcB => CcBCapacity(used, columns, out rows), + _ => CcCCapacity(used, ref columns, out rows, out level), + }; + + if (capacity >= 0) + { + return capacity; + } + + if (component == Gs1CompositeComponent.CcA) + { + component = Gs1CompositeComponent.CcB; + } + else if (component == Gs1CompositeComponent.CcB && ccCColumns > 0) + { + component = Gs1CompositeComponent.CcC; + columns = ccCColumns; + } + else + { + return -1; + } + } + } + + /// + /// Returns the bit capacity of the smallest CC-A of a column count that holds a number of bits. Table 12 converts the data codewords of a size into bits: + /// each full group of seven codewords carries 69 bits, and k more codewords carry 10k - 1. + /// + /// The number of bits. + /// The data columns. + /// The rows of the size. + /// The bit capacity, or -1 when no size holds the bits. + private static int CcACapacity(int used, int columns, out int rows) + { + ReadOnlySpan sizes = CcASizes; + for (int offset = 0; offset < sizes.Length; offset += MicroPdf417Encoder.SymbolSize.Length) + { + MicroPdf417Encoder.SymbolSize size = new(sizes.Slice(offset, MicroPdf417Encoder.SymbolSize.Length)); + if (size.Columns != columns) + { + continue; + } + + int data = (size.Columns * size.Rows) - size.ErrorCorrection; + int remainder = data % Base928GroupCodewords; + int capacity = (Base928GroupBits * (data / Base928GroupCodewords)) + (remainder == 0 ? 0 : (10 * remainder) - 1); + if (capacity >= used) + { + rows = size.Rows; + return capacity; + } + } + + rows = 0; + return -1; + } + + /// + /// Returns the bit capacity of the smallest CC-B of a column count that holds a number of bits. The data codewords of a size less the codeword 920 and the + /// byte latch carry six bytes per five codewords and one byte per codeword after the last group. + /// + /// The number of bits. + /// The data columns. + /// The rows of the size. + /// The bit capacity, or -1 when no size holds the bits. + private static int CcBCapacity(int used, int columns, out int rows) + { + ReadOnlySpan sizes = CcBSizes; + for (int offset = 0; offset < sizes.Length; offset += 2) + { + if (sizes[offset] != columns) + { + continue; + } + + int capacity = 8 * Pdf417Codewords.ByteCapacity(MicroPdf417Encoder.DataCodewords(columns, sizes[offset + 1]) - 2); + if (capacity >= used) + { + rows = sizes[offset + 1]; + return capacity; + } + } + + rows = 0; + return -1; + } + + /// + /// Returns the bit capacity of the CC-C that holds a number of bits. The error correction level is the recommended minimum of Table E.1 of ISO/IEC 15438 + /// for the data codewords, clause 10 g), lowered where the largest symbol leaves too little room. The symbol takes the fewest rows of its columns. It takes + /// more columns when it needs more than 30 rows, and fewer columns when all its columns reach four times its rows. + /// + /// The number of bits. + /// The data columns, updated when more columns are needed. + /// The rows of the symbol. + /// The error correction level. + /// The bit capacity, or -1 when no symbol holds the bits. + private static int CcCCapacity(int used, ref int columns, out int rows, out int level) + { + // The data codewords are the codeword 920, the byte latch and the codewords of the bytes. With the Symbol Length Descriptor and the error correction + // codewords they fill the matrix. + int bytes = (used + 7) / 8; + int data = 2 + Pdf417Codewords.ByteCodewords(bytes); + int largest = MaximumCcCColumns * MaximumCcCRows; + level = Pdf417Encoder.RecommendedErrorCorrectionLevel(data); + while (level > 0 && data + 1 + (2 << level) > largest) + { + level--; + } + + int total = data + 1 + (2 << level); + while (columns < MaximumCcCColumns && (total + columns - 1) / columns > MaximumCcCRows) + { + columns++; + } + + rows = Math.Max(MinimumCcCRows, (total + columns - 1) / columns); + if (rows > MaximumCcCRows) + { + return -1; + } + + // Clause 10: "the total number of CC-C columns shall be less than four times the number of rows in the component". The total counts the start and + // stop patterns and the two row indicators. Fewer columns give more rows, so the symbol drops columns until the rule holds. + while (columns > MinimumCcCColumns && columns + CcCOverheadColumns >= 4 * rows) + { + columns--; + rows = Math.Max(MinimumCcCRows, (total + columns - 1) / columns); + } + + return 8 * Pdf417Codewords.ByteCapacity((columns * rows) - (2 << level) - 3); + } + + /// + /// Converts the padded bit stream into codewords and builds the symbol of the chosen size. + /// + /// The padded bit stream. + /// The 2D component. + /// The data columns. + /// The rows. + /// The error correction level of CC-C. + /// The X dimension in millimetres. + /// The symbol to write. + private static void BuildSymbol( + ref BitStream bits, + Gs1CompositeComponent component, + int columns, + int rows, + int level, + double xDimension, + ref BarcodeSymbol symbol) + { + if (component == Gs1CompositeComponent.CcA) + { + MicroPdf417Encoder.SymbolSize size = MicroPdf417Encoder.FindSize(CcASizes, columns, rows); + + // Section 8.5: each group of up to 69 bits, read as one number with its first bit the most significant, converts to base 928, the most significant + // codeword first. A group of b bits takes b / 10 + 1 codewords. A CC-A holds at most 28 codewords, so they fit inline. + int k = size.ErrorCorrection; + InlineArray32 codewordStorage = default; + Span codewords = ((Span)codewordStorage)[..(columns * rows)]; + int written = 0; + for (int start = 0; start < bits.Position; start += Base928GroupBits) + { + int count = Math.Min(Base928GroupBits, bits.Position - start); + UInt128 value = 0; + for (int b = start; b < start + count; b++) + { + value = (value << 1) | (uint)bits.Bit(b); + } + + int groupCodewords = (count / 10) + 1; + for (int i = groupCodewords - 1; i >= 0; i--) + { + codewords[written + i] = (int)(value % Base928); + value /= Base928; + } + + written += groupCodewords; + } + + InlineArray16 alphaStorage = default; + Pdf417Codewords.AppendErrorCorrection(codewords, k, alphaStorage); + + MicroPdf417Encoder.AddressPatterns patterns = new(size.LeftPattern, size.CentrePattern, size.RightPattern, columns != 3); + MicroPdf417Encoder.BuildRows(codewords, columns, rows, patterns, xDimension, RowHeight, 0F, ref symbol); + return; + } + + // Clauses 9 and 10: the codeword 920, the latch to Byte Compaction mode, and the bytes of the bit stream. Byte Compaction mode takes the latch 924 for + // a byte count that is a multiple of six and 901 otherwise. + int byteCount = bits.Position / 8; + ValueListBuilder data = new(byteCount + 2); + try + { + data.Append(CompositeCodeword); + data.Append(Pdf417Codewords.ByteLatchFor(byteCount)); + Pdf417Codewords.EncodeBytes(bits.Bytes[..byteCount], ref data); + + if (component == Gs1CompositeComponent.CcB) + { + MicroPdf417Encoder.BuildSymbol(data.AsSpan(), columns, rows, xDimension, RowHeight, 0F, ref symbol); + } + else + { + Pdf417Encoder.BuildSymbol(data.AsSpan(), columns, level, xDimension, 0F, ref symbol); + } + } + finally + { + data.Dispose(); + } + } + + /// + /// Returns the numeric mode value of a digit, or 10 for the separator. + /// + /// The digit or the separator. + /// The value. + private static int NumericValue(char c) + => c == Gs1Data.Separator ? 10 : c - '0'; + + /// + /// Returns the character class of a general purpose field character: numeric for a digit or the separator, alphanumeric for a capital letter or one of * + /// , - . /, ISO/IEC 646 for a small letter, a space or the other punctuation of Table 7, and none for a character the field cannot carry. + /// + /// The character. + /// The class. + private static FieldMode ClassOf(char c) + { + if (c == Gs1Data.Separator || char.IsAsciiDigit(c)) + { + return FieldMode.Numeric; + } + + if (char.IsAsciiLetterUpper(c) || c is '*' or ',' or '-' or '.' or '/') + { + return FieldMode.Alphanumeric; + } + + return char.IsAsciiLetterLower(c) || Iso646Punctuation.Contains(c) ? FieldMode.Iso646 : FieldMode.None; + } + + /// + /// Returns whether the next characters, from the given one on, are all of one class. + /// + /// The class of every character of the field. + /// The first character. + /// The number of characters. + /// The class. + /// Whether that many characters remain and all are of the class. + private static bool Next(ReadOnlySpan classes, int start, int count, FieldMode type) + => Next(classes, start, count, type, type); + + /// + /// Returns whether the next characters, from the given one on, are all of one of two classes. + /// + /// The class of every character of the field. + /// The first character. + /// The number of characters. + /// The class. + /// The second class. + /// Whether that many characters remain and each is of one of the classes. + private static bool Next(ReadOnlySpan classes, int start, int count, FieldMode type, FieldMode alternative) + { + if (start + count > classes.Length) + { + return false; + } + + foreach (FieldMode c in classes.Slice(start, count)) + { + if (c != type && c != alternative) + { + return false; + } + } + + return true; + } + + /// + /// Returns whether four or five numeric characters end the field from the given one on. + /// + /// The class of every character of the field. + /// The first character. + /// Whether the rest of the field is four or five numeric characters. + private static bool EndsNumeric(ReadOnlySpan classes, int start) + => classes.Length - start is 4 or 5 && Next(classes, start, classes.Length - start, FieldMode.Numeric); + + /// + /// Returns whether none of the next characters, or the characters to the end, are of a class. + /// + /// The class of every character of the field. + /// The first character. + /// The number of characters. + /// The class. + /// Whether none of them is of the class. + private static bool NoneAhead(ReadOnlySpan classes, int start, int count, FieldMode type) + => !classes[start..Math.Min(start + count, classes.Length)].Contains(type); + + /// + /// A bit stream packed eight bits to a byte, the first bit in the most significant bit of the first byte. + /// + private ref struct BitStream + { + /// + /// The bytes of the stream, cleared before the first bit. + /// + private readonly Span bytes; + + /// + /// Initializes a new instance of the struct. + /// + /// The bytes of the stream, cleared. + public BitStream(Span bytes) => this.bytes = bytes; + + /// + /// Gets the number of bits written. + /// + public int Position { get; private set; } + + /// + /// Gets the bytes of the stream, the first bit in the most significant bit of the first byte. + /// + public readonly ReadOnlySpan Bytes => this.bytes; + + /// + /// Appends a value as a number of bits, the most significant first. + /// + /// The value. + /// The number of bits. + /// The stream is longer than the largest symbol holds. + public void Append(int value, int count) + { + if (this.Position + count > this.bytes.Length * 8) + { + throw new ArgumentException($"The data takes more than {MaximumBits} bits, the most a GS1 Composite 2D component holds."); + } + + for (int i = count - 1; i >= 0; i--) + { + if (((value >> i) & 1) != 0) + { + this.bytes[this.Position >> 3] |= (byte)(0x80 >> (this.Position & 7)); + } + + this.Position++; + } + } + + /// + /// Returns one bit of the stream. + /// + /// The index of the bit. + /// The bit, 0 or 1. + public readonly int Bit(int position) => (this.bytes[position >> 3] >> (7 - (position & 7))) & 1; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Composite/Gs1CompositeComponentSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1CompositeComponentSymbology.cs new file mode 100644 index 00000000..2568b3b6 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1CompositeComponentSymbology.cs @@ -0,0 +1,89 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The 2D component of a GS1 Composite symbol, ISO/IEC 24723, drawn alone. A composite symbology such as draws it above +/// its linear component. +/// +/// The input is the element string syntax the standard prints, each Application Identifier in parentheses followed by its data. The symbol takes the +/// smallest size of its 2D component and columns that holds the data. A CC-A symbol whose data exceeds CC-A takes CC-B of the same columns. The rows are 2X +/// high in CC-A and CC-B and 3X high in CC-C, and the bar height of the options does not change them. The human readable interpretation prints each element +/// string on a line of its own below the symbol. +/// +/// +public sealed class Gs1CompositeComponentSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class: CC-A, or CC-B when the data exceeds CC-A, of two columns. + /// + public Gs1CompositeComponentSymbology() + : this(Gs1CompositeComponent.CcA, Gs1CompositeComponentEncoder.MinimumColumns) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The 2D component. CC-A takes CC-B of the same columns when the data exceeds CC-A. + /// The number of data columns: 2 to 4 for CC-A and CC-B, 1 to 30 for CC-C. A CC-C takes more columns when it needs more than + /// 30 rows. It takes fewer columns when its columns, with the start and stop patterns and the two row indicators, reach four times its rows. + public Gs1CompositeComponentSymbology(Gs1CompositeComponent component, int columns) + { + Guard.MustBeBetweenOrEqualTo((int)component, (int)Gs1CompositeComponent.CcA, (int)Gs1CompositeComponent.CcC, nameof(component)); + if (component == Gs1CompositeComponent.CcC) + { + Guard.MustBeBetweenOrEqualTo( + columns, + Gs1CompositeComponentEncoder.MinimumCcCColumns, + Gs1CompositeComponentEncoder.MaximumCcCColumns, + nameof(columns)); + } + else + { + Guard.MustBeBetweenOrEqualTo(columns, Gs1CompositeComponentEncoder.MinimumColumns, Gs1CompositeComponentEncoder.MaximumColumns, nameof(columns)); + } + + this.Component = component; + this.Columns = columns; + } + + /// + /// Gets the 2D component. + /// + public Gs1CompositeComponent Component { get; } + + /// + /// Gets the number of data columns. + /// + public int Columns { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + double xDimension = options.ResolveXDimension(0, DefaultXDimension, double.PositiveInfinity); + _ = Gs1CompositeComponentEncoder.Build(text, this.Component, this.Columns, 0, xDimension, ref symbol); + if (options.Font is null) + { + return; + } + + // The interpretation is never longer than ReadableCapacity, so short text works inline and longer text rents once. + InlineArray128 readableStorage = default; + PooledBuffer rented = default; + int capacity = Gs1Data.ReadableCapacity(text.Length); + Span readable = capacity <= 128 ? ((Span)readableStorage)[..capacity] : (rented = new PooledBuffer(capacity)).Span; + try + { + int written = Gs1Data.WriteReadable(text, true, readable); + symbol.AddStackedText(readable[..written], BarcodeTextAlignment.Left, Gs1Data.TextScale(options.Font)); + } + finally + { + rented.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Composite/Gs1CompositeLayout.cs b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1CompositeLayout.cs new file mode 100644 index 00000000..9b80c807 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1CompositeLayout.cs @@ -0,0 +1,414 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Joins the linear component and the 2D component of a GS1 Composite symbol, ISO/IEC 24723: the separator pattern of section 11.4 between them, the 2D +/// component above the linear component at the horizontal position of clause 12, and the human readable interpretation of the 2D component. +/// +internal static class Gs1CompositeLayout +{ + /// + /// The number of modules over the finder pattern of a GS1 DataBar symbol whose separator section 11.4 treats apart: elements 1 to 3 of the finder. + /// + private const int FinderSeparatorModules = 13; + + /// + /// The height of each of the three segments of the separator pattern of an EAN/UPC Composite symbol in modules. Section 11.4: the 6X separator steps out + /// after 2X and back after 4X. + /// + private const float EanUpcSeparatorSegment = 2F; + + /// + /// The number of Code 128 symbol characters, from the start character to the stop character, that clause 12 g) needs to place a CC-A or CC-B over a + /// GS1-128 symbol. + /// + private const int MinimumGs1128Characters = 9; + + /// + /// Gets the 13 modules of the right finder pattern of value 3 of GS1 DataBar Omnidirectional, Truncated, Stacked and Stacked Omnidirectional, elements 3 + /// to 5 as they stand in the symbol: a bar of 9 modules, a space of 1 and a bar of 3. + /// + private static ReadOnlySpan FinderValueThree => [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1]; + + /// + /// Validates the element strings of the two components together, as the element strings of one whole symbol. The pairs of section 4.13.1 of the GS1 + /// General Specifications apply to the whole symbol, so an element string of one component can exclude one of the other. + /// + /// The element strings of the linear component. + /// The element strings of the 2D component. + /// The text is not valid element string syntax, or an element string or a pair of them breaks a rule. + public static void Validate(ReadOnlySpan linear, ReadOnlySpan component) + { + // The length checks run before the buffers take their size from the texts. GS1 DataBar Expanded carries the most data of the linear components. + _ = Gs1Data.PreparedCapacity(linear, DataBarExpandedEncoder.MaximumDataCharacters); + _ = Gs1Data.PreparedCapacity(component, Gs1CompositeComponentEncoder.MaximumPrepared); + + int length = linear.Length + component.Length; + PooledBuffer joined = new(length); + InlineArray16 elementStorage = default; + int elementCapacity = (length / 5) + 1; + ValueListBuilder elements = elementCapacity <= Gs1Data.InlineElementCount ? new(elementStorage) : new(elementCapacity); + try + { + Span text = joined.Span; + linear.CopyTo(text); + component.CopyTo(text[linear.Length..]); + Gs1Data.Parse(text, ref elements); + Gs1Data.ValidateSymbol(text, elements.AsSpan()); + } + finally + { + joined.Dispose(); + elements.Dispose(); + } + } + + /// + /// Builds the 2D component at the X dimension of the linear component, section 11.1, and adds its human readable interpretation above it. Section 4.14 rule + /// 1d of the GS1 General Specifications lets the interpretation split, the key attributes above the symbol and the identification key below it. + /// + /// The element strings of the 2D component. + /// The smallest 2D component the symbol takes. + /// The data columns. + /// The data columns of CC-C when a CC-B cannot hold the data, or 0 when the symbol cannot take CC-C. + /// The X dimension of the linear component in millimetres. + /// The options that carry the font. + /// The part to write. + /// The 2D component the symbol took. + public static Gs1CompositeComponent BuildComponent( + ReadOnlySpan text, + Gs1CompositeComponent component, + int columns, + int ccCColumns, + double xDimension, + BarcodeOptions options, + ref BarcodeSymbol part) + { + Gs1CompositeComponent used = Gs1CompositeComponentEncoder.Build(text, component, columns, ccCColumns, xDimension, ref part); + if (options.Font is null) + { + return used; + } + + // The interpretation on one line is never longer than the text. + InlineArray128 readableStorage = default; + PooledBuffer rented = default; + Span readable = text.Length <= 128 ? ((Span)readableStorage)[..text.Length] : (rented = new PooledBuffer(text.Length)).Span; + try + { + int written = Gs1Data.WriteReadable(text, false, readable); + part.ReserveText(1, written); + part.AddText(readable[..written], 0F, part.WidthInModules, BarcodeTextSide.AboveBars, -BarcodeTextPlacement.Clearance); + + // Section 4.14 rule 2.a.iii of the GS1 General Specifications: "The font size SHOULD be at least 2 mm (0.08 in) in height". + part.SetTextSize(Gs1Data.MinimumTextSize, 0F); + } + finally + { + rented.Dispose(); + } + + return used; + } + + /// + /// Adds the 1X separator pattern of section 11.4 above the top row of a GS1 DataBar or GS1-128 linear component. The separator is the complement of the + /// row below it, light over the given modules at each end. Over elements 1 to 3 of a GS1 DataBar finder pattern it is light over the bars and alternates + /// dark and light over the spaces, starting dark after a bar. + /// + /// The linear component. + /// The number of light modules at the start of the separator. + /// The number of light modules at the end of the separator. + /// The first module of the 13 the separator treats apart over each finder pattern of the row. + /// Whether the single dark module over a right finder pattern of value 3 moves one module right, over the start of its + /// three module bar, as section 11.4 requires of GS1 DataBar Omnidirectional, Truncated, Stacked and Stacked Omnidirectional. + public static void AddSeparator(ref BarcodeSymbol linear, int lightStart, int lightEnd, ReadOnlySpan finderStarts, bool shiftOverValueThree) + { + BarcodeRow row = linear.Rows[0]; + ReadOnlySpan runs = linear.Runs.Slice(row.RunStart, row.RunCount); + int width = BarcodeSymbol.RunWidth(runs); + + // One rental holds the modules of the row, the modules of the separator and its runs, at most two more than its modules. + PooledBuffer buffer = new((width * 3) + 2); + try + { + Span below = buffer.Span[..width]; + Span separator = buffer.Span.Slice(width, width); + int position = 0; + for (int i = 0; i < runs.Length; i++) + { + below.Slice(position, runs[i]).Fill((i & 1) == 0 ? 1 : 0); + position += runs[i]; + } + + for (int i = 0; i < width; i++) + { + separator[i] = 1 - below[i]; + } + + separator[..lightStart].Clear(); + separator[(width - lightEnd)..].Clear(); + foreach (int start in finderStarts) + { + for (int i = start; i < start + FinderSeparatorModules; i++) + { + separator[i] = below[i] == 1 ? 0 : below[i - 1] == 1 ? 1 : 1 - separator[i - 1]; + } + + if (shiftOverValueThree && Matches(below.Slice(start, FinderSeparatorModules), FinderValueThree)) + { + separator.Slice(start, FinderSeparatorModules).Clear(); + separator[start + 10] = 1; + } + } + + Span separatorRuns = buffer.Span[(width * 2)..]; + int separatorRunCount = ToRuns(separator, separatorRuns); + linear.InsertRows(0, separatorRuns[..separatorRunCount], [separatorRunCount], 1F); + } + finally + { + buffer.Dispose(); + } + } + + /// + /// Adds the 1X separator pattern of section 11.4 above the top row of a GS1 DataBar Expanded or GS1 DataBar Expanded Stacked linear component. The + /// separator is light over the four modules at each end and treats the finder patterns of the top row apart. + /// + /// The linear component. + public static void AddExpandedSeparator(ref BarcodeSymbol linear) + { + BarcodeRow row = linear.Rows[0]; + InlineArray16 finderStorage = default; + Span finders = finderStorage; + int finderCount = ExpandedFinderStarts(BarcodeSymbol.RunWidth(linear.Runs.Slice(row.RunStart, row.RunCount)), finders); + AddSeparator(ref linear, 4, 4, finders[..finderCount], false); + } + + /// + /// Encodes the parts of an EAN/UPC Composite symbol. Section 7.2: the linear component carries no linkage flag, and the 2D component stands above the 6X + /// separator pattern. + /// + /// The symbology of the linear component. + /// The data columns of the 2D component, Table 1. + /// The parts to write. + /// The options that control layout choices. + /// The digits of the linear component. + /// The element strings of the 2D component. + /// The GTIN that the linear component carries, with its check digit: 8, 12 or 13 digits. + public static void EncodeEanUpc( + BarcodeSymbology linear, + int columns, + Span parts, + BarcodeOptions options, + ReadOnlySpan first, + ReadOnlySpan second, + ReadOnlySpan gtin) + { + // The note of section 4.13 of the GS1 General Specifications: "GTINs encoded in an EAN/UPC and ITF-14 symbols are to be regarded as element strings + // prefixed by an inferred AI (01)". So the symbol validates as the inferred (01) element string, the GTIN right-justified in 14 digits, with the + // element strings of the 2D component. + InlineArray32 inferredStorage = default; + Span inferred = ((Span)inferredStorage)[..18]; + "(01)".CopyTo(inferred); + inferred[4..(18 - gtin.Length)].Fill('0'); + gtin.CopyTo(inferred[(18 - gtin.Length)..]); + Validate(inferred, second); + + linear.Encode(first, options, ref parts[0]); + BuildComponent(second, Gs1CompositeComponent.CcA, columns, 0, parts[0].XDimension, options, ref parts[1]); + + // The outermost guard bars of the linear component are its first and its last module, and EanUpcOffset puts its first module under the module + // linearStart of the 2D component. + int linearWidth = (int)parts[0].WidthInModules; + int linearStart = -EanUpcOffset(linearWidth, (int)parts[1].WidthInModules); + AddEanUpcSeparator(ref parts[1], linearStart, linearStart + linearWidth - 1); + } + + /// + /// Returns the offset of the 2D component of an EAN/UPC Composite symbol from its linear component. Clause 12 a): the last space module before the stop + /// bar of the 2D component stands over the last bar of the linear component. + /// + /// The width of the linear component in modules, from its first guard bar to its last. + /// The width of the 2D component in modules, which ends with its one module stop bar. + /// The offset of the 2D component from the first module of the linear component, in modules. + public static int EanUpcOffset(int linearWidth, int componentWidth) + => (linearWidth - 1) - (componentWidth - 2); + + /// + /// Returns the module of a GS1-128 symbol above which clause 12 g) puts the left edge of a CC-A or CC-B: its right quiet zone stands over the last space + /// module of the symbol character at position (n - 9) div 2, the positions numbered from the right with the stop character at 0, where n is the number of + /// symbol characters. + /// + /// The width of the GS1-128 symbol in modules, validated by . + /// The width of the 2D component in modules. + /// The offset of the 2D component from the first module of the linear component, in modules. + public static int Gs1128Offset(int linearWidth, int componentWidth) + { + // The last space module of a symbol character or the stop character is the last module of its first eleven. + int characters = Gs1128Characters(linearWidth); + int position = (characters - MinimumGs1128Characters) / 2; + return ((characters - 1 - position) * Code128Encoder.ModulesPerCharacter) + (Code128Encoder.ModulesPerCharacter - 1) - componentWidth; + } + + /// + /// Validates that a GS1-128 symbol has the symbol characters that clause 12 g) needs to place a CC-A or CC-B over it. + /// + /// The width of the GS1-128 symbol in modules. + /// The GS1-128 symbol has fewer than nine symbol characters. + public static void ValidateGs1128Length(int linearWidth) + { + int characters = Gs1128Characters(linearWidth); + if (characters < MinimumGs1128Characters) + { + throw new ArgumentException( + $"A GS1-128 Composite symbol with a CC-A or CC-B has at least {MinimumGs1128Characters} symbol characters; the linear component has {characters}."); + } + } + + /// + /// Returns where the 2D component stands: directly above the linear component, its first module over the given module of the linear component. + /// + /// The linear component and the 2D component. + /// The top left corner of the first module of the linear component, in pixels. + /// The module of the linear component under the first module of the 2D component. + /// The sizing options. + /// The top left corner of the first module of the 2D component, in pixels. + public static PointF PlaceAbove(ReadOnlySpan parts, PointF linearOrigin, int offset, BarcodeOptions options) + { + int pixels = options.ModulePixels(parts[0].XDimension); + return new PointF(linearOrigin.X + (offset * pixels), linearOrigin.Y - (parts[1].HeightInModules * pixels)); + } + + /// + /// Returns the number of symbol characters of a GS1-128 symbol, from the start character to the stop character. The stop character is two modules wider + /// than a symbol character. + /// + /// The width of the GS1-128 symbol in modules. + /// The number of symbol characters. + private static int Gs1128Characters(int linearWidth) + => (linearWidth - (Code128Encoder.StopModules - Code128Encoder.ModulesPerCharacter)) / Code128Encoder.ModulesPerCharacter; + + /// + /// Adds the 6X separator pattern of an EAN/UPC Composite symbol, section 11.4, below the rows of the 2D component: a light space with two 1X bars that + /// extend the outermost guard bars of the linear component. The bars step outward by 1X after 2X and back after 4X, three segments of 2X. + /// + /// The 2D component. + /// The module of the 2D component above the left outermost guard bar. + /// The module of the 2D component above the right outermost guard bar. + private static void AddEanUpcSeparator(ref BarcodeSymbol component, int left, int right) + { + int width = (int)component.WidthInModules; + PooledBuffer buffer = new((width * 2) + 2); + try + { + Span modules = buffer.Span[..width]; + Span runs = buffer.Span[width..]; + + // Each segment row has two bars, so it has at most six runs, and the three rows fit the run buffer together. One insert takes all three rows. + InlineArray4 countStorage = default; + Span counts = ((Span)countStorage)[..3]; + int written = 0; + for (int segment = 0; segment < counts.Length; segment++) + { + // The middle segment stands one module further out on each side. + int step = segment == 1 ? 1 : 0; + modules.Clear(); + modules[left - step] = 1; + modules[right + step] = 1; + counts[segment] = ToRuns(modules, runs[written..]); + written += counts[segment]; + } + + component.InsertRows(component.Rows.Length, runs[..written], counts, EanUpcSeparatorSegment); + } + finally + { + buffer.Dispose(); + } + } + + /// + /// Writes where the separator of a GS1 DataBar Expanded row treats its finder patterns apart. A row starts with a guard space and bar, and every pair of + /// symbol characters holds a finder of 15 modules after its first character of 17, so the finders start at module 19 and every 49 modules after it. Section + /// 11.4 treats elements 1 to 3 of the first finder of each two and elements 3 to 5 of the second, which start two modules into it. + /// + /// The width of the row in modules. + /// The buffer the first modules are written to. + /// The number of finder patterns. + private static int ExpandedFinderStarts(int width, Span starts) + { + int count = 0; + for (int start = 19, k = 0; start + FinderSeparatorModules <= width; start += 49, k++) + { + int first = (k & 1) == 0 ? start : start + 2; + if (first + FinderSeparatorModules <= width) + { + starts[count++] = first; + } + } + + return count; + } + + /// + /// Returns whether two module sequences are equal. + /// + /// The modules. + /// The pattern. + /// when they are equal. + private static bool Matches(ReadOnlySpan modules, ReadOnlySpan pattern) + { + for (int i = 0; i < pattern.Length; i++) + { + if (modules[i] != pattern[i]) + { + return false; + } + } + + return true; + } + + /// + /// Converts a row of modules into runs that alternate bar and space from a bar. A row that starts or ends light takes a zero width bar at that end. + /// + /// The modules, 1 for dark. + /// The buffer the runs are written to, at least two longer than the modules. + /// The number of runs. + private static int ToRuns(ReadOnlySpan modules, Span runs) + { + int count = 0; + if (modules[0] == 0) + { + runs[count++] = 0; + } + + int width = 1; + for (int i = 1; i < modules.Length; i++) + { + if (modules[i] != modules[i - 1]) + { + runs[count++] = width; + width = 1; + } + else + { + width++; + } + } + + runs[count++] = width; + if (modules[^1] == 0) + { + runs[count++] = 0; + } + + return count; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Composite/Gs1DataBarExpandedCompositeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1DataBarExpandedCompositeSymbology.cs new file mode 100644 index 00000000..621cc220 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1DataBarExpandedCompositeSymbology.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The GS1 DataBar Expanded Composite symbology of ISO/IEC 24723: a GS1 DataBar Expanded linear component with its linkage bit set, a 1X separator pattern, +/// and a 4-column CC-A or CC-B 2D component above it. The first text is the element strings the linear component carries, and the second those of the 2D +/// component. +/// +public sealed class Gs1DataBarExpandedCompositeSymbology : CompositeBarcodeSymbology +{ + /// + /// The data columns of the 2D component, Table 1 of ISO/IEC 24723. + /// + private const int Columns = 4; + + /// + /// Initializes a new instance of the class. + /// + public Gs1DataBarExpandedCompositeSymbology() + : base(new Gs1DataBarExpandedSymbology(), new Gs1CompositeComponentSymbology(Gs1CompositeComponent.CcA, Columns)) + { + } + + /// + internal override void EncodeParts( + Span parts, + BarcodeOptions options, + ReadOnlySpan first, + ReadOnlySpan second, + ReadOnlySpan third) + { + Gs1CompositeLayout.Validate(first, second); + InlineArray64 bufferStorage = default; + int capacity = Gs1Data.PreparedCapacity(first, DataBarExpandedEncoder.MaximumDataCharacters); + ValueStringBuilder encoded = capacity <= Gs1Data.InlineBufferLength ? new(bufferStorage) : new(capacity); + try + { + Gs1Data.Encode(first, ref encoded); + DataBarExpandedEncoder.ValidateGtinFirst(first); + + // The interpretation is never longer than the text, which PreparedCapacity bounds to 122 characters, so the inline buffer always holds it. + InlineArray128 readableStorage = default; + Span readable = readableStorage; + int readableLength = options.Font is null ? 0 : Gs1Data.WriteReadable(first, false, readable); + DataBarExpandedEncoder.BuildLinear(encoded.AsSpan(), readable[..readableLength], true, options, ref parts[0]); + } + finally + { + encoded.Dispose(); + } + + Gs1CompositeLayout.AddExpandedSeparator(ref parts[0]); + Gs1CompositeLayout.BuildComponent(second, Gs1CompositeComponent.CcA, Columns, 0, parts[0].XDimension, options, ref parts[1]); + } + + /// + internal override PointF Place(int index, ReadOnlySpan parts, ReadOnlySpan symbolOrigins, BarcodeOptions options) + { + // Clause 12 e): the first bar module of the 2D component stands over the first space of the first symbol character of the linear component, after + // its guard space and bar. + return Gs1CompositeLayout.PlaceAbove(parts, symbolOrigins[0], 2, options); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Composite/Gs1DataBarExpandedStackedCompositeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1DataBarExpandedStackedCompositeSymbology.cs new file mode 100644 index 00000000..c8318d45 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1DataBarExpandedStackedCompositeSymbology.cs @@ -0,0 +1,102 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The GS1 DataBar Expanded Stacked Composite symbology of ISO/IEC 24723: a GS1 DataBar Expanded Stacked linear component with its linkage bit set, a 1X +/// separator pattern, and a 4-column CC-A or CC-B 2D component above it. The first text is the element strings the linear component carries, and the second +/// those of the 2D component. Section 7.3 requires at least four symbol characters in the top row of the linear component. +/// +public sealed class Gs1DataBarExpandedStackedCompositeSymbology : CompositeBarcodeSymbology +{ + /// + /// The data columns of the 2D component, Table 1 of ISO/IEC 24723. + /// + private const int Columns = 4; + + /// + /// The fewest segments in a row of the linear component. Section 7.3: "at least four symbol characters in its top row". + /// + private const int MinimumSegments = 4; + + /// + /// Initializes a new instance of the class with four segments in a row. + /// + public Gs1DataBarExpandedStackedCompositeSymbology() + : this(MinimumSegments) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The number of segments in a full row of the linear component, an even number from 4 to 20. A segment is one symbol + /// character. + public Gs1DataBarExpandedStackedCompositeSymbology(int segments) + : base(new Gs1DataBarExpandedStackedSymbology(segments), new Gs1CompositeComponentSymbology(Gs1CompositeComponent.CcA, Columns)) + { + Guard.MustBeBetweenOrEqualTo(segments, MinimumSegments, DataBarExpandedEncoder.MaximumStackedSegments, nameof(segments)); + this.Segments = segments; + } + + /// + /// Gets the number of segments in a full row of the linear component. The last row can hold fewer. + /// + public int Segments { get; } + + /// + internal override void EncodeParts( + Span parts, + BarcodeOptions options, + ReadOnlySpan first, + ReadOnlySpan second, + ReadOnlySpan third) + { + Gs1CompositeLayout.Validate(first, second); + InlineArray64 bufferStorage = default; + int capacity = Gs1Data.PreparedCapacity(first, DataBarExpandedEncoder.MaximumDataCharacters); + ValueStringBuilder encoded = capacity <= Gs1Data.InlineBufferLength ? new(bufferStorage) : new(capacity); + try + { + Gs1Data.Encode(first, ref encoded); + DataBarExpandedEncoder.ValidateGtinFirst(first); + + // The interpretation is never longer than the text, which PreparedCapacity bounds to 122 characters, so the inline buffer always holds it. + InlineArray128 readableStorage = default; + Span readable = readableStorage; + int readableLength = options.Font is null ? 0 : Gs1Data.WriteReadable(first, false, readable); + DataBarExpandedEncoder.BuildStacked( + encoded.AsSpan(), + this.Segments, + true, + [], + readable[..readableLength], + [], + BarcodeTextSide.BelowBars, + DataBarEncoder.MinimumXDimension, + DataBarEncoder.MaximumXDimension, + float.PositiveInfinity, + options, + ref parts[0]); + } + finally + { + encoded.Dispose(); + } + + // The separator stands above the top row, which reads left to right like a GS1 DataBar Expanded symbol. + Gs1CompositeLayout.AddExpandedSeparator(ref parts[0]); + Gs1CompositeLayout.BuildComponent(second, Gs1CompositeComponent.CcA, Columns, 0, parts[0].XDimension, options, ref parts[1]); + } + + /// + internal override PointF Place(int index, ReadOnlySpan parts, ReadOnlySpan symbolOrigins, BarcodeOptions options) + { + // Clause 12 e): the first bar module of the 2D component stands over the first space of the first symbol character of the top row, after its guard + // space and bar. + return Gs1CompositeLayout.PlaceAbove(parts, symbolOrigins[0], 2, options); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Composite/Gs1DataBarLimitedCompositeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1DataBarLimitedCompositeSymbology.cs new file mode 100644 index 00000000..5e03542e --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1DataBarLimitedCompositeSymbology.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The GS1 DataBar Limited Composite symbology of ISO/IEC 24723: a GS1 DataBar Limited linear component with its linkage flag set, a 1X separator pattern, +/// and a 3-column CC-A or CC-B 2D component above it. The first text is the element string of Application Identifier (01) that the linear component carries, +/// and the second the element strings of the 2D component. +/// +public sealed class Gs1DataBarLimitedCompositeSymbology : CompositeBarcodeSymbology +{ + /// + /// The data columns of the 2D component, Table 1 of ISO/IEC 24723. + /// + private const int Columns = 3; + + /// + /// The light modules at the end of the separator: the four at the right end of the bars and the five module space after them. + /// + private const int LightEnd = 9; + + /// + /// The last interior space of the linear component: the guard space before its last bar, 72 modules from its start. + /// + private const int LastInteriorSpace = 72; + + /// + /// Initializes a new instance of the class. + /// + public Gs1DataBarLimitedCompositeSymbology() + : base(new Gs1DataBarLimitedSymbology(), new Gs1CompositeComponentSymbology(Gs1CompositeComponent.CcA, Columns)) + { + } + + /// + internal override void EncodeParts( + Span parts, + BarcodeOptions options, + ReadOnlySpan first, + ReadOnlySpan second, + ReadOnlySpan third) + { + Gs1CompositeLayout.Validate(first, second); + InlineArray14 gtinStorage = default; + Span gtin = gtinStorage; + Gs1Data.ReadGtin(first, gtin); + if (gtin[0] is not ('0' or '1')) + { + throw new ArgumentException($"GS1 DataBar Limited carries a GTIN whose first digit is 0 or 1; got {gtin[0]}.", nameof(first)); + } + + DataBarLimitedEncoder.Build(gtin, true, options, ref parts[0]); + + // Section 11.4: the separator is the complement of the linear component, light over the four modules at each end of the symbol. + Gs1CompositeLayout.AddSeparator(ref parts[0], 4, LightEnd, [], false); + Gs1CompositeLayout.BuildComponent(second, Gs1CompositeComponent.CcA, Columns, 0, parts[0].XDimension, options, ref parts[1]); + } + + /// + internal override PointF Place(int index, ReadOnlySpan parts, ReadOnlySpan symbolOrigins, BarcodeOptions options) + { + // Clause 12 c): the stop bar of the 2D component stands over the last interior space of the linear component. + int offset = LastInteriorSpace - ((int)parts[1].WidthInModules - 1); + return Gs1CompositeLayout.PlaceAbove(parts, symbolOrigins[0], offset, options); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Composite/Gs1DataBarOmnidirectionalCompositeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1DataBarOmnidirectionalCompositeSymbology.cs new file mode 100644 index 00000000..b614ea87 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1DataBarOmnidirectionalCompositeSymbology.cs @@ -0,0 +1,55 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The GS1 DataBar Omnidirectional Composite symbology of ISO/IEC 24723: a GS1 DataBar Omnidirectional linear component with its linkage flag set, a 1X +/// separator pattern, and a 4-column CC-A or CC-B 2D component above it. The first text is the element string of Application Identifier (01) that the +/// linear component carries, and the second the element strings of the 2D component. +/// +public sealed class Gs1DataBarOmnidirectionalCompositeSymbology : CompositeBarcodeSymbology +{ + /// + /// The data columns of the 2D component, Table 1 of ISO/IEC 24723. + /// + private const int Columns = 4; + + /// + /// Initializes a new instance of the class. + /// + public Gs1DataBarOmnidirectionalCompositeSymbology() + : base(new Gs1DataBarOmnidirectionalSymbology(), new Gs1CompositeComponentSymbology(Gs1CompositeComponent.CcA, Columns)) + { + } + + /// + internal override void EncodeParts( + Span parts, + BarcodeOptions options, + ReadOnlySpan first, + ReadOnlySpan second, + ReadOnlySpan third) + { + Gs1CompositeLayout.Validate(first, second); + InlineArray14 gtinStorage = default; + Span gtin = gtinStorage; + Gs1Data.ReadGtin(first, gtin); + DataBarEncoder.BuildLinear(gtin, true, DataBarEncoder.OmnidirectionalHeight, DataBarEncoder.OmnidirectionalMinimumHeight, options, ref parts[0]); + + // Section 11.4: the separator is light over the four modules at each end and treats the two finder patterns apart. The left finder starts after the + // guard pair and the first character of 16 modules. The right finder stands reversed, so its elements 1 to 3 start two modules into it, at module 65. + Gs1CompositeLayout.AddSeparator(ref parts[0], 4, 4, [18, 65], true); + Gs1CompositeLayout.BuildComponent(second, Gs1CompositeComponent.CcA, Columns, 0, parts[0].XDimension, options, ref parts[1]); + } + + /// + internal override PointF Place(int index, ReadOnlySpan parts, ReadOnlySpan symbolOrigins, BarcodeOptions options) + { + // Clause 12 b): the stop bar of the 2D component stands over the last interior space of the linear component, the guard space before its last bar. + int offset = (int)parts[0].WidthInModules - 2 - ((int)parts[1].WidthInModules - 1); + return Gs1CompositeLayout.PlaceAbove(parts, symbolOrigins[0], offset, options); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Composite/Gs1DataBarStackedCompositeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1DataBarStackedCompositeSymbology.cs new file mode 100644 index 00000000..79dbe767 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1DataBarStackedCompositeSymbology.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The GS1 DataBar Stacked Composite symbology of ISO/IEC 24723: a GS1 DataBar Stacked linear component with its linkage flag set, a 1X separator pattern, +/// and a 2-column CC-A or CC-B 2D component above it. The first text is the element string of Application Identifier (01) that the linear component carries, +/// and the second the element strings of the 2D component. +/// +public sealed class Gs1DataBarStackedCompositeSymbology : CompositeBarcodeSymbology +{ + /// + /// The data columns of the 2D component, Table 1 of ISO/IEC 24723. + /// + private const int Columns = 2; + + /// + /// Initializes a new instance of the class. + /// + public Gs1DataBarStackedCompositeSymbology() + : base(new Gs1DataBarStackedSymbology(), new Gs1CompositeComponentSymbology(Gs1CompositeComponent.CcA, Columns)) + { + } + + /// + internal override void EncodeParts( + Span parts, + BarcodeOptions options, + ReadOnlySpan first, + ReadOnlySpan second, + ReadOnlySpan third) + { + Gs1CompositeLayout.Validate(first, second); + InlineArray14 gtinStorage = default; + Span gtin = gtinStorage; + Gs1Data.ReadGtin(first, gtin); + DataBarEncoder.BuildStacked(gtin, true, options, ref parts[0]); + + // Section 11.4: the separator stands above the upper row, light over the four modules at each end, and treats its finder pattern apart. The finder + // starts after the guard pair and the first character of 16 modules. + Gs1CompositeLayout.AddSeparator(ref parts[0], 4, 4, [18], true); + Gs1CompositeLayout.BuildComponent(second, Gs1CompositeComponent.CcA, Columns, 0, parts[0].XDimension, options, ref parts[1]); + } + + /// + internal override PointF Place(int index, ReadOnlySpan parts, ReadOnlySpan symbolOrigins, BarcodeOptions options) + { + // Clause 12 d): the first bar module of the 2D component stands over the first bar of the linear component, after its guard space. + return Gs1CompositeLayout.PlaceAbove(parts, symbolOrigins[0], 1, options); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Composite/Gs1DataBarStackedOmnidirectionalCompositeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1DataBarStackedOmnidirectionalCompositeSymbology.cs new file mode 100644 index 00000000..3bdf8239 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1DataBarStackedOmnidirectionalCompositeSymbology.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The GS1 DataBar Stacked Omnidirectional Composite symbology of ISO/IEC 24723: a GS1 DataBar Stacked Omnidirectional linear component with its linkage flag +/// set, a 1X separator pattern, and a 2-column CC-A or CC-B 2D component above it. The first text is the element string of Application Identifier (01) that +/// the linear component carries, and the second the element strings of the 2D component. +/// +public sealed class Gs1DataBarStackedOmnidirectionalCompositeSymbology : CompositeBarcodeSymbology +{ + /// + /// The data columns of the 2D component, Table 1 of ISO/IEC 24723. + /// + private const int Columns = 2; + + /// + /// Initializes a new instance of the class. + /// + public Gs1DataBarStackedOmnidirectionalCompositeSymbology() + : base(new Gs1DataBarStackedOmnidirectionalSymbology(), new Gs1CompositeComponentSymbology(Gs1CompositeComponent.CcA, Columns)) + { + } + + /// + internal override void EncodeParts( + Span parts, + BarcodeOptions options, + ReadOnlySpan first, + ReadOnlySpan second, + ReadOnlySpan third) + { + Gs1CompositeLayout.Validate(first, second); + InlineArray14 gtinStorage = default; + Span gtin = gtinStorage; + Gs1Data.ReadGtin(first, gtin); + DataBarEncoder.BuildStackedOmnidirectional(gtin, true, options, ref parts[0]); + + // Section 11.4: the separator stands above the upper row, light over the four modules at each end, and treats its finder pattern apart. The finder + // starts after the guard pair and the first character of 16 modules. + Gs1CompositeLayout.AddSeparator(ref parts[0], 4, 4, [18], true); + Gs1CompositeLayout.BuildComponent(second, Gs1CompositeComponent.CcA, Columns, 0, parts[0].XDimension, options, ref parts[1]); + } + + /// + internal override PointF Place(int index, ReadOnlySpan parts, ReadOnlySpan symbolOrigins, BarcodeOptions options) + { + // Clause 12 d): the first bar module of the 2D component stands over the first bar of the linear component, after its guard space. + return Gs1CompositeLayout.PlaceAbove(parts, symbolOrigins[0], 1, options); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Composite/Gs1DataBarTruncatedCompositeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1DataBarTruncatedCompositeSymbology.cs new file mode 100644 index 00000000..e8b989dc --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Composite/Gs1DataBarTruncatedCompositeSymbology.cs @@ -0,0 +1,55 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The GS1 DataBar Truncated Composite symbology of ISO/IEC 24723: a GS1 DataBar Truncated linear component with its linkage flag set, a 1X +/// separator pattern, and a 4-column CC-A or CC-B 2D component above it. The first text is the element string of Application Identifier (01) that the +/// linear component carries, and the second the element strings of the 2D component. +/// +public sealed class Gs1DataBarTruncatedCompositeSymbology : CompositeBarcodeSymbology +{ + /// + /// The data columns of the 2D component, Table 1 of ISO/IEC 24723. + /// + private const int Columns = 4; + + /// + /// Initializes a new instance of the class. + /// + public Gs1DataBarTruncatedCompositeSymbology() + : base(new Gs1DataBarTruncatedSymbology(), new Gs1CompositeComponentSymbology(Gs1CompositeComponent.CcA, Columns)) + { + } + + /// + internal override void EncodeParts( + Span parts, + BarcodeOptions options, + ReadOnlySpan first, + ReadOnlySpan second, + ReadOnlySpan third) + { + Gs1CompositeLayout.Validate(first, second); + InlineArray14 gtinStorage = default; + Span gtin = gtinStorage; + Gs1Data.ReadGtin(first, gtin); + DataBarEncoder.BuildLinear(gtin, true, DataBarEncoder.TruncatedHeight, DataBarEncoder.TruncatedHeight, options, ref parts[0]); + + // Section 11.4: the separator is light over the four modules at each end and treats the two finder patterns apart. The left finder starts after the + // guard pair and the first character of 16 modules. The right finder stands reversed, so its elements 1 to 3 start two modules into it, at module 65. + Gs1CompositeLayout.AddSeparator(ref parts[0], 4, 4, [18, 65], true); + Gs1CompositeLayout.BuildComponent(second, Gs1CompositeComponent.CcA, Columns, 0, parts[0].XDimension, options, ref parts[1]); + } + + /// + internal override PointF Place(int index, ReadOnlySpan parts, ReadOnlySpan symbolOrigins, BarcodeOptions options) + { + // Clause 12 b): the stop bar of the 2D component stands over the last interior space of the linear component, the guard space before its last bar. + int offset = (int)parts[0].WidthInModules - 2 - ((int)parts[1].WidthInModules - 1); + return Gs1CompositeLayout.PlaceAbove(parts, symbolOrigins[0], offset, options); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Composite/UpcACompositeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Composite/UpcACompositeSymbology.cs new file mode 100644 index 00000000..60fdb9e3 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Composite/UpcACompositeSymbology.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The UPC-A Composite symbology of ISO/IEC 24723: a UPC-A linear component, a 6X separator pattern, and a 4-column CC-A or CC-B 2D component above it. +/// The first text is the digits of the UPC-A symbol, and the second the element strings of the 2D component. +/// +public sealed class UpcACompositeSymbology : CompositeBarcodeSymbology +{ + /// + /// The data columns of the 2D component, Table 1 of ISO/IEC 24723. + /// + private const int Columns = 4; + + /// + /// Initializes a new instance of the class. + /// + public UpcACompositeSymbology() + : base(new UpcASymbology(), new Gs1CompositeComponentSymbology(Gs1CompositeComponent.CcA, Columns)) + { + } + + /// + internal override void EncodeParts( + Span parts, + BarcodeOptions options, + ReadOnlySpan first, + ReadOnlySpan second, + ReadOnlySpan third) + { + InlineArray16 gtinStorage = default; + ReadOnlySpan gtin = EanUpcEncoder.ValidateAndApplyCheckDigit(first, 11, gtinStorage); + Gs1CompositeLayout.EncodeEanUpc(this.Symbologies[0], Columns, parts, options, first, second, gtin); + } + + /// + internal override PointF Place(int index, ReadOnlySpan parts, ReadOnlySpan symbolOrigins, BarcodeOptions options) + { + int offset = Gs1CompositeLayout.EanUpcOffset((int)parts[0].WidthInModules, (int)parts[1].WidthInModules); + return Gs1CompositeLayout.PlaceAbove(parts, symbolOrigins[0], offset, options); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Composite/UpcECompositeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Composite/UpcECompositeSymbology.cs new file mode 100644 index 00000000..661024d4 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Composite/UpcECompositeSymbology.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The UPC-E Composite symbology of ISO/IEC 24723: a UPC-E linear component, a 6X separator pattern, and a 2-column CC-A or CC-B 2D component above it. +/// The first text is the digits of the UPC-E symbol, and the second the element strings of the 2D component. +/// +public sealed class UpcECompositeSymbology : CompositeBarcodeSymbology +{ + /// + /// The data columns of the 2D component, Table 1 of ISO/IEC 24723. + /// + private const int Columns = 2; + + /// + /// Initializes a new instance of the class. + /// + public UpcECompositeSymbology() + : base(new UpcESymbology(), new Gs1CompositeComponentSymbology(Gs1CompositeComponent.CcA, Columns)) + { + } + + /// + internal override void EncodeParts( + Span parts, + BarcodeOptions options, + ReadOnlySpan first, + ReadOnlySpan second, + ReadOnlySpan third) + { + InlineArray16 gtinStorage = default; + ReadOnlySpan gtin = UpcESymbology.ExpandToGtin12(first, gtinStorage); + Gs1CompositeLayout.EncodeEanUpc(this.Symbologies[0], Columns, parts, options, first, second, gtin); + } + + /// + internal override PointF Place(int index, ReadOnlySpan parts, ReadOnlySpan symbolOrigins, BarcodeOptions options) + { + int offset = Gs1CompositeLayout.EanUpcOffset((int)parts[0].WidthInModules, (int)parts[1].WidthInModules); + return Gs1CompositeLayout.PlaceAbove(parts, symbolOrigins[0], offset, options); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/CompositeBarcodeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/CompositeBarcodeSymbology.cs new file mode 100644 index 00000000..b79e226d --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/CompositeBarcodeSymbology.cs @@ -0,0 +1,428 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts; +using SixLabors.ImageSharp.Drawing.Helpers; +using SixLabors.ImageSharp.Drawing.Processing; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// A composite barcode symbology: one symbol made of two or more symbologies. Each symbology encodes its own text into its own part of the symbol, and the +/// composite places each part relative to the parts before it. +/// +public abstract class CompositeBarcodeSymbology +{ + /// + /// The symbologies of the parts, in the order of their texts. + /// + private readonly BarcodeSymbology[] symbologies; + + /// + /// Initializes a new instance of the class. + /// + /// The symbologies of the parts, in the order of their texts. There are two or more. + private protected CompositeBarcodeSymbology(params ReadOnlySpan symbologies) + { + Guard.MustBeGreaterThanOrEqualTo(symbologies.Length, 2, nameof(symbologies)); + foreach (BarcodeSymbology symbology in symbologies) + { + Guard.NotNull(symbology, nameof(symbologies)); + } + + // The symbology keeps its own copy, so a caller that changes its span after construction cannot change the parts. + this.symbologies = symbologies.ToArray(); + } + + /// + /// One pass over the arranged parts of a composite: measure or draw. + /// + private interface ICompositePass + { + /// + /// Measures or draws the arranged parts. + /// + /// The arranged parts. + /// The sizing and painting options. + /// The top left corner of the composite, in pixels. + /// The layout box of the composite, relative to . + /// The metrics of the composite. + public BarcodeMetrics Run(in CompositeParts parts, BarcodeOptions options, PointF origin, RectangleF advance); + } + + /// + /// Gets the symbologies of the parts, in the order of their texts. + /// + public ReadOnlySpan Symbologies => this.symbologies; + + /// + /// Encodes a composite of two parts and measures the parts from the origin without drawing anything. + /// + /// The sizing and painting options. + /// The top left corner of the composite, in pixels. + /// The text of the first symbology. + /// The text of the second symbology. + /// The metrics of the composite. + /// The composite does not have two parts, a text is not valid, or the parts do not join. + internal BarcodeMetrics Measure(BarcodeOptions options, PointF origin, ReadOnlySpan first, ReadOnlySpan second) + { + MeasurePass measure = default; + return this.Lay(ref measure, options, origin, 2, first, second, []); + } + + /// + /// Encodes a composite of three parts and measures the parts from the origin without drawing anything. + /// + /// The sizing and painting options. + /// The top left corner of the composite, in pixels. + /// The text of the first symbology. + /// The text of the second symbology. + /// The text of the third symbology. + /// The metrics of the composite. + /// The composite does not have three parts, a text is not valid, or the parts do not join. + internal BarcodeMetrics Measure(BarcodeOptions options, PointF origin, ReadOnlySpan first, ReadOnlySpan second, ReadOnlySpan third) + { + MeasurePass measure = default; + return this.Lay(ref measure, options, origin, 3, first, second, third); + } + + /// + /// Encodes a composite of two parts and draws the parts from the origin. + /// + /// The canvas to render onto. + /// The sizing and painting options. + /// The top left corner of the composite, in pixels. + /// The text of the first symbology. + /// The text of the second symbology. + /// The metrics of the composite. + /// The composite does not have two parts, a text is not valid, or the parts do not join. + internal BarcodeMetrics Draw(DrawingCanvas canvas, BarcodeOptions options, PointF origin, ReadOnlySpan first, ReadOnlySpan second) + { + DrawPass draw = new(canvas); + return this.Lay(ref draw, options, origin, 2, first, second, []); + } + + /// + /// Encodes a composite of three parts and draws the parts from the origin. + /// + /// The canvas to render onto. + /// The sizing and painting options. + /// The top left corner of the composite, in pixels. + /// The text of the first symbology. + /// The text of the second symbology. + /// The text of the third symbology. + /// The metrics of the composite. + /// The composite does not have three parts, a text is not valid, or the parts do not join. + internal BarcodeMetrics Draw( + DrawingCanvas canvas, + BarcodeOptions options, + PointF origin, + ReadOnlySpan first, + ReadOnlySpan second, + ReadOnlySpan third) + { + DrawPass draw = new(canvas); + return this.Lay(ref draw, options, origin, 3, first, second, third); + } + + /// + /// Encodes the text of one part. By default the symbology of the part encodes its text the same as for a symbol of its own. A composite whose standard + /// changes a part when it is joined, a linkage flag for example, overrides this to encode that part the joined way. + /// + /// The index of the part in . + /// The text of the part. + /// The options that control layout choices made during encoding. + /// The part to write. It is empty on entry. + /// The text is not valid for the symbology of the part. + internal virtual void EncodePart(int index, ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol part) + => this.symbologies[index].Encode(text, options, ref part); + + /// + /// Returns where the symbol of a part after the first stands. The composite places it relative to the symbols of the parts before it, as its standard + /// requires. A symbol stands at the top left corner of its first module, so the quiet zones and the text of a part do not move it. + /// + /// The index of the part in , 1 or more. + /// The encoded parts, one for each symbology of and in the same order. + /// The top left corner of the first module of the symbol of each part before it, in pixels. + /// The sizing and painting options. + /// The top left corner of the first module of the symbol of the part, in pixels. + /// The parts do not join. + internal abstract PointF Place(int index, ReadOnlySpan parts, ReadOnlySpan symbolOrigins, BarcodeOptions options); + + /// + /// Returns the metrics of a composite. The renderable bounds are the union of the layout box and the bounds, as they are for one symbol. + /// + /// The top left corner of the composite, in pixels. + /// The layout box of the composite, relative to . + /// The union of the ink of the parts, in pixels. + /// The metrics of the composite. + private static BarcodeMetrics CompositeMetrics(PointF origin, RectangleF advance, RectangleF bounds) + => new(advance, bounds, RectangleF.Union(new RectangleF(origin, advance.Size), bounds)); + + /// + /// Encodes each text with its own symbology, shapes the text of each part one time, arranges the parts and runs the pass over them. The parts live on + /// this stack frame and in the arrays they rent, which they return once the pass ends. + /// + /// The pass that measures or draws the parts. It is a struct, so the compiler specializes this method for each pass. + /// The pass that measures or draws the parts. + /// The sizing and painting options. + /// The top left corner of the composite, in pixels. + /// The number of texts the caller gives, 2 or 3. + /// The text of the first symbology. + /// The text of the second symbology. + /// The text of the third symbology, unused when is 2. + /// The metrics of the composite. + private BarcodeMetrics Lay( + ref TPass pass, + BarcodeOptions options, + PointF origin, + int count, + ReadOnlySpan first, + ReadOnlySpan second, + ReadOnlySpan third) + where TPass : struct, ICompositePass + { + InlineArray3 partStorage = default; + Span symbols = ((Span)partStorage)[..count]; + try + { + if (this.symbologies.Length != count) + { + throw new ArgumentException($"The composite has {this.symbologies.Length} parts; got {count} texts."); + } + + this.EncodeParts(symbols, options, first, second, third); + + // The lines of all the parts share one buffer, and the lines of part i are the range from lineStarts[i] to lineStarts[i + 1]. + InlineArray4 startStorage = default; + Span lineStarts = ((Span)startStorage)[..(count + 1)]; + for (int i = 0; i < count; i++) + { + lineStarts[i + 1] = lineStarts[i] + BarcodeEmitter.LineCount(ref symbols[i], options); + } + + int lineCount = lineStarts[count]; + using PooledBuffer lineBuffer = lineCount == 0 ? default : new(lineCount); + Span lines = lineBuffer.Span; + + // A TextBlock is a class, so the span holds object references and cannot come from the byte pool. A bar symbol has at most 14 text placements, a + // matrix symbol 1 and a hexagonal symbol none, so three parts have at most 42. The 32 stack slots hold up to 32, and a larger count takes an array. + InlineArray32 blockStorage = default; + Span blocks = lineCount <= 32 ? ((Span)blockStorage)[..lineCount] : new TextBlock[lineCount]; + for (int i = 0; i < count; i++) + { + Range range = lineStarts[i]..lineStarts[i + 1]; + BarcodeEmitter.Shape(ref symbols[i], options, lines[range], blocks[range]); + } + + InlineArray3 originStorage = default; + CompositeParts parts = new(symbols, lines, blocks, lineStarts, ((Span)originStorage)[..count]); + RectangleF advance = this.Arrange(in parts, options, origin); + return pass.Run(in parts, options, origin, advance); + } + finally + { + foreach (ref BarcodeSymbol symbol in symbols) + { + symbol.Dispose(); + } + } + } + + /// + /// Encodes each text with the symbology of its part. A composite whose parts depend on each other overrides this: the 2D component of a GS1 Composite + /// symbol takes its X dimension from the linear component, for example. + /// + /// The parts to write, one for each symbology of . + /// The options that control layout choices made during encoding. + /// The text of the first symbology. + /// The text of the second symbology. + /// The text of the third symbology, unused when there are two parts. + /// A text is not valid for its symbology, or the parts do not join. + internal virtual void EncodeParts( + Span parts, + BarcodeOptions options, + ReadOnlySpan first, + ReadOnlySpan second, + ReadOnlySpan third) + { + this.EncodePart(0, first, options, ref parts[0]); + this.EncodePart(1, second, options, ref parts[1]); + if (parts.Length == 3) + { + this.EncodePart(2, third, options, ref parts[2]); + } + } + + /// + /// Writes the origin of each part. The text and the quiet zones of a part set where its symbol stands in its layout box, and nothing in the layout box + /// rounds, so that offset is the same wherever the part stands. The layout of each part at zero gives the offset. The symbol of the first part stands at + /// its offset, puts the symbol of each next part, and each part starts its offset before its symbol. The union of the layout boxes then + /// moves so that its top left corner is the origin, as the layout box of one symbol does. + /// + /// The shaped parts. Their origins are written. + /// The sizing and painting options. + /// The top left corner of the composite, in pixels. + /// The layout box of the composite, relative to . + /// The parts do not join. + private RectangleF Arrange(in CompositeParts parts, BarcodeOptions options, PointF origin) + { + Span symbols = parts.Symbols; + Span origins = parts.Origins; + InlineArray3 symbolStorage = default; + Span symbolOrigins = ((Span)symbolStorage)[..symbols.Length]; + RectangleF box = RectangleF.Empty; + for (int i = 0; i < symbols.Length; i++) + { + BarcodeMetrics metrics = BarcodeEmitter.Measure(ref symbols[i], options, PointF.Empty, parts.Lines(i), out PointF offset); + symbolOrigins[i] = i == 0 ? offset : this.Place(i, symbols, symbolOrigins[..i], options); + origins[i] = new PointF(symbolOrigins[i].X - offset.X, symbolOrigins[i].Y - offset.Y); + + RectangleF partBox = new(origins[i], metrics.Advance.Size); + box = i == 0 ? partBox : RectangleF.Union(box, partBox); + } + + for (int i = 0; i < origins.Length; i++) + { + origins[i] = new PointF(origin.X + origins[i].X - box.X, origin.Y + origins[i].Y - box.Y); + } + + return new RectangleF(0, 0, box.Width, box.Height); + } + + /// + /// The encoded parts of a composite, their shaped text and their origins. The lines and the blocks of all the parts share one span each, and each part + /// reads its own range. + /// + private readonly ref struct CompositeParts + { + /// + /// The shaped lines of all the parts. + /// + private readonly Span lines; + + /// + /// The shaped blocks of all the parts, one for each line. + /// + private readonly ReadOnlySpan blocks; + + /// + /// The index of the first line of each part, and the line count after the last part. + /// + private readonly ReadOnlySpan lineStarts; + + /// + /// Initializes a new instance of the struct. + /// + /// The encoded parts. + /// The shaped lines of all the parts. + /// The shaped blocks of all the parts, one for each line. + /// The index of the first line of each part, and the line count after the last part. + /// The top left corner of each part, to write. + public CompositeParts( + Span symbols, + Span lines, + ReadOnlySpan blocks, + ReadOnlySpan lineStarts, + Span origins) + { + this.Symbols = symbols; + this.lines = lines; + this.blocks = blocks; + this.lineStarts = lineStarts; + this.Origins = origins; + } + + /// + /// Gets the encoded parts. + /// + public Span Symbols { get; } + + /// + /// Gets the top left corner of each part, in pixels. + /// + public Span Origins { get; } + + /// + /// Returns the shaped lines of one part. + /// + /// The index of the part. + /// The lines. + public Span Lines(int index) => this.lines[this.lineStarts[index]..this.lineStarts[index + 1]]; + + /// + /// Returns the shaped blocks of one part. + /// + /// The index of the part. + /// The blocks, one for each line. + public ReadOnlySpan Blocks(int index) => this.blocks[this.lineStarts[index]..this.lineStarts[index + 1]]; + } + + /// + /// The pass that measures each part without drawing anything. + /// + private readonly struct MeasurePass : ICompositePass + { + /// + public BarcodeMetrics Run(in CompositeParts parts, BarcodeOptions options, PointF origin, RectangleF advance) + { + Span symbols = parts.Symbols; + RectangleF bounds = RectangleF.Empty; + for (int i = 0; i < symbols.Length; i++) + { + RectangleF partBounds = BarcodeEmitter.Measure(ref symbols[i], options, parts.Origins[i], parts.Lines(i), out _).Bounds; + bounds = i == 0 ? partBounds : RectangleF.Union(bounds, partBounds); + } + + return CompositeMetrics(origin, advance, bounds); + } + } + + /// + /// The pass that draws the parts onto a canvas. The composite is one symbol, so one background fills the renderable bounds of the composite before any + /// part draws, and each part draws with no background of its own. + /// + private readonly struct DrawPass : ICompositePass + { + /// + /// The canvas to render onto. + /// + private readonly DrawingCanvas canvas; + + /// + /// Initializes a new instance of the struct. + /// + /// The canvas to render onto. + public DrawPass(DrawingCanvas canvas) => this.canvas = canvas; + + /// + public BarcodeMetrics Run(in CompositeParts parts, BarcodeOptions options, PointF origin, RectangleF advance) + { + Span symbols = parts.Symbols; + + // The ink of a part is known only when it is laid out at its origin, so the background needs the measure pass first. + if (options.Background is not null) + { + BarcodeMetrics metrics = default(MeasurePass).Run(in parts, options, origin, advance); + RectangleF renderable = metrics.RenderableBounds; + this.canvas.Fill(options.Background, new RectanglePolygon(renderable.X, renderable.Y, renderable.Width, renderable.Height)); + for (int i = 0; i < symbols.Length; i++) + { + BarcodeEmitter.Draw(this.canvas, ref symbols[i], options, parts.Origins[i], parts.Lines(i), parts.Blocks(i), null); + } + + return metrics; + } + + // Without a background each part draws at its origin in one pass. + RectangleF bounds = RectangleF.Empty; + for (int i = 0; i < symbols.Length; i++) + { + BarcodeMetrics metrics = BarcodeEmitter.Draw(this.canvas, ref symbols[i], options, parts.Origins[i], parts.Lines(i), parts.Blocks(i), null); + bounds = i == 0 ? metrics.Bounds : RectangleF.Union(bounds, metrics.Bounds); + } + + return CompositeMetrics(origin, advance, bounds); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataBar/DataBarEncoder.cs b/src/ImageSharp.Drawing/Barcodes/DataBar/DataBarEncoder.cs new file mode 100644 index 00000000..858b4597 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataBar/DataBarEncoder.cs @@ -0,0 +1,589 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodation for the first group of GS1 DataBar symbols: GS1 DataBar Omnidirectional, Truncated, Stacked and Stacked Omnidirectional. A symbol carries the +/// item identification of GS1 Application Identifier (01) as four symbol characters and two finder patterns, between a guard pattern of a narrow space and a +/// narrow bar at each end. The finder patterns encode a modulo 79 check value. The four variations encode the data in the same way and differ in height and in +/// how the two halves of the symbol stack. +/// +/// The 13 digits before the check digit form a number below 10^13. It splits into a left and a right pair value modulo 4537077, and each pair splits into an +/// outside and an inside symbol character modulo 1597. A symbol character is 8 elements, an odd set of 4 and an even set of 4 interleaved. Its group gives the +/// module count and the widest element of each set, and its value within the group gives the width of every element. +/// +/// +internal static class DataBarEncoder +{ + /// + /// The nominal X dimension in millimetres, the target X dimension of Tables 5-44, 5-50 and 5-56 of the GS1 General Specifications. GS1 DataBar Truncated + /// and GS1 DataBar Stacked are in Tables 5-50 and 5-56 only. + /// + public const double NominalXDimension = 0.33; + + /// + /// The smallest X dimension in millimetres of every GS1 DataBar symbol, the smallest minimum of the symbol specification tables of section 5.12.3 that list + /// them: 0.170 in Table 5-52. + /// + public const double MinimumXDimension = 0.170; + + /// + /// The largest X dimension in millimetres of every GS1 DataBar symbol, the maximum of every symbol specification table that lists them. + /// + public const double MaximumXDimension = 0.660; + + /// + /// The nominal height of GS1 DataBar Omnidirectional in modules. Table 5-44 gives 15.19 millimetres at the target X dimension, and its note (****) requires + /// an over square symbol of 46X in an omnidirectional scanning environment. + /// + public const float OmnidirectionalHeight = 46F; + + /// + /// The minimum height of GS1 DataBar Omnidirectional in modules. Section 5.5.2.1.1 of the GS1 General Specifications: "33X is the minimum height of the + /// symbol". + /// + public const float OmnidirectionalMinimumHeight = 33F; + + /// + /// The height of GS1 DataBar Truncated in modules. Section 5.5.2.1.2 of the GS1 General Specifications gives "96X wide by 13X high". It is also the + /// minimum: Table 5-56 gives 3.43 millimetres, 13X, at the X dimension of 0.264 millimetre, and "the symbol height SHALL NOT be truncated below the + /// minimum". + /// + public const float TruncatedHeight = 13F; + + /// + /// The height of the upper row of GS1 DataBar Stacked in modules. With the lower row and the 1X separator it makes the 13X height of the "50X wide by 13X + /// high" of section 5.5.2.1.3, which gives no height for each row. + /// + private const float StackedUpperRowHeight = 5F; + + /// + /// The height of the lower row of GS1 DataBar Stacked in modules. + /// + private const float StackedLowerRowHeight = 7F; + + /// + /// The nominal height of each row of GS1 DataBar Stacked Omnidirectional in modules. Table 5-44 gives 31.37 millimetres, 95X, at the target X dimension, + /// and section 5.5.2.1.4 places a 3X separator between the two rows. + /// + private const float StackedOmnidirectionalRowHeight = 46F; + + /// + /// The minimum height of each row of GS1 DataBar Stacked Omnidirectional in modules. Section 5.5.2.1.4: "69X is the minimum height of the symbol", and + /// "The height of 69X includes a 3X high separator pattern between two rows of 33X each". + /// + private const float StackedOmnidirectionalMinimumRowHeight = 33F; + + /// + /// The width of a single row symbol in modules, section 5.5.2.1.1: "Its dimensions are 96X wide". + /// + private const int Width = 96; + + /// + /// The width of a stacked symbol in modules, section 5.5.2.1.3: "Its dimensions are 50X wide". + /// + private const int StackedWidth = 50; + + /// + /// The number of elements in a symbol: a guard pair at each end, four symbol characters of 8 elements and two finder patterns of 5, starting with a space. + /// + private const int ElementCount = 46; + + /// + /// The modulus that splits the data value into the left and right pair values. + /// + private const int PairModulus = 4537077; + + /// + /// The modulus that splits a pair value into its outside and inside symbol characters. + /// + private const int CharacterModulus = 1597; + + /// + /// The modulus of the check value. + /// + private const int CheckModulus = 79; + + /// + /// The number of digits that the symbol encodes: the GTIN without its check digit. + /// + private const int DataDigits = 13; + + /// + /// The number of symbol characters of a symbol. + /// + private const int CharacterCount = 4; + + /// + /// The number of elements of a symbol character, an odd set of 4 and an even set of 4. + /// + private const int CharacterElements = 8; + + /// + /// The number of modules of an outside symbol character, the first or the third. + /// + private const int OutsideModules = 16; + + /// + /// The number of modules of an inside symbol character, the second or the fourth. + /// + private const int InsideModules = 15; + + /// + /// The first group of the inside symbol characters in . The groups before it are the groups of the outside symbol characters. + /// + private const int FirstInsideGroup = 5; + + /// + /// The number of elements of a finder pattern. + /// + private const int FinderElements = 5; + + /// + /// The number of finder patterns in . + /// + private const int FinderValues = 9; + + /// + /// The number of modules of the first three elements of a finder pattern, as the left finder reads them. The last two elements are one module each. + /// + private const int FinderLeadModules = 13; + + /// + /// The number of elements of the guard pattern at each end, a narrow space and a narrow bar. Each element is one module, so the guard pattern is also + /// this many modules wide. + /// + private const int GuardElements = 2; + + /// + /// The number of light modules at each end of a separator row. + /// + private const int SeparatorLightModules = 4; + + /// + /// Gets the first symbol character value of each group: groups 0 to 4 are the outside characters and groups 5 to 8 the inside characters. + /// + private static ReadOnlySpan GroupSum => [0, 161, 961, 2015, 2715, 0, 336, 1036, 1516]; + + /// + /// Gets the number of even set combinations of an outside group, and of odd set combinations of an inside group. A value within a group splits into the two + /// set values by this number. + /// + private static ReadOnlySpan Combinations => [1, 10, 34, 70, 126, 4, 20, 48, 81]; + + /// + /// Gets the module count of the odd set of each group. The even set takes the rest of the 16 modules of an outside character or the 15 modules of an inside + /// character. + /// + private static ReadOnlySpan OddModules => [12, 10, 8, 6, 4, 5, 7, 9, 11]; + + /// + /// Gets the widest element of the odd set of each group. The widest element of the even set is nine less this. + /// + private static ReadOnlySpan OddWidest => [8, 6, 4, 3, 1, 2, 4, 6, 8]; + + /// + /// Gets the element widths of the nine finder patterns, five elements each, as the left finder reads them. The right finder reads the same widths in + /// reverse. + /// + private static ReadOnlySpan FinderPatterns => + [ + 3, 8, 2, 1, 1, + 3, 5, 5, 1, 1, + 3, 3, 7, 1, 1, + 3, 1, 9, 1, 1, + 2, 7, 4, 1, 1, + 2, 5, 6, 1, 1, + 2, 3, 8, 1, 1, + 1, 5, 7, 1, 1, + 1, 3, 9, 1, 1 + ]; + + /// + /// Gets the check weights, eight per symbol character in the order the characters are calculated. + /// + private static ReadOnlySpan CheckWeights => + [ + 1, 3, 9, 27, 2, 6, 18, 54, + 4, 12, 36, 29, 8, 24, 72, 58, + 16, 48, 65, 37, 32, 17, 51, 74, + 64, 34, 23, 69, 49, 68, 46, 59 + ]; + + /// + /// Builds a single row symbol, GS1 DataBar Omnidirectional or GS1 DataBar Truncated. + /// + /// The 14 digit GTIN. + /// Whether a 2D component of a GS1 Composite symbol is linked to the symbol. + /// The nominal height of the variation in modules. + /// The minimum height that the variation requires in modules. A smaller bar height draws at this height. + /// The options that control the bar height and the text. + /// The symbol to write. + public static void BuildLinear( + ReadOnlySpan gtin, + bool linked, + float nominalHeight, + float minimumHeight, + BarcodeOptions options, + ref BarcodeSymbol symbol) + { + double xDimension = options.ResolveXDimension(MinimumXDimension, NominalXDimension, MaximumXDimension); + + // The element widths start with the guard space, so a zero width bar leads the runs and the widths follow it. + Span runs = symbol.BeginLinear(ElementCount + 1, xDimension, out Span heights, out _); + _ = EncodeWidths(gtin, linked, runs[1..]); + + // Section 5.5.2 of the GS1 General Specifications gives a minimum height and no maximum: "the actual height of the symbol used depends on the specific + // application requirements". + float barHeight = options.ResolveBarHeight(xDimension, minimumHeight, nominalHeight, float.PositiveInfinity); + heights.Fill(barHeight); + AddCaption(gtin, Width, barHeight, options, ref symbol); + symbol.EndLinear(0, 0); + } + + /// + /// Builds GS1 DataBar Stacked: the left half of the symbol over the right half, with a one module separator between them. The bar height of the options + /// sets the lower row, and the upper row keeps its ratio of five to seven. + /// + /// The 14 digit GTIN. + /// Whether a 2D component of a GS1 Composite symbol is linked to the symbol. + /// The options that control the bar height and the text. + /// The symbol to write. + public static void BuildStacked(ReadOnlySpan gtin, bool linked, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray64 widthStorage = default; + Span widths = ((Span)widthStorage)[..ElementCount]; + _ = EncodeWidths(gtin, linked, widths); + + // The three rows share one buffer, which starts zeroed. The separator loop leaves its first module and the last four at zero, light. + InlineArray256 moduleStorage = default; + Span modules = ((Span)moduleStorage)[..(StackedWidth * 3)]; + Span upper = modules[..StackedWidth]; + Span separator = modules.Slice(StackedWidth, StackedWidth); + Span lower = modules.Slice(StackedWidth * 2, StackedWidth); + Halves(widths, upper, lower); + + // Each separator module is light where the rows above and below are dark. Where they differ, it takes the opposite of the module to its left, so a + // light run under or over an edge breaks into narrow elements. The four modules at each end stay light. + for (int i = 1; i < StackedWidth - SeparatorLightModules; i++) + { + separator[i] = upper[i] == lower[i] + ? (byte)(1 - upper[i]) + : (byte)(1 - separator[i - 1]); + } + + separator[1..SeparatorLightModules].Clear(); + + double xDimension = options.ResolveXDimension(MinimumXDimension, NominalXDimension, MaximumXDimension); + + // Table 5-56 of the GS1 General Specifications gives a minimum symbol height of 3.43 millimetres, 13X, at the X dimension of 0.264 millimetre. The bar + // height sets the lower row, and the upper row keeps its ratio of five to seven, so a symbol of 13X has a lower row of 7X. Section 5.5.2.1.3 gives no + // maximum height. + float lowerHeight = options.ResolveBarHeight(xDimension, StackedLowerRowHeight, StackedLowerRowHeight, float.PositiveInfinity); + float upperHeight = lowerHeight * StackedUpperRowHeight / StackedLowerRowHeight; + int upperRuns = DataBarRows.RunCount(upper); + int separatorRuns = DataBarRows.RunCount(separator); + int lowerRuns = DataBarRows.RunCount(lower); + symbol.BeginStacked(3, upperRuns + separatorRuns + lowerRuns, ((upperRuns + 1) >> 1) + ((separatorRuns + 1) >> 1) + ((lowerRuns + 1) >> 1), xDimension); + DataBarRows.AddRow(upper, upperHeight, ref symbol); + DataBarRows.AddRow(separator, 1F, ref symbol); + DataBarRows.AddRow(lower, lowerHeight, ref symbol); + + AddCaption(gtin, StackedWidth, upperHeight + 1F + lowerHeight, options, ref symbol); + symbol.EndStacked(0F, 0F, 0F); + } + + /// + /// Builds GS1 DataBar Stacked Omnidirectional: the left half of the symbol over the right half, with a separator of three one module rows between them. The + /// bar height of the options sets each row. + /// + /// The 14 digit GTIN. + /// Whether a 2D component of a GS1 Composite symbol is linked to the symbol. + /// The options that control the bar height and the text. + /// The symbol to write. + public static void BuildStackedOmnidirectional(ReadOnlySpan gtin, bool linked, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray64 widthStorage = default; + Span widths = ((Span)widthStorage)[..ElementCount]; + int rightFinder = EncodeWidths(gtin, linked, widths); + + // The five rows share one buffer, which starts zeroed. The separator rows write only some of their modules, and the rest stay light. + InlineArray256 moduleStorage = default; + Span modules = ((Span)moduleStorage)[..(StackedWidth * 5)]; + Span upper = modules[..StackedWidth]; + Span upperSeparator = modules.Slice(StackedWidth, StackedWidth); + Span middleSeparator = modules.Slice(StackedWidth * 2, StackedWidth); + Span lowerSeparator = modules.Slice(StackedWidth * 3, StackedWidth); + Span lower = modules.Slice(StackedWidth * 4, StackedWidth); + Halves(widths, upper, lower); + + // The first and last separator rows are the inverse of the row they touch, inside four light modules at each end. Over a finder pattern the light + // modules break into alternating narrow elements instead, so the separator does not read as part of the finder. + Invert(upper, upperSeparator); + Invert(lower, lowerSeparator); + + // The upper finder starts after the guard pattern and the first character, an outside character. The lower row starts with a narrow bar and a narrow + // space, then the fourth character, an inside character. The lower finder follows it, and its two narrow leading elements stay inverted. + const int upperFinderStart = GuardElements + OutsideModules; + const int lowerFinderLeadStart = GuardElements + InsideModules + 2; + AlternateOverFinder(upper, upperSeparator, upperFinderStart); + + // Finder value 3 reads 9, 1 and 3 modules in the lower row. Over it the lower separator carries a single dark module over the start of the three + // module finder bar. + if (rightFinder == 3) + { + lowerSeparator.Slice(lowerFinderLeadStart, FinderLeadModules).Clear(); + lowerSeparator[lowerFinderLeadStart + 9 + 1] = 1; + } + else + { + AlternateOverFinder(lower, lowerSeparator, lowerFinderLeadStart); + } + + // The middle separator row alternates narrow dark and light modules between the four light modules at each end. + for (int i = SeparatorLightModules + 1; i < StackedWidth - SeparatorLightModules; i += 2) + { + middleSeparator[i] = 1; + } + + double xDimension = options.ResolveXDimension(MinimumXDimension, NominalXDimension, MaximumXDimension); + float rowHeight = options.ResolveBarHeight( + xDimension, + StackedOmnidirectionalMinimumRowHeight, + StackedOmnidirectionalRowHeight, + float.PositiveInfinity); + + int runCount = 0; + int barCount = 0; + for (int row = 0; row < 5; row++) + { + int runs = DataBarRows.RunCount(modules.Slice(row * StackedWidth, StackedWidth)); + runCount += runs; + barCount += (runs + 1) >> 1; + } + + symbol.BeginStacked(5, runCount, barCount, xDimension); + DataBarRows.AddRow(upper, rowHeight, ref symbol); + DataBarRows.AddRow(upperSeparator, 1F, ref symbol); + DataBarRows.AddRow(middleSeparator, 1F, ref symbol); + DataBarRows.AddRow(lowerSeparator, 1F, ref symbol); + DataBarRows.AddRow(lower, rowHeight, ref symbol); + + AddCaption(gtin, StackedWidth, (rowHeight * 2F) + 3F, options, ref symbol); + symbol.EndStacked(0F, 0F, 0F); + } + + /// + /// Adds the human readable interpretation below the symbol: the application identifier in parentheses and the 14 digits, as section 4.14 of the GS1 General + /// Specifications prints an element string, at the size of or larger. A symbol without a font carries no text. + /// + /// The 14 digit GTIN. + /// The symbol width in modules. + /// The symbol height in modules. + /// The options that carry the font. + /// The symbol to write. + public static void AddCaption(ReadOnlySpan gtin, int width, float height, BarcodeOptions options, ref BarcodeSymbol symbol) + { + if (options.Font is null) + { + return; + } + + const string identifier = "(01)"; + symbol.ReserveText(1, identifier.Length + gtin.Length); + symbol.AddText(identifier, gtin, [], 0F, width, BarcodeTextSide.BelowBars, height + BarcodeTextPlacement.Clearance); + symbol.SetTextSize(Gs1Data.MinimumTextSize, 0F); + } + + /// + /// Calculates the 46 element widths of the symbol, starting with the left guard space: the guard pair, the first character, the left finder, the second + /// character reversed, the fourth character, the right finder reversed, the third character reversed and the guard pair. + /// + /// The 14 digit GTIN. + /// Whether a 2D component of a GS1 Composite symbol is linked to the symbol. + /// The buffer of 46 element widths. + /// The value of the right finder pattern. + private static int EncodeWidths(ReadOnlySpan gtin, bool linked, Span widths) + { + // The check digit is not encoded: the 13 digits before it are the data value. The linkage flag stands before them as a fourteenth digit, so a linked + // symbol adds 10^13. + long value = linked ? 1 : 0; + for (int i = 0; i < DataDigits; i++) + { + value = (value * 10) + (gtin[i] - '0'); + } + + int leftPair = (int)(value / PairModulus); + int rightPair = (int)(value % PairModulus); + + // The first and third characters are outside characters and the second and fourth inside ones. An outside value splits into its odd value by the even + // combinations and an inside value into its even value by the odd combinations, so the quotient and the remainder swap roles. The buffer holds the 32 + // widths of the four characters, then the odd and the even set of the character being calculated. + const int setElements = CharacterElements / 2; + InlineArray64 bufferStorage = default; + Span buffer = ((Span)bufferStorage)[..((CharacterCount * CharacterElements) + CharacterElements)]; + Span characterWidths = buffer[..(CharacterCount * CharacterElements)]; + Span odd = buffer.Slice(CharacterCount * CharacterElements, setElements); + Span even = buffer.Slice((CharacterCount * CharacterElements) + setElements, setElements); + int check = 0; + for (int c = 0; c < CharacterCount; c++) + { + bool outside = (c & 1) == 0; + + // The first two characters are the outside and inside characters of the left pair, and the last two those of the right pair. + int pair = c < 2 ? leftPair : rightPair; + int character = outside ? pair / CharacterModulus : pair % CharacterModulus; + int group = outside ? 0 : FirstInsideGroup; + int last = outside ? FirstInsideGroup - 1 : GroupSum.Length - 1; + while (group < last && character >= GroupSum[group + 1]) + { + group++; + } + + int withinGroup = character - GroupSum[group]; + int quotient = withinGroup / Combinations[group]; + int remainder = withinGroup % Combinations[group]; + int totalModules = outside ? OutsideModules : InsideModules; + + // An outside character excludes odd sets without a narrow element in its even set, and an inside character in its odd set. + DataBarWidths.Calculate(odd, outside ? quotient : remainder, OddModules[group], OddWidest[group], !outside); + DataBarWidths.Calculate(even, outside ? remainder : quotient, totalModules - OddModules[group], 9 - OddWidest[group], outside); + + Span elements = characterWidths.Slice(c * CharacterElements, CharacterElements); + for (int k = 0; k < setElements; k++) + { + elements[k * 2] = odd[k]; + elements[(k * 2) + 1] = even[k]; + } + + ReadOnlySpan weights = CheckWeights.Slice(c * CharacterElements, CharacterElements); + for (int k = 0; k < CharacterElements; k++) + { + check += weights[k] * elements[k]; + } + } + + // The check value skips the finder pairs (0, 8) and (8, 0), so it maps onto the 9 x 9 finder pairs less those two. + check %= CheckModulus; + if (check >= FinderValues - 1) + { + check++; + } + + if (check >= (FinderValues - 1) * FinderValues) + { + check++; + } + + int leftFinder = check / FinderValues; + int rightFinder = check % FinderValues; + + // The symbol reads the guard pattern, the first character, the left finder, the second character reversed, the fourth character, the right finder + // reversed, the third character reversed and the guard pattern. + const int leftFinderStart = GuardElements + CharacterElements; + const int secondStart = leftFinderStart + FinderElements; + const int fourthStart = secondStart + CharacterElements; + const int rightFinderStart = fourthStart + CharacterElements; + const int thirdStart = rightFinderStart + FinderElements; + widths[..GuardElements].Fill(1); + widths[^GuardElements..].Fill(1); + characterWidths[..CharacterElements].CopyTo(widths[GuardElements..]); + characterWidths.Slice(3 * CharacterElements, CharacterElements).CopyTo(widths[fourthStart..]); + + Span second = widths.Slice(secondStart, CharacterElements); + characterWidths.Slice(CharacterElements, CharacterElements).CopyTo(second); + second.Reverse(); + + Span third = widths.Slice(thirdStart, CharacterElements); + characterWidths.Slice(2 * CharacterElements, CharacterElements).CopyTo(third); + third.Reverse(); + + ReadOnlySpan left = FinderPatterns.Slice(leftFinder * FinderElements, FinderElements); + ReadOnlySpan right = FinderPatterns.Slice(rightFinder * FinderElements, FinderElements); + for (int k = 0; k < FinderElements; k++) + { + widths[leftFinderStart + k] = left[k]; + widths[rightFinderStart + k] = right[FinderElements - 1 - k]; + } + + return rightFinder; + } + + /// + /// Expands the 46 element widths into the two 50 module rows of a stacked symbol. The upper row is the first 23 elements and a closing narrow bar and + /// space. The lower row is an opening narrow bar and space and the last 23 elements. + /// + /// The 46 element widths, starting with a space. + /// The 50 modules of the upper row. + /// The 50 modules of the lower row. + private static void Halves(ReadOnlySpan widths, Span upper, Span lower) + { + int position = Expand(widths[..23], 0, upper, 0); + upper[position] = 1; + upper[position + 1] = 0; + + lower[0] = 1; + lower[1] = 0; + _ = Expand(widths[23..], 1, lower, 2); + } + + /// + /// Writes element widths into a module row. + /// + /// The element widths. + /// The module value of the first element, 1 for a bar. + /// The module row. + /// The module the first element starts at. + /// The module after the last element. + private static int Expand(ReadOnlySpan widths, byte dark, Span modules, int position) + { + for (int i = 0; i < widths.Length; i++) + { + modules.Slice(position, widths[i]).Fill(dark); + position += widths[i]; + dark = (byte)(1 - dark); + } + + return position; + } + + /// + /// Writes the inverse of a row into a separator row, inside the four modules at each end. The caller passes a zeroed separator row, so those modules stay + /// light. + /// + /// The row the separator touches. + /// The separator row. + private static void Invert(ReadOnlySpan row, Span separator) + { + for (int i = SeparatorLightModules; i < row.Length - SeparatorLightModules; i++) + { + separator[i] = (byte)(1 - row[i]); + } + } + + /// + /// Rewrites the separator modules over the first three elements of a finder pattern, as the left finder reads them. Under a dark module the separator is + /// light. Under a light run it alternates dark and light, starting dark, so it breaks into narrow elements. + /// + /// The row the separator touches. + /// The separator row. + /// The first module of the modules. + private static void AlternateOverFinder(ReadOnlySpan row, Span separator, int start) + { + byte next = 1; + for (int i = start; i < start + FinderLeadModules; i++) + { + if (row[i] == 0) + { + separator[i] = next; + next = (byte)(1 - next); + } + else + { + separator[i] = 0; + next = 1; + } + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataBar/DataBarExpandedEncoder.cs b/src/ImageSharp.Drawing/Barcodes/DataBar/DataBarExpandedEncoder.cs new file mode 100644 index 00000000..28196fc7 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataBar/DataBarExpandedEncoder.cs @@ -0,0 +1,1325 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodation for GS1 DataBar Expanded and GS1 DataBar Expanded Stacked. A symbol carries GS1 element strings as a bit stream cut into 12 bit symbol +/// characters, preceded by a check character that encodes a modulo 211 check value and the symbol length. A finder pattern follows every second symbol +/// character. +/// +/// The bit stream starts with a linkage bit and an encodation method. Common sequences of element strings that start with Application Identifier (01) compress +/// into a fixed length field. The general purpose field encodes whatever remains in numeric, alphanumeric or ISO/IEC 646 mode. A symbol character is 8 elements +/// over 17 modules: an odd set of 4 and an even set of 4, interleaved. The group of a character gives the module count and the widest element of each set. +/// +/// +internal static class DataBarExpandedEncoder +{ + /// + /// The nominal X dimension in millimetres, the target X dimension of Table 5-44 of the GS1 General Specifications. + /// + public const double NominalXDimension = 0.33; + + /// + /// The height of a row in modules. Section 5.5.2.3.1 of the GS1 General Specifications gives "34X high", and Table 5-44 gives 11.23 millimetres, 34X, at + /// the target X dimension. It is also the minimum. Table 5-56 gives GS1 DataBar Expanded 8.98 millimetres, 34X, at the X dimension of 0.264 millimetre. It + /// gives GS1 DataBar Expanded Stacked 18.74 millimetres, 71X, for two rows of 34X and the 3X separator. Its note (*): "the symbol height SHALL NOT be + /// truncated below the minimum". + /// + public const float RowHeight = 34F; + + /// + /// The fewest segments in a row of a stacked symbol. Section 5.5.2.3.2: "It can be printed in widths of 2 to 20 segments". + /// + public const int MinimumSegments = 2; + + /// + /// The most segments in a row of a stacked symbol. + /// + public const int MaximumStackedSegments = 20; + + /// + /// The most data characters a symbol carries. Section 5.5.1.1 of the GS1 General Specifications gives the maximum data capacity of the GS1 DataBar + /// Expanded variations as "74 numeric or 41 alphabetic characters". + /// + public const int MaximumDataCharacters = 74; + + /// + /// The height of each of the three separator rows between two rows of a stacked symbol, in modules. Section 5.5.2.3.2: "Its structure includes a 3X high + /// separator pattern between rows." + /// + private const float SeparatorHeight = 1F; + + /// + /// The most symbol characters a symbol carries, the check character and 21 data characters. Section 5.5.2.3.1: "from 4 to 22 symbol characters". + /// + private const int MaximumSymbolCharacters = 22; + + /// + /// The most data characters a symbol carries, the symbol characters after the check character. + /// + private const int MaximumDataSymbolCharacters = MaximumSymbolCharacters - 1; + + /// + /// The number of data bits one data character carries. + /// + private const int BitsPerSymbolCharacter = 12; + + /// + /// The number of modules of a symbol character. + /// + private const int CharacterModules = 17; + + /// + /// The number of modules of a finder pattern. + /// + private const int FinderModules = 15; + + /// + /// The number of elements of a symbol character, an odd set of 4 and an even set of 4. + /// + private const int CharacterElements = 8; + + /// + /// The number of elements of a finder pattern. + /// + private const int FinderElements = 5; + + /// + /// The number of elements of the guard pattern at each end, a narrow space and a narrow bar. Each element is one module, so the guard pattern is also + /// this many modules wide. + /// + private const int GuardElements = 2; + + /// + /// The fewest symbol characters a symbol carries. Section 5.5.2.3.1: "from 4 to 22 symbol characters". + /// + private const int MinimumSymbolCharacters = 4; + + /// + /// The first module of the first finder pattern of a row, after the guard pattern and the first symbol character. + /// + private const int FirstFinderStart = GuardElements + CharacterModules; + + /// + /// The distance in modules from one finder pattern of a row to the next: the finder pattern and two symbol characters. + /// + private const int FinderPitch = FinderModules + (2 * CharacterModules); + + /// + /// The number of light modules at each end of a separator row. + /// + private const int SeparatorLightModules = 4; + + /// + /// The width in modules of the widest row: two guard patterns, 20 symbol characters and 10 finder patterns. + /// + private const int MaximumRowWidth = (2 * GuardElements) + (MaximumStackedSegments * CharacterModules) + (MaximumStackedSegments / 2 * FinderModules); + + /// + /// The modulus of the check value. + /// + private const int CheckModulus = 211; + + /// + /// The encodation methods, which the encodation method field after the linkage bit selects. Each member names the bits of its field. + /// + private enum EncodationMethod + { + /// + /// Method 1: Application Identifier (01) in a compressed field, then the general purpose field. + /// + Gtin, + + /// + /// Method 00: the general purpose field alone. + /// + GeneralPurpose, + + /// + /// Method 0100: Application Identifiers (01) and (3103). + /// + MetricWeight, + + /// + /// Method 0101: Application Identifiers (01) and (3202) or (3203). + /// + PoundWeight, + + /// + /// Method 01100: Application Identifiers (01) and (392x), then the general purpose field. + /// + Price, + + /// + /// Method 01101: Application Identifiers (01) and (393x), then the general purpose field. + /// + PriceWithCurrency, + + /// + /// Methods 0111000 to 0111111: Application Identifiers (01), (310x) or (320x), and (11), (13), (15) or (17) or no date. + /// + WeightAndDate, + } + + /// + /// The modes of the general purpose field. The class of a character is the mode with the fewest bits that carries it. + /// + private enum GeneralFieldMode : byte + { + /// + /// The class of a character the field cannot carry. + /// + None, + + /// + /// Digits and the separator, two per seven bits. + /// + Numeric, + + /// + /// Digits, capital letters and five punctuation characters. + /// + Alphanumeric, + + /// + /// The ISO/IEC 646 characters GS1 element strings use. + /// + Iso646, + } + + /// + /// Gets the punctuation characters ISO/IEC 646 mode encodes, in the order of their values from 232. + /// + private static ReadOnlySpan Iso646Punctuation => "!\"%&'()*+,-./:;<=>?_ "; + + /// + /// Gets the first symbol character value of each group. + /// + private static ReadOnlySpan GroupSum => [0, 348, 1388, 2948, 3988]; + + /// + /// Gets the number of even set combinations of each group. A value within a group splits into the odd and the even set value by this number. + /// + private static ReadOnlySpan EvenCombinations => [4, 20, 52, 104, 204]; + + /// + /// Gets the module count of the odd set of each group. The even set takes the rest of the 17 modules. + /// + private static ReadOnlySpan OddModules => [12, 10, 8, 6, 4]; + + /// + /// Gets the widest element of the odd set of each group. The widest element of the even set is nine less this. + /// + private static ReadOnlySpan OddWidest => [7, 5, 4, 3, 1]; + + /// + /// Gets the element widths of the twelve finder patterns, five each: A1, A2, B1, B2, C1, C2, D1, D2, E1, E2, F1 and F2. A version 2 finder is its version 1 + /// finder reversed. + /// + private static ReadOnlySpan FinderWidths => + [ + 1, 8, 4, 1, 1, + 1, 1, 4, 8, 1, + 3, 6, 4, 1, 1, + 1, 1, 4, 6, 3, + 3, 4, 6, 1, 1, + 1, 1, 6, 4, 3, + 3, 2, 8, 1, 1, + 1, 1, 8, 2, 3, + 2, 6, 5, 1, 1, + 1, 1, 5, 6, 2, + 2, 2, 9, 1, 1, + 1, 1, 9, 2, 2 + ]; + + /// + /// Gets the finder pattern sequences, one per symbol length. The sequence of a symbol with 2r + 3 or 2r + 4 symbol characters has r + 2 finders and starts + /// at r (r + 3) / 2. + /// + private static ReadOnlySpan FinderSequences => + [ + 0, 1, + 0, 3, 2, + 0, 5, 2, 7, + 0, 9, 2, 7, 4, + 0, 9, 2, 7, 6, 11, + 0, 9, 2, 7, 8, 11, 10, + 0, 1, 2, 3, 4, 5, 6, 7, + 0, 1, 2, 3, 4, 5, 6, 9, 8, + 0, 1, 2, 3, 4, 5, 6, 9, 10, 11, + 0, 1, 2, 3, 4, 7, 6, 9, 8, 11, 10 + ]; + + /// + /// Gets the check weights, 8 for each side of each finder pattern: the character to its left and the character to its right, in the element order the + /// symbol carries them. The check character to the left of the first finder carries no weight. + /// + private static ReadOnlySpan CheckWeights => + [ + 0, 0, 0, 0, 0, 0, 0, 0, + 77, 96, 32, 81, 27, 9, 3, 1, + 20, 60, 180, 118, 143, 7, 21, 63, + 205, 209, 140, 117, 39, 13, 145, 189, + 193, 157, 49, 147, 19, 57, 171, 91, + 132, 44, 85, 169, 197, 136, 186, 62, + 185, 133, 188, 142, 4, 12, 36, 108, + 50, 87, 29, 80, 97, 173, 128, 113, + 150, 28, 84, 41, 123, 158, 52, 156, + 166, 196, 206, 139, 187, 203, 138, 46, + 76, 17, 51, 153, 37, 111, 122, 155, + 146, 119, 110, 107, 106, 176, 129, 43, + 16, 48, 144, 10, 30, 90, 59, 177, + 164, 125, 112, 178, 200, 137, 116, 109, + 70, 210, 208, 202, 184, 130, 179, 115, + 190, 204, 68, 93, 31, 151, 191, 134, + 148, 22, 66, 198, 172, 94, 71, 2, + 40, 154, 192, 64, 162, 54, 18, 6, + 120, 149, 25, 75, 14, 42, 126, 167, + 175, 199, 207, 69, 23, 78, 26, 79, + 103, 98, 83, 38, 114, 131, 182, 124, + 159, 53, 88, 170, 127, 183, 61, 161, + 55, 165, 73, 8, 24, 72, 5, 15, + 89, 100, 174, 58, 160, 194, 135, 45 + ]; + + /// + /// Checks that Application Identifier (01) is the first element string when the data holds it. Section 5.5.2.3.3 of the GS1 General Specifications: "when + /// the data includes AI (01), it SHALL be the first element string encoded." + /// + /// The element strings that accepted. Data ends at the next opening parenthesis, so each opening + /// parenthesis starts an Application Identifier. + /// Application Identifier (01) follows another element string. + public static void ValidateGtinFirst(ReadOnlySpan text) + { + if (text.IndexOf("(01)", StringComparison.Ordinal) > 0) + { + throw new ArgumentException("GS1 DataBar Expanded carries Application Identifier (01) as the first element string.", nameof(text)); + } + } + + /// + /// Validates the number of segments in a full row of a stacked symbol: an even number from to + /// . + /// + /// The number of segments. + /// is outside the range or odd. + public static void ValidateSegments(int segments) + { + Guard.MustBeBetweenOrEqualTo(segments, MinimumSegments, MaximumStackedSegments, nameof(segments)); + if ((segments & 1) == 1) + { + throw new ArgumentOutOfRangeException(nameof(segments), segments, "The number of segments in a row is even."); + } + } + + /// + /// Builds GS1 DataBar Expanded, a single row. + /// + /// The prepared element strings, with a separator after every variable length element string but the last. + /// The human readable interpretation. + /// Whether a 2D component of a GS1 Composite symbol is linked to the symbol: section 7.3 of ISO/IEC 24723 sets the linkage bit. + /// The options that control the bar height and the text. + /// The symbol to write. + /// The data carries a character the symbology cannot encode, or more data than 21 data characters hold. + public static void BuildLinear(ReadOnlySpan text, ReadOnlySpan caption, bool linked, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray256 characterStorage = default; + Span characters = ((Span)characterStorage)[..(MaximumSymbolCharacters * CharacterElements)]; + int symbolCharacters = EncodeCharacters(text, MaximumSymbolCharacters, linked, characters); + ReadOnlySpan finders = Finders(symbolCharacters); + + // A zero width bar, the guard space and bar, the characters with a finder after every second one, and the guard space and bar. The symbol ends on a bar + // or a space, so a zero width bar closes the runs when it ends on a space. The zero width bars are the zeros the symbol storage starts with. + int elements = 1 + GuardElements + (symbolCharacters * CharacterElements) + (finders.Length * FinderElements) + GuardElements; + double xDimension = options.ResolveXDimension(DataBarEncoder.MinimumXDimension, NominalXDimension, DataBarEncoder.MaximumXDimension); + Span runs = symbol.BeginLinear(elements + ((elements & 1) == 0 ? 1 : 0), xDimension, out Span heights, out _); + runs[1] = 1; + runs[2] = 1; + int position = 3; + for (int c = 0; c < symbolCharacters; c++) + { + characters.Slice(c * CharacterElements, CharacterElements).CopyTo(runs[position..]); + position += CharacterElements; + if ((c & 1) == 0) + { + foreach (byte width in FinderWidths.Slice(finders[c / 2] * FinderElements, FinderElements)) + { + runs[position++] = width; + } + } + } + + runs[position] = 1; + runs[position + 1] = 1; + + // Section 5.5.2.3.1 gives a minimum height and no maximum. + float barHeight = options.ResolveBarHeight(xDimension, RowHeight, RowHeight, float.PositiveInfinity); + heights.Fill(barHeight); + + if (options.Font is not null) + { + // The two guard patterns and the fixed module counts of the characters and the finders give the width. + int width = (2 * GuardElements) + (symbolCharacters * CharacterModules) + (finders.Length * FinderModules); + symbol.ReserveText(1, caption.Length); + symbol.AddText(caption, 0F, width, BarcodeTextSide.BelowBars, barHeight + BarcodeTextPlacement.Clearance); + + // Section 4.14 rule 2.a.iii of the GS1 General Specifications: "The font size SHOULD be at least 2 mm (0.08 in) in height". + symbol.SetTextSize(Gs1Data.MinimumTextSize, 0F); + } + + symbol.EndLinear(0, 0); + } + + /// + /// Builds GS1 DataBar Expanded Stacked: rows of the given number of segments, a segment being a symbol character, with a separator of three one module rows + /// between two rows. A row of an even number of segment pairs reads right to left on every second row, and the last row can be partial. + /// + /// The prepared element strings, with a separator after every variable length element string but the last. + /// The number of segments in a full row, even and from 2 to 20. + /// Whether a 2D component of a GS1 Composite symbol is linked to the symbol: section 7.3 of ISO/IEC 24723 sets the linkage bit. + /// The characters of the human readable interpretation before its body. + /// The body of the human readable interpretation. + /// The characters of the human readable interpretation after its body. + /// The side of the symbol the human readable interpretation prints on. + /// The smallest X dimension the application standard permits, in millimetres. + /// The largest X dimension the application standard permits, in millimetres. + /// The largest row height the application standard permits, in modules, or where it + /// gives none. The smallest row height is . + /// The options that control the bar height and the text. + /// The symbol to write. + /// The data carries a character the symbology cannot encode, or more data than 21 data characters hold. + public static void BuildStacked( + ReadOnlySpan text, + int segments, + bool linked, + ReadOnlySpan captionPrefix, + ReadOnlySpan captionBody, + ReadOnlySpan captionSuffix, + BarcodeTextSide side, + double minimumXDimension, + double maximumXDimension, + float maximumRowHeight, + BarcodeOptions options, + ref BarcodeSymbol symbol) + { + InlineArray256 characterStorage = default; + Span characters = ((Span)characterStorage)[..(MaximumSymbolCharacters * CharacterElements)]; + int symbolCharacters = EncodeCharacters(text, segments, linked, characters); + double xDimension = options.ResolveXDimension(minimumXDimension, NominalXDimension, maximumXDimension); + float rowHeight = options.ResolveBarHeight(xDimension, RowHeight, RowHeight, maximumRowHeight); + + // The buffer holds a row, the separator row that is its inverse, and the middle separator row, each one module wider than the widest row, since a + // partial last row can move one module right. It is rented once, outside the row loops. + int stride = MaximumRowWidth + 1; + PooledBuffer buffer = new(stride * 3); + try + { + StackedRowComposer rows = new(characters, symbolCharacters, segments, buffer.Span[..stride], buffer.Span.Slice(stride, stride)); + Span middle = buffer.Span.Slice(stride * 2, stride); + int rowCount = rows.RowCount; + + // The run count of a row is known only once its modules are composed, so a first pass composes every row to count the runs, and the symbol reserves + // its storage once. Between two rows stand the separator of the upper row, the middle separator and the separator of the lower row. + int runCount = 0; + int barCount = 0; + for (int r = 0; r < rowCount; r++) + { + rows.Compose(r); + int rowRuns = DataBarRows.RunCount(rows.Row); + int separatorRuns = DataBarRows.RunCount(rows.Separator); + int separatorUses = (r > 0 ? 1 : 0) + (r < rowCount - 1 ? 1 : 0); + runCount += rowRuns + (separatorUses * separatorRuns); + barCount += ((rowRuns + 1) >> 1) + (separatorUses * ((separatorRuns + 1) >> 1)); + } + + int width = rows.Width; + ComposeMiddle(middle[..width]); + int middleRuns = DataBarRows.RunCount(middle[..width]); + runCount += (rowCount - 1) * middleRuns; + barCount += (rowCount - 1) * ((middleRuns + 1) >> 1); + + symbol.BeginStacked((rowCount * 4) - 3, runCount, barCount, xDimension); + for (int r = 0; r < rowCount; r++) + { + rows.Compose(r); + if (r > 0) + { + DataBarRows.AddRow(rows.Separator, SeparatorHeight, ref symbol); + } + + DataBarRows.AddRow(rows.Row, rowHeight, ref symbol); + if (r < rowCount - 1) + { + DataBarRows.AddRow(rows.Separator, SeparatorHeight, ref symbol); + DataBarRows.AddRow(middle[..width], SeparatorHeight, ref symbol); + } + } + + if (options.Font is not null) + { + float height = (rowCount * rowHeight) + ((rowCount - 1) * 3F * SeparatorHeight); + float textEdge = side == BarcodeTextSide.AboveBars ? -BarcodeTextPlacement.Clearance : height + BarcodeTextPlacement.Clearance; + symbol.ReserveText(1, captionPrefix.Length + captionBody.Length + captionSuffix.Length); + symbol.AddText(captionPrefix, captionBody, captionSuffix, 0F, width, side, textEdge); + + // Section 4.14 rule 2.a.iii of the GS1 General Specifications: "The font size SHOULD be at least 2 mm (0.08 in) in height". + symbol.SetTextSize(Gs1Data.MinimumTextSize, 0F); + } + + symbol.EndStacked(0F, 0F, 0F); + } + finally + { + buffer.Dispose(); + } + } + + /// + /// Encodes the data into the element widths of the symbol characters, 8 each, in the order the symbol carries them: the check character, then the data + /// characters. A data character that stands to the right of a finder pattern reads in reverse. + /// + /// The prepared element strings. + /// The number of segments in a row, which the symbol length avoids leaving one alone on the last row. + /// Whether a 2D component of a GS1 Composite symbol is linked to the symbol. + /// The buffer of 8 widths per symbol character. + /// The number of symbol characters. + private static int EncodeCharacters(ReadOnlySpan text, int segments, bool linked, Span characters) + { + InlineArray24 valueStorage = default; + Span values = ((Span)valueStorage)[..MaximumDataSymbolCharacters]; + int dataCharacters = EncodeBits(text, segments, linked, values) / BitsPerSymbolCharacter; + int symbolCharacters = dataCharacters + 1; + ReadOnlySpan finders = Finders(symbolCharacters); + + // The check value is the weighted sum of the data character widths modulo 211, and the check character adds the symbol length to it. + InlineArray8 setStorage = default; + Span sets = setStorage; + int check = 0; + for (int d = 0; d < dataCharacters; d++) + { + // A data character to the right of a finder, the first of each pair, reads in reverse. + Span widths = characters.Slice((d + 1) * CharacterElements, CharacterElements); + CharacterWidths(values[d], sets, widths, (d & 1) == 0); + + // Data character d is the right character of finder (d + 1) / 2, or the left one when d is odd. Each finder has the weights of its left character + // and then the weights of its right character. + int finder = finders[(d + 1) / 2]; + int side = (d & 1) == 0 ? CharacterElements : 0; + ReadOnlySpan weights = CheckWeights.Slice((finder * 2 * CharacterElements) + side, CharacterElements); + for (int k = 0; k < CharacterElements; k++) + { + check += widths[k] * weights[k]; + } + } + + // The value of the check character is 211 times the number of symbol characters over the minimum, plus the check value. + CharacterWidths((CheckModulus * (symbolCharacters - MinimumSymbolCharacters)) + (check % CheckModulus), sets, characters[..CharacterElements], false); + return symbolCharacters; + } + + /// + /// Writes the 8 element widths of a symbol character. + /// + /// The character value. + /// Scratch for the odd and the even set. + /// The 8 widths of the character. + /// Whether the character reads in reverse. + private static void CharacterWidths(int value, Span sets, Span widths, bool reverse) + { + int group = GroupSum.Length - 1; + while (value < GroupSum[group]) + { + group--; + } + + int withinGroup = value - GroupSum[group]; + Span odd = sets[..4]; + Span even = sets[4..]; + + // Only the odd set excludes the combinations without a one module element. + DataBarWidths.Calculate(odd, withinGroup / EvenCombinations[group], OddModules[group], OddWidest[group], true); + DataBarWidths.Calculate(even, withinGroup % EvenCombinations[group], CharacterModules - OddModules[group], 9 - OddWidest[group], false); + for (int k = 0; k < 4; k++) + { + if (reverse) + { + widths[7 - (k * 2)] = odd[k]; + widths[6 - (k * 2)] = even[k]; + } + else + { + widths[k * 2] = odd[k]; + widths[(k * 2) + 1] = even[k]; + } + } + } + + /// + /// Returns the finder pattern sequence of a symbol length. + /// + /// The number of symbol characters. + /// The finder patterns, an index into each. + private static ReadOnlySpan Finders(int symbolCharacters) + { + int r = (symbolCharacters - 3) / 2; + return FinderSequences.Slice(r * (r + 3) / 2, r + 2); + } + + /// + /// Writes the data bits: the linkage bit, the encodation method, the variable length field, the compressed data field and the general purpose field, padded + /// to a whole number of data characters. + /// + /// The prepared element strings. + /// The number of segments in a row. + /// Whether a 2D component of a GS1 Composite symbol is linked to the symbol, which sets the linkage bit. + /// The buffer that receives the 12 bit value of each data character. + /// The number of bits, a multiple of 12. + private static int EncodeBits(ReadOnlySpan text, int segments, bool linked, Span values) + { + int length = text.Length; + EncodationMethod method = length >= 16 && text[0] == '0' && text[1] == '1' ? EncodationMethod.Gtin : EncodationMethod.GeneralPurpose; + + // The last three bits of a weight and date method: the date Application Identifier 11, 13, 15 or 17 as 0, 2, 4 or 6, plus 1 for a weight in pounds. A + // symbol with no date takes the bits of AI (11). + int weightAndDate = 0; + + // A variable measure GTIN, whose indicator digit is 9, followed by a weight or a price takes one of the shorter fixed length methods. + if (method == EncodationMethod.Gtin && length >= 20 && text[2] == '9' && text[16] == '3') + { + if (length >= 26 && text[17] is '1' or '2' && text[18] == '0') + { + bool metric = text[17] == '1'; + int weight = Number(text, 20, 6); + if (weight is >= 0 and <= 99999) + { + int poundBit = metric ? 0 : 1; + if (length == 26) + { + // Section 5.5.2.3.3 of the GS1 General Specifications: up to 32.767 kilograms in AI (3103), 99.99 pounds in AI (3202) or 22.767 pounds + // in AI (3203). Other weights take the weight and date method with no date. + bool limited = metric + ? text[19] == '3' && weight <= 32767 + : (text[19] == '2' && weight <= 9999) || (text[19] == '3' && weight <= 22767); + method = limited ? (metric ? EncodationMethod.MetricWeight : EncodationMethod.PoundWeight) : EncodationMethod.WeightAndDate; + weightAndDate = poundBit; + } + else if (length == 34 && text[26] == '1' && text[27] is '1' or '3' or '5' or '7' && Date(text, 28) >= 0) + { + // A production, packaging, best before or expiration date follows the weight. + method = EncodationMethod.WeightAndDate; + weightAndDate = text[27] - '1' + poundBit; + } + } + } + else if (text[17] == '9' && text[19] is >= '0' and <= '3') + { + if (text[18] == '2') + { + method = EncodationMethod.Price; + } + else if (length >= 23 && text[18] == '3' && Number(text, 20, 3) >= 0) + { + method = EncodationMethod.PriceWithCurrency; + } + } + } + + // Every method but the general purpose method encodes the GTIN without its check digit. The callers validate the element strings first, and the GS1 + // Barcode Syntax Dictionary gives Application Identifier (01) 14 digits with a correct check digit, so the dropped digit is the one the others give. + BitWriter bits = new(values); + bits.Append(linked ? 1 : 0, 1); + int variableLength = -1; + int read; + switch (method) + { + case EncodationMethod.Gtin: + bits.Append(1, 1); + variableLength = bits.Position; + bits.Append(0, 2); + bits.Append(text[2] - '0', 4); + AppendGtin(ref bits, text); + read = 16; + break; + + case EncodationMethod.GeneralPurpose: + bits.Append(0, 2); + variableLength = bits.Position; + bits.Append(0, 2); + read = 0; + break; + + case EncodationMethod.MetricWeight: + case EncodationMethod.PoundWeight: + bool poundWeight = method == EncodationMethod.PoundWeight; + bits.Append(poundWeight ? 0b0101 : 0b0100, 4); + AppendGtin(ref bits, text); + bits.Append(Number(text, 20, 6) + (poundWeight && text[19] == '3' ? 10000 : 0), 15); + read = 26; + break; + + case EncodationMethod.Price: + case EncodationMethod.PriceWithCurrency: + bool currency = method == EncodationMethod.PriceWithCurrency; + bits.Append(currency ? 0b01101 : 0b01100, 5); + variableLength = bits.Position; + bits.Append(0, 2); + AppendGtin(ref bits, text); + bits.Append(text[19] - '0', 2); + if (currency) + { + bits.Append(Number(text, 20, 3), 10); + } + + read = currency ? 23 : 20; + break; + + default: + // The weight and date methods. The weight field carries the last digit of the identifier and the five digits after the weight's leading zero. + // The date field carries 38400 when there is no date. + bits.Append(0b0111000 + weightAndDate, 7); + AppendGtin(ref bits, text); + bits.Append(((text[19] - '0') * 100000) + Number(text, 21, 5), 20); + bits.Append(length == 34 ? Date(text, 28) : 38400, 16); + read = length; + break; + } + + GeneralFieldMode mode = EncodeGeneralField(text[read..], ref bits, out char lastDigit); + + int symbolCharacters = SymbolCharacters(bits.Position, segments); + int remainder = (BitsPerSymbolCharacter * (symbolCharacters - 1)) - bits.Position; + if (lastDigit != '\0') + { + // A single digit left at the end takes four bits as its value plus one when four to six bits remain to the end of the last character, and otherwise + // seven bits as a pair with a separator. + if (remainder is >= 4 and <= 6) + { + bits.Append(lastDigit - '0' + 1, 4); + } + else + { + bits.Append(((lastDigit - '0') * 11) + 10 + 8, 7); + } + + symbolCharacters = SymbolCharacters(bits.Position, segments); + remainder = (BitsPerSymbolCharacter * (symbolCharacters - 1)) - bits.Position; + } + + if (symbolCharacters > MaximumSymbolCharacters) + { + throw new ArgumentException( + $"GS1 DataBar Expanded carries at most {MaximumDataSymbolCharacters} data characters; the data needs more.", + nameof(text)); + } + + // The pad starts with the numeric to alphanumeric latch, or as much of it as fits, when the field ends in numeric mode, and then repeats 00100 to the + // end of the last data character. + int end = bits.Position + remainder; + if (mode == GeneralFieldMode.Numeric) + { + bits.Append(0, Math.Min(4, remainder)); + } + + for (int k = 0; bits.Position < end; k = (k + 1) % 5) + { + bits.Append(k == 2 ? 1 : 0, 1); + } + + // The variable length field gives whether the number of symbol characters is odd and whether it exceeds 14. + if (variableLength >= 0) + { + bits.Set(variableLength, symbolCharacters & 1); + bits.Set(variableLength + 1, symbolCharacters > 14 ? 1 : 0); + } + + return bits.Position; + } + + /// + /// Encodes the general purpose text. Numeric mode carries two digits or a digit and a separator per seven bits, alphanumeric mode a character per five or + /// six bits, and ISO/IEC 646 mode a character per five, seven or eight bits. Each mode latches to another when the characters ahead favour it. + /// + /// The characters of the general purpose text, the end of the prepared text. + /// The bit writer. + /// A single digit left at the end in numeric mode, or zero. + /// The mode at the end of the text. + private static GeneralFieldMode EncodeGeneralField(ReadOnlySpan text, ref BitWriter bits, out char lastDigit) + { + // The latch decisions look up to ten characters ahead, so every character is classified once. The text is a tail of the prepared text, which + // Gs1Data.PreparedCapacity bounds to 122 characters for MaximumDataCharacters, so the classes fit inline. + InlineArray128 classStorage = default; + Span classes = ((Span)classStorage)[..text.Length]; + for (int k = 0; k < text.Length; k++) + { + classes[k] = ClassOf(text[k]); + if (classes[k] == GeneralFieldMode.None) + { + throw new ArgumentException($"GS1 DataBar Expanded cannot carry the character U+{(int)text[k]:X4}.", nameof(text)); + } + } + + lastDigit = '\0'; + GeneralFieldMode mode = GeneralFieldMode.Numeric; + int i = 0; + while (i < text.Length) + { + char c = text[i]; + GeneralFieldMode type = classes[i]; + switch (mode) + { + case GeneralFieldMode.Numeric: + if (i < text.Length - 1) + { + if (type != GeneralFieldMode.Numeric || classes[i + 1] != GeneralFieldMode.Numeric) + { + bits.Append(0b0000, 4); + mode = GeneralFieldMode.Alphanumeric; + } + else + { + bits.Append((11 * NumericValue(c)) + NumericValue(text[i + 1]) + 8, 7); + i += 2; + } + } + else if (type != GeneralFieldMode.Numeric) + { + bits.Append(0b0000, 4); + mode = GeneralFieldMode.Alphanumeric; + } + else + { + lastDigit = c; + i++; + } + + break; + + case GeneralFieldMode.Alphanumeric: + if (c == Gs1Data.Separator) + { + bits.Append(0b01111, 5); + mode = GeneralFieldMode.Numeric; + i++; + } + else if (type == GeneralFieldMode.Iso646) + { + bits.Append(0b00100, 5); + mode = GeneralFieldMode.Iso646; + } + else if (Next(classes, i, 6, GeneralFieldMode.Numeric) || EndsNumeric(classes, i)) + { + // Six numeric characters ahead, or four or five that end the text. + bits.Append(0b000, 3); + mode = GeneralFieldMode.Numeric; + } + else if (char.IsAsciiDigit(c)) + { + bits.Append(c - 43, 5); + i++; + } + else if (char.IsAsciiLetterUpper(c)) + { + bits.Append(c - 33, 6); + i++; + } + else + { + bits.Append("*,-./".IndexOf(c) + 58, 6); + i++; + } + + break; + + default: + if (c == Gs1Data.Separator) + { + bits.Append(0b01111, 5); + mode = GeneralFieldMode.Numeric; + i++; + break; + } + + // A latch out of ISO/IEC 646 mode needs no ISO/IEC 646 only character in the next ten. + bool noIso646Ahead = NoneAhead(classes, i, 10, GeneralFieldMode.Iso646); + if (noIso646Ahead && Next(classes, i, 4, GeneralFieldMode.Numeric)) + { + bits.Append(0b000, 3); + mode = GeneralFieldMode.Numeric; + } + else if (noIso646Ahead && Next(classes, i, 5, GeneralFieldMode.Alphanumeric, GeneralFieldMode.Numeric)) + { + bits.Append(0b00100, 5); + mode = GeneralFieldMode.Alphanumeric; + } + else if (char.IsAsciiDigit(c)) + { + bits.Append(c - 43, 5); + i++; + } + else if (char.IsAsciiLetterUpper(c)) + { + bits.Append(c - 1, 7); + i++; + } + else if (char.IsAsciiLetterLower(c)) + { + bits.Append(c - 7, 7); + i++; + } + else + { + bits.Append(Iso646Punctuation.IndexOf(c) + 232, 8); + i++; + } + + break; + } + } + + return mode; + } + + /// + /// Returns the number of symbol characters that carry a number of data bits: the data characters rounded up, and the check character. A symbol carries at + /// least four. When the count leaves a single symbol character on the last row, the symbol carries one more. + /// + /// The number of data bits. + /// The number of segments in a row. + /// The number of symbol characters. + private static int SymbolCharacters(int bitCount, int segments) + { + int symbolCharacters = ((bitCount + 11) / 12) + 1; + if (symbolCharacters % segments == 1) + { + symbolCharacters++; + } + + return Math.Max(symbolCharacters, 4); + } + + /// + /// Appends the 12 digits of the GTIN after its indicator digit as four groups of three digits in ten bits each. + /// + /// The bit writer. + /// The prepared element strings, which start with Application Identifier (01). + private static void AppendGtin(ref BitWriter bits, ReadOnlySpan text) + { + for (int i = 3; i < 15; i += 3) + { + bits.Append(Number(text, i, 3), 10); + } + } + + /// + /// Rewrites the 15 separator modules over a finder pattern. Under a dark module the separator is light. Under a light module it is dark after a dark row + /// module, and otherwise the opposite of the separator module to its left. + /// + /// The row the separator touches. + /// The separator row. + /// The first module of the finder pattern. + private static void AlternateOverFinder(ReadOnlySpan row, Span separator, int start) + { + for (int i = start; i < start + FinderModules; i++) + { + separator[i] = row[i] == 1 + ? (byte)0 + : row[i - 1] == 1 ? (byte)1 : (byte)(1 - separator[i - 1]); + } + } + + /// + /// Composes the middle separator row: narrow dark and light modules alternating, inside four light modules at each end. + /// + /// The buffer that receives the modules, one for each module of the symbol width. + private static void ComposeMiddle(Span middle) + { + for (int i = 0; i < middle.Length; i++) + { + middle[i] = (byte)(i & 1); + } + + middle[..SeparatorLightModules].Clear(); + middle[^SeparatorLightModules..].Clear(); + } + + /// + /// Returns the character class of a general purpose field character: numeric for a digit or the separator, alphanumeric for a capital letter or one of * + /// , - . /, ISO/IEC 646 for a small letter, a space or the other punctuation, and none for a character the field cannot carry. + /// + /// The character. + /// The class. + private static GeneralFieldMode ClassOf(char c) + { + if (c == Gs1Data.Separator || char.IsAsciiDigit(c)) + { + return GeneralFieldMode.Numeric; + } + + if (char.IsAsciiLetterUpper(c) || c is '*' or ',' or '-' or '.' or '/') + { + return GeneralFieldMode.Alphanumeric; + } + + return char.IsAsciiLetterLower(c) || Iso646Punctuation.Contains(c) ? GeneralFieldMode.Iso646 : GeneralFieldMode.None; + } + + /// + /// Returns the numeric mode value of a digit, or 10 for the separator. + /// + /// The digit or the separator. + /// The value. + private static int NumericValue(char c) + => c == Gs1Data.Separator ? 10 : c - '0'; + + /// + /// Returns whether the next characters, from the given one on, are all of one class. + /// + /// The class of every character of the field. + /// The first character. + /// The number of characters. + /// The class. + /// Whether that many characters remain and all are of the class. + private static bool Next(ReadOnlySpan classes, int start, int count, GeneralFieldMode type) + => Next(classes, start, count, type, type); + + /// + /// Returns whether the next characters, from the given one on, are all of one of two classes. + /// + /// The class of every character of the field. + /// The first character. + /// The number of characters. + /// The class. + /// The second class. + /// Whether that many characters remain and each is of one of the classes. + private static bool Next(ReadOnlySpan classes, int start, int count, GeneralFieldMode type, GeneralFieldMode alternative) + { + if (start + count > classes.Length) + { + return false; + } + + foreach (GeneralFieldMode c in classes.Slice(start, count)) + { + if (c != type && c != alternative) + { + return false; + } + } + + return true; + } + + /// + /// Returns whether four or five numeric characters end the field from the given one on. + /// + /// The class of every character of the field. + /// The first character. + /// Whether the rest of the field is four or five numeric characters. + private static bool EndsNumeric(ReadOnlySpan classes, int start) + => classes.Length - start is 4 or 5 && Next(classes, start, classes.Length - start, GeneralFieldMode.Numeric); + + /// + /// Returns whether none of the next characters, or the characters to the end, are of a class. + /// + /// The class of every character of the field. + /// The first character. + /// The number of characters. + /// The class. + /// Whether none of them is of the class. + private static bool NoneAhead(ReadOnlySpan classes, int start, int count, GeneralFieldMode type) + { + foreach (GeneralFieldMode c in classes[start..Math.Min(start + count, classes.Length)]) + { + if (c == type) + { + return false; + } + } + + return true; + } + + /// + /// Reads a number of digits as a value. + /// + /// The text. + /// The first digit. + /// The number of digits. + /// The value, or -1 when a character is not a digit. + private static int Number(ReadOnlySpan text, int start, int count) + { + int value = 0; + for (int i = start; i < start + count; i++) + { + if (!char.IsAsciiDigit(text[i])) + { + return -1; + } + + value = (value * 10) + (text[i] - '0'); + } + + return value; + } + + /// + /// Reads a date of six digits, YYMMDD, as the value the weight and date methods carry: the year times 384, the month less one times 32, and the day. A day + /// of zero stands for the last day of the month. + /// + /// The text. + /// The first digit. + /// The value, or -1 when the date is not valid. + private static int Date(ReadOnlySpan text, int start) + { + int year = Number(text, start, 2); + int month = Number(text, start + 2, 2); + int day = Number(text, start + 4, 2); + if (year < 0 || month is < 1 or > 12 || day is < 0 or > 31) + { + return -1; + } + + return (year * 384) + ((month - 1) * 32) + day; + } + + /// + /// Writes the data bits, most significant first, straight into the 12 bit values of the data characters. Bit 0 of the stream is bit 11 of the first value. + /// + private ref struct BitWriter + { + /// + /// The values of the data characters. + /// + private readonly Span values; + + /// + /// Initializes a new instance of the struct. + /// + /// The values of the data characters. + public BitWriter(Span values) => this.values = values; + + /// + /// Gets the number of bits appended, which counts the bits past the buffer too. + /// + public int Position { get; private set; } + + /// + /// Appends a value as a number of bits, most significant first. + /// + /// The value. + /// The number of bits. + public void Append(int value, int count) + { + // Bits past the buffer cannot fit the largest symbol. The position still counts them, so the symbol character count after the general purpose field + // rejects the data. + int end = this.Position + count; + if (end <= this.values.Length * BitsPerSymbolCharacter) + { + for (int bit = count - 1; bit >= 0; bit--) + { + this.Set(this.Position++, (value >> bit) & 1); + } + } + + this.Position = end; + } + + /// + /// Sets one bit of the stream. + /// + /// The index of the bit in the stream. + /// The bit, 0 or 1. + public readonly void Set(int index, int bit) + { + int shift = BitsPerSymbolCharacter - 1 - (index % BitsPerSymbolCharacter); + ref int value = ref this.values[index / BitsPerSymbolCharacter]; + value = (value & ~(1 << shift)) | (bit << shift); + } + } + + /// + /// Composes the rows of a stacked symbol one at a time, each with the separator row that is its inverse, into a row buffer and a separator buffer the + /// caller owns. Both buffers hold the widest row and one module more, since a partial last row can move one module right. + /// + private ref struct StackedRowComposer + { + /// + /// The element widths of the symbol characters, 8 each. + /// + private readonly ReadOnlySpan characters; + + /// + /// The finder pattern values, an index into each. + /// + private readonly ReadOnlySpan finders; + + /// + /// The modules of the row, 1 for dark. + /// + private readonly Span row; + + /// + /// The modules of the separator row. + /// + private readonly Span separator; + + /// + /// The number of segments in a full row. + /// + private readonly int segments; + + /// + /// The number of symbol characters. + /// + private readonly int symbolCharacters; + + /// + /// The number of modules of the row written so far. + /// + private int length; + + /// + /// The module value of the next element, 1 for dark. + /// + private byte colour; + + /// + /// Initializes a new instance of the struct. + /// + /// The element widths of the symbol characters, 8 each. + /// The number of symbol characters. + /// The number of segments in a full row. + /// The buffer that receives the modules of a row. + /// The buffer that receives the modules of its separator row. + public StackedRowComposer(ReadOnlySpan characters, int symbolCharacters, int segments, Span row, Span separator) + { + this.characters = characters; + this.finders = Finders(symbolCharacters); + this.row = row; + this.separator = separator; + this.segments = segments; + this.symbolCharacters = symbolCharacters; + this.RowCount = (symbolCharacters + segments - 1) / segments; + } + + /// + /// Gets the number of rows. + /// + public int RowCount { get; } + + /// + /// Gets the width of the symbol in modules, which the first row sets because it is a full row. + /// + public int Width { get; private set; } + + /// + /// Gets the modules of the last composed row, 1 for dark. + /// + public readonly ReadOnlySpan Row => this.row[..this.Width]; + + /// + /// Gets the modules of the separator row of the last composed row. + /// + public readonly ReadOnlySpan Separator => this.separator[..this.Width]; + + /// + /// Composes the modules of one row and of its separator row. Row 0 must be composed first, since it sets the width. + /// + /// The row index from the top. + public void Compose(int r) + { + // A row of segments that is not a whole number of segment pairs on both sides starts every second row with its guard bar, so the colours of the row + // alternate from row to row. + this.length = 0; + this.colour = (byte)((this.segments & 3) != 0 && (r & 1) == 1 ? 1 : 0); + this.AppendElement(1); + this.AppendElement(1); + int first = r * this.segments; + int last = Math.Min(first + this.segments, this.symbolCharacters); + int finderCount = 0; + for (int c = first; c < last; c++) + { + foreach (int width in this.characters.Slice(c * CharacterElements, CharacterElements)) + { + this.AppendElement(width); + } + + if ((c & 1) == 0) + { + foreach (byte width in FinderWidths.Slice(this.finders[c / 2] * FinderElements, FinderElements)) + { + this.AppendElement(width); + } + + finderCount++; + } + } + + this.AppendElement(1); + this.AppendElement(1); + + // The separator is the inverse of the row, inside four light modules at each end. Over the 15 modules of a finder pattern the light modules break + // into narrow elements instead, so the separator does not read as part of the finder. The first finder starts after the guard pattern and the + // first character. Each next finder starts one finder and two characters later. + int rowLength = this.length; + Span rowModules = this.row[..rowLength]; + Span separatorModules = this.separator[..rowLength]; + for (int i = 0; i < rowLength; i++) + { + separatorModules[i] = (byte)(1 - rowModules[i]); + } + + for (int start = FirstFinderStart; start + FinderModules <= rowLength; start += FinderPitch) + { + AlternateOverFinder(rowModules, separatorModules, start); + } + + separatorModules[..SeparatorLightModules].Clear(); + separatorModules[^SeparatorLightModules..].Clear(); + + // With a whole number of segment pairs on both sides every second row reads right to left. A partial last row with an odd number of finders keeps + // its direction and moves one module right. + if ((this.segments & 3) == 0 && (r & 1) == 1) + { + if (r == this.RowCount - 1 && last - first < this.segments && (finderCount & 1) == 1) + { + rowModules.CopyTo(this.row[1..]); + separatorModules.CopyTo(this.separator[1..]); + this.row[0] = 0; + this.separator[0] = 0; + rowLength++; + } + else + { + rowModules.Reverse(); + separatorModules.Reverse(); + } + } + + // The first row is a full row, and a shorter last row fills to its width with light modules. + if (r == 0) + { + this.Width = rowLength; + } + else + { + this.row[rowLength..this.Width].Clear(); + this.separator[rowLength..this.Width].Clear(); + } + } + + /// + /// Appends an element to the row and switches the colour for the next one. + /// + /// The width of the element in modules. + private void AppendElement(int width) + { + this.row.Slice(this.length, width).Fill(this.colour); + this.length += width; + this.colour = (byte)(1 - this.colour); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataBar/DataBarLimitedEncoder.cs b/src/ImageSharp.Drawing/Barcodes/DataBar/DataBarLimitedEncoder.cs new file mode 100644 index 00000000..806976ca --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataBar/DataBarLimitedEncoder.cs @@ -0,0 +1,241 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodation for GS1 DataBar Limited. A symbol carries the item identification of GS1 Application Identifier (01), with an indicator digit of 0 or 1, as two +/// symbol characters with a check character between them that encodes a modulo 89 check value. A narrow space and bar lead the symbol, and a narrow space, a +/// narrow bar and a space of five modules close it. +/// +/// The 13 digits before the check digit form a number below 2 x 10^12. It splits into a left and a right character value modulo 2013571. A symbol character is +/// 14 elements, an odd set of 7 and an even set of 7 interleaved, over 26 modules. Its group gives the module count and the widest element of each set, and its +/// value within the group gives the width of every element. +/// +/// +internal static class DataBarLimitedEncoder +{ + /// + /// The nominal X dimension in millimetres, the target X dimension of Table 5-50 of the GS1 General Specifications. + /// + private const double NominalXDimension = 0.33; + + /// + /// The height in modules. Section 5.5.2.2 of the GS1 General Specifications: "The minimum height SHALL be 10X", which Table 5-50 gives as the target height + /// too. + /// + private const float Height = 10F; + + /// + /// The width in modules. Section 5.5.2.2: "The total symbol contains 47 elements comprising 79 modules." + /// + private const int Width = 79; + + /// + /// The number of elements: the leading guard pair, two symbol characters and the check character of 14 elements each, and the closing guard of three. + /// + private const int ElementCount = 47; + + /// + /// The modulus that splits the data value into the left and right character values. + /// + private const int CharacterModulus = 2013571; + + /// + /// The value a linked symbol adds to its data value. + /// + private const long LinkedOffset = 2015133531096; + + /// + /// The modulus of the check value. + /// + private const int CheckModulus = 89; + + /// + /// The number of digits that the symbol encodes: the GTIN without its check digit. + /// + private const int DataDigits = 13; + + /// + /// The number of elements of a symbol character or the check character. + /// + private const int CharacterElements = 14; + + /// + /// The number of modules of a symbol character. + /// + private const int CharacterModules = 26; + + /// + /// The number of elements of the leading guard pattern, a narrow space and a narrow bar. + /// + private const int GuardElements = 2; + + /// + /// The number of elements of each of the two sets of the check character before its narrow space and bar. + /// + private const int CheckSetElements = 6; + + /// + /// The number of modules of each of the two sets of the check character. + /// + private const int CheckSetModules = 8; + + /// + /// The widest element of a set of the check character, in modules. + /// + private const int CheckSetWidest = 3; + + /// + /// The number of patterns of one set of the check character, elements over modules with no + /// element wider than . + /// + private const int CheckSetPatterns = 21; + + /// + /// The width in modules of the space that closes the symbol. Section 5.5.2.2: "ending with a 5X space". + /// + private const int TrailingSpaceModules = 5; + + /// + /// Gets the first character value of each group. + /// + private static ReadOnlySpan GroupSum => [0, 183064, 820064, 1000776, 1491021, 1979845, 1996939]; + + /// + /// Gets the number of even set combinations of each group. A value within a group splits into the odd and the even set value by this number. + /// + private static ReadOnlySpan EvenCombinations => [28, 728, 6454, 203, 2408, 1, 16632]; + + /// + /// Gets the module count of the odd set of each group. The even set takes the rest of the 26 modules. + /// + private static ReadOnlySpan OddModules => [17, 13, 9, 15, 11, 19, 7]; + + /// + /// Gets the widest element of the odd set of each group. The widest element of the even set is nine less this. + /// + private static ReadOnlySpan OddWidest => [6, 5, 3, 5, 4, 8, 1]; + + /// + /// Gets the check weights, 14 for the left character and 14 for the right. + /// + private static ReadOnlySpan CheckWeights => + [ + 1, 3, 9, 27, 81, 65, 17, 51, 64, 14, 42, 37, 22, 66, + 20, 60, 2, 6, 18, 54, 73, 41, 34, 13, 39, 28, 84, 74 + ]; + + /// + /// Gets the check character pattern of each check value, an index into the 21 x 21 patterns of two sets of six elements over eight modules with no element + /// wider than three. The quotient by 21 selects the widths of the first set and the remainder those of the second. + /// + private static ReadOnlySpan CheckSequence => + [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 45, 52, 57, + 63, 64, 65, 66, + 73, 74, 75, 76, 77, 78, 79, + 82, + 126, 127, 128, 129, 130, + 132, + 141, 142, 143, 144, 145, 146, + 210, 211, 212, 213, 214, 215, 216, 217, + 220, + 316, 317, 318, 319, 320, + 322, 323, + 326, 337 + ]; + + /// + /// Builds the symbol. + /// + /// The 14 digit GTIN, whose first digit is 0 or 1. + /// Whether a 2D component of a GS1 Composite symbol is linked to the symbol. + /// The options that control the bar height and the text. + /// The symbol to write. + public static void Build(ReadOnlySpan gtin, bool linked, BarcodeOptions options, ref BarcodeSymbol symbol) + { + // The check digit is not encoded: the 13 digits before it are the data value, and a linked symbol adds LinkedOffset to it. + long value = linked ? LinkedOffset : 0; + long digits = 0; + for (int i = 0; i < DataDigits; i++) + { + digits = (digits * 10) + (gtin[i] - '0'); + } + + value += digits; + + double xDimension = options.ResolveXDimension(DataBarEncoder.MinimumXDimension, NominalXDimension, DataBarEncoder.MaximumXDimension); + + // The element widths start with the guard space, so a zero width bar leads the runs, and the symbol ends on the five module space, so a zero width bar + // closes them. Both zero width bars are the zeros the symbol storage starts with. The left character follows the guard pattern, then the check + // character and the right character follow. + const int checkStart = GuardElements + CharacterElements; + const int rightStart = checkStart + CharacterElements; + Span runs = symbol.BeginLinear(ElementCount + 2, xDimension, out Span heights, out _); + Span widths = runs.Slice(1, ElementCount); + widths[..GuardElements].Fill(1); + + // The buffer holds the odd and the even set of the character being calculated. + const int setElements = CharacterElements / 2; + InlineArray14 setStorage = default; + Span sets = setStorage; + Span odd = sets[..setElements]; + Span even = sets[setElements..]; + int check = 0; + for (int c = 0; c < 2; c++) + { + int character = (int)(c == 0 ? value / CharacterModulus : value % CharacterModulus); + int group = GroupSum.Length - 1; + while (character < GroupSum[group]) + { + group--; + } + + int withinGroup = character - GroupSum[group]; + + // Only the even set excludes the combinations without a one module element. + DataBarWidths.Calculate(odd, withinGroup / EvenCombinations[group], OddModules[group], OddWidest[group], false); + DataBarWidths.Calculate(even, withinGroup % EvenCombinations[group], CharacterModules - OddModules[group], 9 - OddWidest[group], true); + + Span elements = widths.Slice(c == 0 ? GuardElements : rightStart, CharacterElements); + ReadOnlySpan weights = CheckWeights.Slice(c * CharacterElements, CharacterElements); + for (int k = 0; k < setElements; k++) + { + elements[k * 2] = odd[k]; + elements[(k * 2) + 1] = even[k]; + check += (weights[k * 2] * odd[k]) + (weights[(k * 2) + 1] * even[k]); + } + } + + // The check character is two sets of six elements over eight modules, interleaved, and a narrow space and bar. + int sequence = CheckSequence[check % CheckModulus]; + Span first = sets[..CheckSetElements]; + Span second = sets.Slice(CheckSetElements, CheckSetElements); + DataBarWidths.Calculate(first, sequence / CheckSetPatterns, CheckSetModules, CheckSetWidest, false); + DataBarWidths.Calculate(second, sequence % CheckSetPatterns, CheckSetModules, CheckSetWidest, false); + Span checkElements = widths.Slice(checkStart, CharacterElements); + for (int k = 0; k < CheckSetElements; k++) + { + checkElements[k * 2] = first[k]; + checkElements[(k * 2) + 1] = second[k]; + } + + checkElements[^2..].Fill(1); + + // A narrow space, a narrow bar and the five module space close the symbol. + widths[^3] = 1; + widths[^2] = 1; + widths[^1] = TrailingSpaceModules; + + // Section 5.5.2.2 gives a minimum height and no maximum. + float barHeight = options.ResolveBarHeight(xDimension, Height, Height, float.PositiveInfinity); + heights.Fill(barHeight); + + // Section 4.14 of the GS1 General Specifications prints an element string with its Application Identifier in parentheses. + DataBarEncoder.AddCaption(gtin, Width, barHeight, options, ref symbol); + symbol.EndLinear(0, 0); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataBar/DataBarRows.cs b/src/ImageSharp.Drawing/Barcodes/DataBar/DataBarRows.cs new file mode 100644 index 00000000..0f543825 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataBar/DataBarRows.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Builds the rows of the stacked GS1 DataBar symbols from their modules. A row or a separator row can start and end with a light module. A bar sequence cannot +/// start or end with a light module, so a zero width bar stands at a light end. +/// +internal static class DataBarRows +{ + /// + /// Returns the number of runs of a row: one for each change of module color and one more, and a zero width bar at an end that is light, so even run indexes + /// stay bars. + /// + /// The modules of the row, 1 for dark. + /// The run count. + public static int RunCount(ReadOnlySpan modules) + { + int count = 1 + (modules[0] == 0 ? 1 : 0) + (modules[^1] == 0 ? 1 : 0); + for (int i = 1; i < modules.Length; i++) + { + if (modules[i] != modules[i - 1]) + { + count++; + } + } + + return count; + } + + /// + /// Adds one row to a stacked symbol from its modules. + /// + /// The modules of the row, 1 for dark. + /// The height of the row in modules. + /// The symbol to write. + public static void AddRow(ReadOnlySpan modules, float height, ref BarcodeSymbol symbol) + { + // A row that starts or ends light gets a zero width bar at that end, so even run indexes stay bars. The zero width bars are the zeros the symbol + // storage starts with. + int leading = modules[0] == 0 ? 1 : 0; + Span runs = symbol.AddRow(RunCount(modules), out Span heights, out _); + int run = leading; + int width = 1; + for (int i = 1; i < modules.Length; i++) + { + if (modules[i] != modules[i - 1]) + { + runs[run++] = width; + width = 1; + } + else + { + width++; + } + } + + runs[run] = width; + heights.Fill(height); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataBar/DataBarWidths.cs b/src/ImageSharp.Drawing/Barcodes/DataBar/DataBarWidths.cs new file mode 100644 index 00000000..5813e0f1 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataBar/DataBarWidths.cs @@ -0,0 +1,119 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The element width algorithm every GS1 DataBar symbol character uses. A set of k elements over n modules has a fixed number of width combinations, and a +/// value selects one of them in order: the widths are chosen element by element, each as narrow as the count of combinations still ahead of the value allows. +/// +internal static class DataBarWidths +{ + /// + /// Writes the element widths that encode a value. + /// + /// The buffer that receives the widths, one for each element. + /// The value, from zero to one less than the number of combinations. + /// The number of modules the elements span. + /// The widest an element can be, in modules. + /// Whether the calculation excludes the combinations without a one module element. + public static void Calculate(Span widths, int value, int modules, int maxWidth, bool noNarrow) + { + // Each bit of narrowMask is set while the element it stands for is one module wide, so the mask is zero once no element so far is narrow. + int elements = widths.Length; + int narrowMask = 0; + int bar; + for (bar = 0; bar < elements - 1; bar++) + { + int elementWidth = 1; + narrowMask |= 1 << bar; + int subValue; + while (true) + { + // The combinations of the remaining modules over the remaining elements with this element at its current width. + subValue = Combinations(modules - elementWidth - 1, elements - bar - 2); + + // Less those with no one module element, when such combinations are excluded and no element so far is narrow. + if (noNarrow && narrowMask == 0 && modules - elementWidth - (elements - bar - 1) >= elements - bar - 1) + { + subValue -= Combinations(modules - elementWidth - (elements - bar), elements - bar - 2); + } + + // Less those with an element wider than the maximum. + if (elements - bar - 1 > 1) + { + int lessValue = 0; + for (int widest = modules - elementWidth - (elements - bar - 2); widest > maxWidth; widest--) + { + lessValue += Combinations(modules - elementWidth - widest - 1, elements - bar - 3); + } + + subValue -= lessValue * (elements - 1 - bar); + } + else if (modules - elementWidth > maxWidth) + { + subValue--; + } + + // The value lies within the combinations at this width when it falls below their count. + value -= subValue; + if (value < 0) + { + break; + } + + elementWidth++; + narrowMask &= ~(1 << bar); + } + + value += subValue; + modules -= elementWidth; + widths[bar] = elementWidth; + } + + // The last element takes the modules that remain. + widths[bar] = modules; + } + + /// + /// Returns the number of combinations of r items from n, n! / (r! (n - r)!), and 1 when r exceeds n. The product runs down from n and divides by the + /// smaller factorial as it goes, so the intermediate values stay small and every division is exact. + /// + /// The number of items. + /// The number chosen. + /// The number of combinations. + private static int Combinations(int n, int r) + { + int minDenominator; + int maxDenominator; + if (n - r > r) + { + minDenominator = r; + maxDenominator = n - r; + } + else + { + minDenominator = n - r; + maxDenominator = r; + } + + int value = 1; + int j = 1; + for (int i = n; i > maxDenominator; i--) + { + value *= i; + if (j <= minDenominator) + { + value /= j; + j++; + } + } + + for (; j <= minDenominator; j++) + { + value /= j; + } + + return value; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1DataBarExpandedStackedSymbology.cs b/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1DataBarExpandedStackedSymbology.cs new file mode 100644 index 00000000..2df2a927 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1DataBarExpandedStackedSymbology.cs @@ -0,0 +1,77 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// GS1 DataBar Expanded Stacked, GS1 DataBar Expanded in rows for a symbol area or a print mechanism that is not wide enough for a single row. Section +/// 5.5.2.3.2 of the GS1 General Specifications: "It can be printed in widths of 2 to 20 segments and can have from 2 to 11 rows. Its structure includes a 3X +/// high separator pattern between rows." Each row is 34X high, and the bar height of the options sets each row. No quiet zone is required. +/// +/// The input is the element string syntax the standard prints, each Application Identifier in parentheses followed by its data. When the first element string +/// is Application Identifier (01), its check digit must be correct. The human readable interpretation prints the element strings on one line, except an element +/// string of Application Identifier (8200), which does not print. +/// +/// +public sealed class Gs1DataBarExpandedStackedSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class with four segments in a row. + /// + public Gs1DataBarExpandedStackedSymbology() + : this(4) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The number of segments in a full row, an even number from 2 to 20. A segment is one symbol character. + public Gs1DataBarExpandedStackedSymbology(int segments) + { + DataBarExpandedEncoder.ValidateSegments(segments); + this.Segments = segments; + } + + /// + /// Gets the number of segments in a full row. The last row can hold fewer. + /// + public int Segments { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray64 bufferStorage = default; + int capacity = Gs1Data.PreparedCapacity(text, DataBarExpandedEncoder.MaximumDataCharacters); + ValueStringBuilder encoded = capacity <= Gs1Data.InlineBufferLength ? new(bufferStorage) : new(capacity); + try + { + Gs1Data.Prepare(text, ref encoded); + DataBarExpandedEncoder.ValidateGtinFirst(text); + + // The interpretation is never longer than the text, which PreparedCapacity bounds to 122 characters, so the inline buffer always holds it. + InlineArray128 readableStorage = default; + Span readable = readableStorage; + int readableLength = options.Font is null ? 0 : Gs1Data.WriteReadable(text, false, readable); + DataBarExpandedEncoder.BuildStacked( + encoded.AsSpan(), + this.Segments, + false, + [], + readable[..readableLength], + [], + BarcodeTextSide.BelowBars, + DataBarEncoder.MinimumXDimension, + DataBarEncoder.MaximumXDimension, + float.PositiveInfinity, + options, + ref symbol); + } + finally + { + encoded.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1DataBarExpandedSymbology.cs b/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1DataBarExpandedSymbology.cs new file mode 100644 index 00000000..beb83395 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1DataBarExpandedSymbology.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// GS1 DataBar Expanded, a single row symbol of variable width that carries GS1 element strings and can be read in segments by an omnidirectional scanner. +/// Section 5.5.2.3.1 of the GS1 General Specifications gives "from 4 to 22 symbol characters, or a minimum of 102X wide and a maximum of 534X wide" and "34X +/// high". No quiet zone is required. +/// +/// The input is the element string syntax the standard prints, each Application Identifier in parentheses followed by its data. A symbol carries up to 74 +/// digits or 41 letters. When the first element string is Application Identifier (01), its check digit must be correct. The human readable interpretation +/// prints the element strings on one line, except an element string of Application Identifier (8200), which does not print. +/// +/// +public sealed class Gs1DataBarExpandedSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Gs1DataBarExpandedSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray64 bufferStorage = default; + int capacity = Gs1Data.PreparedCapacity(text, DataBarExpandedEncoder.MaximumDataCharacters); + ValueStringBuilder encoded = capacity <= Gs1Data.InlineBufferLength ? new(bufferStorage) : new(capacity); + try + { + Gs1Data.Prepare(text, ref encoded); + DataBarExpandedEncoder.ValidateGtinFirst(text); + + // The interpretation is never longer than the text, which PreparedCapacity bounds to 122 characters, so the inline buffer always holds it. + InlineArray128 readableStorage = default; + Span readable = readableStorage; + int readableLength = options.Font is null ? 0 : Gs1Data.WriteReadable(text, false, readable); + DataBarExpandedEncoder.BuildLinear(encoded.AsSpan(), readable[..readableLength], false, options, ref symbol); + } + finally + { + encoded.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1DataBarLimitedSymbology.cs b/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1DataBarLimitedSymbology.cs new file mode 100644 index 00000000..e4757bbe --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1DataBarLimitedSymbology.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// GS1 DataBar Limited, a single row symbol for small items that are not read by omnidirectional scanners. Section 5.5.2.2 of the GS1 General Specifications +/// gives "79X wide, starting with a 1X space and ending with a 5X space, by 10X high". The symbol carries a GTIN-12, a GTIN-13 or a GTIN-14 whose indicator +/// digit is 1: "when using the GTIN-14 data structure, only the indicator value 1 is allowed". "The minimum height SHALL be 10X". +/// A bar height under 10X draws at 10X. No quiet zone is required. +/// +/// The input is (01) and 13 or 14 digits. When the input has 13 digits, the symbology adds the check digit. When the input has 14 digits, the symbology +/// validates the last digit as the check digit. The first digit is 0 or 1. The human readable interpretation prints the element string with the check digit. +/// +/// +public sealed class Gs1DataBarLimitedSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Gs1DataBarLimitedSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray14 gtinStorage = default; + Span gtin = gtinStorage; + Gs1Data.ReadGtin(text, gtin); + if (gtin[0] is not ('0' or '1')) + { + throw new ArgumentException($"GS1 DataBar Limited carries a GTIN whose first digit is 0 or 1; got {gtin[0]}.", nameof(text)); + } + + DataBarLimitedEncoder.Build(gtin, false, options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1DataBarOmnidirectionalSymbology.cs b/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1DataBarOmnidirectionalSymbology.cs new file mode 100644 index 00000000..a9e8fb57 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1DataBarOmnidirectionalSymbology.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// GS1 DataBar Omnidirectional, a single row symbol 96 modules wide that carries the GTIN of GS1 Application Identifier (01) and can be read in four segments +/// by an omnidirectional scanner. Section 5.5.2.1.1 of the GS1 General Specifications gives a minimum height of 33X, and the nominal height is the over square +/// 46X that Table 5-44 requires for omnidirectional scanning. A bar height under 33X draws at 33X. No quiet zone is required. +/// +/// The input is (01) and 13 or 14 digits. When the input has 13 digits, the symbology adds the check digit. When the input has 14 digits, the symbology +/// validates the last digit as the check digit. The human readable interpretation prints the element string with the check digit. +/// +/// +public sealed class Gs1DataBarOmnidirectionalSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Gs1DataBarOmnidirectionalSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray14 gtinStorage = default; + Span gtin = gtinStorage; + Gs1Data.ReadGtin(text, gtin); + DataBarEncoder.BuildLinear(gtin, false, DataBarEncoder.OmnidirectionalHeight, DataBarEncoder.OmnidirectionalMinimumHeight, options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1DataBarStackedOmnidirectionalSymbology.cs b/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1DataBarStackedOmnidirectionalSymbology.cs new file mode 100644 index 00000000..83c21e83 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1DataBarStackedOmnidirectionalSymbology.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// GS1 DataBar Stacked Omnidirectional, GS1 DataBar Omnidirectional in two rows that an omnidirectional scanner reads. Section 5.5.2.1.4 of the GS1 General +/// Specifications gives a width of 50X and a minimum height of 69X, "a 3X high separator pattern between two rows of 33X each". The nominal height is the over +/// square 95X that Table 5-44 requires for omnidirectional scanning, two rows of 46X. The bar height of the options sets each row. A bar height under 33X draws +/// at 33X. No quiet zone is required. +/// +/// The input is (01) and 13 or 14 digits. When the input has 13 digits, the symbology adds the check digit. When the input has 14 digits, the symbology +/// validates the last digit as the check digit. The human readable interpretation prints the element string with the check digit. +/// +/// +public sealed class Gs1DataBarStackedOmnidirectionalSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Gs1DataBarStackedOmnidirectionalSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray14 gtinStorage = default; + Span gtin = gtinStorage; + Gs1Data.ReadGtin(text, gtin); + DataBarEncoder.BuildStackedOmnidirectional(gtin, false, options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1DataBarStackedSymbology.cs b/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1DataBarStackedSymbology.cs new file mode 100644 index 00000000..5e883896 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1DataBarStackedSymbology.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// GS1 DataBar Stacked, GS1 DataBar Truncated in two rows for small items that are not read by omnidirectional scanners. Section 5.5.2.1.3 of the GS1 General +/// Specifications gives "50X wide by 13X high" with "a 1X high separator pattern between the two rows". The rows share the other 12X as an upper row of 5X +/// and a lower row of 7X. The bar height of the options sets the lower row, and the upper row keeps its ratio of five to seven. No quiet zone is required. +/// +/// The input is (01) and 13 or 14 digits. When the input has 13 digits, the symbology adds the check digit. When the input has 14 digits, the symbology +/// validates the last digit as the check digit. The human readable interpretation prints the element string with the check digit. +/// +/// +public sealed class Gs1DataBarStackedSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Gs1DataBarStackedSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray14 gtinStorage = default; + Span gtin = gtinStorage; + Gs1Data.ReadGtin(text, gtin); + DataBarEncoder.BuildStacked(gtin, false, options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1DataBarTruncatedSymbology.cs b/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1DataBarTruncatedSymbology.cs new file mode 100644 index 00000000..5aa84e82 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1DataBarTruncatedSymbology.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// GS1 DataBar Truncated, the reduced height form of GS1 DataBar Omnidirectional for small items that are not read by omnidirectional scanners. Section +/// 5.5.2.1.2 of the GS1 General Specifications gives "96X wide by 13X high". No quiet zone is required. +/// +/// The input is (01) and 13 or 14 digits. When the input has 13 digits, the symbology adds the check digit. When the input has 14 digits, the symbology +/// validates the last digit as the check digit. The human readable interpretation prints the element string with the check digit. +/// +/// +public sealed class Gs1DataBarTruncatedSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Gs1DataBarTruncatedSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray14 gtinStorage = default; + Span gtin = gtinStorage; + Gs1Data.ReadGtin(text, gtin); + DataBarEncoder.BuildLinear(gtin, false, DataBarEncoder.TruncatedHeight, DataBarEncoder.TruncatedHeight, options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1NorthAmericanCouponSymbology.cs b/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1NorthAmericanCouponSymbology.cs new file mode 100644 index 00000000..3b5861c1 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataBar/Gs1NorthAmericanCouponSymbology.cs @@ -0,0 +1,113 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The GS1 North American Coupon, a GS1 DataBar Expanded Stacked symbol that carries a single element string, the coupon code of GS1 Application Identifier +/// (8110). The North American Coupon Application Guideline of GS1 US: "Data shall be encoded in a GS1 DataBar Expanded Symbol. This barcode is constructed in +/// segments and is capable of being stacked (as shown) with a minimum of 2 segments per row." Each row is 34X high at every bar height, a 3X separator stands +/// between rows, and no quiet zone is required. Section 5.4 of the guideline gives an X dimension of 0.254 to 0.33 millimetres. An X dimension outside that +/// range draws at the nearest limit. +/// +/// The input is (8110) and a coupon code of up to 70 digits, whose elements Table C of the guideline defines: the GS1 Company Prefix with its length +/// indicator, the offer code, the save value and the primary purchase requirement, and optional data fields 1, 2, 3, 4, 5, 6 and 9 in increasing order. Section +/// 5.5 of the guideline: "The only human readable digits shown from the GS1 DataBar barcode will be the GS1 Company Prefix and Offer Code separated by a dash." +/// They print above the symbol, as Figure 1 shows. +/// +/// +public sealed class Gs1NorthAmericanCouponSymbology : BarcodeSymbology +{ + /// + /// The most digits the coupon code carries. Section 5.2 of the guideline: "until all desired data is encoded (or the limit of 70 digits is reached)". + /// + private const int MaximumDigits = 70; + + /// + /// The smallest X dimension in millimetres. Section 5.4 of the guideline: "If a specific application requires, the X-dimension may be as small as 0.0100 + /// inch. (0.254 mm) or as large as 0.013 in. (0.33 mm)." + /// + private const double MinimumXDimension = 0.0100 * 25.4; + + /// + /// The largest X dimension in millimetres, 0.013 inch, which section 5.4 also gives as the nominal X dimension. + /// + private const double MaximumXDimension = 0.33; + + /// + /// Initializes a new instance of the class with eight segments in a row, as Figure 1 of the guideline prints + /// the symbol. + /// + public Gs1NorthAmericanCouponSymbology() + : this(8) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The number of segments in a full row, an even number from 2 to 20. A segment is one symbol character. + public Gs1NorthAmericanCouponSymbology(int segments) + { + DataBarExpandedEncoder.ValidateSegments(segments); + this.Segments = segments; + } + + /// + /// Gets the number of segments in a full row. The last row can hold fewer. + /// + public int Segments { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + if (!text.StartsWith("(8110)", StringComparison.Ordinal)) + { + throw new ArgumentException("A GS1 North American Coupon carries a single element string, Application Identifier (8110).", nameof(text)); + } + + InlineArray64 bufferStorage = default; + int capacity = Gs1Data.PreparedCapacity(text, DataBarExpandedEncoder.MaximumDataCharacters); + ValueStringBuilder encoded = capacity <= Gs1Data.InlineBufferLength ? new(bufferStorage) : new(capacity); + try + { + Gs1Data.Prepare(text, ref encoded); + + // Application Identifier (8110) has no predefined length, so a second element string after it stands behind a separator. + ReadOnlySpan data = encoded.AsSpan()[4..]; + if (data.Contains(Gs1Data.Separator)) + { + throw new ArgumentException("A GS1 North American Coupon carries a single element string, Application Identifier (8110).", nameof(text)); + } + + if (data.Length > MaximumDigits) + { + throw new ArgumentException($"The coupon code carries at most {MaximumDigits} digits; got {data.Length}.", nameof(text)); + } + + int prefixLength = NorthAmericanCouponCode.Validate(data); + + // The caption is the GS1 Company Prefix and the offer code, joined by a hyphen. Section 5.4 of the guideline: "Each row shall be 34X high", so the + // row height is fixed at 34X. + DataBarExpandedEncoder.BuildStacked( + encoded.AsSpan(), + this.Segments, + false, + data.Slice(1, prefixLength), + "-", + data.Slice(1 + prefixLength, NorthAmericanCouponCode.OfferCodeLength), + BarcodeTextSide.AboveBars, + MinimumXDimension, + MaximumXDimension, + DataBarExpandedEncoder.RowHeight, + options, + ref symbol); + } + finally + { + encoded.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataMatrix/DataMatrixCodewords.cs b/src/ImageSharp.Drawing/Barcodes/DataMatrix/DataMatrixCodewords.cs new file mode 100644 index 00000000..b19edbf5 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataMatrix/DataMatrixCodewords.cs @@ -0,0 +1,1400 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The data encodation of Data Matrix ECC 200, ISO/IEC 16022. The data is a sequence of message values: 0 to 255 are byte values, and the negative values +/// , and are function characters. The encoder starts in ASCII encodation and moves between the +/// ASCII, C40, Text, X12, EDIFACT and Base 256 encodation schemes by a look-ahead test that compares the codewords each scheme needs for the data that follows. +/// +internal static class DataMatrixCodewords +{ + /// + /// The message value of the Function 1 Symbol Character. + /// + public const int Fnc1 = -1; + + /// + /// The message value of the 05 Macro, which stands for the header [)>RS05GS and the trailer RS EOT. + /// + public const int Macro05 = -3; + + /// + /// The message value of the 06 Macro, which stands for the header [)>RS06GS and the trailer RS EOT. + /// + public const int Macro06 = -4; + + /// + /// The largest number of data codewords, which the 144 x 144 symbol holds. + /// + public const int MaximumCodewords = 1558; + + /// + /// The number of values the encoder can collect in one run of C40, Text, X12 or EDIFACT encodation. Three C40, Text or X12 values pack into two codewords + /// and four EDIFACT values into three, so a run of more than three halves of values cannot fit any symbol. The four places + /// beyond hold the values of one more character. + /// + private const int MaximumValues = (MaximumCodewords * 3 / 2) + 4; + + /// + /// The ASCII codeword of the pad character. + /// + public const int Pad = 129; + + /// + /// The ASCII codeword that latches to C40 encodation, ISO/IEC 16022 Table 2. + /// + private const int LatchC40 = 230; + + /// + /// The ASCII codeword that latches to Base 256 encodation, Table 2. + /// + private const int LatchBase256 = 231; + + /// + /// The ASCII codeword of the Function 1 Symbol Character, Table 2. + /// + private const int Fnc1Codeword = 232; + + /// + /// The ASCII codeword of the Upper Shift, which adds 128 to the next character, Table 2. + /// + private const int UpperShift = 235; + + /// + /// The ASCII codeword of the 05 Macro, Table 2. + /// + private const int Macro05Codeword = 236; + + /// + /// The ASCII codeword of the 06 Macro, Table 2. + /// + private const int Macro06Codeword = 237; + + /// + /// The ASCII codeword that latches to X12 encodation, Table 2. + /// + private const int LatchX12 = 238; + + /// + /// The ASCII codeword that latches to Text encodation, Table 2. + /// + private const int LatchText = 239; + + /// + /// The ASCII codeword that latches to EDIFACT encodation, Table 2. + /// + private const int LatchEdifact = 240; + + /// + /// The ASCII codeword of the first digit pair, 00. The pair nn is this value plus nn, Table 2. + /// + private const int DigitPairBase = 130; + + /// + /// The codeword that unlatches C40, Text and X12 encodation back to ASCII encodation. + /// + private const int Unlatch = 254; + + /// + /// The EDIFACT value that unlatches back to ASCII encodation. + /// + private const int EdifactUnlatch = 31; + + /// + /// The value of a first character that is not in the basic set of C40 or Text encodation: Shift 1 for the control characters, Shift 2 for the punctuation + /// and the function characters, and Shift 3 for the rest. + /// + private const int Shift1 = 0; + + /// + /// The Shift 2 value. + /// + private const int Shift2 = 1; + + /// + /// The Shift 3 value. + /// + private const int Shift3 = 2; + + /// + /// The Shift 2 set value of the Function 1 Symbol Character in C40 and Text encodation. + /// + private const int ShiftedFnc1 = 27; + + /// + /// The Shift 2 set value of the Upper Shift in C40 and Text encodation. + /// + private const int ShiftedUpperShift = 30; + + /// + /// The scale of the look-ahead counts. The counts step in halves, thirds and quarters, so twelve units to the codeword keep every count a whole number and + /// every comparison exact. + /// + private const int Unit = 12; + + /// + /// The number of encodation schemes, one row of running sums each in the look-ahead tables. + /// + private const int SchemeCount = 6; + + /// + /// The number of look-ahead table rows before the running sums: the digit run, the distance to the next X12 terminator and the distance to the next + /// character X12 cannot encode. + /// + private const int RunRows = 3; + + /// + /// The number of rows of the look-ahead tables: the run rows, then one row of running sums for each scheme. + /// + private const int LookAheadRows = RunRows + SchemeCount; + + /// + /// The number of codewords left when no allowed symbol holds one more codeword. + /// + private const int NoSymbol = int.MaxValue; + + /// + /// The number of characters the look-ahead test reads before it can end early, while it still reads data. Annex J of ISO/IEC 16022:2024, step r): "If + /// at least 4 data characters have been processed in this test loop". + /// + private const int MinimumLookAhead = 4; + + /// + /// The encodation schemes. The value of a scheme is also the index of its row of running sums in the look-ahead tables. + /// + private enum EncodationScheme + { + /// + /// ASCII encodation: one codeword per character, or per digit pair. + /// + Ascii, + + /// + /// C40 encodation: three values in two codewords, with the capital letters in the basic set. + /// + C40, + + /// + /// Text encodation: three values in two codewords, with the small letters in the basic set. + /// + Text, + + /// + /// X12 encodation: three values in two codewords, from one set of 40 characters and no shifts. + /// + X12, + + /// + /// EDIFACT encodation: four six-bit values in three codewords. + /// + Edifact, + + /// + /// Base 256 encodation: one codeword per byte after a length field. + /// + Base256, + } + + /// + /// Encodes the message into data codewords, without pad codewords. + /// + /// The message values. + /// The data codeword capacities of the symbols the data can take. + /// The number of message values at the start of the message that encode in C40 encodation, or 0. + /// The buffer the data codewords are written to, long. + /// The scratch of the look-ahead tables and the values of a run, long. + /// The number of data codewords written. + /// The data needs more codewords than the largest symbol holds. + public static int Encode(ReadOnlySpan message, ReadOnlySpan capacities, int c40Prefix, Span codewords, Span scratch) + { + // The C40 prefix cannot run past the data. + int length = message.Length; + Encoder encoder = new(message, capacities, Math.Min(c40Prefix, length), codewords, scratch); + + // A Function 1 Symbol Character in the first position always encodes in ASCII encodation, because a reader identifies GS1 data by codeword 232 at the + // start of the symbol. A Macro character stands for the message header, and Table 2 gives it only as an ASCII codeword, so it also starts the symbol. + // The C40 prefix counts message values, so it moves one place to stay over the same characters. + int first = message[0]; + if (first is Fnc1 or Macro05 or Macro06) + { + encoder.AppendAscii(first); + encoder.Position = 1; + if (encoder.C40Prefix > 0) + { + encoder.C40Prefix++; + } + } + + // Each step encodes in the current scheme until that scheme ends, either by a latch to another scheme or by the end of the data. C40, Text and X12 + // share one method, because they share the packing of three values into two codewords. + while (encoder.Position < length) + { + switch (encoder.Mode) + { + case EncodationScheme.Ascii: + encoder.EncodeAscii(); + break; + + case EncodationScheme.Edifact: + encoder.EncodeEdifact(); + break; + + case EncodationScheme.Base256: + encoder.EncodeBase256(); + break; + + default: + encoder.EncodeTriplets(); + break; + } + } + + return encoder.Written; + } + + /// + /// Returns the length of the scratch takes for a message: the look-ahead tables and the values of one run of C40, Text, X12 or EDIFACT + /// encodation. + /// + /// The number of message values. + /// The scratch length. + public static int ScratchLength(int messageLength) => (LookAheadRows * (messageLength + 1)) + MaximumValues; + + /// + /// Fills the tables the look-ahead test reads, rows of message length plus one entries each. Rows 0 to 2 hold three runs from + /// each position: the digits that start there, the distance to the next X12 terminator, carriage return, asterisk or greater than, and the distance to the + /// next character X12 cannot encode. The entry past the end stops each run. A distance with no end stands past the end of the data, so where neither + /// character follows, the two distances are equal. Rows 3 to 8 hold the cost of each scheme as a running sum, in units, of the characters before each + /// position, the row of a scheme at 3 plus its scheme number. The cost of a scheme for a run of characters is then the difference of two sums. + /// + /// A digit costs half a codeword in ASCII encodation and a character above 127 two whole codewords; any other character rounds the count up first, because + /// it cannot share a codeword with a digit before it. A basic set character costs two thirds of a codeword in C40, Text and X12 encodation, one of three + /// values in two codewords, and three quarters in EDIFACT encodation, one of four values in three codewords. A shifted character costs two values and an + /// Upper Shift two more. X12 costs a character it cannot hold 10/3 codewords, or 13/3 above 127, and EDIFACT 13/4 or 17/4. Base 256 encodation pays one + /// codeword a byte and four for a function character it cannot hold. + /// + /// + /// The message values. + /// The tables, times the message length plus one long. + private static void PrepareLookAhead(ReadOnlySpan message, Span tables) + { + int length = message.Length; + int stride = length + 1; + Span digits = tables[..stride]; + Span terminator = tables.Slice(stride, stride); + Span nonX12 = tables.Slice(2 * stride, stride); + digits[length] = 0; + terminator[length] = length + 1; + nonX12[length] = length + 1; + for (int i = length - 1; i >= 0; i--) + { + int value = message[i]; + digits[i] = IsDigit(value) ? digits[i + 1] + 1 : 0; + terminator[i] = value is '\r' or '*' or '>' ? 0 : terminator[i + 1] + 1; + nonX12[i] = IsX12(value) ? nonX12[i + 1] + 1 : 0; + } + + Span asciiSums = SumRow(tables, EncodationScheme.Ascii, stride); + Span c40Sums = SumRow(tables, EncodationScheme.C40, stride); + Span textSums = SumRow(tables, EncodationScheme.Text, stride); + Span x12Sums = SumRow(tables, EncodationScheme.X12, stride); + Span edifactSums = SumRow(tables, EncodationScheme.Edifact, stride); + Span base256Sums = SumRow(tables, EncodationScheme.Base256, stride); + asciiSums[0] = 0; + c40Sums[0] = 0; + textSums[0] = 0; + x12Sums[0] = 0; + edifactSums[0] = 0; + base256Sums[0] = 0; + for (int i = 0; i < length; i++) + { + int value = message[i]; + bool extended = value > 127; + asciiSums[i + 1] = AddAscii(asciiSums[i], value); + c40Sums[i + 1] = c40Sums[i] + (IsC40Basic(value) ? 2 * Unit / 3 : extended ? 8 * Unit / 3 : 4 * Unit / 3); + textSums[i + 1] = textSums[i] + (IsTextBasic(value) ? 2 * Unit / 3 : extended ? 8 * Unit / 3 : 4 * Unit / 3); + x12Sums[i + 1] = x12Sums[i] + (IsX12(value) ? 2 * Unit / 3 : extended ? 13 * Unit / 3 : 10 * Unit / 3); + edifactSums[i + 1] = edifactSums[i] + (IsEdifact(value) ? 3 * Unit / 4 : extended ? 17 * Unit / 4 : 13 * Unit / 4); + base256Sums[i + 1] = base256Sums[i] + (value < 0 ? 4 * Unit : Unit); + } + } + + /// + /// Returns the row of running sums of one scheme in the look-ahead tables. The sums follow the run rows, one row for each scheme in scheme order. + /// + /// The look-ahead tables. + /// The encodation scheme. + /// The length of one row, the message length plus one. + /// The row of running sums. + private static Span SumRow(Span tables, EncodationScheme scheme, int stride) => tables.Slice((RunRows + (int)scheme) * stride, stride); + + /// + /// Returns the ASCII codeword that latches from ASCII encodation to another scheme, Table 2. + /// + /// The scheme to latch to, any scheme but . + /// The latch codeword. + private static int LatchCodeword(EncodationScheme scheme) => scheme switch + { + EncodationScheme.C40 => LatchC40, + EncodationScheme.Text => LatchText, + EncodationScheme.X12 => LatchX12, + EncodationScheme.Edifact => LatchEdifact, + _ => LatchBase256, + }; + + /// + /// Returns whether the message value is a digit. + /// + /// The message value. + /// for the digits 0 to 9. + private static bool IsDigit(int value) => value is >= '0' and <= '9'; + + /// + /// Returns whether the message value is in the basic set of C40 encodation: space, the digits and the capital letters. + /// + /// The message value. + /// for a basic set character. + private static bool IsC40Basic(int value) => value is ' ' or (>= '0' and <= '9') or (>= 'A' and <= 'Z'); + + /// + /// Returns whether the message value is in the basic set of Text encodation: space, the digits and the small letters. + /// + /// The message value. + /// for a basic set character. + private static bool IsTextBasic(int value) => value is ' ' or (>= '0' and <= '9') or (>= 'a' and <= 'z'); + + /// + /// Returns whether X12 encodation holds the message value: carriage return, asterisk, greater than, space, the digits and the capital letters. + /// + /// The message value. + /// for an X12 character. + private static bool IsX12(int value) => value is '\r' or '*' or '>' || IsC40Basic(value); + + /// + /// Returns whether EDIFACT encodation holds the message value, the characters 32 to 94. + /// + /// The message value. + /// for an EDIFACT character. + private static bool IsEdifact(int value) => value is >= 32 and <= 94; + + /// + /// Rounds a look-ahead count up to a whole codeword. + /// + /// The count in units. + /// The count rounded up to a multiple of . + private static int Ceiling(int count) => (count + Unit - 1) / Unit * Unit; + + /// + /// Adds the ASCII cost of one message value to a look-ahead count. + /// + /// The count in units. + /// The message value. + /// The count with the cost of the value added. + private static int AddAscii(int count, int value) + => IsDigit(value) ? count + (Unit / 2) : Ceiling(count) + (value > 127 ? 2 * Unit : Unit); + + /// + /// Throws the exception for data that needs more codewords than the largest symbol holds. + /// + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowTooLong() + => throw new ArgumentException($"The data needs more than the {MaximumCodewords} data codewords of the largest Data Matrix symbol."); + + /// + /// Writes the C40, Text or X12 values of one message value. + /// + /// The encodation scheme: C40, Text or X12. + /// The message value. + /// The buffer of at least four values that receives the values. + /// The number of values written, or 0 when the scheme cannot encode the message value. + private static int TripletValues(EncodationScheme scheme, int value, Span values) + { + // X12 has one set of 40 values and no shifts: carriage return 0, asterisk 1, greater than 2, space 3, the digits 4 to 13 and the capital letters 14 to + // 39. Any other character ends X12. + if (scheme == EncodationScheme.X12) + { + int x12 = value switch + { + '\r' => 0, + '*' => 1, + '>' => 2, + ' ' => 3, + >= '0' and <= '9' => value - 44, + >= 'A' and <= 'Z' => value - 51, + _ => -1, + }; + + if (x12 < 0) + { + return 0; + } + + values[0] = x12; + return 1; + } + + // A character above 127 takes the Upper Shift in the Shift 2 set, then the values of the character 128 below it. + int count = 0; + if (value > 127) + { + values[0] = Shift2; + values[1] = ShiftedUpperShift; + count = 2; + value -= 128; + } + + // A Macro character is only the first message value, which Encode writes in ASCII encodation, so no Macro character comes here. The Function 1 + // Symbol Character is value 27 of the Shift 2 set. + Span rest = values[count..]; + if (value == Fnc1) + { + rest[0] = Shift2; + rest[1] = ShiftedFnc1; + return count + 2; + } + + // The basic set: space is 3 and the digits 0 to 9 are 4 to 13, so a digit is its character code less 44. + if (value == ' ') + { + rest[0] = 3; + return count + 1; + } + + if (IsDigit(value)) + { + rest[0] = value - 44; + return count + 1; + } + + // The basic set holds the capital letters in C40 encodation and the small letters in Text encodation, as the values 14 to 39, so A is 65 - 51 and a is + // 97 - 83. The other case moves to the Shift 3 set, where C40 puts the small letters at 1 to 26 after the grave accent at 0, and Text puts the capital + // letters there. + if (value is >= 'A' and <= 'Z') + { + if (scheme == EncodationScheme.C40) + { + rest[0] = value - 51; + return count + 1; + } + + rest[0] = Shift3; + rest[1] = value - 64; + return count + 2; + } + + if (value is >= 'a' and <= 'z') + { + if (scheme == EncodationScheme.Text) + { + rest[0] = value - 83; + return count + 1; + } + + rest[0] = Shift3; + rest[1] = value - 96; + return count + 2; + } + + // The rest of the characters to 127 take a shift and a value in that set. The control characters 0 to 31 are the Shift 1 set in order. The Shift 2 set + // holds the punctuation in three runs: 33 to 47 as 0 to 14, 58 to 64 as 15 to 21 and 91 to 95 as 22 to 26. The Shift 3 set holds 96 to 127 as 0 to 31; + // the letters of that range were handled above. + (int shift, int shifted) = value switch + { + < 32 => (Shift1, value), + <= 47 => (Shift2, value - 33), + <= 64 => (Shift2, value - 43), + <= 95 => (Shift2, value - 69), + _ => (Shift3, value - 96), + }; + + rest[0] = shift; + rest[1] = shifted; + return count + 2; + } + + /// + /// The state of the encodation: the position in the message, the current encodation scheme and the runs the look-ahead test reads, with the codewords + /// written so far and the scratch for the values of a run of C40, Text, X12 or EDIFACT encodation. + /// + private ref struct Encoder + { + /// + /// The message values. + /// + private readonly ReadOnlySpan message; + + /// + /// The data codeword capacities of the symbols the data can take. + /// + private readonly ReadOnlySpan capacities; + + /// + /// The number of digits that start at each position. + /// + private readonly ReadOnlySpan digits; + + /// + /// The distance from each position to the next X12 terminator, carriage return, asterisk or greater than. + /// + private readonly ReadOnlySpan terminator; + + /// + /// The distance from each position to the next character that X12 encodation cannot encode. + /// + private readonly ReadOnlySpan nonX12; + + /// + /// The running sum, in units, of the ASCII encodation cost of the characters before each position. + /// + private readonly ReadOnlySpan asciiSums; + + /// + /// The running sum, in units, of the C40 encodation cost of the characters before each position. + /// + private readonly ReadOnlySpan c40Sums; + + /// + /// The running sum, in units, of the Text encodation cost of the characters before each position. + /// + private readonly ReadOnlySpan textSums; + + /// + /// The running sum, in units, of the X12 encodation cost of the characters before each position. + /// + private readonly ReadOnlySpan x12Sums; + + /// + /// The running sum, in units, of the EDIFACT encodation cost of the characters before each position. + /// + private readonly ReadOnlySpan edifactSums; + + /// + /// The running sum, in units, of the Base 256 encodation cost of the characters before each position. + /// + private readonly ReadOnlySpan base256Sums; + + /// + /// The buffer that receives the data codewords. + /// + private readonly Span codewords; + + /// + /// The scratch for the values of a run of C40, Text, X12 or EDIFACT encodation. + /// + private readonly Span values; + + /// + /// Initializes a new instance of the struct and fills the look-ahead tables for the message. + /// + /// The message values. + /// The data codeword capacities of the symbols the data can take. + /// The number of message values at the start that encode in C40 encodation, no more than the message length. + /// The buffer the data codewords are written to, long. + /// The scratch of the look-ahead tables and the values of a run, long. + public Encoder(ReadOnlySpan message, ReadOnlySpan capacities, int c40Prefix, Span codewords, Span scratch) + { + // The look-ahead tables and the values that a run of C40, Text, X12 or EDIFACT encodation collects share the scratch the caller rents with its + // other buffers: first the LookAheadRows rows of the tables, then MaximumValues values. + int stride = message.Length + 1; + Span tables = scratch[..(LookAheadRows * stride)]; + PrepareLookAhead(message, tables); + + this.message = message; + this.capacities = capacities; + this.digits = tables[..stride]; + this.terminator = tables.Slice(stride, stride); + this.nonX12 = tables.Slice(2 * stride, stride); + this.asciiSums = SumRow(tables, EncodationScheme.Ascii, stride); + this.c40Sums = SumRow(tables, EncodationScheme.C40, stride); + this.textSums = SumRow(tables, EncodationScheme.Text, stride); + this.x12Sums = SumRow(tables, EncodationScheme.X12, stride); + this.edifactSums = SumRow(tables, EncodationScheme.Edifact, stride); + this.base256Sums = SumRow(tables, EncodationScheme.Base256, stride); + this.codewords = codewords; + this.values = scratch.Slice(LookAheadRows * stride, MaximumValues); + this.C40Prefix = c40Prefix; + this.Position = 0; + this.Mode = EncodationScheme.Ascii; + this.Written = 0; + } + + /// + /// Gets or sets the position of the next message value to encode. + /// + public int Position { get; set; } + + /// + /// Gets or sets the current encodation scheme. + /// + public EncodationScheme Mode { get; set; } + + /// + /// Gets or sets the number of message values at the start that encode in C40 encodation, or -1 once the encoder has left C40 encodation. + /// + public int C40Prefix { get; set; } + + /// + /// Gets the number of data codewords written. + /// + public int Written { get; private set; } + + /// + /// Appends one data codeword. No symbol holds more than , so the encodation stops there. + /// + /// The codeword. + /// The data needs more codewords than the largest symbol holds. + public void AppendCodeword(int codeword) + { + int written = this.Written; + if (written == this.codewords.Length) + { + ThrowTooLong(); + } + + this.codewords[written] = codeword; + this.Written = written + 1; + } + + /// + /// Encodes in ASCII encodation: a digit pair as one codeword, or the latch the look-ahead test chooses, or one character. + /// + public void EncodeAscii() + { + // Two digits take one codeword in ASCII encodation, which no other scheme does better, so a digit pair encodes before the look-ahead test runs. + // Digits in the C40 prefix stay in C40. + int position = this.Position; + if (position >= this.C40Prefix && this.digits[position] >= 2) + { + this.AppendDigitPair(); + return; + } + + // A latch writes its codeword and changes the scheme. The next step then encodes the same character in the new scheme. + EncodationScheme next = this.LookAhead(); + if (next != EncodationScheme.Ascii) + { + this.AppendCodeword(LatchCodeword(next)); + this.Mode = next; + return; + } + + this.AppendAscii(this.message[position]); + this.Position = position + 1; + } + + /// + /// Encodes in C40, Text or X12 encodation until the look-ahead test chooses another scheme at a group boundary, the data ends, or a character the + /// scheme cannot encode follows. + /// + public void EncodeTriplets() + { + // The values collect until the scheme ends, because the end of the scheme can step back over characters already read. A character takes one to four + // values, so the groups of three do not line up with the characters. The values collect in the scratch of the encoder, which holds more values than + // the codewords of the largest symbol can pack, so a run that fills it cannot fit any symbol. + ReadOnlySpan message = this.message; + EncodationScheme scheme = this.Mode; + Span values = this.values; + int valueCount = 0; + InlineArray4 characterStorage = default; + Span character = characterStorage; + while (this.Position < message.Length) + { + int position = this.Position; + int count = TripletValues(scheme, message[position], character); + if (count == 0) + { + break; + } + + // The scheme can change and the data can end only between whole groups of three values, because a group is the unit that packs into codewords. + if (valueCount % 3 == 0) + { + // At a group boundary the look-ahead test can choose another scheme. The scheme ends with an unlatch, and a latch follows unless the next + // scheme is ASCII. + if (valueCount > 0) + { + EncodationScheme next = this.LookAhead(); + if (next != scheme) + { + this.AppendTriplets(values[..valueCount]); + this.AppendCodeword(Unlatch); + if (next != EncodationScheme.Ascii) + { + this.AppendCodeword(LatchCodeword(next)); + } + + this.Mode = next; + return; + } + } + + // Near the end of the data the last characters can fill the symbol exactly, so the unlatch is left out and the end of the symbol returns to + // ASCII encodation. + int left = message.Length - position; + if (left <= 3 && this.EndTriplets(scheme, values[..valueCount])) + { + return; + } + } + + if (valueCount + count > values.Length) + { + ThrowTooLong(); + } + + character[..count].CopyTo(values[valueCount..]); + valueCount += count; + this.Position = position + 1; + } + + // The scheme ends inside a group or before a character it cannot encode. The encoder steps back to the last whole group, unlatches and encodes the + // characters it stepped back over in ASCII encodation. Each step back removes the values of one character, and a group boundary falls between + // characters, so the loop stops on one. + int length = valueCount; + while (length % 3 != 0) + { + this.Position--; + length -= TripletValues(scheme, message[this.Position], character); + } + + this.AppendTriplets(values[..length]); + this.AppendCodeword(Unlatch); + this.LeaveToAscii(); + } + + /// + /// Encodes in EDIFACT encodation until the look-ahead test chooses another scheme at a group boundary, the data ends, or a character EDIFACT cannot + /// encode follows. + /// + public void EncodeEdifact() + { + // The values collect in the scratch of the encoder. Four values pack into three codewords, so the scratch holds more values than the largest symbol + // can pack, and a run that fills it cannot fit any symbol. + ReadOnlySpan message = this.message; + Span values = this.values; + int valueCount = 0; + while (this.Position < message.Length) + { + int position = this.Position; + int value = message[position]; + if (!IsEdifact(value)) + { + break; + } + + if (valueCount % 4 == 0) + { + // With two characters or fewer left, the symbol can have room for them in ASCII encodation, which the end of the symbol returns to without + // an unlatch. + if (message.Length - position <= 2) + { + // The codewords the whole groups take are counted as written, because the groups so far are whole. A character above 127 takes two + // ASCII codewords. + int remaining = this.Remaining(this.Written + (valueCount / 4 * 3)); + int asciiCount = 0; + for (int i = position; i < message.Length; i++) + { + asciiCount += message[i] > 127 ? 2 : 1; + } + + if ((remaining == 1 || remaining == 2) && asciiCount <= remaining) + { + this.AppendEdifact(values[..valueCount]); + for (int i = position; i < message.Length; i++) + { + this.AppendAscii(message[i]); + } + + this.Mode = EncodationScheme.Ascii; + this.Position = message.Length; + return; + } + } + + if (this.LookAhead() != EncodationScheme.Edifact) + { + break; + } + } + + // The unlatch value takes one more place, so a run stops one value short of the end of the scratch. + if (valueCount == values.Length - 1) + { + ThrowTooLong(); + } + + // EDIFACT keeps the low six bits: the characters 64 to 94 become 0 to 30, and 32 to 63 keep their value. + values[valueCount++] = value & 0x3F; + this.Position = position + 1; + } + + // The unlatch value ends the scheme, unless the data ended on a whole group and fewer than three codewords are left, which the end of the symbol + // returns to ASCII encodation. The count is the codewords left after the whole groups, in the smallest symbol that holds them. + int left = this.Remaining(this.Written + (valueCount / 4 * 3) - 1) - 1; + if (valueCount % 4 != 0 || this.Position != message.Length || left >= 3) + { + values[valueCount++] = EdifactUnlatch; + } + + this.AppendEdifact(values[..valueCount]); + this.LeaveToAscii(); + } + + /// + /// Encodes in Base 256 encodation until the look-ahead test chooses another scheme. A length field leads the bytes, and every codeword of the field and + /// the bytes takes the 255-state randomising. + /// + public void EncodeBase256() + { + // The length field comes before the bytes, so the run is measured first. The look-ahead test runs at each byte, and the run ends where it chooses + // another scheme. Base 256 has no unlatch; the length field tells the decoder where the run ends. + ReadOnlySpan message = this.message; + int start = this.Position; + while (this.Position < message.Length && this.LookAhead() == EncodationScheme.Base256) + { + this.Position++; + } + + // The length field is one codeword below 250 bytes and two codewords from 250: the first is 249 plus the hundreds of 250, the second the rest. A + // field of 0 means that the bytes run to the end of the symbol, which applies when the field and the bytes fill it exactly. + int first = this.Written; + int count = this.Position - start; + if (this.Remaining(first + count) - 1 == 0 && this.Position == message.Length) + { + this.AppendCodeword(0); + } + else if (count < 250) + { + this.AppendCodeword(count); + } + else + { + this.AppendCodeword((count / 250) + 249); + this.AppendCodeword(count % 250); + } + + for (int i = start; i < this.Position; i++) + { + this.AppendCodeword(message[i]); + } + + // The 255-state algorithm adds a pseudo-random number from the position of the codeword in the symbol, counted from 1, so a run of equal bytes does + // not repeat one module pattern. The number is ((149 x position) mod 255) + 1, and the sum wraps at 256. + Span codewords = this.codewords; + for (int i = first; i < this.Written; i++) + { + int randomised = codewords[i] + ((149 * (i + 1)) % 255) + 1; + codewords[i] = randomised <= 255 ? randomised : randomised - 256; + } + + this.Mode = EncodationScheme.Ascii; + } + + /// + /// Appends the ASCII codewords of one message value. + /// + /// The message value. + public void AppendAscii(int value) + { + // Table 2: the characters 0 to 127 are their value plus 1. A character from 128 to 255 is the Upper Shift followed by the codeword of the character + // 128 below it, (value - 128) + 1. + switch (value) + { + case Fnc1: + this.AppendCodeword(Fnc1Codeword); + break; + + case Macro05: + this.AppendCodeword(Macro05Codeword); + break; + + case Macro06: + this.AppendCodeword(Macro06Codeword); + break; + + case < 128: + this.AppendCodeword(value + 1); + break; + + default: + this.AppendCodeword(UpperShift); + this.AppendCodeword(value - 127); + break; + } + } + + /// + /// Encodes the last one to three characters of C40, Text or X12 encodation when they fill the symbol with no unlatch, or when one character is left and + /// ASCII encodation fits it. + /// + /// The encodation scheme. + /// The values of the whole groups so far, not yet written as codewords. + /// when the data ended here. + private bool EndTriplets(EncodationScheme scheme, ReadOnlySpan values) + { + // The end cases apply only when one or two codewords are left after the whole groups, in the smallest symbol that holds them. With more room the + // scheme ends with an unlatch as usual. + ReadOnlySpan message = this.message; + int position = this.Position; + int remaining = this.Remaining(this.Written + (values.Length / 3 * 2)); + if (remaining > 2) + { + return false; + } + + // A character the scheme cannot encode counts as four values, which no end case takes. At most three characters are left, each of at most four + // values. + InlineArray12 restStorage = default; + InlineArray4 characterStorage = default; + Span rest = restStorage; + Span character = characterStorage; + int count = 0; + for (int i = position; i < message.Length; i++) + { + int n = TripletValues(scheme, message[i], character); + if (n == 0) + { + count += 4; + } + else + { + character[..n].CopyTo(rest[count..]); + count += n; + } + } + + // Two codewords hold one more group. Three values fill it. Two values take Shift 1 as the third, which a decoder discards at the end of the symbol; + // X12 has no shifts, so it does not take this case. One value does not fill a group, so it goes to ASCII: after an unlatch when two codewords are + // left, or straight after the groups when one is left, which the end of the symbol returns to ASCII encodation. + if (remaining == 2 && (count == 3 || (count == 2 && scheme != EncodationScheme.X12))) + { + this.AppendTriplets(values); + this.AppendTriplet(rest[0], rest[1], count == 3 ? rest[2] : Shift1); + } + else if (count == 1 && remaining == 2) + { + this.AppendTriplets(values); + this.AppendCodeword(Unlatch); + this.AppendAscii(message[position]); + } + else if (count == 1 && remaining == 1) + { + this.AppendTriplets(values); + this.AppendAscii(message[position]); + } + else + { + return false; + } + + this.Mode = EncodationScheme.Ascii; + this.Position = message.Length; + return true; + } + + /// + /// Returns to ASCII encodation after an unlatch and encodes the next character in it, so the look-ahead test does not latch straight back. + /// + private void LeaveToAscii() + { + // The C40 prefix ends here, so a later look-ahead test is free to choose any scheme. + this.Mode = EncodationScheme.Ascii; + this.C40Prefix = -1; + if (this.Position == this.message.Length) + { + return; + } + + if (this.digits[this.Position] >= 2) + { + this.AppendDigitPair(); + return; + } + + this.AppendAscii(this.message[this.Position]); + this.Position++; + } + + /// + /// Appends the digit pair at the current position as one ASCII codeword. + /// + private void AppendDigitPair() + { + // Table 2: the pairs 00 to 99 are the codewords 130 to 229. + int position = this.Position; + this.AppendCodeword(DigitPairBase + (10 * (this.message[position] - '0')) + this.message[position + 1] - '0'); + this.Position = position + 2; + } + + /// + /// Appends the codewords of whole groups of three C40, Text or X12 values. Each group packs into two codewords as 1600 x C1 + 40 x C2 + C3 + 1. + /// + /// The values, a multiple of three. + private void AppendTriplets(ReadOnlySpan values) + { + for (int i = 0; i < values.Length; i += 3) + { + this.AppendTriplet(values[i], values[i + 1], values[i + 2]); + } + } + + /// + /// Appends the two codewords of one group of three C40, Text or X12 values, packed as 1600 x C1 + 40 x C2 + C3 + 1. + /// + /// The first value. + /// The second value. + /// The third value. + private void AppendTriplet(int c1, int c2, int c3) + { + // The three base 40 values form one number from 1 to 64000, which fits in 16 bits and is written as two codewords, the high byte first. + int packed = (1600 * c1) + (40 * c2) + c3 + 1; + this.AppendCodeword(packed >> 8); + this.AppendCodeword(packed & 0xFF); + } + + /// + /// Appends the codewords of EDIFACT values. Each group of four six-bit values packs into three codewords, and a group of fewer values packs into the + /// codewords its bits need. + /// + /// The six-bit values. + private void AppendEdifact(ReadOnlySpan values) + { + // A group of n values carries 6 x n bits and takes the whole codewords that hold them. The values missing from a short group are zero. + for (int i = 0; i < values.Length; i += 4) + { + // The four values fill a 24 bit number, the first value in the top six bits, so the three codewords are bits 23 to 16, 15 to 8 and 7 to 0. + int count = Math.Min(4, values.Length - i); + int packed = 0; + for (int j = 0; j < 4; j++) + { + packed = (packed << 6) | (j < count ? values[i + j] : 0); + } + + // One value takes 1 codeword, two values 2 and three or four values 3. + int bytes = ((count * 6) + 7) / 8; + for (int j = 0; j < bytes; j++) + { + this.AppendCodeword((packed >> (16 - (8 * j))) & 0xFF); + } + } + } + + /// + /// Returns the number of codewords left in the smallest allowed symbol that holds more than the given number of codewords, counted from that number. + /// + /// The number of codewords. + /// The codewords left, or when no allowed symbol holds more. + /// The count reaches the capacity of the largest symbol. + private readonly int Remaining(int written) + { + if (written >= MaximumCodewords) + { + ThrowTooLong(); + } + + // The symbol that the data ends in is the smallest one with room for one more codeword than is written. The end rules compare against the room left + // in it. + int best = NoSymbol; + foreach (int capacity in this.capacities) + { + if (capacity > written && capacity < best) + { + best = capacity; + } + } + + return best == NoSymbol ? NoSymbol : best - written; + } + + /// + /// The look-ahead test. Each scheme keeps a count of the codewords it needs for the data read so far, starting with the cost of the latch to it. After + /// at least characters, one scheme can win early by a clear codeword. At the end of the data the smallest whole count + /// wins. + /// + /// The encodation scheme for the data at the current position. + private readonly EncodationScheme LookAhead() + { + // The C40 prefix fixes the scheme for its characters, whatever the counts say. + int position = this.Position; + if (position < this.C40Prefix) + { + return EncodationScheme.C40; + } + + // Base 256 cannot carry a Function 1 Symbol Character. When the test chooses Base 256 at one, it returns ASCII instead. + ReadOnlySpan message = this.message; + int length = message.Length; + bool fnc1 = message[position] == Fnc1; + + // Counts are in twelfths of a codeword. A scheme other than the current one starts one codeword dearer for its latch, and leaving any scheme but + // ASCII costs one more for the unlatch. Base 256 starts a quarter codeword dearer again, for its length field. + // + // The count of each scheme but ASCII is its start value plus the costs of the characters from the position. So the count after the characters + // before index n is an offset plus the running sum at n, and the offsets are fixed for the whole test. Stage 1 below sets the ASCII offset. + EncodationScheme mode = this.Mode; + int latch = mode == EncodationScheme.Ascii ? Unit : 2 * Unit; + int ascii = mode == EncodationScheme.Ascii ? 0 : Unit; + SchemeCounts offsets = new() + { + C40 = (mode == EncodationScheme.C40 ? 0 : latch) - this.c40Sums[position], + Text = (mode == EncodationScheme.Text ? 0 : latch) - this.textSums[position], + X12 = (mode == EncodationScheme.X12 ? 0 : latch) - this.x12Sums[position], + Edifact = (mode == EncodationScheme.Edifact ? 0 : latch) - this.edifactSums[position], + Base256 = (mode == EncodationScheme.Base256 ? 0 : latch + (Unit / 4)) - this.base256Sums[position], + }; + + // The test reads the data in three stages. Each stage ends the test when a scheme wins early. The index is the number of characters read, so the + // counts stand after the character before it. + // + // Stage 1 reads one character at a time. The test cannot end early before the fourth character. The ASCII count does not follow its running sum + // where the position is inside a digit run of odd length, because ASCII rounds its count up to a whole codeword before a character that is not a + // digit. So this stage keeps the ASCII count itself and sets the ASCII offset from it after each character, and goes on until the ASCII count and + // its running sum stand at the same part of a codeword. Rounding up keeps a difference of whole codewords, so from there the ASCII offset is fixed + // too. Where stage 1 reads to the end of the data, the offset gives the ASCII count at the end only, which is the one count that the end of the + // test reads. + ReadOnlySpan asciiSums = this.asciiSums; + int index = position; + EncodationScheme choice; + do + { + ascii = AddAscii(ascii, message[index]); + index++; + offsets.Ascii = ascii - asciiSums[index]; + if (index - position >= MinimumLookAhead && this.TryEarlyChoice(this.CountsAt(offsets, index), index, fnc1, out choice)) + { + return choice; + } + } + while (index < length && (index - position < MinimumLookAhead || offsets.Ascii % Unit != 0)); + + // Stage 2 reads a vector of positions at a time. Lane i holds the counts after the character at index + i, which are the offsets plus the running + // sums at index + i + 1. The vectors a, c, t, x, e and b hold the ASCII, C40, Text, X12, EDIFACT and Base 256 counts. A mask lane is all ones where + // the early test of that scheme holds, with the same comparisons as TryEarlyChoice. The C40 mask also holds where the C40 and X12 counts are equal, + // because the X12 terminator decides that case. So TryEarlyChoice runs again for each lane that a mask holds, in lane order, and gives the choice. + if (Vector.IsHardwareAccelerated) + { + int lanes = Vector.Count; + Vector unit = new(Unit); + Vector asciiOffsets = new(offsets.Ascii); + Vector c40Offsets = new(offsets.C40); + Vector textOffsets = new(offsets.Text); + Vector x12Offsets = new(offsets.X12); + Vector edifactOffsets = new(offsets.Edifact); + Vector base256Offsets = new(offsets.Base256); + for (; index + lanes <= length; index += lanes) + { + int next = index + 1; + Vector a = asciiOffsets + new Vector(asciiSums[next..]); + Vector c = c40Offsets + new Vector(this.c40Sums[next..]); + Vector t = textOffsets + new Vector(this.textSums[next..]); + Vector x = x12Offsets + new Vector(this.x12Sums[next..]); + Vector e = edifactOffsets + new Vector(this.edifactSums[next..]); + Vector b = base256Offsets + new Vector(this.base256Sums[next..]); + + // Each count plus one codeword, for the margin that a winning scheme needs. + Vector au = a + unit; + Vector cu = c + unit; + Vector tu = t + unit; + Vector xu = x + unit; + Vector eu = e + unit; + Vector bu = b + unit; + + Vector asciiWins = Vector.LessThanOrEqual(au, c) & Vector.LessThanOrEqual(au, t) + & Vector.LessThanOrEqual(au, x) & Vector.LessThanOrEqual(au, e) & Vector.LessThanOrEqual(au, b); + Vector base256Wins = Vector.LessThanOrEqual(bu, a) + | (Vector.LessThan(bu, c) & Vector.LessThan(bu, t) & Vector.LessThan(bu, x) & Vector.LessThan(bu, e)); + Vector edifactWins = Vector.LessThan(eu, a) & Vector.LessThan(eu, c) & Vector.LessThan(eu, t) + & Vector.LessThan(eu, x) & Vector.LessThan(eu, b); + Vector textWins = Vector.LessThan(tu, a) & Vector.LessThan(tu, c) & Vector.LessThan(tu, x) + & Vector.LessThan(tu, e) & Vector.LessThan(tu, b); + Vector x12Wins = Vector.LessThan(xu, a) & Vector.LessThan(xu, c) & Vector.LessThan(xu, t) + & Vector.LessThan(xu, e) & Vector.LessThan(xu, b); + Vector c40Wins = Vector.LessThan(cu, a) & Vector.LessThan(cu, t) & Vector.LessThan(cu, e) + & Vector.LessThan(cu, b) & Vector.LessThanOrEqual(c, x); + + Vector wins = asciiWins | base256Wins | edifactWins | textWins | x12Wins | c40Wins; + if (wins == Vector.Zero) + { + continue; + } + + for (int lane = 0; lane < lanes; lane++) + { + int read = next + lane; + if (wins[lane] != 0 && this.TryEarlyChoice(this.CountsAt(offsets, read), read, fnc1, out choice)) + { + return choice; + } + } + } + } + + // Stage 3 reads the characters that stage 2 left, one at a time. Without vector hardware this is all the characters after stage 1. + while (index < length) + { + index++; + if (this.TryEarlyChoice(this.CountsAt(offsets, index), index, fnc1, out choice)) + { + return choice; + } + } + + // At the end of the data every count rounds up to whole codewords, because a part of a codeword still takes a codeword. ASCII wins a tie, then each + // scheme wins only by being strictly smallest, in the order Base 256, EDIFACT, Text and X12, and C40 takes what is left. + SchemeCounts end = this.CountsAt(offsets, length); + ascii = Ceiling(end.Ascii); + int c40 = Ceiling(end.C40); + int text = Ceiling(end.Text); + int x12 = Ceiling(end.X12); + int edifact = Ceiling(end.Edifact); + int base256 = Ceiling(end.Base256); + if (ascii <= c40 && ascii <= text && ascii <= x12 && ascii <= edifact && ascii <= base256) + { + return EncodationScheme.Ascii; + } + + if (base256 < ascii && base256 < c40 && base256 < text && base256 < x12 && base256 < edifact) + { + return fnc1 ? EncodationScheme.Ascii : EncodationScheme.Base256; + } + + if (edifact < ascii && edifact < c40 && edifact < text && edifact < x12 && edifact < base256) + { + return EncodationScheme.Edifact; + } + + if (text < ascii && text < c40 && text < x12 && text < edifact && text < base256) + { + return EncodationScheme.Text; + } + + if (x12 < ascii && x12 < c40 && x12 < text && x12 < edifact && x12 < base256) + { + return EncodationScheme.X12; + } + + return EncodationScheme.C40; + } + + /// + /// Returns the look-ahead counts after the characters before an index: the offsets of the test plus the running sums at the index. + /// + /// The offsets of the look-ahead test. + /// The number of characters read. + /// The count of each scheme. + private readonly SchemeCounts CountsAt(SchemeCounts offsets, int index) => new() + { + Ascii = offsets.Ascii + this.asciiSums[index], + C40 = offsets.C40 + this.c40Sums[index], + Text = offsets.Text + this.textSums[index], + X12 = offsets.X12 + this.x12Sums[index], + Edifact = offsets.Edifact + this.edifactSums[index], + Base256 = offsets.Base256 + this.base256Sums[index], + }; + + /// + /// The early test of the look-ahead test, from the fourth character. ASCII wins when it is at least one codeword cheaper than every other scheme. + /// Base 256 wins when it is at least one codeword cheaper than ASCII, or more than one codeword cheaper than C40, Text, X12 and EDIFACT. EDIFACT, + /// Text and X12 win when they are more than one codeword cheaper than every other scheme. C40 wins when it is more than one codeword cheaper than + /// ASCII, Text, EDIFACT and Base 256, and not dearer than X12. + /// + /// The count of each scheme. + /// The number of characters read: the counts stand after the character before it. + /// Whether the test started at a Function 1 Symbol Character, which Base 256 cannot carry. + /// The winning scheme, when one wins. + /// when a scheme wins at this position. + private readonly bool TryEarlyChoice(SchemeCounts counts, int read, bool fnc1, out EncodationScheme scheme) + { + int ascii = counts.Ascii; + int c40 = counts.C40; + int text = counts.Text; + int x12 = counts.X12; + int edifact = counts.Edifact; + int base256 = counts.Base256; + if (ascii + Unit <= c40 && ascii + Unit <= text && ascii + Unit <= x12 && ascii + Unit <= edifact && ascii + Unit <= base256) + { + scheme = EncodationScheme.Ascii; + return true; + } + + if (base256 + Unit <= ascii || (base256 + Unit < c40 && base256 + Unit < text && base256 + Unit < x12 && base256 + Unit < edifact)) + { + scheme = fnc1 ? EncodationScheme.Ascii : EncodationScheme.Base256; + return true; + } + + if (edifact + Unit < ascii && edifact + Unit < c40 && edifact + Unit < text && edifact + Unit < x12 && edifact + Unit < base256) + { + scheme = EncodationScheme.Edifact; + return true; + } + + if (text + Unit < ascii && text + Unit < c40 && text + Unit < x12 && text + Unit < edifact && text + Unit < base256) + { + scheme = EncodationScheme.Text; + return true; + } + + if (x12 + Unit < ascii && x12 + Unit < c40 && x12 + Unit < text && x12 + Unit < edifact && x12 + Unit < base256) + { + scheme = EncodationScheme.X12; + return true; + } + + // C40 wins outright below the X12 count. With equal counts X12 wins when an X12 terminator comes before a character X12 cannot encode. + if (c40 + Unit < ascii && c40 + Unit < text && c40 + Unit < edifact && c40 + Unit < base256 && c40 <= x12) + { + scheme = c40 == x12 && this.terminator[read] < this.nonX12[read] ? EncodationScheme.X12 : EncodationScheme.C40; + return true; + } + + scheme = default; + return false; + } + } + + /// + /// The look-ahead counts of the six encodation schemes at one position, in units of . The look-ahead test also keeps its fixed offsets in + /// this form. + /// + private struct SchemeCounts + { + /// + /// The ASCII count. + /// + public int Ascii; + + /// + /// The C40 count. + /// + public int C40; + + /// + /// The Text count. + /// + public int Text; + + /// + /// The X12 count. + /// + public int X12; + + /// + /// The EDIFACT count. + /// + public int Edifact; + + /// + /// The Base 256 count. + /// + public int Base256; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataMatrix/DataMatrixEncoder.cs b/src/ImageSharp.Drawing/Barcodes/DataMatrix/DataMatrixEncoder.cs new file mode 100644 index 00000000..96c2563c --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataMatrix/DataMatrixEncoder.cs @@ -0,0 +1,713 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.InteropServices; +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Builds Data Matrix ECC 200 symbols: the symbol size, the pad codewords, the Reed-Solomon error correction codewords, the placement of the codewords in the +/// mapping matrix, and the finder and alignment patterns around the data regions. +/// +internal static class DataMatrixEncoder +{ + /// + /// The largest number of message values a symbol carries: 3116 digits in the 144 x 144 symbol. + /// + public const int MaximumLength = 3116; + + /// + /// The quiet zone on each of the four sides in modules, section 5.6.2 of the GS1 General Specifications: "one module wide on all four sides". + /// + private const int QuietZone = 1; + + /// + /// The most modules a mapping matrix holds: the 132 x 132 of the 144 x 144 symbol. + /// + private const int MaximumMappingModules = 132 * 132; + + /// + /// The most codewords a symbol holds, data and error correction: one for every eight modules of the largest mapping matrix. + /// + private const int MaximumTotalCodewords = MaximumMappingModules / 8; + + /// + /// The number of symbol sizes in . + /// + private const int SizeCount = 48; + + /// + /// The number of values of one symbol size in . + /// + private const int SizeFields = 7; + + /// + /// The field of the error correction: GF(2^8) with the primitive polynomial x^8 + x^5 + x^3 + x^2 + 1, 301 decimal. + /// + private static readonly GaloisField Field = new(256, 301); + + /// + /// Gets the symbol sizes, values each: the rows and the columns with the finder pattern, the data regions down and across, the + /// error correction codewords, the interleaved Reed-Solomon blocks, and 1 for a size of the rectangular extension only. The square sizes and the first six + /// rectangular sizes are ISO/IEC 16022 Table 7, which Tables 5-32 and 5-33 of the GS1 General Specifications repeat. The other rectangular sizes are the + /// rectangular extension, which only takes. A data codeword count that two sizes share goes to the size + /// listed first. + /// + private static ReadOnlySpan SizeTable => + [ + 10, 10, 1, 1, 5, 1, 0, + 12, 12, 1, 1, 7, 1, 0, + 14, 14, 1, 1, 10, 1, 0, + 16, 16, 1, 1, 12, 1, 0, + 18, 18, 1, 1, 14, 1, 0, + 20, 20, 1, 1, 18, 1, 0, + 22, 22, 1, 1, 20, 1, 0, + 24, 24, 1, 1, 24, 1, 0, + 26, 26, 1, 1, 28, 1, 0, + 32, 32, 2, 2, 36, 1, 0, + 36, 36, 2, 2, 42, 1, 0, + 40, 40, 2, 2, 48, 1, 0, + 44, 44, 2, 2, 56, 1, 0, + 48, 48, 2, 2, 68, 1, 0, + 52, 52, 2, 2, 84, 2, 0, + 64, 64, 4, 4, 112, 2, 0, + 72, 72, 4, 4, 144, 4, 0, + 80, 80, 4, 4, 192, 4, 0, + 88, 88, 4, 4, 224, 4, 0, + 96, 96, 4, 4, 272, 4, 0, + 104, 104, 4, 4, 336, 6, 0, + 120, 120, 6, 6, 408, 6, 0, + 132, 132, 6, 6, 496, 8, 0, + 144, 144, 6, 6, 620, 10, 0, + 8, 18, 1, 1, 7, 1, 0, + 8, 32, 1, 2, 11, 1, 0, + 8, 48, 1, 2, 15, 1, 1, + 8, 64, 1, 4, 18, 1, 1, + 8, 80, 1, 4, 22, 1, 1, + 8, 96, 1, 4, 28, 1, 1, + 8, 120, 1, 6, 32, 1, 1, + 8, 144, 1, 6, 36, 1, 1, + 12, 26, 1, 1, 14, 1, 0, + 12, 36, 1, 2, 18, 1, 0, + 12, 64, 1, 4, 27, 1, 1, + 12, 88, 1, 4, 36, 1, 1, + 16, 36, 1, 2, 24, 1, 0, + 16, 48, 1, 2, 28, 1, 0, + 16, 64, 1, 4, 36, 1, 1, + 20, 36, 1, 2, 28, 1, 1, + 20, 44, 1, 2, 34, 1, 1, + 20, 64, 1, 4, 42, 1, 1, + 22, 48, 1, 2, 38, 1, 1, + 24, 48, 1, 2, 41, 1, 1, + 24, 64, 1, 4, 46, 1, 1, + 26, 40, 1, 2, 38, 1, 1, + 26, 48, 1, 2, 42, 1, 1, + 26, 64, 1, 4, 50, 1, 1, + ]; + + /// + /// Returns whether the format allows a symbol of the given size. + /// + /// The number of rows. + /// The number of columns. + /// The symbol format. + /// when the size is a size of the format. + public static bool IsSize(int rows, int columns, DataMatrixFormat format) + { + for (int i = 0; i < SizeCount; i++) + { + DataMatrixSize size = new(i); + if (size.Rows == rows && size.Columns == columns && size.Allows(format)) + { + return true; + } + } + + return false; + } + + /// + /// Encodes the text into a symbol with the quiet zone of section 5.6.2 of the GS1 General Specifications. + /// + /// The data, the characters U+0000 to U+00FF. + /// Whether the data is GS1 element strings, which start with the Function 1 Symbol Character and carry it for each + /// . + /// The symbol format. + /// The number of rows, or 0 to choose the size from the data. + /// The number of columns, or 0 to choose the size from the data. + /// The X dimension in millimetres, . + /// The symbol to write. + /// The data is not valid or does not fit the symbol. + public static void BuildSymbol( + ReadOnlySpan text, + bool gs1, + DataMatrixFormat format, + int rows, + int columns, + double xDimension, + ref BarcodeSymbol symbol) + => BuildSymbol(text, gs1, format, rows, columns, 0, QuietZone, xDimension, ref symbol); + + /// + /// Encodes the text into a symbol. + /// + /// The data, the characters U+0000 to U+00FF. + /// Whether the data is GS1 element strings, which start with the Function 1 Symbol Character and carry it for each + /// . + /// The symbol format. + /// The number of rows, or 0 to choose the size from the data. + /// The number of columns, or 0 to choose the size from the data. + /// The number of characters at the start of the data that encode in C40 encodation, or 0. + /// The quiet zone on each of the four sides, in modules. + /// The X dimension in millimetres, . + /// The symbol to write. + /// The data is not valid or does not fit the symbol. + public static void BuildSymbol( + ReadOnlySpan text, + bool gs1, + DataMatrixFormat format, + int rows, + int columns, + int c40Prefix, + int quietZone, + double xDimension, + ref BarcodeSymbol symbol) + { + // A message in format 05 or 06, between the header [)>RS05GS or [)>RS06GS and the trailer RS EOT, takes one Macro character for the header and the + // trailer. The length limit applies to the message values, so it counts the Macro character, not the header and the trailer. + bool macro = !gs1 && MacroMessage.Format(text) != 0; + + Guard.MustBeGreaterThan(text.Length, 0, nameof(text)); + Guard.MustBeLessThanOrEqualTo( + macro ? text.Length - MacroMessage.HeaderLength - MacroMessage.TrailerLength + 1 : text.Length, + MaximumLength, + nameof(text)); + + // Clause 5.2.2 of ISO/IEC 16022: the default character set is ISO/IEC 8859-1, so each character must be one byte value. One vectorized search finds the + // first character above U+00FF, and the message reports a surrogate pair as one character. + int wide = text.IndexOfAnyExceptInRange('\0', (char)byte.MaxValue); + if (wide >= 0) + { + throw new ArgumentException( + $"Data Matrix carries only the characters U+0000 to U+00FF; got {text.CodePointAt(wide).ToDisplayString()}.", + nameof(text)); + } + + // The message is the data as int values, so the function characters can take negative values that no byte has. It holds every character and at most one + // function character in front. One rented block holds the message, the codewords of the largest symbol, the scratch of the encodation and the mapping + // matrix of the largest symbol, so the symbol rents once. The text is at most MaximumLength characters, so the block is bounded. + int messageCapacity = text.Length + 1; + int scratchLength = DataMatrixCodewords.ScratchLength(messageCapacity); + int mappingLength = MaximumMappingModules / sizeof(int); + PooledBuffer block = new(messageCapacity + MaximumTotalCodewords + scratchLength + mappingLength); + try + { + Span messageBuffer = block.Span[..messageCapacity]; + Span codewords = block.Span.Slice(messageCapacity, MaximumTotalCodewords); + Span scratch = block.Span.Slice(messageCapacity + MaximumTotalCodewords, scratchLength); + Span mapping = MemoryMarshal.Cast(block.Span.Slice(messageCapacity + MaximumTotalCodewords + scratchLength, mappingLength)); + + // GS1 data starts with the Function 1 Symbol Character, and each separator is one too. Section 5.6.2 of the GS1 General Specifications transmits + // either as . The element strings carry no of their own, so every in the prepared data is a separator. + int messageLength = 0; + if (gs1) + { + messageBuffer[messageLength++] = DataMatrixCodewords.Fnc1; + foreach (char c in text) + { + messageBuffer[messageLength++] = c == Gs1Data.Separator ? DataMatrixCodewords.Fnc1 : c; + } + } + else + { + if (macro) + { + messageBuffer[messageLength++] = text[5] == '5' ? DataMatrixCodewords.Macro05 : DataMatrixCodewords.Macro06; + text = text[MacroMessage.HeaderLength..^MacroMessage.TrailerLength]; + } + + foreach (char c in text) + { + messageBuffer[messageLength++] = c; + } + } + + // The sizes the data can take, in table order: the index of each and its data codeword capacity. The encoder needs the capacities, because its end + // rules depend on how many codewords are left in the symbol the data ends in. A fixed size leaves one capacity. The table has SizeCount sizes, so + // 64 places hold them all. + InlineArray64 allowedStorage = default; + InlineArray64 capacityStorage = default; + Span allowed = allowedStorage; + Span capacities = capacityStorage; + int allowedCount = 0; + for (int i = 0; i < SizeCount; i++) + { + DataMatrixSize size = new(i); + if (size.Allows(format) && (rows == 0 || (size.Rows == rows && size.Columns == columns))) + { + allowed[allowedCount] = i; + capacities[allowedCount] = size.DataCodewords; + allowedCount++; + } + } + + int dataLength = DataMatrixCodewords.Encode( + messageBuffer[..messageLength], + capacities[..allowedCount], + c40Prefix, + codewords[..DataMatrixCodewords.MaximumCodewords], + scratch); + + // The symbol is the smallest that holds the data codewords. Of two sizes with the same capacity, the one listed first wins. + int chosen = -1; + int chosenCapacity = int.MaxValue; + for (int i = 0; i < allowedCount; i++) + { + if (capacities[i] >= dataLength && capacities[i] < chosenCapacity) + { + chosen = allowed[i]; + chosenCapacity = capacities[i]; + } + } + + if (chosen < 0) + { + throw new ArgumentException($"The data takes {dataLength} data codewords, more than the Data Matrix symbol holds.", nameof(text)); + } + + // The pad characters fill the data codewords the data leaves empty. The first pad character is 129. The others take the 253-state randomising, + // which adds ((149 x position) mod 253) + 1 for the position of the codeword counted from 1, and wraps above 254, so a run of pads does not repeat + // one module pattern. + DataMatrixSize chosenSize = new(chosen); + Span symbolCodewords = codewords[..(chosenCapacity + chosenSize.ErrorCodewords)]; + for (int i = dataLength; i < chosenCapacity; i++) + { + int pad = DataMatrixCodewords.Pad; + if (i > dataLength) + { + pad += ((149 * (i + 1)) % 253) + 1; + if (pad > 254) + { + pad -= 254; + } + } + + symbolCodewords[i] = pad; + } + + AppendErrorCorrection(chosenSize, symbolCodewords); + + // The codewords fill the mapping matrix, the data regions without their finder and alignment patterns, and the frame then puts the patterns around + // each region. + Span matrix = mapping[..(chosenSize.MappingRows * chosenSize.MappingColumns)]; + new MappingMatrix(matrix, chosenSize.MappingRows, chosenSize.MappingColumns).Place(symbolCodewords); + Frame(chosenSize, matrix, symbol.BeginMatrix(chosenSize.Columns, chosenSize.Rows, quietZone, xDimension)); + } + finally + { + block.Dispose(); + } + } + + /// + /// Writes the error correction codewords after the data codewords. The data codewords interleave across the blocks, codeword i in block i mod b, and so do + /// the error correction codewords. + /// + /// The symbol size. + /// The data codewords, followed by room for the error correction codewords. + private static void AppendErrorCorrection(DataMatrixSize size, Span codewords) + { + // Every block has the same number of check codewords, so one generator polynomial serves all blocks. Its roots are alpha^1 to alpha^k. The largest + // block takes 68 check codewords, the 48 x 48, 96 x 96 and 120 x 120 sizes, so the generator and the check codewords of one block are inline. + int blocks = size.Blocks; + int dataCount = size.DataCodewords; + int perBlock = size.ErrorCodewords / blocks; + InlineArray68 generatorStorage = default; + Span generator = ((Span)generatorStorage)[..perBlock]; + GaloisField field = Field; + field.Generator(1, generator); + + // The data codewords divide evenly over the blocks in every size but the 144 x 144 symbol. Its 1558 data codewords leave the first 8 of its 10 blocks + // one codeword longer, 156 against 155, section 5.6.3.2 of the GS1 General Specifications. + int shortLength = dataCount / blocks; + int longBlocks = dataCount % blocks; + + // The check codewords of one block are calculated into an inline buffer, then written to their interleaved places. They go after the data codewords, so + // the division of the next block still reads only data codewords. + InlineArray68 checkStorage = default; + Span check = ((Span)checkStorage)[..perBlock]; + Span target = codewords[dataCount..]; + for (int block = 0; block < blocks; block++) + { + // The data codewords of block b are at b, b + blocks, b + 2 x blocks and so on, so the division reads them in place with a step of the block count. + int count = block < longBlocks ? shortLength + 1 : shortLength; + field.Remainder(codewords[block..], blocks, count, generator, check); + + // Check codeword j of a block goes to index j x blocks plus the column of the block, the same interleave as the data. The column of a block is the + // block number, except in the 144 x 144 symbol, which interleaves the check codewords of its two short blocks first, then those of its eight long + // blocks. With no long blocks the rotation leaves every column at its block number. + int column = (block + blocks - longBlocks) % blocks; + for (int j = 0; j < perBlock; j++) + { + target[(j * blocks) + column] = check[j]; + } + } + } + + /// + /// Builds the module grid: each data region of the mapping matrix with its finder and alignment patterns. Each region has a solid dark column on its left + /// and a solid dark row at its bottom, and alternating modules on its top row and its right column, which start dark at the top left. + /// + /// The symbol size. + /// The mapping matrix, row by row, every module 0 or 1. + /// The modules to write row by row, all light on entry. + private static void Frame(DataMatrixSize size, ReadOnlySpan matrix, Span modules) + { + // Each data region takes regionRows x regionColumns modules of the mapping matrix, and the patterns add one row above and below it and one column left + // and right of it. The symbol is these framed regions side by side, so the position in a framed region tells which part a module is. + int rows = size.Rows; + int columns = size.Columns; + int regionRows = size.MappingRows / size.RegionsDown; + int regionColumns = size.MappingColumns / size.RegionsAcross; + + // The mapping matrix modules come out in reading order, region row by region row, so one running index reads them. + int next = 0; + for (int r = 0; r < rows; r++) + { + Span row = modules.Slice(r * columns, columns); + int regionRow = r % (regionRows + 2); + + // The top row of a region alternates, dark in every even column. The symbol width is even, so the last column of the top row is light. + if (regionRow == 0) + { + for (int c = 0; c < columns; c += 2) + { + row[c] = true; + } + + continue; + } + + // The bottom row of a region is solid. + if (regionRow == regionRows + 1) + { + row.Fill(true); + continue; + } + + // Inside a region row, each framed region is a solid left column, the regionColumns modules of the mapping matrix, and a right column that + // alternates, dark in every odd row. + bool rightColumn = (r & 1) == 1; + for (int c = 0; c < columns; c += regionColumns + 2) + { + row[c] = true; + Span region = row.Slice(c + 1, regionColumns); + for (int i = 0; i < region.Length; i++) + { + region[i] = matrix[next++] == 1; + } + + row[c + regionColumns + 1] = rightColumn; + } + } + } + + /// + /// The mapping matrix of a symbol, row by row: the data regions without their finder and alignment patterns. A module holds -1 until a bit is placed in it, + /// then 0 or 1. + /// + private readonly ref struct MappingMatrix + { + /// + /// The modules, row by row. + /// + private readonly Span modules; + + /// + /// The number of rows of the mapping matrix. + /// + private readonly int rows; + + /// + /// The number of columns of the mapping matrix. + /// + private readonly int columns; + + /// + /// Initializes a new instance of the struct. + /// + /// The modules, row by row, x long. + /// The number of rows of the mapping matrix. + /// The number of columns of the mapping matrix. + public MappingMatrix(Span modules, int rows, int columns) + { + this.modules = modules; + this.rows = rows; + this.columns = columns; + } + + /// + /// Places the codewords. Each codeword fills an eight module shape, and the shapes follow diagonal sweeps up and to the right and then down and to the + /// left, with special shapes in the corners. A bit of 1 is a dark module. Every module holds 0 or 1 afterwards. + /// + /// The codewords in placement order. + public void Place(ReadOnlySpan codewords) + { + // The placement starts at row 4, column 0, the lower right module of the first standard shape, and walks the matrix in diagonal sweeps. A module + // that is not -1 already holds a bit, so a shape is placed only where its lower right module is still empty. + Span matrix = this.modules; + int rows = this.rows; + int columns = this.columns; + matrix.Fill(-1); + int next = 0; + int row = 4; + int column = 0; + do + { + // The four corner shapes, each as the row and the column of its eight modules. Each one applies only at the position and the matrix width it is + // listed with, where the standard shape does not fit whole. + if (row == rows && column == 0) + { + this.PlaceCorner( + codewords[next++], + [ + rows - 1, 0, rows - 1, 1, rows - 1, 2, 0, columns - 2, + 0, columns - 1, 1, columns - 1, 2, columns - 1, 3, columns - 1 + ]); + } + + if (row == rows - 2 && column == 0 && columns % 4 != 0) + { + this.PlaceCorner( + codewords[next++], + [ + rows - 3, 0, rows - 2, 0, rows - 1, 0, 0, columns - 4, + 0, columns - 3, 0, columns - 2, 0, columns - 1, 1, columns - 1 + ]); + } + + if (row == rows - 2 && column == 0 && columns % 8 == 4) + { + this.PlaceCorner( + codewords[next++], + [ + rows - 3, 0, rows - 2, 0, rows - 1, 0, 0, columns - 2, + 0, columns - 1, 1, columns - 1, 2, columns - 1, 3, columns - 1 + ]); + } + + if (row == rows + 4 && column == 2 && columns % 8 == 0) + { + this.PlaceCorner( + codewords[next++], + [ + rows - 1, 0, rows - 1, columns - 1, 0, columns - 3, 0, columns - 2, + 0, columns - 1, 1, columns - 3, 1, columns - 2, 1, columns - 1 + ]); + } + + // Sweep up and to the right, two rows up and two columns right per shape, until the sweep leaves the top or the right edge. A position below + // the matrix or left of it can come up at the start of a sweep and is skipped. + do + { + if (row < rows && column >= 0 && matrix[(row * columns) + column] < 0) + { + this.PlaceShape(row, column, codewords[next++]); + } + + row -= 2; + column += 2; + } + while (row >= 0 && column < columns); + + // The next sweep starts one row down and three columns right of where this one left the matrix, so the sweeps interlock without a gap. + row++; + column += 3; + + // Sweep down and to the left, the mirror of the sweep above. + do + { + if (row >= 0 && column < columns && matrix[(row * columns) + column] < 0) + { + this.PlaceShape(row, column, codewords[next++]); + } + + row += 2; + column -= 2; + } + while (row < rows && column >= 0); + + row += 3; + column++; + } + while (row < rows || column < columns); + + // A mapping matrix whose module count is 4 more than a multiple of 8 has four modules no codeword takes, in the bottom right corner. They take a + // fixed pattern with the corner module and the module diagonally above it dark. + int last = (rows * columns) - 1; + if (matrix[last] < 0) + { + matrix[last] = 1; + matrix[last - 1] = 0; + matrix[last - columns] = 0; + matrix[last - columns - 1] = 1; + } + } + + /// + /// Places one codeword in the standard shape whose lower right module is at the given position: two modules in the row two above, three in the row + /// above and three in the row itself. + /// + /// The row of the lower right module. + /// The column of the lower right module. + /// The codeword. + private void PlaceShape(int row, int column, int codeword) + { + // The bits go from the most significant, at the top left of the shape, row by row to the least significant at its lower right. + this.PlaceBit(row - 2, column - 2, codeword, 0); + this.PlaceBit(row - 2, column - 1, codeword, 1); + this.PlaceBit(row - 1, column - 2, codeword, 2); + this.PlaceBit(row - 1, column - 1, codeword, 3); + this.PlaceBit(row - 1, column, codeword, 4); + this.PlaceBit(row, column - 2, codeword, 5); + this.PlaceBit(row, column - 1, codeword, 6); + this.PlaceBit(row, column, codeword, 7); + } + + /// + /// Places one codeword in a corner shape. + /// + /// The codeword. + /// The row and the column of each of the eight modules, the most significant bit first. + private void PlaceCorner(int codeword, ReadOnlySpan positions) + { + for (int bit = 0; bit < 8; bit++) + { + this.PlaceBit(positions[2 * bit], positions[(2 * bit) + 1], codeword, bit); + } + } + + /// + /// Places one bit of a codeword. A position outside the matrix wraps to the opposite edge, with the offset that keeps the shape whole. + /// + /// The row, which can be outside the matrix. + /// The column, which can be outside the matrix. + /// The codeword. + /// The bit, 0 for the most significant. + private void PlaceBit(int row, int column, int codeword, int bit) + { + // A shape that crosses the top edge continues at the bottom, and one that crosses the left edge continues at the right. The continuation also moves + // along the other axis by 4 - ((n + 4) mod 8), where n is the length of the edge it crossed. + int rows = this.rows; + int columns = this.columns; + if (row < 0) + { + row += rows; + column += 4 - ((rows + 4) % 8); + } + + if (column < 0) + { + column += columns; + row += 4 - ((columns + 4) % 8); + } + + // A rectangular extension symbol of eight rows has six mapping rows, so the column wrap can move a row past the bottom edge. + if (row >= rows) + { + row -= rows; + } + + // Bit 0 is the most significant bit of the codeword, so it is bit 7 of the byte. + this.modules[(row * columns) + column] = (sbyte)((codeword >> (7 - bit)) & 1); + } + } + + /// + /// The dimensions and error correction of one symbol size, read from . + /// + private readonly struct DataMatrixSize + { + /// + /// Initializes a new instance of the struct from one size of . + /// + /// The index of the size in the table, 0 to - 1. + public DataMatrixSize(int index) + { + ReadOnlySpan values = SizeTable.Slice(index * SizeFields, SizeFields); + this.Rows = values[0]; + this.Columns = values[1]; + this.RegionsDown = values[2]; + this.RegionsAcross = values[3]; + this.ErrorCodewords = values[4]; + this.Blocks = values[5]; + this.Extension = values[6] != 0; + } + + /// + /// Gets the number of rows, finder pattern included. + /// + public int Rows { get; } + + /// + /// Gets the number of columns, finder pattern included. + /// + public int Columns { get; } + + /// + /// Gets the number of data regions in a column. + /// + public int RegionsDown { get; } + + /// + /// Gets the number of data regions in a row. + /// + public int RegionsAcross { get; } + + /// + /// Gets the number of error correction codewords. + /// + public int ErrorCodewords { get; } + + /// + /// Gets the number of interleaved Reed-Solomon blocks. + /// + public int Blocks { get; } + + /// + /// Gets a value indicating whether the size belongs to the rectangular extension only. + /// + public bool Extension { get; } + + /// + /// Gets the number of rows of the mapping matrix: the symbol without the two rows of finder and alignment pattern around each data region. + /// + public int MappingRows => this.Rows - (2 * this.RegionsDown); + + /// + /// Gets the number of columns of the mapping matrix. + /// + public int MappingColumns => this.Columns - (2 * this.RegionsAcross); + + /// + /// Gets the number of data codewords: the eight module codewords the mapping matrix holds, less the error correction codewords. + /// + public int DataCodewords => (this.MappingRows * this.MappingColumns / 8) - this.ErrorCodewords; + + /// + /// Returns whether the format allows this size. + /// + /// The symbol format. + /// when the format allows the size. + public bool Allows(DataMatrixFormat format) => format switch + { + DataMatrixFormat.Square => this.Rows == this.Columns, + DataMatrixFormat.Rectangular => this.Rows != this.Columns && !this.Extension, + DataMatrixFormat.RectangularExtension => this.Rows != this.Columns, + DataMatrixFormat.SquareOrRectangular => !this.Extension, + _ => false, + }; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataMatrix/DataMatrixFormat.cs b/src/ImageSharp.Drawing/Barcodes/DataMatrix/DataMatrixFormat.cs new file mode 100644 index 00000000..dc9208f8 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataMatrix/DataMatrixFormat.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The symbol sizes a Data Matrix symbol can take. +/// +internal enum DataMatrixFormat +{ + /// + /// The square sizes, 10 x 10 to 144 x 144. + /// + Square, + + /// + /// The six rectangular sizes, 8 x 18 to 16 x 48. + /// + Rectangular, + + /// + /// The rectangular sizes and the rectangular extension sizes, 8 x 18 to 26 x 64. + /// + RectangularExtension, + + /// + /// The square sizes and the six rectangular sizes. + /// + SquareOrRectangular, +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataMatrix/DataMatrixRectangularExtensionSymbology.cs b/src/ImageSharp.Drawing/Barcodes/DataMatrix/DataMatrixRectangularExtensionSymbology.cs new file mode 100644 index 00000000..58dc6281 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataMatrix/DataMatrixRectangularExtensionSymbology.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Data Matrix ECC 200 symbology with the rectangular extension, ISO/IEC 21471. A symbol is one of the six rectangular sizes of ISO/IEC 16022 or one of the +/// 18 rectangular extension sizes, 8 x 18 to 26 x 64. +/// +public sealed class DataMatrixRectangularExtensionSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. The symbol takes the smallest rectangular size that holds + /// the data. + /// + public DataMatrixRectangularExtensionSymbology() + { + } + + /// + /// Initializes a new instance of the class with a fixed symbol size. + /// + /// The number of rows, finder pattern included. + /// The number of columns, finder pattern included. + /// The size is not a rectangular Data Matrix size. + public DataMatrixRectangularExtensionSymbology(int rows, int columns) + { + if (!DataMatrixEncoder.IsSize(rows, columns, DataMatrixFormat.RectangularExtension)) + { + throw new ArgumentException($"{rows} x {columns} is not a rectangular Data Matrix size.", nameof(rows)); + } + + this.Rows = rows; + this.Columns = columns; + } + + /// + /// Gets the number of rows, or when the size follows from the data. + /// + public int? Rows { get; } + + /// + /// Gets the number of columns, or when the size follows from the data. + /// + public int? Columns { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + DataMatrixEncoder.BuildSymbol( + text, + false, + DataMatrixFormat.RectangularExtension, + this.Rows ?? 0, + this.Columns ?? 0, + options.ResolveXDimension(0, DefaultMatrixXDimension, double.PositiveInfinity), + ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataMatrix/DataMatrixRectangularSymbology.cs b/src/ImageSharp.Drawing/Barcodes/DataMatrix/DataMatrixRectangularSymbology.cs new file mode 100644 index 00000000..8b5edd11 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataMatrix/DataMatrixRectangularSymbology.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Data Matrix ECC 200 symbology, ISO/IEC 16022, in a rectangular size. The symbol takes the smallest of the six rectangular sizes, 8 x 18 to 16 x 48, that +/// holds the data. +/// +public sealed class DataMatrixRectangularSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public DataMatrixRectangularSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + // Clause 8 of ISO/IEC 16022: "the width of a module shall be specified by the application", so the symbology gives no limit. + double xDimension = options.ResolveXDimension(0, DefaultMatrixXDimension, double.PositiveInfinity); + DataMatrixEncoder.BuildSymbol(text, false, DataMatrixFormat.Rectangular, 0, 0, xDimension, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataMatrix/DataMatrixSymbology.cs b/src/ImageSharp.Drawing/Barcodes/DataMatrix/DataMatrixSymbology.cs new file mode 100644 index 00000000..007c9dfa --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataMatrix/DataMatrixSymbology.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Data Matrix ECC 200 symbology, ISO/IEC 16022: a matrix of square modules inside a finder pattern, a solid L on the left and bottom edges and alternating +/// modules on the top and right edges. A symbol is one of 24 square sizes, 10 x 10 to 144 x 144, or six rectangular sizes, 8 x 18 to 16 x 48. It carries the +/// characters U+0000 to U+00FF with Reed-Solomon error correction. +/// +public sealed class DataMatrixSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. The symbol takes the smallest square size that holds the data. + /// + public DataMatrixSymbology() + { + } + + /// + /// Initializes a new instance of the class with a fixed symbol size. + /// + /// The number of rows, finder pattern included. + /// The number of columns, finder pattern included. + /// The size is not a square or rectangular Data Matrix size. + public DataMatrixSymbology(int rows, int columns) + { + if (!DataMatrixEncoder.IsSize(rows, columns, DataMatrixFormat.SquareOrRectangular)) + { + throw new ArgumentException($"{rows} x {columns} is not a square or rectangular Data Matrix size.", nameof(rows)); + } + + this.Rows = rows; + this.Columns = columns; + } + + /// + /// Gets the number of rows, or when the size follows from the data. + /// + public int? Rows { get; } + + /// + /// Gets the number of columns, or when the size follows from the data. + /// + public int? Columns { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + // Clause 8 of ISO/IEC 16022: "the width of a module shall be specified by the application", so the symbology gives no limit. + double xDimension = options.ResolveXDimension(0, DefaultMatrixXDimension, double.PositiveInfinity); + if (this.Rows.HasValue) + { + DataMatrixEncoder.BuildSymbol(text, false, DataMatrixFormat.SquareOrRectangular, this.Rows.Value, this.Columns!.Value, xDimension, ref symbol); + } + else + { + DataMatrixEncoder.BuildSymbol(text, false, DataMatrixFormat.Square, 0, 0, xDimension, ref symbol); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataMatrix/Gs1DataMatrixRectangularSymbology.cs b/src/ImageSharp.Drawing/Barcodes/DataMatrix/Gs1DataMatrixRectangularSymbology.cs new file mode 100644 index 00000000..b961292d --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataMatrix/Gs1DataMatrixRectangularSymbology.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// GS1 DataMatrix, section 5.6 of the GS1 General Specifications, in a rectangular size. The symbol takes the smallest of the six rectangular sizes of Table +/// 5-33, 8 x 18 to 16 x 48, that holds the data, and carries GS1 element strings as does. +/// +/// The input is the element string syntax the standard prints: an Application Identifier in parentheses, then its data, repeated. The symbol does not carry the +/// parentheses. +/// +/// +/// When a font is set, the element strings print below the symbol, one on each line, with the Application Identifiers in parentheses. An element string of +/// Application Identifier (8200) does not print. +/// +/// +/// The X dimension is the target of 0.495 millimetre of Table 5-45, symbol specification table 1 addendum 1. The symbol specification tables of section +/// 5.12.3 give GS1 DataMatrix X dimensions from 0.100 to 3.50 millimetres. An X dimension outside that range draws at the nearest limit. +/// +/// +public sealed class Gs1DataMatrixRectangularSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Gs1DataMatrixRectangularSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray64 bufferStorage = default; + int capacity = Gs1Data.PreparedCapacity(text, DataMatrixEncoder.MaximumLength); + ValueStringBuilder encoded = capacity <= Gs1Data.InlineBufferLength ? new(bufferStorage) : new(capacity); + try + { + Gs1Data.Prepare(text, ref encoded); + DataMatrixEncoder.BuildSymbol( + encoded.AsSpan(), + true, + DataMatrixFormat.Rectangular, + 0, + 0, + options.ResolveXDimension(Gs1Data.Gs1DataMatrixMinimumXDimension, Gs1Data.Gs1MatrixXDimension, Gs1Data.Gs1MatrixMaximumXDimension), + ref symbol); + + if (options.Font is not null) + { + Gs1Data.AddMatrixReadable(text, options.Font, ref symbol); + } + } + finally + { + encoded.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataMatrix/Gs1DataMatrixSymbology.cs b/src/ImageSharp.Drawing/Barcodes/DataMatrix/Gs1DataMatrixSymbology.cs new file mode 100644 index 00000000..1478caaa --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataMatrix/Gs1DataMatrixSymbology.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// GS1 DataMatrix, section 5.6 of the GS1 General Specifications: a Data Matrix ECC 200 symbol in a square size that carries GS1 element strings. The data +/// starts with the Function 1 Symbol Character, and the same character separates an element string of variable length from the next one. +/// +/// The input is the element string syntax the standard prints: an Application Identifier in parentheses, then its data, repeated. For example, +/// (01)09521234543213(17)120508(10)ABCD1234. The symbol does not carry the parentheses. +/// +/// +/// When a font is set, the element strings print below the symbol, one on each line, with the Application Identifiers in parentheses. An element string of +/// Application Identifier (8200) does not print. +/// +/// +/// The X dimension is the target of 0.495 millimetre of Table 5-45, symbol specification table 1 addendum 1. The symbol specification tables of section +/// 5.12.3 give GS1 DataMatrix X dimensions from 0.100 to 3.50 millimetres. An X dimension outside that range draws at the nearest limit. +/// +/// +public sealed class Gs1DataMatrixSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. The symbol takes the smallest square size that holds the data. + /// + public Gs1DataMatrixSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray64 bufferStorage = default; + int capacity = Gs1Data.PreparedCapacity(text, DataMatrixEncoder.MaximumLength); + ValueStringBuilder encoded = capacity <= Gs1Data.InlineBufferLength ? new(bufferStorage) : new(capacity); + try + { + Gs1Data.Prepare(text, ref encoded); + DataMatrixEncoder.BuildSymbol( + encoded.AsSpan(), + true, + DataMatrixFormat.Square, + 0, + 0, + options.ResolveXDimension(Gs1Data.Gs1DataMatrixMinimumXDimension, Gs1Data.Gs1MatrixXDimension, Gs1Data.Gs1MatrixMaximumXDimension), + ref symbol); + + if (options.Font is not null) + { + Gs1Data.AddMatrixReadable(text, options.Font, ref symbol); + } + } + finally + { + encoded.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataMatrix/Gs1DigitalLinkDataMatrixSymbology.cs b/src/ImageSharp.Drawing/Barcodes/DataMatrix/Gs1DigitalLinkDataMatrixSymbology.cs new file mode 100644 index 00000000..6055cd12 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataMatrix/Gs1DigitalLinkDataMatrixSymbology.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// A GS1 Digital Link URI in a Data Matrix symbol of a square size, section 5.9 of the GS1 General Specifications. The symbol carries the URI as plain data. +/// The URI follows the GS1 Digital Link Standard: URI Syntax: a primary identification key and its key qualifiers in the path, data attributes in the query +/// string, and every Application Identifier with valid data. +/// +/// When a font is set, the element strings that the URI carries print below the symbol, one on each line, with the Application Identifiers in parentheses, in +/// the order of the URI. The URI does not print. +/// +/// +/// Table 5-46, symbol specification table 1 addendum 2, gives an X dimension of 0.396 to 0.990 millimetres with a target of 0.495. An X dimension outside +/// that range draws at the nearest limit. +/// +/// +public sealed class Gs1DigitalLinkDataMatrixSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. The symbol takes the smallest square size that holds the URI. + /// + public Gs1DigitalLinkDataMatrixSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + // The URI encodes as plain data, so no URI longer than the largest symbol holds is parsed. + Guard.MustBeLessThanOrEqualTo(text.Length, DataMatrixEncoder.MaximumLength, nameof(text)); + double xDimension = options.ResolveXDimension( + Gs1Data.DigitalLinkMinimumXDimension, + Gs1Data.DigitalLinkXDimension, + Gs1Data.DigitalLinkMaximumXDimension); + + if (options.Font is null) + { + Gs1DigitalLink.Validate(text); + DataMatrixEncoder.BuildSymbol(text, false, DataMatrixFormat.Square, 0, 0, xDimension, ref symbol); + return; + } + + // The element strings print as GS1 DataMatrix prints them, from the values the validation decodes. The URI is bounded above, so the buffer is too, and + // a longer URI rents it once. + InlineArray64 readableStorage = default; + PooledBuffer rented = default; + int capacity = Gs1DigitalLink.ReadableCapacity(text.Length); + Span readable = capacity <= Gs1Data.InlineBufferLength + ? ((Span)readableStorage)[..capacity] + : (rented = new PooledBuffer(capacity)).Span; + try + { + int written = Gs1DigitalLink.Validate(text, readable); + DataMatrixEncoder.BuildSymbol(text, false, DataMatrixFormat.Square, 0, 0, xDimension, ref symbol); + symbol.AddMatrixText(readable[..written], BarcodeTextAlignment.Left, Gs1Data.TextScale(options.Font)); + } + finally + { + rented.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataMatrix/HibcDataMatrixRectangularSymbology.cs b/src/ImageSharp.Drawing/Barcodes/DataMatrix/HibcDataMatrixRectangularSymbology.cs new file mode 100644 index 00000000..f6e9d5d3 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataMatrix/HibcDataMatrixRectangularSymbology.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// HIBC Data Matrix in a rectangular size, a Health Industry Bar Code carried by a Data Matrix symbol of one of the six rectangular sizes. The symbol carries +/// the flag character, the data and the modulo 43 check character. +/// The text follows a data structure of ANSI/HIBC 2.6: primary data, secondary data that ends with its link character, or primary and secondary data joined +/// with "/", each with any supplemental data fields. +/// +/// When a font is set, that same string prints below the symbol between delimiters, with a space printed as an underscore and a zero as "Ø". Primary and +/// secondary data that the symbol joins with "/" print on two lines, broken after the "/". When the options set no X dimension, the X dimension +/// is 0.015 inch, from section 5.2 of ANSI/HIBC 2.6. An X dimension under 0.010 inch draws at 0.010 inch, the minimum of the same section. +/// +/// +public sealed class HibcDataMatrixRectangularSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class, calculating the check character from the data. + /// + public HibcDataMatrixRectangularSymbology() + : this(false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the last character of the input is the check character, which the encoder validates against the data, + /// rather than data the encoder appends a check character to. + public HibcDataMatrixRectangularSymbology(bool validateCheckCharacter) + => this.ValidateCheckCharacter = validateCheckCharacter; + + /// + /// Gets a value indicating whether the last character of the input is the check character. + /// + public bool ValidateCheckCharacter { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray64 bufferStorage = default; + int capacity = HibcData.PreparedCapacity(text, DataMatrixEncoder.MaximumLength); + ValueStringBuilder encoded = capacity <= HibcData.InlineBufferLength ? new(bufferStorage) : new(capacity); + try + { + HibcData.Prepare(text, this.ValidateCheckCharacter, ref encoded); + DataMatrixEncoder.BuildSymbol( + encoded.AsSpan(), + false, + DataMatrixFormat.Rectangular, + 0, + 0, + options.ResolveXDimension(HibcData.MinimumMatrixXDimension, HibcData.NominalMatrixXDimension, double.PositiveInfinity), + ref symbol); + + if (options.Font is not null) + { + HibcData.AddTwoDimensionalReadable(encoded.AsSpan(), false, ref symbol); + } + } + finally + { + encoded.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataMatrix/HibcDataMatrixSymbology.cs b/src/ImageSharp.Drawing/Barcodes/DataMatrix/HibcDataMatrixSymbology.cs new file mode 100644 index 00000000..e32725d4 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataMatrix/HibcDataMatrixSymbology.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// HIBC Data Matrix, a Health Industry Bar Code carried by a Data Matrix symbol in a square size. The symbol carries the flag character, the data and the +/// modulo 43 check character. +/// The text follows a data structure of ANSI/HIBC 2.6: primary data, secondary data that ends with its link character, or primary and secondary data joined +/// with "/", each with any supplemental data fields. +/// +/// When a font is set, that same string prints below the symbol between delimiters, with a space printed as an underscore and a zero as "Ø". Primary and +/// secondary data that the symbol joins with "/" print on two lines, broken after the "/". When the options set no X dimension, the X dimension +/// is 0.015 inch, from section 5.2 of ANSI/HIBC 2.6. An X dimension under 0.010 inch draws at 0.010 inch, the minimum of the same section. +/// +/// +public sealed class HibcDataMatrixSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class, calculating the check character from the data. + /// + public HibcDataMatrixSymbology() + : this(false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the last character of the input is the check character, which the encoder validates against the data, + /// rather than data the encoder appends a check character to. + public HibcDataMatrixSymbology(bool validateCheckCharacter) + => this.ValidateCheckCharacter = validateCheckCharacter; + + /// + /// Gets a value indicating whether the last character of the input is the check character. + /// + public bool ValidateCheckCharacter { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray64 bufferStorage = default; + int capacity = HibcData.PreparedCapacity(text, DataMatrixEncoder.MaximumLength); + ValueStringBuilder encoded = capacity <= HibcData.InlineBufferLength ? new(bufferStorage) : new(capacity); + try + { + HibcData.Prepare(text, this.ValidateCheckCharacter, ref encoded); + DataMatrixEncoder.BuildSymbol( + encoded.AsSpan(), + false, + DataMatrixFormat.Square, + 0, + 0, + options.ResolveXDimension(HibcData.MinimumMatrixXDimension, HibcData.NominalMatrixXDimension, double.PositiveInfinity), + ref symbol); + + if (options.Font is not null) + { + HibcData.AddTwoDimensionalReadable(encoded.AsSpan(), false, ref symbol); + } + } + finally + { + encoded.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataMatrix/Mailmark2DSymbology.cs b/src/ImageSharp.Drawing/Barcodes/DataMatrix/Mailmark2DSymbology.cs new file mode 100644 index 00000000..4e7ce052 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataMatrix/Mailmark2DSymbology.cs @@ -0,0 +1,228 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Royal Mail 2D Mailmark barcode of the Mailmark barcode definition document: a Data Matrix ECC 200 symbol of 24 x 24, 32 x 32 or 16 x 48 modules. The +/// text is the 45 characters of the Royal Mail fields of Table 2, each of fixed length and padded with spaces, followed by optional customer content. The Royal +/// Mail fields are the UPU country ID "JGB ", the information type ID, the version ID, the class, the supply chain ID of 7 digits, the item ID of 8 digits, the +/// destination post code plus DPS of 9 characters, the service type, the return to sender post code of 7 characters and 6 reserved spaces. They encode in C40 +/// encodation from the start of the data. +/// +public sealed class Mailmark2DSymbology : BarcodeSymbology +{ + /// + /// The number of characters of the Royal Mail fields, which encode in C40 encodation. + /// + private const int RoyalMailLength = 45; + + /// + /// The clear zone around the symbol in modules, section 2.4: "A clear zone of at least 4 times the module size should appear around 2D Mailmark barcodes." + /// + private const int ClearZone = 4; + + /// + /// The X dimension in millimetres a symbol takes when the caller sets none: 0.5 mm, the module size section 2.4 recommends from the range of 0.5 mm to 0.7 + /// mm. + /// + private const double NominalXDimension = 0.5; + + /// + /// The smallest module size in millimetres. Section 2.4: "The module size of 2D Mailmark barcodes must be in the range of 0.5mm - 0.7mm." + /// + private const double MinimumXDimension = 0.5; + + /// + /// The largest module size in millimetres, section 2.4. + /// + private const double MaximumXDimension = 0.7; + + /// + /// Initializes a new instance of the class. + /// + /// The barcode type, which sets the symbol size. + /// is not a defined value. + public Mailmark2DSymbology(Mailmark2DType type) + { + Guard.MustBeBetweenOrEqualTo((int)type, (int)Mailmark2DType.Type7, (int)Mailmark2DType.Type29, nameof(type)); + this.Type = type; + } + + /// + /// Gets the barcode type. + /// + public Mailmark2DType Type { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + if (text.Length < RoyalMailLength) + { + throw new ArgumentException( + $"A 2D Mailmark barcode starts with the {RoyalMailLength} characters of the Royal Mail fields, space padded; got {text.Length} characters.", + nameof(text)); + } + + ReadOnlySpan fields = text.Slice(0, RoyalMailLength); + + // Section 2.1: every Royal Mail field uses the C40 basic set, the capital letters, the digits and space. + for (int i = 0; i < fields.Length; i++) + { + char c = fields[i]; + if (c is not (' ' or (>= '0' and <= '9') or (>= 'A' and <= 'Z'))) + { + throw new ArgumentException( + $"The Royal Mail fields carry only capital letters, digits and spaces; got '{c}' at position {i + 1}.", + nameof(text)); + } + } + + // Table 2 gives each field a fixed start position and length, so each check reads its slice: positions 1 to 4 the UPU country ID, 5 the information + // type ID, 6 the version ID, 7 the class, 8 to 14 the supply chain ID and 15 to 22 the item ID. The information type ID and the class take values + // listed in Table 2 and are never empty. The version ID is "Currently "1"". + if (!fields.StartsWith("JGB ")) + { + throw new ArgumentException("A 2D Mailmark barcode starts with the UPU country ID \"JGB \".", nameof(text)); + } + + // Table 2: the information type ID is "0", "1", "2", "A", "B" or "C". The class is "0" to "9" or "A" to "Z", every one listed, so only a space breaks + // it. + if (fields[4] is not ('0' or '1' or '2' or 'A' or 'B' or 'C')) + { + throw new ArgumentException($"The information type ID of a 2D Mailmark barcode is 0, 1, 2, A, B or C; got '{fields[4]}'.", nameof(text)); + } + + if (fields[6] == ' ') + { + throw new ArgumentException("The class of a 2D Mailmark barcode is not a space.", nameof(text)); + } + + if (fields[5] != '1') + { + throw new ArgumentException($"The version ID of a 2D Mailmark barcode is \"1\"; got '{fields[5]}'.", nameof(text)); + } + + if (!IsDigits(fields.Slice(7, 7)) || !IsDigits(fields.Slice(14, 8))) + { + throw new ArgumentException("The supply chain ID is 7 digits and the item ID is 8 digits.", nameof(text)); + } + + // Appendix A, Table 14: the destination post code plus DPS, positions 23 to 31, is an outward code alone or with the inward code and the delivery + // point, or spaces. The return to sender post code, positions 33 to 39, is the outward and inward codes, or spaces. + ReadOnlySpan destination = fields.Slice(22, 9); + if (!IsSpaces(destination) && !IsPostcode(destination, true)) + { + throw new ArgumentException($"The destination post code plus DPS is not a format of Appendix A; got \"{destination}\".", nameof(text)); + } + + // Table 2: the service type at position 32 is one of the listed values "0" to "6". + if (fields[31] is < '0' or > '6') + { + throw new ArgumentException($"The service type of a 2D Mailmark barcode is \"0\" to \"6\"; got '{fields[31]}'.", nameof(text)); + } + + ReadOnlySpan returnToSender = fields.Slice(32, 7); + if (!IsSpaces(returnToSender) && !IsPostcode(returnToSender, false)) + { + throw new ArgumentException($"The return to sender post code is not a format of Appendix A; got \"{returnToSender}\".", nameof(text)); + } + + // Table 2: positions 40 to 45 "Must be filled with characters until further notice." + if (!IsSpaces(fields.Slice(39, 6))) + { + throw new ArgumentException("The field reserved by Royal Mail is six spaces.", nameof(text)); + } + + // Section 2 fixes the symbol size of each type. The customer content after the Royal Mail fields takes any encodation, and a fixed size makes the + // encoder reject content that does not fit. + (int rows, int columns) = this.Type switch + { + Mailmark2DType.Type7 => (24, 24), + Mailmark2DType.Type9 => (32, 32), + _ => (16, 48), + }; + + double xDimension = options.ResolveXDimension(MinimumXDimension, NominalXDimension, MaximumXDimension); + + DataMatrixEncoder.BuildSymbol( + text, + false, + DataMatrixFormat.SquareOrRectangular, + rows, + columns, + RoyalMailLength, + ClearZone, + xDimension, + ref symbol); + } + + /// + /// Returns whether every character is a digit. + /// + /// The field. + /// when the field is all digits. + private static bool IsDigits(ReadOnlySpan field) => !field.ContainsAnyExceptInRange('0', '9'); + + /// + /// Returns whether every character is a space. + /// + /// The field. + /// when the field is all spaces. + private static bool IsSpaces(ReadOnlySpan field) => !field.ContainsAnyExcept(' '); + + /// + /// Returns whether a post code field holds a format of Appendix A, Table 14, padded with spaces. The outward code is AN, AAN, ANN, AANN, ANA or AANA, the + /// inward code NAA and the delivery point NA, where A is a capital letter and N a digit. + /// + /// The field. + /// Whether the field is the destination post code plus DPS, which can hold the outward code alone and holds the delivery point + /// after the inward code, rather than the return to sender post code. + /// when the field holds a valid format. + private static bool IsPostcode(ReadOnlySpan field, bool destination) + { + ReadOnlySpan code = field.TrimEnd(' '); + if (destination && code.Length <= 4) + { + return IsOutward(code); + } + + // The inward code and, in the destination field, the delivery point follow the outward code. + int tail = destination ? 5 : 3; + if (code.Length <= tail) + { + return false; + } + + ReadOnlySpan inward = code[^tail..]; + return IsOutward(code[..^tail]) + && char.IsAsciiDigit(inward[0]) + && char.IsAsciiLetterUpper(inward[1]) + && char.IsAsciiLetterUpper(inward[2]) + && (!destination || (char.IsAsciiDigit(inward[3]) && char.IsAsciiLetterUpper(inward[4]))); + } + + /// + /// Returns whether the characters are an outward code of Appendix A, Table 14: AN, AAN, ANN, AANN, ANA or AANA. + /// + /// The characters. + /// when the characters are an outward code. + private static bool IsOutward(ReadOnlySpan code) + { + // Every outward code starts with a letter, has one or two letters before its first digit, and ends with that digit followed by one more digit or one + // more letter at most. + if (code.Length is < 2 or > 4 || !char.IsAsciiLetterUpper(code[0])) + { + return false; + } + + int letters = char.IsAsciiLetterUpper(code[1]) ? 2 : 1; + if (letters >= code.Length || !char.IsAsciiDigit(code[letters])) + { + return false; + } + + int rest = code.Length - letters - 1; + return rest == 0 || (rest == 1 && (char.IsAsciiDigit(code[^1]) || char.IsAsciiLetterUpper(code[^1]))); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DataMatrix/Mailmark2DType.cs b/src/ImageSharp.Drawing/Barcodes/DataMatrix/Mailmark2DType.cs new file mode 100644 index 00000000..deeaf3b9 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DataMatrix/Mailmark2DType.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The three types of 2D Mailmark barcode, each a fixed Data Matrix size. +/// +public enum Mailmark2DType +{ + /// + /// Type 7, 24 x 24 modules, with room for 6 characters of customer content in C40 encodation. + /// + Type7, + + /// + /// Type 9, 32 x 32 modules, with room for 45 characters of customer content in C40 encodation. + /// + Type9, + + /// + /// Type 29, 16 x 48 modules, with room for 25 characters of customer content in C40 encodation. + /// + Type29, +} diff --git a/src/ImageSharp.Drawing/Barcodes/DotCode/DotCodeEncoder.cs b/src/ImageSharp.Drawing/Barcodes/DotCode/DotCodeEncoder.cs new file mode 100644 index 00000000..2f01692e --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DotCode/DotCodeEncoder.cs @@ -0,0 +1,1543 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodes DotCode symbols: a grid of dots with no finder pattern, in which only the cells whose column and row sum to an even number can be dark. The data is +/// codewords 0 to 112 in code sets A, B and C, a binary mode and upper shifts. The codewords take a mask and Reed-Solomon error correction over the prime field +/// of 113, and each is placed as a pattern of five dark dots in nine. The mask that leaves the most legible edges and the fewest voids wins. +/// +internal static class DotCodeEncoder +{ + /// + /// The longest message, in characters and FNC1 characters. + /// + public const int MaximumLength = 2000; + + /// + /// The fewest columns or rows of a symbol. + /// + public const int MinimumSide = 5; + + /// + /// The most columns or rows of a symbol whose size is given. + /// + public const int MaximumSide = 200; + + /// + /// The quiet zone on each of the four sides in modules. + /// + private const int QuietZone = 3; + + /// + /// The message value of an FNC1 character. Characters are 0 to 255, so a negative value cannot collide. + /// + private const int Fnc1 = -1; + + /// + /// The prime modulus of the Reed-Solomon field and the number of codeword values. + /// + private const int Modulus = 113; + + /// + /// The most codewords of one Reed-Solomon block, one less than . + /// + private const int MaximumBlockLength = Modulus - 1; + + /// + /// The number of dots of a codeword pattern. + /// + private const int DotsPerCodeword = 9; + + /// + /// The number of dots that carry the mask value, before the codewords. + /// + private const int MaskDots = 2; + + /// + /// The number of check codewords of a symbol with no data codewords. Each two data codewords add one more. + /// + private const int MinimumCheckCodewords = 3; + + /// + /// The radix in which binary mode reads the bytes it packs. + /// + private const int BinaryByteRadix = 259; + + /// + /// The radix of the codewords that binary mode writes. + /// + private const int BinaryCodewordRadix = 103; + + /// + /// The width to height ratio of a symbol whose size is not given. + /// + private const double DefaultRatio = 3D / 2D; + + /// + /// The score that disqualifies a symbol with an edge that has no dark dot. + /// + private const double UnlitEdgeScore = -99999; + + /// + /// The score a symbol with lit corners must exceed to replace the best symbol of the first pass. + /// + private const double RetryFloor = -99999999; + + /// + /// The FNC1 codeword. It has this value in code sets A, B and C. + /// + private const int Fnc1Codeword = 107; + + /// + /// The upper shift A codeword. It has this value in code sets A, B and C. + /// + private const int UpperShiftA = 110; + + /// + /// The upper shift B codeword. It has this value in code sets A, B and C. + /// + private const int UpperShiftB = 111; + + /// + /// The codeword that latches to binary mode. It has this value in code sets A, B and C. + /// + private const int BinaryLatch = 112; + + /// + /// The codeword of code sets A and B that shifts to code set C for two digit pairs. The shifts for three and four digit pairs follow it. + /// + private const int ShiftC2 = 103; + + /// + /// The codeword of code sets A and B that latches to code set C. + /// + private const int LatchC = 106; + + /// + /// The codeword of code set A that shifts to code set B for one character. The shifts for two to six characters follow it. + /// + private const int AShiftB1 = 96; + + /// + /// The codeword of code set A that latches to code set B. + /// + private const int ALatchB = 102; + + /// + /// The codeword of code set B that shifts to code set A. + /// + private const int BShiftA = 101; + + /// + /// The codeword of code set B that latches to code set A. + /// + private const int BLatchA = 102; + + /// + /// The codeword of code set B for the CR LF pair. + /// + private const int BCrLf = 96; + + /// + /// The codeword of code set C for the 17xxxxxx10 macro. + /// + private const int CSeventeenTen = 100; + + /// + /// The codeword of code set C that latches to code set A. + /// + private const int CLatchA = 101; + + /// + /// The codeword of code set C that shifts to code set B for one character. The shifts for two to four characters follow it. + /// + private const int CShiftB1 = 102; + + /// + /// The codeword of code set C that latches to code set B. + /// + private const int CLatchB = 106; + + /// + /// The codeword of binary mode that shifts to code set C for two digit pairs. The shifts for three to seven digit pairs follow it. + /// + private const int BinaryShiftC2 = 103; + + /// + /// The codeword of binary mode that terminates to code set A. + /// + private const int BinaryTerminateA = 109; + + /// + /// The codeword of binary mode that terminates to code set B. + /// + private const int BinaryTerminateB = 110; + + /// + /// The codeword of binary mode that terminates to code set C. + /// + private const int BinaryTerminateC = 111; + + /// + /// The first codeword of code set B of a message that starts with the macro header [)>RS05GS. + /// + private const int Macro05 = 97; + + /// + /// The first codeword of code set B of a message that starts with the macro header [)>RS06GS. + /// + private const int Macro06 = 98; + + /// + /// The first codeword of code set B of a message that starts with the macro header [)>RS12GS. + /// + private const int Macro12 = 99; + + /// + /// The first codeword of code set B of a message that starts with the macro header [)>RS and any two other digits. + /// + private const int MacroAny = 100; + + /// + /// The pad codeword. In code sets A and B it latches to code set C, and in code set C it latches to code set B, so a run of them carries nothing. + /// + private const int Pad = 106; + + /// + /// The code sets of the encodation. + /// + private enum CodeSet + { + /// + /// Code set A: space to underscore and the control characters. + /// + A, + + /// + /// Code set B: space to DEL, HT, FS, GS, RS and the CR LF pair. + /// + B, + + /// + /// Code set C: digit pairs. + /// + C, + + /// + /// Binary mode: five bytes in six codewords. + /// + Binary, + } + + /// + /// What the analysis records about the message at a position. + /// + [Flags] + private enum Traits : byte + { + /// + /// Nothing. + /// + None = 0, + + /// + /// Code set A holds the character. + /// + SetA = 1, + + /// + /// Code set B holds the character, or the CR LF pair it starts. + /// + SetB = 2, + + /// + /// Code set C holds the position: two digits start there, or an FNC1 stands there. + /// + SetC = 4, + + /// + /// The character is a byte 128 to 255. + /// + Binary = 8, + + /// + /// A CR LF pair starts at the position. + /// + CrLf = 16, + + /// + /// The 17xxxxxx10 macro starts at the position. + /// + SeventeenTen = 32, + } + + /// + /// Gets the amounts the four masks add to successive data codewords, modulo 113. + /// + private static ReadOnlySpan MaskFactors => [0, 3, 7, 17]; + + /// + /// Gets the dot pattern of each codeword value: nine dots, five of them dark, the first dot as the most significant of nine bits. + /// + private static ReadOnlySpan Patterns => + [ + 0b101010101, 0b010101011, 0b010101101, 0b010110101, 0b011010101, 0b101010110, 0b101011010, 0b101101010, 0b110101010, 0b010101110, + 0b010110110, 0b010111010, 0b011010110, 0b011011010, 0b011101010, 0b100101011, 0b100101101, 0b100110101, 0b101001011, 0b101001101, + 0b101010011, 0b101011001, 0b101100101, 0b101101001, 0b110010101, 0b110100101, 0b110101001, 0b001010111, 0b001011011, 0b001011101, + 0b001101011, 0b001101101, 0b001110101, 0b010010111, 0b010011011, 0b010011101, 0b010100111, 0b010110011, 0b010111001, 0b011001011, + 0b011001101, 0b011010011, 0b011011001, 0b011100101, 0b011101001, 0b100101110, 0b100110110, 0b100111010, 0b101001110, 0b101011100, + 0b101100110, 0b101101100, 0b101110010, 0b101110100, 0b110010110, 0b110011010, 0b110100110, 0b110101100, 0b110110010, 0b110110100, + 0b111001010, 0b111010010, 0b111010100, 0b001011110, 0b001101110, 0b001110110, 0b001111010, 0b010011110, 0b010111100, 0b011001110, + 0b011011100, 0b011100110, 0b011101100, 0b011110010, 0b011110100, 0b100010111, 0b100011011, 0b100011101, 0b100100111, 0b100110011, + 0b100111001, 0b101000111, 0b101100011, 0b101110001, 0b110001011, 0b110001101, 0b110010011, 0b110011001, 0b110100011, 0b110110001, + 0b111000101, 0b111001001, 0b111010001, 0b000101111, 0b000110111, 0b000111011, 0b000111101, 0b001001111, 0b001100111, 0b001110011, + 0b001111001, 0b010001111, 0b011000111, 0b011100011, 0b011110001, 0b100011110, 0b100111100, 0b101111000, 0b110001110, 0b110011100, + 0b110111000, 0b111000110, 0b111001100, + ]; + + /// + /// Validates the rows of a fixed symbol size. The rows are 5 to 200, and the columns and the rows sum to an odd number. + /// + /// The number of columns, already validated. + /// The number of rows. + /// is not 5 to 200. + /// The columns and the rows sum to an even number. + public static void ValidateRows(int columns, int rows) + { + Guard.MustBeBetweenOrEqualTo(rows, MinimumSide, MaximumSide, nameof(rows)); + if (((columns + rows) & 1) == 0) + { + throw new ArgumentException("The columns and the rows of a DotCode symbol must sum to an odd number.", nameof(rows)); + } + } + + /// + /// Builds a DotCode symbol. + /// + /// The text, characters U+0000 to U+00FF. With , the prepared GS1 data. + /// Whether the text is GS1 data: an FNC1 starts the message and every GS1 separator becomes an FNC1. + /// The number of columns, or 0 to follow from the data. + /// The number of rows, or 0 to follow from the data and the columns. + /// The X dimension in millimetres. + /// The symbol to write. + /// The text is empty, too long, has a character above U+00FF, or does not fit. + public static void BuildSymbol(ReadOnlySpan text, bool gs1, int columns, int rows, double xDimension, ref BarcodeSymbol symbol) + { + int length = text.Length + (gs1 ? 1 : 0); + Guard.MustBeBetweenOrEqualTo(length, 1, MaximumLength, nameof(text)); + int invalid = text.IndexOfAnyExceptInRange('\0', '\u00FF'); + if (invalid >= 0) + { + throw new ArgumentException($"DotCode encodes U+0000 to U+00FF; got {text.CodePointAt(invalid).ToDisplayString()}.", nameof(text)); + } + + // One rent holds the message, the five per-position counts of the analysis, the codewords and the per-position traits. Each count array has two entries + // past the message, which end its look-ahead. A character takes at most two codewords, and the message one more. The traits look up to eight places + // ahead, so they have eight entries past the message, read as bytes. + int stride = length + 2; + int codewordCapacity = (2 * length) + 1; + int traitInts = (length + 8 + sizeof(int) - 1) / sizeof(int); + using PooledBuffer analysis = new(length + (5 * stride) + codewordCapacity + traitInts); + Span buffer = analysis.Span; + Span message = buffer[..length]; + Span codewords = buffer.Slice(length + (5 * stride), codewordCapacity); + Span traits = MemoryMarshal.Cast(buffer.Slice(length + (5 * stride) + codewordCapacity, traitInts))[..(length + 8)]; + + int m = 0; + if (gs1) + { + message[m++] = Fnc1; + } + + foreach (char c in text) + { + message[m++] = gs1 && c == Gs1Data.Separator ? Fnc1 : c; + } + + // A message that starts with a macro header and ends with its trailer carries the macro codeword in place of the header. The macro implies the trailer, + // so the writer reads the message without it: RS EOT after the 05, 06 and 12 headers, and EOT alone after the header with any two digits. + int macro = CodewordWriter.MacroAtStart(message); + int trailerLength = macro == 0 ? 0 : macro == MacroAny ? 1 : 2; + CodewordWriter writer = new(message[..(length - trailerLength)], buffer.Slice(length, 5 * stride), stride, traits, codewords, macro); + writer.Analyze(); + int dataCount = writer.Encode(); + + // The symbol size: the smallest area that holds the codewords at the ratio, or the rows for the given columns, or the given size. Half the cells can + // hold a dot. + int minimumArea = DotCount(dataCount) * 2; + if (columns == 0) + { + (columns, rows) = SizeForRatio(minimumArea, DefaultRatio); + } + else if (rows == 0) + { + // The rows round up to hold the area. When the sides then sum to an even number, the rows grow by one. + rows = (minimumArea + columns - 1) / columns; + if (((rows + columns) & 1) == 0) + { + rows++; + } + + if (rows < MinimumSide) + { + rows = MinimumSide + (columns & 1); + } + } + + // The data codewords grow while one more, with its share of check codewords, still fits. + int dotCount = rows * columns / 2; + int nd = dataCount; + while (DotCount(nd + 1) <= dotCount) + { + nd++; + } + + int nw = CodewordCount(nd); + if (DotCount(nd) > dotCount) + { + throw new ArgumentException("The text does not fit in a DotCode symbol of the requested size.", nameof(text)); + } + + // The second rent holds the mask value and the masked codewords, and two grids of cells: the symbol under the current mask and the best so far. + int cellCount = rows * columns; + int cellInts = (cellCount + sizeof(int) - 1) / sizeof(int); + using PooledBuffer layoutBuffer = new(nw + 1 + (2 * cellInts)); + Span masked = layoutBuffer.Span[..(nw + 1)]; + Span cells = MemoryMarshal.Cast(layoutBuffer.Span.Slice(nw + 1, cellInts))[..cellCount]; + Span best = MemoryMarshal.Cast(layoutBuffer.Span.Slice(nw + 1 + cellInts, cellInts))[..cellCount]; + + DotLayout layout = new(codewords[..dataCount], nd, writer.EndsInBinary, columns, rows, masked); + double bestScore = double.MinValue; + for (int mask = 0; mask < MaskFactors.Length; mask++) + { + layout.Place(mask, false, cells); + double score = Score(cells, columns, rows); + if (score > bestScore) + { + bestScore = score; + cells.CopyTo(best); + } + } + + // When no mask scores above half the cells, the six corner dots that carry the last bits are lit in every mask, which gives the edges more dark dots, + // and the best of those wins instead. A lit symbol must score above RetryFloor to replace the best symbol so far. + if (bestScore <= cellCount / 2) + { + bestScore = RetryFloor; + for (int mask = 0; mask < MaskFactors.Length; mask++) + { + layout.Place(mask, true, cells); + double score = Score(cells, columns, rows); + if (score > bestScore) + { + bestScore = score; + cells.CopyTo(best); + } + } + } + + Span modules = symbol.BeginDotted(columns, rows, QuietZone, xDimension); + MemoryMarshal.Cast(best).CopyTo(modules); + } + + /// + /// Returns the size of the smallest symbol near a width to height ratio that holds an area. The sides start from the exact sides of the area at the ratio, + /// rounded down. When their sum is odd and the area does not fit, both sides grow by one. When their sum is even, the side further below its exact value + /// grows by one. If the area does not fit, that side goes back and the other side grows by one. If the area still does not fit, the first side grows by + /// two. + /// + /// The area to hold, in cells. + /// The width to height ratio. + /// The columns and the rows. + private static (int Columns, int Rows) SizeForRatio(int minimumArea, double ratio) + { + double exactHeight = Math.Sqrt(minimumArea / ratio); + double exactWidth = Math.Sqrt(minimumArea * ratio); + int h = (int)exactHeight; + int w = (int)exactWidth; + if (((h + w) & 1) == 1) + { + if (h * w < minimumArea) + { + h++; + w++; + } + } + else if (exactHeight * w < exactWidth * h) + { + // The width is further below its exact value in proportion, so it grows first. + w++; + if (h * w < minimumArea) + { + w--; + h++; + if (h * w < minimumArea) + { + w += 2; + } + } + } + else + { + h++; + if (h * w < minimumArea) + { + h--; + w++; + if (h * w < minimumArea) + { + h += 2; + } + } + } + + return (w, h); + } + + /// + /// Returns the number of codewords of a symbol with the given data codewords: the data and the check codewords, which are three plus half the data. + /// + /// The number of data codewords. + /// The number of codewords, the mask value excluded. + private static int CodewordCount(int dataCount) => dataCount + (dataCount / 2) + MinimumCheckCodewords; + + /// + /// Returns the number of dots that a symbol with the given data codewords needs: the mask dots and the dots of every codeword. + /// + /// The number of data codewords. + /// The number of dots. + private static int DotCount(int dataCount) => (CodewordCount(dataCount) * DotsPerCodeword) + MaskDots; + + /// + /// Adds the Reed-Solomon check codewords over the prime field of 113. The codewords split into blocks of at most 112: block s of the step takes every + /// step-th codeword from s, its data first and then its check codewords, so the blocks interleave in the codeword order. + /// + /// The mask value and the data codewords, followed by room for the check codewords. + /// The number of codewords protected, the mask value and the data. + private static void AddErrorCorrection(Span codewords, int dataCount) + { + int total = codewords.Length; + int step = ((total - 1) / MaximumBlockLength) + 1; + + // A block holds at most 112 codewords, and its check codewords are three plus half its data, so a block has at most 39 check codewords. + InlineArray64 generatorStorage = default; + InlineArray64 registerStorage = default; + for (int start = 0; start < step; start++) + { + // Block start holds codewords start, start + step, and so on: ceil((count - start) / step) of them. + int blockData = (dataCount - start + step - 1) / step; + int checkCount = ((total - start + step - 1) / step) - blockData; + Span generator = ((Span)generatorStorage)[..checkCount]; + Span register = ((Span)registerStorage)[..checkCount]; + Generator(generator); + + // The division of the data polynomial times x^k by the generator, the first data codeword the highest term, as a shift register: each codeword + // feeds back its difference from the first register value, and the register shifts down by one place with the generator terms added in. The + // register ends holding the check codewords, the first one first. + register.Clear(); + for (int k = 0; k < blockData; k++) + { + int feedback = (codewords[start + (k * step)] - register[0] + Modulus) % Modulus; + for (int j = 0; j < checkCount - 1; j++) + { + register[j] = (register[j + 1] + (generator[checkCount - 1 - j] * feedback)) % Modulus; + } + + register[checkCount - 1] = (generator[0] * feedback) % Modulus; + } + + for (int j = 0; j < checkCount; j++) + { + codewords[start + ((blockData + j) * step)] = register[j]; + } + } + } + + /// + /// Writes the generator polynomial of a block, the product of (x - 3^i) for i = 1 to k over the field of 113, without its leading 1: the coefficient of x^0 + /// first. + /// + /// The buffer of k coefficients to write. + private static void Generator(Span generator) + { + // The product builds in k + 1 terms, the coefficient of x^k first. Each factor (x - r) updates every term to itself less r times the term above it, + // from the bottom up, so each update reads the term above before that term changes. + int k = generator.Length; + InlineArray64 productStorage = default; + Span product = ((Span)productStorage)[..(k + 1)]; + product.Clear(); + product[0] = 1; + int root = 1; + for (int i = 1; i <= k; i++) + { + root = (root * 3) % Modulus; + for (int j = k; j >= 1; j--) + { + product[j] = (product[j] + Modulus - ((root * product[j - 1]) % Modulus)) % Modulus; + } + } + + // The coefficient of x^j is product[k - j]. + for (int j = 0; j < k; j++) + { + generator[j] = product[k - j]; + } + } + + /// + /// Scores a symbol: the weakest edge less the square of the voids and isolated dots, less a penalty for runs of blank columns or rows. A symbol with an + /// edge that has no dark dot scores . The penalty grows as a power of the side, past the range of an integer, so the score is a + /// double. + /// + /// The cells, row-major, 1 for a dark dot. + /// The number of columns. + /// The number of rows. + /// The score; higher is better. + private static double Score(ReadOnlySpan cells, int columns, int rows) + { + // An edge scores its dark dots plus the span from its first dark dot to its last, times the length of the other side. The weakest edge counts. + double worst = Math.Min( + Math.Min(EdgeScore(cells, 0, 1, columns), EdgeScore(cells, (rows - 1) * columns, 1, columns)) * (double)rows, + Math.Min(EdgeScore(cells, 0, columns, rows), EdgeScore(cells, columns - 1, columns, rows)) * (double)columns); + + // With odd rows or 12 rows or fewer, the score takes a penalty for runs of blank inner columns. Otherwise, the codewords run down the columns of a + // symbol tall enough to break up runs of light dots. With even rows or 12 columns or fewer, the score takes the same penalty for runs of blank inner + // rows. + double penalty = 0; + if ((rows & 1) == 1 || rows <= 12) + { + penalty += BlankRunPenalty(cells, columns, rows, true); + } + + if ((rows & 1) == 0 || columns <= 12) + { + penalty += BlankRunPenalty(cells, columns, rows, false); + } + + // A void is a light dot cell with no dark diagonal neighbour. An isolated dot is a dark dot with no dark diagonal neighbour and no dark dot two cells + // away along its row or column. Cells beyond the symbol are light. + int count = 0; + for (int y = 0; y < rows; y++) + { + for (int x = y & 1; x < columns; x += 2) + { + if (Dark(cells, columns, rows, x - 1, y - 1) + || Dark(cells, columns, rows, x + 1, y - 1) + || Dark(cells, columns, rows, x - 1, y + 1) + || Dark(cells, columns, rows, x + 1, y + 1)) + { + continue; + } + + if (cells[(y * columns) + x] == 0 + || !(Dark(cells, columns, rows, x - 2, y) + || Dark(cells, columns, rows, x, y - 2) + || Dark(cells, columns, rows, x + 2, y) + || Dark(cells, columns, rows, x, y + 2))) + { + count++; + } + } + } + + return worst == 0 ? UnlitEdgeScore : worst - ((double)count * count) - penalty; + } + + /// + /// Returns the dark dots of an edge plus the span from its first dark dot to its last, or 0 for an edge with no dark dot. + /// + /// The cells. + /// The index of the first cell of the edge. + /// The distance between two cells of the edge. + /// The number of cells of the edge. + /// The edge score before scaling. + private static int EdgeScore(ReadOnlySpan cells, int start, int step, int length) + { + int sum = 0; + int first = -1; + int last = -1; + for (int k = 0; k < length; k++) + { + if (cells[start + (k * step)] == 1) + { + if (first < 0) + { + first = k; + } + + last = k; + sum++; + } + } + + return sum + last - first; + } + + /// + /// Returns the penalty of the runs of blank inner columns or rows: for each run of n, the length of the lines to the power n. + /// + /// The cells. + /// The number of columns. + /// The number of rows. + /// Whether the lines are columns. + /// The penalty. + private static double BlankRunPenalty(ReadOnlySpan cells, int columns, int rows, bool byColumn) + { + int lines = byColumn ? columns : rows; + int lineLength = byColumn ? rows : columns; + int start = byColumn ? 1 : columns; + int step = byColumn ? columns : 1; + int lineStep = byColumn ? 1 : columns; + double penalty = 0; + double run = 0; + for (int line = 1; line < lines - 1; line++, start += lineStep) + { + bool blank = true; + for (int k = 0; k < lineLength && blank; k++) + { + blank = cells[start + (k * step)] == 0; + } + + if (blank) + { + run = run == 0 ? lineLength : run * lineLength; + } + else + { + penalty += run; + run = 0; + } + } + + return penalty + run; + } + + /// + /// Returns whether a cell is a dark dot; cells beyond the symbol are light. + /// + /// The cells. + /// The number of columns. + /// The number of rows. + /// The column. + /// The row. + /// for a dark dot. + private static bool Dark(ReadOnlySpan cells, int columns, int rows, int x, int y) + => (uint)x < (uint)columns && (uint)y < (uint)rows && cells[(y * columns) + x] == 1; + + /// + /// Places the codewords of a symbol under each mask: masks the data, adds the error correction and writes the dots. + /// + private readonly ref struct DotLayout + { + /// + /// The marker of the six corner cells while the stream fills the other dot cells. + /// + private const byte Reserved = 2; + + /// + /// The data codewords before padding. + /// + private readonly ReadOnlySpan data; + + /// + /// The number of data codewords after padding. + /// + private readonly int dataCount; + + /// + /// Whether the data ends in binary mode, so the first pad terminates to code set A. + /// + private readonly bool endsInBinary; + + /// + /// The number of columns. + /// + private readonly int columns; + + /// + /// The number of rows. + /// + private readonly int rows; + + /// + /// The mask value followed by the masked data and check codewords. + /// + private readonly Span masked; + + /// + /// Initializes a new instance of the struct. + /// + /// The data codewords before padding. + /// The number of data codewords after padding. + /// Whether the data ends in binary mode. + /// The number of columns. + /// The number of rows. + /// The buffer of the mask value and the masked codewords, one longer than the data and check codewords. + public DotLayout(ReadOnlySpan data, int dataCount, bool endsInBinary, int columns, int rows, Span masked) + { + this.data = data; + this.dataCount = dataCount; + this.endsInBinary = endsInBinary; + this.columns = columns; + this.rows = rows; + this.masked = masked; + } + + /// + /// Writes the dots of the symbol under one mask. + /// + /// The mask, 0 to 3. + /// Whether the six corner dots are dark whatever bits they carry. + /// The cells, row-major, to write: 1 for a dark dot. + public void Place(int mask, bool litCorners, Span cells) + { + // The mask value leads the codewords and is covered by the error correction. Mask m adds successive multiples of its factor to the data codewords, + // the first plus 0, the next plus the factor, and so on, modulo 113. The data pads to its full count: a pad in binary mode first terminates to code + // set A. + Span codewords = this.masked; + int factor = MaskFactors[mask]; + int weight = 0; + codewords[0] = mask; + for (int k = 0; k < this.dataCount; k++) + { + int value = k < this.data.Length ? this.data[k] : k == this.data.Length && this.endsInBinary ? BinaryTerminateA : Pad; + codewords[k + 1] = (value + weight) % Modulus; + weight += factor; + } + + AddErrorCorrection(codewords, this.dataCount + 1); + + // The six corner cells carry the last six bits, so they are reserved while the stream fills the other dot cells. Each lies where the column and the + // row sum to an even number, because the sides sum to an odd number. + InlineArray6 cornerStorage = default; + Span corners = cornerStorage; + this.CornerCells(corners); + cells.Clear(); + foreach (int corner in corners) + { + cells[corner] = Reserved; + } + + // A dot cell is one whose column and row sum to an even number. With even rows the stream fills them column by column from the top left; + // with odd rows it fills them row by row from the bottom left upward. + int columns = this.columns; + int rows = this.rows; + int bitCount = (rows * columns / 2) - corners.Length; + int bit = 0; + bool evenRows = (rows & 1) == 0; + int outer = evenRows ? columns : rows; + int inner = evenRows ? rows : columns; + for (int a = 0; a < outer && bit < bitCount; a++) + { + for (int b = 0; b < inner && bit < bitCount; b++) + { + int x = evenRows ? a : b; + int y = evenRows ? b : rows - 1 - a; + int index = (y * columns) + x; + if (((x + y) & 1) == 0 && cells[index] != Reserved) + { + cells[index] = (byte)this.Bit(bit++); + } + } + } + + for (int c = 0; c < corners.Length; c++) + { + cells[corners[c]] = litCorners ? (byte)1 : (byte)this.Bit(bitCount + c); + } + } + + /// + /// Returns one bit of the dot stream: the two bits of the mask value, then nine for each codeword after it, the first dot of its pattern first, then + /// dark padding bits to the end. + /// + /// The bit. + /// The bit, 1 for a dark dot. + private readonly int Bit(int index) + { + if (index < MaskDots) + { + return (this.masked[0] >> (MaskDots - 1 - index)) & 1; + } + + int offset = index - MaskDots; + int codeword = 1 + (offset / DotsPerCodeword); + return codeword < this.masked.Length ? (Patterns[this.masked[codeword]] >> (DotsPerCodeword - 1 - (offset % DotsPerCodeword))) & 1 : 1; + } + + /// + /// Writes the cells of the six corner dots in the order of the last six bits. + /// + /// The buffer of six cell indices to write. + private readonly void CornerCells(Span corners) + { + int last = this.columns - 1; + int bottom = (this.rows - 1) * this.columns; + if ((this.rows & 1) == 0) + { + corners[0] = bottom - this.columns + last; + corners[1] = bottom - this.columns; + corners[2] = bottom + last - 1; + corners[3] = bottom + 1; + corners[4] = last; + corners[5] = 0; + } + else + { + corners[0] = last - 1; + corners[1] = bottom + last - 1; + corners[2] = this.columns + last; + corners[3] = bottom - this.columns + last; + corners[4] = 0; + corners[5] = bottom; + } + } + } + + /// + /// Writes the codewords of a message. The analysis pass reads the message from its end. At each position, it records the runs ahead that each code set + /// can take. The encoding pass then walks the message from code set C, the starting set. At each position, it writes the codewords that its current set + /// chooses. + /// + private ref struct CodewordWriter + { + /// + /// The message: characters 0 to 255 and . + /// + private readonly ReadOnlySpan message; + + /// + /// The number of consecutive digits from each position. + /// + private readonly Span digits; + + /// + /// The number of code set C units, digit pairs or FNC1 characters, from each position. + /// + private readonly Span aheadC; + + /// + /// The code set C units from a position that starts with a digit, or 0. A shift or latch to code set C at the position takes this run. + /// + private readonly Span tryC; + + /// + /// The number of consecutive characters from each position that code set A holds and that do not start a run of two or more code set C units. + /// + private readonly Span aheadA; + + /// + /// The number of consecutive characters from each position that code set B holds, a CR LF pair as one, and that do not start a run of two or more code + /// set C units. + /// + private readonly Span aheadB; + + /// + /// The traits of each position, eight past the end of the message. + /// + private readonly Span traits; + + /// + /// The codewords written. + /// + private readonly Span codewords; + + /// + /// The bytes waiting for a binary mode group: five bytes encode as six codewords. + /// + private InlineArray5 pending; + + /// + /// The number of bytes waiting. + /// + private int pendingCount; + + /// + /// The number of codewords written. + /// + private int count; + + /// + /// The position in the message. + /// + private int position; + + /// + /// The current code set. + /// + private CodeSet codeSet; + + /// + /// The macro codeword the message starts with, 0 for none. The message ends before the trailer, which the macro implies. + /// + private readonly int macro; + + /// + /// Initializes a new instance of the struct. + /// + /// The message. + /// The five count arrays of the analysis, each long, end to end. + /// The length of each count array, the message length plus two. + /// The traits, the message length plus eight. + /// The codewords to write, twice the message length plus one. + /// The macro codeword the message starts with, 0 for none. The message ends before the trailer of the macro. + public CodewordWriter(ReadOnlySpan message, Span counts, int stride, Span traits, Span codewords, int macro) + { + this.message = message; + this.digits = counts[..stride]; + this.aheadC = counts.Slice(stride, stride); + this.tryC = counts.Slice(2 * stride, stride); + this.aheadA = counts.Slice(3 * stride, stride); + this.aheadB = counts.Slice(4 * stride, stride); + this.traits = traits; + this.codewords = codewords; + this.pending = default; + this.pendingCount = 0; + this.count = 0; + this.position = 0; + this.codeSet = CodeSet.C; + this.macro = macro; + } + + /// + /// Gets a value indicating whether the encoding ends in binary mode. + /// + public readonly bool EndsInBinary => this.codeSet == CodeSet.Binary; + + /// + /// Records the analysis of every position, from the end of the message back to its start, so each position reads the counts of the positions after it. + /// + public readonly void Analyze() + { + ReadOnlySpan msg = this.message; + int length = msg.Length; + this.digits[length..].Clear(); + this.aheadC[length..].Clear(); + this.tryC[length..].Clear(); + this.aheadA[length..].Clear(); + this.aheadB[length..].Clear(); + this.traits.Clear(); + for (int i = length - 1; i >= 0; i--) + { + int c = msg[i]; + this.digits[i] = c is >= '0' and <= '9' ? this.digits[i + 1] + 1 : 0; + + bool crLf = c == '\r' && i < length - 1 && msg[i + 1] == '\n'; + Traits trait = Traits.None; + if (c is >= 0 and <= 95 or Fnc1) + { + trait |= Traits.SetA; + } + + if (c is >= 32 and <= 127 or '\t' or 28 or 29 or 30 or Fnc1 || crLf) + { + trait |= Traits.SetB; + } + + if (this.digits[i] >= 2 || c == Fnc1) + { + trait |= Traits.SetC; + } + + if (c >= 128) + { + trait |= Traits.Binary; + } + + if (crLf) + { + trait |= Traits.CrLf; + } + + // The 17xxxxxx10 macro: the GS1 expiry date element string (17) followed by the start of a batch number (10). + if (this.digits[i] >= 10 && msg[i] == '1' && msg[i + 1] == '7' && msg[i + 8] == '1' && msg[i + 9] == '0') + { + trait |= Traits.SeventeenTen; + } + + this.traits[i] = trait; + + // An FNC1 is one unit of code set C, and so is each pair of digits. A run of code set C starts only on a digit whose pair begins more units + // than the next position does. + this.aheadC[i] = c == Fnc1 ? this.aheadC[i + 1] + 1 : this.digits[i] <= 1 ? 0 : this.aheadC[i + 2] + 1; + this.tryC[i] = this.digits[i] > 0 && this.aheadC[i] > this.aheadC[i + 1] ? this.aheadC[i] : 0; + this.aheadA[i] = (trait & Traits.SetA) != 0 && this.tryC[i] < 2 ? this.aheadA[i + 1] + 1 : 0; + this.aheadB[i] = (trait & Traits.SetB) != 0 && this.tryC[i] < 2 ? this.aheadB[i + (crLf ? 2 : 1)] + 1 : 0; + } + } + + /// + /// Writes the codewords of the whole message. + /// + /// The number of codewords. + public int Encode() + { + int length = this.message.Length; + while (this.position < length) + { + switch (this.codeSet) + { + case CodeSet.A: + this.EncodeA(); + break; + case CodeSet.B: + this.EncodeB(); + break; + case CodeSet.C: + this.EncodeC(); + break; + default: + this.EncodeBinary(); + break; + } + } + + return this.count; + } + + /// + /// Encodes from the current position in code set C. + /// + private void EncodeC() + { + ReadOnlySpan msg = this.message; + int i = this.position; + if (i == 0) + { + // A message that starts with a macro header and ends with its trailer latches to code set B and carries the macro codeword in place of the + // header. + if (this.macro != 0) + { + this.Add(CLatchB); + this.codeSet = CodeSet.B; + this.Add(this.macro); + if (this.macro == MacroAny) + { + this.Add(BValue(msg[4])); + this.Add(BValue(msg[5])); + this.position = 6; + } + else + { + this.position = 7; + } + + return; + } + + // Two digits in the first codeword mark GS1 data. Other data that starts with two digits leads with an FNC1, and GS1 data drops the FNC1 it + // starts with. + if (this.digits[i] >= 2) + { + this.Add(Fnc1Codeword); + } + + if (msg[i] == Fnc1 && this.digits[i + 1] >= 2) + { + this.position = ++i; + } + } + + Traits trait = this.traits[i]; + if ((trait & Traits.SeventeenTen) != 0) + { + // The macro codeword, then the six digits of the date as three pairs; the (10) is implied. + this.Add(CSeventeenTen); + this.Add(Pair(msg, i + 2)); + this.Add(Pair(msg, i + 4)); + this.Add(Pair(msg, i + 6)); + this.position = i + 10; + return; + } + + if ((trait & Traits.SetC) != 0) + { + this.AddCUnits(1); + return; + } + + if ((trait & Traits.Binary) != 0) + { + // A lone byte before a digit takes an upper shift and stays in code set C. + if (this.digits[i + 1] > 0) + { + this.AddUpperShift(msg[i]); + this.position = i + 1; + return; + } + + this.Latch(BinaryLatch, CodeSet.Binary); + return; + } + + int runA = this.aheadA[i]; + int runB = this.aheadB[i]; + + // HT, FS, GS and RS are macros in the first codeword of code set B, so a message that starts with one latches to code set A. + if (runA > runB || (i == 0 && msg[i] is '\t' or 28 or 29 or 30)) + { + this.Latch(CLatchA, CodeSet.A); + return; + } + + if (runB > 4) + { + this.Latch(CLatchB, CodeSet.B); + return; + } + + // A shift for one to four characters of code set B. + this.Add(CShiftB1 + runB - 1); + this.AddBRun(runB); + } + + /// + /// Encodes from the current position in code set B. + /// + private void EncodeB() + { + ReadOnlySpan msg = this.message; + int i = this.position; + if (this.SwitchToC()) + { + return; + } + + Traits trait = this.traits[i]; + if ((trait & Traits.SetB) != 0) + { + if ((trait & Traits.CrLf) != 0) + { + this.Add(BCrLf); + this.position = i + 2; + return; + } + + this.Add(msg[i] == Fnc1 ? Fnc1Codeword : BValue(msg[i])); + this.position = i + 1; + return; + } + + if ((trait & Traits.Binary) != 0) + { + this.UpperShiftOrBinary(Traits.SetB); + return; + } + + // One code set A character takes a shift; more latch. + if (this.aheadA[i] == 1) + { + this.Add(BShiftA); + this.Add(AValue(msg[i])); + this.position = i + 1; + return; + } + + this.Latch(BLatchA, CodeSet.A); + } + + /// + /// Encodes from the current position in code set A. + /// + private void EncodeA() + { + ReadOnlySpan msg = this.message; + int i = this.position; + if (this.SwitchToC()) + { + return; + } + + Traits trait = this.traits[i]; + if ((trait & Traits.SetA) != 0) + { + this.Add(msg[i] == Fnc1 ? Fnc1Codeword : AValue(msg[i])); + this.position = i + 1; + return; + } + + if ((trait & Traits.Binary) != 0) + { + this.UpperShiftOrBinary(Traits.SetA); + return; + } + + // One to six code set B characters take a shift; more latch. + int run = this.aheadB[i]; + if (run > 6) + { + this.Latch(ALatchB, CodeSet.B); + return; + } + + this.Add(AShiftB1 + run - 1); + this.AddBRun(run); + } + + /// + /// Encodes from the current position in binary mode. + /// + private void EncodeBinary() + { + ReadOnlySpan msg = this.message; + int i = this.position; + int units = this.tryC[i]; + if (units >= 2) + { + // Two to seven code set C units interrupt binary mode with a shift; more terminate to code set C. + this.FlushBinary(); + if (units > 7) + { + this.Latch(BinaryTerminateC, CodeSet.C); + return; + } + + this.Add(BinaryShiftC2 + units - 2); + this.AddCUnits(units); + return; + } + + // A character stays in binary mode while a byte 128 to 255 follows within the next four positions. + if (msg[i] != Fnc1 && ((this.traits[i] | this.traits[i + 1] | this.traits[i + 2] | this.traits[i + 3]) & Traits.Binary) != 0) + { + this.AddBinary(msg[i]); + this.position = i + 1; + if (this.position == msg.Length) + { + this.FlushBinary(); + } + + return; + } + + this.FlushBinary(); + if (this.aheadA[i] > this.aheadB[i]) + { + this.Latch(BinaryTerminateA, CodeSet.A); + } + else + { + this.Latch(BinaryTerminateB, CodeSet.B); + } + } + + /// + /// Switches from code set A or B to code set C when two or more code set C units start at the position: a latch for five or more, and a shift for two + /// to four, which also writes the units. + /// + /// when the position switched. + private bool SwitchToC() + { + int units = this.tryC[this.position]; + if (units < 2) + { + return false; + } + + if (units > 4) + { + this.Latch(LatchC, CodeSet.C); + return true; + } + + this.Add(ShiftC2 + units - 2); + this.AddCUnits(units); + return true; + } + + /// + /// Writes a byte 128 to 255 in code set A or B: an upper shift when the next character belongs to the current set, otherwise a latch to binary mode. + /// + /// The trait of the current code set. + private void UpperShiftOrBinary(Traits currentSet) + { + int i = this.position; + if ((this.traits[i + 1] & currentSet) != 0) + { + this.AddUpperShift(this.message[i]); + this.position = i + 1; + return; + } + + this.Latch(BinaryLatch, CodeSet.Binary); + } + + /// + /// Returns the macro codeword of a message that starts with a macro header and ends with its trailer, or 0. The header is [)>RS and two digits; 05, + /// 06 and 12 also need GS after the digits and RS before the final EOT, and any other digits need only the final EOT. + /// + /// The whole message, the trailer included. + /// The macro codeword, or 0. + public static int MacroAtStart(ReadOnlySpan msg) + { + int end = msg.Length; + if (end < 7 + || msg[0] != '[' + || msg[1] != ')' + || msg[2] != '>' + || msg[3] != 30 + || msg[4] is < '0' or > '9' + || msg[5] is < '0' or > '9' + || msg[end - 1] != 4) + { + return 0; + } + + int format = Pair(msg, 4); + if (format is not (5 or 6 or 12)) + { + return MacroAny; + } + + if (msg[6] != 29 || msg[end - 2] != 30) + { + return 0; + } + + return format switch + { + 5 => Macro05, + 6 => Macro06, + _ => Macro12, + }; + } + + /// + /// Writes a latch and changes the code set. + /// + /// The latch codeword. + /// The code set it latches to. + private void Latch(int codeword, CodeSet codeSet) + { + this.Add(codeword); + this.codeSet = codeSet; + } + + /// + /// Writes a run of code set B characters after a shift, a CR LF pair as one. + /// + /// The number of characters. + private void AddBRun(int run) + { + for (int k = 0; k < run; k++) + { + int c = this.message[this.position]; + this.Add(c == '\r' ? BCrLf : BValue(c)); + this.position += c == '\r' ? 2 : 1; + } + } + + /// + /// Writes code set C units: digit pairs and FNC1 characters. + /// + /// The number of units. + private void AddCUnits(int units) + { + for (int k = 0; k < units; k++) + { + int i = this.position; + bool fnc1 = this.message[i] == Fnc1; + this.Add(fnc1 ? Fnc1Codeword : Pair(this.message, i)); + this.position = i + (fnc1 ? 1 : 2); + } + } + + /// + /// Writes a byte 128 to 255 as an upper shift and a character: upper shift A for 128 to 159, which carries the code set A value of the byte less 128, + /// and upper shift B for 160 to 255, which carries the code set B value. + /// + /// The byte. + private void AddUpperShift(int value) + { + bool shiftA = value < 160; + this.Add(shiftA ? UpperShiftA : UpperShiftB); + this.Add(shiftA ? AValue(value - 128) : BValue(value - 128)); + } + + /// + /// Adds a byte to the binary group, and writes the group once it holds five. + /// + /// The byte. + private void AddBinary(int value) + { + this.pending[this.pendingCount++] = value; + if (this.pendingCount == 5) + { + this.FlushBinary(); + } + } + + /// + /// Writes the waiting bytes of binary mode: n bytes, read as a base 259 number, become n + 1 base 103 codewords, the most significant first. + /// + private void FlushBinary() + { + int n = this.pendingCount; + if (n == 0) + { + return; + } + + // Five bytes are at most 259^5 - 1, under 2^41, so a long holds the value. 259^n is under 103^(n + 1), so n + 1 base 103 digits hold it. The + // division gives the digits the least significant first, so they fill the output from the end. + long value = 0; + foreach (int b in this.pending[..n]) + { + value = (value * BinaryByteRadix) + b; + } + + InlineArray6 outputStorage = default; + Span output = ((Span)outputStorage)[..(n + 1)]; + for (int i = n; i >= 0; i--) + { + output[i] = (int)(value % BinaryCodewordRadix); + value /= BinaryCodewordRadix; + } + + foreach (int codeword in output) + { + this.Add(codeword); + } + + this.pendingCount = 0; + } + + /// + /// Writes one codeword. + /// + /// The codeword, 0 to 112. + private void Add(int value) => this.codewords[this.count++] = value; + + /// + /// Returns the code set C value of the two digits at a position. + /// + /// The message. + /// The position of the first digit. + /// The value, 0 to 99. + private static int Pair(ReadOnlySpan msg, int i) => ((msg[i] - '0') * 10) + (msg[i + 1] - '0'); + + /// + /// Returns the code set A value of a character 0 to 95: space to underscore are 0 to 63, and the control characters NUL to US are 64 to 95. + /// + /// The character. + /// The value. + private static int AValue(int c) => c >= 32 ? c - 32 : c + 64; + + /// + /// Returns the code set B value of a character: space to DEL are 0 to 95, and HT, FS, GS and RS are 97 to 100. + /// + /// The character. + /// The value. + private static int BValue(int c) => c switch + { + >= 32 => c - 32, + '\t' => 97, + _ => c - 28 + 98, + }; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DotCode/DotCodeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/DotCode/DotCodeSymbology.cs new file mode 100644 index 00000000..e6e48a23 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DotCode/DotCodeSymbology.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The DotCode symbology: a grid of round dots with no finder pattern, at least 5 columns and 5 rows whose sum is odd, and a quiet zone of three modules on +/// every side. It carries the characters U+0000 to U+00FF with Reed-Solomon error correction. +/// +public sealed class DotCodeSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. The symbol takes the smallest size near a width to height ratio of 3:2 that + /// holds the data. + /// + public DotCodeSymbology() + { + } + + /// + /// Initializes a new instance of the class with a fixed number of columns. The symbol takes the fewest rows that hold the + /// data. + /// + /// The number of columns, 5 to 200. + public DotCodeSymbology(int columns) + { + Guard.MustBeBetweenOrEqualTo(columns, DotCodeEncoder.MinimumSide, DotCodeEncoder.MaximumSide, nameof(columns)); + this.Columns = columns; + } + + /// + /// Initializes a new instance of the class with a fixed size. + /// + /// The number of columns, 5 to 200. + /// The number of rows, 5 to 200. The columns and the rows sum to an odd number. + public DotCodeSymbology(int columns, int rows) + : this(columns) + { + DotCodeEncoder.ValidateRows(columns, rows); + this.Rows = rows; + } + + /// + /// Gets the number of columns, or when the size follows from the data. + /// + public int? Columns { get; } + + /// + /// Gets the number of rows, or when the rows follow from the data. + /// + public int? Rows { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + double xDimension = options.ResolveXDimension(0, DefaultMatrixXDimension, double.PositiveInfinity); + DotCodeEncoder.BuildSymbol(text, false, this.Columns ?? 0, this.Rows ?? 0, xDimension, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/DotCode/Gs1DotCodeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/DotCode/Gs1DotCodeSymbology.cs new file mode 100644 index 00000000..f7a99f25 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/DotCode/Gs1DotCodeSymbology.cs @@ -0,0 +1,104 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The GS1 DotCode symbology: DotCode carrying GS1 element strings, section 5.8 of the GS1 General Specifications. The text is the element strings with their +/// Application Identifiers in parentheses, for example (01)09521234543213(10)ABC123. The human readable interpretation is drawn below the symbol when +/// is set. +/// +/// Section 5.8.2.4: "GS1 DotCode SHALL only be used to meet the requirements of the EU tobacco traceability regulation EU 2018/574". Table 5-58, symbol +/// specification table 12 for that regulation, gives the unit pack an X dimension of 0.380 to 0.990 millimetres with a target of 0.380. An X dimension +/// outside that range draws at the nearest limit. +/// +/// +public sealed class Gs1DotCodeSymbology : BarcodeSymbology +{ + /// + /// The nominal X dimension in millimetres, the target of Table 5-58 for a unit pack. + /// + private const double XDimension = 0.380; + + /// + /// The smallest X dimension in millimetres, the minimum of Table 5-58 for a unit pack. + /// + private const double MinimumXDimension = 0.380; + + /// + /// The largest X dimension in millimetres, the maximum of Table 5-58 for a unit pack. + /// + private const double MaximumXDimension = 0.990; + + /// + /// Initializes a new instance of the class. The symbol takes the smallest size near a width to height ratio of 3:2 that + /// holds the data. + /// + public Gs1DotCodeSymbology() + { + } + + /// + /// Initializes a new instance of the class with a fixed number of columns. The symbol takes the fewest rows that hold the + /// data. + /// + /// The number of columns, 5 to 200. + public Gs1DotCodeSymbology(int columns) + { + Guard.MustBeBetweenOrEqualTo(columns, DotCodeEncoder.MinimumSide, DotCodeEncoder.MaximumSide, nameof(columns)); + this.Columns = columns; + } + + /// + /// Initializes a new instance of the class with a fixed size. + /// + /// The number of columns, 5 to 200. + /// The number of rows, 5 to 200. The columns and the rows sum to an odd number. + public Gs1DotCodeSymbology(int columns, int rows) + : this(columns) + { + DotCodeEncoder.ValidateRows(columns, rows); + this.Rows = rows; + } + + /// + /// Gets the number of columns, or when the size follows from the data. + /// + public int? Columns { get; } + + /// + /// Gets the number of rows, or when the rows follow from the data. + /// + public int? Rows { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + // The message is an FNC1 and the prepared element strings, so it holds one less than the encoder's maximum. + InlineArray64 bufferStorage = default; + int capacity = Gs1Data.PreparedCapacity(text, DotCodeEncoder.MaximumLength - 1); + ValueStringBuilder encoded = capacity <= Gs1Data.InlineBufferLength ? new(bufferStorage) : new(capacity); + try + { + Gs1Data.Prepare(text, ref encoded); + DotCodeEncoder.BuildSymbol( + encoded.AsSpan(), + true, + this.Columns ?? 0, + this.Rows ?? 0, + options.ResolveXDimension(MinimumXDimension, XDimension, MaximumXDimension), + ref symbol); + + if (options.Font is not null) + { + Gs1Data.AddMatrixReadable(text, options.Font, ref symbol); + } + } + finally + { + encoded.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/EanUpc/AddOnLayout.cs b/src/ImageSharp.Drawing/Barcodes/EanUpc/AddOnLayout.cs new file mode 100644 index 00000000..d9505fc7 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/EanUpc/AddOnLayout.cs @@ -0,0 +1,90 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Shared encodation and layout for the two and five digit EAN/UPC add-on symbols. Add-on bars are uniform in height. For add-ons, unlike the main symbols, +/// the GS1 General Specifications print the human readable interpretation above the bars. As a result, the digits face the bar tops. The renderer reserves the +/// room for the digits, because it knows the font. +/// +internal static class AddOnLayout +{ + /// + /// The quiet zone before an add-on symbol in modules. Table 5-44 of the GS1 General Specifications separates an add-on from its main symbol by 7X to 12X + /// for EAN-13 and UPC-E and by 9X to 12X for UPC-A. The add-on symbol does not know its main symbol, so it takes the smallest separation. + /// + private const int LeadingQuietZone = 7; + + /// + /// The quiet zone after an add-on symbol in modules. Table 5-11 of the GS1 General Specifications gives the right quiet zone of an add-on as 5 modules. + /// + private const int TrailingQuietZone = 5; + + /// + /// The add-on guard bar pattern of Table 5-4 of the GS1 General Specifications: 4 modules, a bar, a space, then a bar two modules wide. + /// + private const int AddOnGuard = 0b1011; + + /// + /// The width of in modules. + /// + private const int AddOnGuardWidth = 4; + + /// + /// The add-on delineator of Table 5-4 of the GS1 General Specifications between two add-on characters: a space, then a bar. + /// + private const int Delineator = 0b01; + + /// + /// The width of in modules. + /// + private const int DelineatorWidth = 2; + + /// + /// Encodes the digits of an add-on and builds the symbol: the guard pattern, then one symbol character per digit with a delineator between each pair. + /// + /// The two or five digits, already validated. + /// The number set sequence, the first digit in the highest of one bit per digit. A set bit selects number set B. + /// The options that control layout choices. + /// The symbol to write. + public static void Build(ReadOnlySpan digits, int parity, BarcodeOptions options, ref BarcodeSymbol symbol) + { + // Five digits take 4 + (5 * 7) + (4 * 2) = 47 modules, so the stream fits the inline buffer. + InlineArray64 moduleStorage = default; + Span modules = moduleStorage; + InlineArray64 correctionStorage = default; + Span corrections = correctionStorage; + int position = 0; + EanUpcEncoder.AppendPattern(modules, ref position, AddOnGuard, AddOnGuardWidth); + for (int i = 0; i < digits.Length; i++) + { + if (i > 0) + { + EanUpcEncoder.AppendPattern(modules, ref position, Delineator, DelineatorWidth); + } + + EanUpcNumberSet numberSet = ((parity >> (digits.Length - 1 - i)) & 1) == 0 ? EanUpcNumberSet.A : EanUpcNumberSet.B; + EanUpcEncoder.AppendCharacter(modules, corrections, ref position, numberSet, digits[i]); + } + + double xDimension = options.ResolveXDimension(EanUpcEncoder.MinimumXDimension, EanUpcEncoder.NominalXDimension, EanUpcEncoder.MaximumXDimension); + EanUpcEncoder.BeginRuns(modules[..position], corrections[..position], xDimension, ref symbol, out Span heights); + + // Section 5.2.3.2 of the GS1 General Specifications: the add-on "must not extend outside the symbol height dimensions of the main symbol". The main + // symbol resolves the same bar height with a minimum and no maximum, so an add-on without limits is never taller than it. + float barHeight = options.ResolveBarHeight(symbol.XDimension, 0, EanUpcEncoder.NominalAddOnBarHeight, float.PositiveInfinity); + heights.Fill(barHeight); + + // Section 5.2.5 of the GS1 General Specifications prints the add-on interpretation above the bars. Each digit prints above its own symbol character. + // The character cells start after the guard pattern and advance by the seven character modules plus the two delineator modules. + if (options.Font is not null) + { + const int cellAdvance = EanUpcEncoder.CharacterWidth + DelineatorWidth; + symbol.ReserveText(digits.Length, digits.Length); + EanUpcEncoder.AddDigits(digits, AddOnGuardWidth, cellAdvance, BarcodeTextSide.AboveBars, -BarcodeTextPlacement.Clearance, ref symbol); + } + + symbol.EndLinear(LeadingQuietZone, TrailingQuietZone, 0F, EanUpcEncoder.RunUnit, false); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/EanUpc/Ean13Symbology.cs b/src/ImageSharp.Drawing/Barcodes/EanUpc/Ean13Symbology.cs new file mode 100644 index 00000000..84beac7c --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/EanUpc/Ean13Symbology.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The EAN-13 symbology, which the GS1 General Specifications specify. An EAN-13 symbol is 95 modules wide: a normal guard pattern, six symbol characters, a +/// centre guard pattern, six more symbol characters and a closing normal guard pattern. The leading digit has no symbol character. The number set parity of the +/// six left half characters carries it. +/// +public sealed class Ean13Symbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Ean13Symbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray14 digitStorage = default; + Span digits = ((Span)digitStorage)[..13]; + EanUpcEncoder.BuildEan13(EanUpcEncoder.ValidateAndApplyCheckDigit(text, 12, digits), options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/EanUpc/Ean2Symbology.cs b/src/ImageSharp.Drawing/Barcodes/EanUpc/Ean2Symbology.cs new file mode 100644 index 00000000..f6732715 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/EanUpc/Ean2Symbology.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The two digit EAN/UPC add-on symbology, which the GS1 General Specifications specify. It carries the issue number on an ISSN periodical barcode. The symbol +/// starts with the 4 module add-on guard bar pattern 1011. Each digit takes a seven module character from number set A or B, with a 01 delineator between +/// them. The number set parity carries the two digit value modulo four. +/// +/// The human readable interpretation prints above the bars. The quiet zone before the symbol is 7 modules, the smallest separation that Table 5-44 of the +/// GS1 General Specifications gives between an add-on and an EAN-13 or UPC-E symbol. A UPC-A symbol needs 9 modules. +/// +/// +public sealed class Ean2Symbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Ean2Symbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + if (text.Length != 2) + { + throw new ArgumentException($"EAN-2 requires exactly 2 digits; got {text.Length} characters.", nameof(text)); + } + + BarcodeSymbology.ValidateDigits(text); + + // Table 5-8 of section 5.2.2.5.1 of the GS1 General Specifications: the two-digit value modulo four selects the parity pattern, where 0 = AA, 1 = AB, 2 = BA and 3 = BB. The remainder bits + // map to the number sets directly: a set bit is B. + int parity = ((10 * (text[0] - '0')) + (text[1] - '0')) % 4; + AddOnLayout.Build(text, parity, options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/EanUpc/Ean5Symbology.cs b/src/ImageSharp.Drawing/Barcodes/EanUpc/Ean5Symbology.cs new file mode 100644 index 00000000..65b8ac9f --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/EanUpc/Ean5Symbology.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The five digit EAN/UPC add-on symbology, which the GS1 General Specifications specify. It carries the price on an ISBN book barcode. The symbol starts with +/// the 4 module add-on guard bar pattern 1011. Each digit takes a seven module character from number set A or B, with a 01 delineator between characters. +/// The add-on checksum has no character of its own. The number set parity carries it. +/// +/// The human readable interpretation prints above the bars. The quiet zone before the symbol is 7 modules, the smallest separation that Table 5-44 of the +/// GS1 General Specifications gives between an add-on and an EAN-13 or UPC-E symbol. A UPC-A symbol needs 9 modules. +/// +/// +public sealed class Ean5Symbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Ean5Symbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + if (text.Length != 5) + { + throw new ArgumentException($"EAN-5 requires exactly 5 digits; got {text.Length} characters.", nameof(text)); + } + + BarcodeSymbology.ValidateDigits(text); + + // Section 5.2.2.5.2 of the GS1 General Specifications: V = (3 x (d1 + d3 + d5) + 9 x (d2 + d4)) mod 10 selects the parity pattern. + int checksum = ((3 * ((text[0] - '0') + (text[2] - '0') + (text[4] - '0'))) + (9 * ((text[1] - '0') + (text[3] - '0')))) % 10; + AddOnLayout.Build(text, EanUpcEncoder.AddOnFiveParity[checksum], options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/EanUpc/Ean8Symbology.cs b/src/ImageSharp.Drawing/Barcodes/EanUpc/Ean8Symbology.cs new file mode 100644 index 00000000..b64b925f --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/EanUpc/Ean8Symbology.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The EAN-8 symbology, which the GS1 General Specifications specify. An EAN-8 symbol is 67 modules wide: a normal guard pattern, four symbol characters from +/// number set A, a centre guard pattern, four symbol characters from number set C and a closing normal guard pattern. All eight digits have symbol characters, +/// so no digit depends on parity. +/// +public sealed class Ean8Symbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Ean8Symbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray8 digitStorage = default; + EanUpcEncoder.BuildEan8(EanUpcEncoder.ValidateAndApplyCheckDigit(text, 7, digitStorage), options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/EanUpc/EanUpcEncoder.cs b/src/ImageSharp.Drawing/Barcodes/EanUpc/EanUpcEncoder.cs new file mode 100644 index 00000000..d90b0348 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/EanUpc/EanUpcEncoder.cs @@ -0,0 +1,581 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Shared encodation for the EAN/UPC symbology family as specified in section 5.2 of the GS1 General Specifications. Symbol characters are seven modules +/// wide, and each carries one digit through number set A, B or C. The choice of number set carries the implied extra digit of EAN-13 and the check digit of +/// UPC-E. +/// +internal static class EanUpcEncoder +{ + /// + /// The nominal X dimension in millimetres. Section 5.2.6.6 of the GS1 General Specifications: "X-dimension = 0.330 mm". + /// + public const double NominalXDimension = 0.33; + + /// + /// The smallest X dimension in millimetres of an EAN-13, EAN-8, UPC-A or UPC-E symbol and its add-on. This value is the smallest minimum in the symbol + /// specification tables of section 5.12.3 that list these symbols: 0.170 in Table 5-52. Section 5.2.3.3: "The X-dimension of an add-on symbol SHALL be the + /// same as the X-dimension of its associated main symbol", so the add-on takes the same range. + /// + public const double MinimumXDimension = 0.170; + + /// + /// The largest X dimension in millimetres of an EAN-13, EAN-8, UPC-A or UPC-E symbol and its add-on, the maximum of every symbol specification table that + /// lists them. + /// + public const double MaximumXDimension = 0.660; + + /// + /// The nominal EAN-13, UPC-A and UPC-E bar height in modules: section 5.2.3.2 of the GS1 General Specifications gives 22.85mm at the nominal 0.33mm + /// X-dimension. + /// + public const float NominalBarHeight = 69.24F; + + /// + /// The nominal EAN-8 bar height in modules: section 5.2.3.2 of the GS1 General Specifications gives 18.23mm at the nominal 0.33mm X-dimension. + /// + public const float NominalEan8BarHeight = 55.24F; + + /// + /// The smallest EAN-13, UPC-A and UPC-E bar height in millimetres: 18.28mm, the smallest "Minimum symbol height" of the symbol specification tables of + /// section 5.12.3 of the GS1 General Specifications. The notes of Table 5-44: "the bar height SHALL NOT be truncated below the minimum as listed in the + /// table above", and "There is no maximum for the height". + /// + public const double MinimumBarHeight = 18.28; + + /// + /// The nominal add-on bar height in modules: Figures 5-27 and 5-28 of the GS1 General Specifications give 21.90mm at the nominal 0.33mm X-dimension. + /// + public const float NominalAddOnBarHeight = 66.36F; + + /// + /// The width of an EAN-8 symbol in modules. + /// + public const int Ean8Width = 67; + + /// + /// The width of an EAN-13 symbol in modules, which a UPC-A symbol shares. + /// + public const int Ean13Width = 95; + + /// + /// The width of a run unit in modules. + /// + public const float RunUnit = 1F / RunUnitsPerModule; + + /// + /// The normal guard pattern that opens and closes a symbol: bar, space, bar. + /// + public const int NormalGuard = 0b101; + + /// + /// The width of in modules. + /// + public const int NormalGuardWidth = 3; + + /// + /// The width of a symbol character in modules. + /// + public const int CharacterWidth = 7; + + /// + /// The first module of the right half of an EAN-13 or UPC-A symbol: the normal guard pattern, six symbol characters and the centre guard pattern. + /// + public const int Ean13RightHalfStart = NormalGuardWidth + (6 * CharacterWidth) + CentreGuardWidth; + + /// + /// The first module of the right half of an EAN-8 symbol: the normal guard pattern, four symbol characters and the centre guard pattern. + /// + public const int Ean8RightHalfStart = NormalGuardWidth + (4 * CharacterWidth) + CentreGuardWidth; + + /// + /// The width in modules of the first and last digits of a UPC-A or UPC-E symbol. Section 5.2.5 of the GS1 General Specifications: "the size of the first + /// and last digits should be reduced to a maximum width equivalent to four modules". + /// + public const int ReducedDigitWidth = 4; + + /// + /// The gap in modules between the first digit of a UPC-A or UPC-E symbol and the left guard pattern. Section 5.2.5 of the GS1 General Specifications: + /// "The right-hand side of the first digit is positioned five module widths to the left of the leftmost guard bar." + /// + public const int ReducedFirstDigitGap = 5; + + /// + /// The number of modules a guard pattern extends below the digit bars. Section 5.2.3.2 of the GS1 General Specifications: "the bars (dark bars) forming + /// the left, centre and right guard bar patterns SHALL be extended downward by 5x". + /// + private const float GuardExtension = 5F; + + /// + /// The smallest EAN-8 bar height in millimetres: 14.58mm, the smallest "Minimum symbol height" of the same tables, with the same notes. + /// + private const double MinimumEan8BarHeight = 14.58; + + /// + /// The gap in modules between the leftmost digit of an EAN-13 symbol and the start guard pattern. Section 5.2.5 of the GS1 General Specifications prints + /// this digit "to the left of the start guard pattern" and gives no distance. + /// + private const int Ean13LeadingDigitGap = 2; + + /// + /// The number of run units in a module. Section 5.2.3.1 of the GS1 General Specifications: for the characters 1, 2, 7 and 8 "the bars (dark bars) and + /// spaces (light bars) are reduced or enlarged by one-thirteenth of a module", so a run counts thirteenths of a module. + /// + private const int RunUnitsPerModule = 13; + + /// + /// The centre guard pattern between the two halves of a symbol: space, bar, space, bar, space. + /// + private const int CentreGuard = 0b01010; + + /// + /// The width of in modules. + /// + private const int CentreGuardWidth = 5; + + /// + /// The quiet zone before an EAN-13 symbol in modules, which holds the leading digit. + /// + private const int Ean13LeadingQuietZone = 11; + + /// + /// The quiet zone after an EAN-13 symbol, and on both sides of an EAN-8 symbol, in modules. + /// + private const int QuietZone = 7; + + /// + /// Gets the number set sequence for the six left-half characters of an EAN-13 symbol, indexed by the leading digit. A set bit selects number set B, a clear + /// bit number set A, most significant bit first. Table 5-5 of the GS1 General Specifications carries the thirteenth digit through this variable parity. The + /// leading digit has no symbol character of its own. + /// + public static ReadOnlySpan Ean13LeftParity => + [ + 0b000000, 0b001011, 0b001101, 0b001110, 0b010011, + 0b011001, 0b011100, 0b010101, 0b010110, 0b011010, + ]; + + /// + /// Gets the number set sequence for the six characters of a UPC-E symbol, indexed by the check digit, from Table 5-6 of the GS1 General Specifications. A + /// set bit selects number set B, most significant bit first. The check digit of UPC-E has no symbol character of its own. + /// + public static ReadOnlySpan UpcEParity => + [ + 0b111000, 0b110100, 0b110010, 0b110001, 0b101100, + 0b100110, 0b100011, 0b101010, 0b101001, 0b100101, + ]; + + /// + /// Gets the number set sequence for a five-digit add-on, indexed by the add-on checksum. A set bit selects number set B, most significant bit first. The + /// GS1 General Specifications derive the checksum from the add-on digits and convey it only through this parity. + /// + public static ReadOnlySpan AddOnFiveParity => + [ + 0b11000, 0b10100, 0b10010, 0b10001, 0b01100, + 0b00110, 0b00011, 0b01010, 0b01001, 0b00101, + ]; + + /// + /// Gets the number set A symbol characters indexed by digit, seven modules per character with the most significant bit first and 1 meaning a dark module. + /// Section 5.2.1.2.1 of the GS1 General Specifications: "Symbol characters in number set A are odd parity characters." Every character starts with a + /// space module and ends with a bar module. + /// + private static ReadOnlySpan NumberSetA => + [ + 0b0001101, 0b0011001, 0b0010011, 0b0111101, 0b0100011, + 0b0110001, 0b0101111, 0b0111011, 0b0110111, 0b0001011, + ]; + + /// + /// Gets the number set B symbol characters indexed by digit. Section 5.2.1.2.1 of the GS1 General Specifications gives number set B even parity, and + /// "Number set C characters are mirror images of number set B characters", so each character is number set C for the same digit, read in reverse module + /// order. + /// + private static ReadOnlySpan NumberSetB => + [ + 0b0100111, 0b0110011, 0b0011011, 0b0100001, 0b0011101, + 0b0111001, 0b0000101, 0b0010001, 0b0001001, 0b0010111, + ]; + + /// + /// Gets the number set C symbol characters indexed by digit. Table 5-3 of the GS1 General Specifications gives number set C the element widths of number + /// set A with bars and spaces exchanged, so each character is the module-wise inverse of number set A. Every character starts with a bar module and ends + /// with a space module. + /// + private static ReadOnlySpan NumberSetC => + [ + 0b1110010, 0b1100110, 0b1101100, 0b1000010, 0b1011100, + 0b1001110, 0b1010000, 0b1000100, 0b1001000, 0b1110100, + ]; + + /// + /// Gets the indexes of the extended bars of an EAN-13 symbol: the two bars of each of the left, centre and right guard patterns. + /// + private static ReadOnlySpan Ean13GuardBars => [0, 1, 14, 15, 28, 29]; + + /// + /// Gets the indexes of the extended bars of an EAN-8 symbol. + /// + private static ReadOnlySpan Ean8GuardBars => [0, 1, 10, 11, 20, 21]; + + /// + /// Gets the correction of the bars of a number set A character in run units, indexed by digit, from Table 5-10 of the GS1 General Specifications: 0.025 mm + /// at the nominal X dimension of 0.330 mm, one run unit. A bar of the characters 1 and 2 is reduced and a bar of 7 and 8 enlarged, and each space takes the + /// opposite. Number sets B and C take the opposite of number set A. + /// + private static ReadOnlySpan NumberSetABarCorrections => [0, -1, -1, 0, 0, 0, 0, 1, 1, 0]; + + /// + /// Validates that the text holds only the decimal digits 0 to 9, and that its length is the data length or the data length plus a check digit. When the + /// check digit is present, this method compares it with the calculated one. When it is absent, this method calculates it. The digits written always carry + /// the check digit. + /// + /// The input text. + /// The number of data digits, excluding the check digit. + /// The buffer the digits are written into, of at least the data length plus one. + /// The written digits, which are the data digits with the check digit appended. + /// The text has a wrong length, contains a non-digit, or carries a wrong check digit. + public static ReadOnlySpan ValidateAndApplyCheckDigit(ReadOnlySpan text, int dataLength, Span destination) + { + Guard.MustBeBetweenOrEqualTo(text.Length, dataLength, dataLength + 1, nameof(text)); + BarcodeSymbology.ValidateDigits(text); + + int check = Gs1Data.ComputeCheckDigit(text[..dataLength]); + if (text.Length > dataLength && text[dataLength] - '0' != check) + { + throw new ArgumentException($"Check digit mismatch: expected {check}, got {text[dataLength]}.", nameof(text)); + } + + text[..dataLength].CopyTo(destination); + destination[dataLength] = (char)('0' + check); + return destination[..(dataLength + 1)]; + } + + /// + /// Appends a pattern to the module stream, most significant bit first. + /// + /// The module stream, where 1 is a dark module. + /// The write position, advanced by . + /// The pattern bits. + /// The number of pattern bits. + public static void AppendPattern(Span modules, ref int position, int pattern, int bitCount) + { + for (int bit = bitCount - 1; bit >= 0; bit--) + { + modules[position++] = (byte)((pattern >> bit) & 1); + } + } + + /// + /// Appends the symbol character of one digit to the module stream, and the width correction of Table 5-10 of the GS1 General Specifications at the first + /// module of each of its bars and spaces. + /// + /// The module stream, where 1 is a dark module. + /// The width corrections in run units, one for each module, 0 where no element starts or no correction applies. + /// The write position, advanced by . + /// The number set the digit is encoded in. + /// The digit, already validated. + public static void AppendCharacter(Span modules, Span corrections, ref int position, EanUpcNumberSet numberSet, char digit) + { + int value = digit - '0'; + ReadOnlySpan patterns = numberSet switch + { + EanUpcNumberSet.A => NumberSetA, + EanUpcNumberSet.B => NumberSetB, + _ => NumberSetC, + }; + + int start = position; + AppendPattern(modules, ref position, patterns[value], CharacterWidth); + + int bar = numberSet == EanUpcNumberSet.A ? NumberSetABarCorrections[value] : -NumberSetABarCorrections[value]; + if (bar == 0) + { + return; + } + + // A character has two bars and two spaces, so its width stays seven modules. + for (int i = start; i < position; i++) + { + if (i == start || modules[i] != modules[i - 1]) + { + corrections[i] = (sbyte)(modules[i] == 1 ? bar : -bar); + } + } + } + + /// + /// Begins a linear symbol from a module stream and writes its alternating bar and space run widths in run units of . The stream must + /// start and end with a dark module so that even run indexes are bars. A symbol character starts and ends on a module of the other colour from the + /// patterns beside it. As a result, each of its bars and spaces is one run. The correction at the first module of a run applies to that run. + /// + /// The module stream, where 1 is a dark module. + /// The width corrections in run units from . + /// The X dimension in millimetres, . + /// The symbol to write. + /// The bar heights of the symbol, to write. + public static void BeginRuns( + ReadOnlySpan modules, + ReadOnlySpan corrections, + double xDimension, + ref BarcodeSymbol symbol, + out Span heights) + { + int count = 1; + for (int i = 1; i < modules.Length; i++) + { + if (modules[i] != modules[i - 1]) + { + count++; + } + } + + Span runs = symbol.BeginLinear(count, xDimension, out heights, out _); + symbol.SetCorrectedModuleUnits(RunUnitsPerModule); + int run = 0; + int start = 0; + for (int i = 1; i < modules.Length; i++) + { + if (modules[i] != modules[i - 1]) + { + runs[run++] = ((i - start) * RunUnitsPerModule) + corrections[start]; + start = i; + } + } + + runs[run] = ((modules.Length - start) * RunUnitsPerModule) + corrections[start]; + } + + /// + /// Writes the bar heights of a symbol whose bars are top aligned. The listed guard bars extend downwards by , with or without + /// text. + /// + /// The per-bar heights in modules to write. + /// The digit bar height in modules. + /// The indexes of the extended bars. + public static void FillGuardedHeights(Span heights, float barHeight, ReadOnlySpan guardBars) + { + heights.Fill(barHeight); + foreach (int guard in guardBars) + { + heights[guard] = barHeight + GuardExtension; + } + } + + /// + /// Adds one text placement per digit, each centered in its own character cell. The nominal symbol drawings of section 5.2.6.6 of the GS1 General + /// Specifications print every digit of the interpretation below its own symbol character. For add-ons, each digit prints above its own symbol character. + /// The drawings do not center digit groups, so placement is per digit. + /// + /// The digits to place. + /// The left edge of the first character cell, in modules. + /// The distance between successive cell left edges, in modules. + /// The side of the bars the digits print on. + /// The edge of the ink of the digits, in modules from the symbol top. + /// The symbol to write, with room for the digits reserved. + public static void AddDigits( + ReadOnlySpan digits, + float firstCellLeft, + float cellAdvance, + BarcodeTextSide side, + float textEdge, + ref BarcodeSymbol symbol) + { + for (int i = 0; i < digits.Length; i++) + { + float left = firstCellLeft + (i * cellAdvance); + symbol.AddText(digits.Slice(i, 1), left, left + CharacterWidth, side, textEdge); + } + } + + /// + /// Encodes thirteen validated digits into an EAN-13 symbol. + /// + /// The thirteen digits, with the check digit already validated. + /// The options that control layout choices. + /// The symbol to write. + public static void BuildEan13(ReadOnlySpan digits, BarcodeOptions options, ref BarcodeSymbol symbol) + => BuildEan13(digits, options, false, [], [], [], ref symbol); + + /// + /// Encodes thirteen validated digits into an EAN-13 symbol with a caption above the bars. The ISBN, ISMN and ISSN symbologies print their own number above + /// their EAN-13 symbol. The caption stands one module above the bar tops. The renderer reserves the room for the caption, because it knows the font. + /// + /// The thirteen digits, with the check digit already validated. + /// The options that control layout choices. + /// The characters of the caption before its body. + /// The body of the caption. + /// The characters of the caption after its body. + /// The symbol to write. + public static void BuildEan13( + ReadOnlySpan digits, + BarcodeOptions options, + ReadOnlySpan captionPrefix, + ReadOnlySpan captionBody, + ReadOnlySpan captionSuffix, + ref BarcodeSymbol symbol) + => BuildEan13(digits, options, true, captionPrefix, captionBody, captionSuffix, ref symbol); + + /// + /// Encodes eight validated digits into an EAN-8 symbol. + /// + /// The eight digits, with the check digit already validated. + /// The options that control layout choices. + /// The symbol to write. + public static void BuildEan8(ReadOnlySpan digits, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray128 moduleStorage = default; + Span modules = ((Span)moduleStorage)[..Ean8Width]; + InlineArray128 correctionStorage = default; + Span corrections = ((Span)correctionStorage)[..Ean8Width]; + WriteEan8Modules(digits, modules, corrections); + + BeginRuns( + modules, + corrections, + options.ResolveXDimension(MinimumXDimension, NominalXDimension, MaximumXDimension), + ref symbol, + out Span heights); + + float barHeight = options.ResolveBarHeight( + symbol.XDimension, + (float)(MinimumEan8BarHeight / symbol.XDimension), + NominalEan8BarHeight, + float.PositiveInfinity); + + FillGuardedHeights(heights, barHeight, Ean8GuardBars); + + // The nominal symbol drawings of section 5.2.6.6 of the GS1 General Specifications print every digit below its own symbol character. Section 5.2.5: + // "Normally the minimum is one module" between the digits and the bars. Digits flow past the extended guard bars. + if (options.Font is not null) + { + symbol.ReserveText(digits.Length, digits.Length); + float textEdge = barHeight + BarcodeTextPlacement.Clearance; + AddDigits(digits[..4], NormalGuardWidth, CharacterWidth, BarcodeTextSide.BelowBars, textEdge, ref symbol); + AddDigits(digits[4..], Ean8RightHalfStart, CharacterWidth, BarcodeTextSide.BelowBars, textEdge, ref symbol); + } + + symbol.EndLinear(QuietZone, QuietZone, 0F, RunUnit, false); + } + + /// + /// Writes the modules of an EAN-8 symbol: the guard patterns, four digits of number set A and four of number set C. + /// + /// The eight digits, with the check digit already validated. + /// The modules to write, 1 for dark. + /// The width corrections to write, zeroed by the caller. + public static void WriteEan8Modules(ReadOnlySpan digits, Span modules, Span corrections) + { + int position = 0; + AppendPattern(modules, ref position, NormalGuard, NormalGuardWidth); + foreach (char digit in digits[..4]) + { + AppendCharacter(modules, corrections, ref position, EanUpcNumberSet.A, digit); + } + + AppendPattern(modules, ref position, CentreGuard, CentreGuardWidth); + foreach (char digit in digits[4..8]) + { + AppendCharacter(modules, corrections, ref position, EanUpcNumberSet.C, digit); + } + + AppendPattern(modules, ref position, NormalGuard, NormalGuardWidth); + } + + /// + /// Writes the modules of an EAN-13 symbol: the guard patterns, six left half digits of number sets A and B and six right half digits of number set C. The + /// leading digit of an EAN-13 number has no symbol character, and the number set sequence of the left half carries it. + /// + /// The left half number set sequence from , the first digit in the highest of six bits. + /// The twelve digits that take symbol characters. + /// The modules to write, 1 for dark. + /// The width corrections to write, zeroed by the caller. + public static void WriteEan13Modules(int leftParity, ReadOnlySpan digits, Span modules, Span corrections) + { + int position = 0; + AppendPattern(modules, ref position, NormalGuard, NormalGuardWidth); + for (int i = 0; i < 6; i++) + { + EanUpcNumberSet numberSet = ((leftParity >> (5 - i)) & 1) == 0 ? EanUpcNumberSet.A : EanUpcNumberSet.B; + AppendCharacter(modules, corrections, ref position, numberSet, digits[i]); + } + + AppendPattern(modules, ref position, CentreGuard, CentreGuardWidth); + foreach (char digit in digits[6..12]) + { + AppendCharacter(modules, corrections, ref position, EanUpcNumberSet.C, digit); + } + + AppendPattern(modules, ref position, NormalGuard, NormalGuardWidth); + } + + /// + /// Encodes thirteen validated digits into an EAN-13 symbol, with or without a caption above the bars. + /// + /// The thirteen digits, with the check digit already validated. + /// The options that control layout choices. + /// Whether the symbol carries a caption above the bars. + /// The characters of the caption before its body. + /// The body of the caption. + /// The characters of the caption after its body. + /// The symbol to write. + private static void BuildEan13( + ReadOnlySpan digits, + BarcodeOptions options, + bool hasCaption, + ReadOnlySpan captionPrefix, + ReadOnlySpan captionBody, + ReadOnlySpan captionSuffix, + ref BarcodeSymbol symbol) + { + InlineArray128 moduleStorage = default; + Span modules = ((Span)moduleStorage)[..Ean13Width]; + InlineArray128 correctionStorage = default; + Span corrections = ((Span)correctionStorage)[..Ean13Width]; + WriteEan13Modules(Ean13LeftParity[digits[0] - '0'], digits[1..], modules, corrections); + + BeginRuns( + modules, + corrections, + options.ResolveXDimension(MinimumXDimension, NominalXDimension, MaximumXDimension), + ref symbol, + out Span heights); + + float barHeight = options.ResolveBarHeight(symbol.XDimension, (float)(MinimumBarHeight / symbol.XDimension), NominalBarHeight, float.PositiveInfinity); + FillGuardedHeights(heights, barHeight, Ean13GuardBars); + + // Section 5.2.5 of the GS1 General Specifications: "the leftmost digit, which is encoded by variable parity, is printed to the left of the start guard + // pattern in line with the other digits". The nominal symbol drawings of section 5.2.6.6 print every other digit below its own symbol character. + // "Normally the minimum is one module" between the digits and the bars. Digits flow past the extended guard bars. A caption stands one module above the + // bar tops. + if (options.Font is not null) + { + int captionLength = captionPrefix.Length + captionBody.Length + captionSuffix.Length; + symbol.ReserveText(digits.Length + (hasCaption ? 1 : 0), digits.Length + captionLength); + float textEdge = barHeight + BarcodeTextPlacement.Clearance; + symbol.AddText(digits[..1], -Ean13LeadingDigitGap - CharacterWidth, -Ean13LeadingDigitGap, BarcodeTextSide.BelowBars, textEdge); + AddDigits(digits[1..7], NormalGuardWidth, CharacterWidth, BarcodeTextSide.BelowBars, textEdge, ref symbol); + AddDigits(digits[7..13], Ean13RightHalfStart, CharacterWidth, BarcodeTextSide.BelowBars, textEdge, ref symbol); + if (hasCaption) + { + symbol.AddText( + captionPrefix, + captionBody, + captionSuffix, + 0F, + Ean13Width, + BarcodeTextSide.AboveBars, + -BarcodeTextPlacement.Clearance, + 1F, + true, + BarcodeTextAlignment.Centered); + } + } + + symbol.EndLinear(Ean13LeadingQuietZone, QuietZone, 0F, RunUnit, false); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/EanUpc/EanUpcNumberSet.cs b/src/ImageSharp.Drawing/Barcodes/EanUpc/EanUpcNumberSet.cs new file mode 100644 index 00000000..21aedcc0 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/EanUpc/EanUpcNumberSet.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The number sets of Table 5-3 of the GS1 General Specifications that an EAN/UPC symbol character is taken from. +/// +internal enum EanUpcNumberSet +{ + /// + /// Number set A, odd parity. + /// + A, + + /// + /// Number set B, even parity. + /// + B, + + /// + /// Number set C, the right half of the symbol. + /// + C, +} diff --git a/src/ImageSharp.Drawing/Barcodes/EanUpc/IsbnSymbology.cs b/src/ImageSharp.Drawing/Barcodes/EanUpc/IsbnSymbology.cs new file mode 100644 index 00000000..061477dc --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/EanUpc/IsbnSymbology.cs @@ -0,0 +1,148 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The ISBN data layer over EAN-13. An International Standard Book Number (ISO 2108) draws as an EAN-13 symbol in the GS1 978 or 979 prefix range, except +/// the 979-0 prefix of the ISMN. The ISBN itself prints above the bars. +/// +/// The input is an ISBN-13, with or without hyphens, or an ISBN-10. This class compares the modulus 11 check digit of an ISBN-10, where X stands for ten, and +/// then converts the number to its 978 prefixed EAN-13 form. The check digit is optional in either form, and this class calculates it when the input carries +/// none. +/// +/// +public sealed class IsbnSymbology : BarcodeSymbology +{ + /// + /// The smallest size of the caption in points. Section 8.1 of the ISBN Users' Manual, 7th edition: "The ISBN should always be printed in type large + /// enough to be easily legible (i.e., 9-point or larger)." + /// + private const float MinimumCaptionSize = 9F; + + /// + /// Initializes a new instance of the class. + /// + public IsbnSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + // An ISBN is at most seventeen characters: thirteen digits and four hyphens. Longer text is rejected before any buffer, so the hyphenless form fits + // eighteen inline characters. + Guard.MustBeLessThanOrEqualTo(text.Length, 17, nameof(text)); + InlineArray18 compactStorage = default; + Span compactBuffer = compactStorage; + int compactLength = 0; + foreach (char character in text) + { + if (character != '-') + { + compactBuffer[compactLength++] = character; + } + } + + // Every character is a digit. The one exception is the check character X, which can be the last character of an ISBN-10. The message decodes the code + // point at the first other character, so a surrogate pair reports as the one character it is. + ReadOnlySpan compact = compactBuffer[..compactLength]; + int invalid = compact.IndexOfAnyExceptInRange('0', '9'); + if (invalid >= 0 && !(invalid == 9 && compact.Length == 10 && compact[9] == 'X')) + { + throw new ArgumentException( + $"ISBN accepts only digits, hyphens and a trailing X check character; got {compact.CodePointAt(invalid).ToDisplayString()}.", + nameof(text)); + } + + // The encoded form is thirteen digits: twelve data digits and the check digit the caption repeats. + InlineArray14 digitStorage = default; + Span digits = ((Span)digitStorage)[..13]; + ReadOnlySpan captionPrefix; + ReadOnlySpan captionBody; + if (compact.Length is 12 or 13) + { + if (!compact.StartsWith("978", StringComparison.Ordinal) && !compact.StartsWith("979", StringComparison.Ordinal)) + { + throw new ArgumentException("An ISBN-13 must start with the 978 or 979 prefix.", nameof(text)); + } + + // Footnote 1 of section 2.1.3.4 of the GS1 General Specifications: "Within GS1 Prefix 979 a subset 9790 has been allocated to the International ISMN Agency for notated music." + if (compact.StartsWith("9790", StringComparison.Ordinal)) + { + throw new ArgumentException("The 979-0 prefix belongs to the ISMN, not the ISBN.", nameof(text)); + } + + if (compact.Length == 13 && compact[12] - '0' != Gs1Data.ComputeCheckDigit(compact[..12])) + { + throw new ArgumentException("Incorrect ISBN-13 check digit.", nameof(text)); + } + + compact[..12].CopyTo(digits); + captionPrefix = "ISBN "; + captionBody = TakeHyphenatedPrefix(text, 12); + } + else if (compact.Length is 9 or 10) + { + // ISO 2108 ISBN-10 check: weights 10 down to 2 over the nine data digits, modulus 11, with X representing the value ten. From the right, the + // weights are 2 to 10. + int check = Modulo11(compact[..9], 10); + if (compact.Length == 10) + { + int provided = compact[9] == 'X' ? 10 : compact[9] - '0'; + if (provided != check) + { + throw new ArgumentException("Incorrect ISBN-10 check digit.", nameof(text)); + } + } + + "978".CopyTo(digits); + compact[..9].CopyTo(digits[3..]); + captionPrefix = "ISBN 978-"; + captionBody = TakeHyphenatedPrefix(text, 9); + } + else + { + throw new ArgumentException($"ISBN requires 9, 10, 12 or 13 digits; got {compact.Length}.", nameof(text)); + } + + // The caption always carries the EAN-13 check digit of the encoded form, so the printed number and the symbol agree even when an ISBN-10 was supplied. + int eanCheck = Gs1Data.ComputeCheckDigit(digits[..12]); + digits[12] = (char)('0' + eanCheck); + if (options.Font is null) + { + EanUpcEncoder.BuildEan13(digits, options, ref symbol); + } + else + { + EanUpcEncoder.BuildEan13(digits, options, captionPrefix, captionBody, ['-', digits[12]], ref symbol); + symbol.SetCaptionMinimumSize(MinimumCaptionSize); + } + } + + /// + /// Returns the prefix of a hyphenated number that contains the given count of digits. The prefix keeps the hyphens of the caller and drops a trailing + /// hyphen. The ISBN caption shows the number as the caller wrote it, without its check character. + /// + /// The hyphenated input. + /// The number of digits the prefix must contain. + /// The hyphenated prefix. + private static ReadOnlySpan TakeHyphenatedPrefix(ReadOnlySpan text, int digitCount) + { + int digits = 0; + int end = 0; + while (digits < digitCount) + { + if (text[end] != '-') + { + digits++; + } + + end++; + } + + return text[..end]; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/EanUpc/IsmnSymbology.cs b/src/ImageSharp.Drawing/Barcodes/EanUpc/IsmnSymbology.cs new file mode 100644 index 00000000..fb2513a3 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/EanUpc/IsmnSymbology.cs @@ -0,0 +1,112 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The ISMN data layer over EAN-13. An International Standard Music Number (ISO 10957) draws as an EAN-13 symbol in the GS1 979-0 range. The ISMN itself prints +/// above the bars. +/// +/// The input is a thirteen digit ISMN that starts 9790, with or without hyphens. The older ten character form that starts with M is also accepted, and 9790 +/// replaces that M. ISO 10957:2009 makes the check digit of both forms the EAN-13 check digit of the thirteen digit number. The check digit is optional, and +/// this class calculates it when the input carries none. +/// +/// +/// The printed ISMN separates its four elements with hyphens. The publisher element ranges of the ISMN Users' Manual give the length of the publisher +/// element, so the hyphens of the input do not change the printed ISMN. +/// +/// +public sealed class IsmnSymbology : BarcodeSymbology +{ + /// + /// The smallest size of the caption in points. Section 7 of the ISMN Users' Manual, 2008: the ISMN "should always be printed in easily legible type (9 + /// point or larger)." + /// + private const float MinimumCaptionSize = 9F; + + /// + /// Initializes a new instance of the class. + /// + public IsmnSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + // An ISMN is at most seventeen characters: thirteen digits and four hyphens. Longer text is rejected before any buffer, so the hyphenless form fits + // eighteen inline characters. + Guard.MustBeLessThanOrEqualTo(text.Length, 17, nameof(text)); + InlineArray18 compactStorage = default; + Span compactBuffer = compactStorage; + int compactLength = 0; + foreach (char character in text) + { + if (character != '-') + { + compactBuffer[compactLength++] = character; + } + } + + ReadOnlySpan compact = compactBuffer[..compactLength]; + bool mForm = compact.Length > 0 && compact[0] == 'M'; + ReadOnlySpan body = mForm ? compact[1..] : compact; + BarcodeSymbology.ValidateDigits(body); + + // The encoded form is thirteen digits: twelve data digits and the check digit the caption repeats. ISO 10957:2009 displays an ISMN only in its 979-0 + // form, so the caption converts the older M prefix rather than echoing it. + InlineArray14 digitStorage = default; + Span digits = ((Span)digitStorage)[..13]; + if (mForm && body.Length is 8 or 9) + { + "9790".CopyTo(digits); + body[..8].CopyTo(digits[4..]); + } + else if (!mForm && body.Length is 12 or 13 && body.StartsWith("9790", StringComparison.Ordinal)) + { + body[..12].CopyTo(digits); + } + else + { + throw new ArgumentException("ISMN requires M plus 8 digits or the 13 digit 9790 form, with an optional check digit.", nameof(text)); + } + + int check = Gs1Data.ComputeCheckDigit(digits[..12]); + bool hasCheck = (mForm && body.Length == 9) || (!mForm && body.Length == 13); + if (hasCheck && body[^1] - '0' != check) + { + throw new ArgumentException("Incorrect ISMN check digit.", nameof(text)); + } + + digits[12] = (char)('0' + check); + if (options.Font is null) + { + EanUpcEncoder.BuildEan13(digits, options, ref symbol); + } + else + { + // Section 2.1 of the ISMN Users' Manual, 2008: "When the ISMN is printed, each element is separated by a hyphen or space." Section 2.2 gives the + // publisher element the ranges 000 - 099, 1000 - 3999, 40000 - 69999, 700000 - 899999 and 9000000 - 9999999, so its first digit gives its + // length. The item element takes the rest of the eight digits after 979-0. + ReadOnlySpan number = digits[4..12]; + int publisherLength = number[0] switch + { + '0' => 3, + <= '3' => 4, + <= '6' => 5, + <= '8' => 6, + _ => 7, + }; + + InlineArray10 captionStorage = default; + Span captionBody = ((Span)captionStorage)[..9]; + number[..publisherLength].CopyTo(captionBody); + captionBody[publisherLength] = '-'; + number[publisherLength..].CopyTo(captionBody[(publisherLength + 1)..]); + EanUpcEncoder.BuildEan13(digits, options, "ISMN 979-0-", captionBody, ['-', digits[12]], ref symbol); + symbol.SetCaptionMinimumSize(MinimumCaptionSize); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/EanUpc/IssnSymbology.cs b/src/ImageSharp.Drawing/Barcodes/EanUpc/IssnSymbology.cs new file mode 100644 index 00000000..7f387bd4 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/EanUpc/IssnSymbology.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The ISSN data layer over EAN-13. An International Standard Serial Number (ISO 3297) draws as an EAN-13 symbol in the GS1 977 prefix range. The ISSN itself +/// prints above the bars. +/// +/// The input is the standard NNNN-NNNC form, where the check character C is optional and X stands for ten. A space and a two digit sequence variant can follow +/// it, which separates issue level variations. That variant is 00 when the input carries none. +/// +/// +/// The EAN-13 form is 977, the seven ISSN data digits, the sequence variant and the EAN check digit. The bars do not carry the ISSN check character itself. +/// +/// +public sealed class IssnSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public IssnSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + ReadOnlySpan issn = text; + ReadOnlySpan variant = "00"; + int space = text.IndexOf(' '); + if (space >= 0) + { + issn = text.Slice(0, space); + variant = text.Slice(space + 1); + } + + if (variant.Length != 2 || variant.ContainsAnyExceptInRange('0', '9')) + { + throw new ArgumentException("The ISSN sequence variant must be exactly two digits.", nameof(text)); + } + + if (issn.Length is not (8 or 9) || issn[4] != '-') + { + throw new ArgumentException("ISSN requires the form NNNN-NNN with an optional check character.", nameof(text)); + } + + // The hyphen at index 4 is checked above. Every other character is a digit. The one exception is the check character at index 8, which can be X. The + // message decodes the code point at the first other character, so a surrogate pair reports as the one character it is. + for (int i = 0; i < issn.Length; i++) + { + char character = issn[i]; + if (i != 4 && !char.IsAsciiDigit(character) && !(i == 8 && character == 'X')) + { + throw new ArgumentException( + $"ISSN accepts only digits, one hyphen and a trailing X check character; got {issn.CodePointAt(i).ToDisplayString()}.", + nameof(text)); + } + } + + // ISO 3297 check: weights 8 down to 2 over the seven data digits, modulus 11, X representing ten. The encoded form is thirteen digits: the 977 prefix, + // the seven data digits, the sequence variant and the EAN-13 check digit. + InlineArray14 digitStorage = default; + Span digits = ((Span)digitStorage)[..13]; + "977".CopyTo(digits); + issn[..4].CopyTo(digits[3..]); + issn.Slice(5, 3).CopyTo(digits[7..]); + variant.CopyTo(digits[10..]); + + // From the right, the weights of the check are 2 to 8. + int check = Modulo11(digits.Slice(3, 7), 8); + if (issn.Length == 9) + { + int provided = issn[8] == 'X' ? 10 : issn[8] - '0'; + if (provided != check) + { + throw new ArgumentException("Incorrect ISSN check digit.", nameof(text)); + } + } + + digits[12] = (char)('0' + Gs1Data.ComputeCheckDigit(digits[..12])); + char checkChar = check == 10 ? 'X' : (char)('0' + check); + if (options.Font is null) + { + EanUpcEncoder.BuildEan13(digits, options, ref symbol); + } + else + { + EanUpcEncoder.BuildEan13(digits, options, "ISSN ", issn[..8], [checkChar], ref symbol); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/EanUpc/MandsSymbology.cs b/src/ImageSharp.Drawing/Barcodes/EanUpc/MandsSymbology.cs new file mode 100644 index 00000000..e9101052 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/EanUpc/MandsSymbology.cs @@ -0,0 +1,92 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Marks & Spencer in-house symbology. It is an EAN-8 symbol that holds the centre guard bars at digit bar height and prints the letters M and S in the +/// quiet zones. It has no public specification. +/// +/// The input is seven or eight digits, and the last digit is the EAN-8 check digit. A zero pads a seven digit number to eight. The printed interpretation +/// does not show that leading zero: the seven input digits fill the first seven character cells, and the eighth cell stays empty. +/// +/// +public sealed class MandsSymbology : BarcodeSymbology +{ + /// + /// The quiet zone in modules. This quiet zone is wider than the EAN-8 minimum of seven. The M and S letters sit twelve modules before and two modules after + /// the symbol. The wider quiet zone keeps these letters inside the reserved area. + /// + private const int QuietZone = 12; + + /// + /// Initializes a new instance of the class. + /// + public MandsSymbology() + { + } + + /// + /// Gets the indexes of the extended bars: the two bars of each outer guard pattern. The centre guard bars stay at digit bar height. + /// + private static ReadOnlySpan GuardBars => [0, 1, 20, 21]; + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Guard.MustBeBetweenOrEqualTo(text.Length, 7, 8, nameof(text)); + + // An M&S number is seven or eight characters with the check digit last, and a seven character one carries an implied leading zero, so the padded + // form fits eight inline characters. + bool padded = text.Length == 7; + InlineArray8 paddedStorage = default; + Span paddedBuffer = paddedStorage; + if (padded) + { + paddedBuffer[0] = '0'; + text.CopyTo(paddedBuffer[1..]); + } + else + { + text.CopyTo(paddedBuffer); + } + + InlineArray8 digitStorage = default; + ReadOnlySpan digits = EanUpcEncoder.ValidateAndApplyCheckDigit(paddedBuffer, 7, digitStorage); + + InlineArray128 moduleStorage = default; + Span modules = ((Span)moduleStorage)[..EanUpcEncoder.Ean8Width]; + InlineArray128 correctionStorage = default; + Span corrections = ((Span)correctionStorage)[..EanUpcEncoder.Ean8Width]; + EanUpcEncoder.WriteEan8Modules(digits, modules, corrections); + + EanUpcEncoder.BeginRuns( + modules, + corrections, + options.ResolveXDimension(0, EanUpcEncoder.NominalXDimension, double.PositiveInfinity), + ref symbol, + out Span heights); + + float digitHeight = options.ResolveBarHeight(symbol.XDimension, 0, EanUpcEncoder.NominalEan8BarHeight, float.PositiveInfinity); + EanUpcEncoder.FillGuardedHeights(heights, digitHeight, GuardBars); + + if (options.Font is not null) + { + // A padded number hides its leading zero: its seven digits, the check digit included, fill the first seven character cells, and the eighth cell + // stays empty. The full eight digit form prints as-is. + float textLine = digitHeight + BarcodeTextPlacement.Clearance; + ReadOnlySpan shown = padded ? digits[1..] : digits; + + // The M fills one character cell at the outer edge of the leading quiet zone. The S fills one character cell, two modules after the symbol. + const int characterWidth = EanUpcEncoder.CharacterWidth; + const int sLeft = EanUpcEncoder.Ean8Width + 2; + symbol.ReserveText(shown.Length + 2, shown.Length + 2); + EanUpcEncoder.AddDigits(shown[..4], EanUpcEncoder.NormalGuardWidth, characterWidth, BarcodeTextSide.BelowBars, textLine, ref symbol); + EanUpcEncoder.AddDigits(shown[4..], EanUpcEncoder.Ean8RightHalfStart, characterWidth, BarcodeTextSide.BelowBars, textLine, ref symbol); + symbol.AddText("M", -QuietZone, -QuietZone + characterWidth, BarcodeTextSide.BelowBars, textLine); + symbol.AddText("S", sLeft, sLeft + characterWidth, BarcodeTextSide.BelowBars, textLine); + } + + symbol.EndLinear(QuietZone, QuietZone, 0F, EanUpcEncoder.RunUnit, false); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/EanUpc/UpcASymbology.cs b/src/ImageSharp.Drawing/Barcodes/EanUpc/UpcASymbology.cs new file mode 100644 index 00000000..3cb1cdfc --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/EanUpc/UpcASymbology.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The UPC-A symbology, which the GS1 General Specifications specify. A UPC-A symbol has the same 95 module structure as an EAN-13 symbol with a leading zero, +/// and all six left half characters use number set A. +/// +/// Only the human readable layout differs. The number system digit and the check digit print in the quiet zones, and the bars of their symbol characters extend +/// below the others. +/// +/// +public sealed class UpcASymbology : BarcodeSymbology +{ + /// + /// The quiet zone on each side of the symbol in modules, which holds the number system digit before the bars and the check digit after them. + /// + private const int QuietZone = 9; + + /// + /// The gap in modules between the right guard pattern and the last digit. Section 5.2.5 of the GS1 General Specifications: "The left-hand side of the + /// last digit is positioned five module widths to the right of the rightmost guard bar for UPC-A barcodes". + /// + private const int LastDigitGap = 5; + + /// + /// Initializes a new instance of the class. + /// + public UpcASymbology() + { + } + + /// + /// Gets the indexes of the extended bars: the guard pattern bars plus the bars of the first and last symbol characters, whose digits print in the quiet + /// zones. + /// + private static ReadOnlySpan GuardBars => [0, 1, 2, 3, 14, 15, 26, 27, 28, 29]; + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray12 digitStorage = default; + ReadOnlySpan digits = EanUpcEncoder.ValidateAndApplyCheckDigit(text, 11, digitStorage); + + // The modules are those of the EAN-13 symbol with a leading zero, whose left half number set sequence is number set A throughout. + InlineArray128 moduleStorage = default; + Span modules = ((Span)moduleStorage)[..EanUpcEncoder.Ean13Width]; + InlineArray128 correctionStorage = default; + Span corrections = ((Span)correctionStorage)[..EanUpcEncoder.Ean13Width]; + EanUpcEncoder.WriteEan13Modules(EanUpcEncoder.Ean13LeftParity[0], digits, modules, corrections); + + EanUpcEncoder.BeginRuns( + modules, + corrections, + options.ResolveXDimension(EanUpcEncoder.MinimumXDimension, EanUpcEncoder.NominalXDimension, EanUpcEncoder.MaximumXDimension), + ref symbol, + out Span heights); + + float barHeight = options.ResolveBarHeight( + symbol.XDimension, + (float)(EanUpcEncoder.MinimumBarHeight / symbol.XDimension), + EanUpcEncoder.NominalBarHeight, + float.PositiveInfinity); + + EanUpcEncoder.FillGuardedHeights(heights, barHeight, GuardBars); + + // Section 5.2.5 of the GS1 General Specifications: "the size of the first and last digits should be reduced to a maximum width equivalent to four + // modules". "The right-hand side of the first digit is positioned five module widths to the left of the leftmost guard bar." "The left-hand side of + // the last digit is positioned five module widths to the right of the rightmost guard bar for UPC-A barcodes". The digits share one baseline, which + // aligns the bottom edges of the smaller digits with the others. Every other digit prints below its own symbol character. Digits hang one module + // below the digit bars and flow past the extended bars. + if (options.Font is not null) + { + float textLine = barHeight + BarcodeTextPlacement.Clearance; + const int firstDigitRight = -EanUpcEncoder.ReducedFirstDigitGap; + const int lastDigitLeft = EanUpcEncoder.Ean13Width + LastDigitGap; + const int characterWidth = EanUpcEncoder.CharacterWidth; + symbol.ReserveText(digits.Length, digits.Length); + symbol.AddTextWithinSpan( + digits[..1], + firstDigitRight - EanUpcEncoder.ReducedDigitWidth, + firstDigitRight, + BarcodeTextSide.BelowBars, + textLine, + BarcodeTextAlignment.Right); + + // The first symbol character carries the number system digit, so the printed left half starts at the second character. + const int leftDigitsStart = EanUpcEncoder.NormalGuardWidth + characterWidth; + EanUpcEncoder.AddDigits(digits[1..6], leftDigitsStart, characterWidth, BarcodeTextSide.BelowBars, textLine, ref symbol); + EanUpcEncoder.AddDigits(digits[6..11], EanUpcEncoder.Ean13RightHalfStart, characterWidth, BarcodeTextSide.BelowBars, textLine, ref symbol); + symbol.AddTextWithinSpan( + digits[11..12], + lastDigitLeft, + lastDigitLeft + EanUpcEncoder.ReducedDigitWidth, + BarcodeTextSide.BelowBars, + textLine, + BarcodeTextAlignment.Left); + } + + symbol.EndLinear(QuietZone, QuietZone, 0F, EanUpcEncoder.RunUnit, false); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/EanUpc/UpcESymbology.cs b/src/ImageSharp.Drawing/Barcodes/EanUpc/UpcESymbology.cs new file mode 100644 index 00000000..fcccfbfd --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/EanUpc/UpcESymbology.cs @@ -0,0 +1,228 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The UPC-E symbology, which the GS1 General Specifications specify. UPC-E is the zero suppression form of a GTIN-12 that starts with a zero. This GTIN-12 +/// holds four or five zeros in the positions of one of four zero suppression rules. The text is the leading zero and the six digits that the rules leave, with +/// an optional check digit. The symbology rejects text that the rules cannot produce. +/// +/// The symbol is 51 modules wide: a normal guard pattern, six symbol characters from number sets A and B, and a special right guard pattern. The number set +/// parity of the six characters carries the check digit of the GTIN-12. +/// +/// +public sealed class UpcESymbology : BarcodeSymbology +{ + /// + /// The width of the symbol in modules. + /// + private const int Width = 51; + + /// + /// The special guard pattern that closes the symbol: space, bar, space, bar, space, bar. + /// + private const int SpecialGuard = 0b010101; + + /// + /// The width of in modules. + /// + private const int SpecialGuardWidth = 6; + + /// + /// The quiet zone before the symbol in modules, which holds the leading zero. Table 5-11 of the GS1 General Specifications gives 9 modules. + /// + private const int LeadingQuietZone = 9; + + /// + /// The quiet zone after the symbol in modules. Table 5-11 of the GS1 General Specifications gives 7 modules. + /// + private const int TrailingQuietZone = 7; + + /// + /// The gap in modules between the special right guard pattern and the last digit. Section 5.2.5 of the GS1 General Specifications: the left-hand side of + /// the last digit is positioned "three module widths for UPC-E barcodes" to the right of the rightmost guard bar. + /// + private const int LastDigitGap = 3; + + /// + /// Initializes a new instance of the class. + /// + public UpcESymbology() + { + } + + /// + /// Gets the indexes of the extended bars: the two bars of the left guard pattern and the three bars of the special right guard pattern. + /// + private static ReadOnlySpan GuardBars => [0, 1, 14, 15, 16]; + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray12 gtinStorage = default; + int check = ExpandToGtin12(text, gtinStorage)[11] - '0'; + int parity = EanUpcEncoder.UpcEParity[check]; + + InlineArray64 moduleStorage = default; + Span modules = ((Span)moduleStorage)[..Width]; + InlineArray64 correctionStorage = default; + Span corrections = ((Span)correctionStorage)[..Width]; + int position = 0; + EanUpcEncoder.AppendPattern(modules, ref position, EanUpcEncoder.NormalGuard, EanUpcEncoder.NormalGuardWidth); + for (int i = 1; i <= 6; i++) + { + EanUpcNumberSet numberSet = ((parity >> (6 - i)) & 1) == 0 ? EanUpcNumberSet.A : EanUpcNumberSet.B; + EanUpcEncoder.AppendCharacter(modules, corrections, ref position, numberSet, text[i]); + } + + EanUpcEncoder.AppendPattern(modules, ref position, SpecialGuard, SpecialGuardWidth); + + EanUpcEncoder.BeginRuns( + modules, + corrections, + options.ResolveXDimension(EanUpcEncoder.MinimumXDimension, EanUpcEncoder.NominalXDimension, EanUpcEncoder.MaximumXDimension), + ref symbol, + out Span heights); + + float barHeight = options.ResolveBarHeight( + symbol.XDimension, + (float)(EanUpcEncoder.MinimumBarHeight / symbol.XDimension), + EanUpcEncoder.NominalBarHeight, + float.PositiveInfinity); + + EanUpcEncoder.FillGuardedHeights(heights, barHeight, GuardBars); + + // Section 5.2.5 of the GS1 General Specifications: "the size of the first and last digits should be reduced to a maximum width equivalent to four + // modules". "The right-hand side of the first digit is positioned five module widths to the left of the leftmost guard bar." The left-hand side of + // the last digit is positioned "three module widths for UPC-E barcodes" to the right of the rightmost guard bar. The digits share one baseline, which + // aligns the bottom edges of the smaller digits with the others. Every other digit prints below its own symbol character. Digits hang one module + // below the digit bars and flow past the extended guard bars. + if (options.Font is not null) + { + float textLine = barHeight + BarcodeTextPlacement.Clearance; + + // The line prints the number system digit, the six digits and the check digit. + const int printedDigits = 8; + const int firstDigitRight = -EanUpcEncoder.ReducedFirstDigitGap; + const int lastDigitLeft = Width + LastDigitGap; + symbol.ReserveText(printedDigits, printedDigits); + symbol.AddTextWithinSpan( + text.Slice(0, 1), + firstDigitRight - EanUpcEncoder.ReducedDigitWidth, + firstDigitRight, + BarcodeTextSide.BelowBars, + textLine, + BarcodeTextAlignment.Right); + + EanUpcEncoder.AddDigits( + text.Slice(1, 6), + EanUpcEncoder.NormalGuardWidth, + EanUpcEncoder.CharacterWidth, + BarcodeTextSide.BelowBars, + textLine, + ref symbol); + + symbol.AddTextWithinSpan( + [(char)('0' + check)], + lastDigitLeft, + lastDigitLeft + EanUpcEncoder.ReducedDigitWidth, + BarcodeTextSide.BelowBars, + textLine, + BarcodeTextAlignment.Left); + } + + symbol.EndLinear(LeadingQuietZone, TrailingQuietZone, 0F, EanUpcEncoder.RunUnit, false); + } + + /// + /// Validates UPC-E digits and writes the GTIN-12 that they zero suppress, with its check digit. + /// + /// The leading zero and the six compressed digits, with an optional check digit. + /// The buffer the twelve digits are written into, at least twelve long. + /// The twelve digits of the GTIN-12. + /// The text is not the zero suppression of a GTIN-12, or carries a wrong check digit. + internal static ReadOnlySpan ExpandToGtin12(ReadOnlySpan text, Span destination) + { + Guard.MustBeBetweenOrEqualTo(text.Length, 7, 8, nameof(text)); + + BarcodeSymbology.ValidateDigits(text); + + // Section 5.2.2.4.1 of the GS1 General Specifications: "D1 SHALL always be zero." + if (text[0] != '0') + { + throw new ArgumentException("UPC-E encodes only a GTIN-12 that starts with a zero.", nameof(text)); + } + + // The four zero suppression rules of section 5.2.2.4.1 exclude each other, so a GTIN-12 suppresses one way only. The last digit selects the rule, and + // each rule requires one digit to be nonzero: X5 (D6) when X6 is 5 to 9, X4 (D5) when X6 is 4, and X3 (D4) from 3 to 9 when X6 is 3. Other digits + // come from a GTIN-12 that a different rule suppresses. + char x6 = text[6]; + if ((x6 >= '5' && text[5] == '0') || (x6 == '4' && text[4] == '0') || (x6 == '3' && text[3] < '3')) + { + throw new ArgumentException("The UPC-E digits are not the zero suppression of a GTIN-12.", nameof(text)); + } + + // The check digit is computed over the GTIN-12, not the compressed digits. Section 5.2.2.4.1: "D12 SHALL be the check digit calculated according to + // the algorithm in section 7.9." + int check = Gs1Data.ComputeCheckDigit(Expand(text, destination[..11])); + if (text.Length == 8 && text[7] - '0' != check) + { + throw new ArgumentException($"UPC-E check digit mismatch: expected {check}, got {text[7]}.", nameof(text)); + } + + destination[11] = (char)('0' + check); + return destination[..12]; + } + + /// + /// Expands the compressed digits to the eleven data digits of the GTIN-12 with the zero suppression rules of section 5.2.2.4.1 of the GS1 General + /// Specifications. The last compressed digit selects the rule. + /// + /// The leading zero followed by the six compressed digits. + /// The buffer the eleven data digits are written into. + /// The eleven UPC-A data digits. + private static ReadOnlySpan Expand(ReadOnlySpan text, Span destination) + { + char d1 = text[1]; + char d2 = text[2]; + char d3 = text[3]; + char d4 = text[4]; + char d5 = text[5]; + char d6 = text[6]; + + destination[0] = text[0]; + destination[1] = d1; + destination[2] = d2; + destination[3..11].Fill('0'); + switch (d6) + { + case '0': + case '1': + case '2': + destination[3] = d6; + destination[8] = d3; + destination[9] = d4; + destination[10] = d5; + break; + case '3': + destination[3] = d3; + destination[9] = d4; + destination[10] = d5; + break; + case '4': + destination[3] = d3; + destination[4] = d4; + destination[10] = d5; + break; + default: + destination[3] = d3; + destination[4] = d4; + destination[5] = d5; + destination[10] = d6; + break; + } + + return destination[..11]; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Fim/FacingIdentificationMarkSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Fim/FacingIdentificationMarkSymbology.cs new file mode 100644 index 00000000..ade31d94 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Fim/FacingIdentificationMarkSymbology.cs @@ -0,0 +1,108 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Facing Identification Mark of the United States Postal Service. The input is the pattern letter, A to E. Each pattern is nine positions, a bar or a +/// space, on a pitch of two modules of 1/32 inch: A is 110010011, B is 101101101, C is 110101011, D is 111010111 and E is 101000101. The bars are 1/32 inch +/// wide and 5/8 inch high, so a pattern is 17 modules wide. The mark carries no data and prints no line. +/// +public sealed class FacingIdentificationMarkSymbology : BarcodeSymbology +{ + /// + /// The number of positions in a pattern. + /// + private const int Positions = 9; + + /// + /// The distance between positions in modules. + /// + private const int Pitch = 2; + + /// + /// The nominal X dimension in millimetres: the 1/32 inch bar width. + /// + private const double XDimension = 25.4 / 32; + + /// + /// The smallest X dimension in millimetres. Section 708.9.3 b of the Domestic Mail Manual: the bars must be "1/32 inch (±0.008 inch) wide". + /// + private const double MinimumXDimension = ((1.0 / 32) - 0.008) * 25.4; + + /// + /// The largest X dimension in millimetres, from the same tolerance. + /// + private const double MaximumXDimension = ((1.0 / 32) + 0.008) * 25.4; + + /// + /// The bar height in millimetres when the caller sets none. Section 708.9.3 b of the Domestic Mail Manual: "The FIM bars must be 5/8 inch (±1/8 inch) + /// high". The height stays 5/8 inch at every module width, so a module that rounds to whole pixels does not change it. + /// + private const double NominalBarHeight = 25.4 * 5 / 8; + + /// + /// The smallest bar height in millimetres, from the tolerance of section 708.9.3 b, "5/8 inch (±1/8 inch)". + /// + private const double MinimumBarHeight = 25.4 * ((5.0 / 8) - (1.0 / 8)); + + /// + /// The largest bar height in millimetres, from the same tolerance. + /// + private const double MaximumBarHeight = 25.4 * ((5.0 / 8) + (1.0 / 8)); + + /// + /// Initializes a new instance of the class. + /// + public FacingIdentificationMarkSymbology() + { + } + + /// + /// Gets the patterns A to E, each as nine bits, most significant first, in which a set bit is a bar. + /// + private static ReadOnlySpan Patterns => [0b110010011, 0b101101101, 0b110101011, 0b111010111, 0b101000101]; + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + if (text.Length != 1 || !char.IsBetween(text[0], 'A', 'E')) + { + throw new ArgumentException($"A Facing Identification Mark is one of the patterns A to E; got \"{text}\".", nameof(text)); + } + + // Every bar is one module, so the space to the next bar is the pitch less that module, plus one pitch for every empty position between them. A bar and + // a space for every position bounds the runs, and the symbol keeps those written. + int pattern = Patterns[text[0] - 'A']; + double xDimension = options.ResolveXDimension(MinimumXDimension, XDimension, MaximumXDimension); + Span buffer = symbol.BeginLinear((Positions * 2) - 1, xDimension, out Span heights, out _); + int written = 0; + int space = 0; + for (int position = Positions - 1; position >= 0; position--) + { + if (((pattern >> position) & 1) == 0) + { + space += Pitch; + continue; + } + + if (written > 0) + { + buffer[written++] = space + Pitch - 1; + } + + buffer[written++] = 1; + space = 0; + } + + symbol.TrimLastRow(written); + float barHeight = options.ResolveBarHeight( + xDimension, + (float)(MinimumBarHeight / xDimension), + (float)(NominalBarHeight / xDimension), + (float)(MaximumBarHeight / xDimension)); + + heights[..((written + 1) >> 1)].Fill(barHeight); + symbol.EndLinear(0, 0); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Flattermarken/FlattermarkenSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Flattermarken/FlattermarkenSymbology.cs new file mode 100644 index 00000000..390fe353 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Flattermarken/FlattermarkenSymbology.cs @@ -0,0 +1,130 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Flattermarken symbology. The valid characters are "0".."9", the print ratio is 1:1, the module width is 2 to 3 mm and the symbol height is 5 to 10 mm. +/// Every digit takes a window of nine modules. A digit from 1 to 9 is one bar of one module at the position the digit names from the left of its window, and +/// the digit 0 leaves its window blank. The printed line shows the digits as given. +/// +/// A symbol that starts or ends with a blank part of a window begins or ends with a bar of zero width, so the windows keep their width. The quiet zone is +/// "Application dependent" and is zero. When the caller sets no X dimension and no bar height, each bar is 2 mm wide and 10 mm high, the size print shops +/// most often use. +/// +/// +public sealed class FlattermarkenSymbology : BarcodeSymbology +{ + /// + /// The largest number of digits a symbol carries. The symbology sets no maximum. + /// + public const int MaximumLength = 500; + + /// + /// The width of a digit's window in modules. + /// + private const int Window = 9; + + /// + /// The nominal X dimension in millimetres: a bar 2 mm wide, the width of the usual 2 by 10 mm mark. + /// + private const double XDimension = 2; + + /// + /// The smallest X dimension in millimetres, from the "Module width: 2-3 mm" of section 6.1.34 of the TEC-IT Barcode Reference. + /// + private const double MinimumXDimension = 2; + + /// + /// The largest X dimension in millimetres, from the same module width. + /// + private const double MaximumXDimension = 3; + + /// + /// The bar height in millimetres when the caller sets none: 10 mm, the height of the usual 2 by 10 mm mark. + /// + private const double NominalBarHeight = 10; + + /// + /// The smallest bar height in millimetres, from the "Symbol height between 5 and 10mm" of section 6.1.34 of the TEC-IT Barcode Reference. + /// + private const double MinimumBarHeight = 5; + + /// + /// The largest bar height in millimetres, from the same symbol height. + /// + private const double MaximumBarHeight = 10; + + /// + /// Initializes a new instance of the class. + /// + public FlattermarkenSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Guard.MustBeGreaterThan(text.Length, 0, nameof(text)); + Guard.MustBeLessThanOrEqualTo(text.Length, MaximumLength, nameof(text)); + + BarcodeSymbology.ValidateDigits(text); + + // One bar and one space per digit at most, plus a zero width bar at either end. The symbol reserves that many runs and keeps those written. + double xDimension = options.ResolveXDimension(MinimumXDimension, XDimension, MaximumXDimension); + Span buffer = symbol.BeginLinear((text.Length * 2) + 3, xDimension, out Span heights, out _); + int written = 0; + int space = 0; + foreach (char c in text) + { + int digit = c - '0'; + if (digit == 0) + { + space += Window; + continue; + } + + space += digit - 1; + if (written == 0 && space > 0) + { + buffer[written++] = 0; + } + + if (written > 0) + { + buffer[written++] = space; + } + + buffer[written++] = 1; + space = Window - digit; + } + + if (written == 0) + { + buffer[written++] = 0; + } + + if (space > 0) + { + buffer[written++] = space; + buffer[written++] = 0; + } + + symbol.TrimLastRow(written); + float barHeight = options.ResolveBarHeight( + xDimension, + (float)(MinimumBarHeight / xDimension), + (float)(NominalBarHeight / xDimension), + (float)(MaximumBarHeight / xDimension)); + + heights[..((written + 1) >> 1)].Fill(barHeight); + + if (options.Font is not null) + { + symbol.ReserveText(1, text.Length); + symbol.AddText(text, 0F, text.Length * Window, BarcodeTextSide.BelowBars, barHeight + BarcodeTextPlacement.Clearance); + } + + symbol.EndLinear(0, 0); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Fonts/BarcodeFonts.cs b/src/ImageSharp.Drawing/Barcodes/Fonts/BarcodeFonts.cs new file mode 100644 index 00000000..9e9fa214 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Fonts/BarcodeFonts.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The OCR-A and OCR-B fonts for barcode text. No symbology selects them. To use one, set it on or +/// at the size you need. +/// +public static class BarcodeFonts +{ + /// + /// Gets the SixLabors OCR-A font family. + /// + public static FontFamily OcrA => OcrAFamily.Value; + + /// + /// Gets the SixLabors OCR-B font family. It holds the barcode repertoire of the constant strokewidth OCR-B design: the digits, the capitals, the Code 39 + /// symbols and the EAN quiet zone indicators. + /// + public static FontFamily OcrB => OcrBFamily.Value; + + /// + /// Loads a font family from the bytes of a font file. + /// + /// The bytes of the font file. + /// The font family. + private static FontFamily Load(ReadOnlySpan data) + { + FontCollection collection = new(); + using MemoryStream stream = new(data.ToArray()); + return collection.Add(stream); + } + + /// + /// Holds the OCR-A family. Each family has its own holder type, so only code that reads references the OCR-A font data, and the + /// trimmer can remove the data of a family that the application does not use. + /// + private static class OcrAFamily + { + /// + /// The OCR-A family, loaded on first use. + /// + private static readonly Lazy Family = new(() => Load(OcrAFontData.Bytes)); + + /// + /// Gets the OCR-A family. + /// + public static FontFamily Value => Family.Value; + } + + /// + /// Holds the OCR-B family. Each family has its own holder type, so only code that reads references the OCR-B font data, and the + /// trimmer can remove the data of a family that the application does not use. + /// + private static class OcrBFamily + { + /// + /// The OCR-B family, loaded on first use. + /// + private static readonly Lazy Family = new(() => Load(OcrBFontData.Bytes)); + + /// + /// Gets the OCR-B family. + /// + public static FontFamily Value => Family.Value; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Fonts/OcrAFontData.cs b/src/ImageSharp.Drawing/Barcodes/Fonts/OcrAFontData.cs new file mode 100644 index 00000000..93f27445 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Fonts/OcrAFontData.cs @@ -0,0 +1,2287 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// +// Generated by ImageSharp.Drawing.FontGenerator. Do not edit. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The SixLabors OCRA TrueType font file. Built clean-room from the dimensioned character drawings of FIPS PUB 32 (1974). +/// +internal static class OcrAFontData +{ + /// + /// Gets the font file bytes. + /// + public static ReadOnlySpan Bytes => + [ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x80, 0x00, 0x03, 0x00, 0x20, 0x4F, 0x53, 0x2F, 0x32, + 0x82, 0xE4, 0x80, 0x51, 0x00, 0x00, 0x00, 0xAC, 0x00, 0x00, 0x00, 0x60, 0x63, 0x6D, 0x61, 0x70, + 0x26, 0x9A, 0x48, 0x28, 0x00, 0x00, 0x01, 0x0C, 0x00, 0x00, 0x00, 0x7C, 0x67, 0x6C, 0x79, 0x66, + 0x09, 0x48, 0x99, 0xB6, 0x00, 0x00, 0x01, 0x88, 0x00, 0x00, 0x87, 0x54, 0x68, 0x65, 0x61, 0x64, + 0x62, 0x1B, 0x43, 0x51, 0x00, 0x00, 0x88, 0xDC, 0x00, 0x00, 0x00, 0x36, 0x68, 0x68, 0x65, 0x61, + 0x07, 0x40, 0x03, 0x27, 0x00, 0x00, 0x89, 0x14, 0x00, 0x00, 0x00, 0x24, 0x68, 0x6D, 0x74, 0x78, + 0x07, 0x40, 0x2B, 0xE8, 0x00, 0x00, 0x89, 0x38, 0x00, 0x00, 0x01, 0x5C, 0x6C, 0x6F, 0x63, 0x61, + 0x00, 0x16, 0xBD, 0xC8, 0x00, 0x00, 0x8A, 0x94, 0x00, 0x00, 0x01, 0x60, 0x6D, 0x61, 0x78, 0x70, + 0x00, 0x5B, 0x00, 0x8E, 0x00, 0x00, 0x8B, 0xF4, 0x00, 0x00, 0x00, 0x20, 0x6E, 0x61, 0x6D, 0x65, + 0x1E, 0xCC, 0x36, 0x7C, 0x00, 0x00, 0x8C, 0x14, 0x00, 0x00, 0x01, 0x7C, 0x70, 0x6F, 0x73, 0x74, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x8D, 0x90, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x03, 0xE8, + 0x01, 0x90, 0x00, 0x05, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x69, 0x78, 0x4C, 0x00, 0x40, 0x00, 0x20, 0x25, 0x88, + 0x04, 0x3A, 0xFE, 0xE7, 0x00, 0x00, 0x03, 0x84, 0x01, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x0C, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x04, 0x00, 0x70, 0x00, 0x00, 0x00, 0x18, + 0x00, 0x10, 0x00, 0x03, 0x00, 0x08, 0x00, 0x20, 0x00, 0x22, 0x00, 0x3B, 0x00, 0x3D, 0x00, 0x3F, + 0x00, 0x5A, 0x00, 0x7A, 0x00, 0x7C, 0x20, 0x15, 0x24, 0x42, 0x25, 0x88, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x20, 0x00, 0x22, 0x00, 0x24, 0x00, 0x3D, 0x00, 0x3F, 0x00, 0x41, 0x00, 0x61, 0x00, 0x7C, + 0x20, 0x15, 0x24, 0x40, 0x25, 0x88, 0xFF, 0xFF, 0xFF, 0xE1, 0xFF, 0xE0, 0xFF, 0xDF, 0xFF, 0xDE, + 0xFF, 0xDD, 0xFF, 0xDC, 0xFF, 0xD6, 0xFF, 0xD5, 0xE0, 0x3D, 0xDC, 0x13, 0xDA, 0xCE, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x78, 0x01, 0x90, 0x02, 0x8D, + 0x03, 0x0C, 0x00, 0x25, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x02, 0x15, + 0x00, 0x00, 0xFF, 0xC8, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xF5, + 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFC, + 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xD4, 0xFF, 0x84, 0xFF, 0xD4, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xF9, + 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF7, + 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xCA, 0x03, 0x0C, 0x00, 0x00, 0xFF, 0xC5, 0xFE, 0xE5, + 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x0B, + 0x00, 0xE5, 0x00, 0x00, 0xFF, 0x1B, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFA, + 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x06, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0A, + 0x00, 0x0B, 0x01, 0x16, 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x6B, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, + 0x00, 0x01, 0x00, 0x02, 0x00, 0x05, 0x00, 0x03, 0x00, 0x09, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x0C, + 0x01, 0x78, 0x00, 0x00, 0xFE, 0x87, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFD, + 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x06, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x0B, + 0x00, 0x0C, 0x00, 0x07, 0x00, 0x9E, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x03, 0x00, 0x08, + 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x0A, + 0x00, 0x03, 0x00, 0x06, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x9F, 0x00, 0x0C, 0x00, 0x0B, + 0x00, 0x05, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x03, 0x00, 0x06, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFF, + 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xFA, 0xFE, 0x8D, + 0x00, 0x00, 0x01, 0x79, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x05, 0x00, 0x02, + 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, + 0xFF, 0x61, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF4, + 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xFE, + 0x00, 0x00, 0xFF, 0x62, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xFA, + 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0x01, 0x6B, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xFB, + 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFD, 0x00, 0x00, 0xFF, 0xB7, 0xFF, 0xFF, 0xFF, 0xFD, + 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xF4, + 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x81, + 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFE, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x05, 0x00, 0x04, 0x00, 0x08, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, + 0x00, 0x7F, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x03, 0x00, 0x07, 0x00, 0x05, 0x00, 0x05, + 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x06, 0x00, 0xBC, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x08, + 0x00, 0x05, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x00, 0x01, 0x00, 0x03, 0x00, 0x05, + 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x05, + 0x00, 0x08, 0x00, 0x05, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x85, 0x00, 0x0C, 0x00, 0x0C, + 0x00, 0x05, 0x00, 0x08, 0x00, 0x07, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xF9, + 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0x81, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, + 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF4, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x24, 0x00, 0x46, 0x00, 0x6B, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x06, 0x00, 0x09, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x06, + 0x00, 0x0D, 0x00, 0x6A, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x08, 0x00, 0x05, + 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF5, + 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0x96, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF7, + 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, + 0x00, 0x08, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0B, + 0x00, 0x04, 0x01, 0xAD, 0x00, 0x04, 0x00, 0x05, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFE, 0xFF, 0xFE, + 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF4, + 0xFF, 0xF6, 0xFF, 0xFB, 0xFE, 0x53, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFE, 0x01, 0x3E, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x03, 0x00, 0x06, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x0C, + 0x00, 0x6B, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x05, 0x00, 0x03, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF4, + 0xFF, 0xFA, 0xFF, 0x95, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xFC, + 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0x02, 0x7B, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF5, + 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x06, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x5B, 0x00, 0x0C, 0x00, 0x06, + 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF7, + 0xFD, 0x83, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFD, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x07, 0x00, 0x04, 0x02, 0x25, 0x00, 0x05, 0x00, 0x0B, + 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x06, + 0x00, 0x03, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFC, 0xFD, 0xDB, 0xFF, 0xFB, + 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xA1, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFB, + 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x06, + 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x5B, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, + 0x00, 0x0A, 0x00, 0x08, 0x00, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFD, + 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF7, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, 0x02, 0xB1, 0x00, 0x7B, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x7A, 0x00, 0x04, 0x00, 0x08, + 0x00, 0x02, 0x00, 0x04, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xFB, + 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0x3B, + 0xFF, 0xAD, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, + 0xFF, 0xFD, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2B, 0x00, 0x08, 0x00, 0x06, 0x00, 0x04, + 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xED, + 0xFF, 0xEB, 0xFF, 0xE9, 0xFF, 0xE8, 0xFF, 0xF4, 0xFF, 0x82, 0xFF, 0xE8, 0xFF, 0xE9, 0xFF, 0xEA, + 0xFF, 0xEC, 0xFF, 0xF7, 0xFF, 0xEF, 0xFF, 0xF2, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x06, 0x01, 0xB1, 0x00, 0x05, 0x00, 0x0B, + 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x07, 0x00, 0x05, + 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xF9, 0xFE, 0x66, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x04, 0x00, 0x04, 0x00, 0x05, 0x00, 0x09, 0x00, 0x07, 0x00, 0x08, 0x00, 0x0C, 0x00, 0x69, + 0x00, 0x09, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x07, 0x00, 0x08, 0x00, 0x07, 0x00, 0x03, 0x00, 0x02, + 0x00, 0x00, 0xFE, 0xD5, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x06, 0x00, 0x09, 0x00, 0x05, 0x00, 0x07, 0x00, 0x0F, 0x00, 0x12, 0x00, 0x14, + 0x00, 0x0A, 0x00, 0x17, 0x00, 0x18, 0x00, 0x0C, 0x00, 0x74, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x0B, + 0x00, 0x05, 0x00, 0x8C, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, + 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x07, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0x57, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, + 0x00, 0x07, 0x00, 0x05, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x30, + 0x01, 0x00, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x5B, 0x00, 0x18, 0x00, 0x17, + 0x00, 0x16, 0x00, 0x14, 0x00, 0x12, 0x00, 0x0F, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x05, 0x00, 0x02, + 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF2, 0xFF, 0xF8, 0xFF, 0xEE, 0xFF, 0xEC, + 0xFF, 0xEA, 0xFF, 0xE9, 0xFF, 0xF4, 0xFF, 0xE9, 0xFF, 0xB6, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xFA, + 0xFF, 0xF6, 0xFE, 0x32, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, + 0x00, 0x06, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0C, + 0x00, 0x0A, 0x01, 0xB6, 0x00, 0x33, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x06, 0x00, 0x08, + 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFB, + 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF4, 0xFF, 0xCC, 0xFF, 0x00, 0xFF, 0xF7, 0xFF, 0xFB, + 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xAB, 0xFF, 0xF4, 0xFF, 0xE8, 0xFF, 0xEA, 0xFF, 0xF5, + 0xFF, 0xF6, 0xFF, 0xED, 0xFF, 0xEF, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xFE, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x01, 0x17, + 0x01, 0x35, 0x01, 0xED, 0x03, 0x0C, 0x00, 0x2B, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x17, 0x00, 0x01, 0x00, 0x01, 0x00, 0x04, 0x00, 0x05, + 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x09, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x08, + 0x00, 0x09, 0x00, 0x05, 0x00, 0x06, 0x00, 0x06, 0x00, 0x05, 0x00, 0x61, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x05, 0x00, 0x02, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFB, + 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0x90, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF7, + 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x02, 0xFF, 0xFE, 0x00, 0x00, 0x01, 0x6B, + 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFD, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x05, 0x00, 0x04, 0x00, 0x06, 0x00, 0x08, + 0x00, 0x0C, 0x01, 0x4C, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x05, 0x00, 0x0D, 0x00, 0x0C, + 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x04, 0x00, 0x06, 0x00, 0x02, 0x00, 0x01, + 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF4, + 0xFF, 0xF3, 0xFF, 0xF7, 0xFF, 0xFA, 0x00, 0x00, 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8C, + 0x03, 0x0C, 0x00, 0x6B, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x00, 0x78, 0x00, 0x02, 0x00, 0x05, 0x00, 0x03, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x06, + 0x00, 0x0C, 0x00, 0x07, 0x00, 0x67, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x08, + 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x05, + 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x11, + 0x00, 0xAD, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x07, 0x00, 0x03, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xFB, + 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0x61, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF4, + 0xFF, 0xF3, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x9F, + 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x06, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, + 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0x59, + 0xFF, 0xF5, 0xFF, 0xEF, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xFB, + 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xF8, + 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0x99, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xFA, + 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFE, 0x01, 0x68, 0xFF, 0xF3, 0xFF, 0xF5, + 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0x32, + 0xFF, 0xED, 0xFF, 0xF0, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF9, + 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x07, + 0x00, 0x04, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x0B, + 0x00, 0x05, 0x00, 0x09, 0x00, 0x04, 0x00, 0x03, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0xCA, + 0x00, 0x17, 0x00, 0x18, 0x00, 0x16, 0x00, 0x15, 0x00, 0x11, 0x00, 0x11, 0x00, 0x15, 0x00, 0x16, + 0x00, 0x18, 0x00, 0x17, 0x00, 0xCA, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x07, 0x00, 0x09, + 0x00, 0x0B, 0x00, 0x05, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, + 0x00, 0x06, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xF7, + 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF2, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF0, 0xFF, 0xED, 0xFF, 0x32, + 0xFF, 0xF4, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xF4, + 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8C, 0x03, 0x0C, 0x00, 0x6B, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, + 0x00, 0x01, 0x00, 0x03, 0x00, 0x06, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x06, + 0x00, 0xAE, 0x00, 0x10, 0x00, 0x0B, 0x00, 0x10, 0x00, 0x09, 0x00, 0x09, 0x00, 0x08, 0x00, 0x0B, + 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, + 0x00, 0x04, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x09, 0x00, 0x68, 0x00, 0x06, + 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x03, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, + 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, + 0xFF, 0x98, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFE, + 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF8, + 0xFF, 0xF8, 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xF5, 0xFF, 0xF0, 0xFF, 0x59, 0xFF, 0xF3, 0xFF, 0xFA, + 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x02, 0x00, 0x05, + 0x00, 0x07, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x9E, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x05, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x0D, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xFB, + 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0x62, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, 0xFC, + 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xFB, + 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x07, + 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x0D, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0B, + 0x00, 0x13, 0x00, 0xCE, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x07, 0x00, 0x09, 0x00, 0x08, 0x00, 0x04, + 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x07, 0x00, 0x09, + 0x00, 0x05, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x09, + 0x00, 0x07, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, + 0x00, 0x07, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x08, 0x00, 0xCE, 0x00, 0x13, 0x00, 0x10, + 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0E, 0x00, 0x08, 0x00, 0x08, 0x00, 0x09, 0x00, 0x07, 0x00, 0x03, + 0x00, 0x03, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xFA, + 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFB, + 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x36, 0xFF, 0xE9, 0xFF, 0xE8, 0xFF, 0xEA, 0xFF, 0xEB, 0xFF, 0xEF, + 0xFF, 0xEF, 0xFF, 0xEB, 0xFF, 0xEA, 0xFF, 0xE8, 0xFF, 0xE9, 0xFF, 0x36, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF3, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x78, 0x00, 0x25, 0x02, 0x8C, 0x02, 0xB1, 0x00, 0x68, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 0x00, + 0x04, 0x00, 0xA9, 0xFF, 0x5C, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x01, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0D, 0x00, + 0x0B, 0x00, 0x0B, 0x00, 0x7B, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x03, 0x00, 0x04, 0x00, + 0x09, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x05, 0x00, + 0x0A, 0x00, 0x08, 0x00, 0x03, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x7B, 0x00, 0x0B, 0x00, + 0x06, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x05, 0x00, + 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0x57, 0x00, 0xA5, 0x00, 0x08, 0x00, + 0x06, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, + 0xF5, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0x85, 0x00, 0x00, 0xFF, + 0xFF, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, + 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, + 0xFE, 0x00, 0x00, 0xFF, 0x85, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, + 0xF4, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFE, 0x02, 0x1E, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, + 0xF6, 0xFF, 0xFB, 0xFF, 0x6F, 0xFF, 0x74, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF3, 0xFF, + 0xF4, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, + 0x02, 0x00, 0x04, 0x00, 0x07, 0x00, 0x6A, 0xFF, 0x65, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, + 0xFB, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x03, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x9B, 0xFF, 0x96, 0xFF, + 0xF9, 0xFF, 0xFE, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x03, 0x00, 0x05, 0x00, 0x08, 0x00, 0x0A, 0x00, + 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x91, 0x00, 0x8C, 0x00, + 0x0A, 0x00, 0x0A, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x09, 0x00, + 0x04, 0x00, 0x05, 0x00, 0x03, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0x96, 0x00, + 0x9B, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x06, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, + 0xF5, 0xFF, 0xF3, 0xFF, 0x65, 0x00, 0x6A, 0x00, 0x03, 0x00, 0x06, 0x00, 0x03, 0x00, 0x00, 0xFF, + 0xFD, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF4, 0x00, 0x00, 0x01, 0x00, 0x78, + 0x00, 0x61, 0x02, 0x8C, 0x02, 0x75, 0x00, 0x4B, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x07, + 0x00, 0x0A, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x9E, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, + 0x00, 0x06, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x06, + 0x00, 0x0B, 0x00, 0x05, 0x00, 0x05, 0x00, 0x08, 0x00, 0x03, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x9F, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x06, 0x00, 0x02, + 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xF5, + 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0x61, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFB, + 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFB, + 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0x62, 0xFF, 0xF3, + 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x01, 0x68, + 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFF, + 0x00, 0x00, 0xFF, 0x62, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFD, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x05, 0x00, 0x03, 0x00, 0x04, 0x00, 0x0A, + 0x00, 0x05, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x9E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x03, + 0x00, 0x08, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, + 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, 0x00, 0x06, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x9E, + 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x07, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFA, + 0xFF, 0xF3, 0xFF, 0x62, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF6, + 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0x00, 0x00, 0x00, 0x01, 0x00, 0xE2, 0xFF, 0xCA, 0x02, 0x22, + 0x01, 0x46, 0x00, 0x2C, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x18, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, + 0xFC, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x08, 0x00, + 0x09, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x9E, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, + 0x03, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x0C, 0x00, + 0x05, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x04, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFD, 0xFF, + 0xFB, 0xFF, 0xFD, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, 0x01, 0x46, 0xFF, + 0xFF, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, + 0xF3, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, + 0xFE, 0x00, 0x00, 0xFF, 0x26, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, + 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x08, 0x00, 0x09, 0x00, + 0x06, 0x00, 0x0C, 0x00, 0x0C, 0x01, 0x16, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, 0x00, + 0x04, 0x00, 0x05, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x78, 0x01, 0x08, 0x02, 0x8D, + 0x01, 0xA1, 0x00, 0x22, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x01, 0x00, 0x04, 0x00, + 0x03, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x0C, 0x01, 0xAF, 0x00, 0x06, 0x00, + 0x0B, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x07, 0x00, 0x06, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFF, 0xFF, + 0xFE, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF5, 0xFE, 0x4B, 0xFF, + 0xF4, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFF, 0x01, + 0x3E, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x06, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x0B, 0x00, + 0x0C, 0x00, 0x06, 0x00, 0x33, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x07, 0x00, + 0x02, 0x00, 0x03, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, + 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x17, 0x00, 0x52, 0x01, 0xED, + 0x01, 0x19, 0x00, 0x24, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x17, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x02, 0x00, 0x07, 0x00, 0x04, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, 0x00, + 0x6A, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x05, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, + 0xF9, 0xFF, 0x96, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, + 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x88, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, + 0xFB, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x06, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x5B, 0x00, 0x06, 0x00, 0x0C, 0x00, + 0x0C, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0xFF, + 0xFF, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF7, 0x00, + 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x21, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x7F, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, 0x05, 0x00, 0x07, + 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0A, + 0x00, 0x04, 0x01, 0xAC, 0x00, 0x05, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF9, + 0xFF, 0xFC, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xFB, + 0xFF, 0xFB, 0xFF, 0xF9, 0x00, 0x1B, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xFA, + 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x06, 0x00, 0x08, 0x00, 0x04, 0x02, 0xDC, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x0C, + 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x07, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFD, + 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF6, 0x00, 0x02, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, + 0x03, 0x0C, 0x00, 0x08, 0x00, 0x3C, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x82, 0x00, 0x9E, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFE, + 0xC5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFD, 0xFF, 0xEE, 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, + 0xF7, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, + 0x07, 0x00, 0x0E, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x12, 0x01, 0x40, 0x00, + 0x12, 0x00, 0x11, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0E, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x05, 0x00, + 0x07, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, + 0xFB, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, 0xEE, 0x02, + 0xA0, 0x00, 0x00, 0xFD, 0x97, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x02, 0x68, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x6C, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, + 0xF8, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF3, 0xFD, 0x94, 0xFF, 0xED, 0xFF, + 0xF5, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, + 0xFB, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, + 0x05, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x0D, 0x00, 0x09, 0x00, 0x10, 0x00, 0x10, 0x00, 0x13, 0x02, + 0x6C, 0x00, 0x0D, 0x00, 0x11, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x0C, 0x00, + 0x07, 0x00, 0x06, 0x00, 0x07, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x78, + 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x3F, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x01, 0x00, 0x03, + 0x00, 0x06, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x06, 0x01, 0xB0, 0x00, 0x06, + 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x06, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFF, + 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF9, + 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, + 0xFF, 0x98, 0x00, 0x00, 0xFE, 0xEF, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xFA, + 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x0B, + 0x00, 0x06, 0x00, 0x0D, 0x00, 0x9E, 0x00, 0x00, 0xFF, 0x62, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xFB, + 0xFF, 0xF6, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF4, + 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x05, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x06, 0x01, 0x17, + 0x00, 0x0C, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x03, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, + 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0x26, + 0x00, 0x00, 0x02, 0xD6, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xF5, + 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFA, + 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0xFD, 0x96, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xFD, + 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF3, 0x00, 0x00, 0x00, 0x01, 0x00, 0x78, + 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x55, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, 0xAE, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFD, + 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x07, 0x00, 0x05, 0x00, 0x0B, + 0x00, 0x0B, 0x00, 0x0D, 0x01, 0x72, 0x00, 0x00, 0xFE, 0xC2, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF5, + 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFB, + 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x01, 0xDF, 0x00, 0x0C, 0x00, 0x0C, + 0x00, 0x05, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFA, + 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF4, 0xFE, 0x8D, 0x00, 0x00, 0x01, 0x3E, 0x00, 0x0A, + 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x09, 0x00, 0x08, 0x00, 0x07, + 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF7, + 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF6, 0x03, 0x0C, 0xFF, 0xFF, + 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, + 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFF, + 0x00, 0x00, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, + 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF5, + 0xFF, 0xF5, 0xFF, 0xF6, 0xFE, 0x94, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x03, 0x00, 0x08, + 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x07, + 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, + 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0A, + 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0A, 0x01, 0x01, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0B, + 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x09, 0x00, 0x08, 0x00, 0x07, 0x00, 0x07, 0x00, 0x05, + 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, + 0x03, 0x0C, 0x00, 0x6E, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 0x00, 0x04, 0x00, + 0x0A, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x06, 0x01, 0x82, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x10, 0x00, + 0x0F, 0x00, 0x09, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x04, 0x00, 0x04, 0x00, 0x03, 0x00, + 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF2, 0x00, 0x0E, 0x00, + 0x06, 0x00, 0x0B, 0x00, 0x07, 0x00, 0x03, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, + 0xFA, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF3, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, + 0xF5, 0xFE, 0x7E, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, + 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x06, 0x00, 0x04, 0x00, 0x0A, 0x00, + 0x0B, 0x00, 0x06, 0x00, 0x0D, 0x01, 0x72, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, + 0xFA, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF4, 0xFF, 0x2A, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, + 0xF5, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFE, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0xD6, 0x00, + 0x08, 0x00, 0x08, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x06, 0x00, 0x03, 0x00, 0x02, 0x00, + 0x00, 0xFE, 0x87, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, + 0xFC, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, + 0xFD, 0xFF, 0xFB, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x06, 0x00, 0x09, 0x00, + 0x07, 0x00, 0x08, 0x00, 0x0D, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x13, 0x00, + 0xCB, 0x00, 0x17, 0x00, 0x18, 0x00, 0x16, 0x00, 0x15, 0x00, 0x11, 0x00, 0x11, 0x00, 0x0A, 0x00, + 0x16, 0x00, 0x17, 0x00, 0x18, 0x00, 0xDE, 0x00, 0x10, 0x00, 0x0B, 0x00, 0x10, 0x00, 0x09, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, 0x00, + 0x00, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, + 0xF9, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, + 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0x33, 0xFF, 0xF4, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, + 0xFB, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFD, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, + 0xFD, 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, + 0xFB, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFF, 0xFF, + 0xFD, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0x33, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, + 0xFA, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xAD, 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x48, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xAD, 0x01, 0x3E, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x03, 0x00, 0x08, 0x00, 0x05, 0x00, 0x05, 0x00, 0x0C, 0x00, + 0x06, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x09, 0x00, 0x04, 0x00, 0x05, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x04, 0x00, + 0x06, 0x00, 0x02, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, + 0xFB, 0xFF, 0xF4, 0xFF, 0xF5, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, + 0xF5, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xFC, 0xFF, + 0xF9, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0x2E, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, + 0xFD, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, + 0xFA, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0xDA, 0x00, + 0x00, 0xFF, 0x26, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, + 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x02, 0x00, 0x05, 0x00, 0x04, 0x00, 0x08, 0x00, 0x05, 0x00, + 0x0C, 0x00, 0x06, 0x00, 0x0C, 0x00, 0xDA, 0x00, 0x02, 0x00, 0x04, 0x00, 0x03, 0x00, 0x08, 0x00, + 0x05, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x04, 0x00, + 0x08, 0x00, 0x04, 0x00, 0x04, 0x00, 0x01, 0x01, 0x35, 0x00, 0x07, 0x00, 0x0C, 0x00, 0x0B, 0x00, + 0x0A, 0x00, 0x07, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, + 0xFB, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF3, 0xFE, 0xCB, 0x00, 0x00, 0x01, 0x90, 0x00, 0x0C, 0x00, + 0x0C, 0x00, 0x06, 0x00, 0x09, 0x00, 0x04, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, + 0xFE, 0xFF, 0xFE, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xFA, 0x00, + 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x50, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, + 0x57, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x06, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, + 0xFA, 0xFF, 0xF4, 0xFE, 0x8B, 0x00, 0x00, 0x01, 0x3E, 0x00, 0x00, 0xFF, 0x0F, 0xFF, 0xF8, 0xFF, + 0xF9, 0xFF, 0xF8, 0xFF, 0xAB, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, + 0xF6, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x03, 0x00, + 0x03, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x60, 0x00, 0x15, 0x00, 0x0A, 0x00, 0x15, 0x00, 0xF5, 0x00, + 0x0A, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x09, 0x00, 0x08, 0x00, + 0x07, 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, + 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, + 0xF7, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0x2C, 0x00, + 0x00, 0x02, 0xA0, 0x00, 0x01, 0x00, 0x02, 0x00, 0x06, 0x00, 0x08, 0x00, 0x05, 0x00, 0x0B, 0x00, + 0x05, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, 0x00, + 0x06, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFE, 0x29, 0x00, 0x00, 0xFF, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x24, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, + 0xFB, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, + 0xFB, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xD7, 0xFF, 0xFA, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, 0x07, 0x00, + 0x08, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0A, 0x01, + 0x01, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x08, 0x00, + 0x08, 0x00, 0x07, 0x00, 0x07, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0x00, 0x00, 0x02, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x36, + 0x00, 0x3A, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, + 0x78, 0x00, 0x01, 0x00, 0x02, 0x00, 0x05, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x06, 0x00, + 0x0C, 0x01, 0xAF, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x06, 0x00, + 0x02, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, + 0xFA, 0xFF, 0xF5, 0xFF, 0xFA, 0xFE, 0x8D, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x06, 0x00, + 0x09, 0x00, 0x08, 0x00, 0x02, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, + 0xF8, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xCB, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, + 0xFB, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x6C, 0x00, + 0x00, 0x01, 0x3C, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, + 0xF8, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x03, 0x00, + 0x08, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x06, 0x01, 0x16, 0x00, 0x0C, 0x00, 0x0B, 0x00, + 0x09, 0x00, 0x04, 0x00, 0x07, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x5A, 0x00, + 0x01, 0x00, 0x05, 0x00, 0x03, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, + 0x06, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x03, 0x00, 0x05, 0x00, 0x01, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, + 0xF4, 0xFE, 0x3A, 0xFF, 0x26, 0x00, 0xA4, 0x00, 0x00, 0xFF, 0x5C, 0x00, 0x00, 0x01, 0x00, 0x78, + 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x37, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x00, 0x78, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0B, + 0x00, 0x06, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, + 0x00, 0x06, 0x00, 0x02, 0x01, 0x3D, 0x00, 0x00, 0xFF, 0x3F, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFB, + 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0x00, 0x06, 0x00, 0x08, 0x00, 0x05, 0x00, 0x0B, + 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x06, 0x00, 0x02, + 0x00, 0x01, 0x00, 0x00, 0x00, 0xC4, 0x00, 0x06, 0x00, 0x06, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, + 0xFE, 0x1B, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFC, + 0xFF, 0xFF, 0x02, 0xA9, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xFA, + 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x06, 0x00, 0x04, 0x00, 0x0A, + 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x00, 0xFF, 0x3F, 0xFF, 0x5A, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xF5, + 0xFF, 0xFB, 0xFF, 0xF4, 0xFE, 0xEA, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, 0xF9, + 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x02, 0x00, 0x05, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x06, + 0x00, 0x0C, 0x00, 0xF7, 0x00, 0xA7, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x09, 0x01, 0x46, + 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF5, + 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x03, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x44, + 0x00, 0x49, 0x00, 0x4D, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, + 0x00, 0x07, 0x00, 0x07, 0x00, 0x0E, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x12, + 0x01, 0x40, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x0B, + 0x00, 0x09, 0x00, 0x07, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFC, + 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xEF, 0xFF, 0xEE, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF4, + 0xFF, 0x2C, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFE, 0xFF, 0xFE, + 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xEE, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF7, + 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x6C, + 0x00, 0x00, 0x01, 0x3C, 0x00, 0x00, 0xFE, 0xC4, 0x00, 0x6A, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, + 0x00, 0x35, 0xFF, 0xED, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF8, + 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x10, + 0x00, 0x10, 0x00, 0x13, 0x01, 0x01, 0x00, 0x13, 0x00, 0x10, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0E, + 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x05, 0x00, 0x04, 0x00, 0x02, 0x01, 0x3B, 0x00, 0x06, + 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x07, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF4, 0xFE, 0xCB, + 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF8, + 0xFF, 0xF6, 0xFF, 0xF1, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xED, 0xFF, 0x01, 0x00, 0xFE, 0x00, 0x00, + 0xFF, 0x01, 0x00, 0x00, 0x01, 0x6B, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x01, 0x00, 0x02, 0x00, 0x78, + 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x35, 0x00, 0x39, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x00, 0x00, 0x03, 0x00, 0x05, 0x00, 0x08, + 0x00, 0x09, 0x00, 0x05, 0x00, 0x06, 0x00, 0x0C, 0x01, 0x78, 0x00, 0x00, 0xFF, 0xF5, 0xFF, 0xF4, + 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x04, + 0x00, 0x06, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x36, 0x00, 0x0C, + 0x00, 0x05, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x06, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF4, + 0xFE, 0x51, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFD, + 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x01, 0x3C, 0x00, 0x00, 0x01, 0xC6, 0xFF, 0xFA, 0xFF, 0xF4, + 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFD, 0x00, 0x00, 0xFE, 0xA6, + 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF9, 0xFF, 0xF4, + 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x05, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x0B, + 0x00, 0x0C, 0x01, 0xC6, 0x01, 0x16, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x08, 0x00, 0x03, + 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0xF7, + 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0x26, 0x00, 0xA4, 0x00, 0x00, 0xFF, 0x5C, 0x00, 0x00, + 0x00, 0x02, 0x01, 0x17, 0x00, 0x52, 0x01, 0xED, 0x02, 0x84, 0x00, 0x24, 0x00, 0x49, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x17, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x02, 0x00, 0x07, 0x00, 0x08, 0x00, 0x05, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x6A, + 0x00, 0x07, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF9, + 0xFF, 0x96, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFE, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x07, 0x00, 0x04, + 0x00, 0x09, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x6A, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0B, + 0x00, 0x0A, 0x00, 0x07, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, + 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF9, 0xFF, 0x96, 0xFF, 0xF4, 0xFF, 0xFA, + 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xF3, + 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFB, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x06, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0C, + 0x00, 0x0C, 0x00, 0x5B, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x06, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF7, + 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF7, 0xFE, 0x3A, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFA, + 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x02, 0x00, 0x06, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x5B, 0x00, 0x06, + 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xFA, + 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x02, 0x00, 0xE2, 0xFF, 0xCA, 0x02, 0x22, 0x02, 0xB1, 0x00, 0x2B, + 0x00, 0x4F, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0xF6, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x04, + 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF6, + 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0x20, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xFC, 0xFF, 0xF8, + 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x08, 0x00, 0x09, + 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x9E, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x03, + 0x00, 0x08, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0C, 0xFF, 0x99, 0x00, 0x0D, + 0x00, 0x05, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, + 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0x96, + 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, 0x05, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x0B, + 0x00, 0x0D, 0x00, 0x6A, 0xFF, 0xCB, 0x00, 0x03, 0x00, 0x03, 0x00, 0x08, 0x00, 0x09, 0x00, 0x06, + 0x00, 0x0C, 0x00, 0x06, 0x01, 0x16, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x08, + 0x00, 0x05, 0x00, 0x03, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xF6, + 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF8, + 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0x26, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFA, + 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x02, 0x21, 0x00, 0x03, + 0x00, 0x02, 0x00, 0x03, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x61, + 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x04, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xFA, + 0xFF, 0xF6, 0xFF, 0xA2, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFC, + 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x02, 0x00, 0x78, 0x00, 0xDA, 0x02, 0x8C, 0x02, 0x29, 0x00, 0x24, + 0x00, 0x49, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x5D, + 0x00, 0x06, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x06, 0x00, 0x02, 0x00, 0x02, + 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xF4, + 0xFF, 0xF4, 0xFE, 0x50, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFD, + 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x06, 0x00, 0x04, 0x00, 0x0A, + 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0D, 0x01, 0xA9, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0A, + 0x00, 0x05, 0x00, 0x07, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, + 0xFF, 0xFD, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, 0xFE, 0x57, 0xFF, 0xF9, + 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x05, 0x00, 0x03, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0D, + 0x01, 0xA9, 0x00, 0xDB, 0x00, 0x01, 0x00, 0x04, 0x00, 0x03, 0x00, 0x08, 0x00, 0x05, 0x00, 0x0B, + 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x04, 0x00, 0x08, + 0x00, 0x04, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xF8, + 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF5, + 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0xE3, 0x00, 0x03, + 0x00, 0x03, 0x00, 0x07, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, + 0x00, 0x07, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x03, 0x00, 0x06, 0x00, 0x02, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xF5, + 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF9, + 0xFF, 0xFB, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, + 0x03, 0x0C, 0x00, 0x40, 0x00, 0x61, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x21, 0x00, 0x0C, 0x00, 0x0B, + 0x00, 0x09, 0x00, 0x07, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2C, 0x00, 0x08, + 0x00, 0x06, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFB, + 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0x90, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF4, + 0xFF, 0xF4, 0xFF, 0xFB, 0xFE, 0xC2, 0xFF, 0xF7, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFE, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x05, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0D, + 0x00, 0x06, 0x00, 0x0B, 0x00, 0x05, 0x01, 0x1B, 0x00, 0x34, 0x00, 0x00, 0xFE, 0xD5, 0xFF, 0xF8, + 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x03, + 0x00, 0x04, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x03, 0x00, 0x0D, + 0x00, 0x0B, 0x00, 0x09, 0x00, 0x04, 0x00, 0x04, 0x00, 0x05, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFE, + 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF3, + 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x02, + 0x00, 0x05, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x80, + 0x00, 0x04, 0x00, 0x06, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x42, + 0x01, 0x00, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x5B, 0x00, 0x0C, + 0x00, 0x05, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, 0x00, 0x5F, 0x00, 0x06, 0x00, 0x02, 0x00, 0x01, + 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFC, 0xFE, 0xF0, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF5, + 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFC, + 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x02, 0x00, 0x06, 0x00, 0x04, 0x00, 0xF2, 0xFF, 0xD3, 0xFF, 0xD7, + 0xFF, 0x00, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xA5, 0xFF, 0xF4, + 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, + 0xFF, 0x4A, 0x00, 0x03, 0x00, 0x06, 0x00, 0x08, 0x00, 0x05, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x07, + 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x04, 0x00, 0x06, 0x00, 0x03, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF9, + 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFD, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x30, + 0x00, 0x33, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x01, 0x00, 0x03, 0x00, 0x07, + 0x00, 0x08, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x05, 0x00, 0x0B, + 0x00, 0x08, 0x00, 0x07, 0x00, 0x2B, 0x00, 0xEE, 0x00, 0x2A, 0x00, 0x05, 0x00, 0x07, 0x00, 0x05, + 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x09, + 0x00, 0x03, 0x00, 0x05, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFD, 0xFF, 0x2C, 0xFF, 0xFB, 0xFF, 0xF9, + 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, + 0xFF, 0xF5, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0x2A, 0x00, 0xB3, 0x00, 0x57, 0x00, 0x57, + 0x00, 0x03, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFD, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x94, 0x00, 0x00, + 0xFF, 0x72, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFF, 0x00, 0x01, + 0x00, 0x02, 0x00, 0x06, 0x00, 0x03, 0x00, 0x09, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x06, + 0x00, 0x0C, 0x02, 0xD6, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x07, 0x00, 0x03, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xF4, + 0xFD, 0x24, 0x00, 0xE3, 0x01, 0x29, 0xFE, 0xD7, 0x00, 0x03, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, + 0x03, 0x0C, 0x00, 0x31, 0x00, 0x4A, 0x00, 0x63, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, + 0x01, 0x49, 0x00, 0x1A, 0x00, 0x0D, 0x00, 0x19, 0x00, 0x0C, 0x00, 0x17, 0x00, 0x16, 0x00, 0x13, + 0x00, 0x09, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x06, 0x00, 0x09, 0x00, 0x06, 0x00, 0x02, 0x00, 0x02, + 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xED, + 0x00, 0x0A, 0x00, 0x12, 0x00, 0x0E, 0x00, 0x07, 0x00, 0x0B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x03, + 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xF7, + 0xFF, 0xED, 0xFF, 0xEA, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xE8, 0xFF, 0xE6, 0xFF, 0xF3, 0xFF, 0xE7, + 0xFE, 0xC3, 0x00, 0x6C, 0x00, 0x00, 0x00, 0xD0, 0x00, 0x0D, 0x00, 0x13, 0x00, 0x13, 0x00, 0x0B, + 0x00, 0x0A, 0x00, 0x0D, 0x00, 0x08, 0x00, 0x08, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, 0xF3, + 0xFF, 0xED, 0xFF, 0xF3, 0xFF, 0x30, 0x00, 0x00, 0x00, 0xD0, 0x00, 0x0D, 0x00, 0x13, 0x00, 0x13, + 0x00, 0x10, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x08, 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF6, 0xFF, 0xEF, + 0xFF, 0xF3, 0xFF, 0xED, 0xFF, 0xF3, 0xFF, 0xCA, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x08, + 0x00, 0x05, 0x00, 0x0C, 0x00, 0x0F, 0x00, 0x11, 0x00, 0x0A, 0x00, 0x14, 0x00, 0x17, 0x00, 0x0B, + 0x00, 0x19, 0x00, 0x19, 0x00, 0x0D, 0x00, 0x19, 0x00, 0x29, 0x00, 0x19, 0x00, 0x1A, 0x00, 0x18, + 0x00, 0x18, 0x00, 0x17, 0x00, 0x14, 0x00, 0x13, 0x00, 0x09, 0x00, 0x14, 0x00, 0x15, 0x00, 0x0C, + 0x00, 0x17, 0x00, 0x0D, 0x00, 0x19, 0x00, 0x1A, 0x00, 0x41, 0x00, 0x1A, 0x00, 0x0D, 0x00, 0x18, + 0x00, 0x18, 0x00, 0x17, 0x00, 0x14, 0x00, 0x0A, 0x00, 0x11, 0x00, 0x0F, 0x00, 0x06, 0x00, 0x06, + 0x00, 0x09, 0x00, 0x06, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFD, 0x2A, 0x00, 0xFF, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF1, 0xFF, 0xF6, 0xFF, 0xEE, + 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xD4, 0xFF, 0xF3, 0xFF, 0xED, 0xFF, 0xF4, 0xFF, 0xF0, + 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFF, 0x01, 0x6B, 0x00, 0xFF, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF6, + 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xED, 0xFF, 0xF2, 0xFF, 0xD4, 0xFF, 0xEC, 0xFF, 0xF4, 0xFF, 0xF4, + 0xFF, 0xEF, 0xFF, 0xF2, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8C, 0x03, 0x0C, 0x00, 0x57, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x57, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x05, + 0x00, 0x05, 0x00, 0x07, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFD, + 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0x2D, 0xFF, 0xE8, + 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xEB, 0xFF, 0xF6, 0xFF, 0xED, 0xFF, 0xEF, 0xFF, 0xF3, 0xFF, 0x8D, + 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x02, 0x00, 0x05, 0x00, 0x04, + 0x00, 0x04, 0x00, 0x0B, 0x00, 0x73, 0x00, 0x0D, 0x00, 0x08, 0x00, 0x12, 0x00, 0x14, 0x00, 0x0A, + 0x00, 0x17, 0x00, 0x17, 0x00, 0x17, 0x00, 0xC8, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x0A, + 0x00, 0x04, 0x00, 0x06, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFA, + 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0x35, 0xFF, 0xF8, 0xFF, 0xF8, + 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0x94, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFE, + 0xFF, 0xFE, 0x00, 0x02, 0x00, 0x02, 0x00, 0x08, 0x00, 0x71, 0x00, 0x08, 0x00, 0x06, 0x00, 0x06, + 0x00, 0x07, 0x00, 0x08, 0x00, 0x0D, 0x02, 0xA0, 0x00, 0x01, 0x00, 0x02, 0x00, 0x06, 0x00, 0x04, + 0x00, 0x04, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x06, + 0x00, 0x0A, 0x00, 0x04, 0x00, 0x04, 0x00, 0x06, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFC, + 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF1, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, 0x1B, + 0xFF, 0xE8, 0xFF, 0xE7, 0xFF, 0xE5, 0xFF, 0xE6, 0xFF, 0xF2, 0xFF, 0xE6, 0xFF, 0xE5, 0xFF, 0xF4, + 0xFF, 0xF3, 0xFF, 0xE8, 0xFF, 0x1B, 0xFF, 0xEC, 0xFF, 0xF7, 0xFF, 0xF0, 0xFF, 0xF3, 0xFF, 0xFB, + 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x03, 0x00, 0x08, + 0x00, 0x05, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x05, 0x00, 0x0B, + 0x00, 0x05, 0x00, 0x08, 0x00, 0x03, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, + 0x00, 0x03, 0x00, 0x05, 0x00, 0x08, 0x00, 0x0B, 0x00, 0xD8, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x0E, + 0x00, 0x17, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x16, 0x00, 0xE3, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x05, + 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8C, + 0x03, 0x0C, 0x00, 0x3D, 0x00, 0x55, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, + 0x00, 0x01, 0x00, 0x03, 0x00, 0x06, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x06, + 0x00, 0xD9, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x16, 0x00, 0x15, 0x00, 0x09, 0x00, 0x12, 0x00, 0x0F, + 0x00, 0x07, 0x00, 0x73, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x05, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFD, + 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0x88, 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xF6, 0xFF, 0xED, 0xFF, 0xEA, + 0xFF, 0xE9, 0xFF, 0xE8, 0xFF, 0x2E, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF9, + 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x06, 0x00, 0x04, 0x00, 0x0A, + 0x00, 0x0B, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x34, 0x00, 0x00, 0xFF, 0xC5, 0xFF, 0xFA, 0xFF, 0xF5, + 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0xD6, 0x00, 0x00, 0x00, 0x2A, + 0x00, 0x0C, 0x00, 0x08, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0x90, 0xFF, 0xFC, 0xFF, 0xFB, + 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF3, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF4, + 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x05, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x07, 0x00, 0x10, 0x00, 0x13, 0x00, 0x0A, 0x00, 0xE5, + 0x00, 0x18, 0x00, 0x19, 0x00, 0x1B, 0x00, 0x1A, 0x00, 0x1B, 0x00, 0x1A, 0x00, 0x1A, 0x00, 0x19, + 0x00, 0xF1, 0x00, 0x13, 0x00, 0x12, 0x00, 0x08, 0x00, 0x0D, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x04, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF4, + 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFE, + 0xFF, 0xFF, 0x00, 0x00, 0xFD, 0x96, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF7, + 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xFA, 0x00, 0x33, 0x02, 0x6A, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFD, + 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0x21, 0xFF, 0xEA, 0xFF, 0xEA, 0xFF, 0xF1, + 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xEA, 0xFF, 0x21, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFB, + 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8C, + 0x03, 0x0C, 0x00, 0x37, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, + 0x01, 0xE5, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x06, 0x00, 0x02, + 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xF5, + 0xFF, 0xF4, 0xFE, 0x87, 0x00, 0x00, 0x00, 0x9E, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x09, + 0x00, 0x08, 0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFD, + 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0x62, 0x00, 0x00, 0x01, 0x73, + 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF4, 0xFE, 0x21, 0xFF, 0xCA, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x03, 0x00, 0x08, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x05, + 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, 0x00, 0x06, + 0x00, 0x03, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x03, 0x00, 0x08, + 0x00, 0x0A, 0x00, 0x06, 0x00, 0x05, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x05, + 0x00, 0x09, 0x00, 0x04, 0x00, 0x06, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x05, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, + 0x00, 0x06, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x07, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8C, 0x03, 0x0C, 0x00, 0x37, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x07, + 0x00, 0x05, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, + 0x00, 0x05, 0x00, 0x08, 0x00, 0x03, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x01, 0x0F, 0x00, 0x0C, + 0x00, 0x06, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x03, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, + 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF3, 0xFE, 0xF8, + 0x00, 0x00, 0x01, 0x79, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x06, + 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xFB, + 0xFF, 0xFA, 0xFF, 0xF4, 0xFE, 0x1B, 0x00, 0x00, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF5, + 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x07, + 0x00, 0x04, 0x00, 0x09, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0C, 0x01, 0x90, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x02, 0x00, 0x07, 0x00, 0x09, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x0C, + 0x00, 0x0C, 0x00, 0x05, 0x00, 0x09, 0x00, 0x04, 0x00, 0x06, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, + 0x00, 0xA4, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x08, 0x00, 0x05, 0x00, 0x0B, + 0x00, 0x05, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, 0x00, 0x03, + 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, + 0x03, 0x0C, 0x00, 0x69, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x02, 0x57, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x06, 0x00, 0x02, + 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xFB, + 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0x5F, 0xFF, 0xEA, 0xFF, 0xF5, 0xFF, 0xEA, 0xFF, 0xEC, 0xFF, 0xED, + 0xFF, 0xEE, 0xFF, 0xF8, 0xFF, 0x67, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFB, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x0F, + 0x00, 0x12, 0x00, 0x14, 0x00, 0x16, 0x00, 0x17, 0x00, 0x0C, 0x00, 0x17, 0x00, 0xD2, 0x00, 0x18, + 0x00, 0x17, 0x00, 0x0C, 0x00, 0x16, 0x00, 0x14, 0x00, 0x11, 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x08, + 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0x24, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF6, + 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x02, 0x00, 0x05, 0x00, 0x08, 0x00, 0x0A, + 0x00, 0x05, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFA, + 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF3, 0xFF, 0x2D, 0xFF, 0xF8, 0xFF, 0xF8, + 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x05, 0x00, 0x08, 0x00, 0x08, 0x00, 0x94, 0x00, 0x08, 0x00, 0x0A, + 0x00, 0x0B, 0x00, 0x0C, 0x02, 0xA0, 0x00, 0x01, 0x00, 0x02, 0x00, 0x06, 0x00, 0x08, 0x00, 0x05, + 0x00, 0x0B, 0x00, 0x05, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x05, + 0x00, 0x08, 0x00, 0x03, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFA, + 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, 0xF8, 0xFF, 0x3C, 0xFF, 0xEC, 0xFF, 0xE8, 0xFF, 0xF4, + 0xFF, 0xE8, 0xFF, 0xE6, 0xFF, 0xF3, 0xFE, 0xD0, 0xFF, 0xE9, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xEA, + 0xFF, 0xEB, 0xFF, 0xED, 0xFF, 0xEF, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF9, 0xFF, 0xFE, 0xFF, 0xFE, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x05, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x11, 0x00, 0x13, + 0x00, 0x15, 0x00, 0x16, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x17, 0x00, 0xDB, 0x00, 0x00, 0xFF, 0xFD, + 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF4, + 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0x93, 0xFF, 0xF7, + 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x05, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, + 0x00, 0x08, 0x00, 0x09, 0x01, 0x20, 0x00, 0x14, 0x00, 0x13, 0x00, 0x12, 0x00, 0x0B, 0x00, 0xBD, + 0x00, 0x08, 0x00, 0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, + 0x03, 0x0C, 0x00, 0x4D, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x07, 0x00, 0x05, + 0x00, 0x0A, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0A, + 0x00, 0x04, 0x00, 0x04, 0x00, 0x06, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x01, 0x3C, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x03, 0x00, 0x02, 0x00, 0x07, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x06, + 0x00, 0x06, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x09, 0x00, 0x07, 0x00, 0x02, + 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xF7, + 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xFC, + 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0xFE, 0xC4, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFF, + 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF3, + 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, + 0x00, 0x00, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFD, + 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x08, 0x00, 0x04, 0x00, 0x05, + 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, 0x01, 0x35, 0x00, 0x00, 0xFE, 0xCB, 0xFF, 0xFA, 0xFF, 0xF4, + 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x05, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, + 0x02, 0xD6, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x08, 0x00, 0x07, 0x00, 0x03, + 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xF5, 0xFF, 0xF4, + 0xFF, 0xF4, 0xFE, 0xCB, 0x00, 0x00, 0x01, 0x35, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0B, + 0x00, 0x08, 0x00, 0x04, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFD, + 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF4, 0x00, 0x01, 0x00, 0x78, + 0xFF, 0xCA, 0x02, 0x8C, 0x03, 0x0C, 0x00, 0x49, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x02, 0x00, 0x05, 0x00, 0x03, 0x00, 0x09, 0x00, 0x0A, + 0x00, 0x06, 0x00, 0x06, 0x00, 0x0D, 0x01, 0xAF, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x05, 0x00, 0x0A, + 0x00, 0x04, 0x00, 0x06, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFA, + 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0x61, 0x00, 0x00, 0x00, 0x9F, + 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x06, 0x00, 0x02, 0x00, 0x02, + 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xFA, + 0xFF, 0xF4, 0xFE, 0x57, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFB, + 0xFF, 0xFE, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x06, + 0x00, 0x0D, 0x00, 0x9E, 0x00, 0x00, 0xFF, 0x62, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF6, + 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xFB, + 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, + 0x00, 0x03, 0x00, 0x08, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0D, + 0x00, 0x05, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, 0x00, 0x03, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, + 0x02, 0x6A, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x06, 0x00, 0x08, 0x00, 0x05, 0x00, 0x0B, + 0x00, 0x05, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, + 0x00, 0x06, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xF8, + 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF8, + 0xFF, 0xFA, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0xFD, 0x96, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFB, + 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0x00, 0x01, 0x00, 0xAD, + 0xFF, 0xCA, 0x02, 0x57, 0x03, 0x0C, 0x00, 0x4F, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x57, 0xFF, 0xFF, 0xFF, 0xFE, + 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF9, 0xFF, 0xF4, + 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF4, + 0xFF, 0x92, 0xFF, 0xF4, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xF5, + 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xFA, + 0xFF, 0xFD, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x05, 0x00, 0x09, 0x00, 0x05, + 0x00, 0x07, 0x00, 0x0F, 0x00, 0x12, 0x00, 0x0A, 0x00, 0x15, 0x00, 0x0B, 0x00, 0x17, 0x00, 0x18, + 0x00, 0x73, 0x00, 0x17, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x17, 0x00, 0x0A, 0x00, 0x14, 0x00, 0x12, + 0x00, 0x0F, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x06, 0x00, 0x01, 0x02, 0xD6, 0x00, 0x0C, 0x00, 0x06, + 0x00, 0x0C, 0x00, 0x09, 0x00, 0x04, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF4, 0xFD, 0x97, + 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x08, + 0x00, 0x09, 0x00, 0xA3, 0x00, 0x07, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x06, + 0x00, 0x02, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF5, + 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, 0xF9, 0xFF, 0x5B, 0xFF, 0xE9, 0xFF, 0xE8, 0xFF, 0xEA, 0xFF, 0xF5, + 0xFF, 0xF6, 0xFF, 0xED, 0xFF, 0xEF, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFC, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x03, 0x00, 0x09, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x11, + 0x00, 0x13, 0x00, 0x15, 0x00, 0x16, 0x00, 0x18, 0x00, 0x17, 0x00, 0x00, 0x00, 0x01, 0x00, 0x78, + 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x47, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x00, 0x78, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x07, 0x00, 0x05, 0x00, 0x0A, + 0x00, 0x05, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, + 0x00, 0x03, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x01, 0x4F, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0C, + 0x00, 0x06, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x03, 0x00, 0x04, 0x00, 0x01, + 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xF8, 0xFE, 0x87, 0x01, 0x7D, 0x00, 0x04, 0x00, 0x06, + 0x00, 0x02, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF5, + 0xFF, 0xFB, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFE, 0xAC, 0x00, 0x00, + 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, + 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xFC, + 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xFC, 0xFF, 0xF8, + 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x07, 0x00, 0x04, 0x00, 0x09, + 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0xF6, 0xFE, 0xE1, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x06, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x0D, + 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x09, 0x01, 0x42, 0x01, 0x46, 0x00, 0x05, 0x00, 0x0B, + 0x00, 0x06, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x06, + 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFD, 0xFE, 0xDD, 0x00, 0xF6, + 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x09, 0x00, 0x04, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, + 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF4, + 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8C, 0x03, 0x0C, 0x00, 0x23, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xE4, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xF9, + 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF5, + 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFD, 0x00, 0x00, 0x01, 0xE5, 0x00, 0x06, 0x00, 0x0C, + 0x00, 0x05, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x06, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF4, 0xFE, 0x87, 0x02, 0xD6, + 0x00, 0x0C, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, + 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF4, + 0xFC, 0xEE, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x03, 0x00, 0x08, 0x00, 0x05, 0x00, 0x0B, + 0x00, 0x05, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, + 0x00, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, + 0x03, 0x0C, 0x00, 0x38, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, + 0x78, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0C, 0x00, + 0x07, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, 0x00, 0x03, 0x00, 0x04, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x03, 0x00, 0x06, 0x00, 0x08, 0x00, 0x0A, 0x00, + 0x0C, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x06, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x05, 0x00, 0x08, 0x00, + 0x0A, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x04, 0x00, + 0x04, 0x00, 0x05, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0xFF, 0x78, 0xFF, 0x7D, 0xFF, 0x7D, 0xFF, + 0x79, 0x00, 0x7F, 0xFF, 0x7B, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, + 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x07, 0x00, 0x04, 0x00, 0x09, 0x00, 0x06, 0x00, + 0x0C, 0x00, 0x0C, 0x02, 0x76, 0xFF, 0x6C, 0xFF, 0xDD, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, + 0xF9, 0xFF, 0xFB, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x02, 0x00, 0x05, 0x00, 0x07, 0x00, 0x0A, 0x00, + 0x0B, 0x00, 0x0C, 0x00, 0x23, 0x00, 0x94, 0xFD, 0x8A, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, + 0xF7, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x02, 0x00, 0x05, 0x00, 0x08, 0x00, + 0x04, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x85, 0x02, 0x8D, 0x00, 0x00, 0xFF, 0x45, 0x00, + 0xBB, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x2D, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x78, 0x00, 0x01, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x0C, + 0x00, 0x07, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, 0x00, 0x03, 0x00, 0x04, + 0x00, 0x01, 0x00, 0x01, 0xFF, 0xCA, 0x00, 0x35, 0xFF, 0xCE, 0x01, 0x4D, 0x00, 0x8F, 0x00, 0x00, + 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, + 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x37, 0xFF, 0xCA, 0x00, 0x32, 0xFE, 0xB3, 0xFF, 0x72, 0x00, 0x00, 0xFF, 0xF4, + 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, + 0x00, 0x03, 0x00, 0x07, 0x00, 0x04, 0x00, 0x09, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x02, 0xDC, + 0xFF, 0xCA, 0x00, 0x00, 0x00, 0x20, 0xFD, 0x0A, 0x00, 0x00, 0x03, 0x0C, 0x00, 0x0C, 0x00, 0x06, + 0x00, 0x0C, 0x00, 0x09, 0x00, 0x04, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xFA, 0xFD, 0x24, 0x00, 0x36, + 0x00, 0x00, 0xFF, 0xE0, 0x02, 0xF6, 0x00, 0x00, 0x00, 0x02, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8C, + 0x03, 0x0C, 0x00, 0x1B, 0x00, 0x56, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x7D, 0xFF, 0xF0, 0x00, 0x06, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x04, 0x00, 0x07, 0xFF, + 0xEF, 0x00, 0x97, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0x69, 0x00, 0x11, 0xFF, + 0xF9, 0xFF, 0xFC, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xFA, 0x00, 0x10, 0xFF, 0x6A, 0xFF, + 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x9C, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x03, 0x00, 0x9F, 0x00, 0x08, 0x00, 0x0D, 0x00, + 0x0A, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0C, 0x00, + 0x0B, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x12, 0x00, 0x09, 0x00, 0x07, 0x00, + 0x9A, 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, + 0xFD, 0xFF, 0xFC, 0xFF, 0x66, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xF3, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, + 0xF5, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, + 0xF5, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, 0xF8, 0xFF, 0xFD, 0x02, 0xAB, 0xFF, + 0xED, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFC, 0x00, 0x13, 0xFE, + 0xDB, 0xFF, 0xF7, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF0, 0xFE, 0xDB, 0x00, 0x13, 0xFF, 0xFC, 0xFF, + 0xFE, 0xFF, 0xFE, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0xFF, 0xED, 0x01, 0x25, 0x00, 0x09, 0x00, + 0x0E, 0x00, 0x0F, 0x00, 0x10, 0x00, 0x2B, 0xFF, 0xF7, 0xFF, 0xF0, 0xFF, 0xF4, 0xFF, 0xE5, 0xFF, + 0xE6, 0xFF, 0xF3, 0xFF, 0xE8, 0xFF, 0xF7, 0xFE, 0xCB, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, + 0xFB, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x08, 0x00, 0x0B, 0x01, 0x2B, 0x00, + 0x09, 0x00, 0x10, 0x00, 0x0C, 0x00, 0x1A, 0x00, 0x0D, 0x00, 0x1B, 0x00, 0x0C, 0x00, 0x10, 0x00, + 0x09, 0x01, 0x2B, 0x00, 0x0B, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, + 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, + 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xFA, 0x00, 0x00, 0x02, 0x00, 0x78, + 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x2B, 0x00, 0x40, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x0C, 0x00, + 0x07, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x07, 0x00, 0x04, 0x00, 0x02, 0x00, + 0x00, 0x01, 0x07, 0x00, 0x18, 0x00, 0x0B, 0x00, 0x18, 0x00, 0x16, 0x00, 0x14, 0x00, 0x11, 0x00, + 0x0F, 0x00, 0x0D, 0x00, 0x08, 0x00, 0x06, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, + 0xF8, 0xFF, 0xFC, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xF8, 0xFF, 0xED, 0xFF, 0xEB, 0xFF, 0xE9, 0xFF, + 0xE9, 0xFF, 0xF4, 0xFE, 0x81, 0x00, 0x6C, 0x00, 0x00, 0x01, 0x0A, 0x00, 0x0C, 0x00, 0x08, 0x00, + 0x07, 0x00, 0x09, 0x00, 0x07, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFE, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF7, 0x02, + 0xA0, 0xFD, 0x60, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, + 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x07, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x0C, 0x00, + 0x0C, 0x01, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x0E, 0x00, + 0x10, 0x00, 0x13, 0x00, 0x15, 0x00, 0x17, 0x00, 0x18, 0x00, 0x17, 0x00, 0xC2, 0x00, 0x17, 0x00, + 0x0C, 0x00, 0x17, 0x00, 0x0B, 0x00, 0x15, 0x00, 0x13, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x0C, 0x00, + 0x09, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0xFE, 0x68, 0x01, 0x2C, 0x00, 0x00, 0xFF, 0xFD, 0xFF, + 0xFD, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0x3D, 0xFF, + 0xF7, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFE, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0A, 0x00, 0x56, + 0x00, 0x7D, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x00, 0x78, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x04, 0x00, 0x06, + 0x00, 0x07, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0B, + 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0B, + 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x58, 0x00, 0x1F, 0x00, 0x07, 0x00, 0x05, 0x00, 0x0A, + 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x69, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x07, + 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xF7, 0xFF, 0xF6, + 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xBC, 0xFF, 0xE5, 0x00, 0x85, 0x00, 0x08, 0x00, 0x02, 0x00, 0x04, + 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, + 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF5, + 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF5, + 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF6, 0xFE, 0xCE, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFF, + 0x00, 0x6C, 0x00, 0x00, 0x01, 0x18, 0x00, 0x05, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x05, + 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xAE, 0xFF, 0xE4, 0xFF, 0xF9, 0xFF, 0xF6, + 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF7, + 0xFF, 0xF9, 0xFF, 0xFE, 0xFF, 0xFD, 0x00, 0x01, 0x00, 0x03, 0x00, 0x26, 0xFF, 0x8C, 0xFF, 0xFD, + 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x49, + 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF8, + 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x05, + 0x00, 0x07, 0x00, 0x49, 0xFF, 0xB4, 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x06, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0D, + 0x00, 0x0C, 0x00, 0x07, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x09, 0x00, 0x07, 0x00, 0x05, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x45, 0x00, 0x70, 0x00, 0x09, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x0C, 0x01, 0x78, + 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x09, + 0x00, 0x09, 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x02, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFA, + 0xFF, 0xFA, 0xFE, 0xFD, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF5, 0xFE, 0x87, 0x01, 0x60, + 0x00, 0xED, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, + 0xFF, 0xFA, 0xFE, 0xA1, 0xFF, 0xBB, 0x00, 0x45, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x05, 0x00, 0x04, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xFA, + 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xA0, 0xFF, 0xA0, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x01, + 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x03, 0x00, 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x78, + 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x3E, 0x00, 0x51, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x0C, + 0x00, 0x07, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, 0x00, 0x06, 0x00, 0x02, + 0x00, 0x01, 0x00, 0x4A, 0x00, 0xFA, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0D, + 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x06, 0x00, 0x04, 0x00, 0x01, 0xFF, 0xFF, + 0xFF, 0xFC, 0xFF, 0x23, 0x00, 0x40, 0x00, 0x18, 0x00, 0x17, 0x00, 0x0C, 0x00, 0x16, 0x00, 0x0A, + 0x00, 0x0A, 0x00, 0x11, 0x00, 0x08, 0x00, 0x0E, 0x00, 0x06, 0x00, 0x08, 0x00, 0x06, 0x00, 0x02, + 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xED, + 0xFF, 0xF6, 0xFF, 0xEA, 0xFF, 0xE8, 0xFF, 0xF5, 0xFF, 0xE8, 0xFE, 0x8D, 0x00, 0x6C, 0x00, 0x00, + 0x01, 0x0A, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, + 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF4, + 0xFF, 0x5F, 0x02, 0xA0, 0xFD, 0x5A, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xFC, + 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x07, 0x00, 0x04, 0x00, 0x09, + 0x00, 0x0C, 0x00, 0x0C, 0x01, 0x96, 0x00, 0x00, 0xFE, 0x55, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFD, + 0xFF, 0xFB, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x05, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0C, + 0x00, 0x06, 0x00, 0x0D, 0x00, 0x0C, 0x01, 0x7A, 0x00, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x04, + 0x00, 0x0A, 0x00, 0x07, 0x00, 0x07, 0x00, 0x10, 0x00, 0x0A, 0x00, 0x14, 0x00, 0x0A, 0x00, 0x17, + 0x00, 0x18, 0x00, 0x17, 0x00, 0x3A, 0x00, 0x17, 0x00, 0x18, 0x00, 0x16, 0x00, 0x0B, 0x00, 0x14, + 0x00, 0x12, 0x00, 0x0F, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, + 0xFE, 0xF0, 0x00, 0xA4, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF9, + 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xC0, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFC, + 0xFF, 0xFD, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x77, 0xFF, 0xCA, 0x02, 0x8D, + 0x03, 0x0C, 0x00, 0x7C, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x02, 0x20, 0x00, 0x02, 0x00, 0x05, 0x00, 0x04, 0x00, 0x08, 0x00, 0x05, 0x00, + 0x0B, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x08, 0x00, + 0x05, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, + 0xF0, 0xFF, 0xED, 0xFF, 0xEC, 0xFF, 0xEB, 0xFF, 0xF4, 0xFF, 0xEB, 0xFE, 0xF2, 0xFF, 0xF4, 0xFF, + 0xF5, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF3, 0xFF, 0xF9, 0xFF, + 0xF9, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x04, 0x00, + 0x05, 0x00, 0x08, 0x00, 0x07, 0x01, 0x8D, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, + 0xFB, 0xFF, 0xFA, 0xFE, 0xF8, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, + 0xFC, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, + 0xFA, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, + 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x0D, 0x00, 0x10, 0x00, 0x09, 0x00, + 0x14, 0x00, 0x15, 0x00, 0x16, 0x00, 0x15, 0x01, 0x0E, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x10, 0x00, + 0x0B, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x09, 0x00, 0x08, 0x00, 0x07, 0x00, 0x07, 0x00, 0x05, 0x00, + 0x07, 0x00, 0x05, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, + 0xF9, 0xFE, 0x73, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x01, + 0x10, 0x00, 0x08, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x03, 0x00, 0x02, 0x02, 0x79, 0xFF, + 0xF4, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, + 0x01, 0x00, 0x04, 0x00, 0x03, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x06, 0x00, + 0x15, 0x00, 0x0B, 0x00, 0x16, 0x00, 0x14, 0x00, 0x12, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x0A, 0x00, + 0x07, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, + 0xFB, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF1, 0xFF, + 0xEF, 0xFF, 0xF0, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xEE, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF5, 0xFE, + 0x03, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x06, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x11, 0x00, 0x0C, 0x00, + 0x06, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x06, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, + 0xFA, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xEB, 0xFF, 0xF5, 0xFF, + 0xEA, 0xFF, 0xEC, 0xFF, 0xEE, 0xFF, 0xF0, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x05, 0x00, 0x06, 0x00, + 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0F, 0x00, 0x11, 0x00, 0x10, 0x00, + 0x11, 0x00, 0x10, 0x00, 0x11, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x0B, 0x01, 0xFD, 0x00, 0x04, 0x00, + 0x03, 0x00, 0x05, 0x00, 0x05, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, + 0xF9, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xF5, 0x00, 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, + 0x03, 0x0C, 0x00, 0x35, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x04, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x0C, + 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, 0x00, 0x06, 0x00, 0x02, 0x00, 0x01, 0x00, 0x68, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x08, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x0C, + 0x00, 0x06, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x03, 0x00, 0x03, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x68, 0x00, 0x01, 0x00, 0x03, 0x00, 0x02, 0x00, 0x07, 0x00, 0x09, + 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x05, + 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0xFD, 0xEB, 0x02, 0xA0, 0xFF, 0xD5, 0xFF, 0xFA, 0xFF, 0xF4, + 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xFE, 0x00, 0x01, 0x00, 0x04, 0x00, 0x06, + 0x00, 0x04, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x2B, 0x00, 0x00, 0xFD, 0x5A, 0xFF, 0xF4, + 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x05, 0x00, 0x08, 0x00, 0x09, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0C, 0x02, 0xA6, + 0x00, 0x00, 0xFF, 0xD5, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFC, + 0xFF, 0xFF, 0x00, 0x02, 0x00, 0x05, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x06, + 0x00, 0x91, 0x00, 0x00, 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x4D, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, 0xE4, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xFA, + 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFA, + 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x0F, 0x00, 0x09, + 0x00, 0x13, 0x00, 0x14, 0x00, 0x0C, 0x00, 0x17, 0x00, 0x0C, 0x00, 0x17, 0x00, 0xD2, 0x00, 0x18, + 0x00, 0x17, 0x00, 0x0C, 0x00, 0x16, 0x00, 0x14, 0x00, 0x11, 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x04, + 0x00, 0x08, 0x00, 0x02, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xF8, + 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xFB, + 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, + 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0x28, 0xFF, 0xF8, 0xFF, 0xF4, + 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFC, 0x00, 0x00, 0x02, 0xD6, 0x00, 0x0C, + 0x00, 0x06, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, + 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF4, 0xFD, 0x84, + 0xFF, 0xF4, 0xFF, 0xE8, 0xFF, 0xEA, 0xFF, 0xEB, 0xFF, 0xED, 0xFF, 0xF7, 0xFF, 0xF1, 0xFF, 0xF4, + 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x02, 0x00, 0x05, 0x00, 0x04, + 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x11, 0x00, 0x13, 0x00, 0x15, 0x00, 0x0B, 0x00, 0x17, 0x00, 0x0C, + 0x00, 0x17, 0x02, 0x71, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x06, 0x00, 0x09, 0x00, 0x04, 0x00, 0x07, + 0x00, 0x02, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xFA, + 0xFF, 0xF4, 0xFF, 0xFA, 0xFD, 0x91, 0xFF, 0xF3, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xFB, + 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x07, 0x00, 0x05, + 0x00, 0x0A, 0x00, 0x07, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, + 0x03, 0x0C, 0x00, 0x37, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xE4, + 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, + 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xFC, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0xD5, 0x00, 0x06, 0x00, 0x08, 0x00, 0x0A, + 0x00, 0x05, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x09, + 0x00, 0x07, 0x00, 0xD7, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xF9, + 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xFB, + 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0x62, 0xFF, 0x62, 0x02, 0xD6, + 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x09, 0x00, 0x04, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, + 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF4, + 0xFF, 0xF4, 0xFF, 0x4A, 0xFF, 0xF6, 0xFF, 0xF7, 0xFD, 0xDF, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF9, + 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x05, 0x00, 0x03, 0x00, 0x09, + 0x00, 0x0A, 0x02, 0x26, 0x00, 0x09, 0x00, 0x0A, 0x00, 0xB6, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, + 0x00, 0x08, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xFC, + 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0x55, 0xFE, 0x6A, 0x01, 0x96, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x5D, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x01, + 0x00, 0x03, 0x00, 0x38, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x69, + 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x05, 0x00, 0x08, 0x00, 0x07, 0x00, 0x02, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x04, 0x00, 0x06, 0x00, 0x04, 0x00, 0x09, 0x00, 0x06, 0x00, 0x0C, + 0x00, 0x0C, 0x00, 0x70, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x35, 0x00, 0x05, + 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xF9, 0xFF, 0xF7, + 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xFC, + 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xE2, 0xFF, 0xB6, 0x00, 0x00, 0xFF, 0xFD, + 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF4, + 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFD, 0x00, 0x00, + 0xFF, 0xB6, 0xFF, 0xE2, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0xFB, + 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF7, + 0xFF, 0xF9, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x5B, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xA0, + 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, + 0x00, 0x03, 0x00, 0x04, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x07, 0x00, 0x09, 0xFF, 0xF7, + 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x06, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x5A, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x0C, + 0x02, 0x7B, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x04, 0x00, 0x05, + 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xF5, 0xFF, 0xF4, + 0xFF, 0xF4, 0xFD, 0x93, 0xFF, 0xCD, 0x00, 0x00, 0x01, 0x96, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x05, + 0x00, 0x0A, 0x00, 0x08, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFE, + 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF4, 0xFE, 0x6A, 0x00, 0x00, 0x00, 0x33, + 0x02, 0x6D, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x09, 0x00, 0x04, 0x00, 0x07, 0x00, 0x02, + 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xFA, + 0xFF, 0xFA, 0xFF, 0xF4, 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x43, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x01, 0x00, 0x01, 0x00, 0x05, 0x00, 0xC5, + 0xFF, 0x38, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x02, 0x00, 0x05, 0x00, 0x03, 0x00, 0x09, 0x00, 0x0A, + 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x07, 0x00, 0xA6, + 0x00, 0xA6, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x06, + 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x03, 0x00, 0x05, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFC, + 0xFF, 0xFD, 0xFF, 0x3C, 0x00, 0xC7, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFA, + 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF6, + 0xFF, 0xF9, 0xFF, 0x5A, 0xFF, 0x5A, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF9, + 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFC, 0x02, 0xD9, + 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF5, 0xFE, 0xB0, 0xFE, 0xAB, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF4, + 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x05, + 0x00, 0x03, 0x00, 0x08, 0x00, 0x0A, 0x01, 0x1B, 0xFE, 0xE5, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFA, + 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x02, 0x00, 0x06, 0x00, 0x08, 0x00, 0x05, 0x00, 0x05, + 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x05, 0x01, 0x50, 0x01, 0x55, 0x00, 0x0C, + 0x00, 0x06, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x07, 0x00, 0x04, 0x00, 0x02, + 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF6, 0xFE, 0xE5, 0x01, 0x1B, 0x00, 0x0A, + 0x00, 0x08, 0x00, 0x06, 0x00, 0x03, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xF8, + 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, + 0x03, 0x0C, 0x00, 0x40, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x01, 0x00, 0x02, 0x00, 0x05, 0x00, + 0x03, 0x00, 0xC9, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x08, 0x00, 0x05, 0x00, + 0x0B, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x08, 0x00, + 0x03, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0xC9, 0x00, 0x04, 0x00, 0x05, 0x00, 0x01, 0x00, + 0x02, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, + 0xFA, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, + 0xFE, 0x00, 0x00, 0xFF, 0x62, 0xFF, 0x62, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, + 0xF8, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, + 0xFC, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFF, 0x02, 0x7B, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, + 0xF5, 0xFF, 0xFB, 0xFE, 0xFE, 0xFE, 0xA2, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, + 0xFC, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x08, 0x00, + 0x09, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x01, 0x5E, 0x01, 0x02, 0x00, 0x05, 0x00, 0x0B, 0x00, + 0x05, 0x00, 0x0C, 0x00, 0x61, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x08, 0x00, 0x05, 0x00, + 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, + 0xF4, 0xFF, 0xF4, 0xFF, 0xB8, 0xFF, 0x35, 0x00, 0xCB, 0x00, 0x4E, 0x00, 0x06, 0x00, 0x0C, 0x00, + 0x06, 0x00, 0x09, 0x00, 0x04, 0x00, 0x07, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, + 0xF9, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF4, 0x00, 0x00, 0x01, 0x00, 0x78, + 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x2C, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xAE, 0x01, 0xDF, 0x00, 0x00, 0xFE, 0x7F, 0x01, + 0x4B, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x04, 0x00, 0x09, 0x00, 0x06, 0x00, 0x05, 0x00, + 0x01, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, + 0xFC, 0xFF, 0xF5, 0xFF, 0xF5, 0xFE, 0x1C, 0x00, 0x00, 0x01, 0x80, 0xFE, 0xB6, 0xFF, 0xF5, 0xFF, + 0xFA, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x06, 0x00, 0x08, 0x00, 0x09, 0x00, 0x05, 0x00, 0x05, 0x00, + 0x0B, 0x03, 0x0C, 0x00, 0x00, 0xFF, 0xBB, 0xFD, 0x6F, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, + 0xFC, 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, + 0xF5, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFD, 0x00, + 0x00, 0x00, 0x45, 0x02, 0x91, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x07, 0x00, + 0x08, 0x00, 0x05, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x05, 0x00, 0x0B, 0x00, + 0x0A, 0x00, 0x08, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x02, 0x00, 0x78, + 0xFF, 0xCA, 0x02, 0x8D, 0x02, 0x29, 0x00, 0x56, 0x00, 0x70, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, + 0x01, 0x00, 0x03, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x0E, 0x00, 0x10, 0x00, 0x13, 0x00, 0x15, 0x00, + 0x0B, 0x00, 0x17, 0x00, 0x0B, 0x00, 0x17, 0x00, 0x85, 0x00, 0x17, 0x00, 0x17, 0x00, 0x17, 0x00, + 0x3F, 0x00, 0x01, 0x00, 0x03, 0x00, 0x06, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x06, 0x00, + 0x0D, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x01, 0xFF, 0xF2, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF8, 0xFF, + 0xEE, 0xFF, 0xED, 0xFF, 0xEB, 0xFF, 0xF4, 0xFF, 0xEA, 0xFF, 0xE9, 0xFF, 0x39, 0xFF, 0xF4, 0xFF, + 0xFA, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFF, 0x00, 0x01, 0x00, + 0x04, 0x00, 0x03, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x0C, 0x00, 0xCA, 0x00, + 0x08, 0x00, 0x07, 0x00, 0x07, 0x00, 0x06, 0x00, 0x09, 0x00, 0x04, 0x00, 0x03, 0x00, 0x04, 0x00, + 0x01, 0x00, 0x02, 0xFF, 0x02, 0xFF, 0xE9, 0xFF, 0xF5, 0xFF, 0xE9, 0xFF, 0xEA, 0xFF, 0xEC, 0xFF, + 0xEF, 0xFF, 0xF1, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFF, 0x00, 0x6C, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x05, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x08, 0x01, + 0x00, 0x00, 0x03, 0xFF, 0x9A, 0xFF, 0xF8, 0xFF, 0xF4, 0xFF, 0xF8, 0xFF, 0x7F, 0xFF, 0xF4, 0xFF, + 0xF8, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, + 0x6A, 0xFF, 0xE9, 0xFF, 0xF4, 0xFF, 0xE9, 0xFF, 0xEA, 0xFF, 0xEC, 0xFF, 0xEF, 0xFF, 0xF1, 0xFF, + 0xF4, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x02, 0x00, 0x05, 0x00, + 0x09, 0x00, 0x25, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x03, 0x00, 0x05, 0x00, 0x08, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x06, 0x00, + 0x06, 0x00, 0x0D, 0x01, 0x8D, 0x00, 0x16, 0x00, 0x0C, 0x00, 0x16, 0x00, 0x15, 0x00, 0x13, 0x00, + 0x09, 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0xFF, + 0xFE, 0xFF, 0xFF, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, + 0xF4, 0xFF, 0xF3, 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, + 0xF5, 0xFF, 0xF7, 0xFF, 0xBE, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, + 0xF3, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, 0xEB, 0xFF, 0xE9, 0xFF, 0xE9, 0xFF, 0xE9, 0xFF, 0xC5, 0x00, + 0x39, 0x00, 0x09, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x06, 0x00, 0x03, 0x00, + 0x01, 0x00, 0x00, 0xFF, 0x9E, 0xFF, 0xC5, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x07, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x49, + 0x00, 0x74, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x0C, 0x00, + 0x0C, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x07, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x29, 0x00, + 0x11, 0x00, 0x14, 0x00, 0x0B, 0x00, 0x15, 0x00, 0x0C, 0x00, 0x15, 0x00, 0x5E, 0x00, 0x16, 0x00, + 0x16, 0x00, 0x15, 0x00, 0x13, 0x00, 0x31, 0x00, 0x0F, 0x00, 0x07, 0x00, 0x0D, 0x00, 0x09, 0x00, + 0x07, 0x00, 0x02, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF3, 0xFF, + 0xF9, 0xFF, 0xF0, 0xFF, 0xD8, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, 0xF5, 0xFF, 0xEA, 0xFF, 0xEA, 0xFF, + 0x98, 0xFF, 0xE9, 0xFF, 0xF5, 0xFF, 0xEB, 0xFF, 0xEC, 0xFF, 0xEF, 0xFF, 0xD7, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, + 0xF4, 0xFF, 0xF5, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x6C, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x05, 0x00, 0x09, 0x00, 0x5A, 0x00, 0x0A, 0x00, 0x07, 0x00, + 0x07, 0x00, 0x0C, 0x00, 0x4F, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x25, 0x00, + 0x06, 0x00, 0x05, 0x00, 0x05, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, + 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xD7, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, + 0xAC, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xA2, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, + 0xFD, 0xFF, 0xFE, 0x00, 0xB8, 0xFF, 0x48, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, + 0xFC, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x07, 0x00, 0x08, 0x00, + 0x0B, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x13, 0xFF, 0xDD, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xFC, 0xFF, + 0xF9, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 0x00, 0x09, 0x00, 0x0D, 0x00, + 0x29, 0x00, 0x0F, 0x00, 0x09, 0x00, 0x14, 0x00, 0x15, 0x00, 0x16, 0x00, 0x0B, 0x00, 0x16, 0x00, + 0xE8, 0x00, 0x17, 0x00, 0x16, 0x00, 0x15, 0x00, 0x13, 0x00, 0x09, 0x00, 0x10, 0x00, 0x22, 0x00, + 0x0D, 0x00, 0x0B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, + 0xF8, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xDD, 0x00, 0xFC, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0B, 0x00, + 0x08, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, + 0xF7, 0xFF, 0xF4, 0xFF, 0xF4, 0xFE, 0x5F, 0xFF, 0x7F, 0x00, 0x7F, 0x00, 0x0C, 0x00, 0x0B, 0x00, + 0x0A, 0x00, 0x08, 0x00, 0x4E, 0x00, 0x06, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, + 0xFF, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xE0, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, + 0xF9, 0xFF, 0xF8, 0xFF, 0x22, 0xFF, 0xF4, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, + 0xDC, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, + 0x03, 0x00, 0x05, 0x00, 0x51, 0x00, 0x08, 0x00, 0x07, 0x00, 0x07, 0x00, 0x0B, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8C, 0x02, 0x29, 0x00, 0x54, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x02, 0x57, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x05, 0x00, + 0x07, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, + 0xFB, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0x1C, 0xFF, 0xEB, 0xFF, 0xF4, 0xFF, + 0xEB, 0xFF, 0xEB, 0xFF, 0xED, 0xFF, 0x92, 0xFF, 0xF9, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF8, 0xFF, + 0xFB, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x0C, 0x00, + 0x0F, 0x00, 0x6E, 0x00, 0x13, 0x00, 0x15, 0x00, 0x15, 0x00, 0x0C, 0x00, 0x15, 0x00, 0xE4, 0x00, + 0x0C, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x06, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, + 0xF4, 0xFF, 0x1E, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xA6, 0xFF, 0xFA, 0xFF, + 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, + 0x05, 0x00, 0x09, 0x00, 0x5E, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x07, 0x00, 0x08, 0x01, 0xBD, 0x00, + 0x01, 0x00, 0x02, 0x00, 0x06, 0x00, 0x03, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x0D, 0x00, + 0x06, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x04, 0x00, 0x06, 0x00, + 0x01, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, + 0xA2, 0xFF, 0xF8, 0xFF, 0xED, 0xFF, 0xEC, 0xFF, 0xEA, 0xFF, 0xEA, 0xFF, 0xF4, 0xFF, 0x73, 0xFF, + 0xEA, 0xFF, 0xE9, 0xFF, 0xF5, 0xFF, 0xEB, 0xFF, 0xED, 0xFF, 0xEF, 0xFF, 0xA2, 0xFF, 0xF4, 0xFF, + 0xF7, 0xFF, 0xFA, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x03, 0x00, + 0x08, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x05, 0x00, + 0x0B, 0x00, 0x05, 0x00, 0x08, 0x00, 0x03, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x02, 0x00, 0x05, 0x00, 0x05, 0x00, 0x4D, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, 0x07, 0x00, + 0x08, 0x00, 0x08, 0x00, 0x7F, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x51, 0x00, + 0x05, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, + 0x03, 0x0C, 0x00, 0x4B, 0x00, 0x75, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, + 0x00, 0x01, 0x00, 0x02, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x10, 0x00, 0x31, + 0x00, 0x13, 0x00, 0x15, 0x00, 0x16, 0x00, 0x16, 0x00, 0x5D, 0x00, 0x16, 0x00, 0x0B, 0x00, 0x16, + 0x00, 0x0A, 0x00, 0x14, 0x00, 0x12, 0x00, 0x28, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x07, + 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0A, + 0x00, 0x08, 0x00, 0x05, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, + 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xF4, + 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xD8, 0xFF, 0xEE, + 0xFF, 0xEC, 0xFF, 0xF6, 0xFF, 0xEA, 0xFF, 0xF5, 0xFF, 0xEA, 0xFF, 0xAD, 0xFF, 0xEB, 0xFF, 0xEA, + 0xFF, 0xEB, 0xFF, 0xEC, 0xFF, 0xF6, 0xFF, 0xD0, 0xFF, 0xF0, 0xFF, 0xF8, 0xFF, 0xF4, 0xFF, 0xF6, + 0xFF, 0xF9, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, + 0x00, 0x05, 0x00, 0x05, 0x00, 0x29, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x53, 0x00, 0x0C, + 0x00, 0x07, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x5E, 0x00, 0x07, 0x00, 0x05, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xA6, 0xFF, 0xF6, 0xFF, 0xF9, + 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xB2, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xDA, + 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x8B, 0xFF, 0xEA, 0xFF, 0xF5, + 0xFF, 0xEA, 0xFF, 0xEB, 0xFF, 0xEC, 0xFF, 0xF7, 0xFF, 0xF1, 0xFF, 0xD6, 0xFF, 0xF4, 0xFF, 0xF7, + 0xFF, 0xFA, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x07, 0x00, 0x04, 0x00, 0x0B, + 0x00, 0x0D, 0x00, 0x23, 0xFF, 0xED, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF9, + 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0C, + 0x00, 0x06, 0x00, 0x0C, 0x00, 0xB8, 0x02, 0x24, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x08, + 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xF7, + 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0x0A, 0x00, 0x23, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x04, + 0x00, 0x06, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xF5, + 0xFF, 0xFA, 0xFF, 0xD7, 0xFF, 0xF0, 0xFF, 0xF7, 0xFF, 0xED, 0xFF, 0xEB, 0xFF, 0xEA, 0xFF, 0xF4, + 0xFF, 0xEA, 0xFF, 0x25, 0x00, 0xD9, 0x00, 0x0C, 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x06, + 0x00, 0x23, 0x00, 0x07, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, + 0xFF, 0xFA, 0xFF, 0xAF, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0x81, 0xFF, 0xF4, + 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xB3, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x05, 0x00, 0x05, 0x00, 0x21, 0x00, 0x06, 0x00, 0x09, + 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, + 0x02, 0x29, 0x00, 0x4E, 0x00, 0x64, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, + 0x01, 0x00, 0x02, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x10, 0x00, 0x31, 0x00, + 0x13, 0x00, 0x15, 0x00, 0x16, 0x00, 0x0B, 0x00, 0x16, 0x01, 0x1E, 0x00, 0x0C, 0x00, 0x06, 0x00, + 0x0A, 0x00, 0x09, 0x00, 0x06, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, + 0xFA, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, 0xF4, 0xFE, 0xEA, 0xFF, 0xF7, 0xFF, + 0xF9, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xDA, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0x00, + 0x00, 0x01, 0x79, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x06, 0x00, + 0x04, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xF3, 0xFF, + 0xF1, 0xFF, 0xC8, 0xFF, 0xF6, 0xFF, 0xED, 0xFF, 0xF5, 0xFF, 0xEA, 0xFF, 0xF5, 0xFF, 0xEA, 0xFF, + 0x6E, 0xFF, 0xEA, 0xFF, 0xF5, 0xFF, 0xEB, 0xFF, 0xF5, 0xFF, 0xED, 0xFF, 0xD0, 0xFF, 0xF0, 0xFF, + 0xF8, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x6C, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x05, 0x00, 0x2C, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x07, 0x00, + 0x08, 0x00, 0x89, 0x00, 0x07, 0x00, 0x08, 0x00, 0x06, 0x00, 0x07, 0x00, 0x2C, 0x00, 0x07, 0x00, + 0x05, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8B, 0xFF, 0xEA, 0xFF, 0xF5, 0xFF, 0xEA, 0xFF, + 0xEB, 0xFF, 0xEC, 0xFF, 0xF7, 0xFF, 0xF1, 0xFF, 0xD6, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, + 0xFE, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0B, 0x00, + 0x05, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, 0x00, + 0x03, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x04, 0x00, 0x07, 0x00, + 0x21, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x05, 0x00, 0x03, 0x00, 0x08, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x90, 0x00, + 0x17, 0x00, 0x0B, 0x00, 0x16, 0x00, 0x0A, 0x00, 0x13, 0x00, 0x12, 0x00, 0x30, 0x00, 0x06, 0x00, + 0x0B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, + 0xF8, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, 0xD7, 0xFF, 0xF0, 0xFF, 0xF7, 0xFF, 0xED, 0xFF, 0xEB, 0xFF, + 0xEA, 0xFF, 0xF4, 0xFF, 0xEA, 0xFF, 0xB1, 0x00, 0x52, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x06, 0x00, + 0x06, 0x00, 0x26, 0x00, 0x04, 0x00, 0x04, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, + 0xFE, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xDA, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, + 0xB3, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xAD, 0xFF, 0xCA, 0x02, 0x8C, 0x03, 0x0C, 0x00, 0x5E, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0xAD, 0x00, 0x02, 0x00, 0x05, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x06, 0x00, + 0x0C, 0x00, 0x3A, 0x00, 0x00, 0x00, 0x03, 0x00, 0x06, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0B, 0x00, + 0x0D, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, 0x00, 0x06, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x6A, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x04, 0x00, + 0x05, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, + 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0x96, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, + 0x06, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0F, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x81, 0x00, + 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x09, 0x00, 0x06, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x00, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, + 0x6B, 0xFF, 0xE8, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xE9, 0xFF, 0xF5, 0xFF, 0xEB, 0xFF, 0xED, 0xFF, + 0xF0, 0xFF, 0xF2, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFF, 0x00, 0x00, 0xFF, + 0xC6, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFE, 0x01, + 0xF0, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xFD, 0x00, + 0x00, 0xFE, 0x3D, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFE, 0x00, + 0x01, 0x00, 0x03, 0x00, 0x07, 0x00, 0x04, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x0C, 0x01, 0xC3, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x0B, 0x00, + 0x0D, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x09, 0x00, 0x07, 0x00, 0x03, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x10, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x09, 0x00, + 0x0C, 0x00, 0x06, 0x00, 0x05, 0x00, 0x06, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x02, 0x00, 0x06, 0x00, 0x04, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x0D, 0x00, 0x0C, 0x00, + 0x0C, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0xFF, + 0xFC, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xF2, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, + 0xEB, 0xFF, 0xF5, 0xFF, 0xE9, 0xFF, 0xF4, 0xFF, 0xE8, 0xFF, 0xE8, 0xFF, 0xDA, 0x00, 0x00, 0xFF, + 0xFD, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, 0xF4, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x78, 0xFE, 0xE7, 0x02, 0x8D, 0x02, 0x29, 0x00, 0x61, 0x00, 0x8B, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x01, + 0x00, 0x02, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x08, 0x00, 0x10, 0x00, 0x30, 0x00, 0x13, + 0x00, 0x15, 0x00, 0x16, 0x00, 0x16, 0x00, 0x68, 0x00, 0x17, 0x00, 0x17, 0x00, 0x16, 0x00, 0x40, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xD7, 0xFF, 0xF9, + 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0x19, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF5, + 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x02, 0x00, 0x05, 0x00, 0x08, + 0x00, 0x09, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0xE3, 0x00, 0x16, 0x00, 0x0B, 0x00, 0x16, + 0x00, 0x15, 0x00, 0x13, 0x00, 0x31, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0B, 0x00, 0x04, 0x00, 0x07, + 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xF7, + 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF8, + 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xD8, 0xFF, 0xEE, 0xFF, 0xEC, 0xFF, 0xF6, + 0xFF, 0xEA, 0xFF, 0xF5, 0xFF, 0xEA, 0xFF, 0xA3, 0xFF, 0xEA, 0xFF, 0xEA, 0xFF, 0xEB, 0xFF, 0xED, + 0xFF, 0xD0, 0xFF, 0xF0, 0xFF, 0xF8, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFE, 0xFF, 0xFF, + 0x00, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x03, 0x00, 0x08, 0x00, 0x29, + 0x00, 0x06, 0x00, 0x07, 0x00, 0x07, 0x00, 0x0B, 0x00, 0x53, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x07, + 0x00, 0x0A, 0x00, 0x5E, 0x00, 0x07, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xA9, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xA6, + 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xDA, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFB, + 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0xB8, 0xFF, 0xEA, 0xFF, 0xF5, 0xFF, 0xE9, 0xFF, 0xEB, 0xFF, 0xED, + 0xFF, 0xF7, 0xFF, 0xF0, 0xFF, 0xD8, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xFF, + 0x00, 0x02, 0x00, 0x05, 0x00, 0x09, 0x00, 0x24, 0xFF, 0x7F, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF9, + 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xDC, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, 0xFA, + 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0D, 0x00, 0x29, 0x00, 0x0F, + 0x00, 0x13, 0x00, 0x14, 0x00, 0x0B, 0x00, 0x16, 0x00, 0x0B, 0x00, 0x16, 0x01, 0x1D, 0x01, 0x34, + 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x08, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, + 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xED, + 0x00, 0x23, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, + 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xD7, 0xFF, 0xF0, 0xFF, 0xF7, 0xFF, 0xED, + 0xFF, 0xEB, 0xFF, 0xEA, 0xFF, 0xF4, 0xFF, 0xEA, 0xFF, 0x52, 0x00, 0xAC, 0x00, 0x09, 0x00, 0x07, + 0x00, 0x07, 0x00, 0x07, 0x00, 0x09, 0x00, 0x23, 0x00, 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xAF, 0xFF, 0xF7, 0xFF, 0xFA, + 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0x89, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF8, + 0xFF, 0xCF, 0xFF, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x05, 0x00, 0x05, + 0x00, 0x20, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x07, 0x00, 0x01, 0x00, 0x78, + 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x56, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x78, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0B, 0x00, + 0x06, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, 0x00, + 0x03, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0xA4, 0x00, 0x07, 0x00, 0x07, 0x00, 0x0C, 0x00, + 0x40, 0x00, 0x08, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x04, 0x00, + 0x02, 0x00, 0x0D, 0x00, 0x02, 0x00, 0x05, 0x00, 0x03, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0C, 0x00, + 0x06, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x07, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xF1, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, + 0xF1, 0xFF, 0xF7, 0xFF, 0xEE, 0xFF, 0xEB, 0xFF, 0xEA, 0xFF, 0xEA, 0xFF, 0xF4, 0xFF, 0xAC, 0xFF, + 0xE9, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xEC, 0xFF, 0x8F, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, + 0xFD, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, + 0xF4, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, + 0x00, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFE, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x07, 0x00, 0x04, 0x00, 0x09, 0x00, 0x06, 0x00, + 0x0C, 0x00, 0x0C, 0x01, 0x4D, 0x00, 0x69, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, + 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF3, 0xFE, + 0x75, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x03, 0x00, 0x08, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x06, 0x00, + 0x06, 0x00, 0x0D, 0x01, 0x98, 0x00, 0x17, 0x00, 0x16, 0x00, 0x0B, 0x00, 0x14, 0x00, 0x12, 0x00, + 0x08, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x08, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFC, 0xFF, + 0xFE, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xB9, 0x01, 0x08, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x06, 0x00, + 0x0B, 0x00, 0x08, 0x00, 0x04, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, + 0xFD, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF4, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0xAD, 0xFF, 0xCA, 0x02, 0x57, 0x03, 0x3A, 0x00, 0x3F, 0x00, 0x62, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0xAD, 0x00, 0x02, 0x00, 0x02, 0x00, 0x07, 0x00, 0x04, 0x00, 0x09, 0x00, + 0x06, 0x00, 0x0C, 0x00, 0x0C, 0x01, 0x45, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x05, 0x00, + 0x07, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, + 0xF7, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF9, 0xFF, 0x97, 0x00, 0x00, 0xFF, 0xFF, 0xFF, + 0xFC, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0x5B, 0xFF, + 0xF4, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x05, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x69, 0x00, + 0x00, 0xFF, 0x97, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, + 0xFE, 0xFF, 0xFE, 0x00, 0x6A, 0x00, 0x01, 0x00, 0x01, 0x00, 0x05, 0x00, 0x08, 0x00, 0x09, 0x00, + 0x06, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x35, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x09, 0x00, + 0x06, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, + 0xF6, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xC5, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, + 0xF6, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xF3, 0xFF, 0xFB, 0xFF, + 0xF5, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x05, 0x00, 0x07, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, 0x00, + 0x07, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x09, 0x00, 0x04, 0x00, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x01, 0xC3, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x04, 0x00, 0x08, 0x00, 0x05, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, + 0xF4, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, + 0xFB, 0xFF, 0xFF, 0x00, 0x00, 0xFE, 0x79, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, + 0xFC, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF3, 0x02, 0xD3, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, + 0xF5, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x05, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x0D, 0x00, 0x2D, 0x00, 0x0D, 0x00, + 0x0C, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x05, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF7, 0x00, + 0x00, 0x02, 0x00, 0x78, 0xFE, 0xE7, 0x02, 0x23, 0x03, 0x3A, 0x00, 0x56, 0x00, 0x79, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x01, 0x00, 0x05, + 0x00, 0x08, 0x00, 0x0C, 0x00, 0x10, 0x00, 0x08, 0x00, 0x09, 0x00, 0x15, 0x00, 0x16, 0x00, 0x0B, + 0x00, 0x18, 0x00, 0x17, 0x00, 0x6B, 0x00, 0x17, 0x00, 0x0C, 0x00, 0x17, 0x00, 0x0B, 0x00, 0x15, + 0x00, 0x12, 0x00, 0x11, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x09, 0x00, 0x05, 0x00, 0x01, 0x00, 0x01, + 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF4, 0xFF, 0xF7, + 0xFF, 0x2B, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFC, + 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x02, 0x00, 0x05, 0x00, 0x03, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x0B, + 0x00, 0x06, 0x00, 0x0C, 0x00, 0x9F, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xF9, + 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0x98, 0xFF, 0xF3, 0xFF, 0xF8, 0xFF, 0xF9, + 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xF9, + 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF6, + 0xFF, 0xF7, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFE, 0x01, 0x09, 0x00, 0x01, 0x00, 0x02, 0x00, 0x05, + 0x00, 0x07, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x36, 0x00, 0x0C, 0x00, 0x0C, + 0x00, 0x0B, 0x00, 0x08, 0x00, 0x06, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, + 0xFF, 0xFE, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xC4, 0xFF, 0xFA, + 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0x7B, + 0xFF, 0xF3, 0xFF, 0xEB, 0xFF, 0xE9, 0xFF, 0xEB, 0xFF, 0xED, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF2, + 0xFF, 0xF6, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x07, + 0x00, 0x05, 0x00, 0x0C, 0x00, 0x0F, 0x00, 0x11, 0x00, 0x14, 0x00, 0x0B, 0x00, 0x16, 0x00, 0x18, + 0x00, 0x0C, 0x02, 0x7A, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x05, + 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF6, + 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xFB, + 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0xFD, 0xCC, 0xFF, 0xF3, 0xFF, 0xF8, + 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x03, 0x00, 0x03, 0x00, 0x07, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, 0x14, 0x00, 0x06, + 0x00, 0x06, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x03, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, 0xFA, 0x03, 0x55, 0xFF, 0xF7, + 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x05, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x0D, 0x00, 0x2D, + 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x05, 0x00, 0x03, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xFA, + 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x47, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x01, 0x00, 0x04, + 0x00, 0x04, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x0C, 0x00, 0x0C, + 0x00, 0x06, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x07, 0x00, 0x04, 0x00, 0x01, 0x00, 0x01, 0x00, 0x34, + 0x01, 0x1B, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x05, + 0x00, 0x09, 0x00, 0x06, 0x00, 0x04, 0x00, 0x01, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFC, + 0xFE, 0xF2, 0x00, 0xDD, 0x00, 0x04, 0x00, 0x06, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF3, + 0xFF, 0xF5, 0xFF, 0xFA, 0xFE, 0xE1, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xF8, + 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF5, + 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xF4, 0xFF, 0xF4, + 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, + 0x00, 0x03, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x06, 0x00, 0xA3, 0x00, 0x2C, + 0xFF, 0x0E, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, 0x00, 0x06, 0x00, 0x03, + 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x04, + 0x00, 0xE8, 0x00, 0xBD, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x0C, + 0x00, 0x06, 0x00, 0x0B, 0x00, 0x04, 0x00, 0x08, 0x00, 0x03, 0x00, 0x05, 0x00, 0x01, 0xFF, 0xFE, + 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0x0B, 0x01, 0xAB, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x09, + 0x00, 0x04, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xF9, + 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x01, 0x00, 0xAD, + 0xFF, 0xCA, 0x02, 0x57, 0x03, 0x0C, 0x00, 0x40, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xAD, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x07, 0x00, 0x04, 0x00, 0x09, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0C, 0x01, 0x45, 0x00, + 0x06, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x07, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, + 0xF9, 0xFF, 0x97, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, + 0xF5, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0x61, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, + 0xF8, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x08, 0x00, + 0x04, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x69, 0x00, 0x00, 0xFF, 0x97, 0xFF, 0xFA, 0xFF, + 0xF4, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, + 0xF3, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFF, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x07, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x0C, 0x00, + 0x06, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x09, 0x00, 0x04, 0x00, 0x06, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x02, 0xA6, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x04, 0x00, + 0x08, 0x00, 0x05, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, + 0xF8, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, + 0xF6, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFF, 0x00, 0x00, 0xFD, 0x96, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF3, 0x00, + 0x00, 0x01, 0x00, 0x6C, 0xFF, 0xCA, 0x02, 0x98, 0x02, 0x2E, 0x00, 0x82, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x6C, 0x00, 0x02, 0x00, 0x01, 0x00, 0x06, 0x00, 0x04, 0x00, 0x04, 0x00, 0x0A, 0x00, + 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x08, 0x00, + 0x03, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x07, 0x00, 0x06, 0x00, 0x04, 0x00, + 0x06, 0x00, 0x06, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, + 0x07, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0A, 0x00, + 0x0A, 0x00, 0x03, 0x00, 0x06, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, + 0x07, 0x00, 0x05, 0x00, 0x04, 0x00, 0x06, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0F, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x06, 0x00, 0x08, 0x00, 0x05, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x07, 0x00, + 0x0C, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x03, 0x00, 0x06, 0x00, 0x01, 0x00, + 0x01, 0xFF, 0xF0, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, + 0xF9, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, + 0xF5, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, + 0xF6, 0xFF, 0xF7, 0xFF, 0xE3, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, + 0xF5, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, + 0xF5, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xE9, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xF6, 0xFF, + 0xFB, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, + 0xFC, 0xFF, 0xFA, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, + 0xFB, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, + 0x07, 0x00, 0x04, 0x00, 0x09, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0C, 0x01, 0x7F, 0x00, 0x3F, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF8, 0xFE, 0x53, 0xFF, + 0xF4, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, + 0x01, 0x00, 0x03, 0x00, 0x07, 0x00, 0x08, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, 0x01, + 0x81, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, + 0xFD, 0xFF, 0xF9, 0xFE, 0x4E, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xFD, 0xFF, + 0xFC, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x07, 0x00, 0x09, 0x00, + 0x05, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x0D, 0x01, 0xBB, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0B, 0x00, + 0x0A, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x09, 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x06, 0x00, + 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, + 0xFD, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xE6, 0x00, 0x0D, 0x00, 0x08, 0x00, + 0x07, 0x00, 0x06, 0x00, 0x06, 0x00, 0x04, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xEE, 0x00, + 0x0B, 0x00, 0x05, 0x00, 0x08, 0x00, 0x04, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, + 0xFE, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF4, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, 0x02, 0x29, 0x00, 0x54, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x07, 0x00, 0x09, 0x00, + 0x0B, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x05, 0x00, + 0x08, 0x00, 0x03, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0xA4, 0x00, 0x07, 0x00, 0x07, 0x00, + 0x0C, 0x00, 0x40, 0x00, 0x08, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, + 0x04, 0x00, 0x02, 0x00, 0x0D, 0x00, 0x02, 0x00, 0x05, 0x00, 0x03, 0x00, 0x09, 0x00, 0x0A, 0x00, + 0x0C, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x04, 0x00, + 0x07, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xF1, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, + 0xF4, 0xFF, 0xF1, 0xFF, 0xF7, 0xFF, 0xEE, 0xFF, 0xF6, 0xFF, 0xEB, 0xFF, 0xE9, 0xFF, 0xF5, 0xFF, + 0xE9, 0xFF, 0xC1, 0xFF, 0xEB, 0xFF, 0xF4, 0xFF, 0xEA, 0xFF, 0xEC, 0xFF, 0x8F, 0xFF, 0xFF, 0xFF, + 0xFE, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, + 0xFA, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0xFF, + 0xF4, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x07, 0x00, 0x04, 0x00, 0x09, 0x00, 0x06, 0x00, 0x0C, 0x00, + 0x0C, 0x01, 0x4D, 0x00, 0x69, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, + 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF3, 0xFE, 0x75, 0xFF, + 0xF4, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x04, 0x00, 0x03, 0x00, 0x08, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x06, 0x00, + 0x0D, 0x01, 0x98, 0x00, 0x17, 0x00, 0x0B, 0x00, 0x16, 0x00, 0x14, 0x00, 0x12, 0x00, 0x08, 0x00, + 0x0E, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, + 0xFE, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xB9, 0x00, 0x2B, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x09, 0x00, + 0x04, 0x00, 0x07, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, + 0xF5, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF4, 0x00, 0x00, 0x02, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, + 0x02, 0x29, 0x00, 0x2D, 0x00, 0x65, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x82, + 0x00, 0x47, 0x00, 0x07, 0x00, 0x0B, 0x00, 0x07, 0x00, 0x06, 0x00, 0x25, 0x00, 0x06, 0x00, 0x05, + 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, + 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xD3, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0x7D, + 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xD7, 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, 0xFD, + 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x05, + 0x00, 0x2C, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x07, 0x00, 0x08, 0xFF, 0xFE, 0xFF, 0xEB, 0xFF, 0xF5, + 0xFF, 0xEA, 0xFF, 0xEB, 0xFF, 0xED, 0xFF, 0xD0, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xF7, + 0xFF, 0xFB, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x08, 0x00, 0x0C, + 0x00, 0x0D, 0x00, 0x08, 0x00, 0x31, 0x00, 0x12, 0x00, 0x09, 0x00, 0x15, 0x00, 0x0B, 0x00, 0x16, + 0x00, 0x16, 0x00, 0x92, 0x00, 0x16, 0x00, 0x16, 0x00, 0x0B, 0x00, 0x14, 0x00, 0x09, 0x00, 0x31, + 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0B, 0x00, 0x04, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, + 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xD0, + 0xFF, 0xED, 0xFF, 0xEB, 0xFF, 0xEA, 0xFF, 0xF5, 0xFF, 0xEA, 0x01, 0xBD, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xE0, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF9, + 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0x27, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF7, + 0xFF, 0xD9, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x03, 0x00, 0x05, 0x00, 0x24, 0x00, 0x09, 0x00, 0x06, 0x00, 0x07, 0x00, 0x0B, 0x00, 0x08, + 0x00, 0xD9, 0x00, 0x09, 0x00, 0x07, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x06, 0x00, 0x26, 0x00, 0x05, + 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0x6C, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xF7, + 0xFF, 0xF4, 0xFF, 0xD7, 0xFF, 0xF0, 0xFF, 0xEE, 0xFF, 0xEC, 0xFF, 0xEA, 0xFF, 0xE9, 0xFF, 0xF5, + 0xFF, 0x18, 0xFF, 0xEA, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xEA, 0xFF, 0xEC, 0xFF, 0xED, 0xFF, 0xF8, + 0xFF, 0xD6, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x06, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x07, 0x00, 0x29, 0x00, 0x0F, 0x00, 0x13, + 0x00, 0x14, 0x00, 0x0B, 0x00, 0x16, 0x00, 0x0B, 0x00, 0x16, 0x00, 0xDD, 0x00, 0x16, 0x00, 0x0C, + 0x00, 0x16, 0x00, 0x0B, 0x00, 0x14, 0x00, 0x12, 0x00, 0x10, 0x00, 0x29, 0x00, 0x0C, 0x00, 0x09, + 0x00, 0x06, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x78, 0xFE, 0xE7, 0x02, 0x8D, + 0x02, 0x40, 0x00, 0x4D, 0x00, 0x70, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x07, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x07, 0x00, 0x0C, 0x00, + 0x0C, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x04, 0x00, 0x07, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x24, 0x00, 0x11, 0x00, 0x09, 0x00, 0x14, 0x00, 0x16, 0x00, 0x0B, 0x00, 0x16, 0x00, 0x24, 0x00, + 0x16, 0x00, 0x0B, 0x00, 0x16, 0x00, 0x15, 0x00, 0x13, 0x00, 0x66, 0x00, 0x0F, 0x00, 0x07, 0x00, + 0x0D, 0x00, 0x09, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, + 0xF8, 0xFF, 0xFB, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0x9B, 0xFF, 0xEE, 0xFF, 0xEC, 0xFF, 0xF6, 0xFF, + 0xF5, 0xFF, 0xEA, 0xFF, 0xEA, 0xFF, 0xE7, 0xFF, 0xEA, 0xFF, 0xF5, 0xFF, 0xEA, 0xFF, 0xF5, 0xFF, + 0xEC, 0xFF, 0xEE, 0xFF, 0xD4, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, + 0xF5, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, + 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x07, 0x00, 0x06, 0x00, + 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x1F, 0x00, 0x07, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x07, 0x00, + 0x5E, 0x00, 0x05, 0x00, 0x06, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, + 0xFD, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xA6, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, + 0xE6, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0x1D, 0xFF, 0xF3, 0xFF, + 0xFA, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x01, 0x00, 0x03, 0x00, 0x07, 0x00, 0x04, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0D, 0x00, + 0xE9, 0xFF, 0xDB, 0xFF, 0xF2, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, + 0xFF, 0x00, 0x02, 0x00, 0x02, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x57, 0x00, 0x10, 0x00, + 0x09, 0x00, 0x13, 0x00, 0x15, 0x00, 0x16, 0x00, 0x0B, 0x00, 0x16, 0x00, 0xB9, 0x00, 0x0C, 0x00, + 0x16, 0x00, 0x16, 0x00, 0x0A, 0x00, 0x13, 0x00, 0x12, 0x00, 0x57, 0x00, 0x0D, 0x00, 0x0B, 0x00, + 0x05, 0x00, 0x03, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, + 0xFB, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xD4, 0x00, 0x23, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0A, 0x00, + 0x04, 0x00, 0x06, 0x00, 0x04, 0x00, 0x01, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, + 0xF6, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF4, 0xFE, 0x94, 0x00, 0xB7, 0x00, 0x6F, 0x00, 0x05, 0x00, + 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, + 0xFB, 0xFF, 0xAF, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0x51, 0xFF, 0xF4, 0xFF, + 0xF9, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xB2, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x78, 0xFE, 0xE7, 0x02, 0x8D, 0x02, 0x29, 0x00, 0x4E, 0x00, 0x71, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x00, 0x78, 0x00, 0x01, 0x00, 0x02, 0x00, 0x07, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x0D, + 0x00, 0x10, 0x00, 0x31, 0x00, 0x13, 0x00, 0x15, 0x00, 0x0B, 0x00, 0x16, 0x00, 0x16, 0x00, 0x52, + 0x00, 0x16, 0x00, 0x0B, 0x00, 0x16, 0x00, 0x0A, 0x00, 0x14, 0x00, 0x12, 0x00, 0x28, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x04, 0x00, 0x03, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, + 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x04, 0x00, 0x05, 0x00, 0x02, + 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xF6, + 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xFC, + 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xD8, 0xFF, 0xEE, 0xFF, 0xEC, 0xFF, 0xF6, + 0xFF, 0xEA, 0xFF, 0xF5, 0xFF, 0xEA, 0xFF, 0xA3, 0xFF, 0xEA, 0xFF, 0xEA, 0xFF, 0xEB, 0xFF, 0xED, + 0xFF, 0xD0, 0xFF, 0xF0, 0xFF, 0xF8, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFE, 0xFF, 0xFF, + 0x00, 0x6C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x03, 0x00, 0x05, 0x00, 0x06, 0x00, 0x26, + 0x00, 0x06, 0x00, 0x07, 0x00, 0x07, 0x00, 0x0B, 0x00, 0x53, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x0A, + 0x00, 0x07, 0x00, 0x6D, 0x00, 0x00, 0xFF, 0x93, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xF9, + 0xFF, 0xAD, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xDA, 0xFF, 0xFA, 0xFF, 0xF9, + 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x8B, 0xFF, 0xEA, 0xFF, 0xF5, 0xFF, 0xEA, 0xFF, 0xF5, + 0xFF, 0xEC, 0xFF, 0xED, 0xFF, 0xF1, 0xFF, 0xD6, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, 0xFC, + 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x07, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x0D, + 0x00, 0x23, 0xFF, 0x0A, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFD, + 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x05, 0x00, 0x08, 0x00, 0x04, 0x00, 0x05, + 0x00, 0x0B, 0x00, 0x06, 0x00, 0x0D, 0x02, 0xD6, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x05, + 0x00, 0x04, 0x00, 0x08, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFD, + 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xE7, 0x00, 0x23, 0x00, 0x0D, + 0x00, 0x0B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFA, + 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xD7, 0xFF, 0xF0, 0xFF, 0xF7, 0xFF, 0xED, 0xFF, 0xEB, 0xFF, 0xEA, + 0xFF, 0xF4, 0xFF, 0xEA, 0xFF, 0x25, 0x00, 0xD9, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x07, 0x00, 0x06, + 0x00, 0x06, 0x00, 0x20, 0x00, 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xA2, 0xFF, 0x4F, 0xFF, 0xA2, 0xFF, 0xFB, 0xFF, 0xFB, + 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x05, 0x00, 0x05, 0x00, 0x21, + 0x00, 0x06, 0x00, 0x09, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x78, + 0xFF, 0xCA, 0x02, 0x8D, 0x02, 0x29, 0x00, 0x53, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, + 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x06, + 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, 0x00, 0x03, 0x00, 0x04, + 0x00, 0x02, 0x00, 0x00, 0x00, 0xA3, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x08, 0x00, 0x45, + 0x00, 0x09, 0x00, 0x07, 0x00, 0x08, 0x00, 0x06, 0x00, 0x07, 0x00, 0x05, 0x00, 0x06, 0x00, 0x03, + 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x04, 0x00, 0x03, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0C, + 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x07, 0x00, 0x04, 0x00, 0x02, + 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, 0xEE, 0xFF, 0xF6, + 0xFF, 0xEC, 0xFF, 0xF5, 0xFF, 0xE9, 0xFF, 0xF4, 0xFF, 0xE9, 0xFF, 0xB1, 0xFF, 0xEA, 0xFF, 0xEA, + 0xFF, 0xEB, 0xFF, 0xED, 0xFF, 0x9A, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xF8, + 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF8, + 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF5, + 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x07, + 0x00, 0x04, 0x00, 0x09, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0C, 0x01, 0x24, 0x00, 0x8C, 0x00, 0x05, + 0x00, 0x05, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFC, + 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xD9, 0xFF, 0xFA, 0xFF, 0xF4, + 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFE, 0x00, 0x01, 0x00, 0x03, 0x00, 0x07, + 0x00, 0x08, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x29, 0x00, 0x18, 0x00, 0x17, 0x00, 0x0B, + 0x00, 0x15, 0x00, 0x13, 0x00, 0x10, 0x00, 0x07, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x07, 0x00, 0x02, + 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xA9, 0x00, 0x40, + 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x09, 0x00, 0x08, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, + 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF4, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x7F, 0xFF, 0xCA, 0x02, 0x8C, 0x02, 0x29, 0x00, 0x81, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x02, 0x20, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x06, + 0x00, 0x0B, 0x00, 0x05, 0x00, 0x05, 0x00, 0x07, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFD, + 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF0, 0xFF, 0xED, 0xFF, 0xF6, 0xFF, 0xEB, 0xFF, 0xE9, 0xFF, 0xF5, + 0xFF, 0xE9, 0xFF, 0x2A, 0xFF, 0xE9, 0xFF, 0xE9, 0xFF, 0xF5, 0xFF, 0xEA, 0xFF, 0xED, 0xFF, 0xEE, + 0xFF, 0xF1, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xFF, 0x00, 0x03, 0x00, 0x05, + 0x00, 0x02, 0x00, 0x07, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x06, 0x01, 0x5F, 0x00, 0x0A, 0x00, 0x06, + 0x00, 0x05, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xFA, + 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0x29, 0xFF, 0xF8, 0xFF, 0xF5, + 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xFC, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF3, 0xFF, 0xF4, + 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFE, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x05, 0x00, 0x09, 0x00, 0x10, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x14, 0x00, 0x16, + 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x17, 0x00, 0xDB, 0x00, 0x17, 0x00, 0x18, 0x00, 0x16, 0x00, 0x15, + 0x00, 0x0A, 0x00, 0x11, 0x00, 0x10, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x02, + 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF9, 0xFF, 0xF1, 0xFF, 0xED, + 0xFF, 0xF7, 0xFF, 0xEB, 0xFE, 0xA3, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x02, 0x00, 0x05, + 0x00, 0x07, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x09, 0x00, 0xD7, 0x00, 0x0B, 0x00, 0x0B, + 0x00, 0x09, 0x00, 0x06, 0x01, 0xA5, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFF, + 0x00, 0x02, 0x00, 0x02, 0x00, 0x05, 0x00, 0x04, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0D, + 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x11, 0x00, 0x0E, 0x00, 0x06, 0x00, 0x0A, + 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xF6, + 0xFF, 0xF3, 0xFF, 0xF0, 0xFF, 0xED, 0xFF, 0xEC, 0xFF, 0xF5, 0xFF, 0xE9, 0xFF, 0xE9, 0xFF, 0xE8, + 0xFF, 0xE9, 0xFF, 0xF0, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFE, 0xFF, 0x64, + 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF4, + 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x03, 0x00, 0x05, 0x00, 0x08, 0x00, 0x0C, 0x00, 0x04, 0x00, 0x07, 0x00, 0x02, + 0x00, 0x03, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF5, + 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xEF, 0xFF, 0xF8, 0xFF, 0xF9, + 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x02, 0x00, 0x05, + 0x00, 0x09, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x10, 0x00, 0x12, 0x00, 0x0A, 0x00, 0x16, 0x00, 0x0B, + 0x00, 0x17, 0x00, 0x18, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x0C, 0x00, 0x17, 0x00, 0x15, 0x00, 0x0A, + 0x00, 0x12, 0x00, 0x10, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x9A, 0xFF, 0xDC, 0x00, 0x09, 0x00, 0x0C, + 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x08, 0x00, 0x05, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, + 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFB, 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, + 0x02, 0xDE, 0x00, 0x70, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x02, 0x00, 0x05, 0x00, 0x07, 0x00, + 0x0A, 0x00, 0x06, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x02, 0x00, 0x05, 0x00, + 0x09, 0x00, 0x06, 0x00, 0x0D, 0x00, 0x08, 0x00, 0x12, 0x00, 0x14, 0x00, 0x15, 0x00, 0x17, 0x00, + 0x18, 0x00, 0x82, 0x00, 0x18, 0x00, 0x0C, 0x00, 0x16, 0x00, 0x15, 0x00, 0x13, 0x00, 0x10, 0x00, + 0x0D, 0x00, 0x0B, 0x00, 0x07, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, + 0xF8, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, + 0xF5, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, + 0xF9, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0x98, 0xFF, 0xF3, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, + 0xF9, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, + 0xD3, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x06, 0x00, 0x03, 0x00, + 0x01, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, + 0xF3, 0xFF, 0x2D, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, + 0xFA, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, + 0xFC, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xC5, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, + 0xFA, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFE, 0x01, 0xF0, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, + 0xF6, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFD, 0x00, 0x00, 0xFE, 0xAD, 0xFF, 0xE9, 0xFF, + 0xE8, 0xFF, 0xEA, 0xFF, 0xF5, 0xFF, 0xEC, 0xFF, 0xF7, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xF6, 0xFF, + 0xF9, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x04, 0x00, 0x03, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x0F, 0x00, + 0x12, 0x00, 0x14, 0x00, 0x15, 0x00, 0x17, 0x00, 0x11, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, + 0x0B, 0x00, 0x09, 0x00, 0x04, 0x00, 0x06, 0x00, 0x03, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, + 0xFE, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xED, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, + 0xF8, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, + 0x03, 0x00, 0x05, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x01, + 0x51, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x06, 0x00, 0x08, 0x00, 0x05, 0x00, 0x0A, 0x00, + 0x0D, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x04, 0x00, 0x06, 0x00, + 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x05, 0x00, + 0x09, 0x00, 0x03, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, + 0xF7, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0x81, 0x00, 0x00, 0xFF, 0xFD, 0xFF, + 0xFD, 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF4, 0x00, 0x00, 0x01, 0x00, 0x78, + 0xFF, 0xCA, 0x02, 0x8D, 0x02, 0x29, 0x00, 0x53, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, + 0x00, 0x0E, 0x00, 0x05, 0x00, 0x07, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x08, 0x00, 0x10, 0x00, 0x12, + 0x00, 0x15, 0x00, 0x16, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x16, 0x00, 0x3F, 0x00, 0x16, 0x00, 0x16, + 0x00, 0x0B, 0x00, 0x15, 0x00, 0x70, 0x00, 0x01, 0x00, 0x03, 0x00, 0x05, 0x00, 0x08, 0x00, 0x0A, + 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x08, + 0x00, 0x05, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xF8, + 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF8, + 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0x60, 0xFF, 0xF5, 0xFF, 0xF9, 0xFF, 0xF9, + 0xFF, 0xC0, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, + 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xF2, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xFA, + 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xFC, + 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFE, 0x01, 0xF1, 0xFE, 0x68, 0xFF, 0xE9, 0xFF, 0xEA, 0xFF, 0xF5, + 0xFF, 0xEC, 0xFF, 0xF7, 0xFF, 0xEF, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFE, + 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x48, 0xFF, 0xD5, + 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, + 0x00, 0x03, 0x00, 0x03, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, 0x01, 0xF3, + 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x08, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, + 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF4, 0xFE, 0xB3, + 0xFF, 0x99, 0xFF, 0xFA, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x05, + 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x0D, 0x01, 0x8B, 0x00, 0x0C, + 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x04, 0x00, 0x04, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, + 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF3, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, 0x02, 0x29, 0x00, 0x35, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x00, 0xE4, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0xFB, + 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xFC, 0xFF, 0xF8, + 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0xB3, 0x00, 0x07, + 0x00, 0x08, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x49, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x0B, + 0x00, 0x09, 0x00, 0x06, 0x00, 0xB3, 0x00, 0x03, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, + 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, + 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x60, 0x00, 0x04, 0xFF, 0x60, + 0x01, 0xF3, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x09, 0x00, 0x04, 0x00, 0x07, 0x00, 0x02, + 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF4, + 0xFF, 0xFA, 0xFF, 0xA0, 0xFF, 0xF4, 0xFF, 0xF6, 0xFE, 0x67, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xFA, + 0xFF, 0xFB, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x0A, + 0x01, 0x99, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x60, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x08, + 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF5, + 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xAB, 0xFE, 0x92, 0x00, 0x00, 0x01, 0x6E, 0x00, 0x01, 0x00, 0x6D, + 0xFF, 0xCA, 0x02, 0x97, 0x02, 0x29, 0x00, 0x5A, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x6D, 0x00, 0x0B, 0x00, 0x01, 0x00, 0x38, 0x00, 0x07, 0x00, + 0x04, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x3C, 0x00, 0x0C, 0x00, 0x0B, 0x00, + 0x05, 0x00, 0x09, 0x00, 0x03, 0x00, 0x3B, 0x00, 0x40, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x05, 0x00, + 0x0C, 0x00, 0x42, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x06, 0x00, 0x02, 0x00, + 0x35, 0x00, 0x02, 0x00, 0x0B, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, + 0xF6, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, + 0xFB, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xF6, 0xFF, 0xDE, 0xFF, 0xB9, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, + 0xFA, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFF, 0xFF, + 0xFE, 0x00, 0x00, 0xFF, 0xB9, 0xFF, 0xDF, 0xFF, 0xF5, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFA, 0xFF, + 0xFC, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, + 0xFA, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFF, 0x01, 0xF1, 0xFE, 0xC3, 0xFF, + 0xF7, 0xFF, 0x40, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFD, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x06, 0x00, 0x03, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x65, 0xFF, 0x95, 0xFF, + 0xF7, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x07, 0x00, + 0x09, 0x00, 0x0B, 0x00, 0x06, 0x00, 0xB5, 0x00, 0x09, 0x01, 0x42, 0x00, 0x06, 0x00, 0x0D, 0x00, + 0x0B, 0x00, 0x05, 0x00, 0x05, 0x00, 0x08, 0x00, 0x03, 0x00, 0x04, 0x00, 0x01, 0xFF, 0xFF, 0xFF, + 0xFD, 0xFF, 0xFE, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, 0xFE, + 0xC9, 0xFF, 0x8E, 0x00, 0x78, 0x00, 0x79, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x0A, 0x00, + 0x07, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, + 0xF6, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0x87, 0xFF, 0x87, 0x00, 0x73, 0x01, 0x37, 0x00, + 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x03, 0x00, + 0x01, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, + 0xF9, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x83, 0xFF, 0xCA, 0x02, 0x81, 0x02, 0x29, 0x00, 0x48, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x83, 0x00, 0x01, 0x00, + 0x02, 0x00, 0x06, 0x00, 0x03, 0x00, 0xAE, 0xFF, 0x4F, 0xFF, 0xFA, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x07, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x06, 0x00, + 0x0C, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x08, 0x00, 0x9F, 0x00, 0x9F, 0x00, + 0x04, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0A, 0x00, + 0x07, 0x00, 0x02, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xF9, 0xFF, 0x53, 0x00, + 0xB1, 0x00, 0x03, 0x00, 0x04, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, + 0xF8, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, + 0xF5, 0xFF, 0xF7, 0xFF, 0x61, 0xFF, 0x61, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, + 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFD, 0x01, 0xF4, 0xFF, + 0xFA, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0x28, 0xFF, 0x24, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, + 0xFA, 0xFF, 0xF4, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, + 0xFE, 0xFF, 0xFE, 0x00, 0x01, 0x00, 0x02, 0x00, 0x05, 0x00, 0x03, 0x00, 0x09, 0x00, 0xC5, 0xFF, + 0x3B, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFD, 0x00, 0x01, 0x00, 0x03, 0x00, 0x06, 0x00, + 0x08, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, + 0xD7, 0x00, 0xDD, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x07, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, 0x00, + 0x06, 0x00, 0x09, 0x00, 0x07, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, + 0xFE, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0x3B, 0x00, 0xC5, 0x00, 0x04, 0x00, 0x08, 0x00, 0x05, 0x00, + 0x03, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF4, 0x00, + 0x00, 0x01, 0x00, 0x78, 0xFE, 0xE7, 0x02, 0x8D, 0x02, 0x29, 0x00, 0x44, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0x01, 0x00, 0x03, 0x00, 0xB3, 0x00, 0x07, 0x00, 0x08, 0x00, + 0x0B, 0x00, 0x09, 0xFF, 0xCA, 0xFF, 0x8B, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xFC, 0xFF, + 0xFA, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x07, 0x00, 0x0A, 0x00, + 0x0B, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x98, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x04, 0x00, + 0x06, 0x01, 0x17, 0x00, 0x03, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xF9, 0xFF, + 0xF7, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, + 0xF9, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x62, 0xFF, 0x62, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, + 0xFA, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, + 0xFA, 0xFF, 0xF5, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFF, 0x01, 0x99, 0xFF, + 0xF4, 0xFF, 0xF6, 0xFE, 0x67, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0x87, 0x00, + 0x00, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, + 0xF4, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFD, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x06, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0B, 0x02, 0x7C, 0x00, 0x0A, 0x00, + 0x0C, 0x00, 0x60, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x07, 0x00, 0x03, 0x00, + 0x01, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, + 0xAB, 0xFE, 0x97, 0x01, 0x69, 0x00, 0x55, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x04, 0x00, + 0x07, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, + 0xF7, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF4, 0x00, 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, + 0x02, 0x29, 0x00, 0x44, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xC8, 0xFF, 0xF4, 0xFF, + 0xFA, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, 0x01, + 0x8F, 0xFF, 0xC9, 0x00, 0x00, 0x00, 0x11, 0xFE, 0x53, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFD, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x06, 0x00, 0x05, 0x00, 0x09, 0x00, 0x05, 0x00, + 0x0C, 0x00, 0x0C, 0x01, 0xA9, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x06, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, + 0xF4, 0xFE, 0x57, 0x00, 0x36, 0x00, 0x00, 0xFF, 0xEF, 0x01, 0xAD, 0x00, 0x07, 0x00, 0x04, 0x00, + 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, + 0xFA, 0xFF, 0xF5, 0xFF, 0xFA, 0x02, 0x29, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, + 0xF6, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, + 0xFB, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x36, 0xFF, 0xD3, 0x00, + 0x27, 0xFE, 0x63, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xCC, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, + 0xFB, 0xFF, 0xF5, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x05, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x0D, 0x00, 0x0C, 0x00, + 0x0C, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x07, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xCA, 0x00, + 0x2E, 0xFF, 0xD9, 0x01, 0x9D, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x33, 0x00, 0x06, 0x00, + 0x0C, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x07, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x01, 0x4C, 0xFF, 0x1D, 0x01, 0xB8, 0x03, 0x84, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xB8, 0xFF, 0x94, 0x00, 0x00, 0x00, 0x6C, + 0x00, 0x00, 0xFF, 0x94, 0x00, 0x00, 0x00, 0x6C, 0x01, 0x3D, 0x00, 0x00, 0xFD, 0xE0, 0x00, 0x00, + 0x04, 0x67, 0x00, 0x00, 0xFD, 0xE0, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x35, 0x03, 0x04, + 0x01, 0xA1, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0x04, + 0x00, 0x00, 0x01, 0xA1, 0xFF, 0x94, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x78, + 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x2B, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xE4, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xF9, + 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF5, + 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0x40, + 0x00, 0x00, 0x00, 0x68, 0x00, 0x01, 0x00, 0x01, 0x00, 0x04, 0x00, 0x03, 0x00, 0x08, 0x00, 0x05, + 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x08, + 0x00, 0x05, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFE, 0xBF, 0x00, 0x00, 0xFF, 0x98, 0x01, 0x10, + 0x00, 0x0D, 0x00, 0x06, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x07, 0x00, 0x05, 0x00, 0x01, + 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xFB, + 0xFF, 0xFA, 0xFF, 0xF3, 0xFE, 0xBA, 0x00, 0x00, 0x02, 0xD6, 0x00, 0x00, 0xFF, 0x1F, 0xFF, 0xFA, + 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x02, 0x00, 0x06, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x0D, 0x01, 0x46, + 0x00, 0x00, 0xFD, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, + 0x03, 0x0C, 0x00, 0x38, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, + 0x78, 0x00, 0x00, 0x00, 0x03, 0x00, 0x06, 0x00, 0x03, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x0B, 0x00, + 0x06, 0x00, 0x07, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x09, 0x00, 0x04, 0x00, + 0x06, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x01, 0x3C, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0D, 0x00, + 0x0C, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x04, 0x00, 0x05, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x00, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, + 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0xFE, + 0x58, 0x00, 0x00, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, + 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x05, 0x00, 0x04, 0x00, 0x08, 0x00, + 0x05, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x06, 0x01, 0x35, 0x00, 0x00, 0xFE, 0xCB, 0xFF, 0xF4, 0xFF, + 0xFA, 0xFF, 0xF4, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, + 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x08, 0x00, 0x04, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x06, 0x00, + 0x0C, 0x02, 0xD6, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x07, 0x00, 0x02, 0x00, + 0x02, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF4, 0xFE, + 0xCB, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, 0x03, 0x0C, 0x00, 0x36, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x00, 0xD4, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x08, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x06, 0x00, + 0x0D, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, + 0x00, 0x00, 0xD5, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, + 0xF4, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, + 0xFB, 0xFF, 0xFD, 0xFF, 0xFF, 0xFE, 0xC4, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, + 0xF7, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xFC, 0xFF, + 0xF8, 0xFF, 0xFA, 0xFF, 0xFD, 0x00, 0x00, 0x01, 0x35, 0x00, 0x00, 0xFE, 0xC5, 0xFF, 0xF4, 0xFF, + 0xFA, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x05, 0x00, 0x08, 0x00, 0x09, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0C, 0x01, 0x3B, 0x00, + 0x00, 0x01, 0xA7, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x04, 0x00, 0x05, 0x00, + 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xF4, 0xFF, + 0xF4, 0xFE, 0xC5, 0x00, 0x00, 0x01, 0x35, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x08, 0x00, + 0x07, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, + 0xF4, 0xFF, 0xF4, 0xFE, 0xC5, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x78, 0xFF, 0xCA, 0x02, 0x8D, + 0x03, 0x0C, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x78, 0x02, 0x15, 0x00, 0x00, + 0xFD, 0xEB, 0xFF, 0xCA, 0x00, 0x00, 0x03, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x20, 0x4C, 0xBE, 0xD3, 0x5F, 0x0F, 0x3C, 0xF5, 0x00, 0x03, 0x03, 0xE8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFE, 0xE7, 0x03, 0x04, 0x03, 0x84, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0x3A, 0xFE, 0xE7, 0x00, 0x00, 0x03, 0xE8, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x03, 0xE8, 0x00, 0x00, 0x03, 0x04, 0x00, 0x00, + 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, + 0x03, 0x04, 0x01, 0x17, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, + 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0xE2, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x01, 0x17, + 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, + 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0xAD, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, + 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x01, 0x17, + 0x03, 0x04, 0x00, 0xE2, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, + 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, + 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, + 0x03, 0x04, 0x00, 0xAD, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, + 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, + 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x77, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, + 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, + 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, + 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0xAD, 0x03, 0x04, 0x00, 0x78, + 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0xAD, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, + 0x03, 0x04, 0x00, 0xAD, 0x03, 0x04, 0x00, 0x6C, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, + 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x7F, + 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x6D, + 0x03, 0x04, 0x00, 0x83, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x01, 0x4C, + 0x03, 0x04, 0x00, 0x00, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, 0x03, 0x04, 0x00, 0x78, + 0x03, 0x04, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xCC, 0x00, 0x00, 0x02, 0xF8, 0x00, 0x00, 0x05, 0x28, 0x00, 0x00, 0x07, 0xA4, + 0x00, 0x00, 0x08, 0x90, 0x00, 0x00, 0x0A, 0xBC, 0x00, 0x00, 0x0C, 0xE8, 0x00, 0x00, 0x0F, 0x04, + 0x00, 0x00, 0x10, 0x90, 0x00, 0x00, 0x11, 0x80, 0x00, 0x00, 0x12, 0x40, 0x00, 0x00, 0x13, 0x08, + 0x00, 0x00, 0x13, 0xC0, 0x00, 0x00, 0x15, 0x04, 0x00, 0x00, 0x16, 0x54, 0x00, 0x00, 0x18, 0x10, + 0x00, 0x00, 0x1A, 0x4C, 0x00, 0x00, 0x1B, 0xC8, 0x00, 0x00, 0x1D, 0x6C, 0x00, 0x00, 0x1E, 0xA4, + 0x00, 0x00, 0x1F, 0xCC, 0x00, 0x00, 0x21, 0x64, 0x00, 0x00, 0x22, 0x98, 0x00, 0x00, 0x24, 0x1C, + 0x00, 0x00, 0x25, 0xBC, 0x00, 0x00, 0x27, 0x40, 0x00, 0x00, 0x29, 0x3C, 0x00, 0x00, 0x2A, 0x50, + 0x00, 0x00, 0x2C, 0x58, 0x00, 0x00, 0x2E, 0x20, 0x00, 0x00, 0x2F, 0xE0, 0x00, 0x00, 0x31, 0x08, + 0x00, 0x00, 0x32, 0x30, 0x00, 0x00, 0x34, 0x50, 0x00, 0x00, 0x35, 0xE4, 0x00, 0x00, 0x37, 0x64, + 0x00, 0x00, 0x39, 0x04, 0x00, 0x00, 0x3A, 0x7C, 0x00, 0x00, 0x3B, 0x40, 0x00, 0x00, 0x3C, 0x6C, + 0x00, 0x00, 0x3D, 0x60, 0x00, 0x00, 0x3F, 0x24, 0x00, 0x00, 0x40, 0x7C, 0x00, 0x00, 0x43, 0x04, + 0x00, 0x00, 0x44, 0xB0, 0x00, 0x00, 0x47, 0x30, 0x00, 0x00, 0x48, 0x4C, 0x00, 0x00, 0x49, 0xE0, + 0x00, 0x00, 0x4B, 0x08, 0x00, 0x00, 0x4C, 0xEC, 0x00, 0x00, 0x4E, 0x50, 0x00, 0x00, 0x4F, 0xA4, + 0x00, 0x00, 0x50, 0x94, 0x00, 0x00, 0x52, 0xDC, 0x00, 0x00, 0x55, 0x38, 0x00, 0x00, 0x56, 0xF0, + 0x00, 0x00, 0x59, 0x50, 0x00, 0x00, 0x5B, 0x5C, 0x00, 0x00, 0x5D, 0x48, 0x00, 0x00, 0x60, 0x14, + 0x00, 0x00, 0x61, 0xD8, 0x00, 0x00, 0x63, 0xD8, 0x00, 0x00, 0x66, 0x4C, 0x00, 0x00, 0x67, 0xC4, + 0x00, 0x00, 0x69, 0x18, 0x00, 0x00, 0x6B, 0xB8, 0x00, 0x00, 0x6D, 0x70, 0x00, 0x00, 0x6F, 0x80, + 0x00, 0x00, 0x71, 0xC8, 0x00, 0x00, 0x74, 0x14, 0x00, 0x00, 0x75, 0xC8, 0x00, 0x00, 0x78, 0x60, + 0x00, 0x00, 0x7A, 0xA4, 0x00, 0x00, 0x7C, 0x58, 0x00, 0x00, 0x7D, 0x74, 0x00, 0x00, 0x7F, 0x4C, + 0x00, 0x00, 0x80, 0xC8, 0x00, 0x00, 0x82, 0x30, 0x00, 0x00, 0x83, 0x98, 0x00, 0x00, 0x83, 0xD0, + 0x00, 0x00, 0x83, 0xF4, 0x00, 0x00, 0x84, 0xE0, 0x00, 0x00, 0x86, 0x0C, 0x00, 0x00, 0x87, 0x30, + 0x00, 0x00, 0x87, 0x54, 0x00, 0x01, 0x00, 0x00, 0x00, 0x57, 0x00, 0x8C, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x5A, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, + 0x00, 0x00, 0x00, 0x8C, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x01, 0x00, 0x1C, + 0x00, 0x8C, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x02, 0x00, 0x0E, 0x00, 0xA8, 0x00, 0x03, + 0x00, 0x01, 0x04, 0x09, 0x00, 0x03, 0x00, 0x1C, 0x00, 0xB6, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, + 0x00, 0x04, 0x00, 0x1C, 0x00, 0xD2, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x05, 0x00, 0x1A, + 0x00, 0xEE, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x06, 0x00, 0x1A, 0x01, 0x08, 0x00, 0x43, + 0x00, 0x6F, 0x00, 0x70, 0x00, 0x79, 0x00, 0x72, 0x00, 0x69, 0x00, 0x67, 0x00, 0x68, 0x00, 0x74, + 0x00, 0x20, 0x00, 0x28, 0x00, 0x63, 0x00, 0x29, 0x00, 0x20, 0x00, 0x53, 0x00, 0x69, 0x00, 0x78, + 0x00, 0x20, 0x00, 0x4C, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x73, 0x00, 0x2E, + 0x00, 0x20, 0x00, 0x4C, 0x00, 0x69, 0x00, 0x63, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x65, + 0x00, 0x64, 0x00, 0x20, 0x00, 0x75, 0x00, 0x6E, 0x00, 0x64, 0x00, 0x65, 0x00, 0x72, 0x00, 0x20, + 0x00, 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x53, 0x00, 0x69, 0x00, 0x78, 0x00, 0x20, + 0x00, 0x4C, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x73, 0x00, 0x20, 0x00, 0x53, + 0x00, 0x70, 0x00, 0x6C, 0x00, 0x69, 0x00, 0x74, 0x00, 0x20, 0x00, 0x4C, 0x00, 0x69, 0x00, 0x63, + 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x65, 0x00, 0x2E, 0x00, 0x53, 0x00, 0x69, 0x00, 0x78, + 0x00, 0x4C, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x73, 0x00, 0x20, 0x00, 0x4F, + 0x00, 0x43, 0x00, 0x52, 0x00, 0x41, 0x00, 0x52, 0x00, 0x65, 0x00, 0x67, 0x00, 0x75, 0x00, 0x6C, + 0x00, 0x61, 0x00, 0x72, 0x00, 0x53, 0x00, 0x69, 0x00, 0x78, 0x00, 0x4C, 0x00, 0x61, 0x00, 0x62, + 0x00, 0x6F, 0x00, 0x72, 0x00, 0x73, 0x00, 0x20, 0x00, 0x4F, 0x00, 0x43, 0x00, 0x52, 0x00, 0x41, + 0x00, 0x53, 0x00, 0x69, 0x00, 0x78, 0x00, 0x4C, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6F, 0x00, 0x72, + 0x00, 0x73, 0x00, 0x20, 0x00, 0x4F, 0x00, 0x43, 0x00, 0x52, 0x00, 0x41, 0x00, 0x56, 0x00, 0x65, + 0x00, 0x72, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E, 0x00, 0x20, 0x00, 0x31, 0x00, 0x2E, + 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x53, 0x00, 0x69, 0x00, 0x78, 0x00, 0x4C, 0x00, 0x61, + 0x00, 0x62, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x73, 0x00, 0x4F, 0x00, 0x43, 0x00, 0x52, 0x00, 0x41, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; +} diff --git a/src/ImageSharp.Drawing/Barcodes/Fonts/OcrBFontData.cs b/src/ImageSharp.Drawing/Barcodes/Fonts/OcrBFontData.cs new file mode 100644 index 00000000..8d0f7434 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Fonts/OcrBFontData.cs @@ -0,0 +1,2769 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// +// Generated by ImageSharp.Drawing.FontGenerator. Do not edit. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The SixLabors OCRB TrueType font file. Built clean-room from the character sheet and dimension tables of ECMA-11. +/// +internal static class OcrBFontData +{ + /// + /// Gets the font file bytes. + /// + public static ReadOnlySpan Bytes => + [ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x80, 0x00, 0x03, 0x00, 0x20, 0x4F, 0x53, 0x2F, 0x32, + 0x83, 0x13, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0xAC, 0x00, 0x00, 0x00, 0x60, 0x63, 0x6D, 0x61, 0x70, + 0x2B, 0xA9, 0x4F, 0x2B, 0x00, 0x00, 0x01, 0x0C, 0x00, 0x00, 0x00, 0xAC, 0x67, 0x6C, 0x79, 0x66, + 0x3D, 0x3A, 0x12, 0x20, 0x00, 0x00, 0x01, 0xB8, 0x00, 0x00, 0xA4, 0x40, 0x68, 0x65, 0x61, 0x64, + 0x61, 0xD3, 0x42, 0xF0, 0x00, 0x00, 0xA5, 0xF8, 0x00, 0x00, 0x00, 0x36, 0x68, 0x68, 0x65, 0x61, + 0x07, 0x37, 0x02, 0xF1, 0x00, 0x00, 0xA6, 0x30, 0x00, 0x00, 0x00, 0x24, 0x68, 0x6D, 0x74, 0x78, + 0x46, 0x91, 0x44, 0x25, 0x00, 0x00, 0xA6, 0x54, 0x00, 0x00, 0x01, 0xDC, 0x6C, 0x6F, 0x63, 0x61, + 0x00, 0x24, 0x17, 0x3C, 0x00, 0x00, 0xA8, 0x30, 0x00, 0x00, 0x01, 0xE0, 0x6D, 0x61, 0x78, 0x70, + 0x00, 0x7D, 0x00, 0xFB, 0x00, 0x00, 0xAA, 0x10, 0x00, 0x00, 0x00, 0x20, 0x6E, 0x61, 0x6D, 0x65, + 0x1E, 0xCD, 0x36, 0x7F, 0x00, 0x00, 0xAA, 0x30, 0x00, 0x00, 0x01, 0x7C, 0x70, 0x6F, 0x73, 0x74, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xAB, 0xAC, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x03, 0xE8, + 0x01, 0x90, 0x00, 0x05, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x69, 0x78, 0x4C, 0x00, 0x40, 0x00, 0x20, 0x25, 0x88, + 0x04, 0x79, 0xFE, 0x91, 0x00, 0x00, 0x03, 0x79, 0x01, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0xA6, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x04, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x24, + 0x00, 0x20, 0x00, 0x04, 0x00, 0x04, 0x00, 0x7E, 0x00, 0xA5, 0x00, 0xA8, 0x00, 0xB4, 0x00, 0xB8, + 0x00, 0xC6, 0x00, 0xD1, 0x00, 0xD6, 0x00, 0xD8, 0x00, 0xDC, 0x00, 0xDF, 0x00, 0xE6, 0x00, 0xF8, + 0x01, 0x33, 0x20, 0x15, 0x22, 0x23, 0x25, 0x88, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x20, 0x00, 0xA3, + 0x00, 0xA7, 0x00, 0xB4, 0x00, 0xB8, 0x00, 0xC4, 0x00, 0xD1, 0x00, 0xD6, 0x00, 0xD8, 0x00, 0xDC, + 0x00, 0xDF, 0x00, 0xE5, 0x00, 0xF8, 0x01, 0x32, 0x20, 0x15, 0x22, 0x23, 0x25, 0x88, 0xFF, 0xFF, + 0xFF, 0xE1, 0xFF, 0xBD, 0xFF, 0xBC, 0xFF, 0xB1, 0xFF, 0xAE, 0xFF, 0xA3, 0xFF, 0x99, 0xFF, 0x95, + 0xFF, 0x94, 0xFF, 0x91, 0xFF, 0x8F, 0xFF, 0x8A, 0xFF, 0x79, 0xFF, 0x40, 0xE0, 0x5F, 0xDE, 0x52, + 0xDA, 0xEE, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x27, 0x00, 0x04, 0x01, 0x9D, + 0x02, 0xA6, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x2F, 0x00, 0x0D, 0x00, 0x51, 0x00, 0x10, 0xFF, 0x8A, 0x00, 0x00, 0x00, 0x6E, 0x00, 0x00, + 0x02, 0xA6, 0xFE, 0x57, 0x00, 0x00, 0x01, 0xA9, 0xFD, 0xC6, 0xFF, 0x98, 0x00, 0x00, 0x00, 0x68, + 0x00, 0x02, 0x00, 0xAF, 0x01, 0xA1, 0x02, 0x0D, 0x02, 0xA6, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xFC, 0x00, 0x20, 0xFF, 0x93, 0x00, 0x00, + 0x01, 0x40, 0x00, 0x1E, 0xFF, 0x93, 0x00, 0x00, 0x01, 0xA1, 0x01, 0x05, 0x00, 0x00, 0xFE, 0xFB, + 0x00, 0x00, 0x01, 0x05, 0x00, 0x00, 0xFE, 0xFB, 0x00, 0x02, 0x00, 0x96, 0x00, 0x00, 0x02, 0x26, + 0x02, 0xA6, 0x00, 0x1B, 0x00, 0x1F, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x3D, 0x00, 0x29, 0xFF, 0xBC, 0xFF, 0xD8, + 0xFF, 0xBD, 0xFF, 0xEF, 0x00, 0x48, 0xFF, 0xDE, 0xFF, 0xDA, 0xFF, 0xF0, 0x00, 0x28, 0xFF, 0xD8, + 0x00, 0x46, 0x00, 0x29, 0x00, 0x79, 0xFF, 0xDA, 0x00, 0x47, 0x00, 0x25, 0x00, 0x41, 0x00, 0x10, + 0xFF, 0xBB, 0x00, 0x22, 0x00, 0x23, 0x00, 0x11, 0xFF, 0xD9, 0x00, 0x27, 0xFF, 0xBA, 0xFF, 0xD8, + 0xFF, 0x58, 0x00, 0x21, 0x00, 0x7A, 0xFF, 0xDE, 0x01, 0xE2, 0x00, 0xC4, 0x00, 0x00, 0xFF, 0x3C, + 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0x5C, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x00, 0xFF, 0x44, + 0x00, 0x00, 0x00, 0xBC, 0x00, 0x00, 0xFF, 0x44, 0x00, 0x00, 0x00, 0xBC, 0x00, 0x00, 0x00, 0x41, + 0x00, 0x00, 0x00, 0xA4, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0xC4, 0x00, 0x00, 0xFF, 0x3C, + 0xFF, 0x1C, 0x00, 0x9F, 0x00, 0x00, 0xFF, 0x61, 0x00, 0x03, 0x00, 0x86, 0x00, 0x00, 0x02, 0x36, + 0x02, 0xA6, 0x00, 0x16, 0x00, 0x2E, 0x00, 0xB4, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x39, 0xFF, 0xE1, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, + 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x02, 0x00, 0x05, 0x00, + 0x04, 0x00, 0x06, 0x00, 0x06, 0x00, 0x08, 0x00, 0x10, 0x00, 0x11, 0x00, 0x16, 0x00, 0x4A, 0x00, + 0x1A, 0x00, 0x13, 0x00, 0x0E, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x02, 0x00, + 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, + 0xFB, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF0, 0xFF, 0xED, 0xFF, 0xEB, 0xFF, 0xFF, 0xFF, + 0xB9, 0x00, 0x00, 0xFF, 0xEC, 0xFF, 0xEC, 0xFF, 0xEC, 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, + 0xF2, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, + 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x05, 0x00, 0x07, 0x00, 0x08, 0x00, + 0x08, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x10, 0x00, 0x13, 0x00, + 0x15, 0x00, 0x1B, 0x00, 0x00, 0xFF, 0xE7, 0xFF, 0xEF, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, + 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, + 0xA5, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x07, 0x00, 0x04, 0x00, 0x07, 0x00, 0x08, 0x00, + 0x08, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0B, 0x00, + 0x0E, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x17, 0x00, + 0x13, 0x00, 0x16, 0x00, 0x13, 0x00, 0x12, 0x00, 0x11, 0x00, 0x0F, 0x00, 0x0C, 0x00, 0x09, 0x00, + 0x06, 0x00, 0x07, 0x00, 0x04, 0x00, 0x05, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFE, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, + 0xF3, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x16, 0x00, + 0x0F, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x04, 0x00, 0x04, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x5C, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, + 0xFC, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, + 0xEA, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xE6, 0xFF, 0xE8, 0x01, 0x97, 0x00, 0x0B, 0x00, 0x06, 0x00, + 0x08, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x05, 0x00, 0x08, 0x00, 0x06, 0x00, 0x0E, 0x00, + 0x0D, 0x00, 0x06, 0x00, 0x09, 0x00, 0x05, 0x00, 0x06, 0x00, 0x04, 0x00, 0x05, 0x00, 0x04, 0x00, + 0x03, 0x00, 0x05, 0x00, 0x03, 0x00, 0x01, 0xFE, 0xF8, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, + 0xF7, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xF3, 0xFF, + 0xF9, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xFC, 0xFF, + 0xFC, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFE, 0x02, 0x23, 0x00, 0x00, 0xFF, 0xD2, 0xFF, 0xFE, 0xFF, + 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, + 0xF6, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, 0xF2, 0xFF, + 0xF1, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, + 0xF6, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0x4B, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x07, 0x00, 0x04, 0x00, 0x04, 0x00, 0x06, 0x00, + 0x05, 0x00, 0x08, 0x00, 0x06, 0x00, 0x09, 0x00, 0x07, 0x00, 0x00, 0xFF, 0xE8, 0xFF, 0xF1, 0xFF, + 0xF1, 0xFF, 0xF0, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, + 0xFA, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xD1, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x04, 0x00, 0x04, 0x00, 0x07, 0x00, 0x08, 0x00, + 0x0A, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0D, 0x00, 0x0A, 0x00, + 0x0E, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0E, 0x00, + 0x0C, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x09, 0x00, + 0x08, 0x00, 0x08, 0x00, 0x12, 0x00, 0xA1, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, + 0xFE, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF2, 0x00, + 0x00, 0x00, 0x13, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x07, 0x00, + 0x09, 0x00, 0x06, 0x00, 0x08, 0x00, 0x07, 0x00, 0x07, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x05, 0x00, + 0x07, 0x00, 0x03, 0x00, 0x00, 0x05, 0x00, 0x91, 0xFF, 0xF8, 0x02, 0x2F, 0x02, 0xA6, 0x00, 0x40, + 0x00, 0x62, 0x00, 0xA3, 0x00, 0xC4, 0x00, 0xCA, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xF5, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, + 0xF9, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, + 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, + 0x05, 0x00, 0x05, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, 0x07, 0x00, 0x09, 0x00, 0x08, 0x00, + 0x0A, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x0A, 0x00, + 0x08, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x08, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x04, 0x00, + 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, + 0xFD, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, + 0xF6, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xFC, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x06, 0x00, + 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, + 0xFD, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, + 0xFA, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x08, 0x00, 0x09, 0x00, 0xDC, 0xFF, 0xF7, 0xFF, + 0xF5, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, + 0xFA, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x05, 0x00, 0x05, 0x00, 0x06, 0x00, 0x06, 0x00, 0x08, 0x00, + 0x06, 0x00, 0x09, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x09, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x08, 0x00, 0x06, 0x00, + 0x06, 0x00, 0x06, 0x00, 0x04, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, + 0xF8, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xFA, 0x00, + 0x0A, 0x00, 0x09, 0x00, 0x06, 0x00, 0x04, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, + 0xF8, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x06, 0x00, 0x07, 0x00, 0x0A, 0x00, + 0x3C, 0xFE, 0x96, 0x00, 0x00, 0x00, 0x32, 0x01, 0x65, 0x00, 0x00, 0x02, 0x9A, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, + 0xF7, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF3, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, + 0xF4, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, + 0xFB, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x01, 0x00, 0x04, 0x00, 0x04, 0x00, 0x05, 0x00, 0x05, 0x00, 0x08, 0x00, 0x06, 0x00, + 0x09, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x0D, 0x00, + 0x0A, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x08, 0x00, + 0x06, 0x00, 0x06, 0x00, 0x04, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xCB, 0x00, + 0x00, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xEE, 0xFF, + 0xF2, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, + 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x05, 0x00, 0x07, 0x00, 0x07, 0x00, 0x08, 0x00, 0x0B, 0x00, + 0x18, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x07, 0x00, 0x04, 0x00, 0x03, 0x00, 0x05, 0x00, 0x03, 0x00, + 0x01, 0xFE, 0x73, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, + 0xFA, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, + 0xF3, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, + 0xF7, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x05, 0x00, + 0x06, 0x00, 0x07, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x09, 0x00, + 0x0C, 0x00, 0x09, 0x00, 0x0D, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x08, 0x00, 0x0B, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x03, 0x00, 0x04, 0x00, 0x03, 0x00, + 0x01, 0x00, 0x01, 0xFF, 0xC9, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, + 0xF6, 0xFF, 0xF5, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, + 0xFC, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x05, 0x00, 0x06, 0x00, + 0x07, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x17, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x06, 0x00, + 0x04, 0x00, 0x03, 0x00, 0x02, 0x02, 0x05, 0xFD, 0x8C, 0xFF, 0xCE, 0x00, 0x00, 0x02, 0x70, 0x00, + 0x36, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x85, 0xFF, 0xF8, 0x02, 0x30, 0x02, 0xA6, 0x00, 0x28, + 0x00, 0x49, 0x00, 0xB4, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1C, 0xFF, 0xEC, 0xFF, + 0xF3, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, 0x00, + 0x0A, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x08, 0x00, + 0x05, 0x00, 0x06, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, + 0xFE, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xC6, 0x00, 0x6F, 0xFF, + 0xF7, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, + 0xF3, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, + 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x0A, 0x00, + 0xB6, 0x00, 0x6A, 0xFF, 0xC3, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x07, 0x00, + 0x04, 0x00, 0x03, 0x00, 0x00, 0xFF, 0xA9, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, + 0xF9, 0xFF, 0x8B, 0x00, 0x41, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x0B, 0x00, + 0x08, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, + 0xF7, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, 0xED, 0xFF, + 0xEA, 0xFF, 0xEB, 0xFF, 0xEA, 0xFF, 0xEA, 0xFF, 0xEE, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xF2, 0xFF, + 0xF3, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0D, 0x00, + 0x0F, 0xFF, 0xED, 0xFF, 0xEF, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, + 0xFC, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x02, 0x00, + 0x04, 0x00, 0x04, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x08, 0x00, + 0x0A, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x10, 0x00, 0x0E, 0x00, + 0x0E, 0x00, 0x0F, 0x00, 0x10, 0x00, 0x10, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0D, 0x00, + 0x0A, 0x00, 0x0C, 0x00, 0x0A, 0x01, 0x92, 0x00, 0x1F, 0x00, 0x18, 0x00, 0x0B, 0x00, 0x0B, 0x00, + 0x0B, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x0A, 0x00, + 0x09, 0x00, 0x07, 0x00, 0x07, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0xFF, + 0xFE, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, + 0xF7, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xF3, 0xFF, 0xF8, 0xFF, + 0xF8, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xD0, 0xFE, 0xC6, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, + 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x04, 0x00, 0x03, 0x00, 0x05, 0x00, + 0x05, 0x00, 0x07, 0x00, 0x07, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0B, 0x00, + 0x09, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x0B, 0x00, + 0x0A, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x08, 0x00, 0x09, 0xFE, 0xE9, 0x00, 0x00, 0x00, 0x5A, 0x00, + 0x12, 0x00, 0x17, 0x00, 0x1B, 0x00, 0x1C, 0x00, 0x1F, 0x00, 0x1E, 0x00, 0x1D, 0x00, 0x1C, 0x00, + 0x00, 0xFF, 0xD7, 0xFF, 0xDF, 0xFF, 0xED, 0xFF, 0xF0, 0xFF, 0xE0, 0x00, 0xAC, 0x00, 0x34, 0x00, + 0x0D, 0x00, 0x0E, 0x00, 0x10, 0x00, 0x10, 0x00, 0x11, 0x00, 0x0F, 0x00, 0x12, 0x00, 0x10, 0x00, + 0x11, 0x00, 0x13, 0x00, 0x12, 0x00, 0x11, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0E, 0x00, + 0x0B, 0x00, 0x09, 0x00, 0x08, 0x00, 0x05, 0x00, 0x04, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, + 0xFA, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, + 0xF3, 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xF0, 0xFF, + 0xEF, 0xFF, 0xED, 0xFF, 0xED, 0xFF, 0xEE, 0xFF, 0xEA, 0xFF, 0xE9, 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, + 0xEC, 0xFF, 0xEB, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xEB, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xE9, 0xFF, + 0xF1, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, + 0xF7, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, + 0xFB, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x04, 0x00, 0x07, 0x00, 0x07, 0x00, + 0x00, 0x01, 0x01, 0x20, 0x01, 0x6A, 0x01, 0x9C, 0x02, 0xA6, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x32, 0x00, 0x58, 0x00, 0x12, 0xFF, 0x84, 0x01, 0x6A, 0x00, 0x00, 0x01, 0x3C, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xDD, 0x00, 0x00, 0x01, 0xD3, 0x02, 0xA6, 0x00, 0x43, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x91, 0xFF, 0xEF, 0xFF, 0xEC, 0xFF, 0xF0, 0xFF, 0xEE, + 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF9, + 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x06, + 0x00, 0x07, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x10, 0x00, 0x0E, + 0x00, 0x12, 0x00, 0x10, 0x00, 0x14, 0x00, 0x11, 0x00, 0x42, 0x00, 0x00, 0xFF, 0xF1, 0xFF, 0xF2, + 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF5, + 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x04, + 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0B, + 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0F, 0x00, 0x0C, 0x00, 0x00, 0x02, 0xA6, + 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xED, 0xFF, 0xEE, 0xFF, 0xEA, 0xFF, 0xEA, 0xFF, 0xE8, + 0xFF, 0xE8, 0xFF, 0xE9, 0xFF, 0xE7, 0xFF, 0xE9, 0xFF, 0xE6, 0xFF, 0xE9, 0xFF, 0xE8, 0xFF, 0xEA, + 0xFF, 0xE9, 0xFF, 0xE9, 0xFF, 0xE9, 0xFF, 0xE6, 0xFF, 0xE9, 0xFF, 0xE6, 0xFF, 0xE7, 0xFF, 0xE8, + 0xFF, 0xE8, 0xFF, 0xEC, 0xFF, 0xEB, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF4, + 0x00, 0x00, 0x00, 0x42, 0x00, 0x05, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x0D, 0x00, 0x10, 0x00, 0x11, + 0x00, 0x13, 0x00, 0x15, 0x00, 0x18, 0x00, 0x19, 0x00, 0x19, 0x00, 0x19, 0x00, 0x18, 0x00, 0x17, + 0x00, 0x13, 0x00, 0x15, 0x00, 0x14, 0x00, 0x15, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x13, + 0x00, 0x15, 0x00, 0x11, 0x00, 0x10, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x08, + 0x00, 0x07, 0x00, 0x04, 0x00, 0x42, 0x00, 0x00, 0x00, 0x01, 0x00, 0xE7, 0x00, 0x00, 0x01, 0xDA, + 0x02, 0xA6, 0x00, 0x42, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xE7, 0x00, 0x00, 0x00, 0x07, 0x00, + 0x0A, 0x00, 0x10, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x0C, 0x00, + 0x08, 0x00, 0x07, 0x00, 0x07, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, + 0xF3, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, + 0xF6, 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x41, 0x00, 0x16, 0x00, 0x14, 0x00, 0x14, 0x00, 0x13, 0x00, + 0x12, 0x00, 0x11, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x07, 0x00, 0x04, 0x00, + 0x01, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, + 0xF4, 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, 0xF0, 0xFF, 0xED, 0xFF, 0xED, 0xFF, 0xED, 0xFF, 0xED, 0x02, + 0xA6, 0xFF, 0xBE, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xED, 0xFF, + 0xEB, 0xFF, 0xE9, 0xFF, 0xE8, 0xFF, 0xED, 0xFF, 0xED, 0xFF, 0xED, 0xFF, 0xED, 0xFF, 0xEE, 0xFF, + 0xEE, 0xFF, 0xEF, 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, + 0xED, 0xFF, 0xED, 0xFF, 0xED, 0xFF, 0xE8, 0xFF, 0xE9, 0xFF, 0xEB, 0xFF, 0xEF, 0xFF, 0xF2, 0xFF, + 0xF4, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xBE, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x12, 0x00, 0x14, 0x00, 0x16, 0x00, 0x18, 0x00, 0x1C, 0x00, 0x1D, 0x00, 0x1E, 0x00, 0x1F, 0x00, + 0x1D, 0x00, 0x1F, 0x00, 0x1D, 0x00, 0x1A, 0x00, 0x17, 0x00, 0x1A, 0x00, 0x1A, 0x00, 0x1A, 0x00, + 0x1A, 0x00, 0x19, 0x00, 0x1C, 0x00, 0x18, 0x00, 0x1A, 0x00, 0x18, 0x00, 0x15, 0x00, 0x16, 0x00, + 0x12, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x6D, 0x00, 0x5A, 0x02, 0x4F, + 0x02, 0x3C, 0x00, 0x0E, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x81, 0x00, 0x0E, 0xFF, 0x9C, 0x00, 0x09, 0xFF, 0x61, 0xFF, + 0xD8, 0x00, 0xAA, 0xFF, 0x91, 0x00, 0x50, 0x00, 0x67, 0x00, 0x60, 0x00, 0x5A, 0xFF, 0x89, 0x00, + 0xAD, 0xFF, 0xD5, 0x01, 0x7E, 0x00, 0xBE, 0x00, 0x00, 0xFF, 0x42, 0x00, 0x47, 0xFF, 0x9A, 0xFF, + 0xC8, 0xFF, 0x6F, 0xFF, 0xC4, 0x00, 0x9D, 0xFF, 0x63, 0x00, 0x3C, 0x00, 0x91, 0x00, 0x32, 0x00, + 0x6C, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x84, 0x00, 0x50, 0x02, 0x38, 0x02, 0x5D, 0x00, 0x0B, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x31, + 0xFF, 0x53, 0x00, 0x00, 0x00, 0xAD, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0xAE, 0x00, 0x00, + 0xFF, 0x52, 0x00, 0x00, 0xFF, 0xA7, 0x01, 0x9A, 0x00, 0x00, 0xFF, 0x94, 0x00, 0x00, 0xFF, 0x22, + 0x00, 0x00, 0x00, 0xDE, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0xC3, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0xAE, 0xFF, 0x52, 0x02, 0x17, 0x00, 0xB2, 0x00, 0x04, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x6D, 0xFF, 0x41, 0x00, 0x6C, 0x00, 0xFD, 0x00, 0x00, 0x00, 0xB2, 0xFE, + 0xA0, 0x00, 0x00, 0x01, 0x14, 0x00, 0x4C, 0x00, 0x00, 0x01, 0x00, 0x81, 0x01, 0x60, 0x02, 0x3B, + 0x01, 0xDD, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x81, 0x01, 0xBA, 0x00, 0x00, + 0xFE, 0x46, 0x01, 0x60, 0x00, 0x00, 0x00, 0x7D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFA, + 0x00, 0x00, 0x01, 0xC3, 0x00, 0xAE, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0xFA, + 0x00, 0x00, 0x00, 0xC9, 0x00, 0x00, 0x00, 0xAE, 0xFF, 0x52, 0x00, 0x00, 0x00, 0xAE, 0x00, 0x00, + 0x00, 0x01, 0x00, 0xB4, 0x00, 0x00, 0x02, 0x0B, 0x02, 0xA6, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0xB2, 0xFF, 0x02, 0x00, 0x57, 0x01, 0x00, 0x02, 0xA6, 0xFD, 0x5A, 0x00, 0x00, + 0x02, 0xA6, 0x00, 0x00, 0x00, 0x02, 0x00, 0x85, 0xFF, 0xF8, 0x02, 0x3B, 0x02, 0xA6, 0x00, 0x53, + 0x00, 0x93, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x60, 0xFF, 0xDF, 0xFF, 0xE3, 0xFF, 0xE7, + 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF8, + 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFC, + 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, 0x04, + 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x07, 0x00, 0x06, + 0x00, 0x07, 0x00, 0x09, 0x00, 0x13, 0x00, 0x17, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x21, 0x00, 0x22, + 0x00, 0x1C, 0x00, 0x1A, 0x00, 0x18, 0x00, 0x12, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x06, 0x00, 0x07, + 0x00, 0x0B, 0x00, 0x05, 0x00, 0x06, 0x00, 0x04, 0x00, 0x06, 0x00, 0x08, 0x00, 0x07, 0x00, 0x05, + 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF9, + 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF8, + 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xE7, 0xFF, 0xE4, 0xFF, 0xDF, + 0xFF, 0xFC, 0x00, 0x1B, 0x00, 0x12, 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x08, 0x00, 0x07, + 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, + 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFB, + 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xEF, 0xFF, 0xEC, + 0xFF, 0xEB, 0xFF, 0xEB, 0xFF, 0xED, 0xFF, 0xEF, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xFA, + 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x04, 0x00, 0x05, + 0x00, 0x06, 0x00, 0x06, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x0F, 0x00, 0x12, 0x00, 0x1A, + 0x02, 0xA6, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFB, + 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF1, 0xFF, 0xEE, 0xFF, 0xF5, 0xFF, 0xF4, + 0xFF, 0xDF, 0xFF, 0xDE, 0xFF, 0xDB, 0xFF, 0xDA, 0xFF, 0xD7, 0xFF, 0xD0, 0xFF, 0xCE, 0xFF, 0xD1, + 0xFF, 0xD5, 0xFF, 0xD9, 0xFF, 0xDE, 0xFF, 0xEF, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF5, 0xFF, 0xF5, + 0xFF, 0xF6, 0xFF, 0xF1, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xFA, + 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x04, 0x00, 0x07, 0x00, 0x08, + 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x07, 0x00, 0x0F, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0B, + 0x00, 0x0F, 0x00, 0x1C, 0x00, 0x23, 0x00, 0x23, 0x00, 0x2B, 0x00, 0x2C, 0x00, 0x32, 0x00, 0x32, + 0x00, 0x2B, 0x00, 0x26, 0x00, 0x26, 0x00, 0x22, 0x00, 0x20, 0x00, 0x18, 0x00, 0x0B, 0x00, 0x0A, + 0x00, 0x0F, 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x05, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, + 0x00, 0x03, 0x00, 0x05, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xB3, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, + 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xEC, + 0xFF, 0xE6, 0xFF, 0xE1, 0xFF, 0xD9, 0xFF, 0xD6, 0xFF, 0xD6, 0xFF, 0xD7, 0xFF, 0xDA, 0xFF, 0xDB, + 0xFF, 0xDE, 0xFF, 0xE5, 0xFF, 0xEA, 0xFF, 0xEE, 0xFF, 0xF1, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFB, + 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, + 0x00, 0x03, 0x00, 0x05, 0x00, 0x05, 0x00, 0x06, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x11, 0x00, 0x15, + 0x00, 0x1B, 0x00, 0x20, 0x00, 0x21, 0x00, 0x23, 0x00, 0x28, 0x00, 0x59, 0x00, 0x24, 0x00, 0x20, + 0x00, 0x1B, 0x00, 0x19, 0x00, 0x13, 0x00, 0x0D, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x07, 0x00, 0x06, + 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xDE, 0x00, 0x00, 0x01, 0xD2, + 0x02, 0xA6, 0x00, 0x06, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x7B, 0x00, + 0x00, 0x00, 0x57, 0x00, 0x00, 0xFF, 0xA2, 0xFF, 0x6A, 0x00, 0x00, 0x02, 0x3A, 0xFD, 0xC6, 0x00, + 0x00, 0x02, 0xA6, 0x00, 0x00, 0xFF, 0x76, 0xFF, 0x89, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x9C, + 0x00, 0x00, 0x02, 0x19, 0x02, 0xA6, 0x00, 0x64, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, + 0xF5, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x10, 0x00, 0x1F, 0x00, 0x38, 0x00, 0x18, 0x00, + 0x19, 0x00, 0x12, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x06, 0x00, 0x07, 0x00, 0x04, 0x00, + 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, + 0xF6, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xEF, 0xFF, 0xF0, 0xFF, 0xEC, 0xFF, 0xEB, 0xFF, 0xEA, 0xFF, + 0xE8, 0xFF, 0xE8, 0xFF, 0xE8, 0xFF, 0xEA, 0xFF, 0xE9, 0xFF, 0xEA, 0xFF, 0xEA, 0xFF, 0xEB, 0xFF, + 0xEC, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x17, 0x00, 0x19, 0x00, 0x1B, 0x00, 0x1C, 0x00, 0x1A, 0x00, + 0x19, 0x00, 0x0E, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x08, 0x00, + 0x0A, 0x00, 0x07, 0x00, 0x09, 0x00, 0x07, 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, + 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, + 0xF5, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xEF, 0xFF, 0xB6, 0xFF, 0xE9, 0xFF, 0xEE, 0xFF, 0xEB, 0xFF, + 0xF0, 0xFF, 0xF1, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, + 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0x75, 0x00, 0x00, 0x00, 0x56, 0x00, 0x11, 0x00, 0x0C, 0x00, + 0x0B, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0D, 0x00, + 0x0C, 0x00, 0x0D, 0x00, 0x19, 0x00, 0x25, 0x00, 0x12, 0x00, 0x14, 0x00, 0x12, 0x00, 0x0B, 0x00, + 0x0D, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x16, 0x00, + 0x17, 0x00, 0x17, 0x00, 0x15, 0x00, 0x13, 0x00, 0x12, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x0D, 0x00, + 0x0D, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x07, 0x00, 0x05, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFF, 0xFF, + 0xFD, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0x9B, 0x00, + 0x10, 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x06, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, + 0xF7, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, + 0xF7, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF4, 0xFF, + 0xF3, 0xFF, 0xCD, 0xFF, 0xEF, 0xFF, 0xF1, 0xFF, 0xED, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xEF, 0xFF, + 0xE9, 0xFF, 0xEB, 0xFF, 0xE8, 0xFF, 0xE6, 0xFF, 0xE5, 0xFF, 0xE5, 0xFF, 0xE9, 0xFF, 0xC7, 0x00, + 0x00, 0x00, 0x56, 0x00, 0x00, 0x01, 0x00, 0x94, 0xFF, 0xF8, 0x02, 0x2D, 0x02, 0xA6, 0x00, 0x65, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x14, 0xFE, 0x80, 0x00, 0x00, 0x01, 0x0D, + 0xFF, 0x5D, 0x00, 0x00, 0x00, 0x49, 0x00, 0x18, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0C, + 0x00, 0x0E, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x04, 0x00, 0x03, + 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, + 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF3, + 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, 0xF0, 0xFF, 0xEF, + 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xF4, + 0x00, 0x00, 0x00, 0x17, 0x00, 0x15, 0x00, 0x15, 0x00, 0x13, 0x00, 0x18, 0x00, 0x16, 0x00, 0x16, + 0x00, 0x18, 0x00, 0x17, 0x00, 0x17, 0x00, 0x17, 0x00, 0x16, 0x00, 0x14, 0x00, 0x12, 0x00, 0x12, + 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x04, 0x00, 0x05, + 0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFC, + 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF6, + 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xF4, + 0xFF, 0xE8, 0x00, 0xA0, 0x02, 0xA6, 0x00, 0x00, 0xFF, 0xA8, 0x00, 0x00, 0xFF, 0x4F, 0xFF, 0xB6, + 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xF8, + 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF6, + 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF8, + 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x03, + 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, 0x06, 0x00, 0x07, 0x00, 0x09, 0xFF, 0xA0, 0xFF, 0xF6, + 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x09, 0x00, 0x08, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0D, + 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x11, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0F, 0x00, 0x0C, 0x00, 0x0F, + 0x00, 0x0D, 0x00, 0x1C, 0x00, 0x1A, 0x00, 0x0D, 0x00, 0x0F, 0x00, 0x0B, 0x00, 0x0E, 0x00, 0x0B, + 0x00, 0x0C, 0x00, 0x13, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x08, 0x00, 0x07, 0x00, 0x07, + 0x00, 0x07, 0x00, 0x04, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x03, 0x00, 0xAA, + 0x00, 0x01, 0x00, 0x83, 0x00, 0x00, 0x02, 0x2D, 0x02, 0xA6, 0x00, 0x0E, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xDC, 0x00, + 0x00, 0xFF, 0xA2, 0x00, 0x00, 0xFF, 0x60, 0x00, 0xC8, 0xFF, 0xA0, 0xFF, 0x3D, 0x00, 0x00, 0x00, + 0xFB, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0xED, 0x00, 0x8B, 0x00, + 0x00, 0xFF, 0x75, 0x00, 0x00, 0x01, 0xB9, 0x00, 0x00, 0xFE, 0x54, 0xFF, 0x9B, 0x00, 0x00, 0xFF, + 0x6B, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xA7, + 0xFF, 0xF8, 0x02, 0x15, 0x02, 0xA6, 0x00, 0x50, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x0C, 0x00, 0xF7, 0x00, + 0x00, 0xFE, 0xB8, 0xFF, 0xEC, 0x00, 0x71, 0x00, 0x16, 0x00, 0x14, 0x00, 0x13, 0x00, 0x13, 0x00, + 0x0F, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x06, 0x00, 0x08, 0x00, 0x07, 0x00, + 0x05, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, + 0xF6, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xEF, 0xFF, 0xEB, 0xFF, 0xEA, 0xFF, 0xE9, 0xFF, + 0xE8, 0xFF, 0xE9, 0xFF, 0xB9, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x21, 0x00, 0x24, 0x00, 0x22, 0x00, + 0x20, 0x00, 0x1D, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x19, 0x00, 0x0D, 0x00, 0x0A, 0x00, 0x0A, 0x00, + 0x0A, 0x00, 0x09, 0x00, 0x08, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x03, 0x00, 0x04, 0x00, + 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, + 0xFB, 0xFF, 0xF8, 0xFF, 0xF3, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, + 0xF4, 0xFF, 0xE9, 0xFF, 0xE6, 0xFF, 0xE4, 0xFF, 0xE3, 0xFF, 0xE5, 0xFF, 0xDB, 0x02, 0x4E, 0x00, + 0x00, 0x00, 0x58, 0x00, 0x00, 0xFE, 0xC5, 0x00, 0x09, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, + 0xFB, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF5, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF3, 0xFF, + 0xF1, 0xFF, 0xEF, 0xFF, 0xEC, 0xFF, 0xEC, 0xFF, 0xEC, 0xFF, 0xED, 0xFF, 0xEF, 0xFF, 0xF9, 0xFF, + 0xF6, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, + 0xFB, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA9, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, + 0x06, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x07, 0x00, 0x0E, 0x00, 0x09, 0x00, 0x08, 0x00, + 0x08, 0x00, 0x09, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x14, 0x00, 0x15, 0x00, 0x0D, 0x00, 0x0A, 0x00, + 0x0F, 0x00, 0x0B, 0x00, 0x10, 0x00, 0x0C, 0x00, 0x1D, 0x00, 0x1B, 0x00, 0x19, 0x00, 0x17, 0x00, + 0x15, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x11, 0x00, 0x07, 0x00, 0x09, 0x00, 0x07, 0x00, 0x08, 0x00, + 0x07, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x07, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x85, 0xFF, 0xF8, 0x02, 0x37, 0x02, 0xA6, 0x00, 0x57, 0x00, 0x98, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x83, 0xFF, 0x57, 0xFF, 0xED, 0xFF, + 0xF1, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x05, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x0A, 0x00, + 0x08, 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0F, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x13, 0x00, + 0x0D, 0x00, 0x0A, 0x00, 0x17, 0x00, 0x1B, 0x00, 0x19, 0x00, 0x12, 0x00, 0x15, 0x00, 0x11, 0x00, + 0x13, 0x00, 0x10, 0x00, 0x11, 0x00, 0x0E, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x0B, 0x00, 0x0C, 0x00, + 0x09, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x06, 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, + 0xF9, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, + 0xF4, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xF1, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, + 0xF0, 0xFF, 0xF0, 0xFF, 0xF3, 0xFF, 0xF0, 0xFF, 0xF4, 0xFF, 0xFF, 0x00, 0x38, 0x00, 0x1A, 0x00, + 0x1A, 0x00, 0x1A, 0x00, 0x1C, 0x00, 0x1B, 0x00, 0x1A, 0xFF, 0x67, 0x00, 0x0C, 0x00, 0x0F, 0x00, + 0x0B, 0x00, 0x0D, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x06, 0x00, + 0x07, 0x00, 0x04, 0x00, 0x05, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, + 0xF5, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, + 0xF5, 0xFF, 0xF3, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, + 0xF9, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x06, 0x00, 0x06, 0x00, 0x09, 0x00, 0x09, 0x00, 0x08, 0x00, + 0x0C, 0x00, 0x09, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0E, 0x00, 0x0C, 0x02, 0xA6, 0xFF, 0x35, 0xFF, + 0xE6, 0xFF, 0xE9, 0xFF, 0xEB, 0xFF, 0xEC, 0xFF, 0xED, 0xFF, 0xE9, 0xFF, 0xEB, 0xFF, 0xE8, 0xFF, + 0xE9, 0xFF, 0xE6, 0xFF, 0xE2, 0xFF, 0xE6, 0xFF, 0xE7, 0xFF, 0xE6, 0xFF, 0xEA, 0xFF, 0xEA, 0xFF, + 0xED, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xF3, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, + 0xF6, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x03, 0x00, 0x05, 0x00, 0x06, 0x00, 0x09, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0C, 0x00, + 0x0F, 0x00, 0x0E, 0x00, 0x11, 0x00, 0x10, 0x00, 0x10, 0x00, 0x12, 0x00, 0x0F, 0x00, 0x0F, 0x00, + 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x11, 0x00, 0x0F, 0x00, 0x0D, 0x00, + 0x10, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x08, 0x00, + 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, + 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFA, 0x00, 0x02, 0x00, 0x4C, 0x00, + 0x22, 0x00, 0x1F, 0x00, 0x1F, 0x00, 0x1F, 0x00, 0x1D, 0x00, 0x1A, 0xFE, 0xB8, 0x00, 0x00, 0xFF, + 0xFE, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, + 0xF7, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, + 0xF0, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, + 0xF8, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0A, 0x00, + 0x0B, 0x00, 0x0D, 0x00, 0x0A, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x10, 0x00, 0x0D, 0x00, 0x10, 0x00, + 0x0D, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0A, 0x00, + 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x89, 0x00, 0x00, 0x02, 0x35, 0x02, 0xA6, 0x00, 0x2E, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xC7, 0xFF, + 0xFF, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xD3, 0xFF, 0xE9, 0xFF, 0xE9, 0xFF, + 0xEA, 0xFF, 0xEF, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, + 0xFB, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x5C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x03, 0x00, 0x05, 0x00, 0x06, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x10, 0x00, + 0x10, 0x00, 0x12, 0x00, 0x32, 0x00, 0x1F, 0x00, 0x14, 0x00, 0x0E, 0x00, 0x05, 0x00, 0x05, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0xFE, 0x54, 0x00, 0x00, 0x02, 0x4E, 0xFF, 0xF8, 0xFF, + 0xF6, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xCA, 0xFF, 0xE2, 0xFF, 0xE0, 0xFF, 0xE0, 0xFF, + 0xE3, 0xFF, 0xE5, 0xFF, 0xE5, 0xFF, 0xE7, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE2, 0xFF, 0xE1, 0xFF, + 0xE9, 0xFF, 0xE8, 0xFF, 0x8A, 0x00, 0x00, 0x00, 0x77, 0x00, 0x12, 0x00, 0x15, 0x00, 0x1A, 0x00, + 0x1A, 0x00, 0x17, 0x00, 0x19, 0x00, 0x17, 0x00, 0x19, 0x00, 0x18, 0x00, 0x19, 0x00, 0x18, 0x00, + 0x18, 0x00, 0x3F, 0x00, 0x28, 0x00, 0x1D, 0x00, 0x17, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0D, 0x00, + 0x0C, 0x00, 0x1B, 0x00, 0x2A, 0x00, 0x00, 0xFF, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x85, + 0xFF, 0xF8, 0x02, 0x32, 0x02, 0xA6, 0x00, 0x3A, 0x00, 0xBB, 0x00, 0xE9, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x5C, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF2, + 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFA, + 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, + 0x00, 0x04, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x06, 0x00, 0x08, 0x00, 0x09, 0x00, 0x14, + 0x00, 0x19, 0x00, 0x25, 0x00, 0x24, 0x00, 0x19, 0x00, 0x14, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x06, + 0x00, 0x06, 0x00, 0x05, 0x00, 0x05, 0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF8, + 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xFD, + 0xFF, 0xEE, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF6, + 0xFF, 0xF3, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFB, + 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x06, + 0x00, 0x07, 0x00, 0x09, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0F, 0x00, 0x10, 0x00, 0x12, + 0xFF, 0xEE, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF8, + 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x03, 0x00, 0x04, 0x00, 0x06, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0D, + 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x11, 0x00, 0x14, 0x00, 0x13, 0x00, 0x17, 0x00, 0x17, 0x00, 0x19, + 0x00, 0x18, 0x00, 0x18, 0x00, 0x16, 0x00, 0x16, 0x00, 0x15, 0x00, 0x11, 0x00, 0x0F, 0x00, 0x0F, + 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x07, 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF8, + 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xEE, + 0x00, 0x13, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x09, 0x00, 0x06, + 0x00, 0x06, 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFB, + 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF4, + 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xEF, + 0xFF, 0xF9, 0x00, 0x17, 0x00, 0x14, 0x00, 0x11, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x06, + 0x00, 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xEE, 0xFF, 0xEB, 0xFF, 0xEA, + 0xFF, 0xE9, 0xFF, 0xEB, 0xFF, 0xEE, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFB, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, + 0x00, 0x0A, 0x00, 0x07, 0x00, 0x08, 0x00, 0x10, 0x00, 0x15, 0x00, 0x17, 0x00, 0x48, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x07, + 0x00, 0x09, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x0D, 0x00, 0x0A, 0x00, 0x09, + 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, + 0x00, 0x09, 0x00, 0x0F, 0x00, 0x10, 0x00, 0x15, 0xFF, 0xEB, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xF7, + 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF5, + 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF7, + 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, + 0xFF, 0xFF, 0x02, 0x5E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFB, + 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF7, + 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF2, + 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF4, + 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF5, + 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xEF, 0xFF, 0xF2, + 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xEB, 0xFF, 0xEA, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, 0xF1, 0xFF, 0xF0, + 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFC, + 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x08, 0x00, 0x07, + 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x13, 0x00, 0x12, + 0x00, 0x16, 0x00, 0x14, 0x00, 0x10, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x11, 0x00, 0x0D, 0x00, 0x0F, + 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0B, + 0x00, 0x09, 0x00, 0x09, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0A, + 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x10, + 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x08, 0x00, 0x08, 0x00, 0x09, + 0x00, 0x06, 0x00, 0x07, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, + 0x00, 0x01, 0x00, 0x01, 0xFF, 0xAA, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFC, + 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, 0xF9, + 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF2, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF3, + 0xFF, 0xF4, 0xFF, 0xF6, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x07, + 0x00, 0x08, 0x00, 0x0E, 0x00, 0x08, 0x00, 0x06, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x08, + 0x00, 0x05, 0x00, 0x06, 0x00, 0x09, 0x00, 0x05, 0x00, 0x04, 0x00, 0x05, 0x00, 0x04, 0x00, 0x02, + 0x00, 0x02, 0x00, 0x86, 0x00, 0x00, 0x02, 0x36, 0x02, 0xA6, 0x00, 0x5A, 0x00, 0x9B, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x3A, 0x00, 0x41, + 0x00, 0x1A, 0x00, 0x16, 0x00, 0x15, 0x00, 0x13, 0x00, 0x11, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x0E, + 0x00, 0x0A, 0x00, 0x09, 0x00, 0x07, 0x00, 0x06, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF9, + 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xED, 0xFF, 0xEB, 0xFF, 0xEA, 0xFF, 0xE8, 0xFF, 0xE7, 0xFF, 0xEE, + 0xFF, 0xEB, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, 0xF1, 0xFF, 0xEE, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF2, + 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFD, + 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, + 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x07, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x0D, + 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0F, + 0x00, 0x0F, 0x00, 0x0B, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x01, + 0x00, 0x01, 0xFF, 0xEE, 0xFF, 0xEA, 0xFF, 0xEA, 0xFF, 0xE7, 0xFF, 0xE6, 0xFF, 0xE1, 0xFF, 0xDF, + 0xFF, 0xD7, 0x00, 0x9E, 0x00, 0x0C, 0x00, 0x0F, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x0A, 0x00, 0x0B, + 0x00, 0x09, 0x00, 0x09, 0x00, 0x08, 0x00, 0x06, 0x00, 0x07, 0x00, 0x04, 0x00, 0x04, 0x00, 0x02, + 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xF9, + 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, 0xF5, + 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF6, 0xFF, 0xF4, + 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x04, 0x00, 0x06, + 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x0E, 0x00, 0x0B, + 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x49, 0x00, 0x1D, 0x00, 0x1D, 0x00, 0x1A, 0x00, 0x1B, + 0x00, 0x19, 0x00, 0x1A, 0x00, 0x1A, 0x00, 0x1A, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x15, + 0x00, 0x19, 0x00, 0x15, 0x00, 0x1B, 0x00, 0x19, 0x00, 0x18, 0x00, 0x1A, 0x00, 0x16, 0x00, 0x17, + 0x00, 0x14, 0x00, 0x11, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x07, + 0x00, 0x06, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFA, + 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xEF, + 0xFF, 0xF1, 0xFF, 0xED, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xF3, + 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF4, + 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xFA, + 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0xFF, 0xFE, 0x00, 0x01, 0xFF, 0xE4, 0xFF, 0xE2, 0xFF, 0xE3, + 0xFF, 0xE2, 0xFF, 0xE2, 0xFF, 0xDF, 0xFF, 0xDD, 0xFF, 0xD6, 0x02, 0x50, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF7, + 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xF0, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF0, + 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF9, + 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, 0x06, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x09, + 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0F, + 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x08, + 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0xFC, + 0x00, 0x00, 0x01, 0xBC, 0x02, 0x10, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x00, 0x00, 0x00, 0xB8, 0x00, 0x00, 0xFF, 0x40, 0x00, 0x00, + 0x00, 0xBF, 0x00, 0x00, 0x02, 0x10, 0xFF, 0x60, 0x00, 0x00, 0x00, 0xA0, 0xFE, 0x90, 0xFF, 0x60, + 0x00, 0x00, 0x00, 0xA0, 0x00, 0x02, 0x00, 0xA9, 0xFF, 0x50, 0x02, 0x0D, 0x01, 0xFE, 0x00, 0x03, + 0x00, 0x07, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x42, 0xFF, 0x67, + 0x00, 0x78, 0x00, 0xEC, 0xFF, 0x3D, 0x00, 0x00, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x9A, 0xFE, 0xB6, + 0x00, 0x00, 0x01, 0x4A, 0x01, 0x64, 0xFF, 0x65, 0x00, 0x00, 0x00, 0x9B, 0x00, 0x01, 0x00, 0x83, + 0x00, 0x41, 0x02, 0x29, 0x02, 0xA6, 0x00, 0x06, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x02, 0x29, 0xFE, 0xDC, 0x01, 0x24, 0x00, 0x00, 0xFE, 0x5A, 0x00, 0x00, 0x01, 0xA6, 0x00, + 0xB5, 0x00, 0xBF, 0x00, 0xC0, 0x00, 0x72, 0xFE, 0xD9, 0xFF, 0xE7, 0xFE, 0xDB, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x86, 0x00, 0xC0, 0x02, 0x36, 0x01, 0xF6, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x86, 0x01, 0xB0, 0x00, 0x00, 0xFE, 0x50, + 0x00, 0x00, 0x01, 0xB0, 0x00, 0x00, 0xFE, 0x50, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, + 0x00, 0x8A, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x01, 0x00, 0x98, 0x00, 0x41, 0x02, 0x39, + 0x02, 0xA6, 0x00, 0x06, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x39, 0xFE, + 0x5F, 0x00, 0x00, 0x01, 0x21, 0xFE, 0xDF, 0x00, 0x00, 0x01, 0xA1, 0x01, 0x7F, 0x01, 0x27, 0xFF, + 0x8E, 0xFF, 0x40, 0xFF, 0x41, 0xFF, 0x8C, 0x01, 0x26, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xAB, + 0x00, 0x00, 0x02, 0x16, 0x02, 0xA6, 0x00, 0x63, 0x00, 0x67, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0xA1, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x05, + 0x00, 0x04, 0x00, 0x07, 0x00, 0x21, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x07, 0x00, 0x07, + 0x00, 0x05, 0x00, 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF6, + 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF0, 0xFF, 0xEF, 0xFF, 0xF1, 0xFF, 0xEE, + 0xFF, 0xF0, 0xFF, 0xEC, 0xFF, 0xED, 0xFF, 0xEE, 0xFF, 0xEF, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xF2, + 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, + 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, + 0x00, 0x07, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0F, 0x00, 0x0A, + 0x00, 0x0A, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x07, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x04, + 0x00, 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, + 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF3, 0xFF, 0xE7, 0xFF, 0xEE, 0xFF, 0xF2, + 0xFF, 0xF6, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xEA, 0x00, 0x00, + 0x00, 0x81, 0x00, 0x00, 0x00, 0xF2, 0x00, 0x18, 0x00, 0x0F, 0x00, 0x08, 0x00, 0x07, 0x00, 0x0C, + 0x00, 0x06, 0x00, 0x08, 0x00, 0x22, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0E, + 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x0E, + 0x00, 0x0D, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0A, + 0x00, 0x0A, 0x00, 0x08, 0x00, 0x08, 0x00, 0x06, 0x00, 0x06, 0x00, 0x05, 0x00, 0x03, 0x00, 0x02, + 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF9, + 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xEE, + 0xFF, 0xEA, 0xFF, 0xEC, 0xFF, 0xDE, 0x00, 0x00, 0x00, 0x17, 0x00, 0x13, 0x00, 0x11, 0x00, 0x0E, + 0x00, 0x0E, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x07, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF8, + 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF6, + 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF1, 0xFF, 0xE7, 0xFF, 0xED, 0xFF, 0xF0, + 0xFF, 0xF1, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xEF, 0xFF, 0xF0, 0xFF, 0xD5, 0xFF, 0x8A, 0xFF, 0x84, + 0x00, 0x00, 0x00, 0x7C, 0x00, 0x02, 0x00, 0x84, 0xFF, 0xF7, 0x02, 0x38, 0x02, 0xA6, 0x00, 0xA9, + 0x00, 0xD4, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x98, 0xFF, 0xB5, 0x00, 0x00, 0xFF, + 0xF0, 0xFF, 0xF5, 0xFF, 0xF1, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF4, 0xFF, 0xEB, 0xFF, + 0xEE, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF2, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xFC, 0xFF, + 0xFA, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, 0x05, 0x00, + 0x05, 0x00, 0x05, 0x00, 0x07, 0x00, 0x06, 0x00, 0x09, 0x00, 0x08, 0x00, 0x0C, 0x00, 0x0A, 0x00, + 0x0A, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0B, 0x00, + 0x0C, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x08, 0x00, 0x04, 0x00, + 0x0A, 0x00, 0x07, 0x00, 0x08, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x12, 0x00, 0x12, 0x00, + 0x0A, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x08, 0x00, 0x06, 0x00, + 0x06, 0x00, 0x07, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, + 0xF9, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, + 0xF5, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, + 0xE6, 0xFF, 0xE1, 0xFF, 0xE3, 0xFF, 0xE5, 0xFF, 0xE6, 0xFF, 0xF3, 0xFF, 0xF6, 0xFF, 0xED, 0xFF, + 0xEF, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, + 0xFE, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, + 0x04, 0x00, 0x04, 0x00, 0x07, 0x00, 0x06, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0C, 0x00, + 0x0C, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x17, 0x00, 0x17, 0x00, 0x13, 0x00, 0x15, 0x00, 0x12, 0x00, + 0x10, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x05, 0x00, 0x03, 0x00, + 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, + 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, + 0xF7, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0x3B, 0x00, + 0x01, 0x00, 0x03, 0x00, 0x05, 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x09, 0x00, 0x0B, 0x00, + 0x0C, 0x00, 0x0E, 0x00, 0x06, 0x00, 0x09, 0x00, 0x09, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, + 0x06, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, + 0xFD, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, + 0xF8, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, + 0xFF, 0xFF, 0xFF, 0x01, 0x83, 0x00, 0x00, 0xFF, 0xE5, 0x00, 0x0E, 0x00, 0x08, 0x00, 0x06, 0x00, + 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, + 0xF7, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF2, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xED, 0xFF, 0xEC, 0xFF, + 0xEB, 0xFF, 0xE7, 0xFF, 0x91, 0xFF, 0xE9, 0xFF, 0xEA, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, + 0xF7, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, + 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x06, 0x00, + 0x05, 0x00, 0x07, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x07, 0xFF, 0xEE, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, + 0xF8, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x04, 0x00, + 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0D, 0x00, 0x0A, 0x00, + 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x10, 0x00, 0x14, 0x00, 0x21, 0x00, 0xD4, 0x00, 0x2A, 0x00, + 0x15, 0x00, 0x13, 0x00, 0x23, 0x00, 0x12, 0x00, 0x0E, 0x00, 0x10, 0x00, 0x0D, 0x00, 0x0C, 0x00, + 0x0C, 0x00, 0x0D, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x09, 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, + 0x06, 0x00, 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFD, 0xFF, + 0xFB, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, + 0xF1, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xED, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xE9, 0xFF, 0xE8, 0x00, + 0x00, 0x00, 0x19, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x09, 0x00, + 0x06, 0x00, 0x07, 0x00, 0x07, 0x00, 0x05, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, + 0xF7, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, + 0xE4, 0xFF, 0x22, 0xFF, 0xE2, 0xFF, 0xE6, 0xFF, 0xEF, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF7, 0xFF, + 0xF9, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFE, 0x00, 0x01, 0x00, 0x02, 0x00, 0x05, 0x00, 0x07, 0x00, + 0x08, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0F, 0x00, 0x6C, 0x00, 0x12, 0x00, 0x0E, 0x00, 0x0F, 0x00, + 0x0A, 0x00, 0x07, 0x00, 0x03, 0x00, 0x07, 0x00, 0x03, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, + 0xFD, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xEC, 0xFF, + 0x99, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, + 0xFC, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, + 0x06, 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x0E, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x6C, 0x00, 0x00, 0x02, 0x50, 0x02, 0xB6, 0x00, 0x03, 0x00, 0x0B, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xA6, 0xFF, 0x70, + 0x00, 0x48, 0x00, 0x01, 0x00, 0x5D, 0x00, 0x30, 0x00, 0x64, 0xFF, 0x4D, 0xFF, 0x82, 0xFF, 0x4D, + 0x00, 0x64, 0x00, 0x2F, 0x01, 0x23, 0x00, 0x00, 0x01, 0x24, 0x00, 0x00, 0xFE, 0x81, 0xFF, 0x38, + 0x00, 0x00, 0x02, 0xB6, 0x00, 0x00, 0xFD, 0x4A, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x03, 0x00, 0x76, + 0x00, 0x00, 0x02, 0x4B, 0x02, 0xAB, 0x00, 0x1B, 0x00, 0x39, 0x00, 0x7C, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xD1, 0x00, 0x7C, 0x00, + 0x16, 0x00, 0x10, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0A, 0x00, + 0x08, 0x00, 0x06, 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, + 0xFB, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, + 0xF0, 0xFF, 0x63, 0x00, 0x00, 0x00, 0x88, 0x00, 0x14, 0x00, 0x13, 0x00, 0x10, 0x00, 0x0E, 0x00, + 0x0E, 0x00, 0x0F, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x05, 0x00, 0x08, 0x00, 0x07, 0x00, 0x04, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, + 0xF4, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, 0xED, 0xFF, 0x76, 0x00, + 0xA0, 0x00, 0x11, 0x00, 0x14, 0x00, 0x0F, 0x00, 0x11, 0x00, 0x0E, 0x00, 0x10, 0x00, 0x0F, 0x00, + 0x0D, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x09, 0x00, 0x08, 0x00, 0x07, 0x00, + 0x07, 0x00, 0x05, 0x00, 0x05, 0x00, 0x03, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, + 0xFB, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, + 0xF1, 0x00, 0x00, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x09, 0x00, 0x07, 0x00, 0x08, 0x00, + 0x06, 0x00, 0x06, 0x00, 0x05, 0x00, 0x05, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, + 0x00, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, + 0xF1, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, 0xEC, 0xFF, 0xEB, 0xFF, 0xE7, 0xFF, 0xE7, 0xFF, + 0xE3, 0xFF, 0x2D, 0x00, 0x00, 0x02, 0x4C, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, + 0xFC, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, + 0xF3, 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, + 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xA1, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, + 0xFB, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, + 0xEF, 0xFF, 0xF1, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, + 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xA4, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0A, 0x00, + 0x0A, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x10, 0x00, 0x10, 0x00, 0x13, 0x00, + 0x14, 0x00, 0x15, 0x00, 0x12, 0x00, 0x14, 0x00, 0x13, 0x00, 0x0F, 0x00, 0x12, 0x00, 0x0E, 0x00, + 0x0D, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x07, 0x00, 0x04, 0x00, 0x02, 0x00, 0x04, 0x00, + 0x06, 0x00, 0x05, 0x00, 0x07, 0x00, 0x07, 0x00, 0x09, 0x00, 0x07, 0x00, 0x0B, 0x00, 0x08, 0x00, + 0x0C, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x0D, 0x00, 0x09, 0x00, 0x1A, 0x00, 0x16, 0x00, 0x14, 0x00, + 0x12, 0x00, 0x11, 0x00, 0x0F, 0x00, 0x11, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x09, 0x00, + 0x07, 0x00, 0x07, 0x00, 0x04, 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0xFD, 0x55, 0x00, + 0x00, 0x01, 0x00, 0x99, 0xFF, 0xF7, 0x02, 0x1E, 0x02, 0xC4, 0x00, 0x8A, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x1E, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, + 0xFC, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF1, 0xFF, + 0xF2, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, + 0xF1, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, + 0xF5, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, + 0xFC, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, + 0x08, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0D, 0x00, + 0x0D, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x10, 0x00, 0x11, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x13, 0x00, + 0x0F, 0x00, 0x11, 0x00, 0x11, 0x00, 0x0D, 0x00, 0x0F, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x09, 0x00, + 0x08, 0x00, 0x06, 0x00, 0x06, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xA0, 0xFF, 0xFE, 0xFF, + 0xFC, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, + 0xF7, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, + 0xF7, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, + 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x05, 0x00, + 0x06, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x07, 0x00, 0x0D, 0x00, + 0x08, 0x00, 0x09, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x09, 0x00, 0x0B, 0x00, + 0x08, 0x00, 0x07, 0x00, 0x07, 0x00, 0x05, 0x00, 0x05, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x02, + 0x1D, 0x00, 0x11, 0x00, 0x11, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0C, 0x00, + 0x0E, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x08, 0x00, 0x06, 0x00, 0x03, 0x00, 0x02, 0x00, + 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, + 0xF5, 0xFF, 0xF6, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, + 0xEA, 0xFF, 0xE9, 0xFF, 0xE9, 0xFF, 0xE9, 0xFF, 0xE8, 0xFF, 0xE8, 0xFF, 0xE4, 0xFF, 0xE5, 0xFF, + 0xE6, 0xFF, 0xE6, 0xFF, 0xE5, 0xFF, 0xE5, 0xFF, 0xE8, 0xFF, 0xE9, 0xFF, 0xE8, 0xFF, 0xE9, 0xFF, + 0xED, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xEF, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, + 0xF6, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFE, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x04, 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0B, 0x00, + 0x0A, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x11, 0x00, 0x11, 0x00, + 0x12, 0x00, 0x00, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, + 0xFB, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, + 0x05, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0F, 0x00, 0x11, 0x00, 0x12, 0x00, + 0x13, 0x00, 0x16, 0x00, 0x17, 0x00, 0x1A, 0x00, 0x1C, 0x00, 0x1D, 0x00, 0x1A, 0x00, 0x1A, 0x00, + 0x18, 0x00, 0x1A, 0x00, 0x1F, 0x00, 0x1A, 0x00, 0x19, 0x00, 0x17, 0x00, 0x12, 0x00, 0x10, 0x00, + 0x06, 0x00, 0x06, 0x00, 0x08, 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, + 0xF7, 0xFF, 0xF7, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x86, 0x00, 0x00, 0x02, 0x27, + 0x02, 0xAE, 0x00, 0x3A, 0x00, 0x5B, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x00, 0x86, 0x00, 0x4B, 0x00, 0x1C, 0x00, 0x24, 0x00, 0x23, 0x00, 0x12, + 0x00, 0x12, 0x00, 0x11, 0x00, 0x11, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0D, + 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x07, 0x00, 0x07, + 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF8, + 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF0, 0xFF, 0xF2, + 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xED, 0xFF, 0xEE, + 0xFF, 0xF0, 0xFF, 0xEC, 0xFF, 0xF0, 0xFF, 0xE2, 0xFF, 0xBE, 0x00, 0x69, 0x00, 0x14, 0x00, 0x16, + 0x00, 0x12, 0x00, 0x14, 0x00, 0x13, 0x00, 0x11, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x0D, + 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x05, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, + 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xF0, + 0xFF, 0xEF, 0xFF, 0xED, 0xFF, 0xEB, 0xFF, 0xED, 0xFF, 0xE9, 0xFF, 0xEB, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x04, 0x00, 0x07, 0x00, 0x04, 0x00, 0x06, 0x00, 0x07, 0x00, 0x07, 0x00, 0x08, + 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0E, + 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x11, 0x00, 0x12, 0x00, 0x12, 0x00, 0x10, 0x00, 0x14, + 0x00, 0x14, 0x00, 0x15, 0x00, 0x12, 0x00, 0x14, 0x00, 0x15, 0x00, 0x14, 0x00, 0x13, 0x00, 0x13, + 0x00, 0x13, 0x00, 0x12, 0x00, 0x11, 0x00, 0x11, 0x00, 0x10, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x0E, + 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x08, + 0x00, 0x07, 0x00, 0x07, 0x00, 0x06, 0x00, 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x02, + 0x00, 0x00, 0xFF, 0xA1, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF3, + 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, 0xEA, 0xFF, 0xEC, 0xFF, 0xE7, 0xFF, 0xE7, 0xFF, 0xE9, + 0xFF, 0xE6, 0xFF, 0xEA, 0xFF, 0xE8, 0xFF, 0xE8, 0xFF, 0xE6, 0xFF, 0xEB, 0xFF, 0xE8, 0xFF, 0xED, + 0xFF, 0xEE, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFB, + 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x86, 0x00, 0x00, 0x02, 0x24, 0x02, 0xB6, 0x00, 0x0B, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xEC, + 0x01, 0x2C, 0x00, 0x00, 0xFE, 0x6E, 0x00, 0x00, 0x01, 0x9E, 0x00, 0x00, 0xFE, 0xC8, 0x00, 0x00, + 0x01, 0x06, 0x00, 0x00, 0xFE, 0xFA, 0x02, 0x56, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0xFD, 0x4A, + 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xDA, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0xA9, 0x00, 0x00, 0x02, 0x07, 0x02, 0xBC, 0x00, 0x09, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x0F, 0x00, 0xF8, 0x00, 0x00, 0xFE, 0xA2, + 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x00, 0xFF, 0x38, 0x02, 0x5B, 0x00, 0x00, + 0x00, 0x61, 0x00, 0x00, 0xFD, 0x44, 0x00, 0x00, 0x01, 0x3D, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x81, 0xFF, 0xF7, 0x02, 0x41, 0x02, 0xC4, 0x00, 0x85, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0xDB, 0x00, 0x00, 0xFF, 0xE7, 0xFF, 0xE9, 0xFF, 0xEE, 0xFF, 0xF1, + 0xFF, 0xED, 0xFF, 0xEF, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF4, + 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x03, 0x00, 0x04, + 0x00, 0x05, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x0C, + 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x11, 0x00, 0x11, 0x00, 0x13, 0x00, 0x10, 0x00, 0x0B, + 0x00, 0x0A, 0x00, 0x09, 0x00, 0x08, 0x00, 0x06, 0x00, 0x06, 0x00, 0x03, 0x00, 0x66, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF2, 0xFF, 0xF0, + 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xF1, 0xFF, 0xED, + 0xFF, 0xEB, 0xFF, 0xEF, 0xFF, 0xED, 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF4, + 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF8, + 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x04, 0x00, 0x06, 0x00, 0x05, + 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, + 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0B, 0x00, 0x0F, + 0x00, 0x10, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x12, 0x00, 0x13, 0x00, 0x12, 0x00, 0x26, + 0x00, 0x12, 0x00, 0x16, 0x00, 0x24, 0x00, 0x12, 0x00, 0x14, 0x00, 0x00, 0xFF, 0x11, 0x00, 0x00, + 0x01, 0x15, 0xFF, 0x53, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0F, + 0x00, 0x10, 0x00, 0x12, 0x00, 0x12, 0x00, 0x16, 0x00, 0x16, 0x00, 0x18, 0x00, 0x1A, 0x00, 0x1E, + 0x00, 0x22, 0x00, 0x1B, 0x00, 0x1B, 0x00, 0x1B, 0x00, 0x12, 0x00, 0x12, 0x00, 0x11, 0x00, 0x11, + 0x00, 0x0F, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x06, 0x00, 0x05, + 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFA, + 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF3, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0D, + 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x08, 0x00, 0x08, + 0x00, 0x06, 0x00, 0x06, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, + 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF4, + 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xEE, 0xFF, 0xF0, 0xFF, 0xEC, 0xFF, 0xEC, + 0xFF, 0xEA, 0xFF, 0xEB, 0xFF, 0xE6, 0xFF, 0xE9, 0xFF, 0xE5, 0xFF, 0xE5, 0xFF, 0xE1, 0xFF, 0xE5, + 0xFF, 0xE6, 0xFF, 0xE5, 0xFF, 0xE6, 0xFF, 0xE7, 0xFF, 0xEA, 0xFF, 0xEE, 0xFF, 0xEF, 0xFF, 0xEF, + 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF7, + 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, + 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x05, 0x00, 0x04, 0x00, 0x05, + 0x00, 0x0B, 0x00, 0x07, 0x00, 0x08, 0x01, 0x55, 0x00, 0x00, 0xFF, 0x9F, 0x00, 0x01, 0x00, 0x82, + 0x00, 0x00, 0x02, 0x3A, 0x02, 0xA6, 0x00, 0x0B, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xD1, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0xFF, 0x97, + 0x00, 0x00, 0xFF, 0x1A, 0x00, 0x00, 0xFF, 0x97, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x01, 0x31, + 0xFE, 0xCF, 0x00, 0x00, 0x02, 0xA6, 0x00, 0x00, 0xFE, 0xE9, 0x00, 0x00, 0x01, 0x17, 0x00, 0x00, + 0xFD, 0x5A, 0x00, 0x00, 0x01, 0x31, 0x00, 0x00, 0x00, 0x01, 0x00, 0xB7, 0x00, 0x00, 0x01, 0xF8, + 0x02, 0xB6, 0x00, 0x0B, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x87, 0x00, 0x5D, 0x00, 0x00, 0xFE, 0xE6, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, + 0xFF, 0x8F, 0x00, 0x00, 0x01, 0x41, 0x00, 0x00, 0xFF, 0x8F, 0x02, 0x56, 0x00, 0x00, 0x00, 0x60, + 0x00, 0x00, 0xFF, 0xA0, 0x00, 0x00, 0xFE, 0x0A, 0x00, 0x00, 0xFF, 0xA0, 0x00, 0x00, 0x00, 0x60, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xBD, 0xFF, 0xF7, 0x02, 0x11, 0x02, 0xB0, 0x00, 0x3C, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0xAD, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, + 0xF6, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, + 0xFC, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0xFF, + 0x9C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x05, 0x00, 0x06, 0x00, 0x08, 0x00, 0x0A, 0x00, + 0x0D, 0x00, 0x0D, 0x00, 0x10, 0x00, 0x10, 0x00, 0x14, 0x00, 0x12, 0x00, 0x14, 0x00, 0x15, 0x00, + 0x10, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x09, 0x00, + 0x0A, 0x00, 0x09, 0x00, 0x09, 0x00, 0x06, 0x00, 0x07, 0x00, 0x06, 0x00, 0x05, 0x00, 0x03, 0x00, + 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x02, 0xB0, 0xFE, 0x00, 0xFF, 0xED, 0xFF, + 0xEE, 0xFF, 0xF2, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, + 0xFF, 0x00, 0x02, 0x00, 0x02, 0x00, 0x03, 0x00, 0x06, 0x00, 0x03, 0x00, 0x06, 0x00, 0x04, 0x00, + 0x07, 0x00, 0x0E, 0x00, 0x09, 0x00, 0x0E, 0x00, 0x43, 0x00, 0x00, 0xFF, 0xBB, 0xFF, 0xEF, 0xFF, + 0xEF, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, + 0xF8, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x04, 0x00, 0x04, 0x00, 0x06, 0x00, 0x07, 0x00, 0x06, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0A, 0x00, + 0x0A, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x10, 0x00, 0x12, 0x00, 0x13, 0x00, + 0x10, 0x01, 0xFA, 0x00, 0x00, 0x01, 0x00, 0x61, 0x00, 0x00, 0x02, 0x63, 0x02, 0xB1, 0x00, 0x0A, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xC4, 0x00, + 0x00, 0xFF, 0x9D, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x01, 0x16, 0x00, 0x89, 0xFE, 0xB1, 0x01, + 0x3C, 0xFF, 0x7F, 0x01, 0x98, 0x01, 0x19, 0x00, 0x00, 0xFD, 0x4F, 0x00, 0x00, 0x01, 0x35, 0xFE, + 0xCB, 0x00, 0x00, 0x01, 0x69, 0x01, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x82, + 0x00, 0x00, 0x02, 0x2C, 0x02, 0xAE, 0x00, 0x05, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, 0xE4, 0x00, 0x00, 0xFF, 0x9E, 0x00, 0x00, 0x01, 0xAA, 0x00, 0x00, 0x00, 0x5F, 0x02, 0x4F, + 0x00, 0x00, 0xFD, 0x52, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x01, 0x00, 0x73, 0x00, 0x00, 0x02, 0x52, + 0x02, 0xA8, 0x00, 0x0F, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x64, 0xFF, 0xFE, 0xFF, 0xB0, 0xFF, 0x61, 0x00, 0x00, + 0x00, 0x67, 0x00, 0x00, 0x00, 0x02, 0x00, 0x5D, 0x00, 0x53, 0x00, 0x5D, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x67, 0x00, 0x00, 0xFF, 0x61, 0x01, 0xA2, 0x00, 0x00, 0x01, 0x06, 0x00, 0x00, 0xFD, 0x58, + 0x00, 0x00, 0x02, 0x37, 0x00, 0x00, 0xFE, 0xA6, 0x00, 0x00, 0x01, 0x5A, 0x00, 0x00, 0xFD, 0xC9, + 0x00, 0x00, 0x02, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x7F, 0x00, 0x00, 0x02, 0x3D, + 0x02, 0xAB, 0x00, 0x0B, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0xE0, 0xFF, 0xFE, 0xFF, 0x18, 0xFF, 0x89, 0x00, 0x00, 0x00, 0x5D, 0x00, 0x00, + 0x00, 0x02, 0x00, 0xF0, 0x00, 0x6F, 0x00, 0x00, 0xFF, 0xA3, 0x00, 0xBC, 0x00, 0x00, 0x01, 0xEF, + 0x00, 0x00, 0xFD, 0x55, 0x00, 0x00, 0x02, 0x05, 0x00, 0x00, 0xFD, 0xFB, 0x00, 0x00, 0x02, 0xAB, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x73, 0xFF, 0xF7, 0x02, 0x4E, 0x02, 0xC7, 0x00, 0x4C, + 0x00, 0x8D, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x61, 0xFF, 0xE8, 0xFF, 0xE9, 0xFF, 0xEB, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF5, + 0xFF, 0xF6, 0xFF, 0xED, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF5, + 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x02, 0x00, 0x03, 0x00, 0x06, 0x00, 0x07, + 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x11, 0x00, 0x10, 0x00, 0x0B, 0x00, 0x0A, + 0x00, 0x14, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x14, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x17, 0x00, 0x18, + 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x15, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0A, + 0x00, 0x0C, 0x00, 0x11, 0x00, 0x11, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x08, + 0x00, 0x06, 0x00, 0x04, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF6, + 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xEE, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, 0xF6, 0xFF, 0xF5, + 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xEC, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xE8, 0xFF, 0xFD, 0x00, 0x0D, + 0x00, 0x10, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x08, + 0x00, 0x07, 0x00, 0x07, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF7, + 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xF3, 0xFF, 0xF2, + 0xFF, 0xF0, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF7, + 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x03, 0x00, 0x04, 0x00, 0x04, 0x00, 0x07, 0x00, 0x06, 0x00, 0x08, 0x00, 0x0A, + 0x00, 0x09, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x10, 0x00, 0x0D, 0x02, 0xC7, + 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xEF, + 0xFF, 0xED, 0xFF, 0xE8, 0xFF, 0xE7, 0xFF, 0xE4, 0xFF, 0xE0, 0xFF, 0xDF, 0xFF, 0xDE, 0xFF, 0xDB, + 0xFF, 0xDD, 0xFF, 0xDE, 0xFF, 0xD9, 0xFF, 0xDC, 0xFF, 0xDB, 0xFF, 0xDE, 0xFF, 0xE0, 0xFF, 0xE0, + 0xFF, 0xE2, 0xFF, 0xE7, 0xFF, 0xE8, 0xFF, 0xEE, 0xFF, 0xF5, 0xFF, 0xF9, 0xFF, 0xF2, 0xFF, 0xFA, + 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, + 0x00, 0x07, 0x00, 0x04, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, 0x07, 0x00, 0x0B, 0x00, 0x12, + 0x00, 0x16, 0x00, 0x19, 0x00, 0x1E, 0x00, 0x1F, 0x00, 0x21, 0x00, 0x21, 0x00, 0x25, 0x00, 0x25, + 0x00, 0x24, 0x00, 0x24, 0x00, 0x24, 0x00, 0x24, 0x00, 0x22, 0x00, 0x21, 0x00, 0x21, 0x00, 0x1C, + 0x00, 0x1A, 0x00, 0x18, 0x00, 0x14, 0x00, 0x11, 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x05, + 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0xFF, 0xA8, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFA, + 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, 0xEC, 0xFF, 0xE7, + 0xFF, 0xE7, 0xFF, 0xE6, 0xFF, 0xE4, 0xFF, 0xE2, 0xFF, 0xE2, 0xFF, 0xE5, 0xFF, 0xE2, 0xFF, 0xE4, + 0xFF, 0xE6, 0xFF, 0xE7, 0xFF, 0xEB, 0xFF, 0xEC, 0xFF, 0xED, 0xFF, 0xEE, 0xFF, 0xF3, 0xFF, 0xF4, + 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, + 0x00, 0x08, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x12, 0x00, 0x13, 0x00, 0x14, 0x00, 0x15, + 0x00, 0x19, 0x00, 0x19, 0x00, 0x1D, 0x00, 0x1B, 0x00, 0x1B, 0x00, 0x1E, 0x00, 0x1D, 0x00, 0x1C, + 0x00, 0x1D, 0x00, 0x19, 0x00, 0x19, 0x00, 0x15, 0x00, 0x13, 0x00, 0x12, 0x00, 0x0D, 0x00, 0x0F, + 0x00, 0x0A, 0x00, 0x08, 0x00, 0x06, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x71, + 0x00, 0x00, 0x02, 0x3A, 0x02, 0xAE, 0x00, 0x2D, 0x00, 0x4A, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x71, 0x00, 0x67, 0x00, 0x00, 0x00, 0x7D, 0x00, + 0x17, 0x00, 0x14, 0x00, 0x1C, 0x00, 0x18, 0x00, 0x17, 0x00, 0x15, 0x00, 0x0C, 0x00, 0x0A, 0x00, + 0x11, 0x00, 0x08, 0x00, 0x07, 0x00, 0x07, 0x00, 0x06, 0x00, 0x05, 0x00, 0x05, 0x00, 0x04, 0x00, + 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, + 0xFC, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, + 0xF5, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xED, 0xFF, 0xE9, 0xFF, 0xE7, 0xFF, 0xE5, 0xFF, + 0xE4, 0xFF, 0x1C, 0x00, 0x6B, 0x00, 0x00, 0x00, 0x84, 0x00, 0x18, 0x00, 0x12, 0x00, 0x0B, 0x00, + 0x0B, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x08, 0x00, 0x06, 0x00, 0x06, 0x00, 0x04, 0x00, 0x06, 0x00, + 0x04, 0x00, 0x03, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, + 0xF6, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, 0xEC, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x1B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x05, 0x00, 0x07, 0x00, 0x08, 0x00, + 0x0A, 0x00, 0x08, 0x00, 0x07, 0x00, 0x0F, 0x00, 0x08, 0x00, 0x09, 0x00, 0x09, 0x00, 0x0B, 0x00, + 0x0A, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x1A, 0x00, 0x0E, 0x00, + 0x12, 0x00, 0x0D, 0x00, 0x10, 0x00, 0x0C, 0x00, 0x0F, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0A, 0x00, + 0x0B, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, + 0x07, 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0xFE, 0xC8, 0x00, 0xD5, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, + 0xF8, 0xFF, 0xF9, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xEE, 0xFF, 0xEC, 0xFF, 0xF2, 0xFF, 0xEF, 0xFF, + 0xF2, 0xFF, 0xF3, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, + 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x02, 0x00, 0x5F, 0x00, 0x00, 0x02, 0x6A, 0x02, 0xBC, 0x00, 0x52, + 0x00, 0x98, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xF0, 0x00, + 0x7A, 0xFF, 0x8C, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x06, 0x00, 0x05, 0x00, 0x03, 0x00, + 0x01, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, + 0xF4, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, + 0xF1, 0xFF, 0xF0, 0xFF, 0xEC, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xF0, 0xFF, 0xED, 0xFF, + 0xF1, 0xFF, 0xEF, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, + 0xF6, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, + 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, + 0x04, 0x00, 0x06, 0x00, 0x07, 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0B, 0x00, + 0x0B, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x11, 0x00, 0x10, 0x00, + 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x13, 0x00, 0x10, 0x00, 0x12, 0x00, 0x11, 0x00, 0x0E, 0x00, + 0x0F, 0x00, 0x0C, 0xFF, 0xC8, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, + 0xF4, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, + 0xF9, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, + 0xFB, 0xFF, 0xFD, 0xFF, 0xFE, 0x00, 0x01, 0x00, 0x03, 0x00, 0x04, 0x00, 0x06, 0x00, 0x07, 0x00, + 0x09, 0x00, 0x06, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x08, 0x00, + 0x09, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x09, 0x00, + 0x09, 0x00, 0x08, 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x07, 0x00, + 0x06, 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xB9, 0xFF, 0x84, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x9A, 0x00, 0x15, 0x00, 0x17, 0x00, 0x1B, 0x00, 0x1C, 0x00, 0x1F, 0x00, + 0x23, 0x00, 0x2C, 0x00, 0x22, 0x00, 0x24, 0x00, 0x21, 0x00, 0x20, 0x00, 0x1D, 0x00, 0x1B, 0x00, + 0x1B, 0x00, 0x16, 0x00, 0x14, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x08, 0x00, 0x0A, 0x00, + 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, + 0xFB, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, + 0xF2, 0xFF, 0xF0, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xEA, 0xFF, 0xE9, 0xFF, + 0xE9, 0xFF, 0xE9, 0xFF, 0xE9, 0xFF, 0xE7, 0xFF, 0xE6, 0xFF, 0xEA, 0xFF, 0xE9, 0xFF, 0xE8, 0xFF, + 0xE9, 0xFF, 0xE9, 0xFF, 0xEA, 0xFF, 0xEA, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xEF, 0xFF, 0xF0, 0xFF, + 0xF1, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, + 0xFC, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x05, 0x00, 0x07, 0x00, + 0x07, 0x00, 0x09, 0x00, 0x09, 0x00, 0x44, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, + 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x04, 0x00, + 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x09, 0x00, 0x08, 0x00, 0x0C, 0x00, 0x16, 0x00, 0x1A, 0x00, + 0x1C, 0x00, 0x20, 0x00, 0x22, 0x00, 0x25, 0x00, 0x24, 0x00, 0x1E, 0x00, 0x1F, 0x00, 0x1B, 0x00, + 0x17, 0x00, 0x15, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x0F, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, + 0x04, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, + 0xFD, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, + 0xF2, 0xFF, 0xF4, 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xEF, 0xFF, 0xEC, 0xFF, 0xEB, 0xFF, 0xE9, 0xFF, + 0xE7, 0xFF, 0xD5, 0xFF, 0xE4, 0xFF, 0xEB, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xF1, 0x00, 0x59, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x72, 0x00, 0x00, 0x02, 0x44, 0x02, 0xB6, 0x00, 0x27, + 0x00, 0x45, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xDB, 0x00, 0x53, 0x00, 0x9E, + 0x00, 0x78, 0xFF, 0x4C, 0x00, 0x14, 0x00, 0x12, 0x00, 0x11, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0E, + 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x06, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFD, + 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF5, + 0xFF, 0xF5, 0xFF, 0xF1, 0xFF, 0xF4, 0xFF, 0xEF, 0xFF, 0xF2, 0xFF, 0xEE, 0xFF, 0xF1, 0xFF, 0xEC, + 0xFF, 0xF0, 0xFF, 0xEB, 0xFF, 0x38, 0x00, 0x00, 0x00, 0x69, 0x00, 0x07, 0x00, 0x00, 0x00, 0x61, + 0x00, 0x19, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x06, 0x00, 0x07, + 0x00, 0x05, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFD, + 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF3, + 0xFF, 0xEE, 0xFF, 0xEF, 0xFF, 0xEB, 0x01, 0x20, 0x00, 0x00, 0xFE, 0xE0, 0x00, 0x00, 0x01, 0x35, + 0x00, 0x05, 0x00, 0x07, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x13, + 0x00, 0x11, 0x00, 0x15, 0x00, 0x16, 0x00, 0x17, 0x00, 0x19, 0x00, 0x14, 0x00, 0x17, 0x00, 0x15, + 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, + 0x00, 0x07, 0x00, 0x07, 0x00, 0x05, 0x00, 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x00, 0xFD, 0x4A, 0x00, 0x00, 0x01, 0x80, 0x00, 0xD6, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xF6, + 0xFF, 0xF8, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xEC, 0xFF, 0xEE, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xF4, + 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFE, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x8B, 0xFF, 0xF7, 0x02, 0x2C, 0x02, 0xCA, 0x00, 0xA9, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x5A, 0x00, 0x14, + 0x00, 0x16, 0x00, 0x16, 0x00, 0x11, 0x00, 0x14, 0x00, 0x11, 0x00, 0x12, 0x00, 0x0E, 0x00, 0x0F, + 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x07, 0x00, 0x05, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFF, + 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, 0xF4, + 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xEC, 0xFF, 0xD7, 0xFF, 0xB3, 0xFF, 0xF2, 0xFF, 0xF6, 0xFF, 0xF2, + 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x05, 0x00, 0x07, 0x00, 0x09, 0x00, 0x08, + 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x09, 0x00, 0x0C, + 0x00, 0x08, 0x00, 0x08, 0x00, 0x07, 0x00, 0x09, 0x00, 0x05, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, + 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFB, + 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xEE, + 0xFF, 0xEE, 0xFF, 0xEB, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, 0xE9, 0xFF, 0xEA, 0xFF, 0xEB, 0xFF, 0xE9, + 0xFF, 0xED, 0xFF, 0xEE, 0xFF, 0xEF, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xFA, + 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x06, 0x00, 0x07, + 0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0D, 0x00, 0x17, 0x00, 0x1C, 0x00, 0x23, 0x00, 0x3A, + 0x00, 0x1C, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x08, 0x00, 0x07, 0x00, 0x05, + 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFA, + 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, 0xED, + 0xFF, 0xEE, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFB, + 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9C, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x05, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0D, + 0x00, 0x0E, 0x00, 0x12, 0x00, 0x10, 0x00, 0x14, 0x00, 0x15, 0x00, 0x16, 0x00, 0x15, 0x00, 0x16, + 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x03, 0x00, 0x05, 0x00, 0x06, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0C, + 0x00, 0x0B, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x10, 0x00, 0x11, 0x00, 0x12, 0x00, 0x13, 0x00, 0x14, + 0x00, 0x12, 0x00, 0x16, 0x00, 0x14, 0x00, 0x13, 0x00, 0x12, 0x00, 0x10, 0x00, 0x11, 0x00, 0x0E, + 0x00, 0x0F, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x16, 0x00, 0x27, 0x00, 0x08, + 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x0B, + 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x06, 0x00, 0x06, + 0x00, 0x06, 0x00, 0x03, 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFE, + 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xF8, + 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF5, 0x00, 0x00, 0x00, 0x14, 0x00, 0x11, + 0x00, 0x11, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x11, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0B, + 0x00, 0x09, 0x00, 0x08, 0x00, 0x07, 0x00, 0x05, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFE, 0xFF, 0xFD, + 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xED, + 0xFF, 0xEC, 0xFF, 0xEE, 0xFF, 0xE9, 0xFF, 0xEB, 0xFF, 0xEA, 0xFF, 0xEC, 0xFF, 0xF3, 0xFF, 0xF1, + 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xEF, 0xFF, 0xEE, + 0xFF, 0xEE, 0xFF, 0xE3, 0xFF, 0xF0, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF5, + 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xF3, + 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x02, 0x00, 0x04, 0x00, 0x03, 0x00, 0x04, 0x00, 0x06, 0x00, 0x05, 0x00, 0x06, + 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x08, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x0B, + 0x00, 0x0A, 0x00, 0x00, 0xFF, 0xE7, 0xFF, 0xEE, 0xFF, 0xEF, 0xFF, 0xED, 0xFF, 0xEF, 0xFF, 0xEF, + 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xFA, + 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x7E, 0x00, 0x00, 0x02, 0x3E, 0x02, 0xC2, 0x00, 0x07, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x8F, 0x00, 0xAF, 0x00, 0x00, + 0xFE, 0x40, 0x00, 0x00, 0x00, 0xAF, 0x00, 0x00, 0x00, 0x62, 0x02, 0x60, 0x00, 0x00, 0x00, 0x62, + 0x00, 0x00, 0xFF, 0x9E, 0x00, 0x00, 0xFD, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x71, + 0xFF, 0xF7, 0x02, 0x42, 0x02, 0xBE, 0x00, 0x4F, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x71, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x02, 0x00, 0x05, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, + 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x10, 0x00, 0x11, 0x00, 0x0F, 0x00, 0x12, + 0x00, 0x15, 0x00, 0x15, 0x00, 0x13, 0x00, 0x12, 0x00, 0x16, 0x00, 0x14, 0x00, 0x13, 0x00, 0x0F, + 0x00, 0x10, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0A, + 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x06, 0x00, 0x05, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, + 0xFF, 0x97, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFC, + 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF5, + 0xFF, 0xF8, 0xFF, 0xE9, 0xFF, 0xE7, 0xFF, 0xE6, 0xFF, 0xE9, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF9, + 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFD, + 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x02, 0xBE, 0xFE, 0x22, 0xFF, 0xE5, + 0xFF, 0xE9, 0xFF, 0xE7, 0xFF, 0xEC, 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF4, + 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, + 0xFF, 0xFD, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x04, + 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0E, + 0x00, 0x0E, 0x00, 0x10, 0x00, 0x10, 0x00, 0x15, 0x00, 0x19, 0x00, 0x17, 0x00, 0x1B, 0x01, 0xDE, + 0x00, 0x00, 0xFE, 0x2D, 0xFF, 0xE1, 0xFF, 0xF0, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF7, + 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFC, + 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x04, 0x00, 0x03, 0x00, 0x04, 0x00, 0x03, + 0x00, 0x05, 0x00, 0x05, 0x00, 0x07, 0x00, 0x07, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x0B, + 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x10, 0x00, 0x1F, 0x01, 0xD3, 0x00, 0x00, 0x00, 0x01, 0x00, 0x6D, + 0x00, 0x00, 0x02, 0x4F, 0x02, 0xAB, 0x00, 0x07, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x5F, 0xFF, 0xFF, 0xFF, 0x70, 0xFF, 0x9F, 0x00, 0xAD, 0x00, 0x86, 0x00, 0xAF, + 0xFF, 0x9F, 0x00, 0x67, 0x00, 0x00, 0x02, 0x44, 0x00, 0x00, 0xFD, 0x55, 0x00, 0x00, 0x02, 0xAB, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x6C, 0x00, 0x00, 0x02, 0x55, 0x02, 0xB4, 0x00, 0x27, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x3A, 0xFF, 0xBB, 0xFF, 0xFE, + 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, + 0xFF, 0x99, 0x00, 0x00, 0x00, 0x03, 0x00, 0x05, 0x00, 0x07, 0x00, 0x07, 0x00, 0x33, 0x00, 0x70, + 0x00, 0x3B, 0x00, 0x02, 0x00, 0x3A, 0x00, 0x70, 0x00, 0x34, 0x00, 0x06, 0x00, 0x07, 0x00, 0x05, + 0x00, 0x03, 0x00, 0x00, 0xFF, 0x99, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFB, + 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xFE, 0xFF, 0xBB, 0x01, 0xAA, 0xFF, 0x0B, 0x00, 0x00, + 0x00, 0x46, 0x00, 0x45, 0x00, 0x44, 0x00, 0x43, 0x00, 0x3B, 0x00, 0x39, 0x00, 0x2F, 0x00, 0x4A, + 0x00, 0x00, 0xFF, 0xB0, 0xFF, 0xAE, 0xFF, 0xA3, 0xFF, 0xAF, 0xFF, 0xCE, 0xFE, 0xCE, 0x00, 0x00, + 0x00, 0xF1, 0x00, 0x00, 0xFF, 0x0F, 0x00, 0x00, 0x01, 0x34, 0x00, 0x30, 0x00, 0x51, 0x00, 0x5D, + 0x00, 0x52, 0x00, 0x50, 0x00, 0x00, 0xFF, 0xB6, 0xFF, 0xD1, 0xFF, 0xC7, 0xFF, 0xC5, 0xFF, 0xBD, + 0xFF, 0xBC, 0xFF, 0xBB, 0xFF, 0xBA, 0x00, 0x00, 0x00, 0xF5, 0x00, 0x00, 0x00, 0x01, 0x00, 0x67, + 0x00, 0x00, 0x02, 0x48, 0x02, 0xAB, 0x00, 0x0B, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x58, 0xFF, 0x85, 0xFF, 0x8A, 0x00, 0xBB, 0xFF, 0x45, + 0x00, 0x76, 0x00, 0x7B, 0x00, 0x79, 0x00, 0x77, 0xFF, 0x4D, 0x00, 0xB3, 0xFF, 0x89, 0x01, 0xBF, + 0x00, 0xEC, 0x00, 0x00, 0xFE, 0xB5, 0xFE, 0xA0, 0x00, 0x00, 0x01, 0x03, 0xFE, 0xFD, 0x00, 0x00, + 0x01, 0x60, 0x01, 0x4B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x7B, 0x00, 0x00, 0x02, 0x41, + 0x02, 0xB4, 0x00, 0x08, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x5E, 0xFF, 0x86, 0xFF, 0x97, 0x00, 0xB4, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0xB3, 0xFF, + 0x97, 0x01, 0xB0, 0x01, 0x04, 0x00, 0x00, 0xFE, 0x8F, 0xFE, 0xBD, 0x00, 0x00, 0x01, 0x43, 0x01, + 0x71, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x97, 0x00, 0x00, 0x02, 0x25, 0x02, 0xAE, 0x00, 0x09, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x0B, 0x00, 0xFA, + 0x00, 0x00, 0xFE, 0x9B, 0x00, 0x00, 0x00, 0xF8, 0xFE, 0xFF, 0x00, 0x00, 0x01, 0x8E, 0x00, 0x00, + 0x00, 0x5F, 0x01, 0xFF, 0x00, 0x50, 0x00, 0x00, 0xFF, 0xA1, 0x00, 0x00, 0xFD, 0xF9, 0xFF, 0xB8, + 0x00, 0x00, 0x00, 0x5F, 0x00, 0x01, 0x00, 0xC1, 0x00, 0x00, 0x01, 0xF3, 0x02, 0xA6, 0x00, 0x07, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x17, 0x00, 0xDC, 0x00, 0x00, + 0xFE, 0xCE, 0x00, 0x00, 0x01, 0x32, 0x00, 0x00, 0xFF, 0x24, 0x02, 0x50, 0x00, 0x00, 0x00, 0x56, + 0x00, 0x00, 0xFD, 0x5A, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xA6, + 0x00, 0x00, 0x02, 0x0F, 0x02, 0xA6, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0xA6, + 0x01, 0x0E, 0x00, 0x5B, 0xFE, 0xF4, 0x02, 0xA6, 0xFD, 0x5A, 0x00, 0x00, 0x02, 0xA6, 0x00, 0x00, + 0x00, 0x01, 0x00, 0xCF, 0x00, 0x00, 0x01, 0xF8, 0x02, 0xA6, 0x00, 0x07, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xA5, 0xFF, 0x2A, 0x00, 0x00, 0x01, 0x29, 0x00, 0x00, + 0xFE, 0xD7, 0x00, 0x00, 0x00, 0xD6, 0x00, 0x56, 0x00, 0x00, 0xFF, 0xAA, 0x00, 0x00, 0x02, 0xA6, + 0x00, 0x00, 0xFF, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x8F, 0x01, 0x49, 0x02, 0x2E, + 0x02, 0xA6, 0x00, 0x06, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x71, 0xFF, + 0xD9, 0xFF, 0x45, 0x00, 0x35, 0x00, 0x9A, 0x00, 0x99, 0x00, 0x37, 0x02, 0xA6, 0x00, 0x00, 0xFE, + 0xD0, 0xFF, 0xD3, 0x00, 0xAF, 0xFF, 0x51, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x7A, + 0xFF, 0x37, 0x02, 0x35, 0xFF, 0xAF, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x7A, + 0x01, 0xBB, 0x00, 0x00, 0xFE, 0x45, 0xFF, 0x37, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x01, 0x00, 0x02, 0x10, 0x01, 0xB6, 0x02, 0xA6, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x6A, 0xFF, 0x96, 0x00, 0x66, 0x00, 0x50, 0x02, 0xA6, 0x00, 0x00, 0xFF, 0x6A, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x91, 0xFF, 0xF7, 0x02, 0x27, 0x02, 0x09, 0x00, 0x2C, + 0x00, 0x9C, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0xC8, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, + 0xF9, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, + 0xF2, 0xFF, 0xF2, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, + 0xF9, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x02, 0x00, 0x05, 0x00, 0x09, 0x00, 0x06, 0x00, + 0x07, 0x00, 0x08, 0x00, 0x08, 0x00, 0x12, 0x00, 0x16, 0x00, 0x16, 0x00, 0x66, 0x00, 0x62, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, + 0xF4, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, 0xE8, 0xFF, + 0xE7, 0xFF, 0xEA, 0xFF, 0xEB, 0xFF, 0xED, 0xFF, 0xF1, 0xFF, 0xEE, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, + 0xF4, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, + 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x62, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x04, 0x00, + 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x11, 0x00, + 0x10, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0F, 0x00, 0x08, 0x00, 0x07, 0x00, 0x09, 0x00, 0x04, 0x00, + 0x04, 0x00, 0x05, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0xFF, 0x9F, 0xFF, 0xEA, 0xFF, 0xED, 0xFF, + 0xE7, 0xFF, 0xE9, 0xFF, 0xE9, 0xFF, 0xED, 0xFF, 0xEE, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, + 0xFA, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x0F, 0x00, + 0x0D, 0x00, 0x10, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x0C, 0x00, + 0x0E, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x09, 0x00, 0xEB, 0xFF, 0xE8, 0xFF, + 0xF3, 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, + 0xF7, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, + 0x07, 0x00, 0x09, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x07, 0x00, 0x07, 0x00, + 0x09, 0x00, 0x06, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x06, 0x00, 0x04, 0x00, 0x04, 0x00, + 0x06, 0x00, 0x04, 0x00, 0x01, 0xFF, 0x15, 0x00, 0x00, 0x01, 0x47, 0x00, 0x1E, 0x00, 0x1C, 0x00, + 0x16, 0x00, 0x13, 0x00, 0x12, 0x00, 0x0F, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x09, 0x00, + 0x06, 0x00, 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0xFF, + 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, + 0xF7, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF1, 0xFF, 0xF4, 0x00, + 0x00, 0x00, 0x07, 0x00, 0x09, 0x00, 0x06, 0x00, 0x07, 0x00, 0x06, 0x00, 0x06, 0x00, 0x04, 0x00, + 0x04, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFB, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF2, 0xFF, 0xEF, 0xFF, + 0xEB, 0xFF, 0xE1, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, + 0xF7, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xEE, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xED, 0xFF, + 0xF3, 0xFF, 0xF5, 0xFF, 0xE9, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xF7, 0xFF, + 0xF5, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, + 0xFA, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x04, 0x00, 0x06, 0x00, 0x06, 0x00, 0x08, 0x00, + 0x08, 0x00, 0x08, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x6B, 0xFF, 0xF7, 0x02, 0x3C, + 0x02, 0xCD, 0x00, 0x40, 0x00, 0x86, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x62, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0A, 0x00, + 0x09, 0x00, 0x08, 0x00, 0x08, 0x00, 0x05, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, + 0xF8, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, + 0xF2, 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, + 0xF7, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, + 0xFF, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x06, 0x00, 0x06, 0x00, 0x08, 0x00, + 0x08, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x10, 0x00, + 0x0E, 0xFF, 0x0C, 0x00, 0x65, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0F, 0x00, + 0x0E, 0x00, 0x11, 0x00, 0x0E, 0x00, 0x12, 0x00, 0x12, 0x00, 0x10, 0x00, 0x10, 0x00, 0x14, 0x00, + 0x0F, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x11, 0x00, 0x0C, 0x00, 0x0F, 0x00, 0x0B, 0x00, 0x0D, 0x00, + 0x09, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x09, 0x00, 0x07, 0x00, 0x07, 0x00, 0x05, 0x00, 0x04, 0x00, + 0x04, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, + 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, + 0xF4, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xEE, 0xFF, 0xF1, 0xFF, + 0xEC, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, 0xF2, 0xFF, + 0xF3, 0xFF, 0xF7, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xFD, 0x00, 0x00, 0xFF, 0x9B, 0x01, 0xA5, 0x00, + 0x00, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, + 0xF3, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, 0xEB, 0xFF, + 0xED, 0xFF, 0xF1, 0xFF, 0xEE, 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, + 0xF6, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, + 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x06, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0B, 0x00, + 0x0C, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x11, 0x00, 0x12, 0x00, 0x12, 0x00, 0x13, 0x00, 0x13, 0x00, + 0x13, 0x00, 0x13, 0x00, 0x12, 0x00, 0x12, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0C, 0x00, + 0x0A, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x06, 0x00, 0x06, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0xFE, + 0x5B, 0x00, 0x00, 0x00, 0x32, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, + 0xFA, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, + 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0B, 0x00, + 0x0E, 0x00, 0x0C, 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x11, 0x00, 0x0E, 0x00, 0x12, 0x00, 0x12, 0x00, + 0x13, 0x00, 0x13, 0x00, 0x10, 0x00, 0x16, 0x00, 0x13, 0x00, 0x13, 0x00, 0x12, 0x00, 0x12, 0x00, + 0x12, 0x00, 0x11, 0x00, 0x10, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0B, 0x00, 0x0C, 0x00, + 0x0A, 0x00, 0x08, 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, + 0xF9, 0xFF, 0xF4, 0xFF, 0xF2, 0x00, 0x00, 0x01, 0x16, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x9E, + 0xFF, 0xF7, 0x02, 0x1E, 0x02, 0x14, 0x00, 0x86, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x02, 0x1E, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, + 0xF8, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, + 0xF1, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xEF, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, + 0xF3, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, + 0xF8, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, + 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, + 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, 0x07, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, + 0x0B, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x0E, 0x00, 0x0B, 0x00, 0x0F, 0x00, 0x0D, 0x00, + 0x10, 0x00, 0x0E, 0x00, 0x12, 0x00, 0x12, 0x00, 0x12, 0x00, 0x12, 0x00, 0x11, 0x00, 0x10, 0x00, + 0x10, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x09, 0x00, 0x09, 0x00, 0x07, 0x00, 0x06, 0x00, 0x04, 0x00, + 0x02, 0x00, 0x01, 0xFF, 0x9F, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, + 0xF6, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, + 0xF7, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, + 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x06, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0D, 0x00, + 0x0B, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x11, 0x00, 0x10, 0x00, 0x0F, 0x00, + 0x08, 0x00, 0x05, 0x00, 0x06, 0x00, 0x04, 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, 0x01, + 0x7E, 0x00, 0x0E, 0x00, 0x11, 0x00, 0x0D, 0x00, 0x0F, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x09, 0x00, + 0x0B, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x08, 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, + 0x02, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, + 0xF9, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, + 0xF0, 0xFF, 0xF1, 0xFF, 0xEE, 0xFF, 0xF1, 0xFF, 0xEC, 0xFF, 0xF0, 0xFF, 0xEC, 0xFF, 0xEE, 0xFF, + 0xEC, 0xFF, 0xEB, 0xFF, 0xEB, 0xFF, 0xEB, 0xFF, 0xEF, 0xFF, 0xEC, 0xFF, 0xED, 0xFF, 0xED, 0xFF, + 0xF1, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, + 0xF7, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x04, 0x00, 0x06, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0B, 0x00, + 0x0A, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x11, 0x00, 0x12, 0x00, + 0x0F, 0x00, 0x00, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, + 0xF8, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x04, 0x00, 0x04, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0E, 0x00, + 0x10, 0x00, 0x10, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x17, 0x00, 0x14, 0x00, 0x15, 0x00, + 0x14, 0x00, 0x10, 0x00, 0x12, 0x00, 0x11, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x0B, 0x00, 0x08, 0x00, + 0x07, 0x00, 0x04, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, + 0xFC, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xFA, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x82, 0xFF, 0xF7, 0x02, 0x4E, 0x02, 0xC7, 0x00, 0x40, 0x00, 0x86, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x5B, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, + 0xF2, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, + 0xFA, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, + 0x03, 0x00, 0x06, 0x00, 0x05, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x09, 0x00, 0x0B, 0x00, + 0x0A, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x11, 0x00, 0x0C, 0x00, + 0x0E, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x08, 0x00, 0x06, 0x00, + 0x06, 0x00, 0x04, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, + 0xFC, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, + 0xF6, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF0, 0xFF, 0xF3, 0x00, 0x8A, 0x00, 0x65, 0x00, 0x00, 0xFF, + 0x9B, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, 0xF7, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, + 0xEF, 0xFF, 0xF1, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xF0, 0xFF, + 0xEE, 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, + 0xF6, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, + 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, + 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0C, 0x00, + 0x0B, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x14, 0x00, + 0x11, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x11, 0x00, 0x0D, 0x00, + 0x10, 0x00, 0x0E, 0x00, 0x0B, 0x00, 0x45, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x06, 0x00, + 0x06, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x11, 0x00, + 0x12, 0x00, 0x13, 0x00, 0x12, 0x00, 0x13, 0x00, 0x12, 0x00, 0x13, 0x00, 0x12, 0x00, 0x0F, 0x00, + 0x12, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x09, 0x00, 0x06, 0x00, + 0x06, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, + 0xFB, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF0, 0xFF, + 0xF2, 0xFF, 0xEE, 0xFF, 0xF0, 0xFF, 0xED, 0xFF, 0xEA, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, 0xED, 0xFF, + 0xEE, 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, + 0xFA, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xBB, 0x00, 0x00, 0x02, 0xC7, 0x00, 0x00, 0xFE, + 0xEE, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x06, 0x00, + 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, + 0xFB, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, + 0xF4, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, + 0xED, 0xFF, 0xED, 0xFF, 0xED, 0xFF, 0xED, 0xFF, 0xED, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xEF, 0xFF, + 0xF0, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, + 0xF8, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x08, 0x00, 0x07, 0x00, 0x0A, 0x00, + 0x0B, 0x00, 0x0A, 0x00, 0x00, 0x02, 0x00, 0x85, 0xFF, 0xF7, 0x02, 0x32, 0x02, 0x0F, 0x00, 0x70, + 0x00, 0x91, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x32, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, + 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xEE, 0xFF, 0xEC, + 0xFF, 0xEA, 0xFF, 0xEB, 0xFF, 0xE6, 0xFF, 0xE8, 0xFF, 0xF2, 0xFF, 0xEF, 0xFF, 0xF3, 0xFF, 0xF0, + 0xFF, 0xF5, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF6, + 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xFB, + 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, + 0x00, 0x02, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x07, 0x00, 0x08, + 0x00, 0x09, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, + 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x10, 0x00, 0x11, 0x00, 0x13, 0x00, 0x12, + 0x00, 0x13, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0A, + 0x00, 0x09, 0x00, 0x07, 0x00, 0x07, 0x00, 0x06, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, + 0xFF, 0x9F, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF7, + 0xFF, 0xF3, 0xFF, 0xE7, 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF6, + 0xFF, 0xF1, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFB, + 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, + 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, 0x05, 0x00, 0x09, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x0A, + 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0A, 0x00, 0x0D, 0x00, 0x0C, + 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x06, 0x00, 0x08, 0x00, 0x05, 0x00, 0x06, + 0x00, 0x04, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0xEE, 0x00, 0x2B, 0x00, 0x1C, + 0x00, 0x1F, 0x00, 0x19, 0x00, 0x1B, 0x00, 0x17, 0x00, 0x14, 0x00, 0x13, 0x00, 0x11, 0x00, 0x0F, + 0x00, 0x0D, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x05, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF7, + 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xEF, + 0xFF, 0xF1, 0xFF, 0xED, 0xFF, 0xF0, 0xFF, 0xEC, 0xFF, 0xEC, 0xFF, 0xEA, 0xFF, 0xEB, 0xFF, 0xED, + 0xFF, 0xEE, 0xFF, 0xEC, 0xFF, 0xEF, 0xFF, 0xED, 0xFF, 0xEE, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF3, + 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF8, + 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, 0x06, + 0x00, 0x06, 0x00, 0x08, 0x00, 0x09, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0A, + 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x00, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFC, + 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, + 0x00, 0x03, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x08, 0x00, 0x09, 0x00, 0x07, 0x00, 0x0A, + 0x00, 0x08, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x13, 0x00, 0x18, 0x00, 0x16, 0x00, 0x5A, 0x00, 0x0A, + 0x00, 0x0E, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x07, 0x00, 0x09, 0x00, 0x06, + 0x00, 0x07, 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF7, + 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF2, 0xFF, 0xF6, 0x00, 0x00, + 0x00, 0x01, 0x00, 0xB1, 0x00, 0x00, 0x02, 0x08, 0x02, 0xC8, 0x00, 0x28, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x8B, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x06, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0D, 0x00, 0x0C, 0x00, + 0x11, 0x00, 0x11, 0x00, 0x20, 0x00, 0x00, 0xFF, 0xCD, 0xFF, 0xF2, 0xFF, 0xEC, 0xFF, 0xEB, 0xFF, + 0xED, 0xFF, 0xEF, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, + 0xF9, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00, 0x00, 0x7F, 0x00, + 0x00, 0x00, 0x5B, 0x00, 0x00, 0x00, 0x7D, 0x00, 0x00, 0x01, 0xEA, 0x00, 0x28, 0x00, 0x1C, 0x00, + 0x0B, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x08, 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x05, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0xFF, + 0xFC, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, + 0xEF, 0xFF, 0xEB, 0xFF, 0xE8, 0xFF, 0xE7, 0xFF, 0xE8, 0xFF, 0xDA, 0x00, 0x00, 0xFF, 0xAC, 0x00, + 0x00, 0xFE, 0x6A, 0x00, 0x00, 0x01, 0x96, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x02, 0x00, 0x8B, + 0xFF, 0x58, 0x02, 0x3E, 0x01, 0xF0, 0x00, 0x40, 0x00, 0xC6, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x5D, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, + 0xF7, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, + 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, + 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x09, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0B, 0x00, + 0x0C, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x07, 0x00, 0x06, 0x00, 0x06, 0x00, 0x04, 0x00, + 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, + 0xFB, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, + 0xF5, 0xFF, 0xF3, 0xFF, 0xF2, 0x00, 0xDD, 0xFF, 0x9C, 0x00, 0x00, 0xFF, 0xFE, 0x00, 0x01, 0xFF, + 0xF8, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, + 0xF1, 0xFF, 0xEE, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, + 0xF3, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, + 0xF9, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x04, 0x00, 0x06, 0x00, 0x05, 0x00, + 0x08, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0C, 0x00, + 0x0F, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x10, 0x00, 0x11, 0x00, 0x11, 0x00, 0x12, 0x00, + 0x0E, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x1D, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, + 0xF9, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xEE, 0xFF, 0xEC, 0xFF, 0xEC, 0xFF, 0xF0, 0xFF, + 0xF3, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, + 0x00, 0xFF, 0x9C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x06, 0x00, 0x06, 0x00, 0x09, 0x00, + 0x0A, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x13, 0x00, + 0x13, 0x00, 0x15, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x12, 0x00, 0x12, 0x00, 0x11, 0x00, + 0x0F, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x07, 0x00, + 0x07, 0x00, 0x06, 0x00, 0x06, 0x00, 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0x00, + 0x02, 0x00, 0x64, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, + 0x07, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0F, 0x00, 0x11, 0x00, + 0x10, 0x00, 0x13, 0x00, 0x13, 0x00, 0x12, 0x00, 0x13, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0E, 0x00, + 0x0D, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x07, 0x00, 0x07, 0x00, 0x06, 0x00, 0x05, 0x00, 0x03, 0x00, + 0x03, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, + 0xFA, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, + 0xF0, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, 0xED, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, + 0xF2, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, + 0xFE, 0xFF, 0xFF, 0x01, 0x83, 0x00, 0x00, 0xFF, 0xD4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, + 0x0A, 0x00, 0x07, 0x00, 0x06, 0x00, 0x07, 0x00, 0x05, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, + 0xF8, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, + 0xF3, 0xFF, 0xF2, 0xFF, 0xEE, 0xFF, 0xF1, 0xFF, 0xED, 0xFF, 0xF0, 0xFF, 0xEC, 0xFF, 0xED, 0xFF, + 0xF0, 0xFF, 0xED, 0xFF, 0xF1, 0xFF, 0xEE, 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, + 0xF4, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, + 0xFB, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x09, 0x00, 0x15, 0xFF, 0xA9, 0xFF, 0xED, 0xFF, + 0xF2, 0xFF, 0xF2, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, + 0xFD, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x07, 0x00, + 0x00, 0xFF, 0xEE, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, + 0xF8, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, + 0x05, 0x00, 0x06, 0x00, 0x05, 0x00, 0x07, 0x00, 0x07, 0x00, 0x09, 0x00, 0x07, 0x00, 0x0B, 0x00, + 0x09, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x1E, 0x00, + 0x00, 0x01, 0x00, 0x94, 0x00, 0x00, 0x02, 0x1F, 0x02, 0xCD, 0x00, 0x38, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xF8, 0xFF, 0x9C, 0x00, 0x00, 0x00, 0x64, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x06, 0x00, 0x07, 0x00, 0x06, 0x00, + 0x05, 0x00, 0x0B, 0x00, 0x07, 0x00, 0x07, 0x00, 0x11, 0x00, 0x14, 0x00, 0x13, 0x00, 0x0F, 0x00, + 0x10, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x07, 0x00, 0x06, 0x00, 0x04, 0x00, 0x03, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, + 0xFC, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, + 0xF2, 0xFF, 0xF0, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, 0xEC, 0xFF, 0xEB, 0xFF, 0xEE, 0xFF, 0xEB, 0xFF, + 0xEE, 0xFF, 0xED, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xF3, 0x02, 0xCD, 0x00, 0x00, 0xFD, 0x33, 0x00, + 0x00, 0x01, 0x15, 0x00, 0x13, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x12, 0x00, 0x0D, 0x00, + 0x08, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x03, 0x00, 0x05, 0x00, 0x04, 0x00, 0x01, 0xFF, + 0xFF, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, + 0xEB, 0xFF, 0xEA, 0xFE, 0xCB, 0x00, 0x00, 0x01, 0x29, 0x00, 0x1E, 0x00, 0x10, 0x00, 0x0F, 0x00, + 0x0F, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x10, 0x00, 0x0B, 0x00, + 0x0C, 0x00, 0x09, 0x00, 0x07, 0x00, 0x06, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, 0xFF, + 0xFC, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF4, 0x00, 0x00, 0x02, 0x00, 0xEB, + 0x00, 0x00, 0x01, 0xE3, 0x02, 0xDA, 0x00, 0x05, 0x00, 0x09, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xEB, 0x00, 0x00, 0x00, 0x97, 0x00, 0x00, 0x00, 0x61, + 0x00, 0x00, 0xFF, 0x74, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x01, 0xEA, 0xFF, 0xA4, 0x00, 0x00, + 0xFE, 0x72, 0x00, 0x00, 0x01, 0xEA, 0x00, 0xF0, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x02, 0x00, 0xE3, 0xFF, 0x59, 0x01, 0xE5, 0x02, 0xC9, 0x00, 0x23, 0x00, 0x27, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1E, 0x00, 0x00, 0x00, 0x6B, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xFA, + 0xFF, 0xF8, 0xFF, 0xF4, 0xFF, 0xEF, 0xFF, 0x9F, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x1F, 0x00, 0x13, + 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x08, 0x00, 0x08, + 0x00, 0x08, 0x00, 0x08, 0x00, 0x06, 0x00, 0x06, 0x00, 0x04, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, + 0xFF, 0x82, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x01, 0xE4, 0xFF, 0xA6, 0x00, 0x00, 0xFE, 0x8F, + 0xFF, 0xE0, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFB, + 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xA5, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, + 0x00, 0x03, 0x00, 0x03, 0x00, 0x04, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x07, 0x00, 0x09, + 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x11, 0x00, 0x17, 0x00, 0x19, 0x00, 0x1B, 0x01, 0xD8, + 0x00, 0xE5, 0xFF, 0x85, 0x00, 0x00, 0x00, 0x7B, 0x00, 0x01, 0x00, 0x7D, 0x00, 0x00, 0x02, 0x2E, + 0x02, 0xC2, 0x00, 0x0A, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0xE0, 0xFF, 0x9D, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0xCE, 0x00, 0x80, 0xFF, + 0x06, 0x00, 0xE5, 0xFF, 0x7E, 0xFF, 0x49, 0x02, 0xC2, 0x00, 0x00, 0xFD, 0x3E, 0x00, 0x00, 0x00, + 0xE3, 0xFF, 0x1D, 0x00, 0x00, 0x01, 0x13, 0x00, 0xD3, 0x00, 0x00, 0xFF, 0x52, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0xD4, 0x00, 0x00, 0x01, 0xD3, 0x02, 0xDE, 0x00, 0x23, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x31, 0xFF, 0xA3, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x03, + 0x00, 0x05, 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0D, + 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x11, 0x00, 0x0F, 0x00, 0x1B, 0x00, 0x57, 0x00, 0x00, 0xFF, 0xC0, + 0xFF, 0xEA, 0xFF, 0xF1, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFB, + 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x02, 0xDE, 0x00, 0x00, 0xFD, 0xF4, + 0xFF, 0xE7, 0xFF, 0xE8, 0xFF, 0xEA, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, 0xEF, 0xFF, 0xF0, 0xFF, 0xF3, + 0xFF, 0xF3, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFD, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, + 0x00, 0x05, 0x00, 0x07, 0x00, 0x07, 0x00, 0x08, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0F, 0x00, 0x0E, + 0x00, 0x13, 0x00, 0x00, 0x00, 0x01, 0x00, 0x72, 0x00, 0x00, 0x02, 0x4A, 0x02, 0x02, 0x00, 0x59, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xD1, 0xFF, 0xA1, + 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, + 0x00, 0x05, 0x00, 0x04, 0x00, 0x05, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x07, 0x00, 0x09, + 0x00, 0x05, 0x00, 0x06, 0x00, 0x04, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x05, + 0x00, 0x08, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x09, 0x00, 0x05, 0x00, 0x06, + 0x00, 0x05, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFB, + 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF6, + 0xFF, 0xEF, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xF7, + 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF0, + 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF8, + 0x01, 0xF9, 0x00, 0x00, 0xFE, 0x07, 0x00, 0x00, 0x01, 0x50, 0x00, 0x17, 0x00, 0x11, 0x00, 0x0E, + 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x04, 0x00, 0x03, 0x00, 0x05, 0x00, 0x03, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF6, + 0xFF, 0xF0, 0xFE, 0x8C, 0x00, 0x00, 0x01, 0x56, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0D, 0x00, 0x0C, + 0x00, 0x0B, 0x00, 0x07, 0x00, 0x08, 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, + 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xE9, 0xFE, 0x8C, + 0x00, 0x00, 0x01, 0x74, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x16, 0x00, 0x0C, 0x00, 0x0B, + 0x00, 0x0B, 0x00, 0x07, 0x00, 0x09, 0x00, 0x06, 0x00, 0x05, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, + 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF8, + 0xFF, 0xF7, 0xFF, 0xF7, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x05, 0x00, 0x05, 0x00, 0x03, + 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF9, + 0xFF, 0xF5, 0xFF, 0xF7, 0x00, 0x01, 0x00, 0x77, 0x00, 0x00, 0x02, 0x3C, 0x02, 0x00, 0x00, 0x41, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x00, 0xDF, 0xFF, 0x98, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x07, + 0x00, 0x09, 0x00, 0x07, 0x00, 0x09, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x0D, 0x00, 0x0A, + 0x00, 0x0E, 0x00, 0x0B, 0x00, 0x12, 0x00, 0x13, 0x00, 0x0E, 0x00, 0x07, 0x00, 0x07, 0x00, 0x0A, + 0x00, 0x05, 0x00, 0x05, 0x00, 0x06, 0x00, 0x05, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x68, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, + 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF3, 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xED, 0xFF, 0xEC, 0xFF, 0xEB, + 0xFF, 0xE8, 0xFF, 0xE8, 0xFF, 0xEA, 0xFF, 0xE9, 0xFF, 0xEA, 0xFF, 0xEA, 0xFF, 0xEC, 0xFF, 0xF0, + 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xFE, 0x01, 0xF7, 0x00, 0x00, 0xFE, 0x09, 0x00, 0x00, + 0x01, 0x11, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x11, + 0x00, 0x09, 0x00, 0x08, 0x00, 0x07, 0x00, 0x05, 0x00, 0x06, 0x00, 0x04, 0x00, 0x04, 0x00, 0x03, + 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFC, + 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xEF, 0xFF, 0xF1, 0xFF, 0xEF, + 0xFE, 0xB9, 0x00, 0x00, 0x01, 0x3C, 0x00, 0x18, 0x00, 0x0D, 0x00, 0x10, 0x00, 0x0C, 0x00, 0x0F, + 0x00, 0x0B, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x09, + 0x00, 0x08, 0x00, 0x06, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF8, + 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xF3, 0x00, 0x00, 0x00, 0x02, 0x00, 0x71, + 0xFF, 0xF7, 0x02, 0x4B, 0x02, 0x05, 0x00, 0x40, 0x00, 0x81, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x5E, + 0xFF, 0xE8, 0xFF, 0xE6, 0xFF, 0xE7, 0xFF, 0xE9, 0xFF, 0xEB, 0xFF, 0xEC, 0xFF, 0xEE, 0xFF, 0xF0, + 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFF, + 0x00, 0x01, 0x00, 0x03, 0x00, 0x05, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x0F, + 0x00, 0x10, 0x00, 0x13, 0x00, 0x13, 0x00, 0x16, 0x00, 0x17, 0x00, 0x16, 0x00, 0x1A, 0x00, 0x18, + 0x00, 0x18, 0x00, 0x1A, 0x00, 0x16, 0x00, 0x17, 0x00, 0x16, 0x00, 0x14, 0x00, 0x12, 0x00, 0x10, + 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x09, 0x00, 0x05, 0x00, 0x03, 0x00, 0x01, + 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF2, + 0xFF, 0xF0, 0xFF, 0xEE, 0xFF, 0xEC, 0xFF, 0xEB, 0xFF, 0xE9, 0xFF, 0xE8, 0xFF, 0xE6, 0xFF, 0xE8, + 0xFF, 0xF9, 0x00, 0x0E, 0x00, 0x11, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0B, + 0x00, 0x09, 0x00, 0x08, 0x00, 0x09, 0x00, 0x06, 0x00, 0x06, 0x00, 0x03, 0x00, 0x04, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF8, + 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xEF, + 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, + 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x08, + 0x00, 0x07, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x11, + 0x00, 0x0E, 0x02, 0x05, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF3, + 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xEE, 0xFF, 0xEC, 0xFF, 0xEA, 0xFF, 0xE9, 0xFF, 0xE7, 0xFF, 0xE6, + 0xFF, 0xE5, 0xFF, 0xE4, 0xFF, 0xE4, 0xFF, 0xE5, 0xFF, 0xE6, 0xFF, 0xE5, 0xFF, 0xE6, 0xFF, 0xEA, + 0xFF, 0xEC, 0xFF, 0xEE, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFB, + 0xFF, 0xFC, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x04, 0x00, 0x05, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0D, + 0x00, 0x0F, 0x00, 0x10, 0x00, 0x12, 0x00, 0x14, 0x00, 0x16, 0x00, 0x17, 0x00, 0x1B, 0x00, 0x1A, + 0x00, 0x1A, 0x00, 0x1C, 0x00, 0x1D, 0x00, 0x1B, 0x00, 0x1A, 0x00, 0x19, 0x00, 0x17, 0x00, 0x16, + 0x00, 0x14, 0x00, 0x13, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x06, + 0x00, 0x04, 0x00, 0x01, 0xFF, 0xB2, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF9, + 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xEE, 0xFF, 0xF1, + 0xFF, 0xED, 0xFF, 0xEC, 0xFF, 0xEC, 0xFF, 0xE9, 0xFF, 0xEC, 0xFF, 0xED, 0xFF, 0xED, 0xFF, 0xEE, + 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFA, + 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x05, 0x00, 0x07, + 0x00, 0x08, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x11, 0x00, 0x12, + 0x00, 0x13, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x13, 0x00, 0x13, 0x00, 0x12, + 0x00, 0x0E, 0x00, 0x10, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x07, 0x00, 0x06, + 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x6C, 0xFF, 0x5B, 0x02, 0x3C, + 0x01, 0xE6, 0x00, 0x40, 0x00, 0x81, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x5E, 0x00, 0x0E, 0x00, 0x11, + 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x09, 0x00, 0x07, 0x00, 0x07, + 0x00, 0x05, 0x00, 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, + 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF6, + 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF0, + 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, + 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, + 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x10, 0x00, 0x0E, 0xFF, 0x76, 0xFF, 0x9B, + 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x0F, 0x00, 0x11, 0x00, 0x12, 0x00, 0x12, + 0x00, 0x16, 0x00, 0x13, 0x00, 0x15, 0x00, 0x14, 0x00, 0x14, 0x00, 0x13, 0x00, 0x12, 0x00, 0x10, + 0x00, 0x13, 0x00, 0x0F, 0x00, 0x10, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0D, + 0x00, 0x0C, 0x00, 0x09, 0x00, 0x06, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF8, + 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF0, 0xFF, 0xF2, + 0xFF, 0xEE, 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xED, 0xFF, 0xF2, 0xFF, 0xF2, + 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xEF, 0xFF, 0xF9, 0x01, 0x9C, + 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF5, + 0xFF, 0xF5, 0xFF, 0xF0, 0xFF, 0xF3, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, 0xEA, + 0xFF, 0xEC, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xF5, + 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x05, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0A, + 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x11, 0x00, 0x12, 0x00, 0x12, 0x00, 0x13, 0x00, 0x14, + 0x00, 0x13, 0x00, 0x13, 0x00, 0x12, 0x00, 0x12, 0x00, 0x11, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x0C, + 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x05, 0x00, 0x06, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, + 0x00, 0x42, 0x00, 0x00, 0xFD, 0x7D, 0x00, 0x00, 0x00, 0xD2, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF7, + 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFE, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, + 0x00, 0x05, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x09, + 0x00, 0x0B, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x00, 0x1B, 0x00, 0x1C, 0x00, 0x10, 0x00, 0x0F, + 0x00, 0x13, 0x00, 0x13, 0x00, 0x12, 0x00, 0x0F, 0x00, 0x12, 0x00, 0x11, 0x00, 0x10, 0x00, 0x0D, + 0x00, 0x0F, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x08, 0x00, 0x08, + 0x00, 0x08, 0x00, 0x05, 0x00, 0x05, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF4, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x02, 0x00, 0x79, 0xFF, 0x59, 0x02, 0x52, 0x01, 0xED, 0x00, 0x40, + 0x00, 0x85, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x60, 0x00, 0x0E, 0x00, 0x0D, + 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x08, 0x00, 0x07, + 0x00, 0x06, 0x00, 0x06, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, + 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF6, + 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xEE, + 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF8, + 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, + 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x10, 0x00, 0x0E, 0x00, 0xF6, 0xFF, 0x98, + 0x00, 0x00, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xEE, + 0xFF, 0xF2, 0xFF, 0xED, 0xFF, 0xF0, 0xFF, 0xEF, 0xFF, 0xEC, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF2, + 0xFF, 0xF0, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF7, + 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x05, 0x00, 0x07, + 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0E, + 0x00, 0x0D, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x12, 0x00, 0x10, 0x00, 0x14, 0x00, 0x11, 0x00, 0x12, + 0x00, 0x12, 0x00, 0x12, 0x00, 0x0E, 0x00, 0x11, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x0C, 0x00, 0x0C, + 0x00, 0x0A, 0x00, 0x00, 0x00, 0x68, 0x01, 0x9E, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFB, + 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xEF, + 0xFF, 0xF1, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, 0xED, 0xFF, 0xED, 0xFF, 0xED, 0xFF, 0xEE, 0xFF, 0xEE, + 0xFF, 0xF0, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFA, + 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x05, + 0x00, 0x05, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x10, + 0x00, 0x12, 0x00, 0x12, 0x00, 0x16, 0x00, 0x15, 0x00, 0x14, 0x00, 0x12, 0x00, 0x13, 0x00, 0x0E, + 0x00, 0x11, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x08, 0x00, 0x06, + 0x00, 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0x00, 0x47, 0x00, 0x00, 0xFF, 0xCF, 0x00, 0x0A, + 0x00, 0x0B, 0x00, 0x09, 0x00, 0x08, 0x00, 0x07, 0x00, 0x04, 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF8, + 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF0, + 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, 0xED, 0xFF, 0xF0, + 0xFF, 0xED, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xF3, 0xFF, 0xF1, + 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF8, + 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, + 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x09, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, 0xFF, 0x29, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x93, 0x00, 0x00, 0x02, 0x16, 0x02, 0x08, 0x00, 0x3F, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x00, 0xFA, 0xFF, 0x99, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x05, 0x00, 0x07, 0x00, 0x06, 0x00, 0x09, 0x00, 0x09, + 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0A, + 0x00, 0x0B, 0x00, 0x09, 0x00, 0x08, 0x00, 0x08, 0x00, 0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x67, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFA, + 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF4, + 0xFF, 0xF1, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xF1, + 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF6, + 0xFF, 0xFE, 0x01, 0xFF, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x00, 0x01, 0x0B, 0x00, 0x16, 0x00, 0x14, + 0x00, 0x13, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x08, + 0x00, 0x07, 0x00, 0x06, 0x00, 0x05, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFE, + 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xEA, + 0x00, 0x00, 0x00, 0x1F, 0x00, 0x0A, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, + 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x08, 0x00, 0x08, 0x00, 0x06, 0x00, 0x06, 0x00, 0x05, + 0x00, 0x05, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, + 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF4, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x91, 0xFF, 0xF7, 0x02, 0x21, 0x02, 0x0B, 0x00, 0xAC, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, + 0x0C, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, + 0xF9, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, + 0xF0, 0xFF, 0xEE, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, 0xF1, 0xFF, + 0xF2, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, + 0xFA, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x05, 0x00, 0x05, 0x00, 0x07, 0x00, 0x06, 0x00, 0x0C, 0x00, + 0x0D, 0x00, 0x13, 0x00, 0x18, 0x00, 0x16, 0x00, 0x1B, 0x00, 0x24, 0x00, 0x29, 0x00, 0x0C, 0x00, + 0x0A, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x06, 0x00, 0x06, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, + 0xF6, 0xFF, 0xF7, 0xFF, 0xF2, 0xFF, 0xE9, 0xFF, 0xE6, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xEE, 0xFF, + 0xF7, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, + 0xFE, 0xFF, 0xFF, 0xFF, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x06, 0x00, 0x06, 0x00, + 0x08, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x12, 0x00, 0x12, 0x00, + 0x12, 0x00, 0x16, 0x00, 0x16, 0x00, 0x15, 0x00, 0x16, 0x00, 0x12, 0x00, 0x13, 0x00, 0x13, 0x00, + 0x0F, 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x08, 0x00, + 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, + 0xF9, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xED, 0xFF, 0xED, 0xFF, + 0xEE, 0xFF, 0xEE, 0xFF, 0xE5, 0xFF, 0xC7, 0xFF, 0xF4, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, + 0xF9, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x16, 0x00, + 0x0C, 0x00, 0x0B, 0x00, 0x12, 0x00, 0x09, 0x00, 0x09, 0x00, 0x08, 0x00, 0x06, 0x00, 0x06, 0x00, + 0x04, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x01, 0x82, 0x00, 0x12, 0x00, 0x0A, 0x00, + 0x09, 0x00, 0x0C, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x09, 0x00, 0x07, 0x00, + 0x07, 0x00, 0x07, 0x00, 0x05, 0x00, 0x06, 0x00, 0x04, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, + 0xF8, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, + 0xF6, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xEA, 0xFF, 0xEA, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, + 0xF5, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, + 0xF8, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, + 0xF7, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, + 0xFA, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, + 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x04, 0x00, 0x05, 0x00, + 0x04, 0x00, 0x08, 0x00, 0x05, 0x00, 0x07, 0x00, 0x07, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0xFF, + 0xF1, 0xFF, 0xEE, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, + 0xF8, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFD, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, + 0x08, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0D, 0x00, + 0x0A, 0x00, 0x0B, 0x00, 0x14, 0x00, 0x17, 0x00, 0x14, 0x00, 0x12, 0x00, 0x11, 0x00, 0x0E, 0x00, + 0x0C, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x05, 0x00, 0x05, 0x00, 0x07, 0x00, + 0x0B, 0x00, 0x04, 0x00, 0x03, 0x00, 0x05, 0x00, 0x06, 0x00, 0x08, 0x00, 0x09, 0x00, 0x09, 0x00, + 0x06, 0x00, 0x0F, 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x04, 0x00, + 0x05, 0x00, 0x05, 0x00, 0x07, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFD, 0xFF, + 0xFD, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, + 0xF9, 0xFF, 0xF8, 0x00, 0x00, 0x01, 0x00, 0xB5, 0xFF, 0xF7, 0x02, 0x10, 0x02, 0x79, 0x00, 0x35, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x6E, 0xFF, 0xA3, 0x00, 0x00, 0xFF, 0xA4, + 0x00, 0x00, 0x00, 0x5C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x04, 0x00, 0x06, 0x00, 0x08, + 0x00, 0x05, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x11, 0x00, 0x14, 0x00, 0x16, + 0x00, 0x14, 0x00, 0x0B, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0C, 0x00, 0x0F, 0x00, 0x0B, 0x00, 0x0E, + 0x00, 0x0A, 0x00, 0x00, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF3, + 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFA, + 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0xA2, + 0x00, 0x00, 0xFF, 0x5E, 0x02, 0x79, 0xFF, 0xE6, 0xFF, 0x91, 0x00, 0x00, 0xFF, 0xAD, 0x00, 0x00, + 0xFF, 0x15, 0xFF, 0xE8, 0xFF, 0xE6, 0xFF, 0xEB, 0xFF, 0xE9, 0xFF, 0xEE, 0xFF, 0xF6, 0xFF, 0xF7, + 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFF, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x04, 0x00, 0x07, 0x00, 0x05, 0x00, 0x5D, + 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFE, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, + 0x00, 0x0A, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x10, 0x01, 0x01, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x7B, 0xFF, 0xF7, 0x02, 0x4A, 0x01, 0xF9, 0x00, 0x3B, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xE0, 0x00, 0x6A, 0x00, 0x00, + 0xFF, 0x96, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFB, + 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF5, + 0xFF, 0xEB, 0xFF, 0xE9, 0xFF, 0xE6, 0xFF, 0xE9, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF7, + 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x95, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x04, 0x00, 0x07, 0x00, 0x07, + 0x00, 0x07, 0x00, 0x0D, 0x00, 0x10, 0x00, 0x11, 0x00, 0x10, 0x00, 0x14, 0x00, 0x16, 0x00, 0x15, + 0x00, 0x18, 0x00, 0x1C, 0x00, 0x19, 0x00, 0x19, 0x00, 0x16, 0x00, 0x14, 0x00, 0x13, 0x00, 0x12, + 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF9, 0x00, 0x00, 0xFE, 0xF2, 0xFF, 0xEC, 0xFF, 0xED, + 0xFF, 0xEF, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, + 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFF, 0x00, 0x01, + 0x00, 0x02, 0x00, 0x04, 0x00, 0x06, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x10, 0x00, 0x0F, + 0x00, 0x15, 0x01, 0x4E, 0x00, 0x00, 0xFE, 0xB5, 0xFF, 0xEC, 0xFF, 0xF3, 0xFF, 0xF0, 0xFF, 0xF4, + 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xF7, 0xFF, 0xF1, 0xFF, 0xF4, 0xFF, 0xF5, + 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x02, 0x00, 0x03, 0x00, 0x06, + 0x00, 0x08, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x01, 0x00, 0x6B, + 0x00, 0x00, 0x02, 0x51, 0x01, 0xF3, 0x00, 0x07, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x00, 0xD7, 0xFF, 0x94, 0x00, 0xAD, 0x00, 0x8C, 0x00, 0xAD, 0xFF, 0x94, 0xFF, 0x79, + 0xFF, 0xFF, 0x01, 0xF3, 0x00, 0x00, 0xFE, 0x0D, 0x00, 0x00, 0x01, 0xF3, 0x00, 0x00, 0xFE, 0x62, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x73, 0x00, 0x00, 0x02, 0x49, 0x01, 0xF8, 0x00, 0x0F, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x00, 0xD8, 0xFF, 0x9B, 0x00, 0x3E, 0x00, 0x74, 0x00, 0x38, 0x00, 0x02, 0x00, 0x38, + 0x00, 0x77, 0x00, 0x3B, 0xFF, 0x9B, 0xFF, 0xDE, 0xFF, 0xFE, 0xFF, 0xCE, 0xFF, 0x9F, 0xFF, 0xCF, + 0xFF, 0xFE, 0x01, 0xF8, 0x00, 0x00, 0xFE, 0x08, 0x00, 0x00, 0x00, 0xCE, 0x00, 0x00, 0xFF, 0x32, + 0x00, 0x00, 0x01, 0xF8, 0x00, 0x00, 0xFE, 0x8B, 0x00, 0x00, 0x00, 0xB2, 0x00, 0x00, 0xFF, 0x59, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x7A, 0x00, 0x00, 0x02, 0x42, 0x01, 0xF8, 0x00, 0x0B, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xFB, + 0xFF, 0x7F, 0x00, 0xAB, 0xFF, 0x55, 0x00, 0x6E, 0x00, 0x71, 0x00, 0x71, 0x00, 0x78, 0xFF, 0x4F, + 0x00, 0x9F, 0xFF, 0x92, 0xFF, 0x9D, 0x01, 0xF8, 0x00, 0x00, 0xFF, 0x06, 0xFF, 0x02, 0x00, 0x00, + 0x00, 0xB8, 0xFF, 0x48, 0x00, 0x00, 0x01, 0x01, 0x00, 0xF7, 0x00, 0x00, 0xFF, 0x57, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x70, 0xFF, 0x57, 0x02, 0x4F, 0x01, 0xEA, 0x00, 0x1D, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x68, 0xFF, 0x7A, + 0xFF, 0x8E, 0x00, 0xC8, 0xFF, 0xAF, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xF7, + 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xE9, 0x00, 0x00, 0x00, 0x21, 0x00, 0x18, 0x00, 0x0B, + 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x09, 0x00, 0x07, 0x00, 0x09, 0x00, 0x06, + 0x00, 0x08, 0x00, 0x10, 0x01, 0x1B, 0xFF, 0x9F, 0x00, 0xD7, 0x01, 0x13, 0x00, 0x00, 0xFE, 0x8B, + 0xFF, 0x66, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xFD, + 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xAC, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x02, 0x00, 0x04, + 0x00, 0x05, 0x00, 0x05, 0x00, 0x07, 0x00, 0x07, 0x00, 0x09, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x1B, + 0x02, 0x38, 0x00, 0x00, 0x00, 0x01, 0x00, 0x9C, 0x00, 0x00, 0x02, 0x27, 0x02, 0x0F, 0x00, 0x09, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x12, 0xFE, 0x9C, + 0x00, 0x00, 0x00, 0xF5, 0xFE, 0xF9, 0x00, 0x00, 0x01, 0x8B, 0x00, 0x00, 0xFE, 0xE1, 0x01, 0x0A, + 0x02, 0x0F, 0x00, 0x00, 0xFF, 0xA6, 0x00, 0x00, 0xFE, 0xB8, 0xFF, 0x93, 0x00, 0x00, 0x00, 0x5A, + 0x00, 0x00, 0x01, 0x4D, 0x00, 0x01, 0x00, 0x9B, 0x00, 0x00, 0x02, 0x21, 0x02, 0xA6, 0x00, 0x70, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x9B, 0x00, 0x36, 0x00, 0x0F, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x08, 0x00, + 0x07, 0x00, 0x05, 0x00, 0x05, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x02, 0x00, 0x05, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x06, 0x00, 0x09, 0x00, + 0x07, 0x00, 0x0A, 0x00, 0x0F, 0x00, 0x12, 0x00, 0x15, 0x00, 0x16, 0x00, 0x12, 0x00, 0x12, 0x00, + 0x61, 0x00, 0x00, 0xFF, 0xB4, 0xFF, 0xEE, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF4, 0xFF, 0xFA, 0xFF, + 0xFB, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0xFF, + 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, + 0xFA, 0xFF, 0xFB, 0xFF, 0xE6, 0x00, 0x1A, 0x00, 0x05, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, + 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x03, 0x00, 0x05, 0x00, 0x07, 0x00, 0x05, 0x00, 0x06, 0x00, 0x0C, 0x00, + 0x0F, 0x00, 0x0F, 0x00, 0x12, 0x00, 0x4C, 0x00, 0x00, 0xFF, 0x9F, 0xFF, 0xEE, 0xFF, 0xEE, 0xFF, + 0xEA, 0xFF, 0xEB, 0xFF, 0xEE, 0xFF, 0xF1, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, + 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0xFF, + 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF4, 0xFF, + 0xF5, 0xFF, 0xF1, 0xFF, 0xCA, 0x01, 0x28, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, + 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF2, 0xFF, + 0xB3, 0xFF, 0xEA, 0xFF, 0xEB, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF8, 0xFF, + 0xF6, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x06, 0x00, + 0x03, 0x00, 0x04, 0x00, 0x09, 0x00, 0x08, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x12, 0x00, + 0x40, 0x00, 0x0E, 0x00, 0x15, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x0A, 0x00, + 0x06, 0x00, 0x05, 0x00, 0x03, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x03, 0x00, 0x05, 0x00, 0x06, 0x00, + 0x0A, 0x00, 0x07, 0x00, 0x0C, 0x00, 0x08, 0x00, 0x0C, 0x00, 0x15, 0x00, 0x0E, 0x00, 0x41, 0x00, + 0x12, 0x00, 0x09, 0x00, 0x08, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x04, 0x00, 0x03, 0x00, + 0x06, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0xFF, 0xFF, 0xFF, + 0xFD, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, + 0xF7, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xEC, 0xFF, 0xEA, 0xFF, 0xB2, 0xFF, + 0xF3, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, + 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x2D, 0xFF, 0xC1, 0x01, 0x8F, + 0x03, 0x33, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x2D, 0x00, 0x62, 0x00, 0x00, + 0xFF, 0x9E, 0xFF, 0xC1, 0x00, 0x00, 0x03, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x9E, + 0x00, 0x00, 0x02, 0x23, 0x02, 0xA6, 0x00, 0x73, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x23, + 0xFF, 0xCA, 0xFF, 0xF1, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFC, + 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFC, + 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF7, + 0xFF, 0xF1, 0xFF, 0xED, 0xFF, 0xEB, 0xFF, 0xEA, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, 0xA0, 0x00, 0x00, + 0x00, 0x4C, 0x00, 0x11, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x05, 0x00, 0x05, 0x00, 0x08, + 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, + 0x00, 0x03, 0x00, 0x02, 0x00, 0x05, 0x00, 0x04, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, 0x04, + 0x00, 0x1A, 0xFF, 0xE6, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xFC, + 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF3, 0xFF, 0xF2, + 0xFF, 0xF0, 0xFF, 0xEF, 0xFF, 0xB4, 0x00, 0x00, 0x00, 0x60, 0x00, 0x12, 0x00, 0x11, 0x00, 0x16, + 0x00, 0x15, 0x00, 0x13, 0x00, 0x0F, 0x00, 0x09, 0x00, 0x08, 0x00, 0x08, 0x00, 0x06, 0x00, 0x07, + 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0C, + 0x00, 0x0B, 0x00, 0x0F, 0x00, 0x36, 0x01, 0x7E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, + 0x00, 0x04, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, 0x07, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0D, + 0x00, 0x44, 0x00, 0x0D, 0x00, 0x16, 0x00, 0x13, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0B, + 0x00, 0x08, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x07, 0x00, 0x05, + 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xAA, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, + 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF7, + 0xFF, 0xEE, 0xFF, 0xBF, 0xFF, 0xF2, 0xFF, 0xEB, 0xFF, 0xF4, 0xFF, 0xF8, 0xFF, 0xF4, 0xFF, 0xF9, + 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xFD, 0xFF, 0xFB, + 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF5, + 0xFF, 0xF2, 0xFF, 0xC0, 0xFF, 0xEE, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF7, + 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xAA, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x05, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x08, + 0x00, 0x07, 0x00, 0x09, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x14, + 0x00, 0x16, 0x00, 0x0D, 0x00, 0x43, 0x00, 0x0D, 0x00, 0x09, 0x00, 0x07, 0x00, 0x07, 0x00, 0x08, + 0x00, 0x06, 0x00, 0x06, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0xA6, 0x02, 0x18, 0x02, 0x11, 0x02, 0xA6, 0x00, 0x42, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0xFB, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x04, 0x00, 0x06, 0x00, 0x04, 0x00, + 0x06, 0x00, 0x06, 0x00, 0x05, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x1A, 0x00, 0x0B, 0x00, + 0x12, 0x00, 0x0F, 0x00, 0x13, 0x00, 0x11, 0x00, 0x10, 0x00, 0x09, 0x00, 0x09, 0x00, 0x0D, 0x00, + 0x0D, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x08, 0x00, 0x05, 0x00, 0x05, 0x00, 0x02, 0x00, + 0x01, 0xFF, 0xAB, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, + 0xFA, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xDE, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, + 0xF5, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, + 0xF6, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, + 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x02, 0x20, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x09, 0x00, + 0x08, 0x00, 0x07, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, + 0xFA, 0xFF, 0xEE, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x03, 0x00, 0x05, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0F, 0x00, + 0x0E, 0x00, 0x11, 0x00, 0x11, 0x00, 0x12, 0x00, 0x00, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, + 0xF8, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x02, 0x00, 0x05, 0x00, 0x06, 0x00, + 0x1A, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, + 0xF7, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, 0xF4, 0xFF, 0xF0, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0xA1, 0x00, 0x00, 0x02, 0x22, 0x02, 0xA6, 0x00, 0x3A, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x67, 0x00, 0x74, 0x00, 0x00, 0xFF, + 0x9F, 0x00, 0x1B, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x08, 0x00, + 0x07, 0x00, 0x06, 0x00, 0x1B, 0x00, 0x00, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, + 0xF6, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, + 0xF5, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, + 0xF8, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xE3, 0xFF, 0x91, 0x00, + 0x00, 0x00, 0x5C, 0xFF, 0xD1, 0xFF, 0xC2, 0x00, 0x00, 0x01, 0x81, 0x00, 0x00, 0xFF, 0x15, 0x01, + 0x33, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x83, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0A, 0x00, + 0x09, 0x00, 0x08, 0x00, 0x05, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, + 0xFC, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x58, 0x00, 0x08, 0x00, + 0x08, 0x00, 0x06, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, + 0xF7, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xE8, 0xFF, + 0xE6, 0xFF, 0x7C, 0x00, 0x00, 0xFF, 0xAA, 0x00, 0x00, 0xFF, 0x23, 0x00, 0x00, 0xFF, 0xAA, 0x00, + 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x6A, 0x00, 0x46, 0x02, 0x4C, + 0x02, 0x33, 0x00, 0x4B, 0x00, 0x8C, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x6A, 0x00, 0x4C, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, + 0xFD, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0xFF, 0xB4, 0x00, 0x42, 0x00, 0x48, 0x00, + 0x06, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x13, 0x00, 0x11, 0x00, 0x0C, 0x00, + 0x0C, 0x00, 0x12, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x06, 0x00, + 0x48, 0x00, 0x41, 0xFF, 0xB4, 0x00, 0x08, 0x00, 0x06, 0x00, 0x05, 0x00, 0x03, 0x00, 0x04, 0x00, + 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, + 0xFB, 0xFF, 0xFA, 0xFF, 0xF8, 0x00, 0x4C, 0xFF, 0xBF, 0xFF, 0xB8, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, + 0xF7, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xEE, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF4, 0xFF, 0xEF, 0xFF, + 0xED, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xB8, 0x00, 0xB0, 0xFF, + 0xF6, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, + 0xF8, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x08, 0x00, + 0x08, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x0D, 0x00, 0x0A, 0x00, + 0x09, 0x00, 0x0D, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x07, 0x00, 0x0A, 0x00, + 0x08, 0x00, 0x06, 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, + 0xF7, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF3, 0xFF, 0xF7, 0x01, + 0xF1, 0xFF, 0xB5, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, + 0xF4, 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, + 0xF3, 0xFF, 0xF2, 0xFF, 0xB5, 0xFF, 0xBF, 0x00, 0x4E, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, + 0xFB, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, + 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x07, 0x00, 0x05, 0xFF, 0xB2, 0x00, 0x41, 0x00, 0x4B, 0x00, + 0x0E, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x10, 0x00, + 0x10, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0E, 0x00, + 0x4B, 0x00, 0x42, 0xFF, 0xB1, 0x00, 0x06, 0x00, 0x06, 0x00, 0x05, 0x00, 0x05, 0x00, 0x04, 0x00, + 0x04, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, + 0xFA, 0xFF, 0xFA, 0xFF, 0xFA, 0x00, 0x4F, 0xFE, 0x92, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, + 0x04, 0x00, 0x04, 0x00, 0x07, 0x00, 0x06, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x0B, 0x00, + 0x09, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x0D, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0E, 0x00, 0x09, 0x00, + 0x0D, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x06, 0x00, 0x07, 0x00, + 0x04, 0x00, 0x05, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, + 0xFC, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, + 0xF7, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF2, 0xFF, 0xF6, 0xFF, + 0xF4, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, + 0xFB, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x64, + 0x00, 0x00, 0x02, 0x53, 0x02, 0xA6, 0x00, 0x10, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x53, 0xFF, 0x8B, 0xFF, + 0x7E, 0xFF, 0x7D, 0xFF, 0x8B, 0x00, 0xC7, 0x00, 0x00, 0xFF, 0x58, 0x00, 0x00, 0x00, 0xA8, 0x00, + 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0xA9, 0x00, 0x00, 0xFF, 0x57, 0x00, 0x00, 0x02, 0xA6, 0x00, + 0x00, 0xFF, 0x49, 0x00, 0xB7, 0x00, 0x00, 0xFE, 0xED, 0xFF, 0x91, 0x00, 0x00, 0xFF, 0xA9, 0x00, + 0x00, 0xFF, 0x33, 0x00, 0x00, 0x00, 0xCD, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x6F, 0x00, + 0x00, 0x02, 0x00, 0x99, 0xFF, 0xF8, 0x02, 0x2F, 0x02, 0xA6, 0x00, 0x24, 0x00, 0xF8, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x51, 0x00, 0x3B, 0x00, 0x1B, 0x00, + 0x0B, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x07, 0x00, 0x05, 0x00, 0x05, 0x00, 0x03, 0x00, 0x00, 0xFF, + 0xFF, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xD5, 0xFF, 0xEC, 0xFF, 0xC3, 0xFF, 0xC2, 0xFF, + 0xFB, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x05, 0x00, 0x09, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x0F, 0x00, + 0x11, 0x00, 0x18, 0xFF, 0x4B, 0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x05, 0x00, 0x06, 0x00, + 0x07, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x0F, 0x00, + 0x0F, 0x00, 0x10, 0x00, 0x11, 0x00, 0x15, 0x00, 0x15, 0x00, 0x19, 0x00, 0x14, 0x00, 0x13, 0x00, + 0x11, 0x00, 0x10, 0x00, 0x12, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0A, 0x00, + 0x09, 0x00, 0x08, 0x00, 0x07, 0x00, 0x04, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0xFF, 0xFE, 0xFF, + 0xFE, 0xFF, 0xFD, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF3, 0x00, 0x0F, 0x00, 0x06, 0x00, + 0x05, 0x00, 0x05, 0x00, 0x02, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFE, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xF2, 0xFF, + 0xF0, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xE3, 0xFF, + 0xDA, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, + 0xFC, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, 0x07, 0x00, 0x08, 0x00, 0x08, 0x00, + 0x0A, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x17, 0x00, 0x14, 0x00, 0x09, 0x00, 0x09, 0x00, + 0x07, 0x00, 0x07, 0x00, 0x06, 0x00, 0x05, 0x00, 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x5B, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, + 0xF5, 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xEE, 0xFF, 0xEF, 0xFF, + 0xEF, 0xFF, 0xED, 0xFF, 0xE4, 0xFF, 0xE7, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, + 0xF7, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, + 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x04, 0x00, 0x03, 0x00, + 0x03, 0x00, 0x05, 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0xFF, 0xEC, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, + 0xF8, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, + 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x10, 0x00, + 0x0E, 0x00, 0x12, 0x00, 0x22, 0x00, 0x38, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0B, 0x00, + 0x0A, 0x00, 0x06, 0x00, 0x07, 0x00, 0x06, 0x00, 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, + 0xF9, 0xFF, 0xF8, 0xFF, 0xEE, 0xFF, 0xE9, 0xFF, 0xE9, 0xFF, 0xE8, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, + 0xF1, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF9, 0x01, 0x7E, 0xFF, 0xF9, 0xFF, + 0xFC, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, + 0xF7, 0xFF, 0xF9, 0xFF, 0xEE, 0xFF, 0xF5, 0xFF, 0xF8, 0x00, 0x07, 0x00, 0x03, 0x00, 0x04, 0x00, + 0x07, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x04, 0x00, 0x07, 0x00, 0x06, 0x00, + 0x05, 0x00, 0x0D, 0x00, 0x05, 0x00, 0x08, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0B, 0xFF, 0xFC, 0xFF, + 0xFC, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0x18, 0xFF, 0xEF, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, + 0xF3, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, + 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x07, 0x00, 0x06, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, 0x00, + 0x0B, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0F, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x12, 0x00, 0x1B, 0x00, + 0x12, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x10, 0x00, 0x09, 0x00, 0x08, 0x00, 0x0F, 0x00, 0x12, 0x00, + 0x07, 0x00, 0x09, 0x00, 0x09, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0F, 0x00, + 0x08, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x07, 0x00, 0x08, 0x00, + 0x0C, 0x00, 0x0B, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, + 0x04, 0x00, 0x03, 0x00, 0x01, 0x00, 0x03, 0x00, 0x04, 0x00, 0x03, 0x00, 0x04, 0x00, 0x04, 0x00, + 0x05, 0x00, 0x05, 0x00, 0x06, 0x00, 0x05, 0x00, 0x07, 0x00, 0x07, 0x00, 0x09, 0x00, 0x08, 0x00, + 0x06, 0x00, 0x07, 0x00, 0x06, 0x00, 0x06, 0x00, 0x05, 0x00, 0x05, 0x00, 0x04, 0x00, 0x05, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, + 0xFD, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, + 0xF9, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0E, 0x00, + 0x0D, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x07, 0x00, 0x06, 0x00, 0x05, 0x00, + 0x03, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, + 0xFA, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, + 0xF5, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF2, 0xFF, 0xE6, 0xFF, 0xEA, 0xFF, + 0xF5, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xEE, 0xFF, 0xF6, 0xFF, + 0xF4, 0xFF, 0xF3, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xF1, 0xFF, 0xEE, 0xFF, 0xEC, 0xFF, 0xF2, 0xFF, + 0xF3, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, + 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, + 0xFD, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, + 0xF6, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, + 0xFB, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x04, 0x00, 0x06, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0xEB, 0x02, 0x1A, 0x01, 0xD5, 0x02, 0xA6, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xD5, 0x00, 0x00, 0xFF, 0xB3, 0x00, 0x00, + 0xFF, 0xB1, 0x00, 0x00, 0xFF, 0xB2, 0x00, 0x00, 0x02, 0x1A, 0x00, 0x8C, 0x00, 0x00, 0xFF, 0x74, + 0x00, 0x00, 0x00, 0x8C, 0x00, 0x00, 0xFF, 0x74, 0x00, 0x01, 0x01, 0x09, 0x02, 0x10, 0x01, 0xB9, + 0x02, 0xA6, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x52, 0xFF, 0xB7, 0x00, 0x4E, + 0x00, 0x62, 0x02, 0xA6, 0xFF, 0x6A, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00, 0x01, 0x01, 0x19, + 0xFF, 0x42, 0x01, 0xA3, 0xFF, 0xF7, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0xA3, + 0xFF, 0xB1, 0xFF, 0xC5, 0x00, 0x61, 0xFF, 0xF7, 0x00, 0x00, 0xFF, 0x4B, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x69, 0x00, 0x00, 0x02, 0x4E, 0x02, 0xEE, 0x00, 0x07, 0x00, 0x0B, 0x00, 0x0F, + 0x00, 0x13, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xBA, 0x00, 0x2F, 0x00, 0x65, 0xFF, 0x4D, + 0xFF, 0x81, 0xFF, 0x4D, 0x00, 0x65, 0x00, 0x2E, 0x00, 0xA2, 0xFF, 0x7B, 0x00, 0x42, 0x00, 0x02, + 0x00, 0xE8, 0x00, 0x00, 0xFF, 0xAB, 0x00, 0x00, 0xFE, 0xDC, 0x00, 0x00, 0xFF, 0xA7, 0x00, 0x00, + 0x00, 0xC8, 0xFF, 0x38, 0x00, 0x00, 0x02, 0xB6, 0x00, 0x00, 0xFD, 0x4A, 0x00, 0x00, 0x00, 0xC8, + 0x00, 0x46, 0x00, 0x00, 0x01, 0x2D, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x88, 0x00, 0x00, 0xFF, 0x78, + 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0xFF, 0x78, 0x00, 0x03, 0x00, 0x6E, 0x00, 0x00, 0x02, 0x46, + 0x02, 0xD3, 0x00, 0x46, 0x00, 0x4B, 0x00, 0x82, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x46, 0xFF, + 0x88, 0x00, 0x0E, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x06, 0x00, 0x06, 0x00, + 0x04, 0x00, 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, + 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, + 0xF3, 0xFF, 0xF6, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, + 0xEC, 0xFF, 0xEC, 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, + 0xF5, 0xFF, 0xF3, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, + 0xFB, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, + 0x04, 0x00, 0x05, 0x00, 0x05, 0x00, 0x07, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, 0x00, + 0x0D, 0xFF, 0x8C, 0x00, 0x60, 0x00, 0x1E, 0x00, 0xDD, 0x00, 0x1E, 0xFF, 0xCC, 0xFF, 0x4E, 0x00, + 0x30, 0x00, 0x29, 0x00, 0x29, 0xFF, 0xD3, 0x00, 0x18, 0x00, 0x14, 0x00, 0x13, 0x00, 0x09, 0x00, + 0x08, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x03, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, + 0xF9, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, + 0xE8, 0xFF, 0xEA, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, + 0xF8, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x06, 0x00, 0x04, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x13, 0x00, + 0x14, 0x00, 0x18, 0x00, 0x00, 0x01, 0xC2, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, + 0x0B, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x0F, 0x00, 0x0D, 0x00, + 0x0C, 0x00, 0x0E, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x09, 0x00, + 0x08, 0x00, 0x09, 0x00, 0x07, 0x00, 0x07, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x03, 0x00, + 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, + 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, + 0xF7, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF6, 0xFF, 0xF1, 0xFF, + 0xF3, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, + 0xF7, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF8, 0xFE, 0x3E, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0xFF, + 0x80, 0x00, 0xD8, 0x00, 0x00, 0x00, 0xCE, 0xFF, 0xFD, 0x00, 0x03, 0x00, 0xE2, 0xFF, 0xFE, 0xFF, + 0xFD, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF8, 0xFF, + 0xFA, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, + 0xF9, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, + 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x04, 0x00, 0x04, 0x00, 0x03, 0x00, 0x04, 0x00, + 0x05, 0x00, 0x05, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x07, 0x00, + 0x08, 0x00, 0x09, 0x00, 0x06, 0x00, 0x08, 0x00, 0x05, 0x00, 0x07, 0x00, 0x04, 0x00, 0x05, 0x00, + 0x05, 0x00, 0x04, 0x00, 0x06, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x77, + 0x00, 0x00, 0x02, 0x3E, 0x02, 0xA8, 0x00, 0x03, 0x00, 0x13, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x68, 0xFF, 0x98, 0x00, 0x3D, 0x00, 0x17, 0x00, 0x19, 0x00, 0x0B, 0x00, 0xC6, 0x00, 0x00, + 0xFF, 0x8E, 0xFF, 0xF0, 0x00, 0x7C, 0x00, 0x00, 0xFF, 0x7E, 0xFF, 0xF2, 0x00, 0x90, 0x00, 0x00, + 0xFE, 0xC1, 0xFF, 0x7E, 0x00, 0x57, 0x00, 0x22, 0x00, 0xFF, 0x00, 0x00, 0x01, 0x44, 0x00, 0x00, + 0xFE, 0x6B, 0xFF, 0x52, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0x00, 0xD8, 0x00, 0x00, 0x00, 0x5E, + 0x00, 0x00, 0x00, 0xB6, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0xFD, 0x58, 0x00, 0x00, 0x00, 0xAE, + 0x00, 0x02, 0x00, 0x86, 0x00, 0x00, 0x02, 0x36, 0x02, 0xFA, 0x00, 0x0B, 0x00, 0x4F, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0xDC, 0xFF, 0xFE, 0xFF, 0x1F, 0xFF, 0x8D, 0x00, 0x00, 0x00, 0x5A, 0x00, 0x00, 0x00, 0x02, + 0x00, 0xE8, 0x00, 0x6C, 0x00, 0x00, 0xFF, 0xA6, 0xFF, 0x18, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, + 0x00, 0x04, 0x00, 0x06, 0x00, 0x05, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, 0x06, 0x00, 0x0C, + 0x00, 0x0B, 0x00, 0x19, 0x00, 0x0D, 0x00, 0x10, 0x00, 0x11, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x13, + 0x00, 0x11, 0x00, 0x09, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x08, + 0x00, 0x09, 0x00, 0x06, 0x00, 0x05, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xA6, 0xFF, 0xFF, 0xFF, 0xFE, + 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xF8, 0xFF, 0xF6, + 0xFF, 0xF6, 0xFF, 0xD4, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF2, + 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF6, + 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x8D, 0x00, 0x00, 0x01, 0x73, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x01, 0x84, 0x00, 0x00, + 0xFE, 0x7C, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x65, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0A, + 0x00, 0x09, 0x00, 0x07, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, + 0xFF, 0xF8, 0xFF, 0xED, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFE, + 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x06, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0D, + 0x00, 0x11, 0x00, 0x13, 0x00, 0x13, 0x00, 0x10, 0x00, 0x14, 0x00, 0x00, 0xFF, 0xF4, 0xFF, 0xF3, + 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, + 0x00, 0x06, 0x00, 0x22, 0x00, 0x06, 0x00, 0x05, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF5, + 0xFF, 0xF7, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, 0xF4, 0xFF, 0xEF, 0xFF, 0xF2, 0xFF, 0xEF, + 0x00, 0x04, 0x00, 0x71, 0xFF, 0xF7, 0x02, 0x47, 0x02, 0xE6, 0x00, 0x44, 0x00, 0x85, 0x00, 0x89, + 0x00, 0x8D, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x62, 0xFF, 0xE9, 0xFF, 0xE9, 0xFF, 0xEB, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xEE, + 0xFF, 0xEC, 0xFF, 0xEE, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF9, + 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xFE, 0x00, 0x02, 0x00, 0x03, 0x00, 0x05, 0x00, 0x08, 0x00, 0x0A, + 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x10, 0x00, 0x12, 0x00, 0x13, 0x00, 0x13, 0x00, 0x0B, + 0x00, 0x0B, 0x00, 0x15, 0x00, 0x17, 0x00, 0x17, 0x00, 0x18, 0x00, 0x16, 0x00, 0x15, 0x00, 0x0C, + 0x00, 0x0B, 0x00, 0x12, 0x00, 0x14, 0x00, 0x11, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0B, + 0x00, 0x0A, 0x00, 0x08, 0x00, 0x05, 0x00, 0x04, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, + 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xEF, 0xFF, 0xEC, + 0xFF, 0xEE, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xEB, 0xFF, 0xE9, 0xFF, 0xE9, 0xFF, 0xFC, 0x00, 0x0E, + 0x00, 0x0F, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x08, + 0x00, 0x08, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF7, + 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF3, + 0xFF, 0xF0, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF8, + 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x05, 0x00, 0x08, 0x00, 0x07, 0x00, 0x09, + 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x10, 0x00, 0x0D, 0x00, 0xE6, + 0x00, 0x00, 0xFF, 0xA7, 0x00, 0x00, 0xFE, 0xDE, 0x00, 0x00, 0xFF, 0xA7, 0x00, 0x00, 0x02, 0x4E, + 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF6, 0xFF, 0xF1, 0xFF, 0xEF, + 0xFF, 0xEE, 0xFF, 0xEB, 0xFF, 0xE8, 0xFF, 0xE8, 0xFF, 0xE4, 0xFF, 0xE4, 0xFF, 0xE2, 0xFF, 0xE2, + 0xFF, 0xE3, 0xFF, 0xE2, 0xFF, 0xE2, 0xFF, 0xE3, 0xFF, 0xE2, 0xFF, 0xE4, 0xFF, 0xE5, 0xFF, 0xE7, + 0xFF, 0xEB, 0xFF, 0xED, 0xFF, 0xEF, 0xFF, 0xF1, 0xFF, 0xF5, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xF9, + 0xFF, 0xFC, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x04, 0x00, 0x07, 0x00, 0x04, 0x00, 0x06, 0x00, 0x0B, + 0x00, 0x0F, 0x00, 0x11, 0x00, 0x13, 0x00, 0x15, 0x00, 0x19, 0x00, 0x18, 0x00, 0x1C, 0x00, 0x1D, + 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x1D, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x1D, 0x00, 0x1D, 0x00, 0x1C, + 0x00, 0x18, 0x00, 0x19, 0x00, 0x14, 0x00, 0x13, 0x00, 0x11, 0x00, 0x0F, 0x00, 0x0B, 0x00, 0x05, + 0x00, 0x05, 0x00, 0x06, 0x00, 0x04, 0x00, 0x02, 0xFF, 0xAD, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFC, + 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xEF, 0xFF, 0xF0, + 0xFF, 0xED, 0xFF, 0xEC, 0xFF, 0xE8, 0xFF, 0xEC, 0xFF, 0xE8, 0xFF, 0xE9, 0xFF, 0xEC, 0xFF, 0xE8, + 0xFF, 0xEC, 0xFF, 0xEC, 0xFF, 0xED, 0xFF, 0xED, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF6, + 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x04, + 0x00, 0x07, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x13, 0x00, 0x10, + 0x00, 0x14, 0x00, 0x14, 0x00, 0x18, 0x00, 0x14, 0x00, 0x17, 0x00, 0x17, 0x00, 0x15, 0x00, 0x18, + 0x00, 0x14, 0x00, 0x13, 0x00, 0x10, 0x00, 0x11, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0A, + 0x00, 0x09, 0x00, 0x06, 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x66, 0x00, 0x85, 0x00, 0x00, + 0xFF, 0x7B, 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0xFF, 0x7B, 0x00, 0x00, 0x00, 0x03, 0x00, 0x77, + 0xFF, 0xF7, 0x02, 0x45, 0x02, 0xDF, 0x00, 0x43, 0x00, 0x84, 0x00, 0x88, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x5E, 0xFF, 0xEA, 0xFF, 0xE7, 0xFF, 0xEC, 0xFF, + 0xEB, 0xFF, 0xEC, 0xFF, 0xEE, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, + 0xF5, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x04, 0x00, 0x05, 0x00, + 0x08, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x11, 0x00, 0x0F, 0x00, 0x0A, 0x00, + 0x0A, 0x00, 0x14, 0x00, 0x15, 0x00, 0x16, 0x00, 0x18, 0x00, 0x16, 0x00, 0x17, 0x00, 0x18, 0x00, + 0x17, 0x00, 0x16, 0x00, 0x13, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x10, 0x00, 0x11, 0x00, 0x0F, 0x00, + 0x0D, 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x07, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0xFF, + 0xFD, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, + 0xEF, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xEC, 0xFF, 0xEB, 0xFF, 0xED, 0xFF, 0xE7, 0xFF, 0xEA, 0xFF, + 0xFC, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x0F, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0A, 0x00, + 0x09, 0x00, 0x07, 0x00, 0x08, 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, + 0xF8, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, + 0xF1, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, + 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x05, 0x00, 0x08, 0x00, + 0x07, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0D, 0x00, + 0x0F, 0x00, 0x44, 0xFF, 0x3B, 0x00, 0x45, 0x00, 0xC5, 0x02, 0x97, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, + 0xFA, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xEC, 0xFF, 0xEB, 0xFF, 0xE9, 0xFF, + 0xE5, 0xFF, 0xE5, 0xFF, 0xE4, 0xFF, 0xE1, 0xFF, 0xE1, 0xFF, 0xE4, 0xFF, 0xE2, 0xFF, 0xE1, 0xFF, + 0xE0, 0xFF, 0xE4, 0xFF, 0xE5, 0xFF, 0xE5, 0xFF, 0xE7, 0xFF, 0xEB, 0xFF, 0xEC, 0xFF, 0xF1, 0xFF, + 0xF8, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFF, 0x00, 0x01, 0x00, + 0x03, 0x00, 0x06, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x08, 0x00, 0x0F, 0x00, 0x13, 0x00, + 0x14, 0x00, 0x16, 0x00, 0x1B, 0x00, 0x1B, 0x00, 0x1C, 0x00, 0x1F, 0x00, 0x20, 0x00, 0x1E, 0x00, + 0x1D, 0x00, 0x1D, 0x00, 0x1F, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x1B, 0x00, 0x1A, 0x00, 0x16, 0x00, + 0x14, 0x00, 0x10, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x06, 0x00, 0x05, 0x00, + 0x01, 0xFF, 0xB7, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, + 0xF3, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xED, 0xFF, 0xEC, 0xFF, 0xEC, 0xFF, 0xEB, 0xFF, 0xE9, 0xFF, + 0xE6, 0xFF, 0xE7, 0xFF, 0xE9, 0xFF, 0xE7, 0xFF, 0xE9, 0xFF, 0xE8, 0xFF, 0xEC, 0xFF, 0xEC, 0xFF, + 0xF0, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, + 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x06, 0x00, 0x07, 0x00, 0x0B, 0x00, + 0x0C, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x10, 0x00, 0x14, 0x00, 0x14, 0x00, 0x15, 0x00, 0x17, 0x00, + 0x16, 0x00, 0x17, 0x00, 0x19, 0x00, 0x19, 0x00, 0x17, 0x00, 0x18, 0x00, 0x15, 0x00, 0x14, 0x00, + 0x13, 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x08, 0x00, 0x04, 0x00, + 0x03, 0x00, 0x02, 0x00, 0x91, 0xFD, 0x2A, 0xFF, 0xEE, 0x02, 0xD6, 0x00, 0x00, 0x03, 0x00, 0x73, + 0xFF, 0xF7, 0x02, 0x64, 0x02, 0xDE, 0x00, 0x51, 0x00, 0x55, 0x00, 0x59, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x73, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, + 0x00, 0x06, 0x00, 0x08, 0x00, 0x07, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x0C, 0x00, 0x0C, + 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x12, 0x00, 0x10, 0x00, 0x14, 0x00, 0x12, + 0x00, 0x17, 0x00, 0x14, 0x00, 0x13, 0x00, 0x17, 0x00, 0x11, 0x00, 0x15, 0x00, 0x10, 0x00, 0x12, + 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x0A, + 0x00, 0x08, 0x00, 0x07, 0x00, 0x08, 0x00, 0x06, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0xFF, 0x90, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, + 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF4, + 0xFF, 0xF7, 0xFF, 0xE8, 0xFF, 0xE8, 0xFF, 0xE8, 0xFF, 0xE7, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF9, + 0xFF, 0xF6, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFC, + 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, + 0xFF, 0x7A, 0x00, 0x00, 0x01, 0xD2, 0x00, 0x00, 0xFF, 0x7E, 0x00, 0x00, 0x02, 0x31, 0xFE, 0xA0, + 0xFF, 0xE3, 0xFF, 0xE8, 0xFF, 0xE9, 0xFF, 0xEB, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF5, + 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, + 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, + 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0B, + 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x15, 0x00, 0x17, 0x00, 0x18, 0x00, 0x1D, + 0x01, 0x60, 0x00, 0x00, 0xFE, 0xAD, 0xFF, 0xEF, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF6, + 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFB, + 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, + 0x00, 0x04, 0x00, 0x03, 0x00, 0x05, 0x00, 0x04, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, 0x06, + 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x10, 0x00, 0x11, 0x01, 0x53, + 0x00, 0x67, 0x00, 0x46, 0x00, 0x00, 0xFF, 0xBA, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0xFF, 0xBA, + 0x00, 0x01, 0x00, 0x71, 0x00, 0x00, 0x02, 0x3E, 0x02, 0xE8, 0x00, 0xA1, 0x00, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x5E, 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x08, + 0x00, 0x09, 0x00, 0x06, 0x00, 0x07, 0x00, 0x04, 0x00, 0x05, 0x00, 0x03, 0x00, 0x02, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, + 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, 0xE0, + 0x00, 0x00, 0x00, 0x1C, 0x00, 0x14, 0x00, 0x16, 0x00, 0x15, 0x00, 0x13, 0x00, 0x13, 0x00, 0x13, + 0x00, 0x10, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x06, 0x00, 0x04, 0x00, 0x02, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF9, + 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF5, + 0xFF, 0xF4, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x08, + 0x00, 0x05, 0x00, 0x05, 0x00, 0x02, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFB, + 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF4, 0xFF, 0xF3, + 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xED, + 0xFF, 0xF0, 0xFF, 0xEF, 0xFF, 0xEC, 0xFF, 0xF1, 0xFF, 0xED, 0xFF, 0xF2, 0xFF, 0xEF, 0xFF, 0xF2, + 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xF9, + 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x02, 0x00, 0x04, 0x00, 0x03, 0x00, 0x05, 0x00, 0x05, + 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x0F, 0x00, 0x10, 0x00, 0x13, 0x00, 0x15, + 0x00, 0x10, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x08, 0x00, 0x05, + 0x00, 0x05, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFC, + 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF0, + 0xFF, 0xE9, 0x00, 0x00, 0x01, 0x56, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFC, + 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF2, 0xFF, 0xF2, + 0xFF, 0xF0, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, + 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xAA, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x06, 0x00, 0x07, + 0x00, 0x0A, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x12, 0x00, 0x12, 0x00, 0x14, 0x00, 0x16, + 0x00, 0x14, 0x00, 0x18, 0x00, 0x18, 0x00, 0x11, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x0C, 0x00, 0x0F, + 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x09, + 0x00, 0x06, 0x00, 0x06, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0E, + 0x00, 0x0C, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x12, 0x00, 0x10, 0x00, 0x10, 0x00, 0x12, 0x00, 0x11, + 0x00, 0x11, 0x00, 0x10, 0x00, 0x11, 0x00, 0x0B, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x09, + 0x00, 0x0A, 0x00, 0x09, 0x00, 0x08, 0x00, 0x07, 0x00, 0x07, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, + 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, + 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF1, + 0xFF, 0xF3, 0xFF, 0xEE, 0xFF, 0xF1, 0xFF, 0xED, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xEF, 0xFD, 0xF3, + 0x00, 0x00, 0x01, 0xE7, 0x00, 0x1B, 0x00, 0x21, 0x00, 0x0F, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0B, + 0x00, 0x09, 0x00, 0x09, 0x00, 0x0D, 0x00, 0x08, 0x00, 0x09, 0x00, 0x06, 0x00, 0x04, 0x00, 0x03, + 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF6, + 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF2, + 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFB, + 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xA1, 0x00, 0x03, 0x00, 0x8E, 0xFF, 0xF7, 0x02, 0x2D, + 0x02, 0xD3, 0x00, 0x98, 0x00, 0xC3, 0x00, 0xF7, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x02, 0x2D, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFC, + 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF8, 0x00, 0x0D, + 0x00, 0x09, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x07, 0x00, 0x04, 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF9, + 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xED, 0xFF, 0xED, + 0xFF, 0xED, 0xFF, 0xEC, 0xFF, 0xED, 0xFF, 0xED, 0xFF, 0xF0, 0xFF, 0xEE, 0xFF, 0xF1, 0xFF, 0xF0, + 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFD, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x02, 0x00, 0x03, + 0x00, 0x05, 0x00, 0x04, 0x00, 0x07, 0x00, 0x0E, 0x00, 0x09, 0x00, 0x0A, 0xFF, 0xF2, 0xFF, 0xF4, + 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFD, 0x00, 0x64, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x01, 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, 0x07, 0x00, 0x08, 0x00, 0x08, + 0x00, 0x0D, 0x00, 0x17, 0x00, 0x12, 0x00, 0x10, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0F, 0x00, 0x08, + 0x00, 0x07, 0x00, 0x0A, 0x00, 0x04, 0x00, 0x04, 0x00, 0x05, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, + 0xFF, 0x9D, 0xFF, 0xE9, 0xFF, 0xED, 0xFF, 0xE6, 0xFF, 0xE8, 0xFF, 0xE8, 0xFF, 0xED, 0xFF, 0xEE, + 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFD, + 0xFF, 0xFE, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x05, + 0x00, 0x07, 0x00, 0x0D, 0x00, 0x09, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0B, + 0x00, 0x0C, 0x00, 0x10, 0x00, 0x0D, 0x00, 0x10, 0x00, 0x11, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x10, + 0x00, 0x0F, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x64, 0xFF, 0x9B, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFB, + 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF1, + 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF8, + 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFE, + 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x07, + 0x00, 0x07, 0x00, 0x10, 0x00, 0x12, 0x00, 0x16, 0x00, 0x17, 0x00, 0x0B, 0x00, 0x17, 0x00, 0x14, + 0x00, 0x11, 0x00, 0x08, 0x00, 0x08, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, + 0x00, 0x02, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, + 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xEF, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xE6, 0xFF, 0xE6, + 0xFF, 0xEA, 0xFF, 0xEF, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFD, + 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, + 0x00, 0x07, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x11, 0x00, 0x09, 0x00, 0x0D, + 0x00, 0x17, 0x01, 0x31, 0x00, 0x25, 0x00, 0x1B, 0x00, 0x16, 0x00, 0x0C, 0x00, 0x08, 0x00, 0x09, + 0x00, 0x07, 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x05, 0x00, 0x05, 0x00, 0x03, 0x00, 0x09, + 0x00, 0x09, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x0C, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0E, 0x00, 0x10, + 0x00, 0x11, 0x00, 0x0E, 0x00, 0x11, 0x00, 0x0D, 0x00, 0x0F, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x09, + 0x00, 0x0B, 0x00, 0x09, 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x04, 0x00, 0x05, 0x00, 0x03, + 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xF8, + 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF3, + 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF8, + 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF2, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF7, + 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x06, + 0x00, 0x09, 0x00, 0x05, 0x00, 0x07, 0x00, 0x06, 0x00, 0x06, 0x00, 0x04, 0x00, 0x04, 0x00, 0x03, + 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xFD, + 0xFF, 0xFC, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xEB, 0xFF, 0xE2, + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF5, + 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xF6, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xED, 0xFF, 0xEC, + 0xFF, 0xEA, 0xFF, 0xF6, 0xFF, 0xF2, 0xFF, 0xF7, 0xFF, 0xF3, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF6, + 0xFF, 0xF6, 0xFF, 0xF0, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFB, + 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, + 0x00, 0x04, 0x00, 0x04, 0x00, 0x06, 0x00, 0x05, 0x00, 0x08, 0x00, 0x07, 0x00, 0x09, 0x00, 0x09, + 0xFF, 0xD1, 0x00, 0x00, 0x00, 0xE8, 0xFF, 0xE8, 0xFF, 0xF3, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xF1, + 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFB, + 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, + 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x07, 0x00, 0x09, 0x00, 0x08, 0x00, 0x0A, + 0x00, 0x0A, 0x00, 0x12, 0x00, 0x06, 0x00, 0x09, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0B, 0x00, 0x06, + 0x00, 0x05, 0x00, 0x08, 0x00, 0x05, 0x00, 0x04, 0x00, 0x01, 0x01, 0xA3, 0xFF, 0xFF, 0xFF, 0xFC, + 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xF7, + 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xEE, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xF9, + 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x01, + 0x00, 0x03, 0x00, 0x06, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x05, + 0x00, 0x08, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x07, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x09, + 0x00, 0x0C, 0x00, 0x06, 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x07, 0x00, 0x03, 0x00, 0x02, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x78, 0xFF, 0xF7, 0x02, 0x44, 0x02, 0x04, 0x00, 0x18, + 0x00, 0x3B, 0x00, 0xDB, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x87, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, + 0x00, 0x06, 0x00, 0x04, 0x00, 0x07, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, 0x0E, 0x00, 0x06, + 0x00, 0x07, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x03, + 0x00, 0x01, 0x00, 0x00, 0xFF, 0x44, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFD, + 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xF8, + 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFD, + 0xFF, 0xFE, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x07, 0x00, 0x07, + 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x13, 0x00, 0x14, 0x01, 0x17, 0x00, 0x00, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF3, + 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xEB, 0xFF, 0xED, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF1, + 0xFF, 0xF3, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFA, + 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xE7, 0xFF, 0xF6, + 0xFF, 0xF3, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xF4, 0xFF, 0xF4, 0xFF, 0xF6, + 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x54, 0x00, 0x02, 0x00, 0x03, + 0x00, 0x06, 0x00, 0x08, 0x00, 0x09, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x0E, 0x00, 0x08, + 0x00, 0x07, 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x02, + 0x00, 0x00, 0xFF, 0xD8, 0xFF, 0xE0, 0xFF, 0xF2, 0xFF, 0xF2, 0xFF, 0xF4, 0xFF, 0xF7, 0xFF, 0xF5, + 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFC, + 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, + 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, + 0x00, 0x0A, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x11, 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x0B, + 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x09, 0x00, 0x09, 0x00, 0x07, 0x00, 0x04, 0x00, 0x06, + 0x00, 0x06, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0F, 0x00, 0x10, + 0x00, 0x11, 0x00, 0x11, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x09, + 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xAA, + 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFA, + 0xFF, 0xFA, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xF8, + 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0x35, 0x00, 0x2E, 0x00, 0x15, + 0x00, 0x14, 0x00, 0x0E, 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x03, 0x00, 0x04, 0x00, 0x02, + 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF9, + 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xED, 0xFF, 0xEC, 0xFF, 0xD2, 0xFF, 0xB3, 0xFF, 0xAD, + 0xFF, 0xEC, 0xFF, 0xF6, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xFC, + 0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x02, 0x00, 0x04, + 0x00, 0x06, 0x00, 0x05, 0x00, 0x08, 0x00, 0x09, 0x00, 0x09, 0x00, 0x16, 0x00, 0x18, 0x00, 0x0B, + 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x06, 0x00, 0x05, 0x00, 0x03, 0x00, 0x02, + 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x6D, 0x00, 0x28, 0x00, 0x16, 0x00, 0x15, 0x00, 0x11, 0x00, 0x0F, + 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF6, 0xFF, 0xF5, + 0x00, 0x07, 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x06, 0x00, 0x05, 0x00, 0x03, 0x00, 0x03, + 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFB, + 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF3, 0xFF, 0xF2, 0xFF, 0xF0, 0xFF, 0xEF, 0xFF, 0xF0, 0xFF, 0xEF, + 0xFF, 0xEE, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x02, + 0x00, 0x02, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF9, + 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xE4, 0xFF, 0xD1, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFE, + 0xFF, 0xFD, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xF6, + 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0xEF, 0xFF, 0xEE, 0xFF, 0xE8, + 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xF4, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF5, + 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x03, 0x00, 0x05, 0x00, 0x05, 0x00, 0x07, 0x00, 0x08, + 0x00, 0x09, 0x00, 0x0B, 0x00, 0x09, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFA, + 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, + 0x00, 0x06, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x11, 0x00, 0x10, + 0x00, 0x10, 0x00, 0x12, 0x00, 0x1B, 0x00, 0x00, 0xFF, 0xE9, 0xFF, 0xF0, 0xFF, 0xF2, 0xFF, 0xF9, + 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0A, 0x00, 0x0F, 0x00, 0x11, 0x00, 0x11, + 0x00, 0x4F, 0x00, 0x00, 0x00, 0x03, 0x00, 0x7B, 0xFF, 0xA6, 0x02, 0x45, 0x02, 0x49, 0x00, 0x45, + 0x00, 0x66, 0x00, 0x87, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x30, + 0xFF, 0xB2, 0xFF, 0xD3, 0xFF, 0xEB, 0xFF, 0xE8, 0xFF, 0xE4, 0xFF, 0xEA, 0xFF, 0xE8, 0xFF, 0xE7, + 0xFF, 0xEB, 0xFF, 0xEA, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF5, 0xFF, 0xF5, + 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF6, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFD, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x02, 0x00, 0x05, 0x00, 0x05, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0C, + 0x00, 0x0D, 0x00, 0x10, 0xFF, 0xBC, 0x00, 0x4C, 0x00, 0x31, 0x00, 0x17, 0x00, 0x17, 0x00, 0x15, + 0x00, 0x17, 0x00, 0x14, 0x00, 0x17, 0x00, 0x13, 0x00, 0x14, 0x00, 0x11, 0x00, 0x12, 0x00, 0x11, + 0x00, 0x10, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x09, 0x00, 0x07, 0x00, 0x07, + 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, 0xF7, + 0xFF, 0xF6, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xE1, 0x00, 0x07, 0x00, 0x06, + 0x00, 0x06, 0x00, 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xF8, 0xFF, 0xF9, + 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF7, 0xFF, 0xF4, 0xFF, 0xF2, + 0xFF, 0xF1, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xC5, 0xFF, 0xF9, + 0xFF, 0xFB, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x05, 0x00, 0x07, 0x00, 0x07, + 0x00, 0x07, 0x00, 0x08, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x08, 0x00, 0x0C, + 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x09, 0x02, 0x49, + 0x00, 0x00, 0xFF, 0xA8, 0x00, 0x06, 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, 0xFF, 0xFE, 0xFF, 0xFB, + 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xF5, 0xFF, 0xF5, 0xFF, 0xF3, 0xFF, 0xF3, + 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xEA, 0xFF, 0xEB, 0xFF, 0xEA, 0xFF, 0xE9, 0xFF, 0xE4, 0xFF, 0xE6, + 0xFF, 0xE8, 0xFF, 0xE7, 0xFF, 0xE7, 0xFF, 0xEB, 0xFF, 0xE8, 0xFF, 0xEC, 0xFF, 0xED, 0xFF, 0xED, + 0xFF, 0xEF, 0xFF, 0xF0, 0xFF, 0x7B, 0x00, 0x00, 0x00, 0x60, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xFD, + 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x03, 0x00, 0x04, 0x00, 0x06, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x0B, + 0x00, 0x0D, 0x00, 0x0D, 0x00, 0x10, 0x00, 0x11, 0x00, 0x12, 0x00, 0x14, 0x00, 0x15, 0x00, 0x19, + 0x00, 0x16, 0x00, 0x1B, 0x00, 0x19, 0x00, 0x16, 0x00, 0x19, 0x00, 0x19, 0x00, 0x17, 0x00, 0x18, + 0x00, 0x15, 0x00, 0x14, 0x00, 0x12, 0x00, 0x11, 0x00, 0x0C, 0xFF, 0xAD, 0xFF, 0xF5, 0xFF, 0xF5, + 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xEE, 0xFF, 0xED, 0xFF, 0xF0, + 0xFF, 0xEC, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, 0xF2, 0xFF, 0xF3, 0xFF, 0xF3, 0xFF, 0xF7, + 0xFF, 0xF8, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFE, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x06, 0x00, 0x25, 0x00, 0x0A, + 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x10, 0x00, 0x16, 0x00, 0x1A, + 0x00, 0x11, 0x00, 0x11, 0x00, 0x10, 0x00, 0x13, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0D, 0x00, 0x0D, + 0x00, 0x09, 0x00, 0x09, 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, + 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xFB, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x68, 0xFF, 0xF7, 0x02, 0x59, 0x02, 0xB0, 0x00, 0x03, 0x00, 0x2C, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xD0, 0xFF, + 0x98, 0x00, 0x00, 0x00, 0x68, 0x01, 0x22, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, + 0xFB, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF4, 0xFF, 0xF1, 0xFF, + 0xEE, 0xFF, 0xEB, 0xFF, 0xEF, 0xFF, 0xEB, 0x00, 0x00, 0x00, 0x68, 0x00, 0x10, 0x00, 0x0D, 0x00, + 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x09, 0x00, + 0x09, 0x00, 0x05, 0x00, 0x07, 0x00, 0x07, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x03, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x00, 0xFF, 0x99, 0x02, 0xB0, 0x00, 0x00, 0xFD, 0x50, 0x00, 0x00, 0x00, + 0xC4, 0xFF, 0xEB, 0xFF, 0xF1, 0xFF, 0xF0, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, + 0xFB, 0xFF, 0xFA, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x04, 0x00, 0x04, 0x00, + 0x07, 0xFF, 0x97, 0xFF, 0xF8, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, + 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x07, 0x00, 0x0B, 0x00, + 0x0C, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x10, 0x00, 0x13, 0x00, 0x14, 0x00, 0x1A, 0x02, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x69, 0xFF, 0x55, 0x02, 0x4E, 0x02, 0xE9, 0x00, 0x03, + 0x00, 0x2A, 0x00, 0x2E, 0x00, 0x32, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xE8, 0xFF, 0xA0, 0x00, + 0x00, 0x00, 0x60, 0x00, 0xE5, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, + 0xFC, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFA, 0xFF, 0xF9, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, 0xF5, 0xFF, + 0xF8, 0xFF, 0xEC, 0xFF, 0xF1, 0xFF, 0xC3, 0x00, 0x00, 0x00, 0x4F, 0x00, 0x1A, 0x00, 0x11, 0x00, + 0x10, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x08, 0x00, + 0x09, 0x00, 0x07, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, + 0x00, 0xFF, 0x93, 0x00, 0x00, 0x00, 0x8D, 0x00, 0x00, 0xFE, 0x1B, 0x00, 0x00, 0x00, 0x8D, 0x00, + 0x00, 0x01, 0xF3, 0x00, 0x00, 0xFE, 0x12, 0x00, 0x00, 0x01, 0xE9, 0xFE, 0x37, 0xFF, 0xDA, 0xFF, + 0xF2, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xFB, 0xFF, 0xFA, 0xFF, 0xFA, 0xFF, + 0xFB, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xAB, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, + 0x07, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x14, 0x00, 0x1A, 0x00, + 0x1C, 0x00, 0x23, 0x00, 0x1E, 0x01, 0xB4, 0x00, 0xFB, 0xFF, 0x7E, 0x00, 0x00, 0x00, 0x82, 0x00, + 0x00, 0xFF, 0x7E, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x01, 0xFF, 0xC5, 0xFE, 0x91, 0x02, 0xF7, + 0xFE, 0xF3, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0xFF, 0xC5, 0x00, 0x00, 0x03, 0x32, + 0x00, 0x00, 0xFE, 0xF3, 0xFF, 0x9E, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x02, 0x01, 0x2D, + 0xFF, 0x7B, 0x01, 0x8F, 0x03, 0x79, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x8F, 0xFF, 0x9E, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0xFF, 0x9E, + 0x00, 0x00, 0x00, 0x62, 0x01, 0x68, 0x00, 0x00, 0xFE, 0x13, 0x00, 0x00, 0x03, 0xFE, 0x00, 0x00, + 0xFE, 0x13, 0x00, 0x00, 0x00, 0x01, 0x00, 0x58, 0xFF, 0xDC, 0x02, 0x64, 0x02, 0xFB, 0x00, 0x03, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x58, 0x02, 0x0C, 0x00, 0x00, 0xFD, 0xF4, 0xFF, 0xDC, + 0x00, 0x00, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x2F, 0x8B, 0xF0, 0x17, 0x5F, 0x0F, 0x3C, 0xF5, 0x00, 0x03, 0x03, 0xE8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xC5, 0xFE, 0x91, + 0x02, 0xF7, 0x03, 0x79, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x04, 0x79, 0xFE, 0x91, 0x00, 0x00, 0x03, 0xE8, 0xFF, 0xC5, 0x00, 0x00, + 0x02, 0xF7, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x77, 0x03, 0xE8, 0x00, 0x00, 0x02, 0xBC, 0x00, 0x00, 0x02, 0xBC, 0x01, 0x27, + 0x02, 0xBC, 0x00, 0xAF, 0x02, 0xBC, 0x00, 0x96, 0x02, 0xBC, 0x00, 0x86, 0x02, 0xBC, 0x00, 0x91, + 0x02, 0xBC, 0x00, 0x85, 0x02, 0xBC, 0x01, 0x20, 0x02, 0xBC, 0x00, 0xDD, 0x02, 0xBC, 0x00, 0xE7, + 0x02, 0xBC, 0x00, 0x6D, 0x02, 0xBC, 0x00, 0x84, 0x02, 0xBC, 0x00, 0xAE, 0x02, 0xBC, 0x00, 0x81, + 0x02, 0xBC, 0x00, 0xFA, 0x02, 0xBC, 0x00, 0xB4, 0x02, 0xBC, 0x00, 0x85, 0x02, 0xBC, 0x00, 0xDE, + 0x02, 0xBC, 0x00, 0x9C, 0x02, 0xBC, 0x00, 0x94, 0x02, 0xBC, 0x00, 0x83, 0x02, 0xBC, 0x00, 0xA7, + 0x02, 0xBC, 0x00, 0x85, 0x02, 0xBC, 0x00, 0x89, 0x02, 0xBC, 0x00, 0x85, 0x02, 0xBC, 0x00, 0x86, + 0x02, 0xBC, 0x00, 0xFC, 0x02, 0xBC, 0x00, 0xA9, 0x02, 0xBC, 0x00, 0x83, 0x02, 0xBC, 0x00, 0x86, + 0x02, 0xBC, 0x00, 0x98, 0x02, 0xBC, 0x00, 0xAB, 0x02, 0xBC, 0x00, 0x84, 0x02, 0xBC, 0x00, 0x6C, + 0x02, 0xBC, 0x00, 0x76, 0x02, 0xBC, 0x00, 0x99, 0x02, 0xBC, 0x00, 0x86, 0x02, 0xBC, 0x00, 0x86, + 0x02, 0xBC, 0x00, 0xA9, 0x02, 0xBC, 0x00, 0x81, 0x02, 0xBC, 0x00, 0x82, 0x02, 0xBC, 0x00, 0xB7, + 0x02, 0xBC, 0x00, 0xBD, 0x02, 0xBC, 0x00, 0x61, 0x02, 0xBC, 0x00, 0x82, 0x02, 0xBC, 0x00, 0x73, + 0x02, 0xBC, 0x00, 0x7F, 0x02, 0xBC, 0x00, 0x73, 0x02, 0xBC, 0x00, 0x71, 0x02, 0xBC, 0x00, 0x5F, + 0x02, 0xBC, 0x00, 0x72, 0x02, 0xBC, 0x00, 0x8B, 0x02, 0xBC, 0x00, 0x7E, 0x02, 0xBC, 0x00, 0x71, + 0x02, 0xBC, 0x00, 0x6D, 0x02, 0xBC, 0x00, 0x6C, 0x02, 0xBC, 0x00, 0x67, 0x02, 0xBC, 0x00, 0x7B, + 0x02, 0xBC, 0x00, 0x97, 0x02, 0xBC, 0x00, 0xC1, 0x02, 0xBC, 0x00, 0xA6, 0x02, 0xBC, 0x00, 0xCF, + 0x02, 0xBC, 0x00, 0x8F, 0x02, 0xBC, 0x00, 0x7A, 0x02, 0xBC, 0x01, 0x00, 0x02, 0xBC, 0x00, 0x91, + 0x02, 0xBC, 0x00, 0x6B, 0x02, 0xBC, 0x00, 0x9E, 0x02, 0xBC, 0x00, 0x82, 0x02, 0xBC, 0x00, 0x85, + 0x02, 0xBC, 0x00, 0xB1, 0x02, 0xBC, 0x00, 0x8B, 0x02, 0xBC, 0x00, 0x94, 0x02, 0xBC, 0x00, 0xEB, + 0x02, 0xBC, 0x00, 0xE3, 0x02, 0xBC, 0x00, 0x7D, 0x02, 0xBC, 0x00, 0xD4, 0x02, 0xBC, 0x00, 0x72, + 0x02, 0xBC, 0x00, 0x77, 0x02, 0xBC, 0x00, 0x71, 0x02, 0xBC, 0x00, 0x6C, 0x02, 0xBC, 0x00, 0x79, + 0x02, 0xBC, 0x00, 0x93, 0x02, 0xBC, 0x00, 0x91, 0x02, 0xBC, 0x00, 0xB5, 0x02, 0xBC, 0x00, 0x7B, + 0x02, 0xBC, 0x00, 0x6B, 0x02, 0xBC, 0x00, 0x73, 0x02, 0xBC, 0x00, 0x7A, 0x02, 0xBC, 0x00, 0x70, + 0x02, 0xBC, 0x00, 0x9C, 0x02, 0xBC, 0x00, 0x9B, 0x02, 0xBC, 0x01, 0x2D, 0x02, 0xBC, 0x00, 0x9E, + 0x02, 0xBC, 0x00, 0xA6, 0x02, 0xBC, 0x00, 0xA1, 0x02, 0xBC, 0x00, 0x6A, 0x02, 0xBC, 0x00, 0x64, + 0x02, 0xBC, 0x00, 0x99, 0x02, 0xBC, 0x00, 0xEB, 0x02, 0xBC, 0x01, 0x09, 0x02, 0xBC, 0x01, 0x19, + 0x02, 0xBC, 0x00, 0x69, 0x02, 0xBC, 0x00, 0x6E, 0x02, 0xBC, 0x00, 0x77, 0x02, 0xBC, 0x00, 0x86, + 0x02, 0xBC, 0x00, 0x71, 0x02, 0xBC, 0x00, 0x77, 0x02, 0xBC, 0x00, 0x73, 0x02, 0xBC, 0x00, 0x71, + 0x02, 0xBC, 0x00, 0x8E, 0x02, 0xBC, 0x00, 0x78, 0x02, 0xBC, 0x00, 0x7B, 0x02, 0xBC, 0x00, 0x68, + 0x02, 0xBC, 0x00, 0x69, 0x02, 0xBC, 0xFF, 0xC5, 0x02, 0xBC, 0x01, 0x2D, 0x02, 0xBC, 0x00, 0x58, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, + 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x04, 0xBC, 0x00, 0x00, 0x08, 0xCC, + 0x00, 0x00, 0x0C, 0x68, 0x00, 0x00, 0x0C, 0x8C, 0x00, 0x00, 0x0D, 0xF0, 0x00, 0x00, 0x0F, 0x50, + 0x00, 0x00, 0x0F, 0xAC, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x10, 0x20, 0x00, 0x00, 0x10, 0x44, + 0x00, 0x00, 0x10, 0x68, 0x00, 0x00, 0x10, 0x8C, 0x00, 0x00, 0x13, 0x80, 0x00, 0x00, 0x13, 0xB4, + 0x00, 0x00, 0x15, 0xBC, 0x00, 0x00, 0x17, 0xC8, 0x00, 0x00, 0x18, 0x24, 0x00, 0x00, 0x19, 0xC8, + 0x00, 0x00, 0x1C, 0xD8, 0x00, 0x00, 0x1D, 0xD4, 0x00, 0x00, 0x22, 0x78, 0x00, 0x00, 0x25, 0x94, + 0x00, 0x00, 0x25, 0xCC, 0x00, 0x00, 0x26, 0x04, 0x00, 0x00, 0x26, 0x38, 0x00, 0x00, 0x26, 0x70, + 0x00, 0x00, 0x26, 0xA4, 0x00, 0x00, 0x28, 0xBC, 0x00, 0x00, 0x2C, 0xF8, 0x00, 0x00, 0x2D, 0x44, + 0x00, 0x00, 0x2F, 0xC8, 0x00, 0x00, 0x32, 0x90, 0x00, 0x00, 0x34, 0x6C, 0x00, 0x00, 0x34, 0xB8, + 0x00, 0x00, 0x34, 0xF8, 0x00, 0x00, 0x37, 0xA4, 0x00, 0x00, 0x37, 0xF0, 0x00, 0x00, 0x38, 0x3C, + 0x00, 0x00, 0x39, 0x7C, 0x00, 0x00, 0x39, 0xC4, 0x00, 0x00, 0x39, 0xF0, 0x00, 0x00, 0x3A, 0x50, + 0x00, 0x00, 0x3A, 0x9C, 0x00, 0x00, 0x3D, 0x74, 0x00, 0x00, 0x3E, 0xFC, 0x00, 0x00, 0x42, 0x0C, + 0x00, 0x00, 0x43, 0x7C, 0x00, 0x00, 0x46, 0xDC, 0x00, 0x00, 0x47, 0x14, 0x00, 0x00, 0x48, 0xB4, + 0x00, 0x00, 0x48, 0xEC, 0x00, 0x00, 0x49, 0xC4, 0x00, 0x00, 0x4A, 0x10, 0x00, 0x00, 0x4A, 0x4C, + 0x00, 0x00, 0x4A, 0x8C, 0x00, 0x00, 0x4A, 0xC4, 0x00, 0x00, 0x4A, 0xE8, 0x00, 0x00, 0x4B, 0x20, + 0x00, 0x00, 0x4B, 0x54, 0x00, 0x00, 0x4B, 0x78, 0x00, 0x00, 0x4B, 0x9C, 0x00, 0x00, 0x4E, 0xC0, + 0x00, 0x00, 0x51, 0x74, 0x00, 0x00, 0x54, 0x28, 0x00, 0x00, 0x56, 0xDC, 0x00, 0x00, 0x59, 0xC8, + 0x00, 0x00, 0x5A, 0xA4, 0x00, 0x00, 0x5E, 0x98, 0x00, 0x00, 0x5F, 0xC4, 0x00, 0x00, 0x60, 0x08, + 0x00, 0x00, 0x60, 0xE0, 0x00, 0x00, 0x61, 0x28, 0x00, 0x00, 0x61, 0xEC, 0x00, 0x00, 0x63, 0xBC, + 0x00, 0x00, 0x65, 0x14, 0x00, 0x00, 0x67, 0xB0, 0x00, 0x00, 0x6A, 0x4C, 0x00, 0x00, 0x6C, 0xFC, + 0x00, 0x00, 0x6E, 0x4C, 0x00, 0x00, 0x71, 0xBC, 0x00, 0x00, 0x72, 0xD8, 0x00, 0x00, 0x74, 0x14, + 0x00, 0x00, 0x74, 0x4C, 0x00, 0x00, 0x74, 0xAC, 0x00, 0x00, 0x74, 0xF8, 0x00, 0x00, 0x75, 0x9C, + 0x00, 0x00, 0x75, 0xDC, 0x00, 0x00, 0x78, 0x20, 0x00, 0x00, 0x78, 0x44, 0x00, 0x00, 0x7A, 0x98, + 0x00, 0x00, 0x7B, 0xF8, 0x00, 0x00, 0x7D, 0x30, 0x00, 0x00, 0x80, 0x04, 0x00, 0x00, 0x80, 0x68, + 0x00, 0x00, 0x85, 0x58, 0x00, 0x00, 0x85, 0x90, 0x00, 0x00, 0x85, 0xB4, 0x00, 0x00, 0x85, 0xD8, + 0x00, 0x00, 0x86, 0x50, 0x00, 0x00, 0x88, 0xF4, 0x00, 0x00, 0x89, 0x68, 0x00, 0x00, 0x8B, 0x08, + 0x00, 0x00, 0x8D, 0xE4, 0x00, 0x00, 0x90, 0xA4, 0x00, 0x00, 0x92, 0x78, 0x00, 0x00, 0x95, 0xB0, + 0x00, 0x00, 0x9A, 0x9C, 0x00, 0x00, 0x9E, 0xFC, 0x00, 0x00, 0xA1, 0xB8, 0x00, 0x00, 0xA2, 0xAC, + 0x00, 0x00, 0xA3, 0xC0, 0x00, 0x00, 0xA3, 0xE4, 0x00, 0x00, 0xA4, 0x1C, 0x00, 0x00, 0xA4, 0x40, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x77, 0x00, 0xF9, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x00, 0x5A, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x00, 0x00, 0x8C, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x01, 0x00, 0x1C, 0x00, 0x8C, 0x00, 0x03, + 0x00, 0x01, 0x04, 0x09, 0x00, 0x02, 0x00, 0x0E, 0x00, 0xA8, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, + 0x00, 0x03, 0x00, 0x1C, 0x00, 0xB6, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x04, 0x00, 0x1C, + 0x00, 0xD2, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x05, 0x00, 0x1A, 0x00, 0xEE, 0x00, 0x03, + 0x00, 0x01, 0x04, 0x09, 0x00, 0x06, 0x00, 0x1A, 0x01, 0x08, 0x00, 0x43, 0x00, 0x6F, 0x00, 0x70, + 0x00, 0x79, 0x00, 0x72, 0x00, 0x69, 0x00, 0x67, 0x00, 0x68, 0x00, 0x74, 0x00, 0x20, 0x00, 0x28, + 0x00, 0x63, 0x00, 0x29, 0x00, 0x20, 0x00, 0x53, 0x00, 0x69, 0x00, 0x78, 0x00, 0x20, 0x00, 0x4C, + 0x00, 0x61, 0x00, 0x62, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x73, 0x00, 0x2E, 0x00, 0x20, 0x00, 0x4C, + 0x00, 0x69, 0x00, 0x63, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x65, 0x00, 0x64, 0x00, 0x20, + 0x00, 0x75, 0x00, 0x6E, 0x00, 0x64, 0x00, 0x65, 0x00, 0x72, 0x00, 0x20, 0x00, 0x74, 0x00, 0x68, + 0x00, 0x65, 0x00, 0x20, 0x00, 0x53, 0x00, 0x69, 0x00, 0x78, 0x00, 0x20, 0x00, 0x4C, 0x00, 0x61, + 0x00, 0x62, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x73, 0x00, 0x20, 0x00, 0x53, 0x00, 0x70, 0x00, 0x6C, + 0x00, 0x69, 0x00, 0x74, 0x00, 0x20, 0x00, 0x4C, 0x00, 0x69, 0x00, 0x63, 0x00, 0x65, 0x00, 0x6E, + 0x00, 0x73, 0x00, 0x65, 0x00, 0x2E, 0x00, 0x53, 0x00, 0x69, 0x00, 0x78, 0x00, 0x4C, 0x00, 0x61, + 0x00, 0x62, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x73, 0x00, 0x20, 0x00, 0x4F, 0x00, 0x43, 0x00, 0x52, + 0x00, 0x42, 0x00, 0x52, 0x00, 0x65, 0x00, 0x67, 0x00, 0x75, 0x00, 0x6C, 0x00, 0x61, 0x00, 0x72, + 0x00, 0x53, 0x00, 0x69, 0x00, 0x78, 0x00, 0x4C, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6F, 0x00, 0x72, + 0x00, 0x73, 0x00, 0x20, 0x00, 0x4F, 0x00, 0x43, 0x00, 0x52, 0x00, 0x42, 0x00, 0x53, 0x00, 0x69, + 0x00, 0x78, 0x00, 0x4C, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x73, 0x00, 0x20, + 0x00, 0x4F, 0x00, 0x43, 0x00, 0x52, 0x00, 0x42, 0x00, 0x56, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, + 0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E, 0x00, 0x20, 0x00, 0x31, 0x00, 0x2E, 0x00, 0x30, 0x00, 0x30, + 0x00, 0x30, 0x00, 0x53, 0x00, 0x69, 0x00, 0x78, 0x00, 0x4C, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6F, + 0x00, 0x72, 0x00, 0x73, 0x00, 0x4F, 0x00, 0x43, 0x00, 0x52, 0x00, 0x42, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; +} diff --git a/src/ImageSharp.Drawing/Barcodes/GaloisField.cs b/src/ImageSharp.Drawing/Barcodes/GaloisField.cs new file mode 100644 index 00000000..f28dd486 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/GaloisField.cs @@ -0,0 +1,167 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// A binary extension field GF(2^m) and the Reed-Solomon error correction over it. Addition is exclusive or. Multiplication adds logarithms to the base alpha, +/// where alpha is the element 2 and the primitive polynomial reduces each power of alpha into the field. +/// +internal sealed class GaloisField +{ + /// + /// The powers of alpha, alpha^i at index i, written twice: for i from 0 to size - 2 and again from size - 1 to 2 x (size - 2). The sum of two logarithms is + /// at most 2 x (size - 2), so a product reads its power directly, with no reduction modulo size - 1. + /// + private readonly int[] exp; + + /// + /// The logarithms to the base alpha, i at index alpha^i. Index 0 is not used, because zero has no logarithm. + /// + private readonly int[] log; + + /// + /// Initializes a new instance of the class. + /// + /// The number of elements, a power of two. + /// The primitive polynomial as a bit mask, the highest term included. + public GaloisField(int size, int primitivePolynomial) + { + this.Size = size; + this.exp = new int[2 * (size - 1)]; + this.log = new int[size]; + + // Each power of alpha is the power before it times x. A product that reaches the x^m term is reduced by the primitive polynomial, which clears that + // term. + int value = 1; + for (int i = 0; i < size - 1; i++) + { + this.exp[i] = value; + this.exp[i + size - 1] = value; + this.log[value] = i; + value <<= 1; + if (value >= size) + { + value ^= primitivePolynomial; + } + } + } + + /// + /// Gets the number of elements in the field. + /// + public int Size { get; } + + /// + /// Returns alpha raised to the given power. + /// + /// The power, 0 or more. + /// The field element. + public int Exp(int power) => this.exp[power % (this.Size - 1)]; + + /// + /// Returns the product of two field elements. + /// + /// The first element. + /// The second element. + /// The product. + public int Multiply(int a, int b) + => a == 0 || b == 0 ? 0 : this.exp[this.log[a] + this.log[b]]; + + /// + /// Writes the generator polynomial (x + alpha^r)(x + alpha^(r+1)) ... (x + alpha^(r+k-1)) of a code with k check symbols, where r is the first root. The + /// polynomial is monic, so the coefficient of x^k is 1 and is not written. + /// + /// The power of alpha of the first root. + /// The buffer that receives the coefficients of x^0 to x^(k-1), the coefficient of x^j at index j. + public void Generator(int firstRoot, Span generator) + { + // The product grows one factor at a time. Multiplying by (x + a) moves every coefficient up one power and adds a times the coefficient itself, so the + // update runs from the top down in place. Before factor d the product has degree d - 1, and its leading coefficient, 1, is at index d - 1 until the + // last factor, whose new leading 1 is the implied coefficient of x^k. + int count = generator.Length; + generator.Clear(); + generator[0] = 1; + for (int degree = 1; degree <= count; degree++) + { + int root = this.Exp(firstRoot + degree - 1); + if (degree < count) + { + generator[degree] = generator[degree - 1]; + } + + for (int j = degree - 1; j > 0; j--) + { + generator[j] = this.Multiply(generator[j], root) ^ generator[j - 1]; + } + + generator[0] = this.Multiply(generator[0], root); + } + } + + /// + /// Calculates the check symbols of a Reed-Solomon code: the remainder of the data polynomial, the first symbol as its highest term, times x^k, divided by + /// the generator polynomial. + /// + /// The data symbols. Symbol i of the block is at index i x . + /// The distance between two symbols of the block, 1 when the block is not interleaved. + /// The number of data symbols in the block. + /// The generator coefficients of x^0 to x^(k-1), from . + /// The buffer that receives the k check symbols, the highest term first. + public void Remainder(ReadOnlySpan data, int step, int count, ReadOnlySpan generator, Span check) + { + // A linear feedback shift register divides as the symbols arrive. The register holds the running remainder with its highest term at index 0. Each + // symbol adds to that term, and the sum times the generator is subtracted, which in a binary field is the same exclusive or. + int[] exp = this.exp; + int[] log = this.log; + int k = check.Length; + check.Clear(); + for (int i = 0; i < count; i++) + { + int feedback = data[i * step] ^ check[0]; + if (feedback == 0) + { + // A zero feedback adds nothing, so the register only moves up one term. + check[1..].CopyTo(check); + check[k - 1] = 0; + continue; + } + + // The logarithm of the feedback is the same for every coefficient, so it is read once, and each product is one table read from the sum of two + // logarithms. + int feedbackLog = log[feedback]; + for (int j = 0; j < k - 1; j++) + { + int coefficient = generator[k - 1 - j]; + check[j] = check[j + 1] ^ (coefficient == 0 ? 0 : exp[log[coefficient] + feedbackLog]); + } + + check[k - 1] = generator[0] == 0 ? 0 : exp[log[generator[0]] + feedbackLog]; + } + } + + /// + /// Calculates the check symbols of interleaved blocks. The data symbols are dealt over the blocks in turn, symbol i to block i mod b, each block takes its + /// own check symbols, and the check symbols follow the data, dealt over the blocks the same way. + /// + /// The data symbols followed by room for the check symbols of every block. + /// The number of data symbols, a multiple of the block count. + /// The number of blocks. + /// The generator coefficients of one block, from . + /// The scratch for the check symbols of one block, as long as the generator. + public void InterleavedRemainders(Span symbols, int dataCount, int blocks, ReadOnlySpan generator, Span check) + { + // A block reads its data symbols in place with a step of the block count. The check symbols go after the data, so the next block still reads only data + // symbols. + int perBlock = generator.Length; + Span target = symbols.Slice(dataCount, perBlock * blocks); + for (int block = 0; block < blocks; block++) + { + this.Remainder(symbols[block..], blocks, dataCount / blocks, generator, check); + for (int i = 0; i < perBlock; i++) + { + target[(i * blocks) + block] = check[i]; + } + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1ApplicationIdentifier.cs b/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1ApplicationIdentifier.cs new file mode 100644 index 00000000..de43c3ca --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1ApplicationIdentifier.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// One entry of the GS1 Barcode Syntax Dictionary: an Application Identifier, or a range of them, with the format of its data, the rules that pair it with +/// other Application Identifiers, and its role in a GS1 Digital Link URI. +/// +internal sealed class Gs1ApplicationIdentifier +{ + /// + /// Initializes a new instance of the class. + /// + /// The first Application Identifier of the range. + /// The last Application Identifier of the range, the same as for one. + /// Whether the element string has a predefined length and takes no separator. + /// Whether the Application Identifier can be a data attribute of a GS1 Digital Link URI. + /// The components of the data, in order. + /// The Application Identifier patterns that must not appear with this one. + /// The qualifier sequences of a GS1 Digital Link primary key, or when it is not one. + public Gs1ApplicationIdentifier( + string first, + string last, + bool predefinedLength, + bool dataAttribute, + Gs1Component[] components, + string[] exclusions, + string[][]? keyQualifiers) + { + this.First = first; + this.Last = last; + this.PredefinedLength = predefinedLength; + this.DataAttribute = dataAttribute; + this.Components = components; + this.Exclusions = exclusions; + this.KeyQualifiers = keyQualifiers; + + // An optional component adds nothing to the shortest data, and every component adds its maximum to the longest. + foreach (Gs1Component component in components) + { + this.MinimumLength += component.Optional ? 0 : component.Minimum; + this.MaximumLength += component.Maximum; + } + } + + /// + /// Gets the first Application Identifier of the range. + /// + public string First { get; } + + /// + /// Gets the last Application Identifier of the range. + /// + public string Last { get; } + + /// + /// Gets a value indicating whether the element string has a predefined length, so that it takes no separator before the element string that follows it. + /// + public bool PredefinedLength { get; } + + /// + /// Gets a value indicating whether the Application Identifier can be a data attribute in the query string of a GS1 Digital Link URI. + /// + public bool DataAttribute { get; } + + /// + /// Gets the components of the data, in order. + /// + public Gs1Component[] Components { get; } + + /// + /// Gets the Application Identifier patterns that must not appear in the same data as this one. + /// + public string[] Exclusions { get; } + + /// + /// Gets the qualifier sequences of a GS1 Digital Link primary key, or when the Application Identifier is not a primary key. A key + /// with no qualifiers has one empty sequence. + /// + public string[][]? KeyQualifiers { get; } + + /// + /// Gets the length of the shortest data. + /// + public int MinimumLength { get; } + + /// + /// Gets the length of the longest data. + /// + public int MaximumLength { get; } + + /// + /// Returns whether the entry holds the given Application Identifier. + /// + /// The Application Identifier. + /// when the identifier is in the range of the entry. + public bool Holds(ReadOnlySpan identifier) + => identifier.Length == this.First.Length && identifier.SequenceCompareTo(this.First) >= 0 && identifier.SequenceCompareTo(this.Last) <= 0; +} diff --git a/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1CharacterSet.cs b/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1CharacterSet.cs new file mode 100644 index 00000000..648ca3c3 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1CharacterSet.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The character set of a component of GS1 Application Identifier data, which the GS1 Barcode Syntax Dictionary writes as N, X, Y or Z. +/// +internal enum Gs1CharacterSet : byte +{ + /// + /// N: the digits. + /// + Digits, + + /// + /// X: GS1 AI encodable character set 82, Table 7-20 of the GS1 General Specifications. + /// + Set82, + + /// + /// Y: GS1 AI encodable character set 39, Table 7-21. + /// + Set39, + + /// + /// Z: GS1 AI encodable character set 64, Table 7-22. + /// + Set64, +} diff --git a/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1Component.cs b/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1Component.cs new file mode 100644 index 00000000..35da0c48 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1Component.cs @@ -0,0 +1,52 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// One component of the data of a GS1 Application Identifier: its character set, its length, whether it can be left out, and the checks its content takes. +/// +internal readonly struct Gs1Component +{ + /// + /// Initializes a new instance of the struct. + /// + /// The character set. + /// The shortest length. + /// The longest length. + /// Whether the component can be left out. + /// The checks the content takes, in order. + public Gs1Component(Gs1CharacterSet characterSet, int minimum, int maximum, bool optional, Gs1Linter[] linters) + { + this.CharacterSet = characterSet; + this.Minimum = minimum; + this.Maximum = maximum; + this.Optional = optional; + this.Linters = linters; + } + + /// + /// Gets the character set. + /// + public Gs1CharacterSet CharacterSet { get; } + + /// + /// Gets the shortest length. + /// + public int Minimum { get; } + + /// + /// Gets the longest length. + /// + public int Maximum { get; } + + /// + /// Gets a value indicating whether the component can be left out. + /// + public bool Optional { get; } + + /// + /// Gets the checks the content takes, in order. + /// + public Gs1Linter[] Linters { get; } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1Data.cs b/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1Data.cs new file mode 100644 index 00000000..1d26c857 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1Data.cs @@ -0,0 +1,593 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts; +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The data layer the GS1 symbologies share. A GS1 symbol carries one or more element strings, each an Application Identifier and the data that belongs to it, +/// and the same parsing and separator rules apply whatever symbology draws it. +/// +internal static class Gs1Data +{ + /// + /// The character an element string of variable length is terminated by. Section 7.8.6.2 of the GS1 General Specifications: the separator "is always + /// represented in the transmitted message by the control character <GS> (ASCII value 29 (decimal), 1D (hexadecimal))". + /// + public const char Separator = (char)29; + + /// + /// The number of characters a caller holds inline for to build in. Section 5.4.1 caps a GS1-128 symbol at 48 data characters, so + /// every such symbol builds inline. + /// + public const int InlineBufferLength = 64; + + /// + /// The number of element strings holds inline. Data with more rents its element buffer once. + /// + public const int InlineElementCount = 16; + + /// + /// The size in points of a font whose em is high. A point is 1/72 inch, so the em of a font is its size times 25.4 / 72 + /// millimetres high at any resolution. + /// + public const float MinimumTextSize = MinimumTextHeight * 72F / 25.4F; + + // The symbol specification tables of section 5.12.3 give each application a range of X dimensions, and most symbologies are in several tables. A + // symbology clamps the X dimension to the widest range of the tables that list it, so the X dimension of every application those tables define stays + // possible. The nominal X dimension is the target of the table that Table 2-37 gives to the main application of the symbology. + + /// + /// The smallest X dimension in millimetres of a GS1-128 symbol, the smallest minimum of the tables that list GS1-128: 0.170 in Table 5-52 and Table 5-57. + /// + public const double Gs1128MinimumXDimension = 0.170; + + /// + /// The largest X dimension in millimetres of a GS1-128 symbol, the largest maximum of the tables that list GS1-128: 1.016 in Table 5-47, Table 5-54 and + /// Table 5-58. + /// + public const double Gs1128MaximumXDimension = 1.016; + + /// + /// The smallest X dimension in millimetres of a GS1-128 symbol that carries an SSCC. Note (*) of Table 5-51: "If the logistic unit is physically too small + /// to accommodate the minimum X-dimension, the minimum X-dimension is 0.250 millimetre (0.0098 inch)." + /// + public const double SsccMinimumXDimension = 0.250; + + /// + /// The largest X dimension in millimetres of a GS1-128 symbol that carries an SSCC, the maximum of the logistic unit rows of Table 5-51 and Table 5-58. + /// + public const double SsccMaximumXDimension = 0.940; + + /// + /// The smallest bar height in millimetres of a GS1-128 or ITF-14 symbol. A small item or package can truncate the bars below the minimum symbol height of + /// its table, but note (****) of Table 5-47 sets "no less than 5.08 millimetres", and the notes of Table 5-50 and Table 5-51: "In no case SHALL the bar + /// height be less than 5.08 millimetres". + /// + public const double MinimumTruncatedBarHeight = 5.08; + + /// + /// The smallest X dimension in millimetres of an ITF-14 symbol, the smallest minimum of the tables that list ITF-14: 0.170 in Table 5-52. + /// + public const double Itf14MinimumXDimension = 0.170; + + /// + /// The largest X dimension in millimetres of an ITF-14 symbol, the largest maximum of the tables that list ITF-14: 1.016 in Table 5-47 and Table 5-54. + /// + public const double Itf14MaximumXDimension = 1.016; + + /// + /// The nominal X dimension in millimetres of a GS1 DataMatrix or GS1 QR Code symbol, the target of Table 5-45, symbol specification table 1 addendum 1. + /// Table 2-37 gives that table to "GS1 element string syntax for extended packaging applications for trade items". + /// + public const double Gs1MatrixXDimension = 0.495; + + /// + /// The smallest X dimension in millimetres of a GS1 DataMatrix symbol, the smallest minimum of the tables that list GS1 DataMatrix: 0.100 for "GS1 + /// DataMatrix direct part marking - A" in Table 5-53. + /// + public const double Gs1DataMatrixMinimumXDimension = 0.100; + + /// + /// The smallest X dimension in millimetres of a GS1 QR Code symbol, the smallest minimum of the tables that list GS1 QR Code: 0.254 in Table 5-53 and + /// Table 5-57. + /// + public const double Gs1QrCodeMinimumXDimension = 0.254; + + /// + /// The largest X dimension in millimetres of a GS1 DataMatrix or GS1 QR Code symbol, the largest maximum of the tables that list them: 3.50 in Table 5-59. + /// + public const double Gs1MatrixMaximumXDimension = 3.50; + + /// + /// The smallest X dimension in millimetres of a GS1 Digital Link Data Matrix or QR Code symbol, the minimum of Table 5-46, symbol specification table 1 + /// addendum 2. Table 2-37 gives that table to "GS1 Digital Link URI syntax for extended packaging applications for trade items". + /// + public const double DigitalLinkMinimumXDimension = 0.396; + + /// + /// The nominal X dimension in millimetres of a GS1 Digital Link Data Matrix or QR Code symbol, the target of Table 5-46. + /// + public const double DigitalLinkXDimension = 0.495; + + /// + /// The largest X dimension in millimetres of a GS1 Digital Link Data Matrix or QR Code symbol, the maximum of Table 5-46 and Table 5-49. + /// + public const double DigitalLinkMaximumXDimension = 0.990; + + /// + /// The smallest height in millimetres of the font of the human readable interpretation. Section 4.14 rule 2a.iii of the GS1 General Specifications: "The + /// font size SHOULD be at least 2 mm (0.08 in) in height". + /// + private const float MinimumTextHeight = 2F; + + /// + /// Returns the factor that lifts a font to , or 1 for a font that is that high or higher. + /// + /// The font of the human readable interpretation. + /// The factor applied to the size of the font. + public static float TextScale(Font font) => MathF.Max(1F, MinimumTextSize / font.Size); + + /// + /// Adds the human readable interpretation of element strings below a built matrix symbol, one element string on each line, as + /// writes them. The lines start on the left edge of the symbol, as Example 1 of section + /// 2.3 of the GS1 DataMatrix Guideline prints them. + /// + /// The element strings, in the element string syntax that accepted. + /// The font of the caller, which lifts to . + /// The built symbol. + public static void AddMatrixReadable(ReadOnlySpan text, Font font, ref BarcodeSymbol symbol) + { + // The text is bounded by the capacity of the symbology, so the buffer is too, and longer text rents it once. + InlineArray64 readableStorage = default; + int capacity = ReadableCapacity(text.Length); + ValueStringBuilder readable = capacity <= InlineBufferLength ? new(readableStorage) : new(capacity); + try + { + Span buffer = readable.AppendSpan(capacity); + int written = WriteReadable(text, true, buffer); + symbol.AddMatrixText(buffer[..written], BarcodeTextAlignment.Left, TextScale(font)); + } + finally + { + readable.Dispose(); + } + } + + /// + /// Returns the number of characters writes at most for the text, after it checks that the text can prepare into no more than the + /// given number of characters. The prepared data drops the two parentheses of each element string and adds at most one separator between two of them, so it + /// is never longer than the text. An element string has at least three prepared characters, a two digit Application Identifier and one data character, so + /// text that prepares into P characters is at most P plus two parentheses for each third of P. + /// + /// The element strings. + /// The largest prepared length the symbology carries. + /// The capacity the prepared data needs, the length of the text. + /// The text is longer than any text that prepares into characters. + public static int PreparedCapacity(ReadOnlySpan text, int maximumPrepared) + { + int maximumText = maximumPrepared + (2 * (maximumPrepared / 3)); + if (text.Length > maximumText) + { + throw new ArgumentException($"The element strings are {text.Length} characters; the symbology carries at most {maximumText}.", nameof(text)); + } + + return text.Length; + } + + /// + /// Parses the element string syntax the standard prints, an Application Identifier in parentheses followed by its data, repeated, validates the element + /// strings as one whole symbol with , and writes the data a symbol carries. The symbol does not carry the parentheses; + /// section 4.14 rule 2c requires them in the human readable interpretation alone. + /// + /// The element strings to parse. + /// The builder that receives the encoded data: each Application Identifier and its data in turn, with a + /// after every variable length element string but the last. + /// The text is not valid element string syntax, or an element string or a pair of them breaks a rule of the GS1 General + /// Specifications. + public static void Prepare(ReadOnlySpan text, ref ValueStringBuilder encoded) + { + // An element string is at least five characters of text, the two parentheses, a two digit Application Identifier and one data character, so the text + // holds at most a fifth of its length in element strings, and the element buffer never grows. + InlineArray16 elementStorage = default; + int elementCapacity = (text.Length / 5) + 1; + ValueListBuilder elements = elementCapacity <= InlineElementCount ? new(elementStorage) : new(elementCapacity); + try + { + Parse(text, ref elements); + ValidateSymbol(text, elements.AsSpan()); + AppendElements(text, elements.AsSpan(), ref encoded); + } + finally + { + elements.Dispose(); + } + } + + /// + /// Parses element strings that the caller validated as part of a whole symbol and writes the data a symbol carries, as writes it. + /// The linear component of a GS1 Composite symbol takes this step: the caller validates the element strings of both components together, because the + /// rules of apply to the whole symbol and not to one component. + /// + /// The validated element strings. + /// The builder that receives the encoded data: each Application Identifier and its data in turn, with a + /// after every variable length element string but the last. + /// The text is not valid element string syntax. + public static void Encode(ReadOnlySpan text, ref ValueStringBuilder encoded) + { + InlineArray16 elementStorage = default; + int elementCapacity = (text.Length / 5) + 1; + ValueListBuilder elements = elementCapacity <= InlineElementCount ? new(elementStorage) : new(elementCapacity); + try + { + Parse(text, ref elements); + AppendElements(text, elements.AsSpan(), ref encoded); + } + finally + { + elements.Dispose(); + } + } + + /// + /// Validates element strings: each one against its entry of the GS1 Barcode Syntax Dictionary, and then the invalid pairs of section 4.13.1 of the GS1 + /// General Specifications. A pair in one symbol is a pair on the item that the symbol marks. The mandatory associations of section 4.13.2 are not + /// validated: its note says that associated element strings need not "appear in the same data carrier", so one symbol cannot break them. + /// + /// The buffer the element strings are in. + /// The element strings. + /// An element string or a pair of them breaks a rule. + public static void Validate(ReadOnlySpan buffer, ReadOnlySpan elements) + { + foreach (Gs1Element element in elements) + { + ValidateData(element.Entry, element.Identifier(buffer), element.Data(buffer)); + } + + for (int i = 0; i < elements.Length; i++) + { + ReadOnlySpan identifier = elements[i].Identifier(buffer); + Gs1ApplicationIdentifier entry = elements[i].Entry; + + // Data from several carriers on one item can repeat an element string, but never with a different value. + for (int j = i + 1; j < elements.Length; j++) + { + if (elements[j].Identifier(buffer).SequenceEqual(identifier) && !elements[j].Data(buffer).SequenceEqual(elements[i].Data(buffer))) + { + throw new ArgumentException($"The Application Identifier ({identifier}) appears twice with different data.", nameof(elements)); + } + } + + // Section 4.13.1: an invalid pair. An Application Identifier is never excluded by itself, even when its own pattern matches it. + foreach (string pattern in entry.Exclusions) + { + int match = FindPattern(buffer, elements, pattern, identifier); + if (match >= 0) + { + throw new ArgumentException( + $"The Application Identifiers ({identifier}) and ({elements[match].Identifier(buffer)}) may not appear together.", + nameof(elements)); + } + } + } + + // Table 4-5 of the GS1 General Specifications puts a digital signature, Application Identifier (8030), with a "Global Returnable Asset Identifier + // including Serial component", a "Global Document Type Identifier including Serial component" or a "Global Coupon Number including Serial component". + // So a GRAI (8003), GDTI (253) or GCN (255) in the same symbol as a digital signature carries its optional serial component. + if (FindPattern(buffer, elements, "8030", []) >= 0) + { + foreach (string key in (ReadOnlySpan)["253", "255", "8003"]) + { + int match = FindPattern(buffer, elements, key, []); + if (match >= 0 && elements[match].DataLength == elements[match].Entry.MinimumLength) + { + throw new ArgumentException( + $"The Application Identifier ({key}) carries no serial component, which a digital signature (8030) requires.", + nameof(elements)); + } + } + } + } + + /// + /// Validates the element strings of one whole symbol: the rules of , and the rules that tie element strings to one symbol. Section + /// 3.9.27 of the GS1 General Specifications uses Application Identifier (8200) "in mandatory association with GTIN AI (01) encoded in one symbol". A + /// component of a GS1 Composite symbol is not a whole symbol, so its own element strings take only. + /// + /// The buffer the element strings are in. + /// The element strings. + /// An element string or a pair of them breaks a rule. + public static void ValidateSymbol(ReadOnlySpan buffer, ReadOnlySpan elements) + { + Validate(buffer, elements); + if (FindPattern(buffer, elements, "8200", []) >= 0 && FindPattern(buffer, elements, "01", []) < 0) + { + throw new ArgumentException("The Application Identifier (8200) needs a GTIN, (01), in the same symbol.", nameof(elements)); + } + } + + /// + /// Returns the number of characters writes at most for element strings that + /// accepted. The interpretation is the text with a line break between two element strings, and an element string is at least five + /// characters of text, so the text holds at most a fifth of its length in element strings. + /// + /// The length of the element strings. + /// The capacity the interpretation needs. + public static int ReadableCapacity(int textLength) => textLength + (textLength / 5); + + /// + /// Writes the human readable interpretation of element strings that accepted, each element string as + /// writes it, in the order the symbol carries them. + /// + /// The element strings, in the element string syntax. + /// Whether each element string prints on a line of its own. + /// The buffer the interpretation is written to: long with line breaks, or as long as the text + /// without, since the interpretation on one line is never longer than the text. + /// The number of characters written. + public static int WriteReadable(ReadOnlySpan text, bool lineBreaks, Span readable) + { + // The parse took each element string as an Application Identifier in parentheses and the data up to the next opening parenthesis, so the same cuts find + // the element strings again. + int written = 0; + while (text.Length > 0) + { + int close = text.IndexOf(')'); + ReadOnlySpan rest = text[(close + 1)..]; + int next = rest.IndexOf('('); + int dataLength = next < 0 ? rest.Length : next; + written = WriteReadable(text[1..close], rest[..dataLength], lineBreaks, readable, written); + text = rest[dataLength..]; + } + + return written; + } + + /// + /// Writes one element string of a human readable interpretation. Section 4.14 rule 2c of the GS1 General Specifications puts the Application Identifier in + /// parentheses before its data. A linear symbol prints the element strings on one line, as Figure 4-7 shows for GS1-128. A two-dimensional symbol prints + /// each element string on a line of its own, the preferred format of Figure 4-6, so a line break comes before every element string but the first, and rule + /// 1e keeps an element string on one line. Rule 3b keeps the URL of Application Identifier (8200) out of the interpretation, so that element string writes + /// nothing. + /// + /// The Application Identifier. + /// The data. + /// Whether each element string prints on a line of its own. + /// The buffer the interpretation is written to. + /// The number of characters of the interpretation written before this element string. + /// The number of characters written with this element string. + public static int WriteReadable(ReadOnlySpan identifier, ReadOnlySpan data, bool lineBreaks, Span readable, int written) + { + if (identifier.SequenceEqual("8200")) + { + return written; + } + + if (lineBreaks && written > 0) + { + readable[written++] = BarcodeTextPlacement.LineBreak; + } + + readable[written++] = '('; + identifier.CopyTo(readable[written..]); + written += identifier.Length; + readable[written++] = ')'; + data.CopyTo(readable[written..]); + return written + data.Length; + } + + /// + /// Parses the element string syntax into the positions of its element strings. + /// + /// The element strings to parse. + /// The builder the positions are written to. + /// The text is not valid element string syntax. + public static void Parse(ReadOnlySpan text, ref ValueListBuilder elements) + { + int position = 0; + while (position < text.Length) + { + if (text[position] != '(') + { + SpanCodePointEnumerator opening = text[position..].EnumerateCodePoints(); + opening.MoveNext(); + throw new ArgumentException( + $"An element string opens with an Application Identifier in parentheses; got {opening.Current.ToDisplayString()} at position {position}.", + nameof(text)); + } + + int close = text[position..].IndexOf(')'); + if (close < 0) + { + throw new ArgumentException("An Application Identifier is missing its closing parenthesis.", nameof(text)); + } + + close += position; + ReadOnlySpan identifier = text[(position + 1)..close]; + if (identifier.Length is < 2 or > 4) + { + throw new ArgumentException($"An Application Identifier is two to four digits; got '{identifier}'.", nameof(text)); + } + + BarcodeSymbology.ValidateDigits(identifier); + + int dataStart = close + 1; + int dataEnd = text[dataStart..].IndexOf('('); + dataEnd = dataEnd < 0 ? text.Length : dataEnd + dataStart; + + ReadOnlySpan data = text[dataStart..dataEnd]; + if (data.Length == 0) + { + throw new ArgumentException($"The Application Identifier ({identifier}) carries no data.", nameof(text)); + } + + // The entry is found once here, and every later rule reads it from the element. + int entryIndex = Gs1SyntaxDictionary.IndexOf(identifier); + if (entryIndex < 0) + { + throw new ArgumentException($"({identifier}) is not an Application Identifier of the GS1 General Specifications.", nameof(text)); + } + + elements.Append(new Gs1Element(position + 1, identifier.Length, dataStart, data.Length, entryIndex)); + position = dataEnd; + } + + if (elements.Length == 0) + { + throw new ArgumentException("A GS1 symbol requires at least one element string.", nameof(text)); + } + } + + /// + /// Computes the GS1 check digit for the given data digits using the standard check digit calculation of section 7.9.1 of the GS1 General Specifications: digits are weighted + /// 3 and 1 alternately starting with 3 at the rightmost data digit, and the check digit lifts the sum to the next multiple of ten. + /// + /// The data digits, excluding the check digit. + /// The check digit value. + public static int ComputeCheckDigit(ReadOnlySpan digits) + { + int sum = 0; + int weight = 3; + for (int i = digits.Length - 1; i >= 0; i--) + { + sum += (digits[i] - '0') * weight; + weight = 4 - weight; + } + + return (10 - (sum % 10)) % 10; + } + + /// + /// Reads the element string of GS1 Application Identifier (01) and writes the 14 digit GTIN. The input is (01) and 13 digits, to which the check + /// digit of section 7.9 of the GS1 General Specifications is added, or 14 digits whose last digit is validated as that check digit. + /// + /// The element string. + /// The buffer the 14 digits are written to. + /// The element string is not (01) and 13 or 14 digits, or the check digit is wrong. + public static void ReadGtin(ReadOnlySpan text, Span gtin) + { + if (text.Length is not (17 or 18)) + { + throw new ArgumentException( + $"The element string takes the (01) application identifier and 13 or 14 digits; got {text.Length} characters.", + nameof(text)); + } + + if (!text.Slice(0, 4).SequenceEqual("(01)")) + { + throw new ArgumentException("The element string begins with the (01) application identifier.", nameof(text)); + } + + ReadOnlySpan digits = text.Slice(4); + BarcodeSymbology.ValidateDigits(digits); + + int check = ComputeCheckDigit(digits[..13]); + if (digits.Length == 14 && digits[13] - '0' != check) + { + throw new ArgumentException($"Incorrect GTIN check digit: expected {check}, got {digits[13]}.", nameof(text)); + } + + digits[..13].CopyTo(gtin); + gtin[13] = (char)('0' + check); + } + + /// + /// Validates the data of one element string against its entry of the GS1 Barcode Syntax Dictionary: its length, and each component in turn with its + /// character set and its checks. + /// + /// The dictionary entry of the Application Identifier. + /// The Application Identifier. + /// The data. + /// The data breaks its format. + private static void ValidateData(Gs1ApplicationIdentifier entry, ReadOnlySpan identifier, ReadOnlySpan data) + { + if (data.Length < entry.MinimumLength || data.Length > entry.MaximumLength) + { + string range = entry.MinimumLength == entry.MaximumLength ? $"{entry.MinimumLength}" : $"{entry.MinimumLength} to {entry.MaximumLength}"; + throw new ArgumentException($"The data of Application Identifier ({identifier}) is {range} characters; got {data.Length}.", nameof(data)); + } + + // Each component takes as many characters as remain, up to its maximum. Only the last component can vary in length, and an optional component that + // finds no data left is skipped. + foreach (Gs1Component component in entry.Components) + { + int length = Math.Min(component.Maximum, data.Length); + if (length == 0 && component.Optional) + { + continue; + } + + if (length < component.Minimum) + { + throw new ArgumentException($"The data of Application Identifier ({identifier}) is too short for its components.", nameof(data)); + } + + ReadOnlySpan part = data[..length]; + Gs1Linters.CheckCharacterSet(component.CharacterSet, identifier, part); + foreach (Gs1Linter linter in component.Linters) + { + Gs1Linters.Check(linter, identifier, part); + } + + data = data[length..]; + } + } + + /// + /// Writes the data a symbol carries for parsed element strings: each Application Identifier and its data in turn. Section 7.8.6.2: an element string + /// without a predefined length is terminated by a separator, unless it is the last one in the symbol. The dictionary marks the predefined lengths of + /// Table 7-6. + /// + /// The buffer the element strings are in. + /// The element strings. + /// The builder that receives the encoded data. + private static void AppendElements(scoped ReadOnlySpan text, scoped ReadOnlySpan elements, ref ValueStringBuilder encoded) + { + for (int i = 0; i < elements.Length; i++) + { + Gs1Element element = elements[i]; + encoded.Append(element.Identifier(text)); + encoded.Append(element.Data(text)); + if (i < elements.Length - 1 && !element.Entry.PredefinedLength) + { + encoded.Append(Separator); + } + } + } + + /// + /// Returns the index of the first element string whose Application Identifier matches a pattern of the GS1 Barcode Syntax Dictionary, such as "01" or + /// "310n", where "n" stands for any digit. + /// + /// The buffer the element strings are in. + /// The element strings. + /// The pattern. + /// An Application Identifier that does not count as a match, or empty. + /// The index of the match, or -1. + private static int FindPattern(ReadOnlySpan buffer, ReadOnlySpan elements, string pattern, ReadOnlySpan ignore) + { + for (int i = 0; i < elements.Length; i++) + { + ReadOnlySpan identifier = elements[i].Identifier(buffer); + if (identifier.Length != pattern.Length || (!ignore.IsEmpty && identifier.SequenceEqual(ignore))) + { + continue; + } + + bool match = true; + for (int c = 0; c < pattern.Length && match; c++) + { + match = pattern[c] == 'n' || pattern[c] == identifier[c]; + } + + if (match) + { + return i; + } + } + + return -1; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1DigitalLink.cs b/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1DigitalLink.cs new file mode 100644 index 00000000..e1d47418 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1DigitalLink.cs @@ -0,0 +1,395 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Validates a GS1 Digital Link URI, the GS1 Digital Link Standard: URI Syntax, release 1.7.0: the scheme and the host, the primary identification key and its +/// key qualifiers in the path, and the data attributes in the query string, with the GS1 data rules applied to every Application Identifier the URI carries. +/// +internal static class Gs1DigitalLink +{ + /// + /// The characters a URI can carry, RFC 3986: the unreserved characters, the reserved characters and the percent sign of percent-encoding. + /// + private static readonly SearchValues UriCharacters = + SearchValues.Create("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=%"); + + /// + /// The characters section 4.2 of the GS1 Digital Link Standard says "must be represented using percent-encoding" in a value, other than "#", "/", "%", + /// "&" and "?", which end a value or start a percent-encoding before the value is read. + /// + private static readonly SearchValues PercentEncodedCharacters = SearchValues.Create("!+,()*';:<=>"); + + /// + /// Validates the URI. + /// + /// The URI. + /// The URI is not a valid GS1 Digital Link URI. + public static void Validate(ReadOnlySpan uri) + => Validate(uri, false, default); + + /// + /// Validates the URI and writes the human readable interpretation of the element strings it carries, each on a line of its own as + /// writes it, in the order of the URI: the primary key + /// and its qualifiers in the path, then the data attributes in the query string. + /// + /// The URI. + /// The buffer that receives the interpretation, long. + /// The number of characters written. + /// The URI is not a valid GS1 Digital Link URI. + public static int Validate(ReadOnlySpan uri, Span readable) + => Validate(uri, true, readable); + + /// + /// Returns the number of characters writes at most for a URI. Every decoded Application Identifier + /// and value character comes from at least one character of the URI, and each element string adds two parentheses and a line break to them. The URI holds + /// at most a quarter of its length in element strings. + /// + /// The length of the URI. + /// The capacity the interpretation needs. + public static int ReadableCapacity(int uriLength) => uriLength + (3 * ((uriLength / 4) + 1)); + + /// + /// Validates the URI and, when asked, writes the human readable interpretation of its element strings. + /// + /// The URI. + /// Whether the interpretation is written. + /// The buffer that receives the interpretation when is . + /// The number of characters written. + /// The URI is not a valid GS1 Digital Link URI. + private static int Validate(ReadOnlySpan uri, bool writeReadable, Span readable) + { + int bad = uri.IndexOfAnyExcept(UriCharacters); + if (bad >= 0) + { + throw new ArgumentException($"A URI does not carry '{uri[bad]}', at position {bad + 1}; percent-encode it.", nameof(uri)); + } + + // Section 4.11: the scheme is "http", "https", "HTTP" or "HTTPS". + ReadOnlySpan rest; + if (uri.StartsWith("https://") || uri.StartsWith("HTTPS://")) + { + rest = uri[8..]; + } + else if (uri.StartsWith("http://") || uri.StartsWith("HTTP://")) + { + rest = uri[7..]; + } + else + { + throw new ArgumentException("A GS1 Digital Link URI starts with http:// or https://.", nameof(uri)); + } + + // The host runs to the first slash. Section 4.11 gives it no user information, so it carries no "@", and it cannot be empty. + int slash = rest.IndexOf('/'); + if (slash <= 0 || rest[..slash].Contains('@')) + { + throw new ArgumentException("A GS1 Digital Link URI has a host name and then a path.", nameof(uri)); + } + + // Section 4.11 builds the URI from the path and the query string with no fragment, and section 4.2 percent-encodes "#" in data, so the URI has no "#". + ReadOnlySpan path = rest[slash..]; + if (path.Contains('#')) + { + throw new ArgumentException("A GS1 Digital Link URI has no fragment; percent-encode \"#\" as %23.", nameof(uri)); + } + + ReadOnlySpan query = []; + int question = path.IndexOf('?'); + if (question >= 0) + { + query = path[(question + 1)..]; + path = path[..question]; + } + + // The decoded Application Identifiers and values collect in one buffer, which the GS1 data rules then read through the element positions. Every decoded + // character comes from at least one character of the URI, so the buffer never holds more characters than the URI. An element takes at least four + // characters of the URI, "AI=v" in the query or "/AI/v" in the path, so the URI holds at most a quarter of its length in elements. Neither builder + // grows. + InlineArray64 bufferStorage = default; + InlineArray16 elementStorage = default; + int elementCapacity = (uri.Length / 4) + 1; + ValueStringBuilder buffer = uri.Length <= Gs1Data.InlineBufferLength ? new(bufferStorage) : new(uri.Length); + ValueListBuilder elements = elementCapacity <= Gs1Data.InlineElementCount ? new(elementStorage) : new(elementCapacity); + try + { + int pathCount = ParsePath(path, ref buffer, ref elements); + ParseQuery(query, ref buffer, ref elements); + + ReadOnlySpan decoded = buffer.AsSpan(); + ReadOnlySpan parsed = elements.AsSpan(); + Gs1ApplicationIdentifier key = parsed[0].Entry; + + // Section 4.9: the path is the key and then its qualifiers, in the order of one of the qualifier sequences of the key, any of them left out. + if (!IsKeyPath(key, decoded, parsed[1..pathCount], -1, default)) + { + throw new ArgumentException( + $"The path of the GS1 Digital Link URI is not a primary key ({parsed[0].Identifier(decoded)}) with its qualifiers in order.", + nameof(uri)); + } + + for (int i = pathCount; i < parsed.Length; i++) + { + // Section 4.10: the query string carries the data attributes the standard lists. + ReadOnlySpan identifier = parsed[i].Identifier(decoded); + if (!parsed[i].Entry.DataAttribute) + { + throw new ArgumentException($"The Application Identifier ({identifier}) is not a data attribute of a GS1 Digital Link URI.", nameof(uri)); + } + + // A qualifier of the key in the path belongs in the path, at its place in the sequence. + for (int position = 0; position < pathCount; position++) + { + if (IsKeyPath(key, decoded, parsed[1..pathCount], position, parsed[i])) + { + throw new ArgumentException( + $"The Application Identifier ({identifier}) qualifies the primary key, so it belongs in the path.", + nameof(uri)); + } + } + } + + Gs1Data.ValidateSymbol(decoded, parsed); + int written = 0; + if (writeReadable) + { + foreach (Gs1Element element in parsed) + { + written = Gs1Data.WriteReadable(element.Identifier(decoded), element.Data(decoded), true, readable, written); + } + } + + return written; + } + finally + { + elements.Dispose(); + buffer.Dispose(); + } + } + + /// + /// Parses the path: optional path segments before the key, then the primary identification key and its qualifiers as "/AI/value" pairs to the end. The key + /// is the rightmost pair whose Application Identifier is a primary key, with every pair after it an Application Identifier too. + /// + /// The path, from its first "/". + /// The buffer that receives the decoded Application Identifiers and values. + /// The builder the element positions are written to. + /// The number of element strings in the path. + private static int ParsePath(ReadOnlySpan path, ref ValueStringBuilder buffer, ref ValueListBuilder elements) + { + // Walk back over the pairs from the end until a pair's Application Identifier is a primary key. A segment that is not an Application Identifier ends + // the walk, because the pairs must run to the end of the path. + int keyStart = -1; + int end = path.Length; + while (end > 0) + { + int valueSlash = path[..end].LastIndexOf('/'); + if (valueSlash <= 0) + { + break; + } + + int identifierSlash = path[..valueSlash].LastIndexOf('/'); + if (identifierSlash < 0) + { + break; + } + + Gs1ApplicationIdentifier? entry = Gs1SyntaxDictionary.Find(path[(identifierSlash + 1)..valueSlash]); + if (entry is null) + { + break; + } + + if (entry.KeyQualifiers is not null) + { + keyStart = identifierSlash; + break; + } + + end = identifierSlash; + } + + if (keyStart < 0) + { + throw new ArgumentException("The path of a GS1 Digital Link URI ends with a primary identification key and its qualifiers.", nameof(path)); + } + + // Each pair after the key start is an Application Identifier and its percent-encoded value. + int count = 0; + ReadOnlySpan pairs = path[(keyStart + 1)..]; + while (pairs.Length > 0) + { + int separator = pairs.IndexOf('/'); + ReadOnlySpan identifier = pairs[..separator]; + ReadOnlySpan value = pairs[(separator + 1)..]; + int next = value.IndexOf('/'); + if (next >= 0) + { + pairs = value[(next + 1)..]; + value = value[..next]; + } + else + { + pairs = []; + } + + AppendElement(identifier, value, ref buffer, ref elements); + count++; + } + + return count; + } + + /// + /// Parses the query string. Section 4.11 separates the parameters with "&" or ";". A parameter with an all-digit key is an Application Identifier, and + /// section 4.10.1 reserves those keys for it. Any other parameter is an extension, which carries no GS1 data. + /// + /// The query string, without "?". + /// The buffer the decoded Application Identifiers and values are written to. + /// The builder the element positions are written to. + private static void ParseQuery(ReadOnlySpan query, ref ValueStringBuilder buffer, ref ValueListBuilder elements) + { + while (query.Length > 0) + { + int separator = query.IndexOfAny('&', ';'); + ReadOnlySpan parameter = separator < 0 ? query : query[..separator]; + query = separator < 0 ? [] : query[(separator + 1)..]; + + int equals = parameter.IndexOf('='); + if (equals <= 0 || parameter[..equals].ContainsAnyExceptInRange('0', '9')) + { + continue; + } + + AppendElement(parameter[..equals], parameter[(equals + 1)..], ref buffer, ref elements); + } + } + + /// + /// Decodes one Application Identifier and its value into the buffer. + /// + /// The Application Identifier. + /// The percent-encoded value. + /// The buffer the decoded Application Identifier and value are written to. + /// The builder the element position is written to. + private static void AppendElement( + ReadOnlySpan identifier, + ReadOnlySpan value, + ref ValueStringBuilder buffer, + ref ValueListBuilder elements) + { + int entryIndex = Gs1SyntaxDictionary.IndexOf(identifier); + if (entryIndex < 0) + { + throw new ArgumentException($"({identifier}) is not an Application Identifier of the GS1 General Specifications.", nameof(identifier)); + } + + if (value.Length == 0) + { + throw new ArgumentException($"The Application Identifier ({identifier}) has no value in the GS1 Digital Link URI.", nameof(value)); + } + + int identifierStart = buffer.Length; + buffer.Append(identifier); + int dataStart = buffer.Length; + + // Section 4.2: the characters that have special meanings in URIs "must be represented using percent-encoding". + int literal = value.IndexOfAny(PercentEncodedCharacters); + if (literal >= 0) + { + throw new ArgumentException( + $"The value of Application Identifier ({identifier}) carries '{value[literal]}', which a GS1 Digital Link URI percent-encodes.", + nameof(value)); + } + + // Section 4.11: pct-encoded = "%" HEXDIG HEXDIG, the percent-encoding of RFC 3986, so two hexadecimal digits stand for one character. + for (int i = 0; i < value.Length; i++) + { + char c = value[i]; + if (c == '%') + { + if (i + 2 >= value.Length || !char.IsAsciiHexDigit(value[i + 1]) || !char.IsAsciiHexDigit(value[i + 2])) + { + throw new ArgumentException( + $"The value of Application Identifier ({identifier}) carries a \"%\" without two hexadecimal digits; percent-encode it as %25.", + nameof(value)); + } + + int decoded = (HexValue(value[i + 1]) << 4) | HexValue(value[i + 2]); + if (decoded == 0) + { + throw new ArgumentException($"The value of Application Identifier ({identifier}) decodes to a null character.", nameof(value)); + } + + buffer.Append((char)decoded); + i += 2; + } + else + { + buffer.Append(c); + } + } + + elements.Append(new Gs1Element(identifierStart, identifier.Length, dataStart, buffer.Length - dataStart, entryIndex)); + } + + /// + /// Returns the value of a hexadecimal digit. + /// + /// The digit. + /// The value, 0 to 15. + private static int HexValue(char c) => c <= '9' ? c - '0' : (c | 0x20) - 'a' + 10; + + /// + /// Returns whether the qualifiers in the path, with one more qualifier inserted when one is given, form one of the qualifier sequences of the key in order, + /// with any of its qualifiers left out. + /// + /// The entry of the primary key. + /// The buffer the element strings are in. + /// The qualifiers in the path, in order. + /// The index the extra qualifier goes before, or -1 for none. + /// The extra qualifier. + /// when the sequence is valid for the key. + private static bool IsKeyPath( + Gs1ApplicationIdentifier key, + ReadOnlySpan buffer, + ReadOnlySpan qualifiers, + int insertAt, + Gs1Element inserted) + { + int count = qualifiers.Length + (insertAt >= 0 ? 1 : 0); + foreach (string[] sequence in key.KeyQualifiers!) + { + // Each qualifier must be found in the sequence after the one before it. + int next = 0; + bool valid = true; + for (int q = 0; q < count && valid; q++) + { + ReadOnlySpan identifier = q == insertAt + ? inserted.Identifier(buffer) + : qualifiers[insertAt >= 0 && q > insertAt ? q - 1 : q].Identifier(buffer); + + while (next < sequence.Length && !identifier.SequenceEqual(sequence[next])) + { + next++; + } + + valid = next < sequence.Length; + next++; + } + + if (valid) + { + return true; + } + } + + return false; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1Element.cs b/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1Element.cs new file mode 100644 index 00000000..73a2c4ac --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1Element.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The position of one element string in a buffer, its Application Identifier and its data, with the dictionary entry of the Application Identifier, which is +/// found once when the element string is parsed. +/// +internal readonly struct Gs1Element +{ + /// + /// Initializes a new instance of the struct. + /// + /// The index of the first digit of the Application Identifier. + /// The number of digits of the Application Identifier. + /// The index of the first character of the data. + /// The number of characters of the data. + /// The index of the entry of the Application Identifier in . + public Gs1Element(int identifierStart, int identifierLength, int dataStart, int dataLength, int entryIndex) + { + this.IdentifierStart = identifierStart; + this.IdentifierLength = identifierLength; + this.DataStart = dataStart; + this.DataLength = dataLength; + this.EntryIndex = entryIndex; + } + + /// + /// Gets the index of the first digit of the Application Identifier. + /// + public int IdentifierStart { get; } + + /// + /// Gets the number of digits of the Application Identifier. + /// + public int IdentifierLength { get; } + + /// + /// Gets the index of the first character of the data. + /// + public int DataStart { get; } + + /// + /// Gets the number of characters of the data. + /// + public int DataLength { get; } + + /// + /// Gets the index of the entry of the Application Identifier in . + /// + public int EntryIndex { get; } + + /// + /// Gets the dictionary entry of the Application Identifier. + /// + public Gs1ApplicationIdentifier Entry => Gs1SyntaxDictionary.Entries[this.EntryIndex]; + + /// + /// Returns the Application Identifier from the buffer. + /// + /// The buffer the element string is in. + /// The Application Identifier. + public ReadOnlySpan Identifier(ReadOnlySpan buffer) => buffer.Slice(this.IdentifierStart, this.IdentifierLength); + + /// + /// Returns the data from the buffer. + /// + /// The buffer the element string is in. + /// The data. + public ReadOnlySpan Data(ReadOnlySpan buffer) => buffer.Slice(this.DataStart, this.DataLength); +} diff --git a/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1Linter.cs b/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1Linter.cs new file mode 100644 index 00000000..b2b189a9 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1Linter.cs @@ -0,0 +1,180 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The content checks a component of a GS1 Application Identifier takes, by the names the GS1 Barcode Syntax Dictionary gives them. +/// +internal enum Gs1Linter +{ + /// + /// The last digit is the GS1 check digit of the digits before it. + /// + Csum, + + /// + /// The last two characters are the GS1 check character pair of the characters before them. + /// + CsumAlpha, + + /// + /// The component starts with a GS1 Company Prefix. + /// + GcpPos1, + + /// + /// The component starts with one digit and then a GS1 Company Prefix. + /// + GcpPos2, + + /// + /// A date YYMMDD, where the day can be 00. + /// + Yymmd0, + + /// + /// A date YYMMDD. + /// + Yymmdd, + + /// + /// A date YYYYMMDD. + /// + Yyyymmdd, + + /// + /// An hour HH. + /// + Hh, + + /// + /// A minute MI. + /// + Mi, + + /// + /// A second SS. + /// + Ss, + + /// + /// A time HHMI. + /// + Hhmi, + + /// + /// An ISO 4217 numeric currency code. + /// + Iso4217, + + /// + /// An ISO 3166-1 numeric country code. + /// + Iso3166, + + /// + /// An ISO 3166-1 numeric country code, or 999. + /// + Iso3166999, + + /// + /// An ISO 3166-1 alpha-2 country code. + /// + Iso3166Alpha2, + + /// + /// An ISO/IEC 5218 code for the sex of a person. + /// + Iso5218, + + /// + /// Percent-encoding is well formed. + /// + PercentEncoded, + + /// + /// The single digit 0 or 1. + /// + YesNo, + + /// + /// Hyphens only. + /// + Hyphen, + + /// + /// A latitude of ten digits, at most 1800000000. + /// + Latitude, + + /// + /// A longitude of ten digits, at most 3599999999. + /// + Longitude, + + /// + /// The importer index of a GS1 UIC: one character of the file-safe base64 alphabet. + /// + ImporterIndex, + + /// + /// A package type code. + /// + PackageType, + + /// + /// An AIDC media type. + /// + MediaType, + + /// + /// A position in a sequence, N/M with 1 <= N <= M. + /// + PositionInSequence, + + /// + /// Digits that are not all zero. + /// + NonZero, + + /// + /// A winding direction: 0, 1 or 9. + /// + Winding, + + /// + /// Zeros only. + /// + Zero, + + /// + /// A piece number and a total count of equal length, with 1 <= piece <= total. + /// + PieceOfTotal, + + /// + /// An International Bank Account Number. + /// + Iban, + + /// + /// Digits with no leading zero, or the single digit 0. + /// + NoZeroPrefix, + + /// + /// At least one character that is not a digit. + /// + HasNonDigit, + + /// + /// The coupon code of Application Identifier (8110). + /// + CouponCode, + + /// + /// The positive offer file coupon code of Application Identifier (8112). + /// + CouponPositiveOffer, +} diff --git a/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1Linters.cs b/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1Linters.cs new file mode 100644 index 00000000..b36a71cd --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1Linters.cs @@ -0,0 +1,742 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The content checks of the components of GS1 Application Identifiers: the character sets of section 7.11 of the GS1 General Specifications, the check digit +/// and check character pair of sections 7.9.1 and 7.9.5, the dates of section 7.12, and the code lists and formats the GS1 Barcode Syntax Dictionary names. +/// +internal static class Gs1Linters +{ + /// + /// The length of the shortest GS1 Company Prefix. + /// + private const int MinimumCompanyPrefix = 4; + + /// + /// The longest International Bank Account Number, ISO 13616. + /// + private const int MaximumIban = 34; + + /// + /// The shortest International Bank Account Number the check accepts. + /// + private const int MinimumIban = 11; + + /// + /// The length the coupon funder ID and serial number length indicators of a positive offer file coupon code add to. + /// + private const int LengthIndicatorOffset = 6; + + /// + /// The characters of the check character pair, section 7.9.5: the digits and capital letters without 0, 1, I and O, in the order of their values 0 to 31. + /// + private const string CheckPairCharacters = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"; + + /// + /// The modulus of the weighted sum of the check character pair, section 7.9.5. + /// + private const int CheckPairModulus = 1021; + + /// + /// The number of bits of each of the two values of the check character pair. Each value selects one of the 32 characters of + /// . + /// + private const int CheckPairValueBits = 5; + + /// + /// GS1 AI encodable character set 82, Table 7-20. A instance checks a whole component with one vectorized search. + /// + private static readonly SearchValues CharacterSet82 = + SearchValues.Create("!\"%&'()*+,-./0123456789:;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"); + + /// + /// GS1 AI encodable character set 39, Table 7-21. + /// + private static readonly SearchValues CharacterSet39 = SearchValues.Create("#-/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + + /// + /// The characters of an International Bank Account Number in electronic form, ISO 13616: digits and capital letters. + /// + private static readonly SearchValues Iban = SearchValues.Create("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + + /// + /// GS1 AI encodable character set 64, Table 7-22, without its pad character. + /// + private static readonly SearchValues CharacterSet64 = SearchValues.Create("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"); + + /// + /// Gets the ISO 4217 numeric currency codes 000 to 999 as a bit field, code n at bit 7 - (n mod 8) of byte n div 8. The constant bytes compile into the + /// assembly data, so reading them allocates nothing. + /// + private static ReadOnlySpan Iso4217Numeric => + [ + 0x00, 0x88, 0x00, 0x00, 0x88, 0x08, 0xb8, 0x08, 0x88, 0x80, 0x08, 0x20, 0x80, 0x88, 0x08, 0x08, + 0x08, 0x80, 0x80, 0x88, 0x00, 0x22, 0x00, 0x08, 0x80, 0x10, 0x82, 0x02, 0x02, 0x82, 0x20, 0x00, + 0x02, 0x02, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x88, 0x88, 0x08, 0x88, 0x88, 0x88, 0x80, 0x80, + 0x08, 0x8a, 0x88, 0xa2, 0x62, 0x22, 0x20, 0x02, 0x02, 0x22, 0x00, 0x00, 0x88, 0x00, 0xa0, 0x80, + 0x88, 0x08, 0x0c, 0x00, 0x08, 0x22, 0x02, 0x00, 0x20, 0x22, 0x02, 0x88, 0x80, 0x00, 0x00, 0x20, + 0x12, 0x02, 0x00, 0x00, 0x00, 0x20, 0x20, 0x02, 0xa2, 0x00, 0x00, 0x80, 0x00, 0x08, 0x88, 0x88, + 0x00, 0x88, 0x88, 0x00, 0x81, 0x00, 0x20, 0x20, 0x20, 0x80, 0x00, 0x28, 0x00, 0x00, 0x22, 0x00, + 0x04, 0x00, 0x00, 0x0f, 0xe6, 0xad, 0xbf, 0xdf, 0xfd, 0xfd, 0xfc, 0xe2, 0x25, + ]; + + /// + /// Gets the reference values of Table 7-18 by character code, 0 to 81 for the characters of character set 82 in code order, and 255 for the other codes + /// below 128. + /// + private static ReadOnlySpan ReferenceValues => + [ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 0, 1, 255, 255, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, + 255, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 255, 255, 255, 255, 55, + 255, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, + 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 255, 255, 255, 255, 255, + ]; + + /// + /// Gets the first 97 primes, the weights of section 7.9.5 step 2 from the rightmost data character. A component of character set 82 is at most 90 + /// characters, so the data before a check pair takes fewer weights than these. + /// + private static ReadOnlySpan Primes => + [ + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, + 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, + 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, + 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, + 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, + 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, + 509, + ]; + + /// + /// Gets the 431 package type codes of Application Identifier (7041), each packed into one number and sorted for a binary search. A code is one to three + /// characters from 0 to 9 and A to Z, so it packs as three base 37 digits, the first character the most significant: 0 for no character, 1 to 10 for the + /// digits and 11 to 36 for the letters. "1A" is 2 x 1369 + 11 x 37 + 0 = 3145, and the largest key, 36 x 1369 + 36 x 37 + 36 = 50652, fits a + /// . + /// + private static ReadOnlySpan PackageTypes => + [ + 3145, 3182, 3256, 3330, 3367, 3959, 4145, 4146, 4147, 4148, 4149, 4150, 4151, 4182, + 4183, 4184, 4588, 5883, 6142, 6993, 7030, 7252, 7289, 7326, 7363, 7437, 7474, 7511, + 8880, 9028, 9065, 10249, 10545, 11359, 11396, 12321, 12728, 12765, 12802, 13690, 15466, 15503, + 15540, 15577, 15651, 15688, 15725, 15762, 15799, 15873, 15910, 16021, 16036, 16169, 16243, 16613, + 16872, 16909, 16946, 16983, 17020, 17057, 17072, 17094, 17131, 17168, 17205, 17242, 17279, 17294, + 17316, 17353, 17390, 17427, 17464, 17483, 17501, 17538, 17575, 17612, 17649, 17686, 17723, 17760, + 18204, 18241, 18263, 18278, 18293, 18315, 18352, 18389, 18426, 18463, 18500, 18537, 18574, 18611, + 18648, 18685, 18722, 18759, 18796, 18833, 18870, 18907, 18944, 18981, 19018, 19055, 19092, 19129, + 19573, 19610, 19647, 19795, 19832, 19869, 19906, 19943, 19980, 20017, 20054, 20128, 20143, 20202, + 20239, 20276, 20313, 20350, 20387, 20424, 20461, 20609, 20646, 20683, 21016, 21053, 21090, 21127, + 21164, 21201, 21238, 21423, 22348, 22385, 22422, 22459, 22607, 22718, 22829, 22841, 22866, 22881, + 22940, 23014, 23125, 23162, 23717, 23976, 24087, 24309, 24420, 24568, 24605, 25049, 25086, 25123, + 25271, 25530, 25678, 26418, 26455, 26492, 26529, 26566, 26603, 26640, 26677, 26788, 26825, 26899, + 27343, 27824, 27861, 28009, 28416, 28490, 28675, 29378, 29452, 30537, 30673, 30747, 31228, 31265, + 31302, 31450, 31894, 31931, 31968, 32042, 32464, 32523, 32560, 32597, 32708, 32745, 33263, 33411, + 33448, 33485, 33929, 33966, 34003, 34040, 34632, 34669, 34706, 34743, 34780, 34817, 35002, 35202, + 35335, 35372, 35705, 36001, 36016, 36038, 36075, 36112, 36149, 36186, 36223, 36260, 36297, 36334, + 36371, 36408, 36434, 36482, 36519, 36545, 36556, 36571, 36630, 36704, 36741, 36756, 36778, 36852, + 36889, 36926, 37370, 37407, 37444, 37481, 37555, 37592, 37629, 37703, 37740, 37777, 37814, 37851, + 37925, 37962, 37999, 38036, 38778, 38779, 38780, 38825, 38850, 38961, 39072, 39109, 39146, 39257, + 39442, 39664, 39775, 40108, 40145, 40182, 40219, 40256, 40269, 40367, 40404, 40478, 40515, 40552, + 40626, 40663, 40774, 40811, 40833, 40848, 40885, 40922, 40959, 40996, 41033, 41144, 41514, 41551, + 41588, 41625, 41657, 41699, 41751, 41773, 41847, 41884, 41958, 41995, 42106, 42121, 42143, 42180, + 42195, 42217, 42254, 42291, 42306, 42365, 42402, 42920, 43327, 43601, 44215, 44437, 44511, 44585, + 44622, 44696, 44733, 44770, 44807, 44844, 44881, 45103, 45584, 45621, 45658, 45695, 45769, 45806, + 45843, 45917, 45954, 45991, 46028, 46065, 46139, 46176, 46213, 46239, 46250, 46287, 46324, 46361, + 46398, 46435, 46472, 46509, 46622, 46623, 46626, 46627, 46628, 46629, 46630, 46658, 46694, 46953, + 46990, 47027, 47064, 47138, 47175, 47212, 47286, 47323, 48322, 48359, 48396, 48433, 48507, 48544, + 48581, 48655, 48692, 48729, 48766, 48803, 48877, 48914, 48951, 48988, 49025, 49099, 49136, 49173, + 49210, 49247, 49691, 49728, 49765, 49802, 49876, 49913, 49950, 50024, 50061, 50098, 50135, 50172, + 50246, 50283, 50320, 50357, 50394, 50431, 50468, 50505, 50542, 50579, 50616, + ]; + + /// + /// Checks the characters of a component against its character set: the digits, GS1 AI encodable character set 82 of Table 7-20, character set 39 of + /// Table 7-21 or character set 64 of Table 7-22. + /// + /// The character set. + /// The Application Identifier, for the error message. + /// The component. + /// A character is not in the set. + public static void CheckCharacterSet(Gs1CharacterSet characterSet, ReadOnlySpan identifier, ReadOnlySpan data) + { + // Character set 64 is base64 with the pad character, which can only end the data, padding it to a multiple of three characters with one or two pads. + if (characterSet == Gs1CharacterSet.Set64) + { + int pads = data.Length - data.TrimEnd('=').Length; + if (pads > 2 || (pads > 0 && data.Length % 3 != 0)) + { + ThrowInvalidData(identifier, "has base64 padding that is not one or two '=' to a multiple of three characters", nameof(data)); + } + + data = data[..^pads]; + } + + // One vectorized search finds the first character outside the set, or -1. + int bad = characterSet switch + { + Gs1CharacterSet.Digits => data.IndexOfAnyExceptInRange('0', '9'), + Gs1CharacterSet.Set82 => data.IndexOfAnyExcept(CharacterSet82), + Gs1CharacterSet.Set39 => data.IndexOfAnyExcept(CharacterSet39), + _ => data.IndexOfAnyExcept(CharacterSet64), + }; + + if (bad >= 0) + { + string set = characterSet switch + { + Gs1CharacterSet.Digits => "digits", + Gs1CharacterSet.Set82 => "GS1 AI encodable character set 82", + Gs1CharacterSet.Set39 => "GS1 AI encodable character set 39", + _ => "GS1 AI encodable character set 64", + }; + + ThrowInvalidData(identifier, $"carries '{data[bad]}' at position {bad + 1}, which is not in {set}", nameof(data)); + } + } + + /// + /// Runs one linter over a component. + /// + /// The linter. + /// The Application Identifier, for the error message. + /// The component, already checked against its character set. Its length is in the length range of the component, so a fixed + /// length component has its exact length. The checks rely on this and do not check the length again. + /// The component fails the check. + public static void Check(Gs1Linter linter, ReadOnlySpan identifier, ReadOnlySpan data) + { + switch (linter) + { + case Gs1Linter.Csum: + // Section 7.9.1: the digits weigh 3 and 1 alternately from the digit before the check digit, and the check digit lifts the sum to a multiple of + // 10. + if (data[^1] - '0' != Gs1Data.ComputeCheckDigit(data[..^1])) + { + ThrowInvalidData(identifier, "has an incorrect check digit", nameof(data)); + } + + break; + + case Gs1Linter.CsumAlpha: + CheckPair(identifier, data); + break; + + case Gs1Linter.GcpPos1: + // A GS1 Company Prefix is at least four digits. Which prefixes are allocated is not known here, so only the digits are checked. + if (data.Length < MinimumCompanyPrefix || data[..MinimumCompanyPrefix].ContainsAnyExceptInRange('0', '9')) + { + ThrowInvalidData(identifier, "does not start with a GS1 Company Prefix of at least four digits", nameof(data)); + } + + break; + + case Gs1Linter.GcpPos2: + if (data.Length < 1 + MinimumCompanyPrefix || data.Slice(1, MinimumCompanyPrefix).ContainsAnyExceptInRange('0', '9')) + { + ThrowInvalidData(identifier, "does not carry a GS1 Company Prefix of at least four digits after its first digit", nameof(data)); + } + + break; + + case Gs1Linter.Yymmd0: + CheckDate(identifier, data, true); + break; + + case Gs1Linter.Yymmdd: + CheckDate(identifier, data, false); + break; + + case Gs1Linter.Yyyymmdd: + CheckFullDate(identifier, data, Number(data[..4]), false); + break; + + case Gs1Linter.Hh: + CheckRange(identifier, data, 23, "an hour from 00 to 23"); + break; + + case Gs1Linter.Mi: + CheckRange(identifier, data, 59, "a minute from 00 to 59"); + break; + + case Gs1Linter.Ss: + CheckRange(identifier, data, 59, "a second from 00 to 59"); + break; + + case Gs1Linter.Hhmi: + CheckRange(identifier, data[..2], 23, "an hour from 00 to 23"); + CheckRange(identifier, data[2..], 59, "a minute from 00 to 59"); + break; + + case Gs1Linter.Iso4217: + if (!Iso3166.IsSet(Iso4217Numeric, Number(data))) + { + ThrowInvalidData(identifier, $"carries '{data}', which is not an ISO 4217 numeric currency code", nameof(data)); + } + + break; + + case Gs1Linter.Iso3166: + if (!Iso3166.IsNumericCode(Number(data))) + { + ThrowInvalidData(identifier, $"carries '{data}', which is not an ISO 3166-1 numeric country code", nameof(data)); + } + + break; + + case Gs1Linter.Iso3166999: + // The value 999 stands for a country that is not known. + if (!data.SequenceEqual("999") && !Iso3166.IsNumericCode(Number(data))) + { + ThrowInvalidData(identifier, $"carries '{data}', which is not an ISO 3166-1 numeric country code or 999", nameof(data)); + } + + break; + + case Gs1Linter.Iso3166Alpha2: + if (!Iso3166.IsAlpha2Code(data)) + { + ThrowInvalidData(identifier, $"carries '{data}', which is not an ISO 3166-1 alpha-2 country code", nameof(data)); + } + + break; + + case Gs1Linter.Iso5218: + // ISO/IEC 5218 has the four codes 0, 1, 2 and 9. + if (data[0] is not ('0' or '1' or '2' or '9')) + { + ThrowInvalidData(identifier, "does not carry an ISO/IEC 5218 code 0, 1, 2 or 9", nameof(data)); + } + + break; + + case Gs1Linter.PercentEncoded: + // Section 7.11: the AIs 4300 to 4320 can percent-encode characters as RFC 3986 defines, so every percent sign starts two hexadecimal digits. + for (int i = 0; i < data.Length; i++) + { + if (data[i] == '%') + { + if (i + 2 >= data.Length || !char.IsAsciiHexDigit(data[i + 1]) || !char.IsAsciiHexDigit(data[i + 2])) + { + ThrowInvalidData(identifier, $"has a '%' at position {i + 1} that does not start two hexadecimal digits", nameof(data)); + } + + i += 2; + } + } + + break; + + case Gs1Linter.YesNo: + if (data[0] is not ('0' or '1')) + { + ThrowInvalidData(identifier, "does not carry 0 or 1", nameof(data)); + } + + break; + + case Gs1Linter.Hyphen: + if (data.ContainsAnyExcept('-')) + { + ThrowInvalidData(identifier, "does not carry a hyphen where one is required", nameof(data)); + } + + break; + + case Gs1Linter.Latitude: + // Section 7.13: X = 10 000 000 x (latitude + 90), so it is at most 1 800 000 000. + if (long.Parse(data, CultureInfo.InvariantCulture) > 1_800_000_000) + { + ThrowInvalidData(identifier, "does not carry a latitude of ten digits up to 1800000000", nameof(data)); + } + + break; + + case Gs1Linter.Longitude: + // Section 7.13: Y = 10 000 000 x ((longitude + 360) mod 360). The remainder is below 360, so Y is at most 3 599 999 999. + if (long.Parse(data, CultureInfo.InvariantCulture) > 3_599_999_999) + { + ThrowInvalidData(identifier, "does not carry a longitude of ten digits up to 3599999999", nameof(data)); + } + + break; + + case Gs1Linter.ImporterIndex: + if (!(data[0] is '-' or '_' || char.IsAsciiLetterOrDigit(data[0]))) + { + ThrowInvalidData(identifier, "does not carry an importer index of one character from the file-safe base64 alphabet", nameof(data)); + } + + break; + + case Gs1Linter.PackageType: + if (!IsPackageType(data)) + { + ThrowInvalidData(identifier, $"carries '{data}', which is not a package type code", nameof(data)); + } + + break; + + case Gs1Linter.MediaType: + // The AIDC media types 01 to 10 are assigned, and 80 to 99 are for local or national use. + int mediaType = Number(data); + if (mediaType is not ((>= 1 and <= 10) or (>= 80 and <= 99))) + { + ThrowInvalidData(identifier, $"carries '{data}', which is not an AIDC media type", nameof(data)); + } + + break; + + case Gs1Linter.PositionInSequence: + CheckPositionInSequence(identifier, data); + break; + + case Gs1Linter.NonZero: + if (!data.ContainsAnyExcept('0')) + { + ThrowInvalidData(identifier, "carries a zero value where one is not allowed", nameof(data)); + } + + break; + + case Gs1Linter.Winding: + if (data[0] is not ('0' or '1' or '9')) + { + ThrowInvalidData(identifier, "does not carry a winding direction 0, 1 or 9", nameof(data)); + } + + break; + + case Gs1Linter.Zero: + if (data.ContainsAnyExcept('0')) + { + ThrowInvalidData(identifier, "does not carry a zero where one is required", nameof(data)); + } + + break; + + case Gs1Linter.PieceOfTotal: + CheckPieceOfTotal(identifier, data); + break; + + case Gs1Linter.Iban: + CheckIban(identifier, data); + break; + + case Gs1Linter.NoZeroPrefix: + // Section 3.9.11: the serial number "SHALL NOT begin with a "0" digit, unless the entire serial number consists of the single digit "0"". + if (data[0] == '0' && data.Length > 1) + { + ThrowInvalidData(identifier, "starts with a zero, which is allowed only for the single digit 0", nameof(data)); + } + + break; + + case Gs1Linter.HasNonDigit: + if (!data.ContainsAnyExceptInRange('0', '9')) + { + ThrowInvalidData(identifier, "carries only digits where at least one other character is required", nameof(data)); + } + + break; + + case Gs1Linter.CouponCode: + // Table C of the North American Coupon Application Guideline defines the coupon code. + _ = NorthAmericanCouponCode.Validate(data); + break; + + default: + CheckPositiveOfferCoupon(identifier, data); + break; + } + } + + /// + /// Returns the number the digits stand for. + /// + /// The digits, at most nine. + /// The number. + private static int Number(ReadOnlySpan digits) + { + int value = 0; + foreach (char c in digits) + { + value = (value * 10) + (c - '0'); + } + + return value; + } + + /// + /// Throws the exception for data that fails a check. The message names the Application Identifier, so the caller states the problem as the end of a + /// sentence about its data. + /// + /// The Application Identifier. + /// The problem, as the end of a sentence about the data. + /// The name of the parameter that holds the data. + /// Always thrown. + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowInvalidData(ReadOnlySpan identifier, string problem, string paramName) + => throw new ArgumentException($"The data of Application Identifier ({identifier}) {problem}.", paramName); + + /// + /// Checks a two digit field against a maximum value. + /// + /// The Application Identifier. + /// The field, two digits. + /// The largest value. + /// What the field holds, for the error message. + private static void CheckRange(ReadOnlySpan identifier, ReadOnlySpan data, int maximum, string what) + { + if (Number(data) > maximum) + { + ThrowInvalidData(identifier, $"does not carry {what}", nameof(data)); + } + } + + /// + /// Checks a date YYMMDD. Section 7.12, Figure 7-17: the two digit year is the year within 49 years before and 50 years after the current year, which + /// decides whether February has 29 days. + /// + /// The Application Identifier. + /// The six digits. + /// Whether a day of 00 is allowed. + private static void CheckDate(ReadOnlySpan identifier, ReadOnlySpan data, bool dayZero) + { + // The current century holds the years from 49 years back to 50 years ahead. A two digit year more than 50 years ahead in the current century belongs to + // the century before, and one more than 49 years back belongs to the century after. + int currentYear = DateTime.UtcNow.Year; + int century = currentYear / 100 * 100; + int year = century + Number(data[..2]); + if (year - currentYear > 50) + { + year -= 100; + } + else if (year - currentYear < -49) + { + year += 100; + } + + CheckFullDate(identifier, data[2..], year, dayZero); + } + + /// + /// Checks the month and the day of a date. + /// + /// The Application Identifier. + /// The date, YYYYMMDD or the MMDD that ends it. + /// The year. + /// Whether a day of 00 is allowed. + private static void CheckFullDate(ReadOnlySpan identifier, ReadOnlySpan data, int year, bool dayZero) + { + ReadOnlySpan monthDay = data[^4..]; + int month = Number(monthDay[..2]); + int day = Number(monthDay[2..]); + if (month is < 1 or > 12) + { + ThrowInvalidData(identifier, $"carries the month {month:D2}, which is not 01 to 12", nameof(data)); + } + + // The runtime gives the days of the month in the Gregorian calendar, leap years included, for the years 1 to 9999. The calendar repeats every 400 + // years, so the year 0000 of a YYYYMMDD date has the month lengths of the year 400. + int lastDay = DateTime.DaysInMonth(year == 0 ? 400 : year, month); + if (day > lastDay || (day == 0 && !dayZero)) + { + ThrowInvalidData(identifier, $"carries the day {day:D2}, which is not a day of month {month:D2} of {year}", nameof(data)); + } + } + + /// + /// Checks the check character pair of section 7.9.5: the reference values of Table 7-18 weighted by the primes 2, 3, 5 and on from the rightmost data + /// character, summed modulo 1021, give two values of five bits each, which the last two characters stand for. + /// + /// The Application Identifier. + /// The data, ending with the check character pair. + private static void CheckPair(ReadOnlySpan identifier, ReadOnlySpan data) + { + if (data.Length < 2) + { + ThrowInvalidData(identifier, "is too short to carry a check character pair", nameof(data)); + } + + // The data is already checked against character set 82, so every character has a reference value. + ReadOnlySpan values = ReferenceValues; + ReadOnlySpan primes = Primes; + int sum = 0; + for (int i = data.Length - 3, weight = 0; i >= 0; i--, weight++) + { + sum += values[data[i]] * primes[weight]; + } + + sum %= CheckPairModulus; + if (data[^2] != CheckPairCharacters[sum >> CheckPairValueBits] || data[^1] != CheckPairCharacters[sum & ((1 << CheckPairValueBits) - 1)]) + { + ThrowInvalidData(identifier, "has an incorrect check character pair", nameof(data)); + } + } + + /// + /// Returns whether the data is a package type code. + /// + /// The data. + /// when the code is in the list. + private static bool IsPackageType(ReadOnlySpan data) + { + if (data.Length is < 1 or > 3) + { + return false; + } + + // Pack the code as the table does. A character outside 0 to 9 and A to Z is in no code. + int key = 0; + for (int i = 0; i < 3; i++) + { + int digit = 0; + if (i < data.Length) + { + char c = data[i]; + if (char.IsAsciiDigit(c)) + { + digit = c - '0' + 1; + } + else if (char.IsAsciiLetterUpper(c)) + { + digit = c - 'A' + 11; + } + else + { + return false; + } + } + + key = (key * 37) + digit; + } + + return PackageTypes.BinarySearch((ushort)key) >= 0; + } + + /// + /// Checks a position in a sequence, N/M: two numbers without leading zeros and N no larger than M. + /// + /// The Application Identifier. + /// The data. + private static void CheckPositionInSequence(ReadOnlySpan identifier, ReadOnlySpan data) + { + int slash = data.IndexOf('/'); + ReadOnlySpan position = slash < 0 ? data : data[..slash]; + ReadOnlySpan end = slash < 0 ? [] : data[(slash + 1)..]; + if (position.Length == 0 + || end.Length == 0 + || position.ContainsAnyExceptInRange('0', '9') + || end.ContainsAnyExceptInRange('0', '9') + || position[0] == '0' + || end[0] == '0') + { + ThrowInvalidData(identifier, "does not carry a position in a sequence N/M of two numbers without leading zeros", nameof(data)); + } + + // Without leading zeros a longer number is larger, and numbers of one length compare as text. + if (position.Length > end.Length || (position.Length == end.Length && position.SequenceCompareTo(end) > 0)) + { + ThrowInvalidData(identifier, "carries a position after the end of its sequence", nameof(data)); + } + } + + /// + /// Checks a piece number and a total count: the data splits into two halves of equal length, neither is zero, and the piece is not after the total. + /// + /// The Application Identifier. + /// The data. + private static void CheckPieceOfTotal(ReadOnlySpan identifier, ReadOnlySpan data) + { + // The dictionary gives this check only to components of a fixed even length, so the two halves have one length. + ReadOnlySpan piece = data[..(data.Length / 2)]; + ReadOnlySpan total = data[(data.Length / 2)..]; + if (!piece.ContainsAnyExcept('0') || !total.ContainsAnyExcept('0')) + { + ThrowInvalidData(identifier, "carries a piece number or a total count of zero", nameof(data)); + } + + // The two halves have one length, so they compare as text. + if (piece.SequenceCompareTo(total) > 0) + { + ThrowInvalidData(identifier, "carries a piece number larger than the total count", nameof(data)); + } + } + + /// + /// Checks an International Bank Account Number, ISO 13616: an ISO 3166-1 alpha-2 country code, then characters whose value, read with the first four + /// characters moved to the end and each letter as two digits from 10 to 35, leaves 1 modulo 97. + /// + /// The Application Identifier. + /// The data. + private static void CheckIban(ReadOnlySpan identifier, ReadOnlySpan data) + { + if (data.Length < MinimumIban || data.Length > MaximumIban) + { + ThrowInvalidData(identifier, "does not carry an International Bank Account Number of 11 to 34 characters", nameof(data)); + } + + Check(Gs1Linter.Iso3166Alpha2, identifier, data[..2]); + + // The electronic form holds digits and capital letters only. + int invalid = data.IndexOfAnyExcept(Iban); + if (invalid >= 0) + { + ThrowInvalidData(identifier, $"carries '{data[invalid]}', which an International Bank Account Number does not hold", nameof(data)); + } + + if (Iso7064.Mod97(data) != 1) + { + ThrowInvalidData(identifier, "has incorrect International Bank Account Number check digits", nameof(data)); + } + } + + /// + /// Checks the positive offer file coupon code of Application Identifier (8112): the format code 0 or 1, the coupon funder ID with its length indicator, the + /// six digit offer code and the serial number with its length indicator, and nothing after them. + /// + /// The Application Identifier. + /// The data. + private static void CheckPositiveOfferCoupon(ReadOnlySpan identifier, ReadOnlySpan data) + { + if (data.ContainsAnyExceptInRange('0', '9')) + { + ThrowInvalidData(identifier, "carries a character that is not a digit", nameof(data)); + } + + if (data[0] is not ('0' or '1')) + { + ThrowInvalidData(identifier, "does not start with the format code 0 or 1", nameof(data)); + } + + // The funder ID and the serial number are each LengthIndicatorOffset digits longer than their length indicator; the funder indicator is 0 to 6. + int position = 1; + if (position >= data.Length || data[position] > '6') + { + ThrowInvalidData(identifier, "does not carry a coupon funder ID length indicator of 0 to 6", nameof(data)); + } + + position += 1 + (data[position] - '0') + LengthIndicatorOffset; + position += NorthAmericanCouponCode.OfferCodeLength; + if (position >= data.Length) + { + ThrowInvalidData(identifier, "is too short for its coupon funder ID, offer code and serial number length indicator", nameof(data)); + } + + position += 1 + (data[position] - '0') + LengthIndicatorOffset; + if (position != data.Length) + { + ThrowInvalidData(identifier, position > data.Length ? "is too short for its serial number" : "carries data after the serial number", nameof(data)); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1SyntaxDictionary.cs b/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1SyntaxDictionary.cs new file mode 100644 index 00000000..5867d0d6 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Gs1/Gs1SyntaxDictionary.cs @@ -0,0 +1,825 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using static SixLabors.ImageSharp.Drawing.Barcodes.Gs1CharacterSet; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The GS1 Application Identifiers and the rules of their data, from the GS1 Barcode Syntax Dictionary, release 2026-01-27, which GS1 maintains in step with +/// release 26 of the GS1 General Specifications. +/// +internal static class Gs1SyntaxDictionary +{ + /// + /// The entries, one for each Application Identifier or range of them, in the lexical order of their first Application Identifier, each with its title from + /// the dictionary. An entry gives the first and last Application Identifier, whether the element string has a predefined length, whether it can be a GS1 + /// Digital Link data attribute, the components of its data, its excluded Application Identifiers, and its GS1 Digital Link qualifier sequences when it is a + /// primary key. The requisite rules of the dictionary are left out: they hold over the combined data of all carriers that mark an item, which one symbol + /// does not hold. + /// + private static readonly Gs1ApplicationIdentifier[] Table = + [ + + // SSCC + new("00", "00", true, true, [new(Digits, 18, 18, false, [Gs1Linter.Csum, Gs1Linter.GcpPos2])], [], [[]]), + + // GTIN + new("01", "01", true, true, [new(Digits, 14, 14, false, [Gs1Linter.Csum, Gs1Linter.GcpPos2])], ["255", "37"], [["22", "10", "21"], ["235"]]), + + // CONTENT + new("02", "02", true, true, [new(Digits, 14, 14, false, [Gs1Linter.Csum, Gs1Linter.GcpPos2])], ["01", "03"], null), + + // MTO GTIN + new("03", "03", true, false, [new(Digits, 14, 14, false, [Gs1Linter.Csum, Gs1Linter.GcpPos2])], ["01", "02", "37"], null), + + // BATCH/LOT + new("10", "10", false, true, [new(Set82, 1, 20, false, [])], [], null), + + // PROD DATE + new("11", "11", true, true, [new(Digits, 6, 6, false, [Gs1Linter.Yymmd0])], [], null), + + // DUE DATE + new("12", "12", true, true, [new(Digits, 6, 6, false, [Gs1Linter.Yymmd0])], [], null), + + // PACK DATE + new("13", "13", true, true, [new(Digits, 6, 6, false, [Gs1Linter.Yymmd0])], [], null), + + // BEST BEFORE or BEST BY + new("15", "15", true, true, [new(Digits, 6, 6, false, [Gs1Linter.Yymmd0])], [], null), + + // SELL BY + new("16", "16", true, true, [new(Digits, 6, 6, false, [Gs1Linter.Yymmd0])], [], null), + + // USE BY or EXPIRY + new("17", "17", true, true, [new(Digits, 6, 6, false, [Gs1Linter.Yymmd0])], [], null), + + // VARIANT + new("20", "20", true, true, [new(Digits, 2, 2, false, [])], [], null), + + // SERIAL + new("21", "21", false, false, [new(Set82, 1, 20, false, [])], ["235"], null), + + // CPV + new("22", "22", false, false, [new(Set82, 1, 20, false, [])], [], null), + + // TPX + new("235", "235", false, false, [new(Set82, 1, 28, false, [])], [], null), + + // ADDITIONAL ID + new("240", "240", false, true, [new(Set82, 1, 30, false, [])], [], null), + + // CUST. PART No. + new("241", "241", false, true, [new(Set82, 1, 30, false, [])], [], null), + + // MTO VARIANT + new("242", "242", false, true, [new(Digits, 1, 6, false, [])], [], null), + + // PCN + new("243", "243", false, true, [new(Set82, 1, 20, false, [])], [], null), + + // SECONDARY SERIAL + new("250", "250", false, true, [new(Set82, 1, 30, false, [])], [], null), + + // REF. TO SOURCE + new("251", "251", false, true, [new(Set82, 1, 30, false, [])], [], null), + + // GDTI + new("253", "253", false, true, [new(Digits, 13, 13, false, [Gs1Linter.Csum, Gs1Linter.GcpPos1]), new(Set82, 1, 17, true, [])], [], [[]]), + + // GLN EXTENSION COMPONENT + new("254", "254", false, false, [new(Set82, 1, 20, false, [])], [], null), + + // GCN + new( + "255", + "255", + false, + true, + [new(Digits, 13, 13, false, [Gs1Linter.Csum, Gs1Linter.GcpPos1]), new(Digits, 1, 12, true, [])], + ["01", "02", "415", "8006", "8020", "8026"], + [[]]), + + // VAR. COUNT + new("30", "30", false, true, [new(Digits, 1, 8, false, [])], [], null), + + // NET WEIGHT (kg) + new("3100", "3105", true, true, [new(Digits, 6, 6, false, [])], ["310n"], null), + + // LENGTH (m) + new("3110", "3115", true, true, [new(Digits, 6, 6, false, [])], ["311n"], null), + + // WIDTH (m) + new("3120", "3125", true, true, [new(Digits, 6, 6, false, [])], ["312n"], null), + + // HEIGHT (m) + new("3130", "3135", true, true, [new(Digits, 6, 6, false, [])], ["313n"], null), + + // AREA (m²) + new("3140", "3145", true, true, [new(Digits, 6, 6, false, [])], ["314n"], null), + + // NET VOLUME (l) + new("3150", "3155", true, true, [new(Digits, 6, 6, false, [])], ["315n"], null), + + // NET VOLUME (m³) + new("3160", "3165", true, true, [new(Digits, 6, 6, false, [])], ["316n"], null), + + // NET WEIGHT (lb) + new("3200", "3205", true, true, [new(Digits, 6, 6, false, [])], ["320n"], null), + + // LENGTH (in) + new("3210", "3215", true, true, [new(Digits, 6, 6, false, [])], ["321n"], null), + + // LENGTH (ft) + new("3220", "3225", true, true, [new(Digits, 6, 6, false, [])], ["322n"], null), + + // LENGTH (yd) + new("3230", "3235", true, true, [new(Digits, 6, 6, false, [])], ["323n"], null), + + // WIDTH (in) + new("3240", "3245", true, true, [new(Digits, 6, 6, false, [])], ["324n"], null), + + // WIDTH (ft) + new("3250", "3255", true, true, [new(Digits, 6, 6, false, [])], ["325n"], null), + + // WIDTH (yd) + new("3260", "3265", true, true, [new(Digits, 6, 6, false, [])], ["326n"], null), + + // HEIGHT (in) + new("3270", "3275", true, true, [new(Digits, 6, 6, false, [])], ["327n"], null), + + // HEIGHT (ft) + new("3280", "3285", true, true, [new(Digits, 6, 6, false, [])], ["328n"], null), + + // HEIGHT (yd) + new("3290", "3295", true, true, [new(Digits, 6, 6, false, [])], ["329n"], null), + + // GROSS WEIGHT (kg) + new("3300", "3305", true, true, [new(Digits, 6, 6, false, [])], ["330n"], null), + + // LENGTH (m), log + new("3310", "3315", true, true, [new(Digits, 6, 6, false, [])], ["331n"], null), + + // WIDTH (m), log + new("3320", "3325", true, true, [new(Digits, 6, 6, false, [])], ["332n"], null), + + // HEIGHT (m), log + new("3330", "3335", true, true, [new(Digits, 6, 6, false, [])], ["333n"], null), + + // AREA (m²), log + new("3340", "3345", true, true, [new(Digits, 6, 6, false, [])], ["334n"], null), + + // VOLUME (l), log + new("3350", "3355", true, true, [new(Digits, 6, 6, false, [])], ["335n"], null), + + // VOLUME (m³), log + new("3360", "3365", true, true, [new(Digits, 6, 6, false, [])], ["336n"], null), + + // KG PER m² + new("3370", "3375", true, true, [new(Digits, 6, 6, false, [])], ["337n"], null), + + // GROSS WEIGHT (lb) + new("3400", "3405", true, true, [new(Digits, 6, 6, false, [])], ["340n"], null), + + // LENGTH (in), log + new("3410", "3415", true, true, [new(Digits, 6, 6, false, [])], ["341n"], null), + + // LENGTH (ft), log + new("3420", "3425", true, true, [new(Digits, 6, 6, false, [])], ["342n"], null), + + // LENGTH (yd), log + new("3430", "3435", true, true, [new(Digits, 6, 6, false, [])], ["343n"], null), + + // WIDTH (in), log + new("3440", "3445", true, true, [new(Digits, 6, 6, false, [])], ["344n"], null), + + // WIDTH (ft), log + new("3450", "3455", true, true, [new(Digits, 6, 6, false, [])], ["345n"], null), + + // WIDTH (yd), log + new("3460", "3465", true, true, [new(Digits, 6, 6, false, [])], ["346n"], null), + + // HEIGHT (in), log + new("3470", "3475", true, true, [new(Digits, 6, 6, false, [])], ["347n"], null), + + // HEIGHT (ft), log + new("3480", "3485", true, true, [new(Digits, 6, 6, false, [])], ["348n"], null), + + // HEIGHT (yd), log + new("3490", "3495", true, true, [new(Digits, 6, 6, false, [])], ["349n"], null), + + // AREA (in²) + new("3500", "3505", true, true, [new(Digits, 6, 6, false, [])], ["350n"], null), + + // AREA (ft²) + new("3510", "3515", true, true, [new(Digits, 6, 6, false, [])], ["351n"], null), + + // AREA (yd²) + new("3520", "3525", true, true, [new(Digits, 6, 6, false, [])], ["352n"], null), + + // AREA (in²), log + new("3530", "3535", true, true, [new(Digits, 6, 6, false, [])], ["353n"], null), + + // AREA (ft²), log + new("3540", "3545", true, true, [new(Digits, 6, 6, false, [])], ["354n"], null), + + // AREA (yd²), log + new("3550", "3555", true, true, [new(Digits, 6, 6, false, [])], ["355n"], null), + + // NET WEIGHT (tr oz) + new("3560", "3565", true, true, [new(Digits, 6, 6, false, [])], ["356n"], null), + + // NET VOLUME (oz) + new("3570", "3575", true, true, [new(Digits, 6, 6, false, [])], ["357n"], null), + + // NET VOLUME (qt (US)) + new("3600", "3605", true, true, [new(Digits, 6, 6, false, [])], ["360n"], null), + + // NET VOLUME (gal.) + new("3610", "3615", true, true, [new(Digits, 6, 6, false, [])], ["361n"], null), + + // VOLUME (qt (US)), log + new("3620", "3625", true, true, [new(Digits, 6, 6, false, [])], ["362n"], null), + + // VOLUME (gal (US)), log + new("3630", "3635", true, true, [new(Digits, 6, 6, false, [])], ["363n"], null), + + // NET VOLUME (in³) + new("3640", "3645", true, true, [new(Digits, 6, 6, false, [])], ["364n"], null), + + // NET VOLUME (ft³) + new("3650", "3655", true, true, [new(Digits, 6, 6, false, [])], ["365n"], null), + + // NET VOLUME (yd³) + new("3660", "3665", true, true, [new(Digits, 6, 6, false, [])], ["366n"], null), + + // VOLUME (in³), log + new("3670", "3675", true, true, [new(Digits, 6, 6, false, [])], ["367n"], null), + + // VOLUME (ft³), log + new("3680", "3685", true, true, [new(Digits, 6, 6, false, [])], ["368n"], null), + + // VOLUME (yd³), log + new("3690", "3695", true, true, [new(Digits, 6, 6, false, [])], ["369n"], null), + + // COUNT + new("37", "37", false, true, [new(Digits, 1, 8, false, [])], [], null), + + // AMOUNT + new("3900", "3909", false, true, [new(Digits, 1, 15, false, [])], ["390n", "391n", "394n", "8111"], null), + + // AMOUNT + new("3910", "3919", false, true, [new(Digits, 3, 3, false, [Gs1Linter.Iso4217]), new(Digits, 1, 15, false, [])], ["391n"], null), + + // PRICE + new("3920", "3929", false, true, [new(Digits, 1, 15, false, [])], ["392n", "393n"], null), + + // PRICE + new("3930", "3939", false, true, [new(Digits, 3, 3, false, [Gs1Linter.Iso4217]), new(Digits, 1, 15, false, [])], ["393n"], null), + + // PRCNT OFF + new("3940", "3943", false, true, [new(Digits, 4, 4, false, [])], ["394n", "8111"], null), + + // PRICE/UoM + new("3950", "3955", false, true, [new(Digits, 6, 6, false, [])], ["392n", "393n", "395n", "8005"], null), + + // ORDER NUMBER + new("400", "400", false, true, [new(Set82, 1, 30, false, [])], [], null), + + // GINC + new("401", "401", false, true, [new(Set82, 1, 30, false, [Gs1Linter.GcpPos1])], [], [[]]), + + // GSIN + new("402", "402", false, true, [new(Digits, 17, 17, false, [Gs1Linter.Csum, Gs1Linter.GcpPos1])], [], [[]]), + + // ROUTE + new("403", "403", false, true, [new(Set82, 1, 30, false, [])], [], null), + + // SHIP TO LOC + new("410", "410", true, true, [new(Digits, 13, 13, false, [Gs1Linter.Csum, Gs1Linter.GcpPos1])], [], null), + + // BILL TO + new("411", "411", true, true, [new(Digits, 13, 13, false, [Gs1Linter.Csum, Gs1Linter.GcpPos1])], [], null), + + // PURCHASE FROM + new("412", "412", true, true, [new(Digits, 13, 13, false, [Gs1Linter.Csum, Gs1Linter.GcpPos1])], [], null), + + // SHIP FOR LOC + new("413", "413", true, true, [new(Digits, 13, 13, false, [Gs1Linter.Csum, Gs1Linter.GcpPos1])], [], null), + + // LOC No. + new("414", "414", true, true, [new(Digits, 13, 13, false, [Gs1Linter.Csum, Gs1Linter.GcpPos1])], [], [["254"], ["7040"]]), + + // PAY TO + new("415", "415", true, true, [new(Digits, 13, 13, false, [Gs1Linter.Csum, Gs1Linter.GcpPos1])], [], [["8020"]]), + + // PROD/SERV LOC + new("416", "416", true, true, [new(Digits, 13, 13, false, [Gs1Linter.Csum, Gs1Linter.GcpPos1])], [], null), + + // PARTY + new("417", "417", true, true, [new(Digits, 13, 13, false, [Gs1Linter.Csum, Gs1Linter.GcpPos1])], [], [["7040"]]), + + // SHIP TO POST + new("420", "420", false, true, [new(Set82, 1, 20, false, [])], ["421"], null), + + // SHIP TO POST + new("421", "421", false, true, [new(Digits, 3, 3, false, [Gs1Linter.Iso3166]), new(Set82, 1, 9, false, [])], ["4307"], null), + + // ORIGIN + new("422", "422", false, true, [new(Digits, 3, 3, false, [Gs1Linter.Iso3166])], ["426"], null), + + // COUNTRY - INITIAL PROCESS + new( + "423", + "423", + false, + true, + [ + new(Digits, 3, 3, false, [Gs1Linter.Iso3166]), + new(Digits, 3, 3, true, [Gs1Linter.Iso3166]), + new(Digits, 3, 3, true, [Gs1Linter.Iso3166]), + new(Digits, 3, 3, true, [Gs1Linter.Iso3166]), + new(Digits, 3, 3, true, [Gs1Linter.Iso3166]) + ], + ["426"], + null), + + // COUNTRY - PROCESS + new("424", "424", false, true, [new(Digits, 3, 3, false, [Gs1Linter.Iso3166])], ["426"], null), + + // COUNTRY - DISASSEMBLY + new( + "425", + "425", + false, + true, + [ + new(Digits, 3, 3, false, [Gs1Linter.Iso3166]), + new(Digits, 3, 3, true, [Gs1Linter.Iso3166]), + new(Digits, 3, 3, true, [Gs1Linter.Iso3166]), + new(Digits, 3, 3, true, [Gs1Linter.Iso3166]), + new(Digits, 3, 3, true, [Gs1Linter.Iso3166]) + ], + ["426"], + null), + + // COUNTRY - FULL PROCESS + new("426", "426", false, true, [new(Digits, 3, 3, false, [Gs1Linter.Iso3166])], [], null), + + // ORIGIN SUBDIVISION + new("427", "427", false, true, [new(Set82, 1, 3, false, [])], [], null), + + // SHIP TO COMP + new("4300", "4300", false, true, [new(Set82, 1, 35, false, [Gs1Linter.PercentEncoded])], [], null), + + // SHIP TO NAME + new("4301", "4301", false, true, [new(Set82, 1, 35, false, [Gs1Linter.PercentEncoded])], [], null), + + // SHIP TO ADD1 + new("4302", "4302", false, true, [new(Set82, 1, 70, false, [Gs1Linter.PercentEncoded])], [], null), + + // SHIP TO ADD2 + new("4303", "4303", false, true, [new(Set82, 1, 70, false, [Gs1Linter.PercentEncoded])], [], null), + + // SHIP TO SUB + new("4304", "4304", false, true, [new(Set82, 1, 70, false, [Gs1Linter.PercentEncoded])], [], null), + + // SHIP TO LOC + new("4305", "4305", false, true, [new(Set82, 1, 70, false, [Gs1Linter.PercentEncoded])], [], null), + + // SHIP TO REG + new("4306", "4306", false, true, [new(Set82, 1, 70, false, [Gs1Linter.PercentEncoded])], [], null), + + // SHIP TO COUNTRY + new("4307", "4307", false, true, [new(Set82, 2, 2, false, [Gs1Linter.Iso3166Alpha2])], [], null), + + // SHIP TO PHONE + new("4308", "4308", false, true, [new(Set82, 1, 30, false, [])], [], null), + + // SHIP TO GEO + new("4309", "4309", false, true, [new(Digits, 10, 10, false, [Gs1Linter.Latitude]), new(Digits, 10, 10, false, [Gs1Linter.Longitude])], [], null), + + // RTN TO COMP + new("4310", "4310", false, true, [new(Set82, 1, 35, false, [Gs1Linter.PercentEncoded])], [], null), + + // RTN TO NAME + new("4311", "4311", false, true, [new(Set82, 1, 35, false, [Gs1Linter.PercentEncoded])], [], null), + + // RTN TO ADD1 + new("4312", "4312", false, true, [new(Set82, 1, 70, false, [Gs1Linter.PercentEncoded])], [], null), + + // RTN TO ADD2 + new("4313", "4313", false, true, [new(Set82, 1, 70, false, [Gs1Linter.PercentEncoded])], [], null), + + // RTN TO SUB + new("4314", "4314", false, true, [new(Set82, 1, 70, false, [Gs1Linter.PercentEncoded])], [], null), + + // RTN TO LOC + new("4315", "4315", false, true, [new(Set82, 1, 70, false, [Gs1Linter.PercentEncoded])], [], null), + + // RTN TO REG + new("4316", "4316", false, true, [new(Set82, 1, 70, false, [Gs1Linter.PercentEncoded])], [], null), + + // RTN TO COUNTRY + new("4317", "4317", false, true, [new(Set82, 2, 2, false, [Gs1Linter.Iso3166Alpha2])], [], null), + + // RTN TO POST + new("4318", "4318", false, true, [new(Set82, 1, 20, false, [])], [], null), + + // RTN TO PHONE + new("4319", "4319", false, true, [new(Set82, 1, 30, false, [])], [], null), + + // SRV DESCRIPTION + new("4320", "4320", false, true, [new(Set82, 1, 35, false, [Gs1Linter.PercentEncoded])], [], null), + + // DANGEROUS GOODS + new("4321", "4321", false, true, [new(Digits, 1, 1, false, [Gs1Linter.YesNo])], [], null), + + // AUTH TO LEAVE + new("4322", "4322", false, true, [new(Digits, 1, 1, false, [Gs1Linter.YesNo])], [], null), + + // SIG REQUIRED + new("4323", "4323", false, true, [new(Digits, 1, 1, false, [Gs1Linter.YesNo])], [], null), + + // NOT BEF DEL DT + new("4324", "4324", false, true, [new(Digits, 6, 6, false, [Gs1Linter.Yymmd0]), new(Digits, 4, 4, false, [Gs1Linter.Hhmi])], [], null), + + // NOT AFT DEL DT + new("4325", "4325", false, true, [new(Digits, 6, 6, false, [Gs1Linter.Yymmd0]), new(Digits, 4, 4, false, [Gs1Linter.Hhmi])], [], null), + + // REL DATE + new("4326", "4326", false, true, [new(Digits, 6, 6, false, [Gs1Linter.Yymmdd])], [], null), + + // MAX TEMP F. + new("4330", "4330", false, true, [new(Digits, 6, 6, false, []), new(Set82, 1, 1, true, [Gs1Linter.Hyphen])], ["4331"], null), + + // MAX TEMP C. + new("4331", "4331", false, true, [new(Digits, 6, 6, false, []), new(Set82, 1, 1, true, [Gs1Linter.Hyphen])], ["4330"], null), + + // MIN TEMP F. + new("4332", "4332", false, true, [new(Digits, 6, 6, false, []), new(Set82, 1, 1, true, [Gs1Linter.Hyphen])], ["4333"], null), + + // MIN TEMP C. + new("4333", "4333", false, true, [new(Digits, 6, 6, false, []), new(Set82, 1, 1, true, [Gs1Linter.Hyphen])], ["4332"], null), + + // NSN + new("7001", "7001", false, true, [new(Digits, 13, 13, false, [])], [], null), + + // MEAT CUT + new("7002", "7002", false, true, [new(Set82, 1, 30, false, [])], [], null), + + // EXPIRY TIME + new("7003", "7003", false, true, [new(Digits, 6, 6, false, [Gs1Linter.Yymmdd]), new(Digits, 4, 4, false, [Gs1Linter.Hhmi])], [], null), + + // ACTIVE POTENCY + new("7004", "7004", false, true, [new(Digits, 1, 4, false, [])], [], null), + + // CATCH AREA + new("7005", "7005", false, true, [new(Set82, 1, 12, false, [])], [], null), + + // FIRST FREEZE DATE + new("7006", "7006", false, true, [new(Digits, 6, 6, false, [Gs1Linter.Yymmdd])], [], null), + + // HARVEST DATE + new("7007", "7007", false, true, [new(Digits, 6, 6, false, [Gs1Linter.Yymmdd]), new(Digits, 6, 6, true, [Gs1Linter.Yymmdd])], [], null), + + // AQUATIC SPECIES + new("7008", "7008", false, true, [new(Set82, 1, 3, false, [])], [], null), + + // FISHING GEAR TYPE + new("7009", "7009", false, true, [new(Set82, 1, 10, false, [])], [], null), + + // PROD METHOD + new("7010", "7010", false, true, [new(Set82, 1, 2, false, [])], [], null), + + // TEST BY DATE + new("7011", "7011", false, true, [new(Digits, 6, 6, false, [Gs1Linter.Yymmdd]), new(Digits, 4, 4, true, [Gs1Linter.Hhmi])], [], null), + + // REFURB LOT + new("7020", "7020", false, true, [new(Set82, 1, 20, false, [])], [], null), + + // FUNC STAT + new("7021", "7021", false, true, [new(Set82, 1, 20, false, [])], [], null), + + // REV STAT + new("7022", "7022", false, true, [new(Set82, 1, 20, false, [])], [], null), + + // GIAI - ASSEMBLY + new("7023", "7023", false, true, [new(Set82, 1, 30, false, [Gs1Linter.GcpPos1])], [], null), + + // PROCESSOR # 0 + new("7030", "7030", false, true, [new(Digits, 3, 3, false, [Gs1Linter.Iso3166999]), new(Set82, 1, 27, false, [])], [], null), + + // PROCESSOR # 1 + new("7031", "7031", false, true, [new(Digits, 3, 3, false, [Gs1Linter.Iso3166999]), new(Set82, 1, 27, false, [])], [], null), + + // PROCESSOR # 2 + new("7032", "7032", false, true, [new(Digits, 3, 3, false, [Gs1Linter.Iso3166999]), new(Set82, 1, 27, false, [])], [], null), + + // PROCESSOR # 3 + new("7033", "7033", false, true, [new(Digits, 3, 3, false, [Gs1Linter.Iso3166999]), new(Set82, 1, 27, false, [])], [], null), + + // PROCESSOR # 4 + new("7034", "7034", false, true, [new(Digits, 3, 3, false, [Gs1Linter.Iso3166999]), new(Set82, 1, 27, false, [])], [], null), + + // PROCESSOR # 5 + new("7035", "7035", false, true, [new(Digits, 3, 3, false, [Gs1Linter.Iso3166999]), new(Set82, 1, 27, false, [])], [], null), + + // PROCESSOR # 6 + new("7036", "7036", false, true, [new(Digits, 3, 3, false, [Gs1Linter.Iso3166999]), new(Set82, 1, 27, false, [])], [], null), + + // PROCESSOR # 7 + new("7037", "7037", false, true, [new(Digits, 3, 3, false, [Gs1Linter.Iso3166999]), new(Set82, 1, 27, false, [])], [], null), + + // PROCESSOR # 8 + new("7038", "7038", false, true, [new(Digits, 3, 3, false, [Gs1Linter.Iso3166999]), new(Set82, 1, 27, false, [])], [], null), + + // PROCESSOR # 9 + new("7039", "7039", false, true, [new(Digits, 3, 3, false, [Gs1Linter.Iso3166999]), new(Set82, 1, 27, false, [])], [], null), + + // UIC+EXT + new( + "7040", + "7040", + false, + false, + [new(Digits, 1, 1, false, []), new(Set82, 1, 1, false, []), new(Set82, 1, 1, false, []), new(Set82, 1, 1, false, [Gs1Linter.ImporterIndex])], + [], + null), + + // UFRGT UNIT TYPE + new("7041", "7041", false, false, [new(Set82, 1, 4, false, [Gs1Linter.PackageType])], [], null), + + // NHRN PZN + new("710", "710", false, true, [new(Set82, 1, 20, false, [])], [], null), + + // NHRN CIP + new("711", "711", false, true, [new(Set82, 1, 20, false, [])], [], null), + + // NHRN CN + new("712", "712", false, true, [new(Set82, 1, 20, false, [])], [], null), + + // NHRN DRN + new("713", "713", false, true, [new(Set82, 1, 20, false, [])], [], null), + + // NHRN AIM + new("714", "714", false, true, [new(Set82, 1, 20, false, [])], [], null), + + // NHRN NDC + new("715", "715", false, true, [new(Set82, 1, 20, false, [])], [], null), + + // NHRN AIC + new("716", "716", false, true, [new(Set82, 1, 20, false, [])], [], null), + + // NHRN SRN + new("717", "717", false, true, [new(Set82, 1, 20, false, [])], [], null), + + // CERT # 1 + new("7230", "7230", false, true, [new(Set82, 2, 2, false, []), new(Set82, 1, 28, false, [])], [], null), + + // CERT # 2 + new("7231", "7231", false, true, [new(Set82, 2, 2, false, []), new(Set82, 1, 28, false, [])], [], null), + + // CERT # 3 + new("7232", "7232", false, true, [new(Set82, 2, 2, false, []), new(Set82, 1, 28, false, [])], [], null), + + // CERT # 4 + new("7233", "7233", false, true, [new(Set82, 2, 2, false, []), new(Set82, 1, 28, false, [])], [], null), + + // CERT # 5 + new("7234", "7234", false, true, [new(Set82, 2, 2, false, []), new(Set82, 1, 28, false, [])], [], null), + + // CERT # 6 + new("7235", "7235", false, true, [new(Set82, 2, 2, false, []), new(Set82, 1, 28, false, [])], [], null), + + // CERT # 7 + new("7236", "7236", false, true, [new(Set82, 2, 2, false, []), new(Set82, 1, 28, false, [])], [], null), + + // CERT # 8 + new("7237", "7237", false, true, [new(Set82, 2, 2, false, []), new(Set82, 1, 28, false, [])], [], null), + + // CERT # 9 + new("7238", "7238", false, true, [new(Set82, 2, 2, false, []), new(Set82, 1, 28, false, [])], [], null), + + // CERT # 10 + new("7239", "7239", false, true, [new(Set82, 2, 2, false, []), new(Set82, 1, 28, false, [])], [], null), + + // PROTOCOL + new("7240", "7240", false, true, [new(Set82, 1, 20, false, [])], [], null), + + // AIDC MEDIA TYPE + new("7241", "7241", false, true, [new(Digits, 2, 2, false, [Gs1Linter.MediaType])], [], null), + + // VCN + new("7242", "7242", false, true, [new(Set82, 1, 25, false, [])], [], null), + + // DOB + new("7250", "7250", false, true, [new(Digits, 8, 8, false, [Gs1Linter.Yyyymmdd])], ["7251"], null), + + // DOB TIME + new("7251", "7251", false, true, [new(Digits, 8, 8, false, [Gs1Linter.Yyyymmdd]), new(Digits, 4, 4, false, [Gs1Linter.Hhmi])], ["7250"], null), + + // BIO SEX + new("7252", "7252", false, true, [new(Digits, 1, 1, false, [Gs1Linter.Iso5218])], [], null), + + // FAMILY NAME + new("7253", "7253", false, true, [new(Set82, 1, 40, false, [Gs1Linter.PercentEncoded])], ["7256", "7259"], null), + + // GIVEN NAME + new("7254", "7254", false, true, [new(Set82, 1, 40, false, [Gs1Linter.PercentEncoded])], ["7256", "7259"], null), + + // SUFFIX + new("7255", "7255", false, true, [new(Set82, 1, 10, false, [])], ["7256", "7259"], null), + + // FULL NAME + new("7256", "7256", false, true, [new(Set82, 1, 90, false, [Gs1Linter.PercentEncoded])], [], null), + + // PERSON ADDR + new("7257", "7257", false, true, [new(Set82, 1, 70, false, [Gs1Linter.PercentEncoded])], [], null), + + // BIRTH SEQUENCE + new("7258", "7258", false, true, [new(Set82, 3, 3, false, [Gs1Linter.PositionInSequence])], [], null), + + // BABY + new("7259", "7259", false, true, [new(Set82, 1, 40, false, [Gs1Linter.PercentEncoded])], ["7256"], null), + + // DIMENSIONS + new( + "8001", + "8001", + false, + true, + [ + new(Digits, 4, 4, false, [Gs1Linter.NonZero]), + new(Digits, 5, 5, false, [Gs1Linter.NonZero]), + new(Digits, 3, 3, false, [Gs1Linter.NonZero]), + new(Digits, 1, 1, false, [Gs1Linter.Winding]), + new(Digits, 1, 1, false, []) + ], + [], + null), + + // CMT No. + new("8002", "8002", false, true, [new(Set82, 1, 20, false, [])], [], null), + + // GRAI + new( + "8003", + "8003", + false, + true, + [new(Digits, 1, 1, false, [Gs1Linter.Zero]), new(Digits, 13, 13, false, [Gs1Linter.Csum, Gs1Linter.GcpPos1]), new(Set82, 1, 16, true, [])], + [], + [[]]), + + // GIAI + new("8004", "8004", false, true, [new(Set82, 1, 30, false, [Gs1Linter.GcpPos1])], [], [["7040"]]), + + // PRICE PER UNIT + new("8005", "8005", false, true, [new(Digits, 6, 6, false, [])], [], null), + + // ITIP + new( + "8006", + "8006", + false, + true, + [new(Digits, 14, 14, false, [Gs1Linter.Csum, Gs1Linter.GcpPos2]), new(Digits, 4, 4, false, [Gs1Linter.PieceOfTotal])], + ["01", "37"], + [["22", "10", "21"]]), + + // IBAN + new("8007", "8007", false, true, [new(Set82, 1, 34, false, [Gs1Linter.Iban])], [], null), + + // PROD TIME + new( + "8008", + "8008", + false, + true, + [ + new(Digits, 6, 6, false, [Gs1Linter.Yymmdd]), + new(Digits, 2, 2, false, [Gs1Linter.Hh]), + new(Digits, 2, 2, true, [Gs1Linter.Mi]), + new(Digits, 2, 2, true, [Gs1Linter.Ss]) + ], + [], + null), + + // OPTSEN + new("8009", "8009", false, true, [new(Set82, 1, 50, false, [])], [], null), + + // CPID + new("8010", "8010", false, true, [new(Set39, 1, 30, false, [Gs1Linter.GcpPos1])], [], [["8011"]]), + + // CPID SERIAL + new("8011", "8011", false, false, [new(Digits, 1, 12, false, [Gs1Linter.NoZeroPrefix])], [], null), + + // VERSION + new("8012", "8012", false, true, [new(Set82, 1, 20, false, [])], [], null), + + // GMN + new("8013", "8013", false, true, [new(Set82, 1, 25, false, [Gs1Linter.CsumAlpha, Gs1Linter.GcpPos1])], [], [[]]), + + // MUDI + new("8014", "8014", false, false, [new(Set82, 1, 25, false, [Gs1Linter.CsumAlpha, Gs1Linter.GcpPos1, Gs1Linter.HasNonDigit])], [], null), + + // GSRN - PROVIDER + new("8017", "8017", false, true, [new(Digits, 18, 18, false, [Gs1Linter.Csum, Gs1Linter.GcpPos1])], ["8018"], [["8019"]]), + + // GSRN - RECIPIENT + new("8018", "8018", false, true, [new(Digits, 18, 18, false, [Gs1Linter.Csum, Gs1Linter.GcpPos1])], ["8017"], [["8019"]]), + + // SRIN + new("8019", "8019", false, false, [new(Digits, 1, 10, false, [])], [], null), + + // REF No. + new("8020", "8020", false, false, [new(Set82, 1, 25, false, [])], [], null), + + // ITIP CONTENT + new( + "8026", + "8026", + false, + true, + [new(Digits, 14, 14, false, [Gs1Linter.Csum, Gs1Linter.GcpPos2]), new(Digits, 4, 4, false, [Gs1Linter.PieceOfTotal])], + ["02", "8006"], + null), + + // DIGSIG + new("8030", "8030", false, true, [new(Set64, 1, 90, false, [])], [], null), + + // IMEI + new("8040", "8040", false, false, [new(Digits, 15, 15, false, [])], [], null), + + // IMEI2 + new("8041", "8041", false, false, [new(Digits, 15, 15, false, [])], [], null), + + // ESIM + new("8042", "8042", false, false, [new(Digits, 32, 32, false, [])], [], null), + + // PSIM + new("8043", "8043", false, false, [new(Digits, 18, 18, false, []), new(Digits, 1, 2, true, [])], [], null), + + new("8110", "8110", false, true, [new(Set82, 1, 70, false, [Gs1Linter.CouponCode])], [], null), + + // POINTS + new("8111", "8111", false, true, [new(Digits, 4, 4, false, [])], [], null), + + new("8112", "8112", false, true, [new(Set82, 1, 70, false, [Gs1Linter.CouponPositiveOffer])], [], null), + + // PRODUCT URL + new("8200", "8200", false, false, [new(Set82, 1, 70, false, [])], [], null), + + // INTERNAL + new("90", "90", false, true, [new(Set82, 1, 30, false, [])], [], null), + + // INTERNAL + new("91", "99", false, true, [new(Set82, 1, 90, false, [])], [], null), + ]; + + /// + /// Gets the entries in the lexical order of their first Application Identifier. + /// + public static ReadOnlySpan Entries => Table; + + /// + /// Returns the entry that holds the given Application Identifier. + /// + /// The Application Identifier, two to four digits. + /// The entry, or when the identifier is not an allocated Application Identifier. + public static Gs1ApplicationIdentifier? Find(ReadOnlySpan identifier) + { + int index = IndexOf(identifier); + return index >= 0 ? Table[index] : null; + } + + /// + /// Returns the index in of the entry that holds the given Application Identifier. + /// + /// The Application Identifier, two to four digits. + /// The index, or -1 when the identifier is not an allocated Application Identifier. + public static int IndexOf(ReadOnlySpan identifier) + { + // The entries are in lexical order, and no Application Identifier is a prefix of another, so the only entry that can hold the identifier is the last + // one whose first identifier is not after it. + int low = 0; + int high = Table.Length - 1; + int candidate = -1; + while (low <= high) + { + int middle = low + ((high - low) / 2); + if (Table[middle].First.AsSpan().SequenceCompareTo(identifier) <= 0) + { + candidate = middle; + low = middle + 1; + } + else + { + high = middle - 1; + } + } + + return candidate >= 0 && Table[candidate].Holds(identifier) ? candidate : -1; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Gs1/NorthAmericanCouponCode.cs b/src/ImageSharp.Drawing/Barcodes/Gs1/NorthAmericanCouponCode.cs new file mode 100644 index 00000000..2a75b6c9 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Gs1/NorthAmericanCouponCode.cs @@ -0,0 +1,300 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The coupon code of GS1 Application Identifier (8110), as Table C of the North American Coupon Application Guideline of GS1 US defines it. The required +/// elements come first: the GS1 Company Prefix with its length indicator, the offer code, the save value with its length indicator, and the primary purchase +/// requirement with its length indicator, its code and its family code. Optional data fields 1, 2, 3, 4, 5, 6 and 9 follow, each led by its field number. +/// The guideline sets no order for them. +/// +internal static class NorthAmericanCouponCode +{ + /// + /// The length of the offer code. Table C: "The Offer Code is a 6-digit number". + /// + public const int OfferCodeLength = 6; + + /// + /// The length the company prefix length indicator adds to. Table C: "an offset of 6 is added to the VLI to get the actual length". + /// + private const int CompanyPrefixOffset = 6; + + /// + /// The length the serial number length indicator and the retailer GS1 Company Prefix or GLN length indicator add to. Table C gives both elements 6 digits + /// more than their indicator. + /// + private const int LengthIndicatorOffset = 6; + + /// + /// The length indicator of a second or third purchase GS1 Company Prefix that stands for the primary GS1 Company Prefix, with no digits after it. + /// + private const int PrimaryCompanyPrefix = 9; + + /// + /// Validates a coupon code against Table C of the guideline. + /// + /// The data of Application Identifier (8110). + /// The length of the primary GS1 Company Prefix, which follows the length indicator. + /// The coupon code is not numeric, an element is missing or out of range, an optional field is unknown, or the start + /// date is later than the expiration date. + public static int Validate(ReadOnlySpan data) + { + BarcodeSymbology.ValidateDigits(data); + + // The required elements. The company prefix length indicator is 0 to 6, and the save value and purchase requirement indicators give lengths of 1 to 5 + // digits. + CouponReader reader = new(data); + int prefixLength = reader.Digit(0, 6, "primary GS1 Company Prefix length indicator") + CompanyPrefixOffset; + reader.Skip(prefixLength, "primary GS1 Company Prefix"); + reader.Skip(OfferCodeLength, "offer code"); + reader.Skip(reader.Digit(1, 5, "save value length indicator"), "save value"); + reader.PurchaseRequirement("primary"); + + int expiration = -1; + int start = -1; + while (!reader.AtEnd) + { + int field = reader.Digit(0, 9, "optional data field number"); + if (field is 0 or 7 or 8) + { + throw new ArgumentException("The optional data fields of a coupon code are 1, 2, 3, 4, 5, 6 and 9.", nameof(data)); + } + + switch (field) + { + case 1: + // The second qualifying purchase: the additional purchase rules code, the requirement and a GS1 Company Prefix whose length indicator 9 + // stands for the primary prefix. + _ = reader.Digit(0, 3, "additional purchase rules code"); + reader.PurchaseRequirement("second"); + reader.CompanyPrefix("second"); + break; + + case 2: + reader.PurchaseRequirement("third"); + reader.CompanyPrefix("third"); + break; + + case 3: + expiration = reader.Date("expiration date"); + break; + + case 4: + start = reader.Date("start date"); + break; + + case 5: + // The serial number is 6 to 15 digits, 6 more than its length indicator. + reader.Skip(reader.Digit(0, 9, "serial number length indicator") + LengthIndicatorOffset, "serial number"); + break; + + case 6: + // The retailer GS1 Company Prefix or GLN is 7 to 13 digits, 6 more than its length indicator. + int retailerLength = reader.Digit(1, 7, "retailer GS1 Company Prefix or GLN length indicator") + LengthIndicatorOffset; + reader.Skip(retailerLength, "retailer GS1 Company Prefix or GLN"); + break; + + default: + // The miscellaneous elements: the save value code, the item the save value applies to, the store coupon flag and the do not multiply flag. + int saveValueCode = reader.Digit(0, 6, "save value code"); + if (saveValueCode is 3 or 4) + { + throw new ArgumentException($"The save value code of a coupon code is 0, 1, 2, 5 or 6; got {saveValueCode}.", nameof(data)); + } + + _ = reader.Digit(0, 2, "save value applies to item"); + _ = reader.Digit(0, 9, "store coupon flag"); + _ = reader.Digit(0, 1, "do not multiply flag"); + break; + } + } + + // Table C: the start date "must be earlier than or equal to the expiration date". The fields can come in any order, so the dates compare once both are + // read. + if (expiration >= 0 && start > expiration) + { + throw new ArgumentException("The start date of a coupon code is later than its expiration date.", nameof(data)); + } + + return prefixLength; + } + + /// + /// Returns the name of an element for an error message: the purchase it belongs to, when it belongs to one, and its own name. + /// + /// The purchase the element belongs to, or . + /// The name of the element. + /// The name for the message. + private static string ElementName(string? which, string name) + => which is null ? name : $"{which} purchase {name}"; + + /// + /// Throws the exception for a coupon code that breaks a rule of Table C. The exception names the parameter that holds the coupon code. + /// + /// The message. + /// The name of the parameter that holds the coupon code. + /// Always thrown. + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowInvalid(string message, string paramName) + => throw new ArgumentException(message, paramName); + + /// + /// Reads the elements of a coupon code in order. Each read checks that the element is present and in range, and moves past it. + /// + private ref struct CouponReader + { + /// + /// The coupon code, all digits. + /// + private readonly ReadOnlySpan data; + + /// + /// The position of the next element. + /// + private int position; + + /// + /// Initializes a new instance of the struct at the start of the coupon code. + /// + /// The coupon code, all digits. + public CouponReader(ReadOnlySpan data) + { + this.data = data; + this.position = 0; + } + + /// + /// Gets a value indicating whether the reader is at the end of the coupon code. + /// + public readonly bool AtEnd => this.position >= this.data.Length; + + /// + /// Reads one digit and checks its range. + /// + /// The smallest value. + /// The largest value. + /// The name of the element, for the error message. + /// The digit. + public int Digit(int minimum, int maximum, string name) + => this.Digit(minimum, maximum, null, name); + + /// + /// Skips the digits of an element. + /// + /// The number of digits. + /// The name of the element, for the error message. + public void Skip(int count, string name) + => this.Skip(count, null, name); + + /// + /// Reads a purchase requirement: its length indicator of 1 to 5, the requirement, its code of 0 to 4 or 9, and its three digit family code. + /// + /// The purchase the requirement belongs to, for the error message. + public void PurchaseRequirement(string which) + { + this.Skip(this.Digit(1, 5, which, "requirement length indicator"), which, "requirement"); + int code = this.Digit(0, 9, which, "requirement code"); + if (code is 5 or 6 or 7 or 8) + { + ThrowInvalid($"The {which} purchase requirement code of a coupon code is 0 to 4 or 9; got {code}.", nameof(this.data)); + } + + this.Skip(3, which, "family code"); + } + + /// + /// Reads a GS1 Company Prefix of a second or third purchase: its length indicator of 0 to 6, or 9 when the primary GS1 Company Prefix applies and no + /// digits follow. + /// + /// The purchase the prefix belongs to, for the error message. + public void CompanyPrefix(string which) + { + int indicator = this.Digit(0, 9, which, "GS1 Company Prefix length indicator"); + if (indicator is 7 or 8) + { + ThrowInvalid($"The {which} purchase GS1 Company Prefix length indicator of a coupon code is 0 to 6 or 9; got {indicator}.", nameof(this.data)); + } + + if (indicator != PrimaryCompanyPrefix) + { + this.Skip(indicator + CompanyPrefixOffset, which, "GS1 Company Prefix"); + } + } + + /// + /// Reads a date of six digits, YYMMDD, with a month of 1 to 12 and a day of 1 to the last day of the month. A two digit year divisible by four is a + /// leap year. + /// + /// The name of the date, for the error message. + /// The date as the number YYMMDD. + public int Date(string name) + { + int start = this.position; + this.Skip(6, name); + ReadOnlySpan date = this.data.Slice(start, 6); + int year = ((date[0] - '0') * 10) + (date[1] - '0'); + int month = ((date[2] - '0') * 10) + (date[3] - '0'); + int day = ((date[4] - '0') * 10) + (date[5] - '0'); + if (month is < 1 or > 12) + { + ThrowInvalid($"The month of the {name} of a coupon code is 1 to 12; got {month}.", nameof(this.data)); + } + + // In the years 2000 to 2099 a year is a leap year when it is divisible by four, so the runtime gives the same month lengths as that rule. + int lastDay = DateTime.DaysInMonth(2000 + year, month); + if (day < 1 || day > lastDay) + { + ThrowInvalid($"The day of the {name} of a coupon code is 1 to {lastDay}; got {day}.", nameof(this.data)); + } + + return (year * 10000) + (month * 100) + day; + } + + /// + /// Reads one digit of an element of a second or third purchase and checks its range. The name of the element joins the purchase only when the error + /// message is built. + /// + /// The smallest value. + /// The largest value. + /// The purchase the element belongs to, or for an element of the coupon. + /// The name of the element, for the error message. + /// The digit. + private int Digit(int minimum, int maximum, string? which, string name) + { + if (this.position >= this.data.Length) + { + ThrowInvalid($"The coupon code is too short: the {ElementName(which, name)} is missing.", nameof(this.data)); + } + + int value = this.data[this.position++] - '0'; + if (value < minimum || value > maximum) + { + ThrowInvalid($"The {ElementName(which, name)} of a coupon code is {minimum} to {maximum}; got {value}.", nameof(this.data)); + } + + return value; + } + + /// + /// Skips the digits of an element of a second or third purchase. The name of the element joins the purchase only when the error message is built. + /// + /// The number of digits. + /// The purchase the element belongs to, or for an element of the coupon. + /// The name of the element, for the error message. + private void Skip(int count, string? which, string name) + { + if (this.position + count > this.data.Length) + { + ThrowInvalid($"The coupon code is too short: the {ElementName(which, name)} is cut off.", nameof(this.data)); + } + + this.position += count; + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/HanXin/HanXinEncoder.cs b/src/ImageSharp.Drawing/Barcodes/HanXin/HanXinEncoder.cs new file mode 100644 index 00000000..c68394f7 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/HanXin/HanXinEncoder.cs @@ -0,0 +1,1016 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodes Han Xin Code symbols: versions 1 to 84, squares of 23 to 189 modules with finder patterns at the four corners and a grid of alignment patterns from +/// version 4. The data is Numeric and Byte mode segments, its codewords take Reed-Solomon error correction over GF(256) in up to three groups of blocks at one +/// of four levels, and one of four masks makes the modules evenly dark. The function information beside the finder patterns carries the version, the level and +/// the mask with error correction over GF(16). +/// +internal static class HanXinEncoder +{ + /// + /// The number of versions. + /// + public const int VersionCount = 84; + + /// + /// The longest input: version 84 at level L1 holds 3264 data codewords, 26112 bits, and Numeric mode, the densest, takes 10 bits for three digits, so no + /// more than 7827 characters fit. + /// + private const int MaximumLength = 7827; + + /// + /// The quiet zone on each of the four sides in modules. + /// + private const int QuietZone = 3; + + /// + /// The side in modules of a version is this value plus two modules for each version: 23 for version 1 and 189 for version 84. + /// + private const int BaseSide = 21; + + /// + /// The step of the placement order of the codewords: codewords 0, 13, 26 and so on, then 1, 14, 27 and so on. + /// + private const int InterleaveStep = 13; + + /// + /// The number of error correction levels, L1 to L4. + /// + private const int LevelCount = 4; + + /// + /// The number of entries of one block group in : the number of blocks, the data codewords of a block and the check codewords of a block. + /// + private const int GroupEntries = 3; + + /// + /// The number of entries of one version and level in : three block groups. + /// + private const int LevelEntries = 3 * GroupEntries; + + /// + /// The data codewords of version 84 at level L1, the most any symbol holds. + /// + private const int MaximumDataCodewords = 3264; + + /// + /// The codewords of version 84, the most any symbol holds, data and error correction: its 31091 data modules, the last entry of , + /// as whole codewords. + /// + private const int MaximumCodewords = 31091 / 8; + + /// + /// The side of version 84 in modules, the largest symbol. + /// + private const int MaximumSide = BaseSide + (2 * VersionCount); + + /// + /// The shortest digit run that starts a Numeric segment between two Byte segments. + /// + private const int ShortestNumericRun = 8; + + /// + /// The shortest digit run that starts a Numeric segment with a Byte segment on one side only. + /// + private const int ShortestNumericRunBesideByte = 5; + + /// + /// The shortest digit run that starts a Numeric segment with no Byte segment on either side. Every digit run is then shorter in Numeric mode. + /// + private const int ShortestNumericRunAlone = 1; + + /// + /// The number of bits of the byte count of a Byte segment. A segment is at most bytes, which the 13 bits hold. + /// + private const int ByteCountBits = 13; + + /// + /// The terminator of a Numeric segment whose last group holds one digit. The terminators for two and three digits follow it. + /// + private const int NumericTerminator = 0b1111111101; + + /// + /// The number of rows and columns of a finder pattern, its separator included. + /// + private const int FinderSize = 8; + + /// + /// The number of bits of the function information: three 4-bit data words, four 4-bit check words and six fixed bits. + /// + private const int FunctionBits = 34; + + /// + /// The number of masks. + /// + private const int MaskCount = 4; + + /// + /// The field of the data error correction: GF(256) with the polynomial x^8 + x^6 + x^5 + x + 1, 355 decimal. + /// + private static readonly GaloisField Field = new(256, 355); + + /// + /// The field of the function information: GF(16) with the polynomial x^4 + x + 1, 19 decimal. + /// + private static readonly GaloisField FunctionField = new(16, 19); + + /// + /// The modes of the data segments, each value the 4-bit mode indicator that starts its segment. + /// + private enum SegmentMode + { + /// + /// Numeric mode: three digits in 10 bits. + /// + Numeric = 0b0001, + + /// + /// Byte mode: a 13-bit byte count, then 8 bits for each byte. + /// + Byte = 0b0011, + } + + /// + /// Gets the alignment pattern spacing of each version, two bytes a version: the regular distance k between the alignment lines and the number n of regular + /// intervals across the side, both 0 for versions 1 to 3, which have none. + /// + private static ReadOnlySpan Alignment => + [ + 0, 0, 0, 0, 0, 0, 14, 1, 16, 1, 16, 1, 17, 1, 18, 1, + 19, 1, 20, 1, 14, 2, 15, 2, 16, 2, 16, 2, 17, 2, 17, 2, + 18, 2, 19, 2, 20, 2, 20, 2, 21, 2, 16, 3, 17, 3, 17, 3, + 18, 3, 18, 3, 19, 3, 19, 3, 20, 3, 20, 3, 21, 3, 17, 4, + 17, 4, 18, 4, 18, 4, 19, 4, 19, 4, 19, 4, 20, 4, 20, 4, + 17, 5, 17, 5, 18, 5, 18, 5, 18, 5, 19, 5, 19, 5, 19, 5, + 17, 6, 17, 6, 18, 6, 18, 6, 18, 6, 18, 6, 19, 6, 19, 6, + 19, 6, 17, 7, 17, 7, 18, 7, 18, 7, 18, 7, 18, 7, 19, 7, + 19, 7, 17, 8, 17, 8, 17, 8, 18, 8, 18, 8, 18, 8, 18, 8, + 19, 8, 19, 8, 17, 9, 17, 9, 17, 9, 18, 9, 18, 9, 18, 9, + 18, 9, 18, 9, 17, 10, 17, 10, + ]; + + /// + /// Gets the number of modules that carry codewords in each version, the side squared less the finder patterns, the alignment patterns and the function + /// information. + /// + private static ReadOnlySpan DataModules => + [ + 205, 301, 405, 439, 555, 675, 805, 943, 1089, 1243, 1289, 1455, + 1629, 1805, 1995, 2187, 2393, 2607, 2829, 3053, 3291, 3383, 3631, 3887, + 4151, 4423, 4703, 4991, 5287, 5591, 5903, 6033, 6353, 6689, 7025, 7377, + 7729, 8089, 8465, 8841, 9009, 9401, 9799, 10207, 10623, 11045, 11477, 11917, + 12111, 12559, 13025, 13489, 13961, 14441, 14939, 15435, 15939, 16171, 16691, 17215, + 17751, 18295, 18847, 19403, 19971, 20229, 20805, 21389, 21993, 22593, 23201, 23817, + 24453, 25085, 25373, 26021, 26677, 27335, 28007, 28687, 29375, 30071, 30387, 31091, + ]; + + /// + /// Gets the error correction blocks of each version and level, nine bytes a version and level from version 1 at level L1: three groups, each the number of + /// blocks, the data codewords of a block and the check codewords of a block. A group of no blocks is three zeros. + /// + private static ReadOnlySpan Blocks => + [ + 1, 21, 4, 0, 0, 0, 0, 0, 0, 1, 17, 8, 0, 0, 0, 0, 0, 0, 1, 13, 12, 0, 0, 0, 0, 0, 0, 1, 9, 16, 0, 0, 0, 0, 0, 0, + 1, 31, 6, 0, 0, 0, 0, 0, 0, 1, 25, 12, 0, 0, 0, 0, 0, 0, 1, 19, 18, 0, 0, 0, 0, 0, 0, 1, 15, 22, 0, 0, 0, 0, 0, 0, + 1, 42, 8, 0, 0, 0, 0, 0, 0, 1, 34, 16, 0, 0, 0, 0, 0, 0, 1, 26, 24, 0, 0, 0, 0, 0, 0, 1, 20, 30, 0, 0, 0, 0, 0, 0, + 1, 46, 8, 0, 0, 0, 0, 0, 0, 1, 38, 16, 0, 0, 0, 0, 0, 0, 1, 30, 24, 0, 0, 0, 0, 0, 0, 1, 22, 32, 0, 0, 0, 0, 0, 0, + 1, 57, 12, 0, 0, 0, 0, 0, 0, 1, 49, 20, 0, 0, 0, 0, 0, 0, 1, 37, 32, 0, 0, 0, 0, 0, 0, 1, 14, 20, 1, 13, 22, 0, 0, 0, + 1, 70, 14, 0, 0, 0, 0, 0, 0, 1, 58, 26, 0, 0, 0, 0, 0, 0, 1, 24, 20, 1, 22, 18, 0, 0, 0, 1, 16, 24, 1, 18, 26, 0, 0, 0, + 1, 84, 16, 0, 0, 0, 0, 0, 0, 1, 70, 30, 0, 0, 0, 0, 0, 0, 1, 26, 22, 1, 28, 24, 0, 0, 0, 2, 14, 20, 1, 12, 20, 0, 0, 0, + 1, 99, 18, 0, 0, 0, 0, 0, 0, 1, 40, 18, 1, 41, 18, 0, 0, 0, 1, 31, 26, 1, 32, 28, 0, 0, 0, 2, 16, 24, 1, 15, 22, 0, 0, 0, + 1, 114, 22, 0, 0, 0, 0, 0, 0, 2, 48, 20, 0, 0, 0, 0, 0, 0, 2, 24, 20, 1, 26, 22, 0, 0, 0, 2, 18, 28, 1, 18, 26, 0, 0, 0, + 1, 131, 24, 0, 0, 0, 0, 0, 0, 1, 52, 22, 1, 57, 24, 0, 0, 0, 2, 27, 24, 1, 29, 24, 0, 0, 0, 2, 21, 32, 1, 19, 30, 0, 0, 0, + 1, 135, 26, 0, 0, 0, 0, 0, 0, 1, 56, 24, 1, 57, 24, 0, 0, 0, 2, 28, 24, 1, 31, 26, 0, 0, 0, 2, 22, 32, 1, 21, 32, 0, 0, 0, + 1, 153, 28, 0, 0, 0, 0, 0, 0, 1, 62, 26, 1, 65, 28, 0, 0, 0, 2, 32, 28, 1, 33, 28, 0, 0, 0, 3, 17, 26, 1, 22, 30, 0, 0, 0, + 1, 86, 16, 1, 85, 16, 0, 0, 0, 1, 71, 30, 1, 72, 30, 0, 0, 0, 2, 37, 32, 1, 35, 30, 0, 0, 0, 3, 20, 30, 1, 21, 32, 0, 0, 0, + 1, 94, 18, 1, 95, 18, 0, 0, 0, 2, 51, 22, 1, 55, 24, 0, 0, 0, 3, 30, 26, 1, 31, 26, 0, 0, 0, 4, 18, 28, 1, 17, 24, 0, 0, 0, + 1, 104, 20, 1, 105, 20, 0, 0, 0, 2, 57, 24, 1, 61, 26, 0, 0, 0, 3, 33, 28, 1, 36, 30, 0, 0, 0, 4, 20, 30, 1, 19, 30, 0, 0, 0, + 1, 115, 22, 1, 114, 22, 0, 0, 0, 2, 65, 28, 1, 61, 26, 0, 0, 0, 3, 38, 32, 1, 33, 30, 0, 0, 0, 5, 19, 28, 1, 14, 24, 0, 0, 0, + 1, 126, 24, 1, 125, 24, 0, 0, 0, 2, 70, 30, 1, 69, 30, 0, 0, 0, 4, 33, 28, 1, 29, 26, 0, 0, 0, 5, 20, 30, 1, 19, 30, 0, 0, 0, + 1, 136, 26, 1, 137, 26, 0, 0, 0, 3, 56, 24, 1, 59, 26, 0, 0, 0, 5, 35, 30, 0, 0, 0, 0, 0, 0, 6, 18, 28, 1, 21, 28, 0, 0, 0, + 1, 148, 28, 1, 149, 28, 0, 0, 0, 3, 61, 26, 1, 64, 28, 0, 0, 0, 7, 24, 20, 1, 23, 22, 0, 0, 0, 6, 20, 30, 1, 21, 32, 0, 0, 0, + 3, 107, 20, 0, 0, 0, 0, 0, 0, 3, 65, 28, 1, 72, 30, 0, 0, 0, 7, 26, 22, 1, 23, 22, 0, 0, 0, 7, 19, 28, 1, 20, 32, 0, 0, 0, + 3, 115, 22, 0, 0, 0, 0, 0, 0, 4, 56, 24, 1, 63, 28, 0, 0, 0, 7, 28, 24, 1, 25, 22, 0, 0, 0, 8, 18, 28, 1, 21, 22, 0, 0, 0, + 2, 116, 22, 1, 122, 24, 0, 0, 0, 4, 56, 24, 1, 72, 30, 0, 0, 0, 7, 28, 24, 1, 32, 26, 0, 0, 0, 8, 18, 28, 1, 24, 30, 0, 0, 0, + 3, 127, 24, 0, 0, 0, 0, 0, 0, 5, 51, 22, 1, 62, 26, 0, 0, 0, 7, 30, 26, 1, 35, 26, 0, 0, 0, 8, 20, 30, 1, 21, 32, 0, 0, 0, + 2, 135, 26, 1, 137, 26, 0, 0, 0, 5, 56, 24, 1, 59, 26, 0, 0, 0, 7, 33, 28, 1, 30, 28, 0, 0, 0, 11, 16, 24, 1, 19, 26, 0, 0, 0, + 3, 105, 20, 1, 121, 22, 0, 0, 0, 5, 61, 26, 1, 57, 26, 0, 0, 0, 9, 28, 24, 1, 28, 22, 0, 0, 0, 10, 19, 28, 1, 18, 30, 0, 0, 0, + 2, 157, 30, 1, 150, 28, 0, 0, 0, 5, 65, 28, 1, 61, 26, 0, 0, 0, 8, 33, 28, 1, 34, 30, 0, 0, 0, 10, 19, 28, 2, 15, 26, 0, 0, 0, + 3, 126, 24, 1, 115, 22, 0, 0, 0, 7, 51, 22, 1, 54, 22, 0, 0, 0, 8, 35, 30, 1, 37, 30, 0, 0, 0, 15, 15, 22, 1, 10, 22, 0, 0, 0, + 4, 105, 20, 1, 103, 20, 0, 0, 0, 7, 56, 24, 1, 45, 18, 0, 0, 0, 10, 31, 26, 1, 27, 26, 0, 0, 0, 10, 17, 26, 3, 20, 28, 1, 21, 28, + 3, 139, 26, 1, 137, 28, 0, 0, 0, 6, 66, 28, 1, 66, 30, 0, 0, 0, 9, 36, 30, 1, 34, 32, 0, 0, 0, 13, 19, 28, 1, 17, 32, 0, 0, 0, + 6, 84, 16, 1, 82, 16, 0, 0, 0, 6, 70, 30, 1, 68, 30, 0, 0, 0, 7, 35, 30, 3, 33, 28, 1, 32, 28, 13, 20, 30, 1, 20, 28, 0, 0, 0, + 5, 105, 20, 1, 94, 18, 0, 0, 0, 6, 74, 32, 1, 71, 30, 0, 0, 0, 11, 33, 28, 1, 34, 32, 0, 0, 0, 13, 19, 28, 3, 16, 26, 0, 0, 0, + 4, 127, 24, 1, 126, 24, 0, 0, 0, 7, 66, 28, 1, 66, 30, 0, 0, 0, 12, 30, 24, 1, 24, 28, 1, 24, 30, 15, 19, 28, 1, 17, 32, 0, 0, 0, + 7, 84, 16, 1, 78, 16, 0, 0, 0, 7, 70, 30, 1, 66, 28, 0, 0, 0, 12, 33, 28, 1, 32, 30, 0, 0, 0, 14, 21, 32, 1, 24, 28, 0, 0, 0, + 5, 117, 22, 1, 117, 24, 0, 0, 0, 8, 66, 28, 1, 58, 26, 0, 0, 0, 11, 38, 32, 1, 34, 32, 0, 0, 0, 15, 20, 30, 2, 17, 26, 0, 0, 0, + 4, 148, 28, 1, 146, 28, 0, 0, 0, 8, 68, 30, 1, 70, 24, 0, 0, 0, 10, 36, 32, 3, 38, 28, 0, 0, 0, 16, 19, 28, 3, 16, 26, 0, 0, 0, + 4, 126, 24, 2, 135, 26, 0, 0, 0, 8, 70, 28, 2, 43, 26, 0, 0, 0, 13, 32, 28, 2, 41, 30, 0, 0, 0, 17, 19, 28, 3, 15, 26, 0, 0, 0, + 5, 136, 26, 1, 132, 24, 0, 0, 0, 5, 67, 30, 4, 68, 28, 1, 69, 28, 14, 35, 30, 1, 32, 24, 0, 0, 0, 18, 18, 26, 3, 16, 28, 1, 14, 28, + 3, 142, 26, 3, 141, 28, 0, 0, 0, 8, 70, 30, 1, 73, 32, 1, 74, 32, 12, 34, 30, 3, 34, 26, 1, 35, 28, 18, 21, 32, 1, 27, 30, 0, 0, 0, + 5, 116, 22, 2, 103, 20, 1, 102, 20, 9, 74, 32, 1, 74, 30, 0, 0, 0, 14, 34, 28, 2, 32, 32, 1, 32, 30, 19, 21, 32, 1, 25, 26, 0, 0, 0, + 7, 116, 22, 1, 117, 22, 0, 0, 0, 11, 65, 28, 1, 58, 24, 0, 0, 0, 15, 38, 32, 1, 27, 28, 0, 0, 0, 20, 20, 30, 1, 20, 32, 1, 21, 32, + 6, 136, 26, 1, 130, 24, 0, 0, 0, 11, 66, 28, 1, 62, 30, 0, 0, 0, 14, 34, 28, 3, 34, 32, 1, 30, 30, 18, 20, 30, 3, 20, 28, 2, 15, 26, + 5, 105, 20, 2, 115, 22, 2, 116, 22, 10, 75, 32, 1, 73, 32, 0, 0, 0, 16, 38, 32, 1, 27, 28, 0, 0, 0, 22, 19, 28, 2, 16, 30, 1, 19, 30, + 6, 147, 28, 1, 146, 28, 0, 0, 0, 11, 66, 28, 2, 65, 30, 0, 0, 0, 18, 33, 28, 2, 33, 30, 0, 0, 0, 22, 21, 32, 1, 28, 30, 0, 0, 0, + 6, 116, 22, 3, 125, 24, 0, 0, 0, 11, 75, 32, 1, 68, 30, 0, 0, 0, 13, 35, 28, 6, 34, 32, 1, 30, 30, 23, 21, 32, 1, 26, 30, 0, 0, 0, + 7, 105, 20, 4, 95, 18, 0, 0, 0, 12, 67, 28, 1, 63, 30, 1, 62, 32, 21, 31, 26, 2, 33, 32, 0, 0, 0, 23, 21, 32, 2, 24, 30, 0, 0, 0, + 10, 116, 22, 0, 0, 0, 0, 0, 0, 12, 74, 32, 1, 78, 30, 0, 0, 0, 18, 37, 32, 1, 39, 30, 1, 41, 28, 25, 21, 32, 1, 27, 28, 0, 0, 0, + 5, 126, 24, 4, 115, 22, 1, 114, 22, 12, 67, 28, 2, 66, 32, 1, 68, 30, 21, 35, 30, 1, 39, 30, 0, 0, 0, 26, 21, 32, 1, 28, 28, 0, 0, 0, + 9, 126, 24, 1, 117, 22, 0, 0, 0, 13, 75, 32, 1, 68, 30, 0, 0, 0, 20, 35, 30, 3, 35, 28, 0, 0, 0, 27, 21, 32, 1, 28, 30, 0, 0, 0, + 9, 126, 24, 1, 137, 26, 0, 0, 0, 13, 71, 30, 2, 68, 32, 0, 0, 0, 20, 37, 32, 1, 39, 28, 1, 38, 28, 24, 20, 32, 5, 25, 28, 0, 0, 0, + 8, 147, 28, 1, 141, 28, 0, 0, 0, 10, 73, 32, 4, 74, 30, 1, 73, 30, 16, 36, 32, 6, 39, 30, 1, 37, 30, 27, 21, 32, 3, 20, 26, 0, 0, 0, + 9, 137, 26, 1, 135, 26, 0, 0, 0, 12, 70, 30, 4, 75, 32, 0, 0, 0, 24, 35, 30, 1, 40, 28, 0, 0, 0, 23, 20, 32, 8, 24, 30, 0, 0, 0, + 14, 95, 18, 1, 86, 18, 0, 0, 0, 13, 73, 32, 3, 77, 30, 0, 0, 0, 24, 35, 30, 2, 35, 28, 0, 0, 0, 26, 21, 32, 5, 21, 30, 1, 23, 30, + 9, 147, 28, 1, 142, 28, 0, 0, 0, 10, 73, 30, 6, 70, 32, 1, 71, 32, 25, 35, 30, 2, 34, 26, 0, 0, 0, 29, 21, 32, 4, 22, 30, 0, 0, 0, + 11, 126, 24, 1, 131, 24, 0, 0, 0, 16, 74, 32, 1, 79, 30, 0, 0, 0, 25, 38, 32, 1, 25, 30, 0, 0, 0, 33, 21, 32, 1, 28, 28, 0, 0, 0, + 14, 105, 20, 1, 99, 18, 0, 0, 0, 19, 65, 28, 1, 72, 28, 0, 0, 0, 24, 37, 32, 2, 40, 30, 1, 41, 30, 31, 21, 32, 4, 24, 32, 0, 0, 0, + 10, 147, 28, 1, 151, 28, 0, 0, 0, 15, 71, 30, 3, 71, 32, 1, 73, 32, 24, 37, 32, 3, 38, 30, 1, 39, 30, 36, 19, 30, 3, 29, 26, 0, 0, 0, + 15, 105, 20, 1, 99, 18, 0, 0, 0, 19, 70, 30, 1, 64, 28, 0, 0, 0, 27, 38, 32, 2, 25, 26, 0, 0, 0, 38, 20, 30, 2, 18, 28, 0, 0, 0, + 14, 105, 20, 1, 113, 22, 1, 114, 22, 17, 67, 30, 3, 92, 32, 0, 0, 0, 30, 35, 30, 1, 41, 30, 0, 0, 0, 36, 21, 32, 1, 26, 30, 1, 27, 30, + 11, 146, 28, 1, 146, 26, 0, 0, 0, 20, 70, 30, 1, 60, 26, 0, 0, 0, 29, 38, 32, 1, 24, 32, 0, 0, 0, 40, 20, 30, 2, 17, 26, 0, 0, 0, + 3, 137, 26, 1, 136, 26, 10, 126, 24, 22, 65, 28, 1, 75, 30, 0, 0, 0, 30, 37, 32, 1, 51, 30, 0, 0, 0, 42, 20, 30, 1, 21, 30, 0, 0, 0, + 12, 126, 24, 2, 118, 22, 1, 116, 22, 19, 74, 32, 1, 74, 30, 1, 72, 28, 30, 38, 32, 2, 29, 30, 0, 0, 0, 39, 20, 32, 2, 37, 26, 1, 38, 26, + 12, 126, 24, 3, 136, 26, 0, 0, 0, 21, 70, 30, 2, 65, 28, 0, 0, 0, 34, 35, 30, 1, 44, 32, 0, 0, 0, 42, 20, 30, 2, 19, 28, 2, 18, 28, + 12, 126, 24, 3, 117, 22, 1, 116, 22, 25, 61, 26, 2, 62, 28, 0, 0, 0, 34, 35, 30, 1, 40, 32, 1, 41, 32, 45, 20, 30, 1, 20, 32, 1, 21, 32, + 15, 105, 20, 2, 115, 22, 2, 116, 22, 25, 65, 28, 1, 72, 28, 0, 0, 0, 18, 35, 30, 17, 37, 32, 1, 50, 32, 42, 20, 30, 6, 19, 28, 1, 15, 28, + 19, 105, 20, 1, 101, 20, 0, 0, 0, 33, 51, 22, 1, 65, 22, 0, 0, 0, 40, 33, 28, 1, 28, 28, 0, 0, 0, 49, 20, 30, 1, 18, 28, 0, 0, 0, + 18, 105, 20, 2, 117, 22, 0, 0, 0, 26, 65, 28, 1, 80, 30, 0, 0, 0, 35, 35, 30, 3, 35, 28, 1, 36, 28, 52, 18, 28, 2, 38, 30, 0, 0, 0, + 26, 84, 16, 0, 0, 0, 0, 0, 0, 26, 70, 30, 0, 0, 0, 0, 0, 0, 45, 31, 26, 1, 9, 26, 0, 0, 0, 52, 20, 30, 0, 0, 0, 0, 0, 0, + 16, 126, 24, 1, 114, 22, 1, 115, 22, 23, 70, 30, 3, 65, 28, 1, 66, 28, 40, 35, 30, 1, 43, 30, 0, 0, 0, 46, 20, 30, 7, 19, 28, 1, 16, 28, + 19, 116, 22, 1, 105, 22, 0, 0, 0, 20, 70, 30, 7, 66, 28, 1, 63, 28, 40, 35, 30, 1, 42, 32, 1, 43, 32, 54, 20, 30, 1, 19, 30, 0, 0, 0, + 17, 126, 24, 2, 115, 22, 0, 0, 0, 24, 70, 30, 4, 74, 32, 0, 0, 0, 48, 31, 26, 2, 18, 26, 0, 0, 0, 54, 19, 28, 6, 15, 26, 1, 14, 26, + 29, 84, 16, 0, 0, 0, 0, 0, 0, 29, 70, 30, 0, 0, 0, 0, 0, 0, 6, 34, 30, 3, 36, 30, 38, 33, 28, 58, 20, 30, 0, 0, 0, 0, 0, 0, + 16, 147, 28, 1, 149, 28, 0, 0, 0, 31, 66, 28, 1, 37, 26, 0, 0, 0, 48, 33, 28, 1, 23, 26, 0, 0, 0, 53, 20, 30, 6, 19, 28, 1, 17, 28, + 20, 115, 22, 2, 134, 24, 0, 0, 0, 29, 66, 28, 2, 56, 26, 2, 57, 26, 45, 36, 30, 2, 15, 28, 0, 0, 0, 59, 20, 30, 2, 21, 32, 0, 0, 0, + 17, 147, 28, 1, 134, 26, 0, 0, 0, 26, 70, 30, 5, 75, 32, 0, 0, 0, 47, 35, 30, 1, 48, 32, 0, 0, 0, 64, 18, 28, 2, 33, 30, 1, 35, 30, + 22, 115, 22, 1, 133, 24, 0, 0, 0, 33, 65, 28, 1, 74, 28, 0, 0, 0, 43, 36, 30, 5, 27, 28, 1, 30, 28, 57, 20, 30, 5, 21, 32, 1, 24, 32, + 18, 136, 26, 2, 142, 26, 0, 0, 0, 33, 66, 28, 2, 49, 26, 0, 0, 0, 48, 35, 30, 2, 38, 28, 0, 0, 0, 64, 20, 30, 1, 20, 32, 0, 0, 0, + 19, 126, 24, 2, 135, 26, 1, 136, 26, 32, 66, 28, 2, 55, 26, 2, 56, 26, 49, 36, 30, 2, 18, 32, 0, 0, 0, 65, 18, 28, 5, 27, 30, 1, 29, 30, + 20, 137, 26, 1, 130, 26, 0, 0, 0, 30, 75, 32, 2, 71, 32, 0, 0, 0, 46, 35, 30, 6, 39, 32, 0, 0, 0, 3, 12, 30, 70, 19, 28, 0, 0, 0, + 20, 147, 28, 0, 0, 0, 0, 0, 0, 35, 70, 30, 0, 0, 0, 0, 0, 0, 49, 35, 30, 5, 35, 28, 0, 0, 0, 70, 20, 30, 0, 0, 0, 0, 0, 0, + 21, 136, 26, 1, 155, 28, 0, 0, 0, 34, 70, 30, 1, 64, 28, 1, 65, 28, 54, 35, 30, 1, 45, 30, 0, 0, 0, 68, 20, 30, 3, 18, 28, 1, 19, 28, + 19, 126, 24, 5, 115, 22, 1, 114, 22, 33, 70, 30, 3, 65, 28, 1, 64, 28, 52, 35, 30, 3, 41, 32, 1, 40, 32, 67, 20, 30, 5, 21, 32, 1, 24, 32, + 2, 150, 28, 21, 136, 26, 0, 0, 0, 32, 70, 30, 6, 65, 28, 0, 0, 0, 52, 38, 32, 2, 27, 32, 0, 0, 0, 73, 20, 30, 2, 22, 32, 0, 0, 0, + 21, 126, 24, 4, 136, 26, 0, 0, 0, 30, 74, 32, 6, 73, 30, 0, 0, 0, 54, 35, 30, 4, 40, 32, 0, 0, 0, 75, 20, 30, 1, 20, 28, 0, 0, 0, + 30, 105, 20, 1, 114, 22, 0, 0, 0, 3, 45, 22, 55, 47, 20, 0, 0, 0, 2, 26, 26, 62, 33, 28, 0, 0, 0, 79, 18, 28, 4, 33, 30, 0, 0, 0, + ]; + + /// + /// Gets the finder pattern at the top left, top right and bottom right corners, row by row from the corner outward, its light separator included. The top + /// right and bottom right patterns mirror it. + /// + private static ReadOnlySpan FinderPattern => + [ + 1, 1, 1, 1, 1, 1, 1, 0, + 1, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 1, 1, 1, 1, 0, + 1, 0, 1, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 1, 1, 0, + 1, 0, 1, 0, 1, 1, 1, 0, + 1, 0, 1, 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + ]; + + /// + /// Gets the finder pattern at the bottom left corner, row by row from the bottom edge upward, its light separator included. + /// + private static ReadOnlySpan CornerFinderPattern => + [ + 1, 1, 1, 0, 1, 0, 1, 0, + 1, 1, 1, 0, 1, 0, 1, 0, + 1, 1, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 1, 0, 1, 0, + 1, 1, 1, 1, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + ]; + + /// + /// Builds a Han Xin Code symbol. + /// + /// The text, characters U+0000 to U+00FF. + /// The error correction level. + /// The version, 1 to 84, or 0 for the smallest that holds the data. + /// The X dimension in millimetres. + /// The symbol to write. + /// The text is empty, has a character above U+00FF, or does not fit. + public static void BuildSymbol(ReadOnlySpan text, HanXinErrorCorrectionLevel level, int version, double xDimension, ref BarcodeSymbol symbol) + { + Guard.MustBeBetweenOrEqualTo(text.Length, 1, MaximumLength, nameof(text)); + int invalid = text.IndexOfAnyExceptInRange('\0', '\u00FF'); + if (invalid >= 0) + { + throw new ArgumentException($"Han Xin Code encodes U+0000 to U+00FF; got {text.CodePointAt(invalid).ToDisplayString()}.", nameof(text)); + } + + // The first pass counts the bits, which choose the version; the second writes them. + int bitCount = WriteBits(text, []); + int levelIndex = (int)level; + int chosen = version; + if (chosen == 0) + { + chosen = 1; + while (chosen < VersionCount && bitCount > DataCodewords(chosen, levelIndex) * 8) + { + chosen++; + } + } + + if (bitCount > DataCodewords(chosen, levelIndex) * 8) + { + throw new ArgumentException("The text does not fit in a Han Xin Code symbol of the requested version and level.", nameof(text)); + } + + // One rented block holds the data codewords, the codewords in block order, the codewords in placement order and the cells of the largest symbol. The + // codeword spans hold one more than the most codewords for the zero codeword that can follow them. The cells are bytes, so their part of the block is + // the side squared in bytes, rounded up to whole ints and read as bytes. + int size = BaseSide + (2 * chosen); + const int codewordLength = MaximumCodewords + 1; + const int cellLength = ((MaximumSide * MaximumSide) + sizeof(int) - 1) / sizeof(int); + using PooledBuffer buffer = new((MaximumDataCodewords + (2 * codewordLength)) + cellLength); + Span data = buffer.Span[..MaximumDataCodewords]; + Span blocks = buffer.Span.Slice(MaximumDataCodewords, codewordLength); + Span placed = buffer.Span.Slice(MaximumDataCodewords + codewordLength, codewordLength); + Span cells = MemoryMarshal.Cast(buffer.Span.Slice(MaximumDataCodewords + (2 * codewordLength), cellLength))[..(size * size)]; + + // The data bits run to the end of the data codewords, and zero bits pad the rest. + int dataCount = DataCodewords(chosen, levelIndex); + data = data[..dataCount]; + data.Clear(); + WriteBits(text, data); + + int codewordCount = AddErrorCorrection(data, chosen, levelIndex, blocks); + Interleave(blocks[..codewordCount], placed); + + // A symbol whose modules are not a whole number of codewords takes one more zero codeword, whose bits fill the modules left. + int moduleCount = DataModules[chosen - 1]; + int placedCount = codewordCount + ((moduleCount & 7) == 0 ? 0 : 1); + placed[codewordCount] = 0; + + Layout(chosen, placed[..placedCount], cells); + int mask = ChooseMask(cells, size); + + Span modules = symbol.BeginMatrix(size, size, QuietZone, xDimension); + for (int i = 0; i < cells.Length; i++) + { + modules[i] = ((cells[i] >> mask) & 1) != 0; + } + + WriteFunctionInformation(chosen, levelIndex, mask, size, modules); + } + + /// + /// Returns the data codewords of a version at a level: the codewords of the version less the check codewords of its blocks. + /// + /// The version, 1 to 84. + /// The level, 0 for L1 to 3 for L4. + /// The data codewords. + private static int DataCodewords(int version, int level) + { + ReadOnlySpan groups = BlockGroups(version, level); + int count = 0; + for (int g = 0; g < groups.Length; g += GroupEntries) + { + count += groups[g] * groups[g + 1]; + } + + return count; + } + + /// + /// Returns the three block groups of a version at a level from . + /// + /// The version, 1 to 84. + /// The level, 0 for L1 to 3 for L4. + /// Nine bytes: for each group, the number of blocks, the data codewords and the check codewords of a block. + private static ReadOnlySpan BlockGroups(int version, int level) + => Blocks.Slice((((version - 1) * LevelCount) + level) * LevelEntries, LevelEntries); + + /// + /// Writes the data as segments: a run of digits as a Numeric segment when it is long enough to pay for its mode indicator and terminator, and the other + /// characters as Byte segments. With an empty buffer the method only counts the bits. + /// + /// The data. + /// The data codewords to write the bits to, most significant bit first, or empty. + /// The number of bits. + private static int WriteBits(ReadOnlySpan text, Span codewords) + { + // A Numeric segment of D digits costs a 4-bit mode indicator, 10 bits for each group of three digits and a 10-bit terminator. The same digits in a Byte + // segment cost 8 bits each. A Byte segment costs a 4-bit mode indicator and a 13-bit count of its own, so the shortest run that saves bits depends on + // the Byte segments beside it: + // - Between two Byte segments, the Numeric segment also starts a new Byte segment: 8 digits, 61 bits against 64. + // - With a Byte segment on one side only: 5 digits, 34 bits against 40. + // - With no Byte segment on either side, the digits alone would make a Byte segment: 1 digit, 24 bits against 25. + BitWriter writer = new(codewords); + + // The start of the pending Byte segment, or -1 when there is none. + int byteStart = -1; + int i = 0; + while (i < text.Length) + { + // The characters before the next digit join the pending Byte segment. + int next = text[i..].IndexOfAnyInRange('0', '9'); + if (next != 0) + { + if (byteStart < 0) + { + byteStart = i; + } + + if (next < 0) + { + break; + } + + i += next; + } + + int digits = text[i..].IndexOfAnyExceptInRange('0', '9'); + if (digits < 0) + { + digits = text.Length - i; + } + + // A run too short for a Numeric segment joins the Byte segment whole. Each later start in the run gives a shorter run with the same end, so none of + // them is long enough either. + bool byteBefore = byteStart >= 0; + bool byteAfter = i + digits < text.Length; + int shortest = byteBefore && byteAfter ? ShortestNumericRun : byteBefore || byteAfter ? ShortestNumericRunBesideByte : ShortestNumericRunAlone; + if (digits >= shortest) + { + if (byteStart >= 0) + { + writer.WriteBytes(text[byteStart..i]); + byteStart = -1; + } + + writer.WriteDigits(text.Slice(i, digits)); + } + else if (byteStart < 0) + { + byteStart = i; + } + + i += digits; + } + + if (byteStart >= 0) + { + writer.WriteBytes(text[byteStart..]); + } + + return writer.Position; + } + + /// + /// Divides the data codewords into the blocks of the version and level, the groups in order, and writes each block followed by its check codewords. The + /// check codewords of a block are the remainder of its data over the generator with the roots alpha^1 to alpha^k. + /// + /// The data codewords. + /// The version. + /// The level, 0 for L1 to 3 for L4. + /// The buffer the blocks are written to. + /// The number of codewords written. + private static int AddErrorCorrection(ReadOnlySpan data, int version, int level, Span blocks) + { + ReadOnlySpan groups = BlockGroups(version, level); + + // A block takes at most 32 check codewords. + InlineArray32 generatorStorage = default; + + // The data codewords run on through the blocks of every group: input is the first data codeword of the next block, and output the start of that block, + // its check codewords after its data. + int input = 0; + int output = 0; + for (int g = 0; g < groups.Length; g += GroupEntries) + { + int count = groups[g]; + int dataLength = groups[g + 1]; + int checkLength = groups[g + 2]; + if (count == 0) + { + continue; + } + + // The blocks of a group share one check length, so one generator serves the group. Section 5.5.2 gives its roots as alpha^1 to alpha^2t, 2t the + // check codewords of a block. + Span generator = ((Span)generatorStorage)[..checkLength]; + Field.Generator(1, generator); + for (int b = 0; b < count; b++) + { + ReadOnlySpan block = data.Slice(input, dataLength); + block.CopyTo(blocks[output..]); + Field.Remainder(block, 1, dataLength, generator, blocks.Slice(output + dataLength, checkLength)); + input += dataLength; + output += dataLength + checkLength; + } + } + + return output; + } + + /// + /// Reorders the codewords for placement: codewords 0, 13, 26 and so on, then 1, 14, 27 and so on, to 12, 25, 38 and so on, so neighbouring codewords of a + /// block lie apart in the symbol. + /// + /// The codewords in block order. + /// The buffer the codewords are written to in placement order. + private static void Interleave(ReadOnlySpan codewords, Span placed) + { + int index = 0; + for (int start = 0; start < InterleaveStep; start++) + { + for (int c = start; c < codewords.Length; c += InterleaveStep) + { + placed[index++] = codewords[c]; + } + } + } + + /// + /// Builds the mask layers of a symbol: the alignment patterns, the finder patterns and the function information modules, which hold light until the mask is + /// chosen, then the codeword bits in the cells left, row by row from the top left. Bit m of a cell is its module value under mask m, so a function cell is + /// 0x0F or 0 and a data cell is its bit repeated, flipped by . + /// + /// The version. + /// The codewords in placement order. + /// The cells, side squared, to write. + private static void Layout(int version, ReadOnlySpan codewords, Span cells) + { + // A cell is unset until a pattern or a data bit claims it. Section 5.8.2 places the fixed patterns first, so the data takes only the cells they leave. + const byte unset = 0xFF; + int size = BaseSide + (2 * version); + cells.Fill(unset); + + int spacing = Alignment[(version - 1) * 2]; + int intervals = Alignment[((version - 1) * 2) + 1]; + if (intervals != 0) + { + AddAlignment(cells, size, spacing, intervals); + } + + // The tables run from the corner inward, so (x, y) of a table maps to the top left cell (x, y), the top right cell (side - 1 - x, y), the bottom right + // cell (side - 1 - x, side - 1 - y) and the bottom left cell (x, side - 1 - y). The finder patterns overwrite any alignment cells under them. + ReadOnlySpan finder = FinderPattern; + ReadOnlySpan corner = CornerFinderPattern; + for (int y = 0; y < FinderSize; y++) + { + for (int x = 0; x < FinderSize; x++) + { + byte value = finder[(y * FinderSize) + x]; + cells[(y * size) + x] = value; + cells[(y * size) + size - 1 - x] = value; + cells[((size - 1 - y) * size) + size - 1 - x] = value; + cells[((size - 1 - y) * size) + x] = corner[(y * FinderSize) + x]; + } + } + + // The function information carries the mask, so this loop reserves its cells as light. The encoder writes these cells after it chooses the mask. As + // function cells, they also stay out of the masking. + for (int i = 0; i < FunctionBits; i++) + { + FunctionPositions(i, size, out int x1, out int y1, out int x2, out int y2); + cells[(y1 * size) + x1] = 0; + cells[(y2 * size) + x2] = 0; + } + + // The bits fill the unset cells in row order, the most significant bit of each codeword first. The count of unset cells is DataModules of the version, + // and the codewords hold at least that many bits. The patterns above write 0 or 1, which become 0 or 0x0F, the same value under every mask. + int bit = 0; + int c = 0; + for (int y = 0; y < size; y++) + { + for (int x = 0; x < size; x++, c++) + { + if (cells[c] == unset) + { + int value = (codewords[bit >> 3] >> (7 - (bit & 7))) & 1; + cells[c] = (byte)(MaskLayer(x + 1, y + 1) ^ (value * 0x0F)); + bit++; + } + else + { + cells[c] = (byte)(cells[c] * 0x0F); + } + } + } + } + + /// + /// Draws the alignment patterns: dark lines on a staggered grid of the regular spacing, each with a light line beside it, and the light breaks where the + /// lines meet the edges. + /// + /// The cells. + /// The side in modules. + /// The regular distance between the lines. + /// The number of regular intervals across the side. + private static void AddAlignment(Span cells, int size, int spacing, int intervals) + { + // The coordinates run from the top right corner: a is the distance from the right edge and b the row. SetMirrored draws each point at (a, b) and at (b, + // a), so each pass draws a row and its matching column together. + // + // The lines lie at 0, spacing, 2 x spacing and so on to intervals x spacing, and the last at side - 1. The last interval takes the side the regular + // intervals leave. + int remainder = size - (spacing * intervals); + + // Each line is dark in alternate stretches of one spacing, and the next line starts with the other phase. The alternation gives the stepped lines of + // the symbol. + int stagger = 0; + int i = 0; + while (i < size) + { + for (int j = 0; j < size; j++) + { + // In the regular part, a module is dark in a dark stretch or where the line crosses another line, at a multiple of the spacing. The first + // stretch of the top line stays clear for the finder pattern at the top right. The last stretch, of the remainder length, continues the + // alternation. + bool dark = j + remainder < size + ? (((j / spacing) + stagger) % 2 == 0 && !(i == 0 && j < spacing)) || j % spacing == 0 + : (intervals + stagger) % 2 == 0; + + // Each dark module has a light module one step down and one step left of it. Together these make the light line beside the dark line, so a + // pattern is 2 modules wide, as section 6.1 requires. + if (dark) + { + SetMirrored(cells, size, j, i, 1); + if (j + 1 < size && i + 1 < size) + { + SetMirrored(cells, size, j + 1, i + 1, 0); + } + } + } + + i = i + remainder == size ? i + remainder - 1 : i + spacing; + stagger = 1 - stagger; + } + + // The assistant alignment patterns sit on the edges where a line meets them. Each is 3 modules along the edge and 2 deep, as section 6.1 requires. The + // line end stays dark, and the five modules around it turn light. On the top and right edges, a pattern goes at every odd line. The phase of the bottom + // and left edges depends on the number of lines, and there a pattern goes where the edge module before the line end is not dark. + for (i = spacing; i <= size - 2; i += spacing) + { + if ((i / spacing) % 2 != 0) + { + SetReflected(cells, size, 0, i - 1, 0); + SetReflected(cells, size, 0, i + 1, 0); + SetReflected(cells, size, 1, i - 1, 0); + SetReflected(cells, size, 1, i, 0); + SetReflected(cells, size, 1, i + 1, 0); + SetReflected(cells, size, i - 1, 0, 0); + SetReflected(cells, size, i + 1, 0, 0); + SetReflected(cells, size, i - 1, 1, 0); + SetReflected(cells, size, i, 1, 0); + SetReflected(cells, size, i + 1, 1, 0); + } + + if (cells[((i - 1) * size) + 0] != 1) + { + SetReflected(cells, size, size - 1, i - 1, 0); + SetReflected(cells, size, size - 2, i - 1, 0); + SetReflected(cells, size, size - 2, i, 0); + SetReflected(cells, size, size - 2, i + 1, 0); + SetReflected(cells, size, size - 1, i + 1, 0); + SetReflected(cells, size, i - 1, size - 1, 0); + SetReflected(cells, size, i - 1, size - 2, 0); + SetReflected(cells, size, i, size - 2, 0); + SetReflected(cells, size, i + 1, size - 2, 0); + SetReflected(cells, size, i + 1, size - 1, 0); + } + } + } + + /// + /// Sets the cell a column from the right edge on a row, x = side - 1 - a and y = b. + /// + /// The cells. + /// The side in modules. + /// The distance from the right edge. + /// The row. + /// The value. + private static void SetReflected(Span cells, int size, int a, int b, byte value) + => cells[(b * size) + size - 1 - a] = value; + + /// + /// Sets the two cells of an alignment pattern point, which mirror each other about the diagonal from the top right corner: x = side - 1 - b with y = a, and + /// x = side - 1 - a with y = b. + /// + /// The cells. + /// The side in modules. + /// The first coordinate. + /// The second coordinate. + /// The value. + private static void SetMirrored(Span cells, int size, int a, int b, byte value) + { + SetReflected(cells, size, b, a, value); + SetReflected(cells, size, a, b, value); + } + + /// + /// Returns the two positions of bit i of the function information: one beside the finder pattern at the top left or the bottom right, and one beside the + /// finder pattern at the top right or the bottom left. + /// + /// The bit, 0 to 33. + /// The side in modules. + /// The column of the first position. + /// The row of the first position. + /// The column of the second position. + /// The row of the second position. + private static void FunctionPositions(int i, int size, out int x1, out int y1, out int x2, out int y2) + { + // Bits 0 to 8 run along row 8 from the left edge, bits 9 to 16 up column 8, bits 17 to 25 down from the top along column side - 9, and bits 26 to 33 + // along row 8 to the right edge, each with its partner by the opposite finder pattern. + if (i < 9) + { + (x1, y1, x2, y2) = (i, 8, size - 1 - i, size - 9); + } + else if (i < 17) + { + int m = i - 8; + (x1, y1, x2, y2) = (8, 8 - m, size - 9, size - 9 + m); + } + else if (i < 26) + { + int k = i - 17; + (x1, y1, x2, y2) = (size - 9, k, 8, size - 1 - k); + } + else + { + int m = i - 25; + (x1, y1, x2, y2) = (size - 9 + m, 8, 8 - m, size - 9); + } + } + + /// + /// Returns the mask layer of a data module at one-based column a and row b: bit m is set where mask m flips the module. Mask 0 flips none, mask 1 where (a + /// + b) mod 2 = 0, mask 2 where ((a + b) mod 3 + a mod 3) mod 2 = 0, and mask 3 where (a mod b + b mod a + a mod 3 + b mod 3) mod 2 = 0. The coordinates + /// are one-based, so a mod b and b mod a never divide by zero. + /// + /// The one-based column. + /// The one-based row. + /// The mask layer, bits 1 to 3. + private static int MaskLayer(int a, int b) + { + int layer = 0; + layer |= (a + b) % 2 == 0 ? 1 << 1 : 0; + layer |= (((a + b) % 3) + (a % 3)) % 2 == 0 ? 1 << 2 : 0; + layer |= ((a % b) + (b % a) + (a % 3) + (b % 3)) % 2 == 0 ? 1 << 3 : 0; + return layer; + } + + /// + /// Chooses the mask whose symbol scores the lowest penalty over its rows and columns. Equal scores keep the first mask. + /// + /// The mask layers of the modules. + /// The side in modules. + /// The mask, 0 to 3. + private static int ChooseMask(ReadOnlySpan layers, int size) + { + // A line of the largest symbol has at most 190 runs, the leading light run included. + InlineArray256 runStorage = default; + Span runs = runStorage; + int best = 0; + int bestScore = int.MaxValue; + for (int mask = 0; mask < MaskCount; mask++) + { + // Row `line` starts at line x size and steps 1; column `line` starts at line and steps size. + int score = 0; + for (int line = 0; line < size; line++) + { + score += Penalty(runs[..RunLengths(layers, line * size, 1, size, mask, runs)]); + score += Penalty(runs[..RunLengths(layers, line, size, size, mask, runs)]); + } + + if (score < bestScore) + { + bestScore = score; + best = mask; + } + } + + return best; + } + + /// + /// Writes the runs of a row or a column under a mask, alternately light and dark from a light run, which is empty when the line starts dark. + /// + /// The mask layers of the modules. + /// The index of the first module. + /// The distance between two modules of the line. + /// The number of modules of the line. + /// The mask. + /// The buffer the runs are written to. + /// The number of runs. + private static int RunLengths(ReadOnlySpan layers, int start, int step, int size, int mask, Span runs) + { + // The first run is light, even when empty, so the even runs are light and the odd runs dark. Penalty depends on this order. + int count = 1; + runs[0] = 0; + int colour = 0; + for (int k = 0; k < size; k++) + { + int value = (layers[start + (k * step)] >> mask) & 1; + if (value == colour) + { + runs[count - 1]++; + } + else + { + colour = value; + runs[count++] = 1; + } + } + + return count; + } + + /// + /// Scores the runs of one line: 4 points for each module of a run of three or more, and 50 for each 1:1:1:1:3 or 3:1:1:1:1 ratio of runs that ends a line + /// or meets a run of three or more modules beyond it. + /// + /// The runs, alternately light and dark from a light run. + /// The penalty. + private static int Penalty(ReadOnlySpan runs) + { + int count = runs.Length; + int score = 0; + for (int j = 0; j < count; j++) + { + if (runs[j] >= 3) + { + score += 4 * runs[j]; + } + } + + // A 1:1:1:1:3 or 3:1:1:1:1 ratio is the scan ratio of a Position Detection Pattern in section 4.2.3, so data that shows it can look like a finder + // pattern. The ratio counts only when it is at the start or the end of the line, or when a light run of three or more modules is next to it. + // + // The odd runs are dark, so j steps over the dark runs only. Here dark run j is the 3 unit run, and runs j - 4 to j - 1 are the four 1 unit runs. j = 5 + // puts the ratio after the first light run alone, at the start of the line, and j + 2 >= count leaves at most the last light run after it, at the end. + for (int j = 5; j <= count - 1; j += 2) + { + if (runs[j] % 3 == 0) + { + int unit = runs[j] / 3; + if (runs[j - 4] == unit && runs[j - 3] == unit && runs[j - 2] == unit && runs[j - 1] == unit + && (j == 5 || j + 2 >= count || runs[j - 5] >= 3 || runs[j + 1] >= 3)) + { + score += 50; + } + } + } + + // Here dark run j is the 3 unit run, and runs j + 1 to j + 4 are the four 1 unit runs. j = 1 puts the ratio at the start of the line, and j + 6 >= + // count leaves at most the last light run after it. + for (int j = 1; j <= count - 5; j += 2) + { + if (runs[j] % 3 == 0) + { + int unit = runs[j] / 3; + if (runs[j + 1] == unit && runs[j + 2] == unit && runs[j + 3] == unit && runs[j + 4] == unit + && (j == 1 || j + 6 >= count || runs[j - 1] >= 3 || runs[j + 5] >= 3)) + { + score += 50; + } + } + } + + return score; + } + + /// + /// Writes the function information: the version plus 20, the level and the mask as three 4-bit words, four 4-bit check words over GF(16) with the roots + /// alpha^1 to alpha^4, and the bits 010101, each bit at its two positions. + /// + /// The version. + /// The level, 0 for L1 to 3 for L4. + /// The mask, 0 to 3. + /// The side in modules. + /// The modules to write. + private static void WriteFunctionInformation(int version, int level, int mask, int size, Span modules) + { + // The 12 bits are the version plus 20 (8 bits, 21 to 104), the level (2 bits, the encodings of Table 13) and the mask (2 bits). They split into three + // 4-bit words, the first word holding the high bits. + int value = ((version + 20) * 16) + (level * 4) + mask; + InlineArray3 wordStorage = default; + InlineArray4 generatorStorage = default; + InlineArray4 checkStorage = default; + Span words = wordStorage; + Span generator = generatorStorage; + Span check = checkStorage; + words[0] = (value >> 8) & 0xF; + words[1] = (value >> 4) & 0xF; + words[2] = value & 0xF; + FunctionField.Generator(1, generator); + FunctionField.Remainder(words, 1, 3, generator, check); + + // Bits 0 to 11 are the three data words and bits 12 to 27 the four check words, each word most significant bit first: i >> 2 selects the word and 3 - + // (i & 3) the bit in it. Bits 28 to 33 alternate 0 and 1. + for (int i = 0; i < FunctionBits; i++) + { + int bit = i < 12 + ? (words[i >> 2] >> (3 - (i & 3))) & 1 + : i < 28 + ? (check[(i - 12) >> 2] >> (3 - ((i - 12) & 3))) & 1 + : (i - 28) & 1; + + FunctionPositions(i, size, out int x1, out int y1, out int x2, out int y2); + modules[(y1 * size) + x1] = bit == 1; + modules[(y2 * size) + x2] = bit == 1; + } + } + + /// + /// Writes the bits of the segments to the data codewords, most significant bit first, or only counts them when the buffer is empty. + /// + private ref struct BitWriter + { + /// + /// The data codewords, or empty to count only. + /// + private readonly Span codewords; + + /// + /// Initializes a new instance of the struct. + /// + /// The data codewords, cleared, or empty to count only. + public BitWriter(Span codewords) + { + this.codewords = codewords; + this.Position = 0; + } + + /// + /// Gets the number of bits written. + /// + public int Position { get; private set; } + + /// + /// Writes a Numeric segment: the mode indicator, the digits three to 10 bits as their value, and the terminator that gives the number of digits in the + /// last group. + /// + /// The digits. + public void WriteDigits(ReadOnlySpan digits) + { + this.Write((int)SegmentMode.Numeric, 4); + for (int start = 0; start < digits.Length; start += 3) + { + int value = 0; + int end = Math.Min(start + 3, digits.Length); + for (int d = start; d < end; d++) + { + value = (value * 10) + (digits[d] - '0'); + } + + this.Write(value, 10); + } + + // Table 2 gives the terminators 1111111101, 1111111110 and 1111111111 for a last group of one, two and three digits, which (length - 1) mod 3 + // selects. + this.Write(NumericTerminator + ((digits.Length - 1) % 3), 10); + } + + /// + /// Writes a Byte segment: the mode indicator, the byte count and the bytes. + /// + /// The characters U+0000 to U+00FF as bytes. + public void WriteBytes(ReadOnlySpan bytes) + { + this.Write((int)SegmentMode.Byte, 4); + this.Write(bytes.Length, ByteCountBits); + foreach (char b in bytes) + { + this.Write(b, 8); + } + } + + /// + /// Writes the low bits of a value, the most significant first. + /// + /// The value. + /// The number of bits, at most 13. + private void Write(int value, int count) + { + if (!this.codewords.IsEmpty) + { + // Bit b of the value goes to stream bit position. position >> 3 is its codeword, and 7 - (position & 7) is its bit in that codeword, the first + // stream bit being the most significant. The codewords start cleared, so OR sets the dark bits alone. + for (int b = count - 1; b >= 0; b--) + { + int position = this.Position + (count - 1 - b); + this.codewords[position >> 3] |= ((value >> b) & 1) << (7 - (position & 7)); + } + } + + this.Position += count; + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/HanXin/HanXinErrorCorrectionLevel.cs b/src/ImageSharp.Drawing/Barcodes/HanXin/HanXinErrorCorrectionLevel.cs new file mode 100644 index 00000000..89c6f59a --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/HanXin/HanXinErrorCorrectionLevel.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The error correction levels of Han Xin Code. A higher level recovers a larger part of a damaged symbol and holds less data in a symbol of the same version. +/// +public enum HanXinErrorCorrectionLevel +{ + /// + /// Level L1, which recovers about 8 percent of the symbol. + /// + L1, + + /// + /// Level L2, which recovers about 15 percent of the symbol. + /// + L2, + + /// + /// Level L3, which recovers about 23 percent of the symbol. + /// + L3, + + /// + /// Level L4, which recovers about 30 percent of the symbol. + /// + L4, +} diff --git a/src/ImageSharp.Drawing/Barcodes/HanXin/HanXinSymbology.cs b/src/ImageSharp.Drawing/Barcodes/HanXin/HanXinSymbology.cs new file mode 100644 index 00000000..18b86826 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/HanXin/HanXinSymbology.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Han Xin Code symbology: a square matrix of 23 x 23 to 189 x 189 modules, versions 1 to 84, with finder patterns at the four corners and a quiet zone of +/// three modules on every side. It carries the characters U+0000 to U+00FF in Numeric and Byte mode segments with Reed-Solomon error correction at one of four +/// levels. +/// +public sealed class HanXinSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class at error correction level L2. The symbol takes the smallest version that holds the + /// data. + /// + public HanXinSymbology() + : this(HanXinErrorCorrectionLevel.L2) + { + } + + /// + /// Initializes a new instance of the class. The symbol takes the smallest version that holds the data. + /// + /// The error correction level. + public HanXinSymbology(HanXinErrorCorrectionLevel errorCorrectionLevel) + { + Guard.MustBeBetweenOrEqualTo( + (int)errorCorrectionLevel, + (int)HanXinErrorCorrectionLevel.L1, + (int)HanXinErrorCorrectionLevel.L4, + nameof(errorCorrectionLevel)); + + this.ErrorCorrectionLevel = errorCorrectionLevel; + } + + /// + /// Initializes a new instance of the class with a fixed version. + /// + /// The error correction level. + /// The version, 1 to 84. + public HanXinSymbology(HanXinErrorCorrectionLevel errorCorrectionLevel, int version) + : this(errorCorrectionLevel) + { + Guard.MustBeBetweenOrEqualTo(version, 1, HanXinEncoder.VersionCount, nameof(version)); + this.Version = version; + } + + /// + /// Gets the error correction level. + /// + public HanXinErrorCorrectionLevel ErrorCorrectionLevel { get; } + + /// + /// Gets the version, or when the version follows from the data. + /// + public int? Version { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + // Section 6.1 of ISO/IEC 20830: "the width of a module shall be specified by the application", so the symbology gives no limit. + double xDimension = options.ResolveXDimension(0, DefaultMatrixXDimension, double.PositiveInfinity); + HanXinEncoder.BuildSymbol(text, this.ErrorCorrectionLevel, this.Version ?? 0, xDimension, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/HexagonalEmitter.cs b/src/ImageSharp.Drawing/Barcodes/HexagonalEmitter.cs new file mode 100644 index 00000000..c6b2f557 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/HexagonalEmitter.cs @@ -0,0 +1,269 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Measures and draws a hexagonal , the form of MaxiCode. The draw puts the dark hexagons and the three dark rings of the finder in +/// one fill of one path. The dimensions are section 4.11 of ISO/IEC 16023. +/// +internal static class HexagonalEmitter +{ + /// + /// The nominal X dimension in millimetres that the finder radii of Table 8 are given at: W = L/29 with L = 25.50 mm, Table 7. + /// + private const double TableXDimension = 0.88; + + /// + /// The amount in millimetres by which a dark hexagon is narrower and shorter than its module, Table 6: a width of (X - 0.12 mm) and a height of (V - 0.12 + /// mm), so that "dark hexagons should not abut contiguously but instead should be distinct from one another". + /// + private const double DarkHexagonReduction = 0.12; + + /// + /// The row of the module the finder is centred on, section 4.11.4: "16Y above the center of modules in the bottom-most row of the symbol, and 14W to the + /// right from the center of the left-most module of the row". + /// + private const int FinderRow = 16; + + /// + /// The column of the module the finder is centred on, section 4.11.4. See . + /// + private const int FinderColumn = 14; + + /// + /// Gets the radii of the finder transitions in millimetres at the nominal size, Table 8: the light centre out to R1, then dark to R2, light to R3, dark to + /// R4, light to R5 and dark to R6. + /// + private static ReadOnlySpan FinderRadii => [0.51, 1.18, 1.86, 2.53, 3.20, 3.87]; + + /// + /// Measures the symbol without drawing anything. + /// + /// The encoded hexagonal symbol. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The top left corner of the first module of the symbol, in pixels. The hexagons do not round onto the pixel grid. + /// The measured layout of the symbol. + public static BarcodeMetrics Measure(ref BarcodeSymbol symbol, BarcodeOptions options, PointF origin, out PointF symbolOrigin) + { + HexagonLayout layout = new(ref symbol, options, origin); + symbolOrigin = layout.SymbolOrigin; + return layout.Metrics; + } + + /// + /// Draws the symbol onto the canvas. + /// + /// The canvas to render onto. + /// The encoded hexagonal symbol. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The brush that fills the renderable bounds before the modules draw, or for none. + /// The measured layout of the symbol, the same as returns. + public static BarcodeMetrics Draw(DrawingCanvas canvas, ref BarcodeSymbol symbol, BarcodeOptions options, PointF origin, Brush? background) + { + HexagonLayout layout = new(ref symbol, options, origin); + + BarcodeMetrics metrics = layout.Metrics; + if (background is not null) + { + RectangleF renderable = metrics.RenderableBounds; + canvas.Fill(background, new RectanglePolygon(renderable.X, renderable.Y, renderable.Width, renderable.Height)); + } + + // The path of the hexagons comes first, then one path for each dark ring. Each dark ring is its outer circle with the inner circle wound the other + // way, which the nonzero rule leaves light. Each ring takes the next pair of transitions of Table 8. + ReadOnlySpan radii = FinderRadii; + IPath[] paths = new IPath[1 + (radii.Length / 2)]; + paths[0] = new BarcodePath(layout.Centres(ref symbol), layout.HalfSize, layout.HexagonInk); + int ring = 1; + for (int i = 0; i < radii.Length; i += 2) + { + float inner = (float)(radii[i] * layout.RadiusScale); + float outer = (float)(radii[i + 1] * layout.RadiusScale); + paths[ring++] = new ComplexPolygon(new EllipsePolygon(layout.FinderCentre, outer), new EllipsePolygon(layout.FinderCentre, inner).Reverse()); + } + + canvas.Fill(options.BarBrush, new ComplexPolygon(paths)); + return metrics; + } + + /// + /// The layout of a hexagonal symbol: where its hexagons and its finder stand, from one origin. The measure and the draw read one layout. + /// + private readonly ref struct HexagonLayout + { + /// + /// The left edge of the symbol, in pixels. + /// + private readonly double left; + + /// + /// The top edge of the symbol, in pixels. + /// + private readonly double top; + + /// + /// The width of a module, W, in pixels. + /// + private readonly double x; + + /// + /// The height of a module, V, in pixels. + /// + private readonly double v; + + /// + /// The distance between rows, Y, in pixels. + /// + private readonly double y; + + /// + /// Initializes a new instance of the struct. + /// + /// The encoded hexagonal symbol. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + public HexagonLayout(ref BarcodeSymbol symbol, BarcodeOptions options, PointF origin) + { + double pixelsPerMillimetre = options.Dpi / BarcodeOptions.MillimetresPerInch; + double x = symbol.XDimension * pixelsPerMillimetre; + this.x = x; + + // Table 7: V = (2/√3)W, the height of a module, and Y = (1.5/√3)W, the distance between rows. + this.v = 2 / Math.Sqrt(3) * x; + this.y = 1.5 / Math.Sqrt(3) * x; + + float sides = options.IncludeQuietZones ? symbol.LeadingQuietZone + symbol.TrailingQuietZone : 0; + float ends = options.IncludeQuietZones ? symbol.TopQuietZone + symbol.BottomQuietZone : 0; + this.left = origin.X + (options.IncludeQuietZones ? symbol.LeadingQuietZone * x : 0); + this.top = origin.Y + (options.IncludeQuietZones ? symbol.TopQuietZone * x : 0); + RectangleF advance = new(origin.X, origin.Y, (float)((symbol.WidthInModules + sides) * x), (float)((symbol.HeightInModules + ends) * x)); + + // A dark hexagon keeps the shape of its module at the reduced width and height. + double reduction = DarkHexagonReduction * pixelsPerMillimetre; + this.HalfSize = new SizeF((float)((x - reduction) / 2), (float)((this.v - reduction) / 2)); + + // The radii of Table 8 scale with the X dimension. + this.FinderCentre = new PointF((float)(this.left + ((FinderColumn + 0.5) * x)), (float)(this.top + (this.v / 2) + (FinderRow * this.y))); + this.RadiusScale = symbol.XDimension / TableXDimension * pixelsPerMillimetre; + + // The ink of the hexagons comes from the first and the last dark module of each row, which a vectorized search finds, so no hexagon is visited. + // A hexagon reaches half its reduced width to each side of its centre and half its reduced height above and below it. + ReadOnlySpan modules = symbol.Modules; + int columns = symbol.Columns; + float inkLeft = float.MaxValue; + float inkRight = float.MinValue; + int firstRow = -1; + int lastRow = -1; + for (int row = 0; row < symbol.MatrixRows; row++) + { + ReadOnlySpan rowModules = modules.Slice(row * columns, columns); + int first = rowModules.IndexOf(true); + if (first < 0) + { + continue; + } + + firstRow = firstRow < 0 ? row : firstRow; + lastRow = row; + inkLeft = MathF.Min(inkLeft, this.CentreX(row, first) - this.HalfSize.Width); + inkRight = MathF.Max(inkRight, this.CentreX(row, rowModules.LastIndexOf(true)) + this.HalfSize.Width); + } + + this.HexagonInk = RectangleF.FromLTRB( + inkLeft, + this.CentreY(firstRow) - this.HalfSize.Height, + inkRight, + this.CentreY(lastRow) + this.HalfSize.Height); + + // The ink is the union of the hexagons and the outermost dark ring of the finder. As in text measuring, the advance is relative to the origin, + // and the bounds and the renderable bounds are absolute. + float radius = (float)(FinderRadii[^1] * this.RadiusScale); + RectangleF finder = new(this.FinderCentre.X - radius, this.FinderCentre.Y - radius, 2 * radius, 2 * radius); + RectangleF bounds = RectangleF.Union(this.HexagonInk, finder); + this.Metrics = new BarcodeMetrics( + new RectangleF(0, 0, advance.Width, advance.Height), + bounds, + RectangleF.Union(advance, bounds)); + } + + /// + /// Gets the union of the dark hexagons, in pixels. + /// + public RectangleF HexagonInk { get; } + + /// + /// Gets the top left corner of the first module of the symbol, in pixels. + /// + public PointF SymbolOrigin => new((float)this.left, (float)this.top); + + /// + /// Gets the metrics of the layout. + /// + public BarcodeMetrics Metrics { get; } + + /// + /// Gets half the width and half the height of every dark hexagon, in pixels. + /// + public SizeF HalfSize { get; } + + /// + /// Gets the centre of the finder, in pixels. + /// + public PointF FinderCentre { get; } + + /// + /// Gets the factor from the radii of Table 8 in millimetres to pixels. + /// + public double RadiusScale { get; } + + /// + /// Returns the centre of every dark hexagon. The pointed hexagons stand on their centres, the odd rows half a module to the right. The path retains the + /// centres until the canvas flushes, so it owns the array, sized by a count of the dark modules. + /// + /// The encoded symbol. + /// The centres, in pixels. + public PointF[] Centres(ref BarcodeSymbol symbol) + { + ReadOnlySpan modules = symbol.Modules; + int columns = symbol.Columns; + PointF[] centres = new PointF[modules.Count(true)]; + int index = 0; + for (int row = 0; row < symbol.MatrixRows; row++) + { + ReadOnlySpan rowModules = modules.Slice(row * columns, columns); + float centreY = this.CentreY(row); + for (int column = 0; column < columns; column++) + { + if (rowModules[column]) + { + centres[index++] = new PointF(this.CentreX(row, column), centreY); + } + } + } + + return centres; + } + + /// + /// Returns the x of the centre of a module, in pixels. The odd rows stand half a module to the right. + /// + /// The row of the module. + /// The column of the module. + /// The x in pixels. + private float CentreX(int row, int column) + => (float)(this.left + ((row & 1) == 1 ? this.x / 2 : 0) + ((column + 0.5) * this.x)); + + /// + /// Returns the y of the centres of a row of modules, in pixels. + /// + /// The row. + /// The y in pixels. + private float CentreY(int row) + => (float)(this.top + (this.v / 2) + (row * this.y)); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Hibc/HibcData.cs b/src/ImageSharp.Drawing/Barcodes/Hibc/HibcData.cs new file mode 100644 index 00000000..fb330058 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Hibc/HibcData.cs @@ -0,0 +1,584 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The data layer the Health Industry Bar Code symbologies share, from ANSI/HIBC 2.6. Every HIBC symbol carries the Code 39 character set, the same flag +/// character in front of the data and the same modulo 43 check character after it, whatever symbology draws it. +/// +internal static class HibcData +{ + /// + /// The number of characters a caller holds inline for to build in. Data this long covers the labels the standard is used for, and + /// longer data rents its buffer once. + /// + public const int InlineBufferLength = 64; + + /// + /// The smallest bar height of a Code 128 or Code 39 carrier as a fraction of the symbol length. Section 5.1: "The height of the bars should be at least 15% + /// of the symbol length." + /// + public const float MinimumBarHeightFraction = 0.15F; + + /// + /// The smallest X dimension in millimetres of an Aztec Code, Data Matrix or QR Code symbol, 0.010 inch. Section 5.2: "Any X-dimension greater than 0.010 + /// (0.25mm) inches is allowable if the print quality requirement is met." The section gives no largest X dimension. + /// + public const double MinimumMatrixXDimension = 0.010 * 25.4; + + /// + /// The smallest X dimension in millimetres of a Code 128 or Code 39 symbol, 0.0067 inch. Section 5.1: "Any X-dimension greater than 0.0067 inches is + /// allowable if the print quality requirement is met." The section gives no largest X dimension. + /// + public const double MinimumLinearXDimension = 0.0067 * 25.4; + + /// + /// The default X dimension in millimetres of a Code 128 or Code 39 symbol: 0.010 inch. Section 5.1: "Labelers should use an X-dimension of 0.010 inches + /// (0.25 mm)." + /// + public const double NominalLinearXDimension = 0.010 * 25.4; + + /// + /// The default X dimension in millimetres of an Aztec Code, Data Matrix or QR Code symbol: 0.015 inch. Section 5.2: "Labelers should use an X-dimension + /// of 0.015 inches (0.37 mm)." + /// + public const double NominalMatrixXDimension = 0.015 * 25.4; + + /// + /// The delimiter that opens and closes the human readable interpretation. Section 4.1: the interpretation "should be bounded in the beginning and at the + /// end of the data string by an asterisk". + /// + private const char ReadableDelimiter = '*'; + + /// + /// The character that begins the encoded data, the HIBC Supplier Labeling Flag Character of Table 1. + /// + private const char FlagCharacter = '+'; + + /// + /// The character that stands in for a space in the human readable interpretation. Section 4.1: "The Check Character or Link Character in the symbol will + /// sometimes be a space character. In this case, the human-readable interpretation shall use an "underscore" to represent the space character." + /// + private const char PrintedSpace = '_'; + + /// + /// The character that stands in for a zero in the human readable interpretation. Section 4.1: "It is the recommendation of HIBCC that the human-readable + /// interpretation of zero be represented as "Ø"." + /// + private const char PrintedZero = 'Ø'; + + /// + /// The character that joins primary and secondary data in one symbol, section 2.2.1.1, and opens each supplemental data field, section 2.3.1. + /// + private const char Delimiter = '/'; + + /// + /// The longest Product or Catalog Number. Table 1: "P 1-18 V Labelers Product or Catalog Number (PCN)." + /// + private const int MaximumProductLength = 18; + + /// + /// The longest Lot/Batch or Serial Number. Appendix E1.2: "The Lot/Batch or Serial Number field can be alphanumeric and vary in length up to a maximum of + /// 18 characters." + /// + private const int MaximumLotLength = 18; + + /// + /// The longest quantity. Section 2.3.2.4: "Field Length - an1+n1...n5 Q Quantity". + /// + private const int MaximumQuantityLength = 5; + + /// + /// Returns the number of characters writes at most for the text: the flag character, the data and the check character. The prepared + /// data is never shorter than the text, so text longer than the carrier holds fails here, before the caller sizes a buffer from it. + /// + /// The text. + /// The largest number of characters the carrier symbology encodes. + /// The capacity the prepared data needs. + /// The text is longer than . + public static int PreparedCapacity(ReadOnlySpan text, int maximumPrepared) + { + if (text.Length > maximumPrepared) + { + throw new ArgumentException($"The text is {text.Length} characters; the symbology carries at most {maximumPrepared}.", nameof(text)); + } + + return text.Length + 2; + } + + /// + /// Validates the given text against the HIBC character set and the data structures of ANSI/HIBC 2.6, calculates the check character over the data and + /// writes the encoded data from both. + /// + /// The data to carry, without the flag character. When is the last + /// character is the check character rather than data. + /// Whether the method validates the check character at the end of . + /// The builder that receives the encoded data: the flag character, the data and the check character. + /// The text is not valid HIBC data. + public static void Prepare(ReadOnlySpan text, bool validateCheckCharacter, ref ValueStringBuilder encoded) + { + // The data is at least one character, and a check character to validate follows it. + Guard.MustBeGreaterThan(text.Length, validateCheckCharacter ? 1 : 0, nameof(text)); + + // The check character covers the flag character the encoder puts in front of the data, so the sum starts at the value that character holds in the set. + int sum = AlphanumericCharacterSet.Value(FlagCharacter); + int dataLength = validateCheckCharacter ? text.Length - 1 : text.Length; + int index = 0; + SpanCodePointEnumerator codePoints = text.EnumerateCodePoints(); + while (codePoints.MoveNext()) + { + CodePoint current = codePoints.Current; + int value = AlphanumericCharacterSet.Value(current.Value); + if (value < 0) + { + throw new ArgumentException( + $"HIBC carries only digits, capital letters, spaces and the symbols -.$/+%; got {current.ToDisplayString()}.", + nameof(text)); + } + + if (index < dataLength) + { + sum += value; + } + + index++; + } + + ReadOnlySpan data = text[..dataLength]; + ValidateStructure(data); + + char check = AlphanumericCharacterSet.CheckCharacter(sum); + if (validateCheckCharacter && text[dataLength] != check) + { + throw new ArgumentException($"Incorrect check character: expected '{check}', got '{text[dataLength]}'.", nameof(text)); + } + + encoded.Append(FlagCharacter); + encoded.Append(data); + encoded.Append(check); + } + + /// + /// Returns the number of characters and write at most for encoded data: the encoded data, the + /// two delimiters and one line break. + /// + /// The length of the encoded data. + /// The capacity the interpretation needs. + public static int ReadableCapacity(int encodedLength) => encodedLength + 3; + + /// + /// Writes the human readable interpretation of encoded data on one line, as a linear symbol prints it. Section 4.1: the interpretation represents "all + /// encoded characters" between asterisks, a space prints as an underscore and a zero as "Ø". + /// + /// The encoded data, as wrote it. + /// The buffer the interpretation is written to, long. + /// The number of characters written. + public static int WriteReadable(ReadOnlySpan encoded, Span readable) + { + readable[0] = ReadableDelimiter; + int written = 1 + WritePrinted(encoded, readable[1..]); + readable[written++] = ReadableDelimiter; + return written; + } + + /// + /// Adds the human readable interpretation of encoded data below a built matrix or stacked symbol, as writes it, centered + /// on the symbol as the figures of sections 4.3.1 and 4.3.3 print it. Section 3.0: "The data structure and human-readable interpretation is identical + /// regardless of symbology used." + /// + /// The encoded data, as wrote it. + /// Whether the symbol is a stacked symbol, which places its text below its rows, rather than a matrix symbol. + /// The built symbol. + public static void AddTwoDimensionalReadable(ReadOnlySpan encoded, bool stacked, ref BarcodeSymbol symbol) + { + InlineArray64 readableStorage = default; + int capacity = ReadableCapacity(encoded.Length); + ValueStringBuilder readable = capacity <= InlineBufferLength ? new(readableStorage) : new(capacity); + try + { + Span buffer = readable.AppendSpan(capacity); + int written = WriteMatrixReadable(encoded, buffer); + if (stacked) + { + symbol.AddStackedText(buffer[..written], BarcodeTextAlignment.Centered, 1F); + } + else + { + symbol.AddMatrixText(buffer[..written], BarcodeTextAlignment.Centered, 1F); + } + } + finally + { + readable.Dispose(); + } + } + + /// + /// Writes the human readable interpretation of encoded data that a two-dimensional symbol carries. Section 3.0 gives every symbology the same + /// interpretation as . The figure of section 4.3.3 prints primary and secondary data that one symbol concatenates on two lines, + /// broken after the character that joins them. + /// + /// The encoded data, as wrote it. + /// The buffer the interpretation is written to, long. + /// The number of characters written. + private static int WriteMatrixReadable(ReadOnlySpan encoded, Span readable) + { + readable[0] = ReadableDelimiter; + int written = 1; + + // Note 1 of section 2.2.1: primary data starts with a letter after the flag character. Primary data holds no "/", so the first "/" after it is the + // join of section 2.2.1.1. Secondary data alone has no join, and its "/" characters open supplemental data fields. The check character at the end can + // be "/" too, so the search stops before it. + int join = char.IsAsciiLetterUpper(encoded[1]) ? encoded[..^1].IndexOf(Delimiter) : -1; + if (join >= 0) + { + written += WritePrinted(encoded[..(join + 1)], readable[written..]); + readable[written++] = BarcodeTextPlacement.LineBreak; + encoded = encoded[(join + 1)..]; + } + + written += WritePrinted(encoded, readable[written..]); + readable[written++] = ReadableDelimiter; + return written; + } + + /// + /// Writes characters of the encoded data as the interpretation prints them: a space as and a zero as . + /// + /// The characters of the encoded data. + /// The buffer the characters are written to. + /// The number of characters written. + private static int WritePrinted(ReadOnlySpan encoded, Span destination) + { + for (int i = 0; i < encoded.Length; i++) + { + char character = encoded[i]; + destination[i] = character switch + { + ' ' => PrintedSpace, + '0' => PrintedZero, + _ => character, + }; + } + + return encoded.Length; + } + + /// + /// Validates the data against the data structures of ANSI/HIBC 2.6. Note 1 of section 2.2.1: "the Primary Data Structure has an alphabetic character + /// following the HIBC Supplier Labeling Flag Character "+", while the Secondary Data Structure has a numeric character or a "$" following" it. Primary + /// data can carry secondary data after the "/" of section 2.2.1.1, without its flag character and its link character. Secondary data alone, in its own + /// symbol, ends with the link character of Table 2. + /// + /// The data, without the flag character and the check character. + /// The data does not follow a data structure. + private static void ValidateStructure(ReadOnlySpan data) + { + char first = data[0]; + if (char.IsAsciiLetterUpper(first)) + { + int join = data.IndexOf(Delimiter); + ValidatePrimary(join < 0 ? data : data[..join]); + if (join >= 0) + { + ValidateSecondary(data[(join + 1)..], false); + } + + return; + } + + if (char.IsAsciiDigit(first) || first == '$') + { + ValidateSecondary(data, true); + return; + } + + throw new ArgumentException("HIBC data starts with a letter for primary data, or with a digit or '$' for secondary data.", nameof(data)); + } + + /// + /// Validates primary data against Table 1: a 4-character Labeler Identification Code "with the first character always being alphabetic", a 1 to 18 + /// character Product or Catalog Number of "A through Z and 0 through 9", and a one digit Unit of Measure. + /// + /// The primary data, without the flag character. + /// The data does not follow Table 1. + private static void ValidatePrimary(ReadOnlySpan primary) + { + // Section 3.0: "No special characters (-, ., $, /, +, %, and space) are used in the Primary data structure". + for (int i = 0; i < primary.Length; i++) + { + if (!char.IsAsciiLetterUpper(primary[i]) && !char.IsAsciiDigit(primary[i])) + { + throw new ArgumentException("HIBC primary data carries only capital letters and digits.", nameof(primary)); + } + } + + int productLength = primary.Length - 5; + if (productLength < 1 || productLength > MaximumProductLength) + { + throw new ArgumentException( + "HIBC primary data is a 4-character labeler code, a 1 to 18 character product number and a one digit unit of measure.", + nameof(primary)); + } + + if (!char.IsAsciiDigit(primary[^1])) + { + throw new ArgumentException("The unit of measure of HIBC primary data is a digit, 0 through 9.", nameof(primary)); + } + } + + /// + /// Validates secondary data against Table 2 and Appendix F, then any supplemental data fields of section 2.3. A numeric start is a 5-digit Julian date + /// alone. "$" starts a lot number, "$+" a serial number, and "$$" and "$$+" a date of the format the next digit gives, then a lot or a serial number. + /// + /// The secondary data, without the flag character. + /// Whether the data stands in its own symbol and so ends with a link character. + /// The data does not follow Table 2 or section 2.3. + private static void ValidateSecondary(ReadOnlySpan secondary, bool linked) + { + int position; + bool requireField; + if (secondary.Length > 0 && char.IsAsciiDigit(secondary[0])) + { + // Table 2: "If the first character is numeric, then R is a fixed 5-digit Julian date. No Lot/Batch or Serial Number is present". + ValidateDate(secondary, 0, "YYJJJ"); + position = 5; + ValidateLinkAndSupplements(secondary, position, linked); + return; + } + + if (secondary.StartsWith("$$")) + { + // Appendix E1.1: after "$$" or "$$+" "the first digit following will specify the Date Field formats". + position = secondary.StartsWith("$$+") ? 3 : 2; + if (position >= secondary.Length) + { + throw new ArgumentException("HIBC secondary data gives a date format digit after \"$$\" or \"$$+\".", nameof(secondary)); + } + + position += secondary[position] switch + { + '0' or '1' => ValidateDate(secondary, position, "MMYY"), + '2' => 1 + ValidateDate(secondary, position + 1, "MMDDYY"), + '3' => 1 + ValidateDate(secondary, position + 1, "YYMMDD"), + '4' => 1 + ValidateDate(secondary, position + 1, "YYMMDDHH"), + '5' => 1 + ValidateDate(secondary, position + 1, "YYJJJ"), + '6' => 1 + ValidateDate(secondary, position + 1, "YYJJJHH"), + '7' => 1, + _ => throw new ArgumentException("The HIBC date format digit is 0 through 7.", nameof(secondary)), + }; + + requireField = false; + } + else if (secondary.StartsWith("$+")) + { + // Table 2: "If the first two characters are "$+" and the third character is alphanumeric then a serial number only follows." + position = 2; + requireField = true; + } + else if (secondary.StartsWith("$")) + { + // Table 2: "If the first character is a "$" and the second character is alphanumeric, then the Date fields are not used." + position = 1; + requireField = true; + } + else + { + throw new ArgumentException("HIBC secondary data starts with a digit or '$'.", nameof(secondary)); + } + + // Section 3.0 permits "." and "-" in the Lot and Serial fields. A required field starts with a letter or a digit. + int fieldStart = position; + while (position < secondary.Length && IsLotCharacter(secondary[position])) + { + position++; + } + + // In a symbol of its own the link character follows the field, and it can be any character of the set. A link character that is a letter, a digit, "." + // or "-" ends the run of field characters. Any other link character stands after the run. + int fieldEnd = position; + if (linked && !EndsWithLinkAfterRun(secondary, fieldEnd)) + { + if (fieldEnd == fieldStart) + { + throw new ArgumentException("HIBC secondary data in its own symbol ends with the link character.", nameof(secondary)); + } + + fieldEnd--; + } + + int fieldLength = fieldEnd - fieldStart; + if (fieldLength > MaximumLotLength) + { + throw new ArgumentException("An HIBC lot or serial number is at most 18 characters.", nameof(secondary)); + } + + if (requireField && (fieldLength < 1 || !(char.IsAsciiLetterUpper(secondary[fieldStart]) || char.IsAsciiDigit(secondary[fieldStart])))) + { + throw new ArgumentException("An HIBC lot or serial number after \"$\" or \"$+\" starts with a letter or a digit.", nameof(secondary)); + } + + ValidateLinkAndSupplements(secondary, fieldEnd, linked); + } + + /// + /// Returns whether the link character of secondary data in its own symbol stands after the run of field characters that ends at a position, rather than + /// being the last character of that run. A link character that is not a field character ends the run, and only "/" can also open a supplemental field. + /// + /// The secondary data. + /// The end of the run of field characters. + /// when the link character is the character at . + private static bool EndsWithLinkAfterRun(ReadOnlySpan secondary, int runEnd) + { + if (runEnd >= secondary.Length) + { + return false; + } + + // A "/" after the run is the link character when the data ends after it or the next field opens after it. Otherwise it opens the next field. + return secondary[runEnd] != Delimiter || runEnd + 1 == secondary.Length || secondary[runEnd + 1] == Delimiter; + } + + /// + /// Validates the link character of secondary data in its own symbol and the supplemental data fields after it. Section 2.3.1: "The Secondary Supplemental + /// Data field is constructed with a "/" character followed by a Data Identifier (DI), followed by data." Sections 2.3.2.1 to 2.3.2.4 give the fields: + /// "S" and a serial number, "16D" and a date of manufacture, "14D" and an expiry date, and "Q" and a quantity, which "must be the last field". + /// + /// The secondary data. + /// The position after the secondary fields. + /// Whether the data stands in its own symbol and so carries a link character. + /// The data does not follow section 2.3. + private static void ValidateLinkAndSupplements(ReadOnlySpan secondary, int position, bool linked) + { + if (linked) + { + // Table 2: "L 1 Link Character (Check Character from primary data field.)" + if (position >= secondary.Length) + { + throw new ArgumentException("HIBC secondary data in its own symbol ends with the link character.", nameof(secondary)); + } + + position++; + } + + bool quantity = false; + while (position < secondary.Length) + { + if (secondary[position] != Delimiter || quantity) + { + throw new ArgumentException("HIBC supplemental data fields each start with '/', and the quantity is the last field.", nameof(secondary)); + } + + ReadOnlySpan field = secondary[(position + 1)..]; + int end = field.IndexOf(Delimiter); + ReadOnlySpan value = end < 0 ? field : field[..end]; + if (value.StartsWith("16D") || value.StartsWith("14D")) + { + // Sections 2.3.2.2 and 2.3.2.3: "Field Length - an3+n8". + if (value.Length != 11) + { + throw new ArgumentException("An HIBC date of manufacture or expiry date is YYYYMMDD.", nameof(secondary)); + } + + ValidateDate(value, 3, "YYYYMMDD"); + } + else if (value.StartsWith("S")) + { + // Section 2.3.2.1: "Field Length - an1 + an18". + if (value.Length < 2 || value.Length - 1 > MaximumLotLength) + { + throw new ArgumentException("An HIBC serial number is 1 to 18 characters.", nameof(secondary)); + } + + foreach (char character in value[1..]) + { + if (!IsLotCharacter(character)) + { + throw new ArgumentException("An HIBC serial number carries letters, digits, '.' and '-'.", nameof(secondary)); + } + } + } + else if (value.StartsWith("Q")) + { + // Section 2.3.2.4: "Field Length - an1+n1...n5". + if (value.Length < 2 || value.Length - 1 > MaximumQuantityLength || value[1..].ContainsAnyExceptInRange('0', '9')) + { + throw new ArgumentException("An HIBC quantity is 1 to 5 digits.", nameof(secondary)); + } + + quantity = true; + } + else + { + throw new ArgumentException("An HIBC supplemental data field starts with the data identifier S, 16D, 14D or Q.", nameof(secondary)); + } + + position += 1 + value.Length; + } + } + + /// + /// Validates a date field of the format the field descriptions of Appendix F give: MM a month, DD a day, YY or YYYY a year, HH an hour and JJJ a Julian + /// day, each of fixed length in digits. + /// + /// The data that holds the date. + /// The position of the date. + /// The format, from the letters Y, M, D, H and J. + /// The length of the date. + /// The date does not follow the format. + private static int ValidateDate(ReadOnlySpan data, int start, string format) + { + if (start + format.Length > data.Length || data.Slice(start, format.Length).ContainsAnyExceptInRange('0', '9')) + { + throw new ArgumentException($"An HIBC date of the format {format} is {format.Length} digits.", nameof(data)); + } + + ReadOnlySpan date = data.Slice(start, format.Length); + int month = Field(date, format, 'M'); + int day = Field(date, format, 'D'); + int hour = Field(date, format, 'H'); + int julian = Field(date, format, 'J'); + if ((month >= 0 && month is < 1 or > 12) || (day >= 0 && day is < 1 or > 31) || hour > 23 || (julian >= 0 && julian is < 1 or > 366)) + { + throw new ArgumentException($"The HIBC date {date} is not a date of the format {format}.", nameof(data)); + } + + return format.Length; + } + + /// + /// Returns the value of the field of one letter in a date, or -1 when the format has no such field. + /// + /// The date digits. + /// The format. + /// The letter of the field. + /// The value. + private static int Field(ReadOnlySpan date, string format, char letter) + { + int start = format.IndexOf(letter); + if (start < 0) + { + return -1; + } + + int value = 0; + for (int i = start; i < format.Length && format[i] == letter; i++) + { + value = (value * 10) + (date[i] - '0'); + } + + return value; + } + + /// + /// Returns whether a character can stand in a lot or serial number. Section 3.0: "The use of the special characters "." and "-" are permitted in the Lot + /// and Serial fields of the Secondary data structure." + /// + /// The character. + /// for a capital letter, a digit, "." or "-". + private static bool IsLotCharacter(char character) => char.IsAsciiLetterUpper(character) || char.IsAsciiDigit(character) || character is '.' or '-'; +} diff --git a/src/ImageSharp.Drawing/Barcodes/Interleaved2Of5/DeutschePostData.cs b/src/ImageSharp.Drawing/Barcodes/Interleaved2Of5/DeutschePostData.cs new file mode 100644 index 00000000..fc506fea --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Interleaved2Of5/DeutschePostData.cs @@ -0,0 +1,110 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The data layer the Deutsche Post symbologies share. Both the Leitcode and the Identcode are Interleaved 2 of 5 symbols whose check digit replaces the +/// weights 3 and 1 of the standard calculation with 4 and 9: "Anstelle der bei 2 aus 5 verwendeten Gewichte 3 und 1 werden die Stellen mit den Werten 4 und 9 +/// gewichtet." +/// +internal static class DeutschePostData +{ + /// + /// The smallest X dimension in millimetres. Deutsche Post: "Breite des schmalen Moduls minimal 0,375 mm". + /// + private const double MinimumXDimension = 0.375; + + /// + /// The default X dimension in millimetres. The value is inside the range that Deutsche Post gives. + /// + private const double NominalXDimension = 0.495; + + /// + /// The largest X dimension in millimetres. Deutsche Post: "maximal 0,5 mm". + /// + private const double MaximumXDimension = 0.5; + + /// + /// The default bar height in millimetres, which is also the smallest bar height. Deutsche Post: "Barcodehöhe mindestens 25 mm". + /// + private const double BarHeight = 25; + + /// + /// The quiet zone in millimetres on each side, at least the ten modules of Interleaved 2 of 5. + /// + private const double QuietZone = 5; + + /// + /// The modulus of the check digit. + /// + private const int CheckModulus = 10; + + /// + /// The weight the first digit and every second digit after it take. + /// + private const int FirstWeight = 4; + + /// + /// The weight the second digit and every second digit after it take. + /// + private const int SecondWeight = 9; + + /// + /// Validates a Deutsche Post number and writes the digits the symbol carries: the data digits and the check digit. The check digit weights the digits 4 and + /// 9 alternately from the first, sums the products, and takes the complement of the sum to the next multiple of ten. The documented Leitcode example + /// 2134807501640 sums to 239 and checks as 1, and the Identcode example 56310243031 sums to 187 and checks as 3. + /// + /// The data digits on their own, in which case the check digit is calculated, or the data digits and the check digit, in which case the + /// last is checked against the rest. + /// The number of data digits the code carries, excluding the check digit. + /// The buffer the data digits and the check digit are written to, one longer than . + /// The text is not a valid Deutsche Post number. + public static void Prepare(ReadOnlySpan text, int digits, Span encoded) + { + Guard.MustBeBetweenOrEqualTo(text.Length, digits, digits + 1, nameof(text)); + + BarcodeSymbology.ValidateDigits(text); + + int sum = 0; + for (int i = 0; i < digits; i++) + { + sum += (text[i] - '0') * ((i & 1) == 0 ? FirstWeight : SecondWeight); + } + + int check = (CheckModulus - (sum % CheckModulus)) % CheckModulus; + if (text.Length > digits && text[digits] - '0' != check) + { + throw new ArgumentException($"Incorrect check digit: expected {check}, got {text[digits]}.", nameof(text)); + } + + text.Slice(0, digits).CopyTo(encoded); + encoded[digits] = (char)('0' + check); + } + + /// + /// Builds the Interleaved 2 of 5 symbol of a Deutsche Post number. The default bar height and the quiet zones are in millimetres, so they hold at every + /// module width, and the quiet zone keeps the ten modules of Interleaved 2 of 5 too. + /// + /// The data digits and the check digit. + /// The human readable interpretation. + /// The options that control layout choices. + /// The symbol to write. + public static void BuildSymbol(ReadOnlySpan encoded, ReadOnlySpan readable, BarcodeOptions options, ref BarcodeSymbol symbol) + { + double xDimension = options.ResolveXDimension(MinimumXDimension, NominalXDimension, MaximumXDimension); + Interleaved2Of5Layout layout = new() + { + XDimension = xDimension, + NominalBarHeight = (float)(BarHeight / xDimension), + MinimumBarHeight = (float)(BarHeight / xDimension), + MaximumBarHeight = float.PositiveInfinity, + QuietZone = MathF.Max(Interleaved2Of5Encoder.QuietZone, (float)(QuietZone / xDimension)), + TextClearance = BarcodeTextPlacement.Clearance, + NarrowElement = 1, + WideElement = Interleaved2Of5Encoder.WideElement, + }; + + Interleaved2Of5Encoder.BuildSymbol(encoded, readable, in layout, options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Interleaved2Of5/IdentcodeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Interleaved2Of5/IdentcodeSymbology.cs new file mode 100644 index 00000000..98100bcd --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Interleaved2Of5/IdentcodeSymbology.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Deutsche Post Identcode, the identification code of a mail item. An Interleaved 2 of 5 symbol carries it. The code is twelve digits: the two digit +/// origin mail centre, the customer number, the item number and the check digit. The default narrow module is 0.495 mm. The default bars are 25 mm high. +/// The narrow module is always 0.375 mm to 0.5 mm. The bars are always at least 25 mm high. The quiet zones are 5 mm on each side, and at least the ten +/// modules of Interleaved 2 of 5. +/// +/// The input is the eleven data digits, or those digits and the check digit. This class always calculates the check digit. When the input carries one, this +/// class compares the two. +/// +/// +/// The printed line separates the fields with full stops and spaces and sets the check digit off with a space. Deutsche Post assigns customer numbers of one to +/// five digits and gives no rule for where the customer number ends, so this class prints the twelve digits in groups of two, three, three and three. The +/// symbol carries neither the full stops nor the spaces. +/// +/// +public sealed class IdentcodeSymbology : BarcodeSymbology +{ + /// + /// The number of data digits, excluding the check digit. + /// + private const int Digits = 11; + + /// + /// Initializes a new instance of the class. + /// + public IdentcodeSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray12 encodedStorage = default; + Span encoded = encodedStorage; + DeutschePostData.Prepare(text, Digits, encoded); + + // The interpretation groups the twelve digits as 12.345 678.901 2. + InlineArray16 readableStorage = default; + Span readable = readableStorage; + encoded[..2].CopyTo(readable); + readable[2] = '.'; + encoded[2..5].CopyTo(readable[3..]); + readable[6] = ' '; + encoded[5..8].CopyTo(readable[7..]); + readable[10] = '.'; + encoded[8..11].CopyTo(readable[11..]); + readable[14] = ' '; + readable[15] = encoded[11]; + + DeutschePostData.BuildSymbol(encoded, readable, options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Interleaved2Of5/Interleaved2Of5Encoder.cs b/src/ImageSharp.Drawing/Barcodes/Interleaved2Of5/Interleaved2Of5Encoder.cs new file mode 100644 index 00000000..d6e9038f --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Interleaved2Of5/Interleaved2Of5Encoder.cs @@ -0,0 +1,187 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Shared encodation for the Interleaved 2 of 5 symbology family, which AIM USS-I 2/5 defines and ISO/IEC 16390 succeeded. Section 2.1 gives every character +/// "two wide elements and three narrow elements", with the bars carrying "the more significant digit of the pair" and the spaces the less significant. Section +/// 5.3 of the GS1 General Specifications gives the same encodation for ITF-14. A symbol carries a quiet zone, the start pattern, the digit pairs, the stop +/// pattern and a quiet zone. +/// +internal static class Interleaved2Of5Encoder +{ + /// + /// The largest number of digits a symbol carries. Note 1 to Table 1 of AIM USS-I 2/5 "permits encodation of any length numeric field having an even number + /// of digits", so the symbology sets no maximum of its own. + /// + public const int MaximumLength = 500; + + /// + /// The smallest quiet zone in modules on each side. Section 3.2 of AIM USS-I 2/5: "The minimum quiet zone width is ten times the X dimension or 0.10 inch + /// (2.54 mm), whichever is greater." Section 5.3.2.2 of the GS1 General Specifications gives ITF-14 the 10X alone. + /// + public const int QuietZone = 10; + + /// + /// The smallest quiet zone in millimetres, the 0.10 inch of section 3.2 of AIM USS-I 2/5. + /// + private const double MinimumQuietZoneMillimetres = 2.54; + + /// + /// The number of characters a caller holds inline to build symbol data in. Data this long covers the labels the symbology is used for, and longer data + /// rents its buffer once. + /// + public const int InlineBufferLength = 64; + + /// + /// The element count of one digit, five bars or five spaces. + /// + private const int ElementsPerDigit = 5; + + /// + /// The element count of the start pattern. Section 2.3 of AIM USS-I 2/5: "The start pattern consists of four narrow elements beginning with a bar." + /// + private const int StartElements = 4; + + /// + /// The element count of the stop pattern. Section 2.3 of AIM USS-I 2/5: "The stop pattern is a wide bar followed by two narrow elements." + /// + private const int StopElements = 3; + + /// + /// The width of a wide element in modules. Section 3.2 of AIM USS-I 2/5: "Wide element widths must be in the range 2.0X to 3.0X", narrowing to "2.2X to + /// 3.0X" below an X dimension of 0.020 inches, and section 5.3.2.2 of the GS1 General Specifications gives ITF-14 the range 2.25:1 to 3.0:1. A wide + /// element of 3 modules lies inside every range, so the symbologies without a target ratio take it. + /// + public const int WideElement = 3; + + /// + /// The width of one digit in modules: the two wide elements and three narrow elements of section 2.1 of AIM USS-I 2/5. + /// + private const int ModulesPerDigit = (2 * WideElement) + 3; + + /// + /// The width of the start and stop patterns together in modules: the four narrow elements of the start pattern, then the wide bar and two narrow elements + /// of the stop pattern. + /// + private const int StartStopModules = StartElements + WideElement + 2; + + /// + /// The bar height as a fraction of the symbol length when the caller sets no height. Section 3.2 of AIM USS-I 2/5: "the minimum bar height should be 0.25 + /// inches (6.35 mm) or 15 percent of the bar code symbol length, whichever is greater." Appendix A defines the symbol length as "The distance between the + /// outside edges of the quiet zones". + /// + private const float NominalBarHeightFraction = 0.15F; + + /// + /// The smallest bar height in millimetres when the caller sets none, section 3.2 of AIM USS-I 2/5. + /// + private const float MinimumBarHeightMillimetres = 6.35F; + + /// + /// Gets the element widths of every digit, one bit per element, most significant element first: a set bit is a wide element and a clear bit a narrow one. + /// These are the patterns of Table 2 of AIM USS-I 2/5 and of Table 5-23 of the GS1 General Specifications, indexed by digit. Section 2.2 weights the + /// positions 1, 2, 4, 7 and parity and gives every digit "exactly two non-zero weights". + /// + private static ReadOnlySpan Patterns => + [ + 0b00110, 0b10001, 0b01001, 0b11000, 0b00101, + 0b10100, 0b01100, 0b00011, 0b10010, 0b01010, + ]; + + /// + /// Encodes digits and builds the symbol at the proportional bar height of section 3.2 of AIM USS-I 2/5. + /// + /// The digits to encode, an even number of them, already validated. + /// The human readable interpretation. + /// The options that control layout choices. + /// The X dimension in millimetres, . + /// The symbol to write. + public static void BuildSymbol(ReadOnlySpan digits, ReadOnlySpan readable, BarcodeOptions options, double xDimension, ref BarcodeSymbol symbol) + { + // The quiet zone of section 3.2 of AIM USS-I 2/5 is ten modules, or 2.54 mm when that is wider. + float quietZone = MathF.Max(QuietZone, (float)(MinimumQuietZoneMillimetres / xDimension)); + + // Every digit has the same width, so the symbol length that sets the proportional bar height is known before the runs are written. + float symbolLength = (digits.Length * ModulesPerDigit) + StartStopModules + (2 * quietZone); + + // Section 3.2 gives the minimum bar height as a recommendation, "should be", so it sets the nominal bar height and does not clamp the bar height. + float nominalBarHeight = MathF.Max(symbolLength * NominalBarHeightFraction, (float)(MinimumBarHeightMillimetres / xDimension)); + Interleaved2Of5Layout layout = new() + { + XDimension = xDimension, + NominalBarHeight = nominalBarHeight, + MinimumBarHeight = 0F, + MaximumBarHeight = float.PositiveInfinity, + QuietZone = quietZone, + TextClearance = BarcodeTextPlacement.Clearance, + NarrowElement = 1, + WideElement = WideElement, + }; + + BuildSymbol(digits, readable, in layout, options, ref symbol); + } + + /// + /// Encodes digits and builds the symbol at the dimensions of an application standard, which can put the symbol inside a bearer bar. The runs alternate bar + /// and space, start with the first bar of the start pattern, carry the digit pairs and end on the bar of the stop pattern. The 2 of 5 symbologies carry no + /// guard bars, so every bar runs the full height. Section 5.3.2.4 of the GS1 General Specifications butts the bearer bar "directly against the top and + /// bottom of the bars", so the bars start one thickness below the symbol top, and the human readable interpretation faces the bottom of the lower bearer + /// bar. + /// + /// The digits to encode, an even number of them, already validated. + /// The human readable interpretation. + /// The dimensions of the application standard. + /// The options that control layout choices. + /// The symbol to write. + public static void BuildSymbol( + ReadOnlySpan digits, + ReadOnlySpan readable, + in Interleaved2Of5Layout layout, + BarcodeOptions options, + ref BarcodeSymbol symbol) + { + // The runs count run units, and a narrow element is one module, so a run unit is the module divided by the narrow width. + int narrow = layout.NarrowElement; + int wide = layout.WideElement; + float runUnit = 1F / narrow; + int runCount = (digits.Length * ElementsPerDigit) + StartElements + StopElements; + Span runs = symbol.BeginLinear(runCount, layout.XDimension, out Span heights, out Span tops); + runs[..StartElements].Fill(narrow); + int written = StartElements; + + // Section 2.2.3 of AIM USS-I 2/5: "the more significant digit is encoded in the bars and the less significant digit is encoded in the spaces". Step 5 + // of section 5.3.2.1.1 of the GS1 General Specifications takes those elements alternately, starting with the first bar. + for (int i = 0; i < digits.Length; i += 2) + { + int bars = Patterns[digits[i] - '0']; + int spaces = Patterns[digits[i + 1] - '0']; + for (int bit = ElementsPerDigit - 1; bit >= 0; bit--) + { + runs[written++] = ((bars >> bit) & 1) != 0 ? wide : narrow; + runs[written++] = ((spaces >> bit) & 1) != 0 ? wide : narrow; + } + } + + runs[written++] = wide; + runs[written++] = narrow; + runs[written] = narrow; + + // Every digit has two wide and three narrow elements, the start pattern four narrow elements and the stop pattern a wide and two narrow elements. + float widthInModules = ((digits.Length * ((2 * wide) + (3 * narrow))) + (StartElements * narrow) + wide + (2 * narrow)) * runUnit; + float bearerBarThickness = layout.BearerBarThickness; + float barHeight = options.ResolveBarHeight(layout.XDimension, layout.MinimumBarHeight, layout.NominalBarHeight, layout.MaximumBarHeight); + heights.Fill(barHeight); + tops.Fill(bearerBarThickness); + + if (options.Font is not null) + { + float textEdge = bearerBarThickness + barHeight + bearerBarThickness + layout.TextClearance; + symbol.ReserveText(1, readable.Length); + symbol.AddText(readable, 0F, widthInModules, BarcodeTextSide.BelowBars, textEdge); + } + + symbol.EndLinear(layout.QuietZone, layout.QuietZone, bearerBarThickness, runUnit, false); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Interleaved2Of5/Interleaved2Of5Layout.cs b/src/ImageSharp.Drawing/Barcodes/Interleaved2Of5/Interleaved2Of5Layout.cs new file mode 100644 index 00000000..e9dcc9ea --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Interleaved2Of5/Interleaved2Of5Layout.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The dimensions an application standard sets for an Interleaved 2 of 5 symbol: the X dimension, the bar height, the quiet zones, the bearer bar, the space +/// between the bars and the text, and the widths of the narrow and wide elements. builds the symbol at them. +/// +internal readonly struct Interleaved2Of5Layout +{ + /// + /// Gets the X dimension in millimetres. + /// + public double XDimension { get; init; } + + /// + /// Gets the bar height in modules when the options set none. + /// + public float NominalBarHeight { get; init; } + + /// + /// Gets the smallest bar height in modules that the application standard permits, or zero where it gives none. The code clamps the bar height to it. + /// + public float MinimumBarHeight { get; init; } + + /// + /// Gets the largest bar height in modules that the application standard permits, or where it gives none. The code + /// clamps the bar height to it. + /// + public float MaximumBarHeight { get; init; } + + /// + /// Gets the quiet zone before and after the bars in modules. + /// + public float QuietZone { get; init; } + + /// + /// Gets the thickness of the bearer bar in modules, or zero for none. + /// + public float BearerBarThickness { get; init; } + + /// + /// Gets the space between the bars, or the bearer bar below them, and the human readable interpretation in modules. + /// + public float TextClearance { get; init; } + + /// + /// Gets the width of a narrow element in run units. A run unit is the module divided by this width, so a narrow element is one module wide. + /// + public int NarrowElement { get; init; } + + /// + /// Gets the width of a wide element in run units. The wide to narrow ratio is this width divided by . + /// + public int WideElement { get; init; } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Interleaved2Of5/Interleaved2Of5Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Interleaved2Of5/Interleaved2Of5Symbology.cs new file mode 100644 index 00000000..19579c8d --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Interleaved2Of5/Interleaved2Of5Symbology.cs @@ -0,0 +1,118 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Interleaved 2 of 5 symbology, which AIM USS-I 2/5 defines and ISO/IEC 16390 succeeded. It carries the digits 0 to 9. Section 2.1 gives every character +/// "two wide elements and three narrow elements". Section 2.2.3: "the more significant digit is encoded in the bars and the less significant digit is encoded +/// in the spaces", so a symbol carries an even number of digits. A symbol carries a start pattern, the digit pairs and a stop pattern. +/// +/// Section 2.2.1 requires a leading zero for an odd number of digits: "then a leading zero must be added to produce an even number of digits". Section 2.5 +/// makes the check digit optional, and Appendix C recommends the modulo 10 calculation with alternate weights of 1 and 3 that section 7.9 of the GS1 General +/// Specifications also uses. Appendix D prints "all numeric characters in the code including leading zeroes", so the printed line shows the leading zero and, +/// by default, the check digit. +/// +/// +public sealed class Interleaved2Of5Symbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public Interleaved2Of5Symbology() + : this(CheckCharacterMode.None, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the symbol carries the modulo 10 check digit, and whether the encoder calculates it or validates it. + public Interleaved2Of5Symbology(CheckCharacterMode checkDigit) + : this(checkDigit, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the symbol carries the modulo 10 check digit, and whether the encoder calculates it or validates it. + /// Whether a check digit the symbol carries is part of the human readable interpretation. + public Interleaved2Of5Symbology(CheckCharacterMode checkDigit, bool printCheckDigit) + { + this.CheckDigit = checkDigit; + this.PrintCheckDigit = printCheckDigit; + } + + /// + /// Gets a value that specifies whether the symbol carries the modulo 10 check digit, and whether the encoder calculates it or validates it. + /// + public CheckCharacterMode CheckDigit { get; } + + /// + /// Gets a value indicating whether a check digit the symbol carries is part of the human readable interpretation. A symbol that carries none prints none + /// either way. Appendix D of AIM USS-I 2/5: "The presentation should include all numeric characters in the code including leading zeroes", so the + /// constructors that do not take this value print the check digit. + /// + public bool PrintCheckDigit { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Guard.MustBeGreaterThan(text.Length, 0, nameof(text)); + Guard.MustBeLessThanOrEqualTo(text.Length, Interleaved2Of5Encoder.MaximumLength, nameof(text)); + + // Validate mode reads the last digit as the check digit, so at least one data digit comes before it. + if (this.CheckDigit == CheckCharacterMode.Validate && text.Length < 2) + { + throw new ArgumentException("A supplied check digit follows at least one data digit.", nameof(text)); + } + + BarcodeSymbology.ValidateDigits(text); + + ReadOnlySpan data = this.CheckDigit == CheckCharacterMode.Validate ? text.Slice(0, text.Length - 1) : text; + + // The optional check digit of section 2.5 of AIM USS-I 2/5 is the modulo 10 calculation of Appendix C, which weights the digits 3 and 1 from the + // right, the same as the GS1 calculation. + int? check = this.CheckDigit == CheckCharacterMode.None ? null : Gs1Data.ComputeCheckDigit(data); + if (this.CheckDigit == CheckCharacterMode.Validate && text[^1] - '0' != check) + { + throw new ArgumentException($"Incorrect check digit: expected {check}, got {text[^1]}.", nameof(text)); + } + + // The digits the symbol carries: a leading zero when the count would otherwise be odd, the data and the check digit. Section 2.2.1 of AIM USS-I 2/5 + // requires the even count, and Appendix C notes that "a leading zero will be required if an even number of data characters are to be appended with one + // check character". The text is at most MaximumLength digits, so a short symbol works inline and a longer one rents its digits once. + int carried = data.Length + (check is null ? 0 : 1); + int padding = carried & 1; + int length = carried + padding; + InlineArray64 digitStorage = default; + PooledBuffer rented = default; + Span encoded = length <= Interleaved2Of5Encoder.InlineBufferLength + ? ((Span)digitStorage)[..length] + : (rented = new PooledBuffer(length)).Span; + + try + { + encoded[0] = '0'; + data.CopyTo(encoded[padding..]); + if (check is not null) + { + encoded[^1] = (char)('0' + check.Value); + } + + ReadOnlySpan readable = check is null || this.PrintCheckDigit ? encoded : encoded[..^1]; + + // Section 4.4 of ISO/IEC 16390: the X dimension "should be defined by the application specification", and section 4.7.1 leaves the "Range of X + // dimension" to the application specification, so the symbology gives no limit. + double xDimension = options.ResolveXDimension(0, DefaultXDimension, double.PositiveInfinity); + Interleaved2Of5Encoder.BuildSymbol(encoded, readable, options, xDimension, ref symbol); + } + finally + { + rented.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Interleaved2Of5/Itf14Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Interleaved2Of5/Itf14Symbology.cs new file mode 100644 index 00000000..abe2be3f --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Interleaved2Of5/Itf14Symbology.cs @@ -0,0 +1,164 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The ITF-14 symbology, which section 5.3 of the GS1 General Specifications defines as an Interleaved 2 of 5 symbol inside a bearer bar. Section 5.3.1 fixes +/// the data at 14 digits, and section 5.3.2.1.3 requires the check digit of section 7.9. The input is 13 digits, and the encoder calculates the check digit, or +/// 14 digits, and the encoder validates it. Section 4.14 rule 2.a permits spaces in the human readable interpretation and forbids them in the symbol, so the +/// input can carry spaces, which the printed line keeps and the symbol drops. +/// +/// Section 5.3.2.4 requires the bearer bar, which for plate printing "has a constant thickness of 4.83 millimetres (0.190 inch) and must completely surround +/// the symbol, including its Quiet Zones and butt directly against the top and bottom of the bars". The X dimension is the target of 0.495 millimetre of +/// Table 5-47, symbol specification table 2 for trade items scanned in general distribution. The symbol specification tables of section 5.12.3 give ITF-14 +/// X dimensions from 0.170 to 1.016 millimetres. An X dimension outside that range draws at the nearest limit. Section 5.3.2.2 gives the 10X quiet zones +/// and 1.02 millimetres between the bearer bar and the human readable characters. The symbol height table of section 5.12.3.2 gives a minimum bar height of +/// 31.75 millimetres and a wide to narrow ratio target of 2.5:1. A wide element rounds half up to a whole pixel, so the ratio is 2.67 at three pixels a +/// module, inside the range of 2.25:1 to 3.0:1 of section 5.3.2.2. The bar height, the bearer bar and the text space are in millimetres, so they hold at any +/// X dimension. The bearer bar is the 4.83 millimetres of plate printing, more than "a minimum of twice the width of a narrow bar" that section 5.3.2.4 +/// requires for printing methods without plates. A calculated check digit prints after the data, and after a space when the input carries spaces, as Figure +/// 5-32 shows. +/// +/// +public sealed class Itf14Symbology : BarcodeSymbology +{ + /// + /// The number of digits the symbol carries, the check digit included. Section 5.3.1: "Data string length encodable: fixed length at 14 digits." + /// + private const int Digits = 14; + + /// + /// The nominal X dimension in millimetres: the target X of 0.495 millimetres of Table 5-47, symbol specification table 2, which Table 2-37 gives to + /// "Fixed measure trade items scanned in general distribution". + /// + private const double XDimension = 0.495; + + /// + /// The bar height in millimetres when the caller sets none. Footnote (****) of the symbol height table in section 5.12.3.2: "For GS1-128 and ITF-14 + /// symbols the minimum symbol height for General distribution scanning is always 31.75 millimetres (1.250 inch)." + /// + private const double NominalBarHeight = 31.75; + + /// + /// The bearer bar thickness in millimetres: the 4.83 millimetres of section 5.3.2.4 for printing plates, more than the two modules that the same section + /// requires for printing methods without plates at every X dimension of Table 5-47. + /// + private const double BearerBarThickness = 4.83; + + /// + /// The smallest space between the bearer bar and the human readable interpretation in millimetres. Section 5.3.2.2: "A minimum space of 1.02 millimetre + /// (0.040 inch) between the bottom line of the bearer bar and the top of the human readable characters is required." + /// + private const double TextClearance = 1.02; + + /// + /// The narrow term of the wide to narrow ratio, / . + /// + private const int NarrowElement = 2; + + /// + /// The wide term of the wide to narrow ratio. The symbol height table of section 5.12.3.2: "The ITF-14 symbol's bar width ratio target is 2.5:1". Section + /// 5.3.2.2 gives the range "2.25:1 to 3.0:1". + /// + private const int WideElement = 5; + + /// + /// Initializes a new instance of the class. + /// + public Itf14Symbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Guard.MustBeGreaterThan(text.Length, 0, nameof(text)); + + // The interpretation repeats the input, and the input can group the digits with spaces, so the input is bounded at one grouping space after each digit. + Guard.MustBeLessThanOrEqualTo(text.Length, Digits * 2, nameof(text)); + + InlineArray14 digitStorage = default; + Span digits = digitStorage; + int count = 0; + bool hasSpaces = false; + SpanCodePointEnumerator codePoints = text.EnumerateCodePoints(); + while (codePoints.MoveNext()) + { + CodePoint current = codePoints.Current; + if (current.Value == ' ') + { + hasSpaces = true; + continue; + } + + if (!current.IsAsciiDigit()) + { + throw new ArgumentException($"ITF-14 carries only digits; got {current.ToDisplayString()}.", nameof(text)); + } + + if (count == Digits) + { + throw new ArgumentException($"ITF-14 carries 13 digits and a check digit; got more than {Digits}.", nameof(text)); + } + + digits[count++] = (char)current.Value; + } + + if (count != Digits - 1 && count != Digits) + { + throw new ArgumentException($"ITF-14 carries 13 digits and a check digit; got {count}.", nameof(text)); + } + + int check = Gs1Data.ComputeCheckDigit(digits[..(Digits - 1)]); + if (count == Digits && digits[Digits - 1] - '0' != check) + { + throw new ArgumentException($"Incorrect check digit: expected {check}, got {digits[Digits - 1]}.", nameof(text)); + } + + digits[Digits - 1] = (char)('0' + check); + + // The interpretation is the input as given, with the check digit after it when the input left it out, set off by a space when the input groups its + // digits with spaces. + int readableLength = text.Length + (count == Digits ? 0 : hasSpaces ? 2 : 1); + InlineArray32 readableStorage = default; + Span readable = ((Span)readableStorage)[..readableLength]; + text.CopyTo(readable); + if (count != Digits) + { + readable[^1] = digits[Digits - 1]; + if (hasSpaces) + { + readable[^2] = ' '; + } + } + + double xDimension = options.ResolveXDimension(Gs1Data.Itf14MinimumXDimension, XDimension, Gs1Data.Itf14MaximumXDimension); + + // The runs count pixels, so every element draws at a whole number of pixels. A wide element is WideElement / NarrowElement modules, rounded half up + // to a whole pixel: at an odd number of pixels a module the ratio of 2.5 becomes 3.0 at one pixel and 2.67 at three, and every wide element takes the + // same width. The notes of Tables 5-47 and 5-51 of the GS1 General Specifications: "There is no maximum for the height". The notes of Tables + // 5-50 and 5-51: "In no case SHALL the bar height be less than 5.08 millimetres". + int pixels = options.ModulePixels(xDimension); + Interleaved2Of5Layout layout = new() + { + XDimension = xDimension, + NominalBarHeight = (float)(NominalBarHeight / xDimension), + MinimumBarHeight = (float)(Gs1Data.MinimumTruncatedBarHeight / xDimension), + MaximumBarHeight = float.PositiveInfinity, + QuietZone = Interleaved2Of5Encoder.QuietZone, + BearerBarThickness = (float)(BearerBarThickness / xDimension), + TextClearance = MathF.Max(BarcodeTextPlacement.Clearance, (float)(TextClearance / xDimension)), + NarrowElement = pixels, + WideElement = ((WideElement * pixels) + (NarrowElement / 2)) / NarrowElement, + }; + + Interleaved2Of5Encoder.BuildSymbol(digits, readable, in layout, options, ref symbol); + + // Section 4.14 rule 2.a.iii: "The font size SHOULD be at least 2 mm (0.08 in) in height". + symbol.SetTextSize(Gs1Data.MinimumTextSize, 0F); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Interleaved2Of5/LeitcodeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Interleaved2Of5/LeitcodeSymbology.cs new file mode 100644 index 00000000..872aac37 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Interleaved2Of5/LeitcodeSymbology.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Deutsche Post Leitcode, the routing code of a mail item. An Interleaved 2 of 5 symbol carries it. The code is fourteen digits: the five digit postal +/// code, the three digit street code, the three digit house number, the two digit product code and the check digit. The default narrow module is 0.495 mm. +/// The default bars are 25 mm high. The narrow module is always 0.375 mm to 0.5 mm. The bars are always at least 25 mm high. The quiet zones are 5 mm on +/// each side, and at least the ten modules of Interleaved 2 of 5. +/// +/// The input is the thirteen data digits, or those digits and the check digit. This class always calculates the check digit. When the input carries one, this +/// class compares the two. +/// +/// +/// The printed line separates the fields with full stops and sets the check digit off with a space: "Die Klartextzeile enthält zwischen den einzelnen +/// Stellenbereichen jeweils einen Punkt, die Prüfziffer wird durch ein Leerzeichen etwas abgesetzt." The symbol carries neither. +/// +/// +public sealed class LeitcodeSymbology : BarcodeSymbology +{ + /// + /// The number of data digits, excluding the check digit. + /// + private const int Digits = 13; + + /// + /// Initializes a new instance of the class. + /// + public LeitcodeSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray14 encodedStorage = default; + Span encoded = encodedStorage; + DeutschePostData.Prepare(text, Digits, encoded); + + // The interpretation groups the fourteen digits as 12345.678.901.23 4. + InlineArray18 readableStorage = default; + Span readable = readableStorage; + encoded[..5].CopyTo(readable); + readable[5] = '.'; + encoded[5..8].CopyTo(readable[6..]); + readable[9] = '.'; + encoded[8..11].CopyTo(readable[10..]); + readable[13] = '.'; + encoded[11..13].CopyTo(readable[14..]); + readable[16] = ' '; + readable[17] = encoded[13]; + + DeutschePostData.BuildSymbol(encoded, readable, options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Iso3166.cs b/src/ImageSharp.Drawing/Barcodes/Iso3166.cs new file mode 100644 index 00000000..fd536222 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Iso3166.cs @@ -0,0 +1,77 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The country codes of ISO 3166-1 that barcode data carries. +/// +internal static class Iso3166 +{ + /// + /// The number of letters A to Z, the number of codes in each row of . + /// + private const int LetterCount = 26; + + /// + /// Gets the ISO 3166-1 alpha-2 country codes as a bit field, code XY at bit index 26 x (X - 'A') + (Y - 'A'), bit n at bit 7 - (n mod 8) of byte n div 8. A + /// bit past the end of the field is clear, so the trailing zero bytes are left out. The constant bytes compile into the assembly data, so reading them + /// allocates nothing. + /// + private static ReadOnlySpan Alpha2Codes => + [ + 0x1e, 0x9a, 0xfb, 0x77, 0xf7, 0xbd, 0xbb, 0x7b, 0xe4, 0xfc, 0x21, 0xa8, 0x01, 0x2b, 0x00, 0x70, + 0x00, 0x3a, 0x90, 0x0d, 0xf9, 0xdf, 0xa8, 0x00, 0xb1, 0x60, 0x18, 0x1e, 0xf0, 0x02, 0x02, 0xc0, + 0x00, 0xb8, 0xd4, 0x2f, 0x82, 0x81, 0xf2, 0xbf, 0x3f, 0xff, 0xeb, 0xa4, 0xd2, 0x10, 0x00, 0x80, + 0x02, 0x3c, 0xf1, 0xca, 0x80, 0x00, 0x00, 0x02, 0x00, 0x8a, 0x8f, 0xbf, 0xe7, 0x5c, 0xdd, 0xf9, + 0x59, 0x82, 0x08, 0x20, 0xea, 0xa1, 0x02, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, + 0x10, 0x20, 0x02, 0x00, 0x80, + ]; + + /// + /// Gets the ISO 3166-1 numeric country codes 000 to 999 as a bit field, code n at bit 7 - (n mod 8) of byte n div 8. The constant bytes compile into the + /// assembly data, so reading them allocates nothing. + /// + private static ReadOnlySpan NumericCodes => + [ + 0x08, 0xa8, 0x88, 0x89, 0x88, 0x88, 0xb8, 0x88, 0x8a, 0xa8, 0x0a, 0x28, 0x88, 0x88, 0x88, 0x88, + 0x08, 0x88, 0x88, 0x8a, 0x22, 0x23, 0x28, 0x89, 0x88, 0x18, 0x8a, 0x22, 0x21, 0xe3, 0x22, 0xa2, + 0x2a, 0x2a, 0x18, 0x00, 0x88, 0x88, 0x88, 0x88, 0x88, 0x8a, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x82, 0x88, 0xa2, 0x62, 0x2a, 0x22, 0xa2, 0x22, 0x22, 0x22, 0x22, 0x88, 0x08, 0xb8, 0x88, + 0x88, 0x88, 0x97, 0x08, 0x08, 0x22, 0x22, 0x22, 0x2d, 0xe1, 0x02, 0x88, 0x88, 0x88, 0xa2, 0x22, + 0x32, 0x0a, 0x1b, 0x22, 0x22, 0x22, 0xa2, 0x03, 0xe2, 0x08, 0x08, 0xc8, 0x08, 0x88, 0x88, 0xa8, + 0x88, 0x88, 0x88, 0x9a, 0x89, 0x00, 0x20, 0x21, 0xe0, 0x80, 0x22, 0x2a, 0x00, 0x08, 0x21, 0x02, + ]; + + /// + /// Returns whether the characters are an ISO 3166-1 alpha-2 country code: two capital letters that name an assigned code. + /// + /// The characters. + /// when they are a country code. + public static bool IsAlpha2Code(ReadOnlySpan code) + { + if (code.Length != 2 || !char.IsAsciiLetterUpper(code[0]) || !char.IsAsciiLetterUpper(code[1])) + { + return false; + } + + return IsSet(Alpha2Codes, (LetterCount * (code[0] - 'A')) + (code[1] - 'A')); + } + + /// + /// Returns whether a number is an ISO 3166-1 numeric country code. + /// + /// The number, 0 to 999. + /// when it is a country code. + public static bool IsNumericCode(int code) => IsSet(NumericCodes, code); + + /// + /// Returns whether a bit is set in a bit field whose bytes hold their bits from the most significant. A bit past the end of the field is clear, so trailing + /// zero bytes are left out of the fields. + /// + /// The bit field. + /// The bit index. + /// when the bit is set. + public static bool IsSet(ReadOnlySpan field, int bit) + => (uint)(bit >> 3) < (uint)field.Length && (field[bit >> 3] & (0x80 >> (bit & 7))) != 0; +} diff --git a/src/ImageSharp.Drawing/Barcodes/Iso7064.cs b/src/ImageSharp.Drawing/Barcodes/Iso7064.cs new file mode 100644 index 00000000..eecd4a80 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Iso7064.cs @@ -0,0 +1,52 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The MOD 97-10 check character system of ISO/IEC 7064, which the International Bank Account Number of ISO 13616 and the Creditor Reference of ISO 11649 +/// share: the first four characters, a two-letter prefix and two check digits, move to the end, each letter reads as the two digits 10 to 35, and the number +/// that gives must leave 1 when divided by 97. +/// +internal static class Iso7064 +{ + /// + /// The modulus of the MOD 97-10 system. + /// + private const int Modulus = 97; + + /// + /// The number of leading characters, the prefix and the check digits, that move to the end before the remainder is taken. + /// + private const int MovedCharacters = 4; + + /// + /// Returns the remainder modulo 97 of the characters read with the first four moved to the end and each letter as the two digits 10 to 35, a small letter + /// as its capital letter. + /// + /// The characters, at least four. + /// The remainder, 0 to 96, or -1 when a character is not a digit or a letter of A to Z. + public static int Mod97(ReadOnlySpan text) + { + // The remainder is kept below 97 after each character, so the long number is never held. + int remainder = 0; + for (int i = 0; i < text.Length; i++) + { + char c = text[(i + MovedCharacters) % text.Length]; + if (char.IsAsciiDigit(c)) + { + remainder = ((remainder * 10) + (c - '0')) % Modulus; + } + else if (char.IsAsciiLetter(c)) + { + remainder = ((remainder * 100) + (char.ToUpperInvariant(c) - 'A' + 10)) % Modulus; + } + else + { + return -1; + } + } + + return remainder; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/MacroMessage.cs b/src/ImageSharp.Drawing/Barcodes/MacroMessage.cs new file mode 100644 index 00000000..034991f0 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/MacroMessage.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The message formats 05 and 06, which Data Matrix and PDF417 compress into one Macro character. A message in either format starts with the header +/// [)>RS05GS or [)>RS06GS and ends with the trailer RS EOT. +/// +internal static class MacroMessage +{ + /// + /// The length of the header, [)>RS05GS or [)>RS06GS. + /// + public const int HeaderLength = 7; + + /// + /// The length of the trailer, RS EOT. + /// + public const int TrailerLength = 2; + + /// + /// Returns the format of a message. + /// + /// The message. + /// 5 or 6 when the text has the header and the trailer of that format, or 0. + public static int Format(ReadOnlySpan text) + => text.Length >= HeaderLength + TrailerLength + && text.StartsWith("[)>\u001E0") + && text[5] is '5' or '6' + && text[6] == '\u001D' + && text.EndsWith("\u001E\u0004") + ? text[5] - '0' + : 0; +} diff --git a/src/ImageSharp.Drawing/Barcodes/MatrixEmitter.cs b/src/ImageSharp.Drawing/Barcodes/MatrixEmitter.cs new file mode 100644 index 00000000..391a4d56 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/MatrixEmitter.cs @@ -0,0 +1,728 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.InteropServices; +using SixLabors.Fonts; +using SixLabors.ImageSharp.Drawing.Helpers; +using SixLabors.ImageSharp.Drawing.Processing; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Converts a matrix, dotted or colour into canvas commands. In a matrix symbol each run of adjacent dark modules in a row becomes +/// one rectangle, and the dark modules render through a single fill of one . In a dotted symbol each dark module becomes one circle, +/// and the dots render through a single fill of one . In a colour symbol each run of one colour in a row becomes one rectangle, and +/// each colour renders through one fill of one . Each text placement draws through one text command. +/// +internal static class MatrixEmitter +{ + /// + /// The number of values in for each dark rectangle of an overlay graphic: its left, top, right and bottom edges. + /// + private const int OverlayRectangleEdges = 4; + + /// + /// Measures the symbol without drawing anything. + /// + /// The encoded matrix symbol. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The measured layout of the symbol. + public static BarcodeMetrics Measure(ref BarcodeSymbol symbol, BarcodeOptions options, PointF origin) + { + using PooledBuffer lineBuffer = BarcodeEmitter.CreateLineBuffer(ref symbol, options); + + // A TextBlock is a class, so the span holds object references and cannot come from the byte pool. A matrix symbol has at most one text placement, + // also when its text has line breaks, so one stack slot holds its block. + InlineArray1 blockStorage = default; + Span blocks = ((Span)blockStorage)[..lineBuffer.Span.Length]; + Shape(ref symbol, options, lineBuffer.Span, blocks); + return Measure(ref symbol, options, origin, lineBuffer.Span, out _); + } + + /// + /// Draws the symbol onto the canvas. + /// + /// The canvas to render onto. + /// The encoded matrix symbol. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The brush that fills the renderable bounds before the modules draw, or for none. + /// The measured layout of the symbol, the same as returns. + public static BarcodeMetrics Draw(DrawingCanvas canvas, ref BarcodeSymbol symbol, BarcodeOptions options, PointF origin, Brush? background) + { + using PooledBuffer lineBuffer = BarcodeEmitter.CreateLineBuffer(ref symbol, options); + + // A TextBlock is a class, so the span holds object references and cannot come from the byte pool. A matrix symbol has at most one text placement, + // also when its text has line breaks, so one stack slot holds its block. + InlineArray1 blockStorage = default; + Span blocks = ((Span)blockStorage)[..lineBuffer.Span.Length]; + Shape(ref symbol, options, lineBuffer.Span, blocks); + return Draw(canvas, ref symbol, options, origin, lineBuffer.Span, blocks, background); + } + + /// + /// Shapes the text of the symbol one time: one text block and one measured line for its text placement. The layout reads the line and the draw renders + /// the block, so neither shapes the text again. + /// + /// The encoded matrix symbol. + /// The sizing and painting options. + /// The measured line to write, or empty when the options carry no font. + /// The text block to write, one for each line. + public static void Shape(ref BarcodeSymbol symbol, BarcodeOptions options, Span lines, Span blocks) + { + Font? font = options.Font; + if (font is null || lines.IsEmpty) + { + return; + } + + // A matrix symbol has at most one text placement, also when its text has line breaks. The block keeps the options it was prepared with, its font and + // alignment included. + BarcodeTextPlacement placement = symbol.Text[0]; + Font placementFont = placement.FontScale == 1F ? font : new Font(font, font.Size * placement.FontScale); + RichTextOptions textOptions = BarcodeTextOptionsFactory.Create(placementFont, options.Dpi, symbol.TextWeight); + textOptions.TextAlignment = placement.Alignment switch + { + BarcodeTextAlignment.Left => TextAlignment.Start, + BarcodeTextAlignment.Right => TextAlignment.End, + _ => TextAlignment.Center, + }; + + TextBlock block = new(symbol.TextOf(in placement), textOptions); + blocks[0] = block; + TextMetrics metrics = block.Measure(-1); + lines[0] = new MeasuredLine(metrics.Advance, metrics.Bounds, metrics.RenderableBounds, 0, 0); + } + + /// + /// Measures the symbol from its shaped text without drawing anything. + /// + /// The encoded matrix symbol. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The lines that wrote. The layout writes the origin of each line. + /// The top left corner of the first module of the symbol, in pixels, before it rounds onto the pixel grid. + /// The measured layout of the symbol. + public static BarcodeMetrics Measure(ref BarcodeSymbol symbol, BarcodeOptions options, PointF origin, Span lines, out PointF symbolOrigin) + { + MatrixLayout layout = new(ref symbol, options, origin, lines); + symbolOrigin = layout.SymbolOrigin; + return layout.Metrics; + } + + /// + /// Draws the symbol from its shaped text onto the canvas. + /// + /// The canvas to render onto. + /// The encoded matrix symbol. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The lines that wrote. The layout writes the origin of each line. + /// The blocks that wrote, one for each line. + /// The brush that fills the renderable bounds before the modules draw, or for none. + /// The measured layout of the symbol. + public static BarcodeMetrics Draw( + DrawingCanvas canvas, + ref BarcodeSymbol symbol, + BarcodeOptions options, + PointF origin, + Span lines, + ReadOnlySpan blocks, + Brush? background) + { + MatrixLayout layout = new(ref symbol, options, origin, lines); + BarcodeMetrics metrics = layout.Metrics; + if (background is not null) + { + RectangleF renderable = metrics.RenderableBounds; + canvas.Fill(background, new RectanglePolygon(renderable.X, renderable.Y, renderable.Width, renderable.Height)); + } + + // A dotted symbol draws each dark module as a dot, a colour symbol draws runs of each colour, and every other matrix symbol draws runs of dark modules. + ModuleGrid grid = layout.Grid; + switch (symbol.Kind) + { + case BarcodeSymbolKind.Dotted: + FillDots(canvas, ref symbol, options.BarBrush, in grid); + break; + case BarcodeSymbolKind.Colored: + FillColorRuns(canvas, ref symbol, options.BarBrush, in grid); + break; + default: + FillRuns(canvas, ref symbol, options.BarBrush, in grid); + break; + } + + layout.DrawText(canvas, blocks, options.BarBrush); + return metrics; + } + + /// + /// Fills the dark modules of a matrix symbol: each run of adjacent dark modules in a row is one rectangle, and the dark parts of an overlay graphic join + /// them, all in one fill of one . + /// + /// The canvas to render onto. + /// The encoded matrix symbol. + /// The brush of the dark modules. + /// The position and the module width of the modules on the pixel grid. + private static void FillRuns(DrawingCanvas canvas, ref BarcodeSymbol symbol, Brush brush, in ModuleGrid grid) + { + int columns = symbol.Columns; + int rows = symbol.MatrixRows; + float moduleWidth = grid.ModuleWidth; + + // A run is a maximal sequence of dark modules in a row, so its left neighbour is light or the edge. The first pass counts the runs to size the + // rectangle array exactly. A bool is the byte 0 or 1, so a run start is a byte that is 1 where the byte before it is 0: current AND NOT previous is 1 + // exactly at a start. The scan runs over the whole grid as one row, a vector of bytes at a time, and adds the lanes of each result; a lane sum is at + // most the lane count, which a byte holds. The scalar loop finishes the bytes that do not fill a vector, with the same test. + ReadOnlySpan modules = symbol.Modules; + ReadOnlySpan bytes = MemoryMarshal.Cast(modules); + int runCount = bytes[0]; + int i = 1; + if (Vector.IsHardwareAccelerated) + { + int lanes = Vector.Count; + for (; i + lanes <= bytes.Length; i += lanes) + { + Vector starts = Vector.AndNot(new Vector(bytes[i..]), new Vector(bytes[(i - 1)..])); + runCount += Vector.Sum(starts); + } + } + + for (; i < bytes.Length; i++) + { + runCount += bytes[i] & ~bytes[i - 1]; + } + + // The flat scan reads the first module of a row as following the last module of the row above, so a row that starts dark below a row that ends dark has + // one start the scan did not count. + for (int r = 1; r < rows; r++) + { + int rowStart = r * columns; + runCount += bytes[rowStart] & bytes[rowStart - 1]; + } + + // An overlay graphic covers a square at the centre of the symbol whose edges need not fall on module edges. A run under the square splits into its + // parts outside it, up to four, and the dark parts of the graphic join the runs, so one fill draws both. The square and the graphic edges round onto + // the pixel grid as the module edges do. This pass counts the parts, so the array is sized exactly. + float overlaySize = symbol.OverlaySize; + ReadOnlySpan edges = symbol.OverlayEdges; + double overlayLeft = grid.Left + ((columns - overlaySize) / 2D * moduleWidth); + double overlayTop = grid.Top + ((rows - overlaySize) / 2D * moduleWidth); + double overlaySide = (double)overlaySize * moduleWidth; + RectangleF hole = RectangleF.Empty; + InlineArray4 pieceStorage = default; + Span pieces = pieceStorage; + if (overlaySize > 0) + { + hole = RectangleF.FromLTRB( + (float)Math.Round(overlayLeft), + (float)Math.Round(overlayTop), + (float)Math.Round(overlayLeft + overlaySide), + (float)Math.Round(overlayTop + overlaySide)); + + runCount += edges.Length / OverlayRectangleEdges; + for (int r = 0; r < rows; r++) + { + float rowTop = grid.RowEdge(r); + float rowBottom = grid.RowEdge(r + 1); + if (rowBottom <= hole.Top || rowTop >= hole.Bottom) + { + continue; + } + + ReadOnlySpan row = modules.Slice(r * columns, columns); + int end = 0; + while (NextRun(row, ref end, out int start)) + { + RectangleF run = RectangleF.FromLTRB(grid.ColumnEdge(start), rowTop, grid.ColumnEdge(end), rowBottom); + runCount += Subtract(run, hole, pieces) - 1; + } + } + } + + // The path retains the rectangles until the canvas flushes, so it owns this array. + RectangleF[] runs = new RectangleF[runCount]; + float pathLeft = float.MaxValue; + float pathTop = float.MaxValue; + float pathRight = float.MinValue; + float pathBottom = float.MinValue; + int index = 0; + for (int r = 0; r < rows; r++) + { + ReadOnlySpan row = modules.Slice(r * columns, columns); + float rowTop = grid.RowEdge(r); + float rowBottom = grid.RowEdge(r + 1); + int end = 0; + while (NextRun(row, ref end, out int start)) + { + float runLeft = grid.ColumnEdge(start); + float runRight = grid.ColumnEdge(end); + RectangleF run = RectangleF.FromLTRB(runLeft, rowTop, runRight, rowBottom); + pathLeft = MathF.Min(pathLeft, runLeft); + pathTop = MathF.Min(pathTop, rowTop); + pathRight = MathF.Max(pathRight, runRight); + pathBottom = MathF.Max(pathBottom, rowBottom); + if (overlaySize == 0) + { + runs[index++] = run; + continue; + } + + // The parts lie within the run, so the run edges still bound the path. + int count = Subtract(run, hole, pieces); + pieces[..count].CopyTo(runs.AsSpan(index)); + index += count; + } + } + + // The graphic scales from its own units to the side of the square. Its dark parts lie within the square, which lies within the symbol, so the module + // edges still bound the path. + if (overlaySize > 0) + { + double scale = overlaySide / symbol.OverlayUnit; + for (int e = 0; e < edges.Length; e += OverlayRectangleEdges) + { + runs[index++] = RectangleF.FromLTRB( + (float)Math.Round(overlayLeft + (edges[e] * scale)), + (float)Math.Round(overlayTop + (edges[e + 1] * scale)), + (float)Math.Round(overlayLeft + (edges[e + 2] * scale)), + (float)Math.Round(overlayTop + (edges[e + 3] * scale))); + } + } + + canvas.Fill(brush, new BarcodePath(runs, RectangleF.FromLTRB(pathLeft, pathTop, pathRight, pathBottom))); + } + + /// + /// Fills the dark modules of a dotted symbol: each dark module is a circle one module across, centred in its cell, all in one fill of one + /// . Dark modules stand only where the column and the row sum to an even number, so the nearest two are diagonal neighbours, the + /// square root of 2 modules apart, and no two circles touch. + /// + /// The canvas to render onto. + /// The encoded dotted symbol. + /// The brush of the dots. + /// The position and the module width of the cells on the pixel grid. + private static void FillDots(DrawingCanvas canvas, ref BarcodeSymbol symbol, Brush brush, in ModuleGrid grid) + { + int columns = symbol.Columns; + int rows = symbol.MatrixRows; + double symbolLeft = grid.Left; + double symbolTop = grid.Top; + float moduleWidth = grid.ModuleWidth; + ReadOnlySpan modules = symbol.Modules; + + // The path retains the centres until the canvas flushes, so it owns this array, sized by a count of the dark modules. + PointF[] centres = new PointF[modules.Count(true)]; + int index = 0; + for (int r = 0; r < rows; r++) + { + ReadOnlySpan row = modules.Slice(r * columns, columns); + float centreY = (float)(symbolTop + ((r + 0.5) * moduleWidth)); + for (int c = 0; c < columns; c++) + { + if (row[c]) + { + centres[index++] = new PointF((float)(symbolLeft + ((c + 0.5) * moduleWidth)), centreY); + } + } + } + + // The cells hold every circle, so they bound the path. + RectangleF cells = new((float)symbolLeft, (float)symbolTop, columns * moduleWidth, rows * moduleWidth); + canvas.Fill(brush, new BarcodePath(centres, moduleWidth / 2, cells)); + } + + /// + /// Fills the modules of a colour symbol: each run of adjacent modules of one colour in a row is one rectangle, and each colour is one fill of one + /// . Dark modules take the bar brush and light modules stay the background, as in every other symbol; the four data colours are + /// fixed, because a reader tells them apart by hue. + /// + /// The canvas to render onto. + /// The encoded colour symbol. + /// The brush of the dark modules. + /// The position and the module width of the modules on the pixel grid. + private static void FillColorRuns(DrawingCanvas canvas, ref BarcodeSymbol symbol, Brush barBrush, in ModuleGrid grid) + { + int columns = symbol.Columns; + int rows = symbol.MatrixRows; + ReadOnlySpan modules = MemoryMarshal.Cast(symbol.ModuleColors); + + // The first pass counts the runs of every colour, so each colour's rectangle array is sized exactly; the paths retain the arrays until the canvas + // flushes. A run starts at the first module that is not light and ends where its colour changes. Light modules are the background. + const int colorCount = (int)BarcodeModuleColor.Green + 1; + InlineArray6 countStorage = default; + Span counts = countStorage; + for (int r = 0; r < rows; r++) + { + ReadOnlySpan row = modules.Slice(r * columns, columns); + int end = 0; + while (NextColorRun(row, ref end, out int start)) + { + counts[row[start]]++; + } + } + + InlineArray6 runStorage = default; + Span runs = runStorage; + InlineArray6 boundsStorage = default; + Span bounds = boundsStorage; + for (int c = (int)BarcodeModuleColor.Dark; c < colorCount; c++) + { + runs[c] = new RectangleF[counts[c]]; + counts[c] = 0; + } + + // The second pass writes each run to the array of its colour, and grows the bounds of that colour's path. + for (int r = 0; r < rows; r++) + { + ReadOnlySpan row = modules.Slice(r * columns, columns); + float rowTop = grid.RowEdge(r); + float rowBottom = grid.RowEdge(r + 1); + int end = 0; + while (NextColorRun(row, ref end, out int start)) + { + int c = row[start]; + RectangleF run = RectangleF.FromLTRB(grid.ColumnEdge(start), rowTop, grid.ColumnEdge(end), rowBottom); + bounds[c] = counts[c] == 0 ? run : RectangleF.Union(bounds[c], run); + runs[c][counts[c]++] = run; + } + } + + for (int c = (int)BarcodeModuleColor.Dark; c < colorCount; c++) + { + if (counts[c] == 0) + { + continue; + } + + Brush brush = (BarcodeModuleColor)c switch + { + BarcodeModuleColor.Dark => barBrush, + BarcodeModuleColor.Cyan => ModuleBrushes.Cyan, + BarcodeModuleColor.Magenta => ModuleBrushes.Magenta, + BarcodeModuleColor.Yellow => ModuleBrushes.Yellow, + _ => ModuleBrushes.Green, + }; + + canvas.Fill(brush, new BarcodePath(runs[c], bounds[c])); + } + } + + /// + /// Finds the next run of one colour in a row of a colour symbol: a maximal sequence of modules of one colour other than light. + /// + /// The module colours of the row. + /// On entry, the column the search starts at. On exit, the column after the run, or the row length when no run remains. + /// The first column of the run. + /// when a run was found. + private static bool NextColorRun(ReadOnlySpan row, ref int end, out int start) + { + int offset = row[end..].IndexOfAnyExcept((byte)BarcodeModuleColor.Light); + if (offset < 0) + { + start = row.Length; + end = row.Length; + return false; + } + + start = end + offset; + int length = row[start..].IndexOfAnyExcept(row[start]); + end = length < 0 ? row.Length : start + length; + return true; + } + + /// + /// Finds the next run of dark modules in a row, a maximal sequence of dark modules. + /// + /// The modules of the row. + /// On entry, the column the search starts at. On exit, the column after the run, or the row length when no run remains. + /// The first column of the run. + /// when a run was found. + private static bool NextRun(ReadOnlySpan row, ref int end, out int start) + { + int offset = row[end..].IndexOf(true); + if (offset < 0) + { + start = row.Length; + end = row.Length; + return false; + } + + start = end + offset; + int length = row[start..].IndexOf(false); + end = length < 0 ? row.Length : start + length; + return true; + } + + /// + /// Writes the parts of a run outside a hole: the part above the hole, the part below it, and the parts to its left and right within its height. + /// + /// The run. + /// The hole. + /// The buffer that receives the parts, at least four long. + /// The number of parts, 0 to 4. A run clear of the hole is its only part. + private static int Subtract(RectangleF run, RectangleF hole, Span pieces) + { + if (run.Right <= hole.Left || run.Left >= hole.Right || run.Bottom <= hole.Top || run.Top >= hole.Bottom) + { + pieces[0] = run; + return 1; + } + + int count = 0; + if (run.Top < hole.Top) + { + pieces[count++] = RectangleF.FromLTRB(run.Left, run.Top, run.Right, hole.Top); + } + + if (run.Bottom > hole.Bottom) + { + pieces[count++] = RectangleF.FromLTRB(run.Left, hole.Bottom, run.Right, run.Bottom); + } + + float top = MathF.Max(run.Top, hole.Top); + float bottom = MathF.Min(run.Bottom, hole.Bottom); + if (run.Left < hole.Left) + { + pieces[count++] = RectangleF.FromLTRB(run.Left, top, hole.Left, bottom); + } + + if (run.Right > hole.Right) + { + pieces[count++] = RectangleF.FromLTRB(hole.Right, top, run.Right, bottom); + } + + return count; + } + + /// + /// The layout of a matrix symbol: where its modules and each text line stand, from one origin. The measure and the draw read one layout. + /// + private ref struct MatrixLayout + { + /// + /// The measured lines, one for each text placement, or none when the options carry no font. + /// + private readonly Span lines; + + /// + /// Initializes a new instance of the struct. + /// + /// The encoded matrix symbol. + /// The sizing and painting options. + /// The top left corner of the barcode, in pixels. + /// The shaped lines, one for each text placement, or empty when the options carry no font. The layout writes their origins. + public MatrixLayout(ref BarcodeSymbol symbol, BarcodeOptions options, PointF origin, Span lines) + { + this.lines = lines; + float moduleWidth = options.ModuleWidth(symbol.XDimension); + + // Most matrix symbols have the same quiet zone on all four sides. Code One versions S and T have none above. + bool quiet = options.IncludeQuietZones; + int leadingZone = quiet ? (int)symbol.LeadingQuietZone : 0; + int trailingZone = quiet ? (int)symbol.TrailingQuietZone : 0; + int topZone = quiet ? (int)symbol.TopQuietZone : 0; + int bottomZone = quiet ? (int)symbol.BottomQuietZone : 0; + int columns = symbol.Columns; + int rows = symbol.MatrixRows; + + ReadOnlySpan text = symbol.Text; + + // The layout box: the barcode and the line box of each line, in exact positions relative to the top left corner of the barcode. Nothing in this + // box rounds, as nothing in the layout box of text rounds. The ink of each line starts on its text edge. + float leadingOffset = leadingZone * moduleWidth; + float topOffset = topZone * moduleWidth; + float layoutLeft = 0; + float layoutTop = 0; + float layoutRight = (columns + leadingZone + trailingZone) * moduleWidth; + float layoutBottom = (rows + topZone + bottomZone) * moduleWidth; + for (int t = 0; t < lines.Length; t++) + { + BarcodeTextPlacement placement = text[t]; + MeasuredLine line = lines[t]; + float cellLeft = leadingOffset + (placement.Left * moduleWidth); + float cellRight = leadingOffset + (placement.Right * moduleWidth); + float x = placement.Origin(cellLeft, cellRight, line.Bounds, line.RenderableBounds.Width); + float y = topOffset + (placement.TextEdge * moduleWidth) - line.Bounds.Top; + layoutLeft = MathF.Min(layoutLeft, x + line.Advance.Left); + layoutTop = MathF.Min(layoutTop, y + line.Advance.Top); + layoutRight = MathF.Max(layoutRight, x + line.Advance.Right); + layoutBottom = MathF.Max(layoutBottom, y + line.Advance.Bottom); + } + + // The origin is the top left corner of the layout box, as it is for text. A line wider than the symbol moves the symbol right. + PointF symbolOrigin = new(origin.X - layoutLeft, origin.Y - layoutTop); + RectangleF advance = new(origin.X, origin.Y, layoutRight - layoutLeft, layoutBottom - layoutTop); + + // Each edge rounds from its exact position onto the device pixel grid, so the modules stay crisp and rounding cannot drift across the symbol. The + // positions are products of a whole module count and the module width, calculated in double from the corner of the symbol, and the edge two + // neighbours share rounds to one pixel for both. The corner itself starts on a whole pixel. When a module is not a whole number of pixels, the + // modules alternate between two widths, and a corner between pixels makes that pattern depend on the width of the quiet zone. The two outer rings + // of a finder pattern can then round to different widths. + this.SymbolOrigin = new PointF(symbolOrigin.X + leadingOffset, symbolOrigin.Y + topOffset); + double symbolLeft = Math.Round(this.SymbolOrigin.X); + double symbolTop = Math.Round(this.SymbolOrigin.Y); + this.Grid = new ModuleGrid(symbolLeft, symbolTop, moduleWidth); + + // The bounds are the exact ink. A run of modules draws between two module edges, which round onto the pixel grid. A dot fills its cell exactly, so + // its edges are the cell edges. The outermost dark modules set the ink, and the symbol always has at least one. + ReadOnlySpan marks = symbol.Kind == BarcodeSymbolKind.Colored + ? MemoryMarshal.Cast(symbol.ModuleColors) + : MemoryMarshal.Cast(symbol.Modules); + + int firstRow = -1; + int lastRow = -1; + int firstColumn = columns; + int lastColumn = -1; + for (int r = 0; r < rows; r++) + { + // A light module is the byte 0 in both module forms: false, and BarcodeModuleColor.Light. The searches are vectorized. + ReadOnlySpan row = marks.Slice(r * columns, columns); + int first = row.IndexOfAnyExcept((byte)0); + if (first < 0) + { + continue; + } + + firstRow = firstRow < 0 ? r : firstRow; + lastRow = r; + firstColumn = Math.Min(firstColumn, first); + lastColumn = Math.Max(lastColumn, row.LastIndexOfAnyExcept((byte)0)); + } + + RectangleF bounds = symbol.Kind == BarcodeSymbolKind.Dotted + ? RectangleF.FromLTRB( + (float)(symbolLeft + ((double)firstColumn * moduleWidth)), + (float)(symbolTop + ((double)firstRow * moduleWidth)), + (float)(symbolLeft + ((double)(lastColumn + 1) * moduleWidth)), + (float)(symbolTop + ((double)(lastRow + 1) * moduleWidth))) + : RectangleF.FromLTRB( + this.Grid.ColumnEdge(firstColumn), + this.Grid.RowEdge(firstRow), + this.Grid.ColumnEdge(lastColumn + 1), + this.Grid.RowEdge(lastRow + 1)); + + // The ink of the first line starts on the text edge, which rounds onto the pixel grid as the module edges do. + for (int t = 0; t < lines.Length; t++) + { + BarcodeTextPlacement placement = text[t]; + ref MeasuredLine line = ref lines[t]; + float x = placement.GridOrigin(line.Bounds, line.RenderableBounds.Width, (float)symbolLeft, moduleWidth); + float y = (float)Math.Round(symbolTop + ((double)placement.TextEdge * moduleWidth)) - line.Bounds.Top; + line.Origin = new Vector2(x, y); + bounds = RectangleF.Union(bounds, new RectangleF(x + line.Bounds.X, y + line.Bounds.Y, line.Bounds.Width, line.Bounds.Height)); + } + + // As in text measuring, the advance is relative to the origin, and the bounds and the renderable bounds are absolute. + this.Metrics = new BarcodeMetrics(new RectangleF(0, 0, advance.Width, advance.Height), bounds, RectangleF.Union(advance, bounds)); + } + + /// + /// Gets the top left corner of the first module of the symbol, in pixels, before it rounds onto the pixel grid. + /// + public readonly PointF SymbolOrigin { get; } + + /// + /// Gets the module grid of the symbol on the page. + /// + public readonly ModuleGrid Grid { get; } + + /// + /// Gets the metrics of the layout. + /// + public readonly BarcodeMetrics Metrics { get; } + + /// + /// Draws each text line where the layout put it. Each line renders its shaped block, so it is not shaped again. + /// + /// The canvas to render onto. + /// The shaped blocks, one for each line. + /// The brush of the text. + public readonly void DrawText(DrawingCanvas canvas, ReadOnlySpan blocks, Brush brush) + { + for (int t = 0; t < this.lines.Length; t++) + { + canvas.DrawText(blocks[t], this.lines[t].Origin, -1, brush, null); + } + } + } + + /// + /// The module grid of a matrix symbol on the page. Each edge rounds from its exact position onto the device pixel grid. The position is a product of a + /// whole module count and the module width, calculated in double from the corner of the symbol, so the edge two neighbours share rounds to one pixel for + /// both and rounding cannot drift across the symbol. + /// + private readonly struct ModuleGrid + { + /// + /// Initializes a new instance of the struct. + /// + /// The left edge of the modules, on the pixel grid. + /// The top edge of the modules, on the pixel grid. + /// The width of a module in pixels. + public ModuleGrid(double left, double top, float moduleWidth) + { + this.Left = left; + this.Top = top; + this.ModuleWidth = moduleWidth; + } + + /// + /// Gets the left edge of the modules, on the pixel grid. + /// + public double Left { get; } + + /// + /// Gets the top edge of the modules, on the pixel grid. + /// + public double Top { get; } + + /// + /// Gets the width of a module in pixels. + /// + public float ModuleWidth { get; } + + /// + /// Returns the x of the left edge of a column, rounded onto the pixel grid. + /// + /// The column, 0 to the column count. + /// The edge in pixels. + public float ColumnEdge(int column) => (float)Math.Round(this.Left + ((double)column * this.ModuleWidth)); + + /// + /// Returns the y of the top edge of a row, rounded onto the pixel grid. + /// + /// The row, 0 to the row count. + /// The edge in pixels. + public float RowEdge(int row) => (float)Math.Round(this.Top + ((double)row * this.ModuleWidth)); + } + + /// + /// The brushes of the colored modules. Brushes are immutable, so every draw shares them. A nested class creates them on the first colored symbol only. + /// + private static class ModuleBrushes + { + /// + /// The brush of a cyan module. + /// + public static readonly SolidBrush Cyan = Brushes.Solid(Color.Cyan); + + /// + /// The brush of a magenta module. + /// + public static readonly SolidBrush Magenta = Brushes.Solid(Color.Magenta); + + /// + /// The brush of a yellow module. + /// + public static readonly SolidBrush Yellow = Brushes.Solid(Color.Yellow); + + /// + /// The brush of a green module. + /// + public static readonly SolidBrush Green = Brushes.Solid(Color.Lime); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/MaxiCode/MaxiCodeEncoder.cs b/src/ImageSharp.Drawing/Barcodes/MaxiCode/MaxiCodeEncoder.cs new file mode 100644 index 00000000..b4aef2fe --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/MaxiCode/MaxiCodeEncoder.cs @@ -0,0 +1,1065 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodes MaxiCode symbols: 144 six-bit codewords in 33 rows of 30 hexagonal modules around a circular finder. The primary message of 10 data and 10 check +/// codewords carries the mode and, in modes 2 and 3, the structured carrier message of postal code, country code and class of service. The secondary message +/// carries the data in two interleaved Reed-Solomon blocks. The code set search finds the shortest sequence of code set latches, shifts and characters. +/// +internal static class MaxiCodeEncoder +{ + /// + /// The longest data the secondary message can hold: 93 codewords of digits in numeric shift, 9 digits in 6 codewords. + /// + private const int MaximumDataLength = 138; + + /// + /// The number of module positions in a row. + /// + private const int Columns = 30; + + /// + /// The number of rows. + /// + private const int Rows = 33; + + /// + /// The number of codewords in a symbol, the primary and secondary messages with their check codewords. + /// + private const int CodewordCount = 144; + + /// + /// The number of data codewords of the primary message. The first is the mode. + /// + private const int PrimaryDataCodewords = 10; + + /// + /// The number of check codewords of the primary message. + /// + private const int PrimaryCheckCodewords = 10; + + /// + /// The index of the first secondary data codeword, after the primary data and check codewords. + /// + private const int SecondaryStart = PrimaryDataCodewords + PrimaryCheckCodewords; + + /// + /// The number of secondary data codewords with standard error correction, modes 2, 3, 4 and 6. + /// + private const int StandardSecondaryLength = 84; + + /// + /// The number of check codewords of one secondary block with standard error correction. + /// + private const int StandardSecondaryCheckCodewords = 20; + + /// + /// The number of check codewords of one secondary block with enhanced error correction, mode 5. + /// + private const int EnhancedSecondaryCheckCodewords = 28; + + /// + /// The length of a code set the search has not reached at a character. A message is at most codewords, so this value is larger + /// than every real length, and a length plus one latch and one operation stays far below the int range. + /// + private const int Unreached = 1 << 20; + + /// + /// The number of code sets, A to E. + /// + private const int CodeSetCount = 5; + + /// + /// The positions the code set search keeps, a power of two so a position maps to its row with a mask. The longest operation reads 9 characters back. + /// + private const int RingLength = 16; + + /// + /// The codeword that latches from code set C, D or E to code set A. + /// + private const int LatchA = 58; + + /// + /// The codeword that latches to code set B from A, C, D or E, and to code set A from B. + /// + private const int LatchBCodeword = 63; + + /// + /// The codeword of the numeric shift, NS, which carries nine digits in the five codewords after it. + /// + private const int NumericShiftCodeword = 31; + + /// + /// The codeword that shifts two characters into code set A from B, 2 SHIFT A. + /// + private const int TwoShiftACodeword = 56; + + /// + /// The codeword that shifts three characters into code set A from B, 3 SHIFT A. + /// + private const int ThreeShiftACodeword = 57; + + /// + /// The codeword that shifts one character into code set B from A, SHIFT B, or into code set A from B, SHIFT A. + /// + private const int ShiftCodeword = 59; + + /// + /// The codeword that shifts one character into code set C. + /// + private const int ShiftCCodeword = 60; + + /// + /// The codeword that shifts one character into code set D. + /// + private const int ShiftDCodeword = 61; + + /// + /// The codeword that shifts one character into code set E. + /// + private const int ShiftECodeword = 62; + + /// + /// The data codewords of the secondary message of modes 2 and 3. + /// + private const int CarrierCapacity = 84; + + /// + /// The data codewords of mode 5 after the mode codeword. + /// + private const int EnhancedCapacity = 77; + + /// + /// The data codewords of modes 4 and 6 after the mode codeword. + /// + private const int StandardCapacity = 93; + + /// + /// The field of the Reed-Solomon codes: GF(64) with the polynomial x^6 + x + 1, 67 decimal. + /// + private static readonly GaloisField Field = new(64, 67); + + /// + /// The code sets of Table 3, in the row and column order of the latch, pad and value tables. + /// + private enum CodeSet : byte + { + /// + /// Code set A. + /// + A, + + /// + /// Code set B. + /// + B, + + /// + /// Code set C. + /// + C, + + /// + /// Code set D. + /// + D, + + /// + /// Code set E. + /// + E, + } + + /// + /// A set of code sets, bit n for the code set of value n in . + /// + [Flags] + private enum CodeSets : byte + { + /// + /// No code set. + /// + None = 0, + + /// + /// Code set A. + /// + A = 1, + + /// + /// Code set B. + /// + B = 2, + + /// + /// Code set C. + /// + C = 4, + + /// + /// Code set D. + /// + D = 8, + + /// + /// Code set E. + /// + E = 16, + } + + /// + /// The operations of the code set search, in the order the search tries them. Equal lengths keep the first operation found. The character operations follow + /// the code sets A to E in order. + /// + private enum Operation : byte + { + /// + /// The numeric shift: nine digits in six codewords. + /// + NumericShift, + + /// + /// One character of code set A in one codeword. + /// + CharacterA, + + /// + /// One character of code set B in one codeword. + /// + CharacterB, + + /// + /// One character of code set C in one codeword. + /// + CharacterC, + + /// + /// One character of code set D in one codeword. + /// + CharacterD, + + /// + /// One character of code set E in one codeword. + /// + CharacterE, + + /// + /// SHIFT A and one character of code set A. + /// + ShiftA, + + /// + /// 2 SHIFT A and two characters of code set A. + /// + ShiftA2, + + /// + /// 3 SHIFT A and three characters of code set A. + /// + ShiftA3, + + /// + /// SHIFT B and one character of code set B. + /// + ShiftB, + + /// + /// The shift into code set C and one character of code set C. + /// + ShiftC, + + /// + /// The shift into code set D and one character of code set D. + /// + ShiftD, + + /// + /// The shift into code set E and one character of code set E. + /// + ShiftE, + } + + /// + /// Gets the pad codeword of each code set, 0 where the code set has none. + /// + private static ReadOnlySpan PadCodes => [33, 33, 0, 0, 28]; + + /// + /// Gets the dark modules of the orientation patterns around the finder, as row x 30 + column. + /// + private static ReadOnlySpan OrientationModules => [28, 29, 280, 281, 311, 457, 488, 500, 530, 670, 700, 677, 707]; + + /// + /// Gets the module of each codeword bit, as row x 30 + column: bit b of codeword c, the most significant bit first, is entry 6c + b. + /// + private static ReadOnlySpan ModuleMap => + [ + 469, 529, 286, 316, 347, 346, 673, 672, 703, 702, 647, 676, 283, 282, 313, 312, 370, 610, 618, 379, + 378, 409, 408, 439, 705, 704, 559, 589, 588, 619, 458, 518, 640, 701, 675, 674, 285, 284, 315, 314, + 310, 340, 531, 289, 288, 319, 349, 348, 456, 486, 517, 516, 471, 470, 369, 368, 399, 398, 429, 428, + 549, 548, 579, 578, 609, 608, 649, 648, 679, 678, 709, 708, 639, 638, 669, 668, 699, 698, 279, 278, + 309, 308, 339, 338, 381, 380, 411, 410, 441, 440, 561, 560, 591, 590, 621, 620, 547, 546, 577, 576, + 607, 606, 367, 366, 397, 396, 427, 426, 291, 290, 321, 320, 351, 350, 651, 650, 681, 680, 711, 710, + 1, 0, 31, 30, 61, 60, 3, 2, 33, 32, 63, 62, 5, 4, 35, 34, 65, 64, 7, 6, + 37, 36, 67, 66, 9, 8, 39, 38, 69, 68, 11, 10, 41, 40, 71, 70, 13, 12, 43, 42, + 73, 72, 15, 14, 45, 44, 75, 74, 17, 16, 47, 46, 77, 76, 19, 18, 49, 48, 79, 78, + 21, 20, 51, 50, 81, 80, 23, 22, 53, 52, 83, 82, 25, 24, 55, 54, 85, 84, 27, 26, + 57, 56, 87, 86, 117, 116, 147, 146, 177, 176, 115, 114, 145, 144, 175, 174, 113, 112, 143, 142, + 173, 172, 111, 110, 141, 140, 171, 170, 109, 108, 139, 138, 169, 168, 107, 106, 137, 136, 167, 166, + 105, 104, 135, 134, 165, 164, 103, 102, 133, 132, 163, 162, 101, 100, 131, 130, 161, 160, 99, 98, + 129, 128, 159, 158, 97, 96, 127, 126, 157, 156, 95, 94, 125, 124, 155, 154, 93, 92, 123, 122, + 153, 152, 91, 90, 121, 120, 151, 150, 181, 180, 211, 210, 241, 240, 183, 182, 213, 212, 243, 242, + 185, 184, 215, 214, 245, 244, 187, 186, 217, 216, 247, 246, 189, 188, 219, 218, 249, 248, 191, 190, + 221, 220, 251, 250, 193, 192, 223, 222, 253, 252, 195, 194, 225, 224, 255, 254, 197, 196, 227, 226, + 257, 256, 199, 198, 229, 228, 259, 258, 201, 200, 231, 230, 261, 260, 203, 202, 233, 232, 263, 262, + 205, 204, 235, 234, 265, 264, 207, 206, 237, 236, 267, 266, 297, 296, 327, 326, 357, 356, 295, 294, + 325, 324, 355, 354, 293, 292, 323, 322, 353, 352, 277, 276, 307, 306, 337, 336, 275, 274, 305, 304, + 335, 334, 273, 272, 303, 302, 333, 332, 271, 270, 301, 300, 331, 330, 361, 360, 391, 390, 421, 420, + 363, 362, 393, 392, 423, 422, 365, 364, 395, 394, 425, 424, 383, 382, 413, 412, 443, 442, 385, 384, + 415, 414, 445, 444, 387, 386, 417, 416, 447, 446, 477, 476, 507, 506, 537, 536, 475, 474, 505, 504, + 535, 534, 473, 472, 503, 502, 533, 532, 455, 454, 485, 484, 515, 514, 453, 452, 483, 482, 513, 512, + 451, 450, 481, 480, 511, 510, 541, 540, 571, 570, 601, 600, 543, 542, 573, 572, 603, 602, 545, 544, + 575, 574, 605, 604, 563, 562, 593, 592, 623, 622, 565, 564, 595, 594, 625, 624, 567, 566, 597, 596, + 627, 626, 657, 656, 687, 686, 717, 716, 655, 654, 685, 684, 715, 714, 653, 652, 683, 682, 713, 712, + 637, 636, 667, 666, 697, 696, 635, 634, 665, 664, 695, 694, 633, 632, 663, 662, 693, 692, 631, 630, + 661, 660, 691, 690, 721, 720, 751, 750, 781, 780, 723, 722, 753, 752, 783, 782, 725, 724, 755, 754, + 785, 784, 727, 726, 757, 756, 787, 786, 729, 728, 759, 758, 789, 788, 731, 730, 761, 760, 791, 790, + 733, 732, 763, 762, 793, 792, 735, 734, 765, 764, 795, 794, 737, 736, 767, 766, 797, 796, 739, 738, + 769, 768, 799, 798, 741, 740, 771, 770, 801, 800, 743, 742, 773, 772, 803, 802, 745, 744, 775, 774, + 805, 804, 747, 746, 777, 776, 807, 806, 837, 836, 867, 866, 897, 896, 835, 834, 865, 864, 895, 894, + 833, 832, 863, 862, 893, 892, 831, 830, 861, 860, 891, 890, 829, 828, 859, 858, 889, 888, 827, 826, + 857, 856, 887, 886, 825, 824, 855, 854, 885, 884, 823, 822, 853, 852, 883, 882, 821, 820, 851, 850, + 881, 880, 819, 818, 849, 848, 879, 878, 817, 816, 847, 846, 877, 876, 815, 814, 845, 844, 875, 874, + 813, 812, 843, 842, 873, 872, 811, 810, 841, 840, 871, 870, 901, 900, 931, 930, 961, 960, 903, 902, + 933, 932, 963, 962, 905, 904, 935, 934, 965, 964, 907, 906, 937, 936, 967, 966, 909, 908, 939, 938, + 969, 968, 911, 910, 941, 940, 971, 970, 913, 912, 943, 942, 973, 972, 915, 914, 945, 944, 975, 974, + 917, 916, 947, 946, 977, 976, 919, 918, 949, 948, 979, 978, 921, 920, 951, 950, 981, 980, 923, 922, + 953, 952, 983, 982, 925, 924, 955, 954, 985, 984, 927, 926, 957, 956, 987, 986, 58, 89, 88, 118, + 149, 148, 178, 209, 208, 238, 269, 268, 298, 329, 328, 358, 389, 388, 418, 449, 448, 478, 509, 508, + 538, 569, 568, 598, 629, 628, 658, 689, 688, 718, 749, 748, 778, 809, 808, 838, 869, 868, 898, 929, + 928, 958, 989, 988, + ]; + + /// + /// Gets the value of each byte in each code set of Table 3, at index code set x 256 + byte, or 0xFF where the code set has no such byte. Code sets A to E + /// follow in turn, 256 bytes each. A byte that two values of one code set stand for takes the later value. + /// + private static ReadOnlySpan SetValues => + [ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 28, 29, 30, 255, + 32, 255, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 255, 255, 255, 255, 255, + 255, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 28, 29, 30, 255, + 47, 53, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 48, 255, 49, 50, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 51, 37, 38, 39, 40, 41, + 52, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 42, 43, 44, 45, 46, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 32, 54, 34, 35, 36, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 28, 29, 30, 255, + 59, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 37, 255, 38, 255, 255, 255, + 255, 39, 40, 41, 255, 42, 255, 255, 255, 43, 44, 255, 45, 46, 47, 255, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 32, 33, 34, 35, 36, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 28, 29, 30, 255, + 59, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 48, 49, 50, 51, 52, + 53, 54, 55, 56, 57, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 37, 255, 255, 255, 255, 255, 255, 38, 255, 255, 39, 255, 255, 255, 40, + 41, 255, 255, 255, 42, 255, 255, 43, 44, 255, 255, 45, 255, 255, 255, 46, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 32, 33, 34, 35, 36, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 30, 32, 33, 34, 35, + 59, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 36, + 37, 255, 38, 39, 40, 41, 42, 43, 255, 44, 255, 255, 255, 45, 46, 255, + 255, 255, 255, 255, 255, 255, 47, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + ]; + + /// + /// Builds a MaxiCode symbol. + /// + /// The text, characters U+0000 to U+00FF. + /// The mode, or for mode 5 when the data fits and mode 4 otherwise. + /// The X dimension in millimetres. + /// The symbol to write. + /// The text is empty, has a character above U+00FF, breaks a rule of the structured carrier message, or does not + /// fit. + public static void BuildSymbol(ReadOnlySpan text, MaxiCodeMode? mode, double xDimension, ref BarcodeSymbol symbol) + { + if (text.IsEmpty) + { + throw new ArgumentException("MaxiCode needs at least one character.", nameof(text)); + } + + int invalid = text.IndexOfAnyExceptInRange('\0', 'ÿ'); + if (invalid >= 0) + { + throw new ArgumentException($"MaxiCode encodes U+0000 to U+00FF; got {text.CodePointAt(invalid).ToDisplayString()}.", nameof(text)); + } + + bool carrier = mode is MaxiCodeMode.StructuredCarrierNumeric or MaxiCodeMode.StructuredCarrierAlphanumeric; + InlineArray10 primaryStorage = default; + Span primary = primaryStorage; + scoped ReadOnlySpan data = text; + InlineArray256 carrierDataStorage = default; + Span carrierData = ((Span)carrierDataStorage)[..MaximumDataLength]; + if (carrier) + { + data = carrierData[..ParseStructuredCarrier(text, mode!.Value, primary, carrierData)]; + } + + if (data.Length > MaximumDataLength) + { + throw new ArgumentException($"MaxiCode holds at most {MaximumDataLength} characters of data; got {data.Length}.", nameof(text)); + } + + // No mode holds more than StandardCapacity codewords, and the closing latch takes one more. The search writes no longer message, and the capacity check + // below rejects it. + InlineArray128 messageStorage = default; + Span message = ((Span)messageStorage)[..(StandardCapacity + 1)]; + int messageLength = EncodeMessage(data, message, out CodeSet endCodeSet); + MaxiCodeMode chosen = mode ?? (messageLength <= EnhancedCapacity ? MaxiCodeMode.FullErrorCorrection : MaxiCodeMode.Standard); + int capacity = chosen switch + { + MaxiCodeMode.StructuredCarrierNumeric or MaxiCodeMode.StructuredCarrierAlphanumeric => CarrierCapacity, + MaxiCodeMode.FullErrorCorrection => EnhancedCapacity, + _ => StandardCapacity, + }; + + if (messageLength > capacity) + { + throw new ArgumentException($"A mode {(int)chosen} MaxiCode holds at most {capacity} codewords of data.", nameof(text)); + } + + // A message that ends in a code set without a pad codeword latches back to code set A when the capacity of the chosen mode leaves room to pad. + int pad = PadCodes[(int)endCodeSet]; + if (pad == 0 && messageLength < capacity) + { + message[messageLength++] = LatchA; + pad = PadCodes[(int)CodeSet.A]; + } + + InlineArray256 codewordsStorage = default; + Span codewords = ((Span)codewordsStorage)[..CodewordCount]; + int secondaryLength; + if (carrier) + { + primary.CopyTo(codewords); + secondaryLength = CarrierCapacity; + Span secondary = codewords.Slice(SecondaryStart, secondaryLength); + secondary.Fill(pad); + message[..messageLength].CopyTo(secondary); + } + else + { + // The mode is the first codeword and the message follows it, through the 9 remaining data codewords of the primary message and on into the + // secondary message. + secondaryLength = capacity + 1 - PrimaryDataCodewords; + codewords[..PrimaryDataCodewords].Fill(pad); + codewords.Slice(SecondaryStart, secondaryLength).Fill(pad); + codewords[0] = (int)chosen; + int head = Math.Min(messageLength, PrimaryDataCodewords - 1); + message[..head].CopyTo(codewords[1..]); + message[head..messageLength].CopyTo(codewords[SecondaryStart..]); + } + + AddCheckCodewords(codewords, secondaryLength); + + // Section 4.11.5: "1W on the left and right sides" and "1Y on the top and bottom", W being X and Y the row pitch, √3/2 X. + Span modules = symbol.BeginHexagonal(Columns, Rows, 1F, (float)(Math.Sqrt(3) / 2), xDimension); + ReadOnlySpan map = ModuleMap; + for (int i = 0; i < CodewordCount; i++) + { + for (int b = 0; b < 6; b++) + { + if (((codewords[i] >> (5 - b)) & 1) == 1) + { + modules[map[(i * 6) + b]] = true; + } + } + } + + foreach (short module in OrientationModules) + { + modules[module] = true; + } + } + + /// + /// Reads the structured carrier message of a mode 2 or 3 symbol from the head of the text and writes the primary data codewords. The text is the postal + /// code, the 3-digit country code and the 3-digit class of service, each followed by a group separator, then the secondary data. A head of "[)>", RS, "01", + /// GS and two digits stays at the front of the secondary data. + /// + /// The text. + /// The mode, or . + /// The buffer the 10 primary data codewords are written to. + /// The buffer the secondary data is assembled in. + /// The length of the secondary data written to . + private static int ParseStructuredCarrier(ReadOnlySpan text, MaxiCodeMode mode, Span primary, Span buffer) + { + ReadOnlySpan header = []; + ReadOnlySpan rest = text; + if (text.Length >= 9 && text.StartsWith("[)>\u001E01\u001D") && char.IsAsciiDigit(text[7]) && char.IsAsciiDigit(text[8])) + { + header = text[..9]; + rest = text[9..]; + } + + if (!TryNextField(ref rest, out ReadOnlySpan postalCode) + || !TryNextField(ref rest, out ReadOnlySpan countryCode) + || !TryNextField(ref rest, out ReadOnlySpan serviceClass)) + { + throw new ArgumentException( + "A mode 2 or 3 MaxiCode starts with the postal code, the country code and the class of service, each followed by a group separator.", + nameof(text)); + } + + bool numeric = mode == MaxiCodeMode.StructuredCarrierNumeric; + if (numeric && (postalCode.Length is 0 or > 9 || postalCode.ContainsAnyExceptInRange('0', '9'))) + { + throw new ArgumentException("A mode 2 postal code is 1 to 9 digits.", nameof(text)); + } + + if (!numeric) + { + if (postalCode.Length is 0 or > 6) + { + throw new ArgumentException("A mode 3 postal code is 1 to 6 characters.", nameof(text)); + } + + foreach (char c in postalCode) + { + if (c is not (' ' or (>= '"' and <= ':') or (>= 'A' and <= 'Z'))) + { + throw new ArgumentException($"A mode 3 postal code carries spaces, \" to : and A to Z; got '{c}'.", nameof(text)); + } + } + } + + if (countryCode.Length != 3 || countryCode.ContainsAnyExceptInRange('0', '9')) + { + throw new ArgumentException("The country code is three digits.", nameof(text)); + } + + if (serviceClass.Length != 3 || serviceClass.ContainsAnyExceptInRange('0', '9')) + { + throw new ArgumentException("The class of service is three digits.", nameof(text)); + } + + // The postal code takes 36 bits. A numeric code is its length in 6 bits and its value in 30 bits, a 5-digit United States code padded with "0000" to 9 + // digits. An alphanumeric code is 6 characters of code set A, cut or padded with spaces. + long postal = 0; + if (numeric) + { + InlineArray10 digitsStorage = default; + Span digits = ((Span)digitsStorage)[..9]; + int length = postalCode.Length; + postalCode.CopyTo(digits); + if (countryCode.SequenceEqual("840") && length == 5) + { + "0000".CopyTo(digits[5..]); + length = 9; + } + + long value = 0; + foreach (char digit in digits[..length]) + { + value = (value * 10) + (digit - '0'); + } + + postal = ((long)length << 30) | value; + } + else + { + for (int i = 0; i < 6; i++) + { + char c = i < postalCode.Length ? postalCode[i] : ' '; + postal = (postal << 6) | (uint)Value(CodeSet.A, c); + } + } + + int country = int.Parse(countryCode, System.Globalization.CultureInfo.InvariantCulture); + int service = int.Parse(serviceClass, System.Globalization.CultureInfo.InvariantCulture); + + // The primary data is one 60-bit number, least significant bits first: the mode in bits 0 to 3, the postal code in bits 4 to 39, the country code in + // bits 40 to 49 and the class of service in bits 50 to 59. Primary codeword i carries bits 6i to 6i + 5. + long primaryBits = (long)mode | (postal << 4) | ((long)country << 40) | ((long)service << 50); + for (int i = 0; i < primary.Length; i++) + { + primary[i] = (int)((primaryBits >> (6 * i)) & 63); + } + + header.CopyTo(buffer); + if (header.Length + rest.Length > buffer.Length) + { + throw new ArgumentException($"MaxiCode holds at most {MaximumDataLength} characters of data.", nameof(text)); + } + + rest.CopyTo(buffer[header.Length..]); + return header.Length + rest.Length; + } + + /// + /// Takes the field before the next group separator off the text. + /// + /// The text, advanced past the separator. + /// The field. + /// when the text has a group separator; otherwise, . + private static bool TryNextField(ref ReadOnlySpan rest, out ReadOnlySpan field) + { + int separator = rest.IndexOf('\u001D'); + if (separator < 0) + { + field = []; + return false; + } + + field = rest[..separator]; + rest = rest[(separator + 1)..]; + return true; + } + + /// + /// Finds the shortest codeword sequence for the data: for each character and each code set, the shortest length that ends in that code set, with the + /// operation and the earlier code set that reach it, then a walk back from the end. + /// + /// The data, at most characters. + /// The buffer the codewords are written to. A message longer than the buffer is not written. + /// The code set the message ends in. + /// The number of codewords. + private static int EncodeMessage(ReadOnlySpan data, Span message, out CodeSet endCodeSet) + { + // The code sets the data needs beyond A. FS, GS, RS and space are in every code set. + CodeSets needed = CodeSets.None; + foreach (char c in data) + { + if (c is (< (char)28 or > (char)30) and not ' ') + { + for (CodeSet set = CodeSet.B; set <= CodeSet.E; set++) + { + if (Has(set, c)) + { + needed |= (CodeSets)(1 << (int)set); + } + } + } + } + + // Code sets with a pad codeword come first, so they win ties. + InlineArray5 priorityStorage = default; + Span priority = priorityStorage; + int priorityCount = 0; + priority[priorityCount++] = CodeSet.A; + ReadOnlySpan order = [CodeSet.B, CodeSet.E, CodeSet.C, CodeSet.D]; + foreach (CodeSet set in order) + { + if (needed.HasFlag((CodeSets)(1 << (int)set))) + { + priority[priorityCount++] = set; + } + } + + priority = priority[..priorityCount]; + + // The lengths and best earlier code sets of the last RingLength characters, row (n mod RingLength) for character n, so row RingLength - 1 holds the + // start before the first character, all zero. The longest operation reads 9 characters back, which the ring keeps. + InlineArray128 bestLengthStorage = default; + Span bestLength = ((Span)bestLengthStorage)[..(RingLength * CodeSetCount)]; + InlineArray128 bestOriginStorage = default; + Span bestOrigin = ((Span)bestOriginStorage)[..(RingLength * CodeSetCount)]; + + // The operation and the earlier code set that reach each character in each code set, for the walk back. The data is at most MaximumDataLength + // characters. + InlineArray256> pathStorage = default; + Span> path = ((Span>)pathStorage)[..data.Length]; + + int digits = 0; + int inA = 0; + for (int n = 0; n < data.Length; n++) + { + int c = data[n]; + digits = c is >= '0' and <= '9' ? digits + 1 : 0; + inA = Has(CodeSet.A, c) ? inA + 1 : 0; + + Span lengthRow = bestLength.Slice((n & (RingLength - 1)) * CodeSetCount, CodeSetCount); + Span originRow = bestOrigin.Slice((n & (RingLength - 1)) * CodeSetCount, CodeSetCount); + foreach (CodeSet state in priority) + { + int best = Unreached; + for (Operation op = Operation.NumericShift; op <= Operation.ShiftE; op++) + { + if (!Applies(op, state, needed) || !Can(op, c, digits, inA)) + { + continue; + } + + int m = (n - Intake(op)) & (RingLength - 1); + CodeSet origin = bestOrigin[(m * CodeSetCount) + (int)state]; + int length = bestLength[(m * CodeSetCount) + (int)origin] + LatchLength(state, origin) + Output(op); + if (best > length) + { + best = length; + path[n][(int)state] = new Step(op, origin); + } + } + + lengthRow[(int)state] = best; + } + + foreach (CodeSet state in priority) + { + int originLength = lengthRow[(int)CodeSet.A] + LatchLength(state, CodeSet.A); + CodeSet origin = CodeSet.A; + foreach (CodeSet candidate in priority[1..]) + { + int length = lengthRow[(int)candidate] + LatchLength(state, candidate); + if (length < originLength) + { + originLength = length; + origin = candidate; + } + } + + originRow[(int)state] = origin; + } + } + + Span last = bestLength.Slice(((data.Length - 1) & (RingLength - 1)) * CodeSetCount, CodeSetCount); + CodeSet endState = CodeSet.A; + int total = Unreached; + foreach (CodeSet state in priority) + { + if (last[(int)state] < total) + { + total = last[(int)state]; + endState = state; + } + } + + endCodeSet = endState; + if (total > message.Length) + { + return total; + } + + // The walk back writes each operation and the latch before it from the end of the message. + int position = total; + CodeSet current = endState; + int index = data.Length; + InlineArray6 outputStorage = default; + Span output = outputStorage; + while (index > 0) + { + Step step = path[index - 1][(int)current]; + index -= Intake(step.Operation); + int count = Encode(step.Operation, data, index, output); + int latchCount = LatchLength(current, step.Prior); + position -= latchCount + count; + + // A latch into A or B is one codeword. A lock into C, D or E is the shift into that code set and then the lock, which has the same value. + if (latchCount != 0) + { + message[position..(position + latchCount)].Fill(LatchCodeword(current, step.Prior)); + } + + output[..count].CopyTo(message[(position + latchCount)..]); + current = step.Prior; + } + + return total; + } + + /// + /// Returns whether an operation is open in a code set. The characters of a code set are open in it. In B, the shifts into A are open. In A, the shift into + /// B is open. In every other code set, the shifts into C, D and E are open. The numeric shift is open everywhere. + /// + /// The operation. + /// The code set, which is A or a code set the data needs. + /// The code sets beyond A that the data needs. + /// when the operation is open. + private static bool Applies(Operation op, CodeSet state, CodeSets needed) + => op switch + { + Operation.NumericShift => true, + Operation.CharacterA => state == CodeSet.A, + Operation.CharacterB => state == CodeSet.B, + Operation.CharacterC => state == CodeSet.C, + Operation.CharacterD => state == CodeSet.D, + Operation.CharacterE => state == CodeSet.E, + Operation.ShiftA or Operation.ShiftA2 or Operation.ShiftA3 => state == CodeSet.B, + Operation.ShiftB => needed.HasFlag(CodeSets.B) && state == CodeSet.A, + Operation.ShiftC => needed.HasFlag(CodeSets.C) && state != CodeSet.C, + Operation.ShiftD => needed.HasFlag(CodeSets.D) && state != CodeSet.D, + _ => needed.HasFlag(CodeSets.E) && state != CodeSet.E, + }; + + /// + /// Returns whether an operation takes the characters ending at the current one. + /// + /// The operation. + /// The current character. + /// The number of digits ending at the current character. + /// The number of characters of code set A ending at the current character. + /// when the operation takes them. + private static bool Can(Operation op, int c, int digits, int inA) + => op switch + { + Operation.NumericShift => digits >= 9, + Operation.CharacterA => Has(CodeSet.A, c), + Operation.CharacterB or Operation.ShiftB => Has(CodeSet.B, c), + Operation.CharacterC or Operation.ShiftC => Has(CodeSet.C, c), + Operation.CharacterD or Operation.ShiftD => Has(CodeSet.D, c), + Operation.CharacterE or Operation.ShiftE => Has(CodeSet.E, c), + Operation.ShiftA => inA >= 1, + Operation.ShiftA2 => inA >= 2, + _ => inA >= 3, + }; + + /// + /// Returns the number of characters an operation takes. + /// + /// The operation. + /// The number of characters. + private static int Intake(Operation op) + => op switch + { + Operation.NumericShift => 9, + Operation.ShiftA2 => 2, + Operation.ShiftA3 => 3, + _ => 1, + }; + + /// + /// Returns the number of codewords an operation writes. + /// + /// The operation. + /// The number of codewords. + private static int Output(Operation op) + => op switch + { + Operation.NumericShift => 6, + Operation.CharacterA or Operation.CharacterB or Operation.CharacterC or Operation.CharacterD or Operation.CharacterE => 1, + Operation.ShiftA2 => 3, + Operation.ShiftA3 => 4, + _ => 2, + }; + + /// + /// Writes the codewords of an operation. + /// + /// The operation. + /// The data. + /// The first character the operation takes. + /// The buffer the codewords are written to. + /// The number of codewords. + private static int Encode(Operation op, ReadOnlySpan data, int n, Span output) + { + switch (op) + { + case Operation.NumericShift: + { + // NS: nine digits as a 30-bit number in five codewords. + int value = 0; + foreach (char digit in data.Slice(n, 9)) + { + value = (value * 10) + (digit - '0'); + } + + output[0] = NumericShiftCodeword; + for (int i = 0; i < 5; i++) + { + output[1 + i] = (value >> (24 - (6 * i))) & 63; + } + + return 6; + } + + case >= Operation.CharacterA and <= Operation.CharacterE: + output[0] = Value((CodeSet)(op - Operation.CharacterA), data[n]); + return 1; + case Operation.ShiftA: + output[0] = ShiftCodeword; + output[1] = Value(CodeSet.A, data[n]); + return 2; + case Operation.ShiftA2: + output[0] = TwoShiftACodeword; + output[1] = Value(CodeSet.A, data[n]); + output[2] = Value(CodeSet.A, data[n + 1]); + return 3; + case Operation.ShiftA3: + output[0] = ThreeShiftACodeword; + output[1] = Value(CodeSet.A, data[n]); + output[2] = Value(CodeSet.A, data[n + 1]); + output[3] = Value(CodeSet.A, data[n + 2]); + return 4; + case Operation.ShiftB: + output[0] = ShiftCodeword; + output[1] = Value(CodeSet.B, data[n]); + return 2; + case Operation.ShiftC: + output[0] = ShiftCCodeword; + output[1] = Value(CodeSet.C, data[n]); + return 2; + case Operation.ShiftD: + output[0] = ShiftDCodeword; + output[1] = Value(CodeSet.D, data[n]); + return 2; + default: + output[0] = ShiftECodeword; + output[1] = Value(CodeSet.E, data[n]); + return 2; + } + } + + /// + /// Adds the check codewords: 10 after the 10 primary data codewords, and for the secondary message one block of its odd codewords and one of its even + /// codewords, each with 20 check codewords in modes 2, 3, 4 and 6 and 28 in mode 5, interleaved. Every block uses the generator with roots alpha^1 to + /// alpha^k. + /// + /// The 144 codewords, primary data at 0 and secondary data at 20. + /// The number of secondary data codewords, 84 or 68. + private static void AddCheckCodewords(Span codewords, int secondaryLength) + { + // A secondary block takes 20 or 28 check codewords, the most any code here takes. + InlineArray32 generatorStorage = default; + InlineArray32 checkStorage = default; + Span generator = generatorStorage; + Field.Generator(1, generator[..PrimaryCheckCodewords]); + Field.Remainder(codewords, 1, PrimaryDataCodewords, generator[..PrimaryCheckCodewords], codewords.Slice(PrimaryDataCodewords, PrimaryCheckCodewords)); + + // The secondary message deals its codewords over two blocks, the odd and the even. The 84 codewords of standard error correction take 20 check + // codewords a block, and the 68 of mode 5 take 28. + int checkCount = secondaryLength == StandardSecondaryLength ? StandardSecondaryCheckCodewords : EnhancedSecondaryCheckCodewords; + Field.Generator(1, generator[..checkCount]); + Field.InterleavedRemainders( + codewords.Slice(SecondaryStart, secondaryLength + (checkCount * 2)), + secondaryLength, + 2, + generator[..checkCount], + ((Span)checkStorage)[..checkCount]); + } + + /// + /// Returns the length of the latch into a code set from another. + /// + /// The code set latched into. + /// The code set latched from. + /// The number of codewords: 0 within a code set, 1 into A or B, 2 into C, D or E. + private static int LatchLength(CodeSet to, CodeSet from) => to == from ? 0 : to <= CodeSet.B ? 1 : 2; + + /// + /// Returns the codeword of the latch into a code set from another, in the code set it latches from. The latch takes codewords of + /// this value. Into C, D or E, these are the shift into that code set and then the lock, which has the same value. + /// + /// The code set latched into. + /// The code set latched from, not . + /// The codeword. + private static int LatchCodeword(CodeSet to, CodeSet from) => to switch + { + CodeSet.A => from == CodeSet.B ? LatchBCodeword : LatchA, + CodeSet.B => LatchBCodeword, + CodeSet.C => ShiftCCodeword, + CodeSet.D => ShiftDCodeword, + _ => ShiftECodeword, + }; + + /// + /// Returns whether a code set holds a byte. + /// + /// The code set. + /// The byte. + /// when the code set holds it. + private static bool Has(CodeSet set, int c) => SetValues[((int)set << 8) | c] != 0xFF; + + /// + /// Returns the value of a byte in a code set. + /// + /// The code set. + /// The byte. + /// The value. + private static int Value(CodeSet set, int c) => SetValues[((int)set << 8) | c]; + + /// + /// One step of the code set search: the operation that reaches a character in a code set, and the code set the operation starts from. + /// + private readonly struct Step + { + /// + /// Initializes a new instance of the struct. + /// + /// The operation. + /// The code set before the operation. + public Step(Operation operation, CodeSet prior) + { + this.Operation = operation; + this.Prior = prior; + } + + /// + /// Gets the operation. + /// + public Operation Operation { get; } + + /// + /// Gets the code set before the operation, which a latch leaves when it differs from the code set of the step. + /// + public CodeSet Prior { get; } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/MaxiCode/MaxiCodeMode.cs b/src/ImageSharp.Drawing/Barcodes/MaxiCode/MaxiCodeMode.cs new file mode 100644 index 00000000..1db06ced --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/MaxiCode/MaxiCodeMode.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The modes of a MaxiCode symbol, which the first codeword carries. +/// +public enum MaxiCodeMode +{ + /// + /// Mode 2: a structured carrier message with a numeric postal code of up to 9 digits, a 3-digit country code and a 3-digit class of service in the primary + /// message, and the rest of the data in the secondary message. + /// + StructuredCarrierNumeric = 2, + + /// + /// Mode 3: a structured carrier message with an alphanumeric postal code of up to 6 characters, a 3-digit country code and a 3-digit class of service in + /// the primary message, and the rest of the data in the secondary message. + /// + StructuredCarrierAlphanumeric = 3, + + /// + /// Mode 4: up to 93 codewords of data with standard error correction of the secondary message. + /// + Standard = 4, + + /// + /// Mode 5: up to 77 codewords of data with enhanced error correction of the secondary message. + /// + FullErrorCorrection = 5, + + /// + /// Mode 6: a symbol that programs the reader, laid out as mode 4. + /// + ReaderProgramming = 6, +} diff --git a/src/ImageSharp.Drawing/Barcodes/MaxiCode/MaxiCodeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/MaxiCode/MaxiCodeSymbology.cs new file mode 100644 index 00000000..523f47f5 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/MaxiCode/MaxiCodeSymbology.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The MaxiCode symbology, ISO/IEC 16023. A symbol is 33 offset rows of hexagonal modules, alternately 30 and 29 wide, around a finder of three dark rings. +/// The symbol has a quiet zone of one module on every side. It carries the characters U+0000 to U+00FF with Reed-Solomon error correction. The X dimension is +/// 0.88 mm. Section 4.11.1 permits 24.00/29 to 27.00/29 mm. When an X dimension is outside that range, it draws at the nearest limit. +/// +/// In modes 2 and 3 the text starts with the structured carrier message: the postal code, the 3-digit country code and the 3-digit class of service, each +/// followed by a group separator (U+001D). A message that starts with "[)>", RS, "01", GS and two digits keeps that header in the secondary message. Without +/// a mode the symbol takes mode 5 when the data fits and mode 4 otherwise. +/// +/// +public sealed class MaxiCodeSymbology : BarcodeSymbology +{ + /// + /// The nominal X dimension in millimetres: W = X = L/29 = 0.88 mm at the nominal L of 25.50 mm, Table 7 of ISO/IEC 16023. Section 4.11.1 accepts L from + /// 24.00 mm to 27.00 mm. + /// + private const double NominalXDimension = 0.88; + + /// + /// The smallest X dimension in millimetres: L/29 at the smallest L of 24.00 mm of section 4.11.1. Section 4.11.6 gives the same limit for the 32X wide + /// symbol with its quiet zones: "Each MaxiCode symbol shall be in the range of 26.48mm wide by 25.32mm high to 29.79mm wide by 28.49mm high." + /// + private const double MinimumXDimension = 24.00 / 29; + + /// + /// The largest X dimension in millimetres: L/29 at the largest L of 27.00 mm of section 4.11.1. + /// + private const double MaximumXDimension = 27.00 / 29; + + /// + /// Initializes a new instance of the class that takes mode 5 when the data fits and mode 4 otherwise. + /// + public MaxiCodeSymbology() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The mode. + public MaxiCodeSymbology(MaxiCodeMode mode) + { + Guard.MustBeBetweenOrEqualTo((int)mode, (int)MaxiCodeMode.StructuredCarrierNumeric, (int)MaxiCodeMode.ReaderProgramming, nameof(mode)); + this.Mode = mode; + } + + /// + /// Gets the mode, or when the symbol takes mode 5 when the data fits and mode 4 otherwise. + /// + public MaxiCodeMode? Mode { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + // The hexagonal modules do not stand on whole pixels, so the code clamps the X dimension in millimetres, and the symbol draws at it exactly. + double xDimension = Math.Clamp(options.XDimension ?? NominalXDimension, MinimumXDimension, MaximumXDimension); + MaxiCodeEncoder.BuildSymbol(text, this.Mode, xDimension, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/MeasuredLine.cs b/src/ImageSharp.Drawing/Barcodes/MeasuredLine.cs new file mode 100644 index 00000000..99fbf32c --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/MeasuredLine.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.Fonts; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The metrics of one shaped text line and the origin the layout gives it. The text block of the line shapes it one time, so the layout and the draw use +/// these values and do not measure the line again. +/// +internal struct MeasuredLine +{ + /// + /// Initializes a new instance of the struct. + /// + /// The advance of the line, from . + /// The bounds of the line, from . + /// The renderable bounds of the line, from . + /// The baseline of the first line, from , or 0 when the layout does not use it. + /// The line height of the first line, from , or 0 when the layout does not use it. + public MeasuredLine(FontRectangle advance, FontRectangle bounds, FontRectangle renderableBounds, float baseline, float lineHeight) + { + this.Advance = advance; + this.Bounds = bounds; + this.RenderableBounds = renderableBounds; + this.Baseline = baseline; + this.LineHeight = lineHeight; + } + + /// + /// Gets or sets the origin of the line, in pixels. The layout sets it once the symbol has its position. + /// + public Vector2 Origin { get; set; } + + /// + /// Gets the advance of the line, from . It is relative to the origin of the line. + /// + public FontRectangle Advance { get; } + + /// + /// Gets the bounds of the line, from . They are relative to the origin of the line. + /// + public FontRectangle Bounds { get; } + + /// + /// Gets the renderable bounds of the line, from . They are relative to the origin of the line. + /// + public FontRectangle RenderableBounds { get; } + + /// + /// Gets the baseline of the first line, from , or 0 when the layout does not use it. + /// + public float Baseline { get; } + + /// + /// Gets the line height of the first line, from , or 0 when the layout does not use it. + /// + public float LineHeight { get; } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Msi/MsiCheckDigit.cs b/src/ImageSharp.Drawing/Barcodes/Msi/MsiCheckDigit.cs new file mode 100644 index 00000000..6311ed51 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Msi/MsiCheckDigit.cs @@ -0,0 +1,47 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Specifies which check digits an MSI symbol carries after its data. The symbology is not self-checking, and the application chooses the calculation. +/// +public enum MsiCheckDigit +{ + /// + /// The symbol carries no check digit. All of the input is data. + /// + None, + + /// + /// One modulo 10 check digit. Every other digit from the right is doubled, the digits of the products and the remaining digits are added, and the check + /// digit lifts the sum to the next multiple of ten. + /// + Modulo10, + + /// + /// Two modulo 10 check digits. The second is calculated over the data and the first. + /// + Modulo1010, + + /// + /// One modulo 11 check digit with the IBM weights 2 to 7. The digits are weighted from the right, starting at 2 and returning to 2 after 7, and the check + /// digit lifts the weighted sum to the next multiple of eleven. + /// + Modulo11, + + /// + /// A modulo 11 check digit with the IBM weights, then a modulo 10 check digit calculated over the data and the first. + /// + Modulo1110, + + /// + /// One modulo 11 check digit with the NCR weights 2 to 9. + /// + NcrModulo11, + + /// + /// A modulo 11 check digit with the NCR weights, then a modulo 10 check digit calculated over the data and the first. + /// + NcrModulo1110, +} diff --git a/src/ImageSharp.Drawing/Barcodes/Msi/MsiEncoder.cs b/src/ImageSharp.Drawing/Barcodes/Msi/MsiEncoder.cs new file mode 100644 index 00000000..e19cfbde --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Msi/MsiEncoder.cs @@ -0,0 +1,145 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodation for the MSI symbology, also called Modified Plessey. Every digit is its four binary coded decimal bits, most significant first, and every bit is +/// a bar and a space three modules wide: a 0 bit is a narrow bar and a wide space, and a 1 bit is a wide bar and a narrow space. The symbol is continuous. A +/// start character, the 1 bit, opens it, and a stop character, two 0 bits without the last space, closes it. +/// +internal static class MsiEncoder +{ + /// + /// The largest number of digits a symbol carries. The symbology sets no maximum. + /// + public const int MaximumLength = 500; + + /// + /// The number of characters a caller holds inline to build symbol data in. Longer data rents its buffer once. + /// + public const int InlineBufferLength = 64; + + /// + /// The highest weight of the IBM modulo 11 calculation, after which the weights return to 2. + /// + public const int IbmMaximumWeight = 7; + + /// + /// The highest weight of the NCR modulo 11 calculation, after which the weights return to 2. + /// + public const int NcrMaximumWeight = 9; + + /// + /// The largest number of check digits a symbol carries after its data: a modulo 10 or modulo 11 check digit and a second modulo 10 check digit. + /// + public const int MaximumCheckDigits = 2; + + /// + /// The quiet zone in modules on each side. + /// + private const int QuietZone = 10; + + /// + /// The number of bits in a digit. + /// + private const int BitsPerDigit = 4; + + /// + /// The width of a wide bar or space in modules. A bit is three modules, one narrow element of one module and one wide element. + /// + private const int WideElement = 2; + + /// + /// The number of runs of the start character: the bar and the space of its one bit. + /// + private const int StartElements = 2; + + /// + /// The number of runs of the stop character: the bars and spaces of its two bits without the last space. + /// + private const int StopElements = 3; + + /// + /// The bar height as a fraction of the symbol width, quiet zones excluded, when the caller sets no height: 15 per cent. + /// + private const float NominalBarHeightFraction = 0.15F; + + /// + /// Calculates the modulo 10 check digit over the digits. Every other digit from the right, the rightmost included, is doubled. The digits of those products + /// and the digits that were not doubled are added, and the check digit lifts the sum to the next multiple of ten. The digits of a product from 10 to 18 + /// add to the product less 9. + /// + /// The digits the check digit covers. + /// The check digit. + public static int Modulo10(ReadOnlySpan digits) + { + int sum = 0; + bool doubled = true; + for (int i = digits.Length - 1; i >= 0; i--) + { + int digit = digits[i] - '0'; + if (doubled) + { + digit *= 2; + if (digit >= 10) + { + digit -= 9; + } + } + + sum += digit; + doubled = !doubled; + } + + return (10 - (sum % 10)) % 10; + } + + /// + /// Encodes digits and builds the symbol. The runs start with the bar of the start character and end on the last bar of the stop character. MSI carries no + /// guard bars, so every bar runs the full height, and the human readable interpretation sits below the symbol. + /// + /// The digits to encode, the check digits included, already validated. + /// The human readable interpretation. + /// The options that control layout choices. + /// The symbol to write. + public static void BuildSymbol(ReadOnlySpan digits, ReadOnlySpan readable, BarcodeOptions options, ref BarcodeSymbol symbol) + { + double xDimension = options.ResolveXDimension(0, BarcodeSymbology.DefaultXDimension, double.PositiveInfinity); + Span runs = symbol.BeginLinear(StartElements + (digits.Length * BitsPerDigit * 2) + StopElements, xDimension, out Span heights, out _); + + // The start character is a 1 bit, a wide bar and a narrow space. + int written = 0; + runs[written++] = WideElement; + runs[written++] = 1; + + foreach (char digit in digits) + { + int value = digit - '0'; + for (int bit = BitsPerDigit - 1; bit >= 0; bit--) + { + bool one = ((value >> bit) & 1) == 1; + runs[written++] = one ? WideElement : 1; + runs[written++] = one ? 1 : WideElement; + } + } + + // The stop character is two 0 bits without the last space: a narrow bar, a wide space and a narrow bar. + runs[written++] = 1; + runs[written++] = WideElement; + runs[written] = 1; + + int widthInModules = BarcodeSymbol.RunWidth(runs); + + float barHeight = options.ResolveBarHeight(xDimension, 0, widthInModules * NominalBarHeightFraction, float.PositiveInfinity); + heights.Fill(barHeight); + + if (options.Font is not null) + { + symbol.ReserveText(1, readable.Length); + symbol.AddText(readable, 0F, widthInModules, BarcodeTextSide.BelowBars, barHeight + BarcodeTextPlacement.Clearance); + } + + symbol.EndLinear(QuietZone, QuietZone); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Msi/MsiSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Msi/MsiSymbology.cs new file mode 100644 index 00000000..d2108262 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Msi/MsiSymbology.cs @@ -0,0 +1,116 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The MSI symbology, also called Modified Plessey. It carries the digits 0 to 9 between a start character and a stop character, which the printed line does +/// not show. +/// +/// The symbology is not self-checking, and the application chooses the check digits. The modulo 10 calculation is the common one, and the modulo 11 calculation +/// exists with the IBM weights 2 to 7 and the NCR weights 2 to 9. Each can be followed by a second modulo 10 check digit over the data and the first. A modulo +/// 11 check value of 10 cannot be carried in one digit, and the encoder rejects data whose check value is 10. The check digits print after the data by default. +/// +/// +public sealed class MsiSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public MsiSymbology() + : this(MsiCheckDigit.None, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The check digits the symbol carries after its data. + public MsiSymbology(MsiCheckDigit checkDigit) + : this(checkDigit, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The check digits the symbol carries after its data. + /// Whether the check digits the symbol carries are part of the human readable interpretation. + public MsiSymbology(MsiCheckDigit checkDigit, bool printCheckDigits) + { + this.CheckDigit = checkDigit; + this.PrintCheckDigits = printCheckDigits; + } + + /// + /// Gets the check digits the symbol carries after its data. + /// + public MsiCheckDigit CheckDigit { get; } + + /// + /// Gets a value indicating whether the check digits the symbol carries are part of the human readable interpretation. A symbol that carries none prints + /// none either way. + /// + public bool PrintCheckDigits { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Guard.MustBeGreaterThan(text.Length, 0, nameof(text)); + Guard.MustBeLessThanOrEqualTo(text.Length, MsiEncoder.MaximumLength, nameof(text)); + + BarcodeSymbology.ValidateDigits(text); + + // The symbol carries the digits and at most two check digits. The text is at most MaximumLength digits, so a short symbol works inline and a longer one + // rents its digits once. + InlineArray64 carriedStorage = default; + int capacity = text.Length + MsiEncoder.MaximumCheckDigits; + ValueStringBuilder carried = capacity <= MsiEncoder.InlineBufferLength ? new(carriedStorage) : new(capacity); + try + { + carried.Append(text); + switch (this.CheckDigit) + { + case MsiCheckDigit.Modulo10: + carried.Append((char)('0' + MsiEncoder.Modulo10(text))); + break; + + case MsiCheckDigit.Modulo1010: + carried.Append((char)('0' + MsiEncoder.Modulo10(text))); + carried.Append((char)('0' + MsiEncoder.Modulo10(carried.AsSpan()))); + break; + + case MsiCheckDigit.Modulo11: + case MsiCheckDigit.Modulo1110: + case MsiCheckDigit.NcrModulo11: + case MsiCheckDigit.NcrModulo1110: + int maximumWeight = this.CheckDigit is MsiCheckDigit.Modulo11 or MsiCheckDigit.Modulo1110 + ? MsiEncoder.IbmMaximumWeight + : MsiEncoder.NcrMaximumWeight; + + int check = BarcodeSymbology.Modulo11(text, maximumWeight); + if (check == 10) + { + throw new ArgumentException("The modulo 11 check value of this data is 10, which one digit cannot carry.", nameof(text)); + } + + carried.Append((char)('0' + check)); + if (this.CheckDigit is MsiCheckDigit.Modulo1110 or MsiCheckDigit.NcrModulo1110) + { + carried.Append((char)('0' + MsiEncoder.Modulo10(carried.AsSpan()))); + } + + break; + } + + ReadOnlySpan digits = carried.AsSpan(); + MsiEncoder.BuildSymbol(digits, this.PrintCheckDigits ? digits : text, options, ref symbol); + } + finally + { + carried.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Pdf417/CompactPdf417Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Pdf417/CompactPdf417Symbology.cs new file mode 100644 index 00000000..0abb8a1b --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Pdf417/CompactPdf417Symbology.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Compact PDF417 symbology, Annex G of ISO/IEC 15438. A symbol is a PDF417 symbol whose rows omit the right row indicator and end with a single module bar +/// in place of the stop character. The bar height of the options sets each row, and section 5.8.2 requires "Y >= 3X", so a bar height under 3X draws at 3X. +/// +public sealed class CompactPdf417Symbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. The number of data columns follows from the data. The error correction + /// level is the recommended minimum of Annex E of ISO/IEC 15438 for the data, raised to the highest level the rows leave room for. Table E.1 ends at 863 + /// data codewords, and more data takes the highest level that fits. + /// + public CompactPdf417Symbology() + { + } + + /// + /// Initializes a new instance of the class. The error correction level is the recommended minimum of Annex E of + /// ISO/IEC 15438 for the data, raised to the highest level the rows leave room for. Table E.1 ends at 863 data codewords, and more data takes the highest + /// level that fits. + /// + /// The number of data columns in a row, 1 to 30. The row count follows from the data. + public CompactPdf417Symbology(int columns) + { + Guard.MustBeBetweenOrEqualTo(columns, Pdf417Encoder.MinimumColumns, Pdf417Encoder.MaximumColumns, nameof(columns)); + + this.Columns = columns; + } + + /// + /// Initializes a new instance of the class. + /// + /// The number of data columns in a row, 1 to 30. The row count follows from the data. + /// The error correction level, 0 to 8, which gives 2 to 512 error correction codewords. + public CompactPdf417Symbology(int columns, int errorCorrectionLevel) + : this(columns) + { + Guard.MustBeBetweenOrEqualTo(errorCorrectionLevel, 0, Pdf417Encoder.MaximumErrorCorrectionLevel, nameof(errorCorrectionLevel)); + + this.ErrorCorrectionLevel = errorCorrectionLevel; + } + + /// + /// Gets the number of data columns in a row, or when it follows from the data. + /// + public int? Columns { get; } + + /// + /// Gets the error correction level, or when it follows from the data. + /// + public int? ErrorCorrectionLevel { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Pdf417Encoder.BuildSymbol(text, this.Columns, this.ErrorCorrectionLevel, true, options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Pdf417/HibcMicroPdf417Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Pdf417/HibcMicroPdf417Symbology.cs new file mode 100644 index 00000000..f8d0d8f7 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Pdf417/HibcMicroPdf417Symbology.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// HIBC MicroPDF417, a Health Industry Bar Code carried by a MicroPDF417 symbol. The symbol carries the flag character, the data and the modulo 43 check +/// character. +/// The text follows a data structure of ANSI/HIBC 2.6: primary data, secondary data that ends with its link character, or primary and secondary data joined +/// with "/", each with any supplemental data fields. +/// +/// When a font is set, that same string prints below the symbol between delimiters, with a space printed as an underscore and a zero as "Ø". Primary and +/// secondary data that the symbol joins with "/" print on two lines, broken after the "/". +/// +/// +public sealed class HibcMicroPdf417Symbology : BarcodeSymbology +{ + /// + /// The number of data columns when the caller sets none. + /// + private const int DefaultColumns = 2; + + /// + /// Initializes a new instance of the class with two data columns, calculating the check character from the data. + /// + public HibcMicroPdf417Symbology() + : this(DefaultColumns, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The number of data columns, 1 to 4. The symbol takes the first size of that many columns that holds the data. + /// Whether the last character of the input is the check character, which the encoder validates against the data, + /// rather than data the encoder appends a check character to. + public HibcMicroPdf417Symbology(int columns, bool validateCheckCharacter) + { + Guard.MustBeBetweenOrEqualTo(columns, MicroPdf417Encoder.MinimumColumns, MicroPdf417Encoder.MaximumColumns, nameof(columns)); + + this.Columns = columns; + this.ValidateCheckCharacter = validateCheckCharacter; + } + + /// + /// Gets the number of data columns. + /// + public int Columns { get; } + + /// + /// Gets a value indicating whether the last character of the input is the check character. + /// + public bool ValidateCheckCharacter { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray64 bufferStorage = default; + int capacity = HibcData.PreparedCapacity(text, Pdf417Codewords.MicroMaximumLength); + ValueStringBuilder encoded = capacity <= HibcData.InlineBufferLength ? new(bufferStorage) : new(capacity); + try + { + HibcData.Prepare(text, this.ValidateCheckCharacter, ref encoded); + + MicroPdf417Encoder.BuildSymbol(encoded.AsSpan(), this.Columns, options, ref symbol); + if (options.Font is not null) + { + HibcData.AddTwoDimensionalReadable(encoded.AsSpan(), true, ref symbol); + } + } + finally + { + encoded.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Pdf417/HibcPdf417Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Pdf417/HibcPdf417Symbology.cs new file mode 100644 index 00000000..850de835 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Pdf417/HibcPdf417Symbology.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// HIBC PDF417, a Health Industry Bar Code carried by a PDF417 symbol. The symbol carries the flag character, the data and the modulo 43 check character. +/// The text follows a data structure of ANSI/HIBC 2.6: primary data, secondary data that ends with its link character, or primary and secondary data joined +/// with "/", each with any supplemental data fields. The bar height of the options sets each row, and section 5.8.2 of ISO/IEC 15438 requires "Y >= 3X", so a +/// bar height under 3X draws at 3X. +/// +/// When a font is set, that same string prints below the symbol between delimiters, with a space printed as an underscore and a zero as "Ø". Primary and +/// secondary data that the symbol joins with "/" print on two lines, broken after the "/". +/// +/// +public sealed class HibcPdf417Symbology : BarcodeSymbology +{ + /// + /// The number of data columns when the caller sets none. + /// + private const int DefaultColumns = 2; + + /// + /// Initializes a new instance of the class with two data columns, calculating the check character from the data. + /// + public HibcPdf417Symbology() + : this(DefaultColumns, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The number of data columns in a row, 1 to 30. The row count follows from the data. + /// Whether the last character of the input is the check character. When , the encoder validates + /// this character against the data. Otherwise, the encoder appends a check character to the data. + public HibcPdf417Symbology(int columns, bool validateCheckCharacter) + { + Guard.MustBeBetweenOrEqualTo(columns, Pdf417Encoder.MinimumColumns, Pdf417Encoder.MaximumColumns, nameof(columns)); + + this.Columns = columns; + this.ValidateCheckCharacter = validateCheckCharacter; + } + + /// + /// Gets the number of data columns in a row. + /// + public int Columns { get; } + + /// + /// Gets a value indicating whether the last character of the input is the check character. + /// + public bool ValidateCheckCharacter { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray64 bufferStorage = default; + int capacity = HibcData.PreparedCapacity(text, Pdf417Codewords.MaximumLength); + ValueStringBuilder encoded = capacity <= HibcData.InlineBufferLength ? new(bufferStorage) : new(capacity); + try + { + HibcData.Prepare(text, this.ValidateCheckCharacter, ref encoded); + + Pdf417Encoder.BuildSymbol(encoded.AsSpan(), this.Columns, null, false, options, ref symbol); + if (options.Font is not null) + { + HibcData.AddTwoDimensionalReadable(encoded.AsSpan(), true, ref symbol); + } + } + finally + { + encoded.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Pdf417/MicroPdf417Encoder.cs b/src/ImageSharp.Drawing/Barcodes/Pdf417/MicroPdf417Encoder.cs new file mode 100644 index 00000000..cdbb48f7 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Pdf417/MicroPdf417Encoder.cs @@ -0,0 +1,425 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Builds MicroPDF417 symbols: 1 to 4 data columns between row address patterns, in one of 34 fixed sizes. The symbol characters and the error correction are +/// those of PDF417, and the rows carry no start or stop character and no row indicators. +/// +internal static class MicroPdf417Encoder +{ + /// + /// The fewest data columns of a symbol. + /// + public const int MinimumColumns = 1; + + /// + /// The most data columns of a symbol. + /// + public const int MaximumColumns = 4; + + /// + /// The row height in modules, nominal and smallest. Section 5.8.2 of ISO/IEC 24728: "The row height (Y) shall conform to: Y ≥ 2X". The section gives no + /// largest row height. + /// + private const float NominalRowHeight = 2F; + + /// + /// The quiet zone on each of the four sides in modules. + /// + private const int QuietZone = 1; + + /// + /// The number of entries in each of the two row address pattern tables. + /// + private const int AddressPatternCount = 52; + + /// + /// The number of modules in a row address pattern. + /// + private const int AddressPatternModules = 10; + + /// + /// The number of elements in a row address pattern, three bars and three spaces. + /// + private const int AddressPatternElements = 6; + + /// + /// Gets the 34 symbol sizes in the order the encoder tries them, one of six values each: the data columns, the rows, the error + /// correction codewords, and the first left, centre and right row address patterns. Only symbols of three or four columns carry a centre pattern. + /// + private static ReadOnlySpan Sizes => + [ + 1, 11, 7, 1, 0, 9, + 1, 14, 7, 8, 0, 8, + 1, 17, 7, 36, 0, 36, + 1, 20, 8, 19, 0, 19, + 1, 24, 8, 9, 0, 17, + 1, 28, 8, 25, 0, 33, + 2, 8, 8, 1, 0, 1, + 2, 11, 9, 1, 0, 9, + 2, 14, 9, 8, 0, 8, + 2, 17, 10, 36, 0, 36, + 2, 20, 11, 19, 0, 19, + 2, 23, 13, 9, 0, 17, + 2, 26, 15, 27, 0, 35, + 3, 6, 12, 1, 1, 1, + 3, 8, 14, 7, 7, 7, + 3, 10, 16, 15, 15, 15, + 3, 12, 18, 25, 25, 25, + 3, 15, 21, 37, 37, 37, + 3, 20, 26, 1, 17, 33, + 3, 26, 32, 1, 9, 17, + 3, 32, 38, 21, 29, 37, + 3, 38, 44, 15, 31, 47, + 3, 44, 50, 1, 25, 49, + 4, 4, 8, 47, 19, 43, + 4, 6, 12, 1, 1, 1, + 4, 8, 14, 7, 7, 7, + 4, 10, 16, 15, 15, 15, + 4, 12, 18, 25, 25, 25, + 4, 15, 21, 37, 37, 37, + 4, 20, 26, 1, 17, 33, + 4, 26, 32, 1, 9, 17, + 4, 32, 38, 21, 29, 37, + 4, 38, 44, 15, 31, 47, + 4, 44, 50, 1, 25, 49, + ]; + + /// + /// Gets the 52 left and right row address patterns, each the 10 modules of three bars and three spaces with the first module in bit 9. + /// + private static ReadOnlySpan SideAddressPatterns => + [ + 802, 930, 946, 818, 882, 890, 826, 954, 922, 986, 970, 906, 778, + 794, 786, 914, 978, 982, 980, 916, 948, 932, 934, 942, 940, 936, + 808, 812, 814, 806, 822, 950, 918, 790, 788, 820, 884, 868, 870, + 878, 876, 872, 840, 856, 860, 862, 846, 844, 836, 838, 834, 866, + ]; + + /// + /// Gets the 52 centre row address patterns, each the 10 modules of three bars and three spaces with the first module in bit 9. + /// + private static ReadOnlySpan CentreAddressPatterns => + [ + 718, 590, 622, 558, 550, 566, 534, 530, 538, 570, 562, 546, 610, + 626, 634, 762, 754, 758, 630, 628, 612, 614, 582, 578, 706, 738, + 742, 740, 748, 620, 556, 552, 616, 744, 712, 716, 708, 710, 646, + 654, 652, 668, 664, 696, 688, 656, 720, 592, 600, 604, 732, 734, + ]; + + /// + /// Encodes the text into a symbol. + /// + /// The data, the characters U+0000 to U+00FF. + /// The number of data columns, 1 to 4, or to choose them from the data. + /// The options that control layout choices. + /// The symbol to write. + /// The data is not valid or does not fit the symbol. + public static void BuildSymbol(ReadOnlySpan text, int? columns, BarcodeOptions options, ref BarcodeSymbol symbol) + { + // A character takes at most two data codewords, as in PDF417, and a message format takes one macro codeword more. The text is bounded, so the builder + // never grows: short data works inline and longer data rents once. + Guard.MustBeLessThanOrEqualTo(text.Length, Pdf417Codewords.MicroMaximumLength, nameof(text)); + InlineArray64 dataStorage = default; + int capacity = (2 * text.Length) + 2; + ValueListBuilder data = capacity <= Pdf417Codewords.InlineBufferLength ? new(dataStorage) : new(capacity); + try + { + Pdf417Codewords.EncodeMicro(text, ref data); + BuildSymbol(data.AsSpan(), columns, options, ref symbol); + } + finally + { + data.Dispose(); + } + } + + /// + /// Returns the number of data codewords of a symbol size: its matrix less its error correction codewords. + /// + /// The number of data columns of the size. + /// The number of rows of the size. + /// The number of data codewords. + public static int DataCodewords(int columns, int rows) + { + SymbolSize size = FindSize(Sizes, columns, rows); + return (size.Columns * size.Rows) - size.ErrorCorrection; + } + + /// + /// Builds a symbol of one size from its data codewords. Section 9 of ISO/IEC 24723 builds the CC-B 2D component of a GS1 Composite symbol this way, at the + /// size its bit stream fills. + /// + /// The data codewords, at most of the size. + /// The number of data columns of the size. + /// The number of rows of the size. + /// The X dimension in millimetres. + /// The height of a row in modules. + /// The quiet zone above and below the symbol in modules. + /// The symbol to write. + public static void BuildSymbol( + ReadOnlySpan data, + int columns, + int rows, + double xDimension, + float rowHeight, + float verticalQuietZone, + ref BarcodeSymbol symbol) + => BuildMatrix(data, FindSize(Sizes, columns, rows), xDimension, rowHeight, verticalQuietZone, ref symbol); + + /// + /// Returns the symbol size of a number of columns and rows in a table of sizes, bytes each. The caller gives a size of the + /// table. + /// + /// The table of sizes: , or the CC-A sizes of a GS1 Composite symbol. + /// The number of data columns. + /// The number of rows. + /// The size. + public static SymbolSize FindSize(ReadOnlySpan sizes, int columns, int rows) + { + int offset = 0; + while (sizes[offset] != columns || sizes[offset + 1] != rows) + { + offset += SymbolSize.Length; + } + + return new SymbolSize(sizes.Slice(offset, SymbolSize.Length)); + } + + /// + /// Builds the symbol from its data codewords in the first size that holds them. + /// + /// The data codewords. + /// The number of data columns, 1 to 4, or to choose them from the data. + /// The options that control layout choices. + /// The symbol to write. + /// The data does not fit the symbol. + private static void BuildSymbol(ReadOnlySpan data, int? columns, BarcodeOptions options, ref BarcodeSymbol symbol) + { + ReadOnlySpan sizes = Sizes; + int offset = 0; + while (offset < sizes.Length && !new SymbolSize(sizes.Slice(offset, SymbolSize.Length)).Holds(data.Length, columns)) + { + offset += SymbolSize.Length; + } + + if (offset == sizes.Length) + { + throw new ArgumentException(columns.HasValue + ? $"The data takes {data.Length} codewords and does not fit in a MicroPDF417 symbol of {columns.Value} columns." + : $"The data takes {data.Length} codewords and does not fit in a MicroPDF417 symbol."); + } + + // Section 5.8.1: the X dimension "should be defined by the application specification", so the symbology gives no limit. + double xDimension = options.ResolveXDimension(0, BarcodeSymbology.DefaultXDimension, double.PositiveInfinity); + float rowHeight = options.ResolveBarHeight(xDimension, NominalRowHeight, NominalRowHeight, float.PositiveInfinity); + BuildMatrix(data, new SymbolSize(sizes.Slice(offset, SymbolSize.Length)), xDimension, rowHeight, QuietZone, ref symbol); + } + + /// + /// Builds a symbol of one size from its data codewords: the data, the pad codewords, the error correction codewords and then the rows. + /// + /// The data codewords, at most the data codewords of the size. + /// The symbol size. + /// The X dimension in millimetres. + /// The height of a row in modules. + /// The quiet zone above and below the symbol in modules. + /// The symbol to write. + private static void BuildMatrix( + ReadOnlySpan data, + SymbolSize size, + double xDimension, + float rowHeight, + float verticalQuietZone, + ref BarcodeSymbol symbol) + { + // The data codewords, the pad codewords that fill the data region with 900, and the error correction codewords. A MicroPDF417 symbol has no Symbol + // Length Descriptor. A symbol holds at most 176 codewords, four columns of 44 rows, so they fit inline. + int k = size.ErrorCorrection; + InlineArray256 codewordStorage = default; + Span codewords = ((Span)codewordStorage)[..(size.Columns * size.Rows)]; + data.CopyTo(codewords); + codewords[data.Length..^k].Fill(Pdf417Codewords.Pad); + + // The generator coefficients take one more than the error correction codewords. A size of Sizes has at most 50 error correction codewords, so the + // coefficients fit inline. + InlineArray64 alphaStorage = default; + Pdf417Codewords.AppendErrorCorrection(codewords, k, alphaStorage); + + AddressPatterns patterns = new(size.LeftPattern, size.CentrePattern, size.RightPattern, true); + BuildRows(codewords, size.Columns, size.Rows, patterns, xDimension, rowHeight, verticalQuietZone, ref symbol); + } + + /// + /// Builds the rows of a MicroPDF417 symbol or of the 2D component of a GS1 Composite symbol, whose rows section 8.2 of ISO/IEC 24723 takes from + /// MicroPDF417: the left row address pattern, if the symbol has one, the codewords of the row with the centre row address pattern after the first column of + /// three or the second of four, the right row address pattern and a single module bar. + /// + /// The codewords in row order, error correction codewords included. + /// The number of data columns, 1 to 4. + /// The number of rows. + /// The row address patterns of the first row. + /// The X dimension in millimetres. + /// The height of a row in modules. + /// The quiet zone above and below the symbol in modules. + /// The symbol to write. + public static void BuildRows( + ReadOnlySpan codewords, + int columns, + int rows, + AddressPatterns patterns, + double xDimension, + float rowHeight, + float verticalQuietZone, + ref BarcodeSymbol symbol) + { + int c = columns; + bool hasCentre = c >= 3; + int centreAfter = c - 2; + int runCount = ((patterns.HasLeft ? 2 : 1) * AddressPatternElements) + (c * Pdf417Patterns.ElementCount) + 1 + (hasCentre ? AddressPatternElements : 0); + + ReadOnlySpan characters = Pdf417Patterns.Modules; + symbol.BeginStacked(rows, rows * runCount, rows * ((runCount + 1) >> 1), xDimension); + for (int row = 0; row < rows; row++) + { + // Each row address pattern steps through its table from the first one of the size, and the cluster of the row follows the left pattern: every third + // pattern shares one of the three clusters. A symbol without a left pattern takes the cluster of the left pattern its row would have. + int leftIndex = (row + patterns.Left - 1) % AddressPatternCount; + int cluster = (row + patterns.Left - 1) % 3; + ReadOnlySpan clusterPatterns = characters.Slice(cluster * Pdf417Patterns.CodewordCount, Pdf417Patterns.CodewordCount); + + Span runs = symbol.AddRow(runCount, out Span heights, out _); + heights.Fill(rowHeight); + int index = 0; + if (patterns.HasLeft) + { + Pdf417Patterns.AppendElements(runs, ref index, SideAddressPatterns[leftIndex], AddressPatternModules, AddressPatternElements); + } + + for (int column = 0; column < c; column++) + { + if (hasCentre && column == centreAfter) + { + int centreModules = CentreAddressPatterns[(row + patterns.Centre - 1) % AddressPatternCount]; + Pdf417Patterns.AppendElements(runs, ref index, centreModules, AddressPatternModules, AddressPatternElements); + } + + Pdf417Patterns.AppendCharacter(runs, ref index, clusterPatterns[codewords[(row * c) + column]]); + } + + int rightModules = SideAddressPatterns[(row + patterns.Right - 1) % AddressPatternCount]; + Pdf417Patterns.AppendElements(runs, ref index, rightModules, AddressPatternModules, AddressPatternElements); + runs[index] = 1; + } + + symbol.EndStacked(0F, QuietZone, QuietZone, verticalQuietZone, verticalQuietZone); + } + + /// + /// The row address patterns of the first row of a symbol, each counted from 1. Every row after it takes the next pattern of each table. + /// + public readonly struct AddressPatterns + { + /// + /// Initializes a new instance of the struct. + /// + /// The left row address pattern of the first row. A symbol without a left pattern gives the one its first row would have, which + /// sets the cluster of the row. + /// The centre row address pattern of the first row, or 0 for a symbol of one or two columns. + /// The right row address pattern of the first row. + /// Whether the rows draw the left row address pattern. + public AddressPatterns(int left, int centre, int right, bool hasLeft) + { + this.Left = left; + this.Centre = centre; + this.Right = right; + this.HasLeft = hasLeft; + } + + /// + /// Gets the left row address pattern of the first row. + /// + public int Left { get; } + + /// + /// Gets the centre row address pattern of the first row, or 0 for a symbol of one or two columns. + /// + public int Centre { get; } + + /// + /// Gets the right row address pattern of the first row. + /// + public int Right { get; } + + /// + /// Gets a value indicating whether the rows draw the left row address pattern. The 3-column 2D component of a GS1 Composite symbol omits it. + /// + public bool HasLeft { get; } + } + + /// + /// One MicroPDF417 symbol size: a view of the six bytes of an entry of , or of the CC-A sizes of a GS1 Composite symbol. + /// + public readonly ref struct SymbolSize + { + /// + /// The number of bytes of an entry of . + /// + public const int Length = 6; + + /// + /// The six bytes of the entry. + /// + private readonly ReadOnlySpan entry; + + /// + /// Initializes a new instance of the struct. + /// + /// The six bytes of the entry. + public SymbolSize(ReadOnlySpan entry) => this.entry = entry; + + /// + /// Gets the number of data columns. + /// + public int Columns => this.entry[0]; + + /// + /// Gets the number of rows. + /// + public int Rows => this.entry[1]; + + /// + /// Gets the number of error correction codewords. + /// + public int ErrorCorrection => this.entry[2]; + + /// + /// Gets the left row address pattern of the first row, counted from 1. + /// + public int LeftPattern => this.entry[3]; + + /// + /// Gets the centre row address pattern of the first row, counted from 1, or 0 for a symbol of one or two columns. + /// + public int CentrePattern => this.entry[4]; + + /// + /// Gets the right row address pattern of the first row, counted from 1. + /// + public int RightPattern => this.entry[5]; + + /// + /// Returns whether the size holds the data codewords: it has the requested columns, if any, and its matrix less the error correction codewords holds + /// the data. + /// + /// The number of data codewords. + /// The requested number of data columns, or for any. + /// when the size holds the data. + public bool Holds(int dataLength, int? columns) + => (!columns.HasValue || columns.Value == this.Columns) && dataLength <= (this.Columns * this.Rows) - this.ErrorCorrection; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Pdf417/MicroPdf417Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Pdf417/MicroPdf417Symbology.cs new file mode 100644 index 00000000..0b28650c --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Pdf417/MicroPdf417Symbology.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The MicroPDF417 symbology: 1 to 4 data columns in one of 34 fixed sizes, each row between row address patterns. It carries the characters U+0000 to U+00FF +/// with the symbol characters and the error correction of PDF417. +/// +public sealed class MicroPdf417Symbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. The symbol takes the first size that holds the data. + /// + public MicroPdf417Symbology() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The number of data columns, 1 to 4. The symbol takes the first size of that many columns that holds the data. + public MicroPdf417Symbology(int columns) + { + Guard.MustBeBetweenOrEqualTo(columns, MicroPdf417Encoder.MinimumColumns, MicroPdf417Encoder.MaximumColumns, nameof(columns)); + + this.Columns = columns; + } + + /// + /// Gets the number of data columns, or when it follows from the data. + /// + public int? Columns { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + MicroPdf417Encoder.BuildSymbol(text, this.Columns, options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Pdf417/Pdf417Codewords.cs b/src/ImageSharp.Drawing/Barcodes/Pdf417/Pdf417Codewords.cs new file mode 100644 index 00000000..962a8b36 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Pdf417/Pdf417Codewords.cs @@ -0,0 +1,785 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The high level encodation and the error correction codewords of ISO/IEC 15438, sections 5.4 and 5.10, which PDF417 and MicroPDF417 share. The data is a +/// sequence of byte values: the characters U+0000 to U+00FF are the byte values of the default character set of Annex B. +/// +internal static class Pdf417Codewords +{ + /// + /// The largest number of characters a symbol carries: 2 710 digits in Numeric Compaction mode at error correction level 0, section 5.1.1 c). + /// + public const int MaximumLength = 2710; + + /// + /// The largest number of characters a MicroPDF417 symbol carries. + /// + public const int MicroMaximumLength = 366; + + /// + /// The number of codewords a caller holds inline for to build in. Data this long covers short labels, and longer data rents its buffer + /// once. + /// + public const int InlineBufferLength = 64; + + /// + /// The codeword that pads the data codewords to fill the symbol matrix, section 5.9.2. + /// + public const int Pad = 900; + + /// + /// The mode latch to Byte Compaction mode for a byte count that is not a multiple of six, Table 2. + /// + private const int ByteLatch = 901; + + /// + /// The mode latch to Byte Compaction mode for a byte count that is a multiple of six, Table 2. + /// + private const int ByteLatchSix = 924; + + /// + /// The modulus of the error correction arithmetic, section 5.10. + /// + private const int Modulus = 929; + + /// + /// The mode latch to Text Compaction mode, Table 2. + /// + private const int TextLatch = 900; + + /// + /// The mode latch to Numeric Compaction mode, Table 2. + /// + private const int NumericLatch = 902; + + /// + /// The mode shift to Byte Compaction mode for one byte, Table 2. + /// + private const int ByteShift = 913; + + /// + /// The MicroPDF417 codeword that stands for the header [)>RS05GS and the trailer RS EOT of a message in format 05, and that puts the encoder in Numeric + /// Compaction mode. + /// + private const int Macro05 = 916; + + /// + /// The MicroPDF417 codeword that stands for the header [)>RS06GS and the trailer RS EOT of a message in format 06, and that puts the encoder in Text + /// Compaction mode, Mixed sub-mode. + /// + private const int Macro06 = 917; + + /// + /// The number of digits from which a run of digits encodes in Numeric Compaction mode, Annex N step 4. + /// + private const int NumericRun = 13; + + /// + /// The number of digits from which data that is all digits encodes in Numeric Compaction mode. + /// + private const int WholeNumericRun = 8; + + /// + /// The number of characters from which a run of text characters encodes in Text Compaction mode, Annex N step 11. + /// + private const int TextRun = 5; + + /// + /// The largest number of digits one base 10 to base 900 conversion takes, section 5.4.4.2. + /// + private const int NumericGroup = 44; + + /// + /// The number of bytes one base 256 to base 900 conversion takes, section 5.4.3.3. + /// + private const int ByteGroup = 6; + + /// + /// The number of codewords one conversion of bytes gives, section 5.4.3.3. + /// + private const int ByteGroupCodewords = 5; + + /// + /// The base of Text Compaction mode values, section 5.4.2.2. + /// + private const int TextBase = 30; + + /// + /// The base of the data codewords, section 5.4. + /// + private const int CodewordBase = 900; + + /// + /// The number of Text Compaction sub-modes. + /// + private const int SubModes = 4; + + /// + /// The number of characters each sub-mode has an entry for in . + /// + private const int TextTableWidth = 128; + + /// + /// The base 30 value that pads an odd count of values, section 5.4.2.4: the latch to Alpha in the Punctuation sub-mode, which takes effect, and the shift + /// to Punctuation in the other sub-modes, which has no character after it. Table 5 gives both the value 29. + /// + private const int PadValue = 29; + + /// + /// The cost of a sub-mode that the encoder cannot reach. It leaves room to add a latch or a shift without overflow. + /// + private const int Unreachable = int.MaxValue / 2; + + /// + /// The compaction modes of section 5.4. + /// + private enum CompactionMode + { + /// + /// Text Compaction mode, section 5.4.2. + /// + Text, + + /// + /// Numeric Compaction mode, section 5.4.4. + /// + Numeric, + + /// + /// Byte Compaction mode, section 5.4.3. + /// + Byte, + } + + /// + /// The sub-modes of Text Compaction mode, Table 5. The values index the sub-mode tables. + /// + private enum SubMode + { + /// + /// No sub-mode, in the search tables where a sub-mode took no latch or no shift. + /// + None = -1, + + /// + /// The Alpha sub-mode: the capital letters and the space. + /// + Alpha, + + /// + /// The Lower sub-mode: the small letters and the space. + /// + Lower, + + /// + /// The Mixed sub-mode: the digits, the space and some punctuation. + /// + Mixed, + + /// + /// The Punctuation sub-mode. + /// + Punctuation, + } + + /// + /// Gets the Text Compaction values of Table 5 of the characters 0 to 127 in each sub-mode, at index (subMode * 128) + character, or -1 where the + /// sub-mode has no such character. The four blocks of 128 entries are the Alpha, Lower, Mixed and Punctuation sub-modes in turn. + /// + private static ReadOnlySpan TextValues => + [ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 26, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 26, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 12, -1, -1, -1, 11, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 26, -1, -1, 15, 18, 21, 10, -1, -1, -1, 22, 20, 13, 16, 17, 19, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 14, -1, -1, 23, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 24, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 12, 15, -1, -1, 11, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 20, -1, 18, -1, -1, 28, 23, 24, 22, -1, 13, 16, 17, 19, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 14, 0, 1, -1, 2, 25, + 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, 5, 6, -1, 7, + 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, 21, 27, 9, -1, + ]; + + /// + /// Encodes the data into data codewords, without the Symbol Length Descriptor. Digit runs encode in Numeric Compaction mode, text runs in Text Compaction + /// mode and the rest in Byte Compaction mode, following the run lengths of Annex N. A text run takes the fewest Text Compaction values its sub-mode latches + /// and shifts allow. + /// + /// The data, the characters U+0000 to U+00FF. + /// The builder the data codewords are written to. + /// The data is empty, too long or carries a character above U+00FF. + public static void Encode(ReadOnlySpan text, ref ValueListBuilder codewords) + { + Guard.MustBeGreaterThan(text.Length, 0, nameof(text)); + Guard.MustBeLessThanOrEqualTo(text.Length, MaximumLength, nameof(text)); + + // Section 5.4.2.1: every symbol starts in Text Compaction mode, Alpha sub-mode. + EncodeFrom(text, CompactionMode.Text, 0, ref codewords); + } + + /// + /// Encodes the data into MicroPDF417 data codewords. The encodation is the one of , except that a symbol starts in Byte Compaction + /// mode, so the first run always takes its latch. A message in format 05 or 06, between the header [)>RS05GS or [)>RS06GS and the trailer RS EOT, + /// takes one codeword for the header and the trailer, which also stands for the latch to the mode the format starts in. + /// + /// The data, the characters U+0000 to U+00FF. + /// The builder the data codewords are written to. + /// The data is empty, too long or carries a character above U+00FF. + public static void EncodeMicro(ReadOnlySpan text, ref ValueListBuilder codewords) + { + Guard.MustBeGreaterThan(text.Length, 0, nameof(text)); + Guard.MustBeLessThanOrEqualTo(text.Length, MicroMaximumLength, nameof(text)); + + int macro = 0; + int format = MacroMessage.Format(text); + if (format != 0) + { + macro = format == 5 ? Macro05 : Macro06; + codewords.Append(macro); + text = text[MacroMessage.HeaderLength..^MacroMessage.TrailerLength]; + } + + EncodeFrom(text, CompactionMode.Byte, macro, ref codewords); + } + + /// + /// Calculates the error correction codewords of section 5.10 over the data codewords and writes them after the data, the most significant first. + /// + /// The data codewords followed by room for the error correction codewords. + /// The number of error correction codewords, 2 to 512. + /// The scratch for the generator coefficients, at least plus one long. + public static void AppendErrorCorrection(Span codewords, int count, Span alpha) + { + // The generator polynomial g(x) = (x - 3)(x - 3^2) ... (x - 3^k) of section 5.10, expanded one factor at a time. Multiplying by (x - r) gives each + // coefficient as the coefficient below it less r times itself, so the update runs from the top down in place. The coefficient of x^k is 1. Each factor + // reads the coefficient above the product before it writes it, so the coefficients start zero. + alpha = alpha[..(count + 1)]; + alpha.Clear(); + alpha[0] = 1; + int root = 1; + for (int degree = 1; degree <= count; degree++) + { + root = (root * 3) % Modulus; + int negated = Modulus - root; + for (int j = degree; j > 0; j--) + { + alpha[j] = (alpha[j - 1] + (negated * alpha[j])) % Modulus; + } + + alpha[0] = (negated * alpha[0]) % Modulus; + } + + // The division of d(x) x^k by g(x) of section 5.10, step 3, with the error correction codewords E(k-1) to E(0) as the register. The register lives in + // place of the error correction codewords, E(j) at index dataCount + k - 1 - j, so the codewords come out most significant first. + int dataCount = codewords.Length - count; + Span register = codewords[dataCount..]; + register.Clear(); + for (int i = 0; i < dataCount; i++) + { + int t1 = (codewords[i] + register[0]) % Modulus; + for (int j = count - 1; j > 0; j--) + { + int t3 = Modulus - ((t1 * alpha[j]) % Modulus); + register[count - 1 - j] = (register[count - j] + t3) % Modulus; + } + + register[count - 1] = (Modulus - ((t1 * alpha[0]) % Modulus)) % Modulus; + } + + // Step 4: each codeword is the complement of its register value. + foreach (ref int value in register) + { + if (value != 0) + { + value = Modulus - value; + } + } + } + + /// + /// Returns the mode latch to Byte Compaction mode for a run of bytes, Table 2: 924 when the byte count is a multiple of six, and 901 otherwise. + /// + /// The number of bytes of the run. + /// The latch codeword. + public static int ByteLatchFor(int byteCount) => byteCount % ByteGroup == 0 ? ByteLatchSix : ByteLatch; + + /// + /// Returns the number of codewords Byte Compaction mode takes for a number of bytes: five per group of six, and one per byte after the last group. + /// + /// The number of bytes. + /// The number of codewords, the latch excluded. + public static int ByteCodewords(int bytes) => (ByteGroupCodewords * (bytes / ByteGroup)) + (bytes % ByteGroup); + + /// + /// Returns the number of bytes Byte Compaction mode carries in a number of codewords: six per group of five, and one per codeword after the last group. + /// + /// The number of codewords, the latch excluded. + /// The number of bytes. + public static int ByteCapacity(int codewords) => (ByteGroup * (codewords / ByteGroupCodewords)) + (codewords % ByteGroupCodewords); + + /// + /// Encodes a run of bytes in Byte Compaction mode, section 5.4.3.3: each group of six bytes converts from base 256 to five base 900 codewords as Annex C + /// gives, and the bytes after the last full group take one codeword each. + /// + /// The type of the bytes: a byte, or a character U+0000 to U+00FF. + /// The bytes. + /// The builder the codewords are written to. + public static void EncodeBytes(ReadOnlySpan bytes, ref ValueListBuilder codewords) + where T : unmanaged, IBinaryInteger + { + int position = 0; + for (; position + ByteGroup <= bytes.Length; position += ByteGroup) + { + // Six bytes are at most 2^48 - 1, which a long holds. The five codewords come out of the division the least significant first and are written the + // most significant first. + long value = 0; + foreach (T b in bytes.Slice(position, ByteGroup)) + { + value = (value << 8) | long.CreateTruncating(b); + } + + Span group = codewords.AppendSpan(ByteGroupCodewords); + for (int i = ByteGroupCodewords - 1; i >= 0; i--) + { + group[i] = (int)(value % CodewordBase); + value /= CodewordBase; + } + } + + foreach (T b in bytes[position..]) + { + codewords.Append(int.CreateTruncating(b)); + } + } + + /// + /// Encodes the data into data codewords from the given compaction mode. + /// + /// The data, the characters U+0000 to U+00FF. + /// The compaction mode the symbol starts in. + /// The MicroPDF417 macro codeword already written in front of the data, or 0. + /// The builder the data codewords are written to. + /// The data carries a character above U+00FF. + private static void EncodeFrom(ReadOnlySpan text, CompactionMode mode, int macro, ref ValueListBuilder codewords) + { + int invalid = text.IndexOfAnyExceptInRange('\0', 'ÿ'); + if (invalid >= 0) + { + throw new ArgumentException( + $"PDF417 carries only the characters U+0000 to U+00FF; got {text.CodePointAt(invalid).ToDisplayString()}.", + nameof(text)); + } + + // The run tables take length + 1 entries each, the last one the zero that ends every run. The text search takes one step per sub-mode and character, + // two integers each. The text values take three per character: the cheapest path costs no more than a latch of two values and the character itself for + // every character. The tables of a short text stay inline, and a longer text rents them once; the text is bounded by the maximum length of the + // symbology. + int length = text.Length; + int runLength = length + 1; + int searchLength = 2 * SubModes * length; + int size = (3 * runLength) + searchLength + (3 * length) + 1; + InlineArray256 tableStorage = default; + PooledBuffer rented = default; + Span tables = size <= 256 + ? ((Span)tableStorage)[..size] + : (rented = new PooledBuffer(size)).Span; + + try + { + Span digits = tables[..runLength]; + Span texts = tables.Slice(runLength, runLength); + Span bytes = tables.Slice(runLength * 2, runLength); + Span search = MemoryMarshal.Cast(tables.Slice(runLength * 3, searchLength)); + Span path = tables.Slice((runLength * 3) + searchLength, (3 * length) + 1); + + // Each table holds the length of the run that starts at a character, counted from the end so every entry reads the one after it. A text run stops + // where a run of digits long enough for Numeric Compaction mode starts, and a byte run stops where either of the other runs does. + digits[length] = 0; + texts[length] = 0; + bytes[length] = 0; + for (int i = length - 1; i >= 0; i--) + { + int c = text[i]; + digits[i] = char.IsAsciiDigit((char)c) ? digits[i + 1] + 1 : 0; + texts[i] = IsText(c) && digits[i] < NumericRun ? texts[i + 1] + 1 : 0; + bytes[i] = texts[i] < TextRun && digits[i] < NumericRun ? bytes[i + 1] + 1 : 0; + } + + // Data that is all digits encodes in Numeric Compaction mode from eight digits. After a macro codeword the message is more than its digits, so the + // rule does not apply. + int wholeLength = macro == 0 ? length : -1; + + // A latch to Text Compaction mode returns to the Alpha sub-mode, section 5.4.2.1. + SubMode subMode = SubMode.Alpha; + int position = 0; + while (position < length) + { + int digitRun = digits[position]; + if (digitRun >= NumericRun || (digitRun == wholeLength && digitRun >= WholeNumericRun)) + { + // The format 05 macro codeword stands for the latch of a first numeric run. + if (macro != Macro05 || position != 0) + { + codewords.Append(NumericLatch); + } + + EncodeNumeric(text.Slice(position, digitRun), ref codewords); + mode = CompactionMode.Numeric; + position += digitRun; + continue; + } + + int textRun = texts[position]; + if (textRun >= TextRun) + { + if (mode != CompactionMode.Text) + { + // The format 06 macro codeword stands for the latch of a first text run, and leaves the encoder in the Mixed sub-mode. + if (macro == Macro06 && position == 0) + { + subMode = SubMode.Mixed; + } + else + { + codewords.Append(TextLatch); + subMode = SubMode.Alpha; + } + } + + EncodeText(text.Slice(position, textRun), ref subMode, search, path, ref codewords); + mode = CompactionMode.Text; + position += textRun; + continue; + } + + // Section 5.4.3.1: the mode shift takes a single byte out of Text Compaction mode, and the two latches take a byte count that is or is not a + // multiple of six. + int byteRun = bytes[position]; + if (byteRun == 1 && mode == CompactionMode.Text) + { + codewords.Append(ByteShift); + codewords.Append(text[position]); + position++; + continue; + } + + codewords.Append(ByteLatchFor(byteRun)); + EncodeBytes(text.Slice(position, byteRun), ref codewords); + mode = CompactionMode.Byte; + position += byteRun; + } + } + finally + { + rented.Dispose(); + } + } + + /// + /// Encodes a run of digits in Numeric Compaction mode, section 5.4.4.2: each group of up to 44 digits takes a leading 1 and converts from base 10 to base + /// 900 as Annex D gives. + /// + /// The digits. + /// The builder the codewords are written to. + private static void EncodeNumeric(ReadOnlySpan digits, ref ValueListBuilder codewords) + { + InlineArray64 numberStorage = default; + InlineArray16 groupStorage = default; + Span number = ((Span)numberStorage)[..(NumericGroup + 1)]; + Span group = ((Span)groupStorage)[..((NumericGroup / 3) + 1)]; + for (int start = 0; start < digits.Length; start += NumericGroup) + { + int length = Math.Min(NumericGroup, digits.Length - start); + number[0] = 1; + for (int i = 0; i < length; i++) + { + number[i + 1] = (byte)(digits[start + i] - '0'); + } + + // Annex D: the codewords are the remainders of repeated division by 900, the least significant first. Each pass divides the decimal digits in place + // by long division, which keeps a quotient digit of at most 9 in each place, and drops the leading zeros it leaves. + int count = length + 1; + int first = 0; + int produced = 0; + while (first < count) + { + int remainder = 0; + for (int i = first; i < count; i++) + { + int value = (remainder * 10) + number[i]; + number[i] = (byte)(value / CodewordBase); + remainder = value % CodewordBase; + } + + group[produced++] = remainder; + while (first < count && number[first] == 0) + { + first++; + } + } + + for (int i = produced - 1; i >= 0; i--) + { + codewords.Append(group[i]); + } + } + } + + /// + /// Encodes a run of text characters in Text Compaction mode, section 5.4.2. The search keeps, for each sub-mode, the fewest base 30 values that encode the + /// characters so far and end in that sub-mode. Before each character every sub-mode can latch to another; the character then extends a sub-mode that holds + /// it, or a sub-mode that shifts to one that does. The run takes the cheapest end, and an odd count of values takes the pad of section 5.4.2.4. + /// + /// The characters, each one that Table 5 holds. + /// The sub-mode in effect, updated to the sub-mode the run ends in. + /// The scratch table of the search, one step per sub-mode and character. + /// The scratch table of the chosen base 30 values, three per character and one more. + /// The builder the codewords are written to. + private static void EncodeText( + ReadOnlySpan text, + ref SubMode subMode, + scoped Span search, + scoped Span path, + ref ValueListBuilder codewords) + { + int length = text.Length; + InlineArray4 costStorage = default; + InlineArray4 nextStorage = default; + Span cost = costStorage; + Span next = nextStorage; + cost.Fill(Unreachable); + cost[(int)subMode] = 0; + + // A latch or a shift never changes the sub-mode the character leaves the encoder in, so the latch sources and the shift targets of each character are + // the whole path. + for (int i = 0; i < length; i++) + { + Span steps = search.Slice(i * SubModes, SubModes); + steps.Fill(new SearchStep { LatchFrom = SubMode.None, ShiftTo = SubMode.None }); + + // A latch can lead to a sub-mode that latches again, so the relaxation repeats until no sub-mode gets cheaper. Each update lowers a cost, so it + // ends. + bool improved = true; + while (improved) + { + improved = false; + for (SubMode from = SubMode.Alpha; from <= SubMode.Punctuation; from++) + { + for (SubMode to = SubMode.Alpha; to <= SubMode.Punctuation; to++) + { + int latched = cost[(int)from] + LatchLength(from, to); + if (latched < cost[(int)to]) + { + cost[(int)to] = latched; + steps[(int)to].LatchFrom = from; + improved = true; + } + } + } + } + + int c = text[i]; + next.Fill(Unreachable); + for (SubMode holder = SubMode.Alpha; holder <= SubMode.Punctuation; holder++) + { + if (TextValue(holder, c) < 0) + { + continue; + } + + int direct = cost[(int)holder] + 1; + if (direct < next[(int)holder]) + { + next[(int)holder] = direct; + steps[(int)holder].ShiftTo = SubMode.None; + } + + for (SubMode from = SubMode.Alpha; from <= SubMode.Punctuation; from++) + { + if (from == holder || !CanShift(from, holder)) + { + continue; + } + + int shifted = cost[(int)from] + 2; + if (shifted < next[(int)from]) + { + next[(int)from] = shifted; + steps[(int)from].ShiftTo = holder; + } + } + } + + next.CopyTo(cost); + } + + SubMode best = SubMode.Alpha; + for (SubMode s = SubMode.Lower; s <= SubMode.Punctuation; s++) + { + if (cost[(int)s] < cost[(int)best]) + { + best = s; + } + } + + // Walk the path back from the cheapest end, writing its base 30 values from the end of the table: each character, the shift before it if any, and then + // the latches taken before it. Table 7 latches directly between most sub-modes. The rest pass through a sub-mode that latches onward: Punctuation + // through Alpha, and the others through Mixed. + int count = 0; + SubMode state = best; + for (int i = length - 1; i >= 0; i--) + { + int c = text[i]; + SearchStep step = search[(i * SubModes) + (int)state]; + if (step.ShiftTo == SubMode.None) + { + path[path.Length - 1 - count++] = TextValue(state, c); + } + else + { + path[path.Length - 1 - count++] = TextValue(step.ShiftTo, c); + path[path.Length - 1 - count++] = ShiftValue(step.ShiftTo); + } + + SubMode from = step.LatchFrom; + while (from != SubMode.None) + { + if (LatchLength(from, state) == 2) + { + SubMode via = from == SubMode.Punctuation ? SubMode.Alpha : SubMode.Mixed; + path[path.Length - 1 - count++] = LatchValue(via, state); + path[path.Length - 1 - count++] = LatchValue(from, via); + } + else + { + path[path.Length - 1 - count++] = LatchValue(from, state); + } + + state = from; + from = search[(i * SubModes) + (int)state].LatchFrom; + } + } + + // Pair the values into codewords, h x 30 + l, section 5.4.2.2. An odd count of values takes the pad, which in the Punctuation sub-mode is the latch to + // Alpha and takes effect. + ReadOnlySpan values = path[^count..]; + for (int i = 0; i < count; i += 2) + { + int low = i + 1 < count ? values[i + 1] : PadValue; + codewords.Append((values[i] * TextBase) + low); + } + + subMode = (count & 1) != 0 && best == SubMode.Punctuation ? SubMode.Alpha : best; + } + + /// + /// Returns the number of base 30 values the latch from one sub-mode to another takes, Table 7, and zero for a sub-mode to itself. + /// + /// The sub-mode the latch starts in. + /// The sub-mode the latch ends in. + /// The number of values. + private static int LatchLength(SubMode from, SubMode to) + => (from, to) switch + { + _ when from == to => 0, + (SubMode.Lower, SubMode.Alpha) or (SubMode.Lower, SubMode.Punctuation) or (SubMode.Alpha, SubMode.Punctuation) => 2, + (SubMode.Punctuation, SubMode.Lower) or (SubMode.Punctuation, SubMode.Mixed) => 2, + _ => 1, + }; + + /// + /// Returns whether a sub-mode shifts to another for one character, Table 7: Lower shifts to Alpha, and every sub-mode but Punctuation shifts to + /// Punctuation. + /// + /// The sub-mode the shift starts in. + /// The sub-mode the character is encoded in. + /// when the shift exists. + private static bool CanShift(SubMode from, SubMode to) + => (to == SubMode.Punctuation && from != SubMode.Punctuation) || (to == SubMode.Alpha && from == SubMode.Lower); + + /// + /// Returns the base 30 value of a direct latch from one sub-mode to another, Table 5. Only the latches of length one in are + /// direct. + /// + /// The sub-mode the latch is encoded in. + /// The sub-mode the latch ends in. + /// The value. + private static int LatchValue(SubMode from, SubMode to) + => (from, to) switch + { + (SubMode.Mixed, SubMode.Punctuation) => 25, + (SubMode.Alpha, SubMode.Lower) or (SubMode.Mixed, SubMode.Lower) => 27, + (SubMode.Alpha, SubMode.Mixed) or (SubMode.Lower, SubMode.Mixed) or (SubMode.Mixed, SubMode.Alpha) => 28, + _ => 29, + }; + + /// + /// Returns the base 30 value of a shift, Table 5: the shift to Alpha, which only the Lower sub-mode has, is 27, and the shift to Punctuation is 29. + /// + /// The sub-mode the shifted character is encoded in. + /// The value. + private static int ShiftValue(SubMode to) => to == SubMode.Alpha ? 27 : 29; + + /// + /// Returns the base 30 value of a character in a sub-mode, Table 5. + /// + /// The sub-mode. + /// The character, U+0000 to U+00FF. + /// The value, or -1 when the sub-mode does not hold the character. + private static int TextValue(SubMode subMode, int c) + => c < TextTableWidth ? TextValues[((int)subMode * TextTableWidth) + c] : -1; + + /// + /// Returns whether Text Compaction mode holds a character in any of its sub-modes. + /// + /// The character, U+0000 to U+00FF. + /// when a sub-mode holds the character. + private static bool IsText(int c) + => TextValue(SubMode.Alpha, c) >= 0 || TextValue(SubMode.Lower, c) >= 0 || TextValue(SubMode.Mixed, c) >= 0 || TextValue(SubMode.Punctuation, c) >= 0; + + /// + /// One step of the Text Compaction search: how a sub-mode reached a character. The step is two integers, so the search shares the integer tables of the run + /// lengths. + /// + private struct SearchStep + { + /// + /// The sub-mode this sub-mode latched from before the character, or . + /// + public SubMode LatchFrom; + + /// + /// The sub-mode this sub-mode shifted to for the character, or when it holds the character itself. + /// + public SubMode ShiftTo; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Pdf417/Pdf417Encoder.cs b/src/ImageSharp.Drawing/Barcodes/Pdf417/Pdf417Encoder.cs new file mode 100644 index 00000000..43562dc5 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Pdf417/Pdf417Encoder.cs @@ -0,0 +1,334 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Builds PDF417 and Compact PDF417 symbols, ISO/IEC 15438: the symbol matrix of section 5.9, the row indicators of section 5.11.3 and the rows of section +/// 5.2.2. +/// +internal static class Pdf417Encoder +{ + /// + /// The fewest data columns of a symbol, section 5.2.1. + /// + public const int MinimumColumns = 1; + + /// + /// The most data columns of a symbol, section 5.2.1. + /// + public const int MaximumColumns = 30; + + /// + /// The highest error correction level, Table 9. + /// + public const int MaximumErrorCorrectionLevel = 8; + + /// + /// The fewest rows of a symbol, section 5.2.1. + /// + private const int MinimumRows = 3; + + /// + /// The most rows of a symbol, section 5.2.1. + /// + private const int MaximumRows = 90; + + /// + /// The most codewords a symbol holds, Annex O parameter 9: c x r < 929. + /// + private const int MaximumCodewords = 928; + + /// + /// The nominal row height in modules, the minimum of section 5.8.2: Y >= 3X. + /// + private const float NominalRowHeight = 3F; + + /// + /// The quiet zone on each of the four sides in modules, the minimum of section 5.8.3. + /// + private const int QuietZone = 2; + + /// + /// The number of rows over which the row indicators repeat their information, section 5.11.3. + /// + private const int IndicatorCycle = 3; + + /// + /// The step of the row indicator values from one cycle of three rows to the next, section 5.11.3. + /// + private const int IndicatorStep = 30; + + /// + /// Gets the element widths of the start character, Table 1. + /// + private static ReadOnlySpan Start => [8, 1, 1, 1, 1, 1, 1, 3]; + + /// + /// Gets the element widths of the stop character, Table 1. + /// + private static ReadOnlySpan Stop => [7, 1, 1, 3, 1, 1, 1, 2, 1]; + + /// + /// Returns the recommended minimum error correction level of Table E.1 of ISO/IEC 15438 for a number of data codewords. + /// + /// The number of data codewords. + /// The error correction level, 2 to 5. + public static int RecommendedErrorCorrectionLevel(int dataCodewords) + => dataCodewords <= 40 ? 2 : dataCodewords <= 160 ? 3 : dataCodewords <= 320 ? 4 : 5; + + /// + /// Encodes the text into a symbol. + /// + /// The data, the characters U+0000 to U+00FF. + /// The number of data columns, 1 to 30, or to choose them from the data. + /// The error correction level, 0 to 8, or to choose it from the data. + /// Whether the symbol is Compact PDF417, Annex G. + /// The options that control layout choices. + /// The symbol to write. + /// The data is not valid or does not fit the symbol. + public static void BuildSymbol( + ReadOnlySpan text, + int? columns, + int? errorCorrectionLevel, + bool compact, + BarcodeOptions options, + ref BarcodeSymbol symbol) + { + // A character takes at most two data codewords. A numeric or text run costs less than two for each character with its latch. A single byte shifted + // out of Text Compaction mode costs two. A byte run costs one latch and at most one codeword for each byte. The text is bounded, so the builder never + // grows. Short data works inline, and longer data rents once. + Guard.MustBeLessThanOrEqualTo(text.Length, Pdf417Codewords.MaximumLength, nameof(text)); + InlineArray64 dataStorage = default; + int capacity = (2 * text.Length) + 1; + ValueListBuilder data = capacity <= Pdf417Codewords.InlineBufferLength ? new(dataStorage) : new(capacity); + try + { + Pdf417Codewords.Encode(text, ref data); + BuildSymbol(data.AsSpan(), columns, errorCorrectionLevel, compact, options, ref symbol); + } + finally + { + data.Dispose(); + } + } + + /// + /// Builds the symbol from its data codewords. + /// + /// The data codewords, without the Symbol Length Descriptor. + /// The number of data columns, 1 to 30, or to choose them from the data. + /// The error correction level, 0 to 8, or to choose it from the data. + /// Whether the symbol is Compact PDF417, Annex G. + /// The options that control layout choices. + /// The symbol to write. + /// The data does not fit the symbol. + private static void BuildSymbol( + ReadOnlySpan data, + int? columns, + int? errorCorrectionLevel, + bool compact, + BarcodeOptions options, + ref BarcodeSymbol symbol) + { + // The data codewords, the Symbol Length Descriptor and at least the two error correction codewords of level 0 must fit in the largest matrix. + int m = data.Length; + if (m + 1 + 2 > MaximumCodewords) + { + throw new ArgumentException($"The data takes {m} codewords; a PDF417 symbol holds at most {MaximumCodewords - 3}."); + } + + // Without a chosen level the symbol takes the recommended minimum of Table E.1, lowered where the data leaves too little room for its error correction + // codewords: 2^(s + 1) codewords fit when s + 1 <= log2(927 - m). + int level; + if (errorCorrectionLevel.HasValue) + { + level = errorCorrectionLevel.Value; + } + else + { + level = RecommendedErrorCorrectionLevel(m); + level = Math.Min(level, BitOperations.Log2((uint)(MaximumCodewords - 1 - m)) - 1); + } + + // Without chosen columns the matrix is close to three rows per column, the nearest whole number of columns to the square root of a third of the + // codewords. + int k = 2 << level; + int c = columns ?? (int)Math.Floor(Math.Sqrt((m + k) / 3D) + 0.5D); + int r = Math.Max(MinimumRows, (m + 1 + k + c - 1) / c); + + // Section 5.1.1 gives a symbol 925 data codewords. Near that capacity the rows of the chosen column count can hold more than the 928 codewords of + // section 5.2.3, or more than 90 rows, so without chosen columns the matrix widens until it fits. + if (!columns.HasValue) + { + while ((r > MaximumRows || c * r > MaximumCodewords) && c < MaximumColumns) + { + c++; + r = Math.Max(MinimumRows, (m + 1 + k + c - 1) / c); + } + } + + if (r > MaximumRows || c * r > MaximumCodewords) + { + throw new ArgumentException($"The data takes {m} codewords and does not fit in a PDF417 symbol of {c} columns."); + } + + // Without a chosen level the error correction grows into the room the rows leave, up to level 8, rather than filling it with pad codewords. + if (!errorCorrectionLevel.HasValue) + { + int roomLevel = Math.Min(BitOperations.Log2((uint)((c * r) - 1 - m)) - 1, MaximumErrorCorrectionLevel); + level = Math.Max(level, roomLevel); + } + + // Section 5.8.1: the X dimension "should be defined by the application specification", so this standard gives no limit. + double xDimension = options.ResolveXDimension(0, BarcodeSymbology.DefaultXDimension, double.PositiveInfinity); + + // Section 5.8.2: the row height is "Y >= 3X", and "the row height may be increased". It gives no maximum. + float rowHeight = options.ResolveBarHeight(xDimension, NominalRowHeight, NominalRowHeight, float.PositiveInfinity); + BuildMatrix(data, c, r, level, compact, xDimension, rowHeight, QuietZone, ref symbol); + } + + /// + /// Builds a symbol of a chosen number of columns and error correction level from its data codewords, in the fewest rows that hold them. Section 10 of + /// ISO/IEC 24723 builds the CC-C 2D component of a GS1 Composite symbol this way: its bit stream fills the data region, so the symbol takes no pad codewords, + /// and section 11.5 gives it no quiet zone above or below. + /// + /// The data codewords, without the Symbol Length Descriptor. + /// The number of data columns, 1 to 30. + /// The error correction level, 0 to 8. + /// The X dimension in millimetres. + /// The quiet zone above and below the symbol in modules. + /// The symbol to write. + public static void BuildSymbol(ReadOnlySpan data, int columns, int level, double xDimension, float verticalQuietZone, ref BarcodeSymbol symbol) + { + int k = 2 << level; + int r = Math.Max(MinimumRows, (data.Length + 1 + k + columns - 1) / columns); + BuildMatrix(data, columns, r, level, false, xDimension, NominalRowHeight, verticalQuietZone, ref symbol); + } + + /// + /// Builds the symbol matrix of section 5.9 from the data codewords: the Symbol Length Descriptor, the data, the pad codewords and the error correction + /// codewords, then the rows. + /// + /// The data codewords, without the Symbol Length Descriptor. + /// The number of data columns. + /// The number of rows. + /// The error correction level. + /// Whether the symbol is Compact PDF417. + /// The X dimension in millimetres. + /// The height of a row in modules. + /// The quiet zone above and below the symbol in modules. + /// The symbol to write. + private static void BuildMatrix( + ReadOnlySpan data, + int c, + int r, + int level, + bool compact, + double xDimension, + float rowHeight, + float verticalQuietZone, + ref BarcodeSymbol symbol) + { + // Section 5.9.2: the Symbol Length Descriptor n = c x r - k counts itself, the data and the pad codewords, which fill the rest of the data region with + // 900. One rental holds the codewords and the generator coefficients of the error correction. + int m = data.Length; + int k = 2 << level; + int total = c * r; + int n = total - k; + PooledBuffer rented = new(total + k + 1); + try + { + Span codewords = rented.Span[..total]; + codewords[0] = n; + data.CopyTo(codewords[1..]); + codewords[(m + 1)..n].Fill(Pdf417Codewords.Pad); + Pdf417Codewords.AppendErrorCorrection(codewords, k, rented.Span[total..]); + + BuildRows(codewords, c, r, level, compact, xDimension, rowHeight, verticalQuietZone, ref symbol); + } + finally + { + rented.Dispose(); + } + } + + /// + /// Builds the rows of the symbol: the start character, the left row indicator, the codewords of the row, the right row indicator and the stop character. + /// Compact PDF417, Annex G, drops the right row indicator and ends the row with a single module bar. + /// + /// The codewords in row order, error correction codewords included. + /// The number of data columns. + /// The number of rows. + /// The error correction level. + /// Whether the symbol is Compact PDF417. + /// The X dimension in millimetres. + /// The height of a row in modules. + /// The quiet zone above and below the symbol in modules. + /// The symbol to write. + private static void BuildRows( + ReadOnlySpan codewords, + int c, + int r, + int level, + bool compact, + double xDimension, + float rowHeight, + float verticalQuietZone, + ref BarcodeSymbol symbol) + { + // A row of a full symbol is the start character, c + 2 symbol characters and the stop character; a compact row is the start character, c + 1 symbol + // characters and one bar. + int runCount = compact + ? Start.Length + ((c + 1) * Pdf417Patterns.ElementCount) + 1 + : Start.Length + ((c + 2) * Pdf417Patterns.ElementCount) + Stop.Length; + + ReadOnlySpan patterns = Pdf417Patterns.Modules; + symbol.BeginStacked(r, r * runCount, r * ((runCount + 1) >> 1), xDimension); + for (int row = 0; row < r; row++) + { + // Section 5.11.1: row 1 uses cluster 0, row 2 cluster 3 and row 3 cluster 6, repeating. The cluster index here is 0, 1 or 2, and the pattern table + // holds the clusters in that order. + int cluster = row % IndicatorCycle; + ReadOnlySpan clusterPatterns = patterns.Slice(cluster * Pdf417Patterns.CodewordCount, Pdf417Patterns.CodewordCount); + + // Section 5.11.3: each indicator carries the row number F = row + 1 as 30 x ((F - 1) div 3), and one of three values in turn: (r - 1) div 3, the + // error correction level and (r - 1) mod 3 as s x 3 + (r - 1) mod 3, and c - 1. + int cycle = IndicatorStep * (row / IndicatorCycle); + int rowValue = cycle + ((r - 1) / IndicatorCycle); + int levelValue = cycle + (level * 3) + ((r - 1) % IndicatorCycle); + int columnValue = cycle + c - 1; + (int left, int right) = cluster switch + { + 0 => (rowValue, columnValue), + 1 => (levelValue, rowValue), + _ => (columnValue, levelValue), + }; + + Span runs = symbol.AddRow(runCount, out Span heights, out _); + heights.Fill(rowHeight); + Start.CopyTo(runs); + int index = Start.Length; + Pdf417Patterns.AppendCharacter(runs, ref index, clusterPatterns[left]); + for (int column = 0; column < c; column++) + { + Pdf417Patterns.AppendCharacter(runs, ref index, clusterPatterns[codewords[(row * c) + column]]); + } + + if (compact) + { + runs[index] = 1; + } + else + { + Pdf417Patterns.AppendCharacter(runs, ref index, clusterPatterns[right]); + Stop.CopyTo(runs[index..]); + } + } + + symbol.EndStacked(0F, QuietZone, QuietZone, verticalQuietZone, verticalQuietZone); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Pdf417/Pdf417Patterns.cs b/src/ImageSharp.Drawing/Barcodes/Pdf417/Pdf417Patterns.cs new file mode 100644 index 00000000..f2ba18ce --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Pdf417/Pdf417Patterns.cs @@ -0,0 +1,245 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The symbol characters of Annex A of ISO/IEC 15438, which PDF417 and MicroPDF417 share. +/// +internal static class Pdf417Patterns +{ + /// + /// The number of codewords, 0 to 928, that each cluster represents. + /// + public const int CodewordCount = 929; + + /// + /// The number of elements in a symbol character, four bars and four spaces. + /// + public const int ElementCount = 8; + + /// + /// The number of modules in a symbol character. + /// + private const int ModuleCount = 17; + + /// + /// Gets the symbol characters of the three clusters. The entry at index (cluster * 929) + codeword holds the 17 modules of that codeword in cluster + /// 0, 3 or 6, where cluster is 0, 1 or 2. The first module is bit 16 and the last module is bit 0, and a set bit is a dark module. A symbol + /// character is four bars and four spaces of one to six modules, which starts with a bar. + /// + public static ReadOnlySpan Modules => + [ + 120256, 125680, 128380, 120032, 125560, 128318, 108736, 119920, 108640, 86080, 108592, 86048, 110016, 120560, 125820, 109792, + 120440, 125758, 88256, 109680, 88160, 89536, 110320, 120700, 89312, 110200, 120638, 89200, 110140, 89840, 110460, 89720, + 110398, 89980, 128506, 119520, 125304, 128190, 107712, 119408, 125244, 107616, 119352, 84032, 107568, 119324, 84000, 107544, + 83984, 108256, 119672, 125374, 85184, 108144, 119612, 85088, 108088, 119582, 85040, 108060, 85728, 108408, 119742, 85616, + 108348, 85560, 108318, 85880, 108478, 85820, 85790, 107200, 119152, 125116, 107104, 119096, 125086, 83008, 107056, 119068, + 82976, 107032, 82960, 82952, 83648, 107376, 119228, 83552, 107320, 119198, 83504, 107292, 83480, 83468, 83824, 107452, + 83768, 107422, 83740, 83900, 106848, 118968, 125022, 82496, 106800, 118940, 82464, 106776, 118926, 82448, 106764, 82440, + 106758, 82784, 106936, 119006, 82736, 106908, 82712, 106894, 82700, 82694, 106974, 82830, 82240, 106672, 118876, 82208, + 106648, 118862, 82192, 106636, 82184, 106630, 82180, 82352, 82328, 82316, 82080, 118830, 106572, 106566, 82050, 117472, + 124280, 127678, 103616, 117360, 124220, 103520, 117304, 124190, 75840, 103472, 75808, 104160, 117624, 124350, 76992, 104048, + 117564, 76896, 103992, 76848, 76824, 77536, 104312, 117694, 77424, 104252, 77368, 77340, 77688, 104382, 77628, 77758, + 121536, 126320, 128700, 121440, 126264, 128670, 111680, 121392, 126236, 111648, 121368, 126222, 111632, 121356, 103104, 117104, + 124092, 112320, 103008, 117048, 124062, 112224, 121656, 126366, 93248, 74784, 102936, 117006, 93216, 112152, 93200, 75456, + 103280, 117180, 93888, 75360, 103224, 117150, 93792, 112440, 121758, 93744, 75288, 93720, 75632, 103356, 94064, 75576, + 103326, 94008, 112542, 93980, 75708, 94140, 75678, 94110, 121184, 126136, 128606, 111168, 121136, 126108, 111136, 121112, + 126094, 111120, 121100, 111112, 111108, 102752, 116920, 123998, 111456, 102704, 116892, 91712, 74272, 121244, 116878, 91680, + 74256, 102668, 91664, 111372, 102662, 74244, 74592, 102840, 116958, 92000, 74544, 102812, 91952, 111516, 102798, 91928, + 74508, 74502, 74680, 102878, 92088, 74652, 92060, 74638, 92046, 92126, 110912, 121008, 126044, 110880, 120984, 126030, + 110864, 120972, 110856, 120966, 110852, 110850, 74048, 102576, 116828, 90944, 74016, 102552, 116814, 90912, 111000, 121038, + 90896, 73992, 102534, 90888, 110982, 90884, 74160, 102620, 91056, 74136, 102606, 91032, 111054, 91020, 74118, 91014, + 91100, 91086, 110752, 120920, 125998, 110736, 120908, 110728, 120902, 110724, 110722, 73888, 102488, 116782, 90528, 73872, + 102476, 90512, 110796, 102470, 90504, 73860, 90500, 73858, 73944, 90584, 90572, 90566, 120876, 120870, 110658, 102444, + 73800, 90312, 90308, 90306, 101056, 116080, 123580, 100960, 116024, 70720, 100912, 115996, 70688, 100888, 70672, 70664, + 71360, 101232, 116156, 71264, 101176, 116126, 71216, 101148, 71192, 71180, 71536, 101308, 71480, 101278, 71452, 71612, + 71582, 118112, 124600, 127838, 105024, 118064, 124572, 104992, 118040, 124558, 104976, 118028, 104968, 118022, 100704, 115896, + 123486, 105312, 100656, 115868, 79424, 70176, 118172, 115854, 79392, 105240, 100620, 79376, 70152, 79368, 70496, 100792, + 115934, 79712, 70448, 118238, 79664, 105372, 100750, 79640, 70412, 79628, 70584, 100830, 79800, 70556, 79772, 70542, + 70622, 79838, 122176, 126640, 128860, 122144, 126616, 128846, 122128, 126604, 122120, 126598, 122116, 104768, 117936, 124508, + 113472, 104736, 126684, 124494, 113440, 122264, 126670, 113424, 104712, 117894, 113416, 122246, 104706, 69952, 100528, 115804, + 78656, 69920, 100504, 115790, 96064, 78624, 104856, 117966, 96032, 113560, 122318, 100486, 96016, 78600, 104838, 96008, + 69890, 70064, 100572, 78768, 70040, 100558, 96176, 78744, 104910, 96152, 113614, 70022, 78726, 70108, 78812, 70094, + 96220, 78798, 122016, 126552, 128814, 122000, 126540, 121992, 126534, 121988, 121986, 104608, 117848, 124462, 113056, 104592, + 126574, 113040, 122060, 117830, 113032, 104580, 113028, 104578, 113026, 69792, 100440, 115758, 78240, 69776, 100428, 95136, + 78224, 104652, 100422, 95120, 113100, 69764, 95112, 78212, 69762, 78210, 69848, 100462, 78296, 69836, 95192, 78284, + 69830, 95180, 78278, 69870, 95214, 121936, 126508, 121928, 126502, 121924, 121922, 104528, 117804, 112848, 104520, 117798, + 112840, 121958, 112836, 104514, 112834, 69712, 100396, 78032, 69704, 100390, 94672, 78024, 104550, 94664, 112870, 69698, + 94660, 78018, 94658, 78060, 94700, 94694, 126486, 121890, 117782, 104484, 104482, 69672, 77928, 94440, 69666, 77922, + 99680, 68160, 99632, 68128, 99608, 115342, 68112, 99596, 68104, 99590, 68448, 99768, 115422, 68400, 99740, 68376, + 99726, 68364, 68358, 68536, 99806, 68508, 68494, 68574, 101696, 116400, 123740, 101664, 116376, 101648, 116364, 101640, + 116358, 101636, 67904, 99504, 115292, 72512, 67872, 116444, 115278, 72480, 101784, 116430, 72464, 67848, 99462, 72456, + 101766, 67842, 68016, 99548, 72624, 67992, 99534, 72600, 101838, 72588, 67974, 68060, 72668, 68046, 72654, 118432, + 124760, 127918, 118416, 124748, 118408, 124742, 118404, 118402, 101536, 116312, 105888, 101520, 116300, 105872, 118476, 116294, + 105864, 101508, 105860, 101506, 105858, 67744, 99416, 72096, 67728, 116334, 80800, 72080, 101580, 99398, 80784, 105932, + 67716, 80776, 72068, 67714, 72066, 67800, 99438, 72152, 67788, 80856, 72140, 67782, 80844, 72134, 67822, 72174, + 80878, 126800, 128940, 126792, 128934, 126788, 126786, 118352, 124716, 122576, 126828, 124710, 122568, 126822, 122564, 118338, + 122562, 101456, 116268, 105680, 101448, 116262, 114128, 105672, 118374, 114120, 122598, 101442, 114116, 105666, 114114, 67664, + 99372, 71888, 67656, 99366, 80336, 71880, 101478, 97232, 80328, 105702, 67650, 97224, 114150, 71874, 97220, 67692, + 71916, 67686, 80364, 71910, 97260, 80358, 97254, 126760, 128918, 126756, 126754, 118312, 124694, 122472, 126774, 122468, + 118306, 122466, 101416, 116246, 105576, 101412, 113896, 105572, 101410, 113892, 105570, 113890, 67624, 99350, 71784, 101430, + 80104, 71780, 67618, 96744, 80100, 71778, 96740, 80098, 96738, 71798, 96758, 126738, 122420, 122418, 105524, 113780, + 113778, 71732, 79988, 96500, 96498, 66880, 66848, 98968, 66832, 66824, 66820, 66992, 66968, 66956, 66950, 67036, + 67022, 100000, 99984, 115532, 99976, 115526, 99972, 99970, 66720, 98904, 69024, 100056, 98892, 69008, 100044, 69000, + 100038, 68996, 66690, 68994, 66776, 98926, 69080, 100078, 69068, 66758, 69062, 66798, 69102, 116560, 116552, 116548, + 116546, 99920, 102096, 116588, 115494, 102088, 116582, 102084, 99906, 102082, 66640, 68816, 66632, 98854, 73168, 68808, + 66628, 73160, 68804, 66626, 73156, 68802, 66668, 68844, 66662, 73196, 68838, 73190, 124840, 124836, 124834, 116520, + 118632, 124854, 118628, 116514, 118626, 99880, 115478, 101992, 116534, 106216, 101988, 99874, 106212, 101986, 106210, 66600, + 98838, 68712, 99894, 72936, 68708, 66594, 81384, 72932, 68706, 81380, 72930, 66614, 68726, 72950, 81398, 128980, + 128978, 124820, 126900, 124818, 126898, 116500, 118580, 116498, 122740, 118578, 122738, 99860, 101940, 99858, 106100, 101938, + 114420, 128352, 129720, 125504, 128304, 129692, 125472, 128280, 129678, 125456, 128268, 125448, 128262, 125444, 125792, 128440, + 129758, 120384, 125744, 128412, 120352, 125720, 128398, 120336, 125708, 120328, 125702, 120324, 120672, 125880, 128478, 110144, + 120624, 125852, 110112, 120600, 125838, 110096, 120588, 110088, 120582, 110084, 110432, 120760, 125918, 89664, 110384, 120732, + 89632, 110360, 120718, 89616, 110348, 89608, 110342, 89952, 110520, 120798, 89904, 110492, 89880, 110478, 89868, 90040, + 110558, 90012, 89998, 125248, 128176, 129628, 125216, 128152, 129614, 125200, 128140, 125192, 128134, 125188, 125186, 119616, + 125360, 128220, 119584, 125336, 128206, 119568, 125324, 119560, 125318, 119556, 119554, 108352, 119728, 125404, 108320, 119704, + 125390, 108304, 119692, 108296, 119686, 108292, 108290, 85824, 108464, 119772, 85792, 108440, 119758, 85776, 108428, 85768, + 108422, 85764, 85936, 108508, 85912, 108494, 85900, 85894, 85980, 85966, 125088, 128088, 129582, 125072, 128076, 125064, + 128070, 125060, 125058, 119200, 125144, 128110, 119184, 125132, 119176, 125126, 119172, 119170, 107424, 119256, 125166, 107408, + 119244, 107400, 119238, 107396, 107394, 83872, 107480, 119278, 83856, 107468, 83848, 107462, 83844, 83842, 83928, 107502, + 83916, 83910, 83950, 125008, 128044, 125000, 128038, 124996, 124994, 118992, 125036, 118984, 125030, 118980, 118978, 106960, + 119020, 106952, 119014, 106948, 106946, 82896, 106988, 82888, 106982, 82884, 82882, 82924, 82918, 124968, 128022, 124964, + 124962, 118888, 124982, 118884, 118882, 106728, 118902, 106724, 106722, 82408, 106742, 82404, 82402, 124948, 124946, 118836, + 118834, 106612, 106610, 124224, 127664, 129372, 124192, 127640, 129358, 124176, 127628, 124168, 127622, 124164, 124162, 117568, + 124336, 127708, 117536, 124312, 127694, 117520, 124300, 117512, 124294, 117508, 117506, 104256, 117680, 124380, 104224, 117656, + 124366, 104208, 117644, 104200, 117638, 104196, 104194, 77632, 104368, 117724, 77600, 104344, 117710, 77584, 104332, 77576, + 104326, 77572, 77744, 104412, 77720, 104398, 77708, 77702, 77788, 77774, 128672, 129880, 93168, 128656, 129868, 92664, + 128648, 129862, 92412, 128644, 128642, 124064, 127576, 129326, 126368, 124048, 129902, 126352, 128716, 127558, 126344, 124036, + 126340, 124034, 126338, 117152, 124120, 127598, 121760, 117136, 124108, 121744, 126412, 124102, 121736, 117124, 121732, 117122, + 121730, 103328, 117208, 124142, 112544, 103312, 117196, 112528, 121804, 117190, 112520, 103300, 112516, 103298, 112514, 75680, + 103384, 117230, 94112, 75664, 103372, 94096, 112588, 103366, 94088, 75652, 94084, 75650, 75736, 103406, 94168, 75724, + 94156, 75718, 94150, 75758, 128592, 129836, 91640, 128584, 129830, 91388, 128580, 91262, 128578, 123984, 127532, 126160, + 123976, 127526, 126152, 128614, 126148, 123970, 126146, 116944, 124012, 121296, 116936, 124006, 121288, 126182, 121284, 116930, + 121282, 102864, 116972, 111568, 102856, 116966, 111560, 121318, 111556, 102850, 111554, 74704, 102892, 92112, 74696, 102886, + 92104, 111590, 92100, 74690, 92098, 74732, 92140, 74726, 92134, 128552, 129814, 90876, 128548, 90750, 128546, 123944, + 127510, 126056, 128566, 126052, 123938, 126050, 116840, 123958, 121064, 116836, 121060, 116834, 121058, 102632, 116854, 111080, + 121078, 111076, 102626, 111074, 74216, 102646, 91112, 74212, 91108, 74210, 91106, 74230, 91126, 128532, 90494, 128530, + 123924, 126004, 123922, 126002, 116788, 120948, 116786, 120946, 102516, 110836, 102514, 110834, 73972, 90612, 73970, 90610, + 128522, 123914, 125978, 116762, 120890, 102458, 110714, 123552, 127320, 129198, 123536, 127308, 123528, 127302, 123524, 123522, + 116128, 123608, 127342, 116112, 123596, 116104, 123590, 116100, 116098, 101280, 116184, 123630, 101264, 116172, 101256, 116166, + 101252, 101250, 71584, 101336, 116206, 71568, 101324, 71560, 101318, 71556, 71554, 71640, 101358, 71628, 71622, 71662, + 127824, 129452, 79352, 127816, 129446, 79100, 127812, 78974, 127810, 123472, 127276, 124624, 123464, 127270, 124616, 127846, + 124612, 123458, 124610, 115920, 123500, 118224, 115912, 123494, 118216, 124646, 118212, 115906, 118210, 100816, 115948, 105424, + 100808, 115942, 105416, 118246, 105412, 100802, 105410, 70608, 100844, 79824, 70600, 100838, 79816, 105446, 79812, 70594, + 79810, 70636, 79852, 70630, 79846, 129960, 95728, 113404, 129956, 95480, 113278, 129954, 95356, 95294, 127784, 129430, + 78588, 128872, 129974, 95996, 78462, 128868, 127778, 95870, 128866, 123432, 127254, 124520, 123428, 126696, 128886, 123426, + 126692, 124514, 126690, 115816, 123446, 117992, 115812, 122344, 117988, 115810, 122340, 117986, 122338, 100584, 115830, 104936, + 100580, 113640, 104932, 100578, 113636, 104930, 113634, 70120, 100598, 78824, 70116, 96232, 78820, 70114, 96228, 78818, + 96226, 70134, 78838, 129940, 94968, 113022, 129938, 94844, 94782, 127764, 78206, 128820, 127762, 95102, 128818, 123412, + 124468, 123410, 126580, 124466, 126578, 115764, 117876, 115762, 122100, 117874, 122098, 100468, 104692, 100466, 113140, 104690, + 113138, 69876, 78324, 69874, 95220, 78322, 95218, 129930, 94588, 94526, 127754, 128794, 123402, 124442, 126522, 115738, + 117818, 121978, 100410, 104570, 112890, 69754, 78074, 94714, 94398, 123216, 127148, 123208, 127142, 123204, 123202, 115408, + 123244, 115400, 123238, 115396, 115394, 99792, 115436, 99784, 115430, 99780, 99778, 68560, 99820, 68552, 99814, 68548, + 68546, 68588, 68582, 127400, 129238, 72444, 127396, 72318, 127394, 123176, 127126, 123752, 123172, 123748, 123170, 123746, + 115304, 123190, 116456, 115300, 116452, 115298, 116450, 99560, 115318, 101864, 99556, 101860, 99554, 101858, 68072, 99574, + 72680, 68068, 72676, 68066, 72674, 68086, 72694, 129492, 80632, 105854, 129490, 80508, 80446, 127380, 72062, 127924, + 127378, 80766, 127922, 123156, 123700, 123154, 124788, 123698, 124786, 115252, 116340, 115250, 118516, 116338, 118514, 99444, + 101620, 99442, 105972, 101618, 105970, 67828, 72180, 67826, 80884, 72178, 80882, 97008, 114044, 96888, 113982, 96828, + 96798, 129482, 80252, 130010, 97148, 80190, 97086, 127370, 127898, 128954, 123146, 123674, 124730, 126842, 115226, 116282, + 118394, 122618, 99386, 101498, 105722, 114170, 67706, 71930, 80378, 96632, 113854, 96572, 96542, 80062, 96702, 96444, + 96414, 96350, 123048, 123044, 123042, 115048, 123062, 115044, 115042, 99048, 115062, 99044, 99042, 67048, 99062, 67044, + 67042, 67062, 127188, 68990, 127186, 123028, 123316, 123026, 123314, 114996, 115572, 114994, 115570, 98932, 100084, 98930, + 100082, 66804, 69108, 66802, 69106, 129258, 73084, 73022, 127178, 127450, 123018, 123290, 123834, 114970, 115514, 116602, + 98874, 99962, 102138, 66682, 68858, 73210, 81272, 106174, 81212, 81182, 72894, 81342, 97648, 114364, 97592, 114334, + 97564, 97550, 81084, 97724, 81054, 97694, 97464, 114270, 97436, 97422, 80990, 97502, 97372, 97358, 97326, 114868, + 114866, 98676, 98674, 66292, 66290, 123098, 114842, 115130, 98618, 99194, 66170, 67322, 69310, 73404, 73374, 81592, + 106334, 81564, 81550, 73310, 81630, 97968, 114524, 97944, 114510, 97932, 97926, 81500, 98012, 81486, 97998, 97880, + 114478, 97868, 97862, 81454, 97902, 97836, 97830, 69470, 73564, 73550, 81752, 106414, 81740, 81734, 73518, 81774, + 81708, 81702, 109536, 120312, 86976, 109040, 120060, 86496, 108792, 119934, 86256, 108668, 86136, 129744, 89056, 110072, + 129736, 88560, 109820, 129732, 88312, 109694, 129730, 88188, 128464, 129772, 89592, 128456, 129766, 89340, 128452, 89214, + 128450, 125904, 128492, 125896, 128486, 125892, 125890, 120784, 125932, 120776, 125926, 120772, 120770, 110544, 120812, 110536, + 120806, 110532, 84928, 108016, 119548, 84448, 107768, 119422, 84208, 107644, 84088, 107582, 84028, 129640, 85488, 108284, + 129636, 85240, 108158, 129634, 85116, 85054, 128232, 129654, 85756, 128228, 85630, 128226, 125416, 128246, 125412, 125410, + 119784, 125430, 119780, 119778, 108520, 119798, 108516, 108514, 83424, 107256, 119166, 83184, 107132, 83064, 107070, 83004, + 82974, 129588, 83704, 107390, 129586, 83580, 83518, 128116, 83838, 128114, 125172, 125170, 119284, 119282, 107508, 107506, + 82672, 106876, 82552, 106814, 82492, 82462, 129562, 82812, 82750, 128058, 125050, 119034, 82296, 106686, 82236, 82206, + 82366, 82108, 82078, 76736, 103920, 117500, 76256, 103672, 117374, 76016, 103548, 75896, 103486, 75836, 129384, 77296, + 104188, 129380, 77048, 104062, 129378, 76924, 76862, 127720, 129398, 77564, 127716, 77438, 127714, 124392, 127734, 124388, + 124386, 117736, 124406, 117732, 117730, 104424, 117750, 104420, 104418, 112096, 121592, 126334, 92608, 111856, 121468, 92384, + 111736, 121406, 92272, 111676, 92216, 111646, 92188, 75232, 103160, 117118, 93664, 74992, 103036, 93424, 112252, 102974, + 93304, 74812, 93244, 74782, 93214, 129332, 75512, 103294, 129908, 129330, 93944, 75388, 129906, 93820, 75326, 93758, + 127604, 75646, 128756, 127602, 94078, 128754, 124148, 126452, 124146, 126450, 117236, 121844, 117234, 121842, 103412, 103410, + 91584, 111344, 121212, 91360, 111224, 121150, 91248, 111164, 91192, 111134, 91164, 91150, 74480, 102780, 91888, 74360, + 102718, 91768, 111422, 91708, 74270, 91678, 129306, 74620, 129850, 92028, 74558, 91966, 127546, 128634, 124026, 126202, + 116986, 121338, 102906, 90848, 110968, 121022, 90736, 110908, 90680, 110878, 90652, 90638, 74104, 102590, 91000, 74044, + 90940, 74014, 90910, 74174, 91070, 90480, 110780, 90424, 110750, 90396, 90382, 73916, 90556, 73886, 90526, 90296, + 110686, 90268, 90254, 73822, 90334, 90204, 90190, 71136, 101112, 116094, 70896, 100988, 70776, 100926, 70716, 70686, + 129204, 71416, 101246, 129202, 71292, 71230, 127348, 71550, 127346, 123636, 123634, 116212, 116210, 101364, 101362, 79296, + 105200, 118140, 79072, 105080, 118078, 78960, 105020, 78904, 104990, 78876, 78862, 70384, 100732, 79600, 70264, 100670, + 79480, 105278, 79420, 70174, 79390, 129178, 70524, 129466, 79740, 70462, 79678, 127290, 127866, 123514, 124666, 115962, + 118266, 100858, 113376, 122232, 126654, 95424, 113264, 122172, 95328, 113208, 122142, 95280, 113180, 95256, 113166, 95244, + 78560, 104824, 117950, 95968, 78448, 104764, 95856, 113468, 104734, 95800, 78364, 95772, 78350, 95758, 70008, 100542, + 78712, 69948, 96120, 78652, 69918, 96060, 78622, 96030, 70078, 78782, 96190, 94912, 113008, 122044, 94816, 112952, + 122014, 94768, 112924, 94744, 112910, 94732, 94726, 78192, 104636, 95088, 78136, 104606, 95032, 113054, 95004, 78094, + 94990, 69820, 78268, 69790, 95164, 78238, 95134, 94560, 112824, 121950, 94512, 112796, 94488, 112782, 94476, 94470, + 78008, 104542, 94648, 77980, 94620, 77966, 94606, 69726, 78046, 94686, 94384, 112732, 94360, 112718, 94348, 94342, + 77916, 94428, 77902, 94414, 94296, 112686, 94284, 94278, 77870, 94318, 94252, 94246, 68336, 99708, 68216, 99646, + 68156, 68126, 68476, 68414, 127162, 123258, 115450, 99834, 72416, 101752, 116414, 72304, 101692, 72248, 101662, 72220, + 72206, 67960, 99518, 72568, 67900, 72508, 67870, 72478, 68030, 72638, 80576, 105840, 118460, 80480, 105784, 118430, + 80432, 105756, 80408, 105742, 80396, 80390, 72048, 101564, 80752, 71992, 101534, 80696, 71964, 80668, 71950, 80654, + 67772, 72124, 67742, 80828, 72094, 80798, 114016, 122552, 126814, 96832, 113968, 122524, 96800, 113944, 122510, 96784, + 113932, 96776, 113926, 96772, 80224, 105656, 118366, 97120, 80176, 105628, 97072, 114076, 105614, 97048, 80140, 97036, + 80134, 97030, 71864, 101470, 80312, 71836, 97208, 80284, 71822, 97180, 80270, 97166, 67678, 71902, 80350, 97246, + 96576, 113840, 122460, 96544, 113816, 122446, 96528, 113804, 96520, 113798, 96516, 96514, 80048, 105564, 96688, 80024, + 105550, 96664, 113870, 96652, 80006, 96646, 71772, 80092, 71758, 96732, 80078, 96718, 96416, 113752, 122414, 96400, + 113740, 96392, 113734, 96388, 96386, 79960, 105518, 96472, 79948, 96460, 79942, 96454, 71726, 79982, 96494, 96336, + 113708, 96328, 113702, 96324, 96322, 79916, 96364, 79910, 96358, 96296, 113686, 96292, 96290, 79894, 96310, 66936, + 99006, 66876, 66846, 67006, 68976, 100028, 68920, 99998, 68892, 68878, 66748, 69052, 66718, 69022, 73056, 102072, + 116574, 73008, 102044, 72984, 102030, 72972, 72966, 68792, 99934, 73144, 68764, 73116, 68750, 73102, 66654, 68830, + 73182, 81216, 106160, 118620, 81184, 106136, 118606, 81168, 106124, 81160, 106118, 81156, 81154, 72880, 101980, 81328, + 72856, 101966, 81304, 106190, 81292, 72838, 81286, 68700, 72924, 68686, 81372, 72910, 81358, 114336, 122712, 126894, + 114320, 122700, 114312, 122694, 114308, 114306, 81056, 106072, 118574, 97696, 81040, 106060, 97680, 114380, 106054, 97672, + 81028, 97668, 81026, 97666, 72792, 101934, 81112, 72780, 97752, 81100, 72774, 97740, 81094, 97734, 68654, 72814, + 81134, 97774, 114256, 122668, 114248, 122662, 114244, 114242, 80976, 106028, 97488, 80968, 106022, 97480, 114278, 97476, + 80962, 97474, 72748, 81004, 72742, 97516, 80998, 97510, 114216, 122646, 114212, 114210, 80936, 106006, 97384, 80932, + 97380, 80930, 97378, 72726, 80950, 97398, 114196, 114194, 80916, 97332, 80914, 97330, 66236, 66206, 67256, 99166, + 67228, 67214, 66142, 67294, 69296, 100188, 69272, 100174, 69260, 69254, 67164, 69340, 67150, 69326, 73376, 102232, + 116654, 73360, 102220, 73352, 102214, 73348, 73346, 69208, 100142, 73432, 102254, 73420, 69190, 73414, 67118, 69230, + 73454, 106320, 118700, 106312, 118694, 106308, 106306, 73296, 102188, 81616, 106348, 102182, 81608, 73284, 81604, 73282, + 81602, 69164, 73324, 69158, 81644, 73318, 81638, 122792, 126934, 122788, 122786, 106280, 118678, 114536, 106276, 114532, + 106274, 114530, 73256, 102166, 81512, 73252, 98024, 81508, 73250, 98020, 81506, 98018, 69142, 73270, 81526, 98038, + 122772, 122770, 106260, 114484, 106258, 114482, 73236, 81460, 73234, 97908, 81458, 97906, 122762, 106250, 114458, 73226, + 81434, 97850, 66396, 66382, 67416, 99246, 67404, 67398, 66350, 67438, 69456, 100268, 69448, 100262, 69444, 69442, + 67372, 69484, 67366, 69478, 102312, 116694, 102308, 102306, 69416, 100246, 73576, 102326, 73572, 69410, 73570, 67350, + 69430, 73590, 118740, 118738, 102292, 106420, 102290, 106418, 69396, 73524, 69394, 81780, 73522, 81778, 118730, 102282, + 106394, 69386, 73498, 81722, 66476, 66470, 67496, 99286, 67492, 67490, 66454, 67510, 100308, 100306, 67476, 69556, + 67474, 69554, 116714 + ]; + + /// + /// Writes the eight element widths of a symbol character, a bar first. + /// + /// The element widths of the row. + /// The index of the next element, advanced past the character. + /// The 17 modules of the character, as holds them. + public static void AppendCharacter(Span runs, ref int index, int modules) + => AppendElements(runs, ref index, modules, ModuleCount, ElementCount); + + /// + /// Writes the element widths of a pattern that starts with a bar, whose modules are held as bits with the first module in the highest bit and a set bit for + /// a dark module. + /// + /// The element widths of the row. + /// The index of the next element, advanced past the pattern. + /// The modules of the pattern. + /// The number of modules in the pattern. + /// The number of elements in the pattern. + public static void AppendElements(Span runs, ref int index, int modules, int moduleCount, int elementCount) + { + // Each element is a run of equal bits, read from the first module down. + int bit = moduleCount - 1; + for (int element = 0; element < elementCount; element++) + { + int dark = (modules >> bit) & 1; + int width = 0; + while (bit >= 0 && ((modules >> bit) & 1) == dark) + { + width++; + bit--; + } + + runs[index++] = width; + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Pdf417/Pdf417Symbology.cs b/src/ImageSharp.Drawing/Barcodes/Pdf417/Pdf417Symbology.cs new file mode 100644 index 00000000..d2324b3a --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Pdf417/Pdf417Symbology.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The PDF417 symbology, ISO/IEC 15438. A symbol is 3 to 90 rows of 1 to 30 data columns, each row between a start character, a left and a right row indicator +/// and a stop character. It carries the characters U+0000 to U+00FF with 2 to 512 error correction codewords. The bar height of the options sets each row, and +/// section 5.8.2 requires "Y >= 3X", so a bar height under 3X draws at 3X. +/// +public sealed class Pdf417Symbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. The number of data columns follows from the data. The error correction level is + /// the recommended minimum of Annex E of ISO/IEC 15438 for the data, raised to the highest level the rows leave room for. Table E.1 ends at 863 data + /// codewords, and more data takes the highest level that fits. + /// + public Pdf417Symbology() + { + } + + /// + /// Initializes a new instance of the class. The error correction level is the recommended minimum of Annex E of ISO/IEC 15438 + /// for the data, raised to the highest level the rows leave room for. Table E.1 ends at 863 data codewords, and more data takes the highest level that + /// fits. + /// + /// The number of data columns in a row, 1 to 30. The row count follows from the data. + public Pdf417Symbology(int columns) + { + Guard.MustBeBetweenOrEqualTo(columns, Pdf417Encoder.MinimumColumns, Pdf417Encoder.MaximumColumns, nameof(columns)); + + this.Columns = columns; + } + + /// + /// Initializes a new instance of the class. + /// + /// The number of data columns in a row, 1 to 30. The row count follows from the data. + /// The error correction level, 0 to 8, which gives 2 to 512 error correction codewords. + public Pdf417Symbology(int columns, int errorCorrectionLevel) + : this(columns) + { + Guard.MustBeBetweenOrEqualTo(errorCorrectionLevel, 0, Pdf417Encoder.MaximumErrorCorrectionLevel, nameof(errorCorrectionLevel)); + + this.ErrorCorrectionLevel = errorCorrectionLevel; + } + + /// + /// Gets the number of data columns in a row, or when it follows from the data. + /// + public int? Columns { get; } + + /// + /// Gets the error correction level, or when it follows from the data. + /// + public int? ErrorCorrectionLevel { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Pdf417Encoder.BuildSymbol(text, this.Columns, this.ErrorCorrectionLevel, false, options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Pharmacode/PharmacodeEncoder.cs b/src/ImageSharp.Drawing/Barcodes/Pharmacode/PharmacodeEncoder.cs new file mode 100644 index 00000000..1c98da71 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Pharmacode/PharmacodeEncoder.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Globalization; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The input handling the one-track and two-track Pharmacode symbologies share. Both carry one number, given in decimal. The Laetus Pharmacode Guide counts +/// the codes of up to sixteen bars in section 4.1, and section 4.9.2 says that "coding that is only one bar wide should never be used", which sets the +/// range. +/// +internal static class PharmacodeEncoder +{ + /// + /// Parses the number a Pharmacode symbol carries and checks its range. + /// + /// The digits of the number. + /// The smallest value the symbology carries. + /// The largest value the symbology carries. + /// The number of digits of . + /// The value. + public static int ParseValue(ReadOnlySpan text, int minimum, int maximum, int maximumDigits) + { + Guard.MustBeBetweenOrEqualTo(text.Length, 1, maximumDigits, nameof(text)); + + BarcodeSymbology.ValidateDigits(text); + int value = int.Parse(text, NumberStyles.None, CultureInfo.InvariantCulture); + + Guard.MustBeBetweenOrEqualTo(value, minimum, maximum, nameof(text)); + + return value; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Pharmacode/PharmacodeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Pharmacode/PharmacodeSymbology.cs new file mode 100644 index 00000000..e9ca3138 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Pharmacode/PharmacodeSymbology.cs @@ -0,0 +1,120 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The one-track Pharmacode symbology, which the Laetus Pharmacode Guide defines. It carries one number from 3 to 131070 in two to sixteen bars, read from the +/// right. The table of section 4.3 gives the bar values. A thin bar in position n has the value 2 to the power n - 1. A thick bar has twice that value. Section +/// 4.4 builds the code from the value Z. While Z is not zero, the next bar takes the lowest position left. When Z is even, this bar is thick and Z becomes +/// (Z - 2) / 2. When Z is odd, this bar is thin and Z becomes (Z - 1) / 2. +/// +/// Section 1.2 gives the standard dimensions: a thin bar of 0.5 mm, a thick bar of 1.5 mm, a gap of 1.0 mm and a height of 8.0 mm, so a thin bar is 1 +/// module, a thick bar 3 and a gap 2. The X dimension is the width of the thin bar, from 0.3 mm to 0.7 mm. From 0.45 mm to 0.7 mm the bars and gaps keep +/// the limits of the standard Pharmacode of section 1.2, and from 0.3 mm to 0.45 mm the limits of the miniature Pharmacode of section 1.3. The bars are +/// 8.0 mm high when the caller sets no height, and the symbol keeps 6 mm clear on either side, the gap section 2.2.4 gives between the first or last code bar +/// and other print. The guide defines no text line, so a font prints none. +/// +/// +public sealed class PharmacodeSymbology : BarcodeSymbology +{ + /// + /// The smallest value, two thin bars. Section 4.9.2: "coding that is only one bar wide should never be used". + /// + public const int MinimumValue = 3; + + /// + /// The largest value, sixteen thick bars, the last count of the table of section 4.1. + /// + public const int MaximumValue = 131070; + + /// + /// The number of digits of . + /// + private const int MaximumDigits = 6; + + /// + /// The nominal X dimension in millimetres: the 0.5 mm thin bar of section 1.2. + /// + private const double XDimension = 0.5; + + /// + /// The smallest X dimension in millimetres, the smallest thin bar of the miniature Pharmacode of section 1.3. A thick bar of three modules and a gap of two + /// stay inside the limits of section 1.3 up to 0.45 mm. + /// + private const double MinimumXDimension = 0.3; + + /// + /// The largest X dimension in millimetres, the largest thin bar of the standard Pharmacode of section 1.2. A thick bar of three modules and a gap of two + /// stay inside the limits of section 1.2 from 0.45 mm. + /// + private const double MaximumXDimension = 0.7; + + /// + /// The clear space in millimetres on each side. Section 2.2.4: "The gap between the first or last code bar and the extraneous print or the edges of the + /// paper or the start or end bar "D" should be a minimum of 6 mm." + /// + private const double QuietZone = 6; + + /// + /// The width of a thin bar in modules, 0.5 mm. + /// + private const int ThinBar = 1; + + /// + /// The width of a thick bar in modules, 1.5 mm. + /// + private const int ThickBar = 3; + + /// + /// The width of the gap between bars in modules, 1.0 mm. + /// + private const int Gap = 2; + + /// + /// The bar height in millimetres when the caller sets none, the standard code bar height of section 1.2. + /// + private const double NominalBarHeight = 8.0; + + /// + /// Initializes a new instance of the class. + /// + public PharmacodeSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + int value = PharmacodeEncoder.ParseValue(text, MinimumValue, MaximumValue, MaximumDigits); + + // Section 4.4 sets the bars from the lowest position, which is the right end of the symbol. + InlineArray16 barStorage = default; + Span bars = barStorage; + int count = 0; + while (value != 0) + { + bool thick = (value & 1) == 0; + bars[count++] = thick ? ThickBar : ThinBar; + value = (value - (thick ? 2 : 1)) / 2; + } + + double xDimension = options.ResolveXDimension(MinimumXDimension, XDimension, MaximumXDimension); + + Span runs = symbol.BeginLinear((count * 2) - 1, xDimension, out Span heights, out _); + + // The bars were found from the right end, so they are written in reverse, with a gap between each pair. + runs.Fill(Gap); + for (int i = 0; i < count; i++) + { + runs[i * 2] = bars[count - 1 - i]; + } + + // Section 1.2 gives the minimum and the maximum code bar height as "application-specific", so the bar height is not clamped. + float barHeight = options.ResolveBarHeight(xDimension, 0, (float)(NominalBarHeight / xDimension), float.PositiveInfinity); + heights.Fill(barHeight); + + float quietZone = (float)(QuietZone / xDimension); + symbol.EndLinear(quietZone, quietZone); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Pharmacode/TwoTrackPharmacodeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Pharmacode/TwoTrackPharmacodeSymbology.cs new file mode 100644 index 00000000..2095f378 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Pharmacode/TwoTrackPharmacodeSymbology.cs @@ -0,0 +1,128 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The two-track Pharmacode symbology, which the Laetus Pharmacode Guide defines. It carries one number from 4 to 64570080 in two to sixteen bars of one width, +/// read from the right, each of which is a lower half bar, an upper half bar or a full bar. The table of section 4.5 gives a bar in position n the value 3 to +/// the power n - 1 for a lower bar, twice that for an upper bar and three times that for a full bar. The code is built as in section 4.4: while the value is +/// not zero, its remainder modulo 3 picks the bar in the lowest position left, 1 a lower bar, 2 an upper bar and 0 a full bar, and the value less the bar's +/// weight is divided by 3. +/// +/// Section 1.4 gives the standard dimensions: bars and gaps of 1 mm, the two half bars of 4 mm and the full bar of 8 mm for a 4 mm two-track sensor head. Bars +/// and gaps are 1 module, the X dimension, which section 1.4 allows from 0.8 mm to 2 mm. The full bar is 8 mm high when the caller sets no height, and the +/// half bars are half the full bar. The symbol keeps 6 mm clear on either side, the gap section 2.2.4 gives between the first or last code bar and other +/// print. The guide defines no text line, so a font prints none. +/// +/// +public sealed class TwoTrackPharmacodeSymbology : BarcodeSymbology +{ + /// + /// The smallest value, two lower bars. Section 4.9.2: "coding that is only one bar wide should never be used". + /// + public const int MinimumValue = 4; + + /// + /// The largest value, sixteen full bars, the last count of the table of section 4.1. + /// + public const int MaximumValue = 64570080; + + /// + /// The number of digits of . + /// + private const int MaximumDigits = 8; + + /// + /// The nominal X dimension in millimetres: the 1 mm bar of section 1.4. + /// + private const double XDimension = 1; + + /// + /// The smallest X dimension in millimetres, the smallest bar width of section 1.4. + /// + private const double MinimumXDimension = 0.8; + + /// + /// The largest X dimension in millimetres, the largest bar width of section 1.4. + /// + private const double MaximumXDimension = 2; + + /// + /// The clear space in millimetres on each side. Section 2.2.4: "The gap between the first or last code bar and the extraneous print or the edges of the + /// paper or the start or end bar "D" should be a minimum of 6 mm." + /// + private const double QuietZone = 6; + + /// + /// The height of the full bar in millimetres when the caller sets none, the long code bar of section 1.4 for a 4 mm two-track sensor head. + /// + private const double NominalBarHeight = 8; + + /// + /// The radix of the bar weights: a bar in position n weighs 3 to the power n - 1 times its kind. + /// + private const int Radix = 3; + + /// + /// Initializes a new instance of the class. + /// + public TwoTrackPharmacodeSymbology() + { + } + + /// + /// The kinds of bar, each valued at the weight the table of section 4.5 gives it in the lowest position. + /// + private enum TrackBar + { + /// + /// The lower half bar, weight 1. + /// + Lower = 1, + + /// + /// The upper half bar, weight 2. + /// + Upper = 2, + + /// + /// The full bar, weight 3. + /// + Full = 3, + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + int value = PharmacodeEncoder.ParseValue(text, MinimumValue, MaximumValue, MaximumDigits); + + // The bars from the lowest position. A remainder of 0 picks the full bar, whose weight is the radix itself. + InlineArray16 barStorage = default; + Span bars = barStorage; + int count = 0; + while (value != 0) + { + int remainder = value % Radix; + TrackBar bar = remainder == 0 ? TrackBar.Full : (TrackBar)remainder; + bars[count++] = bar; + value = (value - (int)bar) / Radix; + } + + double xDimension = options.ResolveXDimension(MinimumXDimension, XDimension, MaximumXDimension); + + float barHeight = options.ResolveBarHeight(xDimension, 0, (float)(NominalBarHeight / xDimension), float.PositiveInfinity); + float halfHeight = barHeight * 0.5F; + Span runs = symbol.BeginLinear((count * 2) - 1, xDimension, out Span heights, out Span tops); + runs.Fill(1); + for (int i = 0; i < count; i++) + { + TrackBar bar = bars[count - 1 - i]; + heights[i] = bar == TrackBar.Full ? barHeight : halfHeight; + tops[i] = bar == TrackBar.Lower ? halfHeight : 0F; + } + + float quietZone = (float)(QuietZone / xDimension); + symbol.EndLinear(quietZone, quietZone); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Plessey/PlesseyEncoder.cs b/src/ImageSharp.Drawing/Barcodes/Plessey/PlesseyEncoder.cs new file mode 100644 index 00000000..aac39ed8 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Plessey/PlesseyEncoder.cs @@ -0,0 +1,225 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodation for the Plessey Code symbology. Every character is a hexadecimal digit sent as four bits, least significant bit first, and every bit is a bar and +/// a space one pitch wide: a 0 bit is a narrow bar and a wide space, and a 1 bit is a wide bar and a narrow space. A symbol is a margin, the start code 1101, +/// the data, eight cyclic redundancy check bits, a full pitch termination bar, the reversed start code 0011 and a margin. +/// +/// The pitch is five modules. The 1 bit is a three module bar and a two module space, and the 0 bit is a one module bar and a four module space, which lie +/// inside the width ranges the symbology gives at its standard pitch of 1.020 mm: 0.533 mm to 0.635 mm and 0.381 mm to 0.483 mm for the 1 bit, and 0.127 mm to +/// 0.229 mm and 0.787 mm to 0.889 mm for the 0 bit. +/// +/// +internal static class PlesseyEncoder +{ + /// + /// The largest number of characters a symbol carries. The symbology sets no maximum. + /// + public const int MaximumLength = 500; + + /// + /// The number of check characters a symbol carries, which hold the eight check bits. + /// + public const int CheckCharacterCount = 2; + + /// + /// The characters the symbology encodes, in value order. + /// + private const string Characters = "0123456789ABCDEF"; + + /// + /// The quiet zone in modules on each side: the margin of "4 bits" the symbology gives, at five modules per bit. + /// + private const int QuietZone = 20; + + /// + /// The nominal X dimension in millimetres: one fifth of the 1.020 mm pitch. + /// + private const double NominalXDimension = 1.020 / 5; + + /// + /// The number of bits in a character. + /// + private const int BitsPerCharacter = 4; + + /// + /// The number of check bits, which the generator polynomial of degree eight leaves. + /// + private const int CheckBits = 8; + + /// + /// The bar height as a fraction of the symbol width, quiet zones excluded, when the caller sets no height: 15 per cent. + /// + private const float NominalBarHeightFraction = 0.15F; + + /// + /// The generator polynomial of the check, x^8 + x^7 + x^6 + x^5 + x^3 + 1, as its nine coefficients from the highest power to the lowest in bits 0 to 8: + /// the coefficients 1, 1, 1, 1, 0, 1, 0, 0, 1. Bit 0 holds the leading term, which tests. + /// + private const int GeneratorMask = 0b1_0010_1111; + + /// + /// The width of the bar of a 1 bit in modules. + /// + private const int OneBar = 3; + + /// + /// The width of the space of a 1 bit in modules. + /// + private const int OneSpace = 2; + + /// + /// The width of the bar of a 0 bit in modules. + /// + private const int ZeroBar = 1; + + /// + /// The width of the space of a 0 bit in modules. + /// + private const int ZeroSpace = 4; + + /// + /// Gets the run widths of the start code 1101, sent least significant bit first, so the bits 1, 1, 0 and 1. + /// + private static ReadOnlySpan Start => [3, 2, 3, 2, 1, 4, 3, 2]; + + /// + /// Gets the run widths of the end of the symbol: the full pitch termination bar and the reversed start code, which is the start code read from the right, + /// so its bars and spaces are mirrored. + /// + private static ReadOnlySpan Stop => [5, 4, 1, 4, 1, 2, 3, 2, 3]; + + /// + /// Returns the value of a character, which is its index in . + /// + /// The code point to value. + /// The value, or -1 when the character is not in the set. + public static int Value(int codePoint) => codePoint switch + { + >= '0' and <= '9' => codePoint - '0', + >= 'A' and <= 'F' => codePoint - 'A' + 10, + _ => -1, + }; + + /// + /// Calculates the two check characters over the data. The encoder divides the data bits by the generator polynomial, least significant bit of each + /// character first. The eight bit remainder follows the data in the same order. As a result, the low four bits of the remainder form the first check + /// character, and the high four bits form the second. + /// + /// The data the check covers, already validated. + /// The buffer that receives the two check characters. + public static void CheckCharacters(ReadOnlySpan text, Span checks) + { + // The bit sequence is the data followed by eight zero bits. Wherever a 1 bit stands, the generator is subtracted from that position, which for bits is + // an exclusive or, and the eight bits left after the data are the remainder. A subtraction at position i reaches only the nine positions from i, so the + // division needs only those: the window holds them, bit k for position i + k. The first eight bits fill the window, and every later bit enters at the + // top, after which the division step for the lowest position runs. The eight zero bits that follow the data enter as nothing, so they only run the last + // steps. + int dataBits = text.Length * BitsPerCharacter; + int window = 0; + int position = 0; + foreach (char c in text) + { + int value = Value(c); + for (int bit = 0; bit < BitsPerCharacter; bit++, position++) + { + int next = (value >> bit) & 1; + if (position < CheckBits) + { + window |= next << position; + } + else + { + window = DivisionStep(window | (next << CheckBits)); + } + } + } + + // One division step runs for every data bit. The data bits after the first eight ran theirs above, so the zero bits run the rest. + for (int step = Math.Max(dataBits - CheckBits, 0); step < dataBits; step++) + { + window = DivisionStep(window); + } + + checks[0] = Characters[window & 0xF]; + checks[1] = Characters[window >> 4]; + } + + /// + /// Encodes the data and its check characters and builds the symbol. The runs start with the first bar of the start code and end on the last bar of the + /// reversed start code. Plessey Code carries no guard bars, so every bar runs the full height, and the human readable interpretation sits below the symbol: + /// the data, then the check characters when they print. + /// + /// The data, already validated. + /// The two check characters. + /// The check characters the human readable interpretation prints, or none. + /// The options that control layout choices. + /// The symbol to write. + public static void BuildSymbol( + ReadOnlySpan data, + ReadOnlySpan checks, + ReadOnlySpan printedChecks, + BarcodeOptions options, + ref BarcodeSymbol symbol) + { + int runCount = Start.Length + ((data.Length + checks.Length) * BitsPerCharacter * 2) + Stop.Length; + double xDimension = options.ResolveXDimension(0, NominalXDimension, double.PositiveInfinity); + Span runs = symbol.BeginLinear(runCount, xDimension, out Span heights, out _); + int written = 0; + foreach (byte width in Start) + { + runs[written++] = width; + } + + AppendCharacters(runs, ref written, data); + AppendCharacters(runs, ref written, checks); + foreach (byte width in Stop) + { + runs[written++] = width; + } + + int widthInModules = BarcodeSymbol.RunWidth(runs); + + float barHeight = options.ResolveBarHeight(xDimension, 0, widthInModules * NominalBarHeightFraction, float.PositiveInfinity); + heights.Fill(barHeight); + + if (options.Font is not null) + { + symbol.ReserveText(1, data.Length + printedChecks.Length); + symbol.AddText(data, printedChecks, [], 0F, widthInModules, BarcodeTextSide.BelowBars, barHeight + BarcodeTextPlacement.Clearance); + } + + symbol.EndLinear(QuietZone, QuietZone); + } + + /// + /// Writes the bar and space of every bit of every character, least significant bit first. + /// + /// The buffer the widths are written to. + /// The write position, advanced by two runs a bit. + /// The characters, already validated. + private static void AppendCharacters(Span runs, ref int written, ReadOnlySpan characters) + { + foreach (char c in characters) + { + int value = Value(c); + for (int bit = 0; bit < BitsPerCharacter; bit++) + { + bool one = ((value >> bit) & 1) == 1; + runs[written++] = one ? OneBar : ZeroBar; + runs[written++] = one ? OneSpace : ZeroSpace; + } + } + } + + /// + /// Runs one step of the check division on a window that holds nine sequence positions, bit k for position i + k. Where position i is 1 the generator is + /// subtracted, which clears it, and the window then moves on one position, so its top bit is free for the next position. + /// + /// The window, positions i to i + 8. + /// The window, positions i + 1 to i + 8. + private static int DivisionStep(int window) => ((window & 1) != 0 ? window ^ GeneratorMask : window) >> 1; +} diff --git a/src/ImageSharp.Drawing/Barcodes/Plessey/PlesseySymbology.cs b/src/ImageSharp.Drawing/Barcodes/Plessey/PlesseySymbology.cs new file mode 100644 index 00000000..a445fe7e --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Plessey/PlesseySymbology.cs @@ -0,0 +1,92 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Plessey Code symbology. It carries the hexadecimal digits 0 to 9 and A to F between a start code and a termination bar with the reversed start code, +/// which the printed line does not show. +/// +/// Every symbol carries two check characters, which hold the eight bit remainder of the data divided by the generator polynomial x^8 + x^7 + x^6 + x^5 + x^3 + +/// 1. The encoder calculates them, or validates the two the input ends with, and by default the printed line shows the data only. +/// +/// +public sealed class PlesseySymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public PlesseySymbology() + : this(false, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the input ends with the two check characters, which the encoder validates, rather than with data, for + /// which the encoder calculates them. + public PlesseySymbology(bool validateCheckCharacters) + : this(validateCheckCharacters, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether the input ends with the two check characters, which the encoder validates, rather than with data, for + /// which the encoder calculates them. + /// Whether the two check characters are part of the human readable interpretation. + public PlesseySymbology(bool validateCheckCharacters, bool printCheckCharacters) + { + this.ValidateCheckCharacters = validateCheckCharacters; + this.PrintCheckCharacters = printCheckCharacters; + } + + /// + /// Gets a value indicating whether the input ends with the two check characters, which the encoder validates, rather than with data, for which the encoder + /// calculates them. + /// + public bool ValidateCheckCharacters { get; } + + /// + /// Gets a value indicating whether the two check characters are part of the human readable interpretation. + /// + public bool PrintCheckCharacters { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Guard.MustBeLessThanOrEqualTo(text.Length, PlesseyEncoder.MaximumLength, nameof(text)); + + int minimum = this.ValidateCheckCharacters ? PlesseyEncoder.CheckCharacterCount + 1 : 1; + Guard.MustBeGreaterThanOrEqualTo(text.Length, minimum, nameof(text)); + + SpanCodePointEnumerator codePoints = text.EnumerateCodePoints(); + while (codePoints.MoveNext()) + { + CodePoint current = codePoints.Current; + if (PlesseyEncoder.Value(current.Value) < 0) + { + throw new ArgumentException($"Plessey carries only the digits 0 to 9 and the letters A to F; got {current.ToDisplayString()}.", nameof(text)); + } + } + + ReadOnlySpan data = this.ValidateCheckCharacters ? text.Slice(0, text.Length - PlesseyEncoder.CheckCharacterCount) : text; + InlineArray2 checkStorage = default; + Span checks = checkStorage; + PlesseyEncoder.CheckCharacters(data, checks); + if (this.ValidateCheckCharacters && !text.Slice(data.Length).SequenceEqual(checks)) + { + throw new ArgumentException($"Incorrect check characters: expected {checks}, got {text.Slice(data.Length)}.", nameof(text)); + } + + // The symbol carries the data and the two check characters, which a validated text already ends with. The interpretation prints the check characters + // when the caller asks for them. + ReadOnlySpan printedChecks = this.PrintCheckCharacters ? checks : []; + PlesseyEncoder.BuildSymbol(data, checks, printedChecks, options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/PosiCode/PosiCodeEncoder.cs b/src/ImageSharp.Drawing/Barcodes/PosiCode/PosiCodeEncoder.cs new file mode 100644 index 00000000..f47ccae9 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/PosiCode/PosiCodeEncoder.cs @@ -0,0 +1,576 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodation for the PosiCode symbology, which AIM ITS/02-001 defines. Every character is three bars of one module and three spaces, and the data lies in the +/// positions of the bars, whose centres are 2G to 9G apart in version A and 3G to 10G apart in version B, where G is the bar width. A symbol is a quiet zone, a +/// start character, the data, a check character and a stop character. The data characters come from three character sets with latches and shifts between them, +/// characters above 127 are announced by the function 4 character, and the check character maps a cyclic redundancy check value to six bar positions. +/// +internal static class PosiCodeEncoder +{ + /// + /// The largest number of characters a symbol carries. The symbology sets no maximum. + /// + public const int MaximumLength = 500; + + /// + /// The number of characters a caller holds inline to build symbol data in. Longer data rents its buffer once. + /// + public const int InlineBufferLength = 64; + + /// + /// The bar height as a fraction of the symbol width, quiet zones excluded, when the caller sets no height: 15 per cent. + /// + private const float NominalBarHeightFraction = 0.15F; + + /// + /// The number of data codewords in the limited character set, the first 38 codewords of set 0. + /// + private const int LimitedCodewords = 38; + + /// + /// The number of runs in one data character: three bars and three spaces. + /// + private const int RunsPerCharacter = 6; + + /// + /// The number of runs in the check character: six bars and six spaces. + /// + private const int CheckRuns = 12; + + /// + /// The number of bar distances the check value maps to. + /// + private const int CheckDistances = 6; + + /// + /// The sum of the six check distances before the version B offset. + /// + private const int CheckDistanceSum = 20; + + /// + /// The polynomial the check divides by, applied bit by bit to the six bit codewords. + /// + private const int CheckPolynomial = 7682; + + /// + /// The number of bits of each codeword the check division reads. + /// + private const int CodewordBits = 6; + + /// + /// The mask of the ten bit remainder of the check division. + /// + private const int CheckValueMask = 1023; + + /// + /// The offset the standard versions add to the check remainder. + /// + private const int StandardCheckOffset = 45; + + /// + /// The first check remainder of the range the limited versions reserve. + /// + private const int LimitedReservedFirst = 825; + + /// + /// The last check remainder of the range the limited versions reserve. + /// + private const int LimitedReservedLast = 852; + + /// + /// The step that moves a limited check remainder out of the reserved range. + /// + private const int LimitedReservedStep = 292; + + /// + /// The number of entries in each row of . + /// + private const int CheckWeightColumns = 8; + + /// + /// The message value of the function 4 character. It is one past the ASCII values, so holds it beside the characters. + /// + private const byte Function4 = 128; + + /// + /// The codeword of the latch to the other one of sets 0 and 1. Both sets hold it at the same codeword. + /// + private const int LatchCodeword = 43; + + /// + /// The codeword of the shift to the other one of sets 0 and 1 for one character. Both sets hold it at the same codeword. + /// + private const int ShiftCodeword = 44; + + /// + /// The codeword of the shift to set 2 for one character. Both sets 0 and 1 hold it at the same codeword. + /// + private const int ShiftToSet2Codeword = 45; + + /// + /// The number of low bits of a entry that hold the codeword. The bits above hold the character set. + /// + private const int CodewordBitCount = 6; + + /// + /// The mask of the codeword bits of a entry. + /// + private const int CodewordMask = (1 << CodewordBitCount) - 1; + + /// + /// The character sets of the standard versions. Every ASCII character is in exactly one set. + /// + private enum CharacterSet + { + /// + /// Set 0: the digits, the capital letters and the symbols - . space $ / + %, then the latch to set 1 and the shifts to sets 1 and 2. + /// + Set0, + + /// + /// Set 1: the symbols ^ ; < = > ? @ [ \ ], the small letters and the symbols _ ` DEL { | } ~, then the latch to set 0 and the shifts to sets 0 + /// and 2. + /// + Set1, + + /// + /// Set 2: the apostrophe, the control characters 27 to 31, the symbols ! " # &, the control characters 1 to 26, the symbols ( ) NUL * , : and the + /// four function characters. + /// + Set2, + } + + /// + /// Gets the character set and the codeword of each message value: the ASCII characters 0 to 127 and then . Each entry is the set + /// shifted left by bits, combined with the codeword. + /// + private static ReadOnlySpan CharacterCodes => + [ + 0xA6, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0x81, 0x82, 0x83, 0x84, 0x85, + 0x26, 0x86, 0x87, 0x88, 0x27, 0x2A, 0x89, 0x80, 0xA4, 0xA5, 0xA7, 0x29, 0xA8, 0x24, 0x25, 0x28, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xA9, 0x41, 0x42, 0x43, 0x44, 0x45, + 0x46, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x47, 0x48, 0x49, 0x40, 0x64, + 0x65, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x67, 0x68, 0x69, 0x6A, 0x66, + 0xAD, + ]; + + /// + /// Gets the run widths of the 46 data codewords of version A, six per codeword in bar and space order. The bar centres of version B are one module further + /// apart, so version B adds one module to every space. + /// + private static ReadOnlySpan CharactersA => + [ + 1, 4, 1, 1, 1, 2, 1, 3, 1, 2, 1, 2, 1, 2, 1, 3, 1, 2, 1, 1, 1, 4, 1, 2, 1, 3, 1, 1, 1, 3, + 1, 2, 1, 2, 1, 3, 1, 1, 1, 3, 1, 3, 1, 2, 1, 1, 1, 4, 1, 1, 1, 2, 1, 4, 1, 1, 1, 1, 1, 5, + 1, 8, 1, 1, 1, 1, 1, 7, 1, 2, 1, 1, 1, 6, 1, 3, 1, 1, 1, 5, 1, 4, 1, 1, 1, 4, 1, 5, 1, 1, + 1, 3, 1, 6, 1, 1, 1, 2, 1, 7, 1, 1, 1, 1, 1, 8, 1, 1, 1, 7, 1, 1, 1, 2, 1, 6, 1, 2, 1, 2, + 1, 5, 1, 3, 1, 2, 1, 4, 1, 4, 1, 2, 1, 3, 1, 5, 1, 2, 1, 2, 1, 6, 1, 2, 1, 1, 1, 7, 1, 2, + 1, 6, 1, 1, 1, 3, 1, 5, 1, 2, 1, 3, 1, 4, 1, 3, 1, 3, 1, 3, 1, 4, 1, 3, 1, 2, 1, 5, 1, 3, + 1, 1, 1, 6, 1, 3, 1, 5, 1, 1, 1, 4, 1, 4, 1, 2, 1, 4, 1, 3, 1, 3, 1, 4, 1, 2, 1, 4, 1, 4, + 1, 1, 1, 5, 1, 4, 1, 4, 1, 1, 1, 5, 1, 3, 1, 2, 1, 5, 1, 2, 1, 3, 1, 5, 1, 1, 1, 4, 1, 5, + 1, 3, 1, 1, 1, 6, 1, 2, 1, 2, 1, 6, 1, 1, 1, 3, 1, 6, 1, 2, 1, 1, 1, 7, 1, 1, 1, 2, 1, 7, + 1, 1, 1, 1, 1, 8, + ]; + + /// + /// Gets the run widths of the 38 data codewords of Limited version A, in the layout of . Limited version B adds one module to + /// every space. + /// + private static ReadOnlySpan CharactersLimitedA => + [ + 1, 1, 1, 4, 1, 1, 1, 1, 1, 3, 1, 2, 1, 1, 1, 2, 1, 3, 1, 1, 1, 1, 1, 4, 1, 2, 1, 3, 1, 1, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 1, 3, 1, 4, 1, 1, 1, 1, 1, 3, 1, 2, 1, 1, 1, 3, 1, 1, 1, 2, + 1, 7, 1, 1, 1, 1, 1, 6, 1, 2, 1, 1, 1, 5, 1, 3, 1, 1, 1, 4, 1, 4, 1, 1, 1, 3, 1, 5, 1, 1, + 1, 2, 1, 6, 1, 1, 1, 1, 1, 7, 1, 1, 1, 6, 1, 1, 1, 2, 1, 5, 1, 2, 1, 2, 1, 4, 1, 3, 1, 2, + 1, 3, 1, 4, 1, 2, 1, 2, 1, 5, 1, 2, 1, 1, 1, 6, 1, 2, 1, 5, 1, 1, 1, 3, 1, 4, 1, 2, 1, 3, + 1, 3, 1, 3, 1, 3, 1, 2, 1, 4, 1, 3, 1, 1, 1, 5, 1, 3, 1, 4, 1, 1, 1, 4, 1, 3, 1, 2, 1, 4, + 1, 2, 1, 3, 1, 4, 1, 1, 1, 4, 1, 4, 1, 3, 1, 1, 1, 5, 1, 2, 1, 2, 1, 5, 1, 1, 1, 3, 1, 5, + 1, 2, 1, 1, 1, 6, 1, 1, 1, 2, 1, 6, 1, 1, 1, 1, 1, 7, + ]; + + /// + /// Gets the run widths of the start character of version A. + /// + private static ReadOnlySpan StartA => [1, 12, 1, 1, 1, 1, 1, 2]; + + /// + /// Gets the run widths of the start character of version B. + /// + private static ReadOnlySpan StartB => [1, 12, 1, 2, 1, 3, 1, 2]; + + /// + /// Gets the run widths of the start character of Limited version A. + /// + private static ReadOnlySpan StartLimitedA => [1, 5, 1, 1, 1, 1]; + + /// + /// Gets the run widths of the start character of Limited version B. + /// + private static ReadOnlySpan StartLimitedB => [1, 4, 1, 2, 1, 2]; + + /// + /// Gets the run widths of the stop character of version A. + /// + private static ReadOnlySpan StopA => [1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 1]; + + /// + /// Gets the run widths of the stop character of version B. + /// + private static ReadOnlySpan StopB => [1, 2, 1, 2, 1, 2, 1, 2, 1, 12, 1]; + + /// + /// Gets the run widths of the stop character of both limited versions: one bar. + /// + private static ReadOnlySpan StopLimited => [1]; + + /// + /// Gets the table that maps a check value to six bar distances. Row r holds the number of ways the remaining distances can be arranged when distance r + /// grows by one more, and the mapping walks the rows until the running sum reaches the check value. The entry at row r and column c is the binomial + /// coefficient C(12 - r - c, 4 - r). + /// + private static ReadOnlySpan CheckWeights => + [ + 495, 330, 210, 126, 70, 35, 15, 5, + 165, 120, 84, 56, 35, 20, 10, 4, + 45, 36, 28, 21, 15, 10, 6, 3, + 9, 8, 7, 6, 5, 4, 3, 2, + 1, 1, 1, 1, 1, 1, 1, 1, + ]; + + /// + /// Returns whether a version carries only the limited character set. + /// + /// The version. + /// Whether the version is limited. + public static bool IsLimited(PosiCodeVersion version) => version is PosiCodeVersion.LimitedA or PosiCodeVersion.LimitedB; + + /// + /// Returns the codeword of a character in the limited character set, which is the first 38 codewords of set 0. + /// + /// The code point to value. + /// The codeword, or -1 when the character is not in the set. + public static int LimitedValue(int codePoint) + { + if ((uint)codePoint >= Function4) + { + return -1; + } + + // A set 0 entry has no set bits above the codeword, so an entry below the limited count is a limited codeword of set 0. + int code = CharacterCodes[codePoint]; + return code < LimitedCodewords ? code : -1; + } + + /// + /// Converts character values to the codewords the symbol carries. Characters at or above 128 drop their high bit. The function 4 character announces these + /// characters. For a run shorter than five, or shorter than three at the end of the data, the function 4 character occurs once. For a longer run, the + /// function 4 character occurs twice and switches the mode. Then every character encodes in the current set, through a shift to set 2, through a latch, or + /// through a shift to the other set. The encoder uses the latch when the next character is also not in the current set. + /// + /// The character values, 0 to 255. + /// The buffer that receives the codewords, at least four per value. + /// The number of codewords written. + public static int ToCodewords(ReadOnlySpan values, Span codewords) + { + // The latch rule looks at the character after the current one, so the announced sequence is built first. Each value takes at most two function 4 + // characters before it, so the sequence is at most three times as long and the builder never grows: short data works inline and longer data rents once. + InlineArray128 messageStorage = default; + int capacity = values.Length * 3; + ValueListBuilder message = capacity <= 128 ? new(messageStorage) : new(capacity); + try + { + // Each run of values on one side of 128 is announced as a whole. A run too short to switch the mode takes a function 4 character before each of its + // values, and a longer run takes two before its first value, which switch the mode for the whole run. + bool extended = false; + int position = 0; + while (position < values.Length) + { + ReadOnlySpan rest = values[position..]; + bool high = rest[0] >= 128; + int run = high ? rest.IndexOfAnyInRange(0, 127) : rest.IndexOfAnyExceptInRange(0, 127); + if (run < 0) + { + run = rest.Length; + } + + bool announceEach = false; + if (extended != high) + { + int limit = run == rest.Length ? 3 : 5; + if (run < limit) + { + announceEach = true; + } + else + { + extended = high; + message.Append(Function4); + message.Append(Function4); + } + } + + foreach (int value in rest[..run]) + { + if (announceEach) + { + message.Append(Function4); + } + + message.Append((byte)(value & 127)); + } + + position += run; + } + + ReadOnlySpan codes = CharacterCodes; + ReadOnlySpan sequence = message.AsSpan(); + CharacterSet current = CharacterSet.Set0; + int written = 0; + for (int i = 0; i < sequence.Length; i++) + { + int code = codes[sequence[i]]; + CharacterSet set = (CharacterSet)(code >> CodewordBitCount); + int codeword = code & CodewordMask; + if (set == current) + { + codewords[written++] = codeword; + } + else if (set == CharacterSet.Set2) + { + codewords[written++] = ShiftToSet2Codeword; + codewords[written++] = codeword; + } + else if (i + 1 == sequence.Length || (CharacterSet)(codes[sequence[i + 1]] >> CodewordBitCount) != current) + { + // The character is in the other one of sets 0 and 1, and so is the next character or none follows: the latch serves both. + codewords[written++] = LatchCodeword; + codewords[written++] = codeword; + current = set; + } + else + { + codewords[written++] = ShiftCodeword; + codewords[written++] = codeword; + } + } + + return written; + } + finally + { + message.Dispose(); + } + } + + /// + /// Encodes codewords and builds the symbol. The runs start with the first bar of the start character and end on the last bar of the stop character, and + /// carry the start character, the codewords, the check character and the stop character. PosiCode carries no guard bars, so every bar runs the full height, + /// and the human readable interpretation sits below the symbol. + /// + /// The codewords the symbol carries. + /// The version, which sets the patterns and the quiet zone. + /// The human readable interpretation. + /// The options that control layout choices. + /// The symbol to write. + public static void BuildSymbol( + ReadOnlySpan codewords, + PosiCodeVersion version, + ReadOnlySpan readable, + BarcodeOptions options, + ref BarcodeSymbol symbol) + { + VersionPatterns patterns = version switch + { + PosiCodeVersion.A => new(CharactersA, StartA, StopA), + PosiCodeVersion.B => new(CharactersA, StartB, StopB), + PosiCodeVersion.LimitedA => new(CharactersLimitedA, StartLimitedA, StopLimited), + _ => new(CharactersLimitedA, StartLimitedB, StopLimited), + }; + + double xDimension = options.ResolveXDimension(0, BarcodeSymbology.DefaultXDimension, double.PositiveInfinity); + int runCount = patterns.Start.Length + (codewords.Length * RunsPerCharacter) + CheckRuns + patterns.Stop.Length; + Span runs = symbol.BeginLinear(runCount, xDimension, out Span heights, out _); + int written = 0; + AppendRuns(patterns.Start, runs, ref written); + + // A character starts with a bar, so its odd runs are its spaces. + int spaceOffset = SpaceOffset(version); + foreach (int codeword in codewords) + { + ReadOnlySpan widths = patterns.Characters.Slice(codeword * RunsPerCharacter, RunsPerCharacter); + for (int i = 0; i < widths.Length; i++) + { + runs[written++] = widths[i] + ((i & 1) * spaceOffset); + } + } + + AppendCheck(codewords, version, runs.Slice(written, CheckRuns)); + written += CheckRuns; + AppendRuns(patterns.Stop, runs, ref written); + + int widthInModules = BarcodeSymbol.RunWidth(runs); + + float barHeight = options.ResolveBarHeight(xDimension, 0, widthInModules * NominalBarHeightFraction, float.PositiveInfinity); + heights.Fill(barHeight); + + if (options.Font is not null) + { + symbol.ReserveText(1, readable.Length); + symbol.AddText(readable, 0F, widthInModules, BarcodeTextSide.BelowBars, barHeight + BarcodeTextPlacement.Clearance); + } + + int quietZone = QuietZone(version); + symbol.EndLinear(quietZone, quietZone); + } + + /// + /// Returns the quiet zone in modules on each side of a version: "12G on either side", and 13G for Limited PosiCode B. + /// + /// The version. + /// The quiet zone in modules. + private static int QuietZone(PosiCodeVersion version) => version == PosiCodeVersion.LimitedB ? 13 : 12; + + /// + /// Returns the modules that a version adds to every space of a data character and to every check distance. The bar centres are 2G to 9G apart in + /// version A and 3G to 10G apart in version B. + /// + /// The version. + /// 1 for version B and Limited version B, otherwise 0. + private static int SpaceOffset(PosiCodeVersion version) => version is PosiCodeVersion.B or PosiCodeVersion.LimitedB ? 1 : 0; + + /// + /// Writes run widths from a pattern table to the runs of the symbol. + /// + /// The run widths in modules. + /// The runs of the symbol. + /// The position in , advanced past the widths. + private static void AppendRuns(ReadOnlySpan widths, Span runs, ref int index) + { + foreach (byte width in widths) + { + runs[index++] = width; + } + } + + /// + /// Writes the twelve runs of the check character. The encoder divides the codewords bit by bit by the check polynomial. The standard versions offset the + /// ten bit remainder by 45. The limited versions move the remainder past a reserved range. Then the encoder maps the remainder to six distances that add to + /// twenty. Version B adds one to every distance. The character is six bars of one module whose spaces are the distances less one, written from the last + /// distance to the first. + /// + /// The codewords the check covers. + /// The version. + /// The twelve runs of the check character. + private static void AppendCheck(ReadOnlySpan codewords, PosiCodeVersion version, Span runs) + { + int value = 0; + foreach (int codeword in codewords) + { + int bits = codeword; + for (int bit = 0; bit < CodewordBits; bit++) + { + if (((bits ^ value) & 1) != 0) + { + value ^= CheckPolynomial; + } + + value >>= 1; + bits >>= 1; + } + } + + value &= CheckValueMask; + if (!IsLimited(version)) + { + value += StandardCheckOffset; + } + else if (value is >= LimitedReservedFirst and <= LimitedReservedLast) + { + value += LimitedReservedStep; + } + + // The walk keeps the running sum of the weights taken so far. A weight that reaches the value exactly ends the walk, a weight that passes it closes the + // distance of the current row, and a smaller weight is taken and widens that distance by one. + InlineArray6 distanceStorage = default; + Span distances = distanceStorage; + distances.Fill(2); + ReadOnlySpan weights = CheckWeights; + int row = 0; + int column = 0; + int width = 0; + int sum = 0; + while (sum != value) + { + int total = sum + weights[(row * CheckWeightColumns) + column]; + if (total == value) + { + width++; + distances[row] = width + 2; + sum = total; + } + else if (total > value) + { + distances[row] = width + 2; + row++; + width = 0; + } + else + { + column++; + width++; + sum = total; + } + } + + distances[CheckDistances - 1] = CheckDistanceSum - distances[0] - distances[1] - distances[2] - distances[3] - distances[4]; + int offset = SpaceOffset(version); + for (int i = 0; i < CheckDistances; i++) + { + runs[i * 2] = 1; + runs[(i * 2) + 1] = distances[CheckDistances - 1 - i] + offset - 1; + } + } + + /// + /// The run widths of the characters of one version. + /// + private readonly ref struct VersionPatterns + { + /// + /// Initializes a new instance of the struct. + /// + /// The run widths of the data codewords, per codeword. + /// The run widths of the start character. + /// The run widths of the stop character. + public VersionPatterns(ReadOnlySpan characters, ReadOnlySpan start, ReadOnlySpan stop) + { + this.Characters = characters; + this.Start = start; + this.Stop = stop; + } + + /// + /// Gets the run widths of the data codewords, per codeword. + /// + public ReadOnlySpan Characters { get; } + + /// + /// Gets the run widths of the start character. + /// + public ReadOnlySpan Start { get; } + + /// + /// Gets the run widths of the stop character. + /// + public ReadOnlySpan Stop { get; } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/PosiCode/PosiCodeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/PosiCode/PosiCodeSymbology.cs new file mode 100644 index 00000000..3965da56 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/PosiCode/PosiCodeSymbology.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The PosiCode symbology, which AIM ITS/02-001 defines. The data lies in the positions of bars of one width. Versions A and B carry all 256 values of ISO/IEC +/// 8859-1, and the Limited versions carry the digits, the capital letters, the hyphen and the full stop. Every symbol carries a cyclic redundancy check +/// character, which the printed line does not show, between a start character and a stop character. The printed line shows the text as given. +/// +public sealed class PosiCodeSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public PosiCodeSymbology() + : this(PosiCodeVersion.A) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The version, which sets the character set and the bar positions. + public PosiCodeSymbology(PosiCodeVersion version) + => this.Version = version; + + /// + /// Gets the version, which sets the character set and the bar positions. + /// + public PosiCodeVersion Version { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Guard.MustBeGreaterThan(text.Length, 0, nameof(text)); + Guard.MustBeLessThanOrEqualTo(text.Length, PosiCodeEncoder.MaximumLength, nameof(text)); + + bool limited = PosiCodeEncoder.IsLimited(this.Version); + + // One value for each character, so the builder never grows: short text works inline and longer text rents once. + InlineArray64 valueStorage = default; + ValueListBuilder values = text.Length <= PosiCodeEncoder.InlineBufferLength ? new(valueStorage) : new(text.Length); + PooledBuffer rentedCodewords = default; + try + { + SpanCodePointEnumerator codePoints = text.EnumerateCodePoints(); + while (codePoints.MoveNext()) + { + CodePoint current = codePoints.Current; + if (limited) + { + int codeword = PosiCodeEncoder.LimitedValue(current.Value); + if (codeword < 0) + { + throw new ArgumentException( + $"Limited PosiCode carries only digits, capital letters, the hyphen and the full stop; got {current.ToDisplayString()}.", + nameof(text)); + } + + values.Append(codeword); + } + else + { + if (current.Value > 255) + { + throw new ArgumentException($"PosiCode carries only the characters of ISO/IEC 8859-1; got {current.ToDisplayString()}.", nameof(text)); + } + + values.Append(current.Value); + } + } + + if (limited) + { + PosiCodeEncoder.BuildSymbol(values.AsSpan(), this.Version, text, options, ref symbol); + } + else + { + // ToCodewords writes at most four codewords for each value. + InlineArray128 codewordStorage = default; + int capacity = values.Length * 4; + Span codewords = capacity <= 128 + ? codewordStorage + : (rentedCodewords = new PooledBuffer(capacity)).Span; + + int written = PosiCodeEncoder.ToCodewords(values.AsSpan(), codewords); + PosiCodeEncoder.BuildSymbol(codewords[..written], this.Version, text, options, ref symbol); + } + } + finally + { + values.Dispose(); + rentedCodewords.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/PosiCode/PosiCodeVersion.cs b/src/ImageSharp.Drawing/Barcodes/PosiCode/PosiCodeVersion.cs new file mode 100644 index 00000000..dcec4ad9 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/PosiCode/PosiCodeVersion.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Specifies the PosiCode version, which sets the character set, the distances between the bar centres, and the start and stop characters. +/// +public enum PosiCodeVersion +{ + /// + /// PosiCode A, whose bar centres lie 2G to 9G apart. It carries all 256 values of ISO/IEC 8859-1. + /// + A, + + /// + /// PosiCode B, whose bar centres lie 3G to 10G apart. It carries all 256 values of ISO/IEC 8859-1. + /// + B, + + /// + /// Limited PosiCode A, which carries the digits 0 to 9, the letters A to Z, the hyphen and the full stop. + /// + LimitedA, + + /// + /// Limited PosiCode B, with the wider bar centre distances, which carries the same characters as . + /// + LimitedB, +} diff --git a/src/ImageSharp.Drawing/Barcodes/Postal/AustraliaPostEncoder.cs b/src/ImageSharp.Drawing/Barcodes/Postal/AustraliaPostEncoder.cs new file mode 100644 index 00000000..cfba14a0 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Postal/AustraliaPostEncoder.cs @@ -0,0 +1,345 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodation for the Australia Post 4-state customer barcode, from the Australia Post Customer Barcoding Technical Specifications. A bar has a value of 0 for +/// a full bar, 1 for an ascender, 2 for a descender and 3 for a tracker. A symbol is the start bars, the format control code, the sorting code, the customer +/// information field with its filler bars, the Reed Solomon error correction bars and the stop bars. +/// +internal static class AustraliaPostEncoder +{ + /// + /// The largest size of the text representation in points: it "should appear above the barcode in 8 point type or less". + /// + private const float MaximumTextSize = 8F; + + /// + /// The number of digits in the format control code and the sorting code together. + /// + private const int FixedDigits = 10; + + /// + /// The number of error correction symbols, which the specification calls "the four parity check symbols of a (n,k) Reed Solomon code over Galois Field + /// GF(64)". + /// + private const int ParitySymbols = 4; + + /// + /// The number of bars from the start of the symbol to the customer information field: two start bars, four format control code bars and sixteen sorting + /// code bars. + /// + private const int CustomerFieldStart = 22; + + /// + /// The number of bars after the customer information field: twelve error correction bars and two stop bars. + /// + private const int TrailingBars = 14; + + /// + /// The number of bars of one Reed Solomon symbol and of one character of the C Encoding Table: three bars of base 4 values. + /// + private const int BarsPerSymbol = 3; + + /// + /// The number of bars of one digit of the N Encoding Table. + /// + private const int BarsPerDigit = 2; + + /// + /// The value of the filler bars that fill the rest of the customer information field: "filler bars (value 3 bars)". + /// + private const byte FillerBar = 3; + + /// + /// The dimensions that the specification gives as ranges, at the middle of each range: + /// + /// A bar width of 0.5 mm, from "0.4" to "0.6". + /// A tracker of 1.3 mm, from "1.0" to "1.6". + /// An ascender or descender bar of 3.15 mm, from "2.6" to "3.7". The extender is thus 1.85 mm. + /// A bar density of 23.5 bars per 25.4 mm, from "22 to 25 bars per 25.4mm". The pitch is thus 1.08 mm. + /// + /// The space is the rest of the pitch, 0.58 mm, inside the bar gap of "0.4" to "0.7". Diagram 14 gives a quiet zone of + /// 6 mm at each end and 2 mm above and below. The text representation "should appear above the barcode" and "must be outside of the barcode's minimum + /// Quiet Zone". The table on page 12 gives the bar width of "0.4" to "0.6" mm, and the code clamps the drawn bar width to that range. The code clamps the + /// bars per inch to the 22 to 25 of the bar density on page 13. The code clamps the full bar to the "4.2" to "5.8" mm of bar type H in the same table. At + /// each end of that range, the tracker and the ascender and descender bars stay inside their own ranges. + /// + private static readonly FourStateMetrics Metrics = new( + 0.5, + 0.58 / 0.5, + (float)(1.85 / 0.5), + (float)(1.3 / 0.5), + (float)(1.85 / 0.5), + (float)(6 / 0.5), + (float)(2 / 0.5), + BarcodeTextSide.AboveBars, + (float)(2 / 0.5), + BarcodeTextAlignment.Centered) + { + MinimumXDimension = 0.4, + MaximumXDimension = 0.6, + MinimumBarsPerInch = 22, + MaximumBarsPerInch = 25, + MinimumFullBar = (float)(4.2 / 0.5), + MaximumFullBar = (float)(5.8 / 0.5), + }; + + /// + /// The field of the parity symbols: GF(64) with the primitive polynomial 1 + x + x^6. + /// + private static readonly GaloisField Field = new(64, 67); + + /// + /// Gets the N Encoding Table: the two bar values of every digit, as one base 4 number, the first bar in the high digit. + /// + private static ReadOnlySpan NumericTable => [0, 1, 2, 4, 5, 6, 8, 9, 10, 12]; + + /// + /// Gets the C Encoding Table: the three bar values of every character, in the order of , as one base 4 number, the first bar + /// in the high digit. + /// + private static ReadOnlySpan CharacterTable => + [ + 0, 1, 2, 4, 5, 6, 8, 9, 10, 16, 17, 18, 20, 21, 22, 24, + 25, 26, 32, 33, 34, 36, 37, 38, 40, 41, 42, 48, 49, 50, 52, 53, + 54, 56, 57, 58, 11, 12, 13, 14, 15, 19, 23, 27, 28, 29, 30, 31, + 35, 39, 43, 44, 45, 46, 47, 51, 55, 59, 60, 61, 62, 63, 3, 7, + ]; + + /// + /// Gets the two start bars, which are also the two stop bars. The specification: "These bars always have the bar values of `1' and `3', respectively". + /// + private static ReadOnlySpan StartStopBars => [1, 3]; + + /// + /// Encodes the text into a symbol. + /// + /// The format control code, the sorting code and the customer information. + /// The encoding table of the customer information. + /// The options that control layout choices. + /// The symbol to write. + public static void Encode(ReadOnlySpan text, AustraliaPostEncodingTable table, BarcodeOptions options, ref BarcodeSymbol symbol) + { + if (text.Length < FixedDigits) + { + throw new ArgumentException( + $"An Australia Post barcode starts with a 2-digit format control code and an 8-digit sorting code; got {text.Length} characters.", + nameof(text)); + } + + ReadOnlySpan formatControlCode = text.Slice(0, 2); + int barCount = BarCount(formatControlCode); + if (barCount == 0) + { + throw new ArgumentException($"The format control code is 00, 11, 59 or 62; got {formatControlCode}.", nameof(text)); + } + + ReadOnlySpan sortingCode = text.Slice(2, 8); + BarcodeSymbology.ValidateDigits(sortingCode); + if (formatControlCode is "00" && sortingCode is not "00000000") + { + throw new ArgumentException($"The Null Customer Barcode is only valid with the sorting code 00000000; got {sortingCode}.", nameof(text)); + } + + ReadOnlySpan customer = text.Slice(FixedDigits); + int barsPerCharacter = table == AustraliaPostEncodingTable.Numeric ? BarsPerDigit : BarsPerSymbol; + int customerBars = barCount - CustomerFieldStart - TrailingBars; + if (customer.Length * barsPerCharacter > customerBars) + { + throw new ArgumentException( + $"The customer information field of format control code {text[..2]} holds {customerBars / barsPerCharacter} characters; got {customer.Length}.", + nameof(text)); + } + + // The longest barcode, format control code 62, has 67 bars. + InlineArray128 valueStorage = default; + Span values = ((Span)valueStorage)[..barCount]; + StartStopBars.CopyTo(values); + int bar = StartStopBars.Length; + foreach (char digit in text.Slice(0, FixedDigits)) + { + bar = AppendBars(NumericTable[digit - '0'], BarsPerDigit, values, bar); + } + + if (table == AustraliaPostEncodingTable.Numeric) + { + BarcodeSymbology.ValidateDigits(customer); + foreach (char digit in customer) + { + bar = AppendBars(NumericTable[digit - '0'], BarsPerDigit, values, bar); + } + } + else + { + // Walking code points reports a surrogate pair, or a lone surrogate, as the one character it is. + SpanCodePointEnumerator codePoints = customer.EnumerateCodePoints(); + while (codePoints.MoveNext()) + { + CodePoint current = codePoints.Current; + int index = CharacterIndex(current.Value); + if (index < 0) + { + throw new ArgumentException($"The C Encoding Table has no character {current.ToDisplayString()}.", nameof(text)); + } + + bar = AppendBars(CharacterTable[index], BarsPerSymbol, values, bar); + } + } + + int parityStart = barCount - TrailingBars; + values[bar..parityStart].Fill(FillerBar); + + // The information symbols are the triples of bars after the start bars, read as base 4 numbers, whatever field each bar belongs to. + int dataSymbols = (parityStart - StartStopBars.Length) / BarsPerSymbol; + InlineArray24 symbolStorage = default; + Span symbols = ((Span)symbolStorage)[..dataSymbols]; + for (int i = 0; i < dataSymbols; i++) + { + symbols[i] = SymbolValue(values, StartStopBars.Length + (i * BarsPerSymbol)); + } + + InlineArray4 parityStorage = default; + Span parity = parityStorage; + Parity(symbols, parity); + bar = parityStart; + foreach (int value in parity) + { + bar = AppendBars(value, BarsPerSymbol, values, bar); + } + + StartStopBars.CopyTo(values[bar..]); + + InlineArray128 stateStorage = default; + Span states = ((Span)stateStorage)[..barCount]; + for (int i = 0; i < barCount; i++) + { + states[i] = values[i] switch + { + 0 => FourState.Full, + 1 => FourState.Ascender, + 2 => FourState.Descender, + _ => FourState.Tracker, + }; + } + + // The representation holds the ten digits, at most fifteen customer characters, the four parity values of at most two digits and the spaces between + // them. + InlineArray64 lineStorage = default; + Span line = lineStorage; + int lineLength = options.Font is null ? 0 : TextRepresentation(text, parity, line); + FourStateEncoder.BuildSymbol(states, Metrics, line[..lineLength], options, ref symbol); + + // The text representation "should appear above the barcode in 8 point type or less". + symbol.SetTextSize(0F, MaximumTextSize); + } + + /// + /// Calculates the four parity symbols of the Reed Solomon code: the remainder of the data polynomial, with the first symbol as its highest term, multiplied + /// by x^4 and divided by the generator polynomial "(x-α)(x-α²)(x-α³)(x-α⁴)". The field is GF(64) with the primitive polynomial 1 + x + x^6 and the + /// generating element α = 000010. + /// + /// The data symbols, each a value of 0 to 63, the first symbol first. + /// The buffer that receives the parity symbols C3, C2, C1 and C0. + public static void Parity(ReadOnlySpan data, Span parity) + { + // The roots start at the first power of α. + InlineArray4 generatorStorage = default; + Span generator = generatorStorage; + Field.Generator(1, generator); + Field.Remainder(data, 1, data.Length, generator, parity); + } + + /// + /// Returns the number of bars in the format a format control code selects, or 0 when the code is not one of the customer barcode formats. + /// + /// The two digits of the format control code. + /// 37 for the Standard Customer Barcode and the Null Customer Barcode, 52 for Customer Barcode 2, 67 for Customer Barcode 3. + private static int BarCount(ReadOnlySpan formatControlCode) => formatControlCode switch + { + "00" or "11" => 37, + "59" => 52, + "62" => 67, + _ => 0, + }; + + /// + /// Converts three bars to their decimal value by the Bar to Decimal Conversion Table, in which the bar values are the digits of a base 4 number. + /// + /// The bar values. + /// The index of the first of the three bars. + /// The decimal value. + private static int SymbolValue(ReadOnlySpan values, int start) + => (values[start] * 16) + (values[start + 1] * 4) + values[start + 2]; + + /// + /// Builds the text representation, which Diagram 14 shows as the format control code, the sorting code and the four error correction symbols as decimal + /// values, separated by spaces. The customer information, when present, stands as given between the sorting code and the error correction symbols. + /// + /// The text as given. + /// The parity symbols. + /// The buffer that receives the text representation. + /// The length of the text representation. + private static int TextRepresentation(ReadOnlySpan text, ReadOnlySpan parity, Span line) + { + ValueStringBuilder builder = new(line); + builder.Append(text.Slice(0, 2)); + builder.Append(' '); + builder.Append(text.Slice(2, 8)); + if (text.Length > FixedDigits) + { + builder.Append(' '); + builder.Append(text.Slice(FixedDigits)); + } + + foreach (int value in parity) + { + builder.Append(' '); + if (value >= 10) + { + builder.Append((char)('0' + (value / 10))); + } + + builder.Append((char)('0' + (value % 10))); + } + + return builder.Length; + } + + /// + /// Writes the bar values of a base 4 number, the high digit first. Each base 4 digit is one bar. + /// + /// The number: a table entry or a Reed Solomon symbol. + /// The number of bars. + /// The bar values. + /// The index of the first bar. + /// The index after the bars. + private static int AppendBars(int value, int count, Span values, int bar) + { + for (int shift = 2 * (count - 1); shift >= 0; shift -= 2) + { + values[bar++] = (byte)((value >> shift) & 3); + } + + return bar; + } + + /// + /// Returns the row of a character in the C Encoding Table, which lists the capital letters, the digits, the small letters, the space and the number sign. + /// + /// The code point of the character. + /// The row, from 0 to 63, or -1 when the table has no such character. + private static int CharacterIndex(int codePoint) => codePoint switch + { + >= 'A' and <= 'Z' => codePoint - 'A', + >= '0' and <= '9' => codePoint - '0' + 26, + >= 'a' and <= 'z' => codePoint - 'a' + 36, + ' ' => 62, + '#' => 63, + _ => -1, + }; +} diff --git a/src/ImageSharp.Drawing/Barcodes/Postal/AustraliaPostEncodingTable.cs b/src/ImageSharp.Drawing/Barcodes/Postal/AustraliaPostEncodingTable.cs new file mode 100644 index 00000000..4be667e8 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Postal/AustraliaPostEncodingTable.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Specifies the encoding table of the customer information field of an Australia Post 4-state barcode. The Customer Barcoding Technical Specifications state +/// that the field can be coded by either of the two tables. +/// +public enum AustraliaPostEncodingTable +{ + /// + /// The C Encoding Table: three bars per character, for capital letters, small letters, digits, the space and the number sign. + /// + Character, + + /// + /// The N Encoding Table: two bars per digit. + /// + Numeric, +} diff --git a/src/ImageSharp.Drawing/Barcodes/Postal/AustraliaPostSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Postal/AustraliaPostSymbology.cs new file mode 100644 index 00000000..32596c7d --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Postal/AustraliaPostSymbology.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Australia Post 4-state customer barcode, which the Australia Post Customer Barcoding Technical Specifications define. The text is a 2-digit format +/// control code, an 8-digit sorting code and the customer information. Format control code 11 gives the 37-bar Standard Customer Barcode, which has no customer +/// information field, 59 gives the 52-bar Customer Barcode 2 with a field of 16 bars and 62 the 67-bar Customer Barcode 3 with a field of 31 bars. Format +/// control code 00 is the Null Customer Barcode, "only valid if DPID is 00000000". The C Encoding Table encodes a character in three bars and the N Encoding +/// Table a digit in two, and filler bars complete the field. The printed line is the format control code, the sorting code, the customer information and the +/// four error correction symbols as decimal values, above the bars in 8 point type or less. +/// +public sealed class AustraliaPostSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class that encodes the customer information with the C Encoding Table. + /// + public AustraliaPostSymbology() + : this(AustraliaPostEncodingTable.Character) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The encoding table of the customer information field. + public AustraliaPostSymbology(AustraliaPostEncodingTable customerInformationTable) + => this.CustomerInformationTable = customerInformationTable; + + /// + /// Gets the encoding table of the customer information field. + /// + public AustraliaPostEncodingTable CustomerInformationTable { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + AustraliaPostEncoder.Encode(text, this.CustomerInformationTable, options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Postal/DaftSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Postal/DaftSymbology.cs new file mode 100644 index 00000000..973a4064 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Postal/DaftSymbology.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// A custom height modulated symbology whose input is the bar states themselves: D for a descender, A for an ascender, F for a full bar and T for a tracker. +/// The bars have the dimensions of Table 11 of the Royal Mail Mailmark barcode definition document. The symbol has no quiet zone and prints no line. +/// +public sealed class DaftSymbology : BarcodeSymbology +{ + /// + /// The largest number of bars a symbol carries. + /// + public const int MaximumLength = 2500; + + /// + /// The dimensions: those of the Royal Mail symbologies, without their clear zone. + /// + private static readonly FourStateMetrics Metrics = new( + FourStateEncoder.RoyalMail.XDimension, + FourStateEncoder.RoyalMail.Space, + FourStateEncoder.RoyalMail.Ascender, + FourStateEncoder.RoyalMail.Tracker, + FourStateEncoder.RoyalMail.Descender, + 0F); + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Guard.MustBeGreaterThan(text.Length, 0, nameof(text)); + Guard.MustBeLessThanOrEqualTo(text.Length, MaximumLength, nameof(text)); + + // One bar for each letter. The text is at most MaximumLength letters, so a short symbol works inline and a longer one rents its states once. + InlineArray128 stateStorage = default; + PooledBuffer rented = default; + Span states = text.Length <= 128 + ? ((Span)stateStorage)[..text.Length] + : (rented = new PooledBuffer(text.Length)).Span; + try + { + int index = 0; + SpanCodePointEnumerator codePoints = text.EnumerateCodePoints(); + while (codePoints.MoveNext()) + { + CodePoint current = codePoints.Current; + states[index++] = current.Value switch + { + 'D' => FourState.Descender, + 'A' => FourState.Ascender, + 'F' => FourState.Full, + 'T' => FourState.Tracker, + _ => throw new ArgumentException($"A DAFT symbol carries only the letters D, A, F and T; got {current.ToDisplayString()}.", nameof(text)), + }; + } + + FourStateEncoder.BuildSymbol(states, Metrics, [], options, ref symbol); + } + finally + { + rented.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Postal/FourState.cs b/src/ImageSharp.Drawing/Barcodes/Postal/FourState.cs new file mode 100644 index 00000000..dc2e74de --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Postal/FourState.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The four states of a height modulated postal bar. Every bar has the tracker in the middle. An ascender adds the part above it, a descender the part below +/// it, and a full bar both, so a state is the combination of its and flags. +/// +[Flags] +internal enum FourState : byte +{ + /// + /// The tracker alone. + /// + Tracker = 0, + + /// + /// The tracker and the descender below it. + /// + Descender = 1, + + /// + /// The tracker and the ascender above it. + /// + Ascender = 2, + + /// + /// The ascender, the tracker and the descender. + /// + Full = Ascender | Descender, +} diff --git a/src/ImageSharp.Drawing/Barcodes/Postal/FourStateEncoder.cs b/src/ImageSharp.Drawing/Barcodes/Postal/FourStateEncoder.cs new file mode 100644 index 00000000..7b84afb3 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Postal/FourStateEncoder.cs @@ -0,0 +1,107 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Builds the symbol of a height modulated postal symbology from its bar states. The bars are one width and equally spaced, and the four states differ in +/// height and vertical position alone. +/// +internal static class FourStateEncoder +{ + /// + /// The dimensions the Royal Mail 4-state symbologies share, from Table 11 of the Royal Mail Mailmark barcode definition document: a bar width of 0.54 mm, + /// ascender and descender bars of 1.90 mm, a tracker bar of 1.30 mm, a full bar of 5.10 mm and a pitch of 21.2 bars per inch, with the clear zone of + /// section 3.5.2, "at least 2mm around all edges". A symbology that prints a text line keeps the same 2 mm between the bars and the text, which the + /// document does not state. A pitch of 1.20 mm, which is 21.17 bars per inch, leaves a space of 0.66 mm between the 0.54 mm bars. + /// + public static readonly FourStateMetrics RoyalMail = new( + 0.54, + (1.20 - 0.54) / 0.54, + (float)(1.90 / 0.54), + (float)(1.30 / 0.54), + (float)(1.90 / 0.54), + (float)(2 / 0.54), + (float)(2 / 0.54), + BarcodeTextSide.BelowBars, + (float)(2 / 0.54), + BarcodeTextAlignment.Centered); + + /// + /// Builds the symbol from bar states. + /// + /// The state of every bar. + /// The dimensions of the symbology. + /// The human readable interpretation, or none. + /// The options that control layout choices. + /// The symbol to write. + public static void BuildSymbol( + ReadOnlySpan states, + FourStateMetrics metrics, + ReadOnlySpan readable, + BarcodeOptions options, + ref BarcodeSymbol symbol) + { + int barCount = states.Length; + double xDimension = options.ResolveXDimension(metrics.MinimumXDimension, metrics.XDimension, metrics.MaximumXDimension); + + // The emitter draws every run of these uniform bars at a whole number of pixels, so the runs count pixels: a bar is one module of whole pixels, and + // the space is the space of the metrics at the nearest whole pixel, at least one, as the emitter rounds a run. + int barPixels = options.ModulePixels(xDimension); + double spacePixels = Math.Max(1, Math.Round(metrics.SpaceAt(xDimension) * barPixels, MidpointRounding.AwayFromZero)); + + // A specification that limits the bars per inch also limits the pitch, which is one bar and one space. A pitch of Dpi / n pixels is n bars per inch. + // Whole pixels can leave that range at a bar width at the end of its range or at a low resolution. Thus the code clamps the pitch in whole pixels, as + // ResolveXDimension clamps the module. The upper limit is the largest whole number of pixels at or under the largest pitch. The lower limit is the + // smallest whole number of pixels at or over the smallest pitch, with a space of at least one pixel. The lower limit applies last. + double highest = Math.Floor((options.Dpi / metrics.MinimumBarsPerInch) + BarcodeOptions.LimitTolerance); + double lowest = Math.Max(barPixels + 1, Math.Ceiling((options.Dpi / metrics.MaximumBarsPerInch) - BarcodeOptions.LimitTolerance)); + int space = (int)Math.Max(Math.Min(barPixels + spacePixels, highest), lowest) - barPixels; + float runUnit = 1F / barPixels; + + Span runs = symbol.BeginLinear((barCount * 2) - 1, xDimension, out Span heights, out Span tops); + for (int i = 0; i < runs.Length; i++) + { + runs[i] = (i & 1) == 0 ? barPixels : space; + } + + // The clear zones and the text clearance are millimetres or inches in each specification, given in modules of the nominal bar width. The module the + // symbol draws at can round away from the nominal width, so the same lengths are this many of its modules. The bar heights are too, unless the + // specification makes them proportional to the bar width. + float nominal = (float)(metrics.XDimension / xDimension); + float barScale = metrics.ProportionalBars ? 1F : nominal; + float scale = options.ResolveBarHeight( + xDimension, + metrics.MinimumFullBar * barScale, + metrics.FullBar * barScale, + metrics.MaximumFullBar * barScale) / metrics.FullBar; + + float ascender = metrics.Ascender * scale; + float tracker = metrics.Tracker * scale; + float descender = metrics.Descender * scale; + for (int i = 0; i < barCount; i++) + { + FourState state = states[i]; + bool up = (state & FourState.Ascender) != 0; + bool down = (state & FourState.Descender) != 0; + tops[i] = up ? 0F : ascender; + heights[i] = tracker + (up ? ascender : 0F) + (down ? descender : 0F); + } + + float widthInModules = ((barCount * barPixels) + ((barCount - 1) * space)) * runUnit; + if (options.Font is not null && readable.Length > 0) + { + float textClearance = metrics.TextClearance * nominal; + float textEdge = metrics.TextSide == BarcodeTextSide.AboveBars + ? -textClearance + : ascender + tracker + descender + textClearance; + + symbol.ReserveText(1, readable.Length); + symbol.AddText(readable, 0F, widthInModules, metrics.TextSide, textEdge, metrics.TextAlignment); + } + + float quietZone = metrics.QuietZone * nominal; + float verticalQuietZone = metrics.VerticalQuietZone * nominal; + symbol.EndLinear(quietZone, quietZone, verticalQuietZone, verticalQuietZone, 0F, runUnit, true); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Postal/FourStateMailmarkEncoder.cs b/src/ImageSharp.Drawing/Barcodes/Postal/FourStateMailmarkEncoder.cs new file mode 100644 index 00000000..8db3cf3b --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Postal/FourStateMailmarkEncoder.cs @@ -0,0 +1,374 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodation for the Royal Mail 4-state Mailmark barcodes C and L, from the Royal Mail Mailmark barcode C and barcode L encoding and decoding instructions, +/// release 1b. Section 2.2 converts the application string to six external user fields, each to an integer internal user field, the six to one consolidated +/// data value, the value to data numbers of 30 or 32 values, adds Reed-Solomon check numbers over a Galois field of 32 values, converts the numbers to six-bit +/// symbols, reorders the symbols into extender groups and maps the bits of every group to the ascenders and descenders of three bars. +/// +internal static class FourStateMailmarkEncoder +{ + /// + /// The length of the application string of a Mailmark barcode C. + /// + private const int LengthC = 22; + + /// + /// The length of the application string of a Mailmark barcode L. + /// + private const int LengthL = 26; + + /// + /// The number of bars in a group, which carries one six-bit extender group. + /// + private const int BarsPerGroup = 3; + + /// + /// The allowed characters of the Format field, Table 2, in value order. + /// + private const string FormatCharacters = "01234"; + + /// + /// The allowed characters of the Version ID field, Table 2, in value order. Section 2.2.1: the encoding process "is valid only for cases in which the value + /// of the Version ID External User Field is 1". + /// + private const string VersionCharacters = "1234"; + + /// + /// The allowed characters of the Class field, Table 2, in value order. + /// + private const string ClassCharacters = "0123456789ABCDE"; + + /// + /// The limited alphabetic character type of a domestic sorting code, Table 3. + /// + private const string LimitedAlphabetic = "ABDEFGHJLNPQRSTUWXYZ"; + + /// + /// The international designation of the destination field, section 2.2.2: "a nine-character string with five trailing spaces", whose internal user field + /// value is zero. + /// + private const string International = "XY11 "; + + /// + /// The length of the Destination Post Code plus DPS field. + /// + private const int DestinationLength = 9; + + /// + /// The Galois field of 32 values, section 2.2.5: the primitive polynomial x^5 + x^2 + 1. + /// + private static readonly GaloisField Field = new(32, 37); + + /// + /// Gets Table 5, the symbols of the numbers with 32 values and of the check numbers: the six-bit values with an odd number of binary 1s, in ascending + /// order. + /// + private static ReadOnlySpan OddSymbols => + [ + 1, 2, 4, 7, 8, 11, 13, 14, 16, 19, 21, 22, 25, 26, 28, 31, + 32, 35, 37, 38, 41, 42, 44, 47, 49, 50, 52, 55, 56, 59, 61, 62 + ]; + + /// + /// Gets Table 5, the symbols of the numbers with 30 values: the first 30 six-bit values with a non-zero, even number of binary 1s, in ascending order. + /// + private static ReadOnlySpan EvenSymbols => + [ + 3, 5, 6, 9, 10, 12, 15, 17, 18, 20, 23, 24, 27, 29, 30, + 33, 34, 36, 39, 40, 43, 45, 46, 48, 51, 53, 54, 57, 58, 60 + ]; + + /// + /// Gets the six character patterns of a domestic sorting code, section 2.2.2, nine characters each in the order the steps test them: F for full alphabetic, + /// N for numeric, L for limited alphabetic and S for a space. + /// + private static ReadOnlySpan PostcodePatterns => "FNFNLLNLS"u8 + "FFNNLLNLS"u8 + "FFNNNLLNL"u8 + "FFNFNLLNL"u8 + "FNNLLNLSS"u8 + "FNNNLLNLS"u8; + + /// + /// Gets the value the accumulator holds before the characters of each pattern are added: 1 for the first pattern, and the steps 6, 8, 10, 12 and 14 add + /// 5,408,000,000, 5,408,000,000, 54,080,000,000, 140,608,000,000 and 208,000,000 before the next. + /// + private static ReadOnlySpan PostcodeOffsets => + [ + 1UL, + 1UL + 5_408_000_000UL, + 1UL + 5_408_000_000UL + 5_408_000_000UL, + 1UL + 5_408_000_000UL + 5_408_000_000UL + 54_080_000_000UL, + 1UL + 5_408_000_000UL + 5_408_000_000UL + 54_080_000_000UL + 140_608_000_000UL, + 1UL + 5_408_000_000UL + 5_408_000_000UL + 54_080_000_000UL + 140_608_000_000UL + 208_000_000UL, + ]; + + /// + /// Gets Table 6 of barcode C: the extender group of each data symbol D0 to D15 and check symbol C0 to C5. + /// + private static ReadOnlySpan ExtenderGroupsC => [3, 5, 7, 11, 13, 14, 16, 17, 19, 0, 1, 2, 4, 6, 8, 9, 10, 12, 15, 18, 20, 21]; + + /// + /// Gets Table 6 of barcode L: the extender group of each data symbol D0 to D18 and check symbol C0 to C6. + /// + private static ReadOnlySpan ExtenderGroupsL => [2, 5, 7, 8, 13, 14, 15, 16, 21, 22, 23, 0, 1, 3, 4, 6, 9, 10, 11, 12, 17, 18, 19, 20, 24, 25]; + + /// + /// Returns the number of bars of the barcode an application string selects by its length. + /// + /// The length of the application string. + /// 66 for barcode C, 78 for barcode L, or 0 for any other length. + public static int BarCount(int length) => length switch + { + LengthC => 66, + LengthL => 78, + _ => 0, + }; + + /// + /// Encodes an application string into bar states. + /// + /// The application string, of 22 or 26 characters. + /// The buffer that receives the bar states. + public static void Encode(ReadOnlySpan text, Span states) + { + // Barcode C carries a Supply Chain ID of 2 digits, 16 data numbers of which the first 9 have 30 values, and 6 check numbers. Barcode L carries a + // Supply Chain ID of 6 digits, 19 data numbers of which the first 11 have 30 values, and 7 check numbers. + BarcodeLayout layout = text.Length == LengthL ? new(6, 1_000_000U, 19, 11, 7, ExtenderGroupsL) : new(2, 100U, 16, 9, 6, ExtenderGroupsC); + const int itemIdLength = 8; + const uint itemIdRadix = 100_000_000U; + + int format = FieldValue(text, 0, 1, FormatCharacters, "Format"); + int version = FieldValue(text, 1, 1, VersionCharacters, "Version ID"); + if (text[1] != '1') + { + throw new ArgumentException($"The encoding is defined for Version ID 1 alone; got {text[1]}.", nameof(text)); + } + + int mailClass = FieldValue(text, 2, 1, ClassCharacters, "Class"); + int supplyChain = FieldValue(text, 3, layout.SupplyChainLength, "0123456789", "Supply Chain ID"); + int itemId = FieldValue(text, 3 + layout.SupplyChainLength, itemIdLength, "0123456789", "Item ID"); + ulong destination = DestinationValue(text, 3 + layout.SupplyChainLength + itemIdLength); + + // Each field is a digit of a mixed radix number, its radix the number of values the field has. + UInt128 consolidated = destination; + consolidated = (consolidated * itemIdRadix) + (uint)itemId; + consolidated = (consolidated * layout.SupplyChainRadix) + (uint)supplyChain; + consolidated = (consolidated * (uint)ClassCharacters.Length) + (uint)mailClass; + consolidated = (consolidated * (uint)FormatCharacters.Length) + (uint)format; + consolidated = (consolidated * (uint)VersionCharacters.Length) + (uint)version; + + // Barcode L has the most numbers, 26. + InlineArray26 numberStorage = default; + Span numbers = ((Span)numberStorage)[..(layout.DataCount + layout.CheckCount)]; + DataNumbers(consolidated, numbers[..layout.DataCount], layout.ThirtyValued); + CheckNumbers(numbers[..layout.DataCount], numbers[layout.DataCount..]); + + InlineArray26 groupStorage = default; + Span extenderGroups = ((Span)groupStorage)[..numbers.Length]; + for (int i = 0; i < numbers.Length; i++) + { + extenderGroups[layout.ExtenderGroups[i]] = i < layout.ThirtyValued ? EvenSymbols[numbers[i]] : OddSymbols[numbers[i]]; + } + + Bars(extenderGroups, states); + } + + /// + /// Section 2.2.2: converts the Destination Post Code plus DPS field to its internal user field value. The international designation is 0, and a domestic + /// sorting code is the offset of its character pattern plus the mixed radix value of its non-space characters. + /// + /// The application string. + /// The index of the nine characters of the field in . + /// The internal user field value. + /// The field is neither the international designation nor a domestic sorting code. + public static ulong DestinationValue(ReadOnlySpan text, int start) + { + ReadOnlySpan field = text.Slice(start, DestinationLength); + if (field.SequenceEqual(International)) + { + return 0; + } + + for (int pattern = 0; pattern < PostcodeOffsets.Length; pattern++) + { + ReadOnlySpan types = PostcodePatterns.Slice(pattern * DestinationLength, DestinationLength); + ulong value = 0; + bool matches = true; + for (int i = 0; i < types.Length && matches; i++) + { + char c = field[i]; + switch ((char)types[i]) + { + case 'F': + matches = char.IsAsciiLetterUpper(c); + value = (value * 26) + (ulong)(c - 'A'); + break; + case 'N': + matches = char.IsAsciiDigit(c); + value = (value * 10) + (ulong)(c - '0'); + break; + case 'L': + int limited = LimitedAlphabetic.IndexOf(c); + matches = limited >= 0; + value = (value * 20) + (ulong)limited; + break; + default: + matches = c == ' '; + break; + } + } + + if (matches) + { + return PostcodeOffsets[pattern] + value; + } + } + + throw new ArgumentException( + $"The Destination Post Code plus DPS field is XY11 with five spaces or one of the six domestic patterns; got {field.ToString()}.", + nameof(text)); + } + + /// + /// Section 2.2.4: divides the consolidated data value into data numbers, the least significant first, by 32 for the numbers with 32 values and by 30 for + /// the rest; the most significant number is the last quotient. + /// + /// The consolidated data value. + /// The buffer that receives the data numbers, D0 first. + /// The number of leading data numbers with 30 values. + public static void DataNumbers(UInt128 consolidated, Span numbers, int thirtyValued) + { + UInt128 x = consolidated; + for (int i = numbers.Length - 1; i > 0; i--) + { + uint radix = i < thirtyValued ? 30U : 32U; + numbers[i] = (int)(x % radix); + x /= radix; + } + + numbers[0] = (int)x; + } + + /// + /// Section 2.2.5: the check numbers are the remainder of the data polynomial, whose first number is its highest term, multiplied by x to the number of + /// check numbers and divided by the generator polynomial over the Galois field of 32 values. + /// + /// The data numbers. + /// The buffer that receives the check numbers, C0 first. Its length is the number of check numbers. + public static void CheckNumbers(ReadOnlySpan data, Span check) + { + // Section 2.2.5 prints the generator polynomials of barcode C and barcode L. Each is the product of (x + α^i) for i = 1 to the number of check + // numbers, so the field calculates it from the first root α^1. Barcode L has the most check numbers, 7. + InlineArray7 generatorStorage = default; + Span generator = ((Span)generatorStorage)[..check.Length]; + Field.Generator(1, generator); + Field.Remainder(data, 1, data.Length, generator, check); + } + + /// + /// Section 2.2.8, Table 7: every extender group fills three bars. Its high three bits are the ascenders and its low three bits the descenders of an even + /// numbered group, and the reverse of an odd numbered group, with the most significant bit at the leftmost bar. + /// + /// The extender groups in physical order. + /// The buffer that receives the bar states. + private static void Bars(ReadOnlySpan extenderGroups, Span states) + { + for (int group = 0; group < extenderGroups.Length; group++) + { + int high = extenderGroups[group] >> 3; + int low = extenderGroups[group] & 7; + int ascenders = (group & 1) == 0 ? high : low; + int descenders = (group & 1) == 0 ? low : high; + for (int bar = 0; bar < BarsPerGroup; bar++) + { + int bit = 2 - bar; + FourState ascender = ((ascenders >> bit) & 1) != 0 ? FourState.Ascender : FourState.Tracker; + FourState descender = ((descenders >> bit) & 1) != 0 ? FourState.Descender : FourState.Tracker; + states[(group * BarsPerGroup) + bar] = ascender | descender; + } + } + } + + /// + /// Section 2.2.2: converts a field to its internal user field value, in which each allowed character has its index in the array of allowed characters as + /// its value. + /// + /// The application string. + /// The index of the field in . + /// The length of the field. + /// The allowed characters, in value order. + /// The name of the field in the error message. + /// The internal user field value. + /// The field has a character that is not allowed. + private static int FieldValue(ReadOnlySpan text, int start, int length, string allowed, string fieldName) + { + int value = 0; + for (int i = start; i < start + length; i++) + { + int index = allowed.IndexOf(text[i]); + if (index < 0) + { + throw new ArgumentException($"The {fieldName} field carries [{allowed}]; got {text[i]}.", nameof(text)); + } + + value = (value * allowed.Length) + index; + } + + return value; + } + + /// + /// The parts of the encoding that differ between barcode C and barcode L. + /// + private readonly ref struct BarcodeLayout + { + /// + /// Initializes a new instance of the struct. + /// + /// The number of digits of the Supply Chain ID. + /// The number of values of the Supply Chain ID, 10 to the power of its digits. + /// The number of data numbers. + /// The number of data numbers, from the first, that have 30 values. The others have 32. + /// The number of check numbers. + /// The extender group of each data number and check number, Table 6. + public BarcodeLayout(int supplyChainLength, uint supplyChainRadix, int dataCount, int thirtyValued, int checkCount, ReadOnlySpan extenderGroups) + { + this.SupplyChainLength = supplyChainLength; + this.SupplyChainRadix = supplyChainRadix; + this.DataCount = dataCount; + this.ThirtyValued = thirtyValued; + this.CheckCount = checkCount; + this.ExtenderGroups = extenderGroups; + } + + /// + /// Gets the number of digits of the Supply Chain ID. + /// + public int SupplyChainLength { get; } + + /// + /// Gets the number of values of the Supply Chain ID, 10 to the power of its digits. + /// + public uint SupplyChainRadix { get; } + + /// + /// Gets the number of data numbers. + /// + public int DataCount { get; } + + /// + /// Gets the number of data numbers, from the first, that have 30 values. The others have 32. + /// + public int ThirtyValued { get; } + + /// + /// Gets the number of check numbers. + /// + public int CheckCount { get; } + + /// + /// Gets the extender group of each data number and check number, Table 6. + /// + public ReadOnlySpan ExtenderGroups { get; } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Postal/FourStateMailmarkSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Postal/FourStateMailmarkSymbology.cs new file mode 100644 index 00000000..8bed5348 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Postal/FourStateMailmarkSymbology.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Royal Mail 4-state Mailmark barcode, which the Royal Mail Mailmark barcode C and barcode L encoding and decoding instructions define. The text is the +/// application string: a 22-character string gives barcode C of 66 bars and a 26-character string barcode L of 78 bars. The fields are the format, the version +/// ID, the class, the supply chain ID of 2 or 6 digits, the item ID of 8 digits and the destination post code plus delivery point suffix of 9 characters. The +/// bars are the 0.54 mm bars of Table 11 of the Mailmark barcode definition document. An X dimension outside the acceptable 0.38 to 0.63 mm of that +/// table draws at the nearest limit. The symbol prints no text line, because the Mailmark documents define none. +/// +public sealed class FourStateMailmarkSymbology : BarcodeSymbology +{ + /// + /// The dimensions of Table 11. The code clamps the bar width to the acceptable sizes of the same table, "Bar width (all) 0.54 0.38 - 0.63". The code + /// clamps the bars per inch to "Bar pitch ... 20 - 24 bars per inch (25.4mm)". The bar heights scale together. The ascender and descender bars of + /// "1.60 - 2.16" mm have the narrowest range relative to their recommended 1.90 mm. Thus the code clamps the full bar to 5.10 mm scaled by 1.60 / 1.90 + /// and by 2.16 / 1.90. The tracker bar and the full bar then stay inside their own acceptable sizes. + /// + private static readonly FourStateMetrics Metrics = FourStateEncoder.RoyalMail with + { + MinimumXDimension = 0.38, + MaximumXDimension = 0.63, + MinimumBarsPerInch = 20, + MaximumBarsPerInch = 24, + MinimumFullBar = (float)(5.10 * 1.60 / 1.90 / 0.54), + MaximumFullBar = (float)(5.10 * 2.16 / 1.90 / 0.54), + }; + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + int barCount = FourStateMailmarkEncoder.BarCount(text.Length); + if (barCount == 0) + { + throw new ArgumentException($"A Mailmark application string is 22 characters for barcode C or 26 for barcode L; got {text.Length}.", nameof(text)); + } + + // Barcode L, the larger, has 78 bars. + InlineArray128 stateStorage = default; + Span states = ((Span)stateStorage)[..barCount]; + FourStateMailmarkEncoder.Encode(text, states); + FourStateEncoder.BuildSymbol(states, Metrics, [], options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Postal/FourStateMetrics.cs b/src/ImageSharp.Drawing/Barcodes/Postal/FourStateMetrics.cs new file mode 100644 index 00000000..b7c7b870 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Postal/FourStateMetrics.cs @@ -0,0 +1,260 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The dimensions of a height modulated postal symbology, in modules of its bar width. +/// +internal readonly struct FourStateMetrics +{ + /// + /// Initializes a new instance of the struct. + /// + /// The bar width in millimetres, which is one module. + /// The width of the space between bars, in modules. + /// The height of the ascender part above the tracker, in modules. + /// The height of the tracker, in modules. + /// The height of the descender part below the tracker, in modules. + /// The clear zone at each end of the symbol, in modules. + public FourStateMetrics(double xDimension, double space, float ascender, float tracker, float descender, float quietZone) + : this(xDimension, space, ascender, tracker, descender, quietZone, BarcodeTextSide.BelowBars, 0F) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The bar width in millimetres, which is one module. + /// The width of the space between bars, in modules. + /// The height of the ascender part above the tracker, in modules. + /// The height of the tracker, in modules. + /// The height of the descender part below the tracker, in modules. + /// The clear zone at each end of the symbol, in modules. + /// The side of the bars the human readable interpretation stands on. + /// The clear space between the bars and the human readable interpretation, in modules. + public FourStateMetrics( + double xDimension, + double space, + float ascender, + float tracker, + float descender, + float quietZone, + BarcodeTextSide textSide, + float textClearance) + : this(xDimension, space, ascender, tracker, descender, quietZone, textSide, textClearance, BarcodeTextAlignment.Centered) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The bar width in millimetres, which is one module. + /// The width of the space between bars, in modules. + /// The height of the ascender part above the tracker, in modules. + /// The height of the tracker, in modules. + /// The height of the descender part below the tracker, in modules. + /// The clear zone at each end of the symbol, in modules. + /// The side of the bars the human readable interpretation stands on. + /// The clear space between the bars and the human readable interpretation, in modules. + /// Where the human readable interpretation stands within the symbol width. + public FourStateMetrics( + double xDimension, + double space, + float ascender, + float tracker, + float descender, + float quietZone, + BarcodeTextSide textSide, + float textClearance, + BarcodeTextAlignment textAlignment) + : this(xDimension, space, ascender, tracker, descender, quietZone, 0F, textSide, textClearance, textAlignment) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The bar width in millimetres, which is one module. + /// The width of the space between bars, in modules. + /// The height of the ascender part above the tracker, in modules. + /// The height of the tracker, in modules. + /// The height of the descender part below the tracker, in modules. + /// The clear zone at each end of the symbol, in modules. + /// The clear zone above and below the symbol, in modules. + /// The side of the bars the human readable interpretation stands on. + /// The clear space between the bars and the human readable interpretation, in modules. + /// Where the human readable interpretation stands within the symbol width. + public FourStateMetrics( + double xDimension, + double space, + float ascender, + float tracker, + float descender, + float quietZone, + float verticalQuietZone, + BarcodeTextSide textSide, + float textClearance, + BarcodeTextAlignment textAlignment) + : this(xDimension, space, ascender, tracker, descender, quietZone, verticalQuietZone, textSide, textClearance, textAlignment, null, false) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The bar width in millimetres, which is one module. + /// The width of the space between bars, in modules at . + /// The height of the ascender part above the tracker, in modules. + /// The height of the tracker, in modules. + /// The height of the descender part below the tracker, in modules. + /// The clear zone at each end of the symbol, in modules. + /// The clear zone above and below the symbol, in modules. + /// The side of the bars the human readable interpretation stands on. + /// The clear space between the bars and the human readable interpretation, in modules. + /// Where the human readable interpretation stands within the symbol width. + /// + /// The pitch in millimetres that the specification fixes at every bar width, or where the space scales with the bar width. + /// + /// + /// Whether the specification makes the bar heights proportional to the bar width, rather than fixing them in millimetres or inches. + /// + public FourStateMetrics( + double xDimension, + double space, + float ascender, + float tracker, + float descender, + float quietZone, + float verticalQuietZone, + BarcodeTextSide textSide, + float textClearance, + BarcodeTextAlignment textAlignment, + double? pitch, + bool proportionalBars) + { + this.ProportionalBars = proportionalBars; + this.VerticalQuietZone = verticalQuietZone; + this.XDimension = xDimension; + this.Space = space; + this.Pitch = pitch; + this.Ascender = ascender; + this.Tracker = tracker; + this.Descender = descender; + this.QuietZone = quietZone; + this.TextSide = textSide; + this.TextClearance = textClearance; + this.TextAlignment = textAlignment; + } + + /// + /// Gets the bar width in millimetres, which is one module. + /// + public double XDimension { get; } + + /// + /// Gets the smallest bar width in millimetres that the specification permits, or zero where it gives none. The code clamps the drawn bar width to + /// this limit. + /// + public double MinimumXDimension { get; init; } + + /// + /// Gets the largest bar width in millimetres that the specification permits, or where it gives none. The code clamps + /// the drawn bar width to this limit. + /// + public double MaximumXDimension { get; init; } = double.PositiveInfinity; + + /// + /// Gets the fewest bars per inch that the specification permits, or zero where it gives no limit. It sets the largest pitch, a bar and a space, that the + /// symbol draws at in whole pixels. + /// + public double MinimumBarsPerInch { get; init; } + + /// + /// Gets the most bars per inch that the specification permits, or where it gives no limit. It sets the smallest + /// pitch, a bar and a space, that the symbol draws at in whole pixels. + /// + public double MaximumBarsPerInch { get; init; } = double.PositiveInfinity; + + /// + /// Gets the smallest full bar in modules that the specification permits, or zero where it gives none. All bar parts scale with the full bar, so this is + /// the smallest full bar that keeps every part at or over its own minimum. + /// + public float MinimumFullBar { get; init; } + + /// + /// Gets the largest full bar in modules that the specification permits, or where it gives none. All bar parts scale + /// with the full bar, so this is the largest full bar that keeps every part at or under its own maximum. + /// + public float MaximumFullBar { get; init; } = float.PositiveInfinity; + + /// + /// Gets the width of the space between bars, in modules at . + /// + public double Space { get; } + + /// + /// Gets the pitch, a bar and a space, in millimetres that the specification fixes at every bar width, or where the space scales + /// with the bar width. + /// + public double? Pitch { get; } + + /// + /// Gets a value indicating whether the specification makes the bar heights proportional to the bar width. Where it does not, the heights keep their size + /// in millimetres or inches when the bar width rounds to whole pixels. + /// + public bool ProportionalBars { get; } + + /// + /// Gets the height of the ascender part above the tracker, in modules. + /// + public float Ascender { get; } + + /// + /// Gets the height of the tracker, in modules. + /// + public float Tracker { get; } + + /// + /// Gets the height of the descender part below the tracker, in modules. + /// + public float Descender { get; } + + /// + /// Gets the clear zone at each end of the symbol, in modules. + /// + public float QuietZone { get; } + + /// + /// Gets the side of the bars the human readable interpretation stands on. + /// + public BarcodeTextSide TextSide { get; } + + /// + /// Gets the clear space between the bars and the human readable interpretation, in modules. + /// + public float TextClearance { get; } + + /// + /// Gets where the human readable interpretation stands within the symbol width. + /// + public BarcodeTextAlignment TextAlignment { get; } + + /// + /// Gets the clear zone above and below the symbol, in modules. + /// + public float VerticalQuietZone { get; } + + /// + /// Gets the height of a full bar in modules. + /// + public float FullBar => this.Ascender + this.Tracker + this.Descender; + + /// + /// Returns the width of the space between bars, in modules of the bar width the symbol draws at. Where is set, the bar width can + /// differ from after rounding to whole pixels. The space is then the rest of the pitch, so the pitch does not move with the bar. + /// + /// The bar width in millimetres the symbol draws at. + /// The space in modules. + public double SpaceAt(double xDimension) => this.Pitch is double pitch ? (pitch / xDimension) - 1 : this.Space; +} diff --git a/src/ImageSharp.Drawing/Barcodes/Postal/IntelligentMailEncoder.cs b/src/ImageSharp.Drawing/Barcodes/Postal/IntelligentMailEncoder.cs new file mode 100644 index 00000000..5bc4aed7 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Postal/IntelligentMailEncoder.cs @@ -0,0 +1,345 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodation for the United States Postal Service Intelligent Mail barcode, from USPS-B-3200 revision H. Section 2.2 converts the routing code and the +/// tracking code to a binary value, generates an 11-bit frame check sequence on it, divides it into ten codewords, converts the codewords to 13-bit characters +/// and maps the character bits to the 65 bars. +/// +internal static class IntelligentMailEncoder +{ + /// + /// The number of bars in a symbol. + /// + private const int BarCount = 65; + + /// + /// The number of digits in the tracking code. + /// + private const int TrackingLength = 20; + + /// + /// The number of codewords, and of characters, in a symbol. + /// + private const int CharacterCount = 10; + + /// + /// The number of entries in the table of characters with five bits set, Table 19. + /// + private const int FiveOf13Length = 1287; + + /// + /// The number of entries in the table of characters with two bits set, Table 20. + /// + private const int TwoOf13Length = 78; + + /// + /// The generator polynomial of the frame check sequence, section 2.2.2. + /// + private const int GeneratorPolynomial = 0xF35; + + /// + /// The number of bits of the frame check sequence, section 2.2.2. + /// + private const int FrameCheckBits = 11; + + /// + /// The mask of the bits of the frame check sequence. + /// + private const int FrameCheckMask = (1 << FrameCheckBits) - 1; + + /// + /// The most significant bit of the frame check sequence. + /// + private const int FrameCheckTopBit = 1 << (FrameCheckBits - 1); + + /// + /// The radix of the rightmost codeword J, section 2.2.3. + /// + private const uint CodewordJRadix = 636; + + /// + /// The radix of the codewords I to B, section 2.2.3: one value for each character of Table 19 and Table 20. + /// + private const uint CodewordRadix = FiveOf13Length + TwoOf13Length; + + /// + /// The value that codeword A gains when the most significant bit of the frame check sequence is set, section 2.2.4. + /// + private const int CodewordAOffset = 659; + + /// + /// The mask of the 13 bits of a character, section 2.2.5. + /// + private const int CharacterMask = (1 << 13) - 1; + + /// + /// The dimensions of Figure 6, which gives every vertical dimension as a minimum and a maximum from the centerline: + /// + /// A full bar of 0.125 to 0.165 inch. + /// A tracker of 0.039 to 0.057 inch. + /// An ascender or descender bar of 0.083 to 0.111 inch. + /// + /// The metrics are the middle of each range: a full bar of 0.145 inch, a tracker of 0.048 inch and an extender of 0.0485 inch above or below it. Section + /// 2.3.1 gives the bar width of 0.020 inch and an "overall barcode pitch of 22±2 bars per inch". Section 2.3.2 gives a clear zone of 0.125 inch on each end + /// and 0.028 inch above and below the bars. Section 2.4.1 keeps the human readable information at least 0.028 inch below the bars. Section 2.4.2 aligns + /// "the left edge of the leftmost digit" with the leftmost bar. The pitch is 20 to 24 bars per inch. The lengths of section 2.3.1 are that range over the 64 + /// pitches. 3.225 inches is 64 / 20 inch and the widest bar of 0.025 inch. 2.667 inches is 64 / 24 inch, rounded up. The code clamps the full bar to the + /// "overall barcode height of 0.125 inch to 0.165 inch" of section 2.3.1. At each end of that range, the tracker and the ascender and descender bars stay + /// inside their own ranges. + /// + public static readonly FourStateMetrics Metrics = new( + UspsPostalEncoder.NominalXDimension, + (UspsPostalEncoder.Pitch / UspsPostalEncoder.NominalXDimension) - 1, + (float)((0.145 - 0.048) / 2 / 0.020), + (float)(0.048 / 0.020), + (float)((0.145 - 0.048) / 2 / 0.020), + UspsPostalEncoder.ClearZone, + (float)(0.028 / 0.020), + BarcodeTextSide.BelowBars, + (float)(0.028 / 0.020), + BarcodeTextAlignment.Left, + UspsPostalEncoder.Pitch, + false) + { + MinimumXDimension = UspsPostalEncoder.MinimumXDimension, + MaximumXDimension = UspsPostalEncoder.MaximumXDimension, + MinimumBarsPerInch = 20, + MaximumBarsPerInch = 24, + MinimumFullBar = (float)(0.125 / 0.020), + MaximumFullBar = (float)(0.165 / 0.020), + }; + + /// + /// Gets Table 19, the characters with five of their 13 bits set, which a codeword below selects. The code of Table 18 orders + /// them: every such 13-bit value is visited in ascending order, a value and its bit reversal fill the next two entries from the start, and a value that is + /// its own reversal fills the next entry from the end. + /// + private static ReadOnlySpan FiveOf13 => + [ + 31, 7936, 47, 7808, 55, 7552, 59, 7040, 61, 6016, 62, 3968, 79, 7744, 87, 7488, 91, 6976, 93, 5952, 94, 3904, 103, 7360, 107, + 6848, 109, 5824, 110, 3776, 115, 6592, 117, 5568, 118, 3520, 121, 5056, 122, 3008, 124, 1984, 143, 7712, 151, 7456, 155, 6944, 157, 5920, + 158, 3872, 167, 7328, 171, 6816, 173, 5792, 174, 3744, 179, 6560, 181, 5536, 182, 3488, 185, 5024, 186, 2976, 188, 1952, 199, 7264, 203, + 6752, 205, 5728, 206, 3680, 211, 6496, 213, 5472, 214, 3424, 217, 4960, 218, 2912, 220, 1888, 227, 6368, 229, 5344, 230, 3296, 233, 4832, + 234, 2784, 236, 1760, 241, 4576, 242, 2528, 244, 1504, 248, 992, 271, 7696, 279, 7440, 283, 6928, 285, 5904, 286, 3856, 295, 7312, 299, + 6800, 301, 5776, 302, 3728, 307, 6544, 309, 5520, 310, 3472, 313, 5008, 314, 2960, 316, 1936, 327, 7248, 331, 6736, 333, 5712, 334, 3664, + 339, 6480, 341, 5456, 342, 3408, 345, 4944, 346, 2896, 348, 1872, 355, 6352, 357, 5328, 358, 3280, 361, 4816, 362, 2768, 364, 1744, 369, + 4560, 370, 2512, 372, 1488, 376, 976, 391, 7216, 395, 6704, 397, 5680, 398, 3632, 403, 6448, 405, 5424, 406, 3376, 409, 4912, 410, 2864, + 412, 1840, 419, 6320, 421, 5296, 422, 3248, 425, 4784, 426, 2736, 428, 1712, 433, 4528, 434, 2480, 436, 1456, 440, 944, 451, 6256, 453, + 5232, 454, 3184, 457, 4720, 458, 2672, 460, 1648, 465, 4464, 466, 2416, 468, 1392, 472, 880, 481, 4336, 482, 2288, 484, 1264, 488, 752, + 527, 7688, 535, 7432, 539, 6920, 541, 5896, 542, 3848, 551, 7304, 555, 6792, 557, 5768, 558, 3720, 563, 6536, 565, 5512, 566, 3464, 569, + 5000, 570, 2952, 572, 1928, 583, 7240, 587, 6728, 589, 5704, 590, 3656, 595, 6472, 597, 5448, 598, 3400, 601, 4936, 602, 2888, 604, 1864, + 611, 6344, 613, 5320, 614, 3272, 617, 4808, 618, 2760, 620, 1736, 625, 4552, 626, 2504, 628, 1480, 632, 968, 647, 7208, 651, 6696, 653, + 5672, 654, 3624, 659, 6440, 661, 5416, 662, 3368, 665, 4904, 666, 2856, 668, 1832, 675, 6312, 677, 5288, 678, 3240, 681, 4776, 682, 2728, + 684, 1704, 689, 4520, 690, 2472, 692, 1448, 696, 936, 707, 6248, 709, 5224, 710, 3176, 713, 4712, 714, 2664, 716, 1640, 721, 4456, 722, + 2408, 724, 1384, 728, 872, 737, 4328, 738, 2280, 740, 1256, 775, 7192, 779, 6680, 781, 5656, 782, 3608, 787, 6424, 789, 5400, 790, 3352, + 793, 4888, 794, 2840, 796, 1816, 803, 6296, 805, 5272, 806, 3224, 809, 4760, 810, 2712, 812, 1688, 817, 4504, 818, 2456, 820, 1432, 824, + 920, 835, 6232, 837, 5208, 838, 3160, 841, 4696, 842, 2648, 844, 1624, 849, 4440, 850, 2392, 852, 1368, 865, 4312, 866, 2264, 868, 1240, + 899, 6200, 901, 5176, 902, 3128, 905, 4664, 906, 2616, 908, 1592, 913, 4408, 914, 2360, 916, 1336, 929, 4280, 930, 2232, 932, 1208, 961, + 4216, 962, 2168, 964, 1144, 1039, 7684, 1047, 7428, 1051, 6916, 1053, 5892, 1054, 3844, 1063, 7300, 1067, 6788, 1069, 5764, 1070, 3716, 1075, 6532, + 1077, 5508, 1078, 3460, 1081, 4996, 1082, 2948, 1084, 1924, 1095, 7236, 1099, 6724, 1101, 5700, 1102, 3652, 1107, 6468, 1109, 5444, 1110, 3396, 1113, + 4932, 1114, 2884, 1116, 1860, 1123, 6340, 1125, 5316, 1126, 3268, 1129, 4804, 1130, 2756, 1132, 1732, 1137, 4548, 1138, 2500, 1140, 1476, 1159, 7204, + 1163, 6692, 1165, 5668, 1166, 3620, 1171, 6436, 1173, 5412, 1174, 3364, 1177, 4900, 1178, 2852, 1180, 1828, 1187, 6308, 1189, 5284, 1190, 3236, 1193, + 4772, 1194, 2724, 1196, 1700, 1201, 4516, 1202, 2468, 1204, 1444, 1219, 6244, 1221, 5220, 1222, 3172, 1225, 4708, 1226, 2660, 1228, 1636, 1233, 4452, + 1234, 2404, 1236, 1380, 1249, 4324, 1250, 2276, 1287, 7188, 1291, 6676, 1293, 5652, 1294, 3604, 1299, 6420, 1301, 5396, 1302, 3348, 1305, 4884, 1306, + 2836, 1308, 1812, 1315, 6292, 1317, 5268, 1318, 3220, 1321, 4756, 1322, 2708, 1324, 1684, 1329, 4500, 1330, 2452, 1332, 1428, 1347, 6228, 1349, 5204, + 1350, 3156, 1353, 4692, 1354, 2644, 1356, 1620, 1361, 4436, 1362, 2388, 1377, 4308, 1378, 2260, 1411, 6196, 1413, 5172, 1414, 3124, 1417, 4660, 1418, + 2612, 1420, 1588, 1425, 4404, 1426, 2356, 1441, 4276, 1442, 2228, 1473, 4212, 1474, 2164, 1543, 7180, 1547, 6668, 1549, 5644, 1550, 3596, 1555, 6412, + 1557, 5388, 1558, 3340, 1561, 4876, 1562, 2828, 1564, 1804, 1571, 6284, 1573, 5260, 1574, 3212, 1577, 4748, 1578, 2700, 1580, 1676, 1585, 4492, 1586, + 2444, 1603, 6220, 1605, 5196, 1606, 3148, 1609, 4684, 1610, 2636, 1617, 4428, 1618, 2380, 1633, 4300, 1634, 2252, 1667, 6188, 1669, 5164, 1670, 3116, + 1673, 4652, 1674, 2604, 1681, 4396, 1682, 2348, 1697, 4268, 1698, 2220, 1729, 4204, 1730, 2156, 1795, 6172, 1797, 5148, 1798, 3100, 1801, 4636, 1802, + 2588, 1809, 4380, 1810, 2332, 1825, 4252, 1826, 2204, 1857, 4188, 1858, 2140, 1921, 4156, 1922, 2108, 2063, 7682, 2071, 7426, 2075, 6914, 2077, 5890, + 2078, 3842, 2087, 7298, 2091, 6786, 2093, 5762, 2094, 3714, 2099, 6530, 2101, 5506, 2102, 3458, 2105, 4994, 2106, 2946, 2119, 7234, 2123, 6722, 2125, + 5698, 2126, 3650, 2131, 6466, 2133, 5442, 2134, 3394, 2137, 4930, 2138, 2882, 2147, 6338, 2149, 5314, 2150, 3266, 2153, 4802, 2154, 2754, 2161, 4546, + 2162, 2498, 2183, 7202, 2187, 6690, 2189, 5666, 2190, 3618, 2195, 6434, 2197, 5410, 2198, 3362, 2201, 4898, 2202, 2850, 2211, 6306, 2213, 5282, 2214, + 3234, 2217, 4770, 2218, 2722, 2225, 4514, 2226, 2466, 2243, 6242, 2245, 5218, 2246, 3170, 2249, 4706, 2250, 2658, 2257, 4450, 2258, 2402, 2273, 4322, + 2311, 7186, 2315, 6674, 2317, 5650, 2318, 3602, 2323, 6418, 2325, 5394, 2326, 3346, 2329, 4882, 2330, 2834, 2339, 6290, 2341, 5266, 2342, 3218, 2345, + 4754, 2346, 2706, 2353, 4498, 2354, 2450, 2371, 6226, 2373, 5202, 2374, 3154, 2377, 4690, 2378, 2642, 2385, 4434, 2401, 4306, 2435, 6194, 2437, 5170, + 2438, 3122, 2441, 4658, 2442, 2610, 2449, 4402, 2465, 4274, 2497, 4210, 2567, 7178, 2571, 6666, 2573, 5642, 2574, 3594, 2579, 6410, 2581, 5386, 2582, + 3338, 2585, 4874, 2586, 2826, 2595, 6282, 2597, 5258, 2598, 3210, 2601, 4746, 2602, 2698, 2609, 4490, 2627, 6218, 2629, 5194, 2630, 3146, 2633, 4682, + 2641, 4426, 2657, 4298, 2691, 6186, 2693, 5162, 2694, 3114, 2697, 4650, 2705, 4394, 2721, 4266, 2753, 4202, 2819, 6170, 2821, 5146, 2822, 3098, 2825, + 4634, 2833, 4378, 2849, 4250, 2881, 4186, 2945, 4154, 3079, 7174, 3083, 6662, 3085, 5638, 3086, 3590, 3091, 6406, 3093, 5382, 3094, 3334, 3097, 4870, + 3107, 6278, 3109, 5254, 3110, 3206, 3113, 4742, 3121, 4486, 3139, 6214, 3141, 5190, 3145, 4678, 3153, 4422, 3169, 4294, 3203, 6182, 3205, 5158, 3209, + 4646, 3217, 4390, 3233, 4262, 3265, 4198, 3331, 6166, 3333, 5142, 3337, 4630, 3345, 4374, 3361, 4246, 3393, 4182, 3457, 4150, 3587, 6158, 3589, 5134, + 3593, 4622, 3601, 4366, 3617, 4238, 3649, 4174, 3713, 4142, 3841, 4126, 4111, 7681, 4119, 7425, 4123, 6913, 4125, 5889, 4135, 7297, 4139, 6785, 4141, + 5761, 4147, 6529, 4149, 5505, 4153, 4993, 4167, 7233, 4171, 6721, 4173, 5697, 4179, 6465, 4181, 5441, 4185, 4929, 4195, 6337, 4197, 5313, 4201, 4801, + 4209, 4545, 4231, 7201, 4235, 6689, 4237, 5665, 4243, 6433, 4245, 5409, 4249, 4897, 4259, 6305, 4261, 5281, 4265, 4769, 4273, 4513, 4291, 6241, 4293, + 5217, 4297, 4705, 4305, 4449, 4359, 7185, 4363, 6673, 4365, 5649, 4371, 6417, 4373, 5393, 4377, 4881, 4387, 6289, 4389, 5265, 4393, 4753, 4401, 4497, + 4419, 6225, 4421, 5201, 4425, 4689, 4483, 6193, 4485, 5169, 4489, 4657, 4615, 7177, 4619, 6665, 4621, 5641, 4627, 6409, 4629, 5385, 4633, 4873, 4643, + 6281, 4645, 5257, 4649, 4745, 4675, 6217, 4677, 5193, 4739, 6185, 4741, 5161, 4867, 6169, 4869, 5145, 5127, 7173, 5131, 6661, 5133, 5637, 5139, 6405, + 5141, 5381, 5155, 6277, 5157, 5253, 5187, 6213, 5251, 6181, 5379, 6165, 5635, 6157, 6151, 7171, 6155, 6659, 6163, 6403, 6179, 6275, 6211, 5189, 4681, + 4433, 4321, 3142, 2634, 2386, 2274, 1612, 1364, 1252, 856, 744, 496, + ]; + + /// + /// Gets Table 20, the characters with two of their 13 bits set, in the order of the code of Table 18. A codeword of or more + /// selects entry codeword minus . + /// + private static ReadOnlySpan TwoOf13 => + [ + 3, 6144, 5, 5120, 6, 3072, 9, 4608, 10, 2560, 12, 1536, 17, 4352, 18, 2304, 20, 1280, 24, 768, 33, 4224, 34, 2176, 36, + 1152, 40, 640, 48, 384, 65, 4160, 66, 2112, 68, 1088, 72, 576, 80, 320, 96, 192, 129, 4128, 130, 2080, 132, 1056, 136, 544, + 144, 288, 257, 4112, 258, 2064, 260, 1040, 264, 528, 513, 4104, 514, 2056, 516, 1032, 1025, 4100, 1026, 2052, 2049, 4098, 4097, 2050, 1028, + 520, 272, 160, + ]; + + /// + /// Gets Table 22, the bar to character mapping, as four values per bar from the left: the character and the bit of the descender, then the character and + /// the bit of the ascender. Characters A to J are 0 to 9. + /// + private static ReadOnlySpan BarMap => + [ + 7, 2, 4, 3, 1, 10, 0, 0, 9, 12, 2, 8, 5, 5, 6, 11, 8, 9, 3, 1, 0, 1, 5, 12, 2, 5, 1, 8, 4, 4, 9, 11, 6, 3, 8, 10, 3, 9, 7, 6, + 5, 11, 1, 4, 8, 5, 2, 12, 9, 10, 0, 2, 7, 1, 6, 7, 3, 6, 4, 9, 0, 3, 8, 6, 6, 4, 2, 7, 1, 1, 9, 9, 7, 10, 5, 2, 4, 0, 3, 8, + 6, 2, 0, 4, 8, 11, 1, 0, 9, 8, 3, 12, 2, 6, 7, 7, 5, 1, 4, 10, 1, 12, 6, 9, 7, 3, 8, 0, 5, 8, 9, 7, 4, 6, 2, 10, 3, 4, 0, 5, + 8, 4, 5, 7, 7, 11, 1, 9, 6, 0, 9, 6, 0, 6, 4, 8, 2, 1, 3, 2, 5, 9, 8, 12, 4, 11, 6, 1, 9, 5, 7, 4, 3, 3, 1, 2, 0, 7, 2, 0, + 1, 3, 4, 1, 6, 10, 3, 5, 8, 7, 9, 4, 2, 11, 5, 6, 0, 8, 7, 12, 4, 2, 8, 1, 5, 10, 3, 0, 9, 3, 0, 9, 6, 5, 2, 4, 7, 8, 1, 7, + 5, 0, 4, 5, 2, 3, 0, 10, 6, 12, 9, 2, 3, 11, 1, 6, 8, 8, 7, 9, 5, 4, 0, 11, 1, 5, 2, 2, 9, 1, 4, 12, 8, 3, 6, 6, 7, 0, 3, 7, + 4, 7, 7, 5, 0, 12, 1, 11, 2, 9, 9, 0, 6, 8, 5, 3, 3, 10, 8, 2, + ]; + + /// + /// Encodes the digits into bar states. + /// + /// The tracking code followed by the routing code, already validated. + /// The buffer that receives the 65 bar states. + public static void Encode(ReadOnlySpan text, Span states) + { + UInt128 binary = Binary(text[..TrackingLength], text[TrackingLength..]); + int fcs = FrameCheckSequence(binary); + InlineArray10 codewordStorage = default; + Span codewords = codewordStorage; + Codewords(binary, fcs, codewords); + InlineArray10 characterStorage = default; + Span characters = characterStorage; + Characters(codewords, fcs, characters); + Bars(characters, states); + } + + /// + /// Step 1, section 2.2.1: converts the routing code to a value by Table 4, then multiplies the value by 10 and adds the first tracking digit, multiplies by + /// 5 and adds the second, and multiplies by 10 and adds each of the remaining 18. + /// + /// The 20 tracking digits. + /// The 0, 5, 9 or 11 routing digits. + /// The binary data, which fills the rightmost 102 bits. + public static UInt128 Binary(ReadOnlySpan tracking, ReadOnlySpan routing) + { + UInt128 value = 0; + for (int i = 0; i < routing.Length; i++) + { + value = (value * 10U) + (uint)(routing[i] - '0'); + } + + value += routing.Length switch + { + 5 => 1UL, + 9 => 100001UL, + 11 => 1000100001UL, + _ => 0UL, + }; + + value = (value * 10U) + (uint)(tracking[0] - '0'); + value = (value * 5U) + (uint)(tracking[1] - '0'); + for (int i = 2; i < tracking.Length; i++) + { + value = (value * 10U) + (uint)(tracking[i] - '0'); + } + + return value; + } + + /// + /// Step 2, section 2.2.2: the 11-bit frame check sequence of the rightmost 102 bits, by the code of Table 17. The data is 13 bytes of which the first byte + /// carries 6 bits. + /// + /// The binary data. + /// The frame check sequence. + public static int FrameCheckSequence(UInt128 data) + { + int fcs = FrameCheckMask; + int word = ((int)(data >> 96) & 0xFF) << 5; + for (int bit = 2; bit < 8; bit++) + { + fcs = Shift(fcs, word); + word <<= 1; + } + + for (int byteIndex = 1; byteIndex < 13; byteIndex++) + { + word = ((int)(data >> (8 * (12 - byteIndex))) & 0xFF) << 3; + for (int bit = 0; bit < 8; bit++) + { + fcs = Shift(fcs, word); + word <<= 1; + } + } + + return fcs; + + static int Shift(int fcs, int word) + => (((fcs ^ word) & FrameCheckTopBit) != 0 ? (fcs << 1) ^ GeneratorPolynomial : fcs << 1) & FrameCheckMask; + } + + /// + /// Steps 3 and 4, sections 2.2.3 and 2.2.4: the rightmost codeword J is the value modulo 636, codewords I to B are successive remainders modulo 1365 and + /// codeword A is what remains. J is then doubled, and A gains 659 when the most significant bit of the frame check sequence is set. + /// + /// The binary data. + /// The frame check sequence. + /// The buffer that receives the ten codewords, A first. + public static void Codewords(UInt128 data, int fcs, Span codewords) + { + codewords[9] = (int)(data % CodewordJRadix) * 2; + data /= CodewordJRadix; + for (int i = 8; i >= 1; i--) + { + codewords[i] = (int)(data % CodewordRadix); + data /= CodewordRadix; + } + + codewords[0] = (int)data; + if ((fcs & FrameCheckTopBit) != 0) + { + codewords[0] += CodewordAOffset; + } + } + + /// + /// Step 5, section 2.2.5: a codeword below 1287 selects its character from Table 19 and any other codeword selects entry codeword minus 1287 from Table 20. + /// Then, by Table 21, each of the frame check sequence bits 0 to 9 that is set negates the 13 bits of characters A to J in turn. + /// + /// The ten codewords. + /// The frame check sequence. + /// The buffer that receives the ten characters, A first. + public static void Characters(ReadOnlySpan codewords, int fcs, Span characters) + { + for (int i = 0; i < CharacterCount; i++) + { + int codeword = codewords[i]; + characters[i] = codeword < FiveOf13Length ? FiveOf13[codeword] : TwoOf13[codeword - FiveOf13Length]; + if (((fcs >> i) & 1) != 0) + { + characters[i] ^= CharacterMask; + } + } + } + + /// + /// Step 6, section 2.2.6: every bar takes its descender from one character bit and its ascender from another, by Table 22. + /// + /// The ten characters. + /// The buffer that receives the 65 bar states. + private static void Bars(ReadOnlySpan characters, Span states) + { + for (int bar = 0; bar < BarCount; bar++) + { + int entry = bar * 4; + FourState descender = ((characters[BarMap[entry]] >> BarMap[entry + 1]) & 1) != 0 ? FourState.Descender : FourState.Tracker; + FourState ascender = ((characters[BarMap[entry + 2]] >> BarMap[entry + 3]) & 1) != 0 ? FourState.Ascender : FourState.Tracker; + states[bar] = ascender | descender; + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Postal/IntelligentMailSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Postal/IntelligentMailSymbology.cs new file mode 100644 index 00000000..78f8a05b --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Postal/IntelligentMailSymbology.cs @@ -0,0 +1,110 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The United States Postal Service Intelligent Mail barcode, which USPS-B-3200 revision H defines. The text is a 20-digit tracking code and then a routing +/// code of 0, 5, 9 or 11 digits. The symbol thus carries 20, 25, 29 or 31 digits. Section 2.1.3: the second digit of the barcode identifier "shall be +/// constrained to the range of 0-4". Table 31 gives the postal barcode identifier 94 a "Fixed STID value of 009". The symbol is 65 bars. Section 2.4.3 gives +/// the printed line: the fields of the tracking code, then the 5-digit ZIP Code, the 4-digit add-on and the remaining 2 digits of the routing code. The fields +/// are "separated with a space added between data fields". Section 2.4.4: the line prints in "a minimum of 10- to 12-point size type". A smaller font thus +/// prints at 10 points. +/// +public sealed class IntelligentMailSymbology : BarcodeSymbology +{ + /// + /// The barcode identifier of the postal barcode, the pIMb of section 11.3.2.1: "A USPS-applied pIMb is identified by the Barcode ID value of 94". + /// + private const string PostalIdentifier = "94"; + + /// + /// The service type identifier of the postal barcode. Table 31: "Fixed STID value of 009". + /// + private const string PostalServiceType = "009"; + + /// + /// The MPE type of the CFS/RFS construct of Table 33: "Coded 5 = CFS/RFS". + /// + private const char ForwardingMpeType = '5'; + + /// + /// The smallest size of the text line in points. Section 2.4.4: "a minimum of 10- to 12-point size type". + /// + private const float MinimumTextSize = 10F; + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + if (text.Length is not (20 or 25 or 29 or 31)) + { + throw new ArgumentException( + $"The Intelligent Mail barcode carries a 20-digit tracking code and a routing code of 0, 5, 9 or 11 digits; got {text.Length} characters.", + nameof(text)); + } + + BarcodeSymbology.ValidateDigits(text); + if (text[1] > '4') + { + throw new ArgumentException($"The second digit of the barcode identifier is 0 to 4; got {text[1]}.", nameof(text)); + } + + if (text.StartsWith(PostalIdentifier) && !text[2..5].SequenceEqual(PostalServiceType)) + { + throw new ArgumentException($"The barcode identifier 94 carries the service type identifier 009; got {text[2..5]}.", nameof(text)); + } + + InlineArray65 stateStorage = default; + Span states = stateStorage; + IntelligentMailEncoder.Encode(text, states); + + // The 31 digits and at most nine spaces between their groups: six between seven tracking code fields and three before the routing code fields. + InlineArray64 lineStorage = default; + Span line = lineStorage; + int lineLength = options.Font is null ? 0 : HumanReadable(text, line); + FourStateEncoder.BuildSymbol(states, IntelligentMailEncoder.Metrics, line[..lineLength], options, ref symbol); + symbol.SetTextSize(MinimumTextSize, 0F); + } + + /// + /// Builds the line of section 2.4.3. The fields of the tracking code follow the layouts of Appendix F. Tables 26 and 27: the barcode identifier, the + /// service type identifier, the mailer ID and the serial number, where section 2.1.3 C gives the 9-digit mailer ID the range 900000000 to 999999999, so a + /// mailer ID that starts with 9 is 9 digits and the serial number 6, and otherwise 6 and 9. Table 32: the barcode identifier 94 carries the service type + /// identifier, the MPE type, the MPE number of 4 digits, the Julian date of 3, the time of 2 and the mailpiece number of 5. Table 34: the MPE type 5 is + /// followed by fields of 4 and 10 digits. + /// + /// The digits, already validated. + /// The buffer that receives the line. + /// The length of the line. + private static int HumanReadable(ReadOnlySpan text, Span line) + { + ReadOnlySpan tracking = text.StartsWith(PostalIdentifier) + ? text[5] == ForwardingMpeType ? [2, 3, 1, 4, 10] : [2, 3, 1, 4, 3, 2, 5] + : text[5] == '9' ? [2, 3, 9, 6] : [2, 3, 6, 9]; + + ValueStringBuilder builder = new(line); + int position = 0; + foreach (int length in tracking) + { + if (position > 0) + { + builder.Append(' '); + } + + builder.Append(text.Slice(position, length)); + position += length; + } + + ReadOnlySpan routing = [5, 4, 2]; + for (int i = 0; i < routing.Length && position < text.Length; i++) + { + builder.Append(' '); + builder.Append(text.Slice(position, routing[i])); + position += routing[i]; + } + + return builder.Length; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Postal/JapanPostEncoder.cs b/src/ImageSharp.Drawing/Barcodes/Postal/JapanPostEncoder.cs new file mode 100644 index 00000000..b83e50d2 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Postal/JapanPostEncoder.cs @@ -0,0 +1,185 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodation for the Japan Post customer barcode. A symbol is a start code of two bars, twenty codes of three bars, a check code of three bars and a stop code +/// of two bars. The codes are the digits, the hyphen and the control codes CC1 to CC8; a letter is CC1, CC2 or CC3 followed by a digit, and CC4 fills the +/// positions the data leaves empty. +/// +internal static class JapanPostEncoder +{ + /// + /// The number of code positions between the start code and the check code. + /// + public const int CodeCount = 20; + + /// + /// The number of bars in a symbol. + /// + public const int BarCount = 2 + ((CodeCount + 1) * BarsPerCode) + 2; + + /// + /// The number of bars in one code. + /// + private const int BarsPerCode = 3; + + /// + /// The value of the hyphen. + /// + private const int Hyphen = 10; + + /// + /// The value of control code CC1; CC2 to CC8 follow it. + /// + private const int ControlCode1 = 11; + + /// + /// The value of control code CC4, the filler. + /// + private const int ControlCode4 = 14; + + /// + /// The modulus of the check calculation. + /// + private const int Modulus = 19; + + /// + /// The dimensions of page 12 of the Japan Post zip code and barcode manual at the 10 point size: a bar width of 0.6 mm, a bar pitch of 1.2 mm, so a space + /// of 0.6 mm, a timing bar of 1.2 mm and a long bar of 3.6 mm, so an extender of 1.2 mm above or below the timing bar. Every size is "×a/10" for a size of + /// a points, 8 to 11.5, so the bars stay proportional to the bar width. Page 13 gives a blank space of 2 mm or more above, below, left and right of the + /// barcode. The bar width sets the size a. Thus the code clamps the bar width to 0.6×a/10 mm at the permitted sizes, a from 8 to 11.5: 0.48 to 0.69 mm. + /// The code clamps the long bar to its range of 3.40×a/10 to 3.60×a/10, which is 3.40 / 0.6 to 3.60 / 0.6 modules. The timing bar then stays inside its + /// own range of 1.05×a/10 to 1.35×a/10. + /// + public static readonly FourStateMetrics Metrics = new( + 0.6, + 1, + (float)(1.2 / 0.6), + (float)(1.2 / 0.6), + (float)(1.2 / 0.6), + (float)(2 / 0.6), + (float)(2 / 0.6), + BarcodeTextSide.BelowBars, + (float)(2 / 0.6), + BarcodeTextAlignment.Centered, + null, + true) + { + MinimumXDimension = 0.6 * 8 / 10, + MaximumXDimension = 0.6 * 11.5 / 10, + MinimumFullBar = (float)(3.40 / 0.6), + MaximumFullBar = (float)(3.60 / 0.6), + }; + + /// + /// Gets the bar states of every code value, three per code, as values: the digits 0 to 9, the hyphen and CC1 to CC8. + /// + private static ReadOnlySpan Patterns => + [ + 3, 0, 0, 3, 3, 0, 3, 1, 2, 1, 3, 2, 3, 2, 1, 3, 0, 3, 1, 2, 3, 2, 3, 1, 2, 1, 3, 0, 3, 3, + 0, 3, 0, + 1, 2, 0, 1, 0, 2, 2, 1, 0, 0, 1, 2, 2, 0, 1, 0, 2, 1, 0, 0, 3, 3, 3, 3, + ]; + + /// + /// Converts the text to code values and validates it. + /// + /// The text to encode. + /// The buffer of values that receives the codes, filled with CC4 after the data. + public static void Codes(ReadOnlySpan text, Span codes) + { + int count = 0; + SpanCodePointEnumerator codePoints = text.EnumerateCodePoints(); + while (codePoints.MoveNext()) + { + int value = codePoints.Current.Value; + + // A letter is two codes, and a digit or the hyphen is one. The switch validates the character before the capacity check, so an invalid + // character gets its own message. + int needed = value switch + { + >= 'A' and <= 'Z' => 2, + (>= '0' and <= '9') or '-' => 1, + _ => throw new ArgumentException( + $"The Japan Post barcode carries only digits, capital letters and the hyphen; got {codePoints.Current.ToDisplayString()}.", + nameof(text)), + }; + + if (count + needed > CodeCount) + { + throw new ArgumentException($"The Japan Post barcode carries {CodeCount} codes, and a letter is two codes; got more in {text}.", nameof(text)); + } + + if (needed == 2) + { + int letter = value - 'A'; + codes[count++] = (byte)(ControlCode1 + (letter / 10)); + codes[count++] = (byte)(letter % 10); + } + else + { + codes[count++] = (byte)(value == '-' ? Hyphen : value - '0'); + } + } + + codes[count..].Fill(ControlCode4); + } + + /// + /// Calculates the check code: the value that brings the sum of the twenty codes to a multiple of 19. + /// + /// The twenty codes. + /// The check code value, 0 to 18. + public static int CheckCode(ReadOnlySpan codes) + { + int sum = 0; + foreach (byte code in codes) + { + sum += code; + } + + return (Modulus - (sum % Modulus)) % Modulus; + } + + /// + /// Writes the bar states of the symbol. + /// + /// The twenty codes. + /// The buffer that receives the bar states. + public static void Bars(ReadOnlySpan codes, Span states) + { + states[0] = FourState.Full; + states[1] = FourState.Descender; + int bar = 2; + foreach (byte code in codes) + { + bar = Append(code, states, bar); + } + + bar = Append(CheckCode(codes), states, bar); + states[bar] = FourState.Descender; + states[bar + 1] = FourState.Full; + } + + /// + /// Writes the three bar states of a code. + /// + /// The code value. + /// The bar states. + /// The index of the first of the three bars. + /// The index after the three bars. + private static int Append(int code, Span states, int bar) + { + for (int i = 0; i < BarsPerCode; i++) + { + states[bar + i] = (FourState)Patterns[(code * BarsPerCode) + i]; + } + + return bar + BarsPerCode; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Postal/JapanPostSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Postal/JapanPostSymbology.cs new file mode 100644 index 00000000..b02daecc --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Postal/JapanPostSymbology.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Japan Post customer barcode, which the Japan Post zip code and barcode manual defines. The text is the postal code and the address number as digits, +/// capital letters and hyphens, and the symbol carries twenty codes, a check code, and a start and a stop code. A letter takes two codes, and CC4 fills the +/// codes the text leaves empty. The symbol prints no text line: page 13 of the manual prints the barcode alone. +/// +public sealed class JapanPostSymbology : BarcodeSymbology +{ + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + if (text.Length == 0) + { + throw new ArgumentException("The Japan Post barcode carries at least one character.", nameof(text)); + } + + InlineArray24 codeStorage = default; + Span codes = ((Span)codeStorage)[..JapanPostEncoder.CodeCount]; + JapanPostEncoder.Codes(text, codes); + InlineArray128 stateStorage = default; + Span states = ((Span)stateStorage)[..JapanPostEncoder.BarCount]; + JapanPostEncoder.Bars(codes, states); + FourStateEncoder.BuildSymbol(states, JapanPostEncoder.Metrics, [], options, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Postal/KixSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Postal/KixSymbology.cs new file mode 100644 index 00000000..e51b6ce1 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Postal/KixSymbology.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The KIX code of the Dutch post, the Royal Mail 4-State Customer Code without its start bar, stop bar and check character. It carries digits and capital +/// letters, each as four bars of which two have an ascender and two a descender, at the dimensions of Table 11 of the Royal Mail Mailmark barcode definition +/// document. The printed line shows the data as given. +/// +public sealed class KixSymbology : BarcodeSymbology +{ + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Guard.MustBeGreaterThan(text.Length, 0, nameof(text)); + Guard.MustBeLessThanOrEqualTo(text.Length, RoyalMailEncoder.MaximumLength, nameof(text)); + RoyalMailEncoder.Validate(text); + + // Four bars for each character. The text is at most MaximumLength characters, so a short symbol works inline and a longer one rents its states once. + int barCount = text.Length * RoyalMailEncoder.BarsPerCharacter; + InlineArray128 stateStorage = default; + PooledBuffer rented = default; + Span states = barCount <= 128 + ? ((Span)stateStorage)[..barCount] + : (rented = new PooledBuffer(barCount)).Span; + try + { + for (int i = 0; i < text.Length; i++) + { + RoyalMailEncoder.Append(RoyalMailEncoder.Value(text[i]), states[(i * RoyalMailEncoder.BarsPerCharacter)..]); + } + + FourStateEncoder.BuildSymbol(states, FourStateEncoder.RoyalMail, text, options, ref symbol); + } + finally + { + rented.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Postal/PlanetSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Postal/PlanetSymbology.cs new file mode 100644 index 00000000..598290bd --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Postal/PlanetSymbology.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The PLANET symbology, the Postal Alpha Numeric Encoding Technique of the United States Postal Service. It carries 11 or 13 digits with the bars of POSTNET +/// inverted: every digit is five bars of which two are half bars, the symbol carries a correction digit that makes the digit sum a multiple of 10, and a full +/// frame bar stands at each end. The dimensions are those of section 708.4.2.5 of the Domestic Mail Manual. The symbol prints no text line, because the +/// Domestic Mail Manual defines none. +/// +public sealed class PlanetSymbology : BarcodeSymbology +{ + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + if (text.Length is not (11 or 13)) + { + throw new ArgumentException($"PLANET carries 11 or 13 digits; got {text.Length} characters.", nameof(text)); + } + + BarcodeSymbology.ValidateDigits(text); + + UspsPostalEncoder.BuildSymbol(text, options, false, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Postal/PostnetSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Postal/PostnetSymbology.cs new file mode 100644 index 00000000..6d3b7bc0 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Postal/PostnetSymbology.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The POSTNET symbology, which section 708.4.2 of the United States Postal Service Domestic Mail Manual defines: "A POSTNET barcode can represent a 5-digit +/// ZIP Code (32 bars), a 9-digit ZIP+4 code (52 bars), or an 11-digit delivery point code (62 bars)." Every digit is five bars of which two are full, the +/// symbol carries a correction digit that makes the digit sum a multiple of 10, and a full frame bar stands at each end. The symbol prints no text line, +/// because the Domestic Mail Manual defines none. +/// +public sealed class PostnetSymbology : BarcodeSymbology +{ + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + if (text.Length is not (5 or 9 or 11)) + { + throw new ArgumentException($"POSTNET carries 5, 9 or 11 digits; got {text.Length} characters.", nameof(text)); + } + + BarcodeSymbology.ValidateDigits(text); + + UspsPostalEncoder.BuildSymbol(text, options, true, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Postal/RoyalMailEncoder.cs b/src/ImageSharp.Drawing/Barcodes/Postal/RoyalMailEncoder.cs new file mode 100644 index 00000000..9a1a0ce0 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Postal/RoyalMailEncoder.cs @@ -0,0 +1,127 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Shared encodation for the Royal Mail 4-State Customer Code and the symbologies that reuse its character set: every character is four bars, of which two +/// carry an ascender and two a descender. +/// +internal static class RoyalMailEncoder +{ + /// + /// The largest number of characters a symbol carries. The symbology sets no maximum. + /// + public const int MaximumLength = 500; + + /// + /// The number of bars in one character. + /// + public const int BarsPerCharacter = 4; + + /// + /// The characters the symbology encodes, in pattern order. + /// + private const string Characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + /// + /// The check characters in the order of the checksum calculation table, which the upper half value selects the row of and the lower half value the column + /// of. + /// + private const string CheckTable = "ZUVWXY501234B6789AHCDEFGNIJKLMTOPQRS"; + + /// + /// Gets the bar states of every character, four per character in the order of , as values. + /// + private static ReadOnlySpan Patterns => + [ + 0, 0, 3, 3, 0, 1, 2, 3, 0, 1, 3, 2, 1, 0, 2, 3, 1, 0, 3, 2, 1, 1, 2, 2, 0, 2, 1, 3, 0, 3, 0, 3, 0, 3, 1, 2, + 1, 2, 0, 3, 1, 2, 1, 2, 1, 3, 0, 2, 0, 2, 3, 1, 0, 3, 2, 1, 0, 3, 3, 0, 1, 2, 2, 1, 1, 2, 3, 0, 1, 3, 2, 0, + 2, 0, 1, 3, 2, 1, 0, 3, 2, 1, 1, 2, 3, 0, 0, 3, 3, 0, 1, 2, 3, 1, 0, 2, 2, 0, 3, 1, 2, 1, 2, 1, 2, 1, 3, 0, + 3, 0, 2, 1, 3, 0, 3, 0, 3, 1, 2, 0, 2, 2, 1, 1, 2, 3, 0, 1, 2, 3, 1, 0, 3, 2, 0, 1, 3, 2, 1, 0, 3, 3, 0, 0, + ]; + + /// + /// Returns the value of a character, which is its index in . + /// + /// The code point to value. + /// The value, or -1 when the character is not in the set. + public static int Value(int codePoint) => codePoint switch + { + >= '0' and <= '9' => codePoint - '0', + >= 'A' and <= 'Z' => codePoint - 'A' + 10, + _ => -1, + }; + + /// + /// Validates that the text is capital letters and digits alone. + /// + /// The text to validate. + public static void Validate(ReadOnlySpan text) + { + SpanCodePointEnumerator codePoints = text.EnumerateCodePoints(); + while (codePoints.MoveNext()) + { + CodePoint current = codePoints.Current; + if (Value(current.Value) < 0) + { + throw new ArgumentException($"The symbology carries only digits and capital letters; got {current.ToDisplayString()}.", nameof(text)); + } + } + } + + /// + /// Writes the four bar states of a character. + /// + /// The character value. + /// The buffer that receives the states. + public static void Append(int value, Span states) + { + for (int i = 0; i < BarsPerCharacter; i++) + { + states[i] = (FourState)Patterns[(value * BarsPerCharacter) + i]; + } + } + + /// + /// Calculates the check character. The upper half of a character is the ascenders of its four bars weighted 4, 2, 1 and 0 from the left, and the lower half + /// its descenders weighted the same. Each half value of 6 counts as 0. The half values of all the data characters are added, each total is reduced modulo + /// 6, and the two remainders select the row and the column of the checksum table. + /// + /// The data characters, already validated. + /// The check character. + public static char CheckCharacter(ReadOnlySpan text) + { + int upper = 0; + int lower = 0; + InlineArray4 stateStorage = default; + Span states = stateStorage; + foreach (char c in text) + { + Append(Value(c), states); + int characterUpper = 0; + int characterLower = 0; + for (int bar = 0; bar < BarsPerCharacter; bar++) + { + int weight = 4 >> bar; + if ((states[bar] & FourState.Ascender) != 0) + { + characterUpper += weight; + } + + if ((states[bar] & FourState.Descender) != 0) + { + characterLower += weight; + } + } + + upper += characterUpper % 6; + lower += characterLower % 6; + } + + return CheckTable[((upper % 6) * 6) + (lower % 6)]; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Postal/RoyalMailSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Postal/RoyalMailSymbology.cs new file mode 100644 index 00000000..88650ed0 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Postal/RoyalMailSymbology.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Royal Mail 4-State Customer Code, RM4SCC. It carries digits and capital letters, each as four bars of which two have an ascender and two a descender, +/// between a start bar with an ascender and a stop bar that is full, and it always carries the check character of the checksum calculation table after the +/// data. The dimensions are those of Table 11 of the Royal Mail Mailmark barcode definition document, which the Royal Mail symbologies share, with the 2 mm +/// clear zone of its section 3.5.2. The printed line shows the data as given. +/// +public sealed class RoyalMailSymbology : BarcodeSymbology +{ + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Guard.MustBeGreaterThan(text.Length, 0, nameof(text)); + Guard.MustBeLessThanOrEqualTo(text.Length, RoyalMailEncoder.MaximumLength, nameof(text)); + RoyalMailEncoder.Validate(text); + + // Four bars for each character and the check character, and the start and stop bars. The text is at most MaximumLength characters, so a short symbol + // works inline and a longer one rents its states once. + int barCount = ((text.Length + 1) * RoyalMailEncoder.BarsPerCharacter) + 2; + InlineArray128 stateStorage = default; + PooledBuffer rented = default; + Span states = barCount <= 128 + ? ((Span)stateStorage)[..barCount] + : (rented = new PooledBuffer(barCount)).Span; + try + { + states[0] = FourState.Ascender; + int bar = 1; + foreach (char c in text) + { + RoyalMailEncoder.Append(RoyalMailEncoder.Value(c), states[bar..]); + bar += RoyalMailEncoder.BarsPerCharacter; + } + + RoyalMailEncoder.Append(RoyalMailEncoder.Value(RoyalMailEncoder.CheckCharacter(text)), states[bar..]); + states[barCount - 1] = FourState.Full; + + FourStateEncoder.BuildSymbol(states, FourStateEncoder.RoyalMail, text, options, ref symbol); + } + finally + { + rented.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Postal/UspsPostalEncoder.cs b/src/ImageSharp.Drawing/Barcodes/Postal/UspsPostalEncoder.cs new file mode 100644 index 00000000..0be271b9 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Postal/UspsPostalEncoder.cs @@ -0,0 +1,170 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Shared encodation for the United States Postal Service height modulated symbologies, POSTNET and PLANET. Section 708.4.2.5 of the Domestic Mail Manual gives +/// the bar dimensions: "All bars must be 0.020 ±0.005 inch wide", "horizontal spacing of the bars must be 22 ±2 bars per inch", "A full bar must be 0.125 +/// ±0.010 inch high" and "A half bar must be 0.050 ±0.010 inch high". The module is the 0.020 inch bar width. The pitch stays 1/22 inch at every bar width, +/// and the space is the rest of it. Section 708.4.2.5 d: "pitch (a bar and a space) must average at least 0.0416 inch but no more than 0.05 inch". The code +/// clamps the pitch to that range in whole pixels. +/// +internal static class UspsPostalEncoder +{ + /// + /// The nominal X dimension in millimetres: the 0.020 inch bar width. + /// + public const double NominalXDimension = 0.508; + + /// + /// The smallest X dimension in millimetres, 0.015 inch. DMM 708.4.2.5 c: "All bars must be 0.020 ±0.005 inch wide." USPS-B-3200 section 2.3.1: "Individual + /// bars shall be printed with a width of 0.020±0.005 inch." + /// + public const double MinimumXDimension = 0.015 * 25.4; + + /// + /// The largest X dimension in millimetres, 0.025 inch, from the same tolerance. + /// + public const double MaximumXDimension = 0.025 * 25.4; + + /// + /// The pitch, a bar and a space, in millimetres: 1/22 inch, from "22 ±2 bars per inch". + /// + public const double Pitch = 25.4 / 22; + + /// + /// The clear zone in modules on each end, 1/8 inch at the 0.020 inch module. Section 202.5.7 c of the Domestic Mail Manual: "The clearance between the + /// leftmost and rightmost bars and any adjacent printing must be at least 1/8 inch." + /// + public const float ClearZone = 6.25F; + + /// + /// The height of a full bar in modules: 0.125 inch at the 0.020 inch module. + /// + private const float FullBar = 6.25F; + + /// + /// The smallest full bar in modules: 0.115 inch at the 0.020 inch module. DMM 708.4.2.5 a: "A full bar must be 0.125 ±0.010 inch high." The half bar + /// keeps its ratio to the full bar, and at each limit of the full bar it stays inside its own "0.050 ±0.010 inch" of 708.4.2.5 b. + /// + private const float MinimumFullBar = 5.75F; + + /// + /// The largest full bar in modules: 0.135 inch at the 0.020 inch module, from the same tolerance. + /// + private const float MaximumFullBar = 6.75F; + + /// + /// The height of a half bar in modules: 0.050 inch at the 0.020 inch module. + /// + private const float HalfBar = 2.5F; + + /// + /// The clear zone in modules above and below the bars, 1/25 inch at the 0.020 inch module. Section 202.5.7 c of the Domestic Mail Manual: "The minimum + /// clearance between the barcode and any information line above or below it within the address block must be at least 1/25 inch". Section 708.4.4.1 b + /// gives the same "1/25 inch above and below the barcode". + /// + private const float VerticalClearZone = 2F; + + /// + /// The number of bars in one digit. + /// + private const int BarsPerDigit = 5; + + /// + /// The fewest bars per inch. Section 708.4.2.5 d: the "pitch (a bar and a space) must average at least 0.0416 inch but no more than 0.05 inch". A pitch + /// of 0.05 inch is 20 bars per inch. + /// + private const int MinimumBarsPerInch = 20; + + /// + /// The most bars per inch, from the same range. A pitch of 0.0416 inch is 24 bars per inch. + /// + private const int MaximumBarsPerInch = 24; + + /// + /// Gets the bars of every digit, one bit per bar, most significant bar first: a set bit is a full bar in POSTNET and a half bar in PLANET. Section + /// 708.4.2.1 of the Domestic Mail Manual: "A tall bar represents "1," and a short bar represents "0."" Each digit sets two of its five bars. + /// + private static ReadOnlySpan Digits => [0b11000, 0b00011, 0b00101, 0b00110, 0b01001, 0b01010, 0b01100, 0b10001, 0b10010, 0b10100]; + + /// + /// Builds the symbol: a frame bar, five bars per digit, five for the correction digit and a frame bar, with full bars where a set bit stands in POSTNET and + /// half bars in PLANET. Section 708.4.2.1: "The first and last bars of the barcode are frame bars and must always be full bars." A half bar stands on the + /// baseline of the full bars. + /// + /// The data digits, already validated. The symbol adds the correction digit after them. + /// The options that control layout choices. + /// Whether a set bit is a full bar, as in POSTNET, rather than a half bar. + /// The symbol to write. + public static void BuildSymbol(ReadOnlySpan digits, BarcodeOptions options, bool setBitIsFull, ref BarcodeSymbol symbol) + { + double xDimension = options.ResolveXDimension(MinimumXDimension, NominalXDimension, MaximumXDimension); + + // The emitter draws every run of these uniform bars at a whole number of pixels, so the runs count pixels. A bar is one module of whole pixels. The + // pitch stays 1/22 inch at every bar width, so the space is the rest of the pitch at the nearest whole pixel, at least one. + int barPixels = options.ModulePixels(xDimension); + double spacePixels = Math.Max(1, Math.Round(((Pitch / xDimension) - 1) * barPixels, MidpointRounding.AwayFromZero)); + + // Whole pixels can move the pitch out of 20 to 24 bars per inch at the end of the bar width range or at a low resolution. A pitch of Dpi / n pixels + // is n bars per inch. So the code clamps the pitch in whole pixels, as ResolveXDimension clamps the module. The upper limit is the largest whole + // number of pixels at or under the largest pitch. The lower limit is the smallest whole number of pixels at or over the smallest pitch, with a space + // of at least one pixel. The lower limit applies last. + double highest = Math.Floor((options.Dpi / MinimumBarsPerInch) + BarcodeOptions.LimitTolerance); + double lowest = Math.Max(barPixels + 1, Math.Ceiling((options.Dpi / MaximumBarsPerInch) - BarcodeOptions.LimitTolerance)); + int space = (int)Math.Max(Math.Min(barPixels + spacePixels, highest), lowest) - barPixels; + + // Every digit and the correction digit is five bars, and a full frame bar stands at each end. + int barCount = ((digits.Length + 1) * BarsPerDigit) + 2; + Span runs = symbol.BeginLinear((barCount * 2) - 1, xDimension, out Span heights, out Span tops); + for (int i = 0; i < runs.Length; i++) + { + runs[i] = (i & 1) == 0 ? barPixels : space; + } + + // The bar heights and the clear zones are inches in the Domestic Mail Manual, given here in modules of the nominal 0.020 inch bar. The module that the + // symbol draws at can round away from 0.020 inch, so the same inches are this many of its modules. The half bar keeps its ratio to the full bar. + float nominal = (float)(NominalXDimension / xDimension); + float scale = options.ResolveBarHeight(xDimension, MinimumFullBar * nominal, FullBar * nominal, MaximumFullBar * nominal) / FullBar; + float halfBar = HalfBar * scale; + float aboveHalfBar = (FullBar - HalfBar) * scale; + float fullBar = aboveHalfBar + halfBar; + + int correction = CorrectionDigit(digits); + heights[0] = fullBar; + heights[^1] = fullBar; + int bar = 1; + for (int i = 0; i <= digits.Length; i++) + { + int pattern = Digits[i < digits.Length ? digits[i] - '0' : correction]; + for (int bit = BarsPerDigit - 1; bit >= 0; bit--) + { + bool full = (((pattern >> bit) & 1) == 1) == setBitIsFull; + heights[bar] = full ? fullBar : halfBar; + tops[bar] = full ? 0F : aboveHalfBar; + bar++; + } + } + + float runUnit = 1F / barPixels; + symbol.EndLinear(ClearZone * nominal, ClearZone * nominal, VerticalClearZone * nominal, VerticalClearZone * nominal, 0F, runUnit, true); + } + + /// + /// Calculates the correction digit. Section 708.4.2.1: it is "derived from adding the numbers in the ZIP Code (or ZIP+4 or delivery point code) and + /// determining which single-digit number must be added to that sum to make the total a multiple of 10". + /// + /// The digits the symbol carries. + /// The correction digit. + private static int CorrectionDigit(ReadOnlySpan digits) + { + int sum = 0; + foreach (char digit in digits) + { + sum += digit - '0'; + } + + return (10 - (sum % 10)) % 10; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/QrCode/D3AqrHostRatio.cs b/src/ImageSharp.Drawing/Barcodes/QrCode/D3AqrHostRatio.cs new file mode 100644 index 00000000..029993f4 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/QrCode/D3AqrHostRatio.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The pitch of the D3 component of a D3 AQR symbol against the modules of its host QR Code symbol: the width of one D3 module in host modules. +/// +public enum D3AqrHostRatio +{ + /// + /// A D3 module is 2 host modules wide. + /// + Two, + + /// + /// A D3 module is 2.5 host modules wide. + /// + TwoAndAHalf, + + /// + /// A D3 module is 3 host modules wide. + /// + Three, +} diff --git a/src/ImageSharp.Drawing/Barcodes/QrCode/D3AqrSymbology.cs b/src/ImageSharp.Drawing/Barcodes/QrCode/D3AqrSymbology.cs new file mode 100644 index 00000000..d17ac2a3 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/QrCode/D3AqrSymbology.cs @@ -0,0 +1,238 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The D3 AQR symbology (Accessible QR): a QR Code symbol, the host, with a D3 component around its lower right corner. The D3 component is a reversed L of two +/// arms of dashes and dots that a camera finds from a distance, and it carries a 14-bit category, 7 bits along each arm with a parity module for each arm. The +/// host carries the data as does. +/// +/// The symbol is a grid of cells. At a of 2, 2.5 or 3, a host module is 1, 4 or 2 cells on a side, and half a D3 module is 1, 5 or +/// 3 cells. A module of the X dimension is one cell. +/// +/// +public sealed class D3AqrSymbology : BarcodeSymbology +{ + /// + /// The largest category, the 14 bits the two arms carry. + /// + private const int MaximumCategory = 16383; + + /// + /// Initializes a new instance of the class with a host at error correction level M and a D3 module 2 host modules wide. + /// + /// The category, 0 to 16383. + public D3AqrSymbology(int category) + : this(category, QrErrorCorrectionLevel.M, D3AqrHostRatio.Two) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The category, 0 to 16383. + /// The error correction level of the host. + /// The width of a D3 module in host modules. + public D3AqrSymbology(int category, QrErrorCorrectionLevel errorCorrectionLevel, D3AqrHostRatio hostRatio) + { + Guard.MustBeBetweenOrEqualTo(category, 0, MaximumCategory, nameof(category)); + Guard.MustBeBetweenOrEqualTo((int)errorCorrectionLevel, (int)QrErrorCorrectionLevel.L, (int)QrErrorCorrectionLevel.H, nameof(errorCorrectionLevel)); + Guard.MustBeBetweenOrEqualTo((int)hostRatio, (int)D3AqrHostRatio.Two, (int)D3AqrHostRatio.Three, nameof(hostRatio)); + this.Category = category; + this.ErrorCorrectionLevel = errorCorrectionLevel; + this.HostRatio = hostRatio; + } + + /// + /// Gets the category, 0 to 16383. + /// + public int Category { get; } + + /// + /// Gets the error correction level of the host. + /// + public QrErrorCorrectionLevel ErrorCorrectionLevel { get; } + + /// + /// Gets the width of a D3 module in host modules. + /// + public D3AqrHostRatio HostRatio { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + // Half a D3 module and a host module must both be whole cells. At a ratio of 2, 2.5 or 3, half a D3 module is 1, 5 or 3 cells, and a host module is + // 1, 4 or 2 cells. + (int half, int hostCellSize) = this.HostRatio switch + { + D3AqrHostRatio.Two => (1, 1), + D3AqrHostRatio.TwoAndAHalf => (5, 4), + _ => (3, 2), + }; + + // The X dimension is one cell. The nominal cell is DefaultMatrixXDimension / hostCellSize, so a host module is DefaultMatrixXDimension wide before the + // cell rounds to whole pixels. + double xDimension = options.ResolveXDimension(0, DefaultMatrixXDimension / hostCellSize, double.PositiveInfinity); + BarcodeSymbol host = default; + try + { + QrEncoder.BuildSymbol(text, false, QrFormat.Full, this.ErrorCorrectionLevel, 0, xDimension, ref host); + + // The arms reach 35 half modules from the lower right corner and stand in a margin of 6 half modules to the right of and below the host. The host + // keeps at least 2 half modules, one D3 module, above and to its left, and more where it is too small to reach the ends of the arms. + int hostSize = host.Columns; + int hostCells = hostSize * hostCellSize; + int hostOffset = Math.Max(2 * half, (29 * half) - hostCells); + int size = hostOffset + hostCells + (6 * half); + + // Section 5.3.7 of ISO/IEC 18004: the host has a 4X quiet zone. The margins to the right and below are wider than 4 host modules at every ratio, so + // the quiet zone around the grid makes up what the offset above and to the left of the host lacks. + int quietZone = Math.Max(0, (4 * hostCellSize) - hostOffset); + Span modules = symbol.BeginMatrix(size, size, quietZone, xDimension); + + ReadOnlySpan hostModules = host.Modules; + for (int y = 0; y < hostCells; y++) + { + int hostRow = y / hostCellSize * hostSize; + Span row = modules.Slice(((hostOffset + y) * size) + hostOffset, hostCells); + for (int x = 0; x < hostCells; x++) + { + row[x] = hostModules[hostRow + (x / hostCellSize)]; + } + } + + // The D3 component, in half modules from the lower right corner, x to the left and y upwards: the elbow, an arm module on each side, the four + // half-module dash tips and two dots on each arm. + CellGrid grid = new(modules, size, half); + grid.PaintModule(2, 2); + grid.PaintModule(4, 2); + grid.PaintModule(2, 4); + grid.PaintCell(6, 2); + grid.PaintCell(6, 3); + grid.PaintCell(2, 6); + grid.PaintCell(3, 6); + grid.PaintModule(9, 2); + grid.PaintModule(2, 9); + grid.PaintModule(13, 2); + grid.PaintModule(2, 13); + + // Category bits 0 to 6 stand up the right arm, a dark module for a 1, with a timing module beside every second bit, and a parity module above them + // that is dark when the bits hold an odd number of ones. Bits 7 to 13 stand along the bottom arm the same way, and their parity module is dark when + // they hold an even number of ones. + int category = this.Category; + int parity = 0; + for (int i = 0; i <= 6; i++) + { + int y = 31 - (2 * i); + if (i % 2 == 0) + { + grid.PaintModule(0, y); + } + + int bit = (category >> i) & 1; + if (bit == 1) + { + grid.PaintModule(2, y); + } + + parity ^= bit; + } + + if (parity == 1) + { + grid.PaintModule(2, 33); + } + + parity = 1; + for (int i = 7; i <= 13; i++) + { + int x = 45 - (2 * i); + if (i % 2 == 1) + { + grid.PaintModule(x, 0); + } + + int bit = (category >> i) & 1; + if (bit == 1) + { + grid.PaintModule(x, 2); + } + + parity ^= bit; + } + + if (parity == 1) + { + grid.PaintModule(33, 2); + } + } + finally + { + host.Dispose(); + } + } + + /// + /// The cell grid of a D3 AQR symbol, addressed in half modules from its lower right corner. + /// + private readonly ref struct CellGrid + { + /// + /// The cells, row-major. + /// + private readonly Span modules; + + /// + /// The number of cells on a side. + /// + private readonly int size; + + /// + /// The number of cells on a side of a half module. + /// + private readonly int half; + + /// + /// Initializes a new instance of the struct. + /// + /// The cells, row-major, x long. + /// The number of cells on a side. + /// The number of cells on a side of a half module. + public CellGrid(Span modules, int size, int half) + { + this.modules = modules; + this.size = size; + this.half = half; + } + + /// + /// Darkens one D3 module, 2 x 2 half modules, whose lower right half module stands at a position. + /// + /// The position in half modules to the left of the lower right corner. + /// The position in half modules above the lower right corner. + public void PaintModule(int x, int y) + { + this.PaintCell(x, y); + this.PaintCell(x, y + 1); + this.PaintCell(x + 1, y); + this.PaintCell(x + 1, y + 1); + } + + /// + /// Darkens one half module. + /// + /// The position in half modules to the left of the lower right corner. + /// The position in half modules above the lower right corner. + public void PaintCell(int x, int y) + { + int half = this.half; + int bottom = this.size - 1 - (y * half); + int right = this.size - 1 - (x * half); + for (int row = bottom - half + 1; row <= bottom; row++) + { + this.modules.Slice((row * this.size) + right - half + 1, half).Fill(true); + } + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/QrCode/Gs1DigitalLinkQrCodeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/QrCode/Gs1DigitalLinkQrCodeSymbology.cs new file mode 100644 index 00000000..c6cda47f --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/QrCode/Gs1DigitalLinkQrCodeSymbology.cs @@ -0,0 +1,117 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// A GS1 Digital Link URI in a QR Code symbol, section 5.10 of the GS1 General Specifications. The symbol carries the URI as plain data. The URI follows the +/// GS1 Digital Link Standard: URI Syntax: a primary identification key and its key qualifiers in the path, data attributes in the query string, and every +/// Application Identifier with valid data. +/// +/// When a font is set, the element strings that the URI carries print below the symbol, one on each line, with the Application Identifiers in parentheses, in +/// the order of the URI. The URI does not print. +/// +/// +/// Table 5-46, symbol specification table 1 addendum 2, gives an X dimension of 0.396 to 0.990 millimetres with a target of 0.495. An X dimension outside +/// that range draws at the nearest limit. +/// +/// +public sealed class Gs1DigitalLinkQrCodeSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class at error correction level M. The symbol takes the smallest version + /// that holds the URI. + /// + public Gs1DigitalLinkQrCodeSymbology() + : this(QrErrorCorrectionLevel.M) + { + } + + /// + /// Initializes a new instance of the class. The symbol takes the smallest version that holds the URI. + /// + /// The error correction level. + public Gs1DigitalLinkQrCodeSymbology(QrErrorCorrectionLevel errorCorrectionLevel) + { + Guard.MustBeBetweenOrEqualTo((int)errorCorrectionLevel, (int)QrErrorCorrectionLevel.L, (int)QrErrorCorrectionLevel.H, nameof(errorCorrectionLevel)); + this.ErrorCorrectionLevel = errorCorrectionLevel; + } + + /// + /// Initializes a new instance of the class with a fixed version. + /// + /// The error correction level. + /// The version, 1 to 40. + public Gs1DigitalLinkQrCodeSymbology(QrErrorCorrectionLevel errorCorrectionLevel, int version) + : this(errorCorrectionLevel) + { + Guard.MustBeBetweenOrEqualTo(version, 1, 40, nameof(version)); + this.Version = version; + } + + /// + /// Gets the error correction level. + /// + public QrErrorCorrectionLevel ErrorCorrectionLevel { get; } + + /// + /// Gets the version, or when the version follows from the URI. + /// + public int? Version { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + // The URI encodes as plain data, so no URI longer than the largest symbol holds is parsed. + Guard.MustBeLessThanOrEqualTo(text.Length, QrEncoder.MaximumLength, nameof(text)); + int version = this.Version ?? 0; + double xDimension = options.ResolveXDimension( + Gs1Data.DigitalLinkMinimumXDimension, + Gs1Data.DigitalLinkXDimension, + Gs1Data.DigitalLinkMaximumXDimension); + + if (options.Font is null) + { + Gs1DigitalLink.Validate(text); + QrEncoder.BuildSymbol( + text, + false, + QrFormat.Full, + this.ErrorCorrectionLevel, + version, + xDimension, + ref symbol); + + return; + } + + // The element strings print as GS1 QR Code prints them, from the values the validation decodes. The URI is bounded above, so the buffer is too, and a + // longer URI rents it once. + InlineArray64 readableStorage = default; + PooledBuffer rented = default; + int capacity = Gs1DigitalLink.ReadableCapacity(text.Length); + Span readable = capacity <= Gs1Data.InlineBufferLength + ? ((Span)readableStorage)[..capacity] + : (rented = new PooledBuffer(capacity)).Span; + try + { + int written = Gs1DigitalLink.Validate(text, readable); + QrEncoder.BuildSymbol( + text, + false, + QrFormat.Full, + this.ErrorCorrectionLevel, + version, + xDimension, + ref symbol); + + symbol.AddMatrixText(readable[..written], BarcodeTextAlignment.Left, Gs1Data.TextScale(options.Font)); + } + finally + { + rented.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/QrCode/Gs1QrCodeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/QrCode/Gs1QrCodeSymbology.cs new file mode 100644 index 00000000..0daa9645 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/QrCode/Gs1QrCodeSymbology.cs @@ -0,0 +1,95 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// GS1 QR Code, section 5.7 of the GS1 General Specifications: a QR Code symbol that carries GS1 element strings. The symbol is in FNC1 first position mode, +/// and an FNC1 character separates an element string of variable length from the next one. +/// +/// The input is the element string syntax the standard prints: an Application Identifier in parentheses, then its data, repeated. For example, +/// (01)09521234543213(17)120508(10)ABCD1234. The symbol does not carry the parentheses. +/// +/// +/// When a font is set, the element strings print below the symbol, one on each line, with the Application Identifiers in parentheses. An element string of +/// Application Identifier (8200) does not print. +/// +/// +/// The X dimension is the target of 0.495 millimetre of Table 5-45, symbol specification table 1 addendum 1. The symbol specification tables of section +/// 5.12.3 give GS1 QR Code X dimensions from 0.254 to 3.50 millimetres. An X dimension outside that range draws at the nearest limit. +/// +/// +public sealed class Gs1QrCodeSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class at error correction level M. The symbol takes the smallest version that holds + /// the data. + /// + public Gs1QrCodeSymbology() + : this(QrErrorCorrectionLevel.M) + { + } + + /// + /// Initializes a new instance of the class. The symbol takes the smallest version that holds the data. + /// + /// The error correction level. + public Gs1QrCodeSymbology(QrErrorCorrectionLevel errorCorrectionLevel) + { + Guard.MustBeBetweenOrEqualTo((int)errorCorrectionLevel, (int)QrErrorCorrectionLevel.L, (int)QrErrorCorrectionLevel.H, nameof(errorCorrectionLevel)); + this.ErrorCorrectionLevel = errorCorrectionLevel; + } + + /// + /// Initializes a new instance of the class with a fixed version. + /// + /// The error correction level. + /// The version, 1 to 40. + public Gs1QrCodeSymbology(QrErrorCorrectionLevel errorCorrectionLevel, int version) + : this(errorCorrectionLevel) + { + Guard.MustBeBetweenOrEqualTo(version, 1, 40, nameof(version)); + this.Version = version; + } + + /// + /// Gets the error correction level. + /// + public QrErrorCorrectionLevel ErrorCorrectionLevel { get; } + + /// + /// Gets the version, or when the version follows from the data. + /// + public int? Version { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray64 bufferStorage = default; + int capacity = Gs1Data.PreparedCapacity(text, QrEncoder.MaximumLength); + ValueStringBuilder encoded = capacity <= Gs1Data.InlineBufferLength ? new(bufferStorage) : new(capacity); + try + { + Gs1Data.Prepare(text, ref encoded); + QrEncoder.BuildSymbol( + encoded.AsSpan(), + true, + QrFormat.Full, + this.ErrorCorrectionLevel, + this.Version ?? 0, + options.ResolveXDimension(Gs1Data.Gs1QrCodeMinimumXDimension, Gs1Data.Gs1MatrixXDimension, Gs1Data.Gs1MatrixMaximumXDimension), + ref symbol); + + if (options.Font is not null) + { + Gs1Data.AddMatrixReadable(text, options.Font, ref symbol); + } + } + finally + { + encoded.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/QrCode/HibcQrCodeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/QrCode/HibcQrCodeSymbology.cs new file mode 100644 index 00000000..5259ecb8 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/QrCode/HibcQrCodeSymbology.cs @@ -0,0 +1,110 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// HIBC QR Code, a Health Industry Bar Code carried by a QR Code symbol. The symbol carries the flag character, the data and the modulo 43 check character. +/// The text follows a data structure of ANSI/HIBC 2.6: primary data, secondary data that ends with its link character, or primary and secondary data joined +/// with "/", each with any supplemental data fields. +/// +/// When a font is set, that same string prints below the symbol between delimiters, with a space printed as an underscore and a zero as "Ø". Primary and +/// secondary data that the symbol joins with "/" print on two lines, broken after the "/". When the options set no X dimension, the X dimension +/// is 0.015 inch, from section 5.2 of ANSI/HIBC 2.6. An X dimension under 0.010 inch draws at 0.010 inch, the minimum of the same section. +/// +/// +public sealed class HibcQrCodeSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class at error correction level M, calculating the check character from the data. + /// The symbol takes the smallest version that holds the data. + /// + public HibcQrCodeSymbology() + : this(QrErrorCorrectionLevel.M, false) + { + } + + /// + /// Initializes a new instance of the class at error correction level M. The symbol takes the smallest version that holds + /// the data. + /// + /// Whether the last character of the input is the check character, which the encoder validates against the data, + /// rather than data the encoder appends a check character to. + public HibcQrCodeSymbology(bool validateCheckCharacter) + : this(QrErrorCorrectionLevel.M, validateCheckCharacter) + { + } + + /// + /// Initializes a new instance of the class. The symbol takes the smallest version that holds the data. + /// + /// The error correction level. + /// Whether the last character of the input is the check character, which the encoder validates against the data, + /// rather than data the encoder appends a check character to. + public HibcQrCodeSymbology(QrErrorCorrectionLevel errorCorrectionLevel, bool validateCheckCharacter) + { + Guard.MustBeBetweenOrEqualTo((int)errorCorrectionLevel, (int)QrErrorCorrectionLevel.L, (int)QrErrorCorrectionLevel.H, nameof(errorCorrectionLevel)); + this.ErrorCorrectionLevel = errorCorrectionLevel; + this.ValidateCheckCharacter = validateCheckCharacter; + } + + /// + /// Initializes a new instance of the class with a fixed version. + /// + /// The error correction level. + /// The version, 1 to 40. + /// Whether the last character of the input is the check character, which the encoder validates against the data, + /// rather than data the encoder appends a check character to. + public HibcQrCodeSymbology(QrErrorCorrectionLevel errorCorrectionLevel, int version, bool validateCheckCharacter) + : this(errorCorrectionLevel, validateCheckCharacter) + { + Guard.MustBeBetweenOrEqualTo(version, 1, 40, nameof(version)); + this.Version = version; + } + + /// + /// Gets the error correction level. + /// + public QrErrorCorrectionLevel ErrorCorrectionLevel { get; } + + /// + /// Gets the version, or when the version follows from the data. + /// + public int? Version { get; } + + /// + /// Gets a value indicating whether the last character of the input is the check character. + /// + public bool ValidateCheckCharacter { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + InlineArray64 bufferStorage = default; + int capacity = HibcData.PreparedCapacity(text, QrEncoder.MaximumLength); + ValueStringBuilder encoded = capacity <= HibcData.InlineBufferLength ? new(bufferStorage) : new(capacity); + try + { + HibcData.Prepare(text, this.ValidateCheckCharacter, ref encoded); + QrEncoder.BuildSymbol( + encoded.AsSpan(), + false, + QrFormat.Full, + this.ErrorCorrectionLevel, + this.Version ?? 0, + options.ResolveXDimension(HibcData.MinimumMatrixXDimension, HibcData.NominalMatrixXDimension, double.PositiveInfinity), + ref symbol); + + if (options.Font is not null) + { + HibcData.AddTwoDimensionalReadable(encoded.AsSpan(), false, ref symbol); + } + } + finally + { + encoded.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/QrCode/MicroQrCodeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/QrCode/MicroQrCodeSymbology.cs new file mode 100644 index 00000000..eabc0ee1 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/QrCode/MicroQrCodeSymbology.cs @@ -0,0 +1,75 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Micro QR Code symbology, ISO/IEC 18004: a square matrix of 11 x 11 to 17 x 17 modules, versions M1 to M4, with one finder pattern. Version M1 carries +/// digits only, M2 digits and the Alphanumeric characters, and M3 and M4 also the characters U+0000 to U+00FF in Byte mode. Error correction level H does not +/// exist, level Q exists in M4 only, and M1 detects errors without correcting them. +/// +public sealed class MicroQrCodeSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class at error correction level L. The symbol takes the smallest version that holds + /// the data. + /// + public MicroQrCodeSymbology() + : this(QrErrorCorrectionLevel.L) + { + } + + /// + /// Initializes a new instance of the class. The symbol takes the smallest version that holds the data at the level. + /// + /// The error correction level, L, M or Q. + public MicroQrCodeSymbology(QrErrorCorrectionLevel errorCorrectionLevel) + { + Guard.MustBeBetweenOrEqualTo((int)errorCorrectionLevel, (int)QrErrorCorrectionLevel.L, (int)QrErrorCorrectionLevel.Q, nameof(errorCorrectionLevel)); + this.ErrorCorrectionLevel = errorCorrectionLevel; + } + + /// + /// Initializes a new instance of the class with a fixed version. + /// + /// The error correction level, L, M or Q. + /// The version, 1 to 4 for M1 to M4. + /// The version does not have the error correction level. + public MicroQrCodeSymbology(QrErrorCorrectionLevel errorCorrectionLevel, int version) + : this(errorCorrectionLevel) + { + Guard.MustBeBetweenOrEqualTo(version, 1, 4, nameof(version)); + + // Table 13: M1 has error detection only, which counts as level L, and only M4 has level Q. + if ((version == 1 && errorCorrectionLevel != QrErrorCorrectionLevel.L) || (errorCorrectionLevel == QrErrorCorrectionLevel.Q && version != 4)) + { + throw new ArgumentException($"Micro QR Code version M{version} does not have error correction level {errorCorrectionLevel}.", nameof(version)); + } + + this.Version = version; + } + + /// + /// Gets the error correction level. + /// + public QrErrorCorrectionLevel ErrorCorrectionLevel { get; } + + /// + /// Gets the version, 1 to 4 for M1 to M4, or when the version follows from the data. + /// + public int? Version { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + // Section 8.1 of ISO/IEC 18004: "the width of a module shall be specified by the application", so the symbology gives no limit. + QrEncoder.BuildSymbol( + text, + false, + QrFormat.Micro, + this.ErrorCorrectionLevel, + this.Version ?? 0, + options.ResolveXDimension(0, DefaultMatrixXDimension, double.PositiveInfinity), + ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/QrCode/QrCodeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/QrCode/QrCodeSymbology.cs new file mode 100644 index 00000000..6cf8ba7e --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/QrCode/QrCodeSymbology.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The QR Code symbology, ISO/IEC 18004: a square matrix of 21 x 21 to 177 x 177 modules, versions 1 to 40, with finder patterns at three corners. It carries +/// the characters U+0000 to U+00FF in Numeric, Alphanumeric and Byte mode segments with Reed-Solomon error correction at one of four levels. +/// +public sealed class QrCodeSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class at error correction level M. The symbol takes the smallest version that holds the + /// data. + /// + public QrCodeSymbology() + : this(QrErrorCorrectionLevel.M) + { + } + + /// + /// Initializes a new instance of the class. The symbol takes the smallest version that holds the data. + /// + /// The error correction level. + public QrCodeSymbology(QrErrorCorrectionLevel errorCorrectionLevel) + { + Guard.MustBeBetweenOrEqualTo((int)errorCorrectionLevel, (int)QrErrorCorrectionLevel.L, (int)QrErrorCorrectionLevel.H, nameof(errorCorrectionLevel)); + this.ErrorCorrectionLevel = errorCorrectionLevel; + } + + /// + /// Initializes a new instance of the class with a fixed version. + /// + /// The error correction level. + /// The version, 1 to 40. + public QrCodeSymbology(QrErrorCorrectionLevel errorCorrectionLevel, int version) + : this(errorCorrectionLevel) + { + Guard.MustBeBetweenOrEqualTo(version, 1, 40, nameof(version)); + this.Version = version; + } + + /// + /// Gets the error correction level. + /// + public QrErrorCorrectionLevel ErrorCorrectionLevel { get; } + + /// + /// Gets the version, or when the version follows from the data. + /// + public int? Version { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + // Section 8.1 of ISO/IEC 18004: "the width of a module shall be specified by the application", so the symbology gives no limit. + QrEncoder.BuildSymbol( + text, + false, + QrFormat.Full, + this.ErrorCorrectionLevel, + this.Version ?? 0, + options.ResolveXDimension(0, DefaultMatrixXDimension, double.PositiveInfinity), + ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/QrCode/QrEncoder.cs b/src/ImageSharp.Drawing/Barcodes/QrCode/QrEncoder.cs new file mode 100644 index 00000000..20320e17 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/QrCode/QrEncoder.cs @@ -0,0 +1,2173 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Globalization; +using System.Numerics; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Builds QR Code and Micro QR Code symbols, ISO/IEC 18004, and Rectangular Micro QR Code symbols, ISO/IEC 23941. The encoder builds the mode segments of the +/// data, the symbol version, the Reed-Solomon error correction and the module placement. It also builds the data mask and the format and version information. +/// Rectangular Micro QR Code shares the data encodation, the error correction and the placement of QR Code. It has its own function patterns, indicator +/// lengths, format information and a single data mask. +/// +internal static class QrEncoder +{ + /// + /// The largest number of message values a symbol carries: 7089 digits in a version 40-L symbol, Table 7. + /// + public const int MaximumLength = 7089; + + /// + /// The message value of the FNC1 character, which separates element strings in a symbol in FNC1 first position mode. No byte has a negative value. + /// + private const int Fnc1 = -1; + + /// + /// The message value of a % of the data in a symbol in FNC1 first position mode. Clause 13.4 of ISO/IEC 18004 writes it as %% in Alphanumeric mode, + /// because a single % there is the FNC1 character, and Byte mode writes it as the one byte %. + /// + private const int DataPercent = -2; + + /// + /// The version group of Micro QR Code version M1. Groups 0 to 2 are QR Code versions 1 to 9, 10 to 26 and 27 to 40, whose character count indicators differ + /// in length (Table 3), groups 3 to 6 are versions M1 to M4, and groups 7 to 38 are Rectangular Micro QR Code versions R7x43 to R17x139, each with + /// indicators of its own length. + /// + private const int GroupM1 = 3; + + /// + /// The version group of Micro QR Code version M2. + /// + private const int GroupM2 = 4; + + /// + /// The version group of Rectangular Micro QR Code version R7x43, the first of 32. + /// + private const int GroupR7x43 = 7; + + /// + /// The data mask pattern reference of Table 10 of ISO/IEC 18004 that every Rectangular Micro QR Code symbol uses: (i div 2 + j div 3) mod 2 = 0. + /// + private const int RectangularMask = 4; + + /// + /// The mask that the encoder XORs with the format information of a Rectangular Micro QR Code symbol, beside the finder pattern. + /// + private const int RectangularFormatMaskLeft = 0x1FAB2; + + /// + /// The mask that the encoder XORs with the format information of a Rectangular Micro QR Code symbol, beside the finder sub-pattern. + /// + private const int RectangularFormatMaskRight = 0x20A7B; + + /// + /// A count no run reaches. A mode change that needs it never happens. + /// + private const short Never = 10000; + + /// + /// The digits that end the data and make it start in Numeric mode, the same in every version group. + /// + private const int FirstNumericToEnd = 1; + + /// + /// The distance stored where no character of a class follows, larger than any message. + /// + private const int NoneAhead = 9999; + + /// + /// The number of rows of per-position values in : four runs and three distances. + /// + private const int RunRows = 7; + + /// + /// The generator polynomial of the (15, 5) BCH code of the format information, Annex C: x^10 + x^8 + x^5 + x^4 + x^2 + x + 1. + /// + private const int FormatGenerator = 0x537; + + /// + /// The generator polynomial of the (18, 6) Golay code of the version information, Annex D: x^12 + x^11 + x^10 + x^9 + x^8 + x^5 + x^2 + 1. + /// + private const int VersionGenerator = 0x1F25; + + /// + /// The mask that the encoder XORs with the format information of a QR Code symbol, section 6.9.1. + /// + private const int FormatMask = 0b101010000010010; + + /// + /// The mask that the encoder XORs with the format information of a Micro QR Code symbol, section 6.9.2. + /// + private const int MicroFormatMask = 0b100010001000101; + + /// + /// The field of the error correction, section 6.5.2: GF(2^8) with the primitive polynomial x^8 + x^4 + x^3 + x^2 + 1, 285 decimal. + /// + private static readonly GaloisField Field = new(256, 285); + + /// + /// The symbol sizes, Micro QR Code versions M1 to M4, QR Code versions 1 to 40 and then Rectangular Micro QR Code versions R7x43 to R17x139. Each carries + /// its data modules (Table 1 of ISO/IEC 18004), its error correction codewords and blocks for levels L, M, Q and H (Table 9, -1 where the level does not + /// exist), and the first two interior alignment coordinates, after which the coordinates step evenly to the last. Those are the second and third + /// coordinates of Table E.1 for QR Code, and the columns of the interior alignment patterns for Rectangular Micro QR Code, which has levels M and H only. + /// + private static readonly SymbolSize[] Sizes = + [ + new(QrFormat.Micro, 1, 36, 0, 0, 2, -1, -1, -1, 1, 0, 0, 0), + new(QrFormat.Micro, 2, 80, 0, 0, 5, 6, -1, -1, 1, 1, 0, 0), + new(QrFormat.Micro, 3, 132, 0, 0, 6, 8, -1, -1, 1, 1, 0, 0), + new(QrFormat.Micro, 4, 192, 0, 0, 8, 10, 14, -1, 1, 1, 1, 0), + new(QrFormat.Full, 1, 208, 0, 0, 7, 10, 13, 17, 1, 1, 1, 1), + new(QrFormat.Full, 2, 359, 18, 0, 10, 16, 22, 28, 1, 1, 1, 1), + new(QrFormat.Full, 3, 567, 22, 0, 15, 26, 36, 44, 1, 1, 2, 2), + new(QrFormat.Full, 4, 807, 26, 0, 20, 36, 52, 64, 1, 2, 2, 4), + new(QrFormat.Full, 5, 1079, 30, 0, 26, 48, 72, 88, 1, 2, 4, 4), + new(QrFormat.Full, 6, 1383, 34, 0, 36, 64, 96, 112, 2, 4, 4, 4), + new(QrFormat.Full, 7, 1568, 22, 38, 40, 72, 108, 130, 2, 4, 6, 5), + new(QrFormat.Full, 8, 1936, 24, 42, 48, 88, 132, 156, 2, 4, 6, 6), + new(QrFormat.Full, 9, 2336, 26, 46, 60, 110, 160, 192, 2, 5, 8, 8), + new(QrFormat.Full, 10, 2768, 28, 50, 72, 130, 192, 224, 4, 5, 8, 8), + new(QrFormat.Full, 11, 3232, 30, 54, 80, 150, 224, 264, 4, 5, 8, 11), + new(QrFormat.Full, 12, 3728, 32, 58, 96, 176, 260, 308, 4, 8, 10, 11), + new(QrFormat.Full, 13, 4256, 34, 62, 104, 198, 288, 352, 4, 9, 12, 16), + new(QrFormat.Full, 14, 4651, 26, 46, 120, 216, 320, 384, 4, 9, 16, 16), + new(QrFormat.Full, 15, 5243, 26, 48, 132, 240, 360, 432, 6, 10, 12, 18), + new(QrFormat.Full, 16, 5867, 26, 50, 144, 280, 408, 480, 6, 10, 17, 16), + new(QrFormat.Full, 17, 6523, 30, 54, 168, 308, 448, 532, 6, 11, 16, 19), + new(QrFormat.Full, 18, 7211, 30, 56, 180, 338, 504, 588, 6, 13, 18, 21), + new(QrFormat.Full, 19, 7931, 30, 58, 196, 364, 546, 650, 7, 14, 21, 25), + new(QrFormat.Full, 20, 8683, 34, 62, 224, 416, 600, 700, 8, 16, 20, 25), + new(QrFormat.Full, 21, 9252, 28, 50, 224, 442, 644, 750, 8, 17, 23, 25), + new(QrFormat.Full, 22, 10068, 26, 50, 252, 476, 690, 816, 9, 17, 23, 34), + new(QrFormat.Full, 23, 10916, 30, 54, 270, 504, 750, 900, 9, 18, 25, 30), + new(QrFormat.Full, 24, 11796, 28, 54, 300, 560, 810, 960, 10, 20, 27, 32), + new(QrFormat.Full, 25, 12708, 32, 58, 312, 588, 870, 1050, 12, 21, 29, 35), + new(QrFormat.Full, 26, 13652, 30, 58, 336, 644, 952, 1110, 12, 23, 34, 37), + new(QrFormat.Full, 27, 14628, 34, 62, 360, 700, 1020, 1200, 12, 25, 34, 40), + new(QrFormat.Full, 28, 15371, 26, 50, 390, 728, 1050, 1260, 13, 26, 35, 42), + new(QrFormat.Full, 29, 16411, 30, 54, 420, 784, 1140, 1350, 14, 28, 38, 45), + new(QrFormat.Full, 30, 17483, 26, 52, 450, 812, 1200, 1440, 15, 29, 40, 48), + new(QrFormat.Full, 31, 18587, 30, 56, 480, 868, 1290, 1530, 16, 31, 43, 51), + new(QrFormat.Full, 32, 19723, 34, 60, 510, 924, 1350, 1620, 17, 33, 45, 54), + new(QrFormat.Full, 33, 20891, 30, 58, 540, 980, 1440, 1710, 18, 35, 48, 57), + new(QrFormat.Full, 34, 22091, 34, 62, 570, 1036, 1530, 1800, 19, 37, 51, 60), + new(QrFormat.Full, 35, 23008, 30, 54, 570, 1064, 1590, 1890, 19, 38, 53, 63), + new(QrFormat.Full, 36, 24272, 24, 50, 600, 1120, 1680, 1980, 20, 40, 56, 66), + new(QrFormat.Full, 37, 25568, 28, 54, 630, 1204, 1770, 2100, 21, 43, 59, 70), + new(QrFormat.Full, 38, 26896, 32, 58, 660, 1260, 1860, 2220, 22, 45, 62, 74), + new(QrFormat.Full, 39, 28256, 26, 54, 720, 1316, 1950, 2310, 24, 47, 65, 77), + new(QrFormat.Full, 40, 29648, 30, 58, 750, 1372, 2040, 2430, 25, 49, 68, 81), + new(1, 7, 43, 104, 21, 0, 7, 10, 1, 1), + new(2, 7, 59, 171, 19, 39, 9, 14, 1, 1), + new(3, 7, 77, 261, 25, 51, 12, 22, 1, 1), + new(4, 7, 99, 358, 23, 49, 16, 30, 1, 1), + new(5, 7, 139, 545, 27, 55, 24, 44, 1, 2), + new(6, 9, 43, 170, 21, 0, 9, 14, 1, 1), + new(7, 9, 59, 267, 19, 39, 12, 22, 1, 1), + new(8, 9, 77, 393, 25, 51, 18, 32, 1, 2), + new(9, 9, 99, 532, 23, 49, 24, 44, 1, 2), + new(10, 9, 139, 797, 27, 55, 36, 66, 2, 3), + new(11, 11, 27, 122, 0, 0, 8, 10, 1, 1), + new(12, 11, 43, 249, 21, 0, 12, 20, 1, 1), + new(13, 11, 59, 376, 19, 39, 16, 32, 1, 2), + new(14, 11, 77, 538, 25, 51, 24, 44, 1, 2), + new(15, 11, 99, 719, 23, 49, 32, 60, 2, 2), + new(16, 11, 139, 1062, 27, 55, 48, 90, 2, 3), + new(17, 13, 27, 172, 0, 0, 9, 14, 1, 1), + new(18, 13, 43, 329, 21, 0, 14, 28, 1, 1), + new(19, 13, 59, 486, 19, 39, 22, 40, 1, 2), + new(20, 13, 77, 684, 25, 51, 32, 56, 2, 2), + new(21, 13, 99, 907, 23, 49, 40, 78, 2, 3), + new(22, 13, 139, 1328, 27, 55, 60, 112, 3, 4), + new(23, 15, 43, 409, 21, 0, 18, 36, 1, 2), + new(24, 15, 59, 596, 19, 39, 26, 48, 1, 2), + new(25, 15, 77, 830, 25, 51, 36, 72, 2, 3), + new(26, 15, 99, 1095, 23, 49, 48, 88, 2, 4), + new(27, 15, 139, 1594, 27, 55, 72, 130, 3, 5), + new(28, 17, 43, 489, 21, 0, 22, 40, 1, 2), + new(29, 17, 59, 706, 19, 39, 32, 60, 2, 2), + new(30, 17, 77, 976, 25, 51, 44, 84, 2, 3), + new(31, 17, 99, 1283, 23, 49, 60, 104, 3, 4), + new(32, 17, 139, 1860, 27, 55, 80, 156, 4, 6), + ]; + + /// + /// The data encodation modes, sections 6.4.3 to 6.4.5. The value is the column of the mode in and the Micro QR Code mode indicator, + /// Table 2. + /// + private enum SegmentMode + { + /// + /// Numeric mode: three digits in 10 bits. + /// + Numeric, + + /// + /// Alphanumeric mode: two characters of Table 5 in 11 bits. + /// + Alphanumeric, + + /// + /// Byte mode: one byte in 8 bits. + /// + Byte, + } + + /// + /// The state of a module while the function patterns and the data fill the grid. + /// + private enum ModuleState : byte + { + /// + /// A light function module. + /// + Light, + + /// + /// A dark function module. + /// + Dark, + + /// + /// A module the codeword stream fills. + /// + Data, + } + + /// + /// Gets the length of the character count indicator of each version group for Numeric, Alphanumeric and Byte mode, Table 3, or 0 where the version does not + /// have the mode. The rows after M4 are the Rectangular Micro QR Code versions R7x43 to R17x139. + /// + private static ReadOnlySpan CountBits => + [ + 10, 9, 8, + 12, 11, 16, + 14, 13, 16, + 3, 0, 0, + 4, 3, 0, + 5, 4, 4, + 6, 5, 5, + 4, 3, 3, + 5, 5, 4, + 6, 5, 5, + 7, 6, 5, + 7, 6, 6, + 5, 5, 4, + 6, 5, 5, + 7, 6, 5, + 7, 6, 6, + 8, 7, 6, + 4, 4, 3, + 6, 5, 5, + 7, 6, 5, + 7, 6, 6, + 8, 7, 6, + 8, 7, 7, + 5, 5, 4, + 6, 6, 5, + 7, 6, 6, + 7, 7, 6, + 8, 7, 7, + 8, 8, 7, + 7, 6, 6, + 7, 7, 6, + 8, 7, 7, + 8, 7, 7, + 9, 8, 7, + 7, 6, 6, + 8, 7, 6, + 8, 7, 7, + 8, 8, 7, + 9, 8, 8, + ]; + + /// + /// Gets the length of the mode indicator of each version group, Table 2. Every Rectangular Micro QR Code version has a 3-bit mode indicator. + /// + private static ReadOnlySpan ModeBits => + [ + 4, 4, 4, 0, 1, 2, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + ]; + + /// + /// Gets the length of the terminator of each version group, Table 2. Every Rectangular Micro QR Code version has a 3-bit terminator. + /// + private static ReadOnlySpan TerminatorBits => + [ + 4, 4, 4, 3, 5, 7, 9, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + ]; + + // The run lengths at which the data analysis changes mode, for each version group. A mode change costs a mode indicator and a character count indicator, so + // a run that is short enough stays in the current mode. The counts are the break-even run lengths for the indicator lengths of each group. Annex J.2 of + // ISO/IEC 18004 gives the first three groups, QR Code versions 1 to 9, 10 to 26 and 27 to 40, of FirstNumericBeforeByte as [4,4,5], + // ByteToAlphanumericBeforeByte as [11,15,16], ByteToNumericBeforeByte as [6,8,9], ByteToNumericBeforeAlphanumeric as [6,7,8] and + // AlphanumericToNumericBeforeAlphanumeric as [13,15,17]. + + /// + /// Gets the digits that start the data in Numeric mode when a Byte mode character follows them. + /// + private static ReadOnlySpan FirstNumericBeforeByte => + [ + 4, 4, 5, Never, Never, 2, 3, + 2, 2, 3, 3, 3, 2, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + ]; + + /// + /// Gets the digits after which a Byte mode character makes the data start in Byte mode. + /// + private static ReadOnlySpan FirstByteAfterNumeric => + [ + 1, 1, 1, Never, Never, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + ]; + + /// + /// Gets the characters of Alphanumeric or Numeric mode that end the data and make it start in Alphanumeric mode. + /// + private static ReadOnlySpan FirstAlphanumericToEnd => + [ + 1, 1, 1, Never, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + ]; + + /// + /// Gets the characters only Alphanumeric mode holds that leave Byte mode when a Byte mode character follows them. + /// + private static ReadOnlySpan ByteToAlphanumericBeforeByte => + [ + 11, 15, 16, Never, Never, 6, 7, + 6, 7, 7, 8, 8, 7, 7, 8, 8, 8, 6, 7, 8, 8, 8, 9, 7, 8, 8, 8, 9, 9, 8, 8, 9, 9, 9, 8, 8, 9, 9, 10, + ]; + + /// + /// Gets the characters only Alphanumeric mode holds that leave Byte mode when a digit follows them. + /// + private static ReadOnlySpan ByteToAlphanumericBeforeNumeric => + [ + 12, 13, 15, Never, Never, 6, 8, + 6, 7, 8, 8, 8, 7, 8, 8, 8, 9, 6, 8, 8, 8, 9, 9, 7, 8, 8, 9, 9, 10, 8, 9, 9, 9, 10, 8, 9, 9, 10, 10, + ]; + + /// + /// Gets the characters only Alphanumeric mode holds that leave Byte mode when they end the data. + /// + private static ReadOnlySpan ByteToAlphanumericToEnd => + [ + 6, 7, 8, Never, Never, 3, 4, + 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 4, 4, 4, 4, 5, 5, 4, 4, 4, 5, 5, 5, 4, 5, 5, 5, 5, 4, 5, 5, 5, 5, + ]; + + /// + /// Gets the digits that leave Byte mode when a Byte mode character follows them. + /// + private static ReadOnlySpan ByteToNumericBeforeByte => + [ + 6, 8, 9, Never, Never, 3, 4, + 3, 4, 4, 5, 5, 4, 4, 5, 5, 5, 3, 4, 5, 5, 5, 5, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, + ]; + + /// + /// Gets the digits that leave Byte mode when a character only Alphanumeric mode holds follows them. + /// + private static ReadOnlySpan ByteToNumericBeforeAlphanumeric => + [ + 6, 7, 8, Never, Never, 3, 4, + 3, 4, 4, 5, 5, 4, 4, 5, 5, 5, 4, 4, 5, 5, 5, 5, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 6, + ]; + + /// + /// Gets the digits that leave Byte mode when they end the data. + /// + private static ReadOnlySpan ByteToNumericToEnd => + [ + 3, 4, 4, Never, Never, 2, 3, + 2, 2, 3, 3, 3, 2, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + ]; + + /// + /// Gets the digits that leave Alphanumeric mode when a character only Alphanumeric mode holds follows them. + /// + private static ReadOnlySpan AlphanumericToNumericBeforeAlphanumeric => + [ + 13, 15, 17, Never, 5, 7, 9, + 7, 8, 9, 9, 9, 8, 9, 9, 9, 11, 7, 9, 9, 9, 11, 11, 8, 9, 9, 10, 11, 11, 9, 10, 11, 11, 11, 9, 11, 11, 11, 11, + ]; + + /// + /// Gets the digits that leave Alphanumeric mode when a Byte mode character follows them. + /// + private static ReadOnlySpan AlphanumericToNumericBeforeByte => + [ + 13, 17, 18, Never, Never, 7, 9, + 7, 8, 9, 9, 9, 8, 9, 9, 9, 10, 7, 9, 9, 9, 10, 11, 8, 9, 9, 9, 11, 11, 9, 9, 11, 11, 11, 9, 10, 11, 11, 11, + ]; + + /// + /// Gets the digits that leave Alphanumeric mode when they end the data. + /// + private static ReadOnlySpan AlphanumericToNumericToEnd => + [ + 7, 8, 9, Never, 3, 4, 5, + 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 4, 5, 5, 5, 6, 6, 5, 5, 5, 5, 6, 6, 5, 5, 6, 6, 6, 5, 6, 6, 6, 6, + ]; + + /// + /// Gets the data mask pattern references of Table 10 a Micro QR Code symbol uses, in the order of its two bit references 00 to 11. + /// + private static ReadOnlySpan MicroMasks => [1, 4, 6, 7]; + + /// + /// Gets the symbol numbers of Table 13, for versions M1 to M4 and levels L, M and Q, or -1 where the version does not have the level. + /// + private static ReadOnlySpan MicroSymbolNumbers => + [ + 0, -1, -1, + 1, 2, -1, + 3, 4, -1, + 5, 6, 7, + ]; + + /// + /// Gets the error correction level indicators of Table 12, for levels L, M, Q and H. + /// + private static ReadOnlySpan LevelIndicators => [0b01, 0b00, 0b11, 0b10]; + + /// + /// Encodes the text into a symbol. + /// + /// The data, the characters U+0000 to U+00FF. + /// Whether the symbol is in FNC1 first position mode, section 6.4.8.1, for GS1 element strings. Each + /// is then an FNC1 character. Micro QR Code does not have the mode. + /// The symbol format. + /// The error correction level, M or H for Rectangular Micro QR Code. + /// The version, 1 to 40, 1 to 4 for M1 to M4 or the value, or 0 to choose it from the + /// data. + /// The X dimension in millimetres, . + /// The symbol to write. + /// The data is not valid or does not fit the symbol. + public static void BuildSymbol( + ReadOnlySpan text, + bool fnc1First, + QrFormat format, + QrErrorCorrectionLevel level, + int version, + double xDimension, + ref BarcodeSymbol symbol) + => BuildSymbol(text, fnc1First, format, level, version, xDimension, 0, ref symbol); + + /// + /// Encodes the text into a symbol whose specification fixes its width rather than its X dimension. The version follows from the data, so the X dimension is + /// the width divided by the module count of the version. + /// + /// The data, the characters U+0000 to U+00FF. + /// Whether the symbol is in FNC1 first position mode, section 6.4.8.1, for GS1 element strings. Each + /// is then an FNC1 character. Micro QR Code does not have the mode. + /// The symbol format. + /// The error correction level, M or H for Rectangular Micro QR Code. + /// The version, 1 to 40, 1 to 4 for M1 to M4 or the value, or 0 to choose it from the + /// data. + /// The X dimension in millimetres the caller sets, , or to take + /// divided by the module count. + /// The width in millimetres the specification fixes for the symbol, its quiet zones excluded. + /// The symbol to write. + /// The data is not valid or does not fit the symbol. + public static void BuildSymbol( + ReadOnlySpan text, + bool fnc1First, + QrFormat format, + QrErrorCorrectionLevel level, + int version, + double? xDimension, + double symbolWidth, + ref BarcodeSymbol symbol) + { + Guard.MustBeGreaterThan(text.Length, 0, nameof(text)); + Guard.MustBeLessThanOrEqualTo(text.Length, MaximumLength, nameof(text)); + + string name = format switch + { + QrFormat.Full => "QR Code", + QrFormat.Micro => "Micro QR Code", + _ => "Rectangular Micro QR Code", + }; + + // Clause 6.3.4: Byte mode carries ISO/IEC 8859-1, the default interpretation, so each character must be one byte value. The message reports the code + // point at the first invalid character, so a surrogate pair reports as one character. + int invalid = text.IndexOfAnyExceptInRange('\0', (char)byte.MaxValue); + if (invalid >= 0) + { + throw new ArgumentException( + $"{name} carries only the characters U+0000 to U+00FF; got {text.CodePointAt(invalid).ToDisplayString()}.", + nameof(text)); + } + + int length = text.Length; + + // One rent holds the message, the seven rows of per-position runs and the mode segments. Each run row has one entry past the message, which stops the + // runs. Every segment holds at least one character, so the message length bounds the segments, and each segment takes two ints. + int stride = length + 1; + PooledBuffer analysis = new(length + (RunRows * stride) + (2 * stride)); + try + { + Span message = analysis.Span[..length]; + Span segments = MemoryMarshal.Cast(analysis.Span.Slice(length + (RunRows * stride), 2 * stride)); + + int m = 0; + foreach (char c in text) + { + if (fnc1First && c == Gs1Data.Separator) + { + message[m++] = Fnc1; + continue; + } + + // Clause 13.4: a % of the data doubles in Alphanumeric mode only, so it stays one value until its segment's mode is known. + message[m++] = fnc1First && c == '%' ? DataPercent : c; + } + + CharacterRuns runs = new(message, analysis.Span.Slice(length, RunRows * stride)); + + // Step 1 of section 6.1: the smallest version that holds the data at the level. The data takes a different number of bits in each version group, + // because the indicators differ in length. The sizes of a group are adjacent in the table, so each group is segmented once, when the walk reaches + // it. A Rectangular Micro QR Code size is smallest by area, and of the two sizes with the same area, R7x99 and R9x77, the later one in the table is + // chosen, so every size that holds the data is compared. + int chosen = -1; + int segmentedGroup = -1; + int segmentCount = -1; + int bits = -1; + for (int i = 0; i < Sizes.Length; i++) + { + SymbolSize size = Sizes[i]; + if (size.Format != format || (version != 0 && size.Version != version) || size.ErrorCodewords(level) < 0) + { + continue; + } + + if (size.Group != segmentedGroup) + { + segmentedGroup = size.Group; + segmentCount = runs.BuildSegments(segmentedGroup, segments); + bits = segmentCount < 0 ? -1 : BitLength(message, segments[..segmentCount], segmentedGroup, fnc1First); + } + + if (bits >= 0 && bits <= size.DataBits(level)) + { + if (format != QrFormat.Rectangular) + { + chosen = i; + break; + } + + if (chosen < 0 || size.Rows * size.Columns <= Sizes[chosen].Rows * Sizes[chosen].Columns) + { + chosen = i; + } + } + } + + if (chosen < 0) + { + string versionName = format switch + { + QrFormat.Full => version.ToString(CultureInfo.InvariantCulture), + QrFormat.Micro => $"M{version}", + _ => ((RectangularMicroQrCodeVersion)version).ToString(), + }; + + throw new ArgumentException( + $"The data does not fit a {name} symbol{(version == 0 ? string.Empty : $" of version {versionName}")} at error correction level {level}.", + nameof(text)); + } + + // The walk past a chosen Rectangular Micro QR Code size can segment later groups, so the segments are rebuilt for the chosen group. + SymbolSize chosenSize = Sizes[chosen]; + if (chosenSize.Group != segmentedGroup) + { + segmentCount = runs.BuildSegments(chosenSize.Group, segments); + } + + double moduleWidth = xDimension ?? (symbolWidth / chosenSize.Columns); + BuildCodewords(chosenSize, level, message, segments[..segmentCount], fnc1First, moduleWidth, ref symbol); + } + finally + { + analysis.Dispose(); + } + } + + /// + /// Returns the number of bits the segments take in a version group, or -1 when a segment holds more characters than its character count indicator counts or + /// its mode does not exist in the group. + /// + /// The message values the segments cover. + /// The segments. + /// The version group. + /// Whether the FNC1 first position mode indicator precedes the segments. + /// The number of bits. + private static int BitLength(ReadOnlySpan message, ReadOnlySpan segments, int group, bool fnc1First) + { + int bits = fnc1First ? ModeBits[group] : 0; + int start = 0; + foreach (Segment segment in segments) + { + int count = EncodedCount(message.Slice(start, segment.Count), segment.Mode); + start += segment.Count; + int countBits = CountBits[(group * 3) + (int)segment.Mode]; + if (countBits == 0 || count >= 1 << countBits) + { + return -1; + } + + // Sections 6.4.3 to 6.4.5: three digits take 10 bits, two Alphanumeric characters 11 bits and a byte 8 bits, with 4 or 7 bits for the last one or + // two digits and 6 bits for a last Alphanumeric character. + int lastDigits = count % 3; + bits += ModeBits[group] + countBits + segment.Mode switch + { + SegmentMode.Numeric => (10 * (count / 3)) + (lastDigits == 2 ? 7 : lastDigits * 4), + SegmentMode.Alphanumeric => (11 * (count / 2)) + (6 * (count % 2)), + _ => 8 * count, + }; + } + + return bits; + } + + /// + /// Returns the number of characters a segment encodes, the value of its character count indicator. Clause 13.4 of ISO/IEC 18004 writes a % of the data + /// as %% in Alphanumeric mode, so each one counts twice there. + /// + /// The message values of the segment. + /// The mode of the segment. + /// The number of characters the segment encodes. + private static int EncodedCount(ReadOnlySpan data, SegmentMode mode) + => mode == SegmentMode.Alphanumeric ? data.Length + data.Count(DataPercent) : data.Length; + + /// + /// Builds the data codewords of a symbol, its error correction codewords and its module grid. + /// + /// The symbol size. + /// The error correction level. + /// The message values. + /// The segments of the message. + /// Whether the symbol is in FNC1 first position mode. + /// The X dimension in millimetres, . + /// The symbol to write. + private static void BuildCodewords( + SymbolSize size, + QrErrorCorrectionLevel level, + ReadOnlySpan message, + ReadOnlySpan segments, + bool fnc1First, + double xDimension, + ref BarcodeSymbol symbol) + { + int total = size.TotalCodewords; + int errorCount = size.ErrorCodewords(level); + int dataCount = total - errorCount; + int dataBits = size.DataBits(level); + int group = size.Group; + + // One rent holds the data codewords, the error correction codewords of each block and the final codeword stream, which has one more codeword for the + // remainder bits. Each codeword is an int, so the Reed-Solomon division reads it directly. + PooledBuffer buffer = new(total + total + 1); + try + { + buffer.Span.Clear(); + Span codewords = buffer.Span[..total]; + Span stream = buffer.Span.Slice(total, total + 1); + + // Section 6.4: the segments, each a mode indicator, a character count indicator and the data. The FNC1 first position indicator is 0101 in a QR + // Code symbol and 101 in a Rectangular Micro QR Code symbol. + int position = 0; + if (fnc1First) + { + position = WriteBits(codewords, position, 0b0101, ModeBits[group]); + } + + // Table 2: a QR Code mode indicator has one bit set for each mode, 0001, 0010 and 0100, a Micro QR Code one counts the modes from 0, and a + // Rectangular Micro QR Code one counts them from 1, 001, 010 and 011. + int start = 0; + foreach (Segment segment in segments) + { + int mode = (int)segment.Mode; + int count = segment.Count; + ReadOnlySpan data = message.Slice(start, count); + start += count; + int modeIndicator = group < GroupM1 ? 1 << mode : group >= GroupR7x43 ? mode + 1 : mode; + position = WriteBits(codewords, position, modeIndicator, ModeBits[group]); + position = WriteBits(codewords, position, EncodedCount(data, segment.Mode), CountBits[(group * 3) + mode]); + + if (segment.Mode == SegmentMode.Numeric) + { + for (int i = 0; i < count; i += 3) + { + int digits = Math.Min(3, count - i); + int value = 0; + for (int d = 0; d < digits; d++) + { + value = (value * 10) + (data[i + d] - '0'); + } + + position = WriteBits(codewords, position, value, digits switch { 3 => 10, 2 => 7, _ => 4 }); + } + } + else if (segment.Mode == SegmentMode.Alphanumeric) + { + // Section 6.4.8.1 writes the FNC1 character as %, and clause 13.4 a % of the data as %%. Each pair of characters takes 11 bits, so a + // character waits for the next one, and a last unpaired character takes 6 bits. + int pending = -1; + foreach (int value in data) + { + int alphanumeric = AlphanumericValue(value is Fnc1 or DataPercent ? '%' : value); + for (int repeat = value == DataPercent ? 2 : 1; repeat > 0; repeat--) + { + if (pending < 0) + { + pending = alphanumeric; + } + else + { + position = WriteBits(codewords, position, (pending * 45) + alphanumeric, 11); + pending = -1; + } + } + } + + if (pending >= 0) + { + position = WriteBits(codewords, position, pending, 6); + } + } + else + { + // Section 6.4.8.1: Byte mode writes the FNC1 character as GS, and a % of the data as the one byte %. + foreach (int value in data) + { + position = WriteBits(codewords, position, value switch { Fnc1 => Gs1Data.Separator, DataPercent => '%', _ => value }, 8); + } + } + } + + // Sections 6.4.9 and 6.4.10: the terminator, shortened where the data leaves less room, then zero bits to the codeword boundary and the pad + // codewords 11101100 and 00010001 in turn. The codewords start clear, so the zero bits need no writing. The last data codeword of an M1 or M3 + // symbol is 4 bits long and takes no pad codeword. + position += Math.Min(TerminatorBits[group], dataBits - position); + int lastPad = dataBits - (size.ShortLastCodeword ? 5 : 1); + int padIndex = 0; + for (int p = (position + 7) & ~7; p <= lastPad; p += 8) + { + WriteBits(codewords, p, padIndex++ % 2 == 0 ? 0b11101100 : 0b00010001, 8); + } + + int blocks = size.Blocks(level); + AppendErrorCorrection(blocks, dataCount, errorCount, codewords); + + // Section 6.6: the data codewords of the blocks in turn, then their error correction codewords in turn. The short last data codeword of an M1 or M3 + // symbol keeps its 4 high bits, so the stream stays continuous. + int shortData = dataCount / blocks; + int longBlocks = dataCount % blocks; + int shortBlocks = blocks - longBlocks; + int errorPerBlock = errorCount / blocks; + int bit = 0; + for (int i = 0; i <= shortData; i++) + { + for (int b = 0; b < blocks; b++) + { + int blockLength = b < shortBlocks ? shortData : shortData + 1; + if (i < blockLength) + { + int blockStart = b < shortBlocks ? b * shortData : (shortBlocks * shortData) + ((b - shortBlocks) * (shortData + 1)); + int codeword = codewords[blockStart + i]; + bit = size.ShortLastCodeword && blockStart + i == dataCount - 1 + ? WriteBits(stream, bit, codeword >> 4, 4) + : WriteBits(stream, bit, codeword, 8); + } + } + } + + for (int i = 0; i < errorPerBlock; i++) + { + for (int b = 0; b < blocks; b++) + { + bit = WriteBits(stream, bit, codewords[dataCount + (b * errorPerBlock) + i], 8); + } + } + + BuildMatrix(size, level, stream, xDimension, ref symbol); + } + finally + { + buffer.Dispose(); + } + } + + /// + /// Writes the error correction codewords of each block after the data codewords, section 6.5.2. Block b takes its error correction codewords at + /// + b x the codewords per block. + /// + /// The number of blocks. + /// The number of data codewords. + /// The number of error correction codewords. + /// The data codewords, followed by room for the error correction codewords. + private static void AppendErrorCorrection(int blocks, int dataCount, int errorCount, Span codewords) + { + // Every block has the same number of error correction codewords, at most 30, so one generator polynomial serves all blocks and it is inline. Its roots + // are alpha^0 to alpha^(k-1). + int perBlock = errorCount / blocks; + InlineArray32 generatorStorage = default; + Span generator = ((Span)generatorStorage)[..perBlock]; + GaloisField field = Field; + field.Generator(0, generator); + + // Table 9: the blocks with fewer data codewords come first, and each later block has one more. + int shortData = dataCount / blocks; + int shortBlocks = blocks - (dataCount % blocks); + int start = 0; + for (int b = 0; b < blocks; b++) + { + int count = b < shortBlocks ? shortData : shortData + 1; + field.Remainder(codewords[start..], 1, count, generator, codewords.Slice(dataCount + (b * perBlock), perBlock)); + start += count; + } + } + + /// + /// Places the function patterns and the codeword stream in the module grid, chooses the data mask and adds the format and version information, sections 6.7 + /// to 6.10. + /// + /// The symbol size. + /// The error correction level. + /// The final codeword stream, one 8-bit codeword in each int. + /// The X dimension in millimetres, . + /// The symbol to write. + private static void BuildMatrix(SymbolSize size, QrErrorCorrectionLevel level, ReadOnlySpan stream, double xDimension, ref BarcodeSymbol symbol) + { + int rows = size.Rows; + int columns = size.Columns; + int cells = rows * columns; + + // One rent holds the state of each module and the mask layers. Bit m of a mask layer is the module under data mask pattern m. + PooledBuffer buffer = new(2 * cells); + try + { + Span states = MemoryMarshal.Cast(buffer.Span[..cells]); + Span layers = buffer.Span.Slice(cells, cells); + states.Fill(ModuleState.Data); + PlaceFunctionPatterns(size, new ModuleGrid(states, rows, columns)); + + // A function module is the same under every mask, so all eight bits of its layer take its state. The walk below writes the layer of each data + // module. + for (int i = 0; i < cells; i++) + { + layers[i] = states[i] == ModuleState.Dark ? (byte)0xFF : (byte)0; + } + + // Section 6.7.3: two-module columns from the lower right corner, upwards and downwards in turn, the right module before the left one, skipping the + // vertical timing pattern of a QR Code symbol. The most significant bit of a codeword comes first. A Rectangular Micro QR Code symbol has a timing + // pattern down its right edge, so its first column pair starts one column in. + int bit = 0; + int x = size.Format == QrFormat.Rectangular ? columns - 2 : columns - 1; + int y = rows - 1; + int direction = -1; + bool right = true; + while (x >= 0) + { + int index = (y * columns) + x; + if (states[index] == ModuleState.Data) + { + bool dark = ((stream[bit >> 3] >> (7 - (bit & 7))) & 1) != 0; + layers[index] = (byte)(MaskLayer(y, x) ^ (dark ? 0xFF : 0)); + bit++; + } + + if (right) + { + right = false; + x--; + } + else + { + right = true; + x++; + y += direction; + if (y < 0 || y >= rows) + { + direction = -direction; + y += direction; + x -= 2; + if (size.Format == QrFormat.Full && x == 6) + { + x--; + } + } + } + } + + // Section 6.8.2.1: "the area to be evaluated is the complete symbol", so the format information of each mask, the dark module and the version + // information of a QR Code symbol are in the layers before the masks are scored. + if (size.Format == QrFormat.Full) + { + WriteFormatLayers(size, level, layers); + } + + int mask = size.Format switch + { + QrFormat.Full => ChooseMask(layers, columns), + QrFormat.Micro => ChooseMicroMask(layers, columns), + _ => RectangularMask, + }; + + // Section 5.3.7 of ISO/IEC 18004: the quiet zone is 4X around a QR Code symbol and 2X around a Micro QR Code symbol. It is 2X around a Rectangular + // Micro QR Code symbol too. + Span modules = symbol.BeginMatrix(columns, rows, size.Format == QrFormat.Full ? 4 : 2, xDimension); + for (int i = 0; i < cells; i++) + { + modules[i] = ((layers[i] >> mask) & 1) != 0; + } + + if (size.Format != QrFormat.Full) + { + WriteMicroFormat(size, level, mask, modules); + } + } + finally + { + buffer.Dispose(); + } + } + + /// + /// Writes the finder patterns with their separators, the timing patterns, the alignment patterns and the reserved format and version information modules + /// into the module states. + /// + /// The symbol size. + /// The module states. + private static void PlaceFunctionPatterns(SymbolSize size, ModuleGrid grid) + { + int rows = size.Rows; + int columns = size.Columns; + if (size.Format == QrFormat.Rectangular) + { + // The timing patterns alternate from a dark module along all four edges. The patterns placed after them replace the modules they cover. + for (int x = 0; x < columns; x++) + { + ModuleState value = Timing(x); + grid[0, x] = value; + grid[rows - 1, x] = value; + } + + for (int y = 0; y < rows; y++) + { + ModuleState value = Timing(y); + grid[y, 0] = value; + grid[y, columns - 1] = value; + } + + // The corner patterns at the upper right and lower left corners: three dark modules along the edge, one dark module inward on each side of a light + // one, and one more on the side edge. They go before the finder pattern, whose separator replaces the lower left one in a 9-row symbol. + grid[0, columns - 2] = ModuleState.Dark; + grid[0, columns - 3] = ModuleState.Dark; + grid[1, columns - 2] = ModuleState.Light; + grid[1, columns - 1] = ModuleState.Dark; + grid[rows - 1, 1] = ModuleState.Dark; + grid[rows - 1, 2] = ModuleState.Dark; + grid[rows - 2, 1] = ModuleState.Light; + grid[rows - 2, 0] = ModuleState.Dark; + grid[rows - 3, 0] = ModuleState.Dark; + + // The finder pattern and its separator at the upper left corner, the separator row only where the symbol has more than 7 rows, and the 5 x 5 finder + // sub-pattern at the lower right corner, which has the form of an alignment pattern. + grid.PlaceFinder(0, 0, 1, 1); + grid.PlaceAlignment(rows - 3, columns - 3); + + // The interior alignment patterns: a timing column that alternates from a dark module, with a 3 x 3 dark ring around a light centre at its top and + // its bottom. + if (size.Align2 > 0) + { + int step = size.Align3 > 0 ? size.Align3 - size.Align2 : columns; + for (int column = size.Align2; column < columns - 12; column += step) + { + for (int y = 3; y < rows - 3; y++) + { + grid[y, column] = Timing(y); + } + + for (int r = 0; r < 3; r++) + { + for (int c = -1; c <= 1; c++) + { + ModuleState value = r == 1 && c == 0 ? ModuleState.Light : ModuleState.Dark; + grid[r, column + c] = value; + grid[rows - 3 + r, column + c] = value; + } + } + } + } + + // The two copies of the format information are reserved dark. The symbol has one data mask, so their value does not take part in a mask evaluation. + for (int i = 0; i < 18; i++) + { + FormatPosition(size, i, 0, out int row, out int column); + grid[row, column] = ModuleState.Dark; + FormatPosition(size, i, 1, out row, out column); + grid[row, column] = ModuleState.Dark; + } + + return; + } + + int n = columns; + + // Section 5.3.4: the timing patterns alternate from a dark module, along row and column 6 of a QR Code symbol and along row and column 0 of a Micro QR + // Code symbol. + bool micro = size.Format == QrFormat.Micro; + int line = micro ? 0 : 6; + int last = micro ? n - 1 : n - 9; + for (int i = 8; i <= last; i++) + { + ModuleState value = Timing(i); + grid[line, i] = value; + grid[i, line] = value; + } + + // Sections 5.3.2 and 5.3.3: a 7 x 7 finder pattern of rings 1 : 1 : 3 : 1 : 1 and a light separator facing the symbol, at the upper left corner, and at + // the other two corners of a QR Code symbol. + grid.PlaceFinder(0, 0, 1, 1); + if (!micro) + { + grid.PlaceFinder(0, n - 1, 1, -1); + grid.PlaceFinder(n - 1, 0, -1, 1); + + // Annex E: an alignment pattern at every pair of the coordinates, except where a finder pattern is. + if (size.Align2 > 0) + { + int step = size.Align3 > 0 ? size.Align3 - size.Align2 : n; + for (int row = 6; row <= n - 7; row = row == 6 ? size.Align2 : row + step) + { + for (int column = 6; column <= n - 7; column = column == 6 ? size.Align2 : column + step) + { + if ((row == 6 && (column == 6 || column == n - 7)) || (row == n - 7 && column == 6)) + { + continue; + } + + grid.PlaceAlignment(row, column); + } + } + } + } + + // Sections 6.9 and 6.10: the format information modules, the dark module and the version information modules are reserved, so the data skips them. + // They take their values before the masks of a QR Code symbol are scored, and after the mask of a Micro QR Code symbol is chosen. + for (int i = 0; i < 15; i++) + { + FormatPosition(size, i, 0, out int row, out int column); + grid[row, column] = ModuleState.Dark; + if (!micro) + { + FormatPosition(size, i, 1, out row, out column); + grid[row, column] = ModuleState.Dark; + } + } + + if (!micro) + { + grid[n - 8, 8] = ModuleState.Light; + if (size.Version >= 7) + { + for (int i = 0; i < 18; i++) + { + int a = i / 3; + int b = i % 3; + grid[5 - a, n - 9 - b] = ModuleState.Light; + grid[n - 9 - b, 5 - a] = ModuleState.Light; + } + } + } + } + + /// + /// Returns the module of a timing pattern at a position along it. Section 5.3.4: the pattern alternates and starts dark at position 0. + /// + /// The row or column of the module. + /// The module state. + private static ModuleState Timing(int position) => (position & 1) == 0 ? ModuleState.Dark : ModuleState.Light; + + /// + /// Returns the module of a format information bit, counted from the most significant: bit 14 - of Figures 25 and 26 of ISO/IEC + /// 18004, or bit 17 - of a Rectangular Micro QR Code symbol. + /// + /// The symbol size. + /// The index of the bit from the most significant, 0 to 14, or 0 to 17. + /// The copy, 0 around the upper left finder pattern and 1 by the other two of a QR Code symbol, or 0 beside the finder pattern and 1 + /// beside the finder sub-pattern of a Rectangular Micro QR Code symbol. + /// The row of the module. + /// The column of the module. + private static void FormatPosition(SymbolSize size, int index, int copy, out int row, out int column) + { + int n = size.Columns; + if (size.Format == QrFormat.Rectangular) + { + // Bits 0 to 14 fill a block of 3 columns x 5 rows column by column, each column from the top, and bits 15 to 17 follow in a line of 3 modules. + // Beside the finder pattern the block is columns 8 to 10 of rows 1 to 5, with bits 15 to 17 down column 11 from row 1. Beside the finder + // sub-pattern the block is columns n - 8 to n - 6 of the 5 rows above the bottom row, with bits 15 to 17 along the top of those rows from column n + // - 5. + int bit = 17 - index; + if (copy == 0) + { + row = bit < 15 ? 1 + (bit % 5) : bit - 14; + column = bit < 15 ? 8 + (bit / 5) : 11; + return; + } + + row = size.Rows - 6 + (bit < 15 ? bit % 5 : 0); + column = bit < 15 ? n - 8 + (bit / 5) : n - 20 + bit; + return; + } + + if (size.Format == QrFormat.Micro) + { + // Bits 14 to 7 along row 8 from column 1, then bits 6 to 0 up column 8 from row 7. + row = index <= 7 ? 8 : 15 - index; + column = index <= 7 ? index + 1 : 8; + return; + } + + if (copy == 0) + { + // Bits 14 to 9 along row 8 from column 0, the timing column skipped, then up column 8 to row 0, the timing row skipped. + row = index <= 7 ? 8 : index <= 8 ? 7 : 14 - index; + column = index <= 5 ? index : index == 6 ? 7 : 8; + return; + } + + // Bits 14 to 8 up column 8 from the bottom, then bits 7 to 0 along row 8 to the right edge. + row = index <= 6 ? n - 1 - index : 8; + column = index <= 6 ? 8 : n - 15 + index; + } + + /// + /// Writes the dark module, the format information of every data mask pattern and the version information of a QR Code symbol into its mask layers. Bit m + /// of a layer is the module under data mask pattern m, and the format information carries the reference of its mask, so bit m of a format information + /// module takes the format information of mask m. The dark module and the version information are the same under every mask. + /// + /// The symbol size. + /// The error correction level. + /// The mask layers of the modules, row-major. + private static void WriteFormatLayers(SymbolSize size, QrErrorCorrectionLevel level, Span layers) + { + int n = size.Columns; + + // Section 6.9.1: the module in position (4V + 9, 8) "shall always be dark". + layers[((n - 8) * n) + 8] = 0xFF; + + for (int mask = 0; mask < 8; mask++) + { + // Section 6.9.1: the level indicator of Table 12 and the mask reference. + int format = BchCode((LevelIndicators[(int)level] << 3) | mask, 10, FormatGenerator) ^ FormatMask; + int clear = ~(1 << mask); + for (int i = 0; i < 15; i++) + { + int bit = ((format >> (14 - i)) & 1) << mask; + for (int copy = 0; copy < 2; copy++) + { + FormatPosition(size, i, copy, out int row, out int column); + int index = (row * n) + column; + layers[index] = (byte)((layers[index] & clear) | bit); + } + } + } + + // Section 6.10 and Figure 28: version 7 and above carry the version in two 6 x 3 blocks, bit 17 - i at row 5 - i / 3 and column n - 9 - i mod 3 of the + // upper right block, transposed in the lower left. + if (size.Version >= 7) + { + int version = BchCode(size.Version, 12, VersionGenerator); + for (int i = 0; i < 18; i++) + { + byte layer = ((version >> (17 - i)) & 1) != 0 ? (byte)0xFF : (byte)0; + int a = i / 3; + int b = i % 3; + layers[((5 - a) * n) + (n - 9 - b)] = layer; + layers[((n - 9 - b) * n) + (5 - a)] = layer; + } + } + } + + /// + /// Writes the format information of a Micro QR Code or Rectangular Micro QR Code symbol over the chosen grid. + /// + /// The symbol size. + /// The error correction level. + /// The data mask pattern reference, 0 to 7, of Table 10. + /// The modules, row-major. + private static void WriteMicroFormat(SymbolSize size, QrErrorCorrectionLevel level, int mask, Span modules) + { + int n = size.Columns; + if (size.Format == QrFormat.Rectangular) + { + // The level, 0 for M and 1 for H, and the version indicator, 0 to 31 for R7x43 to R17x139, in the (18, 6) Golay code of the QR Code version + // information, XORed with a different mask for each copy. + int data = ((level == QrErrorCorrectionLevel.H ? 1 : 0) << 5) | (size.Version - 1); + int code = BchCode(data, 12, VersionGenerator); + int left = code ^ RectangularFormatMaskLeft; + int right = code ^ RectangularFormatMaskRight; + for (int i = 0; i < 18; i++) + { + FormatPosition(size, i, 0, out int row, out int column); + modules[(row * n) + column] = ((left >> (17 - i)) & 1) != 0; + FormatPosition(size, i, 1, out row, out column); + modules[(row * n) + column] = ((right >> (17 - i)) & 1) != 0; + } + + return; + } + + // Section 6.9.2: the symbol number of Table 13 and the two-bit reference of the mask. + int symbolNumber = MicroSymbolNumbers[((size.Version - 1) * 3) + (int)level]; + int format = BchCode((symbolNumber << 2) | MicroMasks.IndexOf((byte)mask), 10, FormatGenerator) ^ MicroFormatMask; + for (int i = 0; i < 15; i++) + { + FormatPosition(size, i, 0, out int row, out int column); + modules[(row * n) + column] = ((format >> (14 - i)) & 1) != 0; + } + } + + /// + /// Chooses the data mask of a QR Code symbol, section 6.8.2.1: the pattern with the lowest penalty of Table 11, the first of equal ones. + /// + /// The mask layers of the modules. + /// The number of modules on a side. + /// The data mask pattern reference. + private static int ChooseMask(ReadOnlySpan layers, int n) + { + // A line of n modules has at most n + 1 runs, the light run first, and n is at most 177. + InlineArray256 runStorage = default; + MaskPenalty penalty = new(layers, n, runStorage); + + // N2 and N4 count the same modules under every mask, so one pass counts them for all eight masks. N1 and N3 read the runs of each mask. + InlineArray8 blockStorage = default; + InlineArray8 darkStorage = default; + Span blocks = blockStorage; + Span dark = darkStorage; + penalty.CountBlocksAndDark(blocks, dark); + + // N4: 10 points for each full 5% the proportion of dark modules is away from 50%. The steps are |100 dark / n^2 - 50| / 5, which integers give + // exactly as |20 dark - 10 n^2| / n^2. + int area = n * n; + int best = 0; + int bestScore = int.MaxValue; + for (int mask = 0; mask < 8; mask++) + { + int score = penalty.LinePenalties(mask) + (3 * blocks[mask]) + (10 * (Math.Abs((20 * dark[mask]) - (10 * area)) / area)); + if (score < bestScore) + { + bestScore = score; + best = mask; + } + } + + return best; + } + + /// + /// Returns the N1 and N3 penalties of one row or column, Table 11. + /// + /// The run lengths, light and dark in turn from a light run. + /// The penalty. + private static int LinePenalty(ReadOnlySpan runs) + { + // N1: 3 points for 5 modules of one colour in a line and 1 point for each further module. + int penalty = 0; + foreach (int run in runs) + { + if (run >= 5) + { + penalty += run - 2; + } + } + + // N3: 40 points for a dark run of 3k modules between light and dark runs of k modules on each side, the 1 : 1 : 3 : 1 : 1 ratio of the finder pattern, + // with a light run of at least 4 modules or the edge of the symbol beside it, the "light area 4 modules wide" of Table 11. Odd runs are dark. + for (int j = 3; j <= runs.Length - 3; j += 2) + { + int center = runs[j]; + if (center % 3 != 0) + { + continue; + } + + int unit = center / 3; + if (runs[j - 2] == unit && runs[j - 1] == unit && runs[j + 1] == unit && runs[j + 2] == unit + && (j == 3 || j + 4 >= runs.Length || runs[j - 3] >= 4 || runs[j + 3] >= 4)) + { + penalty += 40; + } + } + + return penalty; + } + + /// + /// Chooses the data mask of a Micro QR Code symbol, section 6.8.2.2: the pattern with the most dark modules on the right and bottom edges, SUM1 x 16 + SUM2 + /// with the smaller sum first, the first of equal ones. + /// + /// The mask layers of the modules. + /// The number of modules on a side. + /// The data mask pattern reference of Table 10. + private static int ChooseMicroMask(ReadOnlySpan layers, int n) + { + int best = MicroMasks[0]; + int bestScore = -1; + foreach (byte mask in MicroMasks) + { + // The edges exclude the first module of each, which is part of the timing pattern. + int right = 0; + int bottom = 0; + for (int i = 1; i < n; i++) + { + right += (layers[(i * n) + n - 1] >> mask) & 1; + bottom += (layers[((n - 1) * n) + i] >> mask) & 1; + } + + int score = right <= bottom ? (right * 16) + bottom : (bottom * 16) + right; + if (score > bestScore) + { + bestScore = score; + best = mask; + } + } + + return best; + } + + /// + /// Returns the mask layer of a data module: bit m is set where the condition of data mask pattern m, Table 10, is true, so the module is reversed. + /// + /// The row, i in Table 10. + /// The column, j in Table 10. + /// The mask layer. + private static int MaskLayer(int row, int column) + { + int product = row * column; + int layer = 0; + layer |= (row + column) % 2 == 0 ? 1 : 0; + layer |= row % 2 == 0 ? 1 << 1 : 0; + layer |= column % 3 == 0 ? 1 << 2 : 0; + layer |= (row + column) % 3 == 0 ? 1 << 3 : 0; + layer |= ((row / 2) + (column / 3)) % 2 == 0 ? 1 << 4 : 0; + layer |= (product % 2) + (product % 3) == 0 ? 1 << 5 : 0; + layer |= ((product % 2) + (product % 3)) % 2 == 0 ? 1 << 6 : 0; + layer |= (((row + column) % 2) + (product % 3)) % 2 == 0 ? 1 << 7 : 0; + return layer; + } + + /// + /// Returns the value of a character in Alphanumeric mode, Table 5, or -1 when the mode does not hold it. + /// + /// The character value. + /// The value, 0 to 44, or -1. + private static int AlphanumericValue(int value) => value switch + { + >= '0' and <= '9' => value - '0', + >= 'A' and <= 'Z' => value - 'A' + 10, + ' ' => 36, + '$' => 37, + '%' => 38, + '*' => 39, + '+' => 40, + '-' => 41, + '.' => 42, + '/' => 43, + ':' => 44, + _ => -1, + }; + + /// + /// Writes bits into codewords, the most significant first. The codewords start clear. + /// + /// The codewords, eight bits in each. + /// The position of the first bit. + /// The value. + /// The number of bits. + /// The position after the last bit. + private static int WriteBits(Span codewords, int position, int value, int count) + { + for (int i = count - 1; i >= 0; i--) + { + if (((value >> i) & 1) != 0) + { + codewords[position >> 3] |= 1 << (7 - (position & 7)); + } + + position++; + } + + return position; + } + + /// + /// Returns a value followed by the remainder of its division by a generator polynomial over GF(2), Annexes C and D. + /// + /// The data bits. + /// The number of check bits, the degree of the generator. + /// The generator polynomial, its highest term included. + /// The data bits and then the check bits. + private static int BchCode(int data, int checkBits, int generator) + { + int remainder = data << checkBits; + while (remainder != 0) + { + int degree = 31 - BitOperations.LeadingZeroCount((uint)remainder); + if (degree < checkBits) + { + break; + } + + remainder ^= generator << (degree - checkBits); + } + + return (data << checkBits) | remainder; + } + + /// + /// One QR Code, Micro QR Code or Rectangular Micro QR Code symbol size. + /// + private readonly struct SymbolSize + { + /// + /// The error correction codewords at level L, or -1 where the level does not exist. + /// + private readonly short errorL; + + /// + /// The error correction codewords at level M, or -1 where the level does not exist. + /// + private readonly short errorM; + + /// + /// The error correction codewords at level Q, or -1 where the level does not exist. + /// + private readonly short errorQ; + + /// + /// The error correction codewords at level H, or -1 where the level does not exist. + /// + private readonly short errorH; + + /// + /// The error correction blocks at level L. + /// + private readonly byte blocksL; + + /// + /// The error correction blocks at level M. + /// + private readonly byte blocksM; + + /// + /// The error correction blocks at level Q. + /// + private readonly byte blocksQ; + + /// + /// The error correction blocks at level H. + /// + private readonly byte blocksH; + + /// + /// Initializes a new instance of the struct for a square size. Section 5.3.1: a QR Code symbol is 21 modules on a side for + /// version 1 in steps of 4, and a Micro QR Code symbol 11 for M1 in steps of 2. + /// + /// The format, or . + /// The version, 1 to 40, or 1 to 4 for M1 to M4. + /// The modules that hold codewords, Table 1. + /// The second alignment pattern coordinate, or 0 for none. + /// The third alignment pattern coordinate, or 0 for none. + /// The error correction codewords at level L. + /// The error correction codewords at level M, or -1. + /// The error correction codewords at level Q, or -1. + /// The error correction codewords at level H, or -1. + /// The error correction blocks at level L. + /// The error correction blocks at level M, or 0. + /// The error correction blocks at level Q, or 0. + /// The error correction blocks at level H, or 0. + public SymbolSize( + QrFormat format, + int version, + int dataModules, + int align2, + int align3, + int errorL, + int errorM, + int errorQ, + int errorH, + int blocksL, + int blocksM, + int blocksQ, + int blocksH) + { + int side = format == QrFormat.Micro ? 9 + (2 * version) : 17 + (4 * version); + this.Format = format; + this.Version = version; + this.Rows = side; + this.Columns = side; + this.DataModules = dataModules; + this.Align2 = align2; + this.Align3 = align3; + this.errorL = (short)errorL; + this.errorM = (short)errorM; + this.errorQ = (short)errorQ; + this.errorH = (short)errorH; + this.blocksL = (byte)blocksL; + this.blocksM = (byte)blocksM; + this.blocksQ = (byte)blocksQ; + this.blocksH = (byte)blocksH; + } + + /// + /// Initializes a new instance of the struct for a Rectangular Micro QR Code size, which has error correction levels M and H + /// only. + /// + /// The value, 1 to 32. + /// The number of module rows. + /// The number of module columns. + /// The modules that hold codewords. + /// The column of the first interior alignment pattern, or 0 for none. + /// The column of the second interior alignment pattern, or 0 for none. + /// The error correction codewords at level M. + /// The error correction codewords at level H. + /// The error correction blocks at level M. + /// The error correction blocks at level H. + public SymbolSize(int version, int rows, int columns, int dataModules, int align2, int align3, int errorM, int errorH, int blocksM, int blocksH) + { + this.Format = QrFormat.Rectangular; + this.Version = version; + this.Rows = rows; + this.Columns = columns; + this.DataModules = dataModules; + this.Align2 = align2; + this.Align3 = align3; + this.errorL = -1; + this.errorM = (short)errorM; + this.errorQ = -1; + this.errorH = (short)errorH; + this.blocksM = (byte)blocksM; + this.blocksH = (byte)blocksH; + } + + /// + /// Gets the format. + /// + public QrFormat Format { get; } + + /// + /// Gets the version, 1 to 40, 1 to 4 for M1 to M4, or the value. + /// + public int Version { get; } + + /// + /// Gets the number of module rows. + /// + public int Rows { get; } + + /// + /// Gets the number of module columns. + /// + public int Columns { get; } + + /// + /// Gets the modules that hold codewords, Table 1. + /// + public int DataModules { get; } + + /// + /// Gets the second alignment pattern coordinate of Table E.1, or the column of the first interior alignment pattern of a Rectangular Micro QR Code + /// size, or 0 for none. + /// + public int Align2 { get; } + + /// + /// Gets the third alignment pattern coordinate of Table E.1, or the column of the second interior alignment pattern of a Rectangular Micro QR Code + /// size, or 0 when there is none. + /// + public int Align3 { get; } + + /// + /// Gets the version group, whose character count indicators share their lengths. + /// + public int Group => this.Format switch + { + QrFormat.Full => this.Version <= 9 ? 0 : this.Version <= 26 ? 1 : 2, + QrFormat.Micro => GroupM1 - 1 + this.Version, + _ => GroupR7x43 - 1 + this.Version, + }; + + /// + /// Gets a value indicating whether the last data codeword is 4 bits long, in M1 and M3 symbols. + /// + public bool ShortLastCodeword => this.Format == QrFormat.Micro && (this.Version & 1) == 1; + + /// + /// Gets the number of codewords, the last data codeword of M1 and M3 counted as one. + /// + public int TotalCodewords => (this.DataModules / 8) + (this.ShortLastCodeword ? 1 : 0); + + /// + /// Returns the number of error correction codewords at a level, or -1 where the level does not exist. + /// + /// The level. + /// The number of error correction codewords. + public int ErrorCodewords(QrErrorCorrectionLevel level) => level switch + { + QrErrorCorrectionLevel.L => this.errorL, + QrErrorCorrectionLevel.M => this.errorM, + QrErrorCorrectionLevel.Q => this.errorQ, + _ => this.errorH, + }; + + /// + /// Returns the number of error correction blocks at a level. + /// + /// The level. + /// The number of blocks. + public int Blocks(QrErrorCorrectionLevel level) => level switch + { + QrErrorCorrectionLevel.L => this.blocksL, + QrErrorCorrectionLevel.M => this.blocksM, + QrErrorCorrectionLevel.Q => this.blocksQ, + _ => this.blocksH, + }; + + /// + /// Returns the number of data bits at a level, Table 7. + /// + /// The level. + /// The number of data bits. + public int DataBits(QrErrorCorrectionLevel level) => ((this.TotalCodewords - this.ErrorCodewords(level)) * 8) - (this.ShortLastCodeword ? 4 : 0); + } + + /// + /// One mode segment of the message: its mode and the number of characters it holds. + /// + private struct Segment + { + /// + /// Initializes a new instance of the struct. + /// + /// The mode. + /// The number of characters. + public Segment(SegmentMode mode, int count) + { + this.Mode = mode; + this.Count = count; + } + + /// + /// Gets the mode. + /// + public SegmentMode Mode { get; } + + /// + /// Gets or sets the number of characters. Each character the segmentation keeps in the mode in force adds one. + /// + public int Count { get; set; } + } + + /// + /// The per-position runs of a message, from which divides it into mode segments. A character is one of + /// three types. The first type is a digit. The second type is a character that only Alphanumeric mode holds besides the digits (Table 5), or the FNC1 + /// character, which Alphanumeric mode writes as %. The third type is a character that only Byte mode holds. Each row has one entry past the message, which + /// stops the runs. + /// + private readonly ref struct CharacterRuns + { + /// + /// The digits that start at each position. + /// + private readonly ReadOnlySpan numeric; + + /// + /// The characters only Alphanumeric mode holds that start at each position. + /// + private readonly ReadOnlySpan alphanumeric; + + /// + /// The characters Numeric or Alphanumeric mode holds that start at each position. + /// + private readonly ReadOnlySpan either; + + /// + /// The characters only Byte mode holds that start at each position. + /// + private readonly ReadOnlySpan bytes; + + /// + /// The distance from each position to the next digit. + /// + private readonly ReadOnlySpan nextNumeric; + + /// + /// The distance from each position to the next character only Alphanumeric mode holds. + /// + private readonly ReadOnlySpan nextAlphanumeric; + + /// + /// The distance from each position to the next character only Byte mode holds. + /// + private readonly ReadOnlySpan nextByte; + + /// + /// The length of the message. + /// + private readonly int length; + + /// + /// Initializes a new instance of the struct and writes the runs of the message. + /// + /// The message values. + /// The buffer of the rows, each one longer than the message. + public CharacterRuns(ReadOnlySpan message, Span buffer) + { + int length = message.Length; + int stride = length + 1; + Span numeric = buffer.Slice(0, stride); + Span alphanumeric = buffer.Slice(stride, stride); + Span either = buffer.Slice(2 * stride, stride); + Span bytes = buffer.Slice(3 * stride, stride); + Span nextNumeric = buffer.Slice(4 * stride, stride); + Span nextAlphanumeric = buffer.Slice(5 * stride, stride); + Span nextByte = buffer.Slice(6 * stride, stride); + + // Past the message no run continues and no character of any class follows. + numeric[length] = 0; + alphanumeric[length] = 0; + either[length] = 0; + bytes[length] = 0; + nextNumeric[length] = NoneAhead; + nextAlphanumeric[length] = NoneAhead; + nextByte[length] = NoneAhead; + + // One pass from the end: each run and each distance extends the one at the next position. + for (int i = length - 1; i >= 0; i--) + { + int value = message[i]; + bool digit = value is >= '0' and <= '9'; + + // A % of the data counts as one Alphanumeric character here, although Alphanumeric mode writes it as %%. The run lengths only choose the + // segments, and BitLength counts each segment exactly. + bool alpha = value is Fnc1 or DataPercent || AlphanumericValue(value) >= 10; + + numeric[i] = digit ? numeric[i + 1] + 1 : 0; + nextNumeric[i] = digit ? 0 : nextNumeric[i + 1] + 1; + alphanumeric[i] = alpha ? alphanumeric[i + 1] + 1 : 0; + nextAlphanumeric[i] = alpha ? 0 : nextAlphanumeric[i + 1] + 1; + either[i] = digit || alpha ? either[i + 1] + 1 : 0; + bytes[i] = digit || alpha ? 0 : bytes[i + 1] + 1; + nextByte[i] = digit || alpha ? nextByte[i + 1] + 1 : 0; + } + + this.numeric = numeric; + this.alphanumeric = alphanumeric; + this.either = either; + this.bytes = bytes; + this.nextNumeric = nextNumeric; + this.nextAlphanumeric = nextAlphanumeric; + this.nextByte = nextByte; + this.length = length; + } + + /// + /// Divides the message into mode segments for a version group. At each position a run of digits or Alphanumeric characters that is long enough to pay + /// for its indicators starts a segment of its own; a shorter run stays in the current mode. + /// + /// The version group. + /// The buffer that receives the segments, at least as long as the message. + /// The number of segments, or -1 when the version group cannot hold a character of the message. + public int BuildSegments(int group, Span segments) + { + int count = 0; + SegmentMode mode = SegmentMode.Numeric; + int i = 0; + while (i < this.length) + { + int numericCount = this.numeric[i]; + int alphanumericCount = this.alphanumeric[i]; + int eitherCount = this.either[i]; + int byteCount = this.bytes[i]; + + // Sections 6.3.3 and 6.3.4: M1 has only Numeric mode and M2 has no Byte mode. + if ((group == GroupM1 && (alphanumericCount > 0 || byteCount > 0)) || (group == GroupM2 && byteCount > 0)) + { + return -1; + } + + SegmentMode next; + if (count == 0) + { + // The first segment, before any mode is in force. + if (Before(i, numericCount, FirstNumericBeforeByte[group], this.nextByte)) + { + next = SegmentMode.Numeric; + } + else if (Before(i, numericCount, FirstByteAfterNumeric[group], this.nextByte)) + { + next = SegmentMode.Byte; + } + else if (Before(i, numericCount, AlphanumericToNumericToEnd[group], this.nextAlphanumeric)) + { + next = SegmentMode.Numeric; + } + else if (this.Ends(i, numericCount, FirstNumericToEnd)) + { + next = SegmentMode.Numeric; + } + else if (Before(i, eitherCount, ByteToAlphanumericToEnd[group], this.nextByte)) + { + next = SegmentMode.Alphanumeric; + } + else if (this.Ends(i, eitherCount, FirstAlphanumericToEnd[group])) + { + next = SegmentMode.Alphanumeric; + } + else + { + next = SegmentMode.Byte; + } + } + else if (mode == SegmentMode.Byte) + { + if (Before(i, alphanumericCount, ByteToAlphanumericBeforeByte[group], this.nextByte) + || Before(i, alphanumericCount, ByteToAlphanumericBeforeNumeric[group], this.nextNumeric) + || this.Ends(i, alphanumericCount, ByteToAlphanumericToEnd[group])) + { + next = SegmentMode.Alphanumeric; + } + else if (Before(i, numericCount, ByteToNumericBeforeByte[group], this.nextByte) + || Before(i, numericCount, ByteToNumericBeforeAlphanumeric[group], this.nextAlphanumeric) + || this.Ends(i, numericCount, ByteToNumericToEnd[group])) + { + next = SegmentMode.Numeric; + } + else if (this.Ends(i, eitherCount, ByteToAlphanumericToEnd[group]) + && eitherCount <= ByteToAlphanumericBeforeNumeric[group] + && this.NextDigitsShort(i, ByteToNumericBeforeAlphanumeric[group])) + { + // A short run of Alphanumeric and digit characters that ends the data leaves Byte mode for Alphanumeric mode, unless a run of digits + // within it is long enough for Numeric mode. + next = SegmentMode.Alphanumeric; + } + else + { + next = SegmentMode.Byte; + } + } + else if (mode == SegmentMode.Alphanumeric) + { + if (byteCount > 0) + { + next = SegmentMode.Byte; + } + else if (Before(i, numericCount, AlphanumericToNumericBeforeAlphanumeric[group], this.nextAlphanumeric) + || Before(i, numericCount, AlphanumericToNumericBeforeByte[group], this.nextByte) + || this.Ends(i, numericCount, AlphanumericToNumericToEnd[group])) + { + next = SegmentMode.Numeric; + } + else + { + next = SegmentMode.Alphanumeric; + } + } + else + { + next = byteCount > 0 ? SegmentMode.Byte : alphanumericCount > 0 ? SegmentMode.Alphanumeric : SegmentMode.Numeric; + } + + if (count > 0 && next == mode) + { + // The character joins the segment in force. + segments[count - 1].Count++; + i++; + } + else + { + // A new segment takes the run of its own class that starts here. + mode = next; + int take = mode switch + { + SegmentMode.Numeric => numericCount, + SegmentMode.Alphanumeric => alphanumericCount, + _ => byteCount, + }; + + segments[count++] = new Segment(mode, take); + i += take; + } + } + + return count; + } + + /// + /// Returns whether a run starting at a position is at least a given length and a character of a class follows it. + /// + /// The position the run starts at. + /// The length of the run. + /// The length the run must reach. + /// The distances to the next character of the class that must follow. + /// when the run is long enough and the class follows it. + private static bool Before(int position, int run, int threshold, ReadOnlySpan following) => run >= threshold && following[position + run] == 0; + + /// + /// Returns whether a run starting at a position is at least a given length and ends the message. + /// + /// The position the run starts at. + /// The length of the run. + /// The length the run must reach. + /// when the run is long enough and ends the message. + private bool Ends(int position, int run, int threshold) => run >= threshold && position + run == this.length; + + /// + /// Returns whether the next run of digits from a position is shorter than a length, or no digit follows. + /// + /// The position. + /// The length. + /// when the next run of digits is shorter or absent. + private bool NextDigitsShort(int position, int threshold) + { + int distance = this.nextNumeric[position]; + return distance >= this.length || this.numeric[position + distance] < threshold; + } + } + + /// + /// The module states of a symbol, row-major, with the writers of the finder and alignment patterns. + /// + private readonly ref struct ModuleGrid + { + /// + /// The module states, row-major. + /// + private readonly Span states; + + /// + /// The number of module rows. + /// + private readonly int rows; + + /// + /// The number of module columns. + /// + private readonly int columns; + + /// + /// Initializes a new instance of the struct. + /// + /// The module states, row-major, x long. + /// The number of module rows. + /// The number of module columns. + public ModuleGrid(Span states, int rows, int columns) + { + this.states = states; + this.rows = rows; + this.columns = columns; + } + + /// + /// Gets the state of a module. + /// + /// The row. + /// The column. + /// A reference to the state. + public ref ModuleState this[int row, int column] => ref this.states[(row * this.columns) + column]; + + /// + /// Writes a finder pattern and its separator, the 8 x 8 modules from a corner, without the rows the symbol does not have. + /// + /// The row of the corner. + /// The column of the corner. + /// The direction from the corner along a column, 1 or -1. + /// The direction from the corner along a row, 1 or -1. + public void PlaceFinder(int cornerRow, int cornerColumn, int rowStep, int columnStep) + { + // A 7-row Rectangular Micro QR Code symbol has no row for the separator below the finder pattern. + int rowCount = Math.Min(8, this.rows); + for (int r = 0; r < rowCount; r++) + { + for (int c = 0; c < 8; c++) + { + // Row and column 7 are the separator. The outer ring and the 3 x 3 centre are dark. + bool dark = r < 7 && c < 7 && (r == 0 || r == 6 || c == 0 || c == 6 || (r is >= 2 and <= 4 && c is >= 2 and <= 4)); + this[cornerRow + (r * rowStep), cornerColumn + (c * columnStep)] = dark ? ModuleState.Dark : ModuleState.Light; + } + } + } + + /// + /// Writes an alignment pattern, section 5.3.5: dark 5 x 5, light 3 x 3 and a dark centre module. + /// + /// The row of the centre module. + /// The column of the centre module. + public void PlaceAlignment(int row, int column) + { + for (int r = -2; r <= 2; r++) + { + for (int c = -2; c <= 2; c++) + { + bool dark = r is -2 or 2 || c is -2 or 2 || (r == 0 && c == 0); + this[row + r, column + c] = dark ? ModuleState.Dark : ModuleState.Light; + } + } + } + } + + /// + /// Scores the data mask patterns of a QR Code symbol, Table 11, over the mask layers of its modules. + /// + private readonly ref struct MaskPenalty + { + /// + /// The mask layers of the modules, row-major. Bit m of a layer is the module under data mask pattern m. + /// + private readonly ReadOnlySpan layers; + + /// + /// The number of modules on a side. + /// + private readonly int n; + + /// + /// The run lengths of the line being scored, at least n + 1 long. + /// + private readonly Span runs; + + /// + /// Initializes a new instance of the struct. + /// + /// The mask layers of the modules. + /// The number of modules on a side. + /// A buffer of at least n + 1 run lengths. + public MaskPenalty(ReadOnlySpan layers, int n, Span runs) + { + this.layers = layers; + this.n = n; + this.runs = runs; + } + + /// + /// Returns the N1 and N3 penalties of a data mask pattern over every row and every column. + /// + /// The data mask pattern reference. + /// The penalty. + public int LinePenalties(int mask) + { + int n = this.n; + int lines = 0; + for (int i = 0; i < n; i++) + { + lines += LinePenalty(this.runs[..this.RunLengths(i, n, mask)]); + lines += LinePenalty(this.runs[..this.RunLengths(i * n, 1, mask)]); + } + + return lines; + } + + /// + /// Counts, for every data mask pattern, the 2 x 2 blocks of one colour of N2 and the dark modules of N4, in one pass over the layers. + /// + /// The eight block counts to write, one for each pattern. A larger block of one colour counts each 2 x 2 block it holds. + /// The eight dark module counts to write, one for each pattern. + public void CountBlocksAndDark(Span blocks, Span dark) + { + ReadOnlySpan layers = this.layers; + int n = this.n; + + // A block is one colour under a mask where the mask bit of the exclusive or of its upper left layer with each of its other three layers is clear, + // so the complement of that or holds the one colour bit of every mask. A row of blocks is at most 176 bytes. + InlineArray256 sameStorage = default; + Span same = ((Span)sameStorage)[..(n - 1)]; + blocks.Clear(); + for (int r = 0; r < n - 1; r++) + { + ReadOnlySpan row = layers.Slice(r * n, n); + ReadOnlySpan below = layers.Slice((r + 1) * n, n); + for (int c = 0; c < same.Length; c++) + { + int corner = row[c]; + same[c] = (byte)~((corner ^ row[c + 1]) | (corner ^ below[c]) | (corner ^ below[c + 1])); + } + + CountBits(same, blocks); + } + + dark.Clear(); + CountBits(layers, dark); + } + + /// + /// Adds, for each bit position m, the number of bytes of a span whose bit m is set. Bit m of every byte is the module under data mask pattern m. Eight + /// bytes read as one 64-bit word, and a population count of the word under the bit m of each byte counts the eight bytes at once. The bytes after the + /// last whole word count one by one, with the same result. + /// + /// The bytes. + /// The eight counts to add to. + private static void CountBits(ReadOnlySpan bytes, Span counts) + { + const ulong lowBits = 0x0101010101010101UL; + int i = 0; + for (; i + sizeof(ulong) <= bytes.Length; i += sizeof(ulong)) + { + ulong word = MemoryMarshal.Read(bytes[i..]); + for (int m = 0; m < 8; m++) + { + counts[m] += BitOperations.PopCount(word & (lowBits << m)); + } + } + + foreach (byte b in bytes[i..]) + { + for (int m = 0; m < 8; m++) + { + counts[m] += (b >> m) & 1; + } + } + } + + /// + /// Writes the run lengths of one row or column into the run buffer, light and dark in turn from a light run, which is 0 when the line starts dark. + /// + /// The index of the first module. + /// The distance between two modules of the line. + /// The data mask pattern reference. + /// The number of runs. + private int RunLengths(int start, int step, int mask) + { + Span runs = this.runs; + int count = 0; + int color = 0; + runs[0] = 0; + for (int i = 0; i < this.n; i++) + { + int value = (this.layers[start + (i * step)] >> mask) & 1; + if (value == color) + { + runs[count]++; + } + else + { + color = value; + runs[++count] = 1; + } + } + + return count + 1; + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/QrCode/QrErrorCorrectionLevel.cs b/src/ImageSharp.Drawing/Barcodes/QrCode/QrErrorCorrectionLevel.cs new file mode 100644 index 00000000..cd034cf9 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/QrCode/QrErrorCorrectionLevel.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The error correction levels of QR Code, Table 8 of ISO/IEC 18004. A higher level recovers a larger part of a damaged symbol and holds less data in a symbol +/// of the same size. +/// +public enum QrErrorCorrectionLevel +{ + /// + /// Level L, which recovers about 7% of the symbol codewords. + /// + L, + + /// + /// Level M, which recovers about 15% of the symbol codewords. + /// + M, + + /// + /// Level Q, which recovers about 25% of the symbol codewords. + /// + Q, + + /// + /// Level H, which recovers about 30% of the symbol codewords. Micro QR Code symbols do not have it. + /// + H, +} diff --git a/src/ImageSharp.Drawing/Barcodes/QrCode/QrFormat.cs b/src/ImageSharp.Drawing/Barcodes/QrCode/QrFormat.cs new file mode 100644 index 00000000..60c5e13d --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/QrCode/QrFormat.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The symbol formats builds. +/// +internal enum QrFormat +{ + /// + /// QR Code, ISO/IEC 18004: square, versions 1 to 40. + /// + Full, + + /// + /// Micro QR Code, ISO/IEC 18004: square, versions M1 to M4. + /// + Micro, + + /// + /// Rectangular Micro QR Code, ISO/IEC 23941: rectangular, versions R7x43 to R17x139. + /// + Rectangular, +} diff --git a/src/ImageSharp.Drawing/Barcodes/QrCode/RectangularMicroQrCodeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/QrCode/RectangularMicroQrCodeSymbology.cs new file mode 100644 index 00000000..301749fe --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/QrCode/RectangularMicroQrCodeSymbology.cs @@ -0,0 +1,77 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Rectangular Micro QR Code symbology (rMQR), ISO/IEC 23941: a rectangular matrix of 7 to 17 module rows and 27 to 139 module columns, versions R7x43 to +/// R17x139, with a finder pattern at the left end and a finder sub-pattern at the right end. It carries the characters U+0000 to U+00FF in Numeric, +/// Alphanumeric and Byte mode segments with Reed-Solomon error correction at level M or H. +/// +public sealed class RectangularMicroQrCodeSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class at error correction level M. The symbol takes the version of the + /// smallest area that holds the data. + /// + public RectangularMicroQrCodeSymbology() + : this(QrErrorCorrectionLevel.M) + { + } + + /// + /// Initializes a new instance of the class. The symbol takes the version of the smallest area that holds the + /// data at the level. + /// + /// The error correction level, M or H. + /// The error correction level is L or Q. + public RectangularMicroQrCodeSymbology(QrErrorCorrectionLevel errorCorrectionLevel) + { + Guard.MustBeBetweenOrEqualTo((int)errorCorrectionLevel, (int)QrErrorCorrectionLevel.L, (int)QrErrorCorrectionLevel.H, nameof(errorCorrectionLevel)); + if (errorCorrectionLevel is not (QrErrorCorrectionLevel.M or QrErrorCorrectionLevel.H)) + { + throw new ArgumentException( + $"Rectangular Micro QR Code has error correction levels M and H only; got {errorCorrectionLevel}.", + nameof(errorCorrectionLevel)); + } + + this.ErrorCorrectionLevel = errorCorrectionLevel; + } + + /// + /// Initializes a new instance of the class with a fixed version. + /// + /// The error correction level, M or H. + /// The version. + /// The error correction level is L or Q. + public RectangularMicroQrCodeSymbology(QrErrorCorrectionLevel errorCorrectionLevel, RectangularMicroQrCodeVersion version) + : this(errorCorrectionLevel) + { + Guard.MustBeBetweenOrEqualTo((int)version, (int)RectangularMicroQrCodeVersion.R7x43, (int)RectangularMicroQrCodeVersion.R17x139, nameof(version)); + this.Version = version; + } + + /// + /// Gets the error correction level. + /// + public QrErrorCorrectionLevel ErrorCorrectionLevel { get; } + + /// + /// Gets the version, or when the version follows from the data. + /// + public RectangularMicroQrCodeVersion? Version { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + // Section 8.1 of ISO/IEC 23941: "the width of a module shall be specified by the application", so the symbology gives no limit. + QrEncoder.BuildSymbol( + text, + false, + QrFormat.Rectangular, + this.ErrorCorrectionLevel, + (int?)this.Version ?? 0, + options.ResolveXDimension(0, DefaultMatrixXDimension, double.PositiveInfinity), + ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/QrCode/RectangularMicroQrCodeVersion.cs b/src/ImageSharp.Drawing/Barcodes/QrCode/RectangularMicroQrCodeVersion.cs new file mode 100644 index 00000000..3a1b1077 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/QrCode/RectangularMicroQrCodeVersion.cs @@ -0,0 +1,170 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The versions of Rectangular Micro QR Code, ISO/IEC 23941. A version name gives the number of module rows and the number of module columns. +/// +public enum RectangularMicroQrCodeVersion +{ + /// + /// 7 x 43 modules. + /// + R7x43 = 1, + + /// + /// 7 x 59 modules. + /// + R7x59, + + /// + /// 7 x 77 modules. + /// + R7x77, + + /// + /// 7 x 99 modules. + /// + R7x99, + + /// + /// 7 x 139 modules. + /// + R7x139, + + /// + /// 9 x 43 modules. + /// + R9x43, + + /// + /// 9 x 59 modules. + /// + R9x59, + + /// + /// 9 x 77 modules. + /// + R9x77, + + /// + /// 9 x 99 modules. + /// + R9x99, + + /// + /// 9 x 139 modules. + /// + R9x139, + + /// + /// 11 x 27 modules. + /// + R11x27, + + /// + /// 11 x 43 modules. + /// + R11x43, + + /// + /// 11 x 59 modules. + /// + R11x59, + + /// + /// 11 x 77 modules. + /// + R11x77, + + /// + /// 11 x 99 modules. + /// + R11x99, + + /// + /// 11 x 139 modules. + /// + R11x139, + + /// + /// 13 x 27 modules. + /// + R13x27, + + /// + /// 13 x 43 modules. + /// + R13x43, + + /// + /// 13 x 59 modules. + /// + R13x59, + + /// + /// 13 x 77 modules. + /// + R13x77, + + /// + /// 13 x 99 modules. + /// + R13x99, + + /// + /// 13 x 139 modules. + /// + R13x139, + + /// + /// 15 x 43 modules. + /// + R15x43, + + /// + /// 15 x 59 modules. + /// + R15x59, + + /// + /// 15 x 77 modules. + /// + R15x77, + + /// + /// 15 x 99 modules. + /// + R15x99, + + /// + /// 15 x 139 modules. + /// + R15x139, + + /// + /// 17 x 43 modules. + /// + R17x43, + + /// + /// 17 x 59 modules. + /// + R17x59, + + /// + /// 17 x 77 modules. + /// + R17x77, + + /// + /// 17 x 99 modules. + /// + R17x99, + + /// + /// 17 x 139 modules. + /// + R17x139, +} diff --git a/src/ImageSharp.Drawing/Barcodes/QrCode/SwissCross.cs b/src/ImageSharp.Drawing/Barcodes/QrCode/SwissCross.cs new file mode 100644 index 00000000..282fa699 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/QrCode/SwissCross.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Swiss cross of a Swiss QR Code, section 6.4.2 of the Swiss Implementation Guidelines for the QR-bill: a 7 x 7 mm black and white logo over the centre of +/// the 46 x 46 mm symbol. The geometry is the graphic that section refers to, 19.8 units on a side: a white square, a black square inset 1.41785 units, and a +/// white cross of two bars 3.3 units wide and 11 units long. +/// +internal static class SwissCross +{ + /// + /// The width of a Swiss QR Code in millimetres, section 6.4. + /// + public const double SymbolWidth = 46; + + /// + /// The side of the cross in millimetres, section 6.4.2. + /// + public const double Width = 7; + + /// + /// The side of the cross graphic in its own units. + /// + public const float Side = 19.8F; + + /// + /// Gets the black parts of the cross as rectangles, each as its left, top, right and bottom edge in the units of , the y axis down. The + /// black square runs from 1.41785 to 18.38215: the square of the graphic from 0.7 to 19.1 less half of its 1.4357-unit white outline. The white cross is a + /// vertical bar from x 8.3 to 11.6 and y 4 to 15 and a horizontal bar from x 4.4 to 15.4 and y 7.9 to 11.2, so the black square less the cross is these + /// eight rectangles: a band above and below the cross, and on either side of each part of the cross between. + /// + public static ReadOnlySpan DarkEdges => + [ + 1.41785F, 1.41785F, 18.38215F, 4F, + 1.41785F, 4F, 8.3F, 7.9F, + 11.6F, 4F, 18.38215F, 7.9F, + 1.41785F, 7.9F, 4.4F, 11.2F, + 15.4F, 7.9F, 18.38215F, 11.2F, + 1.41785F, 11.2F, 8.3F, 15F, + 11.6F, 11.2F, 18.38215F, 15F, + 1.41785F, 15F, 18.38215F, 18.38215F, + ]; +} diff --git a/src/ImageSharp.Drawing/Barcodes/QrCode/SwissQrBill.cs b/src/ImageSharp.Drawing/Barcodes/QrCode/SwissQrBill.cs new file mode 100644 index 00000000..b462e146 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/QrCode/SwissQrBill.cs @@ -0,0 +1,546 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Globalization; +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The data of a Swiss QR Code, chapter 4 of the Swiss Implementation Guidelines for the QR-bill: the character set of section 4.1.1, the separators of section +/// 4.1.4, the data groups of section 4.1.5 and the lines of Table 8, with the rules of chapter 2, sections 4.3 and 4.4 and Annex B. The checks follow version +/// 2.4 of the guidelines. For EUR, an IBAN takes only a Creditor Reference or no reference. Table 10 includes the Romansh text BETG DUVRAR PER IL PAJAMENT. +/// +internal static class SwissQrBill +{ + /// + /// The largest number of characters of the data, the separators included, section 6.2. + /// + public const int MaximumLength = 997; + + /// + /// The number of lines up to the trailer, which every Swiss QR Code has, Table 8. + /// + private const int MandatoryLines = 31; + + /// + /// The number of lines with the billing information and the two alternative procedures, Table 8. + /// + private const int MaximumLines = 34; + + /// + /// The index of the account line, Table 8 line 4. + /// + private const int Account = 3; + + /// + /// The index of the first line of the creditor, the address type, Table 8 lines 5 to 11. + /// + private const int Creditor = 4; + + /// + /// The index of the first line of the ultimate creditor, Table 8 lines 12 to 18. + /// + private const int UltimateCreditor = 11; + + /// + /// The index of the amount line, Table 8 line 19. + /// + private const int Amount = 18; + + /// + /// The index of the currency line, Table 8 line 20. + /// + private const int Currency = 19; + + /// + /// The index of the first line of the ultimate debtor, Table 8 lines 21 to 27. + /// + private const int UltimateDebtor = 20; + + /// + /// The index of the reference type line, Table 8 line 28. + /// + private const int ReferenceType = 27; + + /// + /// The index of the reference line, Table 8 line 29. + /// + private const int Reference = 28; + + /// + /// The index of the unstructured message line, Table 8 line 30. + /// + private const int Message = 29; + + /// + /// The index of the trailer line, Table 8 line 31. + /// + private const int Trailer = 30; + + /// + /// The index of the billing information line, Table 8 line 32. + /// + private const int BillingInformation = 31; + + /// + /// The number of lines of an address: the address type, the name, the street, the building number, the postal code, the town and the country. + /// + private const int AddressLines = 7; + + /// + /// The longest unstructured message and billing information together, section 4.3.3. + /// + private const int MaximumAdditionalInformation = 140; + + /// + /// The longest alternative procedure, Table 8 lines 33 and 34. + /// + private const int MaximumAlternativeProcedure = 100; + + /// + /// The first QR-IID, section 2.8: "QR-IIDs consist exclusively of numbers from 30000 to 31999". + /// + private const int FirstQrIid = 30000; + + /// + /// The last QR-IID, section 2.8. + /// + private const int LastQrIid = 31999; + + /// + /// The characters of an IBAN in electronic form, ISO 13616: digits and capital letters. + /// + private static readonly SearchValues IbanCharacters = SearchValues.Create("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + + /// + /// The alphanumeric characters of section 4.1.2: "A-Z a-z 0-9". + /// + private static readonly SearchValues AlphanumericCharacters = SearchValues.Create("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); + + /// + /// Gets the first row of the check digit matrix of Annex B, Figure 21: the carry-over that follows carry-over 0 and each digit 0 to 9. Each row of the + /// matrix is this row moved left by its carry-over, so the carry-over that follows carry-over c and digit d is this entry at (c + d) mod 10. + /// + private static ReadOnlySpan CarryOver => [0, 9, 4, 6, 8, 2, 7, 1, 3, 5]; + + /// + /// Checks the data of a Swiss QR Code. + /// + /// The data, at most characters. + /// The data does not follow the guidelines. + public static void Validate(ReadOnlySpan text) + { + // Section 4.1.1 permits U+0020 to U+007E, U+00A0 to U+017F, U+0218 to U+021B and U+20AC. Section 4.1.4 separates the lines with CR LF or with LF, the + // same one throughout. Each line is held as its range in the text, and the text has at most MaximumLines lines. + InlineArray36 rangeStorage = default; + Span ranges = rangeStorage; + int lineCount = 0; + int lineStart = 0; + int position = 0; + bool crLf = false; + bool lf = false; + SpanCodePointEnumerator codePoints = text.EnumerateCodePoints(); + while (codePoints.MoveNext()) + { + CodePoint current = codePoints.Current; + int value = current.Value; + if (value is '\r' or '\n') + { + if (value == '\r' && (position + 1 >= text.Length || text[position + 1] != '\n')) + { + throw new ArgumentException("A Swiss QR Code separates its lines with CR LF or with LF; got a CR without an LF.", nameof(text)); + } + + if (lineCount == MaximumLines) + { + throw new ArgumentException($"A Swiss QR Code has at most {MaximumLines} lines, Table 8 of the QR-bill guidelines.", nameof(text)); + } + + ranges[lineCount++] = new Range(lineStart, position); + if (value == '\r') + { + crLf = true; + codePoints.MoveNext(); + position++; + } + else + { + lf = true; + } + + position++; + lineStart = position; + continue; + } + + if (value is not ((>= 0x20 and <= 0x7E) or (>= 0xA0 and <= 0x17F) or (>= 0x218 and <= 0x21B) or 0x20AC)) + { + throw new ArgumentException( + $"A Swiss QR Code carries only the characters of section 4.1.1 of the QR-bill guidelines; got {current.ToDisplayString()}.", + nameof(text)); + } + + position += current.Utf16SequenceLength; + } + + if (crLf && lf) + { + throw new ArgumentException("A Swiss QR Code separates all its lines with CR LF or all with LF.", nameof(text)); + } + + if (lineCount == MaximumLines) + { + throw new ArgumentException($"A Swiss QR Code has at most {MaximumLines} lines, Table 8 of the QR-bill guidelines.", nameof(text)); + } + + ranges[lineCount++] = new Range(lineStart, text.Length); + if (lineCount < MandatoryLines) + { + throw new ArgumentException( + $"A Swiss QR Code has the {MandatoryLines} lines up to the trailer of Table 8 of the QR-bill guidelines; got {lineCount}.", + nameof(text)); + } + + BillLines lines = new(text, ranges[..lineCount], nameof(text)); + + // Table 8 lines 1 to 3, the header, of fixed values. + lines.CheckFixed(0, "SPC", "the QR type"); + lines.CheckFixed(1, "0200", "the version"); + lines.CheckFixed(2, "1", "the coding type"); + + // Table 8 line 4: "Fixed length: 21 alphanumeric characters, no spaces allowed, only IBANs with CH or LI country code permitted", an IBAN of ISO 13616 + // with its MOD 97-10 check digits. Positions 5 to 9 of a Swiss or Liechtenstein IBAN are the institution identification, and section 2.8 reserves 30000 + // to 31999 for the QR-IID of a QR-IBAN. + ReadOnlySpan account = lines[Account]; + if (account.Length != 21 + || !(account.StartsWith("CH") || account.StartsWith("LI")) + || account.ContainsAnyExcept(IbanCharacters) + || Iso7064.Mod97(account) != 1) + { + throw new ArgumentException( + $"Line 4 of a Swiss QR Code, the account, is an IBAN of 21 characters with the country code CH or LI and valid check digits; got '{account}'.", + nameof(text)); + } + + ReadOnlySpan iid = account[4..9]; + bool qrIban = !iid.ContainsAnyExceptInRange('0', '9') && int.Parse(iid, CultureInfo.InvariantCulture) is >= FirstQrIid and <= LastQrIid; + + // Table 8 lines 5 to 11, the creditor, a mandatory group. Section 4.3.1: "Postal code", "Town" and "Country" must be filled in. + lines.CheckAddress(Creditor, "the creditor"); + + // Table 8 lines 12 to 18, the ultimate creditor: "The entire data group must not be filled in". + for (int i = UltimateCreditor; i < UltimateCreditor + AddressLines; i++) + { + if (!lines[i].IsEmpty) + { + throw new ArgumentException($"Line {i + 1} of a Swiss QR Code belongs to the ultimate creditor, which must not be filled in.", nameof(text)); + } + } + + // Table 8 line 20: "Only CHF and EUR are permitted". + ReadOnlySpan currency = lines[Currency]; + bool chf = currency.SequenceEqual("CHF"); + if (!chf && !currency.SequenceEqual("EUR")) + { + throw new ArgumentException($"Line 20 of a Swiss QR Code, the currency, is CHF or EUR; got '{currency}'.", nameof(text)); + } + + // Table 8 lines 21 to 27, the ultimate debtor, an optional group. Section 4.1.5: "If a data group is used, in those marked with "Optional", all + // sub-elements marked as "Dependent" must be filled." + for (int i = UltimateDebtor; i < UltimateDebtor + AddressLines; i++) + { + if (!lines[i].IsEmpty) + { + lines.CheckAddress(UltimateDebtor, "the ultimate debtor"); + break; + } + } + + // Table 8 lines 28 and 29, the reference type and the reference. Sections 2.10, 2.12 and 4.3.2: a QR reference goes with a QR-IBAN and in CHF only, a + // Creditor Reference with an IBAN, and no reference with an IBAN. + ReadOnlySpan referenceType = lines[ReferenceType]; + ReadOnlySpan reference = lines[Reference]; + bool qrReference = referenceType.SequenceEqual("QRR"); + if (!qrReference && !referenceType.SequenceEqual("SCOR") && !referenceType.SequenceEqual("NON")) + { + throw new ArgumentException($"Line 28 of a Swiss QR Code, the reference type, is QRR, SCOR or NON; got '{referenceType}'.", nameof(text)); + } + + if (qrReference != qrIban) + { + throw new ArgumentException( + qrIban + ? "A Swiss QR Code with a QR-IBAN, an IBAN with a QR-IID of 30000 to 31999, has the reference type QRR." + : $"A Swiss QR Code with the reference type QRR has a QR-IBAN, an IBAN with a QR-IID of 30000 to 31999; got '{account}'.", + nameof(text)); + } + + if (qrReference) + { + if (!chf) + { + throw new ArgumentException("A Swiss QR Code with the reference type QRR is in CHF.", nameof(text)); + } + + // Section 2.12.1: "26 numerical characters followed by a modulo 10 recursive check digit (see Annex B)", and "The reference must not consist + // exclusively of zeros". Annex B: from carry-over 0, each digit from the left takes the next carry-over from the matrix, and the check digit is the + // last column of the row of the final carry-over, 10 less the carry-over, mod 10. + if (reference.Length != 27 || reference.ContainsAnyExceptInRange('0', '9') || !reference[..26].ContainsAnyExcept('0')) + { + throw new ArgumentException( + $"Line 29 of a Swiss QR Code, the reference, is a QR reference of 27 digits, not all zeros; got '{reference}'.", + nameof(text)); + } + + int carry = 0; + for (int i = 0; i < 26; i++) + { + carry = CarryOver[(carry + (reference[i] - '0')) % 10]; + } + + int check = (10 - carry) % 10; + if (reference[26] - '0' != check) + { + throw new ArgumentException( + $"Line 29 of a Swiss QR Code, the QR reference, ends with the check digit {check}; got {reference[26]}.", + nameof(text)); + } + } + else if (referenceType.SequenceEqual("SCOR")) + { + // Section 2.12.2: "a minimum of 5 and a maximum of 25 alphanumerical characters. Starting with RF, followed by the check digits (3rd and 4th + // digit)", calculated "with modulo 97-10". Table 8: "The banks draw no distinction between lower and upper case when processing." + if (reference.Length is < 5 or > 25 + || !reference.StartsWith("RF", StringComparison.OrdinalIgnoreCase) + || reference.ContainsAnyExcept(AlphanumericCharacters) + || Iso7064.Mod97(reference) != 1) + { + throw new ArgumentException( + $"Line 29 of a Swiss QR Code, the reference, is a Creditor Reference: RF, valid check digits, 5 to 25 characters; got '{reference}'.", + nameof(text)); + } + } + else if (!reference.IsEmpty) + { + throw new ArgumentException("Line 29 of a Swiss QR Code, the reference, is empty for the reference type NON.", nameof(text)); + } + + // Table 8 line 30, the unstructured message of at most 140 characters, and line 31, the trailer. + ReadOnlySpan message = lines[Message]; + lines.CheckLength(Message, 0, MaximumAdditionalInformation, "the unstructured message"); + lines.CheckFixed(Trailer, "EPD", "the trailer"); + + // Table 8 line 19, the amount: "without leading zeroes, including decimal separators and two decimal places. Decimal, maximum 12 digits permitted, + // including decimal separators. Only decimal points (".") are permitted as decimal separators. The amount must be between CHF/EUR 0.01 and + // 999,999,999.99." Section 4.4 fills the amount 0.00 for a notification, with one of the texts of Table 10 as the additional information. + ReadOnlySpan amount = lines[Amount]; + if (!amount.IsEmpty) + { + int point = amount.Length - 3; + if (amount.Length > 12 + || point < 1 + || amount[point] != '.' + || amount[..point].ContainsAnyExceptInRange('0', '9') + || amount[(point + 1)..].ContainsAnyExceptInRange('0', '9') + || (amount[0] == '0' && point > 1)) + { + throw new ArgumentException( + $"Line 19 of a Swiss QR Code, the amount, has no leading zeros, a point, two decimals, at most 12 characters; got '{amount}'.", + nameof(text)); + } + + if (amount.SequenceEqual("0.00") && !IsNotification(message)) + { + throw new ArgumentException( + "Line 19 of a Swiss QR Code, the amount, is at least 0.01, or 0.00 with a DO NOT USE FOR PAYMENT text of section 4.4 as the message.", + nameof(text)); + } + } + + // Table 8 line 32, the billing information, and lines 33 and 34, the alternative procedures, are omitted when they are not used. Section 4.3.3: the + // billing information "always begins with "//" (slash slash) followed by the double-digit, abbreviated name" of its rules, and the message and the + // billing information together "must not exceed 140 characters". + if (lineCount > BillingInformation) + { + ReadOnlySpan billing = lines[BillingInformation]; + if (!billing.IsEmpty && (billing.Length < 4 || !billing.StartsWith("//") || billing[2..4].ContainsAnyExcept(AlphanumericCharacters))) + { + throw new ArgumentException( + $"Line 32 of a Swiss QR Code, the billing information, begins with // and a name of two characters; got '{billing}'.", + nameof(text)); + } + + int additional = message.Length + billing.Length; + if (additional > MaximumAdditionalInformation) + { + throw new ArgumentException( + $"The message and the billing information of a Swiss QR Code are at most {MaximumAdditionalInformation} characters; got {additional}.", + nameof(text)); + } + } + + for (int i = BillingInformation + 1; i < lineCount; i++) + { + lines.CheckLength(i, 0, MaximumAlternativeProcedure, "an alternative procedure"); + } + } + + /// + /// Names an element for a message: the element alone, or the element of a party. + /// + /// The element. + /// The party, or . + /// The name. + private static string Describe(string element, string? party) => party is null ? element : $"{element} of {party}"; + + /// + /// Returns whether the message is one of the texts of Table 10 of section 4.4, which a notification that must not be paid carries in capital letters. + /// + /// The unstructured message. + /// when the message is such a text. + private static bool IsNotification(ReadOnlySpan message) + => message.SequenceEqual("NICHT ZUR ZAHLUNG VERWENDEN") + || message.SequenceEqual("NE PAS UTILISER POUR LE PAIEMENT") + || message.SequenceEqual("NON UTILIZZARE PER IL PAGAMENTO") + || message.SequenceEqual("DO NOT USE FOR PAYMENT") + || message.SequenceEqual("BETG DUVRAR PER IL PAJAMENT"); + + /// + /// The lines of the data and the checks of a single line or a group of lines. + /// + private readonly ref struct BillLines + { + /// + /// The data. + /// + private readonly ReadOnlySpan text; + + /// + /// The range of each line in the data. + /// + private readonly ReadOnlySpan ranges; + + /// + /// The name of the parameter that holds the data, which a failed check names. + /// + private readonly string paramName; + + /// + /// Initializes a new instance of the struct. + /// + /// The data. + /// The range of each line in the data. + /// The name of the parameter that holds the data. + public BillLines(ReadOnlySpan text, ReadOnlySpan ranges, string paramName) + { + this.text = text; + this.ranges = ranges; + this.paramName = paramName; + } + + /// + /// Gets the characters of a line. + /// + /// The index of the line, its number in Table 8 less one. + /// The characters. + public ReadOnlySpan this[int index] => this.text[this.ranges[index]]; + + /// + /// Checks a line that holds a fixed value. + /// + /// The index of the line. + /// The fixed value. + /// The element the line holds, for the message. + /// The line holds another value. + public void CheckFixed(int index, string expected, string element) => this.CheckFixed(index, expected, element, null); + + /// + /// Checks a line that holds a fixed value of the address of a party. The message names the element and the party only when the check fails, so a valid + /// line builds no text. + /// + /// The index of the line. + /// The fixed value. + /// The element the line holds, for the message. + /// The party the address belongs to, for the message, or . + /// The line holds another value. + public void CheckFixed(int index, string expected, string element, string? party) + { + ReadOnlySpan line = this[index]; + if (!line.SequenceEqual(expected)) + { + throw new ArgumentException($"Line {index + 1} of a Swiss QR Code, {Describe(element, party)}, is '{expected}'; got '{line}'.", this.paramName); + } + } + + /// + /// Checks the length of a line. Section 4.1.3: "It is not permitted to fill in the elements with blanks up to the maximum length", so a line of the + /// maximum length does not end with a blank. + /// + /// The index of the line. + /// The fewest characters. + /// The most characters. + /// The element the line holds, for the message. + /// The line is shorter or longer, or blanks fill it to the maximum length. + public void CheckLength(int index, int minimum, int maximum, string element) => this.CheckLength(index, minimum, maximum, element, null); + + /// + /// Checks the length of a line of the address of a party, as the overload without a party does. The message names the element and the party only when + /// the check fails, so a valid line builds no text. + /// + /// The index of the line. + /// The fewest characters. + /// The most characters. + /// The element the line holds, for the message. + /// The party the address belongs to, for the message, or . + /// The line is shorter or longer, or blanks fill it to the maximum length. + public void CheckLength(int index, int minimum, int maximum, string element, string? party) + { + ReadOnlySpan line = this[index]; + int length = line.Length; + if (length < minimum || length > maximum) + { + throw new ArgumentException( + minimum == 0 + ? $"Line {index + 1} of a Swiss QR Code, {Describe(element, party)}, is at most {maximum} characters; got {length}." + : $"Line {index + 1} of a Swiss QR Code, {Describe(element, party)}, is {minimum} to {maximum} characters; got {length}.", + this.paramName); + } + + if (length == maximum && line[^1] == ' ') + { + string name = Describe(element, party); + throw new ArgumentException( + $"Line {index + 1} of a Swiss QR Code, {name}, is padded with blanks to {maximum} characters, which section 4.1.3 forbids.", + this.paramName); + } + } + + /// + /// Checks the seven lines of a structured address, Table 8: the address type "S", a name of 1 to 70 characters, a street of at most 70, a building + /// number of at most 16, a postal code of 1 to 16, a town of 1 to 35 and an ISO 3166-1 alpha-2 country code. + /// + /// The index of the address type line. + /// The party the address belongs to, for the message. + /// A line does not follow its definition. + public void CheckAddress(int first, string party) + { + this.CheckFixed(first, "S", "the address type", party); + this.CheckLength(first + 1, 1, 70, "the name", party); + this.CheckLength(first + 2, 0, 70, "the street", party); + this.CheckLength(first + 3, 0, 16, "the building number", party); + this.CheckLength(first + 4, 1, 16, "the postal code", party); + this.CheckLength(first + 5, 1, 35, "the town", party); + + ReadOnlySpan country = this[first + 6]; + if (!Iso3166.IsAlpha2Code(country)) + { + throw new ArgumentException( + $"Line {first + 7} of a Swiss QR Code, the country of {party}, is an ISO 3166-1 alpha-2 country code; got '{country}'.", + this.paramName); + } + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/QrCode/SwissQrCodeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/QrCode/SwissQrCodeSymbology.cs new file mode 100644 index 00000000..4598b791 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/QrCode/SwissQrCodeSymbology.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.InteropServices; +using System.Text; +using System.Text.Unicode; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Swiss QR Code of the QR-bill, chapter 6 of the Swiss Implementation Guidelines for the QR-bill: a QR Code symbol at error correction level M in the +/// smallest version that holds the data, drawn 46 x 46 mm with a 7 x 7 mm Swiss cross over its centre. +/// +/// The input is the payment data of chapter 4: at most 997 characters in 31 to 34 lines, the lines separated by CR LF or by LF, the same one throughout, with +/// the characters of section 4.1.1. Each line follows its definition in Table 8: the header SPC, 0200 and 1; a Swiss or Liechtenstein IBAN with valid check +/// digits; the structured address of the creditor; an empty ultimate creditor; the amount; the currency CHF or EUR; an empty or structured ultimate debtor; the +/// reference type and the reference; the unstructured message; the trailer EPD; and the optional billing information and alternative procedures. A QR reference +/// goes with a QR-IBAN and CHF, a Creditor Reference or no reference with an IBAN. The amount 0.00 carries a DO NOT USE FOR PAYMENT text of section 4.4 as its +/// message. The symbol carries the data in UTF-8. +/// +/// +/// The symbol is always 46 mm wide. Section 6.4: "The measurements of the Swiss QR Code for printing must always be 46 x 46 mm (without surrounding quiet +/// space) regardless of the Swiss QR Code version." So must be . +/// +/// +public sealed class SwissQrCodeSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public SwissQrCodeSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + // Section 6.4: the symbol "must always be 46 x 46 mm", so its X dimension follows from its version. + if (options.XDimension is not null) + { + throw new ArgumentException("A Swiss QR Code is always 46 x 46 mm, so its X dimension is not set.", nameof(options)); + } + + Guard.MustBeLessThanOrEqualTo(text.Length, SwissQrBill.MaximumLength, nameof(text)); + SwissQrBill.Validate(text); + + // Section 4.1.1: the data is UTF-8. The validation leaves only characters of the Basic Multilingual Plane up to U+20AC, which take at most 3 bytes + // each. Each byte goes to the encoder as the character of that value, which Byte mode writes as the byte. One rent holds the characters and, after + // them, the bytes. + int capacity = 3 * text.Length; + PooledBuffer rented = new(capacity + ((capacity + 1) / 2)); + try + { + Span characters = rented.Span[..capacity]; + Span bytes = MemoryMarshal.AsBytes(rented.Span[capacity..])[..capacity]; + _ = Utf8.FromUtf16(text, bytes, out _, out int written); + int count = Encoding.Latin1.GetChars(bytes[..written], characters); + + // Sections 6.1 and 6.4: level M and the smallest version, drawn 46 mm wide with the 7 mm cross at the same scale. + QrEncoder.BuildSymbol(characters[..count], false, QrFormat.Full, QrErrorCorrectionLevel.M, 0, null, SwissCross.SymbolWidth, ref symbol); + symbol.AddOverlay((float)(SwissCross.Width / SwissCross.SymbolWidth * symbol.Columns), SwissCross.Side, SwissCross.DarkEdges); + } + finally + { + rented.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Raw/RawLinearSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Raw/RawLinearSymbology.cs new file mode 100644 index 00000000..12878c10 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Raw/RawLinearSymbology.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// A custom linear symbology whose input is the symbol itself: the digits 1 to 9 give the widths in modules of the bars and spaces in turn, starting with a +/// bar. An even number of digits ends on a space, which the symbol keeps behind a bar of zero width. The symbol has no quiet zones and prints no line. +/// +public sealed class RawLinearSymbology : BarcodeSymbology +{ + /// + /// The largest number of widths a symbol carries. + /// + public const int MaximumLength = 2500; + + /// + /// The bar height as a fraction of the symbol width when the caller sets no height: 15 per cent. + /// + private const float NominalBarHeightFraction = 0.15F; + + /// + /// Initializes a new instance of the class. + /// + public RawLinearSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Guard.MustBeGreaterThan(text.Length, 0, nameof(text)); + Guard.MustBeLessThanOrEqualTo(text.Length, MaximumLength, nameof(text)); + + // An even count of widths ends on a space, so a zero width bar closes the runs. The storage starts zero, so that bar needs no write. + bool endsOnSpace = (text.Length & 1) == 0; + double xDimension = options.ResolveXDimension(0, DefaultXDimension, double.PositiveInfinity); + Span runs = symbol.BeginLinear(text.Length + (endsOnSpace ? 1 : 0), xDimension, out Span heights, out _); + int widthInModules = 0; + int index = 0; + SpanCodePointEnumerator codePoints = text.EnumerateCodePoints(); + while (codePoints.MoveNext()) + { + CodePoint current = codePoints.Current; + if (current.Value is < '1' or > '9') + { + throw new ArgumentException($"A raw symbol carries only the digits 1 to 9; got {current.ToDisplayString()}.", nameof(text)); + } + + int width = current.Value - '0'; + runs[index++] = width; + widthInModules += width; + } + + float barHeight = options.ResolveBarHeight(xDimension, 0, widthInModules * NominalBarHeightFraction, float.PositiveInfinity); + heights.Fill(barHeight); + symbol.EndLinear(0, 0); + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Telepen/TelepenEncoder.cs b/src/ImageSharp.Drawing/Barcodes/Telepen/TelepenEncoder.cs new file mode 100644 index 00000000..a674162e --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Telepen/TelepenEncoder.cs @@ -0,0 +1,226 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodation for the Telepen symbology. Every character is an ASCII value with an even parity bit, sent least significant bit first, and the bits become bars +/// and spaces with a fixed 3:1 ratio: a 1 bit is a narrow bar and a narrow space, the bits 00 are a wide bar and a narrow space, the bits 010 are a wide bar +/// and a wide space, and the bits 01 and, after them, 10 are a narrow bar and a wide space. Every character is sixteen modules. A symbol is the start code, the +/// data, the modulo 127 check character and the stop code, without gaps. +/// +internal static class TelepenEncoder +{ + /// + /// The largest number of characters a symbol carries. The symbology sets no maximum. + /// + public const int MaximumLength = 500; + + /// + /// The number of characters a caller holds inline to build symbol data in. Longer data rents its buffer once. + /// + public const int InlineBufferLength = 64; + + /// + /// The value that the numeric mode adds to a digit followed by X. + /// + public const int NumericSingleOffset = 17; + + /// + /// The value that the numeric mode adds to a pair of digits read as a number. + /// + public const int NumericPairOffset = 27; + + /// + /// The quiet zone in modules on each side. + /// + private const int QuietZone = 10; + + /// + /// The default X dimension in millimetres: 0.01125 inch. The Telepen history of SB Electronic Systems recommends 0.010 inch to 0.0125 inch: "For normal + /// applications the recommended value of x should be between 0.010 inch an 0.0125". The default is the middle of that range. + /// + private const double NominalXDimension = 0.01125 * 25.4; + + /// + /// The smallest X dimension in millimetres, 0.0075 inch. The Telepen history of SB Electronic Systems: "For open systems the minimum value of x is 0.0075 + /// inch or 0.19 mm." The document gives no largest X dimension. + /// + private const double MinimumXDimension = 0.0075 * 25.4; + + /// + /// The start code, "binary 01011111 (ASCII _)". + /// + private const int Start = '_'; + + /// + /// The stop code, "binary 11111010 (ASCII z)". + /// + private const int Stop = 'z'; + + /// + /// The modulus of the check character. + /// + private const int CheckModulus = 127; + + /// + /// The number of bits in a character: seven ASCII bits and the parity bit. + /// + private const int BitsPerCharacter = 8; + + /// + /// The width of a character in modules: eight bits of two modules. + /// + private const int ModulesPerCharacter = 16; + + /// + /// The number of characters a symbol carries beyond its data: the start code, the check character and the stop code. + /// + private const int FrameCharacters = 3; + + /// + /// The width of a wide bar or space in modules, from the fixed 3:1 ratio. A narrow bar or space is one module. + /// + private const int WideElement = 3; + + /// + /// The bar height as a fraction of the symbol length, the quiet zones included, when the caller sets no height: 15 per cent, and at least + /// . + /// + private const float NominalBarHeightFraction = 0.15F; + + /// + /// The smallest bar height in millimetres when the caller sets none. + /// + private const float MinimumBarHeightMillimetres = 6.35F; + + /// + /// Calculates the check character over the character values the symbol carries: "Add the ASCII values of each character excluding the start and stop + /// characters. Divide by 127. Unless remainder equals zero subtract from 127. The character whose ASCII value is the result is the check character." For + /// data of NUL characters alone "the check character is exceptionally ASCII 127". + /// + /// The character values the symbol carries. + /// The value of the check character. + public static int CheckCharacter(ReadOnlySpan values) + { + int sum = 0; + foreach (int value in values) + { + sum += value; + } + + if (sum == 0) + { + return CheckModulus; + } + + int remainder = sum % CheckModulus; + return remainder == 0 ? 0 : CheckModulus - remainder; + } + + /// + /// Encodes character values and builds the symbol. The runs start with the first bar of the start code and end on the last bar of the stop code, and carry + /// the start code, the values, the check character and the stop code. Telepen carries no guard bars, so every bar runs the full height, and the human + /// readable interpretation sits below the symbol. + /// + /// The character values the symbol carries, already validated. + /// The human readable interpretation. + /// The options that control layout choices. + /// The symbol to write. + public static void BuildSymbol(ReadOnlySpan values, ReadOnlySpan readable, BarcodeOptions options, ref BarcodeSymbol symbol) + { + // Every run is at least one module and a character is sixteen modules, so a character is at most sixteen runs. The symbol reserves that many runs and + // keeps those it writes. The stop code ends on a bar, so the space written after it is dropped. + double xDimension = options.ResolveXDimension(MinimumXDimension, NominalXDimension, double.PositiveInfinity); + Span runs = symbol.BeginLinear((values.Length + FrameCharacters) * ModulesPerCharacter, xDimension, out Span heights, out _); + int written = 0; + Append(runs, ref written, Start); + foreach (int value in values) + { + Append(runs, ref written, value); + } + + Append(runs, ref written, CheckCharacter(values)); + Append(runs, ref written, Stop); + symbol.TrimLastRow(written - 1); + runs = runs[..(written - 1)]; + + int widthInModules = BarcodeSymbol.RunWidth(runs); + + float nominalBarHeight = MathF.Max((widthInModules + (2 * QuietZone)) * NominalBarHeightFraction, (float)(MinimumBarHeightMillimetres / xDimension)); + float barHeight = options.ResolveBarHeight(xDimension, 0, nominalBarHeight, float.PositiveInfinity); + heights[..((runs.Length + 1) >> 1)].Fill(barHeight); + + if (options.Font is not null) + { + symbol.ReserveText(1, readable.Length); + symbol.AddText(readable, 0F, widthInModules, BarcodeTextSide.BelowBars, barHeight + BarcodeTextPlacement.Clearance); + } + + symbol.EndLinear(QuietZone, QuietZone); + } + + /// + /// Writes the bars and spaces of one character. The seven ASCII bits take an even parity bit as their eighth, and the eight bits are read least significant + /// first. A 1 bit is a narrow bar and a narrow space. The bits 00 are a wide bar and a narrow space, and the bits 010 are a wide bar and a wide space. The + /// bits 01 followed by a 1 are a narrow bar and a wide space, after which the bits 10 are the same pair, so a lone 0 bit always pairs with a later one. + /// Even parity gives every character an even number of 0 bits, so the pairing always closes inside the character. + /// + /// The buffer the widths are written to. + /// The write position, advanced by the element count. + /// The ASCII value of the character. + private static void Append(Span runs, ref int written, int value) + { + int bits = value; + if ((BitOperations.PopCount((uint)value) & 1) == 1) + { + bits |= 1 << (BitsPerCharacter - 1); + } + + // Every step writes one bar and one space. The pair is narrow and narrow unless a 0 bit is involved, and the step consumes one to three bits. + int i = 0; + bool open = false; + while (i < BitsPerCharacter) + { + int bar = 1; + int space = 1; + int consumed = 1; + if (open) + { + // A pair opened by 01 stays open across 1 bits and closes on the bits 10. + if (((bits >> (i + 1)) & 1) == 0) + { + space = WideElement; + consumed = 2; + open = false; + } + } + else if (((bits >> i) & 1) == 0) + { + if (((bits >> (i + 1)) & 1) == 0) + { + bar = WideElement; + consumed = 2; + } + else if (((bits >> (i + 2)) & 1) == 0) + { + bar = WideElement; + space = WideElement; + consumed = 3; + } + else + { + space = WideElement; + consumed = 2; + open = true; + } + } + + runs[written++] = bar; + runs[written++] = space; + i += consumed; + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Telepen/TelepenNumericSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Telepen/TelepenNumericSymbology.cs new file mode 100644 index 00000000..db26fdff --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Telepen/TelepenNumericSymbology.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Telepen Numeric symbology, the double density numeric mode of Telepen. It packs "two digits into one character" and a single digit followed by X into +/// one of the remaining characters, so a symbol carries an even number of characters in which X can stand only in the second position of a pair. The symbol is +/// a Telepen symbol of the packed characters, and the check character covers the packed characters. The printed line shows the digits as given. +/// +public sealed class TelepenNumericSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public TelepenNumericSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Guard.MustBeGreaterThan(text.Length, 0, nameof(text)); + Guard.MustBeLessThanOrEqualTo(text.Length, TelepenEncoder.MaximumLength, nameof(text)); + + if ((text.Length & 1) == 1) + { + throw new ArgumentException($"Telepen Numeric carries an even number of characters; got {text.Length}.", nameof(text)); + } + + // One value for each pair of characters, so the buffer never grows: short text works inline and longer text rents once. + InlineArray32 bufferStorage = default; + int capacity = text.Length / 2; + ValueListBuilder buffer = capacity <= TelepenEncoder.InlineBufferLength / 2 ? new(bufferStorage) : new(capacity); + try + { + SpanCodePointEnumerator codePoints = text.EnumerateCodePoints(); + int index = 0; + int first = 0; + while (codePoints.MoveNext()) + { + CodePoint current = codePoints.Current; + bool second = (index & 1) == 1; + if (current.IsAsciiDigit()) + { + if (second) + { + buffer.Append(TelepenEncoder.NumericPairOffset + (first * 10) + (current.Value - '0')); + } + else + { + first = current.Value - '0'; + } + } + else if (current.Value == 'X' && second) + { + buffer.Append(TelepenEncoder.NumericSingleOffset + first); + } + else + { + throw new ArgumentException( + $"Telepen Numeric carries only digits, and X in the second position of a pair; got {current.ToDisplayString()}.", nameof(text)); + } + + index += current.Utf16SequenceLength; + } + + TelepenEncoder.BuildSymbol(buffer.AsSpan(), text, options, ref symbol); + } + finally + { + buffer.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Telepen/TelepenSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Telepen/TelepenSymbology.cs new file mode 100644 index 00000000..5dc8a6a1 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Telepen/TelepenSymbology.cs @@ -0,0 +1,52 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Telepen symbology. It carries all 128 ASCII characters without shift characters, between the start code _ and the stop code z, which the printed line +/// does not show. Every symbol carries the modulo 127 check character, which the printed line does not show either. +/// +public sealed class TelepenSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class. + /// + public TelepenSymbology() + { + } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + Guard.MustBeGreaterThan(text.Length, 0, nameof(text)); + Guard.MustBeLessThanOrEqualTo(text.Length, TelepenEncoder.MaximumLength, nameof(text)); + + // One value for each character, so the buffer never grows: short text works inline and longer text rents once. + InlineArray64 bufferStorage = default; + ValueListBuilder buffer = text.Length <= TelepenEncoder.InlineBufferLength ? new(bufferStorage) : new(text.Length); + try + { + SpanCodePointEnumerator codePoints = text.EnumerateCodePoints(); + while (codePoints.MoveNext()) + { + CodePoint current = codePoints.Current; + if (!current.IsAscii) + { + throw new ArgumentException($"Telepen carries only ASCII characters; got {current.ToDisplayString()}.", nameof(text)); + } + + buffer.Append(current.Value); + } + + TelepenEncoder.BuildSymbol(buffer.AsSpan(), text, options, ref symbol); + } + finally + { + buffer.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Ultracode/UltracodeEncoder.cs b/src/ImageSharp.Drawing/Barcodes/Ultracode/UltracodeEncoder.cs new file mode 100644 index 00000000..24f2a9d7 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Ultracode/UltracodeEncoder.cs @@ -0,0 +1,535 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// Encodes Ultracode symbols: rows of colour tiles between black and white timing patterns. Each codeword, 0 to 284, is a tile of five modules in a column, +/// each cyan, magenta, yellow or green. The message is the 8-bit characters of the text, with Reed-Solomon error correction over the prime field of 283. A +/// data column count code beside the left finder tells a reader how many tile columns follow. +/// +internal static class UltracodeEncoder +{ + /// + /// The longest text, in characters: at level EC1, 263 characters make 266 message codewords and 16 check codewords, the 282 tiles of the largest symbol. + /// + private const int MaximumLength = 263; + + /// + /// The quiet zone on each of the four sides in modules. + /// + private const int QuietZone = 1; + + /// + /// The prime modulus of the Reed-Solomon field. + /// + private const int Modulus = 283; + + /// + /// The start codeword of a message of 8-bit characters, ISO/IEC 8859-1. + /// + private const int StartLatin1 = 257; + + /// + /// The codeword that separates the error correction from the data in the tile sequence. + /// + private const int Separator = 283; + + /// + /// The pad codeword that fills the tiles after the data. + /// + private const int Pad = 284; + + /// + /// The codewords before the data in the message: the start codeword, the message codeword count and the auxiliary codeword count. + /// + private const int MessageHeader = 3; + + /// + /// The tiles outside the message and its check codewords: the tile count, the separator and the check codeword count. + /// + private const int StructuralTiles = 3; + + /// + /// The modules of a tile, all in one column. + /// + private const int TileHeight = 5; + + /// + /// The bits of the colour code of one tile module. + /// + private const int ColorBits = 2; + + /// + /// The modules a tile row takes with the timing row below it. + /// + private const int RowPitch = TileHeight + 1; + + /// + /// The columns left of the first tile column: the black edge, a timing column, the column of the data column count code, a black column and a light + /// column. The right edge adds one more column, so a symbol is its tile columns plus six. + /// + private const int LeftColumns = 5; + + /// + /// The column that the vertical timing columns count from. They stand every columns after it. The finder draws this column + /// black. + /// + private const int FirstTimingColumn = 3; + + /// + /// The distance between two vertical timing columns. + /// + private const int TimingSpacing = 16; + + /// + /// The column of the data column count code. + /// + private const int CountColumn = 2; + + /// + /// The most tile columns, timing columns included. + /// + private const int MaximumColumns = 61; + + /// + /// The values of one symbol height in . + /// + private const int HeightFields = 4; + + /// + /// Gets the symbol heights, values each: the tile rows, the fewest and the most tiles a symbol of that height holds, and the + /// fewest tile columns, timing columns included. + /// + private static ReadOnlySpan Heights => + [ + 2, 7, 37, 5, + 3, 36, 84, 13, + 4, 85, 161, 22, + 5, 142, 282, 29, + ]; + + /// + /// Gets the factor of each error correction level, EC1 to EC5: the check codewords are the factor times the started groups of 25 message codewords, + /// plus 5. + /// + private static ReadOnlySpan CheckFactors => [1, 2, 4, 6, 8]; + + /// + /// Gets the tile of each codeword, 0 to 284: the five module colours from the top, two bits each with the top module in the highest bits, 0 cyan, + /// 1 magenta, 2 yellow and 3 green. + /// + private static ReadOnlySpan Tiles => + [ + 70, 71, 73, 75, 77, 78, 97, 98, 99, 100, 102, 103, 108, 109, 110, 113, 114, 115, 116, 118, + 119, 120, 121, 123, 134, 135, 137, 141, 142, 145, 146, 147, 152, 153, 155, 156, 157, 158, 177, 178, + 179, 180, 182, 183, 184, 185, 187, 198, 199, 201, 203, 206, 209, 210, 211, 216, 217, 219, 220, 221, + 222, 225, 226, 227, 228, 230, 231, 236, 237, 238, 274, 275, 280, 283, 284, 286, 289, 290, 291, 292, + 294, 295, 300, 301, 302, 305, 306, 308, 310, 311, 312, 313, 315, 388, 390, 391, 392, 393, 395, 396, + 397, 398, 402, 403, 408, 411, 412, 414, 433, 434, 435, 436, 438, 439, 440, 441, 443, 452, 454, 455, + 456, 457, 459, 461, 462, 466, 467, 472, 475, 476, 478, 481, 482, 483, 484, 486, 487, 492, 493, 494, + 529, 530, 531, 536, 537, 539, 540, 541, 542, 545, 547, 548, 551, 556, 557, 561, 562, 563, 564, 566, + 567, 568, 569, 571, 580, 582, 583, 584, 585, 587, 588, 589, 590, 609, 611, 612, 615, 620, 621, 625, + 626, 627, 628, 630, 631, 632, 633, 635, 708, 710, 711, 712, 713, 715, 716, 717, 718, 721, 722, 723, + 728, 729, 731, 732, 733, 734, 737, 739, 740, 743, 748, 749, 785, 786, 787, 792, 793, 795, 796, 797, + 798, 801, 802, 803, 804, 806, 807, 812, 813, 814, 818, 820, 822, 824, 825, 836, 838, 839, 840, 841, + 843, 844, 845, 846, 865, 866, 867, 868, 870, 871, 876, 877, 878, 881, 882, 884, 886, 888, 889, 900, + 902, 903, 904, 905, 907, 908, 909, 910, 913, 914, 915, 920, 921, 923, 924, 925, 926, 945, 946, 948, + 950, 952, 953, 750, 546, + ]; + + /// + /// Gets the upper tile of the data column count code for each count of tile columns beyond the fewest the height allows, 0 to 31, coded as + /// . + /// + private static ReadOnlySpan CountUpper => + [ + 147, 211, 99, 227, 115, 179, 71, 135, 199, 103, 231, 119, 75, 203, 155, 123, + 145, 209, 97, 225, 113, 177, 73, 137, 201, 217, 121, 185, 77, 141, 157, 109, + ]; + + /// + /// Gets the lower tile of the data column count code for each count of tile columns beyond the fewest the height allows, 0 to 31, coded as + /// . + /// + private static ReadOnlySpan CountLower => + [ + 466, 482, 434, 390, 454, 294, 486, 310, 438, 398, 462, 286, 414, 478, 302, 494, + 786, 914, 866, 818, 902, 806, 870, 822, 886, 950, 846, 910, 798, 926, 814, 878, + ]; + + /// + /// Builds an Ultracode symbol. + /// + /// The text, characters U+0000 to U+00FF. + /// The error correction level. + /// The X dimension in millimetres. + /// The symbol to write. + /// The text is empty, too long, has a character above U+00FF, or does not fit at the level. + public static void BuildSymbol(ReadOnlySpan text, UltracodeErrorCorrectionLevel level, double xDimension, ref BarcodeSymbol symbol) + { + Guard.MustBeBetweenOrEqualTo(text.Length, 1, MaximumLength, nameof(text)); + int invalid = text.IndexOfAnyExceptInRange('\0', 'ÿ'); + if (invalid >= 0) + { + throw new ArgumentException($"Ultracode encodes U+0000 to U+00FF; got {text.CodePointAt(invalid).ToDisplayString()}.", nameof(text)); + } + + // The message is the start codeword, the message codeword count, the auxiliary codeword count and the data, one codeword a character. The check + // codewords grow with the message, and the auxiliary codewords are three fewer than the check codewords. + int messageCount = text.Length + MessageHeader; + int checkCount = (CheckFactors[(int)level] * ((messageCount + 24) / 25)) + 5; + int auxiliaryCount = checkCount - 3; + int tileCount = messageCount + checkCount; + + // The height is the first whose tile range holds the tiles. + if (!SymbolHeight.TryFind(tileCount, out SymbolHeight height)) + { + throw new ArgumentException("The text does not fit in an Ultracode symbol at the requested error correction level.", nameof(text)); + } + + // The width is the fewest tile columns that hold every tile with the three structural tiles; the tiles left over are pads. At MaximumColumns, 58 data + // columns, the heights of 2 to 5 tile rows hold 113, 171, 229 and 287 tiles, at least the most tiles of each height, so the loop ends by then. + int tileColumns = height.FewestColumns; + int pads = (DataColumns(tileColumns) * height.TileRows) - StructuralTiles - tileCount; + while (pads < 0) + { + tileColumns++; + pads = (DataColumns(tileColumns) * height.TileRows) - StructuralTiles - tileCount; + } + + // The message is three header codewords and then one codeword a character, so the division reads it from the text and needs no buffer. The check + // codewords are at most 101: the factor of level EC5, 8, times the 12 started groups of 25 message codewords that 282 tiles allow, plus 5. + InlineArray4 headerStorage = default; + Span header = ((Span)headerStorage)[..MessageHeader]; + header[0] = StartLatin1; + header[1] = messageCount; + header[2] = auxiliaryCount; + InlineArray128 checkStorage = default; + Span check = ((Span)checkStorage)[..checkCount]; + AddErrorCorrection(header, text, check); + + int rows = (height.TileRows * RowPitch) + 1; + int columns = tileColumns + LeftColumns + 1; + TileGrid grid = new(symbol.BeginColored(columns, rows, QuietZone, xDimension), columns, rows); + grid.DrawStructure(); + grid.DrawColumnCount(tileColumns - height.FewestColumns); + + // The tiles run down each tile column, then on to the next: the start codeword, the message codeword count, the check codewords, the tile count, + // the separator, the auxiliary codeword count, the data, the pads and the check codeword count. + grid.Write(StartLatin1); + grid.Write(messageCount); + foreach (int codeword in check) + { + grid.Write(codeword); + } + + grid.Write(tileCount); + grid.Write(Separator); + grid.Write(auxiliaryCount); + foreach (char c in text) + { + grid.Write(c); + } + + for (int p = 0; p < pads; p++) + { + grid.Write(Pad); + } + + grid.Write(checkCount); + } + + /// + /// Returns the tile columns of a width that hold tiles. The vertical timing columns stand at tile columns 15, 31 and 47, one every 16 from 15, and hold + /// none. + /// + /// The tile columns, timing columns included. + /// The tile columns that hold tiles. + private static int DataColumns(int tileColumns) => tileColumns - ((tileColumns + 1) / TimingSpacing); + + /// + /// Writes the Reed-Solomon check codewords of the message over the prime field of 283. The message is the header codewords followed by one codeword a + /// character, the first codeword the highest term. + /// + /// The header codewords. + /// The characters, each one codeword. + /// The check codewords to write, the first one first. + private static void AddErrorCorrection(ReadOnlySpan header, ReadOnlySpan text, Span check) + { + InlineArray128 generatorStorage = default; + Span generator = ((Span)generatorStorage)[..(check.Length + 1)]; + Generator(generator); + check.Clear(); + foreach (int codeword in header) + { + Divide(codeword, generator, check); + } + + foreach (char c in text) + { + Divide(c, generator, check); + } + } + + /// + /// Feeds one codeword into the division of the message polynomial times x^k by the generator, run as a shift register: the codeword feeds back its + /// difference from the first register value, and the register shifts down by one place with the generator terms added in. After the last codeword the + /// register holds the check codewords. + /// + /// The codeword. + /// The generator coefficients, the coefficient of x^0 first. + /// The register, the check codewords. + private static void Divide(int codeword, ReadOnlySpan generator, Span register) + { + int k = register.Length; + int feedback = (codeword - register[0] + Modulus) % Modulus; + for (int j = 0; j < k - 1; j++) + { + register[j] = (register[j + 1] + (generator[k - 1 - j] * feedback)) % Modulus; + } + + register[k - 1] = (generator[0] * feedback) % Modulus; + } + + /// + /// Writes the generator coefficients of k check codewords, the coefficient of x^0 first. The polynomial is the product of (x + 3^i) for i = 1 to k over + /// the field of 283, without its leading 1, with the sign of every second coefficient from the top turned. + /// + /// The buffer of k + 1 coefficients; the first k receive the result. + private static void Generator(Span generator) + { + // Each factor (x + a) moves every coefficient up one degree and adds a times the coefficient below it, from the top down, so each update reads the + // coefficient below before that coefficient changes. + int k = generator.Length - 1; + generator.Clear(); + generator[0] = 1; + int root = 1; + for (int i = 1; i <= k; i++) + { + root = (root * 3) % Modulus; + generator[i] = generator[i - 1]; + for (int j = i - 1; j >= 1; j--) + { + generator[j] = ((generator[j] * root) + generator[j - 1]) % Modulus; + } + + generator[0] = (generator[0] * root) % Modulus; + } + + // The loop negates the coefficients of x^(k-1), x^(k-3) and so on. This turns the product of (x + a) into the product of (x - a) for the terms that the + // division uses. + for (int j = k - 1; j >= 0; j -= 2) + { + generator[j] = (Modulus - generator[j]) % Modulus; + } + } + + /// + /// One symbol height of . + /// + private readonly struct SymbolHeight + { + /// + /// Initializes a new instance of the struct from one entry of . + /// + /// The index of the height, 0 to 3. + private SymbolHeight(int index) + { + ReadOnlySpan values = Heights.Slice(index * HeightFields, HeightFields); + this.TileRows = values[0]; + this.FewestTiles = values[1]; + this.MostTiles = values[2]; + this.FewestColumns = values[3]; + } + + /// + /// Gets the tile rows. + /// + public int TileRows { get; } + + /// + /// Gets the fewest tiles a symbol of this height holds. + /// + public int FewestTiles { get; } + + /// + /// Gets the most tiles a symbol of this height holds. + /// + public int MostTiles { get; } + + /// + /// Gets the fewest tile columns, timing columns included. + /// + public int FewestColumns { get; } + + /// + /// Finds the first height whose tile range holds a tile count. + /// + /// The tiles of the message and its check codewords. + /// The height. + /// when a height holds the tiles. + public static bool TryFind(int tileCount, out SymbolHeight height) + { + for (int i = 0; i < Heights.Length / HeightFields; i++) + { + height = new SymbolHeight(i); + if (tileCount >= height.FewestTiles && tileCount <= height.MostTiles) + { + return true; + } + } + + height = default; + return false; + } + } + + /// + /// Writes the modules of an Ultracode symbol: the structure of finder and timing patterns, the data column count code and the tiles in order. + /// + private ref struct TileGrid + { + /// + /// The module colours, row-major. + /// + private readonly Span modules; + + /// + /// The number of columns. + /// + private readonly int columns; + + /// + /// The number of rows. + /// + private readonly int rows; + + /// + /// The column of the next tile. + /// + private int x; + + /// + /// The row of the top module of the next tile. + /// + private int y; + + /// + /// Initializes a new instance of the struct. + /// + /// The module colours, all light. + /// The number of columns. + /// The number of rows. + public TileGrid(Span modules, int columns, int rows) + { + this.modules = modules; + this.columns = columns; + this.rows = rows; + this.x = LeftColumns; + this.y = 1; + } + + /// + /// Draws the black edges, the horizontal timing rows between the tile rows, the vertical timing columns and the finder at the left. A timing pattern + /// alternates dark and light; the vertical patterns are drawn last, so they cross the horizontal ones. + /// + public readonly void DrawStructure() + { + int columns = this.columns; + int rows = this.rows; + for (int x = 0; x < columns; x++) + { + if (x >= LeftColumns) + { + BarcodeModuleColor timing = (x & 1) == 1 ? BarcodeModuleColor.Dark : BarcodeModuleColor.Light; + for (int y = 0; y < rows; y += RowPitch) + { + this.Set(x, y, timing); + } + } + + this.Set(x, 0, BarcodeModuleColor.Dark); + this.Set(x, rows - 1, BarcodeModuleColor.Dark); + } + + for (int y = 1; y < rows - 1; y++) + { + BarcodeModuleColor timing = (y & 1) == 0 ? BarcodeModuleColor.Dark : BarcodeModuleColor.Light; + for (int x = FirstTimingColumn; x < columns; x += TimingSpacing) + { + this.Set(x, y, timing); + } + + // The finder: a black edge, a timing column, a light column, a black column and a light column before the first tile column. + this.Set(0, y, BarcodeModuleColor.Dark); + this.Set(1, y, timing); + this.Set(2, y, BarcodeModuleColor.Light); + this.Set(3, y, BarcodeModuleColor.Dark); + this.Set(4, y, BarcodeModuleColor.Light); + this.Set(columns - 1, y, BarcodeModuleColor.Dark); + } + } + + /// + /// Draws the data column count code in the middle of column 2: the upper tile, a light module and the lower tile of the count of tile columns beyond + /// the fewest the height allows. + /// + /// The tile columns beyond the fewest, 0 to 31. + public readonly void DrawColumnCount(int extraColumns) + { + int top = (this.rows / 2) - TileHeight; + this.DrawTile(CountColumn, top, CountUpper[extraColumns]); + this.Set(CountColumn, top + TileHeight, BarcodeModuleColor.Light); + this.DrawTile(CountColumn, top + TileHeight + 1, CountLower[extraColumns]); + } + + /// + /// Writes the tile of a codeword at the next place: down the tile column, past each horizontal timing row, then to the top of the next tile column, + /// past each vertical timing column. + /// + /// The codeword, 0 to 284. + public void Write(int codeword) + { + this.DrawTile(this.x, this.y, Tiles[codeword]); + this.y += TileHeight; + if (this.y != this.rows - 1) + { + this.y++; + return; + } + + this.x++; + this.y = 1; + if ((this.x - FirstTimingColumn) % TimingSpacing == 0) + { + this.x++; + } + } + + /// + /// Draws a tile of five modules down from a cell. Each module's 2-bit code counts from cyan, so the colour is cyan plus the code. + /// + /// The column. + /// The row of the top module. + /// The five module colours, the top module in the highest bits. + private readonly void DrawTile(int x, int y, int tile) + { + for (int k = 0; k < TileHeight; k++) + { + int code = (tile >> ((TileHeight - 1 - k) * ColorBits)) & ((1 << ColorBits) - 1); + this.Set(x, y + k, BarcodeModuleColor.Cyan + (byte)code); + } + } + + /// + /// Sets the colour of a module. + /// + /// The column. + /// The row. + /// The colour. + private readonly void Set(int x, int y, BarcodeModuleColor color) => this.modules[(y * this.columns) + x] = color; + } +} diff --git a/src/ImageSharp.Drawing/Barcodes/Ultracode/UltracodeErrorCorrectionLevel.cs b/src/ImageSharp.Drawing/Barcodes/Ultracode/UltracodeErrorCorrectionLevel.cs new file mode 100644 index 00000000..deeb4fea --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Ultracode/UltracodeErrorCorrectionLevel.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The error correction levels of Ultracode. A higher level adds more check codewords for each 25 message codewords, so it recovers a larger part of a +/// damaged symbol and the symbol grows. +/// +public enum UltracodeErrorCorrectionLevel +{ + /// + /// Level EC1, one check codeword for each 25 message codewords, plus 5. + /// + EC1, + + /// + /// Level EC2, two check codewords for each 25 message codewords, plus 5. + /// + EC2, + + /// + /// Level EC3, four check codewords for each 25 message codewords, plus 5. + /// + EC3, + + /// + /// Level EC4, six check codewords for each 25 message codewords, plus 5. + /// + EC4, + + /// + /// Level EC5, eight check codewords for each 25 message codewords, plus 5. + /// + EC5, +} diff --git a/src/ImageSharp.Drawing/Barcodes/Ultracode/UltracodeSymbology.cs b/src/ImageSharp.Drawing/Barcodes/Ultracode/UltracodeSymbology.cs new file mode 100644 index 00000000..65f2fce2 --- /dev/null +++ b/src/ImageSharp.Drawing/Barcodes/Ultracode/UltracodeSymbology.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Barcodes; + +/// +/// The Ultracode symbology: a colour matrix of two to five rows of tiles between black and white timing patterns, with a quiet zone of one module on every +/// side. Each tile is five modules in cyan, magenta, yellow and green. It carries the characters U+0000 to U+00FF with Reed-Solomon error correction. The +/// black modules take and the white modules show ; the tile colours are fixed. +/// +public sealed class UltracodeSymbology : BarcodeSymbology +{ + /// + /// Initializes a new instance of the class at error correction level EC2. + /// + public UltracodeSymbology() + : this(UltracodeErrorCorrectionLevel.EC2) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error correction level. + public UltracodeSymbology(UltracodeErrorCorrectionLevel errorCorrectionLevel) + { + int level = (int)errorCorrectionLevel; + Guard.MustBeBetweenOrEqualTo(level, (int)UltracodeErrorCorrectionLevel.EC1, (int)UltracodeErrorCorrectionLevel.EC5, nameof(errorCorrectionLevel)); + this.ErrorCorrectionLevel = errorCorrectionLevel; + } + + /// + /// Gets the error correction level. + /// + public UltracodeErrorCorrectionLevel ErrorCorrectionLevel { get; } + + /// + internal override void Encode(ReadOnlySpan text, BarcodeOptions options, ref BarcodeSymbol symbol) + { + double xDimension = options.ResolveXDimension(0, DefaultMatrixXDimension, double.PositiveInfinity); + UltracodeEncoder.BuildSymbol(text, this.ErrorCorrectionLevel, xDimension, ref symbol); + } +} diff --git a/src/ImageSharp.Drawing/Helpers/CodePointExtensions.cs b/src/ImageSharp.Drawing/Helpers/CodePointExtensions.cs new file mode 100644 index 00000000..311d72af --- /dev/null +++ b/src/ImageSharp.Drawing/Helpers/CodePointExtensions.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using SixLabors.Fonts.Unicode; + +namespace SixLabors.ImageSharp.Drawing.Helpers; + +/// +/// Extension methods for . +/// +internal static class CodePointExtensions +{ + /// + /// Describes the code point for a reader: its value, then the character it prints as. A value that is + /// not a valid Unicode scalar has no character, so the replacement character stands in for it. + /// + /// The code point to describe. + /// The description, in the form U+0041 'A'. + public static string ToDisplayString(this CodePoint codePoint) + => FormattableString.Invariant(FormattableStringFactory.Create( + "U+{0:X4} '{1}'", + codePoint.Value, + CodePoint.IsValid(codePoint.Value) ? codePoint.ToString() : "\ufffd")); + + /// + /// Returns a value indicating whether the code point is one of the ASCII digits 0 to 9. + /// CodePoint.IsDigit is wider: outside ASCII it takes any code point of the decimal digit + /// category, which no barcode symbology encodes. + /// + /// The code point to test. + /// if the code point is an ASCII digit; otherwise . + public static bool IsAsciiDigit(this CodePoint codePoint) + => codePoint.IsAscii && char.IsAsciiDigit((char)codePoint.Value); + + /// + /// Returns the code point that starts at an index of the text: a surrogate pair as the one code point it + /// encodes, and a lone surrogate as itself. + /// + /// The text. + /// The index of the first UTF-16 code unit of the code point. + /// The code point. + public static CodePoint CodePointAt(this ReadOnlySpan text, int index) + { + SpanCodePointEnumerator codePoints = text[index..].EnumerateCodePoints(); + codePoints.MoveNext(); + return codePoints.Current; + } +} diff --git a/src/ImageSharp.Drawing/Helpers/InlineArray.cs b/src/ImageSharp.Drawing/Helpers/InlineArray.cs new file mode 100644 index 00000000..448780c2 --- /dev/null +++ b/src/ImageSharp.Drawing/Helpers/InlineArray.cs @@ -0,0 +1,227 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Drawing; + +/// +/// Represents a safe, fixed sized buffer of 1 elements. +/// +[InlineArray(1)] +internal struct InlineArray1 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 2 elements. +/// +[InlineArray(2)] +internal struct InlineArray2 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 3 elements. +/// +[InlineArray(3)] +internal struct InlineArray3 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 4 elements. +/// +[InlineArray(4)] +internal struct InlineArray4 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 5 elements. +/// +[InlineArray(5)] +internal struct InlineArray5 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 6 elements. +/// +[InlineArray(6)] +internal struct InlineArray6 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 7 elements. +/// +[InlineArray(7)] +internal struct InlineArray7 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 8 elements. +/// +[InlineArray(8)] +internal struct InlineArray8 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 10 elements. +/// +[InlineArray(10)] +internal struct InlineArray10 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 12 elements. +/// +[InlineArray(12)] +internal struct InlineArray12 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 14 elements. +/// +[InlineArray(14)] +internal struct InlineArray14 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 16 elements. +/// +[InlineArray(16)] +internal struct InlineArray16 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 18 elements. +/// +[InlineArray(18)] +internal struct InlineArray18 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 19 elements. +/// +[InlineArray(19)] +internal struct InlineArray19 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 24 elements. +/// +[InlineArray(24)] +internal struct InlineArray24 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 25 elements. +/// +[InlineArray(25)] +internal struct InlineArray25 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 26 elements. +/// +[InlineArray(26)] +internal struct InlineArray26 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 32 elements. +/// +[InlineArray(32)] +internal struct InlineArray32 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 36 elements. +/// +[InlineArray(36)] +internal struct InlineArray36 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 64 elements. +/// +[InlineArray(64)] +internal struct InlineArray64 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 65 elements. +/// +[InlineArray(65)] +internal struct InlineArray65 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 68 elements. +/// +[InlineArray(68)] +internal struct InlineArray68 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 128 elements. +/// +[InlineArray(128)] +internal struct InlineArray128 +{ + private T t; +} + +/// +/// Represents a safe, fixed sized buffer of 256 elements. +/// +[InlineArray(256)] +internal struct InlineArray256 +{ + private T t; +} + + diff --git a/src/ImageSharp.Drawing/Helpers/InlineArray.tt b/src/ImageSharp.Drawing/Helpers/InlineArray.tt new file mode 100644 index 00000000..15a5daf0 --- /dev/null +++ b/src/ImageSharp.Drawing/Helpers/InlineArray.tt @@ -0,0 +1,38 @@ +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Drawing; + +<#GenerateInlineArrays();#> + +<#+ +private static int[] Lengths = [1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 18, 19, 24, 25, 26, 32, 36, 64, 65, 68, 128, 256]; + +void GenerateInlineArrays() +{ + foreach (int length in Lengths) + { +#> +/// +/// Represents a safe, fixed sized buffer of <#=length#> elements. +/// +[InlineArray(<#=length#>)] +internal struct InlineArray<#=length#> +{ + private T t; +} + +<#+ + } +} +#> diff --git a/src/ImageSharp.Drawing/Helpers/PooledBuffer.cs b/src/ImageSharp.Drawing/Helpers/PooledBuffer.cs new file mode 100644 index 00000000..72159c9f --- /dev/null +++ b/src/ImageSharp.Drawing/Helpers/PooledBuffer.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Drawing.Helpers; + +/// +/// A buffer of unmanaged values in an array rented from the shared pool. Every element type +/// rents from that one pool, so the pool caches one set of arrays for the library instead of one set for +/// each element type. The values start undefined: the pool hands out arrays it has used before, and a new +/// array is allocated without clearing. +/// +/// The buffer stores the array and its length, not a span, so a struct can hold it as a field. A copy shares +/// the array, so exactly one owner disposes it. +/// +/// +/// The element type. +internal struct PooledBuffer : IDisposable + where T : unmanaged +{ + /// + /// The rented array, or once the buffer is returned or when it was never rented. + /// + private byte[]? rented; + + /// + /// The number of elements. + /// + private int length; + + /// + /// Initializes a new instance of the struct. + /// + /// The number of elements. + public PooledBuffer(int length) + { + this.rented = ArrayPool.Shared.Rent(length * Unsafe.SizeOf()); + this.length = length; + } + + /// + /// Gets the elements. The span is empty when the buffer was never rented or is returned. + /// + public readonly Span Span => MemoryMarshal.Cast(this.rented.AsSpan(0, this.length * Unsafe.SizeOf())); + + /// + /// Returns the array to the shared pool. The buffer is empty afterwards, so a caller that keeps it cannot + /// write into an array that another caller now owns. + /// + public void Dispose() + { + byte[]? toReturn = this.rented; + this = default; + if (toReturn is not null) + { + ArrayPool.Shared.Return(toReturn); + } + } +} diff --git a/src/ImageSharp.Drawing/Helpers/ValueListBuilder.cs b/src/ImageSharp.Drawing/Helpers/ValueListBuilder.cs new file mode 100644 index 00000000..9c0cb602 --- /dev/null +++ b/src/ImageSharp.Drawing/Helpers/ValueListBuilder.cs @@ -0,0 +1,170 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Drawing.Helpers; + +/// +/// Builds a list of values in a buffer the caller owns. The builder starts in that buffer, which is small +/// enough to stack allocate, and moves to a pooled array only when the list outgrows it, so the common case +/// allocates nothing and the worst case rents rather than allocates. +/// +/// The builder owns the rented array, so every instance must be disposed. The array comes from the shared +/// pool, which every element type shares, so the values are unmanaged. +/// +/// +/// The type of the values. +internal ref struct ValueListBuilder + where T : unmanaged +{ + private byte[]? pooled; + private Span values; + private int position; + + /// + /// Initializes a new instance of the struct. + /// + /// The buffer to build in until the list outgrows it. + public ValueListBuilder(Span initialBuffer) + { + this.pooled = null; + this.values = initialBuffer; + this.position = 0; + } + + /// + /// Initializes a new instance of the struct that builds in a pooled + /// array from the start. + /// + /// The number of values to rent room for. + public ValueListBuilder(int initialCapacity) + { + this.pooled = ArrayPool.Shared.Rent(initialCapacity * Unsafe.SizeOf()); + this.values = MemoryMarshal.Cast(this.pooled.AsSpan()); + this.position = 0; + } + + /// + /// Gets the number of values written so far. + /// + public int Length => this.position; + + /// + /// Gets the number of values the builder holds before it has to grow. + /// + public int Capacity => this.values.Length; + + /// + /// Gets a reference to the value at the given index. + /// + /// The index of the value, below . + /// A reference to the value. + public ref T this[int index] => ref this.values[index]; + + /// + /// Appends a single value. + /// + /// The value to append. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(T value) + { + int pos = this.position; + Span destination = this.values; + if ((uint)pos < (uint)destination.Length) + { + destination[pos] = value; + this.position = pos + 1; + return; + } + + this.Grow(1); + this.values[this.position++] = value; + } + + /// + /// Appends a run of values. + /// + /// The values to append. + public void Append(scoped ReadOnlySpan source) + { + if (this.position > this.values.Length - source.Length) + { + this.Grow(source.Length); + } + + source.CopyTo(this.values[this.position..]); + this.position += source.Length; + } + + /// + /// Reserves a run of values at the end of the list and returns it for the caller to write into. + /// + /// The number of values to reserve. + /// The reserved values, whose contents are undefined until the caller writes them. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span AppendSpan(int length) + { + int start = this.position; + if (start > this.values.Length - length) + { + this.Grow(length); + } + + this.position = start + length; + return this.values.Slice(start, length); + } + + /// + /// Returns the values written so far, without copying them. + /// + /// The values written so far. + public readonly Span AsSpan() => this.values[..this.position]; + + /// + /// Returns the pooled array, if the builder rented one. An array of references is cleared first so + /// the pool does not keep them alive. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + byte[]? toReturn = this.pooled; + + // Clearing the whole builder stops a caller who keeps using a disposed instance from writing into + // an array another caller now owns. + this = default; + if (toReturn is not null) + { + ArrayPool.Shared.Return(toReturn); + } + } + + /// + /// Moves the list into a pooled array large enough for the values already written plus the ones + /// about to be, doubling where that is larger so a run of appends does not grow every time. + /// + /// The number of values needed beyond those already written. + [MethodImpl(MethodImplOptions.NoInlining)] + private void Grow(int additionalCapacity) + { + // The byte length of the new array must fit an array, so the element count stops at the largest + // array length divided by the element size. + int newCapacity = (int)Math.Max( + (uint)(this.position + additionalCapacity), + Math.Min((uint)this.values.Length * 2, (uint)(Array.MaxLength / Unsafe.SizeOf()))); + + byte[] rented = ArrayPool.Shared.Rent(newCapacity * Unsafe.SizeOf()); + Span grown = MemoryMarshal.Cast(rented.AsSpan()); + this.values[..this.position].CopyTo(grown); + + byte[]? toReturn = this.pooled; + this.pooled = rented; + this.values = grown; + if (toReturn is not null) + { + ArrayPool.Shared.Return(toReturn); + } + } +} diff --git a/src/ImageSharp.Drawing/Helpers/ValueStringBuilder.cs b/src/ImageSharp.Drawing/Helpers/ValueStringBuilder.cs new file mode 100644 index 00000000..09a47dfa --- /dev/null +++ b/src/ImageSharp.Drawing/Helpers/ValueStringBuilder.cs @@ -0,0 +1,202 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Drawing.Helpers; + +/// +/// Builds a string in a buffer the caller owns. The builder starts in that buffer, which is small enough +/// to stack allocate, and moves to a pooled array only when the text outgrows it, so the common case +/// allocates nothing and the worst case rents rather than allocates. +/// +/// The builder owns the rented array, so every instance must be disposed. disposes +/// it, so a builder finished with needs no further call. The array comes from the +/// shared pool, which every element type shares. +/// +/// +internal ref struct ValueStringBuilder +{ + /// + /// The rented array, or while the builder uses the initial buffer. + /// + private byte[]? pooled; + + /// + /// The characters of the buffer in use. + /// + private Span characters; + + /// + /// The number of characters written. + /// + private int position; + + /// + /// Initializes a new instance of the struct. + /// + /// The buffer to build in until the text outgrows it. + public ValueStringBuilder(Span initialBuffer) + { + this.pooled = null; + this.characters = initialBuffer; + this.position = 0; + } + + /// + /// Initializes a new instance of the struct that builds in a pooled + /// array from the start. + /// + /// The number of characters to rent room for. + public ValueStringBuilder(int initialCapacity) + { + this.pooled = ArrayPool.Shared.Rent(initialCapacity * sizeof(char)); + this.characters = MemoryMarshal.Cast(this.pooled.AsSpan()); + this.position = 0; + } + + /// + /// Gets the number of characters written so far. + /// + public int Length => this.position; + + /// + /// Gets the number of characters the builder holds before it has to grow. + /// + public int Capacity => this.characters.Length; + + /// + /// Gets a reference to the character at the given index. + /// + /// The index of the character. + /// A reference to the character. + public ref char this[int index] => ref this.characters[index]; + + /// + /// Appends a single character. + /// + /// The character to append. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(char value) + { + int pos = this.position; + Span destination = this.characters; + if ((uint)pos < (uint)destination.Length) + { + destination[pos] = value; + this.position = pos + 1; + return; + } + + this.Grow(1); + this.characters[this.position++] = value; + } + + /// + /// Appends a run of characters. + /// + /// The characters to append. + public void Append(scoped ReadOnlySpan value) + { + if (this.position > this.characters.Length - value.Length) + { + this.Grow(value.Length); + } + + value.CopyTo(this.characters[this.position..]); + this.position += value.Length; + } + + /// + /// Reserves a run of characters at the end of the text and returns it for the caller to write into. + /// + /// The number of characters to reserve. + /// The reserved characters, whose contents are undefined until the caller writes them. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span AppendSpan(int length) + { + int start = this.position; + if (start > this.characters.Length - length) + { + this.Grow(length); + } + + this.position = start + length; + return this.characters.Slice(start, length); + } + + /// + /// Grows the buffer so it holds at least the given number of characters. + /// + /// The number of characters the buffer must hold. + public void EnsureCapacity(int capacity) + { + if ((uint)capacity > (uint)this.characters.Length) + { + this.Grow(capacity - this.position); + } + } + + /// + /// Returns the text written so far, without copying it. + /// + /// The text written so far. + public readonly ReadOnlySpan AsSpan() => this.characters[..this.position]; + + /// + /// Returns the text written so far as a string and disposes the builder. + /// + /// The text written so far. + public override string ToString() + { + string result = this.characters[..this.position].ToString(); + this.Dispose(); + return result; + } + + /// + /// Returns the pooled array, if the builder rented one. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + byte[]? toReturn = this.pooled; + + // Clearing the whole builder stops a caller who keeps using a disposed instance from writing into + // an array another caller now owns. + this = default; + if (toReturn is not null) + { + ArrayPool.Shared.Return(toReturn); + } + } + + /// + /// Moves the text into a pooled array large enough for the characters already written plus the ones + /// about to be, doubling where that is larger so a run of appends does not grow every time. + /// + /// The number of characters needed beyond those already written. + [MethodImpl(MethodImplOptions.NoInlining)] + private void Grow(int additionalCapacity) + { + // The byte length of the new array must fit an array, so the character count stops at half the + // largest array length. + int newCapacity = (int)Math.Max( + (uint)(this.position + additionalCapacity), + Math.Min((uint)this.characters.Length * 2, (uint)(Array.MaxLength / sizeof(char)))); + + byte[] rented = ArrayPool.Shared.Rent(newCapacity * sizeof(char)); + Span grown = MemoryMarshal.Cast(rented.AsSpan()); + this.characters[..this.position].CopyTo(grown); + + byte[]? toReturn = this.pooled; + this.pooled = rented; + this.characters = grown; + if (toReturn is not null) + { + ArrayPool.Shared.Return(toReturn); + } + } +} diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj index f09f9b7a..5c205032 100644 --- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj +++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj @@ -48,6 +48,23 @@ + + + + + + + True + True + InlineArray.tt + + + + + TextTemplatingFileGenerator + InlineArray.cs + +