diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt
index d2ca66adc..1064c2526 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt
@@ -21,6 +21,8 @@ import eu.kanade.tachiyomi.ui.reader.loader.DownloadPageLoader
 import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
 import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
 import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
+import eu.kanade.tachiyomi.util.lang.byteSize
+import eu.kanade.tachiyomi.util.lang.takeBytes
 import eu.kanade.tachiyomi.util.storage.DiskUtil
 import eu.kanade.tachiyomi.util.system.ImageUtil
 import java.io.File
@@ -450,7 +452,7 @@ class ReaderPresenter(
         // Build destination file.
         val filenameSuffix = " - ${page.number}.${type.extension}"
         val filename = DiskUtil.buildValidFilename(
-                "${manga.title} - ${chapter.name}".take(MAX_FILE_NAME_LENGTH - filenameSuffix.length)
+                "${manga.title} - ${chapter.name}".takeBytes(MAX_FILE_NAME_BYTES - filenameSuffix.byteSize())
         ) + filenameSuffix
 
         val destFile = File(directory, filename)
@@ -641,7 +643,7 @@ class ReaderPresenter(
     }
 
     companion object {
-        // Safe max filename size is 255 bytes and 1 char = 2 bytes
-        private const val MAX_FILE_NAME_LENGTH = 127
+        // Safe theoretical max filename size is 255 bytes and 1 char = 2-4 bytes (UTF-8)
+        private const val MAX_FILE_NAME_BYTES = 250
     }
 }
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt
index de83295a9..68d2786c9 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt
@@ -32,3 +32,24 @@ fun String.truncateCenter(count: Int, replacement: String = "..."): String {
 fun String.compareToCaseInsensitiveNaturalOrder(other: String): Int {
     return String.CASE_INSENSITIVE_ORDER.then(naturalOrder()).compare(this, other)
 }
+
+/**
+ * Returns the size of the string as the number of bytes.
+ */
+fun String.byteSize(): Int {
+    return toByteArray(Charsets.UTF_8).size
+}
+
+/**
+ * Returns a string containing the first [n] bytes from this string, or the entire string if this
+ * string is shorter.
+ */
+@UseExperimental(ExperimentalStdlibApi::class)
+fun String.takeBytes(n: Int): String {
+    val bytes = toByteArray(Charsets.UTF_8)
+    return if (bytes.size <= n) {
+        this
+    } else {
+        bytes.decodeToString(endIndex = n).replace("\uFFFD", "")
+    }
+}