base65536/
lib.rs

1// Copyright 2017-2019 Emma Welker (nuew)
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//  http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! A binary encoding optimized for UTF-32/UCS-4 encoded text and Twitter.
16//!
17//! This is a Rust reimplementation of [qntm]'s original [base65536].
18//!
19//! # Examples
20//!
21//! Decoding:
22//!
23//! ```rust
24//! # fn test() -> Result<(), Box<std::error::Error>> {
25//! use base65536::decode;
26//!
27//! // don't ignore garbage - note that this means that word wrapping doesn't work
28//! assert_eq!(vec![1, 2, 3], decode("㘁ᔃ", false)?);
29//! assert_eq!("hello world", String::from_utf8(decode("驨ꍬ啯𒁷ꍲᕤ", false)?)?);
30//!
31//! // ignore garbage
32//! assert_eq!(vec![1, 2, 3], decode("㘁asdfghjklᔃ", true)?);
33//! assert_eq!("hello world", String::from_utf8(decode("驨ꍬ啯𒁷ꍲᕤ\n", true)?)?);
34//! # Ok(()) }
35//! # test().unwrap();
36//! ```
37//!
38//! Encoding:
39//!
40//! ```rust
41//! use base65536::{WrapOptions, encode};
42//!
43//! // no word wrapping
44//! assert_eq!("㘁ᔃ", encode(&[1, 2, 3], None));
45//! assert_eq!("驨ꍬ啯𒁷ꍲᕤ", encode("hello world", None));
46//!
47//! // word wrapping
48//! assert_eq!("㘁\nᔃ", encode(&[1, 2, 3], 1));
49//! assert_eq!("驨ꍬ啯\n𒁷ꍲᕤ", encode("hello world", 3));
50//!
51//! // word wrapping with a custom line ending
52//! assert_eq!("㘁\r\nᔃ", encode(&[1, 2, 3], WrapOptions::WrapAtWith(1, "\r\n")));
53//! assert_eq!("驨ꍬ啯\r\n𒁷ꍲᕤ", encode("hello world", WrapOptions::WrapAtWith(3, "\r\n")));
54//! ```
55//!
56//! [qntm]: https://qntm.org/
57//! [base65536]: https://github.com/qntm/base65536
58#![cfg_attr(feature = "nightly", feature(test))]
59
60#[cfg(feature = "nightly")]
61extern crate test as test_crate;
62
63#[cfg(test)]
64mod test;
65
66use lazy_static::lazy_static;
67use std::collections::HashMap;
68use std::{error, fmt};
69
70#[cfg(feature = "fnv")]
71use fnv::FnvBuildHasher as Hasher;
72
73#[cfg(not(feature = "fnv"))]
74use std::collections::hash_map::RandomState as Hasher;
75
76const PADDING_BLOCK_START: u32 = 0x1500;
77#[cfg_attr(feature = "cargo-clippy", allow(unreadable_literal))]
78const BLOCK_STARTS: &[u32] = &[
79    0x03400, 0x03500, 0x03600, 0x03700, 0x03800, 0x03900, 0x03A00, 0x03B00, 0x03C00, 0x03D00,
80    0x03E00, 0x03F00, 0x04000, 0x04100, 0x04200, 0x04300, 0x04400, 0x04500, 0x04600, 0x04700,
81    0x04800, 0x04900, 0x04A00, 0x04B00, 0x04C00, 0x04E00, 0x04F00, 0x05000, 0x05100, 0x05200,
82    0x05300, 0x05400, 0x05500, 0x05600, 0x05700, 0x05800, 0x05900, 0x05A00, 0x05B00, 0x05C00,
83    0x05D00, 0x05E00, 0x05F00, 0x06000, 0x06100, 0x06200, 0x06300, 0x06400, 0x06500, 0x06600,
84    0x06700, 0x06800, 0x06900, 0x06A00, 0x06B00, 0x06C00, 0x06D00, 0x06E00, 0x06F00, 0x07000,
85    0x07100, 0x07200, 0x07300, 0x07400, 0x07500, 0x07600, 0x07700, 0x07800, 0x07900, 0x07A00,
86    0x07B00, 0x07C00, 0x07D00, 0x07E00, 0x07F00, 0x08000, 0x08100, 0x08200, 0x08300, 0x08400,
87    0x08500, 0x08600, 0x08700, 0x08800, 0x08900, 0x08A00, 0x08B00, 0x08C00, 0x08D00, 0x08E00,
88    0x08F00, 0x09000, 0x09100, 0x09200, 0x09300, 0x09400, 0x09500, 0x09600, 0x09700, 0x09800,
89    0x09900, 0x09A00, 0x09B00, 0x09C00, 0x09D00, 0x09E00, 0x0A100, 0x0A200, 0x0A300, 0x0A500,
90    0x10600, 0x12000, 0x12100, 0x12200, 0x13000, 0x13100, 0x13200, 0x13300, 0x14400, 0x14500,
91    0x16800, 0x16900, 0x20000, 0x20100, 0x20200, 0x20300, 0x20400, 0x20500, 0x20600, 0x20700,
92    0x20800, 0x20900, 0x20A00, 0x20B00, 0x20C00, 0x20D00, 0x20E00, 0x20F00, 0x21000, 0x21100,
93    0x21200, 0x21300, 0x21400, 0x21500, 0x21600, 0x21700, 0x21800, 0x21900, 0x21A00, 0x21B00,
94    0x21C00, 0x21D00, 0x21E00, 0x21F00, 0x22000, 0x22100, 0x22200, 0x22300, 0x22400, 0x22500,
95    0x22600, 0x22700, 0x22800, 0x22900, 0x22A00, 0x22B00, 0x22C00, 0x22D00, 0x22E00, 0x22F00,
96    0x23000, 0x23100, 0x23200, 0x23300, 0x23400, 0x23500, 0x23600, 0x23700, 0x23800, 0x23900,
97    0x23A00, 0x23B00, 0x23C00, 0x23D00, 0x23E00, 0x23F00, 0x24000, 0x24100, 0x24200, 0x24300,
98    0x24400, 0x24500, 0x24600, 0x24700, 0x24800, 0x24900, 0x24A00, 0x24B00, 0x24C00, 0x24D00,
99    0x24E00, 0x24F00, 0x25000, 0x25100, 0x25200, 0x25300, 0x25400, 0x25500, 0x25600, 0x25700,
100    0x25800, 0x25900, 0x25A00, 0x25B00, 0x25C00, 0x25D00, 0x25E00, 0x25F00, 0x26000, 0x26100,
101    0x26200, 0x26300, 0x26400, 0x26500, 0x26600, 0x26700, 0x26800, 0x26900, 0x26A00, 0x26B00,
102    0x26C00, 0x26D00, 0x26E00, 0x26F00, 0x27000, 0x27100, 0x27200, 0x27300, 0x27400, 0x27500,
103    0x27600, 0x27700, 0x27800, 0x27900, 0x27A00, 0x27B00, 0x27C00, 0x27D00, 0x27E00, 0x27F00,
104    0x28000, 0x28100, 0x28200, 0x28300, 0x28400, 0x28500,
105];
106lazy_static! {
107    static ref BLOCK_START_TO_INDEX: HashMap<u32, u8, Hasher> = (0..BLOCK_STARTS.len())
108        .map(|b| (BLOCK_STARTS[b], b as u8))
109        .collect();
110}
111
112#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
113/// Represents an error while decoding.
114///
115/// Used with [`decode`] and [`decode_buf`]. See them for examples.
116///
117/// [`decode`]: fn.decode.html
118/// [`decode_buf`]: fn.decode_buf.html
119pub enum Error {
120    /// A code point not valid in base65536 was found in the input stream.
121    /// Consider using the `ignore_garbage` option.
122    ///
123    /// Contains the offset from the beginning of the stream at which the
124    /// invalid code point was found, and the actual code point.
125    InvalidCodePoint(usize, char),
126    /// The base65536 stream continued after a terminating padding byte.
127    InvalidLength,
128}
129
130impl fmt::Display for Error {
131    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
132        match *self {
133            Error::InvalidCodePoint(offset, ch) => {
134                write!(f, "invalid code point '{}' at offset {}", ch, offset)
135            }
136            Error::InvalidLength => write!(f, "sequence continued after final byte"),
137        }
138    }
139}
140
141impl error::Error for Error {
142    fn description(&self) -> &str {
143        match *self {
144            Error::InvalidCodePoint(_, _) => "invalid code point",
145            Error::InvalidLength => "invalid length",
146        }
147    }
148}
149
150/// A specialized [`Result`] type for decoding operations.
151///
152/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html
153pub type DecodeResult<T> = ::std::result::Result<T, Error>;
154
155#[inline]
156fn inner_decode<F>(input: &str, ignore_garbage: bool, mut out: F) -> DecodeResult<()>
157where
158    F: FnMut(u8, Option<u8>),
159{
160    let mut done = false;
161    for (index, code_point) in input.char_indices() {
162        let (byte1, block_start) = {
163            const BLOCK_MASK: u32 = (1 << 8) - 1;
164            let code_point = code_point as u32;
165
166            let byte1 = code_point & BLOCK_MASK;
167            (byte1 as u8, code_point - byte1)
168        };
169
170        if block_start == PADDING_BLOCK_START {
171            if done {
172                return Err(Error::InvalidLength);
173            } else {
174                out(byte1, None);
175                done = true;
176            }
177        } else if let Some(byte2) = BLOCK_START_TO_INDEX.get(&block_start) {
178            if done {
179                return Err(Error::InvalidLength);
180            } else {
181                out(byte1, Some(*byte2));
182            }
183        } else if !ignore_garbage {
184            return Err(Error::InvalidCodePoint(index, code_point));
185        }
186    }
187
188    Ok(())
189}
190
191/// Decode from a reference to a base65536-encoded string as octets.
192///
193/// # Errors
194///
195/// If the input string contains a character not inside of a base65536 block,
196/// [`Error::InvalidCodePoint`] will be retuned, along with the bad character,
197/// and it's position in the input.
198///
199/// Note that [`decode`], [`decode_buf`], and [`decode_slice`] are *very*
200/// strict by default, even failing on line breaks (such as those generated by
201/// [`encode`] and [`encode_buf`] when wrapping is enabled),
202/// as to match behaviour with the [original implementation]. To prevent this,
203/// use with the `ignore_garbage` option.
204///
205/// If the base65536 stream continues after a terminating padding character,
206/// [`Error::InvalidLength`] is returned.
207///
208/// # Examples
209///
210/// ```rust
211/// # fn test() -> Result<(), Box<std::error::Error>> {
212/// use base65536::decode;
213///
214/// assert_eq!(vec![1, 2, 3], decode("㘁ᔃ", false)?);
215/// assert_eq!("hello world", String::from_utf8(decode("驨ꍬ啯𒁷ꍲᕤ", false)?)?);
216/// # Ok(()) }
217/// # test().unwrap();
218/// ```
219///
220/// [`decode`]: fn.decode.html
221/// [`decode_buf`]: fn.decode_buf.html
222/// [`decode_slice`]: fn.decode_slice.html
223/// [`encode`]: fn.encode.html
224/// [`encode_buf`]: fn.encode_buf.html
225/// [original implementation]: https://github.com/qntm/base65536
226/// [`Error::InvalidCodePoint`]: enum.Error.html#variant.InvalidCodePoint
227/// [`Error::InvalidLength`]: enum.Error.html#variant.InvalidLength
228pub fn decode<T>(input: &T, ignore_garbage: bool) -> DecodeResult<Vec<u8>>
229where
230    T: ?Sized + AsRef<str>,
231{
232    let mut buf = Vec::with_capacity(input.as_ref().len());
233    decode_buf(input, &mut buf, ignore_garbage).map(|_| buf)
234}
235
236/// Decode from a reference to a base65536-encoded string as octets.
237/// Writes into the supplied output buffer, growing it if needed.
238///
239/// # Errors
240///
241/// If the input string contains a character not inside of a base65536 block,
242/// [`Error::InvalidCodePoint`] will be retuned, along with the bad character,
243/// and it's position in the input.
244///
245/// Note that [`decode`], [`decode_buf`], and [`decode_slice`] are *very*
246/// strict by default, even failing on line breaks (such as those generated by
247/// [`encode`] and [`encode_buf`] when wrapping is enabled),
248/// as to match behaviour with the [original implementation]. To prevent this,
249/// use with the `ignore_garbage` option.
250///
251/// If the base65536 stream continues after a terminating padding character,
252/// [`Error::InvalidLength`] is returned.
253///
254/// # Examples
255///
256/// ```rust
257/// # fn test() -> Result<(), Box<std::error::Error>> {
258/// use base65536::decode_buf;
259///
260/// let mut buf = Vec::new();
261/// decode_buf("㘁ᔃ", &mut buf, false)?;
262/// assert_eq!(vec![1, 2, 3], buf);
263///
264/// let mut buf = Vec::new();
265/// decode_buf("驨ꍬ啯𒁷ꍲᕤ", &mut buf, false)?;
266/// assert_eq!("hello world", String::from_utf8(buf)?);
267/// # Ok(()) }
268/// # test().unwrap();
269/// ```
270///
271/// [`decode`]: fn.decode.html
272/// [`decode_buf`]: fn.decode_buf.html
273/// [`decode_slice`]: fn.decode_slice.html
274/// [`encode`]: fn.encode.html
275/// [`encode_buf`]: fn.encode_buf.html
276/// [original implementation]: https://github.com/qntm/base65536
277/// [`Error::InvalidCodePoint`]: enum.Error.html#variant.InvalidCodePoint
278/// [`Error::InvalidLength`]: enum.Error.html#variant.InvalidLength
279pub fn decode_buf<T>(input: &T, buf: &mut Vec<u8>, ignore_garbage: bool) -> DecodeResult<()>
280where
281    T: ?Sized + AsRef<str>,
282{
283    inner_decode(input.as_ref(), ignore_garbage, |a, b| {
284        buf.push(a);
285        if let Some(b) = b {
286            buf.push(b)
287        }
288    })
289}
290
291/// Decode from a reference to a base65536-encoded string as octets.
292/// Writes into the supplied slice, returning how many bytes were written.
293///
294/// # Panics
295///
296/// Panics if the slice is not long enough.
297///
298/// # Errors
299///
300/// If the input string contains a character not inside of a base65536 block,
301/// [`Error::InvalidCodePoint`] will be retuned, along with the bad character,
302/// and it's position in the input.
303///
304/// Note that [`decode`], [`decode_buf`], and [`decode_slice`] are *very*
305/// strict by default, even failing on line breaks (such as those generated by
306/// [`encode`] and [`encode_buf`] when wrapping is enabled),
307/// as to match behaviour with the [original implementation]. To prevent this,
308/// use with the `ignore_garbage` option.
309///
310/// If the base65536 stream continues after a terminating padding character,
311/// [`Error::InvalidLength`] is returned.
312///
313/// # Examples
314///
315/// ```rust
316/// # fn test() -> Result<(), Box<std::error::Error>> {
317/// use base65536::decode_slice;
318///
319/// let mut buf = [0; 3];
320/// decode_slice("㘁ᔃ", &mut buf, false)?;
321/// assert_eq!([1, 2, 3], buf);
322///
323/// let mut buf = [0; 11];
324/// decode_slice("驨ꍬ啯𒁷ꍲᕤ", &mut buf, false)?;
325/// assert_eq!(b"hello world", &buf);
326/// # Ok(()) }
327/// # test().unwrap();
328/// ```
329///
330/// [`decode`]: fn.decode.html
331/// [`decode_buf`]: fn.decode_buf.html
332/// [`decode_slice`]: fn.decode_slice.html
333/// [`encode`]: fn.encode.html
334/// [`encode_buf`]: fn.encode_buf.html
335/// [original implementation]: https://github.com/qntm/base65536
336/// [`Error::InvalidCodePoint`]: enum.Error.html#variant.InvalidCodePoint
337/// [`Error::InvalidLength`]: enum.Error.html#variant.InvalidLength
338pub fn decode_slice<T>(input: &T, buf: &mut [u8], ignore_garbage: bool) -> DecodeResult<usize>
339where
340    T: ?Sized + AsRef<str>,
341{
342    let mut pos = 0;
343    inner_decode(input.as_ref(), ignore_garbage, |a, b| {
344        buf[pos] = a;
345        pos += 1;
346        if let Some(b) = b {
347            buf[pos] = b;
348            pos += 1;
349        }
350    })
351    .map(|_| pos)
352}
353
354#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
355/// Line Wrapping Options.
356///
357/// Used with [`encode`] and [`encode_buf`]. See them for examples.
358///
359/// Unless you want to specify a custom end-of-line string, use an
360/// [`Option::None`] instead for no wrapping or an [`usize`] instead for
361/// wrapping at a column boundary, and everything will magically work.
362///
363/// [`encode`]: fn.encode.html
364/// [`encode_buf`]: fn.encode_buf.html
365/// [`Option::None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None
366/// [`usize`]: https://doc.rust-lang.org/std/primitive.usize.html
367pub enum WrapOptions<'a> {
368    /// Don't wrap lines at all
369    NoWrap,
370    /// Wrap every so many columns with '\n'. The length must be > 0.
371    WrapAt(usize),
372    /// Wrap every so many columns with a specified string. The length must be > 0.
373    WrapAtWith(usize, &'a str),
374}
375
376impl<'a, T> From<T> for WrapOptions<'a>
377where
378    T: Into<Option<usize>>,
379{
380    fn from(from: T) -> Self {
381        match from.into() {
382            Some(columns) => WrapOptions::WrapAt(columns),
383            None => WrapOptions::NoWrap,
384        }
385    }
386}
387
388/// Encode arbitrary octets as base65536.
389///
390/// The `wrap` option allows wrapping the output every so many characters,
391/// optionally with a supplied string using [`WrapOptions`].
392///
393/// Unless called with `ignore_garbage`, [`decode`] and [`decode_buf`] will
394/// fail on output generated with a wrap. This is to match behaviour with the
395/// original implementation.
396///
397/// # Panics
398///
399/// Panics if set to wrap every 0 columns.
400///
401/// # Examples
402///
403/// Without word wrapping:
404///
405/// ```rust
406/// use base65536::encode;
407///
408/// assert_eq!("驨ꍬ啯𒁷ꍲᕤ", encode("hello world", None));
409/// ```
410///
411/// With word wrapping:
412///
413/// ```rust
414/// # fn test() -> Option<()> {
415/// use base65536::encode;
416///
417/// assert_eq!(base65536::encode(
418///     "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed \
419///      do eiusmod tempor incididunt ut labore et dolore magna \
420///      aliqua. Ut enim ad minim veniam, quis nostrud exercitation \
421///      ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis \
422///      aute irure dolor in reprehenderit in voluptate velit esse \
423///      cillum dolore eu fugiat nulla pariatur. Excepteur sint \
424///      occaecat cupidatat non proident, sunt in culpa qui officia \
425///      deserunt mollit anim id est laborum.", 80)
426///     .lines().next()?.chars().count(), 80);
427/// # Some(()) }
428/// # test().unwrap()
429/// ```
430///
431/// With word wrapping using a custom line ending:
432///
433/// ```rust
434/// # fn test() -> Option<()> {
435/// use base65536::{WrapOptions, encode};
436///
437/// assert_eq!(base65536::encode(
438///     "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed \
439///      do eiusmod tempor incididunt ut labore et dolore magna \
440///      aliqua. Ut enim ad minim veniam, quis nostrud exercitation \
441///      ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis \
442///      aute irure dolor in reprehenderit in voluptate velit esse \
443///      cillum dolore eu fugiat nulla pariatur. Excepteur sint \
444///      occaecat cupidatat non proident, sunt in culpa qui officia \
445///      deserunt mollit anim id est laborum.",
446///     WrapOptions::WrapAtWith(80, "\r\n"))
447///     .lines().next()?.chars().count(), 80);
448/// # Some(()) }
449/// # test().unwrap()
450/// ```
451///
452/// [`WrapOptions`]: enum.WrapOptions.html
453/// [`decode`]: fn.decode.html
454/// [`decode_buf`]: fn.decode_buf.html
455pub fn encode<'a, T, W>(input: &T, wrap: W) -> String
456where
457    T: ?Sized + AsRef<[u8]>,
458    W: Into<WrapOptions<'a>>,
459{
460    // Some output code points are three bytes, while others are four. As every two bytes of input
461    // results in one character of output, this allocates the necessary space for the maximum
462    // possible output length, assuming that `wrap` is set to None.
463    // A reallocation may be necessary if the `wrap` option is used.
464    let mut output = String::with_capacity(input.as_ref().len() * 2);
465    encode_buf(input, &mut output, wrap);
466    output
467}
468
469/// Encode arbitrary octets as base65536. Writes into the supplied output
470/// buffer, growing it if needed.
471///
472/// The `wrap` option allows wrapping the output every so many characters,
473/// optionally with a supplied string using [`WrapOptions`].
474///
475/// Unless called with `ignore_garbage`, [`decode`] and [`decode_buf`] will
476/// fail on output generated with a wrap. This is to match behaviour with the
477/// original implementation.
478///
479/// # Panics
480///
481/// Panics if set to wrap every 0 columns.
482///
483/// # Examples
484///
485/// Without word wrapping:
486///
487/// ```rust
488/// use base65536::encode_buf;
489///
490/// let mut buf = String::new();
491/// encode_buf("hello world", &mut buf, None);
492///
493/// assert_eq!("驨ꍬ啯𒁷ꍲᕤ", buf);
494/// ```
495///
496/// With word wrapping:
497///
498/// ```rust
499/// # fn test() -> Option<()> {
500/// use base65536::encode_buf;
501///
502/// let mut buf = String::new();
503/// base65536::encode_buf(
504///     "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed \
505///      do eiusmod tempor incididunt ut labore et dolore magna \
506///      aliqua. Ut enim ad minim veniam, quis nostrud exercitation \
507///      ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis \
508///      aute irure dolor in reprehenderit in voluptate velit esse \
509///      cillum dolore eu fugiat nulla pariatur. Excepteur sint \
510///      occaecat cupidatat non proident, sunt in culpa qui officia \
511///      deserunt mollit anim id est laborum.", &mut buf, 80);
512///
513/// assert_eq!(buf.lines().next()?.chars().count(), 80);
514/// # Some(()) }
515/// # test().unwrap()
516/// ```
517///
518/// With word wrapping using a custom line ending:
519///
520/// ```rust
521/// # fn test() -> Option<()> {
522/// use base65536::{WrapOptions, encode_buf};
523///
524/// let mut buf = String::new();
525/// base65536::encode_buf(
526///     "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed \
527///      do eiusmod tempor incididunt ut labore et dolore magna \
528///      aliqua. Ut enim ad minim veniam, quis nostrud exercitation \
529///      ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis \
530///      aute irure dolor in reprehenderit in voluptate velit esse \
531///      cillum dolore eu fugiat nulla pariatur. Excepteur sint \
532///      occaecat cupidatat non proident, sunt in culpa qui officia \
533///      deserunt mollit anim id est laborum.",
534///     &mut buf,
535///     WrapOptions::WrapAtWith(80, "\r\n"));
536///
537/// assert_eq!(buf.lines().next()?.chars().count(), 80);
538/// # Some(()) }
539/// # test().unwrap()
540/// ```
541///
542/// [`WrapOptions`]: enum.WrapOptions.html
543/// [`decode`]: fn.decode.html
544/// [`decode_buf`]: fn.decode_buf.html
545pub fn encode_buf<'a, T, W>(input: &T, buf: &mut String, wrap: W)
546where
547    T: ?Sized + AsRef<[u8]>,
548    W: Into<WrapOptions<'a>>,
549{
550    let wrap = wrap.into();
551    for (count, bytes) in input.as_ref().chunks(2).enumerate() {
552        match wrap {
553            WrapOptions::NoWrap => {}
554            WrapOptions::WrapAt(columns) => {
555                if count % columns == 0 && count != 0 {
556                    buf.push('\n');
557                }
558            }
559            WrapOptions::WrapAtWith(columns, eol) => {
560                if count % columns == 0 && count != 0 {
561                    buf.push_str(eol);
562                }
563            }
564        }
565
566        let block_start = match bytes.len() {
567            1 => PADDING_BLOCK_START,
568            2 => BLOCK_STARTS[bytes[1] as usize],
569            _ => unreachable!(),
570        };
571
572        // It is safe to unwrap because we know that all code points within
573        // 0x100 of any possible block_start are defined, and that's the
574        // largest possible addition to block_start.
575        let code_point = block_start + u32::from(bytes[0]);
576        buf.push(std::char::from_u32(code_point).unwrap());
577    }
578}