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}