Bagaimana kami belajar menggambar teks di atas kanvas

Kami sedang mengembangkan platform untuk kolaborasi visual . Kami menggunakan Canvas untuk menampilkan konten: semuanya digambar di atasnya, termasuk teks. Tidak ada solusi yang siap pakai untuk menampilkan teks pada Kanvas satu ke satu seperti dalam html. Selama beberapa tahun bekerja dengan rendering teks, kami mempelajari berbagai opsi implementasi, mengisi banyak benjolan dan, tampaknya, menemukan solusi yang baik. Saya akan memberi tahu Anda dalam sebuah artikel bagaimana kami pindah dari Flash ke Canvas dan mengapa kami meninggalkan SVG foreignObject.



Pindah dengan Flash


Kami menciptakan produk pada tahun 2015 di Flash. Di dalam Flash ada editor teks yang dapat bekerja dengan baik dengan teks, jadi kami tidak perlu melakukan apa pun ekstra untuk bekerja dengan teks. Tetapi pada saat itu Flash sudah sekarat, jadi kami pindah dari itu ke HTML / Canvas. Dan sebelum kita, tugasnya adalah untuk menampilkan teks pada Canvas seperti pada editor html, sementara tidak merusak teks yang dibuat dalam versi Flash saat memindahkan.

Kami ingin membuatnya agar pengguna dapat mengedit teks langsung di produk kami, tanpa memperhatikan transisi antara mode pengeditan dan rendering. Solusi yang kami lihat adalah ini: ketika Anda mengklik area dengan teks, editor teks terbuka di mana Anda dapat mengubah teks; Anda dapat menutup editor dengan memindahkan kursor menjauh dari area teks. Dalam hal ini, tampilan teks pada Kanvas harus 1 in 1 sesuai dengan tampilan teks di editor.

Sebagai editor, kami menggunakan pustaka terbuka, tetapi pustaka siap pakai untuk rendering dari html ke Canvas tidak cocok dengan kami dengan kecepatan kerja dan fungsionalitas yang tidak memadai.

Kami memeriksa beberapa solusi:

  • Canvas.fillText standar. Mampu menggambar teks seperti dalam html, bisa ditata, berfungsi di semua browser. Tetapi ia tidak tahu cara menggambar tautan seperti pada editor multi-baris html editor dengan format berbeda. Kesulitan-kesulitan ini dapat diatasi, tetapi membutuhkan banyak waktu;
  • Gambar DOM di atas Kanvas. Opsi tidak cocok untuk kita, karena dalam produk kami, setiap objek yang dibuat memiliki indeks-z pada kanvas. Dan mencampurnya dengan DOM z-index tidak akan bekerja.
  • Konversi html ke svg. Dia dapat mengubah html menjadi gambar berkat elemen foreignObject. Ini memungkinkan Anda memanggang html di dalam svg dan bekerja dengannya sebagai gambar. Kami telah memilih opsi ini.

Fitur SVG foreignObject


Cara kerja SVG foreignObject: kami memiliki HTML dari editor β†’ taruh HTML di foreignObject β†’ sulap β†’ dapatkan gambar β†’ tambahkan gambar ke kanvas



Tentang sihir. Terlepas dari kenyataan bahwa sebagian besar browser mendukung tag foreignObject, masing-masing memiliki karakteristik sendiri untuk menggunakan hasilnya dengan kanvas. FireFox bekerja dengan objek Blob, di Edge Anda perlu melakukan Base64 untuk gambar dan mengembalikan url data, dan di IE11 tag tidak bekerja sama sekali.

