+ size_t const len = s.length ();
+ int N = 0;
+ for (size_t i = 0; i < len; ++i) {
+ unsigned char c = s[i];
+ if ((c & 0xe0) == 0xc0) {
+ ++i;
+ } else if ((c & 0xf0) == 0xe0) {
+ i += 2;
+ } else if ((c & 0xf8) == 0xf0) {
+ i += 3;
+ }
+ ++N;
+ }
+ return N;
+}
+
+
+/** @param size Size of picture that the subtitle will be overlaid onto */
+void
+emit_subtitle_image (ContentTimePeriod period, dcp::SubtitleImage sub, dcp::Size size, shared_ptr<TextDecoder> decoder)
+{
+ /* XXX: this is rather inefficient; decoding the image just to get its size */
+ FFmpegImageProxy proxy (sub.png_image());
+ auto image = proxy.image(Image::Alignment::PADDED).image;
+ /* set up rect with height and width */
+ dcpomatic::Rect<double> rect(0, 0, image->size().width / double(size.width), image->size().height / double(size.height));
+
+ /* add in position */
+
+ switch (sub.h_align()) {
+ case dcp::HAlign::LEFT:
+ rect.x += sub.h_position();
+ break;
+ case dcp::HAlign::CENTER:
+ rect.x += 0.5 + sub.h_position() - rect.width / 2;
+ break;
+ case dcp::HAlign::RIGHT:
+ rect.x += 1 - sub.h_position() - rect.width;
+ break;
+ }
+
+ switch (sub.v_align()) {
+ case dcp::VAlign::TOP:
+ rect.y += sub.v_position();
+ break;
+ case dcp::VAlign::CENTER:
+ rect.y += 0.5 + sub.v_position() - rect.height / 2;
+ break;
+ case dcp::VAlign::BOTTOM:
+ rect.y += 1 - sub.v_position() - rect.height;
+ break;
+ }
+
+ decoder->emit_bitmap (period, image, rect);
+}
+
+
+/** XXX: could use mmap? */
+void
+copy_in_bits (boost::filesystem::path from, boost::filesystem::path to, std::function<void (float)> progress)
+{
+ dcp::File f(from, "rb");
+ if (!f) {
+ throw OpenFileError (from, errno, OpenFileError::READ);
+ }
+ dcp::File t(to, "wb");
+ if (!t) {
+ throw OpenFileError (to, errno, OpenFileError::WRITE);
+ }
+
+ /* on the order of a second's worth of copying */
+ boost::uintmax_t const chunk = 20 * 1024 * 1024;
+
+ std::vector<uint8_t> buffer(chunk);
+
+ auto const total = dcp::filesystem::file_size(from);
+ boost::uintmax_t remaining = total;
+
+ while (remaining) {
+ boost::uintmax_t this_time = min (chunk, remaining);
+ size_t N = f.read(buffer.data(), 1, chunk);
+ if (N < this_time) {
+ throw ReadFileError (from, errno);
+ }
+
+ N = t.write(buffer.data(), 1, this_time);
+ if (N < this_time) {
+ throw WriteFileError (to, errno);
+ }
+
+ progress (1 - float(remaining) / total);
+ remaining -= this_time;