getImageUrl(svg: string, browser: string): string { let dataUrl = '' switch (browser) { case browsers.FIREFOX: let domUrl = window.URL || window.webkitURL || window let blob = new Blob([svg], {type: 'image/svg+xml;charset=utf-8'}) dataUrl = domUrl.createObjectURL(blob) break case browsers.EDGE: let encodedSvg = encodeURIComponent(svg) dataUrl = 'data:image/svg+xml;base64,' + btoa(window.unescape(encodedSvg)) break default: dataUrl = 'data:image/svg+xml,' + encodeURIComponent(svg) return dataUrl } 

Setelah bekerja dengan SVG, kami mendapat bug menarik yang tidak kami perhatikan di Flash. Teks dengan ukuran dan font yang sama di browser yang berbeda ditampilkan secara berbeda. Misalnya, kata terakhir dalam satu baris dapat dibungkus dan berjalan ke teks di bawah ini. Penting bagi kami bahwa pengguna mendapatkan jenis widget yang sama, apa pun peramban yang digunakan. Tidak ada masalah dengan Flash pada ini, karena dia sama di mana-mana.



Kami telah memecahkan masalah ini. Pertama, untuk semua teks single-line, mereka mulai selalu mempertimbangkan lebar terlepas dari browser dan data dari server. Untuk tinggi, perbedaannya tetap, tetapi dalam kasus kami itu tidak mengganggu pengguna.

Kedua, secara eksperimental kami sampai pada kesimpulan bahwa perlu menambahkan beberapa gaya css yang tidak biasa untuk editor dan svg untuk mengurangi perbedaan tampilan antara browser:

  • font-kerning: auto; mengontrol kerning font. Lebih detail
  • webkit-font-smoothing: antialiased; bertanggung jawab untuk menghaluskan. Lebih detail .

Apa yang akhirnya kami dapatkan berkat SVG <foreignObject>:

  • Kita dapat menggambar html apa saja: teks, tabel, grafik
  • Tag mengembalikan gambar vektor.
  • Tag bekerja di semua browser modern kecuali IE11

Mengapa kami meninggalkan asing?


Semuanya bekerja dengan baik, tetapi begitu desainer datang kepada kami dan meminta untuk menambahkan dukungan font untuk membuat maket.



Kami bertanya-tanya apakah kami bisa melakukan ini dengan foreignObject. Ternyata dia memiliki fitur yang, ketika menyelesaikan masalah ini, menjadi cacat fatal. Ia dapat menampilkan HTML di dalam dirinya sendiri, tetapi tidak dapat mengakses sumber daya eksternal, jadi semua sumber daya yang digunakan harus dikonversi ke base64 dan ditambahkan di dalam svg.



Ini berarti bahwa jika Anda memiliki empat teks yang ditulis oleh OpenSans, Anda perlu mengunduh font ini kepada pengguna empat kali. Opsi ini tidak cocok untuk kita.

Kami memutuskan bahwa kami akan menulis Teks Kanvas kami dengan ... kinerja yang baik, dukungan untuk gambar vektor, kami tidak akan melupakan IE 11

Mengapa gambar vektor penting bagi kami? Dalam produk kami, objek apa pun di papan dapat diperbesar, dan dengan gambar vektor kita dapat membuatnya hanya sekali dan menggunakannya kembali terlepas dari zoom. Canvas.fillText menggambar bitmap: dalam hal ini, kita perlu menggambar ulang gambar dengan zoom masing-masing, yang, seperti yang kami pikir, sangat mempengaruhi kinerja.

Buat prototipe


Pertama-tama, kami membuat prototipe sederhana untuk menguji kinerjanya.



Prinsip pengoperasian prototipe:

  • Kami memberikan fungsi "teks";
  • Dari sana kita mendapatkan objek di mana ada setiap kata dari teks, dengan koordinat dan gaya untuk rendering;
  • Berikan objek ke Canvas;
  • Kanvas menggambar teks.

Prototipe memiliki beberapa tugas: untuk memeriksa bahwa Canvas redrawing dengan penskalaan akan berlangsung tanpa penundaan dan bahwa waktu untuk mengubah html menjadi objek tidak lebih dari membuat gambar svg.

Prototipe mengatasi tugas pertama, penskalaan hampir tidak mempengaruhi kinerja ketika menggambar teks. Ada masalah dengan tugas kedua: memproses teks dalam jumlah besar membutuhkan waktu yang cukup dan pengukuran kinerja pertama menunjukkan hasil yang buruk. Untuk menggambar teks dari karakter 1K, pendekatan baru memakan waktu hampir 2 kali lebih banyak daripada svg.


Kami memutuskan untuk menggunakan cara yang paling dapat diandalkan untuk mengoptimalkan kode - β€œganti tes dengan yang kami butuhkan” ;-). Tapi serius, kami pergi ke analis dan bertanya berapa lama teks paling sering dibuat oleh pengguna kami. Ternyata ukuran teks rata-rata adalah 14 karakter. Untuk teks pendek seperti itu, prototipe kami menunjukkan hasil kinerja yang jauh lebih baik, seperti ketergantungan kecepatan pada volume teks adalah linier, dan membungkus dalam svg hampir selalu dilakukan pada waktu yang sama, terlepas dari panjang teks. Itu cocok untuk kita: kita bisa kehilangan kinerja pada teks yang panjang, tetapi dalam kebanyakan kasus kecepatan kita akan lebih baik daripada svg.


Setelah beberapa iterasi bekerja pada pembaruan Canvas Canvas, kami mendapatkan algoritma berikut:

Tahap 1. Kami masuk ke blok logis

  1. Kami memecah teks menjadi blok: paragraf, daftar;
  2. Kami memecah blok menjadi blok yang lebih kecil sesuai dengan gaya;
  3. Kami memecah blok menjadi kata-kata.

Tahap 2. Kami mengumpulkan dalam satu objek dengan koordinat dan gaya

  1. Hitung lebar dan tinggi setiap kata dalam px;
  2. Kami menghubungkan kata-kata yang dibagi, karena pada poin 2 beberapa kata dibagi menjadi beberapa;
  3. Dari kata-kata yang kami kumpulkan, jika kata itu tidak sesuai dengan garis, kami potong sampai pas;
  4. Kami mengumpulkan paragraf dan daftar;
  5. Kami menghitung x, y untuk setiap kata;
  6. Kami mendapatkan objek yang siap pakai untuk rendering.

Keuntungan dari pendekatan ini adalah kita dapat mencakup semua kode dari HTML ke objek teks dengan unit test. Berkat ini, kami dapat secara terpisah memeriksa rendering dan parsing itu sendiri, yang membantu kami mempercepat pengembangan secara signifikan.

Sebagai hasilnya, kami membuat dukungan untuk font dan IE 11, mencakup semuanya dengan unit test, dan kecepatan rendering dalam kebanyakan kasus menjadi lebih tinggi daripada yang dimiliki asing. Diperiksa dalam pengguna beta dan dirilis. Sepertinya sukses!

Sukses bertahan 30 menit


Sejauh ini, orang-orang dengan sistem tulisan tangan kanan belum menulis dukungan teknis. Ternyata kami lupa tentang keberadaan bahasa tersebut:



Untungnya, menambahkan dukungan untuk sistem penulisan tangan kanan tidak sulit, karena Canvas.fillText standar sudah mendukungnya.

Tetapi ketika kami sedang berurusan dengan ini, kami menemukan kasus yang lebih menarik yang fillText tidak bisa lagi mendukung. Kami menemukan teks dua arah di mana bagian teks ditulis dari kanan ke kiri, lalu dari kiri ke kanan dan lagi dari kanan ke kiri.



Satu-satunya solusi yang kami tahu adalah masuk ke spesifikasi W3C untuk browser dan mencoba untuk mengulanginya di dalam Canvas Text. Itu sulit dan menyakitkan, tetapi kami dapat menambahkan dukungan dasar. Lebih lanjut tentang bidirectional: satu dan dua .

Kesimpulan singkat yang kami buat untuk diri kita sendiri


  1. Untuk menampilkan HTML dalam gambar, gunakan SVG foreignObject;
  2. Selalu analisis produk Anda untuk pengambilan keputusan;
  3. Buat prototipe. Mereka dapat menunjukkan bahwa keputusan-keputusan yang rumit hanya tampak seperti itu pada pandangan pertama;
  4. Tulis kode segera sehingga dapat ditutup dengan tes;
  5. Dalam produk internasional, penting untuk tidak lupa bahwa ada banyak bahasa yang berbeda, termasuk biderectional.

Jika Anda memiliki pengalaman dalam memecahkan masalah seperti itu - bagikan dalam komentar.

Source: https://habr.com/ru/post/id458624/


All Articles